/* =========================================
   OS Holdings — Corporate Identity 2026
   Colour, type and logo per the OSH CI Guideline
   ========================================= */

/* ─────────────────────────────────────────
   PRIMARY FONT FAMILY — Avenir
   ───────────────────────────────────────── */
@font-face {
  font-family: 'Avenir';
  src: url('../fonts/avenir-light.woff2') format('woff2');
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Avenir';
  src: url('../fonts/avenir-book.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Avenir';
  src: url('../fonts/avenir-roman.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Avenir';
  src: url('../fonts/avenir-medium.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Avenir';
  src: url('../fonts/avenir-heavy.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Avenir';
  src: url('../fonts/avenir-black.woff2') format('woff2');
  font-weight: 800; font-style: normal; font-display: swap;
}

/* ─────────────────────────────────────────
   SECONDARY FONT FAMILY — Bebas Neue
   ───────────────────────────────────────── */
@font-face {
  font-family: 'Bebas Neue';
  src: url('../fonts/bebasneue-regular.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Bebas Neue';
  src: url('../fonts/bebasneue-bold.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  /* ── CI palette ─────────────────────────
     Primary   Carrot Curl Orange  #F58B22
               Egyptian Blue       #0B539B
               December Sky Grey   #D1D3D4
               White               #FFFFFF
     Secondary Cyan / Blue         #21B1F5
               Blue Whale          #032B44
     ───────────────────────────────────── */
  --osh-orange:      #F58B22;
  --osh-orange-dark: #D2700F;
  --osh-orange-pale: #FDF0E3;
  /* Hover ground for anything Carrot Curl carrying Blue Whale ink. The CI's
     own dark orange is #D2700F, which drops Blue Whale to 4.2:1 — under AA for
     button-sized text. This is a shade lighter so the press state still reads
     as darker while the ink stays at 5.0:1. */
  --osh-orange-hover: #E07E14;
  --osh-blue:        #0B539B;
  --osh-blue-dark:   #083F76;
  --osh-blue-pale:   #E8F0F8;
  --osh-cyan:        #21B1F5;
  --osh-grey:        #D1D3D4;

  /* Sage's own green, for the Sage product pages and listings only.
     --sage-green is 7.8:1 on Blue Whale and 1.9:1 on white, so on light
     grounds it is a mark, never text; --sage-green-ink (4.5:1 on white) is
     the variant for green type. See DESIGN-SYSTEM.md §2. */
  --sage-green:      #04DB04;
  --sage-green-ink:  #0A8A0A;
  /* Green tonal scale for the Sage product pages. One vivid green across a
     whole page reads as a warning label, so the green arrives in four weights
     and only the mark is Sage's own #04DB04:
       deep   — section ground, matches green-background-pattern.png's field
                to the pixel so the artwork tiles against it. White is 7.9:1.
       deeper — the bottom of a deep-green gradient, and its hover.
       btn    — the only green that carries white BUTTON text (5.5:1). Do not
                use --sage-green-ink for a filled control: it is 4.5:1, which
                clears AA by 0.01 and leaves nothing for antialiasing.
       pale / pale-line — a light tint ground and its rule, for the bands that
                have to stay light so the page is not green end to end. */
  --sage-green-deep:       #0A5F22;
  --sage-green-deeper:     #063F16;
  --sage-green-btn:        #0A7A18;
  --sage-green-btn-hover:  #086212;
  --sage-green-pale:       #EFF7F0;
  --sage-green-pale-line:  #CBE4D1;
  --osh-whale:       #032B44;
  --osh-white:       #FFFFFF;

  /* ── Type ── */
  --font-body:    'Avenir', 'Avenir Next', 'Nunito Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-heading: 'Avenir', 'Avenir Next', 'Nunito Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-display: 'Bebas Neue', 'Avenir', 'Haettenschweiler', Impact, sans-serif;

  /* ── Surfaces ── */
  --white:       #FFFFFF;
  --bg:          #FFFFFF;
  --bg2:         #F4F7FA;
  --bg3:         #E9EBEC;
  --border:      var(--osh-grey);      /* December Sky Grey, straight from the CI */
  --border2:     #E7E9EA;
  --border-strong: #7E93A5;             /* 3:1 on white — form controls */

  /* ── Ink ── */
  --ink:         var(--osh-whale);
  --text:        var(--osh-whale);
  --text-muted:  #4A6575;
  --text-dim:    #527080;

  /* ── Accent roles ──────────────────────
     Orange carries energy: dots, bars, badge
     fills, glows. Egyptian Blue carries every
     accent that has to be READ — links, small
     labels, icons and filled buttons — so the
     palette stays WCAG AA on white.
     ───────────────────────────────────── */
  --accent:      var(--osh-orange);
  --accent-dark: var(--osh-orange-hover);
  --accent-ink:  var(--osh-blue);

  /* Legacy token names — every rule in this sheet
     still references --blue*; they now resolve to
     the CI colours above. */
  --blue:        var(--osh-orange);
  --blue-hi:     var(--osh-cyan);
  --blue-dark:   var(--osh-orange-hover);
  --blue-pale:   var(--osh-blue-pale);
  --blue-light:  var(--osh-cyan);
  --dark-footer: var(--osh-whale);

  --shadow-sm:   0 1px 4px rgba(3,43,68,0.06);
  --shadow:      0 4px 20px rgba(3,43,68,0.08);
  --shadow-lg:   0 16px 50px rgba(3,43,68,0.12);
  --nav-h:       76px;
  --radius:      14px;
  --radius-lg:   20px;
  --ease:        cubic-bezier(0.22, 1, 0.36, 1);
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 16px; -webkit-font-smoothing: antialiased; }

/* Lenis */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }

body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text);
  /* `hidden` here only propagates to the viewport, and the viewport still
     reported a wider scroll width and still let a touch drag sideways. `clip`
     does not create a scroll container at all, so there is nothing to drag.
     Declared on html as well: propagation stops once html sets its own value.
     `hidden` stays first as the fallback for anything without clip support. */
  overflow-x: hidden;
  overflow-x: clip;
  line-height: 1.55;
}
html {
  overflow-x: hidden;
  overflow-x: clip;
}
body.no-scroll { overflow: hidden; }
::selection { background: var(--blue); color: #fff; }
img, svg { display: block; max-width: 100%; }
a { color: inherit; }

/* =========================================
   TYPOGRAPHY
   ========================================= */
.section-headline {
  font-family: var(--font-heading);
  font-size: clamp(1.875rem, 3.2vw, 2.625rem);
  font-weight: 700;
  letter-spacing: -0.022em;
  line-height: 1.1;
  color: var(--osh-whale);   /* was --os-whale, a typo — the token did not exist */
}
.section-sub {
  font-size: 1.0625rem;
  line-height: 1.65;
  color: var(--text-muted);
  max-width: 56ch;
  margin-top: 16px;
}
/* =========================================
   SECTION TAG — one component, every page
   -----------------------------------------
   There used to be two of these: .eyebrow (bare text, home + the four Sage
   pages) and .tag (a navy pill, everywhere else), so the site read as two
   designs depending which page you landed on. They are now the same pill and
   both class names are kept so no markup has to change.

   Cyan is the ground on white and on dark. It only moves when the section
   itself is Cyan — see the .section-alt rule below — because a cyan pill on a
   cyan band disappears.
   ========================================= */
.tag,
.eyebrow {
  display: inline-flex;
  align-items: center;
  /* inline-flex alone is not enough: several of these sit in flex-column
     parents (.ps-card-body, .solution-content) where a stretched cross-axis
     would run the pill the full width of the card. */
  align-self: flex-start;
  gap: 10px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #fff;
  padding: 8px 16px 8px 14px;
  background: var(--osh-cyan);
  border-radius: 100px;
  margin-bottom: 18px;
  box-shadow: 0 4px 14px rgba(3,43,68,0.12);
}
.tag::before,
.eyebrow::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--osh-orange);
  box-shadow: 0 0 0 3px rgba(245,139,34,0.25);
  flex-shrink: 0;
}

/* Cyan bands: the pill takes Egyptian Blue and white (7.7:1). .svc-section is
   excluded — it is the one .section-alt whose ground is Egyptian Blue, so the
   default cyan pill is already the contrasting one there. */
.section-alt:not(.svc-section) :is(.tag, .eyebrow) {
  background: var(--osh-blue);
  color: #fff;
}

/* =========================================
   LAYOUT
   ========================================= */
.container { max-width: 1240px; margin: 0 auto; padding: 0 32px; }
.section { padding: 110px 0; }
.section-sm { padding: 70px 0; }
.section-alt { background: var(--osh-cyan); }
.section-head { margin-bottom: 56px; }
.section-head.center { text-align: center; }
.section-head.center .section-sub { margin-left: auto; margin-right: auto; }

/* =========================================
   SCROLL PROGRESS + PAGE TRANSITION
   ========================================= */
.scroll-progress {
  position: fixed; top: 0; left: 0;
  height: 2px; width: 100%;
  background: var(--blue);
  z-index: 1001;
  transform-origin: left;
  transform: scaleX(0);
  pointer-events: none;
}
/* The transition screen was flat Blue Whale — the one full-viewport moment on
   every page load, carrying nothing but the logo. It now takes the CI's blue
   artwork on --osh-blue, whose field the file matches to two points.

   The artwork is a fixed-size plate anchored to a corner, not a stretched
   background: this element is 100vw × 100vh on every device, so a
   height-driven `auto 100%` would blow the shapes up to several times their
   drawn size on a tall phone. Anchoring bottom-right and masking the two inner
   edges keeps it at a readable scale and leaves the middle clear for the
   lockup, which is what the screen is actually for. */
/* The screen FADES, it does not slide — client direction, 21 Aug 2026. The
   parked state is therefore opacity + visibility rather than a translate.
   visibility matters: a permanently-composited full-viewport layer at
   opacity 0 still costs the compositor a surface on every page, and
   `visibility: hidden` takes it out entirely. GSAP's autoAlpha drives both
   together, which is why the JS never touches opacity on its own.

   Deliberately no !important anywhere in here: GSAP animates this element
   with inline styles, and an !important rule would beat the tween and freeze
   the overlay in its parked state. */
.page-transition {
  position: fixed; inset: 0;
  background: var(--osh-blue);
  z-index: 9990;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  /* Parked by DEFAULT, covering only when <html> carries .osh-nav — see the
     note on the transition below. */
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
}
html.osh-nav .page-transition {
  pointer-events: all;
  opacity: 1;
  visibility: visible;
}

/* The CI corner plates. Real elements rather than ::before/::after, because a
   pseudo-element cannot be targeted by a GSAP tween, and these pan slowly
   across the screen in opposite directions while the fade runs. What is set
   here is only their anchor and artwork; main.js supplies the horizontal
   travel, so the plates are never actually seen sitting at x: 0. */
.pt-plate {
  position: absolute;
  aspect-ratio: 928 / 1055;
  background-image: url('../images/ci/patterns/blue-background-pattern.png');
  background-repeat: no-repeat;
  background-size: cover;
  pointer-events: none;
  width: clamp(220px, 34vw, 520px);
  -webkit-mask-image: linear-gradient(to bottom right, #000 30%, transparent 78%);
          mask-image: linear-gradient(to bottom right, #000 30%, transparent 78%);
  /* The pan is a long, slow, constant-rate translate, which is exactly the
     case where a promoted layer pays for itself: a masked element over a
     large PNG is expensive to repaint, and without this the plate is
     re-rasterised on every frame and the drift stutters. Safe to leave
     standing — the overlay is display:none under reduced motion, and it is
     two elements, not a list. */
  will-change: transform;
}
.pt-plate--tl {
  top: 0; left: 0;
  background-position: left top;
}
/* rotate(180deg), NOT scale(-1, -1) — the two are identical about the centre,
   but this plate is translated by the JS, and a negative base scale would mean
   every tween has to restate scaleX/scaleY with the right sign or the artwork
   flips mid-animation. Expressed as a rotation the base survives on its own
   axis, and because GSAP composes translate BEFORE rotate, `x` stays in screen
   space here — a positive x moves this plate right, exactly as it does the
   top-left one, so the JS mirrors it by negating the value itself. */
.pt-plate--br {
  bottom: 0; right: 0;
  background-position: right bottom;
  transform: rotate(180deg);
}
/* Parked completely OFF SCREEN while the transition covers — starting a slide
   from a plate that is already partly visible reads as a stutter, not as an
   entrance. 100% of the plate's OWN width: a percentage translate resolves
   against the element's border box, so this is exact at every viewport with no
   JS measuring anything. It lives here rather than in main.js because main.js
   is deferred — on a slow parse the overlay can paint before it runs, and the
   plates would show in their corners for a frame and then jump off. */
html.osh-nav .pt-plate--tl { transform: translate3d(-100%, 0, 0); }
html.osh-nav .pt-plate--br { transform: translate3d(100%, 0, 0) rotate(180deg); }
.pt-logo { position: relative; z-index: 1; }
/* height:auto is load-bearing. osh_picture() writes the intrinsic width/height
   onto the tag, and the height attribute is a presentational hint that still
   applies when CSS sets only the width — the aspect-ratio hint is used ONLY
   while height is auto. Without it this logo rendered 140x487. */
.pt-logo img { width: 140px; height: auto; /* no filter — original logo */ }

/* =========================================
   CURSOR FOLLOWER
   ========================================= */
.cursor-follower {
  width: 22px; height: 22px;
  border: 1.5px solid var(--blue);
  border-radius: 50%;
  position: fixed;
  top: 0; left: 0;
  pointer-events: none;
  z-index: 9995;
  transform: translate(-50%, -50%);
  transition: width .25s var(--ease), height .25s var(--ease), background .25s;
  display: none;
  will-change: left, top;
}
body.cursor-enabled .cursor-follower { display: block; }
.cursor-follower.hover {
  width: 46px; height: 46px;
  background: rgba(228, 136, 0, 0.12);
}

/* =========================================
   HEADER — always white/frosted
   ========================================= */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--nav-h);
  z-index: 1000;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s, box-shadow 0.3s;
}
.site-header.scrolled {
  border-bottom-color: var(--border);
  box-shadow: 0 1px 0 rgba(3,43,68,0.04), 0 4px 20px rgba(3,43,68,0.04);
}

.sh-inner {
  max-width: 1440px;
  height: 100%;
  margin: 0 auto;
  padding: 0 32px;
  display: flex;
  align-items: center;
  gap: 24px;
}

/* Logo sits on the white header bar — CI blue-text lockup, bare.
   No plate, no padding, no radius: the mark keeps its own clear space. */
.sh-logo {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  text-decoration: none;
  background: none;
  padding: 0;
  border-radius: 0;
  height: auto;
}
.sh-logo-img { height: 38px; width: auto; display: block; }
/* the anchor was 30px tall on a phone — short of a comfortable tap */
.sh-logo { display: inline-flex; align-items: center; min-height: 44px; }

.sh-nav { flex: 1; display: flex; align-items: center; justify-content: center; position: relative; }
.sh-nav-links { display: flex; align-items: center; gap: 2px; position: relative; z-index: 1; }
.sh-link {
  display: block;
  padding: 9px 16px;
  border-radius: 100px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
  position: relative; z-index: 1;
  transition: color 0.2s;
  line-height: 1;
}
.sh-link:hover { color: var(--ink); }
.sh-link.active { color: #fff; font-weight: 600; }

/* There is only one pill and JS slides it to whichever link is hovered, so the
   white ink has to travel with it. While the pointer is inside the nav the
   pill has left the active link — it goes back to ink there (it would be white
   on the white header otherwise, i.e. invisible) and the hovered link takes
   the white instead. Between links the pill is back home and the active link
   sits at 6.1:1 rather than white; readable, just not the highlight state. */
.sh-nav-links:hover .sh-link.active { color: var(--ink); }
.sh-nav-links:hover .sh-link:hover  { color: #fff; }

.sh-indicator {
  position: absolute;
  top: 50%; left: 0;
  transform: translateY(-50%);
  height: 34px;
  background: var(--osh-cyan);
  border-radius: 100px;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
}

.sh-cta {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--blue);
  color: #fff;
  padding: 11px 20px;
  border-radius: 100px;
  font-size: 0.875rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.2s, transform 0.2s, box-shadow 0.25s;
  margin-left: auto;
  line-height: 1;
}
.sh-cta:hover {
  background: var(--blue-dark);
  transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(11,83,155,0.28);
}
.sh-cta svg { width: 14px; height: 14px; }

.sh-burger {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  /* 44×44 is the minimum comfortable touch target — the box was 38×31, which
     is a miss on a phone. The bars stay 22px; only the hit area grows, and the
     negative right margin keeps the icon optically on the gutter. */
  width: 44px;
  height: 44px;
  padding: 0;
  margin-left: auto;
  margin-right: -10px;
}
.sh-burger span {
  display: block;
  width: 22px; height: 1.6px;
  background: var(--ink);
  border-radius: 2px;
  transition: transform 0.3s, opacity 0.3s;
}
.sh-burger.open span:nth-child(1) { transform: rotate(45deg) translateY(4.5px); }
.sh-burger.open span:nth-child(2) { opacity: 0; }
.sh-burger.open span:nth-child(3) { transform: rotate(-45deg) translateY(-4.5px); }

/* Mobile menu */
.sh-mobile {
  position: fixed; inset: 0;
  background: var(--ink); color: #fff;
  z-index: 999;
  display: flex; flex-direction: column;
  padding: calc(var(--nav-h) + 40px) 32px 40px;
  opacity: 0; visibility: hidden;
  transition: opacity 0.35s, visibility 0.35s;
}
.sh-mobile.open { opacity: 1; visibility: visible; }
.sh-mobile-nav { display: flex; flex-direction: column; gap: 4px; }
.sh-mobile-link {
  font-family: var(--font-heading);
  font-size: 1.875rem; font-weight: 700;
  letter-spacing: -0.02em;
  color: rgba(255,255,255,0.75);
  text-decoration: none;
  padding: 12px 0;
  border-bottom: 1px solid rgba(255,255,255,0.08);
  transition: color 0.2s;
}
.sh-mobile-link:hover { color: #fff; }
.sh-mobile-footer {
  margin-top: auto;
  font-size: 0.75rem;
  color: rgba(255,255,255,0.4);
  display: flex; justify-content: space-between;
}
/* These were bare "FB / IG / TW" text links measuring 13–19px square — the
   smallest tap targets on the site. Same 44px circle the footer uses. */
.sh-mobile-socials { display: flex; gap: 12px; }
.sh-mobile-socials a {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255,255,255,0.22);
  border-radius: 50%;
  color: #fff;
  text-decoration: none;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.75rem;
  transition: background 0.25s var(--ease), border-color 0.25s var(--ease);
}
.sh-mobile-socials a:hover,
.sh-mobile-socials a:focus-visible {
  background: var(--osh-blue);
  border-color: var(--osh-blue);
}

/* =========================================
   BUTTONS
   ========================================= */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 13px 24px;
  border-radius: 100px;
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  border: 1.5px solid transparent;
  cursor: pointer;
  transition: all 0.25s var(--ease);
  line-height: 1;
}
.btn svg { width: 16px; height: 16px; }
.arrow-right { transition: transform 0.3s var(--ease); }
.btn:hover .arrow-right { transform: translateX(3px); }

.btn-primary {
  background: var(--blue);
  color: #fff;
  border-color: var(--blue);
}
.btn-primary:hover {
  background: var(--blue-dark);
  border-color: var(--blue-dark);
  box-shadow: 0 10px 28px rgba(11,83,155,0.28);
  transform: translateY(-2px);
}
.btn-white {
  background: #fff;
  color: var(--ink);
  border-color: #fff;
}
.btn-white:hover {
  background: var(--blue);
  color: #fff;
  border-color: var(--blue);
  transform: translateY(-2px);
  box-shadow: 0 10px 28px rgba(11,83,155,0.3);
}
.btn-outline {
  background: transparent;
  color: var(--ink);
  border-color: var(--border);
}
.btn-outline:hover {
  border-color: var(--ink);
  transform: translateY(-1px);
}
.btn-outline-white {
  background: transparent;
  color: #fff;
  border-color: rgba(255,255,255,0.4);
}
.btn-outline-white:hover {
  background: rgba(255,255,255,0.1);
  border-color: rgba(255,255,255,0.8);
}

/* =========================================
   HERO — full viewport BG image (home)
   ========================================= */
.hero {
  min-height: 100vh;
  position: relative;
  display: flex;
  align-items: center;
  overflow: hidden;
  padding-top: var(--nav-h);
}
.hero-bg {
  position: absolute; inset: 0; z-index: 0;
}
.hero-bg-img {
  width: 100%; height: 110%; /* extra height for parallax room */
  object-fit: cover;
  object-position: center center;
  will-change: transform;
  /* transform managed entirely by GSAP — do not set here */
}
.hero-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(
    to top,
    rgba(3,43,68,0.90) 0%,
    rgba(3,43,68,0.52) 50%,
    rgba(3,43,68,0.28) 100%
  );
}
.hero-content {
  position: relative; z-index: 1;
  width: 100%; max-width: 1240px;
  margin: 0 auto;
  padding: 0 32px 80px;
}
.hero-badge {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 0.75rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: rgba(255,255,255,0.75);
  margin-bottom: 24px;
}
.hero-badge::before {
  content: ''; width: 6px; height: 6px;
  background: var(--blue); border-radius: 50%;
}
.hero-title {
  font-family: var(--font-heading);
  font-size: clamp(2.75rem, 6vw, 5rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.02;
  color: #fff;
  max-width: 16ch;
  margin-bottom: 20px;
}
.hero-title em { font-style: italic; font-weight: 500; color: rgba(255,255,255,0.8); }
.hero-sub {
  font-size: 1.125rem;
  color: rgba(255,255,255,0.65);
  margin-bottom: 36px;
  max-width: 50ch;
  line-height: 1.5;
}
.hero-actions { display: flex; gap: 14px; flex-wrap: wrap; }

/* Scroll indicator */
.hero-scroll {
  position: absolute;
  right: 40px; bottom: 40px; z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  color: rgba(255,255,255,0.5);
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
}
.scroll-line {
  width: 1px; height: 44px;
  background: linear-gradient(to bottom, rgba(255,255,255,0.5), transparent);
  animation: scroll-drop 1.8s ease-in-out infinite;
}
@keyframes scroll-drop {
  0%,100% { opacity: 1; transform: scaleY(1); transform-origin: top; }
  50% { opacity: 0.4; transform: scaleY(0.4); transform-origin: top; }
}

/* Hero floating chips */
.hero-chips {
  position: absolute;
  right: 32px; bottom: 120px; z-index: 2;
  display: flex; flex-direction: column; gap: 12px;
}
.hero-chip {
  background: rgba(255,255,255,0.1);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(255,255,255,0.18);
  border-radius: var(--radius);
  padding: 14px 18px;
  display: flex; align-items: center; gap: 12px;
  color: #fff;
}
.hero-chip .chip-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: #21B1F5;
  box-shadow: 0 0 0 3px rgba(33,177,245,0.25);
  flex-shrink: 0;
}
.hero-chip .chip-val {
  font-family: var(--font-heading);
  font-size: 1.125rem; font-weight: 700; line-height: 1;
}
.hero-chip .chip-lbl { font-size: 0.75rem; color: rgba(255,255,255,0.65); margin-top: 2px; }

/* =========================================
   PAGE HERO — inner pages
   -----------------------------------------
   Pamphlet construction: a photograph of people fills the top of the band and
   then dissolves into Blue Whale, so the headline sits on flat colour instead
   of on a busy picture. The copy is pinned to the bottom, and that is what
   leaves the clear space above it for the faces — which means the crop of each
   photograph matters. --hero-pos sets object-position per page so the heads
   land in that clear zone rather than behind the text or under the navbar.
   ========================================= */
.page-hero {
  position: relative;
  overflow: hidden;
  min-height: 660px;
  display: flex;
  align-items: flex-end;
  padding: calc(var(--nav-h) + 260px) 0 76px;
  text-align: center;
  background: var(--osh-whale);
  color: #fff;
}
.page-hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.page-hero-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-pos, center 30%);
}
/* Held nearly clear through the upper third so faces read, then ramped hard so
   the copy never has to fight the photograph for contrast. */
.page-hero-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom,
      rgba(3,43,68,0.62) 0%,
      rgba(3,43,68,0.30) 12%,
      rgba(3,43,68,0.26) 30%,
      rgba(4,52,84,0.74) 53%,
      rgba(3,43,68,0.95) 75%,
      var(--osh-whale) 93%),
    linear-gradient(118deg, rgba(11,83,155,0.42), transparent 62%);
}
.page-hero-bg { /* legacy element from the flat banner */ display: none; }

/* Per-page crops — each value pulls that photograph's faces into the clear
   zone between the navbar and the start of the fade. */
.page-hero--about         { --hero-pos: center 34%; }
.page-hero--careers       { --hero-pos: center 38%; }
.page-hero--contact       { --hero-pos: center 24%; }
.page-hero--public-sector { --hero-pos: center 30%; }
.page-hero--solutions     { --hero-pos: center 15%; }

/* The hero tag used to be its own translucent glass pill, which is what made
   the page heroes look like a different site from the home page. It takes the
   standard Cyan pill now — the ground here is Blue Whale, so it reads. */
.page-hero .section-headline { color: #fff; position: relative; z-index: 1; }
.page-hero .section-sub { color: rgba(255,255,255,0.8); position: relative; z-index: 1; }
.page-hero .container { position: relative; z-index: 1; }
/* the headline and standfirst are centred columns, not full-bleed text */
.page-hero .hero-headline { max-width: 760px; margin: 0 auto 20px; }
.page-hero .hero-sub { max-width: 720px; margin: 0 auto; text-align: center; }

/* =========================================
   LOGO STRIP
   ========================================= */
.logo-strip {
  padding: 52px 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.logo-strip-label {
  text-align: center;
  font-size: 0.75rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 28px;
}
.logo-strip-row {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: 32px 64px;
  max-width: 1100px; margin: 0 auto; padding: 0 32px;
}
.logo-strip-row img { height: 46px; width: auto; filter: grayscale(1) opacity(0.45); transition: filter 0.3s; }
.logo-strip-row img:hover { filter: grayscale(0) opacity(1); }

/* =========================================
   ABOUT GRID (home page)
   ========================================= */
.about-grid {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 80px;
  align-items: center;
}
.about-img {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 4/5;
  box-shadow: var(--shadow-lg);
}
.about-img img { width: 100%; height: 100%; object-fit: cover; }
.about-img-badge {
  position: absolute; bottom: 28px; left: 28px;
  background: #fff;
  border-radius: var(--radius);
  padding: 16px 20px;
  box-shadow: var(--shadow);
}
.about-img-badge .big {
  font-family: var(--font-heading);
  font-size: 1.375rem; font-weight: 700; color: var(--ink); line-height: 1;
}
.about-img-badge .small { font-size: 0.75rem; color: var(--text-muted); margin-top: 4px; font-weight: 500; }
.about-text .section-headline { margin-top: 12px; }
.about-stats {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 14px; margin: 32px 0;
}
.about-stat {
  background: var(--osh-blue);
  border: 1px solid var(--osh-blue);
  border-radius: var(--radius);
  padding: 20px;
}
.about-stat .num {
  font-family: var(--font-heading);
  font-size: 1.875rem; font-weight: 700; color: var(--ink);
  letter-spacing: -0.02em; line-height: 1;
}
.about-stat .num span { color: var(--blue); }
.about-stat .lbl { font-size: 0.8125rem; color: var(--text-muted); margin-top: 6px; font-weight: 500; }

/* =========================================
   SERVICE CARDS — horizontal scroll (home)
   ========================================= */
/* overflow stays on the scroll-outer only — never on the pin trigger element */
.svc-scroll-outer { overflow: hidden; width: 100%; }
.svc-track {
  display: flex;
  gap: 20px;
  /* align left edge with container (1240px max-width + 32px padding) */
  padding: 8px max(32px, calc((100vw - 1240px) / 2 + 32px));
  width: max-content;
  will-change: transform;
}
.svc-track .svc-card {
  width: 340px;
  flex-shrink: 0;
}
.svc-card {
  position: relative;
  aspect-ratio: 3/4;
  border-radius: var(--radius-lg);
  overflow: hidden;
  cursor: pointer;
}
.svc-card img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.7s var(--ease);
}
.svc-card:hover img { transform: scale(1.06); }
.svc-card-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(3,43,68,0.88) 30%, rgba(3,43,68,0.05) 70%);
  transition: background 0.4s;
}
.svc-card:hover .svc-card-overlay {
  background: linear-gradient(to top, rgba(3,43,68,0.92) 35%, rgba(11,83,155,0.15) 100%);
}
.svc-card-body {
  position: absolute; bottom: 0; left: 0; right: 0;
  padding: 28px;
}
.svc-num {
  font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: rgba(255,255,255,0.5); margin-bottom: 8px;
}
.svc-title {
  font-family: var(--font-heading);
  font-size: 1.25rem; font-weight: 700;
  color: #fff; line-height: 1.2; margin-bottom: 12px;
}
.svc-link {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.8125rem; font-weight: 600;
  color: rgba(255,255,255,0.75); text-decoration: none;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.3s, transform 0.3s, color 0.25s;
}
.svc-card:hover .svc-link { opacity: 1; transform: translateY(0); color: #fff; }

/* Service cards — navy with a floating orange number badge */
.services-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Row gap wider than column gap on purpose (22 Sept 2026 fix): the numeral
     badge below floats to top:-28px, half OUT of its own card — fine for a
     single row, but a 28px row gap put the badge from row 2 exactly at row
     1's bottom edge, and the badge's own shadow/ring made that read as an
     overlap the moment the grid wraps (four cards, or any list past three).
     68px clears the badge's full height (~56px) plus a visible gap above
     the card it sits on. */
  column-gap: 28px;
  row-gap: 68px;
  margin-top: 40px;
}
/* Count modifiers, written by the template (public-sector.php's additional
   solutions can now be any count — see DESIGN-SYSTEM.md §6).

   MUST sit here, immediately after the base rule and before every media
   query below: a bug fixed 22 Sept 2026 had these three declared far later
   in the file (after the 1050px/700px breakpoints that are supposed to
   collapse them to one column). Same specificity, so the LATER declaration
   always wins regardless of which one sits inside a media query — the
   responsive overrides were dead code, and a four-up grid stayed two-up all
   the way down to a phone. Declaring the base modifier first is what lets
   the breakpoints below actually override it. */
.services-grid--n4 { grid-template-columns: repeat(2, 1fr); }
.services-grid--n2 { grid-template-columns: repeat(2, 1fr); }
.services-grid--n1 { grid-template-columns: minmax(0, 560px); justify-content: center; }
.service-card {
  position: relative;
  padding: 56px 36px 40px;
  background: linear-gradient(155deg, #0A4479 0%, #032B44 60%, #021D2F 100%);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--radius-lg);
  color: #fff;
  overflow: visible;
  box-shadow: 0 10px 40px rgba(3,43,68,0.18);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease), border-color 0.4s var(--ease);
}
.service-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  background: radial-gradient(circle at 80% 110%, rgba(245,139,34,0.18), transparent 55%);
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  pointer-events: none;
}
.service-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 24px 60px rgba(3,43,68,0.35);
  border-color: rgba(245,139,34,0.45);
}
.service-card:hover::after { opacity: 1; }

/* Numbered badge — top-left, half-out */
.service-num {
  position: absolute;
  top: -28px;
  left: -14px;
  z-index: 2;
  font-family: var(--font-heading);
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  color: #fff;
  background: linear-gradient(135deg, var(--blue) 0%, #F7A94E 100%);
  padding: 16px 22px;
  border-radius: 16px;
  box-shadow:
    0 12px 30px rgba(245,139,34,0.45),
    0 0 0 4px rgba(255,255,255,0.95),
    0 0 0 5px rgba(245,139,34,0.18);
  transition: transform 0.5s var(--ease), border-radius 0.5s var(--ease);
}
.service-card:hover .service-num {
  transform: scale(1.06) rotate(-3deg);
  border-radius: 20px;
}

/* Optional inline icon — kept for backwards compatibility, restyled for dark card */
.service-icon {
  width: 48px; height: 48px;
  border-radius: 12px;
  background: rgba(245,139,34,0.18);
  color: var(--blue);
  border: 1px solid rgba(245,139,34,0.35);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 20px;
  position: relative;
  z-index: 1;
}
.service-icon svg { width: 22px; height: 22px; }

.service-title {
  font-family: var(--font-heading);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: #fff;
  margin-bottom: 14px;
  line-height: 1.2;
  position: relative;
  z-index: 1;
}
.service-title::after {
  content: '';
  display: block;
  width: 32px;
  height: 2px;
  background: var(--blue);
  margin-top: 14px;
  transition: width 0.5s var(--ease);
}
.service-card:hover .service-title::after { width: 64px; }

.service-desc {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: rgba(255,255,255,0.72);
  position: relative;
  z-index: 1;
}

/* =========================================
   PUBLIC SECTOR SECTION — full BG image (home)
   ========================================= */
.ps-section {
  min-height: 75vh;
  position: relative;
  display: flex;
  align-items: center;
  overflow: hidden; /* critical — clips parallax overflow */
}
.ps-bg {
  position: absolute; inset: 0; z-index: 0;
}
.ps-bg-img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center center;
}
.ps-bg-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(
    135deg,
    rgba(3,43,68,0.88) 0%,
    rgba(3,43,68,0.58) 60%,
    rgba(3,43,68,0.38) 100%
  );
}
.ps-content {
  position: relative; z-index: 1;
  max-width: 1240px; margin: 0 auto;
  padding: 100px 32px;
  width: 100%;
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 80px;
  align-items: center;
}
.ps-left .section-headline { color: #fff; font-size: clamp(1.875rem, 3.5vw, 2.75rem); margin-top: 12px; }
.ps-left p { color: rgba(255,255,255,0.7); font-size: 1.0625rem; line-height: 1.6; margin: 20px 0 32px; max-width: 46ch; }
/* .on-dark used to strip the eyebrow back to translucent white text. The pill
   carries itself on the dark band now, so the modifier is a no-op kept only
   because the markup still says it. */
.ps-chips {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
}
.ps-chip {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  padding: 26px 22px 22px;
  border-radius: var(--radius-lg);
  /* The plexus ground behind these is already soft, so blur alone barely
     registers — the frost only reads once the white tint comes up with it. */
  background: linear-gradient(160deg, rgba(255,255,255,0.16) 0%, rgba(255,255,255,0.07) 100%);
  border: 1px solid rgba(255,255,255,0.20);
  backdrop-filter: blur(26px) saturate(120%);
  -webkit-backdrop-filter: blur(26px) saturate(120%);
  color: #fff;
  font-family: var(--font-heading);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
  overflow: hidden;
  cursor: default;
  transition: transform 0.45s var(--ease), border-color 0.45s var(--ease), background 0.45s var(--ease), box-shadow 0.45s var(--ease);
}
.ps-chip::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 100% 0%, rgba(245,139,34,0.30), transparent 60%);
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  pointer-events: none;
}
.ps-chip::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 100%;
  height: 3px;
  background: linear-gradient(90deg, var(--blue), transparent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.55s var(--ease);
}
.ps-chip:hover {
  transform: translateY(-4px);
  border-color: rgba(245,139,34,0.55);
  background: linear-gradient(160deg, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0.05) 100%);
  box-shadow: 0 18px 40px rgba(0,0,0,0.30);
}
.ps-chip:hover::before { opacity: 1; }
.ps-chip:hover::after { transform: scaleX(1); }

.ps-chip-icon {
  /* Bigger than the old 48px tile: the CI icons are fine line art and the
     detail collapses into a smudge much below this. */
  width: 56px; height: 56px;
  border-radius: 16px;
  display: flex; align-items: center; justify-content: center;
  /* Secondary Cyan — client direction, 21 Aug 2026. The tiles carry line
     ART, not type, and the artwork is aria-hidden decoration, so the 2.4:1
     of white-on-cyan is not a text-contrast question; the chip's own label
     sits outside the tile on the dark ground and is unaffected. */
  background: var(--osh-cyan);
  color: #fff;
  box-shadow:
    0 8px 24px rgba(33,177,245,0.42),
    inset 0 1px 0 rgba(255,255,255,0.28);
  transition: transform 0.45s var(--ease);
  position: relative;
  z-index: 1;
}
.ps-chip-icon svg { width: 24px; height: 24px; }
/* CI icon artwork — the four files differ in aspect (173×215 … 215×215), so
   the box is a square they fit inside rather than a size they are forced to. */
.ps-chip-icon img {
  width: auto; height: auto;
  max-width: 36px; max-height: 36px;
  object-fit: contain;
}
.ps-chip:hover .ps-chip-icon {
  transform: rotate(-6deg) scale(1.06);
}
.ps-chip-text {
  position: relative;
  z-index: 1;
  display: block;
}
.ps-chip-sub {
  display: block;
  margin-top: 4px;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: rgba(255,255,255,0.55);
  font-family: var(--font-body);
}

@media (max-width: 720px) {
  .ps-chips { grid-template-columns: 1fr; gap: 12px; }
  .ps-chip { padding: 22px 20px 18px; font-size: 1rem; }
  .ps-chip-icon { width: 48px; height: 48px; border-radius: 14px; }
  .ps-chip-icon svg { width: 20px; height: 20px; }
  .ps-chip-icon img { max-width: 30px; max-height: 30px; }
}

/* =========================================
   CLIENTS SECTION
   ========================================= */
.clients-section { background: var(--white); }
.clients-logos {
  display: flex; flex-wrap: wrap;
  align-items: center; justify-content: center;
  /* 44, not the old 72: the six marks measure 837px across, and at 72 the
     gaps alone added 360 — over the 1100 container, so the last one dropped
     to a row of its own and read as a mistake rather than as a second group. */
  gap: 40px 44px;
  max-width: 1100px; margin: 0 auto;
}
.client-logo {
  height: 52px; width: auto;
  filter: grayscale(1) opacity(0.5);
  transition: all 0.3s;
}
.client-logo:hover { filter: grayscale(0) opacity(1); transform: scale(1.06); }
/* The Sage Partner and Amathuba marks exist here in TWO files each. The
   supplied artwork is the knockout version — Amathuba's wordmark in white,
   Sage's "Partner" in light grey — which is right for the footer's Blue Whale
   and invisible on this white section. `-light` is the positive counterpart:
   only the achromatic ink was darkened, the red map and the green badge are
   the supplied colours untouched. Use `-light` on light grounds and the plain
   file on dark ones. Both then behave as ordinary client logos here, greyed
   back with the rest and returning to colour on hover. */

/* One line on desktop, always. The six marks measure 837px across at the
   full 52px height, so from about 1250px up they already fit — but between
   the 1050px tablet breakpoint and there, the last one dropped to a row of
   its own. Rather than leave that to chance, above the tablet breakpoint the
   row stops wrapping and the gap and the mark height both track the viewport:
   at 1051px that is a 38px mark and a 31px gap (769px, inside the 987px
   container), at 1440px the full 52px and 44px (1057px, inside the 1100). */
@media (min-width: 1051px) {
  .clients-logos { flex-wrap: nowrap; gap: 0 clamp(20px, 3vw, 44px); }
  .client-logo   { height: clamp(38px, 3.6vw, 52px); flex: 0 0 auto; }
}

/* Two marks per row on a phone. At the desktop 72px column gap a pair comes
   to 338px inside a 350px container — it fits, but with 6px to spare either
   side, which reads as cramped rather than as a grid. */
@media (max-width: 700px) {
  .clients-logos { gap: 26px 28px; }
  .client-logo   { height: 46px; }
}

/* =========================================
   CTA BAND
   ========================================= */
.cta-band {
  background: var(--ink); color: #fff;
  border-radius: var(--radius-lg);
  padding: 60px 52px;
  max-width: 1240px; margin: 0 auto;
  display: grid;
  grid-template-columns: 1.4fr auto;
  gap: 32px; align-items: center;
  position: relative; overflow: hidden;
}
.cta-band::before {
  content: ''; position: absolute;
  top: -60%; right: -5%;
  width: 60%; height: 220%;
  background: radial-gradient(circle, rgba(11,83,155,0.4), transparent 60%);
  filter: blur(50px); pointer-events: none;
}
.cta-band > * { position: relative; z-index: 1; }
/* Support old .cta-band-text/.cta-band-action structure */
.cta-band-text h2,
.cta-band h2 {
  font-family: var(--font-heading);
  font-size: clamp(1.625rem, 2.8vw, 2.25rem);
  font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; color: #fff;
}
.cta-band-text p,
.cta-band > div > p,
.cta-band p {
  margin-top: 12px; font-size: 1.0625rem;
  color: rgba(255,255,255,0.7); line-height: 1.55; max-width: 52ch;
}
.cta-band-action { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.cta-band .btn-white { flex-shrink: 0; }
/* The main CTA button in every band is always Carrot Curl Orange with white
   text and a white icon (the arrow uses currentColor) — client direction,
   10 Sept 2026, and non-negotiable when the band has only one button. This
   used to paint it white instead, for contrast against the dark ink panel;
   that predates the sitewide "every button is orange" rule further down
   (§3) and was only still winning here on selector specificity — pin it
   explicitly rather than leave it as a specificity accident. */
.cta-band .btn-primary {
  background: var(--osh-orange); color: #fff; border-color: var(--osh-orange);
}
.cta-band .btn-primary:hover {
  background: var(--osh-orange-hover); color: #fff; border-color: var(--osh-orange-hover);
}
/* Secondary CTA button (the one alongside the always-orange primary) is
   white with Blue-Whale ink — client direction, 10 Sept 2026. .btn-outline
   has no background of its own anywhere else on the site: it is transparent
   by default and painted orange by the sitewide "every button is orange"
   rule (§3) on a specificity tie it happens to win. Pin it explicitly here
   rather than leave a second button color riding on that tie too. */
.cta-band .btn-outline {
  background: #fff; color: var(--ink); border-color: #fff;
}
.cta-band .btn-outline:hover {
  background: var(--osh-orange-hover); color: #fff; border-color: var(--osh-orange-hover);
}

/* =========================================
   SOLUTIONS PAGE COMPONENTS
   ========================================= */
.solution-feature {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 72px;
  align-items: center;
  margin-bottom: 100px;
  padding-bottom: 100px;
  border-bottom: 1px solid var(--border);
}
.solution-feature:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
.solution-feature.reverse .solution-media { order: 2; }
.solution-feature.reverse .solution-content { order: 1; }

.solution-media {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 4/3;
  box-shadow: var(--shadow-lg);
}
.solution-media img { width: 100%; height: 100%; object-fit: cover; }
/* Was a blue gradient over the picture. Two-column rows show the photograph
   as shot — client direction, 21 Aug 2026. Kept as a no-op because the markup
   still emits the div. */
.solution-media-overlay { display: none; }
.solution-badge {
  position: absolute; bottom: 20px; left: 20px;
  background: rgba(255,255,255,0.95);
  backdrop-filter: blur(8px);
  border-radius: 8px;
  padding: 10px 16px;
  font-family: var(--font-heading);
  font-size: 0.875rem; font-weight: 700; color: var(--blue);
  border: 1px solid rgba(11,83,155,0.15);
}
.solution-content .section-headline { font-size: clamp(1.625rem, 2.5vw, 2.25rem); margin-top: 12px; }
.solution-features-list {
  list-style: none;
  margin: 24px 0 28px;
  display: flex; flex-direction: column; gap: 12px;
}
.solution-features-list li {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 0.9375rem; color: var(--text); line-height: 1.5;
}
.solution-features-list svg {
  width: 16px; height: 16px; flex-shrink: 0;
  color: var(--blue); margin-top: 2px;
}

/* =========================================
   PUBLIC SECTOR PRODUCTS (ps page)
   ========================================= */
.ps-products {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
.ps-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 0.3s var(--ease), box-shadow 0.3s, border-color 0.3s;
  display: flex; flex-direction: column;
}
.ps-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: var(--blue);
}
.ps-card-img {
  aspect-ratio: 16/9;
  overflow: hidden;
}
.ps-card-img img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 0.6s var(--ease);
}
.ps-card:hover .ps-card-img img { transform: scale(1.05); }
.ps-card-body { padding: 28px; flex: 1; display: flex; flex-direction: column; }
.ps-card-title {
  font-family: var(--font-heading);
  font-size: 1.25rem; font-weight: 700; color: var(--ink);
  margin-bottom: 12px; line-height: 1.2;
}
.ps-card-desc { font-size: 0.9375rem; line-height: 1.6; color: var(--text-muted); flex: 1; }

/* mSCOA compliance marks (public sector)
   -------------------------------------------------------------------------
   Was a 2-up grid of white cards parked in the section's right-hand column.
   The band now runs as one left column (client direction, 20 Aug 2026), so
   these read as a wrapping row of pills instead: same six accreditations,
   flowing under the copy rather than boxed opposite it.

   They stay on a WHITE ground on purpose. Carrot Curl is L=0.38, so white ink
   on it is 2.4:1 — fine for the display headline the client asked for, not for
   six 15px labels. White pill + Blue Whale ink keeps them at 13.8:1. */
/* A grid, not a wrapped flex row. Shrink-to-fit pills are as wide as whatever
   words happen to be in them, so four claims of equal weight were arriving at
   four different widths and breaking wherever the row ran out - which read as
   an accident rather than a set. Two equal columns fixes both: same width,
   same rhythm, and a fifth claim lands in a new row instead of re-flowing the
   four above it. The cap keeps a pill from stretching the full 660px of the
   copy column. */
.compliance-pills {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin: 34px 0 0;
  padding: 0;
  max-width: 540px;
}
.compliance-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 20px 11px 16px;
  background: var(--white);
  border-radius: 999px;
  font-family: var(--font-heading);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1;
  color: var(--osh-whale);
  box-shadow: 0 4px 16px rgba(122,58,4,0.18);
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.compliance-pill:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 26px rgba(122,58,4,0.28);
}
/* Egyptian Blue, not Cyan: on Carrot Curl the cyan tick was a second
   warm-cool clash beside the ground itself. */
.compliance-pill-tick {
  width: 15px; height: 15px;
  flex: 0 0 auto;
  color: var(--osh-blue);
}
@media (max-width: 560px) {
  /* One per row at phone width: two columns of a 320px screen puts "Cash &
     Investment Management"-length claims onto two lines apiece. */
  .compliance-pills { grid-template-columns: 1fr; gap: 10px; max-width: none; }
  .compliance-pill  { padding: 10px 16px 10px 13px; font-size: 0.875rem; }
}

/* PS hero image (ps page banner) */
/* Full-bleed: the band is the width of the screen and butts against the
   sections either side of it, so the padding that used to hold it in from the
   page edges is gone. The sections above and below carry their own. */
.ps-banner-section { padding: 0; }

/* The statement banner carried a darkened stock shot of a government building,
   which DESIGN-SYSTEM.md §8 rules out on this page anyway. It is now the CI's
   full-colour artwork on its own field: the file's ground is (11,83,153),
   two points off --osh-blue, so the token carries the rest of the width with
   no seam.

   `auto 150%` is safe here — the caveat in §6 is that height-driven sizing
   grows the artwork as a SECTION grows, and this band's height is clamped, so
   it cannot run away. Anchored right, it stays clear of the copy: the shapes
   are Secondary Cyan, which would put white type at 2.4:1 if it ran underneath
   them. The overlay gradient is what guarantees that — it stays dark across the
   copy and thins out over the artwork.

   Height is a clamp rather than a ratio now that the band is full-bleed: 21:7
   of a 1920px screen is a 640px-deep band, which would make a two-line
   statement into a full screen of empty navy. */
.ps-hero-image {
  position: relative;
  overflow: hidden;
  min-height: clamp(300px, 30vw, 400px);
  display: flex;
  background: var(--osh-blue);
}
/* The artwork rides a pseudo-element rather than the box's own background so
   its LEFT edge can be masked. Painted straight on, that edge is a hard
   vertical cut down the middle of the band — the overlay gradient is already
   thin by the time it reaches it, so it does not hide it. Faded out instead,
   the artwork dissolves into the field it matches. */
.ps-hero-image::before {
  content: '';
  position: absolute;
  inset: 0;
  background: url('../images/ci/patterns/background-pattern.png')
              right center / auto 150% no-repeat;
  -webkit-mask-image: linear-gradient(to right, transparent 30%, #000 62%);
          mask-image: linear-gradient(to right, transparent 30%, #000 62%);
  pointer-events: none;
}
.ps-hero-image-overlay {
  position: absolute; inset: 0;
  z-index: 1;
  background: linear-gradient(
    to right,
    rgba(3,43,68,0.90) 0%,
    rgba(3,43,68,0.82) 38%,
    rgba(3,43,68,0.34) 72%,
    rgba(3,43,68,0.06) 100%
  );
  /* Left and vertically centred. The horizontal padding is the container's,
     not the overlay's — that is what keeps the statement on the same left
     margin as the tiles above it. */
  display: flex; align-items: center; padding: 48px 0;
}
.ps-hero-image-overlay > .container { width: 100%; }
.ps-hero-image-text { max-width: 42rem; }
.ps-hero-image-text h3 {
  font-family: var(--font-heading);
  font-size: clamp(1.5rem, 3vw, 2.25rem);
  font-weight: 700; color: #fff; line-height: 1.15;
  letter-spacing: -0.02em; margin-bottom: 14px;
}
.ps-hero-image-text p {
  font-size: 1rem; color: rgba(255,255,255,0.78); max-width: 56ch; line-height: 1.6;
}
/* Narrow screens: the artwork sits behind the copy rather than beside it, so
   the overlay has to stay dark all the way across instead of thinning out. */
@media (max-width: 780px) {
  .ps-hero-image { min-height: 280px; }
  .ps-hero-image-overlay {
    background: linear-gradient(
      to right,
      rgba(3,43,68,0.92) 0%,
      rgba(3,43,68,0.86) 60%,
      rgba(3,43,68,0.72) 100%
    );
    padding: 40px 0;
  }
}

/* =========================================
   ABOUT PAGE — VALUES, TEAM
   ========================================= */
.values-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 28px;
  margin-top: 40px;
}
.value-card {
  position: relative;
  padding: 0;
  background: linear-gradient(155deg, #0A4479 0%, #032B44 60%, #021D2F 100%);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--radius-lg);
  color: #fff;
  overflow: visible;
  box-shadow: 0 10px 40px rgba(3,43,68,0.18);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease), border-color 0.4s var(--ease);
}
.value-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  background: radial-gradient(circle at 80% 110%, rgba(245,139,34,0.18), transparent 55%);
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  pointer-events: none;
}
.value-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 24px 60px rgba(3,43,68,0.35);
  border-color: rgba(245,139,34,0.45);
}
.value-card:hover::after { opacity: 1; }

/* Photo on top, dark body under it.
   .value-media is the positioning context so the number badge can straddle the
   seam between the two: .value-img clips the photo to the card's top corners,
   and the badge sits outside that clip in .value-media. */
.value-media { position: relative; }
.value-img {
  aspect-ratio: 16/9;
  overflow: hidden;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.value-img img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease);
}
.value-card:hover .value-img img { transform: scale(1.05); }
.value-body { padding: 42px 36px 38px; }

/* Number badge — sits on the seam, bottom-left of the photo */
.value-num {
  position: absolute;
  top: auto;
  bottom: -26px;
  left: 24px;
  z-index: 2;
  font-family: var(--font-heading);
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  color: #fff;
  background: linear-gradient(135deg, var(--blue) 0%, #F7A94E 100%);
  padding: 16px 22px;
  border-radius: 16px;
  box-shadow:
    0 12px 30px rgba(245,139,34,0.45),
    0 0 0 4px rgba(255,255,255,0.95),
    0 0 0 5px rgba(245,139,34,0.18);
  transition: transform 0.5s var(--ease), border-radius 0.5s var(--ease);
}
.value-card:hover .value-num {
  transform: scale(1.06) rotate(-3deg);
  border-radius: 20px;
}
.value-title {
  font-family: var(--font-heading);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: #fff;
  margin-bottom: 14px;
  line-height: 1.2;
  position: relative;
  z-index: 1;
}
.value-title::after {
  content: '';
  display: block;
  width: 32px;
  height: 2px;
  background: var(--blue);
  margin-top: 14px;
  transition: width 0.5s var(--ease);
}
.value-card:hover .value-title::after { width: 64px; }
.value-desc {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: rgba(255,255,255,0.72);
  position: relative;
  z-index: 1;
}

/* Sage certifications block — center the logo */
/* Certifications is the About page's Carrot Curl band, and it now uses exactly
   the same construction as the mSCOA band on Public Sector: the CI artwork
   dropped in as two masked corner plates (.ci-plate, below) rather than one
   height-anchored full-bleed image.

   The ground is the SAGE deep green with the green pattern — client
   direction, 21 Aug 2026, matching .sage-band on the product pages. This band
   is a Sage accreditation, so it now sits in Sage's own field rather than in
   Carrot Curl. White is 7.9:1 on the field and 6.0:1 over the lightest shape
   in the artwork, so the pattern can run under the copy without dropping it
   below AA. The border-top is dropped; a hairline sized for a near-white
   ground reads as a scratch here. */
.cert-section {
  position: relative;
  overflow: hidden;
  background: var(--sage-green-deep);
  padding: 80px 0;
  border-top: 0;
}
/* .ci-plate carries the Carrot Curl artwork by default — this band swaps in
   the green remap, whose field is exactly --sage-green-deep, so the plate sits
   on the ground with no seam. */
.cert-section .ci-plate {
  background-image: url('../images/ci/patterns/green-background-pattern.png');
  aspect-ratio: 928 / 1055;
}
.cert-inner { text-align: center; position: relative; z-index: 1; }
.cert-section .cert-headline { color: #fff; }
.cert-section .cert-desc { color: rgba(255,255,255,0.92); }
/* The mSCOA copy is a left column, so the plates sit in space it never uses.
   This band is centred and full width, so the top-right plate runs straight
   behind the headline — badly at phone widths, where it is 62vw. It is pulled
   in and knocked back here only. The plate carries the artwork's own green
   field, which is --sage-green-deep to the pixel, so fading it stays
   seamless. */
.cert-section .ci-plate { opacity: 0.72; }
.cert-section .ci-plate--tr { width: clamp(240px, 32vw, 520px); }

/* Same pill as .sage-band, the site's other deep-green ground: Sage's own
   #04DB04 as a solid mark with Blue Whale ink — 7.5:1 for the type, 4.2:1
   pill-against-ground. The CI's Carrot Curl dot is far too close to this
   green to register, so it takes the pill's own ink. */
.cert-section .tag { background: var(--sage-green); color: var(--osh-whale); }
.cert-section .tag::before {
  background: var(--osh-whale);
  box-shadow: 0 0 0 3px rgba(3,43,68,0.22);
}
/* The mark is the Sage PARTNER badge, and the file is its dark-ground version
   — the outline and wordmark are green, "Partner" is set in near-white. The
   band's own ground is now dark, so the badge no longer needs a card under
   it: client direction, 21 Aug 2026 is the bare transparent mark, one size up.
   No filter either — a partner's mark is never recoloured to fit a ground. */
.cert-section .cert-logo,
.cert-section .cert-logo:hover {
  filter: none;
  background: none;
  padding: 0;
  border-radius: 0;
  box-shadow: none;
  height: 76px;
  width: auto;
}
.cert-headline {
  font-size: clamp(1.5rem, 3vw, 2.25rem);
  margin-bottom: 20px;
}
.cert-desc {
  font-size: 0.9375rem;
  color: var(--text-muted);
  max-width: 600px;
  margin: 0 auto 48px;
  line-height: 1.7;
}
.cert-logo {
  display: block;
  margin: 0 auto;
  height: 56px;
  width: auto;
  filter: grayscale(1) opacity(0.6);
  transition: filter 0.4s var(--ease);
}
.cert-logo:hover { filter: grayscale(0) opacity(1); }

.team-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
/* Four named people since 22 Sept 2026, and four in a three-column grid
   leaves one on a row of its own. The count comes from the template as a
   modifier (the same trick .press-grid--n2 uses on a release page), so adding
   a fifth member in the dashboard falls back to the three-up default rather
   than silently keeping a four-up track. DESIGN-SYSTEM.md §6. */
.team-grid--n4 { grid-template-columns: repeat(4, 1fr); }
.team-grid--n2 { grid-template-columns: repeat(2, 1fr); }
.team-grid--n1 { grid-template-columns: minmax(0, 360px); justify-content: center; }
.team-card {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 0.3s var(--ease), box-shadow 0.3s;
}
.team-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.team-card img {
  /* `height: auto` is what actually fixes the runaway height here, and it has
     to be explicit. osh_img() writes the intrinsic width/height onto the tag
     to reserve the box, and a presentational height="1400" beats `auto` when
     no author rule sets height at all — so aspect-ratio was being ignored and
     the portraits rendered at their full 1400px, cropped to a sliver. Every
     other aspect-ratio box on the site puts the ratio on a wrapper div and
     the img at height:100%, which is why this was the only one affected.

     Square rather than the old 3/4: in a three-column grid 3/4 makes the row
     much taller than everything around it. */
  width: 100%; height: auto; aspect-ratio: 1/1;
  object-fit: cover; object-position: center 22%;
  transition: transform 0.5s var(--ease);
}
.team-card:hover img { transform: scale(1.04); }
.team-info { padding: 20px 24px; }
/* These two swapped weight on 22 Sept 2026. The cards used to carry a
   DIVISION ("Consulting Division") over a job title, and the division was the
   small uppercase line; they now carry a person's name over their role, and
   the name is what the card is for — so the name takes the heading face and
   the role becomes the line beneath it. Same markup, reversed emphasis. */
.team-name {
  font-family: var(--font-heading);
  font-size: 1.0625rem; font-weight: 700;
  color: var(--ink); margin-bottom: 4px;
}
.team-role {
  font-size: 0.75rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  /* --osh-blue, NOT the legacy --blue this line inherited from the old
     .team-name rule. --blue is one of the leftover names that now resolve to
     Carrot Curl Orange (DESIGN-SYSTEM.md §1), which is 2.4:1 on this card's
     near-white ground — a ground rather than an ink, and never running text.
     Egyptian Blue is 7.7:1 and is what a caption belongs in. */
  color: var(--osh-blue);
}

/* =========================================
   CAREERS PAGE
   ========================================= */
/* The text column sets the height and the photo fills it — a fixed 4/3 next to
   a centred text column left the picture noticeably shorter than the copy
   beside it. The img is taken out of flow so it can never push the row back
   out; the single-column fallback below restores a ratio, since there is no
   sibling left to inherit a height from. */
.careers-intro-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 80px;
  align-items: stretch;
}
.careers-intro-img {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  min-height: 420px;
  box-shadow: var(--shadow-lg);
}
.careers-intro-img img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

.perks-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
.perk-card {
  position: relative;
  overflow: hidden;
  /* Own stacking context, so the photo layer's negative z-index stays inside
     this card instead of sliding behind the section. */
  isolation: isolate;
  background: var(--osh-whale);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 32px;
  color: #fff;
  transition: transform 0.3s var(--ease), box-shadow 0.3s, border-color 0.3s;
}
.perk-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow);
  border-color: var(--blue);
}
.perk-icon {
  width: 48px; height: 48px;
  border-radius: 12px;
  background: var(--blue-pale);
  color: var(--blue);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 20px;
}
.perk-icon svg { width: 22px; height: 22px; }
.perk-title {
  font-family: var(--font-heading);
  font-size: 1.125rem; font-weight: 700; color: #fff; margin-bottom: 10px;
}
.perk-desc { font-size: 0.9375rem; line-height: 1.6; color: rgba(255,255,255,0.86); }

/* ── Life at OS Holdings: photographic cards ─────────────────────────────
   Client direction, 21 Aug 2026 — the six perk tiles carry an AI/technology
   photograph instead of a flat tint. The picture is a real <img> rather than
   a CSS background so it keeps the WebP srcset and lazy loading every other
   photograph on the site gets.

   z-index: -1 inside the card's own stacking context paints the layer above
   the card's background and below its content, so nothing has to be lifted
   with a positive z-index.

   The scrim is a VERTICAL ramp, not a flat wash — the subject sits in the
   upper part of every one of these frames and the copy in the lower, so the
   top is left almost clear and the ramp deepens under the type.

   It is deliberately LIGHT. The artwork here is dark navy/teal to begin with,
   so a scrim sized for a bright photograph crushed it to black and the
   pictures stopped registering at all. Measured, not eyeballed: screenshot
   with the copy hidden, sample the brightest ground pixel under each copy
   block. See the per-card note below for the one bright picture in the set. */
.perk-bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  border-radius: inherit;
}
/* The <picture> is unclassed, so it is display: contents and generates no box
   — the sizing has to land on the image itself. */
.perk-bg img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease);
}
.perk-card:hover .perk-bg img { transform: scale(1.06); }
.perk-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top,
      rgba(2,22,36,0.84) 0%,
      rgba(3,43,68,0.78) 58%,
      rgba(3,43,68,0.34) 72%,
      rgba(3,43,68,0.05) 100%);
  transition: opacity 0.4s var(--ease);
}
.perk-card:hover .perk-bg::after { opacity: 0.92; }

.jobs-list { display: flex; flex-direction: column; gap: 2px; }
.job-item {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 22px 28px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  transition: background 0.2s, border-color 0.2s, transform 0.2s;
}
.job-item:hover { background: var(--bg2); border-color: var(--blue); transform: translateX(4px); }
.job-title {
  font-family: var(--font-heading);
  font-size: 1rem; font-weight: 700; color: var(--ink); margin-bottom: 8px;
}
.job-meta { display: flex; flex-wrap: wrap; gap: 8px; }
/* The one-line summary under the tags, added with the per-vacancy pages on
   22 Sept 2026. It sits BELOW the tags rather than above them because the
   tags are what a scanner reads first — department, type, location — and the
   sentence is what they read once one of the three has caught them. Clamped
   to two lines so a long summary cannot make one row twice the height of the
   five around it. */
.job-summary {
  margin: 10px 0 0;
  max-width: 62ch;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.job-tag {
  font-size: 0.75rem; font-weight: 600;
  color: var(--text-muted);
  padding: 4px 12px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 100px;
}
.job-arrow {
  flex-shrink: 0;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--blue-pale);
  color: var(--blue);
  display: flex; align-items: center; justify-content: center;
  transition: background 0.2s;
}
.job-arrow svg { width: 16px; height: 16px; }
.job-item:hover .job-arrow { background: var(--blue); color: #fff; }

.press-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
.press-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 0.3s var(--ease), box-shadow 0.3s, border-color 0.3s;
  display: flex; flex-direction: column;
}
.press-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: var(--blue);
}
.press-card-img {
  aspect-ratio: 16/9; overflow: hidden;
}
.press-card-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s var(--ease); }
.press-card:hover .press-card-img img { transform: scale(1.05); }
.press-card-body { padding: 24px; flex: 1; }
.press-date { font-size: 0.75rem; font-weight: 600; color: var(--blue); margin-bottom: 8px; letter-spacing: 0.04em; }
.press-title {
  font-family: var(--font-heading);
  font-size: 1rem; font-weight: 700; color: var(--ink); margin-bottom: 10px; line-height: 1.3;
}
.press-excerpt { font-size: 0.875rem; line-height: 1.6; color: var(--text-muted); }

/* =========================================
   CONTACT PAGE
   ========================================= */
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; }
.form-group { margin-bottom: 16px; }
.form-label {
  display: block; font-size: 0.8125rem; font-weight: 600;
  color: var(--ink); margin-bottom: 8px;
}
.form-input, .form-select, .form-textarea {
  width: 100%;
  padding: 12px 16px;
  border: 1.5px solid var(--border-strong);
  border-radius: 10px;
  font-size: 0.9375rem; font-family: inherit;
  color: var(--ink); background: var(--bg);
  transition: border-color 0.2s, box-shadow 0.2s;
  outline: none;
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
  border-color: var(--osh-blue);
  box-shadow: 0 0 0 3px rgba(11,83,155,0.16);
}
.form-textarea { min-height: 130px; resize: vertical; }
.form-submit { width: 100%; justify-content: center; font-size: 1rem; padding: 15px; margin-top: 4px; }

/* Newsletter opt-in above the Send button — the one checkbox on the public
   site, so it gets its own rule rather than a general component. The native
   box is kept (keyboard and screen-reader behaviour for free) and brought into
   brand with accent-color. */
.form-consent { margin: 2px 0 18px; }
.form-consent-label {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 0.875rem; line-height: 1.5; color: var(--text-muted);
  cursor: pointer;
}
.form-consent-label input {
  flex: 0 0 auto; width: 18px; height: 18px; margin: 1px 0 0;
  accent-color: var(--osh-blue); cursor: pointer;
}

/* Social button (used in contact & footer) */
.social-btn {
  width: 38px; height: 38px;
  border-radius: 50%;
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-muted);
  transition: all 0.25s;
  text-decoration: none;
}
.social-btn svg { width: 16px; height: 16px; }
.social-btn:hover { background: var(--blue); border-color: var(--blue); color: #fff; transform: translateY(-2px); }

/* =========================================
   FOOTER
   ========================================= */
.footer { background: var(--ink); color: #fff; padding: 90px 0 36px; }
.footer-top { max-width: 1240px; margin: 0 auto; padding: 0 32px; }
.footer-grid {
  display: grid; grid-template-columns: 1.4fr repeat(4, 1fr);
  gap: 48px 32px;
  padding-bottom: 60px;
  border-bottom: 1px solid rgba(255,255,255,0.1);
}
.footer-brand { max-width: 300px; }
.footer-logo { height: 34px; width: auto; margin-bottom: 20px; display: block; }
.footer-tagline { color: rgba(255,255,255,0.6); font-size: 0.9375rem; line-height: 1.6; margin-bottom: 24px; }
.footer-socials { display: flex; gap: 10px; }
.footer .social-btn { border-color: rgba(255,255,255,0.15); color: #fff; }
.footer .social-btn:hover { background: var(--blue); border-color: var(--blue); }
.footer-heading {
  font-size: 0.875rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: #fff; margin-bottom: 18px;
}
.footer-links { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.footer-links a {
  color: rgba(255,255,255,0.75); text-decoration: none;
  font-size: 0.9375rem; transition: color 0.2s;
}
.footer-links a:hover { color: var(--blue-hi); }
.footer-contact { list-style: none; display: flex; flex-direction: column; gap: 12px; }
.footer-contact li {
  color: rgba(255,255,255,0.75); font-size: 0.9375rem;
  display: flex; align-items: flex-start; gap: 10px; line-height: 1.5;
}
.footer-contact svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 2px; opacity: 0.55; }
/* Partner marks — above the columns, so they appear on every page. Both files
   are the DARK-ground versions of the marks (Amathuba's wordmark is white,
   Sage's "Partner" is light grey), which is why they go straight onto the
   footer's Blue Whale rather than onto a card the way About's orange band
   does. Held small on purpose: this is accreditation, not branding, and it
   must not compete with the OS Holdings lockup directly beneath it. */
.footer-partners {
  display: flex;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
  padding-bottom: 30px;
  margin-bottom: 40px;
  border-bottom: 1px solid rgba(255,255,255,0.1);
}
.footer-partners-label {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* see .footer-bottom — 0.45 is 4.09:1 on this ground */
  color: rgba(255,255,255,0.62);
}
.footer-partner-marks {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 30px;
  flex-wrap: wrap;
}
/* `width: auto` is required, not decorative: osh_img() writes the intrinsic
   width/height onto the tag, and those beat a height-only rule — the mark
   renders stretched without it. */
.footer-partner-logo { display: block; width: auto; }
/* 28, down from the 34 the old knockout outline wanted. The supplied mark is
   a FILLED plaque with a bright green rule, so it carries far more weight per
   pixel than the two open wordmarks beside it — at 34 it was both the widest
   and the only solid block, and read as a button somebody had dropped in the
   row. 28 brings it to ~94px, between Amathuba's 100 and OSBA's 83. */
.footer-partner-logo--sage     { height: 28px; }
/* smaller: it is a wordmark lockup, so it reads bigger than the boxed badge
   beside it at the same pixel height */
.footer-partner-logo--amathuba { height: 30px; }
/* Same reasoning again, one step further: this mark is a wordmark ~6:1 wide
   (icon + two-line-worth of type set on one line), the widest of the three,
   so it gets the smallest height to land at roughly the same VISUAL weight
   as Sage and Amathuba rather than the same pixel height. */
.footer-partner-logo--osba     { height: 26px; }

.footer-bottom {
  max-width: 1240px; margin: 32px auto 0; padding: 0 32px;
  display: flex; justify-content: space-between; gap: 16px;
  /* 0.45 white on Blue Whale is 4.09:1 — under AA for 13px type. 0.62 is
     6.5:1 and still reads as the quietest line on the page. */
  font-size: 0.8125rem; color: rgba(255,255,255,0.62); flex-wrap: wrap;
}

/* =========================================
   MARQUEE
   ========================================= */
.marquee-section { padding: 28px 0; overflow: hidden; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.marquee-track { display: flex; gap: 40px; white-space: nowrap; animation: marquee-run 40s linear infinite; }
@keyframes marquee-run { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.marquee-item { display: inline-flex; align-items: center; gap: 40px; font-size: 0.875rem; font-weight: 500; color: var(--text-muted); flex-shrink: 0; }
.marquee-item .dot { width: 5px; height: 5px; background: var(--blue); border-radius: 50%; }

/* =========================================
   RESPONSIVE
   ========================================= */
@media (max-width: 1050px) {
  .about-grid,
  .careers-intro-grid { grid-template-columns: 1fr; gap: 48px; }
  /* no text column left to take the height from */
  .careers-intro-img { aspect-ratio: 4/3; min-height: 0; }
  /* svc-track falls back to CSS grid on smaller screens (no GSAP pin) */
  .svc-track {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    width: 100%;
    padding: 0 32px;
  }
  .svc-track .svc-card { width: auto; }
  .services-grid,
  .services-grid--n4,
  .ps-products { grid-template-columns: repeat(2, 1fr); }
  .ps-content { grid-template-columns: 1fr; gap: 40px; padding: 80px 32px; }
  .solution-feature { grid-template-columns: 1fr; gap: 40px; }
  .solution-feature.reverse .solution-media { order: 0; }
  .solution-feature.reverse .solution-content { order: 1; }
  .footer-grid { grid-template-columns: 1fr 1fr 1fr; }
  .cta-band { grid-template-columns: 1fr; padding: 48px 36px; }
  .cta-band-action { margin-top: 16px; }
  .values-grid { grid-template-columns: 1fr 1fr; }
  .perks-grid { grid-template-columns: repeat(2, 1fr); }
  .press-grid { grid-template-columns: repeat(2, 1fr); }
  /* Four portraits across stop being portraits at this width. */
  .team-grid, .team-grid--n4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 700px) {
  :root { --nav-h: 68px; }
  .sh-nav, .sh-cta { display: none; }
  .sh-burger { display: flex; margin-left: auto; }
  .sh-inner { padding: 0 20px; }
  .container { padding: 0 20px; }
  .section { padding: 72px 0; }
  .hero-content { padding: 0 20px 60px; }
  .hero-title { font-size: clamp(2.25rem, 9vw, 3rem); }
  .hero-chips { display: none; }
  .hero-scroll { right: 20px; bottom: 20px; }
  .svc-track,
  .services-grid,
  .services-grid--n4,
  .ps-products,
  .values-grid,
  .team-grid,
  .team-grid--n4,
  .perks-grid,
  .press-grid { grid-template-columns: 1fr; }
  .svc-track { padding: 0 20px; }
  .svc-track .svc-card { width: auto; }
  /* One card per row here, so the 3:4 portrait that works in a four-across
     carousel becomes a near-full-screen slab. 3:2 halves the height — the body
     sits at the foot of the card and needs nothing like the room 3:4 gave it. */
  .svc-card { aspect-ratio: 3 / 2; }
  .footer-grid { grid-template-columns: 1fr; }
  .footer { padding: 60px 0 30px; }
  /* the label and the two marks stack rather than wrapping mid-row */
  .footer-partners { gap: 14px; padding-bottom: 24px; margin-bottom: 32px; }
  .footer-partner-marks { gap: 22px; }
  /* 23, not the old 28 — the same step down the filled plaque needed on the
     desktop row, applied to the phone sizes. */
  .footer-partner-logo--sage     { height: 23px; }
  .footer-partner-logo--amathuba { height: 25px; }
  .footer-partner-logo--osba     { height: 22px; }
  .logo-strip-row img { height: 34px; }
  /* Shorter band on phones, but the photo still needs roughly half the height
     above the copy or the faces end up behind the headline. */
  .page-hero {
    min-height: 520px;
    padding: calc(var(--nav-h) + 190px) 0 54px;
  }
  .form-row { grid-template-columns: 1fr; }
}

/* Solution feature CTAs (solutions.php) */
.solution-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 8px;
}

/* =========================================
   PRODUCT PAGES — Sage product detail pages
   ========================================= */

/* ── Product Hero ── */
.product-hero {
  position: relative;
  min-height: 92vh;
  padding-top: var(--nav-h);
  overflow: hidden;
  display: flex;
  align-items: stretch;
  background: #021D2F;
  color: #fff;
}
.product-hero-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.product-hero-bg img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: var(--hero-pos, center 30%);
  transform: scale(1.04);
  animation: ph-kenburns 18s ease-in-out infinite alternate;
}
@keyframes ph-kenburns {
  0%   { transform: scale(1.02) translate(0, 0); }
  100% { transform: scale(1.12) translate(-1.5%, -1.5%); }
}
/* Same pamphlet fade as .page-hero: the picture is left legible across the top
   so the people in it read, then dissolves into Blue Whale behind the copy and
   the stat rail. The old build darkened the left edge to 0.92 from the top
   down, which buried whatever the photograph actually showed. */
.product-hero-overlay {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 22% 88%, rgba(245,139,34,0.16), transparent 55%),
    linear-gradient(118deg, rgba(11,83,155,0.40), transparent 58%),
    linear-gradient(to bottom,
      rgba(2,29,47,0.58) 0%,
      rgba(2,29,47,0.24) 14%,
      rgba(2,29,47,0.24) 32%,
      rgba(3,43,68,0.72) 55%,
      rgba(3,43,68,0.95) 77%,
      var(--osh-whale) 95%);
}
/* Per-page crops, as on .page-hero */
.product-hero--sage-300 { --hero-pos: center 32%; }
.product-hero--sage-200 { --hero-pos: center 34%; }
.product-hero--intacct  { --hero-pos: center 30%; }
.product-hero--sage-x3  { --hero-pos: center 28%; }
.product-hero-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: 80px 64px;
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 60px;
  align-items: end;
}
.product-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.78);
  margin-bottom: 28px;
  padding: 10px 18px 10px 14px;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.product-eyebrow-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 0 0 4px rgba(245,139,34,0.22);
}
.product-hero-title {
  font-family: var(--font-heading);
  font-size: clamp(2.75rem, 6.4vw, 5.5rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 0.98;
  color: #fff;
  margin-bottom: 28px;
  max-width: 16ch;
  padding-right: 0.25em;
}
.product-hero-title em {
  font-style: italic;
  font-weight: 400;
  display: inline-block;
  padding-right: 0.18em;
  margin-right: -0.18em;
  background: linear-gradient(120deg, #fff 0%, #FBD3A6 50%, var(--blue) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.product-hero-sub {
  font-size: 1.1875rem;
  line-height: 1.55;
  color: rgba(255,255,255,0.78);
  max-width: 56ch;
  margin-bottom: 40px;
  font-weight: 300;
}
.product-hero-actions {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}

/* Hero stats column */
.product-hero-stats {
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-self: end;
}
.phs-stat {
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.14);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-radius: var(--radius);
  padding: 20px 24px 20px 22px;
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 16px;
  transition: transform 0.4s var(--ease), border-color 0.4s var(--ease), background 0.4s var(--ease);
}
.phs-stat::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--sage-green);
}
.phs-stat:hover {
  background: rgba(255,255,255,0.10);
  border-color: rgba(255,255,255,0.28);
  transform: translateY(-2px);
}
.phs-icon {
  width: 44px; height: 44px;
  flex-shrink: 0;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, rgba(245,139,34,0.28), rgba(245,139,34,0.08));
  color: var(--blue);
  border: 1px solid rgba(245,139,34,0.35);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06);
}
.phs-icon svg { width: 22px; height: 22px; }
.phs-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.phs-num {
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: #fff;
  line-height: 1.1;
}
.phs-lbl {
  font-size: 0.8125rem;
  color: rgba(255,255,255,0.65);
  margin-top: 4px;
  font-weight: 500;
}

/* ── Product intro block ── */
/* Centred: this is the one section on a product page with no second column
   and no cards, so a left-ranged 760px block left the right half of the row
   visibly empty. The pill is inline-flex, so text-align carries it — but it
   also sets align-self: flex-start for the flex parents it lives in
   elsewhere, which is inert here because .product-intro is a block. */
.product-intro {
  max-width: 760px;
  margin-inline: auto;
  text-align: center;
}
.product-intro .eyebrow,
.product-intro .section-headline,
.product-intro .section-sub { margin-bottom: 20px; }
/* text-align alone is not enough here. .section-sub is capped at 56ch, and
   with no auto side margins that narrower box sits at the LEFT of the 760px
   container — so the lines centre inside the box while the box itself sits
   left of the headline. Same fix .section-head.center already carries. */
.product-intro .section-sub {
  margin-bottom: 0;
  margin-inline: auto;
}

/* ── Feature grid — visual showcase cards ── */
.prod-feature-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px 28px;
  margin-top: 80px;
}
.prod-feature {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: visible; /* allow icon to escape top-left corner */
  isolation: isolate;
  min-height: 380px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  color: #fff;
  padding: 32px 30px;
  transition: transform 0.5s var(--ease), box-shadow 0.5s var(--ease);
  box-shadow: 0 8px 30px rgba(3,43,68,0.12);
}

/* Background image layer */
.prod-feature-bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.prod-feature-bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  transform: scale(1.04);
  transition: transform 1.1s var(--ease), filter 0.6s var(--ease);
  filter: saturate(0.95);
}

/* Gradient overlay */
.prod-feature-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(2,29,47,0.20) 0%, rgba(2,29,47,0.55) 55%, rgba(2,29,47,0.92) 100%),
    linear-gradient(135deg, rgba(245,139,34,0.18) 0%, transparent 55%);
  transition: opacity 0.5s var(--ease);
}

/* Subtle border glow */
.prod-feature::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255,255,255,0.10);
  pointer-events: none;
  z-index: 0;
  transition: border-color 0.5s var(--ease);
}

.prod-feature:hover {
  transform: translateY(-6px);
  box-shadow: 0 24px 60px rgba(3,43,68,0.30);
}
.prod-feature:hover .prod-feature-bg img {
  transform: scale(1.12);
  filter: saturate(1.05);
}
.prod-feature:hover::before {
  border-color: rgba(245,139,34,0.55);
}

/* Floating icon badge — top-left, half outside the card */
.prod-feature-icon {
  position: absolute;
  top: -22px;
  left: -22px;
  z-index: 2;
  width: 64px; height: 64px;
  border-radius: 18px;
  background: linear-gradient(135deg, var(--blue) 0%, #F7A94E 100%);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  box-shadow:
    0 12px 30px rgba(245,139,34,0.45),
    0 0 0 4px rgba(255,255,255,0.95),
    0 0 0 5px rgba(245,139,34,0.18);
  transition: transform 0.5s var(--ease), box-shadow 0.5s var(--ease), border-radius 0.5s var(--ease);
}
.prod-feature-icon svg { width: 28px; height: 28px; }
.prod-feature:hover .prod-feature-icon {
  transform: scale(1.08) rotate(-4deg);
  border-radius: 22px;
  box-shadow:
    0 16px 38px rgba(245,139,34,0.55),
    0 0 0 4px rgba(255,255,255,0.95),
    0 0 0 5px rgba(245,139,34,0.28);
}

/* Step number badge — top-right */
.prod-feature-num {
  position: absolute;
  top: 22px;
  right: 24px;
  z-index: 2;
  font-family: var(--font-heading);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: rgba(255,255,255,0.70);
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.20);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.prod-feature h3 {
  font-family: var(--font-heading);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin-bottom: 12px;
  color: #fff;
  position: relative;
  z-index: 1;
}
.prod-feature p {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: rgba(255,255,255,0.78);
  position: relative;
  z-index: 1;
  margin-bottom: 0;
}

/* Hover reveal accent line */
.prod-feature h3::after {
  content: '';
  display: block;
  width: 32px;
  height: 2px;
  background: var(--blue);
  margin-top: 14px;
  transition: width 0.5s var(--ease);
}
.prod-feature:hover h3::after { width: 64px; }

/* Mobile/tablet feature grid tweaks (extending earlier rules) */
@media (max-width: 1100px) {
  .prod-feature-grid { gap: 32px 20px; margin-top: 60px; }
  .prod-feature { min-height: 340px; }
}
@media (max-width: 720px) {
  .prod-feature-grid { gap: 36px 16px; margin-top: 50px; }
  .prod-feature {
    min-height: 300px;
    padding: 28px 24px;
  }
  .prod-feature-icon {
    width: 54px; height: 54px;
    top: -18px; left: -10px;
    border-radius: 16px;
  }
  .prod-feature-icon svg { width: 24px; height: 24px; }
  .prod-feature-num { top: 18px; right: 18px; }
}

/* ── Benefit split (image + text) ── */
.prod-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 80px;
  align-items: center;
}
.prod-split.reverse .prod-split-media { order: 2; }
.prod-split.reverse .prod-split-text  { order: 1; }
.prod-split-media {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background: var(--bg2);
}
.prod-split-media img {
  width: 100%; height: 100%;
  object-fit: cover;
}
/* inline-flex, not inline-block — the pill lays its dot out with flex. */
.prod-split-text .eyebrow { margin-bottom: 16px; display: inline-flex; }
.prod-split-text .section-headline { margin-bottom: 22px; }
.prod-split-text .section-sub { margin-bottom: 32px; }

.prod-bullets {
  list-style: none;
  margin: 0 0 36px;
  padding: 0;
  display: grid;
  gap: 14px;
}
.prod-bullets li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: 0.9375rem;
  color: var(--text);
  line-height: 1.55;
}
.prod-bullets svg {
  flex-shrink: 0;
  width: 20px; height: 20px;
  color: var(--blue);
  margin-top: 2px;
}

/* ── Product page responsive ── */
@media (max-width: 1100px) {
  .product-hero-inner {
    grid-template-columns: 1fr;
    padding: 80px 40px;
    gap: 48px;
  }
  .product-hero-stats {
    flex-direction: row;
    flex-wrap: wrap;
  }
  .phs-stat { flex: 1 1 200px; }
  .prod-feature-grid { grid-template-columns: repeat(2, 1fr); }
  .prod-split { grid-template-columns: 1fr; gap: 48px; }
  .prod-split.reverse .prod-split-media { order: 1; }
  .prod-split.reverse .prod-split-text  { order: 2; }
  .prod-split-media { aspect-ratio: 16 / 10; max-height: 480px; }
}
@media (max-width: 720px) {
  .product-hero { min-height: 80vh; }
  .product-hero-inner { padding: 56px 24px; gap: 32px; }
  .product-hero-stats { flex-direction: column; }
  .phs-stat { flex: 1 1 auto; padding: 18px 22px; }
  .phs-num { font-size: 1.375rem; }
  .prod-feature-grid { grid-template-columns: 1fr; gap: 16px; margin-top: 40px; }
  .prod-feature { padding: 28px 24px; }
}

/* =========================================
   ERROR PAGES — 403 / 404 / 500
   Standalone styled pages on dark navy with orange accent
   ========================================= */
.error-page {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 60px 24px;
  background: radial-gradient(ellipse at 50% 30%, #063E63 0%, #032B44 55%, #021D2F 100%);
  color: #fff;
  overflow: hidden;
  font-family: var(--font-body);
}
.error-page::before {
  content: attr(data-code);
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-heading);
  font-size: clamp(220px, 36vw, 560px);
  font-weight: 700;
  line-height: 0.85;
  letter-spacing: -0.04em;
  color: rgba(255,255,255,0.035);
  pointer-events: none;
  user-select: none;
  z-index: 0;
  white-space: nowrap;
}
.error-page::after {
  content: '';
  position: absolute;
  left: 50%; top: 35%;
  width: 60vw; height: 60vw;
  max-width: 900px; max-height: 900px;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(245,139,34,0.18), transparent 60%);
  filter: blur(40px);
  pointer-events: none;
  z-index: 0;
}
.error-inner {
  position: relative;
  z-index: 1;
  max-width: 620px;
}
.error-eyebrow {
  display: flex;
  width: max-content;
  margin-inline: auto;
  align-items: center;
  gap: 10px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.78);
  margin-bottom: 28px;
  padding: 10px 18px 10px 14px;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.error-eyebrow::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 0 0 3px rgba(245,139,34,0.25);
}
.error-code {
  font-family: var(--font-heading);
  font-size: clamp(72px, 14vw, 144px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
  background: linear-gradient(120deg, #fff 0%, #FBD3A6 50%, var(--blue) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  margin-bottom: 8px;
  display: inline-block;
  padding-right: 0.18em;
  margin-right: -0.18em;
}
.error-title {
  font-family: var(--font-heading);
  font-size: clamp(1.75rem, 4vw, 2.625rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: #fff;
  margin-bottom: 18px;
}
.error-desc {
  font-size: 1.0625rem;
  line-height: 1.6;
  color: rgba(255,255,255,0.72);
  max-width: 52ch;
  margin: 0 auto 40px;
  font-weight: 300;
}
.error-actions {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
}
.error-page .btn-primary {
  background: var(--osh-orange);
  border-color: var(--osh-orange);
  color: #fff;
  box-shadow: 0 12px 30px rgba(245,139,34,0.45);
}
.error-page .btn-primary:hover {
  background: var(--osh-orange-hover);
  border-color: var(--osh-orange-hover);
  box-shadow: 0 16px 36px rgba(245,139,34,0.55);
  transform: translateY(-2px);
}
.error-brand {
  position: absolute;
  top: 28px;
  left: 32px;
  z-index: 2;
}
.error-brand img { height: 36px; width: auto; }
.error-footer {
  position: absolute;
  bottom: 24px;
  left: 0; right: 0;
  z-index: 1;
  font-size: 0.75rem;
  color: rgba(255,255,255,0.40);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

@media (max-width: 720px) {
  .error-brand { top: 20px; left: 20px; }
  .error-brand img { height: 30px; }
  .error-page::before { font-size: clamp(180px, 50vw, 380px); }
}

/* =========================================
   HERO SLIDER — Sage product showcase
   Premium full-bleed slider with cross-fade,
   parallax bg, ken-burns and progress indicators.
   ========================================= */
.hero-slider {
  position: relative;
  min-height: 100vh;
  width: 100%;
  overflow: hidden;
  padding-top: var(--nav-h);
  background: #021D2F;
  isolation: isolate;
}

.hs-stage {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hs-slide {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 1.1s var(--ease), visibility 0s linear 1.1s;
}
.hs-slide.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 1.1s var(--ease), visibility 0s linear 0s;
  z-index: 2;
}

.hs-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
.hs-bg-img {
  position: absolute;
  inset: -4%;
  width: 108%;
  height: 108%;
  object-fit: cover;
  object-position: center center;
  transform: scale(1.04);
  transition: transform 8s linear;
  will-change: transform;
}
.hs-slide.is-active .hs-bg-img {
  transform: scale(1.14);
}
.hs-overlay {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 20% 80%, rgba(245,139,34,0.22), transparent 55%),
    linear-gradient(115deg, rgba(2,29,47,0.92) 0%, rgba(2,29,47,0.62) 45%, rgba(2,29,47,0.35) 75%, rgba(2,29,47,0.55) 100%),
    linear-gradient(to top, rgba(2,29,47,0.85) 0%, rgba(2,29,47,0.10) 60%);
}

.hs-content {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: 80px 64px 220px;
  color: #fff;
}
.hs-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.78);
  margin-bottom: 28px;
  padding: 10px 18px 10px 14px;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.9s var(--ease) 0.15s, transform 0.9s var(--ease) 0.15s;
}
.hs-eyebrow-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 0 0 4px rgba(245,139,34,0.22);
}
.hs-title {
  font-family: var(--font-heading);
  font-size: clamp(2.75rem, 6.6vw, 5.75rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 0.98;
  color: #fff;
  max-width: 18ch;
  margin-bottom: 28px;
  padding-right: 0.25em;
  opacity: 0;
  transform: translateY(36px);
  transition: opacity 1s var(--ease) 0.3s, transform 1s var(--ease) 0.3s;
}
.hs-title em {
  font-style: italic;
  font-weight: 400;
  display: inline-block;
  padding-right: 0.18em;
  margin-right: -0.18em;
  background: linear-gradient(120deg, #fff 0%, #FBD3A6 50%, var(--blue) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.hs-desc {
  font-size: 1.1875rem;
  line-height: 1.55;
  color: rgba(255,255,255,0.78);
  max-width: 56ch;
  margin-bottom: 44px;
  font-weight: 300;
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.9s var(--ease) 0.5s, transform 0.9s var(--ease) 0.5s;
}
.hs-actions {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.9s var(--ease) 0.7s, transform 0.9s var(--ease) 0.7s;
}

/* On active: animate content in */
.hs-slide.is-active .hs-eyebrow,
.hs-slide.is-active .hs-title,
.hs-slide.is-active .hs-desc,
.hs-slide.is-active .hs-actions {
  opacity: 1;
  transform: translateY(0);
}

/* Top-right brand badge */
/* This line is the one thing in the hero that says what the business actually
   is, so it is set as a statement rather than as a glass chip: no pill, no
   blur, solid white, heavier and larger. The shadow is what keeps it readable
   where a slide runs bright behind it. */
.hs-badge {
  position: absolute;
  top: calc(var(--nav-h) + 30px);
  right: 64px;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  background: none;
  border: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-radius: 0;
  color: #fff;
  font-family: var(--font-heading);
  font-size: 0.9375rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-shadow: 0 2px 16px rgba(3,43,68,0.65);
}
.hs-badge-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--osh-orange);
  box-shadow: 0 0 0 4px rgba(245,139,34,0.28);
  flex-shrink: 0;
}

/* Prev/Next navigation arrows */
.hs-nav {
  position: absolute;
  top: 50%;
  z-index: 6;
  transform: translateY(-50%);
  width: 58px; height: 58px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.18);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-radius: 50%;
  color: #fff;
  cursor: pointer;
  transition: all 0.35s var(--ease);
}
.hs-nav:hover {
  background: rgba(255,255,255,0.16);
  border-color: rgba(255,255,255,0.36);
  transform: translateY(-50%) scale(1.06);
}
.hs-nav:active { transform: translateY(-50%) scale(0.96); }
.hs-nav svg { width: 22px; height: 22px; }
.hs-prev { left: 28px; }
.hs-next { right: 28px; }

/* Bottom controls bar */
.hs-controls {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 5;
  padding: 0 64px 38px;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 40px;
  align-items: end;
}

.hs-counter {
  display: flex;
  align-items: center;
  gap: 14px;
  color: #fff;
  font-family: var(--font-heading);
  font-weight: 600;
}
.hs-current {
  font-size: 2.75rem;
  line-height: 1;
  color: #fff;
  letter-spacing: -0.02em;
}
.hs-divider {
  width: 60px;
  height: 1px;
  background: rgba(255,255,255,0.4);
}
.hs-total {
  font-size: 0.875rem;
  color: rgba(255,255,255,0.55);
  letter-spacing: 0.05em;
}

.hs-thumbs {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
}
.hs-thumb {
  background: transparent;
  border: 0;
  text-align: left;
  color: rgba(255,255,255,0.55);
  cursor: pointer;
  padding: 14px 0 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  transition: color 0.35s var(--ease);
  border-top: 1px solid rgba(255,255,255,0.18);
  position: relative;
}
.hs-thumb:hover { color: rgba(255,255,255,0.9); }
.hs-thumb.is-active { color: #fff; }
.hs-thumb-num {
  font-family: var(--font-heading);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: rgba(255,255,255,0.5);
}
.hs-thumb.is-active .hs-thumb-num { color: var(--blue); }
.hs-thumb-label {
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.hs-thumb-bar {
  position: absolute;
  top: -1px; left: 0; right: 0;
  height: 2px;
  background: transparent;
  overflow: hidden;
}
.hs-thumb-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--blue);
  transition: width 0s linear;
}
.hs-thumb.is-active .hs-thumb-fill {
  width: 100%;
  transition: width var(--hs-duration, 6s) linear;
}

/* Reuse hero-scroll markup */
.hs-scroll {
  position: absolute;
  right: 64px; bottom: 220px; z-index: 5;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  color: rgba(255,255,255,0.5);
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  pointer-events: none;
}
.hs-scroll .scroll-line {
  width: 1px; height: 44px;
  background: linear-gradient(to bottom, rgba(255,255,255,0.5), transparent);
  animation: scroll-drop 1.8s ease-in-out infinite;
}

/* Pause autoplay state — used by JS when user hovers slider */
.hero-slider.is-paused .hs-thumb.is-active .hs-thumb-fill {
  animation-play-state: paused;
}

/* ───── Hero slider — tablet ───── */
@media (max-width: 1100px) {
  .hs-content { padding: 60px 40px 240px; }
  .hs-badge { right: 24px; top: calc(var(--nav-h) + 18px); }
  .hs-controls { padding: 0 40px 32px; gap: 28px; }
  .hs-prev { left: 16px; }
  .hs-next { right: 16px; }
  .hs-scroll { display: none; }
  .hs-thumbs { grid-template-columns: repeat(4, 1fr); gap: 12px; }
  .hs-thumb-label { font-size: 0.875rem; }
}

/* ───── Hero slider — mobile ───── */
@media (max-width: 720px) {
  .hero-slider { min-height: 92vh; }
  /* The slide is a flex box that centres .hs-content, so what positions the
     copy is the DIFFERENCE between its two vertical paddings, not their size:
     the block sits (bottom − top) / 2 above the slide's centre. It was
     260 − 56 = 204, parking the copy ~102px too high with a dead gap beneath.

     The strip the copy actually has to itself runs from the bottom of the nav
     to the top of the controls, so setting padding-top = nav height and
     padding-bottom = controls height centres it in exactly that strip — and
     keeps doing so at any viewport height, because both sides move together. */
  .hs-content { padding: 72px 24px 124px; }
  .hs-title { font-size: clamp(2.25rem, 11vw, 3.75rem); margin-bottom: 22px; }
  .hs-desc { font-size: 1rem; margin-bottom: 32px; }
  .hs-eyebrow { font-size: 0.6875rem; padding: 8px 14px 8px 12px; margin-bottom: 22px; }
  .hs-actions .btn { font-size: 0.875rem; padding: 12px 20px; }
  .hs-badge { display: none; }
  /* Vertically centred, they now sit on top of the action buttons. The slider
     already takes a touch swipe (main.js) and the thumb strip is a full set of
     jump targets, so the arrows are redundant at this width rather than lost. */
  .hs-nav { display: none; }
  .hs-controls {
    padding: 0 24px 28px;
    grid-template-columns: 1fr;
    gap: 22px;
  }
  .hs-counter { gap: 10px; }
  .hs-current { font-size: 2rem; }
  .hs-divider { width: 36px; }
  .hs-thumbs { grid-template-columns: repeat(4, 1fr); gap: 6px; }
  /* the label is hidden here, which left a 26px-tall button. The padding
     carries it back to 44 without changing what is drawn. */
  .hs-thumb { padding-top: 12px; padding-bottom: 12px; min-height: 44px; justify-content: flex-start; }
  .hs-thumb-label { display: none; }
  .hs-thumb-num { font-size: 0.75rem; }
}

/* =========================================================================
   OS HOLDINGS CI — DISPLAY TYPE, ACCENT ROLES & PATTERN
   Everything below implements the OSH CI Guideline 2026 on top of the
   base sheet. Ordered last so it wins without !important.
   ========================================================================= */

/* ── 1. Display type — Bebas Neue ────────────────────────────────────────
   Bebas is the secondary family: it carries the biggest statements and
   every large numeral. Avenir (--font-heading) keeps section headlines,
   card titles and UI, matching the CI billboard/banner artwork.
   Bebas is condensed with a tall cap-height, so the negative tracking the
   old face needed is reset to a hair of positive tracking. */
.hero-title,
.hs-title,
.product-hero-title,
.error-code,
.sh-mobile-link,
.hero-chip .chip-val,
.about-img-badge .big,
.about-stat .num,
.service-num,
.value-num,
.prod-feature-num,
.phs-num,
.hs-current,
.error-page::before {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.012em;
  text-transform: none;
}

.hero-title,
.hs-title,
.product-hero-title,
.error-code {
  line-height: 0.92;
}

/* Bebas has no italic — synthesised oblique looks broken at display size.
   The emphasis word is carried by the gradient fill instead. */
.hero-title em,
.hs-title em,
.product-hero-title em {
  font-style: normal;
  font-weight: 700;
}

/* ── 2. Accent roles ─────────────────────────────────────────────────────
   Carrot Curl Orange is the energy colour: dots, rules, bars, badge fills
   and glows. Egyptian Blue is the colour that gets READ — accent text,
   icons and filled buttons — because orange on white is only 2.2:1.
   Blue on white is 7.1:1, so the palette clears WCAG AA either way. */
.about-stat .num span,
.solution-badge,
.solution-features-list svg,
.team-name,
.perk-icon,
.job-arrow,
.press-date,
.prod-bullets svg {
  color: var(--accent-ink);
}


/* ── 3. Filled accent surfaces ───────────────────────────────────────────
   Every button on the site is Carrot Curl Orange with WHITE ink — client
   direction, 21 Aug 2026. This supersedes the earlier Blue-Whale-on-orange
   rule in DESIGN-SYSTEM.md §2; see the note there. */
.btn,
.btn-primary,
.btn-white,
.btn-outline,
.btn-outline-white,
.sh-cta,
.form-submit {
  background: var(--osh-orange);
  border: 1.5px solid var(--osh-orange);
  color: #fff;
}
.btn:hover,
.btn-primary:hover,
.btn-white:hover,
.btn-outline:hover,
.btn-outline-white:hover,
.sh-cta:hover,
.form-submit:hover {
  background: var(--osh-orange-hover);
  border-color: var(--osh-orange-hover);
  color: #fff;
  transform: translateY(-1px);
  box-shadow: 0 10px 28px rgba(245,139,34,0.35);
}
.btn .arrow-right,
.btn svg,
.sh-cta svg { color: #fff; }

/* The home slider's two actions were both painted Carrot Curl by the block
   above, so the pair read as one wide orange bar. They are separated by the
   PRIMARY moving instead: the Explore action carries .btn-sage, which is now
   an OUTLINE rather than a fill, and the secondary keeps the site's orange.
   Same split on the solutions page and the four product heroes.
   See "SAGE PRODUCT PAGES — GREEN LEAD" below. */

.social-btn:hover,
.footer .social-btn:hover,
.job-item:hover .job-arrow {
  background: var(--osh-blue);
  border-color: var(--osh-blue);
  color: #fff;
}

::selection { background: var(--accent); color: var(--osh-whale); }

/* Orange badge fills carry Blue Whale numerals — the logo's own pairing. */
.service-num,
.value-num,
.prod-feature-num {
  color: var(--osh-whale);
}

/* ── 4. Focus ring — visible on both grounds ─────────────────────────── */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  /* Cyan reads on the dark grounds, the Blue Whale skirt reads on white,
     so one ring covers both. */
  outline: 3px solid var(--osh-cyan);
  outline-offset: 2px;
  box-shadow: 0 0 0 6px rgba(3,43,68,0.75);
  border-radius: 4px;
}

/* ── 5. CI patterns ──────────────────────────────────────────────────────
   The guideline's pattern (CI p.10-11) is a grid of tiles built from one
   grammar: rectangles spanning 1-4 cells, each with some corners rounded
   to a full half-cell radius, plus the odd circle. Redrawn as SVG in
   assets/images/ci/patterns/ so it stays crisp at billboard scale and
   costs ~4KB instead of the 900KB source PNGs.

   Used the way the guideline uses it: LARGE, running off the edge of the
   canvas, in the margins where no content sits — never as a small tile
   scattered behind text.

   .pattern-band — coloured vertical band down one side (p.11 posters) */
/* Corner pattern — a real <div><img></div>, not a pseudo-element, so the
   markup owns it and it can be dropped into any section.

   The wrapper is the window; the image inside is much larger than that
   window and pinned to its bottom-right, so only the image's bottom-right
   corner shows and the rest is clipped off the top and left of the page.

   Everything is sized in px, not vh: the pattern is locked to the section
   and never rescales or shifts when the viewport height changes. Nothing
   animates or parallaxes it. */
.ci-pattern {
  position: absolute;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.ci-pattern img {
  position: absolute;
  display: block;
  max-width: none;   /* beats the global img { max-width: 100% } */
}

.ci-pattern--corner {
  top: 0;
  left: 0;
  /* proportional to the section, clamped so it never becomes a thin strip on
     a short viewport or a wall on a tall one. The image size stays fixed, so
     the tiles are the same physical size at every breakpoint. */
  height: clamp(360px, 72%, 820px);
  /* right edge lands on the horizontal midpoint of the 1.1fr image column */
  width: calc(26.19vw - 6px);
}
.ci-pattern--corner img {
  right: 0;
  bottom: 0;
  width: 1400px;
  height: 1400px;
}
@media (min-width: 1240px) {
  /* container is capped at 1240px and centred from here up */
  .ci-pattern--corner { width: calc(50% - 301px); }
}
@media (max-width: 1050px) {
  /* the grid has stacked, so there is no column to hide behind — the pattern
     runs under the photo instead and reads around its corners. At 1.1:1
     against white it never competes with anything set over it. */
  .ci-pattern--corner {
    width: 78%;
    /* tall enough to clear the stacked photo and carry on behind the text,
       otherwise only the 32px gutter beside the photo would ever show */
    height: clamp(620px, 70%, 1200px);
  }
  .ci-pattern--corner img { width: 1050px; height: 1050px; }
}
@media (max-width: 700px) {
  .ci-pattern--corner {
    width: 84%;
    height: clamp(520px, 64%, 980px);
  }
  .ci-pattern--corner img { width: 820px; height: 820px; }
}

/* Coloured variant — same corner placement, Coloured Pattern 1 tiles.
   Carries the treatment across the inner pages. Those pages have no photo
   column to hide behind, so this one stays in the page margin and only
   kisses the edge of the content rather than running under a text block.
   Declared after the corner media queries so it wins at equal specificity. */
.ci-pattern--corner-colour img { opacity: 0.16; }
.ci-pattern--corner-colour { width: calc((100% - 1240px) / 2 + 90px); }
@media (max-width: 1240px) { .ci-pattern--corner-colour { width: 132px; } }
@media (max-width: 1050px) { .ci-pattern--corner-colour { height: clamp(300px, 40%, 520px); } }
@media (max-width: 700px)  { .ci-pattern--corner-colour { width: 96px; height: clamp(240px, 32%, 400px); } }

/* Host section: lift the content above the pattern. */
.pattern-corner { position: relative; }
.pattern-corner > .container { position: relative; z-index: 1; }

/* =========================================
   SECTION PATTERN SYSTEM
   -----------------------------------------
   Plug-in artwork: one <div class="sec-pattern sec-pattern--tl"> holding
   the pattern PNG, dropped as the first child of any .has-pattern section.

   Unlike .ci-pattern above, nothing is clipped — the artwork is already
   cropped to its corner in the file, so the WHOLE image is shown, anchored
   to that corner of the section and sitting behind all content.

   Two knobs, both per-instance so a section can be tuned from its own rule
   without new classes:
     --pattern-w  rendered width  (default: fluid, capped at 560px)
     --pattern-o  opacity         (default: 0.6)

   Colour comes from the file itself (…-grey.png, …-blue.png, …), position
   from the modifier — so any pattern can be paired with any corner.
   ========================================= */
.has-pattern { position: relative; overflow: hidden; }
.has-pattern > .container { position: relative; z-index: 1; }

.sec-pattern {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  width: var(--pattern-w, clamp(300px, 36vw, 620px));
  opacity: var(--pattern-o, 0.6);
}
.sec-pattern img {
  display: block;
  width: 100%;
  height: auto;
  max-width: 500px;   /* beats the global img { max-width: 100% } */
}

.sec-pattern--tl { top: 0;    left: 0;  }
.sec-pattern--tr { top: 0;    right: 0; }
.sec-pattern--bl { bottom: 0; left: 0;  }
.sec-pattern--br { bottom: 0; right: 0; }

/* The artwork is cropped flush on its TOP and LEFT edges — the shapes run
   straight off both — while its right and bottom edges are where the
   composition finishes. That only bleeds correctly out of a top-left corner.
   Anchored anywhere else the cut edges face inward and the finished edge sits
   against the viewport, so the other three corners mirror it into place. */
.sec-pattern--tr img { transform: scaleX(-1); }
.sec-pattern--bl img { transform: scaleY(-1); }
.sec-pattern--br img { transform: scale(-1, -1); }

@media (max-width: 700px) {
  .sec-pattern { width: var(--pattern-w-sm, clamp(200px, 56vw, 320px)); }
}

/* Coloured band — full-bleed strip of brand tiles down one edge. */
.pattern-band {
  position: relative;
  overflow: hidden;
}
.pattern-band > * { position: relative; z-index: 1; }
.pattern-band::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: clamp(90px, 12vw, 220px);
  background: url('../images/ci/patterns/pattern-coloured.svg') right center / auto 112% no-repeat;
  pointer-events: none;
  z-index: 0;
}
@media (max-width: 900px) {
  .pattern-band::before { display: none; }
}

/* ── 6. Blocks lifted out of inline styles ───────────────────────────────
   These carried the old display face inline, so they are classed here to
   keep every headline and figure on the CI type ramp. */
.mv-title {
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text);
  margin: 12px 0 16px;
  line-height: 1.25;
}
.stat-inline {
  font-family: var(--font-display);
  font-size: 2.5rem;
  font-weight: 700;
  letter-spacing: 0.012em;
  line-height: 1;
  color: var(--osh-blue);
}



/* ── 7. Logo at small widths ─────────────────────────────────────────────
   The lockup is wide (3.9:1); trim it on phones so the burger keeps room. */
@media (max-width: 700px) {
  .sh-logo-img { height: 30px; }
}
@media (max-width: 380px) {
  .sh-logo-img { height: 26px; }
}

/* ── 8. Section grounds ──────────────────────────────────────────────────
   "What we do" sits back on the neutral tint, ready for Coloured Pattern 1
   at full strength (CI p.11, middle poster). Card overlays are warmed to
   Blue Whale so they stay on the CI ink whatever the ground. */
.svc-section .svc-card-overlay { background: linear-gradient(to top, rgba(3,43,68,0.88) 30%, rgba(3,43,68,0.05) 70%); }
.svc-section .section-headline { color: #fff; }

/* Egyptian Blue stat cards in "Who We Are" — white type throughout (7.7:1). */
.about-stat .num,
.about-stat .num span,
.about-stat .lbl { color: #fff; }



/* =========================================================================
   HOW IT WORKS — three-step process
   Each step is a solid CI tile in the pattern's own silhouette (opposing
   corners rounded to a big radius), running Cyan → Egyptian Blue → Carrot
   Curl so the colour order carries the sequence. Ink flips per card so
   every step clears WCAG AA on its own ground: Blue Whale on cyan (6.1:1)
   and on orange (6.0:1), white on Egyptian Blue (7.7:1).
   ========================================================================= */
.hiw-steps {
  position: relative;
  list-style: none;
  margin: 56px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 72px;
}

/* Chevron nodes, dead centre of each gap. They live on the grid rather
   than inside a tile, because the tiles clip their own overflow. */
.hiw-link {
  position: absolute;
  top: 50%;
  z-index: 2;
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--osh-whale);
  color: #fff;
  transform: translate(-50%, -50%);
  box-shadow: 0 6px 18px rgba(3,43,68,0.28);
}
.hiw-link svg { width: 15px; height: 15px; }
/* gap centres for 3 equal columns with a 72px gutter */
.hiw-link--1 { left: calc(33.3333% - 12px); }
.hiw-link--2 { left: calc(66.6667% + 12px); }

.hiw-step {
  position: relative;
  z-index: 1;
  overflow: hidden;
  padding: 34px 32px 36px;
  border-radius: 18px 68px 18px 68px;
  display: flex;
  flex-direction: column;
  transition: transform 0.45s var(--ease), box-shadow 0.45s var(--ease);
}
.hiw-step:hover { transform: translateY(-6px); }

.hiw-step--listen  { background: var(--osh-cyan);   color: #fff; box-shadow: 0 14px 40px rgba(33,177,245,0.30); }
.hiw-step--build   { background: var(--osh-blue);   color: #fff; box-shadow: 0 14px 40px rgba(11,83,155,0.32); }
/* Card 3 carries WHITE ink and white glyphs on Carrot Curl — client
   direction, 21 Aug 2026 — so all three tiles now read the same way. The
   point icons inherit currentColor, so they follow it with no extra rule. */
.hiw-step--deliver { background: var(--osh-orange); color: #fff; box-shadow: 0 14px 40px rgba(245,139,34,0.32); }
.hiw-step--listen:hover  { box-shadow: 0 26px 60px rgba(33,177,245,0.42); }
.hiw-step--build:hover   { box-shadow: 0 26px 60px rgba(11,83,155,0.45); }
.hiw-step--deliver:hover { box-shadow: 0 26px 60px rgba(245,139,34,0.45); }

/* Header — the numeral is the step marker, so it is set big and solid. */
.hiw-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 18px;
  padding-bottom: 18px;
  border-bottom: 1px solid currentColor;
}
.hiw-num {
  font-family: var(--font-display);
  font-size: 4.25rem;
  font-weight: 700;
  line-height: 0.78;
  letter-spacing: 0.012em;
}
.hiw-tag {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.75;
}

.hiw-title {
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.15;
  margin-bottom: 10px;
}
.hiw-desc {
  font-size: 0.9375rem;
  line-height: 1.6;
  margin-bottom: 24px;
}
.hiw-points {
  list-style: none;
  margin: auto 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.hiw-points li {
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.45;
  display: flex;
  align-items: flex-start;
  gap: 11px;
}
/* Icon chip replaces the old square bullet. The tile itself is a solid brand
   colour, so the chip is a translucent white wash rather than a second hue —
   that keeps one marker treatment working on cyan, blue and orange alike, and
   the glyph inherits currentColor so it can never drift out of contrast. */
.hiw-point-icon {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  margin-top: -3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* echoes the tile's one-square-one-round corner motif, scaled down */
  border-radius: 10px 3px 10px 3px;
  background: rgba(255,255,255,0.16);
  border: 1px solid rgba(255,255,255,0.3);
}
.hiw-point-icon svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (max-width: 1050px) {
  .hiw-steps { grid-template-columns: 1fr; gap: 28px; }
  .hiw-step { border-radius: 16px 56px 16px 56px; }
  /* stacked tiles are uneven heights, so a percentage-placed node cannot sit
     reliably in a gap — the numerals carry the sequence on their own */
  .hiw-link { display: none; }
}
@media (max-width: 700px) {
  .hiw-steps { margin-top: 40px; }
  .hiw-step { padding: 30px 26px 32px; }
  .hiw-num { font-size: 3.5rem; }
}

/* =========================================
   CONTACT PAGE
   -----------------------------------------
   Rebuilt to the home page's vocabulary. It used to be two floating panels —
   a Blue Whale info card and a white form card — on a flat tinted ground, with
   the map alone on a third. Nothing else on the site is built that way: every
   other page is full-width BANDS, each opening on the eyebrow → headline →
   supporting-line rhythm. So the page is two bands now:

     1. an Egyptian Blue band carrying the CI blue artwork, with the four
        channels as frosted chips — the same construction as "What We Do" and
        the home public-sector chips;
     2. a white band with the grey corner pattern, the same one under home's
        "Who We Are", holding the form beside the map.

   Lives at the end of the sheet on purpose: the CI-conformance block above
   re-colours .social-btn globally, and the dark band here needs the opposite.
   ========================================= */

/* ── 1. Channels band ────────────────────────────────────────────────────
   .svc-section's ground, verbatim: the artwork's field is (11,83,153) against
   --osh-blue's, so the token carries the rest of the width with no seam. */
.section-alt.contact-band {
  background: var(--osh-blue)
              url('../images/ci/patterns/blue-background-pattern.png')
              right top / auto 100% no-repeat;
}
/* .section-alt flips the pill to Egyptian Blue, which is this band's own
   ground — it would vanish. Cyan is the contrasting one here, as on the other
   two Egyptian Blue bands. */
.contact-band :is(.tag, .eyebrow) {
  background: var(--osh-cyan);
  color: #fff;
}
.contact-band .section-head { max-width: 640px; }

.contact-channels {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
}
/* Frosted, like .ps-chip. Blur alone does nothing on a flat brand ground —
   the frost only reads once the white tint comes up with it. The tint is 0.12
   rather than the chips' 0.16 because this row runs the full container width
   and its right-hand cards cross the pattern band: over the artwork's lightest
   shape, a 0.16 tint drops white to 4.3:1. At 0.12 the worst case is 4.6:1 and
   the plain field 5.8:1. */
.contact-channel {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  padding: 26px 22px 24px;
  border-radius: var(--radius-lg);
  background: linear-gradient(160deg, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0.05) 100%);
  border: 1px solid rgba(255,255,255,0.20);
  backdrop-filter: blur(26px) saturate(120%);
  -webkit-backdrop-filter: blur(26px) saturate(120%);
  overflow: hidden;
  transition: transform 0.45s var(--ease), border-color 0.45s var(--ease),
              background 0.45s var(--ease), box-shadow 0.45s var(--ease);
}
.contact-channel::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 3px;
  background: linear-gradient(90deg, var(--osh-orange), transparent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.55s var(--ease);
}
.contact-channel:hover {
  transform: translateY(-4px);
  border-color: rgba(245,139,34,0.55);
  background: linear-gradient(160deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.08) 100%);
  box-shadow: 0 18px 40px rgba(2,20,34,0.30);
}
.contact-channel:hover::after { transform: scaleX(1); }

.cc-icon {
  width: 52px; height: 52px;
  border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--osh-orange);
  color: var(--osh-whale);          /* Blue Whale on Carrot Curl — §2 */
  box-shadow: 0 8px 22px rgba(245,139,34,0.30);
  transition: transform 0.45s var(--ease);
}
.cc-icon svg { width: 24px; height: 24px; }
.contact-channel:hover .cc-icon { transform: rotate(-6deg) scale(1.06); }

.cc-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #fff;
}
/* Solid white, not a muted tint: the frost lightens the ground, so the 0.7-ish
   value the old panel used would land under AA here. */
.cc-value {
  font-size: 0.9375rem;
  line-height: 1.5;
  color: #fff;
}
/* overflow-wrap, not the default — these addresses are long enough to push a
   card past the phone viewport otherwise */
.cc-value a {
  color: inherit;
  text-decoration: none;
  overflow-wrap: anywhere;
  border-bottom: 1px solid rgba(255,255,255,0.35);
  padding-bottom: 1px;
}
.cc-value a:hover { border-bottom-color: #fff; }

.contact-follow {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-top: 44px;
  padding-top: 30px;
  border-top: 1px solid rgba(255,255,255,0.18);
}
.contact-follow-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.72);
}
.contact-follow-row { display: flex; gap: 10px; }
.contact-band .social-btn {
  border-color: rgba(255,255,255,0.22);
  color: #fff;
}
.contact-band .social-btn:hover {
  background: var(--osh-orange);
  border-color: var(--osh-orange);
  color: var(--osh-whale);
}

/* ── 2. Form + map band ──────────────────────────────────────────────────
   DESIGN-SYSTEM.md §6: the text column sets the height and the media fills
   it. The form is nine fields tall, so the map is taken out of flow and
   stretched to whatever height the form ends up at rather than being given a
   ratio of its own. The single-column breakpoint restores a ratio, because
   there is no sibling left to inherit a height from. */
/* Smaller and lighter than home's instance. There the artwork sits behind a
   photograph and a block of copy; here it runs behind input boxes, where the
   default size and 0.6 opacity read as noise through the fields. */
.contact-form-section {
  --pattern-w: clamp(200px, 22vw, 380px);
  --pattern-o: 0.45;
}

.contact-split {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: 64px;
  align-items: stretch;
}
.contact-form-col .eyebrow { display: inline-flex; margin-bottom: 16px; }
.contact-form-col .section-headline { margin-bottom: 18px; }
.contact-form-col .section-sub { margin-bottom: 36px; }
.contact-form-col form { position: relative; z-index: 1; }

.contact-map-col { position: relative; }
.contact-map-frame {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border2);
  box-shadow: var(--shadow-lg);
}
.contact-map-frame iframe {
  width: 100%; height: 100%;
  border: 0;
  /* No filter. This used to fake a dark map with
     invert(0.9) hue-rotate(195deg) saturate(0.3) brightness(0.85), which also
     inverted Google's own labels, pins and route colours — street names came
     out as light text on a muddy ground and the branding was wrong. The band
     it sits in is light, so the map's native light style is the one that
     belongs here. */
}

/* Paired fields sit in a .form-row that already carries the bottom gap. */
.form-group--flush { margin-bottom: 0; }

/* ── Page hero ── */
.page-hero--contact { padding-bottom: 60px; }
.page-hero--contact .section-sub { text-align: center; margin: 0 auto; }

@media (max-width: 1050px) {
  .contact-channels { grid-template-columns: repeat(2, 1fr); }
  .contact-split { grid-template-columns: 1fr; gap: 44px; }
  /* no sibling to take a height from any more */
  .contact-map-col { min-height: 0; }
  .contact-map-frame { position: relative; inset: auto; aspect-ratio: 16 / 9; }
}
@media (max-width: 700px) {
  .contact-channels { grid-template-columns: 1fr; gap: 14px; }
  .contact-follow { flex-direction: column; align-items: flex-start; gap: 14px; }
  .contact-map-frame { aspect-ratio: 4 / 3; }
}

/* =========================================
   HOME CTA — PHOTO GROUND + FIGURE OVER THE EDGE
   -----------------------------------------
   Opt-in variant (.cta-band--figure) so the other eight .cta-band instances
   keep the flat ink panel. Lives at the end of the sheet because the band has
   to undo two things the base component does — overflow:hidden and the
   1050px padding shorthand — and losing to those in the cascade would clip the
   head off or let the copy run under the figure.

   The band itself can no longer clip, so .cta-bg takes over the rounded-corner
   masking and carries both the photo and the blue glow.
   ========================================= */
.cta-band.cta-band--figure {
  overflow: visible;
  min-height: 300px;
  /* The action drops to its own row, left-aligned under the headline and
     body copy — client direction, 10 Sept 2026 (this band used to centre the
     row in the lane instead; centred read as the button floating loose in
     the middle of the panel rather than lining up with the text above it).
     In the base component the action shares a row with the copy as an
     `auto` column, which pinned it hard against the lane's right edge —
     beside the headline rather than under it. One column gives it a row of
     its own. */
  grid-template-columns: 1fr;
  justify-items: start;
  /* The lane is sized for the WIDEST cut-out in the set (395px at the 420px
     display height), not for a typical one — the poses vary from a narrow
     head-and-shoulders to arms-folded-wide, and a lane cut to the average lets
     the copy slide under the broad ones. Narrower figures simply leave more
     air, which reads as breathing room rather than as a mistake. */
  padding-right: 470px;
  background: var(--ink);
}
/* Mirrored variant. The bands alternate down the site so the eye is not led to
   the same corner on every page; only the reserved lane and the figure's
   anchor move, the copy order stays as authored. */
.cta-band.cta-band--figure.cta-band--figure-left {
  padding-right: 52px;
  padding-left: 470px;
}
.cta-band--figure > .btn,
.cta-band--figure > .cta-band-action {
  justify-self: start;
  margin-top: 6px;
}
.cta-band--figure > .cta-band-action { justify-content: flex-start; }

/* the base glow assumes a clipping parent — .cta-bg::after replaces it */
.cta-band.cta-band--figure::before { display: none; }

.cta-band--figure > .cta-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  border-radius: inherit;
}
.cta-bg picture {
  position: absolute;
  inset: 0;
  display: block;
}
.cta-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 40%;
}
/* Ink wash over the photo. Weighted to the left so the headline and body copy
   sit on near-solid Blue Whale (white stays >12:1 there) while the right-hand
   side keeps enough of the building to read as a photograph. */
.cta-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(100deg,
      rgba(3,43,68,0.95) 0%,
      rgba(3,43,68,0.92) 42%,
      rgba(3,43,68,0.62) 100%),
    radial-gradient(circle at 74% 15%, rgba(11,83,155,0.5), transparent 62%);
}

/* The figure is taller than the band, anchored to the bottom edge — so the
   waistline crop lands exactly on the band's lower border and the head clears
   the top. Height is fixed rather than a % of the band so the reserved
   padding-right above stays correct even when the copy wraps to another line. */
.cta-band--figure > .cta-figure {
  position: absolute;
  right: 48px;
  bottom: 0;
  z-index: 2;
  display: block;
  height: 420px;
  width: auto;
  pointer-events: none; /* must never intercept a click meant for the button */
}
.cta-band--figure-left > .cta-figure {
  right: auto;
  left: 48px;
}
/* No drop-shadow on the figure: the head sits on the white page above the band,
   where any shadow reads as a grey halo around the hair rather than depth. */
.cta-figure img {
  display: block;
  height: 100%;
  width: auto;
}

.cta-section { padding-top: 130px; }
/* about/careers/public-sector/solutions drop the band straight into the section
   with no .container, so it has no gutter of its own and touches the viewport
   edge below 1240px. The base rule's `margin: 0 auto` still centres it. */
.cta-section > .cta-band { width: calc(100% - 48px); }

@media (max-width: 1050px) {
  .cta-band.cta-band--figure {
    min-height: 260px;
    padding: 48px 366px 48px 36px;
  }
  .cta-band.cta-band--figure.cta-band--figure-left {
    padding: 48px 36px 48px 366px;
  }
  .cta-band--figure > .cta-figure { right: 24px; height: 340px; }
  .cta-band--figure-left > .cta-figure { right: auto; left: 24px; }
}

/* Phones: there is no room for a side-by-side figure, so it moves to the top
   centre and the copy runs beneath it. The waist crop is no longer covered by
   the band's edge here, so it is faded out instead of cut. */
@media (max-width: 700px) {
  .cta-section { padding-top: 132px; }
  .cta-band.cta-band--figure,
  .cta-band.cta-band--figure.cta-band--figure-left {
    min-height: 0;
    padding: 118px 28px 44px;
    text-align: center;
    justify-items: center;
  }
  .cta-band--figure p { margin-inline: auto; }
  /* Desktop left-aligns the button row under the copy; phones centre the
     copy itself (text-align + justify-items above), so the row has to be
     recentred here too or it would stay pinned left while everything above
     it moved to the middle. */
  .cta-band--figure > .btn,
  .cta-band--figure > .cta-band-action { justify-self: center; }
  .cta-band--figure .cta-band-action { justify-content: center; }
  .cta-band--figure > .cta-figure,
  .cta-band--figure-left > .cta-figure {
    right: auto;
    left: 50%;
    bottom: auto;
    top: -104px;
    transform: translateX(-50%);
    height: 248px;
    -webkit-mask-image: linear-gradient(to bottom, #000 68%, transparent 97%);
    mask-image: linear-gradient(to bottom, #000 68%, transparent 97%);
  }
}

/* =========================================
   CI ACCENT ROTATION
   -----------------------------------------
   The CI has four colours but the build only ever reached for Carrot Curl, so
   page after page came out white-and-Blue-Whale with one orange button. This
   rotates Cyan → Egyptian Blue → Carrot Curl across the cards of a grid,
   driven entirely by :nth-child so no card needs its own class and the order
   stays correct when a card is added or removed.

   Five tokens per card:
     --card-accent   saturated colour — chips, rules, numerals
     --card-accent-2 lighter partner for the chip gradients
     --card-tint     pale wash behind a light card
     --card-deep     dark companion for the dark card gradients
     --card-ink      what is legible ON --card-accent

   --card-ink is the part that has to be right: white clears AA on Egyptian
   Blue (7.7:1) but fails badly on Cyan (2.2:1) and Carrot Curl (2.4:1), so
   those two flip to Blue Whale (6.1:1 and 6.0:1). Body copy on the pale tints
   stays Blue Whale throughout, which is >15:1 on all three.

   Home is deliberately excluded — none of these grids appear on it.
   ========================================= */
:is(.perks-grid, .ps-products, .press-grid, .values-grid,
    .jobs-list, .team-grid, .prod-feature-grid, .services-grid) > * {
  --card-accent:   var(--osh-blue);
  --card-accent-2: #2F7CC9;
  --card-tint:     var(--osh-blue-pale);
  --card-deep:     #0A4479;
  --card-ink:      #fff;
}
:is(.perks-grid, .ps-products, .press-grid, .values-grid,
    .jobs-list, .team-grid, .prod-feature-grid, .services-grid) > *:nth-child(3n+1) {
  --card-accent:   var(--osh-cyan);
  --card-accent-2: #6FCEFA;
  --card-tint:     #E7F6FE;
  --card-deep:     #0A5C86;
  --card-ink:      var(--osh-whale);
}
:is(.perks-grid, .ps-products, .press-grid, .values-grid,
    .jobs-list, .team-grid, .prod-feature-grid, .services-grid) > *:nth-child(3n+3) {
  --card-accent:   var(--osh-orange);
  --card-accent-2: #F9B061;
  --card-tint:     var(--osh-orange-pale);
  --card-deep:     #8A4A10;
  --card-ink:      var(--osh-whale);
}

/* ── Careers: perk cards ── */
/* The tint fill is gone — these carry a photograph now (see .perk-bg above).
   The rotation still supplies the RULE and the icon chip, so the three
   families still read across the grid. */
.perk-card {
  border-color: var(--card-accent);
}
.perk-card:hover {
  border-color: var(--card-accent);
  box-shadow: 0 20px 48px rgba(3,43,68,0.28);
}
.perk-icon {
  background: var(--card-accent);
  color: #fff;
}
/* All three chips take a white glyph. --card-ink is Blue Whale on Cyan and on
   Carrot Curl because it is sized for TYPE; these are 22px line icons on a
   dark card, where white is the stronger read on all three grounds. */

/* ── Careers: job rows ── */
.job-item { border-left: 4px solid var(--card-accent); }
.job-item:hover {
  background: var(--card-tint);
  border-color: var(--card-accent);
}

/* ── Careers: press cards ── */
.press-card { border-color: var(--card-accent); }
.press-card .press-card-img { border-bottom: 4px solid var(--card-accent); }

/* ── Public sector: product cards ──
   These three opt out of the rotation above: all three bodies are Egyptian
   Blue with white type, so the set reads as one product family rather than
   three unrelated cards. The tag is what distinguishes them, and it takes a
   different CI colour per card — none of them Egyptian Blue, which would
   vanish into the card. Each tag keeps Blue Whale ink (6.0–6.1:1). */
.ps-card { border-color: var(--osh-blue); }
.ps-card .ps-card-body { background: var(--osh-blue); }
.ps-card .ps-card-title { color: #fff; }
.ps-card .ps-card-desc { color: rgba(255,255,255,0.82); }
.ps-card .solution-features-list li { color: rgba(255,255,255,0.88); }

/* Blue Whale here, not the component's white — these two pills are Carrot Curl
   and Grey. The Cyan one below keeps white, in line with the rest of the site. */
.ps-card .tag {
  color: var(--osh-whale);
  box-shadow: none;
}
.ps-products > .ps-card:nth-child(1) .tag { background: var(--osh-cyan); color: #fff; }
.ps-products > .ps-card:nth-child(2) .tag { background: var(--osh-orange); }
.ps-products > .ps-card:nth-child(3) .tag { background: var(--osh-grey); }
/* the dot is Carrot Curl by default, which vanishes on the Carrot Curl pill */
.ps-products > .ps-card:nth-child(2) .tag::before {
  background: var(--osh-whale);
  box-shadow: 0 0 0 3px rgba(3,43,68,0.20);
}

/* ── About: value cards ── */
.value-card { background: linear-gradient(155deg, var(--card-deep) 0%, var(--osh-whale) 62%, #021D2F 100%); }
.value-num {
  background: linear-gradient(135deg, var(--card-accent) 0%, var(--card-accent-2) 100%);
  color: var(--card-ink);
  box-shadow:
    0 12px 30px rgba(3,43,68,0.35),
    0 0 0 4px rgba(255,255,255,0.95);
}

/* ── About: team cards ── */
.team-card { border-top: 4px solid var(--card-accent); }

/* ── Sage pages: feature card icons ── */
.prod-feature-icon {
  background: linear-gradient(135deg, var(--card-accent) 0%, var(--card-accent-2) 100%);
  color: var(--card-ink);
  box-shadow:
    0 12px 30px rgba(3,43,68,0.35),
    0 0 0 4px rgba(255,255,255,0.95);
}

/* ── Solutions: service cards ── */
.services-grid > * .service-icon {
  background: var(--card-accent);
  border-color: var(--card-accent);
  color: var(--card-ink);
}

/* ── Anything sitting directly on a Cyan fill is white ──────────────
   --card-ink is Blue Whale on the Cyan cards because it was chosen for body
   copy. These are numerals and icons, where the brief is white on Cyan — so
   the cyan third of the rotation is overridden here rather than by changing
   the token, which body copy elsewhere still depends on. */
:is(.values-grid, .prod-feature-grid, .services-grid) > *:nth-child(3n+1)
  :is(.value-num, .prod-feature-icon, .service-icon) { color: #fff; }

/* ── Readable accent ink ─────────────────────────────────────────────
   --blue is a legacy alias that now resolves to Carrot Curl, so a run of
   small labels ended up orange on white at 2.4:1 — well under AA. The
   stylesheet already declares the right token for this (--accent-ink,
   Egyptian Blue, 7.7:1 on white); these rules just were not using it.
   Fixing them is also what brings Egyptian Blue back into the pages.

   .eyebrow used to be listed here too. It is now a filled pill rather than
   loose text, so it sets its own ink — see the section tag component.
   ──────────────────────────────────────────────────────────────────── */
.team-name,
.press-date,
.solution-badge { color: var(--accent-ink); }

.job-arrow { color: var(--accent-ink); }
.solution-features-list svg,
.prod-bullets svg { color: var(--accent-ink); }

/* =========================================
   HOME — "WHO WE ARE" EMPHASIS + "WHAT WE DO" GROUND
   -----------------------------------------
   The stat tiles were effectively unreadable: the numeral inherited
   `color: var(--blue)`, which is the legacy alias that now resolves to Carrot
   Curl, putting orange on Cyan at about 1.4:1; the label under it was white on
   Cyan at 2.4:1. The tiles now sit on Egyptian Blue in white (7.7:1), and the
   numeral moves to Bebas Neue — the display face the rest of the site uses for
   figures — at a size that matches how important these numbers actually are.
   ========================================= */
.about-text .section-headline {
  font-size: clamp(2.5rem, 4.6vw, 3.75rem);
  line-height: 1.02;
  letter-spacing: -0.03em;
}
.about-text .section-sub { font-size: 1.125rem; margin-top: 20px; }

.about-stats { gap: 16px; margin: 38px 0; }
.about-stat { padding: 22px 22px 20px; }
.about-stat .num,
.about-stat .num span {
  font-family: var(--font-display);
  font-size: 3.25rem;
  font-weight: 700;
  line-height: 0.86;
  letter-spacing: 0.012em;
  color: #fff;
}
.about-stat .lbl {
  font-family: var(--font-heading);
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.88);
  margin-top: 10px;
}

/* "What We Do" moves from Cyan to Egyptian Blue. The section head was set in
   Blue Whale and Carrot Curl, which read on Cyan but not on Egyptian Blue, so
   both flip to white (7.7:1). Only the home band is affected — the other
   .section-alt sections keep Cyan.

   The ground is the CI blue pattern. Its own field is (11,83,153), two points
   off --osh-blue, so the artwork is anchored right at full section height and
   the token carries the rest of the width with no seam where they meet. */
.section-alt.svc-section {
  background: var(--osh-blue)
              url('../images/ci/patterns/blue-background-pattern.png')
              right top / auto 100% no-repeat;
}

/* Solutions — "Cloud & Beyond".
   The full-colour CI pattern runs as a band down the right and the copy keeps
   to a column on the left, so the two never fight for the same space. The band
   is a pseudo-element rather than a background-size: a height-driven
   `auto 100%` grows wider as the section grows taller, and once the cards
   stack the artwork would have crept across the text. This width is fixed to
   the viewport instead. Ground moves Cyan → Egyptian Blue, so the head and the
   tag move with it. */
.section-alt.cloud-section {
  position: relative;
  overflow: hidden;
  background: var(--osh-blue);
}
.cloud-section > .container { position: relative; z-index: 1; }
.cloud-section::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0; right: 0;
  width: clamp(240px, 38vw, 600px);
  background: url('../images/ci/patterns/background-pattern.png')
              right center / cover no-repeat;
  z-index: 0;
  pointer-events: none;
}
.cloud-content { max-width: 620px; }
.cloud-section .section-headline { color: #fff; }
.cloud-section :is(.tag, .eyebrow) {
  background: var(--osh-cyan);
  color: #fff;
}

/* One card per row, laid out as a row: numeral in its own column beside the
   copy rather than the badge hanging off the corner, which only works when
   the cards sit side by side. */
.cloud-section .services-grid {
  grid-template-columns: 1fr;
  gap: 16px;
  margin-top: 40px;
}
/* Frosted glass, not the dark gradient the component ships with — the same
   treatment as the public-sector chips on the home page. On an Egyptian Blue
   band a near-black card reads as a hole punched in the section; frost lets
   the ground and the CI pattern behind it carry through.

   As with .ps-chip, blur alone barely registers on a flat ground — the frost
   only appears once a white tint comes up with it. The tint is 0.12 rather
   than the chips' 0.16 because these cards sit OVER the pattern band, whose
   lightest shapes plus a 0.16 tint drop white type to 4.3:1. At 0.12 the worst
   case is 4.6:1 and the plain ground 5.8:1. */
.cloud-section .service-card {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 22px;
  padding: 26px 30px;
  background: linear-gradient(160deg, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0.05) 100%);
  border-color: rgba(255,255,255,0.20);
  backdrop-filter: blur(26px) saturate(120%);
  -webkit-backdrop-filter: blur(26px) saturate(120%);
  box-shadow: 0 10px 40px rgba(2,20,34,0.18);
}
.cloud-section .service-card:hover {
  background: linear-gradient(160deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.08) 100%);
  border-color: rgba(245,139,34,0.55);
  box-shadow: 0 18px 44px rgba(2,20,34,0.30);
}
.cloud-section .service-num {
  position: static;
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: start;
  font-size: 1.75rem;
  padding: 12px 16px;
  border-radius: 14px;
}
.cloud-section .service-title {
  grid-column: 2;
  grid-row: 1;
  font-size: 1.1875rem;
  margin-bottom: 10px;
}
/* The component sets the description at 72% white, which was written for a
   near-black card. Over the frost it composites to 3.2:1 — under AA — and even
   0.90 only reaches 4.1:1 where a card crosses the pattern band's lightest
   shape. So it goes to solid white (4.6:1 worst case, 5.8:1 on the plain
   band) and the hierarchy is carried by size and weight instead of by opacity,
   which it largely was already: 19px/600 title against 15px/400 body. */
.cloud-section .service-desc {
  grid-column: 2;
  grid-row: 2;
  margin: 0;
  color: #fff;
}

@media (max-width: 1000px) {
  /* no gutter left to hold the band — copy takes the full width */
  .cloud-section::after { display: none; }
  .cloud-content { max-width: none; }
}
.svc-section .section-headline { color: #fff; }

/* =========================================================================
   SAGE PRODUCT LOCKUPS  +  WHITE-ON-CYAN  +  SAGE GREEN
   See DESIGN-SYSTEM.md §2, §5.
   ========================================================================= */

/* ── Lockup sizing ───────────────────────────────────────────────────────
   The lockups replace typed titles, so they are sized by HEIGHT to match the
   type they stand in for. Both caps are max-* with width and height left auto:
   pairing a fixed height with max-width would squash the image, because the
   browser honours the fixed dimension and shrinks the other. The four files
   also differ widely in ratio (X3 is 3.3:1, 200 Evolution 7.3:1), so nothing
   here may assume a shape. */
.sage-lockup { display: block; }
.sage-lockup img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
}

.hs-title--logo,
.product-hero-title--logo {
  max-width: none;
  padding-right: 0;
}
.hs-title--logo .sage-lockup img          { max-height: clamp(40px, 5.4vw, 84px); }
.product-hero-title--logo .sage-lockup img { max-height: clamp(40px, 5.2vw, 80px); }
.sage-headline .sage-lockup img            { max-height: clamp(30px, 3.2vw, 44px); }
.sage-plan-title .sage-lockup img          { max-height: 28px; }

/* the heading box is now an image, so its own type metrics no longer set the
   rhythm — restate the spacing the typed titles carried */
.solution-content .sage-headline { margin: 14px 0 0; }

/* ── White on Cyan ───────────────────────────────────────────────────────
   Everything sitting directly on a cyan band is white — not just the tag.
   Scoped to the section head: the cards inside these bands carry their own
   grounds and set their own ink. */
.section-alt .section-headline,
.section-alt .section-sub { color: #fff; }

/* ── Sage green ──────────────────────────────────────────────────────────
   Sage's own green leads on the product pages so they read as Sage rather than
   as more OS Holdings. #04DB04 is 7.8:1 on Blue Whale and free to carry text
   and icons there, but only 1.9:1 on white — so on light grounds it appears as
   solid marks, and anything that has to be READ uses --sage-green-ink
   (#0A8A0A, 4.5:1). */
.product-eyebrow-dot {
  background: var(--sage-green);
  box-shadow: 0 0 0 4px rgba(4,219,4,0.28);
}
.product-eyebrow { border-color: rgba(4,219,4,0.34); }
.phs-icon {
  background: linear-gradient(135deg, rgba(4,219,4,0.26), rgba(4,219,4,0.06));
  color: var(--sage-green);
  border-color: rgba(4,219,4,0.38);
}
.phs-num { color: #fff; }

/* a green rule under each lockup, on both grounds */
.product-hero-title--logo::after,
.sage-headline::after {
  content: '';
  display: block;
  width: 64px;
  height: 4px;
  border-radius: 2px;
  background: var(--sage-green);
  margin-top: 22px;
}
.sage-headline::after { width: 52px; height: 3px; margin-top: 18px; }

/* tick lists on the product pages and the solutions listings */
.prod-bullets svg,
.solution-features-list svg { color: var(--sage-green-ink); }

/* the badge over each solution photo names the product — green ink ties it to
   the lockup below it */
.solution-badge {
  color: var(--sage-green-ink);
  border-color: rgba(4,219,4,0.28);
}

/* ── Two-column rows: the text column sets the height ────────────────────
   DESIGN-SYSTEM.md §6. .solution-media and .prod-split-media were both locked
   to a fixed ratio (4/3 and 4/5) beside a centred text column, which leaves the
   picture visibly shorter or taller than the copy it is paired with. The row
   stretches instead and the image fills whatever height the text needs; the img
   is taken out of flow so it can never push the row back out. The
   single-column breakpoints below restore a ratio, because there is no sibling
   left to inherit a height from. */
.solution-feature,
.prod-split { align-items: stretch; }

.solution-media,
.prod-split-media {
  aspect-ratio: auto;
  min-height: 380px;
}
.solution-media > img,
.prod-split-media > img,
.solution-media > picture,
.prod-split-media > picture {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The <picture> around these is unclassed, so it is display: contents and
   generates no box of its own — the absolute positioning has to live on the
   image itself rather than on the wrapper above. */
.solution-media > picture > img,
.prod-split-media > picture > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (max-width: 1000px) {
  .solution-media  { aspect-ratio: 16 / 10; min-height: 0; }
  .prod-split-media { aspect-ratio: 16 / 10; min-height: 0; max-height: 480px; }
}

/* =========================================================================
   SAGE PRODUCT PAGES — GREEN LEAD
   See DESIGN-SYSTEM.md §2, §5, §10.

   The Sage pages lead green so they read as Sage rather than as more OS
   Holdings, with Egyptian Blue and Carrot Curl coming in as accents. The rule
   that keeps it from shouting is that the green arrives in WEIGHTS, never as
   one flat colour: the grounds alternate deep green → white → pale green so no
   two green bands ever touch, and inside a band the marks step down through
   --sage-green / --sage-green-btn / --sage-green-ink by what they have to do.
   Sage's own #04DB04 stays reserved for small marks on dark, where it is the
   brand and not a surface.
   ========================================================================= */

/* ── 1. Sage action — outline, not a green fill ──────────────────────────
   Client direction, 21 Aug 2026: no filled green buttons anywhere on the
   site. The Sage action becomes an OUTLINE — a transparent ground, so the
   photograph or the card behind it shows through and the Carrot Curl fill
   beside it is unmistakably the primary of the pair.

   Two variants, picked by the ground the button lands on:
     .btn-sage       — white rule, white ink. For the dark photographic
                       heroes: the home slider and the four product heroes.
     .btn-sage--ink  — Egyptian Blue Dark rule and ink, 10.2:1 on white. For
                       the white grounds: the solutions rows and the Sage
                       suite cards. Hover fills, so the press state still
                       reads as a state change rather than a tint.

   These beat the site-wide "every button is Carrot Curl" block with a second
   class rather than !important; the arrow rule needs the type selector too,
   because `.btn .arrow-right` over there is already two classes deep. */
.btn.btn-sage {
  background: transparent;
  border-color: #fff;
  color: #fff;
}
.btn.btn-sage:hover,
.btn.btn-sage:focus-visible {
  background: rgba(255,255,255,0.16);
  border-color: #fff;
  color: #fff;
  transform: translateY(-2px);
  box-shadow: 0 10px 28px rgba(0,0,0,0.30);
}
.btn.btn-sage svg,
.btn.btn-sage .arrow-right { color: #fff; }

/* Light-ground variant. Three classes deep so it also beats the arrow rule
   directly above, which is two. */
.btn.btn-sage.btn-sage--ink {
  background: transparent;
  border-color: var(--osh-blue-dark);
  color: var(--osh-blue-dark);
}
.btn.btn-sage.btn-sage--ink:hover,
.btn.btn-sage.btn-sage--ink:focus-visible {
  background: var(--osh-blue-dark);
  border-color: var(--osh-blue-dark);
  color: #fff;
  transform: translateY(-2px);
  box-shadow: 0 10px 28px rgba(8,63,118,0.30);
}
.btn.btn-sage.btn-sage--ink svg,
.btn.btn-sage.btn-sage--ink .arrow-right { color: currentColor; }

/* solutions.php's closing action, which used to carry an inline style */
.btn.btn-lg { font-size: 1rem; padding: 16px 36px; }

/* ── 2. The pattern band (Capabilities) ──────────────────────────────────
   green-background-pattern.png is the CI blue artwork remapped green: its
   field is exactly --sage-green-deep and its shapes keep the blue original's
   tonal step, so it sits on the token with no seam and reads no louder.

   DESIGN-SYSTEM.md §6 warns that `auto 100%` grows the artwork WIDER as a
   section grows TALLER, and this band stacks six cards — three rows on
   tablet, six on a phone. So the artwork is placed as two fixed-size corner
   plates sized off the VIEWPORT instead, top-right and bottom-left, each
   masked out at its own far edge. Section height then cannot touch the scale,
   and there is no cut edge to seam at any height.

   Ink: white throughout, inherited from .section-alt. 7.9:1 on the field and
   6.0:1 over the lightest shape in the artwork, so the pattern can run under
   copy without dropping it below AA. */
.section-alt.sage-band {
  position: relative;
  overflow: hidden;
  background: var(--sage-green-deep);
  /* sits above the plates, so it adds depth without seaming against them */
  box-shadow: inset 0 -140px 120px -110px rgba(3,43,68,0.45);
}
.sage-band > .container { position: relative; z-index: 1; }
.sage-band::before,
.sage-band::after {
  content: '';
  position: absolute;
  z-index: 0;
  pointer-events: none;
  aspect-ratio: 928 / 1055;
  background-image: url('../images/ci/patterns/green-background-pattern.png');
  background-repeat: no-repeat;
  background-size: cover;
}
.sage-band::before {
  top: 0; right: 0;
  width: clamp(300px, 44vw, 700px);
  background-position: right top;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 100%);
}
.sage-band::after {
  bottom: 0; left: 0;
  width: clamp(200px, 28vw, 440px);
  background-position: left bottom;
  transform: scaleX(-1);
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 46%, transparent 100%);
          mask-image: linear-gradient(to top, #000 0%, #000 46%, transparent 100%);
}
/* ── 2a. Eyebrows ────────────────────────────────────────────────────────
   Every pill on a Sage product page is green, and the green changes with the
   ground it sits on. On white and on the pale tint that is --sage-green-btn
   with white ink (5.5:1). On the DEEP band it cannot be: --sage-green-btn is
   1.4:1 against --sage-green-deep, so the pill would dissolve into the band.
   There it takes Sage's own #04DB04 with Blue Whale ink — 7.5:1 for the type
   and 4.2:1 pill-against-ground. That is §2's "solid mark on a coloured
   ground", not green-as-text, so the bright green is in bounds.

   The CI's Carrot Curl dot does not survive either green — 2.3:1 on the button
   green, and closer still on the bright one — so on both it takes the pill's
   own ink instead.

   .section-alt:not(.svc-section) already claims these at three classes, so
   the deep-band rule has to match that weight to win. */
.product-intro :is(.tag, .eyebrow),
.prod-split-text :is(.tag, .eyebrow),
.sage-suite-section :is(.tag, .eyebrow) {
  background: var(--sage-green-btn);
  color: #fff;
}
/* the CI's Carrot Curl dot is 2.3:1 on this green — too weak to register as a
   mark, so it takes the pill's own ink like the bright-green variant does */
.product-intro :is(.tag, .eyebrow)::before,
.prod-split-text :is(.tag, .eyebrow)::before,
.sage-suite-section :is(.tag, .eyebrow)::before {
  background: #fff;
  box-shadow: 0 0 0 3px rgba(255,255,255,0.28);
}
.section-alt.sage-band :is(.tag, .eyebrow) {
  background: var(--sage-green);
  color: var(--osh-whale);
}
.section-alt.sage-band :is(.tag, .eyebrow)::before {
  background: var(--osh-whale);
  box-shadow: 0 0 0 3px rgba(3,43,68,0.22);
}

/* ── 3. Capability cards: one green, not a rotation ──────────────────────
   The site-wide card rotation is Cyan → Egyptian Blue → Carrot Curl, and this
   grid opts out of all three slots: every icon badge is the SAME green with a
   white glyph (5.5:1), so the six read as one set rather than as three
   families that happen to share a band.

   All three nth-child selectors are restated with one declaration block
   because that is what it takes to outweigh the rotation — it claims each
   slot at three points of specificity, so a single `> *` rule would lose.

   --card-mark goes green with them. It is the hairline under each h3 and the
   hover border, both of which sit on the cards' own DARK photo grounds, where
   the badge green would disappear and Sage's #04DB04 is 8.6:1 and free to be
   itself. Leaving those rotating while the badges were uniform made the cards
   look mismatched rather than varied. */
.sage-band .prod-feature-grid > *:nth-child(3n+1),
.sage-band .prod-feature-grid > *:nth-child(3n+2),
.sage-band .prod-feature-grid > *:nth-child(3n+3) {
  --card-accent:   var(--sage-green-btn);
  --card-accent-2: #0E8D1E;
  --card-ink:      #fff;
  --card-mark:     var(--sage-green);
}
/* the whole-site rule forcing white on the 3n+1 chip was written for the cyan
   slot; white is right on green too, so it stands and now matches its siblings */

/* photo wash and hover accents follow each card rather than staying orange */
.sage-band .prod-feature-bg::after {
  background:
    linear-gradient(180deg, rgba(2,29,47,0.20) 0%, rgba(3,43,68,0.58) 55%, rgba(4,38,20,0.94) 100%),
    linear-gradient(135deg, rgba(10,95,34,0.30) 0%, transparent 58%);
}
.sage-band .prod-feature h3::after { background: var(--card-mark); }
.sage-band .prod-feature:hover::before { border-color: var(--card-mark); }
.sage-band .prod-feature-num {
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.24);
}

/* ── 4. Product hero ─────────────────────────────────────────────────────
   Green does NOT go near the photograph. A green diagonal over the top of it
   was tried and it tinted the whole picture: the person in a factory read as
   a green silhouette and the hero looked like a colour cast rather than a
   photograph. Same failure mode DESIGN-SYSTEM.md §6 records for the old 0.92
   left-edge darkening — it buries what the picture actually shows.

   So the CI blue diagonal and the Blue Whale ramp are left exactly as they
   were, and green only arrives in the last eighth, as the handover into the
   page below. Green leads these pages through grounds, marks and actions,
   never by tinting photography. */
.product-hero { background: #04240F; }
.product-hero .product-hero-overlay {
  background:
    radial-gradient(ellipse at 22% 88%, rgba(4,219,4,0.10), transparent 52%),
    linear-gradient(118deg, rgba(11,83,155,0.40), transparent 58%),
    linear-gradient(to bottom,
      rgba(2,29,47,0.58) 0%,
      rgba(2,29,47,0.24) 14%,
      rgba(2,29,47,0.24) 32%,
      rgba(3,43,68,0.72) 55%,
      rgba(3,43,68,0.95) 76%,
      rgba(5,52,25,0.97) 90%,
      var(--sage-green-deeper) 100%);
}

/* ── 5. Other Sage Products — pricing table, prices removed ──────────────
   A pricing table is the right shape for "here are the other three": equal
   columns, one feature list each, one action each, and the reader scanning
   ACROSS a row rather than down a page. Sage is quoted per deployment, so the
   price row is simply absent and the product photograph takes its place at the
   top of the card — the cards sell the fit and the CTA is where the number
   comes from.

   The ground is the pale green tint, not another deep one: this band sits two
   sections below the pattern band, and a second dark green would make the page
   read as one long green field. */
.sage-suite-section {
  background: var(--sage-green-pale);
  border-top: 1px solid var(--sage-green-pale-line);
  border-bottom: 1px solid var(--sage-green-pale-line);
}

.sage-plans {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: stretch;
  gap: 24px;
  /* headroom for the featured column, which lifts out of the row */
  padding-top: 18px;
  margin-top: 40px;
}
.sage-plan {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border: 1px solid var(--sage-green-pale-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: 0 6px 24px rgba(3,43,68,0.07);
  transition: transform 0.45s var(--ease), box-shadow 0.45s var(--ease), border-color 0.45s var(--ease);
}
.sage-plan:hover {
  transform: translateY(-6px);
  border-color: var(--sage-green-btn);
  box-shadow: 0 26px 60px rgba(3,43,68,0.16);
}

/* Featured tier in the CENTRE, per DESIGN-SYSTEM.md §6. It lifts out of the
   row rather than growing, so all three cards keep the same internal rhythm
   and the grid still stretches them to one height. */
.sage-plan--feature {
  border-color: var(--sage-green-btn);
  border-top-width: 4px;
  box-shadow: 0 18px 48px rgba(3,43,68,0.16);
  transform: translateY(-18px);
}
.sage-plan--feature:hover { transform: translateY(-24px); }

.sage-plan-media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--bg2);
}
/* This used to be a plain <img> because an inline <picture> inside a flex
   card broke width: 100% on the image. That constraint is gone: unclassed
   pictures are display: contents (see the end of this sheet), so the wrapper
   generates no box and the img lays out exactly as it did before. */
.sage-plan-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1s var(--ease);
}
.sage-plan:hover .sage-plan-media img { transform: scale(1.06); }
.sage-plan-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(3,43,68,0.42) 0%, transparent 55%);
}
.sage-plan-flag {
  position: absolute;
  z-index: 1;
  top: 14px; right: 14px;
  padding: 7px 14px;
  border-radius: 100px;
  background: var(--sage-green-btn);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  box-shadow: 0 6px 18px rgba(3,43,68,0.28);
}

/* flex column with the CTA pushed to the foot, so the three actions line up
   across the row however long the copy above them runs */
.sage-plan-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 28px 28px 30px;
}
/* --sage-green-btn, not --sage-green-ink: this is the smallest green text on
   the site (11px), and the ink token's 4.51:1 clears AA by a hundredth, which
   leaves nothing for antialiasing. The button green is 5.5:1. (The card's CTA
   used to carry this same green; it is an outline now, so the kicker and the
   flag pill are what keep the green on the card.) The ticks below keep the ink
   token — they are graphics against a 3:1 bar, where 4.51 is ample. */
.sage-plan-kicker {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sage-green-btn);
  margin-bottom: 14px;
}
.sage-plan-title { margin: 0 0 14px; }
.sage-plan-desc {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-muted);
  margin: 0 0 20px;
}
.sage-plan-list {
  list-style: none;
  margin: 0 0 26px;
  padding: 20px 0 0;
  border-top: 1px solid var(--sage-green-pale-line);
  display: grid;
  gap: 12px;
}
.sage-plan-list li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--text);
}
.sage-plan-list svg {
  flex-shrink: 0;
  width: 17px; height: 17px;
  margin-top: 2px;
  color: var(--sage-green-ink);
}
/* margin-top: auto is what equalises the row; the button then stretches so the
   three CTAs are the same width regardless of product name length */
.sage-plan-cta {
  margin-top: auto;
  justify-content: center;
  text-align: center;
}

/* A set of three never divides into two columns — one card is left orphaned
   beside a gap, and "featured" stops meaning "the middle one". So this grid
   goes three-up straight to one-up, tightening the columns in between rather
   than dropping to two. */
@media (max-width: 1040px) {
  .sage-plans { gap: 16px; }
  .sage-plan-body { padding: 24px 20px 26px; }
  .sage-plan-cta { font-size: 0.875rem; padding: 13px 16px; }
  .sage-plan-desc, .sage-plan-list li { font-size: 0.8438rem; }
}
@media (max-width: 840px) {
  .sage-plans {
    grid-template-columns: 1fr;
    max-width: 460px;
    margin-inline: auto;
    padding-top: 0;
    gap: 20px;
  }
  .sage-plan-body { padding: 26px 24px 28px; }
  .sage-plan-cta { font-size: 0.9375rem; padding: 13px 24px; }
  .sage-plan-desc { font-size: 0.9375rem; }
  .sage-plan-list li { font-size: 0.875rem; }
  /* nothing to lift out of — the flag and the border carry "featured" here */
  .sage-plan--feature { transform: none; }
  .sage-plan--feature:hover { transform: translateY(-6px); }
}
@media (max-width: 680px) {
  /* no gutter left for a second corner plate at this width */
  .sage-band::after { display: none; }
  .sage-band::before { width: 62vw; }
}

/* =========================================================================
   OS HOLDINGS' OWN PRODUCT ROWS & PAGES — BLUE LEAD
   iServe Billing and Cloud Services sit beside the Sage suite on the
   solutions page and get pages of their own, built from the same
   .solution-feature / .prod-split components the Sage rows use above. The
   Sage-green block just above recolours .solution-badge,
   .solution-features-list svg and .prod-bullets svg for every element
   carrying those classes — not just the four Sage rows — so anything OS
   Holdings' own product markup shares with Sage needs pulling back to OS
   Holdings blue here. Scoped to a modifier (higher specificity) rather than
   fighting the cascade, so it wins regardless of source order. */
.solution-feature--osh .solution-badge {
  color: var(--accent-ink);
  border-color: rgba(11,83,155,0.28);
}
.solution-feature--osh .solution-features-list svg { color: var(--accent-ink); }

/* Stand-in for .sage-headline where a product has no lockup image yet — a
   typed heading with the same accent rule underneath, in OS Holdings blue. */
.solution-heading { position: relative; margin: 14px 0 0; }
.solution-heading::after {
  content: '';
  display: block;
  width: 52px; height: 3px;
  border-radius: 2px;
  background: var(--accent-ink);
  margin-top: 18px;
}

/* Small icon badge standing in for a lockup logo (Cloud Services has no
   brand mark to show) — round, blue-tinted, sits above the heading. */
.solution-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px; height: 52px;
  border-radius: 14px;
  background: rgba(11,83,155,0.08);
  color: var(--accent-ink);
  margin-bottom: 6px;
}
.solution-icon svg { width: 26px; height: 26px; }

/* Same pull-back for the "Why X" split section's tick list on the two new
   product pages, and for the cyan section-tag pill that .product-intro and
   .prod-split-text also force green further up (same unscoped-selector
   issue as .solution-badge above). */
.prod-split--osh .prod-bullets svg { color: var(--accent-ink); }
.product-intro--osh :is(.tag, .eyebrow),
.prod-split--osh .prod-split-text :is(.tag, .eyebrow) {
  background: var(--osh-cyan);
  color: #fff;
}
.product-intro--osh :is(.tag, .eyebrow)::before,
.prod-split--osh .prod-split-text :is(.tag, .eyebrow)::before {
  background: var(--osh-orange);
  box-shadow: 0 0 0 3px rgba(245,139,34,0.25);
}

/* Centred action row under a .page-hero's sub copy — iServe Billing and
   Cloud Services use the plain page hero rather than the Sage-only
   .product-hero, so their CTAs borrow the site's generic .hero-actions. */
.page-hero .hero-actions { justify-content: center; margin-top: 8px; }

/* =========================================================================
   PUBLIC SECTOR — mSCOA BAND
   The right half of this section was a stock circuit-brain photo, darkened to
   0.5 brightness with a stat badge floated on it: it illustrated nothing and
   it was the only thing on the page carrying an accreditation figure. It is
   gone. The CI's Carrot Curl artwork becomes the section's own ground, the six
   accreditation chips take the space the picture held, and the 100% moves into
   the copy where it can be read.

   Ink is white on the orange ground — client direction, 18 Aug 2026.
   ========================================================================= */
.mscoa-section {
  position: relative;
  overflow: hidden;
  background: var(--osh-orange);
  border-top: 0;
}
.mscoa-section > .container { position: relative; z-index: 1; }

/* Corner plates rather than a height-anchored `right top / auto 100%` image.
   That anchoring is height-driven, and this band stacks six chips — one column
   of them on a phone — so the artwork would grow wider as the section grew
   taller. Fixed plates sized off the VIEWPORT and masked out at their own far
   edge cannot be reached by section height, and a masked edge cannot seam.
   Same construction as .sage-band.

   Shared: .cert-section on About uses the same two plates, so the class is
   .ci-plate and not .mscoa-plate. Both places carry them as aria-hidden divs
   at the top of the section, ahead of the container. */
.ci-plate {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  aspect-ratio: 927 / 1055;
  background: url('../images/ci/patterns/orange-background-pattern.png') no-repeat;
  background-size: cover;
}
.ci-plate--tr {
  top: 0; right: 0;
  width: clamp(280px, 42vw, 660px);
  background-position: right top;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 100%);
}
.ci-plate--bl {
  bottom: 0; left: 0;
  width: clamp(190px, 26vw, 420px);
  background-position: left bottom;
  transform: scaleX(-1);
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 46%, transparent 100%);
          mask-image: linear-gradient(to top, #000 0%, #000 46%, transparent 100%);
}

/* Held at 660 so the measure stays readable. The grid column below caps it at
   the same figure; this keeps the cap if the band is ever used without the
   grid, and it is what the single-column breakpoint releases. */
.mscoa-block {
  max-width: 660px;
}
/* The plate is sized off the VIEWPORT, and the copy column is now 660 wide on
   its own side, so the shared clamp let the two meet at around 1200. Held
   narrower here — the artwork keeps its own gutter at every width. */
.mscoa-section .ci-plate--tr { width: clamp(240px, 32vw, 520px); }

/* White on Carrot Curl, matching .cert-section. The standfirst is held at 0.92
   rather than the old 0.82 so it keeps as much separation from the ground as
   the headline does. */
.mscoa-section .section-headline { color: #fff; }
.mscoa-section .section-sub { color: rgba(255,255,255,0.92); }
/* Cyan pill on the orange ground — client direction, 18 Aug 2026. It keeps the
   site-wide white pill ink; the dot flips to white as well, because the default
   Carrot Curl dot reads as a hole straight back to the ground behind it. */
.mscoa-section .tag { background: var(--osh-cyan); color: #fff; }
.mscoa-section .tag::before {
  background: #fff;
  box-shadow: 0 0 0 3px rgba(255,255,255,0.32);
}

.mscoa-stat {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid rgba(255,255,255,0.38);
}
/* Bebas is numerals-only (§4) — this is a figure, so it qualifies. */
.mscoa-stat-num {
  font-family: var(--font-display);
  font-size: 3.25rem;
  font-weight: 700;
  letter-spacing: 0.012em;
  line-height: 1;
  color: #fff;
}
.mscoa-stat-lbl {
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.35;
  color: rgba(255,255,255,0.92);
}

/* Pillar 4 — the mSCOA copy and bullets that used to sit on a solution card
   higher up the page, moved into this band (client direction, 10 Sept 2026).
   White ink on Carrot Curl like the rest of the band; the list drops its
   bottom margin because the stat below brings its own. */
.mscoa-section .mscoa-body { margin-top: 16px; }
.mscoa-section .mscoa-points { margin: 24px 0 0; }
.mscoa-section .mscoa-points li,
.mscoa-section .mscoa-points svg { color: #fff; }

/* Pillars 2 and 3 on the Public Sector page — the body paragraph under the
   standfirst, a size down from it so the client's one-line pillar summary
   stays the lead. */
.pillar-body {
  margin-top: 14px;
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--text-muted);
}

/* ── Compliance figure ───────────────────────────────────────────────────
   Client direction, 21 Aug 2026. The copy is a 660-wide left column, which
   leaves the right half of the band empty; a cut-out puts a person in it.

   The two are a stretched grid ROW, not a floating absolute box, because the
   figure has to be sized BY the copy — DESIGN-SYSTEM.md §5b. The copy column
   is the only one with intrinsic height, so it sets the row; the figure's
   column stretches to match and the image fills whatever height that is. Add
   a compliance chip and the person grows with the copy.

   The frame then bleeds one band's-padding BELOW the row (`bottom` is negative
   by --band-pad, which tracks .section's own padding), so the waist crop lands
   on the section's lower edge rather than on the container's. .mscoa-section
   is overflow: hidden, which is what cuts it there. */
.mscoa-grid {
  --band-pad: 110px;
  display: grid;
  grid-template-columns: minmax(0, 660px) 1fr;
  gap: 40px;
  align-items: stretch;
}
.mscoa-figure-col { position: relative; }

/* The <picture> is the frame. It carries no size of its own — it is pinned to
   its column and hangs the band's bottom padding below it. */
.mscoa-figure {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  bottom: calc(var(--band-pad) * -1);
  display: block;
  pointer-events: none;
  user-select: none;
}
/* Height drives, width follows. `width: auto` is load-bearing twice over: the
   sheet's `img { max-width: 100% }` beats the intrinsic ratio and would squash
   a height-sized image (hence max-width: none), and osh_img() writes the
   file's own width/height onto the tag, which the browser honours as a ratio
   only while the other axis is auto. Sizing the IMG rather than the <picture>
   also sidesteps shrink-to-fit: an absolutely positioned <picture> with
   `width: auto` takes its MAX-CONTENT width — the file's intrinsic pixels, not
   the width it actually paints at. */
.mscoa-figure img {
  position: absolute;
  right: 0;
  bottom: 0;
  display: block;
  height: 100%;
  width: auto;
  max-width: none;
}

/* The figure column carries only absolutely positioned content, so it has no
   intrinsic width — `1fr` is what gives it one, and the collapse point is
   where that 1fr stops being wide enough for a person. Container inner width
   is viewport - 64; take the 660 copy and the 40 gap off that and the figure
   needs about 400 left, which runs out at ~1164. Rounded to 1180.

   Below it the row becomes one column and the figure moves UNDER the copy —
   still bottom-flush, because the frame keeps its negative bottom and the band
   keeps its overflow clip. Its column takes an explicit height there, since
   there is no sibling left to inherit one from. */
@media (max-width: 1180px) {
  .mscoa-grid { grid-template-columns: 1fr; gap: 0; }
  .mscoa-block { max-width: none; }
  .mscoa-figure-col {
    height: 380px;
    margin-top: 40px;
  }
  .mscoa-figure { left: 0; right: 0; }
  .mscoa-figure img { left: 50%; right: auto; transform: translateX(-50%); }
}
@media (max-width: 700px) {
  .mscoa-grid { --band-pad: 72px; }
  .mscoa-figure-col { height: 300px; margin-top: 32px; }
}
@media (max-width: 680px) {
  /* no gutter left for a second plate at this width */
  .ci-plate--bl { display: none; }
  .ci-plate--tr { width: 62vw; }
  /* centred copy — see .cert-section .ci-plate above */
  .cert-section .ci-plate--tr { width: 44vw; opacity: 0.55; }
  .mscoa-stat-num { font-size: 2.75rem; }
}

/* =========================================================================
   INLINE STYLES, LIFTED — About / Careers / Public Sector
   -------------------------------------------------------------------------
   Those three pages still carried 49 style="" attributes between them, and
   three of them were hard-coded grids with no breakpoint at all: `1fr 1fr`
   at gap 80, and `1fr 1px 1fr` at gap 60. That second one is why About was
   the only page that scrolled sideways on a phone — at a 390px viewport its
   three columns come out 115px wide and the copy pushes 16px past the
   viewport edge. They are classes now, with the breakpoints they always
   needed, and nothing on those pages sets style="" any more.
   ========================================================================= */

/* Supporting paragraph under a .section-sub — the most repeated of the
   inline blocks (five instances across the three pages). */
.prose-sm {
  font-size: 0.9375rem;
  color: var(--text-muted);
  line-height: 1.7;
}
.section-sub + .prose-sm,
.prose-sm + .prose-sm { margin-top: 16px; }

/* Section heads */
.section-head        { margin-bottom: 48px; }
.section-head-center { text-align: center; margin-bottom: 60px; }
.section-head-center .section-sub,
.section-sub--center { text-align: center; margin: 16px auto 0; }
.section-head-split {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 48px;
}
.section--flush-top    { padding-top: 0; }
.section-headline--sm  { font-size: clamp(1.5rem, 3vw, 2.25rem); }

/* Tinted band between two white sections. --lg is the taller Careers
   variant, which has a top border only. */
.section-band {
  background: var(--bg2);
  padding: 80px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.section-band--lg { padding: 100px 0; border-bottom: 0; }

/* About — "Born in Joburg" */
.story-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 80px;
  align-items: center;
}
.story-media {
  border-radius: 20px;
  overflow: hidden;
  aspect-ratio: 4/3;
  position: relative;
}
/* Client direction, 21 Aug 2026: in a two-column row the photograph runs at
   full strength with nothing over it. This one was dimmed to 60% and
   desaturated under a blue wash; both are gone. .story-media-wash is kept as
   a no-op so the markup that still emits the div cannot leave a stray box. */
.story-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: none;
}
.story-media-wash { display: none; }

/* About — mission / vision, split by a hairline column */
.mv-grid {
  display: grid;
  grid-template-columns: 1fr 1px 1fr;
  gap: 60px;
  align-items: center;
}
.mv-rule { background: var(--border); height: 200px; }

/* Careers — the three facts under the intro copy */
.careers-facts {
  margin-top: 32px;
  display: flex;
  align-items: center;
  gap: 32px;
  flex-wrap: wrap;
}
.fact-label { font-size: 0.8125rem; color: var(--text-muted); margin-top: 2px; }
.fact-rule  { width: 1px; height: 48px; background: var(--border); }
.careers-speculative { margin-top: 32px; text-align: center; }
.careers-speculative p { margin-bottom: 20px; }

/* Public Sector — product card bullets. The tag here used to be set to
   0.65rem inline, which is 10.4px: below the legible-type floor and the
   smallest text on the site. It keeps the component's own size now. */
.ps-bullets { margin-top: 16px; margin-bottom: 0; }
.ps-bullets svg {
  width: 14px;
  height: 14px;
  color: var(--blue-light);
  margin-top: 3px;
  flex-shrink: 0;
}
.ps-card .tag { margin-bottom: 14px; }
.btn--lg { font-size: 1rem; padding: 16px 36px; }

@media (max-width: 900px) {
  .story-grid { grid-template-columns: 1fr; gap: 40px; }
  .mv-grid    { grid-template-columns: 1fr; gap: 32px; }
  /* the divider was a 200px-tall hairline COLUMN; stacked, it becomes the
     rule between the two blocks */
  .mv-rule { height: 1px; width: 100%; }
}
@media (max-width: 560px) {
  /* three facts across 390px wrap to two rows, and a leftover vertical
     hairline between wrapped items reads as a mistake */
  .careers-facts {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 22px 24px;
  }
  .fact-rule { display: none; }
}

/* =========================================================================
   MOBILE HARDENING
   -------------------------------------------------------------------------
   Everything below was found by rendering all eleven pages at a real 390px
   viewport and measuring the result, rather than by reading the sheet.
   ========================================================================= */

/* ── 1. Images the sheet sizes by HEIGHT ─────────────────────────────────
   `img, svg { max-width: 100% }` near the top of this file is right for
   anything that fills a box and wrong for anything sized by height: the
   clamp beats the intrinsic ratio, so the picture keeps its height and
   loses width — it renders SQUASHED, not scaled. That is exactly what the
   CTA cut-outs were doing on phones (696×780 artwork rendering 171×248, a
   23% horizontal crush) and why the figures looked stretched. Every image
   this sheet sizes by height opts out of the clamp.

   The <picture> wrapper needs `width: max-content` for the same reason: it
   is absolutely positioned with `width: auto`, so shrink-to-fit was sizing
   it from the space left beside it rather than from the artwork, and the
   translateX(-50%) centring then worked off the wrong box. */
.cta-figure img,
.cert-logo,
.client-logo,
.footer-logo,
.footer-partner-logo,
.sh-logo-img { max-width: none; }

.cta-band--figure > .cta-figure,
.cta-band--figure-left > .cta-figure { width: max-content; }

/* ── 2. Legible type floor ───────────────────────────────────────────────
   A run of eyebrow labels sat at 11px, and the Public Sector card tags at
   10.4px. The design intends them small, so they keep their size on the
   desktop layouts the client signed off and take the 12px floor only where
   the screen is actually small. */
@media (max-width: 700px) {
  .svc-num,
  .hiw-tag,
  .hs-scroll,
  .sage-plan-kicker,
  .sage-plan-flag,
  .footer-partners-label,
  .press-date,
  .tag,
  .eyebrow { font-size: 0.75rem; }
}

/* ── 3. Tap targets ──────────────────────────────────────────────────────
   Anything a thumb has to hit gets 44px of vertical room. These are links
   inside running copy, so only the box grows — the type is untouched. */
@media (max-width: 700px) {
  .svc-link,
  .cc-value a,
  .job-item { min-height: 44px; display: inline-flex; align-items: center; }
  .job-item { display: flex; }
  .footer-links a,
  .footer-contact li a { display: inline-flex; align-items: center; min-height: 34px; }
}
/* The error pages carry their own lockup rather than the site header's. */
.error-brand { display: inline-flex; align-items: center; min-height: 44px; }

/* ── 4. Paint cost ───────────────────────────────────────────────────────
   Twenty-six backdrop-filters, several of them on elements that repaint on
   every scroll frame. A blur is the single most expensive thing a phone GPU
   can be asked to do per frame, and it is what made scrolling stutter. On
   small screens the blurred panels take a solid tint instead — visually
   near-identical over these grounds, and free.

   Everything here is scoped to a coarse pointer as well as a small screen,
   so a small desktop window keeps the full treatment. */
@media (max-width: 900px) and (pointer: coarse) {
  /* 0.92 white over a blur reads as frosted; over nothing it reads as a
     mistake, so the panels that lose their blur take a solid ground. */
  .site-header { background: #fff; }

  .site-header,
  .sh-mobile,
  .hs-badge,
  .contact-channel,
  .glass-card,
  .stat-card,
  .prod-hero-card,
  .value-card,
  .svc-card,
  .cta-band {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }

  /* content-visibility: auto was here and has been taken out again. It implies
     PAINT containment, and this design deliberately overflows its sections —
     the CTA cut-outs sit 104px above their band, the corner patterns bleed
     out of theirs. Containment clips exactly those, so the saving would have
     cost the artwork. */

  /* A phone cannot hover, so the transforms behind these rules only ever
     fire from a tap and then stick. */
  .value-card:hover,
  .team-card:hover,
  .press-card:hover,
  .perk-card:hover,
  .ps-card:hover,
  .sage-plan:hover,
  .contact-channel:hover,
  .compliance-pill:hover { transform: none; }
}

/* Honour the OS-level setting. Everything on this site is decoration, so
   there is nothing to keep running. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* The transition, and why it is gated on .osh-nav
   ------------------------------------------------
   Both halves run on touch and desktop alike, same durations — a transition
   that only plays on the way out reads as a missing transition.

   The cost of the ARRIVAL half is real though: the overlay is opaque, so
   every millisecond it covers the viewport is a millisecond the filmstrip
   spends on a blue screen rather than on the page. Running it on every load
   measured Speed Index 8.3s against 1.5s without it, and took the mobile
   performance score from 100 to 89.

   So it is gated on where the visitor came from. An inline script in
   <head> — before anything paints, so neither state can flash — sets
   .osh-nav when sessionStorage says the previous page of this site handed
   over. Follow a link and you get the full cover-and-reveal. Land here cold
   from a search result, which is also exactly what a page speed test does,
   and there is no previous page to transition from, so nothing covers the
   content and the page paints immediately.

   Reduced motion is the one opt-out. */
@media (prefers-reduced-motion: reduce) {
  .page-transition { display: none !important; }
}

/* Every photograph now goes through osh_picture(), which wraps it in a
   <picture> so a WebP srcset can be offered. An UNCLASSED picture is only a
   carrier for its <img> and must not become a box of its own — display:
   contents removes it from layout entirely, so every existing rule that
   sizes the img still resolves against the same parent it always did. The
   classed ones (.cta-figure, .sage-lockup) are real elements and keep their
   boxes, which is why this is scoped rather than applied to `picture`. */
picture:not([class]) { display: contents; }

/* ...but display: contents hoists ALL of the picture's children into the
   parent's layout, and that includes its <source> elements. Normally a
   <source> is harmless: <picture> is an inline box and the source generates
   nothing inside it. Hoisted into a FLEX container it becomes a real flex
   item — zero-width, but still consuming a column gap and still counting
   when the line decides where to wrap.

   That is what knocked the client logo rows off centre: with a 72px gap the
   lines came out [source][img][source], [img][source], [img][source], [img],
   so two of the four rows centred a 205px line instead of a 133px one and sat
   36px left of the rest. Sources generate no box, so nothing is lost by
   removing them from layout outright. */
picture > source { display: none; }

/* =========================================
   MAINTENANCE PAGE + PREVIEW BAR
   The page reuses the .error-page shell (dark navy ground, orange glow,
   corner logo, centred column); only the pieces it does NOT share live here.
   ========================================= */

/* No giant watermark number behind this one — .error-page::before renders
   attr(data-code), and the maintenance page has no code to show. */
.maint-page::before { content: none; }

.maint-title { margin-top: 4px; }

/* Three pulsing dots. Deliberately not a spinner: a spinner promises the page
   is waiting on something and will resolve itself, and this page will not —
   the visitor has to come back. Dots read as "in progress elsewhere". */
.maint-dots {
  display: flex;
  gap: 10px;
  justify-content: center;
  margin: 0 0 30px;
}
.maint-dots span {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--osh-orange);
  opacity: 0.35;
  animation: maintPulse 1.4s ease-in-out infinite;
}
.maint-dots span:nth-child(2) { animation-delay: 0.2s; }
.maint-dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes maintPulse {
  0%, 100% { opacity: 0.25; transform: scale(0.82); }
  50%      { opacity: 1;    transform: scale(1); }
}

/* Honoured, not ignored: a visitor who has asked for reduced motion is not
   served a permanently animating page while they wait. */
@media (prefers-reduced-motion: reduce) {
  .maint-dots span { animation: none; opacity: 0.7; }
}

.maint-eta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 34px;
  padding: 11px 20px;
  border-radius: 999px;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.16);
  color: rgba(255,255,255,0.88);
  font-size: 0.9375rem;
  font-weight: 500;
}
.maint-eta::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--osh-cyan);
  box-shadow: 0 0 0 3px rgba(33,177,245,0.22);
}

.maint-contact { margin-top: 8px; }
.maint-contact-label {
  display: block;
  margin-bottom: 16px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
}

/* ── The bar an exempt visitor sees on the LIVE site while it is closed ──
   Fixed rather than sticky, and the body is padded to match, so it cannot be
   scrolled away and forgotten — the whole point is that somebody working on
   a closed site never loses track of the fact that it is closed. */
.osh-maint-bar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 9999;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 12px;
  padding: 10px 20px;
  background: var(--osh-orange);
  color: #fff;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  line-height: 1.4;
  text-align: center;
  box-shadow: 0 2px 14px rgba(0,0,0,0.22);
}
.osh-maint-bar strong { font-weight: 700; }
.osh-maint-bar a {
  color: #fff;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.osh-maint-bar a:hover { text-decoration: none; }

/* The site's own header is fixed to the top too, so it has to be pushed down
   by exactly the bar's height or the two overlap. */
body.has-maint-bar { padding-top: 40px; }
body.has-maint-bar .site-header { top: 40px; }

@media (max-width: 720px) {
  body.has-maint-bar { padding-top: 62px; }
  body.has-maint-bar .site-header { top: 62px; }
}

/* =========================================
   CONTACT FORM — status line + spam honeypot
   ========================================= */

/* The outcome message above the form. One element, two states, so the script
   and the no-JavaScript server render use the same markup. */
.form-note {
  margin: 0 0 22px;
  padding: 14px 18px;
  border-radius: 10px;
  font-size: 0.9375rem;
  line-height: 1.55;
  border: 1px solid transparent;
}
.form-note--ok {
  background: #E7F6EE;
  border-color: #B9E4CE;
  color: #0E7A4A;
}
.form-note--bad {
  background: #FDECEC;
  border-color: #F3C4C4;
  color: #9B1C1C;
}

/* The honeypot. Hidden from sight, from the keyboard (tabindex="-1" in the
   markup) and from screen readers (aria-hidden), so no real visitor can reach
   it — which is what makes "this field has a value" a reliable signal.

   NOT display:none, and not visibility:hidden: the crawlers this is meant to
   catch have been skipping fields hidden that obviously for years. Positioning
   it off-canvas leaves it a real, focusable-looking field in the DOM. */
.form-hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* =========================================
   CLIENT REVIEW — 2 SEPTEMBER 2026
   The components added for the public-sector pillars, the bespoke/AI section,
   and the two new pages (Press, Partnerships & Certifications).
   ========================================= */

/* ── Header: six nav links instead of five ─────────────────────────────
   The Press page took the nav from five links to six. The row is centred
   between a fixed-width logo and the CTA, so it does not wrap — it pushes,
   and at the tablet end of the range the CTA was being shoved off the bar.
   The links tighten in two steps before the burger takes over at 700px. */
@media (max-width: 1000px) {
  .sh-inner     { gap: 12px; padding: 0 20px; }
  .sh-nav-links { gap: 0; }
  .sh-link      { padding: 9px 10px; font-size: 0.8125rem; }
  .sh-logo-img  { height: 34px; }
}
@media (max-width: 830px) {
  .sh-cta     { padding: 10px 15px; font-size: 0.8125rem; gap: 0; }
  .sh-cta svg { display: none; }
  .sh-link    { padding: 9px 8px; }
}

/* Per-page hero crops for the two new pages — see the block beside
   .page-hero--about for what these values are doing. */
.page-hero--press        { --hero-pos: center 36%; }
.page-hero--partnerships { --hero-pos: center 32%; }

/* =========================================
   PUBLIC SECTOR — THE 15 BUSINESS PROCESSES
   =========================================
   Fifteen items is too many for the card grids the rest of the site uses: at
   3-up with a photograph apiece they would run five rows deep and the page's
   first section would be a scroll rather than a statement.

   The first attempt was the opposite mistake - one hairline table, fifteen
   cells of identical type, and nothing for the eye to catch on. Rebuilt on
   client feedback (2 Sept 2026) as separated tiles carrying three signals in
   a fixed place: a coloured pictogram top-left, a faint numeral top-right,
   then name and one line. The icon does the differentiating; the type stays
   quiet, so the set still reads as one system rather than fifteen posters. */
.proc-section { padding-top: 88px; }
.proc-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
  margin: 52px 0 0;
  padding: 0;
}
/* Three CI colours, one per COLUMN: Egyptian Blue, Sky Blue, Carrot Curl,
   repeating every third tile. Fifteen divides by three exactly, so each colour
   lands five times and the desktop grid reads as three clean vertical bands.
   The two-up and one-up breakpoints restate the pattern for the shape they
   are in - see the media queries at the foot of this block.

   The colour lands on the fill of the icon chip and on a rule across the top
   of the tile - never on the type. Carrot Curl on white is 2.4:1
   (DESIGN-SYSTEM.md section 1); as a badge FILL under white ink it is the
   logo's own pairing, which is why the chip may take it and the process name
   may not. */
.proc-item {
  --accent:   var(--osh-blue);
  --accent-2: #1D74C9;
  --accent-soft: rgba(11,83,155,0.06);
  position: relative;
  display: block;
  padding: 26px 26px 24px;
  background: var(--white);
  border: 1px solid var(--border2);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease),
              border-color 0.35s var(--ease), background 0.35s var(--ease);
}
/* The tile's colour, stated across its top edge. Always on - a rule that only
   appears on hover is invisible on a phone, which is where half of this grid
   is read. */
.proc-item::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
}
.proc-item:hover {
  transform: translateY(-4px);
  background: var(--accent-soft);
  box-shadow: 0 18px 40px rgba(3,43,68,0.12);
  border-color: var(--accent);
}

.proc-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 14px;
  margin-bottom: 18px;
  background: linear-gradient(140deg, var(--accent), var(--accent-2));
  color: #fff;
  box-shadow: 0 8px 18px -8px var(--accent);
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.proc-icon svg { width: 23px; height: 23px; }
.proc-item:hover .proc-icon {
  transform: translateY(-2px);
  box-shadow: 0 12px 24px -8px var(--accent);
}

/* The numeral is an index, not a headline: it says where you are in the
   fifteen and gets out of the way of the name. It stays grey through every
   colour in the cycle - four different numeral colours would compete with the
   chips they sit beside, and orange type on white fails contrast outright. */
.proc-num {
  position: absolute;
  top: 30px;
  right: 24px;
  font-family: var(--font-display);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.12em;
  color: var(--text-muted);
  opacity: 0.55;
}
.proc-title {
  display: block;
  font-family: var(--font-heading);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.012em;
  line-height: 1.3;
  color: var(--ink);
  /* Clears the numeral so a long name wraps before it reaches it. */
  padding-right: 34px;
}
.proc-desc {
  display: block;
  margin-top: 8px;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--text-muted);
}
.proc-note {
  margin-top: 24px;
  text-align: center;
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* Middle column - Sky Blue */
.proc-item:nth-child(3n+2) {
  --accent:   var(--osh-cyan);
  --accent-2: #5CC8F8;
  --accent-soft: rgba(33,177,245,0.07);
}
/* Right column - Carrot Curl */
.proc-item:nth-child(3n+3) {
  --accent:   var(--osh-orange);
  --accent-2: #F7A94E;
  --accent-soft: rgba(245,139,34,0.07);
}

@media (max-width: 1050px) {
  .proc-grid { grid-template-columns: repeat(2, 1fr); gap: 14px; }
}
/* Two columns. A three-colour sequence across two columns would step one place
   left on every row and come back out as the scatter this pattern replaced, so
   at this width the colour goes by ROW instead - the tiles pair up, and the
   grid reads as horizontal bands rather than vertical ones. */
@media (min-width: 621px) and (max-width: 1050px) {
  .proc-item:nth-child(6n+3),
  .proc-item:nth-child(6n+4) {
    --accent:   var(--osh-cyan);
    --accent-2: #5CC8F8;
    --accent-soft: rgba(33,177,245,0.07);
  }
  .proc-item:nth-child(6n+5),
  .proc-item:nth-child(6n+6) {
    --accent:   var(--osh-orange);
    --accent-2: #F7A94E;
    --accent-soft: rgba(245,139,34,0.07);
  }
  /* Undo the three-up rule where the six-up one disagrees with it. */
  .proc-item:nth-child(6n+1),
  .proc-item:nth-child(6n+2) {
    --accent:   var(--osh-blue);
    --accent-2: #1D74C9;
    --accent-soft: rgba(11,83,155,0.06);
  }
}
@media (max-width: 620px) {
  .proc-grid { grid-template-columns: 1fr; gap: 12px; }
  /* The icon moves beside the name rather than above it. A grid, not a flex
     row: the title and the description are siblings, so the icon has to span
     both of their rows instead of sitting next to a wrapper that is not
     there. A tile with no description simply has one row to span. */
  .proc-item {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 14px;
    align-items: center;
    padding: 18px 20px;
  }
  .proc-icon {
    grid-row: 1 / span 2;
    width: 40px;
    height: 40px;
    border-radius: 12px;
    margin-bottom: 0;
  }
  .proc-icon svg { width: 20px; height: 20px; }
  .proc-title { grid-column: 2; align-self: end; }
  .proc-desc  { grid-column: 2; align-self: start; margin-top: 3px; }
  .proc-num   { top: 18px; right: 18px; }
}

/* ── Public sector: additional solutions, and Solutions: bespoke & AI ──
   Both reuse .services-grid / .service-card, which ships as a navy card with
   the orange numeral badge hanging half out of its corner. That badge needs
   headroom above the first row, and the grid's own margin is what provides
   it; both sections sit on the light --bg2 band, which is the ground the
   component was drawn for. (The Solutions "Cloud & beyond" copy of it frosts
   instead, because that one sits on Egyptian Blue.) */
.ps-additional-section,
.bespoke-section { border-bottom: 0; }
.ps-additional-section .services-grid,
.bespoke-section .services-grid { margin-top: 46px; }
/* .services-grid--n4/n2/n1 (the count modifiers) are declared once, right
   after the base .services-grid rule near the top of this file — they have
   to come BEFORE the 1050px/700px breakpoints below, not here. See the note
   there. */

/* Feature bullets inside a navy service card. The tick is Carrot Curl — on
   this near-black ground it is 6.4:1, and it is a mark rather than type. */
.service-points {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  position: relative;
  z-index: 1;
}
.service-points li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 7px 0;
  font-size: 0.875rem;
  line-height: 1.5;
  color: rgba(255,255,255,0.86);
}
.service-points li + li { border-top: 1px solid rgba(255,255,255,0.09); }
.service-points svg {
  width: 15px; height: 15px;
  flex: 0 0 auto;
  margin-top: 3px;
  color: var(--osh-orange);
}

/* ── About: the button out of the green partnerships band ───────────── */
.cert-cta { margin-top: 28px; }

/* =========================================
   PARTNERSHIPS & CERTIFICATIONS PAGE
   ========================================= */

/* A partnership card is a logo box over copy. The logo box is a FIXED height
   with the mark contained inside it, so a wide wordmark and a square badge
   still line their copy up on the same baseline — logos arrive at whatever
   proportion the partner supplies them, and a grid that lets each one set its
   own height reads as broken. */
.partner-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}
/* The Public Sector page shows a SUBSET of this same list (see
   includes/partner-card.php), so the count is no longer fixed at three. The
   template passes it as a modifier; two cards pair up and one is held to a
   readable width rather than being stretched across the container.
   DESIGN-SYSTEM.md §6 — three-up never steps through an orphaned row. */
.partner-grid--n2 { grid-template-columns: repeat(2, 1fr); }
.partner-grid--n1 { grid-template-columns: minmax(0, 420px); justify-content: center; }

/* The band this sits in on the Public Sector page: the cards are the content,
   so the link out to the full page is a quiet outline button under them
   rather than a second call to action competing with the closing CTA. */
.ps-accred-section .partner-grid { margin-top: 44px; }
.ps-accred-action { margin-top: 36px; text-align: center; }
.partner-card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease), border-color 0.3s;
}
.partner-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: var(--osh-blue);
}
.partner-mark {
  height: 132px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 28px;
  background: var(--bg2);
  border-bottom: 1px solid var(--border2);
}
.partner-mark picture { display: flex; align-items: center; height: 100%; }
.partner-mark img {
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* No approved artwork yet: the name set large holds the box rather than
   leaving a grey rectangle waiting for a file.

   Set in the HEADING face, not --font-display. Bebas Neue is numerals-only on
   this site (DESIGN-SYSTEM.md §4) and this is a name — "Sage ISV" — not a
   figure. It was reaching for Bebas because a mark substitute wants to be
   big and solid; §4's answer to exactly that is the heading face one size up,
   which is what this now is. */
.partner-mark-word {
  font-family: var(--font-heading);
  font-size: clamp(1.375rem, 2.6vw, 1.75rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.1;
  text-align: center;
  color: var(--osh-blue);
}
.partner-body {
  padding: 26px 28px 30px;
  display: flex;
  flex-direction: column;
  flex: 1;
}
.partner-level {
  align-self: flex-start;
  display: inline-block;
  margin-bottom: 12px;
  padding: 6px 13px;
  border-radius: 999px;
  background: var(--osh-blue);
  color: #fff;
  font-family: var(--font-heading);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1;
}
.partner-name {
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.2;
  color: var(--ink);
  margin-bottom: 10px;
}
.partner-desc {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-muted);
  flex: 1;
}
.partner-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 20px;
  font-family: var(--font-heading);
  font-size: 0.875rem;
  font-weight: 700;
  /* Egyptian Blue: this is a link, and accent text that has to be READ is
     never Carrot Curl on white. */
  color: var(--osh-blue);
  text-decoration: none;
  transition: gap 0.25s var(--ease), color 0.2s;
}
.partner-link:hover { gap: 14px; color: var(--osh-blue-dark); }
.partner-link svg { width: 15px; height: 15px; }

/* ── The certificates. Same anatomy, laid out as a row rather than a card,
   because a certificate is a line of fact — name, issuer, number — and not a
   pitch. Two-up rather than three, so a long standard name has room. ── */
.creds-section { border-bottom: 0; }
.creds-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px;
}
.cred-card {
  display: grid;
  grid-template-columns: 132px 1fr;
  align-items: stretch;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease), border-color 0.3s;
}
.cred-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: var(--osh-blue);
}
.cred-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 22px;
  background: var(--osh-blue-pale);
  border-right: 1px solid var(--border2);
}
.cred-mark picture { display: flex; align-items: center; }
.cred-mark img { max-height: 84px; width: auto; height: auto; object-fit: contain; }
.cred-mark-word {
  font-family: var(--font-display);
  font-size: 1.375rem;
  letter-spacing: 0.04em;
  line-height: 1.05;
  text-align: center;
  color: var(--osh-blue);
}
.cred-body { padding: 24px 26px; }
.cred-name {
  font-family: var(--font-heading);
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: -0.012em;
  color: var(--ink);
  margin-bottom: 6px;
}
.cred-meta {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--osh-blue);
  margin-bottom: 10px;
}
.cred-desc { font-size: 0.9375rem; line-height: 1.6; color: var(--text-muted); }
.creds-note {
  margin-top: 26px;
  text-align: center;
  font-size: 0.875rem;
  color: var(--text-muted);
}

@media (max-width: 1050px) {
  .partner-grid, .partner-grid--n2 { grid-template-columns: repeat(2, 1fr); }
  .creds-grid   { grid-template-columns: 1fr; }
}
@media (max-width: 700px) {
  .partner-grid, .partner-grid--n2, .partner-grid--n1 { grid-template-columns: 1fr; }
  .partner-mark { height: 108px; }
  .cred-card    { grid-template-columns: 1fr; }
  .cred-mark    { border-right: 0; border-bottom: 1px solid var(--border2); padding: 20px; }
  .cred-mark img { max-height: 64px; }
}

/* =========================================
   PRESS PAGE
   ========================================= */
/* The media desk address, under the enquiries copy. The icon is a mark and
   the address is a link, so the two take different inks. */
.press-desk-mail {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 20px;
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
}
.press-desk-mail svg { width: 18px; height: 18px; flex: 0 0 auto; color: var(--osh-orange); }
.press-desk-mail a { color: var(--osh-blue); text-decoration: none; }
.press-desk-mail a:hover { color: var(--osh-blue-dark); text-decoration: underline; }

/* The press room shows every item, so it gets three-up where the careers
   teaser's two-up would run long. */
.page-press .press-grid { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 1050px) { .page-press .press-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px)  { .page-press .press-grid { grid-template-columns: 1fr; } }

/* A press card's "Read more" used to borrow .svc-link — a hover-reveal
   component built for the dark .svc-card family (opacity 0 until a
   .svc-card ancestor is hovered). Nested in a plain, light .press-card that
   ancestor never existed, so the link never became visible at all. This is
   the equivalent that is actually on: reused for the release page's "Full
   coverage" link too, so the two look like the same action. */
.press-card-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 14px;
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--osh-blue);
  text-decoration: none;
  transition: gap 0.25s var(--ease), color 0.2s;
}
.press-card-link:hover,
.press-card-link:focus-visible {
  gap: 10px;
  color: var(--osh-blue-dark);
  text-decoration: underline;
}

/* ── Filter + search toolbar ──
   A client-side convenience over the list already on the page — every card
   is real, visible HTML underneath, so a visitor with JavaScript disabled
   still gets every release, just without the narrowing. */
.press-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 32px;
}
.press-filters { display: flex; flex-wrap: wrap; gap: 10px; }
.press-filter {
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink);
  background: var(--bg);
  border: 1.5px solid var(--border);
  border-radius: 999px;
  padding: 9px 18px;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}
.press-filter:hover { border-color: var(--osh-blue); }
.press-filter.is-active {
  background: var(--osh-blue);
  border-color: var(--osh-blue);
  color: #fff;
}
.press-search { max-width: 280px; flex: 1 1 220px; }
.press-no-results { text-align: center; padding: 32px 0 8px; }
/* initPressFilter() hides a filtered-out card with the [hidden] attribute.
   The UA stylesheet's [hidden] rule and .press-card's own `display: flex`
   are equal specificity, and an author rule always wins that tie regardless
   of source order — so without this, a "hidden" card would stay a visible,
   empty flex box. */
.press-card[hidden] { display: none; }

@media (max-width: 700px) {
  .press-toolbar { flex-direction: column; align-items: stretch; }
  .press-search { max-width: none; }
}

/* =========================================
   PRESS RELEASE (single item) — press-release.php
   ========================================= */

/* ── 1. Header band ──
   Egyptian Blue, with the CI blue artwork down the right edge (§8) — the
   ground the other brand bands use, rather than a darkened photograph. The
   artwork is a fixed-width pseudo-element, not an `auto 100%` background:
   this band's height follows the headline, and a height-driven background
   would creep across the text as a long headline made it taller (§6). Its
   left edge is masked out so there is no seam against the token. */
.release-hero {
  position: relative;
  overflow: hidden;
  background: var(--osh-blue);
  color: #fff;
  padding: calc(var(--nav-h) + 64px) 0 72px;
}
.release-hero--figure { padding-bottom: 210px; }
.release-hero::after {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: clamp(280px, 38vw, 560px);
  background: url('../images/ci/patterns/blue-background-pattern.png') right top / cover no-repeat;
  -webkit-mask-image: linear-gradient(to left, #000 55%, transparent);
          mask-image: linear-gradient(to left, #000 55%, transparent);
  pointer-events: none;
}
.release-hero > .container { position: relative; z-index: 1; }

/* Breadcrumb — visible trail, mirrored in BreadcrumbList JSON-LD. */
.release-crumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 0 36px;
  padding: 0;
  font-size: 0.8125rem;
  font-weight: 600;
}
.release-crumbs li { display: flex; align-items: center; gap: 8px; color: rgba(255,255,255,0.72); }
.release-crumbs li + li::before { content: '/'; color: rgba(255,255,255,0.4); }
.release-crumbs a { color: rgba(255,255,255,0.86); text-decoration: none; transition: color 0.2s; }
.release-crumbs a:hover { color: #fff; text-decoration: underline; }
.release-crumbs [aria-current] span {
  display: inline-block;
  max-width: 42ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

.release-hero-inner { max-width: 860px; }
/* Cyan is the contrasting pill on an Egyptian Blue band (see .contact-band). */
.release-hero .tag { margin-bottom: 22px; }
/* The headline is words, so --font-heading, not the display face (§4). */
.release-title {
  font-family: var(--font-heading);
  font-size: clamp(2rem, 4.2vw, 3.25rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.025em;
  color: #fff;
  margin: 0;
  text-wrap: balance;
}
.release-standfirst {
  margin-top: 22px;
  max-width: 62ch;
  font-size: clamp(1.0625rem, 1.5vw, 1.25rem);
  line-height: 1.6;
  color: rgba(255,255,255,0.88);
}
.release-meta {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 28px;
  margin: 32px 0 0;
  padding: 22px 0 0;
  border-top: 1px solid rgba(255,255,255,0.2);
  font-size: 0.875rem;
  font-weight: 600;
  color: rgba(255,255,255,0.88);
}
.release-meta li { display: inline-flex; align-items: center; gap: 8px; }
/* Small solid marks on a coloured ground (§2 rule 3), aria-hidden. */
.release-meta svg { width: 17px; height: 17px; flex: 0 0 auto; color: var(--osh-orange); }

/* ── 2. Feature photograph, lifted over the band ──
   The wrapper is sized, not the <img> — osh_picture() writes width/height
   attributes that would beat an aspect-ratio on the image itself (§6). The
   Carrot Curl rule under it is the press card's own image rule, set larger. */
.release-figure-wrap { position: relative; z-index: 2; margin-top: -150px; }
.release-figure {
  max-width: 1040px;
  margin: 0 auto;
  aspect-ratio: 16 / 8;
  overflow: hidden;
  border-radius: var(--radius-lg);
  border-bottom: 4px solid var(--osh-orange);
  background: var(--bg2);
  box-shadow: var(--shadow-lg);
}
.release-figure img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ── 3. Body + aside ── */
.release-main { padding: 72px 0 96px; }
.release-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 72px;
  max-width: 1040px;
  margin: 0 auto;
  align-items: start;
}
/* Body copy is set in ink rather than --text-muted: this is the one page on
   the site that is read end to end, not scanned. */
.release-body {
  max-width: 680px;
  font-size: 1.0625rem;
  line-height: 1.8;
  color: var(--ink);
}
.release-body > p + p { margin-top: 1.25em; }
.release-lead { font-size: 1.25rem; line-height: 1.65; }
.release-dateline {
  font-size: 0.8em;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--osh-blue);
}
.release-body > .release-source { margin-top: 44px; text-align: center; }

/* Sticky aside: share + media desk, beside the copy while it is read. */
.release-aside {
  position: sticky;
  top: calc(var(--nav-h) + 24px);
  display: grid;
  gap: 20px;
}
.release-card {
  padding: 24px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.release-card-title {
  font-family: var(--font-heading);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 16px;
}
.release-card p { font-size: 0.9375rem; line-height: 1.65; color: var(--text-muted); margin: 0; }
.release-card .press-desk-mail { margin-top: 14px; font-size: 0.9375rem; overflow-wrap: anywhere; }
/* Five 40px chips fit the 300px aside on one row; once the aside stacks
   (below) they go back to the site's 44px tap target. */
.release-share { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; }
.release-share a,
.release-share button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: var(--bg);
  color: var(--osh-blue);
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s, transform 0.25s var(--ease);
}
.release-share a:hover,
.release-share a:focus-visible,
.release-share button:hover,
.release-share button:focus-visible,
.release-share button.is-copied {
  background: var(--osh-blue);
  border-color: var(--osh-blue);
  color: #fff;
  transform: translateY(-2px);
}
.release-share svg { width: 17px; height: 17px; }
.release-card .release-copy-note { margin-top: 10px; font-size: 0.8125rem; overflow-wrap: anywhere; }
/* Empty until the copy button is used — its margin alone would leave a blank
   strip at the foot of the card. Collapsed rather than display:none: that
   would drop the aria-live region from the accessibility tree, and "Link
   copied" might then never be announced when the text arrives. */
.release-card .release-copy-note:empty { margin-top: 0; }
.release-aside-link {
  display: inline-block;
  margin-top: 16px;
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--osh-blue);
  text-decoration: none;
}
.release-aside-link:hover { color: var(--osh-blue-dark); text-decoration: underline; }

/* ── 4. Related news ──
   Up to three cards. Three equal cards break three-up straight to one-up,
   never through two (§6); a row of one or two is sized to what it holds. */
.release-related .press-grid--n2 { grid-template-columns: repeat(2, 1fr); }
.release-related .press-grid--n1 { grid-template-columns: minmax(0, 420px); }

@media (max-width: 1050px) {
  .release-related .press-grid { grid-template-columns: 1fr; }
}
@media (max-width: 1000px) {
  .release-layout { grid-template-columns: 1fr; gap: 48px; }
  .release-body { max-width: none; }
  .release-aside { position: static; grid-template-columns: 1fr 1fr; }
  .release-share a,
  .release-share button { width: 44px; height: 44px; }
}
@media (max-width: 780px) {
  .release-hero { padding-top: calc(var(--nav-h) + 40px); }
  .release-hero--figure { padding-bottom: 130px; }
  .release-hero::after { opacity: 0.35; }
  .release-figure-wrap { margin-top: -90px; }
  .release-figure { aspect-ratio: 4 / 3; }
  .release-main { padding: 48px 0 72px; }
}
@media (max-width: 560px) {
  .release-crumbs [aria-current] { display: none; }
  .release-lead { font-size: 1.125rem; }
  .release-aside { grid-template-columns: 1fr; }
}

/* =========================================
   SOLUTIONS — BESPOKE BUSINESS SOLUTIONS & AI
   =========================================
   Was the plain navy .service-card. Client direction, 2 Sept 2026: give each
   card a photograph, set the title larger and the copy smaller, so the three
   read as products rather than as a list of services.

   The card is the site's navy card with a picture grafted onto the top. Two
   things make the graft invisible: the photograph is ramped into the card's
   own ground at its foot rather than being cut off by a border, and the orange
   numeral badge straddles the seam — the same badge the .service-card family
   hangs off its corner, moved to where the two halves meet so it ties them
   together instead of floating over one of them. */
.bespoke-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
  margin-top: 52px;
}
.bespoke-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: linear-gradient(155deg, #0A4479 0%, #032B44 60%, #021D2F 100%);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--radius-lg);
  overflow: hidden;
  color: #fff;
  box-shadow: 0 10px 40px rgba(3,43,68,0.18);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease), border-color 0.4s var(--ease);
}
.bespoke-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  background: radial-gradient(circle at 80% 110%, rgba(245,139,34,0.18), transparent 55%);
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  pointer-events: none;
}
.bespoke-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 24px 60px rgba(3,43,68,0.35);
  border-color: rgba(245,139,34,0.45);
}
.bespoke-card:hover::after { opacity: 1; }

/* The photograph. A ratio on the WRAPPER with the image filling it, never on
   the <img> — osh_picture() writes intrinsic width/height onto the tag and a
   presentational height beats `aspect-ratio` (DESIGN-SYSTEM.md §6). */
.bespoke-media {
  position: relative;
  aspect-ratio: 16 / 10;
  flex: 0 0 auto;
}
/* The clip lives here, one level in. .bespoke-media itself must NOT clip: the
   numeral badge is its child and is meant to hang below its lower edge. */
.bespoke-media-frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
.bespoke-media picture { display: block; height: 100%; }
.bespoke-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease);
}
.bespoke-card:hover .bespoke-media img { transform: scale(1.05); }

/* The ramp is what removes the seam. It ends on the card's own top colour, so
   the picture does not stop — it dissolves into the ground the copy sits on.
   A plain border there reads as two stacked boxes. */
.bespoke-media-ramp {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom,
      rgba(3,43,68,0.10) 0%,
      rgba(3,43,68,0.18) 45%,
      rgba(8,58,98,0.72) 78%,
      #0A4479 100%),
    linear-gradient(118deg, rgba(11,83,155,0.34), transparent 64%);
  pointer-events: none;
}

/* The numeral, straddling the seam. Blue Whale ink on Carrot Curl: this is a
   badge FILL, which is the logo's own pairing and the one place the site's
   white-on-orange rule does not apply (DESIGN-SYSTEM.md §2). */
.bespoke-num {
  position: absolute;
  left: 26px;
  bottom: -22px;
  z-index: 2;
  font-family: var(--font-heading);
  font-size: 1.75rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--osh-whale);
  background: linear-gradient(135deg, var(--osh-orange) 0%, #F7A94E 100%);
  padding: 13px 18px;
  border-radius: 14px;
  box-shadow:
    0 12px 30px rgba(245,139,34,0.45),
    0 0 0 4px rgba(255,255,255,0.95),
    0 0 0 5px rgba(245,139,34,0.18);
  transition: transform 0.5s var(--ease), border-radius 0.5s var(--ease);
}
.bespoke-card:hover .bespoke-num { transform: scale(1.06) rotate(-3deg); }

/* A card with no photograph has no seam to straddle, so the badge returns to
   the flow at the top of the body and the body loses its badge clearance. */
.bespoke-num--inline {
  position: static;
  display: inline-block;
  margin-bottom: 18px;
}

.bespoke-body {
  /* The top padding clears the badge hanging off the picture above it. */
  padding: 40px 30px 34px;
  display: flex;
  flex-direction: column;
  flex: 1;
  position: relative;
  z-index: 1;
}
.bespoke-card > .bespoke-body:first-child { padding-top: 32px; }

/* Larger than the .service-card title it replaces, and the copy under it is a
   step smaller — the client asked for the name to carry the card. */
.bespoke-title {
  font-family: var(--font-heading);
  font-size: clamp(1.375rem, 1.6vw, 1.625rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: #fff;
  margin-bottom: 14px;
}
.bespoke-title::after {
  content: '';
  display: block;
  width: 36px;
  height: 2px;
  background: var(--osh-orange);
  margin-top: 14px;
  transition: width 0.5s var(--ease);
}
.bespoke-card:hover .bespoke-title::after { width: 72px; }

.bespoke-desc {
  font-size: 0.875rem;
  line-height: 1.65;
  color: rgba(255,255,255,0.74);
  flex: 1;
}

/* Same bullet row as .service-points; the tick is Carrot Curl, 6.4:1 on this
   ground and a mark rather than type. */
.bespoke-points {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
}
.bespoke-points li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgba(255,255,255,0.88);
}
.bespoke-points li + li { border-top: 1px solid rgba(255,255,255,0.09); }
.bespoke-points svg {
  width: 14px; height: 14px;
  flex: 0 0 auto;
  margin-top: 3px;
  color: var(--osh-orange);
}

/* The standfirst sets to TWO lines on a desktop screen, not three (client
   direction, 2 Sept 2026). .section-sub's 56ch measure is what put it on three;
   the copy is about 158 characters, so the measure has to clear ~79 per line
   with headroom for a later edit. Widened only from 1000px up — below that the
   column is narrower than the measure anyway, and the 56ch default is the
   right one for reading on a phone. */
@media (min-width: 1000px) {
  .bespoke-section .section-sub { max-width: 84ch; }
}

/* A set of three never breaks into two columns — DESIGN-SYSTEM.md §6. Three-up
   straight to one-up, tightening type and padding in between. */
@media (max-width: 1000px) {
  .bespoke-grid  { gap: 20px; }
  .bespoke-body  { padding: 36px 22px 28px; }
  .bespoke-title { font-size: 1.25rem; }
  .bespoke-num   { left: 20px; font-size: 1.5rem; padding: 11px 15px; }
}
@media (max-width: 780px) {
  .bespoke-grid  { grid-template-columns: 1fr; gap: 26px; max-width: 460px; margin-inline: auto; }
  .bespoke-media { aspect-ratio: 16 / 9; }
  .bespoke-body  { padding: 40px 26px 30px; }
  .bespoke-title { font-size: 1.375rem; }
}


/* =========================================
   DEMO / SPECIALIST POPUP — 15 SEPTEMBER 2026
   One form for every "Request a Demo", "Book a Demo" and "Talk to a
   Specialist" button (includes/demo-form.php, initDemoModal() in main.js).
   Sits above the header (1001) and below the page-transition screen (9990),
   so a link followed from inside it still gets the normal transition.
   ========================================= */
.demo-modal {
  position: fixed; inset: 0;
  z-index: 9000;
  display: flex; align-items: center; justify-content: center;
  padding: 24px 16px;
}
.demo-modal[hidden],
.demo-modal [hidden] { display: none; }
.demo-modal-backdrop {
  position: absolute; inset: 0;
  background: rgba(3,43,68,0.62);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
}
.demo-modal-dialog {
  position: relative;
  width: 100%; max-width: 640px;
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: var(--white);
  border-radius: var(--radius-lg);
  box-shadow: 0 30px 80px rgba(3,43,68,0.35);
  padding: 44px 44px 36px;
  opacity: 0;
  transform: translateY(24px) scale(0.98);
  transition: opacity 0.35s var(--ease), transform 0.45s var(--ease);
}
/* Brand accent across the top, as in the notification email. */
.demo-modal-dialog::before {
  content: '';
  position: absolute; top: 0; left: 0; right: 0;
  height: 5px;
  background: linear-gradient(90deg, var(--osh-orange), var(--osh-orange) 40%, var(--osh-blue));
}
.demo-modal.is-open .demo-modal-backdrop { opacity: 1; }
.demo-modal.is-open .demo-modal-dialog { opacity: 1; transform: none; }

.demo-modal-close {
  position: absolute; top: 16px; right: 16px;
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: var(--osh-blue-pale);
  color: var(--ink);
  cursor: pointer;
  transition: background 0.2s, color 0.2s, transform 0.3s var(--ease);
}
.demo-modal-close svg { width: 18px; height: 18px; }
.demo-modal-close:hover { background: var(--osh-blue); color: #fff; transform: rotate(90deg); }
.demo-modal-close:focus-visible { outline: 3px solid var(--osh-cyan); outline-offset: 2px; }

.demo-modal-head { padding-right: 36px; margin-bottom: 26px; }
.demo-modal-head .eyebrow { margin-bottom: 14px; }
.demo-modal-title {
  font-family: var(--font-heading);
  font-size: clamp(1.5rem, 3vw, 1.875rem);
  font-weight: 800;
  line-height: 1.15;
  color: var(--ink);
  margin: 0 0 10px;
}
.demo-modal-title:focus { outline: none; }
.demo-modal-sub { font-size: 0.9375rem; line-height: 1.6; color: var(--text-muted); margin: 0; }

/* Request type — two segmented pills over native radios. */
.demo-type { border: 0; padding: 0; margin: 0 0 18px; min-width: 0; }
.demo-type .form-label { padding: 0; }
.demo-type-options {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
  padding: 5px;
  background: var(--osh-blue-pale);
  border-radius: 100px;
}
.demo-type-option { position: relative; display: block; cursor: pointer; }
.demo-type-option input {
  position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer;
}
.demo-type-option span {
  display: block;
  padding: 11px 12px;
  border-radius: 100px;
  text-align: center;
  font-size: 0.875rem; font-weight: 600;
  color: var(--text-muted);
  transition: background 0.25s var(--ease), color 0.25s, box-shadow 0.25s;
}
.demo-type-option:hover span { color: var(--ink); }
.demo-type-option input:checked + span {
  background: var(--osh-blue); color: #fff;
  box-shadow: 0 6px 16px rgba(11,83,155,0.28);
}
.demo-type-option input:focus-visible + span { outline: 3px solid var(--osh-cyan); outline-offset: 2px; }

.demo-modal-form .form-textarea { min-height: 96px; }
.demo-modal-form .form-note { margin: 0 0 16px; }
.demo-modal-form .form-input.is-invalid,
.demo-modal-form .form-select.is-invalid {
  border-color: #C43C3C;
  box-shadow: 0 0 0 3px rgba(196,60,60,0.14);
}

/* Success state replaces the form. */
.demo-modal-done { text-align: center; padding: 20px 0 6px; }
.demo-modal-done-icon {
  width: 72px; height: 72px; margin: 0 auto 20px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--osh-orange-pale); color: var(--osh-orange);
}
.demo-modal-done-icon svg { width: 34px; height: 34px; }
.demo-modal-done .demo-modal-sub { max-width: 440px; margin: 0 auto 26px; }

body.demo-open { overflow: hidden; }

@media (max-width: 640px) {
  .demo-modal { align-items: flex-end; padding: 0; }
  .demo-modal-dialog {
    max-width: none;
    max-height: 94vh; max-height: 94dvh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding: 34px 20px calc(24px + env(safe-area-inset-bottom));
    transform: translateY(40px);
  }
  .demo-modal-close { top: 12px; right: 12px; }
  .demo-type-option span { font-size: 0.8125rem; padding: 10px 6px; }
}
@media (prefers-reduced-motion: reduce) {
  .demo-modal-backdrop, .demo-modal-dialog { transition: none; }
}

/* =========================================
   CLIENT UPDATES — 22 SEPTEMBER 2026
   A single vacancy (career.php) and the application form on it.

   The advert itself reuses the press-release page's components wholesale —
   .release-hero, .release-layout, .release-body, .release-card — because the
   two pages have the same job: one row of a dashboard list, given a page and
   a shareable address. What is added here is what a press release has no
   equivalent of: the bulleted requirement blocks, the "at a glance" facts,
   and the upload form.
   ========================================= */

/* ── The advert body ──────────────────────────────────────────────── */
/* Sized between the lead paragraph and the body copy around it, so a block
   heading separates without competing with the H1 in the band above. */
.vacancy-heading {
  font-family: var(--font-heading);
  font-size: 1.1875rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 38px 0 16px;
}
.vacancy-heading:first-child { margin-top: 0; }

/* The same tick-and-text row as .solution-features-list, kept as its own rule
   rather than reusing that class: this one sits in a prose column at body
   size, and the solutions list is sized for a marketing column. The tick is
   Egyptian Blue rather than Carrot Curl — on white, Carrot Curl is 2.4:1 and
   this is a mark the reader is meant to scan down. DESIGN-SYSTEM.md §2. */
.vacancy-list {
  list-style: none;
  margin: 0 0 8px;
  padding: 0;
}
.vacancy-list li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 12px;
  font-size: 1rem;
  line-height: 1.65;
  color: var(--text);
}
.vacancy-list li svg {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 3px;
  padding: 3px;
  border-radius: 50%;
  background: var(--osh-blue-pale);
  color: var(--osh-blue);
}

/* ── Hero actions ─────────────────────────────────────────────────── */
.vacancy-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}

/* ── "At a glance" ────────────────────────────────────────────────── */
/* A definition list in everything but markup: the label left, the value
   right, a hairline between rows. Two columns rather than stacked pairs
   because all four values are short, and stacking them would make the card
   twice as tall as the share card under it for no gain. */
.vacancy-facts {
  list-style: none;
  margin: 0 0 20px;
  padding: 0;
}
.vacancy-facts li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.vacancy-facts li:last-child { border-bottom: 0; }
.vacancy-facts span {
  flex: 0 0 auto;
  font-size: 0.8125rem;
  color: var(--text-muted);
}
.vacancy-facts strong {
  font-family: var(--font-heading);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink);
  text-align: right;
}
.vacancy-aside-btn { width: 100%; justify-content: center; }

/* ── The application form ─────────────────────────────────────────── */
/* A panel rather than the contact page's two-column split: there is no
   contact-details column to sit beside here, and a form this long running the
   full container width would have inputs a screen wide. */
/* scroll-margin-top clears the fixed header for the plain scrollIntoView()
   path in initAnchorLinks() (assets/js/main.js) — before Locomotive/Lenis has
   loaded, and on any touch/reduced-motion device that never loads it. */
.apply-section { background: var(--bg2); scroll-margin-top: calc(var(--nav-h) + 20px); }
.apply-panel {
  max-width: 760px;
  margin: 0 auto;
  padding: 44px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.apply-head { margin-bottom: 30px; }
.apply-head .tag { margin-bottom: 14px; }
.apply-head .section-headline { margin-bottom: 14px; }
.apply-head .section-sub { margin-bottom: 0; }

/* The file input. Browsers will not let the button itself be restyled beyond
   ::file-selector-button, so the control is treated as a dashed drop-well
   with the native button inside it — which also reads as "you can drop files
   here", because the input genuinely accepts a drop. */
.form-file {
  width: 100%;
  padding: 18px;
  border: 1.5px dashed var(--border-strong);
  border-radius: 10px;
  background: var(--bg2);
  font-size: 0.9375rem;
  font-family: inherit;
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
}
.form-file:hover { border-color: var(--osh-blue); background: var(--osh-blue-pale); }
.form-file:focus-visible {
  outline: none;
  border-color: var(--osh-blue);
  box-shadow: 0 0 0 3px rgba(11,83,155,0.16);
}
/* White on Carrot Curl, the site-wide button pairing — DESIGN-SYSTEM.md §2
   rule 1. This is a real button with a real label, not body copy. */
.form-file::file-selector-button {
  margin-right: 14px;
  padding: 9px 16px;
  border: 0;
  border-radius: 8px;
  background: var(--osh-orange);
  color: #fff;
  font-family: var(--font-heading);
  font-size: 0.875rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.2s;
}
.form-file::file-selector-button:hover { background: #D97812; }

.form-help {
  margin: 8px 0 0;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--text-muted);
}

/* What was actually chosen, written by main.js. Empty and invisible without
   JavaScript, which is correct — the browser's own "3 files selected" label
   is still there. */
.form-filelist {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}
.form-filelist:empty { margin: 0; }
.form-filelist li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 9px 14px;
  margin-bottom: 6px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.875rem;
  color: var(--ink);
}
.form-filelist li span { color: var(--text-muted); flex: 0 0 auto; }
.form-filelist li b {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* One file over the limit, flagged before the upload starts rather than after
   it has been sent and rejected. */
.form-filelist li.over {
  background: #FDECEC;
  border-color: #F3C4C4;
  color: #9B1C1C;
}
.form-filelist li.over span { color: #9B1C1C; }

.form-consent-note {
  margin: 0 0 18px;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--text-muted);
}

@media (max-width: 780px) {
  .apply-panel { padding: 30px 22px; }
  .vacancy-hero-actions .btn { flex: 1 1 100%; justify-content: center; }
  /* The label and value stop fitting on one line once the card is full
     width and the values are no longer clipped by a 300px aside. */
  .vacancy-facts li { flex-direction: column; align-items: flex-start; gap: 2px; }
  .vacancy-facts strong { text-align: left; }
}

/* =========================================
   HOME — SERVICE CARD POPUPS & "EXPLORE MORE" — 23 SEPTEMBER 2026
   Client direction: each "What we do" card opens a full-page popup about its
   topic, and an "Explore more" button sits centred under the cards. Built on
   the demo popup's shape (same backdrop, accent bar, close button) but sized
   to the viewport, with the photograph taking the left half.
   ========================================= */
.svc-action { display: flex; justify-content: center; padding: 48px 16px 0; }

html.svc-modal-lock, html.svc-modal-lock body { overflow: hidden; }

.svc-modal {
  position: fixed; inset: 0;
  z-index: 8990;               /* under the demo popup (9000), which it hands over to */
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.svc-modal[hidden] { display: none; }
.svc-modal-backdrop {
  position: absolute; inset: 0;
  background: rgba(3,43,68,0.72);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
}
.svc-modal-dialog {
  position: relative;
  width: 100%; max-width: 1180px;
  height: 100%; max-height: 760px;
  display: grid; grid-template-columns: 45% 1fr;
  background: var(--white);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: 0 30px 80px rgba(3,43,68,0.4);
  opacity: 0;
  transform: translateY(24px) scale(0.98);
  transition: opacity 0.35s var(--ease), transform 0.45s var(--ease);
}
.svc-modal-dialog::before {
  content: '';
  position: absolute; top: 0; left: 0; right: 0; z-index: 2;
  height: 5px;
  background: linear-gradient(90deg, var(--osh-orange), var(--osh-orange) 40%, var(--osh-blue));
}
.svc-modal.is-open .svc-modal-backdrop { opacity: 1; }
.svc-modal.is-open .svc-modal-dialog { opacity: 1; transform: none; }

.svc-modal-close {
  position: absolute; top: 18px; right: 18px; z-index: 3;
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: var(--osh-blue-pale);
  color: var(--ink);
  cursor: pointer;
  transition: background 0.2s, color 0.2s, transform 0.3s var(--ease);
}
.svc-modal-close svg { width: 18px; height: 18px; }
.svc-modal-close:hover { background: var(--osh-blue); color: #fff; transform: rotate(90deg); }
.svc-modal-close:focus-visible { outline: 3px solid var(--osh-cyan); outline-offset: 2px; }

.svc-modal-media { position: relative; min-height: 0; background: var(--osh-whale); }
.svc-modal-media picture,
.svc-modal-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.svc-modal-media-shade {
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(3,43,68,0.85) 0%, rgba(3,43,68,0.1) 55%);
}
.svc-modal-num {
  position: absolute; left: 32px; bottom: 28px;
  display: inline-block;
  padding: 8px 16px;
  border-radius: 100px;
  background: var(--osh-orange);
  color: var(--osh-whale);                /* §2 rule 3: whale ink on a carrot fill */
  font-size: 0.75rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
}

.svc-modal-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 64px 56px 48px;
}
.svc-modal-body .eyebrow { margin-bottom: 14px; }
.svc-modal-title {
  font-family: var(--font-heading);
  font-size: clamp(1.75rem, 3.2vw, 2.5rem);
  font-weight: 800; line-height: 1.1;
  color: var(--ink);
  margin: 0 0 20px;
}
.svc-modal-title:focus { outline: none; }
.svc-modal-text { font-size: 1rem; line-height: 1.7; color: var(--text-muted); margin: 0 0 14px; }
.svc-modal-points { list-style: none; padding: 0; margin: 26px 0 34px; display: grid; gap: 12px; }
.svc-modal-points li {
  display: flex; gap: 12px; align-items: flex-start;
  font-size: 0.9375rem; line-height: 1.55; color: var(--ink);
}
.svc-modal-points svg {
  flex-shrink: 0;
  width: 22px; height: 22px; padding: 4px; margin-top: 1px;
  border-radius: 50%;
  background: var(--osh-orange);
  color: var(--osh-whale);
}
.svc-modal-actions { display: flex; flex-wrap: wrap; gap: 12px; }

@media (max-width: 900px) {
  .svc-modal { padding: 0; }
  .svc-modal-dialog {
    max-height: none; border-radius: 0;
    display: block; overflow-y: auto; overscroll-behavior: contain;
  }
  .svc-modal-media { height: 38vh; }
  .svc-modal-num { left: 16px; bottom: 16px; }
  .svc-modal-body { overflow: visible; padding: 32px 16px 40px; }
  .svc-modal-close { top: 12px; right: 12px; }
}
@media (prefers-reduced-motion: reduce) {
  .svc-modal-backdrop, .svc-modal-dialog { transition: none; }
}

/* All the cards fit the screen: no pin, no sideways scroll — the row sits
   centred in the page (initSvcCards adds this class, 23 Sept 2026). */
.svc-track.svc-track--fit {
  width: 100%;
  justify-content: center;
  padding-left: 32px; padding-right: 32px;
  transform: none !important;
}
