/* ==========================================================================
   Growack v2.0 — Sushil feedback build  (loaded LAST, after graphics.css)
   Implements: hero compression (A), early trust/logos (B), repeated reviews (C),
   bold section rhythm (D), CTA emphasis (E), 4-column footer (F).
   Palette unchanged: orange #F97316/#C2410C · teal #0F766E · slate #111827 ·
   surfaces #FFFFFF / #F1F5F9 / #F8FAFC.  Fonts unchanged: DM Sans + Inter.
   ========================================================================== */

/* ---------- A. HERO COMPRESSION — fit the first fold, CTA visible ---------- */
/* His #1 item: "content in hero banner is big… must be contained in first fold
   with clear visible CTA at the end of the fold." */
.hero--dark { padding-block: clamp(28px, 4.5vh, 56px) clamp(24px, 4vh, 44px); }
.hero--dark .hero-grid { gap: clamp(24px, 3.5vw, 56px); align-items: center; }
.hero--dark .hero-superline { margin-bottom: var(--space-4); }
/* H1 kept strong (weight 800 + tight tracking) but sized to the brief §2.2
   scale: 44px desktop / 32px mobile, line-height 1.2. */
.hero--dark h1 { font-size: clamp(2rem, 1.4rem + 2.4vw, 2.75rem); line-height: 1.2; margin-bottom: var(--space-4); }
.hero--dark .hero-sub { font-size: 1.03rem; line-height: 1.55; max-width: 46ch; margin-bottom: var(--space-5, 20px); }
.hero--dark .check-list { margin-top: var(--space-4); gap: 10px; }
.hero--dark .check-list li { font-size: 0.94rem; line-height: 1.45; }
.hero--dark .check-list .cl-ic { width: 22px; height: 22px; }
.hero--dark .check-list .cl-ic svg { width: 13px; height: 13px; }
.hero--dark .cta-row { margin-top: var(--space-6); }
.hero--dark .hero-trust { margin-top: var(--space-4); font-size: 0.85rem; }
/* the product mockup: smaller so it supports rather than competes */
.hero--dark .app-window { transform: perspective(1600px) rotateY(-6deg) rotateX(2deg) scale(0.94); }
@media (max-width: 1180px) { .hero--dark .app-window { transform: scale(0.9); } }
@media (max-width: 860px)  { .hero--dark .app-window { transform: none; } }
/* short viewports (laptops) — clamp harder so the CTA never drops below fold */
@media (min-width: 861px) and (max-height: 860px) {
  .hero--dark { padding-block: 24px 20px; }
  .hero--dark h1 { font-size: clamp(2rem, 1.3rem + 2.1vw, 2.7rem); }
  .hero--dark .hero-sub { font-size: 0.98rem; margin-bottom: 14px; }
  .hero--dark .check-list { margin-top: 12px; gap: 8px; }
  .hero--dark .check-list li { font-size: 0.88rem; }
  .hero--dark .app-window { transform: scale(0.82); }
}

/* ---------- B. TRUST STRIP — real client logos, directly under the hero ---- */
.trust-strip { padding-block: clamp(22px, 3vw, 34px); background: var(--color-surface); border-bottom: 1px solid var(--color-border); }
/* ---- ONE MICRO-LABEL FOR EVERY LOGO LOOP ---------------------------------
   The client strip and the partner strip are the same object: a short
   uppercase caption sitting directly above a looping row of marks. Both are
   rendered on all 45 content pages, one under the other, ~300px apart. They
   were declared twice and had drifted into two different type treatments:
   .trust-strip__label ran 0.78rem / 0.13em (12.48px, tracking 1.62px) and
   .partner-strip__label ran var(--fs-sm) / .08em (14px, tracking 1.12px).
   Each passes every per-element check on its own; stacked, one caption is
   visibly smaller and wider-tracked than the caption 300px below it.
   Converged on the TOKEN values -- 0.78rem was a magic number used nowhere
   else on the site, while var(--fs-sm) is the site's small-text rung and was
   already carried by two of the three loop captions.
   The THIRD caption of the same object is .marquee-label, the line above the
   tools loop, and it is the one that stacks directly under the other two on
   every page. It was still declared on its own in premium.css at weight 500,
   no letter-spacing and NO text-transform, so it rendered sentence case at a
   lighter weight immediately below two uppercase, 600-weight, tracked
   captions. Folded in here: v2.css loads after premium.css, so this single
   declaration is the one that lands, and all three captions now resolve to
   byte-identical computed type. Uppercase is the treatment kept because it
   was already the site's micro-label convention on two of the three and on
   every other logo-loop caption; the copy strings themselves are untouched
   and the case is applied purely by text-transform.
   DECLARED ONCE, for all three selectors, so they cannot drift apart again.
   margin-BOTTOM only, never the `margin` shorthand: the two trust-inset-*
   partials lift this caption with .mt-8 / .mt-12 in a hero, and a shorthand
   here would out-specify those utilities and delete the lift. */
.trust-strip__label,
.partner-strip__label,
.marquee-label {
  text-align: center; font-family: var(--font-body); font-size: var(--fs-sm); font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-muted);
  margin-bottom: var(--space-6);
}
.trust-logos {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: clamp(18px, 2.6vw, 40px);
}
/* Keep the strip on a SINGLE row on desktop so no logo is orphaned underneath
   (6 logos previously wrapped, stranding the last one on a second row). */
@media (min-width: 861px) {
  .trust-logos { flex-wrap: nowrap; gap: clamp(16px, 2.2vw, 34px); }
  .trust-logos img { flex: 0 1 auto; min-width: 0; }
}
/* Box comes from the optical scale table at the foot of this file.
   Ink, opacity and the hover transition come from figures.css section 9,
   the one rule that owns a logo mark's appearance. */
/* dark variant — sits inside the dark hero band (Anaplan pattern) */
.trust-strip--on-dark { background: transparent; border-bottom: 0; border-top: 1px solid rgba(255,255,255,0.12); margin-top: var(--space-8); }
/* One declared on-dark token, not an ad-hoc alpha: at 0.6 this caption ran
   3.3:1 on #111827 and failed AA, and it was a second treatment for an
   element that is identical on the other 43 pages. --color-on-dark is the
   site's on-dark body token (~10:1 here). */
.trust-strip--on-dark .trust-strip__label { color: var(--color-on-dark); }
/* REMOVED — `filter: grayscale(1) brightness(0) invert(1)` forced every mark in a
   .trust-logos row on a dark strip to pure white. The cream inset band travels
   WITH the loop, so those marks rendered white-on-cream: the historic
   invisible-logo bug, documented again further down this file. The rule was
   dormant only because no page emits .trust-logos today; leaving it shipped
   meant reintroducing the old markup would silently restore the bug. The
   on-dark strip now inherits the one measured treatment, like every other
   surface. */

/* ---------- B/C. LOGO GRID + TESTIMONIAL (Unmuted pattern, our styling) ---- */
.proof-split { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(24px, 3.5vw, 48px); align-items: stretch; }
@media (max-width: 900px) { .proof-split { grid-template-columns: 1fr; } }
.logo-grid { display: grid; grid-template-columns: repeat(3, 1fr); border: 1px solid var(--color-border); border-radius: var(--radius-lg); overflow: hidden; background: var(--color-surface); }
@media (max-width: 520px) { .logo-grid { grid-template-columns: repeat(2, 1fr); } }
.logo-grid__cell { display: grid; place-items: center; padding: clamp(16px, 2.4vw, 26px); border-right: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border); }
.logo-grid__cell img { height: 30px; width: auto; max-width: 100%; object-fit: contain; filter: grayscale(1); opacity: 0.66; transition: filter .25s, opacity .25s; }
.logo-grid__cell:hover img { filter: grayscale(0); opacity: 1; }

/* ---------- C. TESTIMONIAL CARD (real, named) ----------------------------- */
.tq {
  display: flex; flex-direction: column; gap: var(--space-4);
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-lg); padding: clamp(20px, 2.6vw, 30px); height: 100%;
}
.tq__mark { color: var(--color-primary); font-family: var(--font-heading); font-weight: 800; font-size: 2rem; line-height: 0.6; }
.tq__quote { font-size: 1.02rem; line-height: 1.55; color: var(--color-text); }
.tq__foot { display: flex; align-items: center; gap: var(--space-3); margin-top: auto; padding-top: var(--space-2); }
.tq__avatar {
  width: 42px; height: 42px; border-radius: 50%; flex: none; display: grid; place-items: center;
  font-family: var(--font-heading); font-weight: 700; font-size: 0.9rem;
  background: var(--color-primary-soft); color: var(--color-primary);
}
/* display:block is REQUIRED — these are <span>s, and inline spans sit on one line,
   which made the name and role run together ("SusieDigital Marketing Manager"). */
.tq__name { display: block; font-weight: 700; font-size: 0.95rem; line-height: 1.2; }
.tq__role { display: block; font-size: var(--fs-sm); color: var(--color-text-muted); line-height: 1.3; }
/* accent variant for the hero-adjacent card */
.tq--accent { background: linear-gradient(160deg, #fff, var(--color-primary-soft)); border-color: #f6dfae; }
.tq--teal .tq__mark { color: var(--color-teal); }
.tq--teal .tq__avatar { background: var(--color-teal-soft); color: var(--color-teal); }
/* on dark sections */
.section--dark .tq { background: rgba(255,255,255,0.05); border-color: rgba(255,255,255,0.14); }
.section--dark .tq__quote { color: #fff; }
.section--dark .tq__name { color: #fff; }
.section--dark .tq__role { color: rgba(255,255,255,0.62); }

/* ---------- C. REVIEW RATING BADGES (Clutch + HubSpot, links out) --------- */
.ratings { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: stretch; }
.rating-card {
  display: flex; align-items: center; gap: var(--space-4);
  border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-surface);
  padding: 14px 18px; text-decoration: none; color: inherit;
  /* hover timing: MOTION SYSTEM role 3, foot of this file */
}
.rating-card:hover { border-color: #d9c9b8; box-shadow: var(--shadow-md); transform: translateY(-2px); text-decoration: none; }
.rating-card img { height: 40px; width: auto; flex: none; }
.rating-card__score { font-family: var(--font-heading); font-weight: 800; font-size: 1.5rem; line-height: 1; color: var(--color-text); }
.rating-card__stars { color: var(--color-primary-bright); font-size: 0.9rem; letter-spacing: 1px; line-height: 1; margin-top: 3px; }
.rating-card__meta { font-size: var(--fs-xs); color: var(--color-text-muted); margin-top: 3px; }
.section--dark .rating-card { background: rgba(255,255,255,0.05); border-color: rgba(255,255,255,0.14); }
.section--dark .rating-card__score { color: #fff; }
.section--dark .rating-card__meta { color: rgba(255,255,255,0.62); }
/* The review count is the third row of the same card and needs the same
   dark-surface treatment as __meta above. Left on --color-text-muted it
   composites to 2.83:1 on rgb(29,36,50) and fails AA; this token gives 6.84:1. */
.section--dark .rating-card__reviews { color: rgba(255,255,255,0.62); }

/* ---------- D. SECTION RHYTHM — his prescribed alternation ---------------- */
/* Bold, deliberate differentiation so "big moments" read differently to
   supporting sections. Use these instead of everything defaulting to flat. */
.sec-white  { background: #FFFFFF; }
.sec-gray   { background: #F1F5F9; }              /* his exact value for Services */
.sec-paper  { background: var(--color-bg); }
.sec-accent { background: linear-gradient(180deg, #FFF7ED 0%, #FEF3C7 100%); }  /* soft orange */
.sec-accent-teal { background: linear-gradient(180deg, #FFFFFF 0%, var(--color-teal-soft) 100%); }
.sec-band   { background: linear-gradient(180deg, #F8FAFC 0%, #F1F5F9 100%); }
/* hairline separators so adjacent light bands still read as distinct */
.sec-gray + .sec-white, .sec-white + .sec-gray,
.sec-paper + .sec-white, .sec-white + .sec-paper { border-top: 1px solid var(--color-border); }

/* ---------- E. PROMOTIONAL BAND — for high-priority CTAs (Quick Audit) ---- */
/* "this is important CTA and it cannot just go like double and unhighlighted" */
.promo {
  position: relative; overflow: hidden;
  background: linear-gradient(135deg, #111827 0%, #17233b 60%, #1f2937 100%);
  border-radius: var(--radius-lg); padding: clamp(26px, 3.4vw, 44px);
  color: rgba(255,255,255,0.78);
}
.promo::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(52% 78% at 88% 12%, rgba(249,115,22,0.26), transparent 62%),
              radial-gradient(46% 70% at 4% 96%, rgba(15,118,110,0.22), transparent 66%);
}
.promo > * { position: relative; z-index: 1; }
.promo__inner { display: flex; align-items: center; justify-content: space-between; gap: var(--space-8); flex-wrap: wrap; }
.promo__eyebrow {
  display: inline-flex; align-items: center; gap: 8px; font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-primary-bright); margin-bottom: 10px;
}
.promo__eyebrow::before { content: ""; width: 20px; height: 1.5px; background: var(--color-primary-bright); }
.promo h2, .promo .promo__title { color: #fff; font-size: clamp(1.4rem, 1.05rem + 1.3vw, 1.9rem); line-height: 1.2; max-width: 24ch; }
.promo p { color: rgba(255,255,255,0.76); margin-top: 10px; max-width: 60ch; }
.promo .free-tag { color: var(--color-primary-bright); font-weight: 700; }
.promo__cta { flex: none; display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.promo__note { font-size: var(--fs-xs); color: rgba(255,255,255,0.55); }

/* ---------- D5. SERVICE / EXPERTISE TILES — visible without hover --------- */
/* "at the moment even you hover, then only appears" */
.tile-grid .card, .card--tile {
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-lg); box-shadow: 0 1px 2px rgba(17,24,39,.04), 0 10px 24px -18px rgba(17,24,39,.18);
  position: relative; overflow: hidden;
}
.card--tile::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--color-primary-bright);
}
.card--tile:nth-child(even)::before { background: var(--color-teal); }
.card--tile:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: #dfe6ee; }
/* centred section head option (D6) */
.section-head--mid { text-align: center; margin-inline: auto; }
.section-head--mid p { margin-inline: auto; }

/* ---------- F. FOOTER — 4 middle columns per Brief §3.2 ------------------- */
.footer-top--v2 { grid-template-columns: 1.25fr repeat(4, 1fr); }
@media (max-width: 1040px) { .footer-top--v2 { grid-template-columns: 1fr 1fr 1fr; } }
@media (max-width: 720px)  { .footer-top--v2 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 460px)  { .footer-top--v2 { grid-template-columns: 1fr; } }
.footer-addr { font-size: var(--fs-sm); line-height: 1.6; color: #94a3b8; margin-top: var(--space-4); }
.footer-addr strong { color: #e2e8f0; font-weight: 600; display: block; }
/* Meeting doc item 9: email id sits ABOVE the Growack address block. */
.footer-addr .footer-mail { display: block; color: #e2e8f0; margin-bottom: var(--space-3); word-break: break-word; }
.footer-addr .footer-mail:hover { color: #fff; }
/* Meeting doc item 10: styles.css `svg { fill: currentColor }` outranks the
   fill="none" presentation attribute, which floods closed outline shapes solid.
   Scoped guard so any outline glyph dropped into the social row stays an outline. */
.footer-social svg[fill="none"] { fill: none; }

/* ---------- G. BLOG DETAIL — two columns, guided nav LEFT (Exit Five / Dapper)
   "content on the right and navigation on the left… that's guided navigation,
   what is in this article, all the sections. That's pretty cool."             */
.article-layout { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: clamp(28px, 4vw, 64px); align-items: start; }
@media (max-width: 940px) { .article-layout { grid-template-columns: 1fr; } }
.article-nav { position: sticky; top: calc(var(--header-h) + 20px); align-self: start; }
@media (max-width: 940px) { .article-nav { position: static; } }
.article-nav__title { font-family: var(--font-heading); font-weight: 700; font-size: 0.92rem; margin-bottom: var(--space-4); }
.article-nav ol { list-style: none; padding: 0; margin: 0; display: grid; gap: 2px; counter-reset: toc; border-left: 2px solid var(--color-border); }
.article-nav li { margin: 0; }
/* SCOPED TO THE TOC LIST — a container rule must not reach a component.
   These three rules used to read `.article-nav a`, specificity (0,2,1), in the
   last stylesheet loaded. That beat `.btn--primary { color: var(--color-text) }`
   (0,1,0) in premium.css, so the sidebar's own primary CTA rendered muted grey
   #626977 on the orange fill (1.97:1), and `.article-nav a:hover` beat
   `.btn--primary:hover`, so the site's highest-priority CTA lost its orange fill
   on the exact interaction that precedes the click. The TOC links are the only
   intended target and they all live inside the <ol>. */
.article-nav ol a {
  display: block; padding: 8px 14px; font-size: 0.88rem; line-height: 1.4;
  color: var(--color-text-muted); border-left: 2px solid transparent; margin-left: -2px;
}
.article-nav ol a:hover { color: var(--color-text); text-decoration: none; background: var(--color-surface-alt); }
.article-nav ol a.is-current { color: var(--color-primary); font-weight: 600; border-left-color: var(--color-primary-bright); background: var(--color-primary-soft); }
.article-body { max-width: 38em; }   /* see figures.css — `ch` overshoots */
.article-body h2 { margin-top: var(--space-12); margin-bottom: var(--space-4); scroll-margin-top: calc(var(--header-h) + 24px); }
.article-body h3 { margin-top: var(--space-8); margin-bottom: var(--space-2); }
.article-body p { margin-bottom: var(--space-4); line-height: 1.7; color: var(--color-text-muted); }
.article-body p strong { color: var(--color-text); }
.article-body ul { margin: 0 0 var(--space-4); padding-left: 1.2rem; }
.article-body li { color: var(--color-text-muted); line-height: 1.65; margin-block: 6px; }
.article-meta { display: flex; align-items: center; gap: var(--space-3); font-size: var(--fs-sm); color: var(--color-text-muted); margin-top: var(--space-4); }
.article-meta .am-avatar { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-primary-soft); color: var(--color-primary); font-family: var(--font-heading); font-weight: 700; font-size: 0.8rem; }
/* newsletter promo inside the article template (Exit Five pattern) */
.article-cta {
  margin: var(--space-12) 0; padding: clamp(22px, 3vw, 32px);
  border-radius: var(--radius-lg); border: 1px solid var(--color-border);
  background: linear-gradient(160deg, #fff, var(--color-teal-soft));
}
.article-cta h3 { margin-bottom: var(--space-2); }
.article-cta p { color: var(--color-text-muted); margin-bottom: var(--space-6); }

/* ---------- G7. EVENTS — listing + empty state --------------------------- */
.event-empty {
  border: 1px dashed var(--color-border); border-radius: var(--radius-lg);
  background: var(--color-surface); padding: clamp(24px, 3.4vw, 40px); text-align: center;
}
.event-empty .ee-ic { width: 46px; height: 46px; border-radius: 12px; display: grid; place-items: center; margin: 0 auto var(--space-4); background: var(--color-primary-soft); color: var(--color-primary); }
.event-empty .ee-ic svg { width: 24px; height: 24px; }
.event-list { display: grid; gap: var(--space-4); }
.event-row {
  display: grid; grid-template-columns: 92px minmax(0,1fr) auto; gap: var(--space-6); align-items: center;
  border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-surface);
  padding: var(--space-6);   /* hover timing: MOTION SYSTEM role 3 */
}
.event-row:hover { border-color: #dfe6ee; box-shadow: var(--shadow-md); transform: translateY(-2px); }
@media (max-width: 700px) { .event-row { grid-template-columns: 1fr; gap: var(--space-3); } }
.event-date { text-align: center; border-right: 1px solid var(--color-border); padding-right: var(--space-4); }
@media (max-width: 700px) { .event-date { text-align: left; border-right: 0; padding-right: 0; } }
.event-date .ed-day { font-family: var(--font-heading); font-weight: 800; font-size: 1.5rem; line-height: 1; color: var(--color-primary); }
.event-date .ed-mon { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em; color: var(--color-text-muted); margin-top: 4px; }
.event-row h3 { font-size: var(--fs-h4); margin-bottom: 4px; }
.event-row p { color: var(--color-text-muted); font-size: 0.93rem; }
.event-tag { font-size: var(--fs-xs); font-weight: 700; padding: 3px 10px; border-radius: var(--radius-pill); background: var(--color-surface-alt); color: var(--color-text-muted); }

/* ---------- misc utilities used by v2 sections --------------------------- */
.mt-5 { margin-top: 20px; }
.stack-sm { display: grid; gap: var(--space-3); }

/* ---------- His #1 rule holds on SMALL PHONES too --------------------------
   The hero .check-list is an addition (CONTENT §1.1 / BRIEF §4.1 list only
   eyebrow, H1, subcopy, CTA, secondary link, micro-trust). On a 390x740 viewport
   its 157px pushed the CTA and trust line below the fold. Hide it there so the
   spec'd hero content and a visible CTA always fit the first fold. */
@media (max-width: 480px) and (max-height: 780px) {
  .hero--dark .check-list { display: none; }
}

/* Same rule, applied to the WHOLE hero band — both columns, not just the copy.
   At 860px and below the hero grid stacks, so the illustration lands underneath
   the CTA and pushes the band 450-500px past the fold (390x740 leaves 667px, and
   the spec'd eyebrow + H1 + subcopy + CTAs + trust line already fill ~620px of
   it; 768x1024 overflows the same way). Wherever the hero stacks, the mockup
   drops out so the band itself ends inside the first fold with both CTAs
   visible. From 861px up — every two-column layout — it is back, untouched.
   Verified: 1440x800, 1366x768, 1280x720, 768x1024, 430x932, 390x740. */
/* Scale the hero graphic down on small screens rather than deleting it — he wants
   a sales & marketing site to LOOK like one, and mobile is the most-viewed screen. */
@media (max-width: 860px) {
  .hero--dark .hero-visual { margin-top: var(--space-6); }
  .hero--dark .app-window { transform: none; }
}
/* v3 item 1 — the 340px cap below 560px squeezed the DESKTOP render into a
   third of the screen, which is what made it stack and run ~1,040px tall. The
   mockup now takes the full container width and graphics.css holds the desktop
   3-up / 4-up grids inside it. (font-size: 0.9em did nothing: every child of
   .app-window is sized in rem, not em.) */
@media (max-width: 560px) {
  .hero--dark .hero-visual { max-width: none; margin-inline: 0; }
}

/* ============================================================
   v3 — WHY FOUNDER-LED (Sushil 28.07, item A)
   "Design idea for key sections (with elevated cards/tiles)"
   ============================================================ */
.wfl-sec { position: relative; }                 /* v3 background rhythm: white -> soft band -> gray */
.wfl-sec::after {                                  /* faint accent wash so it reads as a "big moment" */
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(52rem 22rem at 12% 0%,  rgba(249,115,22,.055), transparent 62%),
    radial-gradient(46rem 20rem at 88% 100%, rgba(15,118,110,.05),  transparent 62%);
}
.wfl-sec > .container { position: relative; z-index: 1; }
.wfl-grid { margin-top: var(--space-8); align-items: stretch; }
/* ---- ELEVATED CARDS ALL SIT ON ONE RADIUS RUNG --------------------------
   The radius scale is --radius-sm 7 / --radius 12 / --radius-lg 18, and the
   site follows a clear rule: a flat bordered card is 12 (.card, .step-card,
   .card--hover) and an elevated one -- border + shadow -- is 18
   (.card--tile, 199 instances). .wfl-card, .msys-card and .rvai-card are all
   elevated cards and all hardcoded 14px, a value that is on no rung of the
   scale and appears nowhere else.
   .wfl-card is the clearest case: it is .card--tile rebuilt by hand, down to
   the 3px accent rule across the top, but at 14px with a shadow of
   10px 26px -14px against the tile's 10px 24px -18px. On / the two sit six
   sections apart, so the home page shipped two elevated-tile families whose
   corners and drop shadows almost, but not quite, agree.
   All three now take the tile's rung and the tile's elevation. */
.wfl-card {
  position: relative; padding: var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: 0 1px 2px rgba(17,24,39,.04), 0 10px 24px -18px rgba(17,24,39,.18);
  /* hover timing: MOTION SYSTEM role 3, foot of this file */
}
.wfl-card::before {                       /* elevated tile: accent top rule */
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: linear-gradient(90deg, var(--color-primary-bright), var(--color-secondary));
}
.wfl-card:hover {
  transform: translateY(-4px);
  border-color: var(--color-primary-soft);
  box-shadow: 0 2px 4px rgba(17,24,39,.05), 0 22px 44px -20px rgba(17,24,39,.28);
}
.wfl-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; margin-bottom: var(--space-4);
  border-radius: 11px; color: var(--color-primary);
  background: var(--color-primary-soft);
}
.wfl-card:nth-child(2) .wfl-ico { color: var(--color-secondary); background: var(--color-secondary-soft); }
.wfl-card h3 { margin: 0 0 var(--space-2); font-size: var(--fs-h4); line-height: 1.3; }
.wfl-card p  { margin: 0; color: var(--color-text-muted); }
.wfl-close {
  max-width: 36em; margin: var(--space-8) auto 0; text-align: center;
  padding-top: var(--space-6); border-top: 1px solid var(--color-border);
  color: var(--color-text-muted); font-size: 1.0625rem;
}
@media (max-width: 860px) {
  .wfl-grid { grid-template-columns: 1fr; gap: var(--space-4); }
  .wfl-close { text-align: left; }
}

/* ============================================================
   v3 — MANAGED REVOPS & AI-REVOPS (Sushil 28.07)
   Sequential section => progressive-flow rail + numbered steps
   ============================================================ */
.msys { list-style: none; margin: var(--space-8) 0 0; padding: 0; display: grid; gap: var(--space-6); }
.msys-item { position: relative; display: grid; grid-template-columns: 56px 1fr; gap: var(--space-5, 20px); align-items: start; }
.msys-n {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%;
  font-family: var(--font-heading); font-weight: 700; font-size: 1.0625rem;
  color: #fff; background: var(--color-primary);
  box-shadow: 0 0 0 5px var(--color-primary-soft);
}
.msys-item:not(:last-child)::before {          /* progressive flow rail */
  content: ""; position: absolute; left: 22px; top: 46px; bottom: calc(-1 * var(--space-6));
  width: 2px; background: linear-gradient(180deg, var(--color-primary-soft), var(--color-border));
}
/* radius rung + elevation: see the .wfl-card note above */
.msys-card { padding: var(--space-6); border-radius: var(--radius-lg); border: 1px solid var(--color-border);
  background: var(--color-surface); box-shadow: 0 1px 2px rgba(17,24,39,.04), 0 10px 24px -18px rgba(17,24,39,.18); }
.msys-card h3 { margin: 0; font-size: var(--fs-h4); line-height: 1.25; }
.msys-sub { margin: 2px 0 0; font-size: var(--fs-sm); font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--color-text-muted); }
.msys-tag { margin: var(--space-3) 0 var(--space-2); font-weight: 600; color: var(--color-primary); }
.msys-card > p { color: var(--color-text-muted); margin-bottom: var(--space-3); }
.msys-lbl { margin: var(--space-4) 0 var(--space-2) !important; font-weight: 700; color: var(--color-text); }
.msys-list { margin: 0 0 var(--space-4); padding-left: 1.1em; color: var(--color-text-muted); }
.msys-list li { margin-bottom: 6px; }
.msys-for { margin: 0 !important; padding-top: var(--space-4); border-top: 1px solid var(--color-border);
  color: var(--color-text-muted); }
.msys-for span { display: block; font-size: var(--fs-sm); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--color-secondary); margin-bottom: 4px; }

/* ---- RevOps vs AI-RevOps: two distinctive cards, centred ---- */
.section-head--center { text-align: center; margin-left: auto; margin-right: auto; max-width: 60ch; }
.rvai { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6); margin-top: var(--space-8); }
/* radius rung + elevation: see the .wfl-card note above */
.rvai-card { padding: var(--space-6); border-radius: var(--radius-lg); border: 1px solid var(--color-border);
  background: var(--color-surface); box-shadow: 0 1px 2px rgba(17,24,39,.04), 0 10px 24px -18px rgba(17,24,39,.18); }
.rvai-card--ops { border-top: 3px solid var(--color-secondary); }
.rvai-card--ai  { border-top: 3px solid var(--color-primary-bright); }
.rvai-ico { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px;
  border-radius: 11px; margin-bottom: var(--space-4); }
.rvai-card--ops .rvai-ico { color: var(--color-secondary); background: var(--color-secondary-soft); }
.rvai-card--ai  .rvai-ico { color: var(--color-primary);   background: var(--color-primary-soft); }
.rvai-card h3 { margin: 0 0 var(--space-3); font-size: var(--fs-h4); line-height: 1.25; }
.rvai-alt { font-weight: 400; color: var(--color-text-muted); }
.rvai-list { margin: 0; padding-left: 1.1em; color: var(--color-text-muted); }
.rvai-list li { margin-bottom: 8px; }
.rvai-close { max-width: 36em; margin: var(--space-8) auto 0; text-align: center; font-weight: 600;
  color: var(--color-text); }
@media (max-width: 860px) {
  .rvai { grid-template-columns: 1fr; gap: var(--space-4); }
  .msys-item { grid-template-columns: 40px 1fr; gap: var(--space-4); }
  .msys-n { width: 36px; height: 36px; font-size: .95rem; box-shadow: 0 0 0 4px var(--color-primary-soft); }
  .msys-item:not(:last-child)::before { left: 18px; top: 38px; }
  .msys-card { padding: var(--space-5, 20px); }
  /* .section-head--center USED TO BE LEFT-ALIGNED HERE and no longer is.
     .section-head--mid and .section-head--center are two names for one thing:
     a centred section head. Both centre at 1280. Below 860 only --center
     dropped to left, so the two inverted: measured at 390, 21 --center heads
     went left while 31 --mid heads stayed centred, and on /services/ BOTH
     appear on the same page -- "How the work lands" centred, "What it's like
     to work with me" left, four sections apart. Converged on the majority and
     on their own desktop behaviour: centred stays centred at every width.
     .rvai-close keeps the left-align; it is a closing sentence on
     /services/revops-systems/, not a section head, and is why this rule
     exists. */
  .rvai-close { text-align: left; }
}
/* section-level hooks: v3 background rhythm (bolder alternation) */
.msys-sec { position: relative; overflow: hidden; }
.msys-sec::before { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(48rem 20rem at 88% 0%, rgba(249,115,22,.05), transparent 60%); }
.msys-sec > .container { position: relative; z-index: 1; }
.rvai-sec { position: relative; border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border); }

/* ============================================================
   v3 — TECHNOLOGY PARTNER STRIP (Sushil 28.07)
   "only logo in loop, with a slight different background",
   near client reviews / client logo sections.
   ============================================================ */
.partner-strip {
  padding-block: var(--space-12);
  background: var(--color-surface-alt, #F1F5F9);       /* the "slightly different background" */
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}
/* .partner-strip__label is declared ONCE, with .trust-strip__label, at the top
   of this file. It used to be declared a second time here, and the two copies
   had drifted apart (0.78rem/.13em vs var(--fs-sm)/.08em). Do not re-add a
   local copy: two loop captions on the same page must stay one type object. */
/* The edge fade is inherited from .marquee's --mq-fade token (premium.css).
   It was typed out here as a literal 6%/94% and again on .trust-mq, which is
   how the third loop on the page kept the base rule's 8%/92% and read as a
   different component. One token, three loops. Do not re-declare it here. */
/* Duration is written at runtime from --mq-speed (see the MOTION SYSTEM block
   at the foot of this file); the box that decides the track's width is set
   below, and the two no longer have to be chosen by hand together. */
.partner-mq__item {
  flex: none; display: flex; align-items: center; justify-content: center;
}
/* Ink, opacity and the hover transition: figures.css section 9. */
@media (max-width: 860px) {
  .partner-strip { padding-block: var(--space-8); }
}
@media (prefers-reduced-motion: reduce) {
  .partner-mq__track { animation: none !important; }
  .partner-mq { -webkit-mask-image: none; mask-image: none; overflow-x: auto; }
}

/* ============================================================
   v3 — BACKGROUND RHYTHM (Sushil 28.07)
   "Alternate section backgrounds more boldly ... sections feel
   more similar in weight, making it harder to identify big
   moments vs supporting sections."
   The palette only sanctions three light neutrals (#FFFFFF /
   #F8FAFC / #F1F5F9) which sit ~2% apart, so contrast alone
   cannot separate two adjacent light bands. A hairline fold
   edge does - and it auto-applies wherever they meet.
   ============================================================ */
.sec-white  + .sec-paper, .sec-paper + .sec-white,
.sec-white  + .sec-gray,  .sec-gray  + .sec-white,
.sec-paper  + .sec-gray,  .sec-gray  + .sec-paper,
.sec-white  + .sec-band,  .sec-band  + .sec-white,
.sec-paper  + .sec-band,  .sec-band  + .sec-paper,
.sec-gray   + .sec-band,  .sec-band  + .sec-gray,
.sec-white  + .sec-white, .sec-paper + .sec-paper,
.sec-gray   + .sec-gray,  .sec-band  + .sec-band,
.trust-strip + .sec-white, .trust-strip + .sec-paper, .trust-strip + .sec-gray,
.partner-strip + .sec-white, .partner-strip + .sec-paper, .partner-strip + .sec-gray,
/* ---- THE MATRIX WAS ONLY HALF WIRED -------------------------------------
   Measured every adjacent band junction on all 48 pages, comparing the colour
   each band actually PAINTS at the seam (a gradient band's bottom stop, not
   its declared background). Of 210 junctions where the two sides land within
   one palette step of each other, the hairline was present on every one
   except these three groups:

     .sec-gray  + .trust-strip   22 pages   #F1F5F9 meets #F8FAFC
     .sec-band  + .trust-strip    4 pages   #F1F5F9 meets #F8FAFC
     .sec-white + .sec-accent-teal 9 pages  #FFFFFF meets #FFFFFF

   The strip direction was covered (.trust-strip + .sec-*) but not the
   approach to it (.sec-* + .trust-strip), so on 26 pages a grey section ran
   into the client logo strip with no fold while the identical pairing one
   section further down had one.
   .sec-accent-teal is the worse case: it is a gradient that BEGINS on pure
   #FFFFFF, so after a .sec-white section there is literally no boundary --
   and the same junction on /services/revops-systems/ already drew one.
   Junctions where the two sides are genuinely far apart (cream, teal-soft,
   navy) are deliberately NOT listed: contrast already separates them. */
.sec-gray + .trust-strip, .sec-band + .trust-strip,
.sec-white + .sec-accent-teal {
  border-top: 1px solid var(--color-border);
}
/* the accent band now carries this fold, so drop the competing radial wash */
.wfl-sec::after { display: none; }

/* ============================================================
   v3 — TILE FAMILIES (Sushil 28.07, items 4 & 5:
   "Who I serve / Services tiles - missing visual distinction",
   + "borrow the card + mini icon + subtitle style from your
   Services dropdown and repeat it in Services cards and
   Expertise cards for consistency")

   The accent used to alternate by :nth-child(even), so it was
   decorative noise: two services could look different and a
   service could look identical to an expertise area. Here the
   accent CARRIES MEANING - orange = a service you can buy,
   teal = an expertise that runs through the work. Declared
   after the nth-child rule so the family always wins.
   ============================================================ */
.card--tile.tile--service::before   { background: var(--color-primary-bright); }
.card--tile.tile--expertise::before { background: var(--color-teal); }

.tile--service   .card-icon { background: var(--color-primary-soft); color: var(--color-primary); }
.tile--expertise .card-icon { background: var(--color-teal-soft);    color: var(--color-teal); }

/* the family reads on hover too, not just the 3px rule */
.card--tile.tile--service:hover   { border-color: #f3cba7; }
.card--tile.tile--expertise:hover { border-color: #a7d5d0; }

/* compact variant for the 5-up teaser rows (homepage) */
.tile--compact { padding: var(--space-5); }
.tile--compact .card-icon { width: 38px; height: 38px; border-radius: 10px; margin-bottom: var(--space-4); }
.tile--compact .card-icon svg { width: 19px; height: 19px; }
/* 1.0625rem = 17px rendered the five homepage expertise-card headings 3px
   smaller than the SAME five headings in the SAME card on /expertise/ (20px),
   and only 0.5px above the blog h4, so the hierarchy read flat. One size for
   one role: the site's card-heading step. */
.tile--compact h3 { font-size: var(--fs-h4); line-height: 1.3; margin: 0; }
.tile--compact h3 a { color: inherit; text-decoration: none; }
.tile--compact h3 a:hover { color: var(--color-secondary); }
.tile--compact p { margin: 6px 0 0; font-size: var(--fs-sm); color: var(--color-text-muted); line-height: 1.45; }

/* ============================================================================
   v3 — CSS CONSOLIDATION PASS (one edit, all implementer requests)
   Every rule below was requested by a markup agent during the V3 gap-register
   run. Requests that duplicated an existing rule (the .sec-band/.sec-white and
   same-on-same hairlines already declared above) are NOT repeated here.
   Only palette tokens that exist in styles.css are used.
   ============================================================================ */

/* ---------- 1. GAP-08 — above-the-fold heroes must never start invisible ---
   .js .reveal computes to opacity 0 until deferred JS runs. Every hero is
   above the fold, so the reveal buys nothing there and costs the primary CTA.
   .reveal stays intact for everything below the fold.                        */
.js .hero .reveal, .js .hero h1, .js .hero .hero-sub, .js .hero .cta-row,
.js .page-hero .reveal, .js .page-hero h1, .js .page-hero .lead, .js .page-hero .cta-row {
  opacity: 1 !important;
  transform: none !important;
}

/* ---------- 2. GAP-26 — remaining band boundaries --------------------------
   The gray/white/paper/band pairs are already covered further up this file.
   These two adjacencies were not.                                           */
.sec-accent-teal + .sec-accent-teal,
.sec-accent + .sec-accent,
.page-hero.sec-white + .trust-strip,
.page-hero.sec-paper + .trust-strip {
  border-top: 1px solid var(--color-border);
}

/* ---------- 3. Sub-heads inside a section (h3 in a .section-head) ----------
   Used by the two "Outcomes" blocks in the named cases on /work, which keep
   one h1 per page and a sane heading order by dropping to an h3.            */
.section-head h3 {
  /* 1.75rem = 28px is a rung that exists nowhere else: sitewide h3 runs 24px
     (31 pages) and 20px (44 pages), and 28px sat 4px under the 32px h2, which
     compressed the level gap on this one page. Mapped onto the existing 24px
     h3 step so /work/ uses the same scale as the rest of the site. */
  font-size: var(--fs-h3);
  line-height: 1.25;
  margin-bottom: var(--space-2);
}

/* ---------- 4. GAP-17 — a client logo may be wrapped in a case-anchor link -
   .trust-logos is a flex row that only sizes its img children, so a bare
   anchor becomes an unsized flex item.                                      */
.trust-logos a {
  display: inline-flex; align-items: center; min-width: 0;
  text-decoration: none; border-radius: var(--radius-sm);
}
.trust-logos a:hover img { filter: grayscale(0); opacity: 1; }
.trust-logos a:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
@media (max-width: 860px) {
  .trust-logos a { flex: 0 1 auto; }
  .trust-logos a img { max-width: 100%; }
}

/* ---------- 5. GAP-30b — category chips are links on /resources ------------ */
a.stack-chip { text-decoration: none; transition: color .2s, border-color .2s, background-color .2s; }
a.stack-chip:hover,
a.stack-chip:focus-visible {
  color: var(--color-primary);
  border-color: var(--color-primary-bright);
  background: var(--color-primary-soft);
  text-decoration: none;
}
a.stack-chip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
@media (max-width: 860px) { a.stack-chip { font-size: 0.78rem; padding: 6px 12px; } }

/* ---------- 6. GAP-30 + GAP-28 — soft CTA inside the blog article rail ----- */
.article-nav__cta {
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-border);
}
.article-nav__cta p { font-family: var(--font-heading); margin-bottom: var(--space-4); }
/* flex-wrap must go too: a WRAPPING column flex line takes its cross size from
   the widest item, so the cards stretched to 262px inside a 260px rail */
.article-nav__cta .ratings { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: var(--space-3); }
/* The rating cards are <a>s INSIDE .article-nav, so the table-of-contents
   anchor rule above (display:block, border-left:2px, margin-left:-2px, 0.88rem)
   lands on them and pushed each card 2px wider than the rail. Reset it, and
   let the card and its text column shrink so the label wraps in ~260px. */
.article-nav__cta .rating-card {
  display: flex; padding: 12px 14px; gap: var(--space-3); min-width: 0;
  margin-left: 0; border-left-width: 1px; font-size: var(--fs-body); line-height: 1.4;
}
.article-nav__cta .rating-card > span { min-width: 0; }
.article-nav__cta .rating-card img { height: 32px; }
@media (max-width: 860px) {
  .article-nav__cta { margin-top: var(--space-6); }
  .article-nav__cta .rating-card { padding: 11px 13px; }
}

/* ---------- 7. GAP-14 — progressive-flow rail for .steps-cards -------------
   Aligns to the centre of the 30px .sc-n badge (1px border + 24px padding + 15px).
   The rail is only correct while the grid is a SINGLE row, so it is switched
   on per breakpoint and never shows once the cards wrap or stack.           */
.steps-cards { position: relative; }
.steps-cards::before {
  content: ""; display: none; position: absolute;
  top: 39px; left: 8%; right: 8%; height: 2px;
  background: repeating-linear-gradient(90deg, var(--color-border) 0 8px, transparent 8px 16px);
  z-index: 0;
}
.step-card { position: relative; z-index: 1; }
@media (min-width: 901px) { .steps-cards--4::before { display: block; } }
@media (min-width: 1041px) { .steps-cards::before { display: block; } }
/* stacked / wrapped: the numbered badges carry the sequence on their own */
@media (max-width: 860px) { .steps-cards::before { display: none; } }

/* four-up step cards that carry a list rather than a paragraph: match the
   optical weight of .step-card p so the list never outweighs the card title */
.step-card .feature-list li { font-size: 0.9rem; line-height: 1.55; }
@media (max-width: 860px) { .step-card .feature-list li { font-size: 0.92rem; } }

/* ---------- 8. GAP-14 (secondary) — .journey keeps its rail on phones ------
   visuals.css kills .journey::before below 860px, so the progressive-flow
   device vanished on mobile on every page that uses it. Below 860 the row
   becomes a vertical timeline instead, with the rail running between the
   badges exactly as .msys does.                                            */
@media (max-width: 860px) {
  .journey, .journey--3, .journey--5 { grid-template-columns: 1fr; gap: var(--space-6); }
  .journey-step { text-align: left; padding-left: 88px; min-height: 84px; }
  .journey-step .js-badge { position: absolute; left: 0; top: 8px; margin: 0; }
  .journey-step:not(:last-child)::before {
    content: ""; position: absolute; left: 33px; top: 8px;
    bottom: calc(-1 * var(--space-6)); width: 2px;
    background: repeating-linear-gradient(180deg, var(--color-border) 0 8px, transparent 8px 16px);
  }
  .journey-step p { max-width: 60ch; }
}

/* ---------- 9. The AI band keeps the teal accent, including its rail -------
   Every other AI moment is teal (.eyebrow--ai, .callout--ai, .tq--teal), so
   an orange .msys rail inside a teal band read as the odd one out.          */
.sec-accent-teal .msys-n { background: var(--color-teal); box-shadow: 0 0 0 5px var(--color-teal-soft); }
.sec-accent-teal .msys-item:not(:last-child)::before {
  background: linear-gradient(180deg, var(--color-teal-soft), var(--color-border));
}
.sec-accent-teal .msys-tag { color: var(--color-teal); }

/* ---------- 10. GAP-33 — the two-system sync diagram reads as live ---------
   .dataflow sits inside .chart-card, which main.js primes with .gfx-anim and
   releases with .gfx-in on scroll.
   DELIBERATE LIMIT: nothing that carries a WORD is primed to opacity 0. The
   established .gfx-anim rules only ever hide bars, rings and cells, never
   copy, so that a stalled observer can never blank out a sentence. Here the
   system boxes ("HubSpot", "Salesforce") and the row lines therefore stay
   fully rendered at all times; only the object pills slide in, and only the
   status dot keeps moving. That is the "feels live" cue on its own.         */
.gfx-anim .df-obj { transform: translateX(-8px); }
.gfx-in .df-obj { transform: none; transition: transform 0.45s var(--ease-out); }
.gfx-in .df-mid .df-obj:nth-child(2) { transition-delay: 0.10s; }
.gfx-in .df-mid .df-obj:nth-child(3) { transition-delay: 0.20s; }
.gfx-in .df-mid .df-obj:nth-child(4) { transition-delay: 0.30s; }

/* the status dot on each synced object keeps a slow, quiet pulse: the one
   element on the diagram whose whole job is to say "this is still running" */
@keyframes dfSync {
  0%, 100% { transform: scale(1);   opacity: 1; }
  50%      { transform: scale(1.5); opacity: 0.45; }
}
/* 2.4s ease-in-out, not the 2.6s var(--ease) this used to carry. See MOTION
   SYSTEM role 4 at the foot of this file: 2.4s is the site's ambient-loop
   period (.aw-live and .journey-step both run it, and .aw-live shares this
   page), and var(--ease) is an ENTRANCE curve, which on a symmetric
   0%,100% -> 50% breathe made the return leg read differently from the
   outbound one. Delays below hold the same phase they had at 2.6s. */
.df-obj::before { animation: dfSync 2.4s ease-in-out infinite; }
.df-mid .df-obj:nth-child(2)::before { animation-delay: 0.32s; }
.df-mid .df-obj:nth-child(3)::before { animation-delay: 0.64s; }
.df-mid .df-obj:nth-child(4)::before { animation-delay: 0.96s; }

/* the synced-row ticks settle in behind the pills, motion only, never opacity */
.gfx-anim .dfl-ok { transform: translateY(3px); }
.gfx-in .dfl-ok { transform: none; transition: transform 0.5s var(--ease-out) 0.30s; }

@media (prefers-reduced-motion: reduce) {
  .gfx-anim .df-obj, .gfx-anim .dfl-ok { transform: none; }
  .gfx-in .df-obj, .gfx-in .dfl-ok { transition: none; }
  .df-obj::before { animation: none; }
}
/* on phones the object pills sit in a wrapped flex row, where a horizontal
   entrance would shuffle them sideways: hold them still, keep the dot pulse */
@media (max-width: 860px) {
  .gfx-anim .df-obj, .gfx-anim .dfl-ok { transform: none; }
  .gfx-in .df-obj, .gfx-in .dfl-ok { transition: none; }
}

/* ============================================================
   v3 — CLIENT LOGO STRIP AS A MOVING LOOP
   "should keep moving horizontally just like the stack I build
   these systems in ... on top of the stack" (Sushil).
   Same marquee mechanics as .marquee-track; keeps the greyscale
   -> colour hover he explicitly said he liked.
   ============================================================ */
/* Edge fade comes from .marquee's --mq-fade token; see premium.css. */
/* animation-duration is written at runtime from --mq-speed. The 40s that used
   to be pinned here made this loop run at 45.4 px/s on the six pages carrying
   the 12-mark roster and 37.6 px/s on the other 39, because the duration was
   fixed while the roster was not. Speed, not duration, is the thing the eye
   compares between two loops. */
/* min-height, not height: the emblem band of the optical scale table caps a
   square mark at 45-48px, and a fixed 40px box would CLIP it if one were ever
   added to the marquee roster. min-height keeps the 40px rhythm for the
   current wordmark roster and lets the track grow instead of cropping. */
.trust-mq__item {
  flex: none; display: flex; align-items: center; justify-content: center;
  min-height: 40px; margin-inline: clamp(18px, 3vw, 44px);
}
/* Box comes from the optical scale table at the foot of this file. */
/* ---- LOGO TREATMENT — one rule, measured per pixel, not judged by eye -------
   `grayscale(1)` + an opacity knock-down is a treatment that assumes every mark
   is dark. It is not. Measured per pixel over the real cream band, the old
   grayscale(1)/opacity .74 put ZERO ink above 3:1 for settly, bijles, clairify
   and phantombuster, and 8-19% for activecampaign and wind-energy, while
   breachlock and curioinvest sat at 86-93%. Two failure modes were hiding in one
   rule: light BRAND marks greyscale to a light tone, and several assets
   (activecampaign 73% of ink, hubspot 43%, phantombuster 35%) are WHITE-ink
   files meant for a dark surface.
   Desaturating and then DARKENING fixes both, because it maps every mark, light
   or dark, brand-coloured or white, into the same narrow ink band — which is
   also what "all the logos should look uniform" asks for. Measured across the
   whole 23-mark roster on all four surfaces the loop paints on (#FEF3C7,
   #FFF7ED, #F1F5F9, #FFFFFF): the worst mark now puts 95% of its ink at >=3:1,
   against a previous floor of 0%. Re-run the per-pixel test before changing the
   0.55, and before adding any mark to the roster.

   *** SUPERSEDED 02.08 by figures.css section 9, which sets brightness(0)
   at opacity .72 for BOTH loops and for the /resources/ plate tiles. The
   0.55 measurement above is kept as the record of why a single flat ink is
   the right shape of answer; it is NOT the live value and this rule no
   longer declares one. Do not re-add a filter here: a second declaration of
   the same property is how the plate tiles ended up a scroll away from the
   same marks in a different ink. */
@media (max-width: 860px) {
  .trust-mq__item { margin-inline: 14px; min-height: 34px; }
}
@media (prefers-reduced-motion: reduce) {
  .trust-mq__track { animation: none !important; }
  .trust-mq { -webkit-mask-image: none; mask-image: none; overflow-x: auto; }
}

/* ============================================================
   v3 — PROGRESSIVE-FLOW ARROWS on sequential sections
   His highlighted rule (Feedback p.2): "All sections on any page
   - if it is sequential flow (one after another) add some
   graphical elements that depicts progressive flow in visual".
   On that page he hand-drew ARROWS between Diagnose > Design >
   Build > Optimise. The rail was a dashed line with no
   direction; these are the arrowheads he drew.
   ============================================================ */
.journey-step { --jarrow: var(--color-primary); }
.journey-step:not(:last-child)::after {          /* arrowhead into the next step */
  content: ""; position: absolute; z-index: 2;
  top: 34px; right: -9px; transform: translateY(-50%) rotate(45deg);
  width: 13px; height: 13px;
  border-top: 3px solid var(--jarrow);
  border-right: 3px solid var(--jarrow);
  border-radius: 2px;
}

/* ------------------------------------------------------------
   FINAL FEEDBACK, ITEM 9 — "make the visual look more
   progressive and the flow tangible".

   What he was looking at: a 2px near-white dashed rule with one
   9px chevron floating mid-span. At the rendered scale the four
   steps read as four unrelated tiles, because the only thing
   joining them was invisible and the only thing that changed
   from step 1 to step 4 was the digit in the badge.

   THE SHAPE TEST, applied. The geometry now carries the argument
   on its own, without reading a word:
     - the rail is a 5px TRACK, not a hairline, so the row is one
       object rather than four;
     - it is cut into one visible SEGMENT PER LEG by hard colour
       stops, and each leg is a stronger tint of the brand orange
       than the one before it, so the direction of travel is
       legible from the colour ramp alone. The stop positions are
       a function of the node count: 3 legs at 33.3/66.6 for the
       four-step row, 2 legs at 50 for .journey--3, 4 legs at
       25/50/75 for .journey--5, which is where the badge centres
       actually sit (the rail already spans centre-to-centre, see
       figures.css);
     - the arrowheads are 13px at 3px weight in the AA-safe
       orange ink, so they read on the pale first leg as well as
       on the saturated last one;
     - the arrival node is emphasised, so the flow has an end and
       not just a right-hand edge.
   Tints of --color-primary-bright #F97316 only. No new hue.
   ------------------------------------------------------------ */
.journey::before {
  top: 34px; height: 5px; border-radius: 999px;
  transform: translateY(-50%);
  background: linear-gradient(90deg,
    rgba(249,115,22,0.30) 0%,     rgba(249,115,22,0.30) 33.333%,
    rgba(249,115,22,0.62) 33.333%, rgba(249,115,22,0.62) 66.666%,
    rgba(249,115,22,1)    66.666%, rgba(249,115,22,1)    100%) !important;
}
.journey--3::before {
  background: linear-gradient(90deg,
    rgba(249,115,22,0.42) 0%,  rgba(249,115,22,0.42) 50%,
    rgba(249,115,22,1)    50%, rgba(249,115,22,1)    100%) !important;
}
.journey--5::before {
  background: linear-gradient(90deg,
    rgba(249,115,22,0.26) 0%,   rgba(249,115,22,0.26) 25%,
    rgba(249,115,22,0.50) 25%,  rgba(249,115,22,0.50) 50%,
    rgba(249,115,22,0.74) 50%,  rgba(249,115,22,0.74) 75%,
    rgba(249,115,22,1)    75%,  rgba(249,115,22,1)    100%) !important;
}

/* The numbers are the sequence, so they have to be readable as one.
   The chip keeps its orange fill and gains a surface-coloured ring, which
   separates it from the icon tile it overlaps at every background the
   component ships on. */
.journey-step .js-badge .js-n {
  width: 26px; height: 26px; top: -9px; right: -9px;
  font-size: 13px;
  box-shadow: 0 0 0 2px var(--color-surface), 0 2px 6px rgba(249,115,22,0.35);
}
/* Arrival: the last node is where the flow lands. */
.journey-step:last-child .js-badge {
  border-color: rgba(249,115,22,0.55);
  box-shadow: 0 6px 18px rgba(249,115,22,0.16);
}
/* ...but the rule above matches the hover rule in visuals.css at equal
   specificity and would otherwise win it on cascade order, so the last badge
   would be the one tile in the row with no hover feedback. Restated here. */
.journey-step:last-child:hover .js-badge {
  border-color: #d9c9b8;
  box-shadow: var(--shadow-md);
}

@media (max-width: 860px) {
  /* the row becomes a vertical timeline below 860, so the arrow points DOWN
     and the rail runs between the badges instead of across them. Same
     treatment, rotated: a solid 4px track, not a dashed hairline. */
  .journey-step:not(:last-child)::after {
    top: auto; bottom: -16px; right: auto; left: 34px;   /* the badge is 68px wide at left:0, so its axis is 34 */
    transform: translateX(-50%) rotate(135deg);
  }
  .journey-step:not(:last-child)::before {
    left: 32px; width: 4px; border-radius: 999px;
    background: linear-gradient(180deg,
      rgba(249,115,22,0.34), rgba(249,115,22,0.9));
  }
}
@media (prefers-reduced-motion: no-preference) {
  .js .journey.is-in .journey-step:not(:last-child)::after { animation: jarrow 2.4s ease-in-out infinite; }
  @keyframes jarrow { 0%,100% { opacity:.62; } 50% { opacity:1; } }
}

/* ============================================================
   CLIENT LOGO OPTICAL SCALE TABLE

   *** SUPERSEDED by the v3.1 block at the foot of this file. ***
   Sushil ruled against the two-band system in writing (meeting doc
   item 4: "all the logos should be of same size. For example the
   covalent logo is small in proportion to other logos"). Every mark
   now gets an identical 150x46 box, so the per-mark --logo-h rows
   below are inert. They are kept only as the record of each mark's
   native pixels and ink ratio, which is still what a re-supplied
   file has to be checked against.

   The marks are supplied at native resolution from each company's own domain,
   so their aspect ratios run from 0.95:1 (a round emblem) to 6.7:1 (a long
   wordmark). One shared height cap makes a long wordmark read as a thin sliver
   and an emblem read as oversized, so each mark carries its own height cap.

   TWO BANDS, because equal AREA is the right target for wordmarks and the
   wrong one for emblems:

   1. WORDMARKS (aspect >= 1.7) hold a constant optical area:
          h = min(40, sqrt(3300 / aspect)) ,  w = h * aspect ,  hard cap w <= 140
      Only the height needs a table entry; the width follows from the mark's
      own aspect ratio and never exceeds 140px, so a long wordmark cannot
      dominate a row of compact marks. Areas land between 2,931 and 3,369 px2,
      against 1,882 to 3,733 px2 before. BreachLock is the one mark the 140px
      width cap binds (2,931 px2, ~11% under the band): that is the cap doing
      its job on the widest mark in the set, not a miss.

   2. ROUND / SQUARE EMBLEMS (aspect < 1.7) are sized by HEIGHT, not area.
      Equal area would put a 0.95:1 emblem at 57px beside a 21px wordmark and
      it would read as a badge, not a peer. They sit at 45-48px, about 1.3x the
      tallest wordmark, which is where a compact mark reads level with a
      wordmark row. The cap is additionally held at or below the mark's own
      native height so contain only ever scales DOWN: Wind Energy is 45px
      because its artwork is 45px tall, not because 45 balanced best.

   THE RATIOS ARE INK RATIOS, NOT CANVAS RATIOS. Five of the marks shipped with
   baked-in white padding, so sizing off the canvas sized the padding: Wind
   Energy's canvas was 2.85:1 but its artwork was 0.96:1 inside it and rendered
   as a 33x34 smudge, and Covalent's artwork rendered 40% under its intended
   height. Every file has since been cropped to its artwork (originals kept in
   _v3-source/logos-untrimmed/), so canvas ratio now equals ink ratio and this
   table can be read straight off the file dimensions. A NEW MARK MUST BE
   CROPPED TO ITS ARTWORK BEFORE IT GETS A ROW HERE.

   object-fit: contain with width/height auto means a mark is only ever scaled
   DOWN inside its cap, and never squashed when a flex row has to shrink.
   ============================================================ */
/* --logo-unit is declared on :root, not just on the strips, so a mark dropped
   into any other context still resolves a cap instead of falling back to none
   and painting at its native 1549px. */
:root { --logo-unit: 1px; }
@media (max-width: 860px) { .trust-logos, .trust-mq { --logo-unit: 0.82px; } }

.trust-logos img,
.trust-mq__item img {
  --logo-h: 34; --logo-w: 140;
  width: auto; height: auto;
  max-height: calc(var(--logo-h) * var(--logo-unit, 1px));
  max-width: calc(var(--logo-w) * var(--logo-unit, 1px));
  object-fit: contain;
}

/* band 1 - wordmarks, constant area, in descending aspect ratio.
   native px / ratio / drawn */
img[src*="/logos/breachlock.png"]  { --logo-h: 21; }  /* 1337x200  6.68  140x21 */
img[src*="/logos/curioinvest.png"] { --logo-h: 23; }  /* 1154x192  6.01  138x23 */
img[src*="/logos/cogoport.png"]    { --logo-h: 26; }  /* 1089x224  4.86  126x26 */
img[src*="/logos/coflo.png"]       { --logo-h: 27; }  /*  858x186  4.61  125x27 */
img[src*="/logos/everynet.svg"]    { --logo-h: 30; }  /*  180x40   4.50  135x30  VECTOR */
img[src*="/logos/covalent.png"]    { --logo-h: 28; }  /*  119x29   4.10  115x28 */
img[src*="/logos/settly.png"]      { --logo-h: 32; }  /* 1549x480  3.23  103x32 */
img[src*="/logos/peddler.png"]     { --logo-h: 32; }  /*  800x256  3.12  100x32 */
img[src*="/logos/quantamix.png"]   { --logo-h: 34; }  /*  376x129  2.91   99x34 */
img[src*="/logos/leapest.png"]     { --logo-h: 35; }  /*  250x90   2.78   97x35 */
img[src*="/logos/edcast.png"]      { --logo-h: 36; }  /* 1206x511  2.36   85x36 */
img[src*="/logos/bijles.png"]      { --logo-h: 37; }  /*  300x125  2.40   89x37 */
img[src*="/logos/enar.png"]        { --logo-h: 40; }  /*  411x240  1.71   69x40 */

/* band 2 - round / square emblems, sized by height. Each cap is <= the mark's
   own native height, so contain still only ever scales DOWN. */
img[src*="/logos/clairify.png"]    { --logo-h: 48; }  /*  128x128  1.00   48x48 */
img[src*="/logos/wind-energy.png"] { --logo-h: 45; }  /*  160x160  1.00   45x45 */

/* 05.08 - three rows above carried the pre-02.08 native sizes (everynet
   173x47, leapest 104x38, clairify 59x62, wind-energy 43x45) after those
   files were re-supplied. The block is inert, but it is declared as the
   record a re-supplied file is checked against, so a stale row is a trap.
   Measured off the shipped files this session. */

/* Covalent, Leapest and Wind Energy are retired brands whose only surviving
   artwork is small, so they draw at roughly 1:1 and go soft on a 2x display.
   Nothing to fix in CSS; they need re-supplied files. Wind Energy is the limit
   case: its cap IS its native height, so it cannot be enlarged any further
   without upscaling, and it stays the smallest mark in any row it sits in. */

/* ==========================================================================
   HubSpot form embeds (item 16)
   --------------------------------------------------------------------------
   The form cards render Sushil's own HubSpot forms. HubSpot ships its own
   markup and its own class names, so everything below is a skin: the embedded
   form has to be indistinguishable from the hand-built one it replaces.
   ========================================================================== */

/* Shell. Hidden until the loader decides the embed is the one to show. */
.hs-embed[hidden] { display: none; }
.hs-embed { display: block; }
.hs-embed__head { margin-bottom: var(--space-6); }
.hs-embed__head h2 { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; }
.hs-embed__head .hint { font-size: var(--fs-xs); color: var(--color-text-muted); margin-top: 4px; }
.hs-embed__status { font-size: var(--fs-sm); color: var(--color-text-muted); }

/* Skeleton while HubSpot paints, so the card never collapses to nothing. */
.hs-embed__target { min-height: 220px; }
.hs-embed.is-ready .hs-embed__target { min-height: 0; }

/* NO HEIGHT FLOOR WHILE LOADING. The previous build held an unpainted frame
   open at 620px, which is the empty white card in his v3.1 feedback: the frame
   existed, so the loader called it painted, and the floor gave it the height to
   look like a form. Nothing here may give an empty frame height, because the
   loader's paint test would then be reading this stylesheet's own work.
   .has-frame is added only AFTER HubSpot has reported a real height itself. */
.hs-embed .hs-form-frame { width: 100%; }
.hs-embed.has-frame .hs-form-frame iframe { width: 100%; border: 0; display: block; }

/* Neutralise the site's own .input rule: HubSpot uses .input as a WRAPPER div
   around each control, which would otherwise draw a second bordered box. */
.hs-embed .hs-form-field > .input {
  border: 0; background: none; padding: 0; border-radius: 0;
  box-shadow: none; width: auto;
}

.hs-embed .hs-form { font-family: var(--font-body); }
.hs-embed .hs-form-field { margin-bottom: var(--space-4); }
.hs-embed .hs-form-field > label {
  font-size: var(--fs-sm); font-weight: 600; display: block; margin-bottom: 7px;
  color: var(--color-text);
}
.hs-embed .hs-form-required { color: var(--color-error); margin-left: 2px; }
.hs-embed .hs-field-desc {
  font-size: var(--fs-xs); color: var(--color-text-muted);
  margin: 0 0 7px; padding: 0; float: none; display: block;
}

.hs-embed .hs-input {
  font-family: var(--font-body); font-size: var(--fs-body); color: var(--color-text);
  padding: 11px 13px;
  border: 1.5px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-bg);
  transition: border-color 0.18s var(--ease), box-shadow 0.18s var(--ease), background 0.18s;
  width: 100%; max-width: 100%; box-sizing: border-box;
}
.hs-embed .hs-input:focus {
  outline: none; border-color: var(--color-primary); background: #fff;
  box-shadow: 0 0 0 3px var(--color-primary-soft);
}
.hs-embed textarea.hs-input { resize: vertical; min-height: 120px; }
.hs-embed select.hs-input {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; padding-right: 38px;
}
.hs-embed .hs-input[type="checkbox"],
.hs-embed .hs-input[type="radio"] { width: auto; accent-color: var(--color-primary); margin-right: 8px; }
.hs-embed .inputs-list { list-style: none; padding: 0; margin: 0; }
.hs-embed .inputs-list > li { margin: 0 0 6px; }
.hs-embed .hs-form-booleancheckbox-display,
.hs-embed .hs-form-checkbox-display,
.hs-embed .hs-form-radio-display {
  display: flex; align-items: flex-start; gap: 4px;
  font-size: var(--fs-sm); color: var(--color-text-muted); font-weight: 400;
}

/* Legal consent / rich text blocks */
.hs-embed .hs-richtext,
.hs-embed .legal-consent-container {
  font-size: var(--fs-sm); color: var(--color-text-muted); margin-bottom: var(--space-4);
}
.hs-embed .hs-richtext p { margin: 0 0 8px; }

/* Validation messages */
.hs-embed .hs-error-msgs { list-style: none; padding: 0; margin: 6px 0 0; }
.hs-embed .hs-error-msg,
.hs-embed .hs-main-font-element { font-size: var(--fs-sm); color: var(--color-error); font-weight: 500; }
.hs-embed .hs-input.invalid { border-color: var(--color-error); }

/* Submit button — matches .btn.btn--primary.btn--lg.btn--block */
.hs-embed .hs_submit { margin-top: var(--space-8); }
.hs-embed .hs_submit .actions { padding: 0; margin: 0; }
.hs-embed .hs-button {
  display: block; width: 100%;
  font-family: var(--font-body); font-size: 1.0625rem; font-weight: 600; line-height: 1;
  padding: 15px 26px;
  border-radius: var(--radius-sm); border: 1.5px solid transparent;
  background: var(--color-primary); color: #fff;
  box-shadow: var(--shadow-xs); cursor: pointer; text-align: center;
  transition: background-color 0.18s var(--ease), box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.hs-embed .hs-button:hover { background: var(--color-primary-hover); box-shadow: var(--shadow-primary); }
.hs-embed .hs-button:active { transform: translateY(1px); }
.hs-embed .hs-button:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 2px; }

/* HubSpot's own inline thank-you, styled like .form-success */
.hs-embed .submitted-message {
  text-align: center; padding: var(--space-8) var(--space-4);
  font-size: var(--fs-body); color: var(--color-text);
}

@media (max-width: 600px) {
  .hs-embed .hs-form-field { margin-bottom: var(--space-4); }
}

/* --------------------------------------------------------------------------
   The current HubSpot loader paints into a shadow root on .hs-form-frame, where
   a stylesheet in the page cannot reach it — hubspot-embed.js pushes the same
   skin in there. These rules cover the case where it paints into the light DOM
   instead, so the form looks the same either way.
   -------------------------------------------------------------------------- */
.hs-embed .hsfc-Form,
.hs-embed .hsfc-Step { font-family: var(--font-body); color: var(--color-text); }
.hs-embed .hsfc-FieldGroup,
.hs-embed .hsfc-Row { margin-bottom: var(--space-4); }
.hs-embed .hsfc-FieldLabel,
.hs-embed .hsfc-Label {
  font-size: var(--fs-sm); font-weight: 600; display: block;
  margin-bottom: 7px; color: var(--color-text);
}
.hs-embed .hsfc-TextInput,
.hs-embed .hsfc-EmailInput,
.hs-embed .hsfc-PhoneInput,
.hs-embed .hsfc-NumberInput,
.hs-embed .hsfc-TextareaInput,
.hs-embed .hsfc-DropdownInput {
  font-family: var(--font-body); font-size: var(--fs-body); color: var(--color-text);
  padding: 11px 13px;
  border: 1.5px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-bg);
  width: 100%; max-width: 100%; box-sizing: border-box;
}
.hs-embed .hsfc-TextInput:focus,
.hs-embed .hsfc-EmailInput:focus,
.hs-embed .hsfc-TextareaInput:focus,
.hs-embed .hsfc-DropdownInput:focus {
  outline: none; border-color: var(--color-primary); background: #fff;
  box-shadow: 0 0 0 3px var(--color-primary-soft);
}
.hs-embed .hsfc-TextareaInput { resize: vertical; min-height: 120px; }
.hs-embed .hsfc-ErrorAlert,
.hs-embed .hsfc-FieldError { font-size: var(--fs-sm); color: var(--color-error); font-weight: 500; }
.hs-embed .hsfc-RichText,
.hs-embed .hsfc-Description { font-size: var(--fs-sm); color: var(--color-text-muted); }
.hs-embed .hsfc-Button {
  display: block; width: 100%;
  font-family: var(--font-body); font-size: 1.0625rem; font-weight: 600; line-height: 1;
  padding: 15px 26px;
  border-radius: var(--radius-sm); border: 1.5px solid transparent;
  background: var(--color-primary); color: #fff;
  box-shadow: var(--shadow-xs); cursor: pointer; text-align: center;
  margin-top: var(--space-6);
  transition: background-color 0.18s var(--ease), box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.hs-embed .hsfc-Button:hover { background: var(--color-primary-hover); box-shadow: var(--shadow-primary); }
.hs-embed .hsfc-Button:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 2px; }

/* --------------------------------------------------------------------------
   Which form is on screen. One rule decides it, and it is driven by a class
   that hubspot-embed.js adds to the card the moment that file runs:

     no .hs-live   -> the hand-built form is the form (surfaces with no GUID
                      yet, and the case where the loader itself is blocked)
     .hs-live      -> HubSpot's embed is the form, the hand-built one is hidden
     .is-fallback  -> the embed failed to paint inside 12s, so the hand-built
                      form is restored; it posts to the HubSpot Submissions API
                      with the same GUID, so the lead is still captured

   Nothing here can hide both: the fallback is only ever hidden by a class that
   arrives with the script that can also bring it back.
   -------------------------------------------------------------------------- */
.form-card.hs-live [data-hs-native] { display: none; }
.form-card.hs-live [data-hs-native].is-fallback { display: block; }
.hs-embed.is-failed { display: none; }

/* No JavaScript: neither HubSpot's embed nor the JS-submitted hand-built form
   can work, so both are hidden and the <noscript> block carries the email
   route instead. */
html:not(.js) [data-hs-native] { display: none; }
html:not(.js) .hs-embed { display: none; }
.form-noscript { font-size: var(--fs-sm); color: var(--color-text-muted); }

/* ============================================================
   v3.1 — MEETING DOC (Sushil). Items 4, 5, 6 and 7.
   This file loads LAST, so this block deliberately overrides the
   earlier blocks above and in graphics.css. Each sub-block quotes
   the instruction it implements so nothing here is untraceable.
   ============================================================ */

:root {
  /* item 6 — one bar language for the whole site */
  --bar-grey:  #CBD5E1;     /* "use light grey instead of green bars" */
  --bar-track: #F1F5F9;     /* the unfilled rail behind a bar */
  --bar-h:     9px;         /* "the bars should be thinner/slimmer" */
  --bar-h-xs:  6px;
  /* item 4 — ONE identical display box for EVERY client mark */
  --logo-box-w: 150;
  --logo-box-h: 46;
  /* item 5 — the Financial Times quotation mark is a serif "66" */
  --ft-serif: Georgia, "Times New Roman", "Droid Serif", serif;
}

/* ------------------------------------------------------------
   ITEM 4a — "there needs to be same light yellow/cream/beige
   background which we have used for 'Not sure where your RevOps
   is breaking?' behind Trusted by B2B Tech & SaaS Teams"
   That cream is .sec-accent (v2.css above). Same two stops here.
   ------------------------------------------------------------ */
.trust-strip:not(.trust-strip--on-dark) {
  background: linear-gradient(180deg, #FFF7ED 0%, #FEF3C7 100%);
  border-bottom: 1px solid var(--color-border);
  padding-block: clamp(28px, 3.4vw, 40px);
}

/* ------------------------------------------------------------
   ITEM 4b/4c — "Logos needs to be bigger ... all the logos should
   be of same size. For example the covalent logo is small in
   proportion to other logos."

   This REPLACES the two-band optical scale table at the foot of
   this file (wordmarks by area, emblems by height). He ruled
   against it in writing, so every mark now gets an IDENTICAL
   150 x 46 box and object-fit: contain fits its artwork inside.
   Same box = same size, and no mark is distorted.

   Consequence, recorded here so it is not rediscovered later:
   contain now scales UP as well as down, so a mark whose artwork
   is smaller than the box goes soft. covalent.png (119x29, ~1.26x),
   leapest.png (104x38, ~1.21x) and wind-energy.png (43x45, ~1.02x)
   are the three affected. They need re-supplied files (item 11).
   ------------------------------------------------------------ */
.trust-logos img,
.trust-mq__item img {
  width:  calc(var(--logo-box-w) * var(--logo-unit, 1px));
  height: calc(var(--logo-box-h) * var(--logo-unit, 1px));
  max-width: none;
  max-height: none;
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.74;                 /* .62 left CO-FLO barely legible on cream */
}
.trust-logos img:hover,
.trust-mq__item img:hover { filter: none; opacity: 1; }

/* equal boxes wrap cleanly, so the old nowrap guard against an
   orphaned last logo is no longer needed and would squash them */
.trust-logos { flex-wrap: wrap; gap: clamp(14px, 2vw, 28px); }
@media (min-width: 861px) { .trust-logos { flex-wrap: wrap; gap: clamp(14px, 2vw, 28px); } }
/* Between 861 and 1120 a six-mark row wraps 4 + 2, which reads like an accident.
   Cap the row at three so it wraps 3 + 3 (or 3 + 2 for a five-mark strip) and
   the block stays deliberate. Flexbox cannot balance rows any other way. */
@media (min-width: 861px) and (max-width: 1120px) {
  .trust-logos { max-width: calc(3 * var(--logo-box-w) * 1px + 2 * 28px); margin-inline: auto; }
}

.trust-mq__item {
  min-height: calc(var(--logo-box-h) * var(--logo-unit, 1px));
  margin-inline: clamp(20px, 3.2vw, 48px);
}
@media (max-width: 860px) {
  .trust-logos, .trust-mq { --logo-unit: 0.92px; }   /* 138 x 42 */
  .trust-mq__item { margin-inline: 16px; min-height: calc(var(--logo-box-h) * var(--logo-unit, 1px)); }
}
@media (max-width: 420px) {
  .trust-logos, .trust-mq { --logo-unit: 0.82px; }   /* 123 x 38 - still wider
       than the OLD desktop draw for every mark except breachlock and settly */
  .trust-mq__item { margin-inline: 13px; }
}

/* ------------------------------------------------------------
   ITEM 7 — "The logos in CERTIFIED AND TECHNOLOGY PARTNERS also
   need to be slightly bigger", and its sequel:
   "why are the certified and training partner animation not as
   good as the others ... it's too dark, make it as sleek and
   smooth as the Trusted by B2B Tech & SaaS teams logos".

   The two strips used to differ in EVERY variable at once - box,
   margin, edge fade, duration, hover target, and ink - and each
   difference was individually defensible, which is exactly why
   none of them got caught. They are now one component with one
   roster difference. Only the box differs, and only because he
   asked for these marks bigger: 168 x 52 against the client
   strip's 150 x 46, i.e. +12%, on the same --logo-unit so the
   two shrink together at 860 and 420.

   The old note here said the uneven fill was "an asset-trim job,
   not a CSS one". That job is done: every partner PNG is now
   trimmed to its own ink, so `contain` fits the MARK to the box
   instead of fitting a canvas of baked-in padding to it.
   ------------------------------------------------------------ */
.partner-mq { --pbox-w: 168; --pbox-h: 52; }
.partner-mq__item {
  min-height: calc(var(--pbox-h) * var(--logo-unit, 1px));
  margin-inline: clamp(20px, 3.2vw, 48px);          /* == .trust-mq__item */
}
.partner-mq__item img {
  width:  calc(var(--pbox-w) * var(--logo-unit, 1px));
  height: calc(var(--pbox-h) * var(--logo-unit, 1px));
  max-width: none; max-height: none;
  object-fit: contain;
}
@media (max-width: 860px) {
  .partner-mq { --logo-unit: 0.92px; }
  .partner-mq__item { margin-inline: 16px; }
}
@media (max-width: 420px) {
  .partner-mq { --logo-unit: 0.82px; }
  .partner-mq__item { margin-inline: 13px; }
}

/* MOTION — @keyframes marquee translates the track by -50% of ITS OWN
   width, so what the eye reads is (track width / 2) / duration, and two
   loops that share a duration but not a track width do NOT move at the
   same speed.

   The per-loop durations that used to live here (64s / 49s, and 40s / 30s on
   .trust-mq__track) were each hand-calibrated on / against the client loop's
   width on that one page. They are gone. A hand-picked duration is only ever
   correct for the roster and the viewport it was measured at, which is why the
   client loop itself ended up running at two different speeds across the site
   and the stack loop at a third. Speed is now the constant and duration is
   derived from it per track — see the MOTION SYSTEM block at the foot of this
   file, and the marquee section of main.js.

   Timing function and hover-to-pause both come from .marquee-track and
   .marquee:hover, which all three loops already shared. */

/* ------------------------------------------------------------
   ITEM 5a — "The testimonial boxes also need slight grey
   background"
   ------------------------------------------------------------ */
.tq {
  background: var(--color-surface-alt, #F1F5F9);
  border-color: var(--color-border);
}
.tq--accent { background: var(--color-surface-alt, #F1F5F9); border-color: var(--color-border); }
.section--dark .tq { background: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.14); }

/* ------------------------------------------------------------
   ITEM 5b — "The quotation mark needed is good old [66] like the
   one financial times uses. Change all the quotation marks across
   the website with financial times quotation."
   A solid serif left-double-quote, set large. Applied to all three
   quote components on the site: .tq__mark, .pq-mark, .quote-mark.
   ------------------------------------------------------------ */
.tq__mark,
.pullquote .pq-mark,
.quote .quote-mark {
  font-family: var(--ft-serif);
  font-weight: 700;
  font-style: normal;
  color: var(--color-text);
  -webkit-font-smoothing: antialiased;
}
.tq__mark {
  display: block;
  font-size: 3.4rem;
  line-height: 0.78;
  height: 0.5em;             /* the glyph sits high in its em box */
  margin-bottom: 2px;
  color: var(--color-text);
}
/* one mark sitewide — the teal variant no longer recolours it */
.tq--teal .tq__mark { color: var(--color-text); }
.section--dark .tq__mark { color: rgba(255,255,255,0.86); }
.pullquote .pq-mark {
  font-size: 1.35em;
  line-height: 0;
  color: var(--color-text);
  margin-right: 0.04em;
  vertical-align: -0.12em;
}
.section--dark .pullquote .pq-mark { color: rgba(255,255,255,0.86); }
.quote .quote-mark {
  display: block;
  font-size: 3rem;
  line-height: 0.78;
  height: 0.5em;
  color: var(--color-text);
}

/* ------------------------------------------------------------
   ITEM 5c - "the testimonial to keep looping ONE AT A TIME AT THE
   SAME LOCATION. ONE BOX AT A TIME. the second box REPLACES the
   first box and so on, NOT a continuous horizontal scroll ... I do
   not want the logo loop for testimonials." ... "with the dots
   below ... for all the testimonials across the website"

   THIS IS NOT THE LOGO STRIP AND MUST NEVER BECOME IT AGAIN. Every
   quote in a group is placed in the SAME grid cell, so all of them
   share one origin and one box, and exactly one is visible at a
   time. Advancing swaps .is-current, which cross-fades the leaving
   card out and the arriving card in AT THE IDENTICAL POSITION.
   There is no track, no clone set, no translate, no edge mask and
   no --mq-speed anywhere below: .marquee / .marquee-track belong
   to the client logo and partner loops alone, and this component
   borrows nothing from them.

   A CARD CANNOT BE CLIPPED HERE, BY CONSTRUCTION. The box is one
   card wide and as tall as the tallest quote in the group, the
   width is asked of the ROW and never of the window, and the
   viewport does not hide its overflow - there is nothing beside
   the card that would need hiding and nothing that could bleed in
   at an edge.

   Progressive enhancement. main.js upgrades any container holding
   two or more sibling figure.tq; WITHOUT JS the markup is
   untouched and the cards stay a plain static grid, so nothing is
   hidden behind a script and no empty band is reserved. A lone
   testimonial is never converted - a rotation of one has nothing
   to replace.
   ------------------------------------------------------------ */
.tq-carousel { display: block; position: relative; }
/* ONE CELL, EVERY QUOTE. minmax(0,1fr) so a long unbroken word cannot push
   the column past its row. Overflow is deliberately left visible: the old
   build hid it, and any card that measured a pixel wide of its viewport was
   then silently cut instead of simply being seen. */
.tq-carousel__viewport {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border-radius: var(--radius-lg);
}
.tq-carousel__viewport:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 4px; }
/* THE CARD IS SIZED BY ITS OWN ROW, NOT BY THE WINDOW. Measured at 1280: the
   quote block that sits inside the services grid on / has a 368px row, and a
   card sized off the VIEWPORT WIDTH came out 520px there - 1.41x its own row,
   so that one block could never show a whole card at any window size while
   every other block on the site was fine. width:100% asks the row itself and
   can never exceed it; the 560px cap only keeps the measure readable in a
   wide container, and matches the ~588px column the static two-up grid gives
   the same card above 861px.
   height:auto overrides .tq's height:100%, which in a grid cell would resolve
   against the ROW - i.e. the tallest quote - and stretch a two-line card into
   a box with a void inside its own border.
   align-self:start therefore leaves the spare height OUTSIDE the card, on the
   page background, which is where an absence reads as space rather than as a
   layout fault. */
.tq-carousel__slide {
  grid-area: 1 / 1;
  align-self: start;
  justify-self: center;
  width: 100%;
  max-width: 560px;
  height: auto;
  padding: clamp(24px, 3vw, 38px);
}
.tq-carousel__slide .tq__quote { font-size: 1.06rem; }
/* THE CROSS-FADE. 600ms, which is long enough to read as one card replacing
   another rather than as a blink, against the 7000ms hold main.js keeps. The
   8px lift is a settle, not travel: nothing moves horizontally, ever.
   visibility (not display) is what takes the hidden cards out of the tab
   order and the accessibility tree while still letting them set the height of
   the box, so the box is sized for the tallest quote before the first change
   and never resizes under the reader. Its transition is delayed by exactly
   the fade so the outgoing card stays paintable until it is fully gone. */
.tq-carousel__slide {
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity .6s var(--ease-out), transform .6s var(--ease-out),
              visibility 0s linear .6s;
}
.tq-carousel__slide.is-current {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity .6s var(--ease-out), transform .6s var(--ease-out),
              visibility 0s linear 0s;
}

.tq-dots {
  display: flex; justify-content: center; align-items: center;
  gap: 10px; margin-top: var(--space-6);
}
.tq-dot {
  appearance: none; -webkit-appearance: none;
  width: 9px; height: 9px; padding: 0; border-radius: 50%;
  border: 1px solid var(--color-border);
  background: #CBD5E1;
  cursor: pointer;
  transition: background .25s var(--ease-out), width .25s var(--ease-out), border-color .25s;
}
.tq-dot[aria-current="true"] {
  width: 26px; border-radius: 999px;
  background: var(--color-primary);
  border-color: var(--color-primary);
}
.tq-dot:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.tq-dot:hover { border-color: var(--color-primary); }
.section--dark .tq-dot { background: rgba(255,255,255,0.3); border-color: rgba(255,255,255,0.2); }
.section--dark .tq-dot[aria-current="true"] { background: var(--color-primary-bright); border-color: var(--color-primary-bright); }
/* REDUCED MOTION: a static, readable list. main.js does not upgrade the group
   at all under this preference, so the reader gets exactly the authored
   stacked cards the no-JS reader gets - no rotation, no fade, no dots, and
   nothing hidden. The rules below are the safety net for a preference that
   flips AFTER the page has loaded: every quote returns to the flow, visible,
   with no transition left on it. */
@media (prefers-reduced-motion: reduce) {
  .tq-carousel__viewport { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
  .tq-carousel__slide,
  .tq-carousel__slide.is-current {
    grid-area: auto;
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: none;
  }
  .tq-dot { transition: none; }
}

/* ------------------------------------------------------------
   ITEM 6 — "All the graphs need to look subtle, sophisticated and
   sleek. Use light grey instead of green bars and the bars should
   be thinner/slimmer. Implement this across the website for all
   bar graphs."

   Two changes everywhere: every TEAL fill becomes --bar-grey, and
   every bar becomes a slim rail. The funnel and roadmap bars used
   to carry their label as white text INSIDE a 40px block, which a
   9px rail cannot hold, so the label is lifted above the rail in
   dark ink and the rail is drawn as a pseudo-element at the row's
   own width. No page markup changes; no copy changes.
   ------------------------------------------------------------ */

/* --- 6.1 funnel (index, expertise-strategy-gtm, service-revops-systems) */
.funnel { gap: 14px; max-width: 520px; }
.funnel-row { grid-template-columns: 150px 1fr auto; gap: 14px; align-items: center; }
.funnel-bar {
  position: relative;
  width: 100% !important;           /* the ROW no longer steps down; the rail does */
  height: auto;
  min-height: 0;
  padding: 0 0 calc(var(--bar-h) + 7px) 0;
  border-radius: 0;
  display: block;
  background: none !important;      /* beats the four nth-child fills above */
  color: var(--color-text);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--fs-xs);
  letter-spacing: 0.01em;
  line-height: 1.2;
}
/* the unfilled rail, so the step-down is legible against a constant span */
.funnel-bar::before {
  content: "";
  position: absolute; left: 0; bottom: 0;
  width: 100%; height: var(--bar-h);
  border-radius: 999px;
  background: var(--bar-track);
}
.funnel-bar::after {
  content: "";
  position: absolute; left: 0; bottom: 0;
  width: var(--bar-w, 100%); height: var(--bar-h);
  border-radius: 999px;
  background: var(--bar-fill, var(--bar-grey));
}
.funnel-row:nth-child(1) .funnel-bar { --bar-fill: var(--bar-grey); --bar-w: 100%; }
.funnel-row:nth-child(2) .funnel-bar { --bar-fill: var(--bar-grey); --bar-w: 72%; }
.funnel-row:nth-child(3) .funnel-bar { --bar-fill: var(--color-primary-hover); --bar-w: 46%; }
.funnel-row:nth-child(4) .funnel-bar { --bar-fill: var(--color-primary-bright); --bar-w: 26%; }
@media (max-width: 560px) {
  .funnel-row { grid-template-columns: 1fr auto; }
  .funnel-row .f-lab { grid-column: 1 / -1; }
}

/* --- 6.2 roadmap (.rm-bar) — request-audit, contact, diagnostic,
       resource-blog-revops-diagnostic, expertise-growth-channels,
       service-fractional-marketing-lead */
.roadmap { gap: 14px; }
.rm-bar {
  position: relative;
  width: 100% !important;
  border-radius: 0;
  padding: 0 0 calc(var(--bar-h-xs) + 7px) 0;
  background: none !important;
  color: var(--ui-ink, var(--color-text));
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.75rem;
  line-height: 1.25;
}
.rm-bar::before {
  content: "";
  position: absolute; left: 0; bottom: 0;
  width: 100%; height: var(--bar-h-xs);
  border-radius: 999px;
  background: var(--bar-track);
}
.rm-bar::after {
  content: "";
  position: absolute; left: 0; bottom: 0;
  width: var(--bar-w, 100%); height: var(--bar-h-xs);
  border-radius: 999px;
  background: var(--bar-fill, var(--bar-grey));
}
.rm-row:nth-child(1) .rm-bar { --bar-fill: var(--bar-grey); --bar-w: 46%; }
.rm-row:nth-child(2) .rm-bar { --bar-fill: var(--bar-grey); --bar-w: 66%; }
.rm-row:nth-child(3) .rm-bar { --bar-fill: var(--color-primary-hover); --bar-w: 82%; }
.rm-row:nth-child(4) .rm-bar { --bar-fill: var(--color-primary-bright); --bar-w: 100%; color: var(--ui-ink, var(--color-text)); }
.rm-bar .rm-tag { color: var(--ui-mut, var(--color-text-muted)); font-weight: 600; opacity: 1; }

/* --- 6.3 pipeline stage strip (.uw-stage .s-bar) — already 4px, colour only */
.uw-stage .s-bar { height: 4px; }
.uw-stage:nth-child(1) .s-bar,
.uw-stage:nth-child(2) .s-bar,
.uw-stage:nth-child(3) .s-bar { background: var(--bar-grey); }
.uw-stage:nth-child(4) .s-bar { background: var(--color-primary-bright); }

/* --- 6.4 attribution + ladder fills */
.attr-row .a-fill,
.attr-row:nth-child(2) .a-fill,
.attr-row:nth-child(4) .a-fill { background: var(--bar-grey); }
.attr-row:nth-child(1) .a-fill { background: var(--color-primary-bright); }
.ladder-row .lr-fill { background: var(--bar-grey); }
.ladder-row.is-now .lr-fill { background: var(--color-primary-bright); }

/* --- 6.5 SVG column chart in the app-window mockups */
.uw-chart .cbar { fill: var(--color-primary-bright); }
.uw-chart .cbar--muted { fill: var(--bar-grey); }

/* --- 6.6 results chart (work.html) — the teal series goes grey */
.growth-chart .bar-a { fill: var(--bar-grey); opacity: 1; }
.growth-chart .bar-b { fill: var(--color-primary-bright); }

/* =====================================================================
   STACKED CONTENT LIST  (meeting doc v3.0, item 13)
   One full-width entry per item, single column, latest on top. Copy and
   the read-more sit left; a visual tile sits right. The tile carries a
   real client logo where one exists, a real date for an event, and an
   abstract schematic motif otherwise. Motifs are decorative only: they
   carry no axis, no scale and no number, so nothing is asserted.
   Used by /resources/, /resources/blogs/ and /resources/events/.
   ===================================================================== */
.stack-list { display: grid; gap: var(--space-6); }

.stack-entry {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(220px, 33%);
  gap: clamp(20px, 3vw, 44px);
  align-items: center;
  padding: clamp(18px, 2.4vw, 28px);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  /* hover timing: MOTION SYSTEM role 3, foot of this file */
}
.stack-entry:hover { border-color: #d7dfe9; box-shadow: var(--shadow-md); transform: translateY(-2px); }
.stack-entry:focus-within { border-color: #d7dfe9; box-shadow: var(--shadow-md); }

/* hairline connector in the gap, so the column reads as one run */
.stack-entry + .stack-entry::before {
  content: "";
  position: absolute;
  top: calc(-1 * var(--space-6) - 1px);
  left: 50%;
  width: 1px;
  height: var(--space-6);
  background: var(--color-border);
}

.se-main { min-width: 0; }

.se-cat {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--color-primary);
}
.se-cat::before { content: ""; width: 18px; height: 1.5px; background: var(--color-primary-bright); }

.se-title { font-size: clamp(1.15rem, 0.95rem + 0.8vw, 1.5rem); line-height: 1.24; margin-top: var(--space-3); }
.se-title a { color: var(--color-text); text-decoration: none; }
.se-title a:hover { color: var(--color-primary); text-decoration: none; }
.se-title a:focus-visible { outline: 2.5px solid var(--color-secondary); outline-offset: 3px; border-radius: 3px; }

.se-meta {
  margin-top: var(--space-3);
  font-size: var(--fs-sm); color: var(--color-text-muted);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
}
/* the separator glyph was --color-border (#E2E8F0), a BORDER token used as text:
   1.23:1 on white, so it did no separating and the meta line read as run-together
   fragments. It is text, so it takes the AA-safe text grey. */
.se-meta .se-dot { color: var(--color-text-muted); }
/* The dots are siblings in a wrapping flex row, so a wrap could leave one
   stranded at the end of a line with nothing after it: measured 4 on
   /resources/events/ and 1 on /resources/, each reading as a stray full stop
   floating in white space. The glyph is decorative and aria-hidden, so it is
   drawn from the item that FOLLOWS it instead - the separator now travels onto
   the next line with its own item and can never trail. */
.se-meta > .se-dot { display: none; }
.se-meta > span:not(.se-dot) ~ span:not(.se-dot)::before {
  content: "\00B7";
  margin-right: var(--space-2);
  color: var(--color-text-muted);
}

.se-more {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-6);
  padding: 9px 18px;
  border: 1.5px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  font-size: var(--fs-sm); font-weight: 600; color: var(--color-text);
  text-decoration: none;
  transition: background-color .18s var(--ease), border-color .18s var(--ease), color .18s var(--ease);
}
.se-more svg { width: 15px; height: 15px; }
.se-more:hover { background: var(--color-primary); border-color: var(--color-primary); color: #fff; text-decoration: none; }
.se-more:focus-visible { outline: 2.5px solid var(--color-secondary); outline-offset: 2px; }

/* ---- the visual tile ---- */
.se-vis {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 14px;
  border: 1px solid var(--color-border);
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--color-surface-alt);
}
.se-vis--warm  { background: linear-gradient(160deg, #FFF7ED 0%, #FEF3C7 100%); }
.se-vis--teal  { background: linear-gradient(160deg, #FFFFFF 0%, var(--color-teal-soft) 100%); }
.se-vis--slate { background: linear-gradient(160deg, #FFFFFF 0%, var(--color-surface-alt) 100%); }
/* Plain white plate for a tile carrying a client logo. Several marks still ship
   with a baked-in white patch (leapest.png is the worst), which reads as a
   rectangle on any tinted field. White hides it until the files are re-supplied. */
.se-vis--plate { background: #FFFFFF; }
/* The plate carried no logo treatment at all, so settly.png rendered raw brand
   green on white: 0% of ink at 3:1. It then carried a COPY of what the two
   loops said at the time, which went stale the moment they moved on - and
   cogoport and settly appear in both places on this page. The plate is now IN
   the loops' own selector list (figures.css section 9), so it cannot go stale
   again. Nothing to declare here. */

.se-motif { position: absolute; inset: 0; width: 100%; height: 100%; }
.se-motif .sm-bar  { fill: #E2E8F0; }
.se-motif .sm-bar--ink { fill: #CBD5E1; }
.se-motif .sm-line { fill: none; stroke: var(--color-primary-bright); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; opacity: .55; }
.se-motif .sm-line--teal { stroke: var(--color-teal); opacity: .45; }
.se-motif .sm-rule { stroke: #E2E8F0; stroke-width: 1; }
.se-motif .sm-node { fill: var(--color-primary-bright); opacity: .7; }

/* Corner chip, not centred: a centred label collides with the middle node of
   the flow motif and with the peak of the curve motif. */
.se-vis__tag {
  position: absolute; z-index: 1; left: 14px; bottom: 12px;
  font-family: var(--font-heading); font-weight: 800;
  font-size: var(--fs-xs); letter-spacing: .12em; text-transform: uppercase;
  color: var(--color-text-muted-strong);
  background: rgba(255,255,255,0.86);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  padding: 6px 14px;
}

/* Logo lockup on a case entry. One height for every mark so the row reads as
   peers rather than a hierarchy — the same complaint the client raised about
   the client strip. 46px is the ceiling the smallest surviving artwork
   (leapest.png, 104x38) can carry without visible upscaling; it draws at 1.21x
   and stays the soft one until that file is re-supplied (see ROSTER-NOTE.md). */
.se-logo {
  position: relative; z-index: 1;
  height: 46px; width: auto; max-width: 62%;
  object-fit: contain;
}
@media (max-width: 860px) { .se-logo { height: 36px; } }

/* date block on an event entry */
.se-date { position: relative; z-index: 1; text-align: center; line-height: 1; }
.se-date .se-day { font-family: var(--font-heading); font-weight: 800; font-size: clamp(1.8rem, 1.4rem + 1.4vw, 2.5rem); color: var(--color-primary); }
.se-date .se-mon { display: block; margin-top: 8px; font-size: var(--fs-xs); font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--color-text-muted); }

/* Mirrored variant: visual left, copy right, used to break the rhythm.
   The column template has to mirror too — swapping `order` alone drops the
   visual into the 1fr column and it renders roughly twice its intended size. */
.stack-entry--flip { grid-template-columns: minmax(220px, 33%) minmax(0, 1fr); }
.stack-entry--flip .se-main { order: 2; }
.stack-entry--flip .se-vis  { order: 1; }

/* The resources page opens straight on content with no hero above it
   (meeting doc item 12), so the first band needs a shorter top. */
/* was clamp(28px,3.4vw,56px) = 43.5px at 1280, a fourth rhythm step that existed
   on exactly ONE section on the whole site. The intent (a shorter top because
   /resources/ opens with no hero above it) is preserved by reusing the step the
   site already declares for a shortened band, .section--tight. */
.section--open { padding-top: clamp(40px, 4vw, 64px); }

@media (max-width: 860px) {
  .stack-entry,
  .stack-entry--flip { grid-template-columns: 1fr; gap: var(--space-5); padding: var(--space-5); }
  .stack-entry .se-vis { order: -1; aspect-ratio: 16 / 7; }
  .stack-entry--flip .se-main { order: 0; }
  .stack-entry--flip .se-vis  { order: -1; }
  .se-more { margin-top: var(--space-5); }
}

/* group heading above each stack (blogs / events / cases) */
.stack-group + .stack-group { margin-top: var(--space-16); }
.stack-group__head {
  display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-8);
}
.stack-group__head h2 { font-size: var(--fs-h2); }
.stack-group__count { font-size: var(--fs-sm); color: var(--color-text-muted); }

/* --- 6.7 reveal animation follows the bar, not the label.
       graphics.css scaleX()es the whole .funnel-bar / .rm-bar element; now
       that the element also carries the text, that would squash the label.
       Hold the element still and grow the rail instead. */
.gfx-anim .funnel-bar, .gfx-anim .rm-bar,
.gfx-in   .funnel-bar, .gfx-in   .rm-bar { transform: none; transition: none; }
.gfx-anim .funnel-bar::after, .gfx-anim .rm-bar::after { transform: scaleX(0); transform-origin: left; }
.gfx-in   .funnel-bar::after, .gfx-in   .rm-bar::after { transform: scaleX(1); transition: transform 0.9s var(--ease-out); }
.gfx-in .funnel-row:nth-child(2) .funnel-bar::after, .gfx-in .rm-row:nth-child(2) .rm-bar::after { transition-delay: 0.08s; }
.gfx-in .funnel-row:nth-child(3) .funnel-bar::after, .gfx-in .rm-row:nth-child(3) .rm-bar::after { transition-delay: 0.16s; }
.gfx-in .funnel-row:nth-child(4) .funnel-bar::after, .gfx-in .rm-row:nth-child(4) .rm-bar::after { transition-delay: 0.24s; }
@media (prefers-reduced-motion: reduce) {
  .gfx-anim .funnel-bar::after, .gfx-anim .rm-bar::after { transform: none; }
  .gfx-in   .funnel-bar::after, .gfx-in   .rm-bar::after { transition: none; }
}

/* =====================================================================
   v3.1 FEEDBACK — SIZING AND CHROME (items 1, 4, 7)
   This block is the LAST thing the cascade sees, so it is the single
   place that fixes geometry sitewide. Each sub-block quotes his line.
   ===================================================================== */

/* ---------------------------------------------------------------------
   ITEM 1 — "Both should of same size across the website"
   The two credential pills (Clutch + HubSpot Solution Partner) were
   content-sized flex items, so each pill tracked its own label length
   and every surface rendered a different pair. One geometry now, on
   every surface, at every label length: identical width, identical
   height, identical icon box.
   --------------------------------------------------------------------- */
.ratings { gap: 14px; align-items: stretch; }
.rating-card {
  flex: 1 1 var(--pill-w, 288px);
  max-width: var(--pill-w, 288px);
  min-height: 72px;
  box-sizing: border-box;
  align-items: center;
  padding: 14px 18px;
}
.rating-card > span { flex: 1 1 auto; min-width: 0; }
.rating-card img { height: 40px; width: 40px; flex: none; object-fit: contain; }
/* Browser-measured: with one pill geometry the SHORTER label sets a 1-line box
   and the longer one a 2-line box, so a wrapped pair still came out 81px vs
   72px. Reserving two lines on both makes the pair identical whether it sits
   side by side or stacks. */
.rating-card__meta { display: block; margin-top: 0; line-height: 1.35; min-height: 2.7em; }
/* the narrow article sidebar keeps its own stacked, full-width variant */
.article-nav__cta .rating-card { flex: 1 1 auto; max-width: none; min-height: 64px; }
.article-nav__cta .rating-card img { height: 32px; width: 32px; }
@media (max-width: 520px) {
  .ratings { --pill-w: 100%; }
  .rating-card { max-width: 100%; }
}

/* ---------------------------------------------------------------------
   ITEM 4 — "The bars in bar graphs should be slimmer all across the
   website." Repeated from v3.0, so the last pass did not go far enough.
   Two mechanisms carry bars on this site and BOTH move here:
     (a) CSS rails  — .funnel-bar / .rm-bar / .a-track / .lr-track
     (b) SVG <rect> — width is a markup attribute, re-derived per chart
         in the page sources (work, expertise, growth-marketing,
         salesforce-personalisation).
   Light grey stays the resting colour; only the accent series is orange.
   --------------------------------------------------------------------- */
:root {
  --bar-h:    6px;   /* was 9px  */
  --bar-h-xs: 4px;   /* was 6px  */
}
/* ONE THICKNESS SYSTEM. The site shipped SIX different bar heights for what
   reads as the same chart idiom (.s-bar 2.8-3.1px, .rm-bar 4px, .funnel-bar 6px,
   .lr-fill 6px, .a-fill 6px, .figbars 8px), so two pages in the same nav group
   could show a 3px bar and an 8px bar. The tokens existed; these rails simply
   did not read them. Every rail on the site now resolves through --bar-h (the
   figure/chart rail) or --bar-h-xs (the compact in-mockup rail), so the whole
   set moves together and there is one place to change it. */
.attr-row .a-track { height: var(--bar-h); border-radius: calc(var(--bar-h) / 2); }
.attr-row .a-fill  { border-radius: calc(var(--bar-h) / 2); }
.ladder-row .lr-track { height: var(--bar-h); border-radius: calc(var(--bar-h) / 2); }
.ladder-row .lr-fill  { border-radius: calc(var(--bar-h) / 2); }
.uw-stage .s-bar { height: var(--bar-h-xs); border-radius: calc(var(--bar-h-xs) / 2); }
/* the muted comparison series is the light grey he asked for; the second
   series stays brand orange so the chart still carries its argument */
.uw-chart .cbar--muted, .growth-chart .bar-a { fill: var(--bar-grey); opacity: 1; }
/* work.html legend: the "Before" swatch was teal while its bars rendered
   grey, so the key contradicted the data on every render */
.chart-head .ch-legend i.legend-grey { background: var(--bar-grey); }

/* ---------------------------------------------------------------------
   ITEM 7 — "Both the CTA buttons should be of same size all across the
   website. Hovering should have change effect on them"
   Three separate defects were live: (a) 5 pairs where the second button
   was missing btn--lg, so it sat at a different height and font size;
   (b) two different second-button variants across pages; (c) even where
   the classes matched, the buttons were shrink-to-fit, so a longer label
   made a wider button. (a) and (b) are fixed in the page sources; (c) is
   fixed here — a CTA group that holds only buttons lays them out on one
   equal track, so both members are always the same size.
   --------------------------------------------------------------------- */
.cta-row { align-items: stretch; }
.cta-row > .btn { justify-content: center; text-align: center; }
/* EQUAL MEANS "BOTH MATCH THE WIDEST LABEL" — DERIVED FROM CONTENT, NEVER PINNED.
   The previous rule pinned the pair to min-width:13.5rem / max-width:34rem. That is
   an arbitrary box, and three labels on the site are intrinsically wider than it
   ("Start the HubSpot Quick Audit" 306.7px, "Subscribe to our latest insights"
   314.4px, "Bigger than a read? Book a diagnostic" 368.5px), so the label painted
   OUTSIDE its own border box and collided with the neighbouring button.

   The rule now derives the shared width instead of declaring it:
     grid-auto-columns: 1fr  + width: max-content
   Under a max-content constraint the fr algorithm resolves every flexible track to
   the LARGEST track's max-content contribution, so both members size to the widest
   label and neither can be squeezed below the text it has to hold.

   `white-space: normal` on the pair is the safety valve, not the sizing input:
   max-content sizing ignores soft-wrap opportunities, so the derived width is
   identical to the nowrap width, but if max-width:100% ever clamps the row on a
   narrow viewport the labels wrap inside the box instead of spilling out of it. */
.cta-row:has(> .btn + .btn):not(:has(> :not(.btn))) {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  width: max-content;
  max-width: 100%;
  align-items: stretch;
}
.cta-row:has(> .btn + .btn):not(:has(> :not(.btn))) > .btn { white-space: normal; }
.cta-row--center:has(> .btn + .btn):not(:has(> :not(.btn))) { margin-inline: auto; }
/* MIXED ROW — a button pair that also carries a text link (e.g. the /about/ social
   pair beside "Browse resources"). The pair must still be equal, but the link must
   NOT be widened to match it. Two EXPLICIT 1fr tracks take the buttons and equalise
   on the widest label exactly as above; anything after them flows into an IMPLICIT
   track sized to its own content, which `grid-auto-columns` governs separately. */
.cta-row:has(> .btn:first-child + .btn):has(> :not(.btn)) {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  width: max-content;
  max-width: 100%;
  align-items: center;
}
.cta-row:has(> .btn:first-child + .btn):has(> :not(.btn)) > .btn { white-space: normal; align-self: stretch; }
.cta-row--center:has(> .btn:first-child + .btn):has(> :not(.btn)) { margin-inline: auto; }
@media (max-width: 640px) {
  .cta-row:has(> .btn:first-child + .btn):has(> :not(.btn)) {
    grid-template-columns: 1fr; grid-auto-flow: row; grid-auto-columns: auto; width: 100%; max-width: none;
  }
}
@media (max-width: 640px) {
  /* stacked pair: equal rows, so a two-line label cannot make one member taller */
  .cta-row:has(> .btn + .btn):not(:has(> :not(.btn))) { grid-auto-flow: row; grid-auto-rows: 1fr; width: 100%; max-width: none; }
}
/* A CTA pair BESIDE A HERO VISUAL is the one place where sizing the pair from
   its own content is not free. The pair's max-content (629.5px for the widest
   pair on the site) becomes the minimum of the grid track it sits in, and in a
   two-column hero inside .container--narrow (820px) that pushes the sibling
   visual off the page: measured, the /expertise/revops-crm/ mockup ran 99px
   past the viewport at 1024 and 65px at 1100, clipped by body{overflow-x:hidden}.
   Stacking the pair in that band drops the track floor from 629.5px to 306.7px
   and the visual comes back inside, WITHOUT reintroducing a pinned width: the
   stacked members are still both exactly as wide as the widest label. Above
   1200px the column is wide enough to carry the pair on one line, and below
   821px the hero is already single-column. */
@media (min-width: 821px) and (max-width: 1200px) {
  .page-hero .split .cta-row:has(> .btn + .btn):not(:has(> :not(.btn))),
  .hero .hero-grid .cta-row:has(> .btn + .btn):not(:has(> :not(.btn))),
  .hero .split .cta-row:has(> .btn + .btn):not(:has(> :not(.btn))) {
    grid-auto-flow: row;
    grid-auto-rows: 1fr;
    width: max-content;
    max-width: 100%;
  }
}
/* the dark CTA band already stacks its pair at >=861px; keep it stacked
   and let the equal track handle the width */
@media (min-width: 861px) {
  .cta-band-inner > .cta-row:has(> .btn + .btn):not(:has(> :not(.btn))) {
    grid-auto-flow: row; width: auto; max-width: none; min-width: 260px;
  }
}
/* the promo panel pair is stacked at every width. Measured in-browser: where
   the panel copy is long enough to wrap .promo__inner, the CTA stack took the
   whole line and rendered 500px+ buttons on two pages. The stack is now sized
   from its own widest label rather than pinned to 20rem — at 20rem the label
   "Or take the HubSpot Quick Audit" rendered with 28.6px of optical padding
   against 30px declared, i.e. 1.4px of spill on each side. */
.promo__cta { align-items: stretch; flex: none; width: max-content; max-width: 100%; }
.promo__cta > .btn { width: 100%; justify-content: center; text-align: center; white-space: normal; }
.promo__note { text-align: left; }
/* one declared size for btn--lg. styles.css and premium.css each declared
   it with different padding, so the winner depended on load order. */
.btn--lg { padding: 16px 30px; font-size: 1.05rem; line-height: 1.25; }

/* "Hovering should have change effect on them" — every variant now lifts
   by the same restrained 2px and gains a shadow of its own surface. The
   primary did this already; the outline, secondary, ghost and on-dark
   variants changed fill only, so a pair responded asymmetrically. */
.btn--secondary:hover,
.btn--outline-light:hover,
.btn--ghost:hover,
.btn--on-dark:hover,
.btn--primary:hover { transform: translateY(-2px); }
.btn--secondary:hover { box-shadow: 0 10px 22px -10px rgba(15, 118, 110, 0.40); border-color: var(--color-teal); }
.btn--outline-light:hover { box-shadow: 0 10px 22px -10px rgba(0, 0, 0, 0.55); }
.btn--ghost:hover { box-shadow: 0 10px 22px -12px rgba(17, 24, 39, 0.30); border-color: #d3deea; }
.btn--on-dark:hover { box-shadow: 0 12px 26px -10px rgba(0, 0, 0, 0.50); }
.btn:focus-visible { outline: 2px solid var(--color-primary-bright); outline-offset: 3px; }
.btn:active { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .btn { transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; }
  .btn--secondary:hover, .btn--outline-light:hover, .btn--ghost:hover,
  .btn--on-dark:hover, .btn--primary:hover, .btn:active { transform: none; }
}

/* ============================================================
   v3.1 ITEMS 3, 5, 6 - EVERYTHING THAT MOVES IN A LOOP
   One mechanism per thing that loops, shipped from a shared
   partial, so a page cannot drift out of the pattern again.
   ============================================================ */

/* ---- ITEM 3 -------------------------------------------------
   "The logos should be moving in loop all across the website ...
    grey on the light yellow/crem/beige is good ... No double rows"

   The static wrapping .trust-logos row is gone from every page;
   there is now exactly one client-logo component, .trust-mq, and
   a marquee is a single row by construction, so the 4 + 1 wrap he
   rejected cannot come back. The cream band travels WITH the loop
   for the strips that sit inside a hero, a page body or a dark
   section, so the treatment is the same on all of them.
   ------------------------------------------------------------ */
.trust-mq--inset {
  background: linear-gradient(180deg, #FFF7ED 0%, #FEF3C7 100%);
  border-radius: var(--radius-lg);
  padding-block: clamp(14px, 1.8vw, 20px);
  /* the edge mask fades the BAND as well as the marks, which on a dark
     section leaves a muddy cream-to-navy ramp at both ends. The band is a
     solid, deliberate shape here; .marquee already clips the track. */
  -webkit-mask-image: none;
          mask-image: none;
}
/* inside the cream .trust-strip section the surface is already cream, so the
   loop must not paint a second band on top of it */
.trust-strip:not(.trust-strip--on-dark) .trust-mq--inset {
  background: none; border-radius: 0; padding-block: 0;
}

/* ---- THE BAND KEPT ITS EDGES, THE MARKS LOST THEIRS -----------------
   Switching the mask off above stopped the muddy cream-to-navy ramp, but it
   also took away the only thing stopping a wordmark being guillotined against
   the band edge: measured on /resources/ and
   /resources/events/tactical-growth-marketing-meetup/, the loop parks with
   CO-FLO sliced through the L, hard against the cream. A marquee is never
   still, so at some point in every loop a mark is cut in half at both ends.
   The fade cannot go back on the element - that is what faded the band. It
   goes on two static scrims that repeat the band's OWN gradient and fade
   horizontally, so the band keeps a solid edge and the marks fade into it.
   Static, so they do not travel with the track the way a mask on the track
   would. Only where the band is actually painted. */
.trust-mq--inset { position: relative; }
.trust-mq--inset::before,
.trust-mq--inset::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  width: clamp(28px, 5%, 56px);
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(180deg, #FFF7ED 0%, #FEF3C7 100%);
}
.trust-mq--inset::before {
  left: 0;
  border-radius: var(--radius-lg) 0 0 var(--radius-lg);
  -webkit-mask-image: linear-gradient(90deg, #000 35%, transparent);
          mask-image: linear-gradient(90deg, #000 35%, transparent);
}
.trust-mq--inset::after {
  right: 0;
  border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
  -webkit-mask-image: linear-gradient(270deg, #000 35%, transparent);
          mask-image: linear-gradient(270deg, #000 35%, transparent);
}
/* no band means no scrim to match: the ordinary edge mask does the job there */
.trust-strip:not(.trust-strip--on-dark) .trust-mq--inset::before,
.trust-strip:not(.trust-strip--on-dark) .trust-mq--inset::after { content: none; }
.trust-strip:not(.trust-strip--on-dark) .trust-mq--inset {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

/* ---- ITEM 2, resolved by the same rule ----------------------
   The on-dark strip used to invert its marks to white
   (filter: brightness(0) invert(1)), which is both invisible
   against a cream band and a SECOND logo treatment competing
   with item 3's "all the logos should look uniform". The marks
   now carry the identical grey-on-cream treatment here; only the
   label and the divider rule adapt to the dark surface.
   ------------------------------------------------------------ */
.trust-strip--on-dark .trust-mq__item img { filter: grayscale(1) brightness(.55); opacity: 1; }
.trust-strip--on-dark .trust-mq__item img:hover { filter: none; opacity: 1; }
.trust-strip--on-dark .trust-mq--inset { margin-top: var(--space-4); }

/* ---- ITEM 5 -------------------------------------------------
   "All the testimonials should move in loop all across the website."

   .tq-group is the one container main.js upgrades into the
   rotating .tq-carousel with dots. Every testimonial surface on
   every page is now a .tq-group holding two or more quotes and
   nothing else, which is exactly the shape the upgrader requires,
   so no surface is left as a lone static card. Without JS the
   group degrades to the stacked cards it already was.
   ------------------------------------------------------------ */
.tq-group { display: grid; gap: var(--space-6); }
.tq-group.tq-carousel { display: block; }   /* the upgraded state is not a grid */
@media (min-width: 861px) {
  .tq-group:not(.tq-carousel) { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
}

/* Narrow-screen button size. This has to sit AFTER the unmediated .btn--lg
   above: same specificity, so source order decides. One size per breakpoint,
   and the longest CTA label on the site now sets a single line inside the
   306px promo stack (measured at 390px, where 8 pairs came out 56 vs 77). */
@media (max-width: 640px) {
  .btn--lg { padding: 15px 16px; font-size: 0.98rem; }
}

/* ==========================================================================
   G20. RESOURCE ARTICLES — editorial figure system, prev/next, data tables
   Used by every /resources/blogs/<slug>/ and /resources/events/<slug>/ page.
   Palette is the house palette only; no new colours are introduced.
   ========================================================================== */

/* --- the figure shell ---------------------------------------------------- */
.figx {
  margin: var(--space-12) 0;
  padding: clamp(22px, 3vw, 34px);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: 0 1px 2px rgba(17, 24, 39, 0.04);
}
.figx__head {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-bottom: var(--space-5);
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--color-border);
}
.figx__eyebrow {
  font-size: 0.75rem;   /* 12px floor (was 10.88px) — "Figure 1" on 23 pages */
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-primary);
}
.figx__title {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: clamp(1.02rem, 1.6vw, 1.18rem);
  line-height: 1.3;
  color: var(--color-text);
}
.figx__note {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
  font-size: 0.78rem;
  font-style: italic;
  line-height: 1.5;
  color: var(--color-text-muted);
}

/* --- rail: the article's own sections, in order -------------------------- */
.article-body .figrail {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  position: relative;
}
.figrail__step {
  position: relative;
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  margin: 0;
}
/* connecting spine between the numerals */
.figrail__step::before {
  content: "";
  position: absolute;
  left: 21px;
  top: 50%;
  width: 2px;
  height: 100%;
  background: var(--color-border);
  z-index: 0;
}
.figrail__step:last-child::before { display: none; }
.figrail__n {
  position: relative;
  z-index: 1;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--color-teal-soft);
  color: var(--color-teal);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}
.figrail__step:first-child .figrail__n {
  background: var(--color-primary-soft);
  color: var(--color-primary);
}
.figrail__label {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.98rem;
  line-height: 1.45;
  color: var(--color-text);
}

/* --- grid: a set the article enumerates ---------------------------------- */
.figgrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
@media (max-width: 620px) { .figgrid { grid-template-columns: 1fr; } }
.figgrid__cell {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-5);
  background: var(--color-surface);
}
.figgrid__n {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.75rem;   /* 12px floor (was 11.20px) */
  letter-spacing: 0.08em;
  color: var(--color-primary);
  font-variant-numeric: tabular-nums;
}
.figgrid__lab {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1.35;
  color: var(--color-text);
}
.figgrid__txt {
  font-size: 0.87rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

/* --- record: fields a short news piece states in prose ------------------- */
.figrec { margin: 0; display: grid; gap: 0; }
.figrec__row {
  display: grid;
  grid-template-columns: 168px minmax(0, 1fr);
  gap: var(--space-5);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
}
.figrec__row:last-child { border-bottom: 0; padding-bottom: 0; }
@media (max-width: 620px) {
  .figrec__row { grid-template-columns: 1fr; gap: 4px; }
}
.figrec__k {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  line-height: 1.5;
}
.figrec__v {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--color-text);
}

/* --- data tables inside an article --------------------------------------- */
.table-wrap {
  margin: var(--space-8) 0;
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}
.dtable { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.dtable th,
.dtable td {
  padding: 11px 16px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--color-border);
  line-height: 1.55;
}
.dtable th {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text);
  background: var(--color-surface-alt);
  white-space: nowrap;
}
.dtable td { color: var(--color-text-muted); }
.dtable tr:last-child td { border-bottom: 0; }

/* --- prev / next --------------------------------------------------------- */
.prevnext {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}
@media (max-width: 680px) { .prevnext { grid-template-columns: 1fr; } }
.prevnext__item {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-5) var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  text-decoration: none;
  /* hover timing: MOTION SYSTEM role 3, foot of this file */
}
.prevnext__item.is-empty { border: 0; background: none; padding: 0; }
@media (max-width: 680px) { .prevnext__item.is-empty { display: none; } }
a.prevnext__item:hover {
  border-color: var(--color-primary-bright);
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(17, 24, 39, 0.07);
  text-decoration: none;
}
a.prevnext__item:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.prevnext__item--prev { text-align: left; }
.prevnext__item--next { text-align: right; }
.prevnext__dir {
  font-size: 0.75rem;   /* 12px floor (was 10.88px) — real navigation UI, 21 pages */
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-primary);
}
.prevnext__title {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.98rem;
  line-height: 1.4;
  color: var(--color-text);
}

@media (prefers-reduced-motion: reduce) {
  .prevnext__item { transition: none; }
  a.prevnext__item:hover { transform: none; }
}

/* ------------------------------------------------------------------
   HubSpot form typography: match the hand-built form EXACTLY.
   HubSpot ships its own stylesheet whose labels render heavier than
   ours, which is the "extra bold" Sushil flagged against the clean
   v2.0 form. These rules restate the .field label / .input treatment
   from styles.css at a specificity that beats HubSpot's own sheet.
   ------------------------------------------------------------------ */
.hs-embed form .hs-form-field > label,
.hs-embed form .hs-form-field > label span,
.hs-embed .hs-form-field > label,
.hs-embed .hs-form-field > label span {
  font-family: var(--font-body) !important;
  font-size: var(--fs-sm) !important;
  font-weight: 600 !important;
  line-height: 1.45 !important;
  color: var(--color-text) !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
}
.hs-embed form .hs-form-field > label > span > span,
.hs-embed .hs-richtext,
.hs-embed .hs-field-desc {
  font-weight: 400 !important;
}
.hs-embed .hs-form h1, .hs-embed .hs-form h2,
.hs-embed form h1, .hs-embed form h2 {
  font-family: var(--font-heading) !important;
  font-weight: 700 !important;
  font-size: 1.5rem !important;
  letter-spacing: -0.02em !important;
}
.hs-embed form .hs-input,
.hs-embed .hs-input {
  font-family: var(--font-body) !important;
  font-size: var(--fs-body) !important;
  font-weight: 400 !important;
  color: var(--color-text) !important;
}
.hs-embed form .hs-button,
.hs-embed .hs-button {
  font-family: var(--font-body) !important;
  font-weight: 700 !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
}

/* ============================================================
   VISUAL SWEEP — defects found by eye in a real browser at
   1280 and 390 that no link/anchor/alt audit can see.
   ============================================================ */

/* ---- The client logo loop hard-cut its marks at both edges ---
   Measured on / /services/ /approach/ /work/: .marquee and
   .partner-mq both fade their ends, but .trust-mq--inset carried
   `mask-image: none`, so the client strip - the most prominent of
   the three, sitting directly under every hero - sliced the first
   and last wordmark off mid-letter ("breachlock" rendered as
   "achlock"). Three loops on one page, one of them cut: it reads
   as a broken image, not as motion.

   The mask was switched off for a real reason: .trust-mq--inset
   paints its own cream band on dark sections and inside a hero,
   and masking the element fades the BAND as well as the marks,
   leaving a muddy cream-to-navy ramp at both ends. That reason
   does not apply inside a cream .trust-strip, where the rule
   below already strips the band and the element is fully
   transparent - measured background-color rgba(0,0,0,0) - so the
   mask can only touch the marks. Restore it exactly there and
   leave the deliberate band alone everywhere else. */
.trust-strip:not(.trust-strip--on-dark) .trust-mq--inset {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  /* the loop stops and becomes a horizontal scroller; a fade over a
     scrollable region hides content the user has to reach */
  .trust-strip:not(.trust-strip--on-dark) .trust-mq--inset {
    -webkit-mask-image: none; mask-image: none;
  }
}

/* ---- A card's closing link floated wherever its copy ended ---
   The service and functional-area cards are equal height because
   the grid stretches them, but each card is display:block, so the
   "Explore ..." / "See ..." link sat immediately after that card's
   own list. Measured on /services/: three cards all ending at
   y=450 with their links at y=311, y=335 and y=404, i.e. 139px,
   115px and 46px of dead space below three elements that are
   meant to read as one row. The eye reads the ragged link row
   before it reads the copy.

   Bottom-align the link by making the card a column and letting
   the element BEFORE the link absorb the slack. Putting the auto
   margin on the second-to-last child rather than on the link
   itself keeps the link's own .mt-6 gap intact, so on the tallest
   card - where there is no slack to distribute - the link still
   clears the list by a full 24px instead of butting against it. */
.card:has(> .link-arrow:last-child) {
  display: flex;
  flex-direction: column;
}
.card:has(> .link-arrow:last-child) > :nth-last-child(2) { margin-bottom: auto; }
/* the icon tile and the link are intrinsically sized; as flex items
   they would otherwise stretch to the full card width, which would
   turn a 215px link into a 368px hit area */
.card:has(> .link-arrow:last-child) > .card-icon,
.card:has(> .link-arrow:last-child) > .link-arrow { align-self: flex-start; }

/* ---- Hero mock: the KPI figures broke their own baseline ------
   .uw-kpi is label-over-figure in a stretched grid track. At 1280
   every label is one line and the three figures line up. At 390
   the track narrows, "Deals, all stages" wraps to two lines, and
   96 drops a full line below 8 and 4 / 4 - three numbers that are
   the whole point of the mock, reading as a stagger. The tiles are
   already equal height, so anchoring the figure to the bottom of
   the tile aligns all three whatever the label does. */
.uw-kpi { display: flex; flex-direction: column; }
.uw-kpi .k-num { margin-top: auto; }

/* ============================================================
   MOTION SYSTEM — one value per ROLE, declared once.

   The sweep this block comes out of did not ask "is this element
   broken?". Every value it replaces passed that test on its own.
   It asked "does this element MATCH ITS COUNTERPART?", and the
   answers were measured in a browser across all 48 pages at 1280
   and 390, then grouped by role and diffed against each group's
   most common value.

   RULE FOR ANYONE EDITING BELOW: a motion value is a property of
   a ROLE, never of a component. If a new component moves, add its
   selector to the role it belongs to. Do not give it a duration
   of its own, however reasonable that duration looks in isolation
   — that is exactly how the six values this block deletes got in.
   ============================================================ */

/* ---- ROLE 1: the horizontal logo/stack loop ------------------
   THE MEASUREMENT. Three loops render on every one of the 48
   pages, all three within one scroll of each other. Measured
   (track width / 2) / duration, which is what the eye actually
   reads:

     1280   client  45.4 px/s on 6 pages, 37.6 px/s on 39
            partner 45.3 px/s
            stack   36.3 px/s on all 45
      390   client  37.1 px/s on 4 pages, 30.8 px/s on the rest
            partner 37.0 px/s
            stack   31.0 px/s

   Two separate faults in one family. The client loop disagreed
   with ITSELF between pages because its roster changes (12 marks
   on /, 10 on /services/) while its duration was pinned; and the
   stack loop, which nobody had calibrated, ran a fifth slower
   than the two beside it.

   THE FIX. Speed is the constant, duration is derived. --mq-speed
   is the only number here, and main.js writes each track's
   duration as (its own scrollWidth / 2) / --mq-speed on load and
   on resize. Add a mark to any roster, change a margin, change
   the box, and every loop still travels at the same pace.
   45 px/s is the value the client and partner loops were already
   converged on at 1280 by hand; 37 px/s is theirs at 390. */
.marquee { --mq-speed: 45; }
@media (max-width: 860px) { .marquee { --mq-speed: 37; } }

/* ---- ROLE 2: an infinite loop is paused while off screen -----
   THREE POLICIES FOR ONE BEHAVIOUR, measured on / at scrollY 0:
   the testimonial carousel rotated only while >=25% in view and
   stopped on tab-hide (IntersectionObserver, main.js); the
   journey arrows started on .is-in and then ran forever, on
   screen or not; and the three marquees, the live dots and the
   floating chips ran from load, off screen, and kept running in
   a backgrounded tab. Converged on the carousel's policy, which
   was the considered one. main.js adds .is-anim-paused; this is
   the one rule that acts on it, and it has to reach pseudo
   elements because two of the four loops live on ::before /
   ::after. */
.is-anim-paused,
.is-anim-paused::before,
.is-anim-paused::after { animation-play-state: paused !important; }

/* ---- ROLE 3: a bordered surface that lifts on hover ----------
   Same interaction everywhere: border-colour shifts, shadow
   deepens, surface rises. The group's dominant value was .card /
   .card--tile at 190 instances across 36 pages. Diverging
   members, all of which pass a per-element check and only show
   up beside each other:
     .rating-card    .2s  ease            43 pages (shared partial,
                                          sits under the client loop)
     .wfl-card       .25s ease            home page, 3 cards
     .stack-entry    .22s --ease           3 pages
     .prevnext__item .2s  --ease-out      21 pages
     .js-badge       .3s  --ease-out, but border-color untokened,
                     so ONE declaration carried two easings
     .event-row      .2s  ease            0 pages (ships, renders nowhere)
   Lift DISTANCE is deliberately left alone: -2px on a badge and
   -4px on a card is motion scaled to the object, not drift. */
.card,
.card--tile,
.wfl-card,
.rating-card,
.event-row,
.stack-entry,
.prevnext__item,
.pillar,
.quote,
.case-card,
.journey-step .js-badge {
  transition:
    border-color 0.25s var(--ease-out),
    box-shadow   0.30s var(--ease-out),
    transform    0.30s var(--ease-out);
}

/* ---- ROLE 4: the ambient "this is live" loop ----------------
   Three of these, and they share pages: .aw-live + .journey-step
   on 10 pages, .aw-live + .df-obj on /expertise/revops-crm/.
   Periods ran 2.4s / 2.4s / 2.6s. The odd one out is now on the
   group's period.

   Easing is deliberately NOT flattened, because the three
   keyframes are different shapes and the curve has to match the
   shape: aw-pulse is a one-way ripple (0 -> 70% -> gone) and
   wants ease-out; jarrow and dfSync are symmetric breathes
   (0%,100% -> 50%) and want ease-in-out. dfSync was running
   --ease, an ENTRANCE curve, which made its return leg read
   differently from its outbound leg — the one easing here that
   was wrong on its own terms, not merely inconsistent.
   Applied at source on .df-obj::before above, not re-declared here: a
   single-selector value belongs with its keyframes. */

/* ---- ROLE 5: a logo mark's resting ink ----------------------
   figures.css moved both loops to grayscale(1) brightness(0) at
   .72 on 02.08 ("make it as sleek and smooth as the Trusted by
   B2B Tech & SaaS teams logos"). The plate tiles on /resources/
   were left on the superseded brightness(.55) at opacity 1 — and
   cogoport.png and settly.png appear in BOTH places on that one
   page, so the same two marks rendered in two different inks a
   single scroll apart. Fixed by adding the plate to the ONE rule
   that owns logo ink, in figures.css section 9 — not by writing
   the values a second time here. There is no logo-mark filter or
   opacity anywhere in this file any more; if you are looking for
   one, it is in figures.css. */

/* ---------------------------------------------------------------------
   CASE STUDY CLIENT MARK  (Sushil feedback 04.08, item 17)

   "For every case study where the name of the client is mentioned, put
   the latest logo of the client in the whitespace on the right hand
   side." That whitespace is real and it is structural: .section-head is
   capped at 720px inside a container that is far wider, so the right of
   every case lede on /work/ is empty by construction.

   .case-lede is a WRAPPER, not a restyle. The .section-head inside it
   keeps its own measure and type scale untouched; the only thing taken
   off it is its bottom margin, which moves to the wrapper so the
   vertical rhythm below the lede is unchanged to the pixel.

   The mark's ink is NOT set here. It rides the selector list in
   figures.css section 9, the one place on the site that owns a logo
   mark's filter and opacity - the same reason the /resources/ plate was
   moved there rather than given a copy of the values.
   --------------------------------------------------------------------- */
.case-lede {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-8);
  margin-bottom: var(--space-12);
}
.case-lede > .section-head { margin-bottom: 0; }

/* White plate, as on the /resources/ tiles: several marks still ship with
   a baked-in white patch, which reads as a rectangle on any tinted field.
   White hides it, and it works on both bands this page alternates between
   (sec-gray and sec-white), where the border carries the edge. */
.case-mark {
  justify-self: end;
  display: grid;
  place-items: center;
  min-width: 168px;
  padding: var(--space-5) var(--space-6);
  background: #FFFFFF;
  border: 1px solid var(--color-border);
  border-radius: 14px;
}
/* v4 item 9 - "make the existing logos on this page a bit bigger. After
   making them bigger, ensure All the logo should be of same size."

   The old rule was `height: 40px; width: auto; max-width: 148px`, which is
   a height-driven box for a tall mark and a width-CAPPED box for a wide one,
   so no two case marks were ever the same size: leapest measured 111 x 40 and
   cogoport 148 x 40, and because the plate shrink-wraps its mark the two WHITE
   PLATES came out 168px and 198px wide. That difference is what reads on the
   page, and it is the same defect he ruled on for the client strip.

   So this now uses the SAME box the client strip uses - the one he already
   signed off - rather than inventing a third size: --logo-box-w x --logo-box-h
   (150 x 46) with object-fit: contain, on the same --logo-unit so the case
   marks and the strip shrink together at 860 and 420. Equal box = equal size,
   and the plates come out identical because the box no longer varies.

   Bigger, and by how much: leapest 111 x 40 -> 128 x 46, cogoport's drawn ink
   30.5px tall -> 46px. Nothing upscales that did not before - the binding mark
   is leapest.png at 250 x 90, which at a 46px box needs 92px at DPR 2 against
   90px native, i.e. 1.02x. Raising the box any further breaks that, which is
   why this reuses 46 rather than the partner strip's 52. */
.case-mark img {
  width:  calc(var(--logo-box-w) * var(--logo-unit, 1px));
  height: calc(var(--logo-box-h) * var(--logo-unit, 1px));
  max-width: none;
  max-height: none;
  object-fit: contain;
}

/* v4 item 10 - the case summary row on /work/ went from four cards to five when
   Peddler was published, and five cards in a two-column grid leave the last one
   alone with a bare half-row beside it. That orphan cell is precisely what he
   objected to in item 11 on /about/, so adding a case must not reintroduce it
   here. Same fix the site already uses for .figgrid and .figcheck
   (figures.css, "ODD COUNTS IN THE TWO-COLUMN SETS"): span the leftover cell
   across the row. Even counts are untouched, and at one column this is a no-op,
   so the rule costs nothing if a sixth case is added later. `.case-card` inside
   a `.grid-2` exists on /work/ and nowhere else, so the scope is one row. */
.grid-2 > .case-card:last-child:nth-child(odd) { grid-column: 1 / -1; }

/* Below 860 the lede is already the full container, so there is no
   whitespace to sit in: the mark drops under the copy and goes left,
   where it reads as a caption of the case rather than a floating tile. */
@media (max-width: 860px) {
  .case-lede { grid-template-columns: 1fr; gap: var(--space-6); }
  .case-mark { justify-self: start; min-width: 0; padding: var(--space-4) var(--space-5); --logo-unit: 0.92px; }
}
@media (max-width: 420px) {
  .case-mark { --logo-unit: 0.82px; }
}

/* ============================================================
   v3 — HORIZONTAL STEP CARDS  (Sushil final feedback, items 13 + 19)
   ------------------------------------------------------------
   .msys is shared by FOUR lists: /services/revops-systems/ (3 items),
   /about/ (5 items) and /approach/ TWICE (4 + 4 items). Only the first
   two were asked to go horizontal, so this is a scoped .msys--row
   modifier and NOT an edit to the base .msys rules. /approach/ keeps
   the vertical rail exactly as it is.

   Reflow, stated: 3-up at >=1024px, 2-up at 640-1023px, and below
   640px it falls back to the shared vertical rail unchanged, which is
   the treatment already shipping at 390px. Nothing is ever squeezed
   into a narrow column on a phone.

   The vertical rail is a column device, so in row mode it is replaced
   by a horizontal one: each card carries a 3px orange rule along its
   top edge, the numbered badge straddles that rule, and a matching
   3px segment bridges the gutter to the next card in the SAME row.
   The bridge is suppressed on the last card of each row (:nth-child)
   and on the last card overall, so it never points into empty space.
   Palette is unchanged: --color-primary / --color-primary-soft /
   --color-border only.
   ============================================================ */
@media (min-width: 640px) {
  /* v4 feedback item 5: "make the boxes look uniform, right now the middle box
     is taller". align-items was start, which was the right call while /about/
     also used this modifier, because /about/'s card 5 carries an extra "Where
     it sits today" block and stretching left card 4 with a ~200px hole.
     v4 item 11 takes /about/ off .msys--row, so the modifier now belongs to
     /services/revops-systems/ alone, where the three cards differ only by
     bullet count (msys-list 166/248/191, cards 652/684/652 measured at 1440).
     Stretch is therefore safe and is what he asked for: every card in a row
     takes the row's height, so the three boxes are uniform. The copy is
     verbatim and untouched - the boxes grow, the text does not shrink. */
  .msys--row { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
  .msys--row .msys-item {
    display: flex; flex-direction: column;
    position: relative;
    padding-top: 24px;                 /* room for the badge over the rule */
  }
  /* the shared vertical rail has no meaning in a row */
  .msys--row .msys-item:not(:last-child)::before { content: none; }
  .msys--row .msys-n {
    position: absolute; top: 3px; left: var(--space-6);
    width: 44px; height: 44px; font-size: 1.0625rem;
    box-shadow: 0 0 0 5px var(--color-primary-soft);
  }
  .msys--row .msys-card {
    flex: 1 1 auto;
    border-top: 3px solid var(--color-primary);
    padding-top: calc(var(--space-6) + 16px);
  }
  /* horizontal flow bridge, drawn in the gutter, level with the card rule */
  .msys--row .msys-item::after {
    content: ""; position: absolute;
    top: 24px; left: 100%; width: var(--space-6); height: 3px;
    background: linear-gradient(90deg, var(--color-primary), var(--color-primary-soft));
  }
  .msys--row .msys-item:nth-child(2n)::after,
  .msys--row .msys-item:last-child::after { content: none; }
}
@media (min-width: 1024px) {
  .msys--row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .msys--row .msys-item:nth-child(2n)::after { content: ""; }
  .msys--row .msys-item:nth-child(3n)::after,
  .msys--row .msys-item:last-child::after { content: none; }
}

/* ---------- EU Alternatives — the seventh top-level nav item -------------
   The header carries six nav items plus the 212px "Book a RevOps Diagnostic"
   button, and it collapses to the hamburger at 940px. Measured at 940px the
   six items leave 22px of slack, so a seventh item of ANY label overflows the
   header and puts a horizontal scrollbar on all 48 pages between 941px and
   about 1100px. The alternative — moving the hamburger breakpoint up — also
   moves the point at which .header-cta .btn is hidden while .mobile-cta-bar
   still appears only at 940px, which would leave a 200px-wide band with no
   visible primary CTA at all. Neither is acceptable collateral for one link.

   So the item is shown exactly where it fits: at 1140px and above. Below
   940px it is in the mobile drawer like every other item, and at every width
   it is in the footer's Resources column. Nothing else about the header
   changes: no breakpoint moved, no spacing retuned, no other item touched. */
.nav-item--eu > .nav-link { white-space: nowrap; }
@media (min-width: 941px) and (max-width: 1139px) {
  .nav-item--eu { display: none; }
}
