/*
 * Team Wheel — site CSS
 *
 * Canonical source: docs/brief/wordpress_design_tokens.css
 * Rules below open with that file verbatim; only Elementor-specific
 * widget overrides are added beneath it.
 *
 * Class names MUST match the token file exactly. No !important.
 * If a style doesn't apply: fix the Elementor container settings,
 * not this file. See CLAUDE.md for the background gradient rule.
 */

/* ============================================================
   TOKEN FILE — verbatim from docs/brief/wordpress_design_tokens.css
   ============================================================ */

:root {
  --tw-blue: #009fe3;
  --tw-green: #3aaa35;
  --tw-yellow: #f9b233;
  --tw-purple: #662483;
  --tw-orange: #e94e1b;
  --tw-white: #ffffff;
  --tw-offwhite: #f8f5fb;
  --tw-lilac: #f1e9f8;
  /* Deviation from the token file, which specifies #17131d here and a separate
     --tw-black: #000000 that nothing ever consumed. Both the client's Visual Identity
     Guidelines ("Use Deep Black for typography") and the brief's own global design rules
     (docs/brief/wordpress_build_notes.md:110 — "Deep Black remains the main text colour")
     name #000000 as the body text colour, so the near-black was the odd one out. Folding
     the two: --tw-text now carries Deep Black and --tw-black is gone rather than left as
     an exact duplicate of it. --tw-muted and the tint ramp below are untouched — those
     are build-spec, not brand. */
  --tw-text: #000000;
  --tw-muted: #625c6d;
  /* On-dark text ramp — three steps for copy over the purple heros/bands, folding
     ~10 near-identical white opacities (0.5–0.9) that had accreted per element into
     one governed scale. Headings/buttons stay solid --tw-white; these are body,
     secondary/meta, and footer-dim respectively. */
  --tw-on-dark:   rgba(255, 255, 255, 0.88);
  --tw-on-dark-2: rgba(255, 255, 255, 0.78);
  --tw-on-dark-3: rgba(255, 255, 255, 0.66);   /* footer dim — matches the footer nav-menu's inline colour so all footer text is one value */
  --tw-border: #ece4f3;
  /* Not in token file — the card/tile hover border. Deliberately deeper than
     --tw-border (the resting edge) so the hover lift reads; folds the identical
     literals that had accreted on .tw-card and .tw-svc-card. */
  --tw-border-hover: #d9c7e8;
  --tw-shadow: 0 18px 48px rgba(34, 15, 47, 0.10);
  --tw-radius-lg: 28px;
  --tw-radius-md: 20px;
  --tw-radius-sm: 14px;

  /* ---- TYPE SCALE (single governing tier) ------------------------------
     Not in token file — the one coherent typographic scale the whole site
     draws from. Every text role references a token below rather than a
     literal, so hierarchy stays consistent by construction. Sizes: fluid
     clamp() for the large display/heading/lead roles (rescale with the
     viewport), static rem for everything at or below body. The three big
     heading caps (60/56/42) and the stat (46) match the prior desktop sizes;
     the consolidation is below h2, where ~15 near-duplicate values are folded
     onto h3 / h4 / lead / body / body-sm / caption / badge. */
  --tw-fs-display : clamp(36px, 24px + 2.5vw, 60px);   /* hero H1                       */
  --tw-fs-h1      : clamp(36px, 21px + 2.45vw, 56px);  /* 2-column / interior hero H1   */
  --tw-fs-h2      : clamp(28px, 20px + 1.55vw, 42px);  /* section heading               */
  --tw-fs-h3      : 1.5rem;      /* 24px — subsection heading (a real step below h2)    */
  --tw-fs-h4      : 1.125rem;    /* 18px — card / small heading                         */
  --tw-fs-lead    : clamp(17px, 1.4vw, 20px);          /* hero + intro lead paragraph   */
  --tw-fs-body    : 1rem;        /* 16px — base body                                    */
  --tw-fs-body-sm : 0.9375rem;   /* 15px — dense body, nav, card copy                   */
  --tw-fs-eyebrow : 1.125rem;    /* 18px — section-header eyebrow (.tw-sec-top) only    */
  --tw-fs-caption : 0.8125rem;   /* 13px — captions, meta, card kickers, small labels   */
  --tw-fs-badge   : 0.75rem;     /* 12px — uppercase badges / tags / tiny labels        */
  --tw-fs-stat    : 2.875rem;    /* 46px — large proof metric                           */

  /* Weight — one named ramp, replaces ad-hoc 400/500/600/700.
     700 is the ceiling: the client's Visual Identity Guidelines recommend Maven Pro
     at 600/700 and Open Sans at 400/600, and rule out "excessively light or condensed"
     cuts. The old --tw-fw-heavy (800) rung is therefore gone rather than redefined to
     700 — two names for one value would just invite a future edit to split them apart
     again and drift back off-guideline. */
  --tw-fw-regular  : 400;
  --tw-fw-medium   : 500;
  --tw-fw-semibold : 600;
  --tw-fw-bold     : 700;

  /* Line-height — role-based, replaces ~10 scattered values (0.97→1.55) */
  --tw-lh-display : 0.98;   /* display / hero H1              */
  --tw-lh-tight   : 1.05;   /* section headings              */
  --tw-lh-snug    : 1.2;    /* subsection / card titles      */
  --tw-lh-normal  : 1.5;    /* body copy                     */
  --tw-lh-relaxed : 1.55;   /* long-form reading             */
}

body {
  color: var(--tw-text);
  background: #ffffff;
}

/* Base line-height must inherit as a UNITLESS multiplier, not the Kit's fixed
   1.5em (= 24px at the 16px body size). Elementor's Kit emits body
   `line-height:1.5em` on `.elementor-kit-4` (0,1,0); a *fixed length* inherits
   verbatim, so every label/pill/tag that doesn't set its own line-height was
   rendering a 25.5px line box regardless of its font-size — a 12px pill at
   ratio 2.13, a 13px meta label at 1.96, an 18px month heading at 1.42. That
   single inherited length was the largest source of line-height drift in the
   type audit (the 2.13/1.96/1.7/1.42 spray across every page). Elementor's
   line-height control offers only px/em/rem — no unitless — so per the
   CLAUDE.md "no Kit control" rule this base decision lives here (tier 3).
   body.tw-site (0,1,1) beats the Kit's (0,1,0); descendants now inherit
   the multiplier and recompute line-height against their own size. */
body.tw-site { line-height: var(--tw-lh-normal); }

/* Accessible skip-link focus treatment (child-CSS tier: accessible focus treatments).
   The WordPress skip-link is visually hidden until focused; Hello Elementor's a{color:#c36}
   reset would show it pink. Give it the brand focus style instead. (0,3,0) beats the reset. */
.skip-link.screen-reader-text:focus {
  color: #ffffff;
  background: var(--tw-purple);
}

/* Base link colour — the ONE global statement that neutralises Hello Elementor's
   a{color:#c36} pink reset with the brand colour, so any unstyled link is on-brand and no
   per-location patching is needed. (This install's Kit has no link-colour control — only
   link typography — so the base belongs here in the child stylesheet, tier 3.) (0,1,1)
   beats the reset's (0,0,1); components that set their own link colour at higher
   specificity still win — buttons (.e-button-base, 0,3,0 → white), header nav and footer
   links (below), and links inside headings (next rule). Those are deliberate contextual
   choices, not pink fixes. Must sit BEFORE the heading rule below: both are (0,1,1), so the
   later one wins the tie, and headings must keep `inherit` (white in dark heros/bands). */
.elementor a { color: var(--tw-purple); }
.elementor a:hover { color: #4b1661; } /* TW_PURPLE_D — darken on hover */

/* Hello Elementor reset.css sets a { color: #c36 } globally, including inside heading widgets.
   Elementor's title_color widget setting applies to .elementor-heading-title but the <a> inside
   does not inherit it because Hello's reset is an explicit value, not a cascade default.
   Fix: force <a> inside headings to inherit the heading's colour. One rule, covers all headings. */
.elementor-heading-title a { color: inherit; }

/* Not in token file — sticky header + pinned announcement bar, elevated on scroll.
   The header's location wrapper is the outermost header element on every page, so
   pinning it here keeps the nav in view on scroll without touching the scaffold.
   Opaque background (the header container sets none — it inherited the white body) so
   page content doesn't show through underneath; z-index above page content
   (heroes/cards top out ~z-index:3); hairline bottom border reads as a subtle shelf.

   Deliberately NO resize on scroll. A shrinking logo/button/padding animates several
   things at once in the reader's periphery, which pulls the eye back up to the header
   and away from the content — the opposite of what a sticky nav is for. Instead the
   header stays a constant size and merely fades in a soft shadow once scrolled, so it
   reads as "floating above" the content with nothing actually moving (the
   Stripe/Linear/GitHub pattern). header-scroll.js toggles .tw-scrolled on <body>.

   The homepage announcement bar (.tw-announce — page content directly below the
   header) is pinned at the header's height (--tw-header-h, measured by the same JS) so
   it sits flush under the header. The shadow is applied to BOTH so it always lands
   below the *bottom* of the pinned group: the header on interior pages, the
   announcement bar on the homepage (the header's own shadow there falls on the purple
   bar and is negligible). Header stays z-100 above the bar so nav dropdowns render over
   it. --tw-header-h's default is only the pre-JS first-paint value; JS then owns it.

   Gentle scroll-proportional padding shrink: as you scroll through the top of the page,
   the header row and the announcement bar tighten their *vertical padding* a little —
   the contents (logo, nav, button, message) keep their size; only the whitespace closes
   up. It's driven continuously by --tw-shrink (0 at the top → 1 once past ~220px, set by
   header-scroll.js) rather than a CSS transition, so it eases with the scroll instead of
   snapping. --tw-header-h tracks the resulting height each frame so the announce bar
   stays flush. */
:root {
  --tw-header-h: 116px;  /* header height; owned by header-scroll.js for the announce pin */
  --tw-shrink: 0;        /* 0 at top → 1 past the shrink range; set by header-scroll.js */
  --tw-logo-h: 88px;     /* header logo height; drives the bar height and the nav's rise */
  /* The client's lockup is not vertically symmetric: the dot swirl is, but the
     "teamwheel" wordmark sits high in the box — its ink centre is 41.25% down, not
     50% (measured off the rendered PNG). So centring the logo box leaves the
     wordmark ~9% of the logo height above the nav's centreline, and the two read as
     misaligned. Raise the nav by that difference to sit the nav text on the same
     line as the wordmark. Derived from --tw-logo-h so it stays correct at any size,
     including the mobile override below. */
  --tw-logo-word-rise: calc(var(--tw-logo-h) * 0.0875);

  /* Nav item type + box. Both the rest and :hover/.highlighted rules below must
     paint an identical box or the item jumps when its dropdown opens (see there),
     so the values live here once rather than being repeated by hand in both.
     16px/medium (up from 15px/regular) to carry the taller bar the client's
     full-size logo needs — at 15px the nav read as lost against an 88px logo. */
  --tw-nav-fs: var(--tw-fs-body);
  --tw-nav-fw: var(--tw-fw-medium);
  --tw-nav-pad: 10px 14px;
}

.elementor-location-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--tw-white);
  border-bottom: 1px solid var(--tw-border);
  transition: box-shadow 0.25s ease;
}
.tw-announce {
  transition: box-shadow 0.25s ease;
}
.tw-scrolled .elementor-location-header,
.tw-scrolled .tw-announce {
  box-shadow: 0 10px 30px rgba(34, 15, 47, 0.16);
}
/* Padding-only shrink (contents unchanged). The boxed row carries its padding on the
   inner wrapper (.e-con-inner); only top/bottom shrink — horizontal padding stays with
   Elementor. Scoped under .tw-site (0,4,0) to beat Elementor's generated
   container padding, per the specificity rule in CLAUDE.md. */
.tw-site .elementor-location-header .tw-header-row > .e-con-inner {
  padding-top: calc(14px - 8px * var(--tw-shrink));
  padding-bottom: calc(14px - 8px * var(--tw-shrink));
}

/* Per-location link colour overrides — Hello Elementor reset.css sets a { color: #c36 } (pink).
   Specificity (0,2,0) beats the reset's (0,0,1) regardless of load order. */

/* Header nav — dark text, purple on active/hover */
.elementor-location-header .elementor-item {
  color: var(--tw-text);
}
.elementor-location-header .elementor-item:hover,
.elementor-location-header .elementor-item:focus,
.elementor-location-header .elementor-item-active,
.elementor-location-header .elementor-item.highlighted {
  color: var(--tw-purple);
}
.elementor-location-header .elementor-sub-item {
  color: var(--tw-text);
}
.elementor-location-header .elementor-sub-item:hover,
.elementor-location-header .elementor-sub-item:focus {
  color: var(--tw-purple);
}

/* Not in token file — site logo (V4 e-image → the "teamwheel" lockup). e-image
   renders the class on the bare <img> (no wrapper), so .tw-site-logo IS the img.
   The compound .tw-site-logo.e-image-base under .tw-site reaches (0,4,0) to
   beat Elementor's atomic image baseline (.tw-site .elementor .e-image-base,
   0,3,0), which otherwise renders the img full-width. height sizes the lockup down
   from its 240px raster; width:auto + max-width:none preserve its aspect. Same
   compound-selector pattern
   as the button variants (see CLAUDE.md).

   --tw-logo-h (88px, was 50px, and the bar grew 78px -> 116px to hold it) because
   the client asked for their logo exactly as-is. Their arrangement sets the wordmark
   ink at 1/4.37 of the dot height, where the retired "Option C" lockup had blown it
   up to 1/1.93 — so the same wordmark needs ~1.6x the render height to read at the
   same size. At 88px the wordmark ink is ~20px against the 16px nav. Don't shrink
   this without re-reading scripts/build-brand-lockup.py's ARRANGEMENT note — the two
   arrangements are not interchangeable at equal height. */
.tw-site .elementor-location-header .tw-site-logo.e-image-base {
  height: var(--tw-logo-h);
  width: auto;
  max-width: none;
  display: block;
}
/* The e-image link wrapper (.e-image-link-base) is the header row's flex child —
   keep it from shrinking so the logo holds its width. inline-block (not
   Elementor's display:flex base style): as a flex item the img's width:auto
   resolves to the file's natural width instead of the height-scaled ratio —
   invisible at the natural height, but a smaller mobile size kept the full
   natural width (distorted lockup) and pushed the burger onto a second row. */
.elementor-location-header .e-image-link-base {
  flex-shrink: 0;
  display: inline-block;
}
/* Smaller logo on phones so it shares a row with the burger toggle rather than
   pushing it onto a second line. 64px: at the lockup's natural 2.72 ratio that's
   ~174px wide — 174 + 136 burger group + 16 gap fits the 350px content box.
   Width, not height, is the binding constraint here: 72px would still fit the
   row (196px) but leaves no margin, so 64px keeps the old comfort. Overriding the
   var (not the height directly) keeps --tw-logo-word-rise in step. */
@media (max-width: 600px) {
  :root {
    --tw-logo-h: 64px;
  }
}

/* Not in token file — nav group (logo's sibling) — hug its content (nav + CTA) so the outer
   space-between places it hard against the right edge, CTA last. Without this it
   stretched full width and the button floated mid-row. */
.elementor-location-header .tw-header-navgroup {
  flex: 0 0 auto;
  width: auto;
  /* Sit the nav text on the wordmark's line — see --tw-logo-word-rise. A bottom
     margin, not transform: translateY(). The row centres its items' *margin* boxes,
     so 2x the rise as bottom margin raises the group by exactly the rise, while a
     transform would make this element a containing block for the absolutely-
     positioned dropdown panel — which the mobile rules below deliberately anchor to
     .elementor-location-header instead. The logo is still the tallest item, so this
     doesn't change the bar's height. */
  margin-bottom: calc(2 * var(--tw-logo-word-rise));
}

/* ------------------------------------------------------------------
   Header nav — APPEARANCE ONLY. The native nav-menu widget (SmartMenus)
   owns behaviour: show/hide on hover, positioning, animation, ARIA, and the
   mobile burger. We never touch display/position here — that was the mistake
   that caused the earlier jump/no-animation. These rules only paint the
   widget's own markup, keyed to the real state classes SmartMenus sets
   (.highlighted + aria-expanded on the open parent). Tier 3 (child stylesheet)
   per CLAUDE.md: brand appearance the widget's Style controls can't express in
   this atomic-era Pro build (its dropdown Style controls aren't registered).
   ------------------------------------------------------------------ */

/* Top-level item — compact pill on hover and while its dropdown is open
   (.highlighted is SmartMenus' open-state class). The .elementor-nav-menu--main
   scope (0,3,0) is required to beat Elementor's generated item padding (0,2,0),
   which otherwise keeps the tall 13/20 padding. Size/weight/box come from the
   --tw-nav-* vars (see :root). The old note here — "15px keeps all seven items on
   one row, 17px wrapped the last" — no longer binds: that was seven items beside a
   275px-wide logo, and it's now six beside the client's 239px one. Measured at
   16px/medium there's ~180px of slack between the logo and the nav group, so this
   is not near wrapping; re-measure before assuming it is. */
.elementor-location-header .elementor-nav-menu--main .elementor-item {
  font-size: var(--tw-nav-fs);
  font-weight: var(--tw-nav-fw);
  padding: var(--tw-nav-pad);
  border-radius: 10px;
  /* transition:none makes the hover/open pill instant, per the design. Elementor
     sets transition:all on the item, which otherwise fades the background — this
     overrides it. (The dropdown panel fade is handled in header-nav.js.) */
  transition: none;
}
.elementor-location-header .elementor-nav-menu--main .elementor-item:hover,
.elementor-location-header .elementor-nav-menu--main .elementor-item.highlighted {
  background: var(--tw-offwhite);
  /* Repeat padding/size here so the item box is identical at rest and when
     open/hovered. Without this, Elementor's own :hover rule (same (0,3,0)
     specificity but later in source order) reinstates its 13/20 padding on open,
     growing the item and shoving its siblings — the "jump". This selector is
     (0,4,0), so it wins in every state and the box never changes. Same vars as the
     rest state above, so the two cannot drift apart. */
  font-size: var(--tw-nav-fs);
  font-weight: var(--tw-nav-fw);
  padding: var(--tw-nav-pad);
}

/* Not in token file — dropdown chevron — the design's own clean chevron (a symmetric
   stroke in a 0 0 24 24 box), injected as real markup by the walker in the child theme
   functions.php, exactly like the sketch. We use this instead of the widget's
   FontAwesome fa-angle-down (a filled glyph in a lopsided 0 0 320 512 viewBox that
   renders small and pivots off-centre). Inherits currentColor (turns purple on
   hover); rotates about its centre, keyed to SmartMenus' real .highlighted state.
   On mobile the injected chevron is hidden and the native .sub-arrow — the
   accordion toggle — is used instead. */
.elementor-location-header .tw-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin-left: 5px;
  vertical-align: -3px;
  transition: transform 0.2s ease;
}
.elementor-location-header .tw-chevron svg {
  width: 16px;
  height: 16px;
  display: block;
}
.elementor-location-header .elementor-item.highlighted .tw-chevron {
  transform: rotate(180deg);
}
@media (min-width: 1025px) {
  /* desktop: our chevron replaces the native FontAwesome sub-arrow */
  .elementor-location-header .menu-item-has-children > .elementor-item .sub-arrow {
    display: none;
  }
}
@media (max-width: 1024px) {
  /* mobile: native .sub-arrow is the accordion toggle; hide our visual chevron */
  .elementor-location-header .tw-chevron {
    display: none;
  }
}

/* Dropdown panel — white rounded card. background/border/radius/shadow/padding
   are NOT set inline by SmartMenus, so a scoped class rule wins without
   !important. translateY(10px) creates the item→panel gap the sketch has (its
   inline margin-top:0 can't be beaten without !important, but transform isn't set
   inline). Fade in/out and open/close timing are handled by SmartMenus' JS opts
   (child theme header-nav.js) — the widget's own show/hide pipeline, so both
   directions animate; a CSS animation can only fade in. */
.elementor-location-header .sub-menu.elementor-nav-menu--dropdown {
  background: #ffffff;
  border: 1px solid var(--tw-border);
  border-radius: 16px;
  box-shadow: 0 22px 50px rgba(27, 18, 39, 0.16);
  padding: 8px;
  transform: translateY(10px);
}
/* Hover bridge — a transparent block filling the 10px gap between the item and
   the panel, so moving the cursor from item to panel never crosses dead space
   (which would let SmartMenus' hideTimeout close it). It's part of the .sub-menu
   element, so entering it counts as entering the dropdown. */
.elementor-location-header .sub-menu.elementor-nav-menu--dropdown::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 0;
  right: 0;
  height: 10px;
}

/* Dropdown items — two-line rows (title + muted description). The <a> carries no
   inline width, so sizing it here sets the panel width. white-space:normal at
   (0,4,0) beats Elementor's per-item nowrap so long descriptions wrap instead of
   running off the side. */
.elementor-location-header .elementor-nav-menu--main .sub-menu .elementor-sub-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  width: 288px;
  white-space: normal;
  border-radius: 11px;
  padding: 10px 14px;
  font-size: var(--tw-fs-body-sm);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  /* Instant hover highlight — Elementor sets transition:all on the item, which
     otherwise makes the row's background/colour lag behind the cursor. */
  transition: none;
}
.elementor-location-header .elementor-nav-menu--main .sub-menu .elementor-sub-item:hover,
.elementor-location-header .elementor-nav-menu--main .sub-menu .elementor-sub-item.highlighted,
/* Current-page row. Elementor Pro's widget-nav-menu.min.css paints the active
   dropdown item background #3f444b (a dark grey band) at (0,2,0); without this it
   showed through as a heavy dark bar under the current page. Match the hover
   treatment so "you are here" reads as the same gentle offwhite highlight. */
.elementor-location-header .elementor-nav-menu--main .sub-menu .elementor-sub-item.elementor-item-active {
  background: var(--tw-offwhite);
  color: var(--tw-purple);
}

/* Not in token file — dropdown sub-item description (second line). Appended
   inside each sub-item <a> by the walker_nav_menu_start_el filter in the child
   theme functions.php (WordPress's standard mechanism for menu-item
   descriptions). Muted + regular weight, wraps freely; stays muted on hover so
   only the title turns purple. */
.elementor-location-header .tw-sub-desc {
  display: block;
  margin-top: 1px;
  font-size: var(--tw-fs-caption);
  font-weight: 400;
  line-height: 1.4;
  color: var(--tw-muted);
  white-space: normal;
  letter-spacing: normal;
}

/* Not in token file — header CTA. The global button default (padding 16px 28px) is
   still too large for the bar, but this is no longer the "compact" pill it was: it
   grew with the nav (13/24 at 16px, from 11/20 at 15px) so it holds its own against
   the client's full-size logo. Scoped under .tw-site (0,4,0) to beat the
   atomic e-button base padding, per the button-specificity rule in CLAUDE.md. */
.tw-site .elementor-location-header .tw-header-cta.e-button-base {
  padding: 13px 24px;
  font-size: var(--tw-nav-fs);
  white-space: nowrap;
}

/* ------------------------------------------------------------------
   Mobile burger dropdown (≤1024px — the widget's tablet breakpoint,
   class elementor-nav-menu--dropdown-tablet).

   Two things made the opened menu look broken: (1) the nav lives in the
   right-hand .tw-header-navgroup (flex:0 0 auto), so Elementor's dropdown
   panel inherited that group's ~116px width and crammed every item against
   the right edge; (2) the desktop dropdown-CARD rules above (fixed width,
   shadow, translateY, hover-bridge ::before) leaked onto the mobile
   accordion. This block turns it into a real full-width panel.
   ------------------------------------------------------------------ */
@media (max-width: 1024px) {
  /* Neutralise the positioned ancestors between the panel and the header so
     the absolute panel below anchors to .elementor-location-header (sticky,
     full viewport width) rather than the narrow nav widget/group. */
  .elementor-location-header .tw-header-row,
  .elementor-location-header .tw-header-navgroup,
  .elementor-location-header .elementor-widget-nav-menu {
    position: static;
  }

  /* Full-width panel dropping flush under the header. Header is z-100, so this
     renders above the z-99 announce bar; it overlays the page rather than
     shoving it down. Scrolls internally if it ever exceeds the viewport. */
  .elementor-location-header .elementor-nav-menu__container.elementor-nav-menu--dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    width: 100%;
    max-height: calc(100vh - var(--tw-header-h));
    overflow-y: auto;
    padding: 6px 0;
    background: #ffffff;
    border-top: 1px solid var(--tw-border);
    box-shadow: 0 24px 40px rgba(27, 18, 39, 0.14);
  }

  /* Full-width, left-aligned rows (override the desktop pill: 9/12 padding,
     15px, radius 10). Both the resting and hover/open (.highlighted) selectors
     are re-stated to beat the (0,4,0) desktop hover rule above. */
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-nav-menu,
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item {
    width: 100%;
  }
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item,
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item:hover,
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item.highlighted {
    justify-content: flex-start;
    text-align: left;
    font-size: var(--tw-fs-body);
    padding: 14px 24px;
    border-radius: 0;
  }
  /* Hairline between top-level rows. */
  .elementor-location-header .elementor-nav-menu--dropdown > .elementor-nav-menu > .menu-item:not(:last-child) {
    border-bottom: 1px solid var(--tw-border);
  }

  /* Accordion caret → its own right-aligned tap target (the row text stays a
     link; the caret toggles the sub-menu). */
  .elementor-location-header .elementor-nav-menu--dropdown .sub-arrow {
    margin-left: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    align-self: stretch;
    transition: transform 0.2s ease;
  }
  /* Flip the caret up while its section is open. */
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item.highlighted .sub-arrow,
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item[aria-expanded="true"] .sub-arrow {
    transform: rotate(180deg);
  }
  /* The caret's font-icon SVG ships with a hard-coded black fill; make it follow
     the row's text colour so it's white on the purple active band, dark
     otherwise. */
  .elementor-location-header .elementor-nav-menu--dropdown .sub-arrow svg,
  .elementor-location-header .elementor-nav-menu--dropdown .sub-arrow svg path {
    fill: currentColor;
  }

  /* Interactive + open states — kill Elementor Pro's dark #3f444b fill AND its
     purple text-recolour (widget-nav-menu.min.css) that otherwise flashes on
     tap/hover and turns the open "Services" row into a dark band / purple text.
     Covers hover, focus, tap-highlight (.highlighted) and the expanded parent,
     for both top-level items and dropdown sub-items. We keep the text the SAME
     --tw-text as the resting rows (only the background shifts to lilac) so the
     menu font colour is consistent in every state. Placed BEFORE the active rule
     below so the current page keeps its purple fill even while being tapped
     (equal specificity → later source wins). */
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item.highlighted,
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item[aria-expanded="true"],
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item:hover,
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item:focus,
  .elementor-location-header .elementor-nav-menu--dropdown .sub-menu .elementor-sub-item:hover,
  .elementor-location-header .elementor-nav-menu--dropdown .sub-menu .elementor-sub-item:focus {
    background: var(--tw-offwhite);
    color: var(--tw-text);
  }

  /* Current-page ("you are here") item — bold brand-purple fill + white text,
     replacing Elementor Pro's generic dark #3f444b default (which also left the
     sub-item's text near-black on the dark fill = unreadable). Covers both the
     top-level active item and the active dropdown sub-item. Placed AFTER the
     .highlighted rule above so that when the current page is also the expanded
     parent, this active treatment wins over the lilac open-state (equal
     specificity → later source order). */
  .elementor-location-header .elementor-nav-menu--dropdown .elementor-item.elementor-item-active,
  .elementor-location-header .elementor-nav-menu--dropdown .sub-menu .elementor-sub-item.elementor-item-active {
    background: var(--tw-purple);
    color: #ffffff;
  }

  /* Reset the desktop dropdown-CARD styling on the nested accordion. NOTE: the
     mobile dropdown's <ul> is .elementor-nav-menu (NOT --main), so the desktop
     sub-menu rules keyed to --main never reach it — but the un-scoped card rule
     (.elementor-location-header .sub-menu.elementor-nav-menu--dropdown) does.
     Match that same selector to flatten the card into a tinted, indented list:
     no floating card / fixed width / shadow / translateY / hover-bridge. */
  .elementor-location-header .sub-menu.elementor-nav-menu--dropdown {
    width: 100%;
    transform: none;
    box-shadow: none;
    border: 0;
    border-radius: 0;
    padding: 0;
    background: var(--tw-offwhite);
  }
  .elementor-location-header .sub-menu.elementor-nav-menu--dropdown::before {
    display: none;
  }
  .elementor-location-header .elementor-nav-menu--dropdown .sub-menu .elementor-sub-item {
    display: block;
    width: 100%;
    padding: 12px 24px 12px 40px;
    white-space: normal;
    /* Standard, accessible size (Elementor's default left the title ~11px). The
       40px indent, not size or weight, marks the hierarchy — so match the
       top-level rows' weight (400); 500 here read as "darker" beside them. */
    font-size: var(--tw-fs-body);
    font-weight: 400;
  }
  /* Drop the second-line description in the mobile accordion — on a phone the
     title alone is the wayfinding; the blurb was smaller-feeling than the title
     and cluttered the list. (Desktop dropdown keeps its title + description.) */
  .elementor-location-header .elementor-nav-menu--dropdown .tw-sub-desc {
    display: none;
  }

  /* Burger/close toggle — drop Elementor's default grey box for a clean brand
     glyph. */
  .elementor-location-header .elementor-menu-toggle {
    background: transparent;
    border: 0;
    padding: 6px;
    color: var(--tw-purple);
  }
}

/* ============================================================
   FOOTER — site-wide Theme Builder template (scripts/scaffold-footer.php)
   Not in token file — the token file defines no footer component. Dark ink band
   (TW_INK, matching the v2 refresh sketch) with a white brand lockup, sitting
   beneath the dark rounded .tw-cta-band that closes each page — the dark-under-dark
   stack is intended, not avoided.

   Typography, text/nav colours, nav spacing and the divider colour are set on the
   widgets in scaffold-footer.php (Elementor-native, editor-visible). Only what a
   widget can't express lives below — each rule notes why it can't be a widget setting.
   ============================================================ */

/* Full-bleed band + base text colour. .elementor-location-footer is the theme's
   location wrapper (not a widget/container in the scaffold), so its background can
   only be set here — no Elementor-generated background competes, so a plain rule wins. */
.elementor-location-footer {
  background: var(--tw-text);
  color: var(--tw-on-dark-3);
}

/* Columns row: brand col wider, the three link cols share the rest, wrapping on
   narrow. Neutralise the per-container flex CSS vars Elementor sets, then drive the
   layout with flex here — an Elementor container has no control for this flex-basis
   override, so it can't be a widget setting. */
.tw-footer__cols > .e-con {
  --flex-basis: auto;
  --width: auto;
  width: auto;
  flex: 1 1 160px;
}
.tw-footer__cols > .tw-footer__brand {
  flex: 1.7 1 240px;
}

/* All-white lockup sizing. V4 bare-<img> stretch gotcha: a flex column child with
   no width constraint stretches to fill; pin a height and opt out of stretch (same
   pattern as .tw-site-logo). The e-image size control can't express align-self.
   80px (was 40px) for the same reason the header grew — see .tw-site-logo. At the
   client's 2.72 ratio that's ~218px, inside this column's 240px basis.

   Must be the compound .tw-footer__logo.e-image-base under .tw-site (0,4,0)
   to beat our own atomic-image baseline .tw-site .elementor .e-image-base
   (0,3,0) further down this file, which sets height:auto/align-self:center. The bare
   .tw-footer__logo (0,1,0) this used to carry silently lost that fight: the height
   never applied and the logo just took the flex column's width. It looked plausible
   at the old 5.5 lockup ratio (~58px tall) so it went unnoticed until the 2.72
   lockup made it render 118px. Same trap as .tw-site-logo — see CLAUDE.md. */
.tw-site .elementor-location-footer .tw-footer__logo.e-image-base {
  height: 80px;
  width: auto;
  max-width: none;
  align-self: flex-start;
}

/* Nav :hover-padding pin. Elementor's own nav-menu default sets padding:13px 20px on
   .elementor-item at base (0,2,1) AND :hover/:focus (0,3,1). The widget's padding
   controls (set in the scaffold) win the base state at (0,3,0) but lose to that
   (0,3,1) :hover default — so a footer item with 4px 0 padding grows to 13px 20px on
   hover and shoves the column down ("links jump around on mouseover"). Pin it back at
   (0,4,0). This is the one nav property the widget's controls genuinely can't hold. */
.elementor-location-footer .elementor-nav-menu--main .elementor-item:hover,
.elementor-location-footer .elementor-nav-menu--main .elementor-item:focus,
.elementor-location-footer .elementor-nav-menu--main .elementor-item-active {
  padding: 4px 0;
}

/* Linked-text colour (get-in-touch + bottom-bar Privacy). The heading widget's
   title_color targets .elementor-heading-title, never the nested <a>, which Hello
   Elementor resets to a{color:#c36}. Size/weight are native (see $T_LINK/$T_LEGAL);
   only the <a> colour + hover — which no heading control can reach — live here. */
.elementor-location-footer .tw-footer__link .elementor-heading-title a,
.elementor-location-footer .tw-footer__legal .elementor-heading-title a {
  color: var(--tw-on-dark-3);
  text-decoration: none;
}
.elementor-location-footer .tw-footer__link .elementor-heading-title a:hover,
.elementor-location-footer .tw-footer__legal .elementor-heading-title a:hover {
  color: #ffffff;
}

/* Divider + bottom bar. */
.elementor-location-footer .tw-footer__rule .elementor-divider-separator {
  border-top-color: rgba(255, 255, 255, 0.12);
}
.tw-footer__bottom .elementor-heading-title {
  color: var(--tw-on-dark-3);
  font-size: var(--tw-fs-caption);
  font-weight: 400;
}

/* Token file specifies 96px; reduced to 72px 2026-07-05 (user feedback: section
   seams read too airy at 96+96). Keep in sync with TW_SECTION_PAD in _tokens.php. */
.tw-section {
  padding: 72px 0;
}

.tw-container {
  width: min(1180px, calc(100% - 48px));
  margin: 0 auto;
}

.tw-hero {
  /* Token file specifies a flat linear-gradient; radial overlays below are a kept
     visual enhancement layered on top of the same base gradient. */
  background:
    radial-gradient(circle at 82% 18%, rgba(249,178,51,0.18), transparent 22%),
    radial-gradient(circle at 88% 35%, rgba(0,159,227,0.14), transparent 24%),
    radial-gradient(circle at 8%  12%, rgba(255,255,255,0.10), transparent 26%),
    linear-gradient(135deg, #4b1661 0%, #662483 54%, #7d3697 100%);
  color: var(--tw-white);
  border-radius: 34px;
  box-shadow: var(--tw-shadow);
  overflow: hidden;
}

/* Not in token file — brand dot-swirl decoration (artifact-adopted 2026-07-05): the
   33-dot spiral from the brand lockup, white at low opacity, bleeding off the corner of
   the two dark gradient bands. A ::after background (child-CSS tier: pseudo-elements) so
   no scaffold markup changes; the shared image lives in a custom property. Elementor's
   lazy-load `background-image: none` kill matches real elements only, never
   pseudo-elements, so this survives on late containers like the CTA band. The bands get
   z-index: 0 to open a stacking context so the z-index: -1 dots paint above the band's
   own gradient but below every child (the glass card's backdrop blur picks them up). */
:root {
  --tw-dots-white: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1352 1263'%3E%3Cg fill='%23fff'%3E%3Ccircle cx='727' cy='98' r='97'/%3E%3Ccircle cx='163' cy='226' r='67'/%3E%3Ccircle cx='361' cy='236' r='60'/%3E%3Ccircle cx='850' cy='336' r='81'/%3E%3Ccircle cx='528' cy='311' r='54'/%3E%3Ccircle cx='1228' cy='427' r='123'/%3E%3Ccircle cx='645' cy='422' r='51'/%3E%3Ccircle cx='886' cy='556' r='68'/%3E%3Ccircle cx='687' cy='555' r='43'/%3E%3Ccircle cx='1178' cy='758' r='117'/%3E%3Ccircle cx='693' cy='682' r='35'/%3E%3Ccircle cx='837' cy='748' r='60'/%3E%3Ccircle cx='43' cy='755' r='42'/%3E%3Ccircle cx='188' cy='745' r='23'/%3E%3Ccircle cx='286' cy='753' r='16'/%3E%3Ccircle cx='640' cy='774' r='31'/%3E%3Ccircle cx='305' cy='804' r='18'/%3E%3Ccircle cx='202' cy='829' r='27'/%3E%3Ccircle cx='566' cy='842' r='27'/%3E%3Ccircle cx='348' cy='851' r='19'/%3E%3Ccircle cx='730' cy='897' r='54'/%3E%3Ccircle cx='83' cy='900' r='51'/%3E%3Ccircle cx='486' cy='873' r='23'/%3E%3Ccircle cx='410' cy='880' r='22'/%3E%3Ccircle cx='254' cy='916' r='31'/%3E%3Ccircle cx='1012' cy='1013' r='96'/%3E%3Ccircle cx='599' cy='990' r='51'/%3E%3Ccircle cx='334' cy='985' r='35'/%3E%3Ccircle cx='460' cy='1004' r='43'/%3E%3Ccircle cx='156' cy='1068' r='60'/%3E%3Ccircle cx='788' cy='1154' r='80'/%3E%3Ccircle cx='327' cy='1153' r='64'/%3E%3Ccircle cx='544' cy='1192' r='72'/%3E%3C/g%3E%3C/svg%3E");
}
.tw-hero,
.tw-cta-band {
  position: relative;
  overflow: hidden; /* .tw-hero already clips; the CTA band needs it for the -60px bleed */
  z-index: 0;
}
.tw-hero::after,
.tw-cta-band::after {
  content: "";
  position: absolute;
  z-index: -1;
  background: var(--tw-dots-white) no-repeat center / contain;
  aspect-ratio: 1352 / 1263;
  pointer-events: none;
}
.tw-hero::after {
  right: -70px;
  bottom: -90px;
  width: min(46%, 420px);
  opacity: 0.16;
}
.tw-cta-band::after {
  right: -60px;
  top: 50%;
  transform: translateY(-50%);
  width: 300px;
  opacity: 0.14;
}
@media (max-width: 900px) {
  .tw-hero::after { opacity: 0.12; }
}
@media (max-width: 820px) {
  .tw-cta-band::after { display: none; }
}

.tw-card {
  background: var(--tw-white);
  border: 1px solid var(--tw-border);
  border-radius: var(--tw-radius-lg);
  box-shadow: var(--tw-shadow);
  /* Standard card hover — a subtle lift + stronger shadow + lilac border. Promoted
     here from the homepage case cards (.tw-case-card) so EVERY .tw-card
     across the site shares one consistent hover treatment (user request 2026-07-06:
     "review what is already present on the homepage and standardise"). */
  transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s;
}
.tw-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 20px 44px rgba(27, 18, 39, 0.10);
  border-color: var(--tw-border-hover);
}

.tw-card--soft {
  background: var(--tw-offwhite);
}

/* =============================================================
   BUTTON SYSTEM
   Three layers — global default → named variant → contextual.

   Global default: Site Settings → Theme Style → Buttons sets
   background #662483, radius 999px, Maven Pro 700 14px. CSS baseline
   below adds transitions and ensures the default is correct even
   before Kit CSS loads in the editor.

   Named variants: class goes on the Button widget wrapper
   (Advanced → CSS Classes), selector targets .elementor-button inside.

   Contextual: class on a container, targets buttons within it.

   Specificity: Kit CSS and base atomic styles load as separate linked
   stylesheets after our inline block, so we scope under .tw-site
   to reach (0,3,0) and beat the conflicting (0,2,0) Elementor rules.

   Not in token file — .tw-site is this stylesheet's specificity ANCHOR, not a
   visual class: it sets nothing itself and exists only to add (0,1,0) so our
   rules out-weigh Elementor's generated ones. The child theme puts it on <body>
   unconditionally via body_class (see themes/teamwheel-child/functions.php).

   It replaced `.elementor-kit-4` on 2026-07-30. That was Elementor's own body
   class, and 4 is the Kit's auto-increment POST ID — true only of installs that
   happened to create it fourth. A build from empty put a wp_navigation post
   there first, the Kit became #5, and all 145 kit-scoped rules stopped matching
   at once: full-size logo, overflowing nav, unstyled buttons, on every page,
   while dev looked perfect. It was also precisely what CLAUDE.md's "never target
   Elementor's generated identifiers, they break on re-render" rule forbids.

   The swap is specificity-neutral — one class for one class — so no cascade
   behaviour changed. Do not reintroduce a selector containing a post ID; both
   check-standards.sh (source) and check-rendered.mjs (a logo-size canary that
   catches the stylesheet silently not applying) now fail on it.
   ============================================================= */

/* ---------------------------------------------------------------
   BUTTON SYSTEM — classic (.elementor-button) + V4 (.e-button-base)
   ---------------------------------------------------------------
   Specificity note: our CSS is injected inline immediately after
   elementor-frontend-css, but the Kit (post-4.css) and atomic base
   styles (base-desktop.css) are separate LINKED stylesheets that
   load later. At equal specificity, later wins — so we must be more
   specific than the conflicting Elementor rules:
     Kit:   .elementor-kit-4 .elementor-button     (0,2,0)
     Base:  .elementor .e-button-base              (0,2,0)
   We scope everything under .tw-site to reach (0,3,0).

   Variant class placement differs by generation:
     Classic scaffold _css_classes → widget wrapper → descendant
     V4 scaffold classes prop      → <a> element   → compound
     Editor CSS Classes field      → widget wrapper → descendant
   ---------------------------------------------------------------- */

/* Classic baseline — keeps editor/preview correct before Kit applies.
   The family mirrors the Kit's button typography deliberately (that's this rule's whole
   job) and must be changed with it — see setup-env.php's button_typography_font_family. */
.elementor-button {
  background-color: var(--tw-purple);
  border-radius: 999px;
  font-family: "Maven Pro", sans-serif;
  font-weight: 700;
  color: #ffffff;
  transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
}

/* Classic variants — (0,3,0) beats Kit's (0,2,0) */
.tw-site .tw-btn-primary .elementor-button {
  background: var(--tw-white);
  color: var(--tw-purple);
  border: none;
  padding: 16px 28px;
}
.tw-site .tw-btn-primary .elementor-button:hover {
  background: rgba(255,255,255,0.9);
}

.tw-site .tw-btn-secondary .elementor-button {
  background: rgba(255,255,255,0.12);
  color: var(--tw-white);
  border: 1px solid rgba(255,255,255,0.30);
  padding: 16px 28px;
}
.tw-site .tw-btn-secondary .elementor-button:hover {
  background: rgba(255,255,255,0.20);
}

/* V4 baseline — (0,3,0) beats base-desktop.css .elementor .e-button-base (0,2,0).
   V4 atomic buttons render as bare <a class="e-button-base"> directly inside containers —
   no elementor-widget-e-button wrapper div exists in the DOM. Scope via .elementor instead. */
.tw-site .elementor .e-button-base {
  background-color: var(--tw-purple);
  border-radius: 999px;
  /* Mirrors the Kit's button typography — see the classic baseline above. */
  font-family: "Maven Pro", sans-serif;
  font-weight: 700;
  color: #ffffff;
  border: none;
  padding: 16px 28px;
  transition: background-color 150ms ease, color 150ms ease;
  /* inline-flex centring: as a flex item in a button row the anchor is stretched
     to the tallest sibling (the ghost button is 2px taller from its 1px border),
     and Elementor's inline-block leaves the single line box top-anchored in the
     stretched content box — the label rides ~2px high. Flex centring keeps the
     text centred whatever height the button is stretched to. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* V4 variants — descendant (editor CSS Classes) + compound (scaffold classes prop). Both (0,3,0). */
.tw-site .tw-btn-primary .e-button-base,
.tw-site .tw-btn-primary.e-button-base {
  background: var(--tw-white);
  color: var(--tw-purple);
  padding: 16px 28px;
}
.tw-site .tw-btn-primary .e-button-base:hover,
.tw-site .tw-btn-primary.e-button-base:hover { background: rgba(255,255,255,0.9); }

.tw-site .tw-btn-secondary .e-button-base,
.tw-site .tw-btn-secondary.e-button-base {
  background: rgba(255,255,255,0.12);
  color: var(--tw-white);
  border: 1px solid rgba(255,255,255,0.30);
  padding: 16px 28px;
}
.tw-site .tw-btn-secondary .e-button-base:hover,
.tw-site .tw-btn-secondary.e-button-base:hover { background: rgba(255,255,255,0.20); }

/* V4 image baseline — atomic-image's only base style is `display: block` (no width
   constraint at all). Classic image widgets got implicit natural sizing because the
   *wrapper div* (not the <img>) was the flex child, and the img inside was centred at
   its intrinsic size via text-align. V4 images render bare, so the <img> itself is now
   the direct flex child — width:auto does NOT opt out of a column flex container's
   default align-items:stretch (stretch overrides any cross-axis size that resolves to
   auto, and width:auto is that default), so align-self is required to restore natural,
   centred sizing. .tw-image-fill opts back into full-width stretch where that's wanted
   (e.g. the founder photo). */
.tw-site .elementor .e-image-base {
  max-width: 100%;
  height: auto;
  align-self: center;
}

/* .tw-eyebrow — legacy eyebrow used by the classic single-case-study Theme Builder
   template (the V4 pages use .tw-type-eyebrow). Same values as the V4 eyebrow so the
   two look identical. (.tw-heading-xl/.tw-heading-lg removed 2026-07-08 — dead code,
   no scaffold references anywhere.) */
.tw-eyebrow {
  font-size: var(--tw-fs-caption);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: var(--tw-fw-bold);
  line-height: 1.45;   /* match .tw-type-eyebrow exactly (was inheriting a different lh) */
  color: var(--tw-purple);
}

.tw-text-muted {
  color: var(--tw-muted);
}

.tw-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border-radius: 999px;
  background: var(--tw-lilac);
  color: var(--tw-purple);
  font-size: var(--tw-fs-badge);
  font-weight: 700;
}

/* Not in token file — small pill variant, hero card platform chips */
.tw-pill--sm {
  font-size: var(--tw-fs-badge);
  padding: 5px 10px;
}

/* Not in token file — pill row wrapper, flex container for a group of pills */
.tw-pill-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 7px 5px;
}

.tw-cta-band {
  background: linear-gradient(135deg, #4b1661 0%, #662483 100%);
  color: var(--tw-white);
  border-radius: 34px;
  padding: 34px;
  /* Cap the band's content area at the hero's boxed content width — 1140px, the
     e-con-inner width Elementor's default --content-width yields — so the two bands'
     text edges align (user feedback 2026-07-05: text/buttons stretched to the band
     edges as the band grows). Padding percentages resolve against the PARENT's content
     box (the full-width component section minus its 24px side padding, i.e.
     viewport - 48px = P), so each arm maps one segment of the band-width curve to
     (band - 1140) / 2:
       band = P            (below a 1440 viewport) → pad = 50% - 570px
       band = 75% + 348px  (above, see max-width)  → pad = 37.5% - 396px
     min() picks whichever segment is active; 34px is the floor (the band's original
     uniform padding). The ≤768px media query below still overrides mobile wholesale. */
  padding-inline: max(34px, min(calc(50% - 570px), calc(37.5% - 396px)));
  /* Same large-screen growth curve as .e-con-full.tw-hero — the two rounded dark
     bands bookend the page and should hold the same width. The band's parent is a
     full-width section with 24px side padding (content = viewport - 48px), so the
     hero's viewport-based 75% + 312px becomes 75% + 348px of the parent:
     0.75(V - 48) + 348 = 0.75V + 312. Below a 1440px viewport the formula exceeds
     the parent width and the band simply fills it, exactly as before. */
  max-width: calc(75% + 348px);
  margin-inline: auto;
}

/* Hero overflow fix: Elementor renders .e-con-full at width: 100% (1280px on a 1280px
   viewport). Combined with margin: 24px left/right, the hero overflows by 24px on the
   right. Override width at (0,2,0) to account for the margin on both sides.
   On large screens the band keeps growing but only takes 75% of each extra viewport
   pixel — the white gutters absorb the other 25%, so it never reads edge-to-edge
   (user feedback 2026-07-05: a hard 1400px cap "stops growing entirely"; full-bleed
   was a wall of purple). The two min() arms cross exactly at a 1440px viewport
   (1440 - 48 = 0.75 × 1440 + 312), so nothing changes at 1440 and below.
   margin-inline: auto recentres the band (same (0,2,0) as Elementor's per-element
   24px margins, but design.css loads later; margin-top stays per-element).
   .tw-hero-card (Insights/Events masthead) shares this curve so the index headers
   hold the same inset width as the flagship hero — see the .tw-hero-card block. */
.e-con-full.tw-hero,
.e-con-full.tw-hero-card {
  width: min(calc(100% - 48px), calc(75% + 312px));
  margin-inline: auto;
}

/* Not in token file — hero CTA button row (both hero builders apply it via
   css_classes). The pills must never compress until their labels line-wrap into
   multi-line "eggs" (999px radius on a three-line button): at ANY width they keep
   natural size and the row wraps instead (flex_wrap: wrap in the hero builders),
   dropping the second pill to its own line at squeezed intermediate widths. */
.tw-hero-ctas .e-button-base {
  flex-shrink: 0;
  white-space: nowrap;
}
/* On phones, stack them full-width instead of side by side. Direct properties
   beat Elementor's per-element flex settings, which only set --flex-* variables
   consumed by frontend.css at (0,1,0). */
@media (max-width: 640px) {
  .tw-hero-ctas {
    width: 100%; /* the row otherwise shrink-wraps to the widest pill */
  }
  .tw-hero-ctas .e-button-base {
    flex: 1 1 100%;
  }
  /* Track the heroes' 12px margin_mobile (see tw_component_hero_* and
     tw_hero_card_masthead in _components.php) — the base rule above assumes the
     desktop 24px margins. */
  .e-con-full.tw-hero,
  .e-con-full.tw-hero-card {
    width: calc(100% - 24px);
  }
}

/* Not in token file — fluid hero inner padding (artifact-adopted 2026-07-05). Replaces
   the fixed per-element padding/padding_mobile pair on both hero builders' inner
   containers (tw_component_hero_2col / tw_component_hero_centred in _components.php) so
   the band's interior rescales continuously with the viewport instead of jumping at the
   mobile breakpoint. Same --padding-* variable mechanism as .tw-cmp-section above:
   (0,1,0) beats frontend.css's .e-con 10px defaults by load order; endpoints match the
   old fixed values (2col 64/24 → 40/16 mobile; centred 80/24 → 48/16 mobile). */
.tw-hero-inner {
  --padding-top: clamp(40px, 4.45vw, 64px);
  --padding-bottom: clamp(40px, 4.45vw, 64px);
  --padding-left: clamp(16px, 1.7vw, 24px);
  --padding-right: clamp(16px, 1.7vw, 24px);
}
.tw-hero-inner--tall {
  --padding-top: clamp(48px, 5.55vw, 80px);
  --padding-bottom: clamp(48px, 5.55vw, 80px);
}
/* Not in token file — left-justified flagship hero (tw_component_hero_centred, now
   left-aligned to match the Insights artifact 4407e60c). Cap the headline and lead width
   so they clear the right-hand brand dot-swirl (.tw-hero::after) instead of running under
   it. Desktop only — below 900px the swirl fades and the band needs the full width. */
.tw-hero-inner--left h1 { max-width: 20ch; }
.tw-hero-inner--left .tw-hero-lead { max-width: 56ch; }
@media (max-width: 900px) {
  .tw-hero-inner--left h1,
  .tw-hero-inner--left .tw-hero-lead { max-width: none; }
}

/* ============================================================
   SHORT-VIEWPORT GLOBAL ZOOM (landscape laptops / short windows)
   Not in token file. Every other responsive rule keys off WIDTH
   (vw type, %/calc max-widths), so a wide-but-short screen — e.g. a
   laptop at ~1435x660 once browser tabs/address bar + an OS taskbar
   eat the height — reads as "large" to all of them and nothing
   compresses; the homepage hero (header + announce + a 68px headline
   wrapping to ~4 lines + lead + CTA row + band padding) then overflows
   a 660px fold. Rather than hand-tune padding/type per element per
   breakpoint (fiddly, and the band stays full-width), scale the whole
   page down uniformly — exactly like the user zooming the browser
   out. `zoom` reflows content at the new scale (unlike transform:
   scale, which leaves layout width unchanged and needs overflow
   compensation), and because it shrinks the layout in BOTH axes the
   band gets narrower as well as shorter — reclaiming vertical space
   AND easing the too-wide feel on these screens. Interoperable in
   current Chrome/Edge/Safari and Firefox ≥126. Gated min-width:1024px
   so phones (tall+narrow, only short in landscape) never zoom; stepped
   so the primary CTA clears the fold across the common short-laptop
   heights without the type getting gratuitously small.
   `design.css` also loads in Elementor's editor preview iframe
   (functions.php → elementor/preview/enqueue_styles), whose <body>
   carries `elementor-editor-active` (verified in the running editor)
   — excluded here via :not() so the client's editing canvas is never
   zoomed, only the live frontend. The frontend <body> never has that
   class, so it always matches. Zoom sits on <body> (not :root) purely
   so this body-class guard is expressible in one plain selector. */
@media (min-width: 1024px) and (max-height: 820px) { body:not(.elementor-editor-active) { zoom: 0.90; } }
@media (min-width: 1024px) and (max-height: 740px) { body:not(.elementor-editor-active) { zoom: 0.82; } }
@media (min-width: 1024px) and (max-height: 680px) { body:not(.elementor-editor-active) { zoom: 0.76; } }
@media (min-width: 1024px) and (max-height: 620px) { body:not(.elementor-editor-active) { zoom: 0.70; } }

/* ============================================================
   PAGE INTRO — light header (.tw-page-intro)
   Not in token file. The purple .tw-hero band is reserved for flagship landing
   pages; every interior page (service/partner detail, Contact/Events/Quiz/
   Resources) uses this calmer, left-aligned, tinted-light header instead, so the
   site isn't a wall of near-identical purple blocks (user steer 2026-07-06).
   Built by tw_component_page_intro() in _components.php. The tint is set as an
   Elementor background control on the container; this owns the base border,
   the boxed inner's fluid vertical padding, and the lead/CTA styling. */
.tw-page-intro {
  border-bottom: 1px solid var(--tw-border);
}
/* Boxed inner (content_width: boxed → Kit-centred). Fluid vertical rhythm via the
   same --padding-* variable mechanism as .tw-hero-inner above: (0,1,0) beats
   frontend.css's .e-con 10px default by load order. Content is left-aligned
   (align-items: flex-start set as a container setting) — the deliberate contrast
   with the centred purple hero. */
.tw-page-intro__inner {
  --padding-top: clamp(44px, 5vw, 76px);
  --padding-bottom: clamp(40px, 4.4vw, 64px);
  --padding-left: clamp(16px, 1.7vw, 24px);
  --padding-right: clamp(16px, 1.7vw, 24px);
  text-align: left;
}
/* Muted lead, slightly larger than body, capped for readable line length. Applied
   directly on the V4 paragraph via _css_classes, so it's on the element — no
   wrapper/`p` descendant needed (see V4 bare-element note in CLAUDE.md). */
.tw-page-intro__lead {
  max-width: 660px;
  font-size: var(--tw-fs-lead);
  line-height: var(--tw-lh-relaxed);
  color: var(--tw-muted);
  text-wrap: pretty;   /* avoid a lone last-line word on the interior-page intro lead */
}
/* Partner-detail variant: the platform logo sits top-right, so the lead runs wide to
   flow under the logo, ending close to the right margin but with a small inset so it
   isn't edge-to-edge (the full 1140px container width read as stretching past the
   margins). (The extra gap below the headline is set via the inner container's flex-gap
   in tw_component_page_intro(), not here — the atomic paragraph base's margin:0 outranks
   a margin-top on this class.) Not in token file. */
.tw-page-intro__lead--wide {
  max-width: 1080px;
}
/* Optional right-hand platform logo (partner detail pages). Bare V4 e-image: its
   baseline .elementor .e-image-base (0,3,0) sets display:block with no size and, as a
   flex child, would stretch — so the compound .tw-page-intro__logo.e-image-base under
   .tw-site (0,4,0) beats it (same pattern as the site + partner-card logos).
   max-width keeps a wide wordmark inside its column; align-self:center centres it. */
.tw-site .tw-page-intro__logo.e-image-base {
  height: auto;
  max-height: 200px;
  width: auto;
  max-width: 100%;
  align-self: center;
  object-fit: contain;
}
/* Stack on mobile — the row wraps to a column (copy above, logo below); make both
   full width and left-align the logo. (0,2,0) via .e-flex beats Elementor's inline
   column widths, same mechanism as .tw-sec-top's mobile stack above. */
@media (max-width: 820px) {
  .tw-page-intro__row > .e-con { width: 100%; }
  .tw-site .tw-page-intro__logo.e-image-base {
    align-self: flex-start;
    max-height: 192px;
    margin-top: 4px;
  }
}

/* Elementor's .e-con-full.e-flex rule is (0,2,0) so beats .tw-cta-band (0,1,0).
   Match specificity with .e-flex.tw-cta-band to override flex-direction via load order.
   flex-direction/align-items/flex-wrap are per-element CSS vars orphaned by e-component ID remapping. */
.e-flex.tw-cta-band {
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;
  gap: 48px;
}

/* CTA band child columns: Elementor's base CSS sets width/flex-basis via CSS variables orphaned
   by e-component ID remapping. Override directly at (0,2,0) to beat .e-con (0,1,0).
   flex-basis: 0 on the text col makes it grow from zero, avoiding wrapping before the
   button col can fit on the same line. */
.tw-cta-band > .e-con {
  --flex-basis: 0;
  --width: auto;
  width: auto;
  flex-basis: 0;
}
.tw-cta-band > .e-con:first-child {
  flex: 1 1 0;
  min-width: 260px;
}
.tw-cta-band > .e-con:last-child {
  flex: 0 0 auto;
}

/* ============================================================
   E-COMPONENT RENDERING FIXES
   Elementor's e-component (Atomic Widget) remaps element IDs at render time,
   which orphans per-element-ID CSS rules. Two fixes needed:

   1. --display variable: Elementor sets `--display: flex` via per-element CSS.
      Remapped elements have no matching rule, so var(--display) is undefined
      and the container falls back to display: inline. Provide the flex fallback
      via the .e-flex class that Elementor reliably adds to all flex containers.

   2. Lazy-load override: Elementor sets `background-image: none` on 4th+
      containers before they scroll into view. Inside e-components the
      IntersectionObserver doesn't fire to add .e-lazyloaded. Adding
      e-no-lazyload to containers with CSS class backgrounds opts them out.
   ============================================================ */

.e-flex { --display: flex; }

/* Not in token file — section-level vertical rhythm for native e-components.
   A component root section's `padding` setting is emitted as per-element-ID CSS
   (`--padding-*` variables) that the e-component ID remapping orphans (same
   mechanism as fixes 1–2 above), so the awards/founder/newsletter/CTA root
   sections silently collapsed to Elementor's 10px container default. This class
   carries the section padding by name instead — same variable-level fix as
   .e-flex above: (0,1,0) beats frontend.css's .e-con defaults on load order,
   while Elementor's (0,2,0) per-element rules still win in plain page context
   (About / kitchen-sink inline placements). Vertical value tracks
   TW_SECTION_PAD. --close: tighter bottom for the CTA band that sits directly
   above the dark footer (the dark-under-dark stack is deliberate; a full 72px
   white gap there would dilute the close). */
.tw-cmp-section {
  --padding-top: 72px;
  --padding-bottom: 72px;
  --padding-left: 24px;
  --padding-right: 24px;
}
.tw-cmp-section--close {
  --padding-bottom: 28px;
}
/* Flush sides — a component band that needs to line up horizontally with an adjacent inline
   section that has no side padding (the homepage recognition strip beside the partner/client
   logo bands). The content's own gutter still keeps it off the edge. */
.tw-cmp-section--flush {
  --padding-left: 0;
  --padding-right: 0;
}

/* Not in token file — off-white section band, used by placeholder native components
   and alternating sections. CSS class so it applies inside e-component widgets where
   Elementor's per-element background_color setting can't reach remapped element IDs. */
.tw-band-bg {
  background: var(--tw-offwhite);
}

/* Not in token file — makes an e-image widget fill its flex column. V4 atomic image
   widgets have no settings-array width control (unlike classic image's custom width),
   so this replaces what used to be an inline width:100% widget setting. Overrides the
   .e-image-base baseline's align-self:center back to a full-width stretch. */
.tw-image-fill {
  width: 100%;
  align-self: stretch;
}

/* Hero lead paragraph — larger body text matching mockup (22px, slightly muted white).
   No `p` descendant requirement: classic puts this class on the widget wrapper (font
   properties cascade to the inner <p> via inheritance); V4 e-paragraph puts it directly
   on the bare <p> itself, so the class must also match standalone. */
.tw-hero-lead {
  /* Shares the --tw-fs-lead token with the interior-page intro lead — one lead size
     across heroes and page intros. Sits a step above card body without competing with
     the H1. */
  font-size: var(--tw-fs-lead);
  line-height: var(--tw-lh-normal);
  color: var(--tw-on-dark);
  text-wrap: pretty;   /* avoid a lone last-line word on the hero/intro lead — see .tw-sec-top__detail */
}

/* Heading colours on dark gradient backgrounds.
   post-{id}.css sets .elementor-widget-heading .elementor-heading-title { color: var(--e-global-color-primary) }
   at (0,2,0) and loads AFTER our inline style, so it wins at equal specificity.
   Fix: override the CSS variable at the container level — it cascades to all widget heading
   instances inside, and our inline style beats frontend.css at (0,1,0). */
.tw-hero,
.tw-cta-band {
  --e-global-color-primary: var(--tw-white);
}
/* Fallback for non-Elementor heading elements. */
.tw-hero h1, .tw-hero h2, .tw-hero h3,
.tw-hero h4, .tw-hero h5, .tw-hero h6,
.tw-cta-band h1, .tw-cta-band h2, .tw-cta-band h3,
.tw-cta-band h4, .tw-cta-band h5, .tw-cta-band h6 {
  color: var(--tw-white);
}
/* Hero eyebrow softened to the lead's muted tone (var(--tw-on-dark)) instead of the pure
   white the fallback above gives it, so the small label doesn't out-shout the lead beneath
   it. (0,2,0) beats the .tw-hero h4 fallback (0,1,1). */
.tw-hero .tw-type-eyebrow { color: var(--tw-on-dark); }

/* ============================================================
   V4 TYPOGRAPHY CLASSES
   Not in token file — these .tw-type-* classes are the V4 typography layer.
   Replace TW_TYPE_* settings-array values (scripts/_tokens.php) for V4 atomic
   heading/paragraph widgets — those have no settings-array typography controls,
   only a `classes` prop. Values are copied 1:1 from TW_TYPE_* so visual output is
   unchanged from the classic per-widget-settings approach. None of these set `color`
   for the base H1/H2/H3/card-title/eyebrow looks that lack a fixed title_color in
   the PHP constants — those inherit `body { color: var(--tw-text) }` normally, and
   automatically go white inside .tw-hero/.tw-cta-band via the existing
   "Fallback for non-Elementor heading elements" h1-h6 rule above, since V4 headings
   render as bare <h1>-<h6> tags with no wrapper (same mechanism, no changes needed
   there). Modifier classes (--suffix) are for one-off inline overrides found at
   specific call sites; apply alongside the base class.
   ============================================================ */

.tw-type-eyebrow {
  font-size: var(--tw-fs-caption);
  font-weight: var(--tw-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  line-height: 1.45;   /* breathe when a two-category eyebrow wraps */
  color: var(--tw-purple);
}
/* Deliberate deviation from the brief (client feedback 2026-07-15: "the section headings
   — platform expertise, services, our work — need to stand out more on all pages").
   Both the token file's .tw-eyebrow and the mockup's .kicker specify 13px/800/0.14em, and
   we shipped that faithfully — but at 13px against a 42px h2 the eyebrow is the quietest
   text in the block, while being the only text that NAMES the section (our h2s are
   editorial sentences: "We know the tools, inside out"). Hence the
   client reading the eyebrow as "the section heading" and finding it lost.
   Bumped 13px → 18px (the h4 rung — an existing step, not an ad-hoc value), narrowing the
   gap to the h2 from ~3.2x to ~2.3x so the label reads as section-level rather than as a
   caption. 15px was tried first and the client wanted it bigger still. Tracking eases off
   as size grows, following the mockup's own two data points (13px→0.14em kicker,
   14px→0.12em hero eyebrow); 0.08em keeps uppercase legible without re-loosening it.
   Colour/weight are untouched — purple #662483 on the light bands already has ample
   contrast, and 700 is the top of the weight ramp, so size is the only lever here.
   Scoped to .tw-sec-top: card kickers ("Featured offer", "Featured case study") and the
   white-on-purple hero eyebrows are NOT section headings and stay at the caption rung —
   promoting those would flatten the deliberate section > card label hierarchy below. */
.tw-sec-top .tw-type-eyebrow {
  font-size: var(--tw-fs-eyebrow);
  letter-spacing: 0.08em;
}

/* Glass-card kicker ("Featured offer") wants semi-transparent white, not the fixed
   purple default or the hero's plain-white h6 fallback — (0,2,0) beats both. */
.tw-card--glass .tw-type-eyebrow {
  color: var(--tw-on-dark-2);
}

/* H1/H2 sizes are fluid clamp()s (artifact-adopted 2026-07-05), replacing the previous
   stepped 1024px/767px media-query jumps — the heading rescales continuously with the
   viewport. Deliberately capped at the SAME desktop sizes as before (60/56/42px): the
   artifact's larger hero type was rejected, only its scaling behaviour is adopted. */
.tw-type-h1 {
  font-size: var(--tw-fs-display);
  font-weight: var(--tw-fw-bold);
  line-height: var(--tw-lh-display);
  letter-spacing: -0.025em;
  /* Prevent a short trailing word dangling on its own line (widow) without re-balancing the
     whole heading: pretty keeps line 1 full and only pulls a word down when the last line
     would otherwise be a lone word — a lighter touch than balance, which visibly re-centres
     multi-line headings (rejected 2026-07-27). Applied at the shared heading role, not
     per-call-site, so every H1/H2/H3 built through the V4 helpers gets it by construction.
     Progressive enhancement: ignored where unsupported, falling back to today's wrap. */
  text-wrap: pretty;
}
/* Hero 2-column variant — smaller H1 to balance the right-column card height. */
.tw-type-h1--56 {
  font-size: var(--tw-fs-h1);
}

.tw-type-h2 {
  font-size: var(--tw-fs-h2);
  font-weight: var(--tw-fw-bold);
  line-height: var(--tw-lh-tight);
  letter-spacing: -0.02em;
  text-wrap: pretty;
}

.tw-type-h3 {
  font-size: var(--tw-fs-h3);
  font-weight: var(--tw-fw-bold);
  line-height: var(--tw-lh-snug);
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

.tw-type-card-title {
  font-size: var(--tw-fs-h4);
  font-weight: var(--tw-fw-bold);
  line-height: var(--tw-lh-snug);
  letter-spacing: -0.01em;
}
/* Tag-independent: decorative stat figures also render as <p> (kept out of the
   document outline — see About's principle-card numbers), so it pins Maven Pro
   (the Kit only sets it per h1-h6 tag) and is scoped under .tw-site
   .elementor (0,3,0) so .tw-card p (0,1,1) can't shrink an in-card stat to
   15px muted card-body text — same precedent as .tw-callout. */
.tw-site .elementor .tw-type-stat {
  font-size: var(--tw-fs-stat);
  font-weight: var(--tw-fw-bold);
  color: var(--tw-purple);
  font-family: "Maven Pro", sans-serif;
}
.tw-type-stat--center {
  text-align: center;
}

/* Small caption under a .tw-type-stat number (e.g. "providers researched"). */
.tw-type-stat-label {
  font-size: var(--tw-fs-caption);
  font-weight: var(--tw-fw-medium);
  color: var(--tw-muted);
  text-align: center;
}

/* Faded counter used in numbered process step cards (01 / 02 / 03).
   30px is bespoke — a decorative counter, deliberately between h3 and stat;
   not a text role on the scale. */
.tw-type-step-num {
  font-size: 30px;
  font-weight: var(--tw-fw-bold);
  color: #bdbdbd;
}

/* Platform partner badge caption (e.g. "SERVICE PARTNER & AMS PARTNER"). */
.tw-type-badge {
  font-size: var(--tw-fs-badge);
  font-weight: var(--tw-fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--tw-muted);
}

/* Health-check hero card lead paragraph — smaller body text for the glass card,
   on the shared body-sm rung (15px) so glass-card copy matches other dense body. */
.tw-type-body-sm {
  font-size: var(--tw-fs-body-sm);
  line-height: var(--tw-lh-normal);
}

/* ============================================================
   SHARED SECTION HEADER — .tw-sec-top
   Not in token file — the single, common header treatment every content section
   below the hero draws from, so section intros stay consistent by construction.
   Mirrors the brief mockup / sketch .sec-top: eyebrow + H2 on the left, a muted
   detail sentence bottom-aligned on the right, stacking to a single column on
   narrow viewports. Built by tw_section_top() in _components.php.

   Layout note: the row's direction / wrap / justify / align / gap AND the two
   column proportions all live HERE in the class — NOT as per-element Elementor
   settings — so they survive native e-component ID remapping. That remapping
   orphans per-element CSS (documented for .tw-cta-band / .tw-founder), and was
   the sole reason the homepage Awards header — the only tw_section_top() inside
   a native Component — collapsed to a single stacked column while every inline
   section header rendered two-column. With layout in the class it can't drift.
   Scoped .e-flex.tw-sec-top (0,2,0) to beat Elementor's .e-con.e-flex base
   (also (0,2,0)); design.css enqueues after elementor-frontend so it wins. */
.e-flex.tw-sec-top {
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: flex-start;
  align-items: flex-end;
  /* 40px, not the original 48 — see the lead's max-width below: the gap and the detail
     column are the only two things standing between the heading and the row's content
     box, so the gutter is where the heading's headroom comes from. */
  gap: 40px;
}
/* width:auto neutralises Elementor's default full-width container sizing (the same
   reason .tw-cta-band > .e-con resets --width) so the lead block and detail sit
   content-sized side by side; the max-widths cap line length as in the mockup.

   660px, raised from 640 (2026-07-30). The number is not free — it is exactly the space
   the row has left for the heading, and the three values are one equation:

       row content box 1120  =  lead 660  +  gap 40  +  detail 420

   (1120 = the 1140 container less Elementor's 10px .e-con padding each side.) At the old
   640/48 the heading had 652px of room, and it was pure luck that no heading wanted
   653 — until /team/'s "Leading each part of the business", which needs 652.1px and so
   wrapped to a second line reading "the business", leaving a chasm across to the detail.
   It missed by a TENTH OF A PIXEL. Reclaiming 8px from the gutter and letting the cap
   follow clears it by 8px, and unwraps five more headings that were also just over:
   "Wherever you are on the journey" (645, on all four service pages) and "A calm,
   structured path to go-live" (656). Nothing newly wraps and the detail column does not
   move — it stays 420 wide, flush to the content edge, on every section.

   Keep max-width and the equation in step if you ever retune this. Setting the cap
   ABOVE the available width buys nothing (flex shrinks the lead back to fit); setting it
   below wastes room the row already has, which is precisely the bug above.

   Known cost, accepted: any cap over ~646 lets the partner pages' "An independent
   partner invested in your …" heading pull one more word onto line 1, so it goes from a
   coincidentally-even two-line stack to a full line plus a short one. It stays two lines
   either way, so nothing moves. text-wrap: balance would restore the even break, but it
   re-breaks 20 other section headings — several with line 2 LONGER than line 1 — which
   is the "visibly re-centres multi-line headings" rejected 2026-07-27. Don't reach for
   it; if those partner headings need to read better, shorten the copy. */
.tw-sec-top__lead {
  width: auto;
  max-width: 660px;
}
/* flex: 0 1 auto — the lead hugs its content and does NOT grow.
   padding:0 kills Elementor's default 10px .e-con padding on the lead column — the row
   bottom-aligns (align-items: flex-end) to the lead COLUMN's bottom, not the H2's, and
   that 10px bottom padding otherwise dragged the intro text 10px below the heading.
   (0,2,0) beats Elementor's default container padding (0,1,0). */
.tw-sec-top > .tw-sec-top__lead {
  flex: 0 1 auto;
  padding: 0;
}
/* Detail: a fixed 420 column, may shrink, bottom-aligned to the lead column.
   margin-left: auto pins it to the row's RIGHT edge — its left and right edges are then
   identical on every section, and the right edge lines up with the page content margin
   (cards, images, band edges) instead of stopping wherever the heading happened to end.
   Trailing the heading (the previous behaviour) made the column wander by up to 318px
   between sections — 92px on the homepage, 318px on Our Work — which read as a ragged
   right edge down the whole page (user call 2026-07-29, superseding the 2026-07 steer
   that removed justify: space-between for the opposite reason). The cost is accepted:
   after a short heading the 48px gutter opens up to ~380px of white, but because the
   detail now sits at a CONSTANT x it reads as a column, not the variable chasm that was
   rejected before. Same treatment the .tw-sec-top--badge variant already gives its
   emblem. The auto margin absorbs the row's free space, so the row's `gap` stays the
   minimum gutter when a long heading fills the lead column. (That auto margin is also
   why the lead can't simply be given flex-grow to fill the row: auto margins eat the
   free space first and zero out flex-grow, so the lead only ever gets its max-content
   width. Hence the max-width above doing the work.)
   Cross-axis caveat: this must be reset in the mobile stack below — in a column flex
   container `margin-left: auto` overrides align-items and would right-align the block. */
.tw-sec-top > .tw-sec-top__detail {
  flex: 0 1 420px;
  margin-left: auto;
}
.tw-sec-top__detail {
  width: auto;
  max-width: 420px;
  color: var(--tw-muted);
  font-size: var(--tw-fs-body);
  line-height: var(--tw-lh-normal);
  /* pretty (not balance) for multi-line body copy: it pulls a lone last-line word back up
     without reflowing the whole paragraph — the fix for the "…weighing up." widow on the
     partner strip. Progressive enhancement; degrades to today's wrap where unsupported. */
  text-wrap: pretty;
}
/* Elementor's .e-con.e-flex rule is (0,2,0) and sets flex-direction via
   var(--flex-direction), beating a plain .tw-sec-top (0,1,0) regardless of load
   order — same mechanism documented for .tw-cta-band above. Match specificity
   with .e-flex.tw-sec-top to force the single-column stack on narrow viewports. */
@media (max-width: 820px) {
  .e-flex.tw-sec-top {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }
  .tw-sec-top > .tw-sec-top__lead,
  .tw-sec-top > .tw-sec-top__detail {
    flex: 0 1 auto;
  }
  /* Undo the desktop flush-right pin: with flex-direction: column, margin-left: auto is a
     CROSS-axis auto margin and overrides align-items: flex-start, right-aligning the block. */
  .tw-sec-top > .tw-sec-top__detail {
    margin-left: 0;
  }
  .tw-sec-top__lead,
  .tw-sec-top__detail {
    max-width: 100%;
  }
}

/* Card backgrounds handled via CSS so .tw-card--soft can override .tw-card.
   background_color in TW_CARD_SETTINGS is blank; no per-element CSS conflict. */
.tw-card {
  background: var(--tw-white);
}
.tw-card--soft {
  background: var(--tw-offwhite);
}

/* White cards inside the hero: our .tw-hero rule overrides --e-global-color-primary to white
   so headings inside the hero render white. Reset to dark text inside .tw-card so they stay legible.
   Does NOT apply to .tw-card--glass which intentionally stays on the dark palette. */
.tw-hero .tw-card:not(.tw-card--glass) {
  --e-global-color-primary: var(--tw-text);
  color: var(--tw-text);
}
/* Bare tag selector needed alongside the container colour above: the hero's
   "Fallback for non-Elementor heading elements" rule (.tw-hero h1..h6 { white })
   beats inherited colour on any heading inside the card — V4 headings render as
   bare tags, so without this they went white-on-white (the "Platform knowledge"
   eyebrow was unreadable). (0,3,1) beats the fallback's (0,1,1). */
.tw-hero .tw-card:not(.tw-card--glass) h1, .tw-hero .tw-card:not(.tw-card--glass) h2,
.tw-hero .tw-card:not(.tw-card--glass) h3, .tw-hero .tw-card:not(.tw-card--glass) h4,
.tw-hero .tw-card:not(.tw-card--glass) h5, .tw-hero .tw-card:not(.tw-card--glass) h6 {
  color: var(--tw-text);
}
/* .tw-type-eyebrow: V4 headings carry the type class, not .tw-eyebrow. */
.tw-hero .tw-card:not(.tw-card--glass) .tw-eyebrow,
.tw-hero .tw-card:not(.tw-card--glass) .tw-type-eyebrow {
  color: var(--tw-purple);
}

/* Remove <p> bottom margin inside all card types.
   Cards use flex_gap for inter-element spacing; the paragraph's default margin-bottom
   adds on top of that and creates a double gap. Plain `p` (not `.elementor-widget-text-editor p`)
   so this matches both the classic wrapper structure AND V4 e-paragraph's bare <p> — the
   card container class is always an ancestor of the text either way. */
.tw-card p,
.tw-card--glass p {
  margin-bottom: 0;
}

/* Card body text — the single, shared treatment for prose inside cards, so every card
   variant (services, cases, lead magnets, soft hero card) reads the same by construction.
   One deliberate step down from the 16px near-black page body, matching the sketch
   (docs/sketches): 15px (var(--tw-fs-body-sm)) and muted grey (var(--tw-muted)) rather than the full
   ink colour, so the card title carries the weight and the body "fades a little". Plain
   `p` matches classic + V4 bare <p>. Glass cards keep their own semi-transparent-white body
   (set below) and are excluded — .tw-card--glass is not .tw-card. Micro-links, stat figures
   and metric labels set their own colour at higher specificity and are unaffected. */
.tw-card p,
.tw-card li {
  font-size: var(--tw-fs-body-sm);
  line-height: var(--tw-lh-relaxed);
  color: var(--tw-muted);
}
/* Bullet lists inside a card (e.g. partner "Who this is for" cards, rendered as a classic
   <ul> via tw_text_classic) must read as card body, not base page body. Without `li` here
   the list items fall through to the 16px/dark base body — the single most visible
   inconsistency on the partner pages, where they sat bigger and darker than every peer
   card's prose. Keep the bullet marker and a little inter-item rhythm. */
.tw-card ul {
  margin: 0;
  padding-left: 1.15em;
}
.tw-card li + li {
  margin-top: 6px;
}
/* ============================================================
   TESTIMONIALS — .tw-testi  (homepage "Client voices")
   Not in token file — an asymmetric editorial grid: one featured pull-quote panel
   (lilac) spanning the height of two smaller white supporting quote cards beside it.
   Deliberately NOT a fourth uniform card row — Services / Cases / Lead-magnets are all
   equal-column grids, so this varies the page rhythm and spotlights the strongest voice.
   Shares the founder band's serif quote-mark + "name · role" attribution motif, recoloured
   dark-on-light, so the two quote sections read as one family. Built by
   tw_component_testimonials() in _components.php.
   ============================================================ */
/* Static quote wall (no carousel — every voice stays on the page, skimmable and indexable):
   one featured lead quote spanning the full width, then a 3-up row of supporting cards.
   CSS grid, scoped (0,3,0) to beat Elementor's .e-con flex on load order — same pattern as
   .tw-lead-grid / .tw-cases-grid. Scales to any supporting count; the row just reflows. */
.tw-site .elementor .tw-testi-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  align-items: stretch;
}
.tw-testi--featured {
  grid-column: 1 / -1;            /* full-width lead panel above the supporting row */
  background: var(--tw-lilac);    /* (0,1,0), later than .tw-card — repaints the panel */
  border-color: transparent;
}

/* Decorative quote-mark, echoing .tw-founder__qmark but purple-on-light. height clamps
   the glyph's own leading so the quote sits directly beneath it (as in the founder band).
   Was Georgia — the brand guidelines rule out serif faces outright. Named explicitly as
   the display face (not left to inherit the body face) because this is an ornament, and
   of the two brand faces Maven Pro's ldquo is the better glyph: Open Sans draws it as two
   hard rectangular slabs, Maven Pro as a softer tapered wedge that sits closer to the
   rounded geometry of the logo. Neither sans reproduces Georgia's curl — that character
   is simply not available within the guidelines. Sizes are up (66→82 / 42→52) because the
   sans ldquo draws smaller in the em box than Georgia's did.
   Naming a family here duplicates Kit knowledge, as the button baseline does; if the Kit's
   heading face ever changes, change it here too. */
.tw-site .elementor .tw-testi__qmark {
  display: block;
  font-family: "Maven Pro", sans-serif;
  font-weight: 700;
  line-height: 0.5;
  color: var(--tw-purple);
  margin: 0;
}
.tw-testi--featured .tw-testi__qmark { font-size: 82px; height: 0.30em; opacity: 0.26; }
.tw-testi--sm .tw-testi__qmark { font-size: 52px; height: 0.26em; opacity: 0.2; }

/* Quote body. Featured is a bold near-display pull-quote (a clear step below the founder
   band's 34px so the founder stays the page's biggest statement); supporting quotes are
   quieter, at card-body weight. Bare custom class (V4 <p> has no wrapper) — scoped to
   beat the atomic .e-paragraph-base { margin:0 } baseline. */
.tw-site .elementor .tw-testi__quote {
  color: var(--tw-text);
  margin: 0;
  text-wrap: pretty;
}
.tw-testi--featured .tw-testi__quote {
  font-size: clamp(20px, 1.6vw, 26px);
  font-weight: 700;
  line-height: 1.34;
  letter-spacing: -0.01em;
  max-width: 58ch;   /* readable measure; centred in the full-width panel below */
}
.tw-testi--sm .tw-testi__quote {
  font-size: var(--tw-fs-body);
  font-weight: var(--tw-fw-medium);
  line-height: var(--tw-lh-normal);
}

/* Attribution — reuses the founder band's name · rule · role motif, recoloured for the
   light card. margin-top:auto sticks it to the card foot so the supporting cards align
   their attributions to the bottom even at unequal quote lengths (grid-stretched height).
   On these narrow cards the role reads better on its own line under the name, so the
   .rule divider is hidden and .rl takes a full-width flex basis. */
.tw-site .elementor .tw-testi__who {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: auto 0 0;
  padding-top: 6px;
}
.tw-testi__who .nm {
  font-weight: 700;
  font-size: var(--tw-fs-body-sm);
  letter-spacing: -0.01em;
  color: var(--tw-text);
}
.tw-testi__who .rule { display: none; }
.tw-testi__who .rl {
  flex-basis: 100%;
  font-size: var(--tw-fs-caption);
  line-height: 1.4;
  color: var(--tw-muted);
}
/* The full-width featured panel reads as a centred hero quote — quote mark, quote and
   attribution all centred — so the wide lilac band doesn't leave an empty right flank.
   .e-flex (0,2,0) beats Elementor's column-flex default (same as .e-flex.tw-founder__body);
   align-items:center centres the max-width quote as a flex item. The attribution follows
   the quote with a normal gap here (not pushed to the foot). (0,4,0) beats margin:auto. */
.e-flex.tw-testi--featured {
  align-items: center;
  text-align: center;
}
.tw-site .elementor .tw-testi--featured .tw-testi__who {
  margin-top: 10px;
  justify-content: center;
}

@media (max-width: 860px) {
  .tw-site .elementor .tw-testi-grid {
    grid-template-columns: 1fr;
    grid-template-rows: none;
  }
  .tw-testi--featured { grid-column: auto; grid-row: auto; }
}

/* Glass card — frosted semi-transparent card for use on dark hero/band backgrounds.
   background_background: classic must be set in container settings to prevent Elementor
   generating background: none, which would clear this class's rgba background. */
.tw-card--glass {
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.16);
  backdrop-filter: blur(12px);
  border-radius: var(--tw-radius-lg);
}
/* Text content uses --e-global-color-text (dark) regardless of hero context.
   Override to semi-transparent white inside glass cards. Plain `p`/`li span` (not
   `.elementor-widget-text-editor p`) so this matches V4 e-paragraph's bare <p> too. */
.tw-card--glass,
.tw-card--glass p,
.tw-card--glass li span:not(.tw-tick) {
  color: var(--tw-on-dark);
}
/* Checklist inside the glass card — artifact-adopted 2026-07-05: slightly roomier
   gap and rem-based size (~14.7px) matching the artifact's .ticks (gap 11px, 0.92rem). */
.tw-card--glass .tw-checklist {
  margin: 0;
  gap: 11px;
}
.tw-card--glass .tw-checklist li {
  font-size: var(--tw-fs-body-sm);
  line-height: 1.4;
}
/* Dark-background tick variant: solid green circle, white check. */
.tw-card--glass .tw-tick {
  background: rgba(58, 170, 53, 0.9);
}
.tw-card--glass .tw-tick::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 6.5 5 9.5 10 3' fill='none' stroke='%23fff' stroke-width='2.6'/%3E%3C/svg%3E");
}

/* ============================================================
   ELEMENTOR WIDGET OVERRIDES
   Only selectors that cannot be achieved via CSS class alone.
   ============================================================ */

/* Not in token file — logo strip: logos sit in a flex row that wraps at tablet/mobile.
   Generous column gap because the logos are bare (no pill boxes) and need breathing room
   to read as separate marks. */
.tw-logo-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* No justify-content here: each strip sets its own via Elementor — the partner
     section aligns left (flex-start) under its header, the client trust row centres. */
  gap: 20px 32px;
}

/* Not in token file — individual logo cell within .tw-logo-strip. Alpha-blended: the
   logos are transparent PNGs that sit directly on the section band with no white pill
   (user: "we want alpha blending so we don't need pills"). Each logo file is normalised
   to a common canvas height so this one max-height keeps their main-text heights matched. */
.tw-logo-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  /* Override Elementor's --width: 100% default so cells hug their logo */
  --width: auto;
  --flex-basis: auto;
  flex: 0 0 auto;
}

.tw-logo-chip img {
  max-height: 52px;
  width: auto;
  object-fit: contain;
  /* Brand colour, slightly faded back at rest (muted saturation + a touch of transparency)
     so the mixed palette reads as one calm trust row; resolves to full colour plus a subtle
     grow on hover. */
  filter: saturate(0.65);
  opacity: 0.92;
  transition: transform 0.2s ease, filter 0.2s ease, opacity 0.2s ease;
  transform-origin: center;
}

.tw-logo-chip:hover img {
  filter: saturate(1);
  opacity: 1;
  transform: scale(1.07);
}

/* Not in token file — partner platform strip (homepage/About/Services). Nine logos that
   wrap 5+4 across two centred rows (user 2026-07-13: the set grew from six to nine when
   Eightfold was dropped for Teamtailor/ADP/Zellis/Legion, so one row no longer fits at
   the size the client asked for).

   Everything here is sized as a PROPORTION of the strip's own width, via a container
   query, rather than in px at a handful of breakpoints. That's what makes the 5+4 split
   hold at every viewport instead of only at the widths someone tuned by hand. Measured
   rendered logo widths, at a notional 100px height, in tw_partner_logo_slugs() order:

     row 1  workday 146 + dayforce 159 + hibob 113 + adp 85 + teamtailor 171  =  674px
     row 2  zellis 94 + factorial 197 + talentia 186 + legion 221             =  697px

   With height = 10.5cqw and a 5cqw gap, row 1 always occupies 674×0.105 + 5×0.05 = 96%
   of the strip and a sixth logo would always need 96 + 5 + 9.8 = 111% — so five per row,
   never four or six, at any width, and the rows always fill ~96% of the content column.
   The px-at-breakpoints version this replaced broke to 4+4+1 at 1100px and 768px, where
   a fixed 70px gap no longer fitted the narrowed container (measured).

   Sizing knobs (client 2026-07-15 "more space between them"; user "increase size a
   little so we fill the main content width"): at the 1140px max-width — the site content
   width, matching the section header above and the hero, so the rows line up with their
   column — 10.5cqw resolves to a 120px logo and 5cqw to a 57px gap, up from 100px/32px.

   Note the rows can't be both exactly equal AND widely gapped: row 2 carries 23px more
   logo (at 100px height) than row 1, so they equalise only at a ~23px gap and every px
   beyond that widens row 1 relative to row 2. Here that leaves row 1 ~29px wider across
   a ~1090px row (under 3%) — the deliberate trade for the spacing asked for, and far
   from the 263px imbalance this replaced.

   The gap is applied as chip margin, not column-gap, because a container query unit on
   the container's own properties would resolve against the next ancestor container (an
   element can't query itself) — only descendants can read the strip's width. Hence the
   half-gap each side of every chip, and column-gap:0 to drop the inherited .tw-logo-strip
   gap. The compound .tw-logo-strip.tw-logo-strip--partners (0,2,0) is needed because the
   generic `.tw-logo-strip { gap: 12px }` mobile rule further down this file would
   otherwise win on source order.

   justify-content:center centres each row (the short second row included); align-self:
   center centres the whole block whether the parent column is stretch- (full-section
   variant) or centre-aligned (compact Services variant).

   Per-logo size/baseline alignment is NOT done here — it is baked into each PNG by
   scripts/build-logos.py's scale/dy knobs, so this strip needs only the one uniform
   proportional max-height and zero per-logo CSS. To re-tune a logo, edit that script and
   re-run it; never add per-logo rules here. */
.tw-logo-strip.tw-logo-strip--partners {
  container-type: inline-size;
  flex-wrap: wrap;
  justify-content: center;
  align-self: center;
  margin: 10px auto 0;
  width: 100%;
  max-width: 1140px;
  column-gap: 0;
  row-gap: 0;   /* two-row 5+4 arrangement: no row-gap needed — each logo PNG is normalised onto a
                   common canvas taller than its glyph, so the baked-in transparent padding already
                   separates the rows visually; 28px on top read too loose (user 2026-07-27). */
}

.tw-logo-strip--partners .tw-logo-chip {
  margin-inline: 2.5cqw;
}

.tw-logo-strip--partners .tw-logo-chip img {
  max-height: 10.5cqw;
}

/* Two columns on mobile (user 2026-07-15), not the nine-row single-file stack this
   otherwise falls into. That stack is NOT a flex-direction flip — the strip stays
   row+wrap — it's Elementor's own `.e-con.e-flex { --width: 100% }` at its ≤767px mobile
   breakpoint hitting each chip (every chip is an .e-con container), which beats the
   `--width: auto` on .tw-logo-chip. So the fix is to re-set --width to a half-column
   basis at the same breakpoint; matching Elementor's 767px (not the 640px step below)
   covers the whole range where its rule applies. Two columns of a ~370px strip are too
   narrow for the proportional sizing above to give a legible logo, so the mobile stack
   reverts to px: a flat gap and the 640px max-height step. Nine logos over two columns
   leaves the last row holding one, centred by justify-content above. */
@media (max-width: 767px) {
  .tw-logo-strip.tw-logo-strip--partners { column-gap: 24px; row-gap: 24px; }
  .tw-logo-strip--partners .tw-logo-chip {
    margin-inline: 0;
    --width: calc((100% - 24px) / 2);
  }
  .tw-logo-strip--partners .tw-logo-chip img { max-height: 88px; }
}

@media (max-width: 640px) {
  .tw-logo-strip--partners .tw-logo-chip img { max-height: 72px; }
}

/* Not in token file — partner overview card logo. Replaces the platform-name heading,
   so it sits top-left where the name was. V4 e-image renders a bare <img>: its baseline
   .elementor .e-image-base (0,3,0) sets display:block with no size, and as a column flex
   child it would stretch — so the compound .tw-partner-card-logo.e-image-base under
   .tw-site (0,4,0) beats that baseline (same pattern as the site logo). Fixed
   height for a uniform row of logos; max-width:100% keeps a wide wordmark inside the card;
   align-self:flex-start left-aligns it with the card's other content. */
.tw-site .tw-partner-card-logo.e-image-base {
  height: 96px;
  width: auto;
  max-width: 100%;
  align-self: flex-start;
  object-fit: contain;
}

/* Not in token file — partnership role card (fuller partner pages) with a full-bleed
   header strip. The .tw-card sets padding:0 + this overflow:hidden so the tinted strip
   meets the card edges and clips to the border radius; the strip and body carry their
   own padding via container settings. */
.tw-site .elementor .tw-role-card {
  overflow: hidden;
}
/* Not in token file — the purple header strip at the top of a role card. Holds either
   the official vendor badge (Workday's Partner mark, sat on a white chip added below so it
   reads on purple) or the journey-phase tag (Dayforce), plus a caption.
   Row layout + padding are set on the container; the purple is a solid ELEMENT background
   (a background-color, not a settings gradient) so the card's overflow:hidden clips it to
   the rounded top corners — a settings gradient paints on an atomic ::before overlay that
   the parent clip doesn't catch. Text on the strip flips to white. */
.tw-role-head {
  background: var(--tw-purple);
}
.tw-role-head .tw-type-eyebrow { color: var(--tw-white); }
.tw-role-head .tw-type-badge   { color: var(--tw-on-dark-2); }
/* Caption on the right of the strip (e.g. "Official accreditation") — pushed to the
   far end, right-aligned.

   Scoped .tw-site .elementor (0,3,0) for the margin: the caption is a V4 atomic
   heading, and e-heading-base's structural `margin: 0` baseline lands at that specificity,
   so a plain .tw-role-head__cap (0,1,0) auto margin was silently dropped — the caption
   trailed whatever preceded it instead of sitting at the far end. (text-align isn't in the
   atomic baseline, so it applied fine and masked this.) Same trap as the button/image
   variants. Fixed 2026-07-30 while adding the role tag to the badge strip, which is what
   made the dead auto margin visible. */
.tw-site .elementor .tw-role-head__cap {
  margin-left: auto;
}
.tw-role-head__cap {
  text-align: right;
}
/* The body column must fill the card's full height (the card is a flex column and the grid
   stretches both cards to equal height); only then does .tw-role-prose have slack to absorb.
   The atomic _flex_grow setting computed to 0, so this is set as a class instead. */
.tw-site .elementor .tw-role-body {
  flex-grow: 1;
}
/* The role card's pitch prose grows to fill the space above the callout, so the lilac
   callout (and the checklist below it) line up across both cards regardless of body
   length. Needs .tw-role-body to fill the card (above). */
.tw-role-prose {
  flex-grow: 1;
}
/* The vendor badge. Bare V4 <img class="e-image-base"> — same baseline-stretch fix as
   .tw-partner-card-logo, beaten by the compound (0,4,0). Centred in the strip, height-capped.

   The white chip is load-bearing, not decoration. Workday replaced its wide per-designation
   lockups (which had a white background baked into the artwork) with a square Partner mark
   filled navy #0f2e66 on transparent corners. Navy on the purple strip is 1.34:1 — the badge
   read as a dark hole, where the old lockup's own white ground gave 9.79:1. The chip restores
   that contrast, and matches the light background Workday's partner kit expects the mark to
   sit on. Padding is even on all four sides so the chip stays square around a square mark;
   the radius is a hair tighter than the mark's own rounded corners so the two nest.
   Height here is the MARK's height — the chip adds its padding outside that. */
.tw-site .tw-role-head__badge.e-image-base {
  height: 42px;
  width: auto;
  max-width: 100%;
  align-self: center;
  object-fit: contain;
  background: var(--tw-white);
  padding: 7px;
  border-radius: 12px;
}
/* Not in token file — the "What this means for you" block inside a role card body,
   set off from the pitch prose above by a dashed rule so the card reads in two parts. */
.tw-role-means {
  border-top: 1px dashed var(--tw-border);
  padding-top: 20px;
  margin-top: 4px;
}
/* The block's flex gap is 0 (heading + checklist), so give the "What this means for
   you" heading its own breathing room over the bullets that follow. */
.tw-role-means .tw-type-badge {
  margin-bottom: 12px;
}

/* Not in token file — a single company-level accreditation emblem shown to the right
   of a section heading (Dayforce's Emerging Partner shield). The row reuses .tw-sec-top
   for the lead-left layout; .tw-sec-top--badge (0,3,0, beating the base .e-flex.tw-sec-top
   at (0,2,0)) pushes the emblem to the far right and centres it against the heading. */
.e-flex.tw-sec-top.tw-sec-top--badge {
  justify-content: space-between;
  align-items: center;
}
.tw-site .tw-sec-top__badge.e-image-base {
  height: 120px;
  width: auto;
  max-width: 100%;
  flex: 0 0 auto;
  align-self: center;
  object-fit: contain;
  /* Pull the emblem in from the right edge — it otherwise sits only the row's atomic
     padding (~10px) off the content edge and reads as hugging it. */
  margin-right: 16px;
}
/* Not in token file — horizontal co-brand wordmark (Factorial's "Partners by Factorial"
   lockup) in the section-top badge slot. Its ~3:1 aspect would render ~360px wide at the
   default 120px shield height, dominating the heading — so cap it shorter. */
.tw-site .tw-sec-top__badge--wordmark.e-image-base {
  height: 72px;
}
@media (max-width: 820px) {
  /* Stack with the rest of .tw-sec-top on mobile: emblem drops below the heading,
     both left-aligned (overrides the desktop centre/space-between at equal 0,3,0
     specificity by coming later in the cascade). */
  .e-flex.tw-sec-top.tw-sec-top--badge {
    justify-content: flex-start;
    align-items: flex-start;
  }
}

/* CTA band lead text — slightly muted white matching mockup. Plain `p` (not
   `.elementor-widget-text-editor p`) so this matches V4 e-paragraph's bare <p> too. */
.tw-cta-band p {
  color: var(--tw-on-dark-2);
}

/* Not in token file — service cards numbered icon badge (01 / 02 / 03).
   Rendered as a raw <div> via tw_text() so we own the element fully
   without fighting Elementor heading typography defaults. */
.tw-service-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 16px;
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  background: linear-gradient(135deg, rgba(102, 36, 131, 0.14), rgba(0, 159, 227, 0.12));
  color: var(--tw-purple);
  margin-bottom: 18px;
}

/* Not in token file — service / case study CTA text link style, applied directly to <a>
   elements in tw_text() HTML. Heading widget links use Elementor title_color + typography
   settings instead (not this class).
   Specificity note: Elementor injects an inline rule `.e-heading-base a, .e-paragraph-base
   a { all: unset; ...; color: unset; ... }` (one class + one tag = (0,1,1)) that resets any
   link nested inside a V4 heading/paragraph, regardless of whether the link came from the
   widget's own `link` prop or from raw HTML in the content string (our micro-links are the
   latter). A plain `.tw-micro-link` class alone ((0,1,0)) loses to that. Same fix as the
   button system: scope under `.tw-site .elementor` to reach (0,3,0). */
.tw-site .elementor .tw-micro-link {
  display: inline-block;
  color: var(--tw-purple);
  font-weight: 700;
  text-decoration: none;
  margin-top: 4px;
}
.tw-site .elementor .tw-micro-link:hover { text-decoration: underline; }

/* ============================================================
   ANNOUNCEMENT BAR — slim purple band at the top of the home page
   Not in token file — brief "Home page sections" §1. First element of the homepage
   content (below the header, above the hero), built in scaffold-home.php as classic
   widgets bound to ACF fields on the front page. Copy + on/off are edited as ACF
   fields on the Home page's edit screen, NOT in Elementor.

   Show/hide + link presence are driven by body classes added in the child theme's
   body_class filter (themes/teamwheel-child/functions.php), reading the ACF
   `announce_enabled` / `announce_link_url` fields:
     - hidden unless <body> has .tw-announce-on   (so an "off" toggle disappears the bar)
     - link hidden unless <body> has .tw-announce-haslink (so an empty link never shows)
   The hide rules sit at (0,2,0) to beat Elementor's own .e-con display rule (0,1,0).
   ============================================================ */
.tw-announce {
  background: var(--tw-purple);
  /* Vertical padding shrinks a little with scroll (see --tw-shrink); 24px sides stay. */
  padding: calc(11px - 5px * var(--tw-shrink)) 24px;
  /* Pinned directly under the sticky header: sticks at the header's live height
     (--tw-header-h, tracked by header-scroll.js) so it rides the header's shrink.
     z-index below the header (100) but above page content. */
  position: sticky;
  top: var(--tw-header-h);
  z-index: 99;
}
body:not(.tw-announce-on) .tw-announce { display: none; }

/* Message — classic text-editor: the class lands on the widget wrapper, so colour/size
   cascade to the inner <p> by inheritance (also targeted directly for robustness). */
.tw-announce__text,
.tw-announce__text p {
  color: var(--tw-white);
  font-size: var(--tw-fs-body-sm);
  line-height: 1.4;
  margin: 0;
}

/* Inline link — classic heading (span) rendered as a text link. Hello Elementor's
   reset colours links pink, so the white + underline is scoped under .tw-site
   (0,3,0 with the wrapper class + tag) to win. The heading wraps its text in an <a>
   when a link is set. */
.tw-site .elementor .tw-announce__link .elementor-heading-title,
.tw-site .elementor .tw-announce__link a {
  color: var(--tw-white);
  font-size: var(--tw-fs-body-sm);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.tw-site .elementor .tw-announce__link a:hover { text-decoration: none; }
body:not(.tw-announce-haslink) .tw-announce__link { display: none; }

/* ============================================================
   SERVICES STRAPLINE BAND — "Buy it, Fix it, Optimise it"
   Not in token file — the homepage services section reframed as a bold deep-purple
   panel around the client's strapline (built by tw_component_services_strapline()).
   One purple family, a single luminous violet accent; the curve + phase cards are
   delivered as a classic `html` widget, so all their styling lives here. The two
   accent tints are scoped as local custom properties on the band, not global tokens,
   since they're specific to this treatment. Link colours are scoped under .tw-svc-band
   to reach (0,2,0) and beat the base `.elementor a { color: var(--tw-purple) }` (0,1,1).
   ============================================================ */
.tw-svc-band {
  --svc-accent: #d9bcff;         /* luminous accent — eyebrow, dot label */
  --svc-accent-bright: #ecdcff;  /* brighter — bolded verbs, chip glyph, links */
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: 34px;
  color: #fff;
  background:
    radial-gradient(120% 90% at 88% 8%, rgba(178, 113, 255, 0.30) 0%, rgba(178, 113, 255, 0) 55%),
    linear-gradient(157deg, #370f4d 0%, #571d76 52%, #7d3697 100%);
  box-shadow: var(--tw-shadow);
}
/* Faint measurement grid — quiet "systems / data" texture, very low contrast, masked
   to fade out toward the lower half so it never competes with the cards. */
.tw-svc-band::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.5;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 78%);
  mask-image: linear-gradient(180deg, #000 0%, transparent 78%);
}

/* Section header inside the band (reuses .tw-sec-top layout; these style the copy). */
.tw-svc-eyebrow {
  margin: 0 0 16px;
  font-size: var(--tw-fs-caption);
  font-weight: var(--tw-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--svc-accent);
}
.tw-svc-strap {
  margin: 0;
  font-size: clamp(2.3rem, 5.6vw, 3.7rem);
  line-height: 0.98;
  letter-spacing: -0.02em;
  font-weight: var(--tw-fw-bold);
  color: #fff;
}
.tw-svc-strap b { color: var(--svc-accent-bright); font-weight: var(--tw-fw-bold); }
/* Detail sentence: base .tw-sec-top__detail is dark/muted; override to light on the band. */
.tw-svc-band .tw-sec-top__detail {
  max-width: 380px;
  color: var(--tw-on-dark-2);
}
.tw-svc-band .tw-sec-top__detail b { color: #fff; font-weight: 700; }

/* Signature value curve (decorative SVG). */
.tw-svc-stage { position: relative; height: 240px; margin: 8px 0 6px; }
.tw-svc-curve { display: block; width: 100%; height: 240px; }
.tw-svc-line {
  fill: none;
  stroke: url(#twSvcStroke);
  stroke-width: 3.5;
  stroke-linecap: round;
  filter: drop-shadow(0 0 8px rgba(178, 113, 255, 0.9));
  stroke-dasharray: 2000;
  stroke-dashoffset: 0;
}
.tw-svc-dot {
  position: absolute;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  transform: translate(-50%, -50%);
  background: #fff;
  border: 3px solid rgba(255, 255, 255, 0.35);
  box-shadow: 0 0 0 6px rgba(178, 113, 255, 0.18), 0 0 22px 4px rgba(178, 113, 255, 0.75);
}
.tw-svc-dot--1 { left: 16.66%; top: 190px; }
.tw-svc-dot--2 { left: 50%;    top: 154px; }
.tw-svc-dot--3 { left: 83.33%; top: 90px; }
.tw-svc-dot--3::after {
  content: "Peak value";
  position: absolute;
  left: 50%;
  top: -30px;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--svc-accent);
}

/* Glass phase cards. */
.tw-svc-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.tw-svc-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 26px 26px 28px;
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 28px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  transition: transform 0.28s ease, background 0.28s ease, border-color 0.28s ease;
}
.tw-svc-card:hover {
  transform: translateY(-5px);
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(217, 188, 255, 0.4);
}
.tw-svc-card__head { display: flex; align-items: center; gap: 12px; }
.tw-svc-chip {
  flex: none;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 11px;
  color: var(--svc-accent-bright);
  background: rgba(217, 188, 255, 0.14);
  border: 1px solid rgba(217, 188, 255, 0.28);
}
.tw-svc-chip svg { width: 20px; height: 20px; display: block; }
.tw-svc-verb {
  margin: 0;
  font-size: 1.42rem;
  font-weight: var(--tw-fw-bold);
  letter-spacing: -0.01em;
  color: #fff;
}
.tw-svc-promise {
  margin: 0;
  font-size: var(--tw-fs-body-sm);
  line-height: 1.5;
  color: var(--tw-on-dark-2);
}
.tw-svc-links {
  margin-top: auto;
  padding-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
/* Scoped under .tw-svc-band to reach (0,2,0) — beats base `.elementor a` (0,1,1). */
.tw-svc-band .tw-svc-link {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  color: var(--svc-accent-bright);
  font-weight: 700;
  font-size: var(--tw-fs-body-sm);
  text-decoration: none;
  transition: gap 0.2s ease;
}
.tw-svc-band .tw-svc-link:hover { color: #fff; text-decoration: underline; }
.tw-svc-arw { transition: transform 0.2s ease; }
.tw-svc-link:hover .tw-svc-arw { transform: translateX(4px); }
.tw-svc-link:focus-visible {
  outline: 2px solid var(--svc-accent-bright);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---- Light variant ------------------------------------------------------
   Not in token file — .tw-svc-band--light: the same strapline band on an off-white
   ground with purple accents and white bordered cards, for pages where a full dark
   slab is too heavy (e.g. the homepage, between the dark hero and CTA bookends). Built
   with the 'light' variant of tw_component_services_strapline(), which also drops the
   value curve. Each rule below is (0,2,0) so it overrides the dark base (0,1,0). */
.tw-svc-band--light {
  color: var(--tw-text);
  background: var(--tw-offwhite);
  border: 1px solid var(--tw-border);
}
.tw-svc-band--light::before { display: none; }        /* no dark grid texture */
.tw-svc-band--light .tw-svc-eyebrow { color: var(--tw-purple); }
.tw-svc-band--light .tw-svc-strap { color: var(--tw-text); }
.tw-svc-band--light .tw-svc-strap b { color: var(--tw-purple); }
.tw-svc-band--light .tw-sec-top__detail { color: var(--tw-muted); }
.tw-svc-band--light .tw-sec-top__detail b { color: var(--tw-text); }
.tw-svc-band--light .tw-svc-card {
  background: var(--tw-white);
  border: 1px solid var(--tw-border);
  box-shadow: var(--tw-shadow);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.tw-svc-band--light .tw-svc-card:hover {
  background: var(--tw-white);
  border-color: var(--tw-border-hover);
}
.tw-svc-band--light .tw-svc-chip {
  color: var(--tw-purple);
  background: rgba(102, 36, 131, 0.10);
  border-color: rgba(102, 36, 131, 0.18);
}
.tw-svc-band--light .tw-svc-verb { color: var(--tw-text); }
.tw-svc-band--light .tw-svc-promise { color: var(--tw-muted); }
.tw-svc-band--light .tw-svc-links { border-top-color: var(--tw-border); }
.tw-svc-band--light .tw-svc-link { color: var(--tw-purple); }
.tw-svc-band--light .tw-svc-link:hover { color: var(--tw-purple-d, #4b1661); }
.tw-svc-band--light .tw-svc-link:focus-visible { outline-color: var(--tw-purple); }

/* Stacked layout: the horizontal value curve carries no meaning stacked, so hide it
   and drop the cards to one column. Header stack is handled by the .tw-sec-top rule. */
@media (max-width: 860px) {
  .tw-svc-stage { display: none; }
  .tw-svc-cards { grid-template-columns: 1fr; }
  .tw-svc-band .tw-sec-top__detail { max-width: 100%; }
}

@media (prefers-reduced-motion: no-preference) {
  .tw-svc-line { animation: twSvcDraw 1.7s ease forwards; }
  .tw-svc-dot { opacity: 0; animation: twSvcPop 0.5s ease forwards; }
  .tw-svc-dot--1 { animation-delay: 0.7s; }
  .tw-svc-dot--2 { animation-delay: 1.05s; }
  .tw-svc-dot--3 { animation-delay: 1.4s; }
  @keyframes twSvcDraw { from { stroke-dashoffset: 2000; } to { stroke-dashoffset: 0; } }
  @keyframes twSvcPop {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.4); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }
}

/* The homepage SERVICES GRID that lived here — four white .tw-card tiles in a
   repeat(4,1fr) grid, each with a rounded-square icon chip (.tw-service-grid /
   .tw-service-tile / .tw-service-ico / .tw-service-desc) — is gone with its builder:
   the section is now the connected services timeline, sharing the process timeline's
   .tw-svc-steps classes further down this file. */

/* ============================================================
   LINE-ICON FEATURE BLOCK — tw_icon_box() (brand guidelines §5)
   Not in token file — a thin-stroke SVG chip + card-title heading + description,
   the single line-icon language that replaced the classic FontAwesome icon-boxes
   (About "who we work with", Our Work / Services service cards, Services challenge &
   deliverable lists, partner "why choose us"). The chip is the 46px rounded-square form
   of that language, and now its only home: the homepage service cards used to mirror it,
   and became the numbered services timeline instead. Two layouts: default stacks
   the chip on top (card grids); --left sets it in a left gutter (services rows).
   ============================================================ */
.tw-ib-ico {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 13px;
  color: var(--tw-purple);
  background: var(--tw-lilac);
}
.tw-ib-ico svg { width: 24px; height: 24px; }
/* Stacked (default): chip over title over description. The chip widget stretches to the
   column width; the 46px chip inside it sits at the left. */
.tw-site .elementor .tw-iconbox:not(.tw-iconbox--left) {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Beside (services challenge/deliverable rows): chip in a left gutter spanning the
   title and description rows. */
.tw-site .elementor .tw-iconbox--left {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 16px;
  align-items: start;
}
.tw-iconbox--left > .elementor-widget-html { grid-column: 1; grid-row: 1 / span 2; }
.tw-iconbox--left .tw-ib-title { margin-bottom: 4px; }
/* Not in token file — the contact page's direct-route rows ("Call us" / "Email us").
   Their single-line links sat a full container row-gap below the title; collapse the
   grid's row gap so the title's own 4px margin controls the spacing. Scoped here so
   the services/partner icon rows (longer descriptions) keep their default rhythm. */
.tw-contact-direct .tw-iconbox--left { row-gap: 0; }
.tw-iconbox--left .tw-ib-desc {
  color: var(--tw-muted);
  font-size: var(--tw-fs-body-sm);
  line-height: 1.5;
}

/* ============================================================
   RECOGNITION — the "Credentials" wall (data-driven: recognition CPT + Loop Grids)
   Not in token file — Team Wheel's unified credentials set (accreditations,
   certifications, awards, memberships, ratings) in ONE list, no per-type sections.
   Two Loop Grids over the recognition CPT (see _components.php + the child-theme
   [tw_rec_*] shortcodes / recognition_* query filters):
     - index (the /credentials/ page, .tw-rec-index-loop) — stacked horizontal rows: the
       badge sits LARGE and free on the white card (NO plinth) beside the type label,
       title, issuer and blurb. Colour marks the TYPE via the type label.
     - strip (homepage component, .tw-rec-strip-loop) — a centred row of the featured
       badges, linking through.
   Where a credential has no badge, a type-coloured text wordmark stands in. On the white
   card every badge we hold reads (white-lockup art blends in).
   ============================================================ */

/* Per-type colour now lives in PHP: [tw_rec_type]/[tw_rec_token] emit the type label /
   wordmark with the wheel colour inline (accreditation #662483, certification #2f9c2b,
   award #dd4a1b, membership #c9880e, rating #0a93cf) — see tw_rec_type_colour() in the child
   theme. The former .tw-rec--<slug>{--c} class map is retired: a single loop-item template
   can't carry a per-item modifier class, so the colour moved to the shortcodes. */

/* ---- badge holder: no plinth — badge sits large, centred in its column ---- */
.tw-site .elementor .tw-rec-token {
  display: grid;
  place-items: center;          /* centres the badge; no width constraint needed */
  width: 100%;
  padding: 0;                   /* kill Elementor's default e-con 10px boxed padding so the
                                   badge sits flush and doesn't inflate the row height */
  gap: 0;
  text-align: center;           /* centre the inline badge <img> emitted by [tw_rec_token] */
}
.tw-site .elementor .tw-rec-token__badge {
  max-width: 100%;
  max-height: 124px;
  width: auto;
  height: auto;
  object-fit: contain;
}
.tw-site .elementor .tw-rec-token__wm {
  margin: 0;
  text-align: center;
  font-family: "Maven Pro", sans-serif;
  font-weight: var(--tw-fw-bold);
  font-size: 22px;
  line-height: 1.12;
}

/* ---- index layout (the /credentials/ page): a 1-col Loop Grid of stacked rows ----
   The rows are the "TW: Credential Row" loop item rendered by a loop-grid widget classed
   .tw-rec-index-loop; the grid CONTAINER (.elementor-loop-container) carries what the old
   .tw-rec-index flex column did. gap:0 so each row's border-bottom reads as the continuous
   hairline (Elementor's default grid gap would separate the rows and double the rules). */
.tw-site .elementor .tw-rec-index-loop .elementor-loop-container {
  grid-template-columns: 1fr;                 /* single column */
  gap: 0;
  border-top: 1px solid var(--tw-border);
}
.tw-site .elementor .tw-rec-row {
  display: grid;                              /* grid, not flex, so direction can't be
                                                orphaned by e-component ID remapping */
  grid-template-columns: 210px 1fr;
  gap: 40px;
  align-items: start;                         /* pin the badge to the TOP of the text: a long
                                                blurb must lengthen the row downward, not drag
                                                the badge to the vertical centre */
  padding: 22px 6px;
  border-bottom: 1px solid var(--tw-border);
}
/* No hover treatment: the row is not a link/hotspot, so a hover tint just implied
   clickability that isn't there (user steer 2026-07-29). */
/* The row's text pieces are now CLASSIC text-editor widgets ([tw_rec_*] shortcodes), so
   Elementor's default 20px inter-widget margin would swamp the deliberate 6/3/15px element
   margins below and flatten the hierarchy. Zero the widget spacing so the element margins are
   the sole authority — each flex-item widget establishes a BFC, so those margins stay
   contained and add up correctly across the gap:0 column. Also zero the e-con padding/gap. */
.tw-site .elementor .tw-rec-body { min-width: 0; padding: 0; gap: 0; }
.tw-site .elementor .tw-rec-body .elementor-widget { margin: 0; }
.tw-site .elementor .tw-rec-token .elementor-widget { margin: 0; }
.tw-site .elementor .tw-rec-token p { margin: 0; }   /* defensive: any wpautop <p> round the badge <img> */
/* Hierarchy is carried by GROUPING, not uniform gaps: the type label captions the title
   and the issuer byline locks right under it — one tight identity cluster (2–4px internal
   gaps) — then a single clear break drops to the blurb. Line-heights are pinned tight on the
   single-line elements so leading doesn't reinflate those gaps. */
.tw-site .elementor .tw-rec-eyebrow {
  margin: 0 0 6px;
  font-weight: var(--tw-fw-semibold);
  font-size: var(--tw-fs-badge);
  line-height: 1.2;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.tw-site .elementor .tw-rec-title {
  margin: 0;
  font-size: var(--tw-fs-h3);                 /* 24px — the row headline, a clear step up
                                                from the 18px card-title default */
  font-weight: var(--tw-fw-bold);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--tw-text);
}
/* Issuer = the awarding body, set as the title's byline: display face (Maven Pro) medium,
   pinned tight under the title so it reads as one unit with it, not a stray line. */
.tw-site .elementor .tw-rec-issuer {
  margin: 3px 0 0;
  font-family: "Maven Pro", sans-serif;
  font-weight: var(--tw-fw-semibold);
  font-size: var(--tw-fs-body-sm);
  line-height: 1.25;
  letter-spacing: 0.005em;
  color: var(--tw-muted);
}
.tw-site .elementor .tw-rec-blurb {
  margin: 15px 0 0;                           /* the one clear break — separates the blurb
                                                from the identity cluster above */
  font-size: var(--tw-fs-body-sm);
  line-height: var(--tw-lh-normal);
  color: var(--tw-muted);
  max-width: 62ch;
}

/* ---- homepage strip: a featured-only Loop Grid styled as the shared logo strip ----
   Not in token file — the strip is now the "TW: Credential Chip" loop item rendered by a
   loop-grid widget classed .tw-rec-strip-loop. Each .tw-logo-chip cell keeps the shared
   logo-strip device (alpha-blend + desaturate-then-colour hover) the partner & client rows
   use, so the three homepage strips still read as one family. A loop grid emits
   .elementor-loop-container (a CSS grid) rather than the flex row tw_logo_row produced, so
   re-plumb the container to the centred flex-wrap the old .tw-logo-strip--recognition had. */
.tw-site .elementor .tw-rec-strip-loop .elementor-loop-container {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  column-gap: 56px;             /* match the partner strip's ~57px inter-mark gap — the
                                   largest of the three homepage strips — so all three align. */
  row-gap: 20px;
}
/* Badges sit LARGER than the flat logos (they carry internal detail and read as seals, not
   wordmarks) — roughly the partner-strip scale. Badges vary in aspect ratio far more than the
   pre-normalised logos, so cap WIDTH too: the compact seals grow with max-height while
   Factorial's wide 3:1 wordmark is held by the width cap, which keeps the row balanced (user
   2026-07-29: "bigger apart from factorial"). The badge-wall analogue of the logos' normalise. */
.tw-site .elementor .tw-rec-strip-loop .tw-logo-chip img { max-height: 112px; max-width: 186px; }

/* header -> wall -> link stack spacing as a class (survives e-component remapping) */
.tw-site .elementor .tw-rec-inner {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 32px;
}
.tw-rec-more { margin-top: 4px; text-align: center; }

/* ---- responsive: rows stack the badge above the text on narrow screens ---- */
@media (max-width: 600px) {
  .tw-site .elementor .tw-rec-row { grid-template-columns: 1fr; gap: 18px; justify-items: center; }
  .tw-site .elementor .tw-rec-body { text-align: center; }
}

/* ============================================================
   CASE STUDIES — homepage curated block (docs/sketches .case-grid)
   Not in token file — three identical cards in one row (Lore, Xenco, Abbeyfield).
   Sits on the alternating soft-lilac band (TW_CASES_BAND, a per-element section bg).
   Built by tw_component_case_studies_cards().

   The sketch's featured card (a wider 1.15fr track, a lilac wash, a logo instead of a
   title, a three-cell proof bar) is gone: the client found the resulting cards
   inconsistent and asked for one size and one running order (2026-07-15). Every rule
   below therefore applies to every card — resist adding a .tw-case-card--featured.

   Card anatomy: photo (+ overlaid platform pill) / sector eyebrow / title / summary /
   one stat / link. The photo replaced a client wordmark slot in the body — same feedback
   round, follow-up steer: the logos "don't work on those cards" (2026-07-15).
   ============================================================ */
/* The row is a CSS grid, not flex — three equal tracks, each card filling its own (so
   .e-con-full's width:100% is a non-issue). Scoped under .tw-site .elementor to
   beat Elementor's .e-con display:flex base (equal specificity would lose, as that base
   loads after design.css). */
.tw-site .elementor .tw-cases-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  /* align-items:stretch — all three cards share one height. The "dead gap" risk (a
     stranded bottom link in a shorter card) is handled structurally: the proof footer is
     pinned to the card base via .tw-case-proof-w { margin-top:auto }, so any leftover
     height lands as one gap ABOVE the footer, not a hole below it — and the balanced
     summaries fill that gap. Equal height reads as a deliberate set. */
  align-items: stretch;
}
@media (max-width: 900px) {
  .tw-site .elementor .tw-cases-grid { grid-template-columns: 1fr; }
}
/* Not in token file — 2-column variant of the cases grid (Team page bio cards: four
   cards as 2×2). The compound selector is (0,4,0), so it needs its own mobile override —
   the base rule's 900px single-column reset above is only (0,3,0) and would lose. */
.tw-site .elementor .tw-cases-grid.tw-cases-grid--2 {
  grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 900px) {
  .tw-site .elementor .tw-cases-grid.tw-cases-grid--2 { grid-template-columns: 1fr; }
}
/* Hover lift (sketch .case:hover) is inherited from the base .tw-card rule — every case
   card carries .tw-card (TW_CLASS_CARD), so the shared treatment lives there. */

/* Card photo — full-bleed to the card's top edge, with the platform pill overlaid. The
   ratio lock and the brand duotone come from the shared .tw-media / .tw-media--brand frame
   (see "MEDIA FRAME + BRAND DUOTONE"); only the card-side plumbing lives here. Replaced a
   client wordmark between title and summary, which read as a stray asset on a white card
   (client feedback 2026-07-15).
   overflow:hidden lets .tw-media's border-radius:inherit clip the photo to the card's
   rounded top corners — .tw-card itself only sets the radius. */
.tw-case-card { overflow: hidden; }
/* The frame is a flex item in the card's column, so it would otherwise shrink under the
   body's content and lose its ratio.
   border-radius:0 undoes the shared .tw-media { border-radius: inherit }, which rounds the
   photo's BOTTOM corners here and leaves two white notches at the seam with the body. That
   rule is a no-op on the Insights cards — their media's parent is a boxed container's
   .e-con-inner (radius 0), so `inherit` resolves to 0 and the card's overflow:hidden does
   all the clipping. This card is built with tw_col's content_width:full, so it renders as
   .e-con-full with no .e-con-inner and the frame inherits the card's full 28px direct.
   Match Insights: no radius of its own, let overflow:hidden clip the top corners. */
.tw-site .elementor .tw-case-card__media {
  flex-shrink: 0;
  border-radius: 0;
}
/* Body grows to fill the card below the photo, so the proof bar's margin-top:auto reaches
   the card base and the footers line up across the row. Drives Elementor's own --flex-grow
   variable (consumed by .e-con { flex-grow: var(--flex-grow) }); the inner runs full height
   so margin-top:auto has room to push. Same mechanism as the Insights card body. */
.tw-site .elementor .tw-case-card__body { --flex-grow: 1; }
.tw-case-card__body > .e-con-inner { height: 100%; }

/* --- Type hierarchy, scoped to the cases block ------------------------------------
   The shared TW_TYPE_* tokens are used by ~11 other call sites, so these steps are
   scoped here rather than changed globally. Goal: three clearly distinct levels
   (category label < card title < metric proof), which the flat 13/20/17px scale lost. */
/* Card category label: the badge rung (12px), below the section eyebrow (18px, see
   .tw-sec-top .tw-type-eyebrow) so a card-level label doesn't weigh the same as the
   section-level one. */
.elementor .tw-cases-grid .tw-type-eyebrow {
  font-size: var(--tw-fs-badge);
  letter-spacing: 0.12em;
}
/* Card title: the card title base (18px) sat barely above the body, so "Xenco"/"Abbeyfield"
   hardly read as titles. Promote to the h3 subsection rung (24px) for a clear step, and
   brand purple makes the client name — not the grey category label above it — the thing the
   eye lands on. This is where the section's purple gets weight; the eyebrow above stays
   small so it doesn't compete. (Lore is included now: it used to have no title at all, its
   logo standing in for one.) */
.elementor .tw-case-card .tw-type-card-title {
  font-size: var(--tw-fs-h3);
  line-height: 1.12em;
  color: var(--tw-purple);
}

/* The platform pill (the HR system the work was on: Workday, Dayforce) is now the shared
   .tw-tag overlay inside .tw-media — the same device, and the same top-left inset, as the
   Insights cards' category pill — so it needs no positioning of its own here. It sits over
   the photo, out of the body's flow, so a card with no single named product (Xenco combined
   two unnamed legacy systems) just omits it without changing the card's size or child order.
   This replaced a .tw-pill--sm chip absolutely positioned against the card itself. */

/* Proof bar — was ragged inline "figure + label" lines that stepped because each label
   started after a different-width figure. Now number-over-label in equal, hairline-divided
   cells: figures share one baseline (tabular-nums) so nothing steps. Built by
   tw_case_proof_bar(); every homepage card now passes exactly one stat (the old featured
   card's three cells were part of what made it a different height from its neighbours), but
   the bar still lays out any number of cells. */
.tw-case-proof {
  padding-top: 20px;
  border-top: 1px solid var(--tw-border);
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;            /* equal cells → figures align across the bar */
}
/* Pin the footer to the card base. The flex child of the stretched card is the html
   widget WRAPPER, not the inner .tw-case-proof — so margin-top:auto lives here. */
.tw-case-proof-w { margin-top: auto; }
.tw-case-proof .tw-case-stat { padding: 0 18px; }
.tw-case-proof .tw-case-stat:first-child { padding-left: 0; }
.tw-case-proof .tw-case-stat + .tw-case-stat { border-left: 1px solid var(--tw-border); }
.tw-case-stat__num {
  display: block;
  /* 24px — was 28px, with .tw-case-proof--sm stepping the single-stat cards down to 24.
     Now that every card carries one stat in a third-width track, the compact figure is the
     only one left, so it's the base and the modifier is gone. */
  font-size: 24px;
  font-weight: var(--tw-fw-bold);
  color: var(--tw-purple);
  letter-spacing: -0.01em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.tw-case-stat__cap {
  display: block;
  margin-top: 8px;
  font-size: var(--tw-fs-badge);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--tw-muted);
  line-height: 1.3;
}
/* Narrow viewports: stack a multi-cell bar to rows so figures don't crush. */
@media (max-width: 520px) {
  .tw-case-proof { grid-auto-flow: row; grid-auto-columns: auto; gap: 16px; }
  .tw-case-proof .tw-case-stat { padding: 0; }
  .tw-case-proof .tw-case-stat + .tw-case-stat {
    border-left: 0;
    border-top: 1px solid var(--tw-border);
    padding-top: 16px;
  }
}

/* Not in token file — customer-logo trust row below the case cards (built by
   tw_component_case_studies_cards() when $logos is passed). Sits inside the cases
   section, separated from the card grid by a hairline rule + generous top space. The
   chips themselves reuse .tw-logo-strip / .tw-logo-chip; this only handles the wrapper
   spacing + the centred "Trusted by" caption. */
.tw-cases-logos {
  margin-top: 20px;
  padding-top: 40px;
  border-top: 1px solid var(--tw-border);
  align-items: center;
  text-align: center;
}
/* Caption sits centred above the row (the eyebrow token is purple; keep it muted here
   so it reads as a quiet label, not a second section eyebrow competing with "Our work"). */
.tw-cases-logos .tw-type-eyebrow {
  color: var(--tw-muted);
}
/* Not in token file — size of the client "trusted by" logos. 80px: the client asked for
   them larger, with more air between them, 2026-07-15 (dropping PPHE from the set at the
   same time freed the width to spend). The client logos are built on a 450px canvas
   (build-logos.py client set), so 80px renders ≈ 300/450×80 ≈ 53px of ink — which lands
   close to the partner strip's ≈ 300/640×120 ≈ 56px (its 10.5cqw at the 1140px max-width).
   An earlier version of this rule sized the row for exact parity with a partner strip that
   was then a flat 100px; that strip has since gone container-query sized, so parity is a
   coincidence here, not the constraint — the client's request is. Scoped to .tw-cases-logos
   so the About/Services partner strips (base .tw-logo-chip 52px) are untouched.

   Per-logo size/baseline is NOT tuned here — it's baked into each PNG by build-logos.py's
   scale/dy knobs. That script's client `strip_h` must track this max-height, or every dy in
   the set silently stops meaning on-page pixels. */
.tw-cases-logos .tw-logo-chip img {
  max-height: 88px;
}
/* Full-bleed the client logo ROW (same break-out as .tw-logo-strip--partners): at this size
   the six logos don't fit the boxed content column, so the row spans the viewport to sit on
   one line (~1155px of ink+gaps at 1280). Only the row breaks out — the .tw-cases-logos
   wrapper (top divider + centred "Trusted by" caption) stays in the content column. The
   section is centred in the viewport, so left:50% + translateX(-50%) + 100vw centres cleanly
   (identical maths to the partner strip). Steps size AND gap down, then allows wrap, on
   narrower viewports so it never overflows into a horizontal scroll.

   column-gap is set explicitly at every step rather than inherited from .tw-logo-strip's
   base 32px, because this selector is (0,2,0): it beats the generic
   `@media (max-width:768px) { .tw-logo-strip { gap: 12px } }` rule further down the file
   (0,1,0) whatever the source order, so a mobile value has to be restated here or phones
   get the desktop gap on a wrapped strip. Same trap .tw-logo-strip--partners documents. */
.tw-cases-logos .tw-logo-strip {
  flex-wrap: nowrap;
  column-gap: 36px;   /* the six logos fill one full-bleed line, so a larger logo (88px, bumped for
                         weight parity with the partner strip) has to trade some inter-mark gap to
                         stay on that one line at the 1200px breakpoint floor */
  /* Opt out of the wrapper's align-items:center — it pre-centres this wider-than-
     container row, which then stacks with the left:50%/translateX break-out and shifts
     the whole strip left. flex-start makes the break-out maths match the partner strip. */
  align-self: flex-start;
  width: 100vw;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
}
@media (max-width: 1200px) {
  .tw-cases-logos .tw-logo-chip img { max-height: 68px; }
  .tw-cases-logos .tw-logo-strip { column-gap: 36px; }
}
@media (max-width: 1000px) {
  .tw-cases-logos .tw-logo-strip { flex-wrap: wrap; column-gap: 28px; }
  .tw-cases-logos .tw-logo-chip img { max-height: 60px; }
}
/* Phones: match the intent of the generic `.tw-logo-strip { gap: 12px }` rule at this same
   breakpoint, which this (0,2,0) selector would otherwise beat — see the note above. */
@media (max-width: 768px) {
  .tw-cases-logos .tw-logo-strip { column-gap: 16px; }
}

/* Not in token file — lead magnets gradient wrapper matching mockup lead-engine */
.tw-lead-engine {
  background: linear-gradient(135deg, rgba(102, 36, 131, 0.08), rgba(0, 159, 227, 0.04));
  border-radius: 34px;
  border: 1px solid var(--tw-border);
}

/* Lead-magnet card row — CSS grid so the three cards fill the lead-engine's inner width
   (sketch .lead-grid{repeat(3,1fr)}), like the props/services/cases grids. Previously
   fixed-width flex cards (width:30%) left a gap on the right inside the box. Scoped (0,3,0)
   to beat Elementor's .e-con display:flex base. */
.tw-site .elementor .tw-lead-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  align-items: stretch;
}
/* Not in token file — 2-up variant of the lead-magnet grid, applied by
   tw_component_lead_magnets() when a page has exactly two cards (e.g. home /
   resources after the maturity-assessment + events consolidation) so the pair
   fills the row rather than leaving an empty third column. */
.tw-site .elementor .tw-lead-grid--2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 900px) {
  .tw-site .elementor .tw-lead-grid { grid-template-columns: 1fr; }
}

/* Not in token file — lead magnet tag pill (replaces icon-box kicker) */
.tw-tag {
  display: inline-block;
  font-size: var(--tw-fs-badge);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: var(--tw-fw-bold);
  color: var(--tw-purple);
  background: var(--tw-lilac);
  border-radius: 999px;
  padding: 8px 12px;
}

/* Not in token file — checklist used inside lead cards and hero right column */
.tw-checklist {
  list-style: none;
  padding: 0;
  margin: 16px 0 0;
  display: grid;
  gap: 10px;
}
.tw-checklist li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  /* Body-sm rung (15px) — same dense-body size as card copy, so scannable tick items sit
     level with the description rather than out-sizing it. Colour inherits (ink), matching
     the sketch: these are scannable feature points, not faded prose. */
  font-size: var(--tw-fs-body-sm);
  line-height: 1.4;
}
/* Not in token file — circled checkmark inside .tw-checklist li (artifact-adopted
   2026-07-05): an 18px tinted circle holding a clean stroke check, replacing the previous
   bare 18px "✓" glyph. The literal ✓ stays in the scaffold markup (hidden via font-size: 0
   — it still reads to screen readers and survives Elementor's editor-save content
   sanitizer, which inline <svg> might not); the visible check is the artifact's exact
   stroke path as a ::after data-URI background, drawn per colour context below. Base =
   light-background treatment (soft green circle, green check); the glass-card variant
   (see .tw-card--glass .tw-tick) flips to a solid green circle with a white check. */
.tw-tick {
  flex: 0 0 auto;
  position: relative;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(58, 170, 53, 0.16);
  margin-top: 2px;
  font-size: 0;
}
/* inset: 0 (not grid placement) — the hidden ✓ text node would otherwise occupy a grid
   row of its own and push the drawn check below centre. */
.tw-tick::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 6.5 5 9.5 10 3' fill='none' stroke='%233aaa35' stroke-width='2.6'/%3E%3C/svg%3E") no-repeat center / 11px 11px;
}

/* Not in token file — partner role callout: the hook line inside a partnership role
   card (e.g. "Considering Workday? Team Wheel makes your implementation a success…").
   A tinted inset with a purple rail. Scoped under .tw-site .elementor so it
   beats .tw-card p (0,1,1) — otherwise the callout would inherit 15px muted card-body
   text instead of its own emphasised treatment. */
.tw-site .elementor .tw-callout {
  background: var(--tw-lilac);
  border-left: 3px solid var(--tw-purple);
  border-radius: 0 var(--tw-radius-sm) var(--tw-radius-sm) 0;
  padding: 14px 18px;
  font-weight: var(--tw-fw-semibold);
  color: var(--tw-text);
}

/* Not in token file — 2-up grid of partnership role cards (partner detail pages,
   fuller template). A real CSS grid so the two role cards
   sit side-by-side and equal-height, stacking below 780px. (0,3,0) beats the
   container's .e-con flex default. */
.tw-site .elementor .tw-role-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px;
  align-items: stretch;
}
@media (max-width: 780px) {
  .tw-site .elementor .tw-role-grid { grid-template-columns: 1fr; }
}

/* Not in token file — "Why choose Team Wheel?" feature grid (fuller partner pages).
   Four icon cards filling the band, replacing the two floating tick columns that read
   as lost whitespace. (0,3,0) beats the container's .e-con flex default; wraps 4→2→1. */
.tw-site .elementor .tw-why-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
  align-items: stretch;
}
@media (max-width: 900px) {
  .tw-site .elementor .tw-why-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
  .tw-site .elementor .tw-why-grid { grid-template-columns: 1fr; }
}
/* The "why choose us" cards use the shared line-icon block (.tw-iconbox / .tw-ib-ico,
   see the LINE-ICON FEATURE BLOCK section) — no per-card chip rule needed here. */

/* Not in token file — proof block (fuller partner pages): a coloured stat panel beside
   the client story, giving the metrics real size and colour instead of small inline
   figures. Two-column grid inside a bordered, clipped card; stacks below 760px. */
.tw-site .elementor .tw-proof {
  display: grid;
  /* Purple stats column: each stat is a horizontal row (figure + label side by side, see
     .tw-proof-stat below), sized to hold the widest figure+label pair (e.g. "40%
     implementation discount secured") on one line without leaving a wide band of empty
     purple to its right. */
  grid-template-columns: 0.82fr 1.18fr;
  /* Force the container flush (the scaffold also zeroes these per-element, but pin them in
     CSS too so the purple panel always meets the card border — no stale seed data can leave
     it inset as a "purple box floating inside the card"). */
  padding: 0;
  gap: 0;
  border: 1px solid var(--tw-border);
  border-radius: var(--tw-radius-lg);
  overflow: hidden;
  box-shadow: var(--tw-shadow);
}
@media (max-width: 760px) {
  .tw-site .elementor .tw-proof { grid-template-columns: 1fr; }
}
/* Left panel — solid purple as a background-color on the element. The parent .tw-proof
   zeroes its atomic padding/gap (in the scaffold) so this panel meets the card border
   directly and overflow:hidden clips it to the rounded corners; the earlier "purple square
   inside the rounded card" look was that padding inset leaving a white margin all round,
   not a clipping failure. White stat rows are divided by hairlines. */
.tw-proof-stats {
  /* Purple gradient (same language as the hero / CTA band) rather than a flat fill —
     deep purple top-left easing to a lighter tint bottom-right, giving the stats panel
     depth. Applies to every proof card. */
  background: linear-gradient(150deg, #4b1661 0%, #662483 55%, #7d3697 100%);
}
/* Spread the (now single-line) stat rows to fill the panel height — overrides the scaffold's
   centred justify, which otherwise left dead purple above and below the shorter rows. */
.tw-site .elementor .tw-proof-stats {
  justify-content: space-between;
}
.tw-proof-stat + .tw-proof-stat {
  border-top: 1px solid rgba(255, 255, 255, 0.18);
}
/* Each stat is a horizontal row: the big figure and its label sit side by side, with the
   label vertically centred to the figure (not stacked below it). */
.tw-site .elementor .tw-proof-stat {
  flex-direction: row;
  align-items: center;
  column-gap: 14px;
  flex-wrap: nowrap;
}
/* Big white metric figure on the purple panel (was the purple .tw-type-stat, which
   is invisible on purple and undersized here). */
.tw-proof-stat__n {
  font-size: clamp(1.4rem, 1.15rem + 0.7vw, 1.8rem);
  font-weight: var(--tw-fw-bold);
  letter-spacing: -0.015em;
  line-height: 1;
  color: var(--tw-white);
  white-space: nowrap;   /* keep the figure (incl. wordy ones like "18 months") on one line */
}
.tw-proof-stat__l {
  font-size: var(--tw-fs-body-sm);
  color: var(--tw-on-dark-2);
  margin: 0;             /* beside the figure now, not below — no top offset */
}
/* The story headline ("The right platform, chosen on evidence") — a step up from the
   card-title default so it anchors the story side. */
.tw-site .elementor .tw-proof-story h3 {
  font-size: 22px;
  line-height: 1.2;
}

/* Not in token file — co-branded paper alert on partner detail pages
   (scaffold-partners.php $mk_resource): a slim purple callout directly under
   the page intro flagging the joint white paper. The face (gradient, radius,
   shadow, white-on-purple copy) comes from the shared .tw-res-panel family;
   these rules only size the banner's parts. */
/* The cover thumb's html-widget wrapper must size to content, not the widget
   default 100%, so the copy column beside it gets the width. */
.tw-site .elementor .tw-ptn-paper__coverw {
  width: auto;
  flex: 0 0 auto;
}
.tw-ptn-paper__cover img {
  display: block;
  height: 92px;
  width: auto;
  border-radius: 3px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  box-shadow: 0 10px 22px rgba(27, 18, 39, 0.35);
}
/* e-con children size via Elementor's --flex-* variables (same mechanism as the
   .tw-res-panel--mini copy rule above); the plain flex shorthand loses to them.
   Growing the copy column is what pushes the CTA to the banner's right edge. */
.tw-ptn-paper > .e-con.tw-ptn-paper__copy {
  --flex-grow: 1;
  --flex-shrink: 1;
  --flex-basis: 0%;
  min-width: 0;
}
.tw-ptn-paper__meta { font-size: 0.85rem; }
@media (max-width: 1024px) {
  .tw-ptn-paper__cover img { height: 76px; }
}

/* ============================================================
   SERVICE DETAIL PAGES — journey rail + flowing sections
   Not in token file — the redesigned service detail template
   (scaffold-services.php sub-pages). Replaces the previous eight
   disconnected bands. The rail encodes the real HR-systems lifecycle
   (Selection → Implementation → Optimisation → Recovery/AMS); the current
   page's stage is marked with a .tw-journey--active-N modifier class
   (N = 1-based stage), NOT an inline style, so the fill line is data-driven
   without breaking the no-inline-style rule. The rail is rendered as raw
   HTML in a classic text widget (same idiom as the partner .tw-checklist).
   ============================================================ */
.tw-journey__label {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}
.tw-journey__label .tw-type-badge { color: var(--tw-purple); }
.tw-journey__rule { height: 1px; flex: 1; background: var(--tw-border); }
.tw-journey__track {
  position: relative;
  display: flex;
  justify-content: space-between;
  gap: 12px;
}
/* Grey base line + purple fill line between the dot centres (dots sit at 12.5%,
   37.5%, 62.5%, 87.5% of the track, so the line spans left:12.5%..right:12.5%). */
.tw-journey__track::before,
.tw-journey__track::after {
  content: "";
  position: absolute;
  top: 18px;
  left: 12.5%;
  height: 2px;
}
.tw-journey__track::before { right: 12.5%; background: var(--tw-border); }
.tw-journey__track::after { width: 0; background: var(--tw-purple); }
.tw-journey--active-2 .tw-journey__track::after,
.tw-journey__track.tw-journey--active-2::after { width: 25%; }
.tw-journey--active-3 .tw-journey__track::after,
.tw-journey__track.tw-journey--active-3::after { width: 50%; }
.tw-journey--active-4 .tw-journey__track::after,
.tw-journey__track.tw-journey--active-4::after { width: 75%; }
.tw-journey__stage {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  flex: 1 1 0;
  text-align: center;
  text-decoration: none;
}
.tw-journey__dot {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--tw-white);
  border: 2px solid var(--tw-border);
  color: var(--tw-muted);
  font-weight: var(--tw-fw-bold);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  position: relative;
  z-index: 1;
  transition: 0.18s;
}
.tw-journey__name {
  font-size: var(--tw-fs-body-sm);
  font-weight: 600;
  color: var(--tw-muted);
  line-height: 1.25;
  max-width: 15ch;
}
.tw-journey__stage.is-done .tw-journey__dot {
  border-color: var(--tw-purple);
  color: var(--tw-purple);
  background: var(--tw-lilac);
}
.tw-journey__stage.is-done .tw-journey__name { color: var(--tw-text); }
.tw-journey__stage.is-active .tw-journey__dot {
  background: var(--tw-purple);
  border-color: var(--tw-purple);
  color: var(--tw-white);
  box-shadow: 0 0 0 5px rgba(102, 36, 131, 0.14);
}
.tw-journey__stage.is-active .tw-journey__name { color: var(--tw-purple); font-weight: var(--tw-fw-bold); }
.tw-journey__stage:hover .tw-journey__name { color: var(--tw-purple); }
@media (max-width: 640px) {
  /* Stack to a wrapped row of pill chips; the connecting line is dropped and only
     the active stage keeps its label so the current step still reads at a glance. */
  .tw-journey__track::before,
  .tw-journey__track::after { display: none; }
  .tw-journey__track { flex-wrap: wrap; justify-content: flex-start; gap: 10px; }
  .tw-journey__stage {
    flex: 0 0 auto;
    flex-direction: row;
    gap: 9px;
    text-align: left;
    background: var(--tw-white);
    border: 1px solid var(--tw-border);
    border-radius: 999px;
    padding: 5px 12px 5px 5px;
  }
  .tw-journey__dot { width: 30px; height: 30px; font-size: 12px; }
  .tw-journey__name { max-width: none; font-size: var(--tw-fs-caption); }
  .tw-journey__stage:not(.is-active) .tw-journey__name { display: none; }
  .tw-journey__stage.is-active { border-color: var(--tw-purple); }
}

/* Header two-column row inside the tinted .tw-page-intro shell: copy left, the
   "is this the right stage?" snapshot card right. Grid override needs the
   .tw-site .elementor scope to beat the container's .e-con flex default. */
.tw-site .elementor .tw-svc-intro__row {
  display: grid;
  grid-template-columns: 1.55fr 1fr;
  gap: 44px;
  align-items: start;
}
@media (max-width: 900px) {
  .tw-site .elementor .tw-svc-intro__row { grid-template-columns: 1fr; gap: 28px; }
}
/* Benefit sub-headline sitting under the H1 service name — a real step below the
   name, above the muted lead. */
.tw-svc-outcome {
  font-size: clamp(19px, 1.5vw, 23px);
  font-weight: var(--tw-fw-semibold);
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--tw-text);
  max-width: 26ch;
}

/* Challenge + approach: problem list left, approach card right (merges the old
   "Sound familiar?" and "Our approach" sections into one problem→answer pair). */
.tw-site .elementor .tw-svc-pa {
  display: grid;
  grid-template-columns: 1fr 0.92fr;
  gap: 40px;
  align-items: start;
}
@media (max-width: 900px) {
  .tw-site .elementor .tw-svc-pa { grid-template-columns: 1fr; gap: 28px; }
}

/* Icon lists — challenges (1-col) and deliverables (2-col grid). Each item is a
   line-icon block in the --left layout (chip beside title+desc — the chip, layout and
   description type all come from .tw-iconbox, see the LINE-ICON FEATURE BLOCK section);
   only the deliverables grid and the hairline dividers live here. */
.tw-site .elementor .tw-svc-deliv {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 40px;
}
@media (max-width: 760px) {
  .tw-site .elementor .tw-svc-deliv { grid-template-columns: 1fr; }
}
.tw-svc-challenges .tw-iconbox,
.tw-svc-deliv .tw-iconbox {
  padding: 20px 0;
  border-top: 1px solid var(--tw-border);
}
.tw-svc-challenges .tw-iconbox:first-child,
.tw-svc-deliv .tw-iconbox:nth-child(-n+2) {
  border-top: none;
  padding-top: 0;
}
@media (max-width: 760px) {
  /* Back to one column — restore the divider on the 2nd deliverable. */
  .tw-svc-deliv .tw-iconbox:nth-child(2) {
    border-top: 1px solid var(--tw-border);
    padding-top: 20px;
  }
}

/* Deliverables closing line — a lilac callout at lead size (reuses .tw-callout
   look but wider/emphasised). */
.tw-svc-deliv-outro {
  margin-top: 26px;
  background: var(--tw-lilac);
  border-left: 3px solid var(--tw-purple);
  border-radius: 0 var(--tw-radius-sm) var(--tw-radius-sm) 0;
  padding: 16px 20px;
  font-weight: var(--tw-fw-semibold);
  font-size: var(--tw-fs-lead);
}

/* Process / services timeline — one connected numbered sequence (a real one, so the
   numbers are earned) replacing four separate cards. Two pages share these classes and
   the same 01–04 markers, deliberately: the /services/ stage pages' "How we work" phases,
   and the home page's four services (which are the four stages of the same lifecycle
   those pages document). One connector implementation below serves both, so they can't
   drift — home adds only --centred, see there.
   The connecting line is drawn PER STEP as
   a segment from this circle's centre to the next circle's centre, rather than one
   absolute bar across the grid: the circles are left-aligned in their columns, so
   a single percentage-positioned line drifts off them. Each step column is
   position:relative with zero padding (set in the scaffold), so the 40px circle's
   centre sits at exactly (20px, 20px) from the column's top-left — the segment
   starts there (left/top: 20/19px) and runs one column + one 24px grid gap wide to
   land on the next circle's centre. The white circles (z-index 1) clip both ends. */
.tw-site .elementor .tw-svc-steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
}
.tw-site .elementor .tw-svc-step { position: relative; }
.tw-svc-step:not(:last-child)::after {
  content: "";
  position: absolute;
  top: 19px;
  left: 20px;
  width: calc(100% + 24px);
  height: 2px;
  background: repeating-linear-gradient(90deg, var(--tw-purple) 0 8px, transparent 8px 16px);
  opacity: 0.5;
  z-index: 0;
}
@media (max-width: 760px) {
  .tw-site .elementor .tw-svc-steps { grid-template-columns: 1fr 1fr; gap: 28px 24px; }
  /* Connectors only make sense on the single 4-across row. */
  .tw-svc-step:not(:last-child)::after { display: none; }
}
@media (max-width: 460px) {
  .tw-site .elementor .tw-svc-steps { grid-template-columns: 1fr; }
}
/* Scoped to (0,3,0) to beat the e-heading-base baseline that would otherwise
   impose the heading's own size on the numbered circle. */
.tw-site .elementor .tw-svc-step__n {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--tw-white);
  border: 2px solid var(--tw-purple);
  color: var(--tw-purple);
  display: grid;
  place-items: center;
  /* Tag-independent, and load-bearing for the home page: the Kit sets Maven Pro per
     h1-h6 only, and home's figure renders as <p> (a decorative number, deliberately kept
     out of the document outline), so without this the two pages' numerals fall on
     different faces — Open Sans on home, Maven Pro on /services/. Same pin, same reason,
     as .tw-type-card-title and the proof stat figures. */
  font-family: "Maven Pro", sans-serif;
  font-weight: var(--tw-fw-bold);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  position: relative;
  z-index: 1;
}
/* Centred markers — the home page's variant: each marker sits over the middle of its own
   text column instead of at its left edge, and the connector segment then runs from this
   marker's centre (50%) to the next one's, one column + one grid gap away — the same
   "centre to centre" geometry as the left-aligned base, re-anchored. A modifier rather
   than a change to .tw-svc-step, because the /services/ "How we work" timeline shares
   these classes and keeps its left-aligned markers.
   The copy is centred with the marker rather than left under it: a centred circle over a
   ragged-right block reads as a mistake, and centring both makes each step one balanced
   unit on the line. align-items does the horizontal centring of the marker itself (it's a
   flex child of the step column), so no per-marker rule is needed. */
.tw-svc-steps--centred .tw-svc-step {
  text-align: center;
  align-items: center;
}
/* Extra room under the marker, on top of the step column's 12px gap. Centring changes
   what the marker row IS: left-aligned, each marker reads as the top of its own column
   and 12px is right; centred, the four markers and the line between them read as one
   band across the section, and that band needs separating from the text block below or
   the titles crowd it. Margin on the marker rather than a wider column gap, so the
   title/description/link keep their existing rhythm. */
.tw-svc-steps--centred .tw-svc-step__n { margin-bottom: 10px; }
.tw-svc-steps--centred .tw-svc-step:not(:last-child)::after {
  left: 50%;
  width: calc(100% + 24px);
}
/* Filled markers — solid purple disc, white numeral, instead of the outlined circle the
   /services/ process timeline uses. With the connector now a quiet neutral rule, the
   markers are the only purple left in the row, so filling them keeps the sequence
   legible from across the page: four solid dots read as stations at a glance where four
   thin rings recede. Scoped to (0,4,0) — the base marker's background/colour are set at
   (0,3,0) to beat Elementor's atomic-heading baseline, so a bare modifier would lose. */
.tw-site .elementor .tw-svc-steps--filled .tw-svc-step__n {
  background: var(--tw-purple);
  color: var(--tw-white);
}
/* Home markers wrap the number in a link — V4 renders <p.tw-svc-step__n><a>01</a></p>.
   Expand the link to fill the whole disc so the entire bubble is the click target (and beat
   Elementor's .e-paragraph-base a { all:unset } reset), then lift the disc on hover. */
.tw-site .elementor .tw-svc-step__n > a {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  color: inherit;
  text-decoration: none;
}
.tw-site .elementor .tw-svc-step__n:has(> a) {
  cursor: pointer;
  transition: transform 0.18s, box-shadow 0.18s;
}
.tw-site .elementor .tw-svc-step__n:has(> a):hover {
  transform: scale(1.08);
  box-shadow: 0 0 0 5px rgba(102, 36, 131, 0.16);
}

/* Solid connector — the quiet neutral rule the .tw-journey rail draws between its stage
   dots (same 2px, same --tw-border), instead of the dashed purple of the process
   timeline. On the home page the timeline is wayfinding between four services rather
   than a process being walked through, so the line should recede and let the numbered
   markers carry the sequence. Full opacity: the base connector's 0.5 is there to soften
   purple, and would leave this pale border colour barely visible.
   A separate modifier from --centred so the two decisions stay independently reusable. */
.tw-svc-steps--solid-line .tw-svc-step:not(:last-child)::after {
  background: var(--tw-border);
  opacity: 1;
}
/* Description grows so the "Explore this service" link pins to the bottom of every step,
   level across the row (the grid stretches all four columns to the tallest). Only the
   home page's steps carry a link, so only they need this. */
.tw-svc-step__desc { flex: 1; }

/* Continue-the-journey — prev/next as bubble+name links that echo the top journey rail.
   The numbered bubble reuses .tw-journey__dot; the whole link goes to that stage's page. */
.tw-site .elementor .tw-svc-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 20px 40px;
  justify-content: space-between;
}
.tw-site .elementor .tw-svc-nav__link {
  display: flex;
  align-items: center;
  gap: 16px;
  text-decoration: none;
}
/* A little larger than the top rail's 38px dots — this is the page's onward CTA. */
.tw-site .elementor .tw-svc-nav__link .tw-journey__dot {
  width: 48px;
  height: 48px;
  font-size: 15px;
}
.tw-svc-nav__body { display: flex; flex-direction: column; gap: 2px; }
.tw-svc-nav__meta {
  font-size: var(--tw-fs-badge);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: var(--tw-fw-semibold);
  color: var(--tw-muted);
}
.tw-svc-nav__name {
  font-size: var(--tw-fs-lead);
  font-weight: var(--tw-fw-bold);
  color: var(--tw-text);
  line-height: 1.2;
}
/* Onward bubble is filled purple to read as the primary next step; the back one keeps the
   outlined dot. Both lift on hover. */
.tw-site .elementor .tw-svc-nav__link--next .tw-journey__dot {
  background: var(--tw-purple);
  border-color: var(--tw-purple);
  color: var(--tw-white);
}
.tw-svc-nav__link:hover .tw-svc-nav__name { color: var(--tw-purple); }
.tw-site .elementor .tw-svc-nav__link:hover .tw-journey__dot {
  border-color: var(--tw-purple);
  box-shadow: 0 0 0 5px rgba(102, 36, 131, 0.14);
}
@media (max-width: 640px) {
  .tw-site .elementor .tw-svc-nav { flex-direction: column; }
}

/* Case study cards — featured first card with gradient tint */
.tw-card--featured {
  background: linear-gradient(145deg, var(--tw-white) 0%, var(--tw-offwhite) 100%);
}

/* Not in token file — metric pills inside featured case card */
.tw-metric-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 18px;
}
.tw-metric {
  border-radius: 14px;
  background: rgba(102, 36, 131, 0.08);
  padding: 10px 14px;
  font-weight: 700;
  color: var(--tw-purple);
  font-size: var(--tw-fs-caption);
}

/* ============================================================
   FOUNDER BLOCK — purple gradient panel, cover-fill portrait + pull quote.
   Not in token file — bespoke founder credibility panel from the homepage brief.

   Rendered as a native Elementor Component (tw_component_founder → TW_CUID_FOUNDER),
   so layout is CSS-CLASS-DRIVEN, not per-element settings: e-component ID remapping
   orphans per-element flex/width/background CSS (same mechanism documented for
   .tw-cta-band above). The panel is the container-query context so the quote scales
   to the panel width, not the viewport — matches the reviewed design sketch.
   ============================================================ */
.tw-founder {
  container-type: inline-size;
  border-radius: 34px;
  overflow: hidden;
  box-shadow: 0 18px 60px rgba(41, 10, 54, 0.14);
  background:
    radial-gradient(circle at 88% 8%, rgba(125, 54, 151, 0.5), transparent 46%),
    linear-gradient(135deg, #4b1661 0%, #662483 54%, #7d3697 100%);
  color: var(--tw-white);
}
/* Two equal-height columns via CSS grid (overriding Elementor's flex), set on the
   class so it survives e-component ID remapping. Grid stretches both cells to the
   tallest row automatically and reliably — unlike flex align-items:stretch, which
   can leave the EMPTY media column at its min-height in the editor canvas / some
   browsers, showing purple where the portrait should reach. .e-flex.tw-founder is
   (0,2,0) to beat Elementor's .e-con-full.e-flex on load order. */
.e-flex.tw-founder {
  display: grid;
  grid-template-columns: 41% 1fr;
  align-items: stretch;
  gap: 0;
  /* Kill Elementor's default .e-con 10px padding. The tw_pad(0) PHP setting is
     orphaned by e-component ID remapping (same reason layout is CSS-driven here),
     so 10px survives and frames the flush portrait with purple top/bottom. (0,2,0)
     beats Elementor's .e-con padding rule (0,1,0). */
  padding: 0;
}
.tw-founder > .e-con {
  --flex-basis: auto;
  --width: auto;
}
.tw-founder > .tw-founder__photo {
  position: relative;
  min-height: 460px;
}
.tw-founder > .tw-founder__body {
  min-width: 0;   /* let the 1fr text column shrink instead of overflowing */
}
.e-flex.tw-founder__body {
  flex-direction: column;
  justify-content: center;
  gap: 0;
}
.tw-founder__body {
  padding: clamp(40px, 5cqi, 64px);
}

/* Portrait: a real e-image widget (bare <img class="e-image-base">), cover-filling
   its column. NOT a container background image — that emits per-element-ID CSS that
   e-component remapping orphans, so it would vanish inside the component. Absolute
   positioning takes the img out of flex flow, sidestepping the e-image-base
   align-self/width stretch gotcha. Scoped (0,3,1) to beat the .e-image-base baseline. */
.tw-site .elementor .tw-founder__photo img {
  position: absolute;
  /* top/right/bottom/left longhands, NOT `inset: 0`: browsers without `inset`
     support (Safari < 14.1) drop the shorthand, leaving only height:100% — which
     collapses to auto against a grid-stretched parent, so the portrait renders at
     natural height with purple showing above/below. The longhands size the box
     against the column with no percentage-height dependency. */
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 16%;
}

/* Body type. Margin-bearing rules on the root V4 <p> are scoped under
   .tw-site .elementor to reach (0,3,0) and beat the atomic baseline
   `.elementor .e-paragraph-base { margin: 0 }` (0,2,0). Sizes scale via cqi
   against the .tw-founder container. */
.tw-site .elementor .tw-founder__eyebrow {
  font-weight: var(--tw-fw-bold);
  font-size: var(--tw-fs-caption);
  line-height: 1;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tw-on-dark-2);
  margin: 0 0 clamp(16px, 2.2cqi, 26px);
}
/* Was Georgia — see .tw-testi__qmark for the face choice and the sizing rationale. */
.tw-site .elementor .tw-founder__qmark {
  display: block;
  font-family: "Maven Pro", sans-serif;
  font-weight: 700;
  font-size: clamp(56px, 8cqi, 84px);
  line-height: 0.55;
  color: rgba(255, 255, 255, 0.26);
  height: 0.30em;
  margin: 0 0 0.1em;
}
.tw-site .elementor .tw-founder__quote {
  font-weight: 700;
  font-size: clamp(26px, 3.4cqi, 34px);
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0;
  max-width: 30ch;
  text-wrap: pretty;
}
.tw-site .elementor .tw-founder__who {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: clamp(24px, 3.2cqi, 38px) 0 0;
}
.tw-founder__who .nm {
  font-weight: 700;
  font-size: clamp(15px, 1.5cqi, 18px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--tw-white);
}
.tw-founder__who .rule {
  display: inline-block;
  width: 28px;
  height: 2px;
  background: rgba(255, 255, 255, 0.42);
}
.tw-founder__who .rl {
  font-weight: 500;
  font-size: clamp(13px, 1.3cqi, 15px);
  line-height: 1.3;
  color: var(--tw-on-dark-2);
}
/* Not in token file — optional one-line factual credential under the founder
   attribution (About: "Founded in 2019…"). (0,3,0) to beat e-paragraph-base margin:0. */
.tw-site .elementor .tw-founder__facts {
  font-weight: 500;
  font-size: clamp(13px, 1.3cqi, 15px);
  line-height: 1.5;
  color: var(--tw-on-dark-2);
  margin: 10px 0 0;
}

/* Founder panel stacks below the header/hero breakpoint. */
@media (max-width: 768px) {
  .e-flex.tw-founder {
    grid-template-columns: 1fr;
  }
  .tw-founder > .tw-founder__photo {
    min-height: 0;
    height: clamp(300px, 54vw, 440px);
  }
}

/* ============================================================
   TEAM PAGE
   ============================================================ */

/* Not in token file — Team page founder feature: portrait media frame + bio copy,
   2-col desktop stacking to 1-col. Layout lives on the class (not per-element
   Elementor settings) per the same e-component-remapping rule as .tw-sec-top; grid
   because a wrap-based flex row can't hold the 5/7 proportion. (0,3,0) via the
   .tw-site .elementor scope to beat Elementor's .e-con.e-flex base. */
.tw-site .elementor .tw-team-founder {
  display: grid;
  grid-template-columns: minmax(280px, 5fr) 7fr;
  /* 0, not the old clamp(32px, 4.5vw, 64px): the portrait now runs hard into the card's
     left/top/bottom edges, so the gutter beside it is the copy column's own padding
     (scaffold-team.php) rather than a grid gap. A gap here would reopen a strip of card
     background down the photo's right edge and undo the hard seam. */
  gap: 0;
  align-items: center;
  /* Clips the portrait to the card's rounded outer corners — the same mechanism as
     .tw-case-card, and what lets the frame itself carry border-radius:0 so its seam with
     the copy stays square. .tw-card has no overflow of its own. */
  overflow: hidden;
}
/* Fill the full card height rather than centring at the frame's natural 3/4 height, so
   there's no card background above and below the bleed. Overrides align-items:center for
   the photo only — the copy stays vertically centred, which is what reads right when it's
   the shorter of the two. When the copy is the taller (narrow viewports, long bios) the
   photo grows with it and object-fit:cover absorbs the extra height. */
.tw-site .elementor .tw-team-founder > .tw-media { align-self: stretch; }
@media (max-width: 820px) {
  .tw-site .elementor .tw-team-founder { grid-template-columns: 1fr; }
  /* Stacked: the photo is a full-width band across the card top and its height comes back
     from the frame's own 3/4 ratio, so stretch has to go — otherwise it would inherit the
     row height the copy sets. */
  .tw-site .elementor .tw-team-founder > .tw-media { align-self: auto; }
}

/* Not in token file — founder pull-quote (real testimonial excerpt) on the Team page:
   one text widget, purple accent bar, muted single-line attribution. */
.tw-bio-quote {
  border-left: 3px solid var(--tw-purple);
  padding-left: 18px;
}
.tw-bio-quote em { display: block; }
.tw-bio-quote small {
  color: var(--tw-muted);
  font-size: var(--tw-fs-caption);
}

/* Not in token file — pins the bio card's footer row to the card base so it lines up
   across the 2×2 grid whatever each bio's length. Same mechanism as the case cards'
   .tw-case-proof-w; the body's --flex-grow:1 (shared .tw-case-card__body rule) gives
   the auto margin room to push. Named for the slot, not its contents: it held a
   platform-specialism pill row until 2026-07-30, when that was replaced by a LinkedIn
   micro-link (platform expertise only describes the delivery roles, so the pills read
   as filler on a Brand Director or Head of Marketing card). */
.tw-bio-foot {
  margin-top: auto;
  /* Right-justified so the link reads as a card action rather than another line of
     body copy — it's the only interactive thing in the card, and left-aligned it
     disappeared into the bio's last paragraph. text-align (not flex) because the
     .tw-micro-link inside is inline-block. */
  text-align: right;
}

/* Not in token file — the LinkedIn profile link on the Team page's bio cards. Lays the
   brand mark and the label on one baseline; inline-flex so the pair stays glued when the
   line wraps, and so text-align:right on the parent still positions it. */
.tw-site .elementor .tw-linkedin-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
/* Sized in em, so the mark tracks the link's font-size instead of needing a pixel value
   per context. 0.95 rather than 1: the bug is a solid square and reads visually heavier
   than the cap-height beside it at matched size. */
.tw-linkedin-mark {
  width: 0.95em;
  height: 0.95em;
  flex-shrink: 0;
}

/* ============================================================
   INSIGHTS ARCHIVE + SINGLE POST
   ============================================================ */

/* Not in token file — archive/index hero (Insights + Events). Rounded inset dark band,
   matching the flagship .tw-hero (Our Work / About) rather than a full-bleed strip
   (client request 2026-07-16): same 34px radius, shadow, side margins and large-screen
   width-growth curve, so all four purple headers read as one system. The filters/meta
   still live inside via the shared inner column. background_background:classic must be
   set on the container to prevent Elementor generating background:none which would
   clear this gradient. Inset margins are set on the container in tw_hero_card_masthead();
   the width curve is .e-con-full.tw-hero-card below (shared with .tw-hero). */
.tw-hero-card {
  background:
    radial-gradient(circle at 82% 18%, rgba(249,178,51,0.18), transparent 22%),
    radial-gradient(circle at 88% 35%, rgba(0,159,227,0.14), transparent 24%),
    linear-gradient(135deg, #4b1661 0%, #662483 54%, #7d3697 100%);
  color: var(--tw-white);
  --e-global-color-primary: var(--tw-white);
  border-radius: 34px;
  box-shadow: var(--tw-shadow);
}

/* Not in token file — inline CTA card, dark gradient used in archive + single templates.
   Structurally similar to .tw-cta-band but rendered as a card (has border-radius). */
.tw-cta-card {
  background: linear-gradient(135deg, #4b1661 0%, #662483 100%);
  border-radius: var(--tw-radius-lg);
  --e-global-color-primary: var(--tw-white);
}
.tw-cta-card .elementor-widget-text-editor p {
  color: var(--tw-on-dark-2);
}

/* Brand dot-swirl on the card-style dark bands (hero masthead + inline CTA card), matching
   the .tw-hero/.tw-cta-band decoration. z-index:-1 under a z-index:0 stacking context so the
   dots sit above the gradient but behind content; overflow:hidden clips them to the card. */
.tw-hero-card,
.tw-cta-card {
  position: relative;
  overflow: hidden;
  z-index: 0;
}
.tw-hero-card::after,
.tw-cta-card::after {
  content: "";
  position: absolute;
  z-index: -1;
  background: var(--tw-dots-white) no-repeat center / contain;
  aspect-ratio: 1352 / 1263;
  pointer-events: none;
}
.tw-hero-card::after {
  right: -70px;
  bottom: -120px;
  width: min(42%, 420px);
  opacity: 0.15;
}
.tw-cta-card::after {
  right: -50px;
  top: 50%;
  transform: translateY(-50%);
  width: 300px;
  opacity: 0.14;
}
@media (max-width: 820px) {
  .tw-cta-card::after { display: none; }
}

/* Not in token file — single post hero band, off-white background behind title + featured image */
.tw-post-hero {
  background: var(--tw-offwhite);
}

/* Not in token file — single post meta line, date · reading time ([tw_post_meta] shortcode
   output). _css_classes on text-editor widget puts .tw-post-meta on the widget wrapper. */
.tw-post-meta .elementor-widget-container p {
  font-size: var(--tw-fs-caption);
  color: var(--tw-muted);
  margin: 0;
}

/* Not in token file — single post body, narrow readable width, constrained from the
   boxed container max (1180px) */
.tw-post-body {
  max-width: 780px;
  width: 100%;
  margin: 0 auto;
}

/* Post content typography — scoped to theme-post-content widget.
   Deliberately its own long-form reading scale (17px body / 28px h2 / 22px h3), NOT the
   page-chrome type tokens: article headings sit in a narrow 780px reading column and run
   smaller than hero/section headings, and 17px body is the editorial reading measure.
   Kept as literals on purpose — not drift from the scale. */
.elementor-widget-theme-post-content h2 {
  font-size: 28px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--tw-text);
  margin-top: 2em;
  margin-bottom: 0.5em;
}
.elementor-widget-theme-post-content h3 {
  font-size: 22px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--tw-text);
  margin-top: 1.8em;
  margin-bottom: 0.4em;
}
/* Not in token file — h4 is the article's third level (first use: the five numbered
   review areas in "HR Transformation Starts Here"); a step below h3 on the same
   reading scale so the outline is real markup, not bold paragraphs. */
.elementor-widget-theme-post-content h4 {
  font-size: 18px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--tw-text);
  margin-top: 1.5em;
  margin-bottom: 0.4em;
}
.elementor-widget-theme-post-content p {
  font-size: 17px;
  line-height: 1.75;
  color: var(--tw-text);
  margin-bottom: 1.2em;
}
/* (post-content link colour now comes from the global `.elementor a` base rule near the top) */
.elementor-widget-theme-post-content ul,
.elementor-widget-theme-post-content ol {
  font-size: 17px;
  line-height: 1.75;
  padding-left: 1.5em;
  margin-bottom: 1.2em;
}
/* Not in token file — a data table inside post content (first use: the HR maturity
   model in "HR Transformation Starts Here", which the old site carried as a screenshot
   of a table; it's real markup here so it's editable, accessible and set in the site's
   fonts). Purple header row, lilac band on the first column, hairline grid; scrolls
   inside its own wrapper on narrow screens rather than widening the reading column. */
.elementor-widget-theme-post-content table {
  width: 100%;
  border-collapse: collapse;
  font-size: 16px;
  line-height: 1.5;
  margin: 1.2em 0 1.6em;
}
.elementor-widget-theme-post-content th,
.elementor-widget-theme-post-content td {
  border: 1px solid var(--tw-border);
  padding: 12px 14px;
  text-align: left;
  vertical-align: top;
  color: var(--tw-text);
}
.elementor-widget-theme-post-content thead th {
  background: var(--tw-purple);
  border-color: var(--tw-purple);
  color: #ffffff;
  font-weight: 700;
}
.elementor-widget-theme-post-content tbody td:first-child {
  background: var(--tw-offwhite);
  font-weight: 700;
}
.elementor-widget-theme-post-content tbody td:nth-child(2) {
  color: var(--tw-purple);
  font-weight: 700;
}
@media (max-width: 600px) {
  .elementor-widget-theme-post-content table {
    display: block;
    overflow-x: auto;
    font-size: 15px;
  }
  .elementor-widget-theme-post-content th,
  .elementor-widget-theme-post-content td {
    padding: 8px 10px;
  }
}

/* ============================================================
   RESPONSIVE
   ============================================================ */

@media (max-width: 768px) {
  .tw-hero {
    border-radius: 24px;
  }

  /* Hero 2-col: stack columns vertically on mobile.
     e-con-inner uses flex-direction: var(--flex-direction, row) — override the custom property. */
  .tw-hero .e-con-boxed > .e-con-inner {
    --flex-direction: column;
    gap: 32px;
  }

  /* Header CTA button: hide on mobile — hamburger menu gives nav access.
     V4 button wraps to a second flex row at 390px because _element_width has no V4 equivalent.
     Scoped to (0,3,0) to beat base-desktop.css .elementor .e-button-base { display:inline-block } (0,2,0). */
  .tw-site .elementor-location-header .e-button-base {
    display: none;
  }

  .tw-cta-band {
    border-radius: 24px;
    padding: 28px 24px;
  }
  .e-flex.tw-cta-band {
    flex-direction: column;
    gap: 32px;
  }

  .tw-logo-strip {
    gap: 12px;
  }
}

/* ============================================================
   NEWSLETTER — "Field Notes" sign-up panel
   Not in token file — bespoke component (homepage + Resources hub). Compact
   lavender panel matching docs/sketches `.news`: kicker + heading + one-line
   pitch left, inline pill email form right. Markup: tw_component_newsletter()
   — an Elementor container row (.tw-newsletter) holding real widgets; the
   form is an Elementor Pro form widget (save-to-database; Brevo push from the
   child theme once configured). Success swap: newsletter-form.js adds
   .is-done on Elementor's submit_success.
   ============================================================ */

/* Panel chrome on the row container (layout — columns, gap, wrap — comes from
   the container's own Elementor settings). Scoped under .tw-site (0,2,0) so
   max-width/margin/padding beat Elementor's e-con container rules (0,1,0). */
.tw-site .tw-newsletter {
  max-width: 1120px;
  margin-left: auto;
  margin-right: auto;
  background: var(--tw-offwhite); /* lavender panel (sketch --lavender) */
  border: 1px solid var(--tw-border);
  border-radius: 32px;
  padding: clamp(28px, 4vw, 44px);
}

.tw-newsletter .tw-newsletter__kicker {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: var(--tw-fs-caption);
  font-weight: var(--tw-fw-bold);   /* unify to the eyebrow spec */
  line-height: 1.45;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tw-purple);
}
/* The heading's type comes from .tw-type-h2 (shared H2 role — set once
   globally); only the wrap behaviour is panel-specific. */
.tw-newsletter .tw-newsletter__title {
  text-wrap: balance;
}
.tw-newsletter .tw-newsletter__lead {
  font-size: var(--tw-fs-body);
  line-height: var(--tw-lh-normal);   /* short lead — normal body rhythm, not long-form relaxed */
  color: var(--tw-muted);
  max-width: 46ch;
}

/* Form: the Elementor form widget rendered as one pill row — the email group
   grows, the submit button sits beside it. Overrides the widget's own
   field-group flow (wrap + width classes + per-field padding). */
.tw-site .tw-newsletter__form .elementor-form-fields-wrapper {
  flex-wrap: nowrap;
  gap: 10px;
}
.tw-site .tw-newsletter__form .elementor-field-group {
  padding: 0;
  margin-bottom: 0;
}
.tw-site .tw-newsletter__form .elementor-field-type-email {
  flex: 1 1 auto;
  min-width: 0; /* let the input shrink so the button stays on the same row (it stacks at the 420px breakpoint) */
}
.tw-site .tw-newsletter__form .elementor-field-type-submit {
  width: auto;
  flex: 0 0 auto;
}
/* The single email field keeps its label for screen readers only (compact
   single-row form) — standard visually-hidden treatment. */
.tw-site .tw-newsletter__form .elementor-field-group > label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Pill input. (0,3,0) beats Hello Elementor's reset
   `input[type=email]{border-radius:3px;border:1px solid #666;…}` (0,1,1) and
   the form widget's own .elementor-field-textual styling — same Hello-reset
   override pattern as CLAUDE.md CSS rule 5. */
.tw-site .tw-newsletter__form .elementor-field-textual {
  width: 100%;
  font: inherit;
  font-size: var(--tw-fs-body-sm);
  color: var(--tw-text);
  background: var(--tw-white);
  border: 1.5px solid #e0d6ec; /* sketch --border-2 */
  border-radius: 999px;
  padding: 14px 20px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.tw-site .tw-newsletter__form .elementor-field-textual::placeholder { color: #9c93a8; }
.tw-site .tw-newsletter__form .elementor-field-textual:focus {
  outline: none;
  border-color: var(--tw-purple);
  box-shadow: 0 0 0 4px rgba(102, 36, 131, 0.15); /* --tw-purple @ 15% */
}

/* Pill button — matches the Kit default solid-purple button on light sections
   (see CLAUDE.md CSS rule 4: tw-btn-primary/secondary are dark-bg only).
   (0,3,0) beats the Kit button theme-style (0,1,1)/(0,2,0), which would
   otherwise set its own size/radius on this <button>. font-size 17px matches
   the V4 e-button-base pills it sits beside. */
.tw-site .tw-newsletter__form .elementor-button {
  font: inherit;
  font-weight: 700;
  font-size: 17px;
  color: var(--tw-white);
  background: var(--tw-purple);
  border: 1.5px solid var(--tw-purple);
  border-radius: 999px;
  padding: 14px 26px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, transform 0.08s ease;
}
.tw-site .tw-newsletter__form .elementor-button:hover {
  background: #4b1661; /* TW_PURPLE_D */
  border-color: #4b1661;
}
.tw-site .tw-newsletter__form .elementor-button:active { transform: translateY(1px); }
.tw-site .tw-newsletter__form .elementor-button[disabled] { opacity: 0.65; cursor: progress; }

.tw-newsletter .tw-newsletter__fine {
  font-size: var(--tw-fs-caption);
  line-height: 1.5;
  color: var(--tw-muted);
  margin: 0;
}
.tw-newsletter .tw-newsletter__fine a {
  color: var(--tw-purple);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Success state — swapped in place of the form + fine print when
   newsletter-form.js adds .is-done on submit_success. Our panel replaces
   Elementor's own inline success message, which is hidden outright. */
.tw-newsletter .elementor-message-success { display: none; }
.tw-newsletter__success {
  display: none;
  align-items: flex-start;
  gap: 14px;
}
.tw-newsletter.is-done .tw-newsletter__form,
.tw-newsletter.is-done .tw-newsletter__fine { display: none; }
.tw-newsletter.is-done .tw-newsletter__success { display: flex; }
.tw-newsletter__check {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(102, 36, 131, 0.14); /* --tw-purple @ 14% */
  color: var(--tw-purple);
  display: grid;
  place-items: center;
}
.tw-newsletter__check svg { width: 20px; height: 20px; }
.tw-newsletter__success h3 {
  margin: 0 0 4px;
  font-size: var(--tw-fs-h4);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--tw-text);
}
.tw-newsletter__success p {
  margin: 0;
  font-size: var(--tw-fs-body-sm);
  line-height: 1.5;
  color: var(--tw-muted);
}

/* Column stacking at tablet/mobile comes from the container's own width_tablet/
   width_mobile settings; only the pill row needs a CSS fallback — on very
   narrow screens the button drops below the input. */
@media (max-width: 420px) {
  .tw-site .tw-newsletter__form .elementor-form-fields-wrapper { flex-wrap: wrap; }
  .tw-site .tw-newsletter__form .elementor-field-type-submit { width: 100%; }
  .tw-site .tw-newsletter__form .elementor-field-type-submit .elementor-button { width: 100%; }
}

/* ============================================================
   MEDIA FRAME + BRAND DUOTONE
   Not in token file — a reusable, ratio-locked image frame and the brand-duotone
   treatment that unifies the client's mixed stock photography (grayscale base,
   purple shadows, lilac highlights) into one editorial system. Applied to the only
   real photo surfaces on the site: Insights cards + single hero, case-study cards +
   single hero. NOT applied to logos, icons, the founder portrait, or the dot-swirl.
   Design proposal approved 2026-07-06 (artifact 4407e60c).

   DOM note: unlike the artifact's bare <img>, the live images are classic Elementor
   `image` / `theme-post-featured-image` widgets nested in wrapper divs. The image
   widget carries .tw-media__img (via _css_classes) so it can be stretched to fill the
   frame regardless of the intermediate wrappers; the frame owns aspect-ratio.
   ============================================================ */

.tw-media {
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: inherit;      /* clip to the card's top corners (card has overflow:hidden) */
  background: var(--tw-lilac);  /* placeholder tint before the image paints */
  isolation: isolate;           /* contain the blend modes to this box — without it the
                                   featured card's z-index:3 "Latest" ::before pulls the
                                   media's multiply/screen into the card stacking context
                                   and the whole card composites to white. No tone change. */
}
/* Stretch the classic image widget (and its wrapper divs) to fill the ratio box. */
.tw-site .elementor .tw-media__img,
.tw-site .elementor .tw-media__img .elementor-widget-container {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  z-index: 0;
}
/* When the photo links to its page (case-study cards), Elementor wraps the <img> in an
   inline <a>. Left inline, the img's height:100% resolves against the anchor's auto height
   instead of the frame, so object-fit:cover stops cropping to the ratio box (the img keeps
   its natural aspect). Make the anchor a block that fills the frame so the chain is restored. */
.tw-site .elementor .tw-media__img > a {
  display: block;
  height: 100%;
  width: 100%;
}
.tw-site .elementor .tw-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: filter 0.28s ease, transform 0.25s ease;
}

/* Brand duotone — grayscale base + a purple multiply (shadows) + a light lilac screen
   (highlights). Values match the approved artifact (4407e60c) exactly: an earlier local
   "tune" desaturated the purple and dropped the overlay opacities, which read as brighter
   and less purple than the approved proposal (user feedback 2026-07-06). Restored to the
   artifact: saturated plum endpoints at full opacity, brightness 1.02 (not 1.04). */
.tw-media--brand img {
  filter: grayscale(1) contrast(1.03) brightness(1.02);
}
.tw-media--brand::before,
.tw-media--brand::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  transition: opacity 0.28s ease;
}
.tw-media--brand::before {
  mix-blend-mode: multiply;
  background: linear-gradient(145deg, #3a1153, #7d3697);
  /* Dialled to 0.35 (artifact renders this at 1). The artifact's <img> is an in-flow,
     non-positioned child, so its multiply barely composites against the photo; the WP
     image is a positioned z-index:0 widget layer, against which the same multiply blends
     far more strongly — full opacity read as a heavy, over-saturated purple. 0.35 makes
     the combined result match the approved artifact, where the light screen highlight
     drives the look and the multiply only anchors the shadows. */
  opacity: 0.35;
}
.tw-media--brand::after {
  mix-blend-mode: screen;
  background: linear-gradient(145deg, #241033, #efe6f9);
}
/* Hover eases each photo back toward its true colour — the client's image is still
   there, it's a treatment not a mask. Shared by standard + featured cards (both .tw-card). */
.tw-card:hover .tw-media--brand img {
  filter: grayscale(0.12) contrast(1.02);
  transform: scale(1.03);
}
.tw-card:hover .tw-media--brand::before,
.tw-card:hover .tw-media--brand::after {
  opacity: 0.22;
}

/* Not in token file — opts a media frame out of the brand duotone, rendering the photo
   in its true full colour. Used on the single case-study page's big lead image (the one
   larger image per case study), while the smaller listing cards keep the duotone. Rules
   sit after .tw-media--brand at equal specificity (0,1,1), so later-wins restores colour
   and hides the multiply/screen overlays. */
.tw-media--full img { filter: none; }
.tw-media--full::before,
.tw-media--full::after { display: none; }

/* Not in token file — pure full-colour opt-out, no layout overrides (unlike
   .tw-media--full above, which is welded to the case-study lead image's 2/1 crop).
   Team page founder portrait + bio cards render in true colour (user steer
   2026-07-16: headshots read wrong in the purple duotone). Same later-wins
   mechanism as .tw-media--full; the hover rule keeps the card-hover zoom but
   re-suppresses the duotone's hover filter at matching specificity. */
.tw-media--photo img { filter: none; }
.tw-media--photo::before,
.tw-media--photo::after { display: none; }
.tw-card:hover .tw-media--photo img {
  filter: none;
  transform: scale(1.03);
}

/* Not in token file — portrait-ratio variant of the media frame (Team page founder
   portrait; the base .tw-media ratio above is 3/2 landscape).
   border-radius:0 (2026-07-30) undoes .tw-media's `border-radius: inherit`, which since the
   founder feature became a .tw-card now resolves to the card's full 28px and rounded all
   four of the portrait's corners — a rounded photo floating inside a rounded card, which is
   nothing like the bleed every other photo card on the site uses. Square here, and
   .tw-team-founder's overflow:hidden rounds only the two corners that meet the card's
   outside; the seam with the copy stays hard. Same pattern as .tw-case-card__media.
   (The comment that used to sit here said this frame "stands alone in the founder grid" so
   `inherit` would land on 0 — that stopped being true when the card class was added.) */
.tw-media--portrait {
  aspect-ratio: 3 / 4;
  border-radius: 0;
}

/* Not in token file — square variant for the Team page's 2x2 bio cards. The base
   .tw-media ratio is 3/2 LANDSCAPE, which is right for the case-study and Insights
   cards (scenes, wide crops) and wrong for a headshot: object-fit:cover on a square
   or portrait master then trims ~33-50% off the height, straight through foreheads
   and chins. Square keeps every master's full head height and matches the shape the
   photos actually arrive in (3 of the 5 are already 1:1). Ratio only — it inherits
   the card's corner clipping via .tw-case-card__media, same as the case cards.
   Sizing of the masters to suit this frame is baked in by scripts/build-team-photos.py. */
.tw-media--headshot {
  aspect-ratio: 1 / 1;
}
/* Lead image is cropped tighter than the 3/2 card frame (2/1 shows ~25% less height)
   and held a little narrower than its column, centred. */
.tw-media--full {
  aspect-ratio: 2 / 1;
  width: 82%;
  align-self: center;
}

/* Category pill overlaid on the media (primary term via [tw_primary_term] shortcode). */
.tw-tag {
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 2;
  font-size: var(--tw-fs-badge);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #ffffff;
  background: rgba(35, 12, 50, 0.42);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  padding: 6px 12px;
  line-height: 1;
}
/* .tw-tag lands on a classic text/heading widget wrapper — neutralise its widget margin. */
.tw-tag.elementor-widget { margin: 0; width: auto; }
.tw-tag .elementor-widget-container { margin: 0; }
.tw-tag p { margin: 0; }
.tw-tag--feature {
  background: rgba(255, 255, 255, 0.92);
  color: var(--tw-purple-d, #4b1661);
  border-color: transparent;
}

/* Featured lead story — the Insights loop grid's FIRST card, promoted to a horizontal
   layout in CSS. One loop-item template drives every card, so the native taxonomy-filter
   works cleanly (no query offset to drop a post) and the featured card always matches the
   active filter — it is simply the first result. :first-of-type (not :first-child) is
   robust to the per-item <style> Elementor injects into the loop container.
   Each loop item is wrapped in a grid cell (.e-loop-item) with the .tw-card inside; the
   cell spans all loop columns via grid-column: 1/-1, and the 2-col layout goes on the
   card's .e-con-inner (Elementor wraps a nested container's children there). */
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type {
  grid-column: 1 / -1;
}
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-card {
  height: 100%;
}
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-card > .e-con-inner {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  align-items: stretch;
  height: 100%;
}
/* Featured media fills the taller copy column instead of the 3:2 card ratio. */
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-media {
  aspect-ratio: auto;
  height: 100%;
  /* Taller than the body's natural content height so the media drives the card height and
     the body column gains vertical slack — that slack is what margin-top:auto on the footer
     consumes to push date+link to the base, with the gap falling below the lead text. */
  min-height: 400px;
}
/* Featured title steps up from the grid-card 20px to a lead-story size, and heavier
   (800) so it anchors the card as the lead story. */
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type h3 {
  font-size: clamp(24px, 2.2vw, 34px);
  line-height: 1.1;
  font-weight: var(--tw-fw-bold);
}
@media (max-width: 820px) {
  .tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-card > .e-con-inner {
    grid-template-columns: 1fr;
  }
  .tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-media {
    min-height: 0;
    aspect-ratio: 16 / 10;
  }
}

/* Insights card body + footer. The card fills its (stretched) grid cell so the footer
   pins to the base and aligns across a row; the body grows to push it down. Same
   margin-top:auto mechanism as the case-study proof bar. */
.tw-ins-grid .e-loop-item > .tw-card,
.tw-ins-grid .e-loop-item > .tw-card > .e-con-inner {
  height: 100%;
}
/* Body grows to fill the card below the media so the nested footer's margin-top:auto
   reaches the card base. Drives Elementor's own --flex-grow variable (consumed by
   .e-con { flex-grow: var(--flex-grow) }); scoped to (0,4,0) to beat Elementor's
   per-element `.elementor-114 .elementor-element.elementor-element-liins2` (0,3,0) which
   sets it to 0. The body's inner runs full height so margin-top:auto has room to push. */
.tw-ins-grid .elementor-loop-container > .e-loop-item .tw-card-body { --flex-grow: 1; }
.tw-card-body > .e-con-inner { height: 100%; }
/* Footer: date + read link on one row, hairline separator above, pinned to the base. */
.tw-card-foot {
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid var(--tw-border);
}
/* The featured first card is a 2-col grid, not the vertical stack. It reads as an
   editorial lead story: eyebrow sits near the top (modest top padding), and the footer
   (date + read link) is pushed to the card BASE via margin-top:auto so the slack falls
   between the lead and the footer, not above the eyebrow. Footer differs from the grid
   cards — date + read link sit together on the LEFT with no separator rule.
   PADDING GOES ON .e-con-inner, NOT the outer .tw-card-body: Elementor applies a
   container's own padding (scaffold liins2 = 26/28/28/28) to .e-con-inner, so padding on
   the outer element STACKS on top of it (56px above the eyebrow) AND the doubled padding
   fills the card so margin-top:auto has no slack to push the footer down. Setting the
   inner padding here replaces the scaffold value (higher specificity) — single padding,
   trimmed top gap, and slack freed so the footer pins to the base. */
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-card-body > .e-con-inner {
  padding: 34px 40px;
}
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-card-foot {
  margin-top: auto;
  padding-top: 0;
  border-top: none;
}
/* justify-content + gap live on the boxed footer's .e-con-inner (the real flex row);
   setting them on the outer .tw-card-foot has no effect — Elementor drives the inner via
   a cascading --justify-content variable that still resolves to the scaffold's
   space-between. Target the inner so date + read link pack together on the left. */
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-card-foot > .e-con-inner {
  justify-content: flex-start;
  gap: 24px;
}
/* Featured card carries a white "Latest" pill to the LEFT of the category pill. It is a
   ::before on the category .tw-tag (which lives INSIDE .tw-media), NOT on the .tw-card — a
   positioned card-level pseudo pulls the media's mix-blend overlays into the card stacking
   context and composites the whole card to white. Anchoring it inside the isolated .tw-media
   keeps the blend contained. The category pill is nudged right to make room. */
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-tag {
  left: 100px;
}
.tw-ins-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-tag::before {
  content: "Latest";
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  right: calc(100% + 8px);
  white-space: nowrap;
  font-size: var(--tw-fs-badge);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--tw-purple-d, #4b1661);
  background: #ffffff;
  border-radius: 999px;
  padding: 6px 12px;
}

/* Card excerpt colour — post-excerpt renders as bare text (no <p>), so the .tw-card p
   rule misses it; colour the text-editor content itself a clean dark grey. Scoped to
   .tw-card-body so it does NOT hit the .tw-tag pill (a text-editor in .tw-media, which
   must stay white on its dark glass). */
.tw-card-body .elementor-widget-text-editor,
.tw-card-body .elementor-widget-text-editor p {
  color: var(--tw-muted);
}

/* Category filter — styles the native Elementor `taxonomy-filter` widget output as the
   brand chip row. Wrapper class .tw-cat-filter applied via the widget's CSS Classes.
   Inner item selectors verified against rendered output at build time. */
.tw-cat-filter { margin-top: 4px; }
.tw-cat-filter .e-filter,
.tw-cat-filter ul {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
}
.tw-cat-filter .e-filter-item,
.tw-cat-filter a {
  font-family: inherit;
  line-height: 1;
  font-size: var(--tw-fs-caption);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--tw-on-dark);
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.20);
  border-radius: 999px;
  padding: 8px 16px;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.18s, color 0.18s, border-color 0.18s;
}
.tw-cat-filter .e-filter-item:hover,
.tw-cat-filter a:hover {
  background: rgba(255, 255, 255, 0.20);
}
.tw-cat-filter .e-filter-item[aria-pressed="true"],
.tw-cat-filter .e-filter-item.e-filter-item-active {
  background: #ffffff;
  color: var(--tw-purple-d, #4b1661);
  border-color: #ffffff;
}

@media (prefers-reduced-motion: reduce) {
  .tw-media img,
  .tw-media--brand::before,
  .tw-media--brand::after { transition: none; }
  .tw-card:hover .tw-media--brand img { transform: none; }
}

/* Not in token file — Insights archive + Events hero, left-aligned editorial masthead
   (matches the approved artifact 4407e60c). Both are built by tw_hero_card_masthead()
   with V4 atomic widgets, so these style the bare <p>/<h1>/<h2> the helper emits — no
   per-widget typography settings. Cap the headline and lead line-length so they don't
   span the full band toward the dot-swirl, and pin the filter chips to the left (the
   native taxonomy-filter defaults its .e-filter to justify-content:center). */
/* max-width caps the line-length off the dot-swirl; text-wrap:balance evens the wrap so a
   short trailing word doesn't hang on its own line (Events' "…Team Wheel" orphaned "Wheel"
   at the tighter 17ch cap — client 2026-07-16). 20ch gives Events' shorter headline room to
   break as "Where to find / Team Wheel" while Insights' longer one still balances to 2 lines. */
.tw-hero-card h1 { max-width: 20ch; text-wrap: balance; }
/* Muted-white eyebrow on the purple card (the .tw-type-eyebrow default is purple; the
   card's plain-white heading colour would be too stark) — (0,2,0) beats the base class. */
.tw-hero-card .tw-type-eyebrow { color: rgba(255, 255, 255, 0.72); }
/* The lead paragraph. Owns its full typography here (was per-widget settings on the old
   classic heading) so the V4 e-paragraph needs no inline styling: fluid lead size, regular
   weight, muted white, line-length capped. Bare class selector — V4 renders no wrapper. */
.tw-archive-lead {
  font-size: var(--tw-fs-lead);
  font-weight: 400;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.86);
  max-width: 560px;
}
.tw-hero-card .tw-cat-filter .e-filter,
.tw-hero-card .tw-cat-filter ul { justify-content: flex-start; }

/* ============================================================
   EVENTS — masthead meta, month-grouped timeline, past cards
   (Events page; matches the approved artifact a2b2c035). Reuses the Insights
   .tw-hero-card masthead + .tw-cat-filter chips + .tw-card / .tw-media shells.
   Role colours map to the brand dot palette: sponsoring=purple, attending=blue,
   judging=orange, partner=green (--tw-* vars above).
   ============================================================ */

/* Not in token file — masthead meta row (count · window · location). The text
   widget's content is wpautop-wrapped in a <p>; lay it out as a dot-separated
   flex row, white on the purple band. */
/* The text-editor widget renders its content directly in the widget element (no
   .elementor-widget-container wrapper in this build), so the flex row lives on the
   widget div itself. */
.tw-ev-mhmeta {
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  font-size: var(--tw-fs-caption);
  font-weight: 600;
  color: var(--tw-on-dark-2);
}
.tw-ev-mhmeta .tw-ev-mm b { font-weight: var(--tw-fw-bold); color: #fff; }
.tw-ev-mm-sep {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.42);
}

/* --- Role colour map (shared) ------------------------------------------------
   Set on .tw-ev-row (via events-timeline.js) and on .tw-ev-roletag (via the
   [tw_event_role_tag] shortcode class), so the spine dot + tag colour resolve
   from one --dot custom property either way. */
.is-role-sponsoring { --dot: var(--tw-blue); }
.is-role-attending  { --dot: var(--tw-purple); }
.is-role-judging    { --dot: var(--tw-orange); }
.is-role-partner    { --dot: var(--tw-green); }
.is-role-hosted     { --dot: var(--tw-yellow); }  /* Team Wheel-run event — book on-site */

/* Filter pills carry a role-colour dot before the label (matches the artifact). The
   native taxonomy-filter buttons already expose the term slug as data-filter, so the
   dot + colour are pure CSS — no markup or JS. "All events" (data-filter __all) gets a
   four-colour conic swatch. Scoped to .tw-ev-filter so the Insights filter is unaffected;
   the white ring lifts the dot off the translucent pill on the purple masthead. */
.tw-ev-filter .e-filter-item { display: inline-flex; align-items: center; gap: 8px; }
.tw-ev-filter .e-filter-item::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: var(--tw-white);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.18);
}
/* True brand colours, matching the artifact exactly. On the purple masthead the purple
   dot reads as a subtle ring rather than a solid disc — that's the artifact's own look
   (its purple "Sponsoring" dot does the same); kept faithful by request. */
.tw-ev-filter .e-filter-item[data-filter="attending"]::before  { background: var(--tw-purple); }
.tw-ev-filter .e-filter-item[data-filter="sponsoring"]::before { background: var(--tw-blue); }
.tw-ev-filter .e-filter-item[data-filter="judging"]::before    { background: var(--tw-orange); }
.tw-ev-filter .e-filter-item[data-filter="partner"]::before    { background: var(--tw-green); }
.tw-ev-filter .e-filter-item[data-filter="hosted"]::before     { background: var(--tw-yellow); }
.tw-ev-filter .e-filter-item[data-filter="__all"]::before {
  background: conic-gradient(var(--tw-purple) 0 20%, var(--tw-blue) 0 40%, var(--tw-orange) 0 60%, var(--tw-green) 0 80%, var(--tw-yellow) 0);
}

/* Not in token file — timeline. A continuous vertical spine runs down the loop
   grid; each row hangs a role-coloured dot on it, and JS inserts .tw-ev-month
   divider rows with a ring "node" that breaks the line. Spine lives on the
   widget (not the grid container) so it isn't auto-placed as a grid item. */
.tw-ev-timeline { position: relative; }
.tw-ev-timeline::before {
  content: "";
  position: absolute;
  left: 34px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--tw-border);
}

.tw-ev-row {
  position: relative;
  padding: 20px 0 20px 96px;
  border-top: 1px solid var(--tw-border);
  align-items: flex-start;
}
/* First row of each month (immediately after a JS-inserted divider) drops its rule. */
.tw-ev-month + .e-loop-item .tw-ev-row,
.tw-ev-month + .elementor-loop-item .tw-ev-row { border-top: none; }

/* Role dot on the spine. Spine centre is 35px (left 34 + 1); an 18px dot centres
   there at left 26px. */
.tw-ev-row::before {
  content: "";
  position: absolute;
  left: 26px;
  top: 26px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--dot, var(--tw-purple));
  border: 3px solid var(--tw-white);
  box-shadow: 0 0 0 1px var(--tw-border);
}

/* Reset Kit per-widget spacing inside rows — spacing is controlled explicitly below. */
.tw-ev-row .elementor-widget,
.tw-ev-body .elementor-widget { margin-bottom: 0; }

/* Date column. */
.tw-ev-datecol { flex: 0 0 78px; line-height: 1; }
.tw-ev-day {
  display: block;
  font-size: 1.85rem;
  font-weight: var(--tw-fw-bold);
  letter-spacing: -0.02em;
  color: var(--tw-text);
  font-variant-numeric: tabular-nums;
}
.tw-ev-dow {
  display: block;
  margin-top: 6px;
  font-size: var(--tw-fs-badge);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--tw-muted);
}

/* Body. */
.tw-ev-body { flex: 1 1 auto; min-width: 0; }
.tw-ev-title { margin: 0 0 6px; }
.tw-ev-title .elementor-heading-title {
  font-size: var(--tw-fs-h4);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: var(--tw-lh-snug);   /* was 1.25 — card-title rhythm, matches .tw-ev-pasttitle so the two event-title instances are one treatment */
  color: var(--tw-text);
}

/* Meta row — venue (15px, +pin) and role tag (12px uppercase, +dot) on one line,
   different sizes, so they align by TEXT BASELINE (not box centre, which left the
   larger venue hanging low). Each chip lives in its own text-editor widget; those
   wrappers stay plain blocks, whose baseline IS their text baseline — so aligning
   the row's flex parent (.e-con-inner) by baseline lands both texts on one line.
   Within each chip the items are baseline-aligned too, so the chip exports its
   text baseline; the icons then use align-self:center (below) to sit on their own
   caps rather than dropping to that baseline. */
.tw-ev-meta .e-con-inner { align-items: baseline; }
.tw-ev-meta .tw-ev-venue { margin-right: 16px; }
.tw-ev-venue-in {
  display: inline-flex;
  align-items: baseline;
  /* 4px, not the tag's 7px: the pin glyph fills only ~9px of its 15px box, so ~3px
     of icon whitespace sits between it and the text — a smaller gap here matches the
     pin's VISUAL distance to the text to the dot's (both ~7px of real whitespace). */
  gap: 4px;
  color: var(--tw-muted);
  font-size: var(--tw-fs-body-sm);
  font-weight: 500;
}
/* Chip aligns its TEXT by baseline (so the row lines up venue + tag baselines),
   but the pin should stay centred on its own text, not drop to the baseline. */
.tw-ev-venue-in svg { width: 15px; height: 15px; flex: 0 0 auto; align-self: center; opacity: 0.75; }
/* Venue links to a Google Maps search: muted at rest, brand purple on hover (no
   underline — matches the other links across the site). The base
   `.elementor a { color: var(--tw-purple) }` (0,1,1) would otherwise force it purple
   at rest, so the two-class compound (0,2,0) below wins for both states. */
.tw-ev-venue-link { text-decoration: none; transition: color 0.15s ease; }
.tw-ev-venue-in.tw-ev-venue-link { color: var(--tw-muted); }
.tw-ev-venue-in.tw-ev-venue-link:hover { color: var(--tw-purple); }
.tw-ev-venue-link:hover svg { opacity: 1; }

.tw-ev-roletag {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  font-size: var(--tw-fs-badge);
  font-weight: 700;
  line-height: 1;   /* uppercase caps fill the em box; a full line box would sit the centred dot below the cap centre */
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--dot, var(--tw-purple));
  white-space: nowrap;
}
.tw-ev-roletag .tw-ev-swatch {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--dot, var(--tw-purple));
  align-self: center;
  /* Geometric centre reads slightly low against uppercase caps (solid disc carries
     visual weight); a 1px optical lift sits it on the cap centre. */
  transform: translateY(-1px);
}

/* Note + CTAs. The shortcodes emit their own wrapper element ONLY when non-empty,
   so an absent note/CTA leaves the (bare) widget empty and contributes no gap. */
.tw-ev-note-in {
  margin: 10px 0 0;
  font-size: var(--tw-fs-body-sm);
  color: var(--tw-muted);
  line-height: 1.5;
}
.tw-ev-ctas-in {
  margin: 14px 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 22px;
}
.tw-ev-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--tw-fs-caption);
  font-weight: 600;
  text-decoration: none;
  transition: color 0.15s ease;
}
/* "Event details" reads muted grey at rest (matches the comp). The base
   `.elementor a { color: var(--tw-purple) }` (0,1,1) / `:hover` (0,2,1) would force it
   purple, so scope under the .tw-ev-ctas-in parent (0,2,0 / 0,3,0) to win. */
.tw-ev-ctas-in .tw-ev-link { color: var(--tw-muted); }
.tw-ev-ctas-in .tw-ev-link:hover { color: var(--tw-purple); }
/* Arrow nudges right on hover — but only on "Event details" (not the book CTA, which
   carries a calendar glyph, not an arrow). */
.tw-ev-link svg { width: 14px; height: 14px; transition: transform 0.15s ease; }
.tw-ev-link:not(.tw-ev-link--book):hover svg { transform: translateX(2px); }
.tw-ev-ctas-in .tw-ev-link--book { color: var(--tw-purple); font-weight: 700; }
.tw-ev-ctas-in .tw-ev-link--book:hover { color: var(--tw-purple-d, #4b1661); }

/* Hidden data hook (JS reads it; kept out of layout). */
/* Not in token file — the linked partner/organiser logo, sitting to the right of the
   event's identity (title / venue / role tag) and above the note + CTAs. That's what
   .tw-ev-head buys: it pairs the logo with .tw-ev-headmain only, so the note and CTAs
   below still run the full width of the row.
   [tw_event_logo] emits nothing when the event has no logo, and Elementor skips
   printing an empty widget entirely, so a logo-less row carries no logo column at all
   (same mechanism as [tw_event_note] above). The 24px separation still lives on
   .tw-ev-logo rather than the column, so the gap can never outlive the logo.
   Uniform max-height across every logo, like .tw-logo-strip: per-logo sizing belongs
   in build-logos.py, never here. */
.tw-ev-head > .e-con-inner { align-items: flex-start; justify-content: flex-start; }
/* One fixed width for the title/venue/role block on every row, so every logo starts
   at the same x and the set reads as a tidy right-hand column — rather than each logo
   hugging its own title's edge, which left them scattered across ~400px (client steer
   2026-07-15: "force the size of the middle column … so kind of align to the right ..
   but not"). Filling the row instead (flex-grow) was tried and rejected earlier: that
   shoves each logo hard against the right edge.
   640px is chosen against the real content: it clears the longest title (616px — the
   Agentic AI breakfast) so no title wraps that didn't already, and leaves the widest
   logo (CIPP, 136px) ~166px short of the row's right edge — right-aligned as a group,
   deliberately not flush.
   Set through Elementor's own --width (the container width control .tw-logo-chip also
   overrides), NOT a plain `flex`/`width` here: .e-con.e-flex applies
   `flex: var(--flex-grow) var(--flex-shrink) var(--flex-basis)` at (0,2,0) and
   .e-con `width: var(--width)`, so a (0,1,0) rule of ours silently loses to them.
   --flex-basis stays auto so the basis resolves from that width; Elementor's default
   --flex-shrink: 1 then still compresses the column on narrower viewports. */
.tw-ev-headmain {
  --width: 640px;
  --flex-basis: auto;
  min-width: 0;
}
/* A *definite* 640px column only compresses if nothing upstream floors the row at its
   min-content width — and two things do, both via the automatic minimum size (the
   `min-width: auto` default on flex/grid items):
     1. .e-loop-item is a grid item of .elementor-loop-container, so the grid track
        itself was sized to the row's min-content (640+gap+logo) — measured as
        `grid-template-columns: 974px` inside a 748px grid at a 768px viewport.
     2. .e-con-inner (Elementor's own) then repeats the floor further down.
   Left alone, the row simply overflowed and put the whole page into horizontal scroll
   below ~1014px. Releasing both floors inside the timeline lets the fixed column
   shrink gracefully instead — logos stay aligned, the column narrows and titles wrap —
   so this needs no breakpoint to switch the design off. Scoped to the timeline: other
   loop grids (cards) want the default behaviour. */
.tw-ev-timeline .e-loop-item { min-width: 0; }
.tw-ev-row .e-con-inner { min-width: 0; }
.tw-ev-logocol { flex: 0 0 auto; align-self: center; }
.tw-ev-logo { display: block; margin-left: 24px; }
.tw-ev-logo .tw-ev-logo-img {
  display: block;
  /* These are the same taller-canvas platform PNGs the homepage strip uses at 100px,
     not the client-trust set at 52px — the canvas carries deliberate transparent
     headroom (build-logos.py "partner" set: canvas_h 640 around target_ink 300), so a
     cap near the text's own size would render the wordmark at half of it. 68px lands
     the ink at a weight that sits comfortably against the title.
     The flip side is that the canvas, not the ink, is what the row measures: left as
     is, ~18px of transparent headroom top and bottom pushes the note below down and a
     logo row no longer matches a logo-less one. The negative block margin gives that
     headroom back — 68px x 170/640 = 18px each side at the sets' baseline ink of 300 —
     so the row is sized by the ink, not the canvas. Uniform for every logo, computed
     from the canvas geometry: NOT a per-logo nudge (those belong in build-logos.py).
     Re-derive it if either set's canvas_h/target_ink change. A logo built with
     scale>1 renders ink taller than this reclaimed box, which is harmless — the head
     row is floored by the title+meta beside it — until scale ≈1.3, where the ink
     outgrows that block and would start crowding the note. */
  max-height: 68px;
  /* Width is a safety bound only — height is what's uniform. Set clear of the widest
     logo in the set (CIPP renders 155px at a 68px canvas) so it never clamps the
     height instead, which would desync the headroom margin below. */
  max-width: 170px;
  margin-block: -18px;
  width: auto;
  height: auto;
  object-fit: contain;
}

.tw-ev-hook { display: none; }

/* Month divider (JS-inserted). Ring "node" breaks the spine at each month. */
.tw-ev-month {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 28px 0 8px 96px;
}
.tw-ev-month::before {
  content: "";
  position: absolute;
  left: 21px;
  top: 24px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--tw-white);
  border: 2px solid var(--tw-purple);
}
.tw-ev-month::after {
  content: "";
  position: absolute;
  /* 10px (even) so it stays concentric with the 28px ring at integer px — a 9px
     (odd) dot lands half a pixel off both axes. Centre: 30+5=35, 33+5=38 = ring centre. */
  left: 30px;
  top: 33px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--tw-purple);
}
.tw-ev-month__name {
  font-size: var(--tw-fs-h4);
  font-weight: var(--tw-fw-bold);
  letter-spacing: -0.01em;
  color: var(--tw-text);
}
.tw-ev-month__year {
  font-size: var(--tw-fs-caption);
  font-weight: 600;
  color: var(--tw-muted);
  letter-spacing: 0.02em;
}

/* --- Past events grid --------------------------------------------------------
   Reuses .tw-card; the media panel is a shortcode-emitted div (wpautop leaves an
   empty <p> sibling, hidden below). */
.tw-ev-pastcard .tw-ev-pastmedia-w,
.tw-ev-pastcard .tw-ev-pastmedia-w .elementor-widget-container { margin: 0; padding: 0; }
.tw-ev-pastmedia-w p:empty { display: none; }
.tw-ev-pastmedia {
  position: relative;
  /* Fixed height, not aspect-ratio: this media is a flex item inside the card's
     column flex, and a flex item's auto basis ignores an aspect-ratio child's
     derived height — the wrapper collapsed and the media overflowed onto the body
     (mobile past-card overlap, 2026-07-07). A definite height contributes to the
     flex basis, so the wrapper reserves the right space. */
  height: 200px;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-bottom: 1px solid var(--tw-border);
}
.tw-ev-pastmedia--logo {
  background: linear-gradient(160deg, var(--tw-white) 0%, var(--tw-lilac) 100%);
  padding: 22px 26px;
}
.tw-ev-brandlogo {
  display: block;
  max-height: 96px;
  max-width: 82%;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* Full-colour, no duotone: the original purple duotone overlay was retired when
   real event photos replaced the stock venue shot (client full-colour steer,
   2026-07-16 — same call as the team page and case-study photos). */
.tw-ev-pastmedia--photo { background: var(--tw-lilac); }
.tw-ev-pastmedia--photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Past card body — pin the role tag to the base. */
.tw-ev-pastcard .tw-ev-pastbody { flex-grow: 1; }
.tw-ev-pastbody .elementor-widget { margin-bottom: 0; }
.tw-ev-pastdate {
  font-size: var(--tw-fs-badge);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--tw-muted);
  font-variant-numeric: tabular-nums;
}
.tw-ev-pasttitle { margin: 0; }
.tw-ev-pasttitle .elementor-heading-title {
  font-size: var(--tw-fs-h4);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: var(--tw-lh-snug);
  color: var(--tw-text);
}
.tw-ev-pastvenue .tw-ev-venue-in { font-size: var(--tw-fs-body-sm); }
.tw-ev-recap-in {
  margin: 0;
  font-size: var(--tw-fs-body-sm);
  line-height: var(--tw-lh-normal);   /* was 1.55 — same body-sm/muted rhythm as .tw-ev-note-in */
  color: var(--tw-muted);
  font-style: italic;
}
.tw-ev-pastfoot { margin-top: auto; padding-top: 14px; }

/* --- Timeline responsive ----------------------------------------------------- */
@media (max-width: 767px) {
  .tw-ev-timeline::before { left: 22px; }
  .tw-ev-row { padding-left: 68px; }
  .tw-ev-row::before { left: 14px; }
  .tw-ev-datecol { flex-basis: 60px; }
  .tw-ev-day { font-size: 1.5rem; }
  .tw-ev-month { padding-left: 68px; }
  .tw-ev-month::before { left: 8px; }
  .tw-ev-month::after { left: 17px; }
  .tw-ev-logo { margin-left: 16px; }
  /* This is exactly the band where the logo wraps onto its own line under the meta
     (Elementor flips .tw-ev-head's inner to flex-wrap: wrap at its 767 mobile
     breakpoint — verified: every row wraps at 767, none at 768). Stacked, the two
     edges want different things, so the headroom reclaim is no longer symmetric:
       bottom  -14px — the full 52px x 170/640 headroom, as on desktop, keeping the
                       note tucked 10px under the logo.
       top      -4px — a partial reclaim. At the full -14px the ink landed flush
                       against the location line (measured: 0px clear). The ink sits
                       13.8px inside the canvas, so -4px leaves 10px of visual gap —
                       matching the gap below, so the logo sits evenly between the
                       location and the note. */
  .tw-ev-logo .tw-ev-logo-img { max-height: 52px; max-width: 104px; margin-top: -4px; margin-bottom: -14px; }
}
@media (max-width: 479px) {
  .tw-ev-row { flex-direction: column; gap: 4px; }
  .tw-ev-datecol { flex-basis: auto; }
  /* Day number + weekday sit inline on one line; add a real gap between them and
     keep a date range ("WED–THU") from breaking mid-token. */
  .tw-ev-day { display: inline; }
  .tw-ev-dow { display: inline; margin-top: 0; margin-left: 8px; white-space: nowrap; }
}
@media (prefers-reduced-motion: reduce) {
  .tw-ev-link { transition: none; }
}

/* ============================================================
   EVENT DETAIL PAGE  (single event — scaffold-single-event.php)

   The page behind an event's "Event details" link, for events that have body
   copy. Everything inside the masthead is emitted by the SAME [tw_event_*]
   shortcodes the timeline row uses, so the pieces that carry their own styling
   — .tw-ev-roletag, .tw-ev-venue-in, .tw-ev-note-in, .tw-ev-ctas-in,
   .tw-ev-link — arrive already styled and are deliberately NOT restated here.
   This block is only the detail page's own layout.
   ============================================================ */

/* Not in token file — masthead identity block: title + role tag on the left, the
   organiser logo on the right. Mirrors .tw-ev-head on the timeline row so the logo
   reads as belonging to the event rather than floating at the page edge. */
.tw-evd-head > .e-con-inner {
  align-items: flex-start;
  justify-content: space-between;
}
/* min-width: 0 opts the text column out of a flex item's automatic minimum size,
   so a long event title wraps instead of forcing the row wider than the page —
   the same floor that caused the timeline's horizontal-scroll bug. */
.tw-evd-headmain { min-width: 0; }
.tw-evd-logocol { flex: 0 0 auto; align-self: center; }

/* The logo is bigger here than in a timeline row (a masthead H1 dwarfs the row's
   68px box). max-height sets the CANVAS box, not the ink: build-logos.py's event set
   is canvas 640 / ink 300, so ink renders at ~47% of the box and the rest is
   transparent headroom — 88px box → ~41px of visible logo, and the negative margin
   reclaims the leftover (88 − 41) / 2 ≈ 23px per side so the headroom doesn't push
   the masthead apart. Change either number and the other must be re-derived from
   that same 300/640 ratio. */
.tw-evd-logocol .tw-ev-logo { margin-left: 32px; }
.tw-evd-logocol .tw-ev-logo-img {
  max-height: 88px;
  max-width: 210px;
  margin-block: -23px;
}

/* Not in token file — back link to the timeline. The arrow is a pseudo-element
   (stylesheet tier, per the CSS rules) rather than a character in the scaffold copy,
   so the link text stays clean for screen readers and translation. */
.tw-evd-backw { margin-bottom: 4px; }
.tw-site .elementor .tw-evd-back::before {
  content: "←";
  margin-right: 7px;
  display: inline-block;
  transition: transform 0.15s ease;
}
.tw-site .elementor .tw-evd-back:hover::before { transform: translateX(-2px); }

/* Not in token file — role tag sits above the H1 as the masthead's eyebrow. */
.tw-evd-roletag { margin-bottom: 12px; }

/* Not in token file — date + venue row under the title. The date is the emphasis
   (it's why someone opened the page); the venue keeps its muted link treatment
   from .tw-ev-venue-in. */
.tw-evd-meta > .e-con-inner {
  align-items: center;
  gap: 8px 22px;
}
.tw-evd-date {
  font-size: var(--tw-fs-body-sm);
  font-weight: 700;
  color: var(--tw-text);
}

/* Not in token file — the event write-up (post_content via theme-post-content) and
   the action row under it. Measure-capped for readability; the masthead above keeps
   the full boxed width. The CTA row shares the cap so its rule stops level with the
   text rather than running on past it.

   Scoped to (0,4,0) deliberately: Elementor's frontend.css carries
   `.e-con.e-con > .e-con-inner > .elementor-widget { max-width: 100% }` at (0,3,0),
   which a plain .tw-evd-body (0,1,0) loses to — the same specificity fight the button
   and image baselines document. Four classes clear it outright rather than relying on
   design.css merely loading later. */
.tw-evd-bodywrap > .e-con-inner { gap: 20px; }
.tw-site .elementor .tw-evd-bodywrap .tw-evd-body,
.tw-site .elementor .tw-evd-bodywrap .tw-evd-ctas {
  max-width: 720px;
}
.tw-evd-body p { margin: 0 0 1em; }
.tw-evd-body p:last-child { margin-bottom: 0; }
.tw-evd-body strong { color: var(--tw-text); font-weight: 700; }

/* A rule above the action row separates it from the body copy it follows. */
.tw-evd-ctas {
  border-top: 1px solid var(--tw-border);
  padding-top: 20px;
}
/* The shortcode emits nothing when every CTA is suppressed (a bookable event drops
   "Book your place" because the registration form is directly below this row) — but
   the classic widget wrapper still renders, so without this the border-top above
   would show as a stray hairline under the body copy. */
.tw-evd-ctas:not(:has(.tw-ev-ctas-in)) {
  display: none;
}

@media (max-width: 767px) {
  /* Logo drops under the title (Elementor wraps .tw-evd-head's inner at its 767
     mobile breakpoint) — clear the left offset it had as a side-by-side column.

     The reclaim is asymmetric on purpose, and the two sides are NOT symmetrical
     situations: above the logo the wrapped flex row contributes no gap, so the PNG's
     own canvas headroom (~12px at this box size) is the only thing separating the ink
     from the title — reclaiming it there would leave the logo crowding the title, the
     same fault corrected on the timeline row. Below, the masthead column's own 16px
     gap already separates it from the date, so the headroom there is pure dead space
     and is reclaimed. Measured: ~12px above the ink, ~12px below. */
  .tw-evd-logocol .tw-ev-logo { margin-left: 0; }
  .tw-evd-logocol .tw-ev-logo-img {
    max-height: 64px;
    max-width: 150px;
    margin-top: 0;
    margin-bottom: -17px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .tw-site .elementor .tw-evd-back::before { transition: none; }
}

/* Not in token file — the registration section (hosted events). The section is
   baked into the shared single-event template for EVERY event; the
   tw-event-bookable body class (child theme: hosted role + show_booking) decides
   whether it exists on the page — the same one-template body-class mechanism as
   the resource gate. Hidden-by-default so a missing class can only under-show,
   never leak a registration form onto a third-party event. */
.tw-site:not(.tw-event-bookable) .tw-evd-registerw { display: none; }
/* "Book your place" anchors here (#register); the sticky header would otherwise
   sit over the panel's top edge on arrival. */
.tw-evd-registerw { scroll-margin-top: 96px; }

/* ============================================================================
   CASE STUDY — single-post template (scaffold-single-case-study.php)
   Not in token file — aligned to the site's component vocabulary (alignment steer
   2026-07-16): the shared light .tw-page-intro header and the shared purple CTA Band
   (in place of the old bespoke results scoreboard), so the case study reads as part
   of the site. Its own feel comes from the narrative spine + a sticky "At a glance"
   rail (client + the three headline metrics) with the Team Wheel services as links
   below it; sector + platform surface as hero pills, beside the full-colour photo.
   All type is Maven Pro / Open Sans per the brand guideline. Classes land on
   classic-widget wrappers, so text rules target both the wrapper and its <p>.
   ============================================================================ */

/* — header H1 (dynamic post-title inside the shared .tw-page-intro shell): match the
   site H1 scale (.tw-type-h1) rather than a bespoke display size, so the case-study
   header is a true sibling of every other interior page header. */
.tw-cs-title .elementor-heading-title {
  font-size: var(--tw-fs-display);
  font-weight: var(--tw-fw-bold);
  line-height: var(--tw-lh-display);
  letter-spacing: -0.025em;
  margin: 0;
  text-wrap: balance;
}

/* — hero 2-col (differentiator): copy + headline figure left, full-colour photo right — */
/* Give the hero band a touch more bottom room than the plain page-intro so the photo
   column doesn't crowd the border. */
.tw-cs-herocol { max-width: 620px; }
/* Sector + platform pills — soft purple chips; the dynamic post-terms tag renders the term
   text straight into each .tw-cs-pill text widget. */
.tw-cs-herotags { margin-top: 2px; }
.tw-cs-pill, .tw-cs-pill p {
  margin: 0;
  display: inline-block;
  padding: 7px 15px;
  border-radius: 999px;
  background: #f2eaf7;
  color: var(--tw-purple);
  font-size: 0.82rem;
  font-weight: var(--tw-fw-bold);
  letter-spacing: 0.01em;
  line-height: 1.2;
}
.tw-cs-photocol { display: flex; }
/* Full-colour photo (.tw-media--full drops the brand duotone): a clean 4/3 crop filling the
   right column. This rule loads after .tw-media--full so it wins at equal specificity and
   overrides its 82%/2:1 crop. */
.tw-cs-ledger-photo {
  align-self: center;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 22px;
}

/* — body (differentiator): narrative spine + sticky facts rail — */
.tw-cs-spine { max-width: 64ch; }
.tw-cs-rail { position: sticky; top: 96px; }
.tw-cs-chapter {
  padding-bottom: 32px;
  border-bottom: 1px solid var(--tw-border);
  margin-bottom: 32px;
}
.tw-cs-chapter:last-child { border-bottom: 0; padding-bottom: 0; margin-bottom: 0; }
.tw-cs-chtitle .elementor-heading-title {
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-weight: var(--tw-fw-bold);
  margin: 0;
}
.tw-cs-chbody, .tw-cs-chbody p { font-size: 1.05rem; line-height: 1.65; }
.tw-cs-chbody p { margin: 0 0 1em; }
.tw-cs-chbody p:last-child { margin-bottom: 0; }

/* approach process steps — numbered brand pills (authored as
   <ol class="tw-cs-steps"><li><strong>title</strong><span>detail</span></li>) */
.tw-cs-steps {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  counter-reset: tw-step;
}
.tw-cs-steps li {
  position: relative;
  padding: 11px 0 11px 40px;
  border-top: 1px solid var(--tw-border);
  counter-increment: tw-step;
}
.tw-cs-steps li:first-child { border-top: 0; padding-top: 0; }
.tw-cs-steps li::before {
  content: counter(tw-step);
  position: absolute;
  left: 0;
  top: 10px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--tw-purple);
  color: #fff;
  font-weight: var(--tw-fw-bold);
  font-size: 0.78rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.tw-cs-steps li:first-child::before { top: 0; }
.tw-cs-steps li strong { display: block; font-weight: var(--tw-fw-bold); font-size: 1rem; }
.tw-cs-steps li span { display: block; color: var(--tw-muted); font-size: 0.95rem; line-height: 1.5; margin-top: 2px; }

/* results benefits list — the migrated "Project Benefits", brand-dot bullets */
.tw-cs-benefits {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.tw-cs-benefits li {
  position: relative;
  padding-left: 26px;
  font-size: 1rem;
  line-height: 1.5;
}
.tw-cs-benefits li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.62em;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--tw-purple);
}

/* facts rail card + rail CTA */
.tw-cs-dossier {
  background: var(--tw-white);
  border: 1px solid var(--tw-border);
  border-radius: 22px;
  box-shadow: var(--tw-shadow);
  padding: 24px 24px 26px;
}
.tw-cs-dtitle, .tw-cs-dtitle p {
  margin: 0 0 14px;
  font-size: var(--tw-fs-eyebrow);
  font-weight: var(--tw-fw-bold);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tw-muted);
}
.tw-cs-drow {
  padding: 12px 0;
  border-top: 1px solid var(--tw-border);
}
.tw-cs-drow:first-of-type { border-top: 0; padding-top: 0; }
.tw-cs-dk, .tw-cs-dk p {
  margin: 0;
  font-size: 0.72rem;
  font-weight: var(--tw-fw-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--tw-muted);
}
.tw-cs-dv, .tw-cs-dv p { margin: 0; font-weight: var(--tw-fw-bold); font-size: 1rem; line-height: 1.35; }
.tw-cs-dv a { color: inherit; text-decoration: none; }
/* Client row shows the brand logo where one exists ([tw_cs_client]); a client with no logo
   asset (Xenco) falls back to the name, which inherits the .tw-cs-dv value styling above.
   Cap the logo so a wide mark can't stretch the narrow rail. */
.tw-cs-clientlogo { display: block; max-height: 46px; max-width: 190px; width: auto; margin-top: 2px; object-fit: contain; }
/* metric rows in the "At a glance" box — the figure over a small caption. */
.tw-cs-dstat {
  padding: 12px 0;
  border-top: 1px solid var(--tw-border);
}
.tw-cs-dstat-n, .tw-cs-dstat-n p {
  margin: 0;
  font-weight: var(--tw-fw-bold);
  color: var(--tw-purple);
  font-size: 1.5rem;
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.tw-cs-dstat-l, .tw-cs-dstat-l p {
  margin: 0;
  color: var(--tw-muted);
  font-size: 0.85rem;
  line-height: 1.35;
}
/* Team Wheel services — a condensed echo of the /services/ index cards: each service is a
   boxed icon-link ([tw_cs_services] shortcode → .tw-cs-svc). The chips stack in the rail; the
   line-icon sits in a soft-purple tile on the left, the name centre, a chevron on the right. */
.tw-cs-services { margin-top: 2px; }
.tw-cs-svctitle, .tw-cs-svctitle p {
  margin: 0 0 4px;
  font-size: 0.72rem;
  font-weight: var(--tw-fw-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--tw-muted);
}
/* The shortcode output is wrapped in the text widget's <p>; make it the flex column so the
   chips stack full-width. */
.tw-cs-svcgrid p {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.tw-cs-svc {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--tw-border);
  border-radius: 14px;
  background: var(--tw-white);
  color: var(--tw-text);
  text-decoration: none;
  font-weight: var(--tw-fw-bold);
  font-size: 0.95rem;
  line-height: 1.25;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.tw-cs-svc__ico {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: #f2eaf7;
  color: var(--tw-purple);
}
.tw-cs-svc__ico svg { width: 19px; height: 19px; }
.tw-cs-svc__label { flex: 1 1 auto; }
.tw-cs-svc__go {
  flex: 0 0 auto;
  color: var(--tw-purple);
  font-weight: var(--tw-fw-bold);
  transition: transform 0.15s ease;
}
.tw-cs-svc:hover {
  border-color: var(--tw-purple);
  box-shadow: var(--tw-shadow);
}
.tw-cs-svc:hover .tw-cs-svc__go { transform: translateX(3px); }

/* — more of our work (related cases) — */
.tw-cs-more { border-top: 1px solid var(--tw-border); padding-top: 34px; }
/* Equal-height related-case cards: the loop grid's cells already stretch to the tallest in
   the row (CSS grid); make each .tw-card fill its cell so the two card boxes end level even
   when one summary runs longer. Same height:100% mechanism as the Insights loop grid. */
.tw-cs-more .e-loop-item > .tw-card,
.tw-cs-more .e-loop-item > .tw-card > .e-con-inner { height: 100%; }
.tw-cs-moreeye, .tw-cs-moreeye p {
  margin: 0;
  font-size: var(--tw-fs-eyebrow);
  font-weight: var(--tw-fw-bold);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tw-muted);
}
.tw-cs-morelink, .tw-cs-morelink p { margin: 0; font-size: 0.9rem; font-weight: var(--tw-fw-bold); }
.tw-cs-morelink a { color: var(--tw-purple); text-decoration: none; }
.tw-cs-morelink a:hover { text-decoration: underline; }

/* — responsive: stack the two-column rows, release the sticky rail — */
@media (max-width: 1024px) {
  .tw-cs-rail { position: static; }
  .tw-cs-ledger-photo { min-height: 220px; }
}

/* =============================================================
   QUIZ PAGE — HR maturity assessment embed
   The section is built by tw_component_quiz_embed() and mounted by
   themes/teamwheel-child/typeform-embed.js. Everything here is
   "Not in token file": the token file predates the assessment and
   has no embed component.
   ============================================================= */

/* Not in token file — mount point for the inline Typeform. Height is the whole
   problem with a third-party embed: the SDK sizes its iframe to the container,
   so the container must have one, but a fixed pixel height is wrong twice over.
   It gets scaled by the SHORT-VIEWPORT GLOBAL ZOOM block on landscape laptops,
   and it cannot adapt to a phone. So: a viewport-relative height, clamped so it
   never collapses below a usable question view or grows past the fold on a tall
   monitor. CLAUDE.md CSS rule 8 forbids per-element height compression at
   breakpoints, and this needs none — one clamp() covers every viewport.

   It must be `height`, not `min-height`. The embed's iframe sizes itself with
   height:100%, and a percentage height resolves against the containing block's
   SPECIFIED height. With min-height alone the specified height is auto, the
   percentage computes to auto, and the iframe collapses to HTML's default 150px
   inside a correctly-sized box. */
.tw-quiz-embed {
  /* Typeform centres its content vertically inside the frame and offers no control
     over that, so frame height is the only lever on how much dead space sits above
     the question: shorter frame, less of it, top and bottom equally. Tuned so a
     desktop question sits high enough to read as the top of the form, while the
     floor keeps a phone (where the options wrap and the content is much taller)
     from scrolling inside the iframe. */
  height: clamp(660px, 76vh, 720px);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--tw-radius-lg, 18px);
  overflow: hidden;
  background: var(--tw-offwhite);
}

/* Shorter on desktop, and a WIDTH query rather than a height one. What varies here is
   the content, not the viewport: at 1140px wide the question and its options fit on
   one line each, so the frame can lose ~80px of Typeform's centring padding. On a
   phone the same options wrap to two and three lines and need every pixel of the
   floor above. This is not the per-element height compression CSS rule 8 forbids —
   that rule is about hand-tuning elements against a short viewport, which the global
   zoom block owns; this is sizing a third-party frame to content it renders itself. */
@media (min-width: 1024px) {
  .tw-quiz-embed {
    height: clamp(600px, 71vh, 660px);
  }
}

/* Once the SDK has mounted, the iframe owns the box: drop the centring (which
   would otherwise fight the iframe's own layout) and the placeholder tint. */
.tw-quiz-embed.is-loaded {
  display: block;
  background: transparent;
}

/* The SDK does not drop the iframe straight into the container: it inserts its own
   wrapper, .tf-v1-widget, and that wrapper is height:auto. So `height: 100%` on the
   iframe alone resolves against an auto-height parent, computes to auto, and the
   iframe falls back to HTML's default 150px — the form renders in a letterbox with
   the rest of the box empty. The wrapper has to be sized too.

   .tf-v1-widget is a third-party class, which normally this file avoids. It is not
   the same case as Elementor's .elementor-element-<hash>: that is generated per
   element and changes on re-render, whereas this is the SDK's one fixed public
   wrapper class, the same one Typeform's own widget.css targets. It is the only
   handle the SDK offers, so the alternative is not a better selector, it is a
   broken layout. */
.tw-quiz-embed .tf-v1-widget {
  width: 100%;
  height: 100%;
}

.tw-quiz-embed iframe {
  border: 0;
  width: 100%;
  height: 100%;
}

/* Not in token file — the no-JavaScript route into the assessment, and the state
   the mount point ships in. Styled as the site's primary button rather than a
   bare link because that is what it is: the page's main action until the embed
   replaces it. Matches the Kit's button (purple pill, Maven Pro 700) by value,
   the same way the .elementor-button baseline above does, because this is raw
   markup in an html widget and no Elementor button rule reaches it. */
.tw-quiz-embed__fallback {
  display: inline-block;
  padding: 14px 30px;
  border-radius: 999px;
  background: var(--tw-purple);
  color: var(--tw-white);
  font-family: "Maven Pro", sans-serif;
  font-weight: var(--tw-fw-bold);
  font-size: 0.95rem;
  text-decoration: none;
  transition: background-color 150ms ease;
}
.tw-quiz-embed__fallback:hover,
.tw-quiz-embed__fallback:focus {
  /* --tw-purple-d is not declared anywhere; the file's convention for the dark
     purple is the literal behind a fallback (see .tw-svc-link:hover). */
  background: var(--tw-purple-d, #4b1661);
  color: var(--tw-white);
}

/* =============================================================
   RESOURCES — gated downloads / lead-magnet landing pages
   Built by scaffold-single-resource.php (the single template) and
   scaffold-resources.php (the shelf). Everything here is "Not in
   token file": the token file predates the resource CPT and has no
   lead-magnet landing component.

   The gate is CSS, not markup. The page always contains BOTH the
   capture form and the delivery panel; the body class set by
   tw_resource_is_gated() decides which is live, and
   resources-form.js flips it on submit_success. That is what lets
   one template serve gated and open assets alike.
   ============================================================= */

/* Not in token file — gate state. Three cases, expressed as two rules:
   gated (form only), open (delivery only), delivered (delivery only,
   reached from gated by JS). */
.tw-site.tw-resource-gated .tw-rs-deliverw,
.tw-site.tw-resource-open .tw-rs-formwrap,
.tw-site.tw-resource-delivered .tw-rs-formwrap {
  display: none;
}

/* Not in token file — masthead type tag. Colour keys off the term slug so a new
   type added in CPT UI falls back to the neutral pill rather than breaking. */
.tw-rs-typetag {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--tw-lilac);
  color: var(--tw-purple);
  font-family: "Maven Pro", sans-serif;
  font-weight: var(--tw-fw-bold);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.tw-rs-typetag.is-type-guide { background: rgba(0, 159, 227, 0.12); color: #0b6f9c; }
.tw-rs-typetag.is-type-checklist { background: rgba(58, 170, 53, 0.14); color: #2a7a26; }

.tw-rs-backw { margin-bottom: 4px; }

/* Not in token file — the one-line promise under the H1. Sized between the H1 and
   body so it reads as a deck, not a first paragraph. */
.tw-rs-deck {
  max-width: 46ch;
  font-size: 1.15rem;
  line-height: 1.5;
  color: var(--tw-muted);
}

.tw-rs-metarow {
  gap: 12px 20px;
  margin-top: 4px;
}
.tw-rs-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.tw-rs-metabit {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--tw-border);
  background: var(--tw-white);
  font-size: 0.8rem;
  color: var(--tw-muted);
}
.tw-rs-partner {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 0.8rem;
  color: var(--tw-muted);
}
.tw-rs-partnerlogo-img {
  display: block;
  max-height: 22px;
  width: auto;
}

/* Not in token file — the body/gate two-column split. The aside is sticky on
   desktop so the form stays reachable while the visitor reads the argument. */
.tw-rs-split {
  align-items: flex-start;
}
.tw-rs-main {
  flex: 1 1 58%;
  min-width: 0;
}
.tw-rs-aside {
  /* Cover only since the form moved into the full-width gate panel (2026-08-03);
     a touch wider than the old form aside so the document holds its own. */
  flex: 0 0 400px;
  max-width: 400px;
  position: sticky;
  /* Clears the sticky header, whose live height header-scroll.js tracks. */
  top: calc(var(--tw-header-h, 116px) + 24px);
}

/* V4/classic-neutral: no .elementor-widget-text-editor in the selector, so this
   matches whichever generation renders the body (see the wrapper-div note in
   CLAUDE.md). */
.tw-rs-body p {
  margin: 0 0 14px;
  line-height: 1.65;
}

.tw-rs-whyhead {
  font-family: "Maven Pro", sans-serif;
  font-size: 1.25rem;
  font-weight: var(--tw-fw-bold);
  margin: 0;
}
.tw-rs-why {
  margin-top: 8px;
  padding-top: 24px;
  border-top: 1px solid var(--tw-border);
}

/* Not in token file — the cover. A co-branded asset carries the PARTNER's palette
   on its cover, so it is framed and contained here rather than allowed to bleed
   and set the page's colour. */
.tw-rs-cover {
  display: block;
}
.tw-rs-cover-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--tw-radius-sm);
  border: 1px solid var(--tw-border);
  box-shadow: var(--tw-shadow);
}

/* Not in token file — the gate panel. The capture form first lived in the 360px
   aside, which made the page lopsided and the fields cramped (user feedback
   2026-08-03); it is now a full-width .tw-res-panel below the argument (gradient
   and copy-rail rules live in the HUB PANELS block), with the form on this white
   card filling the panel's wide half. */
.tw-rs-formcard {
  background: var(--tw-white);
  border-radius: var(--tw-radius-md);
  box-shadow: var(--tw-shadow);
}
/* The panel's copy rail carries the record's own intro and trust lines; they render
   from classic text-editor widgets (no <p> wrapper guaranteed), so colour is set on
   the class, not via the panel's p rule. */
.tw-rs-formintro {
  font-weight: var(--tw-fw-medium);
  line-height: 1.45;
  color: var(--tw-on-dark);
}
.tw-rs-trust {
  font-size: 0.78rem;
  color: var(--tw-on-dark-3);
  line-height: 1.45;
}
@media (max-width: 1024px) {
  /* Stacked: the form card takes the full panel width — the hub panels' 460px
     stacked-card cap is for reading cards, not a form. */
  .tw-rs-formwrap .e-flex.tw-res-panel__latest,
  .tw-evd-register .e-flex.tw-res-panel__latest { max-width: none; }
}

/* Elementor's form widget ships its own field styling; these only tighten the
   rhythm inside the capture cards and align the control shape with the site's
   inputs. Shared by the resource capture form (.tw-rs-form), the contact page
   form (.tw-contact-form) and the event registration form (.tw-evd-regform —
   not in token file: same widget, same treatment). */
.tw-site .tw-rs-form .elementor-field-group > label,
.tw-site .tw-contact-form .elementor-field-group > label,
.tw-site .tw-evd-regform .elementor-field-group > label {
  font-size: 0.8rem;
  font-weight: var(--tw-fw-medium);
  margin-bottom: 4px;
}
.tw-site .tw-rs-form .elementor-field-textual,
.tw-site .tw-contact-form .elementor-field-textual,
.tw-site .tw-evd-regform .elementor-field-textual {
  border: 1px solid var(--tw-border);
  border-radius: var(--tw-radius-sm);
  padding: 10px 12px;
  font-size: 0.9rem;
}
.tw-site .tw-rs-form .elementor-field-textual:focus,
.tw-site .tw-contact-form .elementor-field-textual:focus,
.tw-site .tw-evd-regform .elementor-field-textual:focus {
  border-color: var(--tw-purple);
  outline: 2px solid rgba(102, 36, 131, 0.18);
  outline-offset: 1px;
}
.tw-site .tw-rs-form .elementor-field-type-acceptance .elementor-field-option label,
.tw-site .tw-contact-form .elementor-field-type-acceptance .elementor-field-option label,
.tw-site .tw-evd-regform .elementor-field-type-acceptance .elementor-field-option label {
  font-size: 0.78rem;
  color: var(--tw-muted);
  line-height: 1.4;
}

/* Not in token file — the delivery panel: what a gated visitor sees after
   submitting, and what an open resource shows straight away. Rebuilt 2026-08-03
   from a small white card into a full-width purple panel (it shares the
   .tw-res-panel gradient — see the HUB PANELS block): the download is the page's
   payoff, so it gets the boldest treatment on the page. Copy on the left, the
   action on the right; one column below 900px. */
.tw-rs-deliver {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 28px 56px;
  align-items: center;
  padding: 44px;
}
.tw-rs-deliver:focus {
  outline: none;
}
.tw-rs-deliver .tw-res-panel__ico { margin-bottom: 18px; }
.tw-rs-deliverhead {
  font-family: "Maven Pro", sans-serif;
  font-weight: var(--tw-fw-bold);
  font-size: clamp(24px, 2.4vw, 32px);
  line-height: 1.15;
  color: var(--tw-white);
  margin: 0 0 10px;
}
.tw-rs-delivernote {
  margin: 0;
  color: var(--tw-on-dark-2);
  max-width: 52ch;
}
/* The submitted address, filled in by resources-form.js on a gated submit. */
.tw-rs-delivernote strong { color: var(--tw-white); }
.tw-rs-deliver__act {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
}
/* The panel's action pill (the .tw-btn-primary treatment — this always sits on
   the purple band): the download button on an open asset, "Book a conversation"
   on a gated one. Scoped under .tw-site .elementor to clear Elementor's link
   reset, the same reason .tw-micro-link is (see the link-reset note in CLAUDE.md). */
.tw-site .elementor .tw-rs-pill {
  display: inline-block;
  padding: 15px 30px;
  border-radius: 999px;
  background: var(--tw-white);
  color: var(--tw-purple);
  font-family: "Maven Pro", sans-serif;
  font-weight: var(--tw-fw-bold);
  font-size: 1rem;
  text-decoration: none;
  transition: background-color 150ms ease;
}
.tw-site .elementor .tw-rs-pill:hover,
.tw-site .elementor .tw-rs-pill:focus {
  background: rgba(255, 255, 255, 0.9);
  color: var(--tw-purple);
}
.tw-rs-delivernext {
  margin: 0;
  font-size: 0.85rem;
  color: var(--tw-on-dark-3);
}
/* Links inside the panel copy ("contact us", "browse more") must read on purple. */
.tw-site .elementor .tw-rs-deliver .tw-micro-link { color: var(--tw-white); }
@media (max-width: 900px) {
  .tw-rs-deliver {
    grid-template-columns: 1fr;
    padding: 28px 22px;
  }
}

/* Not in token file — resource card on the /downloads/ shelf loop grid. */
.tw-rs-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}
/* The cover renders as a DOCUMENT PAGE, not a cropped photo (user steer 2026-08-03):
   the whole A4 face, shrunk to fit, centred on a soft lilac field (the same field as
   .tw-res-quiz-media) with a paper edge and drop shadow. Photo cards elsewhere keep
   their cover-crop — this treatment is only for things that ARE documents. */
.tw-rs-card-media {
  display: grid;
  place-items: center;
  height: 220px;
  background: linear-gradient(135deg, var(--tw-lilac) 0%, var(--tw-offwhite) 100%);
}
/* The link wraps the img; contents keeps the img itself as the centred grid item. */
.tw-rs-card-medialink { display: contents; }
.tw-rs-card-media img {
  display: block;
  height: 176px;
  width: auto;
  max-width: 80%;
  border: 1px solid var(--tw-border);
  border-radius: 3px;
  box-shadow: 0 12px 26px rgba(27, 18, 39, 0.16);
}
.tw-rs-card-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 22px;
}
.tw-rs-card-title {
  font-family: "Maven Pro", sans-serif;
  font-size: 1.05rem;
  font-weight: var(--tw-fw-bold);
  line-height: 1.3;
  margin: 0;
}
.tw-rs-card-deck {
  font-size: var(--tw-fs-body-sm);
  color: var(--tw-muted);
  line-height: 1.5;
  margin: 0;
}

@media (max-width: 1024px) {
  .tw-rs-split { flex-direction: column; }
  .tw-rs-main,
  .tw-rs-aside {
    flex: 1 1 auto;
    max-width: 100%;
    width: 100%;
    position: static;
  }
}

/* Not in token file — downloads shelf list (.tw-rs-grid on the shelf's Loop Grid).
   EVERY item renders as an editorial row (user steer 2026-08-03: with only a few
   assets, the 3-across cards were tight — one shared format, stacked): the document
   page on the left, copy directly on the page background on the right, no card.
   One loop-item template drives every row; the first (newest) just gains a Latest
   marker. Below 821px each row falls back to the plain vertical card. */
.tw-rs-grid .elementor-loop-container > .e-loop-item {
  grid-column: 1 / -1;
}
/* "Latest" marker folded into the newest row's format pill. */
.tw-rs-grid .elementor-loop-container > .e-loop-item:first-of-type .tw-rs-typetag::before {
  content: "Latest · ";
}
@media (min-width: 821px) {
  .tw-rs-grid .elementor-loop-container.elementor-grid {
    grid-template-columns: 1fr;
    row-gap: 72px;
  }
  .tw-rs-grid .elementor-loop-container > .e-loop-item .tw-card {
    background: transparent;
    border: none;
    box-shadow: none;
    overflow: visible;  /* the page image's drop shadow must not clip */
  }
  .tw-rs-grid .elementor-loop-container > .e-loop-item .tw-card:hover {
    transform: none;
    box-shadow: none;
    border-color: transparent;
  }
  .tw-rs-grid .elementor-loop-container > .e-loop-item .tw-card > .e-con-inner {
    display: grid;
    grid-template-columns: 0.9fr 1.1fr;
    column-gap: 48px;
    align-items: center;
  }
  /* Document left, copy right (matches the DOM's media-first order). */
  .tw-rs-grid .elementor-loop-container > .e-loop-item .tw-rs-card-media {
    grid-column: 1;
    grid-row: 1;
    height: auto;
    min-height: 0;
    padding: 12px 0;
    background: none;   /* no card, no field — the page floats on the page background */
  }
  .tw-rs-grid .elementor-loop-container > .e-loop-item .tw-rs-card-body {
    grid-column: 2;
    grid-row: 1;
    padding: 0;
  }
  /* A bigger page, with a stronger shadow to carry it without a card behind. */
  .tw-rs-grid .elementor-loop-container > .e-loop-item .tw-rs-card-media img {
    height: 420px;
    box-shadow: 0 24px 48px rgba(27, 18, 39, 0.22);
  }
  /* Lead-story title size. */
  .tw-rs-grid .elementor-loop-container > .e-loop-item .tw-rs-card-title {
    font-size: clamp(24px, 2.2vw, 34px);
    line-height: 1.12;
  }
}

/* =============================================================
   RESOURCES HUB PANELS — .tw-res-panel (scaffold-resources.php)
   Not in token file — the /resources/ hub's three type panels:
   one deep purple gradient band per resource type, the type's
   pitch + CTA on the left and a live "latest" card (white
   .tw-card) on the right. Same gradient family as .tw-cta-card;
   .tw-btn-primary (white pill, dark-band button) is correct here.
   ============================================================= */
/* .tw-rs-deliver (the single resource page's delivery panel) is a plain-markup
   member of the same family — it shares the face here; its own layout rules live
   in the RESOURCES block. */
.tw-res-panel,
.tw-rs-deliver {
  background:
    radial-gradient(circle at 88% 12%, rgba(249, 178, 51, 0.16), transparent 26%),
    linear-gradient(135deg, #4b1661 0%, #662483 62%, #7d3697 100%);
  border-radius: var(--tw-radius-lg);
  box-shadow: var(--tw-shadow);
  position: relative;
  overflow: hidden;
  z-index: 0; /* stacking context so the z-index:-1 watermark paints above the gradient
                 but below every child — the dot-swirl layering pattern */
}
/* Anchor the absolute watermark to the panel, not its columns (.e-con defaults to
   position:relative, which would otherwise capture it) nor the html widget that emits
   it (.elementor-widget is position:relative too). */
.tw-res-panel > .e-con { position: static; }
.tw-res-panel .elementor-widget-html { position: static; }

/* Copy column — white-on-purple, same mechanism as .tw-hero/.tw-cta-band but scoped
   to the column so the white latest card in the other column is untouched. */
.tw-res-panel__copy { min-width: 0; }
.tw-res-panel__copy h2,
.tw-res-panel__copy h3 { color: var(--tw-white); }
.tw-res-panel__copy p { color: var(--tw-on-dark-2); max-width: 46ch; }
.tw-res-panel__copy .tw-type-eyebrow { color: var(--tw-on-dark); }

/* Mini variant — the copy treatment alone as a 2-up cross-sell panel (downloads
   page "Other ways to get started"). The row is a grid, sidestepping the e-con flex
   variables entirely; the copy column must NOT inherit the hub panels' fixed
   320px rail basis (that basis is the MAIN axis, which is vertical here). */
.tw-res-panel-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
}
@media (max-width: 900px) {
  .tw-res-panel-row { grid-template-columns: 1fr; }
}
.tw-res-panel--mini > .e-flex.tw-res-panel__copy {
  --flex-basis: auto;
  --flex-shrink: 1;
}

/* Proportions: the copy is a fixed narrow signpost rail (~1/3), and the latest card is
   the panel's hero (~2/3) — the page's job is routing to live content, so the content
   gets the width. Sized by driving Elementor's own flex variables, not the flex
   property (the .tw-card-body --flex-grow precedent), compounded with .e-flex to match
   the (0,2,0) of Elementor's `.e-con.e-flex { --flex-basis: auto; ... }` — the same
   pattern as .e-flex.tw-cta-band. */
.e-flex.tw-res-panel__copy {
  --flex-basis: 320px;
  --flex-shrink: 0;
}
.e-flex.tw-res-panel__latest {
  --flex-grow: 1;
  --flex-basis: 0;
  min-width: 0;
}
.tw-res-panel__latest .tw-type-eyebrow { color: var(--tw-on-dark-3); }

/* The wide latest card lays out horizontally — media beside body — reusing the Insights
   archive's featured-lead-story device (.tw-ins-grid :first-of-type): the 2-col grid
   goes on the card's .e-con-inner, and the media half stretches to the card height.
   Desktop only; stacked panels keep the ordinary vertical card. */
@media (min-width: 1025px) {
  /* Loop cards are boxed containers (grid goes on their .e-con-inner); the static
     assessment card is e-con-full (children are direct, grid goes on the card). */
  .tw-res-panel__latest .tw-card > .e-con-inner,
  .tw-res-panel__latest .e-con-full.tw-card {
    display: grid;
    grid-template-columns: 0.95fr 1.05fr;
    align-items: stretch;
  }
  .tw-res-panel__latest .tw-media {
    aspect-ratio: auto;
    height: 100%;
    min-height: 280px;
  }
  /* The cover renders as a document page here too (see .tw-rs-card-media); a fixed
     page height so the A4 shape doesn't drive the card height — the body column sets
     the height, the page sits centred in its half. */
  .tw-res-panel__latest .tw-rs-card-media { height: 100%; min-height: 280px; padding: 16px; }
  .tw-res-panel__latest .tw-rs-card-media img { height: 240px; }
  .tw-res-panel__latest .tw-res-quiz-media { height: 100%; }
  /* The body column centres its content against the taller media half. */
  .tw-res-panel__latest .tw-card-body,
  .tw-res-panel__latest .tw-rs-card-body {
    justify-content: center;
  }
}

/* Icon chip — the shared .tw-ib-ico device, enlarged and inverted for the dark band. */
.tw-res-panel__ico {
  width: 56px;
  height: 56px;
  border-radius: 16px;
  color: var(--tw-white);
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.22);
}
.tw-res-panel__ico svg { width: 28px; height: 28px; }

/* Watermark — the panel's own line icon, oversized and faint, bleeding off the
   bottom-left corner under the copy rail. z-index:-1, never interactive. */
.tw-res-panel__wm {
  position: absolute;
  z-index: -1;
  left: -48px;
  bottom: -72px;
  width: 260px;
  height: 260px;
  color: var(--tw-white);
  opacity: 0.08;
  pointer-events: none;
}
.tw-res-panel__wm svg { width: 100%; height: 100%; }

@media (max-width: 1024px) {
  /* Stacked (flex_direction_tablet: column, align stretch): card capped, watermark off. */
  .e-flex.tw-res-panel__copy { --flex-basis: auto; }
  .tw-res-panel__copy p { max-width: none; }
  .e-flex.tw-res-panel__latest { --flex-grow: 0; --flex-basis: auto; --flex-shrink: 1; max-width: 460px; }
  .tw-res-panel__wm { display: none; } /* would sit under the stacked card */
}

/* Not in token file — the assessment panel's static card header: the line-icon device
   standing in for the photo the two loop cards carry (same 200px band as
   .tw-rs-card-media img, so the three cards read as siblings). */
.tw-res-quiz-media {
  display: grid;
  place-items: center;
  height: 200px;
  background:
    radial-gradient(circle at 80% 18%, rgba(102, 36, 131, 0.14), transparent 45%),
    linear-gradient(135deg, var(--tw-lilac) 0%, var(--tw-offwhite) 100%);
  color: var(--tw-purple);
}
.tw-res-quiz-media svg { width: 84px; height: 84px; }

/* =============================================================
   LEGAL PROSE — .tw-legal (scaffold-legal.php)
   Not in token file — the two long-form utility pages (Privacy
   Policy, Terms of Use). Nothing else on the site is an unbroken
   run of body copy, so this is the one place that needs a reading
   measure and a heading rhythm rather than card/section layout.
   The container stays boxed; the measure is set on the text itself
   so the column keeps the site's normal gutters at every width.
   ============================================================= */
.tw-site .elementor .tw-legal p,
.tw-site .elementor .tw-legal h2 {
  /* ~72 characters — the readable measure for continuous prose. The boxed
     container's 1180px would run to well over 150 characters a line here. */
  max-width: 72ch;
}

/* Section headings need more air above than the container's 18px flex gap gives,
   and none below it (the gap already separates them from the first paragraph).
   V4 atomic headings ship margin:0, so this is additive, not a reset. */
.tw-site .elementor .tw-legal h2 {
  margin-top: 26px;
}
.tw-site .elementor .tw-legal h2:first-child {
  margin-top: 0;
}

/* The "Last updated" line. Sits above the first paragraph as a date stamp, not as
   body copy, so it is smaller and muted; the <strong> it arrives wrapped in comes
   from the client's markdown and would otherwise read as emphasis. */
.tw-site .elementor .tw-legal__meta {
  font-size: 14px;
  color: var(--tw-muted);
}
.tw-site .elementor .tw-legal__meta strong { font-weight: 600; }

/* Links inside the prose — email addresses and the ICO. Elementor injects an
   inline `.e-paragraph-base a { all: unset; color: unset; }` rule at (0,1,1) that
   resets any link inside a V4 paragraph, including ones in raw HTML content, so a
   plain class loses to it. Scoped under `.tw-site .elementor` to reach (0,3,1) —
   the same fix as .tw-micro-link and the button variants. Underlined, unlike
   .tw-micro-link: these sit mid-sentence, where colour alone is not an accessible
   distinction from the surrounding text. */
.tw-site .elementor .tw-legal a {
  color: var(--tw-purple);
  text-decoration: underline;
}
.tw-site .elementor .tw-legal a:hover { text-decoration: none; }
