/* ==========================================================================
   Quorim visual system
   Typography and color direction adapted from the approved landing reference.
   ========================================================================== */

@font-face {
  font-family: "Metropolis";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/metropolis-400.woff2") format("woff2");
}
@font-face {
  font-family: "Metropolis";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/metropolis-500.woff2") format("woff2");
}
@font-face {
  font-family: "Metropolis";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/metropolis-600.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/36cc7328_file.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/e1f9f046_file.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/85fb821f_file.woff2") format("woff2");
}

:root {
  /* ------------------------------------------------------------------
     Color

     Orange is the accent: interactive by default (buttons, links, focus,
     selection, form-field hover/focus). Non-click brand accent is a closed
     list — two-tone heading spans, team H1, contact card H1 — documented
     in style.html. Not for roles, partner frames, legal titles, or errors.
     ------------------------------------------------------------------ */
  --q-orange: #d2764b;
  /* Single interactive hover step. All orange controls lift to this. */
  --q-orange-hover: #eac0ac;
  /* Alias kept for existing call sites; prefer --q-orange-hover. */
  --q-orange-light: var(--q-orange-hover);
  --q-orange-tint: rgba(210, 118, 75, 0.1);
  --q-orange-tint-hover: rgba(210, 118, 75, 0.22);
  --q-black: #000000;

  /* Marketing dark field — deep indigo-navy, hue ~235°.
     The whole site sits in this range: ink is the ambient floor the page
     never drops below (true black is reserved for scrims and ::selection
     text), deep is the long shoulder, mid is the crest. */
  --q-ink: #090a18;
  --q-navy-deep: #0d0f20;
  --q-navy-mid: #141436;
  /* Luminous center of the well swell — the brightest the field ever gets.
     Background light only, never a surface or text color. */
  --q-navy-bloom: #1c1e52;
  --q-blue-soft: #7fa3cc;

  /* Page chrome. Header and footer both wear the veil: ink glass —
     translucent enough for the field to glow through the blur, so each
     bar reads as a surface of the page, not a slab on it. Opaque chrome
     is reserved for mobile header / full-screen menu ground. */
  --q-chrome: #05060f;
  --q-chrome-veil: rgba(9, 10, 24, 0.6);

  /* Card surface — one face for well, press, team, contact, consent. */
  --q-card-face: linear-gradient(180deg, rgba(20, 20, 54, 0.9) 0%, rgba(13, 15, 32, 0.95) 100%);
  --q-card-border: rgba(184, 203, 225, 0.18);
  --q-card-border-hover: rgba(184, 203, 225, 0.34);
  --q-white: #ffffff;
  --q-text: #f6f5f3;
  /* Long-form reading tone: recessed from white headings. */
  --q-text-soft: #c5d4e6;
  /* Marketing blurb tone: recessed reading light in the field's hue. */
  --q-text-blurb: #dee7f1;
  --q-muted: #97a7b9;
  /* Deepest chrome text (copyright, fine print). */
  --q-text-recessed: #6f7b88;
  --q-border: rgba(151, 167, 185, 0.22);
  --q-border-subtle: rgba(151, 167, 185, 0.13);

  /* Semantic — distinct from brand orange so error ≠ CTA. */
  --q-success: #9fd4b8;
  --q-danger: #e8a090;

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

     Metropolis: display / headings / brand, and marketing blurbs (.q-copy).
     Inter: long-form reading (articles, legal), form fields, UI chrome.
     ------------------------------------------------------------------ */
  --q-font-display: "Metropolis", "Inter", system-ui, sans-serif;
  --q-font-marketing: "Metropolis", "Inter", system-ui, sans-serif;
  --q-font-body: "Inter", system-ui, sans-serif;
  --q-font-ui: "Inter", system-ui, sans-serif;

  /* ------------------------------------------------------------------
     Type scale — modular scale on a 16px base.

     Editorial rungs are fluid over a SHARED 380px–1280px viewport range.

     EVERY RUNG IS IN rem, and the comments give the px it resolves to at
     the browser's 16px default. Not decoration: rem is relative to the
     ROOT font size, which is the setting a reader changes when they make
     text bigger for themselves. In px this scale ignored that setting
     completely — a visitor who had set their default to 24px got a page
     rendered at exactly the same size as everyone else's, because every
     number here was absolute and every clamp was pinned to absolutes at
     both ends. The vw terms stay vw: those answer "how wide is the
     window", which is a different question from "how big does this person
     need the words", and both still get a say.

     --q-header-h scales with them (see below) so a larger setting grows
     the bar rather than pushing the nav out of it. The --sp-* scale does
     NOT: section gaps are the page's architecture, not its reading size,
     and tripling a 160px gap to keep proportion with 24px type turns a
     page into a corridor.

     Body stays at exactly 1rem, so at the default it is the 16px that
     stops iOS zooming a focused input, and above the default it grows with
     everything else.

     Exemptions: brand wordmark, hero headline (.q-display).
     ------------------------------------------------------------------ */
  --fs-title:   clamp(1.875rem, 1.1625rem + 3vw, 3.5625rem);        /* 30→57px  page titles     */
  --fs-heading: clamp(1.6875rem, 1.1333rem + 2.333vw, 3rem);        /* 27→48px  section heads   */
  --fs-card:    clamp(1.3125rem, 1.1275rem + 0.7778vw, 1.75rem);    /* 21→28px  card headlines  */
  --fs-card-sm: clamp(1.1875rem, 1.0819rem + 0.4444vw, 1.4375rem);  /* 19→23px  names in cards  */
  --fs-lead:    clamp(1.125rem, 1.075rem + 0.45vw, 1.25rem);        /* 18→20px  leads, longform */
  --fs-body:    1rem;                                               /* 16px     body, inputs    */
  --fs-ui:      0.8125rem;                                          /* 13px     buttons, nav    */
  --fs-label:   0.6875rem;                                          /* 11px     eyebrows, tags  */

  --ls-display: -0.042em;
  --ls-title: -0.036em;
  --ls-heading: -0.028em;
  --ls-card: -0.02em;
  --ls-caps: 0.1em;
  --ls-caps-wide: 0.18em;

  /* ------------------------------------------------------------------
     Spacing scale — 4px base.

     Prefer the semantic aliases (--q-section-y, --q-stack-gap, …) over
     raw steps in component rules. Section Y is generous by design:
     institutional pages need air between blocks.
     ------------------------------------------------------------------ */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;
  --sp-9: 80px;
  --sp-10: 96px;
  --sp-11: 128px;
  --sp-12: 160px;

  /* Semantic spacing */
  --q-section-y: var(--sp-11);       /* primary section block padding */
  --q-section-y-sm: var(--sp-9);     /* same on small screens */
  --q-band-y: var(--sp-10);          /* page-title bands */
  --q-band-y-sm: var(--sp-8);
  --q-stack-gap: var(--sp-6);        /* vertical gap in centered stacks */
  --q-card-pad: var(--sp-6);
  --q-field-gap: 22px;               /* form field stack */

  /* The bar comes along with the type scale. In px it became a lid: its
     min-height stayed at 76 while the nav inside it grew with the reader's
     setting, so the nav pushed out of the bar and body's padding-top no
     longer cleared it — the first section slid up underneath. One unit, one
     behaviour, and every offset derived from it follows. */
  --q-header-h: 4.75rem;             /* 76px at the 16px default */
  /* How far the header's glass dissolves PAST its own underside. The bar has
     no hard bottom edge; this is the distance over which it stops. It scales
     with the veil's density — the denser the glass, the more contrast there
     is to dissolve and the longer the runway has to be. At the current
     ink@0.6 veil, 18px. */
  --q-header-edge: 18px;

  /* ------------------------------------------------------------------
     Press feedback

     Every control acknowledges the finger on the way DOWN, not on release.
     The site had hover and focus only — it was built for a mouse that
     hovers and had nothing to say to a finger that presses, and Bootstrap
     removes the platform's own tap highlight, so on touch there was no
     feedback at all between finger-down and navigation.

     Two depths, because a big surface that shrinks as far as a small one
     reads as a glitch: controls take --q-press-scale, card-sized surfaces
     take --q-press-scale-surface.

     These drive the `scale` property, NEVER `transform`. The scroll reveal
     animates transform, and a running animation beats a static declaration
     — a pressed button inside a .reveal-stagger would simply not respond.
     `scale` is a separate, composited property, so the two never collide.

     Reduced motion sets both to 1 (see the block at the end of the file):
     the press then reads through colour alone, which is the point — gentler
     feedback, not no feedback. */
  --q-press-scale: 0.97;
  --q-press-scale-surface: 0.994;
  --q-press-ms: 90ms;

  /* The smallest a finger may be asked to hit. Chrome type is 11–13px and
     the words are the design, so the fix is never to inflate the type — it
     is to grow the TARGET around it, with padding where the box can take it
     and with a min-height where it cannot. Set once here so the number is
     arguable in one place rather than guessed per component. */
  --q-hit: 44px;
  /* The floor for a link sitting inside a line of prose, where 44px would
     collide with the lines above and below and steal their taps. Two tiers,
     because "make everything 44" is how footers end up with fine print
     three times taller than the sentence it belongs to. */
  --q-hit-inline: 24px;

  /* Radius scale */
  --q-radius: 6px;
  --q-radius-btn: 14px;
  --q-radius-btn-sm: 12px;
  --q-radius-card: 16px;

  /* ------------------------------------------------------------------
     Width map — one value per role, no one-off max-widths in markup.
     ------------------------------------------------------------------ */
  --q-container: 1360px;             /* reserved wide chrome; prefer --q-content */
  /* Shared column between side gutters — logo left edge → GIT button right. */
  --q-content: 1240px;
  --q-measure: 45rem;                /* long-form reading (~720px) */
  --q-form: 38.75rem;                /* contact card (~620px) */
  --q-card-narrow: 32.5rem;          /* press teaser (~520px) */

  /* Single horizontal gutter — header, footer, and content share this so
     logos and page blocks register on one left edge. */
  --q-gutter: 24px;
  /* Outer border-box width of the padded page column (content + gutters). */
  --q-page: calc(var(--q-content) + 2 * var(--q-gutter));
  /* Optical align with brand mark artwork inset */
  --q-mark-inset: 3px;

  /* Shared right-arrow, drawn as a mask so it inherits currentColor. */
  --q-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cpath d='M104.133 18.918l-9.431 8.947 61.913 65.269-137.498.938.089 13 138.096-.942-61.338 66.11 9.529 8.842 75.39-81.254-76.75-80.91z'/%3E%3C/svg%3E");

  --bs-body-font-family: var(--q-font-body);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Takes up the slack so the footer is pushed to the bottom of short pages
   and sits right under the content on long ones. */
main {
  flex: 1 0 auto;
}

html {
  scroll-behavior: smooth;
  background: var(--q-ink);
  /* Tells the browser to render native UI — caret, scrollbars, autofill
     heuristics, validation bubbles — for a dark surface. */
  color-scheme: dark;
}

/* Column flex + min-height is the sticky-footer pattern. Without it a short
   page (contact is barely one viewport) ends where its content ends, and
   the html background shows below the footer as a black band. The header is
   position:fixed so it takes no part in this; main does the growing. */
body {
  display: flex;
  min-height: 100svh;
  flex-direction: column;
  margin: 0;
  padding-top: var(--q-header-h);
  overflow-x: hidden;
  background: var(--q-ink);
  color: var(--q-text);
  font-family: var(--q-font-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ==========================================================================
   Page field

   One gradient for the whole page; the sections on top are transparent so
   there are no seams to disguise. The field is immersive: it never drops to
   true black — the page opens and closes on ink, the darkest ambient tone
   of the deep navy, so the brand hue is the constant medium the site swims
   in rather than a color the page visits. Same arc as before: ink at both
   ends, navy-deep shoulders, one navy-mid crest at 60%. Percentage stops
   scale the arc to each page's own height, so every page opens on ink under
   the header, breathes through the same navy heart, and settles back to ink
   before the glass footer — the same tonal shape whether the page is one
   viewport tall or six.
   ========================================================================== */
.q-page-home,
.q-page-contact,
.q-page-press,
.q-page-team,
.q-page-legal {
  background-color: var(--q-ink);
  /* sRGB first, oklab second — progressive enhancement, and the pair matters
     on a dark ramp. sRGB is gamma-encoded, so interpolating between two dark
     tones spaces the steps unevenly and cuts the corner through a
     desaturated middle; oklab spaces them perceptually, which is what keeps
     the travel between stops reading as one light rather than as mud. An
     engine without oklab keeps the first declaration and still has a field. */
  background-image: linear-gradient(
    180deg,
    var(--q-ink) 0%,
    var(--q-navy-deep) 34%,
    var(--q-navy-mid) 60%,
    var(--q-navy-deep) 84%,
    var(--q-ink) 100%
  );
  background-image: linear-gradient(
    180deg in oklab,
    var(--q-ink) 0%,
    var(--q-navy-deep) 34%,
    var(--q-navy-mid) 60%,
    var(--q-navy-deep) 84%,
    var(--q-ink) 100%
  );
  background-repeat: no-repeat;
}

/* The home page takes only the arc's rising half, stretched over its full
   height — the experience the contact page gives in one screen (ink
   opening, slow climb, navy crest at the bottom), retold across six. The
   57% mid-stop keeps the same proportional shape as the shared curve's
   rise (34/60). No descent: the page ends on the crest; the glass footer
   lets that crest (and the horizon) show through rather than sealing it. */
.q-page-home {
  background-image: linear-gradient(
    180deg,
    var(--q-ink) 0%,
    var(--q-navy-deep) 57%,
    var(--q-navy-mid) 100%
  );
  background-image: linear-gradient(
    180deg in oklab,
    var(--q-ink) 0%,
    var(--q-navy-deep) 57%,
    var(--q-navy-mid) 100%
  );
}

/* The horizon: the field's single source of light — the Auros globe
   technique in the site's cool navy. One vast circle sits below the
   viewport; its lit rim crosses the lower view as a gentle arc, with
   atmosphere rising from the rim and the body of the sphere deepening
   the ground beneath. One center, one light: focused, not scattered.
   It breathes slowly in place — the rim itself never moves.

   Stacking: the pseudo is z-index 0 — above the body's own background
   (in-flow backgrounds paint before positioned layers) — and main and the
   footer are lifted to z-index 1 so all content, cards, and glass chrome
   stay in front. A -1 here would paint the layer UNDER the body's opaque
   field and hide it entirely. Reduced motion freezes the breath into a
   still glow via the global block.

   Geometry, and why the layer is pinned to the LARGE viewport: the circle's
   centre is a percentage OF THIS ELEMENT'S BOX while its radii are viewport
   units. On a desktop those are the same number. On a phone they are not —
   a retracting URL bar resizes the fixed layer's box but leaves `vh` pinned
   to the large viewport — and the `190%` multiplies the disagreement:
   rim = 1.9 x boxHeight - 1.1 x viewportHeight, so the rim travelled 1.9px
   for every 1px the box moved. Measured, a 68px toolbar retract slid the
   horizon 130px up the screen.

   So the box is pinned to the large viewport (`height: 100lvh`, which the
   toolbar cannot change) and the radii are expressed in the SAME unit, so
   centre and radius can never decouple again. The `vh` pair below is the
   fallback for engines without `lvh`; since `vh` == `lvh` wherever both
   exist, it costs nothing. */
.q-page-home::before,
.q-page-contact::before,
.q-page-press::before,
.q-page-team::before,
.q-page-legal::before,
.q-page-style::before {
  content: "";
  position: fixed;
  inset: 0;
  bottom: auto;
  height: 100vh;
  z-index: 0;
  background: radial-gradient(
    circle at 50% 190%,
    rgba(9, 10, 24, 0.55) 0,
    rgba(9, 10, 24, 0.55) calc(110vh - 20px),
    rgba(127, 163, 204, 0.38) calc(110vh - 1px),
    rgba(28, 30, 82, 0.85) calc(110vh + 8px),
    rgba(28, 30, 82, 0.38) 128vh,
    rgba(28, 30, 82, 0) 152vh
  );
  animation: q-horizon 10s ease-in-out infinite alternate;
  pointer-events: none;
}

/* The real geometry. Identical numbers to the fallback above — only the unit
   changes — so nothing about the composition moves; the rim lands on exactly
   the pixel it always did, and now stays there. */
@supports (height: 100lvh) {
  .q-page-home::before,
  .q-page-contact::before,
  .q-page-press::before,
  .q-page-team::before,
  .q-page-legal::before,
  .q-page-style::before {
    height: 100lvh;
    background: radial-gradient(
      circle at 50% 190%,
      rgba(9, 10, 24, 0.55) 0,
      rgba(9, 10, 24, 0.55) calc(110lvh - 20px),
      rgba(127, 163, 204, 0.38) calc(110lvh - 1px),
      rgba(28, 30, 82, 0.85) calc(110lvh + 8px),
      rgba(28, 30, 82, 0.38) 128lvh,
      rgba(28, 30, 82, 0) 152lvh
    );
    background: radial-gradient(
      circle at 50% 190% in oklab,
      rgba(9, 10, 24, 0.55) 0,
      rgba(9, 10, 24, 0.55) calc(110lvh - 20px),
      rgba(127, 163, 204, 0.38) calc(110lvh - 1px),
      rgba(28, 30, 82, 0.85) calc(110lvh + 8px),
      rgba(28, 30, 82, 0.38) 128lvh,
      rgba(28, 30, 82, 0) 152lvh
    );
  }
}

/* ==========================================================================
   The grain

   The field is built from very wide, very shallow gradients. The hero bloom
   is the extreme case: 0.16 alpha to nothing across roughly 400px, which is
   one alpha level every ten pixels. Eight-bit colour cannot express that, so
   it renders as concentric contour rings — the light coming apart into steps
   instead of arriving as one field.

   No arrangement of colour stops fixes this; there is no way to give 8-bit
   colour more levels. It is fixed the way print fixes it, by dithering. A
   pixel of noise moves each sample about a level, which scatters the boundary
   between two adjacent bands into a stipple the eye reads as continuous tone.
   Measured, it perturbs by ~1.3 levels: enough to destroy a contour, far too
   faint to read as noise.

   IT LIVES ON THE FIELD, NOT ON THE HERO, and that placement is the whole
   lesson. Confined to the hero it dithers the bloom beautifully and then ends
   at the hero's bottom edge in a full-width, ruler-straight step — the same
   bug as the scrim rectangle it was brought in to finish cleaning up. Grain
   is a property of the surface, so it covers the surface.

   z-index 2 puts it over the field and the content but under the fixed chrome
   (header 1030, menu 1050, consent 1080), which is correct: the chrome is
   glass and paints its own material.
   ========================================================================== */
.q-page-home::after,
.q-page-contact::after,
.q-page-press::after,
.q-page-team::after,
.q-page-legal::after,
.q-page-style::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 2;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E")
    0 0 / 160px 160px repeat;
  pointer-events: none;
}

/* Content rides above the horizon. The header, mobile menu, and consent
   card are fixed with their own (higher) stacking; these two are the
   in-flow layers that need the lift. */
main,
.q-footer {
  position: relative;
  z-index: 1;
}

@keyframes q-horizon {
  from {
    opacity: 0.82;
  }

  to {
    opacity: 1;
  }
}

/* The hero is the one section that opts out of the field, so the sphere
   canvas sits directly on the field's ink opening (the additive canvas
   only ever adds its own light to it). The footer used to opt out here
   too, which is exactly why it rendered a different colour on every page
   — it now paints its own chrome instead. */
.q-page-home .q-hero {
  background: transparent;
}

::selection {
  background: var(--q-orange);
  color: var(--q-black);
}

a {
  transition:
    color 180ms ease,
    border-color 180ms ease,
    background-color 180ms ease,
    transform 180ms ease;
}

/* ==========================================================================
   Page chrome gutter

   Header, footer, and marketing columns share --q-page (content + gutters) so
   the brand mark lines up with wells, cards, and section stacks. Bootstrap's
   .container-fluid / .container-xl default paddings are overridden here and
   on the section containers below.
   ========================================================================== */
.q-header > .container-fluid,
.q-footer > .container-xl {
  width: min(100%, var(--q-page));
  max-width: none;
  margin-inline: auto;
  padding-inline: var(--q-gutter);
}

/* Header — ink glass. Saturate lets the warm field's color come through
   the blur rather than greying out, which is what makes glass over a
   tinted field read as premium instead of foggy. */
/* The bar carries no material of its own. 60% ink plus a 16px blur ending on
   a ruler-straight line 76px down the page cut straight across the brightest
   part of the hero sphere's backlight: measured at the bar's underside while
   scrolling, 1.4 levels at the top of the page, 4 after 40px, 11 by 90px, 26
   by 260px, 67 once fully condensed. No amount of work on the bloom or the
   scrims could have fixed that; a blurred slab stopping dead is going to cut
   whatever is behind it.

   Deferring the glass to the hero's foot was tried and is worse: it removes
   the cut and removes the backing the nav needs along with it, so the
   headline scrolls up through a transparent bar and collides with the
   wordmark. The bar has to be legible over its own page AND not slice the
   image behind it, so the material has to end GRADUALLY rather than LATER.
   Hence ::before below. */
.q-header {
  /* No `position` here: Bootstrap's .fixed-top supplies `position: fixed`,
     which is already the containing block ::before needs. Declaring
     `position: relative` overrides it and drops the bar out of fixed
     positioning entirely. */
  min-height: var(--q-header-h);
  padding: 0;
  background: transparent;
}

/* The material, one layer behind the contents. Three things matter here:

   - It extends --q-header-edge PAST the bar's underside, so the glass has
     somewhere to dissolve into rather than stopping at the bar's edge.
   - The mask is load-bearing. A backdrop-filter is CLIPPED TO ITS ELEMENT,
     so a merely fading background would still leave the blur terminating on
     a hard line. Masking the layer is what releases the blur with the tint.
   - It is on ::before rather than on .q-header because a mask on the bar
     itself would feather the wordmark and the nav with it. */
.q-header::before {
  content: "";
  position: absolute;
  inset: 0 0 calc(var(--q-header-edge) * -1) 0;
  z-index: -1;
  background: linear-gradient(
    to bottom,
    var(--q-chrome-veil) calc(100% - var(--q-header-edge)),
    rgba(9, 10, 24, 0) 100%
  );
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-mask-image: linear-gradient(
    to bottom,
    #000 calc(100% - var(--q-header-edge)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    #000 calc(100% - var(--q-header-edge)),
    transparent 100%
  );
  pointer-events: none;
}

/* Scroll-aware glass: over the top of the page the bar is pure field, and it
   condenses across the first 260px of scroll. Zero-JS, same technique as the
   reveals; browsers without it keep the constant glass above.

   It animates OPACITY now, not background-color. Fading a background leaves
   the blur at full strength the whole way — the old keyframes could not have
   faded the material, only its tint. Opacity takes the blur with it. */
@supports (animation-timeline: scroll()) {
  .q-header::before {
    animation: q-header-glass linear both;
    animation-timeline: scroll();
    animation-range: 0 260px;
  }
}

@keyframes q-header-glass {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.q-header > .container-fluid {
  min-height: var(--q-header-h);
}

/* Brand lockup — the official two-tone artwork (orange mark, white word)
   as one image, so the header and footer render the identical lockup on
   every page. Width follows the artwork's own proportions. */
.q-header .navbar-brand,
.q-footer .q-footer-logo {
  display: inline-flex;
  align-items: center;
  /* The lockup is 30px of artwork. Padding takes the target to --q-hit
     without moving the artwork: the header and footer both centre their
     row, so the box grows symmetrically around the mark. */
  padding-block: calc((var(--q-hit) - 30px) / 2);
  padding-inline: 0;
  margin: 0;
  text-decoration: none;
  transition: scale var(--q-press-ms) ease-out;
}

.q-header .navbar-brand:active,
.q-footer .q-footer-logo:active {
  scale: var(--q-press-scale);
}

/* Surface anchors inherit their own colour rather than Bootstrap's link
   blue, which is what they fall back to the moment one of them is a bare
   <a> without a colour of its own. */
.q-header .navbar-brand,
.q-footer .q-footer-logo,
.q-cred,
.q-press-card {
  color: inherit;
}

.q-brand-lockup {
  display: block;
  width: auto;
  height: 30px;
}


.q-nav-pill {
  align-items: center;
  gap: 30px;
  padding: 0;
}

.q-header .nav-link {
  position: relative;
  padding: 28px 0 25px;
  color: var(--q-muted);
  font-family: var(--q-font-ui);
  font-size: var(--fs-ui);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
}

.q-header .nav-link::after {
  position: absolute;
  right: 0;
  bottom: 19px;
  left: 0;
  height: 1px;
  background: var(--q-orange);
  content: "";
  opacity: 0;
  transform: scaleX(0);
  transform-origin: left;
  transition: opacity 180ms ease, transform 180ms ease;
}

/* :active joins the hover list rather than getting a scale of its own: the
   rule wiping in under the word IS the press. Text that shrinks reads as a
   glitch, not as feedback. */
.q-header .nav-link:hover,
.q-header .nav-link:focus-visible,
.q-header .nav-link:active,
.q-header .nav-link.active {
  color: var(--q-white);
}

.q-header .nav-link:hover::after,
.q-header .nav-link:focus-visible::after,
.q-header .nav-link:active::after,
.q-header .nav-link.active::after {
  opacity: 1;
  transform: scaleX(1);
}

/* The home page doesn't need to flag itself in its own nav, so the resting
   active rule under "Home" is suppressed there. Scoped to the body rather
   than the link so the four headers stay byte-identical. Hover and focus
   still draw it, otherwise the link would have no interactive feedback --
   .active already colours it white. */
/* On the home page the wordmark already says where you are, so the current-page
   underline is redundant and stays down until pointed at. The footer was left
   out of this, so the two navs on the same page disagreed: the header's marker
   hidden, the footer's lit. Both are quiet here now. */
.q-page-home .q-header .nav-link.active:not(:hover):not(:focus-visible)::after,
.q-page-home .q-footer .nav-link.active:not(:hover):not(:focus-visible)::after {
  opacity: 0;
  transform: scaleX(0);
}

.q-header .nav-item:last-child {
  margin-left: 14px;
}

/* The nav CTA is a button, so it follows the button language — not the
   underline treatment of its sibling links. */
.q-header .nav-item:last-child .nav-link {
  display: inline-flex;
  min-width: 174px;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 13px 22px;
  color: var(--q-orange);
  background: var(--q-orange-tint);
  border: 1.5px solid var(--q-orange);
  border-radius: var(--q-radius-btn-sm);
  text-align: center;
  transition:
    color 180ms ease,
    background-color 180ms ease,
    border-color 180ms ease;
}

/* Repurpose the underline pseudo-element as the arrow. */
.q-header .nav-item:last-child .nav-link::after {
  position: static;
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  background: currentColor;
  opacity: 1;
  transform: none;
  transition: transform 180ms ease;
  -webkit-mask: var(--q-arrow) center / contain no-repeat;
  mask: var(--q-arrow) center / contain no-repeat;
}

.q-header .nav-item:last-child .nav-link:hover,
.q-header .nav-item:last-child .nav-link:focus-visible {
  color: var(--q-orange-hover);
  background: var(--q-orange-tint-hover);
  border-color: var(--q-orange-hover);
}

.q-header .nav-item:last-child .nav-link:active {
  color: var(--q-orange-hover);
  background: var(--q-orange-tint-hover);
  border-color: var(--q-orange-hover);
  scale: var(--q-press-scale);
}

.q-header .nav-item:last-child .nav-link:hover::after,
.q-header .nav-item:last-child .nav-link:focus-visible::after {
  transform: translateX(3px);
}

/* The toggle is ONE control with two faces: the bars open the menu, the cross
   closes it, and both live at the same point in the header. A separate close
   button inside the panel put the same job in two places and moved it between
   them — measured, the toggler was 46x40 at (320,14) and the close 38x38 at
   (328,24), overlapping but not coincident, and the close was --q-muted where
   the toggler was white, so the "same" control shrank and dimmed on open.
   One fixed --q-hit box, so the glyph swaps inside a target that never moves.

   No `display` at this level: Bootstrap's .navbar-toggler display rule is
   equal specificity and later in the cascade, so setting it here would be
   overridden. The mobile block below sets it. */
.q-header .navbar-toggler {
  width: 46px;
  height: var(--q-hit);
  align-items: center;
  justify-content: center;
  padding: 0;
  color: var(--q-white);
  border: 0;
  transition: scale var(--q-press-ms) ease-out;
}

.q-header .navbar-toggler .q-icon-menu {
  display: block;
  width: 26px;
  height: 20px;
}

.q-header .navbar-toggler .q-icon-close {
  width: 21px;
  height: 21px;
}

/* The faces swap off aria-expanded, which Bootstrap maintains on the toggle
   itself — so the glyph is driven by the control's own announced state and
   the two can never disagree. */
.q-header .navbar-toggler .q-icon-close,
.q-header .navbar-toggler[aria-expanded="true"] .q-icon-menu {
  display: none;
}

.q-header .navbar-toggler[aria-expanded="true"] .q-icon-close {
  display: block;
}

.q-header .navbar-toggler:active {
  scale: var(--q-press-scale);
}

.q-header .navbar-toggler svg {
  display: block;
  width: 26px;
  height: 20px;
}

.q-header .navbar-toggler:focus {
  box-shadow: 0 0 0 2px var(--q-orange);
}

/* Footer — same ink glass as the header. Saturate lets the field's color
   come through the blur rather than greying out. The hairline is a soft
   edge on glass, not a hard chrome seam. */
.q-footer {
  padding: var(--sp-8) 0 var(--sp-7);
  color: var(--q-white);
  background: var(--q-chrome-veil);
  border-top: 1px solid var(--q-border-subtle);
  backdrop-filter: blur(16px) saturate(1.3);
}

/* Logo base, nav word bottoms, and LinkedIn mark share one horizontal
   line. Underlines hang below that line (absolute, outside the text box).
   The row uses align-items-end in markup; this keeps the intent in CSS
   if the utility is dropped. */
.q-footer > .container-xl > .d-flex {
  align-items: flex-end;
}

.q-footer .nav {
  gap: 8px;
  align-items: flex-end;
}

/* Matches the header: muted -> white, with the accent carried by a rule that
   wipes in rather than by recolouring the text. */
.q-footer .nav-link {
  /* The rule and the horizontal inset share one value, so the underline
     never drifts away from the word. Vertical: zero bottom padding so the
     text's lower edge is the box edge (aligned with the logo base); top
     padding only for hit area, growing upward. */
  --q-rule-inset: 14px;
  /* How far the underline sits below the text box. Negative on desktop,
     where the box hugs the word; the mobile block grows the box downward
     for hit area and pushes this back out so the rule stays on the WORD
     rather than following the box. */
  --q-rule-drop: -6px;
  position: relative;
  padding: 8px var(--q-rule-inset) 0;
  color: var(--q-muted);
  font-family: var(--q-font-ui);
  font-size: var(--fs-ui);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
}

.q-footer .nav-link::after {
  position: absolute;
  right: var(--q-rule-inset);
  /* Below the shared logo/text baseline — not inside the text box. */
  bottom: var(--q-rule-drop);
  left: var(--q-rule-inset);
  height: 1px;
  background: var(--q-orange);
  content: "";
  opacity: 0;
  transform: scaleX(0);
  transform-origin: left;
  transition: opacity 180ms ease, transform 180ms ease;
}

.q-footer .nav-link:hover,
.q-footer .nav-link:focus-visible,
.q-footer .nav-link:active,
.q-footer .nav-link.active {
  color: var(--q-white);
}

.q-footer .nav-link:hover::after,
.q-footer .nav-link:focus-visible::after,
.q-footer .nav-link:active::after,
.q-footer .nav-link.active::after {
  opacity: 1;
  transform: scaleX(1);
}

.q-footer .q-linkedin {
  display: inline-flex;
  color: var(--q-muted);
  /* Row is flex-end so logo base + nav word bottoms share a line. The
     22px mark is taller than the 13px caps, so its center sits high of
     theirs; drop half the height delta so the centers match. */
  transform: translateY(calc((22px - var(--fs-ui)) / 2));
  transition:
    color 180ms ease,
    scale var(--q-press-ms) ease-out;
}

/* `scale`, not `transform` — the translateY above is doing real alignment
   work and a transform-based press would wipe it. */
.q-footer .q-linkedin:active {
  color: var(--q-white);
  scale: var(--q-press-scale);
}

.q-footer .q-linkedin svg {
  width: 22px;
  height: 22px;
  fill: currentColor;
}

.q-footer .q-linkedin:hover,
.q-footer .q-linkedin:focus-visible {
  color: var(--q-white);
}

/* Close under the logo row — offset so the first glyph aligns with the
   mark's artwork, not with its bounding box. */
/* Grouped, not one string. As a single run of text the separator `·` was a
   break opportunity like any other, so at narrow widths a line ended on a
   stranded bullet. The notice and the legal pair are now two boxes on one
   flex line: the pair wraps as a unit and never splits. */
.q-footer .q-copyright {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-1) var(--sp-4);
  margin: var(--sp-3) 0 0;
  padding-left: var(--q-mark-inset);
  color: var(--q-text-recessed);
  font-family: var(--q-font-ui);
  font-size: var(--fs-label);
}

.q-footer .q-copyright-legal {
  display: flex;
  align-items: baseline;
  gap: var(--sp-4);
  /* The pair travels together: two short links that split across lines read
     as unrelated fragments. */
  flex-wrap: nowrap;
}

.q-footer .q-copyright-legal span {
  opacity: 0.6;
}

/* Legal links live inside the copyright line — same recessed tone, lifting
   to white on hover like everything else in the chrome. */
.q-footer .q-copyright a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--q-text-recessed) 85%, transparent);
  text-underline-offset: 3px;
}

.q-footer .q-copyright a:hover,
.q-footer .q-copyright a:active,
.q-footer .q-copyright a:focus-visible {
  color: var(--q-white);
  text-decoration-color: currentColor;
}

/* ==========================================================================
   Buttons and arrow links

   One interaction language across the site: a low-opacity accent fill that
   deepens on hover while the text lifts orange -> light orange. Nothing
   flips to a solid fill and nothing moves except the arrow — .q-btn--orbit
   (see "Orbit CTA" below) adds a one-shot border glow on top of this,
   without replacing hover behaviour.
   ========================================================================== */
.q-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-width: 210px;
  min-height: 48px;
  padding: 13px 26px;
  color: var(--q-orange);
  background: var(--q-orange-tint);
  border: 1.5px solid var(--q-orange);
  border-radius: var(--q-radius-btn);
  font-family: var(--q-font-ui);
  font-size: var(--fs-ui);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--ls-caps);
  text-decoration: none;
  text-transform: uppercase;
  cursor: pointer;
  transition:
    color 180ms ease,
    background-color 180ms ease,
    border-color 180ms ease,
    scale var(--q-press-ms) ease-out;
}

.q-btn:hover,
.q-btn:focus-visible {
  color: var(--q-orange-hover);
  background: var(--q-orange-tint-hover);
  border-color: var(--q-orange-hover);
}

/* The press: the hover state arrives immediately, plus a shrink sized to the
   object. On a pointer this is the last 90ms before the click; on a finger it
   is the only acknowledgement there is. */
.q-btn:active {
  color: var(--q-orange-hover);
  background: var(--q-orange-tint-hover);
  border-color: var(--q-orange-hover);
  scale: var(--q-press-scale);
}

/* .q-arrow was shipping as half a component: only its ::after glyph existed,
   and the style guide faked the rest with inline styles. The base rule is
   what makes the class usable in a page. */
.q-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  color: var(--q-orange);
  font-family: var(--q-font-ui);
  font-size: var(--fs-ui);
  font-weight: 600;
  letter-spacing: var(--ls-caps);
  text-decoration: none;
  text-transform: uppercase;
  transition:
    color 180ms ease,
    scale var(--q-press-ms) ease-out;
}

.q-arrow:hover,
.q-arrow:focus-visible {
  color: var(--q-orange-hover);
}

.q-arrow:active {
  color: var(--q-orange-hover);
  scale: var(--q-press-scale);
}

/* The arrow is generated, so every button carries one without markup. */
.q-arrow::after,
.q-btn::after {
  width: 17px;
  height: 17px;
  flex: 0 0 auto;
  background: currentColor;
  content: "";
  transition: transform 180ms ease;
  -webkit-mask: var(--q-arrow) center / contain no-repeat;
  mask: var(--q-arrow) center / contain no-repeat;
}

.q-arrow:hover::after,
.q-arrow:focus-visible::after,
.q-arrow:active::after,
.q-btn:hover::after,
.q-btn:focus-visible::after,
.q-btn:active::after {
  transform: translateX(3px);
}

/* Opt out where a button is an endpoint rather than a next step. */
.q-btn--noarrow::after {
  display: none;
}

/* The declining option: same size and shape as its sibling — one click
   either way — but in the chrome's neutral voice, because orange is the
   colour of the path we're inviting. */
.q-btn--quiet {
  color: var(--q-muted);
  background: transparent;
  border-color: var(--q-border);
}

.q-btn--quiet:hover,
.q-btn--quiet:focus-visible {
  color: var(--q-white);
  background: rgba(151, 167, 185, 0.08);
  border-color: var(--q-muted);
}

/* Shared type */
/* Rung 9 of the scale is 83px, and this deliberately does not use it.
   The hero is an absolutely-positioned composition: the headline sits at
   --q-sphere-copy-top and the CTA at --q-sphere-cta-offset, both pinned to
   the sphere. Growing the headline does not push the button down, it lands
   on top of it — and at 1440x900 the current 78px already clears the CTA by
   only 12px. So this stays at its tuned sizes and steps at the same
   breakpoints as the sphere geometry it belongs to.

   To put the hero on the scale, the sphere offsets have to be re-tuned
   alongside it. Deliberately not done here. */
.q-display {
  margin: 0;
  color: var(--q-white);
  font-family: var(--q-font-display);
  font-size: 78px;
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: var(--ls-display);
  text-wrap: balance;
}

/* Shared section display heading. A <span> on the second line carries the
   accent AND steps down to the card rung — a subtitle, not a second
   heading. Color alone never carried the hierarchy; size does. */
.q-h-section {
  margin: 0;
  color: var(--q-white);
  font-family: var(--q-font-display);
  font-size: var(--fs-heading);
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
}

.q-h-section span {
  display: block;
  margin-top: var(--sp-2);
  color: var(--q-orange);
  font-size: var(--fs-card);
  line-height: 1.25;
  letter-spacing: var(--ls-card);
}

.q-sub {
  color: var(--q-orange);
  font-family: var(--q-font-ui);
  font-weight: 600;
}

/* Home hero */
.q-hero {
  --q-sphere-size: min(82svh, 900px);
  --q-sphere-top: -16px;
  --q-sphere-copy-offset: min(29.52svh, 324px);
  --q-sphere-copy-top: calc(-6px + var(--q-sphere-copy-offset));
  --q-sphere-cta-offset: min(54.94svh, 603px);
  position: relative;
  display: flex;
  height: calc(100svh - var(--q-header-h));
  min-height: 680px;
  align-items: center;
  justify-content: center;
  /* Stays `hidden`, deliberately. The workshop branch replaced this with
     `overflow: clip` + a 96px clip margin to stop the ambient bloom being cut
     at the hero's top edge — but on THIS geometry there is nothing to free.
     .q-hero::before reaches zero alpha at 68% of its radius, which lands 64px
     (desktop) and 84px (mobile) INSIDE the hero: the clip never touched it.
     The margin did have an effect — it added its own 96px to the document's
     scrollable width, so a 390px phone laid out at 486px and the whole page
     rendered zoomed out, which also put every tap coordinate in the wrong
     place. The sphere canvas is 608px wide at 390 and is meant to be cropped;
     `hidden` is what crops it. */
  overflow: hidden;
  background: var(--q-ink);
  text-align: center;
}

/* Ambient bloom, painted *under* the canvas. The sphere composites with
   "lighter", so on the near-black ink it can only ever add its own light —
   this gives it something to spill into. Centred on the sphere, not the hero. */
.q-hero::before {
  position: absolute;
  top: calc(var(--q-sphere-top) + var(--q-sphere-size) / 2);
  left: 50%;
  width: calc(var(--q-sphere-size) * 1.15);
  height: calc(var(--q-sphere-size) * 1.15);
  background: radial-gradient(
    circle,
    rgba(210, 118, 75, 0.16) 0%,
    rgba(210, 118, 75, 0.06) 38%,
    rgba(210, 118, 75, 0) 68%
  );
  content: "";
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* Painted *over* the canvas, under the copy (which holds z-index 1).
   Layer 1 buys contrast where the type lands and nowhere else, so the
   sphere keeps its bright limb; layer 2 just grounds the section seam. */
.q-hero::after {
  position: absolute;
  inset: 0;
  /* Scrims are mixed from ink, not black, so the hero settles into the field
     instead of stepping cold. A scrim that is still dark where it terminates
     prints a hard line against whatever is next.

     THE SAME RULE APPLIES SIDEWAYS, and the reading scrim used to break it.

     It was painted into its own background box — min(1100px,92vw) wide by
     min(46svh,420px) tall — with the ellipse sized `60% 58%` OF THAT BOX.
     Two numbers that cannot both be true: 60% of 1100 is a 660px horizontal
     radius inside a box whose half-width is 550, and 58% of 470 is a 271px
     vertical radius while the centre sits only 188px below the box's top
     edge. The gradient ran out of BOX before it ran out of ALPHA — still
     ~9% opaque ink at the sides and ~17% at the top — and then `no-repeat`
     stopped it dead. The "soft" scrim was a rectangle with four cut edges
     laid over the sphere's bloom. Each edge is only two or three levels,
     which is why it survived review; it is also 1100px long and perfectly
     straight, and a long straight edge is the one thing vision never
     misses.

     The fix is to stop giving it a box of its own. The ellipse is painted on
     the full hero layer, where the only edges are the hero's and they are
     hundreds of pixels away, sized in percentages of that layer with the
     falloff guaranteed to complete inside it. There is no arithmetic left
     that can print an edge. Anchoring to hero percentages rather than to
     --q-sphere-copy-top costs nothing: the copy block is itself positioned
     in svh and the hero is 100svh minus the header, so the two track each
     other by construction. */
  background:
    radial-gradient(
      ellipse 46% 34% at 50% 54%,
      rgba(9, 10, 24, 0.58) 0%,
      rgba(9, 10, 24, 0.32) 45%,
      rgba(9, 10, 24, 0) 100%
    ),
    /* The grounding wash under the CTA. It used to run 0.5 alpha at 84%
       straight to zero at 100% — reaching zero exactly ON the hero's bottom
       edge. The VALUE was continuous there, which is why the old comment
       believed it was safe, but the SLOPE was not: inside the hero the wash
       is falling fast, outside it is flat, and a slope discontinuity is a
       Mach band — the eye manufactures an edge from it. It lands on zero at
       94% now, with a stop at 88% bending the curve flat before it arrives,
       so nothing is happening at the edge any more. */
    linear-gradient(
      to bottom,
      transparent 56%,
      rgba(9, 10, 24, 0.44) 80%,
      rgba(9, 10, 24, 0.12) 88%,
      rgba(9, 10, 24, 0) 94%
    );
  content: "";
  pointer-events: none;
}

.q-hero .q-hero-sphere {
  position: absolute;
  top: var(--q-sphere-top);
  left: 50%;
  width: var(--q-sphere-size);
  height: var(--q-sphere-size);
  opacity: 0.9;
  pointer-events: none;
  transform: translateX(-50%);
}

.q-hero .q-hero-copy {
  position: absolute;
  top: var(--q-sphere-copy-top);
  left: 50%;
  z-index: 1;
  width: min(100%, var(--q-page));
  padding: 0 var(--q-gutter);
  transform: translateX(-50%);
}

/* Second contrast pass over the scrim — carries the type across the few
   bright particles the ellipse doesn't reach. */
.q-hero .q-display {
  text-shadow: 0 1px 16px rgba(0, 0, 0, 0.32);
}

/* Card rung — the shared subtitle size, two rungs under the section heads
   and far under the hero pin, so the support line reads as support.
   Tracking: not --ls-display (tight, for large mixed-case) and not full
   --ls-caps (0.1em, for small UI). Mid open tracking suits these caps. */
.q-hero .q-sub {
  margin: var(--sp-6) 0 0;
  font-family: var(--q-font-display);
  font-size: var(--fs-card);
  line-height: 1.3;
  letter-spacing: 0.05em;
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.35);
  text-transform: uppercase;
}

.q-hero .q-btn {
  position: absolute;
  top: var(--q-sphere-cta-offset);
  left: 50%;
  margin-top: 0;
  transform: translateX(-50%);
}

/* Home sections */
.q-section {
  position: relative;
  padding: var(--q-section-y) 0;
  background: transparent;
}

/* The diagram reads as part of the block above it, so that block drops its
   bottom padding and .graphic-wrap sets the whole gap. Doubled class beats
   the shorthand `padding` in the mobile block, which would otherwise put
   the padding back. */
.q-section.q-section--flush {
  padding-bottom: 0;
}

/* The section that follows the hero. The hero fills 100svh, but its
   composition is capped — copy offset tops out at 324px and the CTA at
   603px — so on viewports taller than ~1065px the leftover below the CTA
   grows 1:1 with the viewport. The margin claws back exactly that surplus
   and nothing else; on ordinary laptop heights it is 0 and the padding is
   the whole gap. */
.q-section.q-section--after-hero {
  margin-top: min(0px, 1065px - 100svh);
  padding-top: var(--q-section-y);
}

/* Content column — same outer page width and gutter as header/footer chrome. */
.q-section > .container-xl,
.q-press > .container-xl,
.q-team > .container-xl {
  max-width: var(--q-page);
  padding-inline: var(--q-gutter);
}

/* Marketing blurbs — Metropolis at lead size; weight, leading, and color match
   the approved landing #backing body (size stays on --fs-lead, not their 16px).
   Long-form articles/legal stay on Inter via .q-article-body. */
/* Line breaking, as a policy rather than a sprinkle. `balance` evens every
   line in a block, which is right for a heading of two or three lines and
   wrong for a paragraph — it is capped at a handful of lines and it fights
   long prose. `pretty` only reworks the LAST lines, so a sentence never ends
   on a stranded word. Headings balance; paragraphs pretty. */
.q-copy {
  color: var(--q-text-blurb);
  font-family: var(--q-font-marketing);
  font-weight: 400;
  font-size: var(--fs-lead);
  line-height: 1.6;
  text-wrap: pretty;
}

/* Links sit in the same tone as the blurb, lift to white on hover — same as
   the reference backing links (not orange, not Bootstrap link-light). */
.q-copy a {
  color: inherit;
  text-decoration: none;
  transition: color 180ms ease;
}

.q-copy a:hover,
.q-copy a:active,
.q-copy a:focus-visible {
  color: var(--q-white);
}

/* Top pad is the only air between the Meet Quorim heading and the diagram
   (the section above is --flush). One rung tighter than section air. */
.graphic-wrap {
  max-width: var(--q-page);
  margin: 0 auto;
  padding: var(--sp-4) var(--q-gutter) var(--sp-6);
  background: transparent;
}

/* The diagram's closing couplet lives inside the wrap. The two lines are
   one thought, so the second sits close under the first. */
.graphic-wrap .q-copy {
  max-width: var(--q-measure);
  margin: var(--sp-6) auto 0;
}

.graphic-wrap .q-copy + .q-copy {
  margin-top: var(--sp-1);
}

/* The orbit system: the original diagram's identity — the Q mark at the
   core, octagon participants, blue-in / green-out flows — restaged
   as hairline celestial geometry. Octagons are half the old visual weight,
   packets carry the in/out story, and the core glyph anchors the brand.
   Width is capped below the content column: the object is a punctuation
   mark between subjects, not a wall. */
/* On a phone the artwork scales to a third of its desktop size, which takes
   the 21-unit mobile labels to a 9px cap box at 390 and 6px at 320 —
   "AGENT 1", "FUND ADMIN" and "CUSTODIAN / TRUSTEE" become texture, and at
   320 they collide with their own node chips. Shrinking a diagram below the
   legibility of its own labels is not responsive, it is just smaller.

   So it stops shrinking and starts panning. The scroller does nothing on
   desktop, where the artwork caps at 920px inside a wider column; below the
   breakpoint the svg holds a floor width and the reader moves across it. The
   edge mask says there is more in that direction without adding a control. */
.q-graphic-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

.graphic {
  display: block;
  width: min(100%, 920px);
  height: auto;
  margin-inline: auto;
  --quorim: #d2764b;
  --in: #7fa3cc;
  --out: #8fdcb4;
}

.graphic .oct {
  fill: #10182a;
  stroke-width: 1.3;
}

.graphic .node--in .oct {
  stroke: rgba(127, 163, 204, 0.8);
}

.graphic .node--out .oct {
  stroke: rgba(143, 220, 180, 0.75);
}

.graphic .icon {
  fill: none;
  stroke: #d6ecff;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Off-scale by design: drawn inside the diagram's own 1040x445 viewBox, so
   "12.5" is SVG user units that scale with the artwork — it is not a page
   size and must not track the page scale. */
.graphic .node-label {
  fill: rgba(234, 242, 251, 0.78);
  font-family: var(--q-font-ui);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-anchor: middle;
  text-transform: uppercase;
}

/* The ring is static by design: nothing travels it — it is structure, not
   a data path. Motion lives only where data moves (the packets) and at
   the core. */
.graphic .orbit {
  stroke: rgba(127, 163, 204, 0.26);
  stroke-width: 1;
}

.graphic .flow {
  fill: none;
  stroke: rgba(127, 163, 204, 0.18);
  stroke-width: 1;
}

/* ==========================================================================
   Well swell

   Not a panel: a swell of light in the page field itself. The radial bloom
   reaches full transparency before it meets the well's own box edges, so
   the stage has no outline anywhere — it gathers out of the gradient and
   releases back into it. Subjects on the home page are separated by
   breathing room and light, never by lines.
   ========================================================================== */
/* Centred content rhythm, shared by feature sections. */
.q-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--q-stack-gap);
  text-align: center;
}

/* Width-only: the voice comes from .q-copy alongside it. */
.q-section-copy {
  max-width: var(--q-measure);
  margin: 0;
}

/* The well is a dedicated stage, not a bordered card — and not a box: the
   bloom (rgba stops are --q-navy-mid's rgb) hits zero alpha by 80% of the
   ellipse, inside the box on every side, so no edge of the element ever
   prints. Radius and overflow clipping are gone because there is nothing
   left to clip. */
.q-well {
  padding: clamp(var(--sp-7), 6vw, var(--sp-9)) clamp(var(--sp-5), 5vw, var(--sp-8));
  /* Centered 80%/80% ellipse, zero alpha by 60%: every box edge sits at
     ≥62.5% of the gradient (50/80), so the glow dies before ALL four
     edges — top included — and the element never prints a line. An
     off-center or oversized ellipse leaves one edge tinted and draws a
     boundary the eye reads as a card frame. */
  background: radial-gradient(
    80% 80% at 50% 50%,
    var(--q-navy-bloom) 0%,
    rgba(20, 20, 54, 0.5) 28%,
    rgba(20, 20, 54, 0) 60%
  );
  border: 0;
}

/* Credential rail: the well's provenance, presented as one engraved strip
   rather than floating logo boxes — a single card-material surface, two
   cells split by a hairline, label above each mark. One object reads as
   structure; two boxes read as widgets. */
.q-cred-rail {
  display: flex;
  width: min(100%, 720px);
  margin-top: var(--sp-4);
  background: var(--q-card-face);
  border: 1px solid var(--q-card-border);
  border-radius: var(--q-radius-card);
}

.q-cred {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  text-decoration: none;
  transition:
    background-color 200ms ease,
    scale var(--q-press-ms) ease-out;
}

/* A surface the size of a card shrinking as far as a button reads as a
   glitch, so cards get the shallower of the two depths. */
.q-cred:active {
  scale: var(--q-press-scale-surface);
}

.q-cred:first-child {
  border-radius: var(--q-radius-card) 0 0 var(--q-radius-card);
}

.q-cred:last-child {
  border-radius: 0 var(--q-radius-card) var(--q-radius-card) 0;
}

.q-cred:hover,
.q-cred:focus-visible {
  background: rgba(184, 203, 225, 0.06);
}

.q-cred-divider {
  width: 1px;
  margin: var(--sp-4) 0;
  background: var(--q-card-border);
}

.q-logo-label {
  color: var(--q-blue-soft);
  font-family: var(--q-font-ui);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--ls-caps-wide);
  text-indent: 0.18em;
  text-transform: uppercase;
  transition: color 200ms ease;
}

.q-cred:hover .q-logo-label,
.q-cred:focus-visible .q-logo-label {
  color: var(--q-white);
}

/* Heights are per-mark: the Tradeweb lockup carries a starburst above its
   wordmark, so matching pixel heights would leave its text looking smaller. */
.q-cred img {
  display: block;
  width: auto;
}

.q-cred--ialta img {
  height: 34px;
}

.q-cred--tradeweb img {
  height: 38px;
}

/* Standard button proportions — matching primary CTAs, not logo boxes. */
.q-well-cta {
  margin-top: var(--sp-3);
}

/* ==========================================================================
   Orbit CTA — one-shot border glow

   The whole border softens with a brief, even glow, then goes quiet — once,
   not a loop. Overlay ::before keeps the translucent tint and normal hover
   intact (fill / text / border / arrow still behave like a plain .q-btn).
   ========================================================================== */

.q-btn--orbit {
  position: relative;
}

/* Soft even band on the border footprint only (face punched out). Starts
   invisible; the keyframes bring it up and out once. */
.q-btn--orbit::before {
  position: absolute;
  inset: -2px;
  padding: calc(2px + 1.5px);
  border-radius: calc(var(--q-radius-btn) + 2px);
  background: var(--q-orange-hover);
  -webkit-mask:
    linear-gradient(#000, #000) content-box,
    linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000, #000) content-box,
    linear-gradient(#000, #000);
  mask-composite: exclude;
  content: "";
  pointer-events: none;
  filter: blur(1.5px);
  opacity: 0;
  /* One play, then gone. Delay lets the section settle before the whisper. */
  animation: q-btn-orbit-glow 2.2s ease-in-out 0.5s 1 both;
}

/* If the CTA sits in a scroll-reveal container, don't spend the one-shot
   while it's still below the fold — start only after .is-in. Without JS
   (no .q-anim), the load-time animation above still runs. */
body.q-anim .reveal:not(.is-in) .q-btn--orbit::before,
body.q-anim .reveal-stagger:not(.is-in) .q-btn--orbit::before {
  animation: none;
  opacity: 0;
}

body.q-anim .reveal.is-in .q-btn--orbit::before,
body.q-anim .reveal-stagger.is-in .q-btn--orbit::before {
  animation: q-btn-orbit-glow 2.2s ease-in-out 0.35s 1 both;
}

/* Rise to a soft full-border glow, ease slightly outward, fade away. Peak
   opacity is intentionally low — a hint, not a spotlight. */
@keyframes q-btn-orbit-glow {
  0% {
    opacity: 0;
    inset: -2px;
    padding: calc(2px + 1.5px);
    border-radius: calc(var(--q-radius-btn) + 2px);
    filter: blur(1.5px);
  }

  40% {
    opacity: 0.38;
    inset: -4px;
    padding: calc(4px + 1.5px);
    border-radius: calc(var(--q-radius-btn) + 4px);
    filter: blur(2px);
  }

  100% {
    opacity: 0;
    inset: -5px;
    padding: calc(5px + 1.5px);
    border-radius: calc(var(--q-radius-btn) + 5px);
    filter: blur(2.5px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .q-btn--orbit::before {
    animation: none !important;
    opacity: 0;
  }
}

/* Press teaser — sits closer under the veterans well than a full section gap. */
.q-section:has(+ .q-press) {
  padding-bottom: var(--sp-8);
}

.q-press {
  padding: var(--sp-8) 0 var(--q-section-y);
  background: transparent;
}

/* Eyebrow is a label, never a heading. Left-aligned with the well above
   (same content column). Lead size for presence. */
.q-press .q-eyebrow {
  margin: 0 0 var(--sp-6);
  color: var(--q-blue-soft);
  font-family: var(--q-font-ui);
  font-size: var(--fs-lead);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: var(--ls-caps);
  text-align: left;
  text-transform: uppercase;
}

/* The press rail: an auto-fill grid so the wire card sits left with open
   track beside it — the layout itself says more is coming. New releases
   are added as more .q-press-card cells; nothing else changes. */
.q-press-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(380px, 100%), 1fr));
  gap: var(--sp-5);
}

/* The wire card: the announcement grammar of a market newswire — chip,
   date, statement, one "go" affordance. It is the page's featured drop, so
   it earns the elevated treatment: a brighter bloom-family face, a real
   shadow, and a gradient hairline frame running orange into steel. The
   whole card is the link, so the accent frame IS the action's edge —
   consistent with "orange is interactive", not an exception to it. On
   hover only light changes (shadow, chip, disc) and only the arrow moves. */
.q-press-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  padding: var(--q-card-pad);
  background:
    linear-gradient(180deg, rgba(28, 30, 82, 0.92) 0%, rgba(13, 15, 32, 0.96) 100%) padding-box,
    linear-gradient(135deg, rgba(210, 118, 75, 0.6) 0%, rgba(127, 163, 204, 0.35) 45%, rgba(184, 203, 225, 0.2) 100%) border-box;
  border: 1px solid transparent;
  border-radius: var(--q-radius-card);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.45);
  text-decoration: none;
  transition:
    box-shadow 200ms ease,
    scale var(--q-press-ms) ease-out;
}

.q-press-card:active {
  scale: var(--q-press-scale-surface);
}

.q-press-card:hover,
.q-press-card:focus-visible {
  box-shadow:
    0 18px 56px rgba(0, 0, 0, 0.5),
    0 4px 28px rgba(210, 118, 75, 0.16);
}

.q-press-card .q-press-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin: 0;
}

.q-press-card .q-press-chip {
  padding: 5px 10px;
  color: var(--q-orange);
  background: var(--q-orange-tint);
  border: 1px solid rgba(210, 118, 75, 0.45);
  border-radius: var(--q-radius);
  font-family: var(--q-font-ui);
  font-size: var(--fs-label);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  transition: color 180ms ease, background-color 180ms ease, border-color 180ms ease;
}

.q-press-card:hover .q-press-chip,
.q-press-card:focus-visible .q-press-chip {
  color: var(--q-orange-hover);
  background: var(--q-orange-tint-hover);
  border-color: var(--q-orange-hover);
}

.q-press-card .q-press-date {
  color: var(--q-muted);
  font-family: var(--q-font-ui);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
}

.q-press-card h2,
.q-press-card h3 {
  margin: 0;
  color: var(--q-white);
  font-family: var(--q-font-display);
  font-size: var(--fs-card-sm);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: var(--ls-card);
  text-wrap: balance;
}

/* Footer row: the action's words on the left, the go disc on the right. */
.q-press-card .q-press-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-top: auto;
  padding-top: var(--sp-2);
}

.q-press-card .q-press-cta {
  color: var(--q-orange);
  font-family: var(--q-font-ui);
  font-size: var(--fs-ui);
  font-weight: 600;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  transition: color 180ms ease;
}

.q-press-card:hover .q-press-cta,
.q-press-card:focus-visible .q-press-cta {
  color: var(--q-orange-hover);
}

/* The go disc: the arrow at button proportions, marking the whole card as
   one action. Only the arrow moves on hover — the shared 3px nudge. */
.q-press-card .q-press-go {
  display: flex;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  color: var(--q-orange);
  background: var(--q-orange-tint);
  border: 1.5px solid var(--q-orange);
  border-radius: 50%;
  transition: color 180ms ease, background-color 180ms ease, border-color 180ms ease;
}

.q-press-card .q-press-go::after {
  width: 17px;
  height: 17px;
  background: currentColor;
  content: "";
  transition: transform 180ms ease;
  -webkit-mask: var(--q-arrow) center / contain no-repeat;
  mask: var(--q-arrow) center / contain no-repeat;
}

.q-press-card:hover .q-press-go,
.q-press-card:focus-visible .q-press-go {
  color: var(--q-orange-hover);
  background: var(--q-orange-tint-hover);
  border-color: var(--q-orange-hover);
}

.q-press-card:hover .q-press-go::after,
.q-press-card:focus-visible .q-press-go::after {
  transform: translateX(3px);
}

/* Page title bands — one protocol: white display type, sentence case. */
.q-band {
  padding: var(--q-band-y) var(--q-gutter) var(--sp-9);
  background: transparent;
  text-align: center;
}

.q-band h1 {
  margin: 0;
  color: var(--q-white);
  font-family: var(--q-font-display);
  font-size: var(--fs-title);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: var(--ls-title);
  text-wrap: balance;
}

/* Team page title only — keep size on --fs-title; accent with brand orange. */
.q-page-team .q-band h1 {
  color: var(--q-orange);
}

.q-team {
  padding: 0 0 var(--sp-8);
  background: transparent;
}

.q-team .row {
  --bs-gutter-x: var(--sp-5);
  --bs-gutter-y: var(--sp-5);
}

.q-team-card {
  display: flex;
  height: 100%;
  min-height: 236px;
  gap: var(--sp-6);
  align-items: center;
  padding: var(--sp-3);
  background: var(--q-card-face);
  border: 1px solid var(--q-card-border);
  border-radius: var(--q-radius-card);
  transition: border-color 200ms ease;
}

/* Card is not a link — only the LinkedIn mark is. Border lift only. */
.q-team-card:hover,
.q-team-card:focus-within {
  border-color: var(--q-card-border-hover);
}

.q-team-card img {
  width: 210px;
  height: 210px;
  flex: 0 0 auto;
  object-fit: cover;
  aspect-ratio: 1;
  border-radius: 3px;
  filter: saturate(0.9) contrast(1.03);
}

.q-team-card .q-team-info {
  padding: var(--sp-2) var(--sp-3) var(--sp-2) 0;
}

.q-team-card h2 {
  margin: 0 0 var(--sp-2);
  color: var(--q-white);
  font-family: var(--q-font-display);
  font-size: var(--fs-card-sm);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: var(--ls-card);
}

/* Roles are metadata, not actions — muted, not accent. */
.q-team-card p {
  margin: 0 0 var(--sp-5);
  color: var(--q-muted);
  font-family: var(--q-font-ui);
  font-size: var(--fs-ui);
  line-height: 1.5;
}

.q-team-card .q-linkedin {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 19px of artwork, --q-hit of target. Padding builds the box and an equal
     negative margin takes it back out of the flow, so the glyph stays on the
     pixel it was on and the card's stack does not move — only the tappable
     area grows. */
  padding: calc((var(--q-hit) - 19px) / 2);
  margin: calc((var(--q-hit) - 19px) / -2);
  color: var(--q-muted);
}

.q-team-card .q-linkedin svg {
  width: 19px;
  height: 19px;
  fill: currentColor;
}

.q-team-card .q-linkedin:hover,
.q-team-card .q-linkedin:focus-visible {
  color: var(--q-orange-hover);
}

.q-team-cta {
  padding: 0 var(--sp-4) var(--q-section-y);
  background: transparent;
  text-align: center;
}

/* Contact */
.q-contact {
  position: relative;
  min-height: 720px;
  padding: var(--q-band-y) var(--q-gutter) var(--sp-12);
  background: transparent;
}

.q-contact-card {
  position: relative;
  max-width: var(--q-form);
  margin: 0 auto;
  padding: var(--sp-7) var(--sp-7);
  background: var(--q-card-face);
  border: 1px solid var(--q-card-border);
  border-radius: var(--q-radius-card);
}

/* Contact card title: card-headline size, brand orange (interactive accent). */
.q-contact-card h1 {
  margin: 0 0 var(--sp-6);
  color: var(--q-orange);
  font-family: var(--q-font-display);
  font-size: var(--fs-card);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: var(--ls-card);
}

.q-contact-card .form-label {
  margin-bottom: 8px;
  color: var(--q-muted);
  font-family: var(--q-font-ui);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
}

/* Fields sit in the same material as the card: a slight cool lift for the
   surface, with the boundary carried by the border rather than by a white
   fill. Both the border and the focus ring clear 3:1 against the card and
   against the field interior (WCAG 1.4.11). */
.q-contact-card .form-control {
  min-height: 48px;
  padding: 10px 13px;
  color: var(--q-white);
  background: rgba(184, 203, 225, 0.05);
  border: 1px solid rgba(184, 203, 225, 0.47);
  border-radius: var(--q-radius);
  font-family: var(--q-font-body);
  font-size: var(--fs-body);
  transition:
    color 180ms ease,
    background-color 180ms ease,
    border-color 180ms ease,
    box-shadow 180ms ease;
}

.q-contact-card .form-control::placeholder {
  color: var(--q-muted);
  opacity: 1;
}

.q-contact-card .form-control:hover {
  background: rgba(184, 203, 225, 0.08);
  border-color: var(--q-orange);
}

/* The old ring was rgba(orange, .22) with 0 blur and 3px spread, so it
   painted on the card, not the field — 1.34:1, effectively invisible. The
   solid border is what carries the indicator now; the glow is decoration. */
.q-contact-card .form-control:focus {
  color: var(--q-white);
  background: rgba(184, 203, 225, 0.08);
  border-color: var(--q-orange);
  box-shadow: 0 0 0 3px rgba(210, 118, 75, 0.3);
}

/* Browsers force their own fill on autofilled inputs and ignore
   background-color; a large inset shadow is the only reliable override. */
/* Autofill paints its own background, so it is overridden with a 1000px inset
   shadow — the standard trick. But box-shadow is also how the focus ring is
   drawn, and this selector is more specific and later, so on a FOCUSED
   autofilled field the inset fill replaced the ring outright and the field
   lost its focus state. The focused case carries both shadows. */
.q-contact-card .form-control:-webkit-autofill,
.q-contact-card .form-control:-webkit-autofill:hover {
  -webkit-text-fill-color: var(--q-white);
  -webkit-box-shadow: 0 0 0 1000px #191b32 inset;
  caret-color: var(--q-white);
}

.q-contact-card .form-control:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--q-white);
  -webkit-box-shadow:
    0 0 0 1000px #191b32 inset,
    0 0 0 3px rgba(210, 118, 75, 0.3);
  caret-color: var(--q-white);
}

.q-contact-card textarea.form-control {
  min-height: 112px;
  resize: vertical;
}

.q-contact-card .mb-field {
  margin-bottom: var(--q-field-gap);
}

/* Honeypot: off-screen, not display:none (some bots skip display:none fields). */
.q-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The widget is a fixed 300x65 iframe. At 320px the slot is only 238px wide,
   and a fixed-width child wider than its container pushes the document's
   scroll width past the device — measured, a 320px phone laid out at 341px
   and rendered the whole page zoomed out. Containing it here means the page
   can never be widened by a third party's iframe; the interactive checkbox is
   left-aligned inside the widget, so what a very narrow phone loses is the
   branding on its right, not the control.

   min-height is measured, not the vendor's nominal 65: the widget renders at
   73 and the difference was an 8px jump on insert. */
.q-turnstile {
  min-height: 73px;
  max-width: 100%;
  overflow-x: clip;
}

/* Field-level errors. --q-danger is deliberately not the brand orange, so an
   error can never be mistaken for a call to action. */
.q-field-error {
  margin: 7px 0 0;
  color: var(--q-danger);
  font-family: var(--q-font-ui);
  font-size: var(--fs-ui);
  line-height: 1.4;
}

.q-field-error[hidden] {
  display: none;
}

.q-contact-card .form-control[aria-invalid="true"] {
  border-color: var(--q-danger);
}

.q-contact-card .form-control[aria-invalid="true"]:focus {
  border-color: var(--q-danger);
  box-shadow: 0 0 0 3px rgba(232, 160, 144, 0.26);
}

.q-form-status {
  margin: var(--sp-4) 0 0;
  min-height: 1.4em;
  font-family: var(--q-font-body);
  font-size: var(--fs-body);
  line-height: 1.45;
  color: var(--q-muted);
}

.q-form-status--ok {
  color: var(--q-success);
}

.q-form-status--err {
  color: var(--q-danger);
}

.q-btn--submit {
  min-width: 140px;
}

.q-btn--submit:disabled {
  opacity: 0.6;
  cursor: default;
  pointer-events: none;
}

/* Press release — longer headline, soft deck under it. */
.q-band--press {
  padding-bottom: var(--sp-9);
}

.q-band--press h1 {
  max-width: 54rem;
  margin: 0 auto var(--sp-5);
  font-size: var(--fs-heading);
  text-wrap: balance;
}

.q-band--press p {
  max-width: 41rem;
  margin: 0 auto;
  color: var(--q-text-soft);
  font-family: var(--q-font-marketing);
  font-weight: 400;
  font-size: var(--fs-lead);
  font-style: normal;
  line-height: 1.65;
}

/* Article body — one long-form reading column for press + legal. */
.q-article {
  padding: var(--sp-9) var(--q-gutter) var(--q-section-y);
  background: transparent;
}

/* Reading panel: ink glass under the body only (not the title band), so
   long-form stays legible over the field and horizon without a solid slab.
   Same veil + blur language as header/footer; card radius so it reads as
   a surface, not a full-bleed band. */
/* A single unbreakable token — the Google opt-out URL in the privacy policy —
   is wider than a phone's reading column, and an overflowing inline box pushes
   the document's scroll width past the device. Measured on a 320px device,
   privacy.html reported an innerWidth of 379: the layout viewport forced 18%
   wider than `device-width`, so the entire page rendered shrunk. Every other
   page reported 320. break-word only breaks a word that has nowhere else to
   go, so ordinary prose is unaffected. */
.q-article-body {
  max-width: var(--q-measure);
  margin: 0 auto;
  overflow-wrap: break-word;
  padding: var(--sp-7) clamp(var(--sp-5), 4vw, var(--sp-7));
  color: var(--q-text-soft);
  font-family: var(--q-font-body);
  font-size: var(--fs-lead);
  line-height: 1.78;
  background: var(--q-chrome-veil);
  border: 1px solid var(--q-border-subtle);
  border-radius: var(--q-radius-card);
  backdrop-filter: blur(16px) saturate(1.3);
}

.q-article-body p {
  margin: 0 0 var(--sp-6);
  text-wrap: pretty;
}

.q-article-body > :last-child {
  margin-bottom: 0;
}

.q-article-body a {
  color: var(--q-orange);
  text-decoration-color: rgba(210, 118, 75, 0.45);
  text-underline-offset: 3px;
}

.q-article-body a:hover,
.q-article-body a:active,
.q-article-body a:focus-visible {
  color: var(--q-orange-hover);
  text-decoration-color: currentColor;
}

/* Legal meta line under the title — UI voice, not lead body. */
.q-band--legal {
  padding-bottom: var(--sp-8);
}

.q-band--legal p {
  margin: var(--sp-4) 0 0;
  color: var(--q-muted);
  font-family: var(--q-font-ui);
  font-size: var(--fs-ui);
  font-weight: 500;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
}

.q-article-body h2 {
  margin: var(--sp-8) 0 var(--sp-4);
  color: var(--q-white);
  font-family: var(--q-font-display);
  font-size: var(--fs-card);
  font-weight: 600;
  letter-spacing: var(--ls-card);
  line-height: 1.25;
}

.q-article-body h2:first-child {
  margin-top: 0;
}

.q-article-body h3 {
  margin: var(--sp-6) 0 var(--sp-3);
  color: var(--q-white);
  font-family: var(--q-font-display);
  font-size: var(--fs-lead);
  font-weight: 600;
  line-height: 1.35;
}

.q-article-body ul,
.q-article-body ol {
  margin: 0 0 var(--sp-6);
  padding-left: 26px;
}

.q-article-body li {
  margin-bottom: var(--sp-2);
}

.q-article-body li::marker {
  color: var(--q-orange);
}

/* A button that reads as body copy — the privacy policy's "change your
   cookie preferences" is an action, not a destination, so it's a <button>
   wearing the article link's clothes. */
.q-linklike {
  padding: 0;
  border: 0;
  background: none;
  color: var(--q-orange);
  font: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(210, 118, 75, 0.45);
  text-underline-offset: 3px;
  cursor: pointer;
}

.q-linklike:hover,
.q-linklike:active,
.q-linklike:focus-visible {
  color: var(--q-orange-hover);
  text-decoration-color: currentColor;
}

/* ==========================================================================
   Cookie consent

   One compact card, bottom-left, injected by assets/consent.js only when a
   choice is still owed. Both answers are the same size one click apart —
   accept simply speaks in the accent, decline in the chrome's neutral.
   ========================================================================== */
.q-consent {
  position: fixed;
  z-index: 1080;
  right: var(--sp-5);
  bottom: var(--sp-5);
  left: var(--sp-5);
  max-width: 480px;
  padding: var(--sp-5) var(--sp-5) var(--sp-5);
  background: var(--q-navy-deep);
  border: 1px solid var(--q-card-border);
  border-radius: var(--q-radius-card);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
}

.q-consent[hidden] {
  display: none;
}

.q-consent p {
  margin: 0 0 var(--sp-4);
  color: var(--q-text-soft);
  font-family: var(--q-font-body);
  font-size: var(--fs-body);
  line-height: 1.55;
}

.q-consent a {
  color: var(--q-orange);
  text-decoration-color: rgba(210, 118, 75, 0.45);
  text-underline-offset: 3px;
}

.q-consent a:hover,
.q-consent a:active,
.q-consent a:focus-visible {
  color: var(--q-orange-hover);
  text-decoration-color: currentColor;
}

.q-consent-actions {
  display: flex;
  gap: var(--sp-3);
}

.q-consent-actions .q-btn {
  min-width: 0;
  min-height: 44px;
  flex: 1;
  padding: 11px 18px;
}

@media (prefers-reduced-motion: no-preference) {
  .q-consent:not([hidden]) {
    animation: q-consent-in 260ms ease;
  }

  @keyframes q-consent-in {
    from {
      opacity: 0;
      transform: translateY(10px);
    }
  }
}

/* Network motion. Participants and the ring hold still; motion belongs to
   the data — the packets on the flow arcs (SMIL animateMotion in the SVG)
   — and to the core's tick ring and slow heartbeat. The global
   reduced-motion block freezes all of it to a legible still. */
.graphic .hub-glow {
  animation: graphic-halo 3.6s ease-in-out infinite;
}

/* The heartbeat: a hairline ring swells out from the core and dissolves.
   Scale is on the wrapping group so the ring stays hairline as it grows. */
.graphic .core-beat {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: graphic-beat 4.8s cubic-bezier(0.2, 0.6, 0.35, 1) infinite;
}

.graphic .spin-cw {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: graphic-spin 20s linear infinite;
}

@keyframes graphic-halo {
  0%, 100% { opacity: 0.75; }
  50% { opacity: 1; }
}
@keyframes graphic-beat {
  0% { transform: scale(1); opacity: 0.7; }
  70%, 100% { transform: scale(2.1); opacity: 0; }
}
@keyframes graphic-spin {
  to { transform: rotate(360deg); }
}

/* Responsive navigation and layout */
@media (max-width: 767.98px) {
  :root {
    --q-header-h: 4.25rem;         /* 68px at the 16px default */
    /* Keep --q-gutter at 24px so logos and blocks share one mobile edge. */
    --q-section-y: var(--q-section-y-sm);
    --q-band-y: var(--q-band-y-sm);
    --q-stack-gap: var(--sp-5);
  }

  .q-header {
    background: transparent;
  }

  /* Mobile keeps the FEATHER and drops only the blur. Per-frame backdrop blur
     is costly on phones, and the menu needs a solid ground — but an opaque bar
     cuts the sphere harder than a translucent one, and the phone is where the
     hero is most of the screen. So the material goes solid and still ends
     gradually. */
  .q-header::before {
    background: linear-gradient(
      to bottom,
      var(--q-chrome) calc(100% - var(--q-header-edge)),
      rgba(5, 6, 15, 0) 100%
    );
    backdrop-filter: none;
    animation: none;
    opacity: 1;
  }

  .q-header > .container-fluid {
    min-height: var(--q-header-h);
  }

  /* Same artwork; hold 30px on mobile (already compact). */
  .q-brand-lockup {
    height: 30px;
  }

  .q-header .navbar-toggler {
    display: flex;
  }

  /* The panel opens BELOW the header, not over it.

     It used to cover the whole viewport, header included, which cost the one
     thing a menu exists to answer: where am I. With the bar gone there was no
     wordmark, no way home, and a close button floating in the corner of an
     otherwise empty screen. Anchoring the panel under the header keeps the
     brand and the control that opened the menu exactly where they were — the
     panel is something the header produced, and it visibly comes out of it. */
  .q-header .navbar-collapse {
    position: fixed;
    top: var(--q-header-h);
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1050;
    /* Laid out at all times, closed or open. `display: none` CANNOT be
       transitioned, which is what made this panel snap into existence — the
       one piece of chrome on the site with no manners. The closed state is
       carried by visibility + opacity instead, so both directions animate and
       neither is reachable by pointer or tab while hidden. */
    display: flex !important;
    flex-direction: column;
    overflow-y: auto;
    padding: var(--sp-7) var(--q-gutter) var(--sp-8);
    background: var(--q-chrome);
    opacity: 0;
    visibility: hidden;
    /* Materialises downward, out of the header. The exit retraces the same
       path upward — a panel that arrives one way and leaves another reads as
       two unrelated events. */
    transform: translateY(-10px);
    transition:
      opacity 200ms ease,
      transform 200ms cubic-bezier(0.4, 0, 1, 1),
      visibility 0s 200ms;
  }

  /* Open state is .q-menu-open, added by site.js from show.bs.collapse and
     removed on hide.bs.collapse — NOT Bootstrap's .show/.collapsing. Those
     cannot express DIRECTION: .collapsing is present while opening AND while
     closing, so styling it as "open" holds the panel at full opacity through
     the entire dismissal and then blinks it away. The event pair is the only
     signal that knows which way the panel is going.

     .collapsing also writes an inline height it animates from zero, which
     would flatten a fixed full-height panel; overridden below. Bootstrap
     still reads its transition duration from this element and completes its
     own bookkeeping normally. */
  .q-header .navbar-collapse.collapsing {
    height: auto !important;
  }

  .q-header .navbar-collapse.q-menu-open {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition:
      opacity 240ms ease,
      transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1);
  }

  /* The rules bracket the LINKS. The CTA used to sit inside them, which made
     the group read as a three-item list with one item wearing a button — it is
     a different kind of thing and now stands clear below. */
  .q-nav-pill {
    display: block;
    width: 100%;
    max-width: 520px;
    /* auto on all four sides centres the group without
       `justify-content: center`, which clips the top of an overflowing flex
       child instead of scrolling to it. */
    margin: auto;
    padding: 0;
    text-align: center;
    border: 0;
  }

  .q-header .nav-item:first-child {
    border-top: 1px solid var(--q-border);
  }

  .q-header .nav-item + .nav-item {
    border-top: 1px solid var(--q-border-subtle);
  }

  .q-header .nav-item:nth-last-child(2) {
    border-bottom: 1px solid var(--q-border);
  }

  .q-header .nav-link {
    padding: 24px 12px;
    font-size: var(--fs-body);
  }

  .q-header .nav-link::after {
    display: none;
  }

  /* The rule above is for the sibling links' underline pseudo. The CTA's
     ::after IS its arrow — the affordance that says "this takes you
     somewhere" — and it was being switched off exactly where the button is
     largest. */
  .q-header .nav-item:last-child .nav-link::after {
    display: inline-block;
  }

  .q-header .nav-item:last-child {
    margin: var(--sp-7) 0 0;
    border-top: 0;
  }

  /* Block-level flex, not inline-flex — `margin: 0 auto` only centres a
     block-level box, and the desktop rule sets inline-flex. */
  .q-header .nav-item:last-child .nav-link {
    display: flex;
    width: min(100%, 250px);
    margin: 0 auto;
    padding: 16px 22px;
  }

  /* Each row arrives just after the panel it is in, in reading order, so the
     menu resolves rather than appearing all at once. Short and small: the
     panel is the gesture, this is only its grain. */
  .q-header .navbar-collapse.q-menu-open .nav-item {
    animation: q-menu-item 260ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }

  .q-header .navbar-collapse.q-menu-open .nav-item:nth-child(1) { animation-delay: 60ms; }
  .q-header .navbar-collapse.q-menu-open .nav-item:nth-child(2) { animation-delay: 105ms; }
  .q-header .navbar-collapse.q-menu-open .nav-item:nth-child(3) { animation-delay: 150ms; }
  .q-header .navbar-collapse.q-menu-open .nav-item:nth-child(4) { animation-delay: 195ms; }

  .q-hero {
    height: calc(100svh - var(--q-header-h));
    min-height: 620px;
  }

  .q-team-card img {
    width: 190px;
    height: 190px;
  }
}

@keyframes q-menu-item {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

/* Hero headline steps with the sphere geometry, not with the type scale.
   See .q-display for why. The sub uses --fs-heading and needs no pin. */
@media (min-width: 768px) and (max-width: 1100px) {
  .q-display {
    font-size: 61px;
  }
}

@media (max-width: 767.98px) {
  .q-display {
    font-size: 40px;
  }
}

/* The label size steps with the artwork's rendered scale, not with the page.
   Below 768 the diagram holds a 640px floor and the mobile rule takes over
   (see .q-graphic-scroll); between 768 and 1100 the svg renders at roughly
   720–860px, where the base 12.5 user units resolve to only 9–10px. Without
   this rung the labels dropped 37% across the single pixel between 767 and
   768 — legible on a phone, texture on a tablet. */
@media (min-width: 768px) and (max-width: 1099.98px) {
  .graphic .node-label {
    font-size: 17px;
    font-weight: 600;
    letter-spacing: 0.08em;
    fill: rgba(234, 242, 251, 0.86);
  }
}

@media (max-width: 359.98px) {
  .q-display {
    font-size: 36px;
  }
}

/* Short viewports — landscape phones and 320x480-class devices.

   The hero is an absolutely-positioned composition: the copy is pinned at
   --q-sphere-copy-top and the CTA at --q-sphere-cta-offset, both keyed to the
   sphere, and the section carries a min-height so the composition always has
   room. When the viewport is shorter than that min-height the arithmetic runs
   off the bottom of the screen and takes the hero's only invitation with it.
   Measured at 844x390 (a rotated phone): the hero is held at 680px against a
   390px viewport and the CTA's bottom lands at 447 — 57px below the fold. At
   320x480 it is 15px below; at 320x568 it clears by 7.

   The pins are not retuned here. That is the hero redesign, and it is out of
   scope; this is an escape hatch. Below the threshold the copy and the CTA
   return to normal flow and .q-hero's existing flex centring does the layout,
   so the invitation physically cannot leave the screen. The sphere stays
   behind as atmosphere.

   The threshold is measured, not guessed. The pinned composition needs about
   730px of viewport before its svh caps stop protecting it; below that the
   CTA starts leaving the screen, and it does so on ordinary laptops, not just
   phones — 1440x700 put it 9px under and 1024x640 put it 19px under. 767.98px
   catches every viewport measured to fail and leaves 390x844, 1440x900 and
   everything taller on the tuned composition, byte for byte. */
@media (max-height: 767.98px) {
  .q-hero {
    height: auto;
    min-height: calc(100svh - var(--q-header-h));
    padding: var(--sp-7) 0;
  }

  .q-hero .q-hero-copy {
    position: static;
    transform: none;
  }

  .q-hero .q-btn {
    position: static;
    margin-top: var(--sp-6);
    transform: none;
  }
}

/* A rotated phone is the extreme case: ~390px of height for a headline, a
   support line and a button, with 68px of it already spent on the bar. Flow
   alone still overran it by 5px, so the air comes in. */
@media (max-height: 449.98px) {
  .q-hero {
    padding: var(--sp-5) 0;
  }

  .q-hero .q-sub {
    margin-top: var(--sp-3);
  }

  .q-hero .q-btn {
    margin-top: var(--sp-4);
  }
}

@media (max-width: 767.98px) {
  .q-footer {
    padding: var(--sp-7) 0 var(--sp-6);
  }

  .q-footer > .container-xl > .d-flex {
    flex-direction: column;
    align-items: flex-start !important;
  }

  .q-footer .nav {
    display: block;
    margin: var(--sp-2) 0 !important;
  }

  /* Stacked: fit-content keeps underlines on the word, not full width.

     This is the one part of the site that exists to be tapped, and measured
     at 390px these links were 21px tall — the desktop rule carries no bottom
     padding because there the links sit on a shared baseline with the logo,
     and a mouse is precise enough not to care. The box grows to --q-hit here
     and the words do not change size; --q-rule-drop pushes the underline back
     out so it stays on the WORD rather than following the box down. */
  .q-footer .nav-link {
    --q-rule-inset: 0px;
    --q-rule-drop: 10px;
    display: flex;
    align-items: center;
    width: fit-content;
    min-width: var(--q-hit);
    padding: 15px 0 16px;
  }

  /* 22px mark, 44px target. The desktop rule nudges the glyph down to share
     the nav's baseline; here the row is stacked, so the nudge comes off and
     padding does the work. Negative left margin keeps the artwork on the
     page's left register while the box grows around it. */
  .q-footer .q-linkedin {
    padding: 11px;
    margin-left: -11px;
    transform: none;
  }

  /* Fine print inside a sentence: the inline tier, not the full 44 — at 44
     these three would be taller than the line they sit in. */
  .q-footer .q-copyright a {
    display: inline-flex;
    min-height: var(--q-hit-inline);
    align-items: center;
  }

  .q-hero {
    --q-sphere-size: min(76svh, 156vw, 700px);
    --q-sphere-top: 18px;
    --q-sphere-copy-offset: min(25.84svh, 53.04vw, 238px);
    --q-sphere-copy-top: calc(18px + var(--q-sphere-copy-offset));
    --q-sphere-cta-offset: min(49.4svh, 101.4vw, 455px);
    min-height: 560px;
  }

  .q-hero .q-hero-copy {
    padding: 0 var(--q-gutter);
  }

  /* Caps cost more width than mixed case, so this wraps to two lines on a
     phone — measured 55px against a 27.4px line box at both 390 and 320. That
     is fine and the margin below absorbs it; the old comment here claimed it
     had to stay on one line "or it lands on the CTA", which the measurement
     does not support. Kept as a note so nobody re-tightens the tracking to
     satisfy a constraint that is not real. */
  .q-hero .q-sub {
    margin-top: var(--sp-5);
  }

  /* Section/press horizontal inset lives on the shared container gutter only
     — do not add a second padding-inline here or logos and blocks drift. */

  .graphic-wrap {
    padding: var(--sp-4) var(--q-gutter) var(--sp-4);
  }

  .q-graphic-scroll {
    -webkit-mask-image: linear-gradient(
      90deg,
      transparent 0,
      #000 14px,
      #000 calc(100% - 14px),
      transparent 100%
    );
    mask-image: linear-gradient(
      90deg,
      transparent 0,
      #000 14px,
      #000 calc(100% - 14px),
      transparent 100%
    );
  }

  /* The floor, and it is a compromise with its reasons. The artwork is
     1040x445 — very wide — so on a phone you cannot have both the whole
     topology in one view AND labels you can read: at full width the 21-unit
     labels resolve to 6.9px. 560px puts them at ~11.3px, which is the site's
     own --fs-label rung, and keeps ~70% of the composition in view at 390
     with the core dead centre. Larger floors read better and show less; this
     is the point where both are tolerable. */
  .q-graphic-scroll .graphic {
    width: 560px;
    min-width: 560px;
  }

  /* The artwork scales to a third of its desktop size here, which would
     take the 12.5-unit labels below legibility (~5px). Larger units and a
     heavier weight keep the participants readable; tracking comes in so
     the longer labels still clear their neighbours. */
  .graphic .node-label {
    font-size: 21px;
    font-weight: 600;
    letter-spacing: 0.06em;
    fill: rgba(234, 242, 251, 0.9);
  }

  .q-band {
    padding-bottom: var(--sp-7);
  }

  .q-press-card .q-press-meta {
    flex-wrap: wrap;
  }

  .q-team {
    padding-bottom: var(--q-section-y);
  }

  .q-team-cta {
    padding: 0 var(--sp-4) var(--q-section-y);
  }

  .q-contact {
    min-height: 0;
    padding: var(--sp-8) var(--sp-4) var(--sp-10);
  }

  .q-article {
    padding: var(--sp-7) var(--q-gutter) var(--q-section-y);
  }
}

@media (max-width: 575.98px) {
  .q-hero {
    height: calc(100svh - var(--q-header-h));
    min-height: 540px;
  }

  .q-cred-rail {
    flex-direction: column;
  }

  .q-cred:first-child {
    border-radius: var(--q-radius-card) var(--q-radius-card) 0 0;
  }

  .q-cred:last-child {
    border-radius: 0 0 var(--q-radius-card) var(--q-radius-card);
  }

  .q-cred-divider {
    width: auto;
    height: 1px;
    margin: 0 var(--sp-5);
  }

  .q-team-card {
    min-height: 0;
    gap: var(--sp-4);
    align-items: flex-start;
  }

  /* Keep square crop; scale down rather than changing aspect. */
  .q-team-card img {
    width: 120px;
    height: 120px;
  }

  /* The reading column was paying two gutters twice over: the section's own
     padding plus the panel's. At 390px that spent 96px — a quarter of the
     screen — on horizontal chrome and left a 30-character line, against 60 on
     a desktop. Halving both recovers roughly four characters a line. The
     leading eases with it: 1.78 is tuned for a 60-character measure and reads
     loose on a short one. */
  .q-article {
    padding-inline: var(--sp-4);
  }

  .q-article-body {
    padding-inline: var(--sp-4);
    line-height: 1.68;
  }

  .q-team-card .q-team-info {
    padding: var(--sp-2) var(--sp-1) var(--sp-2) 0;
  }

  .q-team-card p {
    margin-bottom: var(--sp-3);
  }

  .q-contact-card {
    padding: var(--sp-6) var(--sp-5);
  }
}

/* Narrower than the block above, and it must come AFTER it: both rules target
   .q-team-card img at equal specificity, so source order decides. */
@media (max-width: 359.98px) {
  /* 272px of card, minus padding, minus a 120px portrait, left 110px for a
     19px name — five of the six wrapped across two lines. The portrait gives up
     the difference rather than the name, because the name is what the card is
     for. */
  .q-team-card img {
    width: 88px;
    height: 88px;
  }
}

/* ==========================================================================
   Scroll reveal

   Subjects rise into view as the reader scrolls. Two engines, one look:

   Where scroll-driven animations exist, each reveal is scrubbed by its own
   travel through the viewport (animation-timeline: view()): the rise starts
   the moment the element peeks above the fold and completes about a third
   of the way up. Nothing pops — subjects flow in and out with the scroll,
   in both directions, and stagger children cascade naturally because each
   child scrubs its own position. Elsewhere, an IntersectionObserver plays
   the same rise on entry, tuned to fire the moment the element appears, so
   the fallback keeps the immediate feel.

   Everything is gated on body.q-anim, which only site.js adds — and only
   when IntersectionObserver exists and the user has not asked for reduced
   motion. No JS, old browser, or reduced motion all degrade to the same
   thing: nothing is ever hidden.

   .reveal          — the element rises as one piece.
   .reveal-stagger  — its direct children rise in sequence instead.

   Children animate with a keyframe rather than a transition so components
   that own a `transition` (the CTA button) keep their hover behaviour. */
.q-anim .reveal,
.q-anim .reveal-stagger > * {
  opacity: 0;
}

.q-anim .reveal.is-in {
  animation: q-rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

.q-anim .reveal-stagger.is-in > * {
  animation: q-rise 0.6s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

.q-anim .reveal-stagger.is-in > :nth-child(1) { animation-delay: 0.04s; }
.q-anim .reveal-stagger.is-in > :nth-child(2) { animation-delay: 0.11s; }
.q-anim .reveal-stagger.is-in > :nth-child(3) { animation-delay: 0.18s; }
.q-anim .reveal-stagger.is-in > :nth-child(4) { animation-delay: 0.25s; }

@keyframes q-rise {
  from {
    opacity: 0;
    transform: translateY(24px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* Scroll-scrubbed reveals. The same q-rise keyframes, but the timeline is
   the element's own position instead of a clock — .is-in coming and going
   changes nothing, so the observer can keep running untouched. The range
   ends at cover 38% so a subject is fully present by the time the eye
   reaches it, while taller pieces (the network diagram) still resolve
   before their midpoint. Time-based delays are meaningless against a
   scrubbed timeline; the :nth-child(n) rule zeroes the stagger delays at
   the same specificity they were set. */
@supports (animation-timeline: view()) {
  .q-anim .reveal,
  .q-anim .reveal.is-in,
  .q-anim .reveal-stagger > *,
  .q-anim .reveal-stagger.is-in > * {
    animation: q-rise linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 38%;
  }

  .q-anim .reveal-stagger.is-in > :nth-child(n) {
    animation-delay: 0s;
  }

  /* An element in the page's LAST viewport can never travel to `cover 38%` —
     the document runs out of scroll before the animation runs out of range,
     so it freezes partway and stays there. Measured on team.html at maximum
     scroll, the closing CTA sat at opacity 0.851 at 1440x900, 0.779 at
     1440x1000 and 0.764 on an iPad in portrait: permanently half-faded, which
     reads as disabled on the one control the page ends with.

     A tail reveal scrubs over its own ENTRY instead, which always completes
     because "fully entered" is reachable by definition. Put .reveal--tail on
     a reveal that sits in the final screenful of its page. */
  .q-anim .reveal--tail.reveal,
  .q-anim .reveal--tail.reveal.is-in,
  .q-anim .reveal--tail.reveal-stagger > *,
  .q-anim .reveal--tail.reveal-stagger.is-in > * {
    animation-range: entry 0% entry 75%;
  }
}

/* Anchor targets clear the fixed header.

   An in-page link scrolls its target to the top of the VIEWPORT, and the top
   of the viewport is behind a 76px bar — so every #hash on the site landed
   with the heading invisible and the reader dropped mid-sentence, including
   the privacy link the cookie card itself points at. scroll-margin-top is the
   property for this: it changes where scrolling considers the element's edge
   to be, without moving the element.

   Put .q-anchor on the element a link should LAND ON, not on the link. */
.q-anchor {
  scroll-margin-top: calc(var(--q-header-h) + var(--sp-7));
}

:focus-visible {
  outline: 2px solid var(--q-orange);
  outline-offset: 3px;
}

/* Bootstrap's `.nav-link:focus-visible` is specificity (0,2,0) and this file's
   bare `:focus-visible` is (0,1,0), so on every nav link in the header and the
   footer the site's ring lost and Bootstrap painted its own — a blue box
   shadow, the only blue anywhere in the palette, on all seven pages. The guide
   states plainly that keyboard focus is a 2px orange outline offset 3px and
   that a focus style is never removed without a replacement; this is what
   makes that true. */
.q-header .nav-link:focus-visible,
.q-footer .nav-link:focus-visible {
  outline: 2px solid var(--q-orange);
  outline-offset: 3px;
  box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  /* The press still happens, it just happens in colour: scale is the one part
     of it that is motion, so it goes to nothing here while the fill, the
     border and the text all still answer the finger. */
  :root {
    --q-press-scale: 1;
    --q-press-scale-surface: 1;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    /* NOT `transition-duration: 0`. Killing every transition takes the
       comprehension with the motion — a button whose fill snaps between two
       states gives less information than one that visibly crosses between
       them, and reduced motion never asked for less information. So
       transitions survive at a short, fixed tempo, but only for the
       properties that are not travel: colour, light and opacity. Anything
       positional (transform, scale, height, width) is dropped from the list
       entirely and therefore lands instantly. */
    transition-duration: 120ms !important;
    transition-delay: 0s !important;
    transition-property:
      color, background-color, border-color, outline-color, box-shadow,
      opacity, fill, stroke, text-decoration-color !important;
  }

  /* The menu keeps its cross-fade — a panel that simply exists on one frame
     and not the next is the disorientation this preference is asking us to
     avoid — but loses the slide and the per-row cascade.

     Restated in full because the blanket rule above zeroes every delay, and
     `visibility` is what makes a closed panel unreachable: without its delay
     it flips to hidden on the first frame of the dismissal, the fade plays to
     nobody, and the panel hard-cuts out after having softly faded in.
     Symmetry in, symmetry out. */
  .q-header .navbar-collapse {
    transform: none !important;
    transition-property: opacity, visibility !important;
    transition-duration: 120ms, 0s !important;
    transition-delay: 0s, 120ms !important;
  }

  .q-header .navbar-collapse.q-menu-open {
    transition-property: opacity, visibility !important;
    transition-duration: 120ms, 0s !important;
    transition-delay: 0s, 0s !important;
  }

  .q-header .navbar-collapse.q-menu-open .nav-item {
    animation: none;
  }
}

/* Glass is a technique, not the message. Where the system asks for less of it,
   every translucent surface goes to solid chrome and the blur comes off — the
   hierarchy the material was carrying is then carried by the hairline that was
   already on each of these surfaces. The header's scroll-driven condensation
   goes with it: those keyframes animate opacity and would fade the solid
   ground back out. */
@media (prefers-reduced-transparency: reduce) {
  .q-footer,
  .q-article-body {
    background-color: var(--q-chrome);
    backdrop-filter: none;
  }

  .q-header::before {
    inset: 0;
    background: var(--q-chrome);
    backdrop-filter: none;
    animation: none;
    opacity: 1;
    -webkit-mask-image: none;
    mask-image: none;
  }
}

/* More contrast means more definition, not more brightness: the recessed tones
   step up until each clears its ground, and every hairline that was
   deliberately near-invisible becomes a real edge. The palette's hue is
   untouched — this is the same design with its edges drawn in. */
@media (prefers-contrast: more) {
  :root {
    --q-muted: #c9d6e4;
    --q-text-recessed: #a5b3c2;
    --q-blue-soft: #a9c6e8;
    --q-border: rgba(184, 203, 225, 0.5);
    --q-border-subtle: rgba(184, 203, 225, 0.3);
    --q-card-border: rgba(184, 203, 225, 0.42);
    --q-card-border-hover: rgba(184, 203, 225, 0.6);
    --q-orange-tint: rgba(210, 118, 75, 0.2);
    --q-orange-tint-hover: rgba(210, 118, 75, 0.34);
  }

  /* Reading over a translucent panel is where thin contrast hurts most. */
  .q-footer,
  .q-article-body {
    background-color: var(--q-chrome);
    backdrop-filter: none;
  }

  .q-header::before {
    inset: 0;
    background: var(--q-chrome);
    backdrop-filter: none;
    animation: none;
    opacity: 1;
    -webkit-mask-image: none;
    mask-image: none;
  }

  .q-contact-card .form-control {
    border-color: rgba(184, 203, 225, 0.7);
  }

  :focus-visible {
    outline-width: 3px;
  }
}

/* Forced colours replaces every colour the page declares — including
   `background: currentColor`, which is how the shared arrow glyph is painted.
   Every arrow on the site would simply vanish. ButtonText is a system colour
   the mode keeps, and forced-color-adjust lets it survive. */
@media (forced-colors: active) {
  .q-arrow::after,
  .q-btn::after,
  .q-header .nav-item:last-child .nav-link::after,
  .q-press-card .q-press-go::after {
    background: ButtonText;
    forced-color-adjust: none;
  }
}

/* ==========================================================================
   Print

   A press release and a privacy policy are documents. People print them, save
   them to PDF and attach them to email — and until now doing so produced pale
   text on a near-black field, a fixed header stamped over the first page, a
   translucent reading panel that came out as a grey box, and a full-bleed
   gradient that empties a cartridge.

   The screen design is a dark field lit by one horizon. None of that is a
   property of the CONTENT, so print takes the content and leaves the lighting
   behind: ink on paper, the brand mark as a masthead, and every external
   link's destination spelled out, because a printed page cannot be clicked.
   ========================================================================== */
@media print {
  html {
    /* The dark scheme is what tells the browser to render pale UI. On paper
       there is no UI, and it would only tint the text. */
    color-scheme: light;
    scroll-behavior: auto;
    background: #fff;
  }

  body {
    display: block;
    min-height: 0;
    padding-top: 0;
    background: #fff;
    color: #000;
    font-size: 11pt;
    line-height: 1.5;
  }

  /* The field, its light, and everything that only exists to move. */
  .q-page-home,
  .q-page-contact,
  .q-page-press,
  .q-page-team,
  .q-page-legal,
  .q-page-style {
    background: #fff;
  }

  .q-page-home::before,
  .q-page-contact::before,
  .q-page-press::before,
  .q-page-team::before,
  .q-page-legal::before,
  .q-page-style::before,
  .q-page-home::after,
  .q-page-contact::after,
  .q-page-press::after,
  .q-page-team::after,
  .q-page-legal::after,
  .q-page-style::after,
  .q-hero,
  .q-hero-sphere,
  .q-consent,
  .q-header .navbar-nav,
  .q-header .navbar-toggler,
  .q-footer .nav,
  .q-footer .q-linkedin,
  /* The diagram is a dark illustration with pale labels: on paper it prints
     as mud. Its closing couplet goes with it, because that copy is written
     about the picture. */
  .graphic-wrap {
    display: none !important;
  }

  /* The header stops floating and becomes a masthead on page one: the
     lockup, a rule under it, nothing else. */
  .q-header {
    position: static;
    min-height: 0;
    padding: 0 0 12pt;
    background: none;
    border-bottom: 1px solid #999;
    backdrop-filter: none;
    animation: none;
  }

  .q-header::before {
    display: none;
  }

  .q-header > .container-fluid {
    min-height: 0;
    padding-inline: 0;
  }

  .q-header .navbar-brand,
  .q-footer .q-footer-logo {
    padding: 0;
  }

  /* The lockup is an orange mark beside a WHITE wordmark, which on paper is
     an orange square next to nothing at all — the company's name vanishes
     from its own press release. The artwork gives way to the name set as
     real text in the brand face: legible in ink, and selectable and
     searchable in a PDF, which the image never was. */
  .q-brand-lockup {
    display: none;
  }

  .q-header .navbar-brand::after,
  .q-footer .q-footer-logo::after {
    content: "Quorim";
    color: #000;
    font-family: var(--q-font-display);
    font-size: 15pt;
    font-weight: 600;
    letter-spacing: -0.02em;
  }

  .q-footer .q-footer-logo::after {
    font-size: 11pt;
  }

  /* Titles and reading columns go full measure — a centred 720px column
     with 260px of white either side wastes a third of the sheet. */
  .q-band,
  .q-article {
    padding: 18pt 0 0;
    text-align: left;
  }

  .q-band h1,
  .q-band--press h1 {
    max-width: none;
    margin: 0 0 8pt;
    color: #000;
    font-size: 20pt;
    line-height: 1.15;
  }

  .q-page-team .q-band h1 {
    color: #000;
  }

  .q-band--press p,
  .q-band--legal p {
    max-width: none;
    margin: 0;
    color: #333;
    font-size: 11pt;
    letter-spacing: 0;
    text-transform: none;
  }

  .q-article-body {
    max-width: none;
    padding: 0;
    color: #000;
    font-size: 11pt;
    line-height: 1.55;
    background: none;
    border: 0;
    border-radius: 0;
    backdrop-filter: none;
  }

  .q-article-body h2,
  .q-article-body h3 {
    color: #000;
    /* A heading orphaned at the foot of a sheet is the one thing a printed
       document must not do. */
    break-after: avoid;
    page-break-after: avoid;
  }

  .q-article-body p,
  .q-article-body li {
    orphans: 3;
    widows: 3;
  }

  .q-article-body li::marker {
    color: #000;
  }

  .q-article-body a,
  .q-footer .q-copyright a {
    color: #000;
    text-decoration: underline;
  }

  /* Paper cannot be clicked. Spell out where a link went — but only for
     real destinations: printing "(#cookies)" after an in-page link, or the
     whole of a mailto:, is noise rather than a citation. */
  .q-article-body a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    word-break: break-all;
  }

  .q-footer {
    padding: 12pt 0 0;
    color: #000;
    background: none;
    border-top: 1px solid #999;
    backdrop-filter: none;
  }

  .q-footer > .container-xl {
    padding-inline: 0;
  }

  .q-footer .q-copyright {
    margin: 0;
    color: #333;
    font-size: 8pt;
  }

  .q-btn,
  .q-arrow {
    color: #000;
    background: none;
    border-color: #999;
  }

  @page {
    margin: 16mm 14mm;
  }
}
