/* ============ Yeelance — design tokens ============ */
:root {
  /* Cobalt sampled straight out of the logo PNG (#0046B6) so every blue on
     the site is the same family as the mark. --blue-dark / --blue-accent are
     the same hue and saturation at lower / higher lightness. --blue-rgb is
     the channel triplet for the rgba(...) shadows and tints, so they shift
     with the base colour instead of being hardcoded to the old indigo. */
  --blue: #0046b6;
  --blue-dark: #003994;
  --blue-accent: #0058e6;
  --blue-rgb: 0, 70, 182;
  /* pale cobalt tints */
  --hero-top: #e6ecfa;
  --hero-bottom: #edf1fb;
  --hero-bottom-rgb: 237, 241, 251;
  /* 2026-09-16: was #ee1c25 — white text on it measured 4.35:1, just under
     the 4.5:1 WCAG AA floor for normal-size text (the audit script has
     flagged this every run). This is the lightest step down that clears AA
     (4.59:1) — about 3% darker, not a visible brand change. */
  --red: #e71b24;
  --red-dark: #cf131b;
  --ink: #0c0c10;
  --body: #3c4250;
  /* #6b7282, not the old #818898. That value is 3.55:1 on white — below the
     4.5:1 WCAG AA floor — and it is used for real prose (testimonial roles,
     blog excerpts, the testimonials subheading), not decoration. This is the
     lightest shade of the same hue that clears AA on both white and the
     #f6f8fd card background. */
  --muted: #6b7282;
  --bg: #ffffff;
  --bg-soft: #e8f0fd;
  /* channel triplet so gradients can fade --bg-soft to alpha 0 without
     hardcoding the literal in two more places */
  --bg-soft-rgb: 232, 240, 253;
  --bg-card: #ffffff;
  --line: #e6e9f2;
  --radius-lg: 28px;
  --radius-md: 18px;
  /* single source of truth for every CTA button's size */
  --btn-h: 52px;
  --btn-w: 178px;
  /* ONE dark-glass surface, shared by the nav pill and the mobile dropdown.
     These previously carried different values (0.72 at rest, 0.82 once
     scrolled, 0.72 on the panel) and never matched. Worth knowing WHY equal
     numbers still didn't look equal: a translucent fill takes on whatever is
     behind it, and the pill sits on the pale hero strip at rest but on dark
     page content once scrolled — identical CSS, different rendered colour.
     0.84 is measured off the reference screenshot: sampling the bar there
     gave rgb(39,41,48) over a rgb(205,210,222) backdrop, and solving
     bar = fg*a + bg*(1-a) per channel returns a = 0.843/0.841/0.845. That
     shade was the old SCROLLED value; the old at-rest value (0.72) was the
     lighter one, which is why matching 0.72 looked too transparent.
     Bar and dropdown share this single value, and the scroll state no longer
     changes the fill, so those three surfaces can never diverge again.
     This number is the dial: higher = more solid, lower = more see-through.

     Base is near-NEUTRAL rather than the old (8,9,16). That old base carried
     a blue cast, and over the pale hero it composited to rgb(43,44,52) — a
     blue-grey charcoal rather than black.

     TRANSLUCENT, by choice (0.80, chosen from a live on-device slider preview
     — was 0.85). This was briefly pushed to 0.98 so the pill and the dropdown
     would render identically — at real translucency they cannot, because the
     pill sits on the flat nav strip while the dropdown sits over the hero
     illustration and picks up its linework. Glass was preferred over that
     guarantee, so 0.80 it is: roughly 20% of what is behind shows through.
     Blur dropped from 20px to 10px in the same pass. The base stays
     near-neutral (10,10,12) so
     the result reads as a dark charcoal-black rather than the blue-grey the
     old (8,9,16) base produced. Expect the dropdown to look slightly livelier
     than the pill wherever art sits behind it — that is the glass working,
     not a mismatch. Raise toward 0.98 to trade it back for an exact match. */
  --glass: rgba(10, 10, 12, 0.80);
  --glass-border: rgba(255, 255, 255, 0.10);
  --glass-blur: blur(10px) saturate(115%);
  /* Real measured heights of the two fixed bands either side of the hero.
     nav  = 16 padding-top + 10+10 inner padding + 36 logo + 2 border = 74
     strip= 26+26 padding + (16+16 item padding + 34 logo) = 118
     The first screen is nav + hero + marquee, so the hero gets exactly what
     is left over — see .hero below. */
  --nav-h: 74px;
  --marquee-h: 118px;
  /* Type system
     --font-display  Satoshi — hero headlines, section headings, campaign
                     statements, prominent card titles. Weights 500 / 700 only.
     --font-sans     Inter — navigation, body copy, forms, buttons, metadata,
                     everything UI. Weights 400 / 500 only.
     --font-wordmark DM Sans Medium — the footer "eelance" wordmark ONLY.
                     Deliberately independent of the main system.
     Only the weights listed above are loaded, so every rule below sticks to
     them; anything that would otherwise fall back to a browser-default bold
     (strong, b, bare h1-h6) is pinned explicitly to avoid faux-bold. */
  --font-display: "Satoshi", "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-sans: "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-wordmark: "DM Sans", "Inter", -apple-system, sans-serif;
  --shadow-card: 0 24px 60px -28px rgba(16, 24, 64, 0.25);
  --shadow-soft: 0 10px 30px -12px rgba(16, 24, 64, 0.18);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font-sans);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  line-height: 1.55;
  overflow-x: hidden;
  /* Sticky footer. Short pages — contact especially, at ~1970px — do not fill
     a tall viewport, and without this the footer ended mid-screen with the
     white body background showing beneath it. Most visible with a phone's
     "Desktop site" mode (roughly 980 x 2100) and on tall or zoomed-out
     desktop windows. The spare height goes to <main>, so the footer sits on
     the bottom edge instead of floating in the middle. */
  min-height: 100vh;
  min-height: 100dvh;   /* dvh discounts mobile browser chrome; vh is the fallback */
  display: flex;
  flex-direction: column;
}
/* React renders nav + main + footer into #root, so the growth has to be passed
   down through it rather than applied to <main> directly. */
#root {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}
#root > main { flex: 1 0 auto; }
img, svg { display: block; max-width: 100%; }
/* Deterrent against casual image saving (logos, testimonial photos,
   illustrations) — pairs with useNoImageDownload() in Shared.jsx, which blocks
   the right-click/drag events these properties would otherwise still allow
   through on some browsers. Not real protection (devtools and screenshots
   always work); it just removes the one-click "Save Image As" / drag-to-
   desktop path. */
img {
  -webkit-user-drag: none;
  user-drag: none;
  -webkit-touch-callout: none;
  user-select: none;
}
/* width:100%, height:auto is the right default for a ONE-OFF illustration —
   it just fills its box at its own natural shape. It is the wrong default
   for a box that holds a DIFFERENT illustration on every page (hero art on
   About vs. Contact, the "seamless" art on the landing page vs. About's two
   ecosystem rows, the CTA art on three separate pages): the eleven source
   images range from 530x330 (About-hero) to 245x414 — actually portrait,
   taller than wide — for About's folder illustration. With height:auto, each
   one sets its OWN box height, so the exact same box class renders at a
   visibly different size depending only on which illustration happens to
   land in it. The four contexts below fix that with aspect-ratio + contain:
   every illustration in a given context now occupies an identical box
   footprint, at the cost of a little letterboxing on whichever image's
   native shape departs most from the group's chosen ratio. Sep 2026. */
.illus { width: 100%; height: auto; }
.icon-img { display: block; }
a { color: inherit; text-decoration: none; }
.container { width: min(1180px, calc(100% - 48px)); margin: 0 auto; }
.center { text-align: center; }

/* ============ type ============ */
/* Headings are Satoshi. Bold (700) is the only heading weight — Satoshi's
   700 already reads heavier than Inter's did at 800/900, so dropping to 700
   keeps the same visual impact while staying inside the two-weight system. */
.h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 4.4vw, 3.1rem);
  line-height: 1.12;
  letter-spacing: -0.02em;
  margin: 0 0 28px;
}
.h2--display {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.5rem, 2.6vw, 2.1rem);
  line-height: 1.18;
  letter-spacing: -0.015em;
  margin: 0 0 22px;
}
/* Nothing may inherit a browser-default bold, since only 400/500 of Inter
   and 500/700 of Satoshi are downloaded — an unpinned 600/800/900 would be
   synthesised by the browser and look smeared. */
strong, b { font-weight: 500; }

/* ============ buttons ============ */
/* ONE button size for the whole site. Height and min-width are fixed rather
   than derived from padding, so "Hire Talent" and "Get Hired" come out
   identical despite different label lengths, and a CTA in the hero is the
   same size as the same CTA in a card or the closing section. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  /* Native <button> reset. Nearly every CTA on the site is an <a class="btn">,
     which has no default border — so for a long time nothing here showed the
     gap. "Send Message" is the one real <button class="btn">, and it was
     picking up Chrome's UA `border: 2px outset ButtonBorder`: a bevelled ring,
     grey top-left and black bottom-right, around the red pill.
     That border also broke the two overlays. Both .btn::before and .btn__trace
     use `inset: 0`, which resolves against the PADDING box, so a 2px border
     pulled the hairline ring inward and shrank the trace SVG to 174x48 while
     pillPath() still drew 178x52 geometry — which is why the dot rode outside
     the bottom edge. Removing the border realigns all three. */
  border: 0;
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  cursor: pointer;
  min-height: var(--btn-h);
  min-width: var(--btn-w);
  padding: 0 30px;
  border-radius: 999px;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 1rem;
  line-height: 1;
  text-align: center;
  transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
  will-change: transform;
}
.btn:active { transform: translateY(1px) scale(0.99); }
/* red reads more saturated than blue at the same alpha, so its glow is
   toned down further to keep the two buttons visually even instead of
   the red one "bleeding" into the space around it */
.btn--blue { background: var(--blue); color: #fff; box-shadow: 0 6px 14px -10px rgba(var(--blue-rgb), 0.4); }
.btn--blue:hover { background: var(--blue-dark); transform: translateY(-2px); }
.btn--red { background: var(--red); color: #fff; box-shadow: 0 6px 14px -10px rgba(231, 27, 36, 0.28); }
.btn--red:hover { background: var(--red-dark); transform: translateY(-2px); }
.btn--sm { min-height: 42px; min-width: 0; padding: 0 22px; font-size: 0.92rem; }
/* --wide used to be a second, larger, UPPERCASE treatment. That meant the
   very same "Hire Talent" label rendered at one size in the hero and a
   different size inside the audience card. It is now an alias of the base
   button so every CTA on the site is identical. */
.btn--wide { }
/* paired CTAs sit side by side — force them to exactly equal width */
.hero__actions .btn, .cta__actions .btn, .audience__cta .btn { flex: 0 0 auto; }

/* ============ reveal animation ============ */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2, 0.65, 0.25, 1); }
.reveal.is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
}

/* ============ navbar ============ */
/* floating translucent glass pill, inset from the top edge rather
   than a full-bleed solid bar */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  padding-top: 16px;
  /* At rest the nav sits in normal flow ABOVE the hero, so whatever is
     behind it is the plain white page background — which merged with the
     white pill into one solid white band. Every page's hero starts at
     var(--hero-top), so painting that same token here makes the strip
     seamless with the hero directly below it: the pill reads as floating
     on the hero. Sharing the token means the two can never drift apart.
     Once the hero has actually scrolled up behind the nav (see Navbar's
     scroll handler, which compares against the nav's measured height) the
     nav goes transparent so real content passes under the frosted glass. */
  background: var(--hero-top);
  /* No transition on the opaque state ON PURPOSE. The fade must be
     one-directional. Scrolling DOWN, fading out to transparent is safe: the
     hero is already behind the bar. Scrolling UP, a fade *in* is not — you
     cross the threshold and keep moving, so scrollY drops ~60px while the
     background is still 20-40% opaque, and the empty white slot behind the
     sticky bar shows through it for ~200ms. Snapping straight to opaque is
     invisible (the hero sits at the same colour right behind it) and kills
     that flash. The fade-out lives on .nav--scrolled below. */
}
.nav--scrolled {
  background: transparent;
  transition: background 0.25s ease;
}
.nav__inner {
  /* matches .container exactly so the pill's edges line up with the
     section cards stacked below it */
  width: min(1180px, calc(100% - 48px));
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 40px;
  padding: 10px 22px;
  border-radius: 999px;
  box-shadow: 0 12px 34px -18px rgba(6, 8, 20, 0.6);
  transition: box-shadow 0.25s ease;
  /* .nav sets z-index:50 AND position:sticky, which makes it a STACKING
     CONTEXT. Everything inside is then stacked against its siblings, not
     against the rest of the page — so the panel's z-index:49 was never
     "below the nav", it was above this pill, which had no z-index at all.
     Pinning the pill above the panel is what makes the menu slide out from
     BEHIND the bar instead of across it. z-index needs a position to apply. */
  position: relative;
  z-index: 2;
}

/* ---- the one dark-glass surface, declared ONCE ----
   The bar, the mobile menu panel and the scroll-to-top button are the same
   material. Listing them together rather than repeating the four tokens in
   three places is what stops them drifting apart again — a change here moves
   all three or none.

   Equally important is that none of these is nested inside another. An
   element with backdrop-filter becomes a Backdrop Root, so a descendant's
   backdrop-filter samples the ancestor's already-filtered output rather than
   the page: two 0.85 layers compose to 0.978 and the inner one renders almost
   opaque. That is a compositing difference, not a colour one, and no amount
   of tuning --glass can correct it. Keep these three as siblings. */
.nav__inner,
.nav__sheet,
.scroll-top {
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
}

/* hidden until the mobile breakpoint turns it into the menu panel */
.nav__sheet { display: none; }
/* only the shadow deepens on scroll — the fill deliberately does NOT change,
   so the bar looks identical at the top of the page and halfway down it */
.nav--scrolled .nav__inner { box-shadow: 0 16px 40px -16px rgba(0, 0, 0, 0.55); }
.nav__logo { display: flex; align-items: center; line-height: 0; }
.nav__logo .logo__mark { width: 36px; height: 36px; }
.nav__links {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: clamp(20px, 4vw, 56px);
  flex: 1;
}
.nav__links > a {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 0.82rem;
  line-height: 1;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #d7d9e0;
  padding: 8px 2px;
  position: relative;
  transition: color 0.2s ease;
}
.nav__links > a::after {
  content: "";
  position: absolute;
  left: 0; right: 100%;
  bottom: 2px;
  height: 2px;
  background: var(--blue-accent);
  transition: right 0.25s ease;
}
.nav__links > a:hover, .nav__links > a.is-active { color: #fff; }
.nav__links > a:hover::after, .nav__links > a.is-active::after { right: 0; }
.nav__burger {
  display: none;
  background: none;
  border: 0;
  padding: 0;
  margin-left: auto;
  cursor: pointer;
  /* Pinned to the logo's box. Allowed to size itself, three 2.6px bars with
     5px margins plus 8px padding measure 43.8px — TALLER than the 36px logo —
     so the burger silently became the tallest item and stretched the pill to
     ~82px while --nav-h still claimed 74px. Everything positioned from
     --nav-h was then wrong by 8px, which is what drove the dropdown up into
     the bar. Bars are spaced with gap now rather than margins so the height
     is exactly what is declared here. */
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
}
.nav__burger span {
  display: block;
  width: 26px; height: 2.6px;
  margin: 0;   /* spacing comes from the parent's gap: 5px */
  /* --blue-accent, not --blue: same cobalt hue and saturation, just lighter.
     The logo tone (#0046b6) against the near-black pill only reaches about
     2:1 contrast and the bars almost disappear; the accent keeps it clearly
     cobalt while staying legible. */
  background: var(--blue-accent);
  border-radius: 2px;
  transition: transform 0.25s ease, opacity 0.25s ease;
}
.nav__burger.is-open span:nth-child(1) { transform: translateY(7.6px) rotate(45deg); }
.nav__burger.is-open span:nth-child(2) { opacity: 0; }
.nav__burger.is-open span:nth-child(3) { transform: translateY(-7.6px) rotate(-45deg); }

/* logo */
.logo { display: inline-flex; align-items: center; gap: 3px; }
.logo__mark { width: 52px; height: 52px; border-radius: 50%; }
/* Wordmark — one supplied 183x71 asset (circle + y + eelance), NOT a badge
   plus a text element. The "eelance" overlaps the circle by design, and no
   amount of negative letter-spacing on a separate span reproduces that.
   Height is set from the circle: the circle is 61 of the asset's 71px, so a
   64px lockup puts the circle at ~55px — matching the 56px badge the footer
   used before, keeping the footer's vertical rhythm unchanged. */
.logo--wordmark { display: inline-flex; }
.logo__lockup {
  height: 64px;
  width: auto;
  display: block;
}
@media (max-width: 480px) {
  .logo__lockup { height: 54px; }
}

/* ============ hero ============ */
/* The landing view is exactly three bands: nav, hero, marquee. The hero is
   sized to whatever the viewport has left after the other two, so the logo
   strip always lands flush at the bottom of the first screen instead of
   being half cut off.
   This has to be `height`, not `min-height`. min-height only sets a floor —
   when the hero's own content ran taller than it (which it does on phones,
   because of the illustration) the section simply grew and shoved the
   marquee off screen. With a fixed height the illustration flexes down to
   fit instead, so the arithmetic holds on any screen. */
.hero {
  background: linear-gradient(180deg, var(--hero-top) 0%, var(--hero-bottom) 100%);
  padding: 40px 0 48px;
  overflow: hidden;
}
/* ONLY the landing page gets the one-screen treatment. It is the only page
   with a marquee under the hero, so it is the only page where the
   nav + hero + strip arithmetic applies. Applying it everywhere reserved
   118px of marquee that does not exist on Contact/About/Legal and then
   centred the content in the leftover box — which is what opened those big
   gaps around the illustration. */
.hero--landing {
  height: calc(100svh - var(--nav-h) - var(--marquee-h));
  min-height: 420px;
  padding: 20px 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.hero__inner { width: min(900px, calc(100% - 48px)); margin: 0 auto; text-align: center; }
.hero--landing .hero__inner {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 0;                  /* lets the art actually shrink */
  height: 100%;
}
/* the illustration is the only elastic part — the headline, subline and
   buttons keep their size and the art gives up space as the screen shortens */
.hero__art { width: min(480px, 82%); margin: 0 auto; }
.hero--landing .hero__art {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero--landing .hero__art > svg, .hero--landing .hero__art > .illus {
  width: 100%;
  height: 100%;
  max-height: 100%;
  /* inline SVG letterboxes inside its box via preserveAspectRatio, so giving
     it both dimensions scales it to fit rather than distorting it */
  object-fit: contain;
}
.hero__title {
  font-family: var(--font-display);
  font-size: clamp(2.8rem, 7.5vw, 5.3rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: 20px 0 14px;
}
.hero__sub { font-size: clamp(1.05rem, 2vw, 1.3rem); color: var(--body); margin: 0 0 26px; }
.hero__actions { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }
/* when a paired blue/red button wraps onto its own line (narrow screens),
   give it more vertical breathing room than the horizontal gap alone —
   otherwise the two glow shadows sit close enough to blur together. */
.hero__actions, .cta__actions { row-gap: 22px; }
.hero--contact { padding: 64px 0 44px; }
.hero--contact .hero__sub { max-width: 46ch; margin-inline: auto; margin-bottom: 0; }
.cta--page { padding: 20px 0 100px; }

/* ============ logo marquee ============ */
/* The edge fade used to be a mask-image on this element. A mask makes the
   element's OWN background transparent as well, so the pale blue band faded
   out at both ends and the white page behind it showed through — reading as
   a white halo either side of the logos. Two overlay gradients instead: the
   band stays solid edge to edge, and the logos dissolve into the band colour
   rather than into nothing. */
/* The strip used to be --bg-soft (#e8f0fd), 13 levels lighter than the hero
   above it, so it read as a separate panel bolted under the landing view.
   Using the hero gradient's END colour makes the band a seamless
   continuation of the hero instead. */
.marquee {
  position: relative;
  background: var(--hero-bottom);
  padding: 26px 0;
  overflow: hidden;
}
.marquee::before,
.marquee::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(40px, 9%, 130px);
  z-index: 2;
  pointer-events: none;
}
.marquee::before { left: 0; background: linear-gradient(90deg, var(--hero-bottom) 15%, rgba(var(--hero-bottom-rgb), 0)); }
.marquee::after { right: 0; background: linear-gradient(270deg, var(--hero-bottom) 15%, rgba(var(--hero-bottom-rgb), 0)); }
.marquee__track {
  display: flex;
  gap: 22px;
  width: max-content;
  animation: marquee 28s linear infinite;
}
/* (hover: hover) and (pointer: fine) — real mouse/trackpad only. A touchscreen
   has no hover/leave pair: a tap fires a synthetic hover that never clears
   until something else is tapped, so on phones this rule was pausing the
   marquee on the first tap and leaving it frozen forever. Scoping it to
   hover-capable pointers keeps the "pause to read the logos" convenience on
   desktop and leaves touch devices scrolling continuously, tap or not. */
@media (hover: hover) and (pointer: fine) {
  .marquee:hover .marquee__track { animation-play-state: paused; }
}
@keyframes marquee { to { transform: translateX(-50%); } }
/* 2026-09-09: was auto-width (padding + the logo's own intrinsic width at a
   fixed 34px height) — with real client logos spanning aspect ratios from
   1.14:1 (We Sale Leads) to 5.03:1 (ecomersify), that produced pills from
   ~71px to ~231px wide in the same row, which read as uneven rather than as
   a set. Every pill is now the same fixed size; the logo inside is capped by
   BOTH a max-width and max-height (see .marquee__logo-img) and centered, so
   whichever dimension the aspect ratio hits first is what constrains it —
   nothing overflows the pill, nothing needs cropping, and a squarish mark
   like RideRiver/We Sale Leads just sits with a bit more side padding than a
   wide wordmark like ecomersify's. --marquee-h above (118px = this 66px
   pill + the .marquee section's 26px top/bottom padding) is unchanged. */
.marquee__item {
  background: #fff;
  border-radius: 12px;
  box-shadow: var(--shadow-soft);
  width: 178px;
  height: 66px;
  box-sizing: border-box;
  padding: 8px 16px;
  white-space: nowrap;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.marquee__logo-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}

/* ============ seamless ============ */
.seamless { padding: 110px 0 90px; }
.seamless__grid {
  display: grid;
  grid-template-columns: 1fr 1.05fr;
  gap: clamp(32px, 6vw, 90px);
  align-items: center;
}
/* Uniform box across all three illustrations that use this class: the
   landing page's own seamless art (1.11:1) and About's two ecosystem rows
   (1.58:1 and 0.59:1 — the folder illustration is genuinely portrait). 1.2:1
   sits close to the landing image so that one barely letterboxes, while the
   whiteboard and folder images — the two furthest from each other — pillar/
   letterbox symmetrically instead of stretching the row to whatever height
   today's image happens to be. */
/* width:100% is not decorative here — leaving width on its implicit "auto"
   and relying on grid stretch is what caused the portrait folder image's box
   to shrink to its own natural pixel width (245px) plus padding instead of
   filling the grid track (~340px): with aspect-ratio set on a box whose only
   content is a smaller-than-track replaced element, "auto" resolved via
   content size instead of stretch. Landscape images never exposed this,
   because their natural width already exceeded the track. min-width:0 is
   kept alongside it as the standard belt-and-braces grid/flex fix, but on
   its own it did not solve this — width:100% is what did. */
.seamless__art { padding: 12px; aspect-ratio: 1.2 / 1; min-width: 0; width: 100%; }
.seamless__art .illus { height: 100%; object-fit: contain; }
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 26px; }
.step { display: flex; gap: 18px; align-items: flex-start; }
.step__icon {
  flex: 0 0 auto;
  width: 56px; height: 56px;
  border-radius: 14px;
  display: grid;
  place-items: center;
  box-shadow: 0 10px 20px -14px rgba(16, 24, 64, 0.45);
  overflow: hidden;
}
.step__icon svg, .step__icon .icon-img { width: 100%; height: 100%; object-fit: contain; }
.step strong { font-size: 1.06rem; display: block; margin-bottom: 2px; }
.step p { margin: 0; color: var(--body); }

/* ============ services ============ */
.services { padding: 40px 0 110px; }
.services__grid {
  margin-top: 46px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 54px 28px;
}
.service {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  text-align: center;
  transition: transform 0.22s ease;
}
.service:hover { transform: translateY(-6px); }
.service__icon {
  width: 76px; height: 76px;
  border-radius: 50%;
  background: #f4f4f6;
  color: var(--blue);
  display: grid;
  place-items: center;
  box-shadow: var(--shadow-soft);
  transition: box-shadow 0.22s ease, background 0.22s ease, color 0.22s ease;
}
.service:hover .service__icon { background: #e4edfd; box-shadow: 0 14px 26px -18px rgba(16, 24, 64, 0.4); }
.service__icon svg, .service__icon .icon-img { width: 36px; height: 36px; object-fit: contain; }
.service__label {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.05rem, 1.8vw, 1.45rem);
  color: var(--ink);
}

/* services revealed by "View All Services" animate in on their own —
   they mount after the page's scroll-reveal observer has already run,
   so they use a plain CSS animation instead of the .reveal--t/IntersectionObserver
   combo (which would never fire for elements added after mount). */
.service--extra { animation: serviceExtraIn 0.45s cubic-bezier(0.2, 0.65, 0.25, 1) both; }
@keyframes serviceExtraIn {
  from { opacity: 0; transform: translateY(18px); }
}
@media (prefers-reduced-motion: reduce) {
  .service--extra { animation: none; }
}

/* expand/collapse toggle for the services grid */
.services__more { display: flex; justify-content: center; margin-top: 46px; }
.services__toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: none;
  border: 1.6px solid var(--ink);
  border-radius: 999px;
  padding: 13px 30px;
  font: inherit;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.services__toggle svg { width: 16px; height: 16px; transition: transform 0.25s ease; }
.services__toggle:hover { background: var(--ink); color: #fff; }
.services__toggle[aria-expanded="true"] svg { transform: rotate(-90deg); }
.services__toggle:not([aria-expanded="true"]) svg { transform: rotate(90deg); }

/* ============ audience cards ============ */
.audience { padding: 80px 0; background: var(--bg-soft); }
.audience + .testimonials { background: #fff; }
.audience__card {
  margin-top: 14px;
  background: #fff;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: clamp(28px, 4.5vw, 56px);
  display: grid;
  grid-template-columns: 1.05fr 1.1fr 1fr;
  gap: clamp(22px, 3.5vw, 44px);
  align-items: center;
}
.audience__points { list-style: none; margin: 0; padding: 0; display: grid; gap: 26px; }
/* these items are a single paragraph next to an icon (no separate title
   line like the "How It Works" steps have), so the icon should center
   against the whole text block — flex-start left it pinned to the top,
   which looked wrong whenever the text wrapped to two lines. */
.audience__points li { display: flex; align-items: center; gap: 16px; }
.audience__points p { margin: 0; color: var(--ink); font-size: 1.02rem; }
/* The two AudienceCard illustrations (1.25:1 and 1.33:1) were already the
   closest-matched pair on the site — this is here for the same reason as
   the other three, not because it was visibly broken. */
.audience__art { aspect-ratio: 1.28 / 1; min-width: 0; width: 100%; }
.audience__art .illus { height: 100%; object-fit: contain; }
.point__icon {
  flex: 0 0 auto;
  width: 52px; height: 52px;
  border-radius: 12px;
  background: #fff;
  color: var(--blue);
  display: grid;
  place-items: center;
  box-shadow: var(--shadow-soft);
}
.point__icon svg, .point__icon .icon-img { width: 30px; height: 30px; object-fit: contain; }
.audience__cta .btn { margin-top: 4px; }

/* ============ testimonials ============ */
.testimonials { padding: 120px 0; position: relative; overflow: hidden; }
.testimonials::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(52% 60% at 50% 38%, rgba(var(--blue-rgb), 0.045), transparent 70%);
  pointer-events: none;
}
.testimonials__sub { max-width: 460px; margin: 14px auto 0; color: var(--muted); }
.testimonials__stage {
  position: relative;
  max-width: 1160px;
  margin: 64px auto 0;
  padding: 24px 0 48px;
  /* NOT overflow-x:hidden + overflow-y:visible — per spec, if one axis is
     anything other than visible the other computes to auto, so that combo
     silently clipped the cards' bottoms and shadows into a hard flat edge.
     The side cards are scaled down and fit inside the track now, and body
     already has overflow-x:hidden, so nothing here needs clipping. */
  overflow: visible;
}
.testimonials__track {
  --t-shift: 150px;
  /* 2026-09-05: widened from clamp(230,52vw,300) — at the taller card height
     below (needed for the real, longer quotes) the old width read as thin
     and flattened. Wider brings the proportions back toward a normal card;
     margins were re-verified at every width after the change (see the
     height note), so this did not reopen the truncation bug. */
  --t-card-w: clamp(260px, 58vw, 340px);
  /* ONE definition of the card height. The track reserves exactly this and
     .t-card is exactly this, so the two can never disagree — which is what
     let a tall card ride up over the section heading before.
     The real 8 client quotes run far longer than the old placeholder set,
     and at too small a multiplier several of them need more lines than the
     card has room for: -webkit-line-clamp doesn't add an ellipsis when the
     FLEX box itself is shorter than the clamped line count, it just
     hard-clips mid-word into the author row below.
     2026-09-08: "too tall, a lot of negative space" — fair. The flat 1.4
     here was sized for the WORST case across this whole clamp's width range
     (260-340px), measured at its narrowest point (~279px, where a quote
     needs the most lines relative to width) with 100px+ of spare margin on
     top. But a card this wide almost always renders at the clamp's own
     ceiling (340px, reached the moment the viewport clears ~586px — most
     desktop browsers), where 1.4 leaves ~130px of dead space no quote ever
     uses. A single multiplier can't be tight at 340px AND safe at 279px at
     once — the same text needs proportionally more height on a narrower
     card. So this is now the WIDE case only (measured needed 1.024 at
     340px, +buffer). The narrow end of this same clamp (481-586px viewport,
     card still short of 340px) gets its own tighter-but-safer multiplier in
     the @media block right below — same split already used for the mobile
     tiers further down, for the same reason.
     Every multiplier on this page was re-measured against the real Inter
     font (a fallback font renders more compactly and understates real
     wrapping), by temporarily un-clamping each of the 8 real quotes and
     reading the natural content height at each width — not guessed. */
  --t-card-h: calc(var(--t-card-w) * 1.12);
  --t-side-scale: 0.84;
  position: relative;
  height: calc(var(--t-card-h) + 16px);
  /* claim horizontal gestures for the swipe handler while leaving vertical
     scrolling to the page */
  touch-action: pan-y;
  outline: none;
}
/* The narrow sliver of --t-card-w's own clamp range that never reaches its
   340px ceiling — from just above the mobile breakpoint until the viewport
   clears ~586px. Real-font-measured worst case here is ~1.39 (at ~279px,
   the narrowest this band gets); 1.48 keeps a real margin on top of that
   without carrying it into the (much more common) 340px case above, which
   is exactly the split this fixes. */
@media (min-width: 481px) and (max-width: 590px) {
  .testimonials__track { --t-card-h: calc(var(--t-card-w) * 1.48); }
}
.testimonials__track:focus-visible { outline: 2px solid var(--blue); outline-offset: 10px; border-radius: 20px; }
/* Portrait "stack of cards". The two behind stay upright (no tilt) and sit
   slightly SMALLER than the middle one, so the trio reads as a clean
   1-small / 2-big / 3-small row rather than a fanned-out pile. They're a
   pale cobalt tint — the same family as the section below — so the white
   active card in the middle is clearly the focus. */
.t-card {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--t-card-w);
  /* HEIGHT, not aspect-ratio. aspect-ratio sets only the PREFERRED size: a
     card whose content is taller simply grows past it. And because the card is
     centred with translate(-50%, -50%) it grows UPWARD as well as down, so a
     long quote rode up over the section heading and hid the subtitle. The
     track reserves exactly this height too, so the two cannot disagree.
     Every card is now identical regardless of how long its quote is. */
  height: var(--t-card-h);
  background: var(--bg-soft);
  border-radius: var(--radius-md);
  box-shadow: 0 14px 32px -22px rgba(var(--blue-rgb), 0.35);
  padding: clamp(22px, 3vw, 32px) clamp(20px, 2.6vw, 28px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  transition: transform 0.55s cubic-bezier(0.22, 0.8, 0.28, 1), opacity 0.5s ease, background 0.4s ease 0.05s, box-shadow 0.4s ease;
}
.t-card blockquote,
.t-card__meta {
  margin: 0;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.t-card blockquote {
  margin: 0 0 20px;
  font-size: clamp(0.96rem, 1.6vw, 1.1rem);
  line-height: 1.6;
  color: var(--ink);
}
.t-card__meta { display: flex; align-items: center; justify-content: center; gap: 12px; }
.t-card__meta span:last-child { text-align: left; }
.t-card strong { display: block; }
.t-card em { font-style: normal; color: var(--muted); font-size: 0.88rem; }
.t-card--active {
  background: #fff;
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
  z-index: 3;
  box-shadow: 0 30px 70px -28px rgba(16, 24, 64, 0.32);
}
.t-card--active blockquote,
.t-card--active .t-card__meta { opacity: 1; transition-delay: 0.2s; }

/* The quote takes the space left over after the author block and is capped in
   LINES. This is a guarantee, not a design choice — the quotes below are
   written to sit inside it, but if a longer one is ever added it truncates
   instead of deforming the card. */
.t-card blockquote {
  margin: 0;
  flex: 1 1 auto;
  min-height: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 11;
  overflow: hidden;
  /* centre a short quote in the space rather than pinning it to the top */
  align-content: center;
}
/* the author block never shrinks, so a long quote cannot squeeze it away */
.t-card__meta {
  /* Fixed, not auto. "CEO, Bombay Berlin Film Production" wraps to three lines
     while "Founder, Topee" takes one, so an auto-height author block left each
     card a DIFFERENT amount of room for its quote — the quote area has to be
     constant for the cards to read as uniform. */
  flex: 0 0 96px;
  height: 96px;
  align-items: center;
}
/* Three lines, sized to the longest real role ("Founder, Kitchen Krafts
   Organic Food"). The clamp is a backstop against a future longer one, not a
   working constraint — every current role fits without truncating. */
.t-card__meta em {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}
/* upright and scaled down — --t-side-scale keeps the two side cards
   visibly smaller than the middle one without changing their box, so the
   layout height stays driven by the active card alone */
.t-card--prev, .t-card--next {
  opacity: 1;
  z-index: 2;
  cursor: pointer;
}
.t-card--prev { transform: translate(calc(-50% - var(--t-shift)), -50%) scale(var(--t-side-scale)); }
.t-card--next { transform: translate(calc(-50% + var(--t-shift)), -50%) scale(var(--t-side-scale)); }
.t-card--prev:hover { transform: translate(calc(-50% - var(--t-shift)), -50%) scale(calc(var(--t-side-scale) + 0.03)); }
.t-card--next:hover { transform: translate(calc(-50% + var(--t-shift)), -50%) scale(calc(var(--t-side-scale) + 0.03)); }
.t-card--far {
  transform: translate(-50%, -50%) scale(var(--t-side-scale));
  opacity: 0;
  z-index: 1;
  pointer-events: none;
}
.avatar {
  /* flex items shrink by default. The avatar sits in a flex row next to the
     name + role, so whenever that text wrapped to two lines ("Head of Growth,
     OGQ", "Brand Designer, Yeelancer") the avatar got squeezed horizontally
     and rendered as an oval, while a short one-line role ("CMO, Railofy")
     stayed a circle. Locking the flex basis keeps every avatar identical. */
  flex: 0 0 48px;
  width: 48px; height: 48px;
  border-radius: 50%;
  background: var(--blue);
  color: #fff;
  box-shadow: 0 0 0 4px #fff, 0 0 0 6px var(--bg-soft);
  display: grid;
  place-items: center;
  font-weight: 500;
  font-size: 1.15rem;
  /* overflow hidden clips .avatar__img to the circle — the img itself is a
     plain square (192x192, see assets.js), so without this it would square
     off the corners of what should read as a round headshot. */
  overflow: hidden;
}
.avatar__img {
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: 50%;
}
.t-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 52px; height: 52px;
  border-radius: 50%;
  border: 0;
  background: #fff;
  color: var(--ink);
  box-shadow: var(--shadow-soft);
  display: grid;
  place-items: center;
  cursor: pointer;
  z-index: 4;
  transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.t-arrow svg { width: 22px; height: 22px; }
.t-arrow--prev { left: clamp(4px, 1vw, 16px); }
.t-arrow--prev svg { transform: rotate(180deg); }
.t-arrow--next { right: clamp(4px, 1vw, 16px); }
.t-arrow:hover { background: var(--blue); color: #fff; transform: translateY(-50%) scale(1.06); }
.t-dots { position: absolute; left: 0; right: 0; bottom: 16px; display: flex; gap: 10px; justify-content: center; }
.t-dots button {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto;
  width: 10px; height: 10px;
  padding: 0;
  margin: 0;
  border-radius: 50%;
  border: 0;
  background: #bfd0ee;
  cursor: pointer;
  transition: transform 0.2s ease, background 0.2s ease;
}
.t-dots button.is-active { background: var(--blue); transform: scale(1.25); }

/* ============ blogs ============ */
.blogs { padding: 110px 0 70px; }
.blogs__grid {
  margin-top: 56px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 34px;
}
.blog {
  display: flex;
  flex-direction: column;
  transition: transform 0.22s ease;
}
.blog:hover { transform: translateY(-6px); }
.blog__media {
  position: relative;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: var(--shadow-soft);
}
.blog__scene { width: 100%; height: auto; transition: transform 0.4s ease; }
.blog:hover .blog__scene { transform: scale(1.04); }
.blog__tag {
  position: absolute;
  left: 14px; bottom: 14px;
  background: #c32cd6;
  color: #fff;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  padding: 7px 14px;
  border-radius: 999px;
}
.blog h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.35rem;
  line-height: 1.3;
  margin: 20px 0 10px;
}
.blog p { margin: 0 0 18px; color: var(--muted); }
.blog__more {
  width: 56px; height: 34px;
  border: 1.6px solid var(--ink);
  border-radius: 999px;
  display: grid;
  place-items: center;
  transition: background 0.2s ease, color 0.2s ease;
}
.blog__more svg { width: 18px; height: 18px; }
.blog__more:hover { background: var(--ink); color: #fff; }

/* ============ CTA ============ */
.cta { padding: 60px 0 120px; }
.cta__card {
  background: var(--bg-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: clamp(30px, 5vw, 64px);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(24px, 5vw, 70px);
  align-items: center;
}
.cta__content p { color: var(--body); font-size: 1.08rem; margin: 0 0 30px; max-width: 46ch; }
.cta__actions { display: flex; gap: 16px; flex-wrap: wrap; }
/* Same class, three different illustrations across three pages — the
   landing page's puzzle-piece art (1.04:1), About's airplane send-off
   (1.21:1), and Contact's phone call (1.09:1). All close in shape already,
   so 1.1:1 keeps every one of them close to full-bleed with only a sliver
   of letterboxing on the About image. */
.cta__art { aspect-ratio: 1.1 / 1; min-width: 0; width: 100%; }
.cta__art .illus { height: 100%; object-fit: contain; }

/* ============ footer ============ */
.footer { background: #040405; color: #fff; padding: 72px 0 40px; }
.footer__inner { display: grid; justify-items: center; gap: 30px; text-align: center; }
.footer .logo__mark { width: 56px; height: 56px; }
.footer__social { display: flex; gap: 20px; }
.footer__social a {
  width: 54px; height: 54px;
  border-radius: 50%;
  background: #fff;
  color: #0c0c10;
  display: grid;
  place-items: center;
  transition: transform 0.2s ease, background 0.2s ease, color 0.2s ease;
}
.footer__social a svg { width: 24px; height: 24px; }
.footer__social a:hover { transform: translateY(-4px); background: var(--blue-accent); color: #fff; }
.footer__links, .footer__legal { display: flex; gap: clamp(20px, 4vw, 44px); flex-wrap: wrap; justify-content: center; }
.footer__links a, .footer__legal a { color: #c9cbd4; transition: color 0.2s ease; }
.footer__links a:hover, .footer__legal a:hover { color: #fff; }
.footer__copy {
  margin: 26px 0 0;
  color: #8e909c;
  font-size: 0.8rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* ============ scroll-to-top ============ */
.scroll-top {
  position: fixed;
  right: 22px;
  bottom: 22px;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  /* the glass itself is declared once, up with .nav__inner / .nav__sheet —
     repeating it here is how the three drifted apart before */
  /* --blue-accent, matching .nav__burger span exactly: the same control
     language top and bottom of the screen. Not --blue (#0046b6), which only
     reaches ~2:1 against the near-black glass and nearly disappears. */
  color: var(--blue-accent);
  display: grid;
  place-items: center;
  box-shadow: 0 10px 24px -12px rgba(6, 8, 20, 0.6);
  cursor: pointer;
  opacity: 0;
  transform: translateY(14px) scale(0.9);
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease, background 0.2s ease;
  z-index: 60;
}
.scroll-top.is-visible { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
/* Hover lifts ONLY. The background is deliberately left alone so this button
   is the same glass as the bar at every moment — any hover tint, even 0.92,
   made it read as a different component the instant you touched it. */
.scroll-top:hover { transform: translateY(-3px); }
.scroll-top svg { width: 22px; height: 22px; }
@media (max-width: 480px) {
  .scroll-top { right: 14px; bottom: 14px; width: 44px; height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-top { transition: opacity 0.2s ease; }
}

/* ============ responsive ============ */
@media (max-width: 1020px) {
  /* TABLET: keep the desktop 3x2 shape (this matches the base rule, so the
     grid simply stays 3-up here) but tighten the gaps for the narrower page */
  .services__grid { gap: 44px 22px; }
  .audience__card { grid-template-columns: 1fr 1fr; }
  .audience__cta { grid-column: 1 / -1; text-align: center; }
  .audience__cta .h3 { margin-bottom: 18px; }
}
@media (max-width: 880px) {
  .seamless__grid { grid-template-columns: 1fr; }
  .seamless__art { max-width: 420px; margin: 0 auto; }
  .blogs__grid { grid-template-columns: 1fr; max-width: 460px; margin-inline: auto; }
  .cta__card { grid-template-columns: 1fr; text-align: center; }
  .cta__art { max-width: 380px; margin: 0 auto; }
  .cta__content p { margin-inline: auto; }
  .cta__actions { justify-content: center; }
}
@media (max-width: 780px) {
  /* the in-pill row is removed entirely here; .nav__sheet takes over, and
     because it is display:none above this breakpoint exactly one of the two
     is ever exposed to assistive tech */
  .nav__links { display: none; }
  .nav__sheet {
    display: flex;
    /* ABSOLUTE, positioned against .nav — not fixed at a hardcoded offset.
       top:100% resolves to the bar's real rendered bottom whatever it
       measures, so the 6px gap holds at every breakpoint and through any
       future change to the pill's padding or contents. The previous
       80px/70px values were derived from --nav-h, and the moment the real
       pill grew past that (the burger did exactly that) the panel slid up
       INTO the bar.
       24px inset matches .nav__inner's calc(100% - 48px), so the panel lines
       up exactly under the pill instead of being narrower than it. */
    position: absolute;
    top: calc(100% + 6px);
    left: 24px;
    right: 24px;
    /* BELOW .nav__inner (z-index 2), so it emerges from behind the bar */
    z-index: 1;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 6px 0;
    border-radius: 20px;
    transform: translateY(-130%);
    opacity: 0;
    pointer-events: none;
    transition: transform 0.3s ease, opacity 0.3s ease;
    box-shadow: 0 30px 50px -20px rgba(0, 0, 0, 0.6);
  }
  .nav__sheet.is-open { transform: none; opacity: 1; pointer-events: auto; }
  .nav__sheet > a {
    font-family: var(--font-sans);
    font-weight: 500;
    font-size: 0.82rem;
    line-height: 1;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: #d7d9e0;
    padding: 17px 0;
    margin: 0 22px;
    text-align: center;
    transition: color 0.2s ease;
  }
  .nav__sheet > a:hover, .nav__sheet > a.is-active { color: #fff; }
  /* cobalt separators between the items.
     full-strength #0058e6 hairlines read as harsh electric rules against the
     near-black panel — same cobalt, dialled back so they separate the items
     without competing with them */
  .nav__sheet > a + a { border-top: 1px solid rgba(var(--blue-rgb), 0.65); }
  .nav__burger { display: flex; }

  /* Sections keep their generous desktop padding by default, which
     reads as a huge dead-space wall on short phone viewports. Scale
     every section's vertical rhythm down for <=780px instead. */
  .seamless { padding: 56px 0 48px; }
  .seamless--stack { padding: 48px 0 56px; }
  .seamless--stack .seamless__grid { margin-top: 32px; }
  .seamless--stack .seamless__grid + .seamless__grid { margin-top: 40px; }
  .services { padding: 32px 0 56px; }
  .services__grid { margin-top: 32px; }
  .audience { padding: 48px 0; }
  .testimonials { padding: 56px 0; }
  .testimonials__stage { margin: 32px auto 0; padding: 16px 0 48px; }
  .testimonials__track { --t-shift: 100px; }
  .blogs { padding: 56px 0 40px; }
  .blogs__grid { margin-top: 36px; }
  .cta { padding: 40px 0 56px; }
  .footer { padding: 48px 0 28px; }
  .about-story { padding: 36px 0 48px; }
  .legal { padding: 36px 0 56px; }
  /* .hero--about and .hero--legal mobile padding moved out of this block —
     their unconditional base rules live further down the file (near line
     1356), textually AFTER this media query, so at equal specificity those
     later rules always won regardless of viewport and these two lines were
     dead: every phone was getting the 64px/60px desktop padding instead of
     40px. .hero--contact's base rule sits earlier in the file (line 461),
     so its override here is the only one of the three that actually worked
     — kept as-is. The other two now live next to their base rules instead. */
  .hero--contact { padding: 40px 0 32px; }
  .cta--page { padding: 20px 0 48px; }

  /* On phones the hero + marquee should read as "one screen" — the
     marquee peeking in at the bottom is the cue to keep scrolling.
     Without this, a naturally-short hero lets the next section's
     heading start bleeding into the first view on taller phones.
     min-height only ever ADDS space (never clips), so shorter/older
     phones that need to scroll a bit sooner are unaffected. Nav here
     is ~52px tall and the marquee strip is ~118px tall. */
  /* height/flex now come from the base .hero rule — the old hardcoded
     "100svh - 190px" guessed at the nav+marquee total and drifted whenever
     either changed, which is what left the logo strip half visible */
}
/* MOBILE: 2 columns x 3 rows for the featured six. Switching here rather
   than at 620px because the longest labels ("Full Stack Development",
   "Social Media Marketing") wrap to three lines when squeezed 3-up below
   roughly 720px. */
@media (max-width: 720px) {
  .services__grid { grid-template-columns: repeat(2, 1fr); gap: 34px 16px; }
}
@media (max-width: 620px) {
  .audience__card { grid-template-columns: 1fr; }
  .service__icon { width: 64px; height: 64px; }
  .service__icon svg, .service__icon .icon-img { width: 30px; height: 30px; }
  /* arrows go away on phones — swipe is the gesture here, and the dots stay
     as the visible affordance. Pad them out to a proper 44px tap target
     without changing how large the dot itself looks. */
  .t-arrow { display: none; }
  .testimonials__track { --t-shift: 82px; }
  .t-dots { gap: 0; bottom: 6px; }
  .t-dots button {
    width: 44px; height: 34px;
    background: none;
    border-radius: 0;
    display: grid;
    place-items: center;
  }
  .t-dots button::before {
    content: "";
    width: 10px; height: 10px;
    border-radius: 50%;
    background: #bfd0ee;
    transition: transform 0.2s ease, background 0.2s ease;
  }
  .t-dots button.is-active { background: none; transform: none; }
  .t-dots button.is-active::before { background: var(--blue); transform: scale(1.25); }
}
@media (max-width: 480px) {
  .nav__inner { padding: 8px 16px; gap: 24px; }
  .nav__logo .logo__mark { width: 30px; height: 30px; }
  .nav__burger { width: 30px; height: 30px; flex-basis: 30px; }
  /* No .nav__sheet offset needed here any more: top:calc(100% + 6px) follows
     the bar's real height, so the smaller pill at this width is handled
     automatically instead of being guessed at a second time. */
  /* trim the empty space around the hero on small screens without
     shrinking the illustration or heading themselves */
  .hero--landing { padding: 14px 0; }
  /* the pill is 64px tall at this width, not 74 — keeps nav+hero+marquee
     summing to exactly one screen */
  :root { --nav-h: 64px; }
  .hero__title { margin: 14px 0 10px; }
  .hero__sub { margin: 0 0 20px; }
  .h2, .audience h2 { font-size: 1.5rem; margin-bottom: 20px; }
  .testimonials__track {
    /* 2026-09-05: widened from clamp(190,58vw,250) to match the desktop
       widening below — same "looked flattened at the taller height"
       feedback applied here too. */
    --t-card-w: clamp(230px, 74vw, 300px);
    --t-shift: 64px;
    /* height deliberately NOT set here any more — see the six @media
       blocks below. */
  }
}
/* 2026-09-09: replaced the three multiplier-based mobile tiers above with
   six fixed-pixel tiers. Root cause: the real content height a card needs
   is a STEP function of width (it only changes when text re-wraps to one
   more/fewer line), while --t-card-w grows continuously with vw. A
   `calc(var(--t-card-w) * multiplier)` formula can only be tight at one
   point inside any range where the true requirement is flat — everywhere
   else in that range it's either too tall (excess negative space, the
   phone complaint this fixes) or, worse, too short (the previous
   345/405 boundary actually clipped Surya Gunreddy's quote with NO
   ellipsis at vw 345-354 — see -webkit-line-clamp comment above — because
   the multiplier undershot right at the tier's own low end).
   Each band below was measured against the real Inter font (not the
   headless fallback) by cloning every blockquote, stripping the line-clamp,
   and reading its natural scrollHeight across a dense width sweep, then
   picking the worst (tallest) testimonial's requirement as that band's
   fixed height, +~30px buffer. */
@media (min-width: 394px) and (max-width: 480px) {
  /* worst case (Dharam Vir Singh) needed 374.6px */
  .testimonials__track { --t-card-h: 405px; }
}
@media (min-width: 378px) and (max-width: 393px) {
  /* worst case (Kailash Nichani) needed 399.6px */
  .testimonials__track { --t-card-h: 430px; }
}
@media (min-width: 368px) and (max-width: 377px) {
  /* worst case (Surya Gunreddy) needed 424.6px */
  .testimonials__track { --t-card-h: 455px; }
}
@media (min-width: 326px) and (max-width: 367px) {
  /* worst case (Kailash Nichani) needed 448.6px */
  .testimonials__track { --t-card-h: 480px; }
}
@media (min-width: 314px) and (max-width: 325px) {
  /* worst case (Kailash Nichani) needed 473.9px */
  .testimonials__track { --t-card-h: 505px; }
}
@media (max-width: 313px) {
  /* --t-card-w is pinned at its 230px floor through this whole band
     (iPhone SE 1st-gen, folded foldables). Same-width measurements were
     noisy here (472-498px across otherwise-identical renders — likely
     font-hinting jitter right at a line-wrap boundary), so this uses the
     worst observed value (Kailash Nichani, 498.3px) rather than a typical
     one. */
  .testimonials__track { --t-card-h: 530px; }
}

/* ============================================================
   ENHANCEMENTS: stagger, tilt, stats, hero draw, contact form
   ============================================================ */

/* tilt-aware reveal variant: uses `translate` for entrance so
   `transform` stays free for the cursor tilt (they compose) */
.reveal--t {
  opacity: 0;
  translate: 0 26px;
  transition: opacity 0.7s ease, translate 0.7s cubic-bezier(0.2, 0.65, 0.25, 1);
}
.reveal--t.is-visible { opacity: 1; translate: 0 0; }

.tilt {
  transform: perspective(820px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(var(--ty, 0px));
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.2s ease;
  transform-style: preserve-3d;
}
/* tilt replaces the old translateY hover on these cards */
.service.tilt:hover { transform: perspective(820px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(-4px); }
.service.tilt .service__icon { transition: box-shadow 0.22s ease, background 0.22s ease, color 0.22s ease, transform 0.25s ease; transform: translateZ(28px); }
.blog.tilt:hover { transform: perspective(820px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(-4px); }
.blog.tilt .blog__media { transform: translateZ(22px); }

/* ---- hero illustration materializes out of nothing (Raycast-style) ---- */
.hero__art--draw {
  animation: heroMaterialize 1.5s cubic-bezier(0.16, 0.8, 0.2, 1) both;
}
@keyframes heroMaterialize {
  0%   { opacity: 0; transform: scale(0.94) translateY(14px); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}

/* ---- contact form ---- */
.cform { margin-top: 26px; display: grid; gap: 14px; position: relative; }
.cform__row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.cform__field { display: grid; gap: 6px; }
.cform__field > span { font-size: 0.82rem; font-weight: 500; color: var(--body); }
.cform input, .cform select, .cform textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: #fff;
  border: 1.5px solid var(--line);
  border-radius: 12px;
  padding: 12px 14px;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.cform textarea { resize: vertical; min-height: 88px; }
.cform input:focus, .cform select:focus, .cform textarea:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 4px rgba(var(--blue-rgb), 0.12);
}
.cform input[aria-invalid="true"], .cform textarea[aria-invalid="true"] { border-color: var(--red); }
.cform__err { color: var(--red); font-size: 0.78rem; font-style: normal; }
/* NOT width:100%. Stretched to the form's full width the button's outline is
   more than twice the perimeter of every other CTA, so the travelling dot has
   to cover 622px instead of 286px at the same 50px/s — it crawls, and the
   round caps read as flattened. Pinned to the shared --btn-w / --btn-h it is
   the identical box to "Get Hired". */
.cform__submit { margin-top: 4px; width: auto; justify-self: center; }
.cform__toast {
  position: absolute;
  left: 0; right: 0; bottom: -54px;
  background: #0c7a44;
  color: #fff;
  border-radius: 12px;
  padding: 12px 16px;
  text-align: center;
  font-weight: 500;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.cform__toast.is-shown { opacity: 1; transform: none; }

/* CTA content alignment with form */
.cta__content { display: flex; flex-direction: column; }

/* focus-visible polish across interactive elements */
a:focus-visible, button:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 6px;
}

@media (max-width: 700px) {
  .cform__row { grid-template-columns: 1fr; }
}

/* respect reduced motion for all new effects */
@media (prefers-reduced-motion: reduce) {
  .reveal--t { opacity: 1; translate: none; transition: none; }
  .tilt { transform: none !important; }
  .hero__art--draw { animation: none; opacity: 1; filter: none; transform: none; }
  .nav__progress { transition: none; }
  .cform__toast { transition: none; }
}

/* ============================================================
   NEW LOGO + RAYCAST TRAVELLING-LIGHT EFFECTS
   ============================================================ */

/* --- logo: plain tile, no animation --- */
.logo__badge {
  position: relative;
  display: inline-grid;
  place-items: center;
  border-radius: 14px;
}
.logo__mark { display: block; border-radius: 14px; }

/* --- buttons: travelling LINE around the border ---
   An <svg><rect> tracing the pill is injected into each .btn by
   useBorderLights() in Shared.jsx. The rect carries pathLength="100", which
   renormalises its perimeter to 100 units no matter the button's real size —
   so the dash below is always 16% of the outline and the dashoffset sweep is
   arc-length parameterised by construction. That is what keeps the line
   moving at an even rate along the straight edges AND around the round caps,
   which is exactly what an angle-based conic-gradient cannot do on a pill.
   --trace-dur is set per button (JS) to hold a constant px/second speed. */
.btn { position: relative; isolation: isolate; }

/* HAIRLINE RING. A continuous 1.5px outline the dot rides around.
   A plain border rather than a conic gradient on purpose: a full ring looks
   identical at every angle, so rotating a gradient behind it buys a shimmer
   almost nobody notices while repainting on every CTA forever. This costs one
   static border and never repaints at all.
   It is deliberately OUTSIDE the reduced-motion rule below — the ring does not
   move, and leaving it in place keeps the button looking finished rather than
   bare for anyone who has motion turned down. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 1.5px solid rgba(255, 255, 255, 0.26);
  pointer-events: none;
  /* below .btn__trace (z-index 2) so the dot passes over the ring, not under */
  z-index: 1;
}

.btn__trace {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 2;
}
/* SINGLE DOT. A zero-length dash with stroke-linecap:round draws as a circle
   of stroke-width diameter, so one path renders as one dot riding the mirrored
   pill outline. pathLength="100" (set in Shared.jsx) renormalises the
   perimeter, which is what keeps the dot moving at an even rate around the end
   caps as well as along the straight edges. */
.btn__trace path {
  fill: none;
  stroke: #fff;
  stroke-width: 5;
  stroke-linecap: round;
  stroke-dasharray: 0 100;
  filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.95))
          drop-shadow(0 0 7px rgba(255, 255, 255, 0.55));
  animation: btnTrace var(--trace-dur, 8s) linear infinite;
}
@keyframes btnTrace { to { stroke-dashoffset: -100; } }
/* NOTE: there is deliberately no `animation-direction: reverse` here. The red
   button's opposite travel comes from its path being the mirrored geometry
   (see pillPath() in Shared.jsx), so both buttons run the identical animation
   and cannot drift out of step. */
.btn:hover .btn__trace path {
  stroke-width: 6.5;
  filter: drop-shadow(0 0 4px rgba(255, 255, 255, 1))
          drop-shadow(0 0 9px rgba(255, 255, 255, 0.8));
}

@media (prefers-reduced-motion: reduce) {
  .btn__trace { display: none; }
}

/* ============================================================
   HERO — flat background matching Figma (no animated color beams)
   ============================================================ */
.hero { position: relative; }
.hero__inner { position: relative; z-index: 2; }
.hero > * { position: relative; z-index: 2; }

/* ============================================================
   SECONDARY-PAGE HERO MODIFIERS (About / Contact / Legal)
   ============================================================ */
.hero__eyebrow {
  display: inline-block;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 0.85rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--blue);
  margin-bottom: 14px;
}
.hero__title--md { font-size: clamp(2.1rem, 5vw, 3.3rem); margin-top: 0; }
/* About's hero illustration is 1.08:1 (near-square); Contact's is 1.61:1
   (a wide two-person desk scene) — the exact mismatch that made Contact's
   art look noticeably larger than About's at an identical box WIDTH, purely
   because the box height was left to float with whichever image landed in
   it. 1.3:1 is the midpoint; both illustrations are full-bleed within their
   own canvas already, so contain here only adds letterboxing where the two
   shapes genuinely disagree, not on top of existing internal padding. */
.hero__art--sm { width: min(320px, 60%); aspect-ratio: 1.3 / 1; }
.hero__art--sm .illus { height: 100%; object-fit: contain; }
/* Secondary pages (Contact / About / Legal) are plain stacked content, so the
   illustration needs explicit margins — previously the spacing here was an
   accident of the landing page's flex centring, which is what left the big
   voids above and below it. */
/* ONE gap between the paragraph and the illustration. .hero__sub already
   carries margin-bottom:26px and the art was adding another 26px on top, so
   the real distance was 52px — that was the void under the copy. The sub's
   margin is zeroed here so this single value is the whole gap. */
.hero--contact .hero__sub, .hero--about .hero__sub { margin-bottom: 0; }
.hero--contact .hero__art, .hero--about .hero__art {
  margin-top: 30px;
  margin-bottom: 0;
}
.hero--about { padding: 64px 0 40px; }
.hero--legal { padding: 60px 0 24px; }
@media (max-width: 780px) {
  .hero--about { padding: 40px 0 30px; }
  .hero--legal { padding: 40px 0 20px; }
}

/* ============================================================
   ABOUT PAGE
   ============================================================ */
.about-story { padding: 50px 0 90px; }
.about-story__inner { max-width: 760px; margin: 0 auto; text-align: center; }
.about-story__inner p { color: var(--body); font-size: 1.08rem; margin: 0 0 20px; }
.about-story__inner p:last-child { margin-bottom: 0; }

.seamless--stack { padding: 90px 0 100px; background: var(--bg-soft); }
.seamless--stack .seamless__grid { margin-top: 50px; }
.seamless--stack .seamless__grid + .seamless__grid { margin-top: 70px; }
/* .seamless__grid's track sizes (1fr / 1.05fr) are tied to column
   POSITION, not to which child lands there — so reordering the art/content
   children with `order` alone would leave the art sitting in the wider
   column and the text squeezed into the narrower one. Swap the track
   sizes too so the flipped row keeps the same proportions, just mirrored. */
.seamless__grid--flip { grid-template-columns: 1.05fr 1fr; }
.seamless__grid--flip .seamless__art { order: 2; }
.seamless__grid--flip .seamless__content { order: 1; }

@media (max-width: 880px) {
  /* Same bug class as .hero--legal / .hero--about earlier: this unconditional
     .seamless__grid--flip rule (line 1425) sits AFTER the base .seamless__grid
     mobile collapse (line 1012) in the file, so at equal specificity it always
     won on every phone — every flipped row (About's second ecosystem row,
     "Meet the right people") stayed a cramped 2-column grid instead of
     stacking, which is what squeezed its illustration into a narrow strip.
     Collapsing it here, in the media query that already resets its order for
     mobile, keeps the fix next to the other half of the same problem. */
  .seamless__grid--flip { grid-template-columns: 1fr; }
  .seamless--stack .seamless__grid--flip .seamless__art { order: 0; }
  .seamless--stack .seamless__grid--flip .seamless__content { order: 0; }
}

/* ============================================================
   LEGAL PAGES (Privacy Policy / Terms & Conditions)
   ============================================================ */
.legal { padding: 50px 0 120px; }
.legal__inner { max-width: 820px; margin: 0 auto; color: var(--body); font-size: 1rem; line-height: 1.75; }
.legal__inner h2 {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
  margin: 44px 0 14px;
}
.legal__inner h2:first-child { margin-top: 0; }
.legal__inner h3 { font-family: var(--font-display); font-size: 1.12rem; font-weight: 500; color: var(--ink); margin: 26px 0 8px; }
.legal__inner p { margin: 0 0 16px; }
.legal__inner ul { margin: 0 0 16px; padding-left: 22px; }
.legal__inner li { margin-bottom: 8px; }
.legal__inner a { color: var(--blue); text-decoration: underline; text-underline-offset: 2px; }

/* ============================================================
   404 — NOT FOUND
   Static page: it must render correctly even when the React bundle is the
   thing that failed to load, so nothing here depends on JS. The only script
   on the page is progressive enhancement (meteor trace + burger).
   ============================================================ */
.hero--error {
  /* the nav paints var(--hero-top) behind itself, so the error hero has to
     start on that same token or a seam appears under the bar */
  min-height: calc(100svh - var(--nav-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 30px 0 60px;
  text-align: center;
}
.err__code {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(5.5rem, 21vw, 12.5rem);
  line-height: 0.88;
  letter-spacing: -0.045em;
  margin: 0;
  position: relative;
  /* the gradient is painted through the glyphs; -webkit- prefix included
     because background-clip:text is still prefixed in Safari */
  background: linear-gradient(135deg, var(--blue-accent) 0%, var(--blue) 55%, #002a6e 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
/* soft brand-blue bloom behind the numerals, sized off the text box so it
   scales with the clamp() above instead of needing its own breakpoints */
.err__code::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 52%;
  width: 62%;
  height: 62%;
  transform: translate(-50%, -50%);
  background: radial-gradient(ellipse at center, rgba(var(--blue-rgb), 0.20), rgba(var(--blue-rgb), 0) 70%);
  filter: blur(26px);
  z-index: -1;
  pointer-events: none;
}
.err__rule {
  width: 62px;
  height: 4px;
  border-radius: 99px;
  background: var(--red);
  margin: 26px auto 0;
}
.hero--error .hero__title { margin-top: 22px; }
.hero--error .hero__sub { margin: 14px auto 0; max-width: 46ch; }
/* No bespoke mobile override here on purpose. The base .hero__actions rule
   (flex-wrap: wrap, buttons at their normal intrinsic width) already handles
   narrow viewports — see audit-layout.js #1. A 404-only 280px fixed width
   used to make these two buttons wider than every other button on the site;
   removing it is what makes them uniform again. */
.hero--error .hero__actions { margin-top: 34px; justify-content: center; }

/* ============================================================
   CONTACT — "YOUR INTENT" SEGMENTED CONTROL
   ONE control split in two, not two stacked cards. Arriving from a CTA or a
   service card preselects a side, and both halves stay visible so that choice
   is obvious — a collapsed <select> would hide it.
   ============================================================ */
.cseg {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* the pill is the field: same white fill, border and focus language as the
     text inputs above and below it */
  background: #fff;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  padding: 4px;
  gap: 4px;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.cseg__opt {
  font: inherit;
  font-weight: 500;
  font-size: 0.95rem;
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
  border: 0;
  background: none;
  border-radius: 999px;
  padding: 11px 6px;
  /* One row at EVERY width. This used to collapse to a single column below
     420px — a leftover from when each option carried a label plus a hint line
     and genuinely needed the width. The compact pill does not: two labels fit
     side by side even at 320px, and stacking them made the control read as two
     separate buttons rather than one either/or choice. */
  white-space: nowrap;
  /* Faded until chosen, matching the placeholder text in the fields — these
     read as "not answered yet" rather than as two live options.
     #757575 is the browser's own placeholder grey and clears AA at 4.61:1 on
     white, which matters here because unlike a real placeholder these ARE the
     content, not a hint about it. */
  color: #757575;
  transition: background 0.18s ease, color 0.18s ease;
}
.cseg__opt:hover { color: var(--ink); }
.cseg__opt.is-on {
  background: var(--blue);
  color: #fff;
  box-shadow: 0 8px 18px -12px rgba(var(--blue-rgb), 0.85);
}
.cseg__opt:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}
.cseg.is-invalid { border-color: var(--red); }

.cform__submit[disabled] { opacity: 0.62; cursor: not-allowed; }
/* the trace would keep sweeping a button that cannot be pressed */
.cform__submit[disabled] .btn__trace { display: none; }

/* 2026-09-09: the button is red (.btn--red) by default already, matching the
   site's CTA color elsewhere — so "turn it red" on a failed submit needs its
   own, more alarming state rather than just re-applying the same color. A
   short shake plus a hard outline ring does that without permanently
   changing the button's resting look. */
@keyframes cform-shake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-4px); }
  40%, 60% { transform: translateX(4px); }
}
.cform__submit.is-shake {
  animation: cform-shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
  box-shadow: 0 0 0 4px rgba(231, 27, 36, 0.35), 0 6px 14px -10px rgba(231, 27, 36, 0.5);
}

.cform__fail {
  margin: 0;
  color: var(--red);
  font-size: 0.84rem;
  line-height: 1.5;
}
.cform__fail a { color: var(--red); text-decoration: underline; text-underline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .cseg__opt { transition: none; }
}

/* Honeypot. Moved off-screen rather than display:none or visibility:hidden —
   the better bots skip fields hidden by those, but will fill one that is
   merely positioned away. Kept out of the tab order and off the a11y tree so
   nobody using a keyboard or screen reader ever lands on it. */
.cform__hp {
  position: absolute !important;
  left: -9999px !important;
  top: auto;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  border: 0 !important;
  opacity: 0;
  pointer-events: none;
}

/* Client wordmark — used when a client has no logo file. Set in the display
   face at a size that sits on the same optical weight as the real logo images
   beside it, so a mixed strip does not look half-finished. */
.marquee__wordmark {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
  line-height: 34px;      /* matches .marquee__logo-img height, so the pill
                             height — and therefore --marquee-h — is unchanged */
  letter-spacing: -0.01em;
  color: var(--ink);
  white-space: nowrap;
}
@media (max-width: 480px) {
  .marquee__wordmark { font-size: 0.95rem; }
}
