/* ============================================================
   QedON theme — base stylesheet
   Everything is expressed in rem/em. Pixels appear only where a
   physical hairline is intended (1px borders) or where an SVG icon
   needs a device-pixel size.
   Contrast: every text/background pair below meets WCAG 2.2 AA.
   ============================================================ */

:root {
  /* --- colour (set from theme fields in base.html) --- */
  --q-navy: #003C5B;
  --q-navy-deep: #00293E;
  --q-blue: #0B5A82;
  --q-sky: #3DB3E3;          /* decorative only — 2.4:1 on white */
  --q-sky-pale: #8FD6F2;
  --q-ink: #0d1f2b;
  --q-body: #4b606e;
  --q-muted: #5F7180;
  --q-line: #dfe8ee;
  --q-tint: #f4f9fc;
  --q-white: #ffffff;
  --q-green: #0F7A57;
  --q-green-deep: #0B4A38;
  --q-mint: #8FF0D5;
  --q-error: #B3261E;
  --q-error-ink: #8C1D18;
  --q-success-ink: #14532A;

  /* --- type scale (fluid, rem based) --- */
  --q-fs-eyebrow: 0.8125rem;
  --q-fs-small:   0.875rem;
  --q-fs-body:    1.0625rem;
  --q-fs-lede:    clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);
  --q-fs-h4:      1.125rem;
  --q-fs-h3:      1.375rem;
  --q-fs-h2:      clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem);
  --q-fs-h1:      clamp(2rem, 1.5rem + 2.6vw, 3rem);
  --q-fs-display: clamp(1.625rem, 1.3rem + 1.7vw, 2.5rem);

  /* --- spacing scale --- */
  --q-1: 0.25rem;  --q-2: 0.5rem;   --q-3: 0.75rem;  --q-4: 1rem;
  --q-5: 1.25rem;  --q-6: 1.5rem;   --q-8: 2rem;     --q-10: 2.5rem;
  --q-12: 3rem;    --q-16: 4rem;

  --q-gutter: clamp(1.25rem, 4vw, 2.5rem);
  --q-section-y: clamp(2.5rem, 5vw, 4rem);
  --q-max: 78rem;

  --q-radius: 1.125rem;
  --q-radius-sm: 0.625rem;
  --q-radius-pill: 62.4375rem;

  --q-shadow-1: 0 0.25rem 0.75rem rgba(0, 60, 91, .06);
  --q-shadow-2: 0 1.25rem 2.5rem rgba(0, 60, 91, .18);

  --q-tap: 2.75rem;             /* 44px minimum target */
  --q-ease: cubic-bezier(.22, .61, .36, 1);
}

/* ---------- extra font weights ----------
   HubSpot's custom_fonts mechanism (theme.json + fonts/*/font.json) only ever
   generates one real @font-face per theme font field — whichever variant that
   field's "variant" is set to. Sora and Inter are each used at more weights
   than that single face covers (e.g. .q-header/.q-footer text at 400/500/700,
   .q-field label/.q-error at 600), so without these the browser would fake
   those weights by synthetically emboldening the one loaded face rather than
   using real glyphs. Manrope only ever appears at 800, so it needs no extras.
   Same WOFF2 files already uploaded under fonts/, just referenced directly.
   Sora-400 is deliberately not declared here even though the file exists —
   HubSpot's asset pipeline silently strips any manual @font-face whose
   (family, weight) matches a font.json variant's "default" entry (Sora's
   default is Regular/400), so the rule would be dead code. Sora text left at
   weight 400 by default just matches to the nearest real face (500) instead.

   Paths are root-relative rather than relative to this file — HubSpot serves
   the compiled/combined version of this stylesheet from a completely
   different hub_generated/template_assets URL, so a `../` path resolves
   against that bundle location, not qedon-theme/css/, and 404s. The actual
   files live under /hubfs/raw_assets/public/qedon-theme/fonts/ regardless of
   which domain (production or a HubSpot preview) serves the page. */
@font-face { font-family: 'Sora'; font-weight: 500; font-display: swap;
  src: url('/hubfs/raw_assets/public/qedon-theme/fonts/Sora/WEB/Sora-500.woff2') format('woff2'); }
@font-face { font-family: 'Sora'; font-weight: 700; font-display: swap;
  src: url('/hubfs/raw_assets/public/qedon-theme/fonts/Sora/WEB/Sora-700.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 400; font-display: swap;
  src: url('/hubfs/raw_assets/public/qedon-theme/fonts/Inter/WEB/Inter-400.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 500; font-display: swap;
  src: url('/hubfs/raw_assets/public/qedon-theme/fonts/Inter/WEB/Inter-500.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 600; font-display: swap;
  src: url('/hubfs/raw_assets/public/qedon-theme/fonts/Inter/WEB/Inter-600.woff2') format('woff2'); }

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { font-size: 100%; -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--q-white);
  color: var(--q-body);
  font-family: var(--q-font-body, 'Inter', system-ui, sans-serif);
  font-size: var(--q-fs-body);
  line-height: 1.6;
  /* clip, not hidden — hidden turns body into a scroll container, which silently
     breaks position:sticky on any descendant (e.g. the proposal page's top nav);
     clip prevents the same horizontal bleed without that side effect. */
  overflow-x: clip;
}
img, svg, video { display: block; max-width: 100%; height: auto; }
h1, h2, h3, h4 {
  font-family: var(--q-font-heading, 'Manrope', system-ui, sans-serif);
  color: var(--q-ink);
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.15;
  margin: 0 0 var(--q-4);
  text-wrap: balance;
}
h1 { font-size: var(--q-fs-h1); line-height: 1.08; letter-spacing: -.03em; }
h2 { font-size: var(--q-fs-h2); letter-spacing: -.03em; }
h3 { font-size: var(--q-fs-h3); letter-spacing: -.01em; }
h4 { font-size: var(--q-fs-h4); }
p  { margin: 0 0 var(--q-4); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }
ul, ol { margin: 0 0 var(--q-4); padding-left: 1.25em; }

a { color: var(--q-blue); text-decoration: underline; text-underline-offset: 0.1875rem; text-decoration-thickness: 0.0625rem; transition: color .22s ease; }
a:hover { color: var(--q-navy); }
.q-nounderline, a.q-btn, a.q-card-link, nav a { text-decoration: none; }

/* ---------- focus ---------- */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 0.1875rem solid var(--q-blue);
  outline-offset: 0.1875rem;
  border-radius: var(--q-radius-sm);
}
.q-on-dark :focus-visible { outline-color: var(--q-sky-pale); }

/* ---------- skip link ---------- */
.q-skip {
  position: fixed; left: 0.75rem; top: -5rem; z-index: 3000;
  background: var(--q-navy); color: #fff;
  padding: 0.875rem 1.375rem; border-radius: 0 0 var(--q-radius-sm) var(--q-radius-sm);
  font-family: var(--q-font-heading, 'Manrope', sans-serif); font-weight: 800;
  text-decoration: none; transition: top .18s ease;
}
.q-skip:focus { top: 0; color: #fff; }

.q-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- layout ---------- */
.q-shell { max-width: var(--q-max); margin-inline: auto; padding-inline: var(--q-gutter); }
.q-section { padding-block: var(--q-section-y); }
.q-section--tint { background: var(--q-tint); border-block: 1px solid #e3ecf2; }
/* ExpertEd's brand is green, not the site-wide blue -- its own light tint keeps
   "tint" sections on this one page consistent with that palette instead of
   clashing with the blue used everywhere else. */
.q-page-experted .q-section--tint { background: #F0F9F5; border-block-color: #D8EFE4; }
/* Course and bundle product pages are a focused, single-product page, not a wide
   marketing page -- their own narrower content width keeps text/cards from
   sprawling across a large screen, without touching the site-wide --q-max
   every other page uses.
   Headings and card padding elsewhere use vw-based clamp() sizing tuned for a
   full-width page -- inside this narrower column that still computes near its
   maximum on a wide monitor, so heading size and padding are scoped down here
   too, not just the width, otherwise text/cards look oversized for the column. */
.q-page-product {
  --q-max: 56rem;
  --q-fs-h1: clamp(1.75rem, 1.5rem + 1.2vw, 2.25rem);
  --q-fs-h2: clamp(1.375rem, 1.2rem + 0.6vw, 1.625rem);
}
.q-page-product .q-card { padding: 1.5rem; }
.q-page-product .q-card__icon { width: 2.75rem; height: 2.75rem; }
.q-page-product .q-stat__value { font-size: clamp(1.5rem, 1.3rem + 0.6vw, 1.875rem); }
/* The site-wide grid breakpoints (e.g. 18rem min for 3 columns) assume a full-width
   page and don't actually fit 3-4 columns inside this narrower 56rem shell once
   gaps are accounted for -- auto-fit was silently collapsing to fewer, wider
   columns than intended (e.g. 2 columns of ~396px instead of 3 of ~277px). */
.q-page-product .q-grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }
.q-page-product .q-grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)); }
/* Topics as wide horizontal rows (icon/number on the left, title+body on the
   right) instead of narrow vertical cards -- scoped to this one section via its
   anchor ID so Methodology/Certification below it keeps its normal card layout. */
#course-topics .q-grid--3 { grid-template-columns: 1fr; }
#course-topics .q-card {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  align-items: start; column-gap: 1.25rem;
}
/* q-card__meta (icon+number) spans both rows as one block, so its height comes
   from its own content, not from the taller title/body column next to it --
   without this, the number gets pushed down to match the title's row height
   instead of sitting snug under the icon. */
#course-topics .q-card__meta { grid-column: 1; grid-row: 1 / -1; display: flex; flex-direction: column; gap: 0.5rem; }
#course-topics .q-card__title { grid-column: 2; grid-row: 1; }
#course-topics .q-card p:not(.q-stat__value) { grid-column: 2; grid-row: 2; margin: 0; }
.q-section--navy { background: var(--q-navy); color: #D5E8F2; }
.q-section--navy h1, .q-section--navy h2, .q-section--navy h3 { color: #fff; }
.q-section--green { background: var(--q-green-deep); color: #E3FBF3; }
.q-section--green h1, .q-section--green h2, .q-section--green h3 { color: #fff; }

.q-grid { display: grid; gap: var(--q-6); }
ol.q-grid, ul.q-grid { list-style: none; margin: 0; padding: 0; }
.q-grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.q-grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); }
.q-grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }
.q-split { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center;
           grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.q-split--media-first > .q-split__media { order: -1; }
/* Grid items get an automatic min-width based on their content's intrinsic size —
   for a column holding an <img> that's roughly the image's natural width, so the
   column (and the image) stops shrinking well before the window is actually narrow
   enough, even though width:100%/max-width:100% is set on the image itself. */
.q-split > *, .q-grid > *, .q-case-study > *, .q-profile-grid > *, .q-hero-split__grid > * { min-width: 0; }

.q-eyebrow {
  font-family: var(--q-font-heading, 'Manrope', sans-serif);
  font-size: var(--q-fs-eyebrow); font-weight: 800;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--q-blue); margin: 0 0 var(--q-3);
}
.q-section--navy .q-eyebrow, .q-section--green .q-eyebrow { color: var(--q-sky-pale); }
.q-section--green .q-eyebrow { color: var(--q-mint); }
.q-lede { font-size: var(--q-fs-lede); line-height: 1.62; max-width: 42rem; }

/* ---------- buttons ---------- */
.q-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  min-height: 2.75rem; padding: 0.75rem 1.5rem;
  font-family: var(--q-font-heading, 'Manrope', sans-serif); font-weight: 800; font-size: 0.9375rem;
  border-radius: var(--q-radius-pill); border: 0.09375rem solid transparent;
  cursor: pointer; text-decoration: none;
  transition: background-color .24s var(--q-ease), color .24s var(--q-ease),
              border-color .24s var(--q-ease), transform .24s var(--q-ease);
}
.q-btn:hover { transform: translateY(-0.125rem); text-decoration: none; }
.q-btn--primary   { background: var(--q-navy); color: #fff; }
.q-btn--primary:hover { background: var(--q-navy-deep); color: #fff; }
.q-btn--secondary { background: #fff; color: var(--q-blue); border-color: var(--q-muted); } /* #c3d3de was 1.5:1 on white, failing the 3:1 non-text minimum */
.q-btn--secondary:hover { background: #eef6fb; border-color: var(--q-blue); color: var(--q-navy); }
.q-btn--on-dark   { background: var(--q-sky-pale); color: var(--q-navy); }
.q-btn--on-dark:hover { background: #B6E6F8; color: var(--q-navy); }
.q-btn--ghost-dark { background: transparent; color: #fff; border-color: rgba(255,255,255,.6); }
.q-btn--ghost-dark:hover { background: rgba(255,255,255,.12); color: #fff; }

/* ---------- cards ---------- */
.q-card {
  background: #fff; border: 1px solid var(--q-line);
  border-radius: var(--q-radius); padding: clamp(1.75rem, 3vw, 2.25rem);
  box-shadow: var(--q-shadow-1);
  transition: transform .28s var(--q-ease), box-shadow .28s var(--q-ease), border-color .28s var(--q-ease);
}
/* Lift only for cards that are actually links — a card that isn't clickable
   shouldn't look like it is. Scoped to .q-card-link (added only when a real
   href exists), not plain .q-card. */
.q-card-link:hover { transform: translateY(-0.375rem); border-color: #b9cfdd; }
/* Transitioning box-shadow directly between two very different shadow values
   (tight+faint to big+soft) doesn't read as a smooth fade in most browsers —
   crossfading a second shadow layer's opacity does, so the deepened shadow is
   its own layer that fades in rather than the shadow itself changing shape. */
.q-card-link { position: relative; }
.q-card-link::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: var(--q-shadow-2); opacity: 0; pointer-events: none;
  transition: opacity .32s var(--q-ease);
}
.q-card-link:hover::after, .q-card-link:focus-visible::after { opacity: 1; }
/* Wraps icon+number so a horizontal card layout (see #course-topics) can size
   this pair by its own content -- display:contents removes this wrapper from
   layout entirely by default, so every existing card looks exactly as before. */
.q-card__meta { display: contents; }
.q-card__icon {
  display: flex; align-items: center; justify-content: center;
  width: 3.25rem; height: 3.25rem; border-radius: 0.875rem;
  background: var(--q-navy); margin-bottom: var(--q-5);
}
.q-section--green .q-card__icon { background: var(--q-green); }
.q-card__title { margin: 0 0 var(--q-2); }
.q-card p { color: var(--q-body); margin: 0; }
.q-card__title { transition: color .28s ease; }
/* .q-section--navy/--green h1/h2/h3{color:#fff} is meant for headings sitting
   directly on that dark background — cards are always white regardless of the
   section colour, so without this override a card title on a navy/green
   section renders white-on-white and disappears. */
.q-card .q-card__title { color: var(--q-ink); }
.q-card-link:hover .q-card__title { color: var(--q-navy); }

/* ---------- timeline (feature-cards, layout="timeline") ---------- */
/* Connected numbered steps for sequential process content, as an alternative to the
   boxed card grid above — reserved for genuinely sequential content (e.g. "our
   process"), not value-prop/differentiator cards, which should stay as a grid. */
.q-timeline { display: flex; list-style: none; margin: 0; padding: 3.5rem 0 0; gap: 0; }
.q-timeline__step { flex: 1; position: relative; padding: 0 1.5rem 0 0; }
.q-timeline__step:last-child { padding-right: 0; }
.q-timeline__node {
  position: absolute; top: -3.5rem; left: 0;
  display: flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; border-radius: 50%;
  background: var(--q-navy); color: #fff;
  font-family: var(--q-font-heading, 'Manrope', sans-serif); font-weight: 800; font-size: 1.0625rem;
}
.q-section--green .q-timeline__node { background: var(--q-green); }
/* The connecting line sits at the node's vertical centre and spans from the node's
   right edge to the start of the next node — not full-width, so it doesn't run
   through the numbers themselves. */
.q-timeline__step:not(:last-child)::after {
  content: ""; position: absolute; top: -2.125rem; left: 2.75rem; right: 0;
  height: 2px; background: var(--q-line);
}
.q-timeline__step .q-card__title { margin-top: 0; }
.q-timeline__step p { color: var(--q-body); margin: 0; }
@media (max-width: 48rem) {
  .q-timeline { flex-direction: column; padding-top: 0; gap: 2rem; }
  .q-timeline__step { padding: 0 0 0 3.75rem; min-height: 2.75rem; }
  .q-timeline__step:last-child { padding-bottom: 0; }
  .q-timeline__node { top: 0; left: 0; }
  .q-timeline__step:not(:last-child)::after {
    top: 2.75rem; left: 1.375rem; right: auto; bottom: -2rem; width: 2px; height: auto;
  }
}

/* ---------- header ---------- */
.q-header {
  position: sticky; top: 0; z-index: 1000;
  background: #fff; border-bottom: 1px solid #dbe4ea;
  font-family: var(--q-font-ui, 'Sora', sans-serif);
}
.q-header__bar {
  max-width: var(--q-max); margin-inline: auto;
  padding: 0.625rem var(--q-gutter);
  display: flex; align-items: center; justify-content: space-between; gap: 1.25rem;
}
.q-header__logo { display: inline-flex; align-items: center; padding-block: 0.5rem; flex: none; }
.q-header__logo img { width: auto; height: 3.25rem; }

.q-nav > ul { display: flex; align-items: center; gap: 0.25rem; list-style: none; margin: 0; padding: 0; }
/* align-items:stretch (not center) so the link and its dropdown-arrow button are
   forced to the exact same height — otherwise their separate border-bottoms
   (used to underline the active item) can end up a pixel or two out of line. */
.q-nav li { position: relative; display: flex; align-items: stretch; }
.q-nav a {
  display: flex; align-items: center; min-height: var(--q-tap);
  padding: 0.875rem 0.75rem; font-size: 0.9375rem; font-weight: 500; color: #31454f;
  border-bottom: 0.1875rem solid transparent;
  transition: color .22s ease, border-bottom-color .22s ease;
}
/* Hover now previews the same underline the active item shows permanently —
   same mechanic, lighter weight — instead of hover being just a colour
   change while active is bold+underlined, which read as two unrelated
   treatments rather than two strengths of one system. Scoped to the whole
   <li> (not just <a>:hover) so moving the mouse onto the dropdown-arrow
   button shows the same full-width underline as hovering the link text,
   rather than only half of it lighting up. */
.q-nav li:hover > a { color: var(--q-blue); border-bottom-color: var(--q-blue); }
.q-nav li:hover > .q-nav__toggle { border-bottom-color: var(--q-blue); }
.q-nav [aria-current="page"] { color: var(--q-navy); font-weight: 700; border-bottom-color: var(--q-blue); }
/* Pin the active item's colour so hovering it doesn't flip it from
   active-navy to hover-blue — :hover is otherwise slightly more specific
   than the plain [aria-current] rule above and would win that fight. */
.q-nav li:hover > [aria-current="page"] { color: var(--q-navy); }
.q-nav__toggle {
  display: flex; align-items: center; justify-content: center;
  width: 2.125rem; min-height: var(--q-tap); padding: 0 0.75rem 0 0.125rem;
  background: none; border: none; border-bottom: 0.1875rem solid transparent;
  color: #31454f; cursor: pointer; font-size: 0.6875rem;
  transition: color .22s ease, border-bottom-color .22s ease;
}
.q-nav [aria-current="page"] + .q-nav__toggle { border-bottom-color: var(--q-blue); }
.q-nav__submenu {
  position: absolute; top: 100%; left: 0; min-width: 15rem;
  background: #fff; border: 1px solid #cfdbe3; border-radius: 0.875rem;
  box-shadow: 0 1.125rem 2.75rem rgba(0, 60, 91, .2);
  list-style: none; margin: 0; padding: 0.5rem; z-index: 30;
}
.q-nav__submenu li { display: block; }
.q-nav__submenu a {
  min-height: var(--q-tap); padding: 0.6875rem 0.75rem;
  border-radius: 0.5rem; border-bottom: none; font-size: 0.90625rem; color: #22323d;
  transition: background-color .22s ease, color .22s ease;
}
.q-nav__submenu a:hover { background: #eef6fb; color: var(--q-navy); }

.q-header__actions { display: flex; align-items: center; gap: 0.5rem; }
.q-iconbtn {
  display: flex; align-items: center; justify-content: center;
  width: var(--q-tap); height: var(--q-tap);
  background: none; border: none; border-radius: var(--q-radius-sm); cursor: pointer;
  transition: background-color .22s ease;
}
.q-iconbtn:hover { background: var(--q-tint); }

/* ---------- header search ---------- */
.q-search { position: relative; }
.q-search__panel {
  position: absolute; top: calc(100% + 0.5rem); right: 0; z-index: 20;
  display: flex; gap: 0.5rem;
  min-width: 17rem; max-width: min(22rem, calc(100vw - 2rem));
  background: #fff; border-radius: var(--q-radius-sm);
  box-shadow: var(--q-shadow-2); padding: 0.75rem;
}
/* display:flex above has the same specificity as the browser's [hidden] rule and
   comes later, so it silently wins and the panel never actually hides — same
   fix already used for .q-mobile-nav__sub and .q-alert. */
.q-search__panel[hidden] { display: none; }
.q-search__panel .q-input { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
/* The panel is small, so the sitewide focus ring's gap (outline-offset) reads
   as a second concentric border here more than it does on a full-width field —
   tighten just this instance rather than the shared focus style. */
.q-search__panel .q-input:focus-visible { outline-offset: 0; }

.q-burger {
  display: none; align-items: center; gap: 0.5rem;
  min-height: var(--q-tap); padding-inline: 0.875rem;
  background: var(--q-navy); border: none; border-radius: var(--q-radius-sm); color: #fff;
  font-family: var(--q-font-heading, 'Manrope', sans-serif); font-weight: 800; font-size: 0.875rem;
  cursor: pointer;
}
.q-mobile-nav {
  display: none; border-top: 1px solid #dbe4ea; background: #fff;
  max-height: calc(100dvh - 4.25rem); overflow-y: auto;
  padding: 0.5rem var(--q-gutter) 1.5rem;
}
.q-mobile-nav[data-open="true"] { display: block; }
.q-mobile-nav ul { list-style: none; margin: 0; padding: 0; }
.q-mobile-nav__row { display: flex; align-items: center; border-bottom: 1px solid #eef2f5; }
.q-mobile-nav__row > a {
  flex: 1; display: flex; align-items: center; min-height: 3.25rem;
  padding: 0.75rem 0.25rem; font-size: 1.0625rem; font-weight: 600; color: #12293a;
  transition: color .22s ease;
}
/* Explicit, since these no longer inherit an underline-on-hover from the
   generic nav a:hover rule removed above (that rule was also double-firing
   with the desktop nav's new border-bottom hover treatment). */
.q-mobile-nav__row > a:hover, .q-mobile-nav__sub a:hover { text-decoration: underline; text-underline-offset: 0.1875rem; }
.q-mobile-nav__toggle {
  width: 3.25rem; height: 3.25rem; background: none; border: none;
  color: var(--q-blue); font-size: 0.875rem; cursor: pointer;
}
.q-mobile-nav__sub { margin: 0 0 0.75rem; padding-left: 0.875rem; border-left: 0.125rem solid #cfe6f4; }
.q-mobile-nav__sub[hidden] { display: none; }
.q-mobile-nav__sub a { display: flex; align-items: center; min-height: 3rem; padding: 0.625rem 0.25rem; font-size: 0.96875rem; color: #33454f; transition: color .22s ease; }

@media (max-width: 68.6875rem) {
  .q-nav, .q-header__cta { display: none !important; }
  .q-burger { display: inline-flex; }
}

/* ---------- footer ---------- */
.q-footer {
  background: var(--q-navy); color: #C3D3DC;
  padding: 2.75rem var(--q-gutter) 1.875rem;
  font-family: var(--q-font-ui, 'Sora', sans-serif);
  /* Without this, a navy content section directly above the footer (both the
     same solid colour) bleeds straight into it with no visible seam at all. */
  border-top: 1px solid rgba(255, 255, 255, .14);
}
.q-footer a { color: #C3D3DC; text-decoration: none; transition: color .22s ease; }
.q-footer a:hover { color: var(--q-sky-pale); text-decoration: underline; text-underline-offset: 0.1875rem; }
.q-footer h2 { font-size: 0.84375rem; font-weight: 700; color: #fff; margin: 0 0 0.625rem; letter-spacing: 0; }
.q-footer ul { list-style: none; margin: 0; padding: 0; font-size: 0.875rem; }
.q-footer li a { display: flex; align-items: center; min-height: 2.5rem; }
.q-footer__cols { display: flex; flex-wrap: wrap; gap: clamp(1.5rem, 4vw, 2.5rem); justify-content: space-between;
                  padding-bottom: var(--q-8); border-bottom: 1px solid rgba(255,255,255,.28); }
.q-footer__social { display: flex; gap: 0.375rem; }
.q-footer__social a { display: flex; align-items: center; justify-content: center; width: var(--q-tap); height: var(--q-tap); border-radius: var(--q-radius-sm); }

/* ---------- accordion (native details/summary) ---------- */
summary::-webkit-details-marker { display: none; }
summary::marker { content: ''; }

/* ---------- forms ---------- */
.q-field { margin-bottom: var(--q-5); }
.q-field label, .q-label {
  display: block; font-size: 0.90625rem; font-weight: 600; color: #22333e; margin-bottom: 0.375rem;
}
.q-req { color: var(--q-error-ink); }
.q-input, .q-textarea {
  width: 100%; min-height: 3rem;
  font-family: inherit; font-size: 1rem; color: var(--q-ink);
  background: #f7fafc; border: 0.09375rem solid var(--q-muted); border-radius: 0.8125rem;
  padding: 0.875rem 1rem;
  transition: border-color .18s ease, background-color .18s ease;
}
.q-textarea { min-height: 8rem; resize: vertical; }
.q-input::placeholder, .q-textarea::placeholder { color: var(--q-muted); opacity: 1; }
.q-input:focus-visible, .q-textarea:focus-visible { border-color: var(--q-blue); background: #fff; }
/* The footer newsletter field sits on a translucent-white-over-navy background —
   var(--q-muted) is only readable on light backgrounds (1.7:1 here). Its inline
   background (module.html) always wins over the shared :focus-visible rule above
   (inline style beats any class selector), so it never actually turns white on
   focus — text must stay light at all times, including focused, or it goes dark
   text on a dark background. */
.q-footer .q-input { color: #fff; }
.q-footer .q-input::placeholder { color: var(--q-sky-pale); opacity: 1; }
.q-input[aria-invalid="true"], .q-textarea[aria-invalid="true"] {
  border-color: var(--q-error); border-width: 0.125rem; background: #fff7f6;
}
.q-hint { display: block; font-size: 0.84375rem; color: var(--q-body); margin-top: 0.3125rem; }
.q-error { display: block; font-size: 0.84375rem; color: var(--q-error); font-weight: 600; margin-top: 0.375rem; }
.q-error:empty { display: none; }
.q-alert { border-radius: 0.75rem; padding: 1rem 1.125rem; margin-bottom: var(--q-5); }
.q-alert--error { border: 0.125rem solid var(--q-error); background: #fff5f4; color: var(--q-error-ink); }
.q-alert--success { border: 0.125rem solid #1B6B3A; background: #f2fbf5; color: var(--q-success-ink); font-weight: 600; }
.q-alert[hidden] { display: none; }
.q-alert ul { margin: 0.5rem 0 0; padding-left: 1.25rem; }
.q-alert a { color: inherit; }

/* ---------- carousel (native scroll-snap, no JS dependency) ---------- */
.q-rail { overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory;
          border-radius: var(--q-radius); outline-offset: 0.25rem; scrollbar-width: thin; }
/* Top padding matches bottom's -- .q-rail has overflow-y:hidden (needed to avoid
   a stray vertical scrollbar on the horizontal carousel), so anything a card
   renders above its own box, like a shadow, needs real clearance up there or it
   gets hard-clipped instead of fading out softly. */
.q-rail ul { display: flex; gap: var(--q-5); list-style: none; margin: 0; padding: var(--q-4) 0; }
.q-rail li { flex: 0 0 auto; width: min(20.5rem, 80vw); scroll-snap-align: start; }
/* The default card shadow is a faint navy tint (barely visible even on white) --
   against a solid dark section it disappears entirely, which is what was
   reading as "flat". Dark sections get a real black-based shadow instead, plus
   a stronger lift on hover, so the cards actually look like they're floating.
   Sized to fit inside the padding above, so it isn't clipped by overflow-y. */
.q-section--navy .q-rail .q-card, .q-section--green .q-rail .q-card {
  border-color: transparent; box-shadow: 0 0.625rem 1.25rem rgba(0,0,0,.35);
}
.q-section--navy .q-rail .q-card-link:hover, .q-section--green .q-rail .q-card-link:hover {
  border-color: transparent; transform: translateY(-0.25rem);
}
.q-section--navy .q-rail .q-card-link::after, .q-section--green .q-rail .q-card-link::after {
  box-shadow: 0 0.875rem 1.75rem rgba(0,0,0,.45);
}
.q-rail__controls { display: flex; align-items: center; justify-content: space-between;
                    gap: var(--q-4); flex-wrap: wrap; margin-top: var(--q-5); }
.q-rail__nav { display: flex; gap: 0.5rem; }
.q-rail__btn { display: flex; align-items: center; justify-content: center;
               width: var(--q-tap); height: var(--q-tap); border-radius: var(--q-radius-pill);
               background: transparent; border: 0.09375rem solid rgba(255,255,255,.6); color: #fff; cursor: pointer;
               transition: background-color .22s ease; }
.q-rail__btn:hover { background: rgba(255,255,255,.12); }

/* ---------- logo strip ---------- */
.q-logos { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: clamp(3rem, 7vw, 5rem); row-gap: 2.5rem; }
/* Height-only cap, no max-width: a wide wordmark (e.g. a 5.8:1 aspect ratio
   logo) needs to render wider at the same height as a squarer one -- capping
   width too just silently shrinks the effective height of wide logos below
   this value, which is what made the first sizing fix not actually work. */
.q-logos img { max-height: 7.5rem; width: auto; }
.q-logos__tile { display: flex; align-items: center; justify-content: center; background: var(--q-navy); border-radius: 0.5rem; padding: 0.75rem 1.25rem; }
.q-logos__tile img { max-height: 4rem; }
@media (max-width: 35rem) {
  .q-logos { justify-content: center; }
}

/* ---------- stats ---------- */
.q-stat__value { font-family: var(--q-font-heading, 'Manrope', sans-serif); font-size: clamp(2rem, 1.6rem + 1.8vw, 3rem); font-weight: 800; line-height: 1; margin: 0; }
.q-stat__label { font-size: 0.9375rem; margin: 0.5rem 0 0; }
.q-section--navy .q-stat__value { color: #fff; }
.q-section--navy .q-stat__label { color: #D5E8F2; }

/* ---------- hero entrance (one-shot, staggered on page load) ---------- */
/* Finishes in well under a second and never repeats, so it doesn't trigger the
   WCAG 2.2.2 pause/stop requirement that a looping effect would. Respects
   prefers-reduced-motion via the global rule below like everything else. */
@keyframes q-hero-in { from { opacity: 0; transform: translateY(0.9rem); } to { opacity: 1; transform: none; } }
.q-hero-in { animation: q-hero-in .6s var(--q-ease) both; }

/* Decorative background shapes: drift/settle once over ~4.5s then hold still.
   Deliberately finite (animation-iteration-count defaults to 1, fill-mode
   both) rather than a continuous loop — motion under 5s and not repeating
   doesn't trigger the WCAG 2.2.2 pause/stop requirement a true loop would. */
.q-hero-blobs { position: absolute; inset: 0; overflow: hidden; z-index: 0; pointer-events: none; }
.q-hero-blob {
  position: absolute; border-radius: 50%; filter: blur(2.5rem);
  animation-duration: 4.5s; animation-timing-function: cubic-bezier(.22, 1.24, .38, 1); animation-fill-mode: both;
}
.q-hero-blob--a {
  top: -12%; right: -8%; width: 26rem; height: 26rem;
  background: radial-gradient(circle, rgba(61, 179, 227, .32), transparent 70%);
  animation-name: q-hero-blob-a;
}
.q-hero-blob--b {
  bottom: -18%; left: -6%; width: 20rem; height: 20rem;
  background: radial-gradient(circle, rgba(0, 60, 91, .16), transparent 70%);
  animation-name: q-hero-blob-b; animation-delay: .15s;
}
@keyframes q-hero-blob-a {
  0%   { opacity: 0; transform: translate(3%, -4%) scale(.85); }
  45%  { opacity: 1; transform: translate(-1.5%, 1.5%) scale(1.06); }
  72%  { opacity: 1; transform: translate(.6%, -.6%) scale(.98); }
  100% { opacity: 1; transform: translate(0, 0) scale(1); }
}
.q-hero-has-blobs { position: relative; overflow: hidden; }
.q-hero-has-blobs .q-shell { position: relative; z-index: 1; }
/* Reuses the same one-shot blob glow as the hero, clipped to the CTA panel's
   own rounded corners rather than the whole section. The panel is much
   shorter than a full hero, so the hero's blob sizes/positions would mostly
   clip away — smaller, repositioned, and brighter so they actually show. */
.q-cta-panel { position: relative; overflow: hidden; }
.q-cta-panel .q-hero-blob--a {
  top: -35%; right: -6%; width: 16rem; height: 16rem;
  background: radial-gradient(circle, rgba(143, 214, 242, .55), transparent 70%);
}
.q-cta-panel .q-hero-blob--b {
  bottom: -40%; left: -4%; width: 13rem; height: 13rem;
  background: radial-gradient(circle, rgba(143, 214, 242, .32), transparent 70%);
}
@keyframes q-hero-blob-b {
  0%   { opacity: 0; transform: translate(-3%, 4%) scale(.85); }
  45%  { opacity: 1; transform: translate(1.5%, -1.5%) scale(1.08); }
  72%  { opacity: 1; transform: translate(-.6%, .6%) scale(.97); }
  100% { opacity: 1; transform: translate(0, 0) scale(1); }
}

/* ---------- hero-cover ---------- */
.q-hero-cover { position: relative; overflow: hidden; padding-block: clamp(3rem, 6vw, 4.5rem); }
.q-hero-cover--navy { background: var(--q-navy); }
.q-hero-cover--green { background: var(--q-green-deep); }
.q-hero-cover__media { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.q-hero-cover__scrim { position: absolute; inset: 0; }
/* Stops at .97/.88/.25 so text at the left, where the copy column sits, keeps AA contrast
   even over a pure-white photo — verified in accessibility-notes.md. Do not soften these. */
.q-hero-cover--navy .q-hero-cover__scrim { background: linear-gradient(90deg, rgba(0,60,91,.97) 0%, rgba(0,60,91,.88) 50%, rgba(0,60,91,.25) 100%); }
.q-hero-cover--green .q-hero-cover__scrim { background: linear-gradient(90deg, rgba(11,74,56,.97) 0%, rgba(11,74,56,.88) 50%, rgba(11,74,56,.25) 100%); }
.q-hero-cover__inner { position: relative; }
.q-hero-cover__content { max-width: 40rem; }
.q-hero-cover--center .q-hero-cover__content { max-width: 44rem; margin-inline: auto; text-align: center; }
.q-hero-cover h1, .q-hero-cover h2 { color: #fff; }
.q-hero-cover__eyebrow { color: var(--q-sky-pale); }
.q-hero-cover--green .q-hero-cover__eyebrow { color: var(--q-mint); }
.q-hero-cover__lede { color: #E8F4FB; margin-top: 1.5rem; }
.q-hero-cover--green .q-hero-cover__lede { color: #E3FBF3; }
.q-hero-cover__buttons { display: flex; gap: 0.875rem; margin-top: 2rem; flex-wrap: wrap; }
.q-hero-cover--center .q-hero-cover__buttons { justify-content: center; }
.q-hero-cover__proof { display: flex; flex-wrap: wrap; gap: 0.75rem 1.75rem; list-style: none; margin: 2.25rem 0 0; padding: 0; font-size: 0.9375rem; color: #D5E8F2; }
.q-hero-cover--center .q-hero-cover__proof { justify-content: center; }
.q-hero-cover__proof li { display: flex; align-items: center; gap: 0.5rem; }
/* Underlined at rest (not just on hover/focus) — it sits in a plain-text proof-points
   list, so without a permanent underline there's no way to tell it's a link at all. */
.q-hero-cover__proof-link { color: inherit; text-decoration: underline; text-underline-offset: 0.1875rem; }
/* The global a:hover sets navy text, invisible against this hero's navy
   background -- this link sits on dark, so it needs its own hover colour. */
.q-hero-cover__proof-link:hover { color: var(--q-sky-pale); }

/* ---------- coming soon ---------- */
.q-coming-soon {
  position: relative; overflow: hidden;
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: clamp(2rem, 6vw, 4rem) var(--q-gutter); text-align: center;
  background: linear-gradient(135deg, var(--q-navy-deep) 0%, var(--q-navy) 50%, var(--q-blue) 100%);
}
.q-coming-soon__inner { position: relative; z-index: 1; max-width: 34rem; margin-inline: auto; }
.q-coming-soon__logo { height: 2.75rem; width: auto; margin: 0 auto 2.5rem; }
.q-coming-soon__heading {
  font-family: var(--q-font-heading, 'Manrope', system-ui, sans-serif);
  font-size: var(--q-fs-h1);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1.08;
  text-wrap: balance;
  color: #fff;
  margin: 0 0 1rem;
}
.q-coming-soon .q-lede { margin: 0 auto; }
/* Explicit colour needed — without it this inherits body's default text colour
   (meant for light backgrounds), which is only 1.78:1 against this navy gradient. */
.q-coming-soon__contact { margin-top: 2rem; font-size: 0.9375rem; color: #D5E8F2; }
.q-coming-soon__contact a { color: var(--q-sky-pale); }

/* ---------- password prompt (native @hubspot/password_prompt) ---------- */
/* Targeted by element type, not HubSpot's internal class names, since this module's
   markup can't be inspected without a live published page to check — type selectors
   work regardless of what wrapper classes HubSpot actually renders. */
/* Default/fallback text colour for this wrapper — HubSpot's native error message
   ("Sorry, please try again.") is a bare text node with no element/class of its
   own, sitting inside a HubSpot-generated span with its own id
   (#hs_cos_wrapper_password_prompt_). That id-selector specificity can beat a
   plain class rule regardless of source order, which is why an earlier attempt
   at this (color, no !important) never actually reached the text — !important
   here guarantees this wins. Elements below (label/input/button) all set their
   own explicit colour and are unaffected either way. */
.q-password-prompt { margin-top: 2.5rem; color: #FFB4B4 !important; }
/* The error text has no element of its own to put a margin-bottom on, so the gap
   below it is set here, on the form that follows it — otherwise it sits flush
   against the input's top edge with no breathing room. */
.q-password-prompt form { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; margin-top: 1rem; }
.q-password-prompt label { display: block; width: 100%; text-align: left; max-width: 20rem; margin: 0 auto 0.375rem; font-family: var(--q-font-ui); font-size: 0.875rem; color: #D5E8F2; }
.q-password-prompt input[type="password"],
.q-password-prompt input[type="text"] {
  flex: 1 1 16rem; max-width: 20rem; min-height: 3rem; padding: 0.875rem 1rem;
  border-radius: 0.8125rem; border: 0.09375rem solid rgba(255,255,255,.55);
  background: rgba(255,255,255,.1); color: #fff; font-family: inherit; font-size: 1rem;
}
.q-password-prompt input[type="password"]::placeholder,
.q-password-prompt input[type="text"]::placeholder { color: var(--q-sky-pale); opacity: 1; }
.q-password-prompt input[type="password"]:focus-visible,
.q-password-prompt input[type="text"]:focus-visible { outline: 2px solid var(--q-sky-pale); outline-offset: 2px; }
.q-password-prompt input[type="submit"],
.q-password-prompt button {
  flex: none; min-height: 3rem; padding: 0.875rem 1.75rem; border: none; border-radius: 999px;
  background: var(--q-sky-pale); color: var(--q-navy); font-family: var(--q-font-ui);
  font-weight: 700; font-size: 1rem; cursor: pointer;
}
.q-password-prompt input[type="submit"]:hover,
.q-password-prompt button:hover { background: #B6E6F8; }
/* Errors ("incorrect password") often render as plain unstyled text by default —
   force it to a legible, clearly-an-error colour on this dark background. */
.q-password-prompt [class*="error" i], .q-password-prompt [class*="incorrect" i] {
  width: 100%; margin-top: 0.5rem; color: #FFB4B4; font-size: 0.875rem;
}

/* ---------- native HubSpot form embeds ---------- */
/* HubSpot's newer form renderer reads its own 40px content padding from a CSS custom
   property (--hsf-background__padding) via a selector more specific than a plain class
   override can beat — so override the variable itself, not the padding property.
   Redundant with .q-card's own padding wherever a native form sits inside one. */
.q-card .hs-form-html { --hsf-background__padding: 0px; }

/* ---------- maintenance overlay ---------- */
/* Real header/main/footer keep rendering server-side untouched — this just covers
   them client-side, so the maintenance toggle can never affect module content mapping. */
.q-maintenance-overlay.q-coming-soon { display: none; }
body.q-maintenance-active .q-maintenance-overlay.q-coming-soon { display: flex; position: fixed; inset: 0; z-index: 9999; }
body.q-maintenance-active > a.q-skip,
body.q-maintenance-active header.q-header,
body.q-maintenance-active #main,
body.q-maintenance-active footer.q-footer { display: none; }

/* ---------- hero-split ---------- */
/* Two hard-edged panels rather than hero-cover's single photo-with-scrim treatment —
   the point of this hero is to look distinct from hero-cover while keeping the same
   "photo + dark panel" language. The stat card straddles the seam between panels. */
.q-hero-split { position: relative; }
.q-hero-split--navy { background: var(--q-navy); }
.q-hero-split--green { background: var(--q-green-deep); }
.q-hero-split__grid { position: relative; display: grid; grid-template-columns: minmax(24rem, 54%) 1fr; min-height: clamp(28rem, 54vw, 38rem); }
.q-hero-split__panel {
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(3rem, 6vw, 4.5rem) clamp(1.5rem, 4vw, 3rem) clamp(3rem, 6vw, 4.5rem)
           max(var(--q-gutter), calc((100vw - var(--q-max)) / 2 + var(--q-gutter)));
}
.q-hero-split h1, .q-hero-split h2 { color: #fff; }
.q-hero-split .q-eyebrow { color: var(--q-sky-pale); }
.q-hero-split--green .q-eyebrow { color: var(--q-mint); }
.q-hero-split__lede { color: #E8F4FB; margin-top: 1.5rem; }
.q-hero-split--green .q-hero-split__lede { color: #E3FBF3; }
.q-hero-split__buttons { display: flex; gap: 0.875rem; margin-top: 2rem; flex-wrap: wrap; }
.q-hero-split__proof { display: flex; flex-wrap: wrap; gap: 0.75rem 1.75rem; list-style: none; margin: 2.25rem 0 0; padding: 0; font-size: 0.9375rem; color: #D5E8F2; }
.q-hero-split__proof li { display: flex; align-items: center; gap: 0.5rem; }
.q-hero-split__media { position: relative; overflow: hidden; clip-path: url(#q-hero-split-curve); }
.q-hero-split__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 68.6875rem) {
  .q-hero-split__grid { grid-template-columns: 1fr; min-height: 0; }
  .q-hero-split__panel { padding: clamp(2.5rem, 6vw, 3.5rem) var(--q-gutter) 2rem; }
  /* Panels stack top/bottom below this width, so a curve meant for a side-by-side
     vertical boundary no longer makes sense here -- back to a plain rectangle. */
  .q-hero-split__media { min-height: 16rem; clip-path: none; }
}
/* On phone-sized screens the photo is competing with the text for very
   little payoff — drop it so the hero is copy-first and loads lighter. */
@media (max-width: 35rem) {
  .q-hero-split__media { display: none; }
}

/* ---------- case study feature ---------- */
.q-case-study { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); background: var(--q-tint); border: 1px solid var(--q-line); border-radius: var(--q-radius); overflow: hidden; }
.q-case-study__media { min-height: 16rem; }
.q-case-study__media img { width: 100%; height: 100%; object-fit: cover; }
.q-case-study__body { padding: clamp(1.75rem, 3.5vw, 2.75rem); }
.q-case-study__stats { display: flex; flex-wrap: wrap; gap: 1.5rem; margin: 0 0 1.25rem; }
.q-case-study__quote { margin: 1.25rem 0; padding-left: 1.125rem; border-left: 0.1875rem solid var(--q-sky); }
.q-case-study__quote p { font-style: italic; color: var(--q-ink); font-weight: 500; }
.q-case-study__quote footer { font-size: 0.8125rem; color: var(--q-muted); margin-top: 0.5rem; }
.q-case-study__specs { border-top: 1px solid var(--q-line); margin: 1.25rem 0 0; }
.q-case-study__specs > div { display: flex; justify-content: space-between; gap: 1rem; padding: 0.6875rem 0; border-bottom: 1px solid var(--q-line); font-size: 0.84375rem; }
.q-case-study__specs dt { color: var(--q-muted); margin: 0; }
.q-case-study__specs dd { color: var(--q-ink); font-weight: 700; text-align: right; margin: 0; }

/* ---------- video cards ---------- */
.q-video-card { display: block; border-radius: var(--q-radius); overflow: hidden; background: #fff; border: 1px solid var(--q-line); text-decoration: none; transition: transform .28s var(--q-ease), box-shadow .28s var(--q-ease); }
.q-video-card:hover { transform: translateY(-0.375rem); box-shadow: var(--q-shadow-2); }
.q-video-card--pending { cursor: default; }
.q-video-card--pending:hover { transform: none; box-shadow: none; }
.q-video-card__media { position: relative; display: block; aspect-ratio: 16 / 10; background: var(--q-navy); }
.q-video-card__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The play circle is near-opaque so its icon stays high-contrast regardless of the photo beneath it. */
.q-video-card__play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 4.25rem; height: 4.25rem; border-radius: 50%; background: rgba(255,255,255,.94);
  display: flex; align-items: center; justify-content: center; color: var(--q-navy);
  transition: transform .28s var(--q-ease); }
.q-video-card:hover .q-video-card__play { transform: translate(-50%, -50%) scale(1.06); }
.q-video-card__caption { display: block; padding: 1rem 1.125rem;
  color: var(--q-ink); font-family: var(--q-font-ui, 'Sora', sans-serif); font-weight: 600; font-size: 0.9375rem; }

/* ---------- profile cards ---------- */
/* Fixed-size tracks (not 1fr) plus justify-content:center: with few items
   (e.g. 2 founders) the row clusters and centers instead of CSS Grid's
   default behaviour of stretching each column to fill the full row width,
   which is what left two narrow cards stranded far apart either side. */
/* flex-start, not center — centering within the full section width floated the
   cluster away from the heading above it, which sits at the left edge. */
/* align-items:start, not the grid default of stretch — otherwise every card
   in a row is forced to match the height of whichever one has the longest
   wrapped text, leaving shorter cards with dead space at the bottom. */
/* Back to the grid default (stretch), not align-items:start — at this
   smaller card size, matching height across a row reads better than
   letting cards differ in height whenever caption text wraps differently. */
.q-profile-grid { display: grid; gap: var(--q-6); justify-content: flex-start; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 15rem)); }
.q-profile-card { text-align: center; max-width: 17rem; margin-inline: auto; width: 100%; padding: 1.75rem 1.25rem; }
/* Below ~2 columns' worth of width, the fixed 15rem track sits flush left
   (flex-start) instead of centered — fine for multi-column rows, but a lone
   card on mobile reads as mis-aligned. A full-width track lets the card's own
   margin-inline:auto centre it instead. */
@media (max-width: 35rem) {
  .q-profile-grid { grid-template-columns: 1fr; justify-content: center; gap: var(--q-2); }
  /* The card's desktop padding (1.75rem top/bottom) plus the grid gap was
     designed for team-photo cards with a name + caption + link. Stacked to
     one column on mobile, a sparser card (e.g. just a flag + country name,
     no caption) reads as mostly empty space between entries -- tighten it. */
  .q-profile-card { padding-top: 1rem; padding-bottom: 1rem; }
}
.q-profile-card__img { margin: 0 auto 1.25rem; box-shadow: var(--q-shadow-1); }
.q-profile-card__img--circle { width: 8.5rem; height: 8.5rem; border-radius: 50%; object-fit: cover; }
.q-profile-card__img--square { width: 5.5rem; height: 5.5rem; border-radius: 0.625rem; object-fit: contain; background: #fff; padding: 0.5rem; }
.q-profile-card__name { font-family: var(--q-font-heading, 'Manrope', sans-serif); font-weight: 800; font-size: 1.0625rem; margin: 0; }
.q-profile-card__caption { font-size: 0.84375rem; color: var(--q-muted); margin: 0.25rem 0 0; }
.q-profile-card__link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; margin-top: 0.75rem;
  color: var(--q-blue); border: 1px solid var(--q-line); border-radius: 50%;
  transition: color .22s ease, border-color .22s ease, background-color .22s ease;
}
.q-profile-card__link:hover { color: #fff; background: var(--q-blue); border-color: var(--q-blue); }
.q-section--navy .q-profile-card, .q-section--green .q-profile-card { background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.2); border-radius: var(--q-radius); backdrop-filter: blur(12px); }
.q-section--navy .q-profile-card__name, .q-section--green .q-profile-card__name { color: #fff; }
.q-section--navy .q-profile-card__caption, .q-section--green .q-profile-card__caption { color: #C3D3DC; }
.q-section--navy .q-profile-card__link, .q-section--green .q-profile-card__link { color: var(--q-sky-pale); border-color: rgba(255,255,255,.35); }
.q-section--navy .q-profile-card__link:hover, .q-section--green .q-profile-card__link:hover { color: var(--q-navy); background: var(--q-sky-pale); border-color: var(--q-sky-pale); }

/* ---------- motion ---------- */
.q-reveal { opacity: 0; transform: translateY(0.75rem); transition: opacity .45s ease, transform .45s var(--q-ease); }
.q-reveal.is-in { opacity: 1; transform: none; }
/* If JS never runs, nothing may stay hidden. */
.no-js .q-reveal, html:not(.q-js) .q-reveal { opacity: 1; transform: none; }
/* Cards in a grid each carry .q-reveal individually and all cross the
   IntersectionObserver threshold within the same frame, so without this
   they all fade in at once — technically "revealed" but not staggered.
   Adding a delay per grid position turns that into a visible cascade. */
.q-grid > .q-reveal:nth-child(1), .q-profile-grid > .q-reveal:nth-child(1), .q-grid > li:nth-child(1) > .q-reveal { transition-delay: 0s; }
.q-grid > .q-reveal:nth-child(2), .q-profile-grid > .q-reveal:nth-child(2), .q-grid > li:nth-child(2) > .q-reveal { transition-delay: .08s; }
.q-grid > .q-reveal:nth-child(3), .q-profile-grid > .q-reveal:nth-child(3), .q-grid > li:nth-child(3) > .q-reveal { transition-delay: .16s; }
.q-grid > .q-reveal:nth-child(4), .q-profile-grid > .q-reveal:nth-child(4), .q-grid > li:nth-child(4) > .q-reveal { transition-delay: .24s; }
.q-grid > .q-reveal:nth-child(5), .q-profile-grid > .q-reveal:nth-child(5), .q-grid > li:nth-child(5) > .q-reveal { transition-delay: .32s; }
.q-grid > .q-reveal:nth-child(n+6), .q-profile-grid > .q-reveal:nth-child(n+6), .q-grid > li:nth-child(n+6) > .q-reveal { transition-delay: .4s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .q-reveal { opacity: 1 !important; transform: none !important; }
}

@media (forced-colors: active) {
  .q-btn, .q-card, .q-iconbtn, .q-nav__toggle, .q-mobile-nav__toggle { border: 1px solid CanvasText; }
}

/* ---------- HubSpot drag-and-drop grid ---------- */
.dnd-section { padding-block: var(--q-section-y); padding-inline: var(--q-gutter); }
.dnd-section > .row-fluid { max-width: var(--q-max); margin-inline: auto; }
.content-wrapper { max-width: var(--q-max); margin-inline: auto; padding-inline: var(--q-gutter); }

/* ---------- long-form prose (legal pages, case study summaries) ---------- */
/* h1/h2/h3/ul/ol/a already have global sizing and colour — this just adds the
   vertical rhythm dense multi-section body copy needs that short marketing
   copy doesn't. */
.q-prose > *:first-child { margin-top: 0; }
.q-prose h2 { margin-top: 2.5rem; }
.q-prose h3 { margin-top: 2rem; }
.q-prose p { margin: 0 0 1.25rem; }
.q-prose ul, .q-prose ol { margin-bottom: 1.25rem; }
.q-prose li { margin-bottom: 0.5rem; }
.q-prose li:last-child { margin-bottom: 0; }

/* ---------- system pages ---------- */
.q-password-prompt { max-width: 26rem; margin-inline: auto; text-align: center; }
.q-search-results { max-width: 48rem; margin-inline: auto; }
.q-search-results h2 { margin-bottom: 1.5rem; }
/* q-pagination-disabled is applied by initSearchPagination() in theme.js, which works
   out the real first/last page itself — HubSpot's own --disabled class on this module
   can't be trusted (it still marks "Next" disabled even when a real next page exists). */
.q-search-results .q-pagination-disabled { opacity: 0.35; pointer-events: none; }