/* =====================================================================
   folio-v2 — DESIGN TOKENS (single source of truth)
   Loaded FIRST, before every other stylesheet.

   Rule: no component may hard-code a value a token exists for.
   If nothing fits, add a token here rather than inlining the value.
   ===================================================================== */
:root {
  color-scheme: light;

  /* ---- Neutral ramp -------------------------------------------------
     One achromatic scale serving BOTH themes. Deliberately hue-free:
     dark mode is built from black's greys, never from tinted greens.
     Every surface/text token below resolves to a step on this ramp so
     the two themes stay perceptually symmetric.
     ------------------------------------------------------------------ */
  --n-0:    #ffffff;
  --n-25:   #fafafa;
  --n-50:   #f4f4f4;
  --n-75:   #ededed;
  --n-100:  #e4e4e4;
  --n-200:  #d4d4d4;
  --n-300:  #b4b4b4;
  --n-400:  #8e8e8e;
  --n-500:  #6e6e6e;
  --n-600:  #545454;
  --n-700:  #3a3a3a;
  --n-750:  #2b2b2b;
  --n-800:  #212121;
  --n-850:  #1a1a1a;
  --n-900:  #141414;
  --n-950:  #0f0f0f;
  --n-1000: #0a0a0a;

  /* ---- Brand ---------------------------------------------------------
     One brand hue, tuned per theme. -500 is the light-mode accent, -400
     its dark-mode counterpart (lifted so it reads on black). The -700 /
     -300 steps are the text-safe versions on light / dark respectively.
     -------------------------------------------------------------------- */
  --brand-300: #8bef63;
  --brand-400: #6ee43f;
  --brand-500: #4dce16;
  --brand-700: #347e12;

  /* ---- Colour ------------------------------------------------------ */
  --ink:            var(--n-800);   /* primary text             */
  /* Figma styleguide (517:4565): Title #222222, Sub Text #787878.
     One sub-text value serves both ledes and meta. #787878 measured
     4.01:1 on the #f4f4f4 page, under the 4.5:1 AA floor for body text,
     so sub text sits one step darker: #6e6e6e is 4.64:1 on the page and
     5.1:1 on white cards. */
  --ink-strong:     #222222;        /* headings, high contrast  */
  --ink-muted:      #6e6e6e;        /* ledes, secondary copy    */
  --ink-soft:       #6e6e6e;        /* meta, captions           */
  --ink-invert:     var(--n-0);

  --surface-page:     var(--n-50);
  --surface-card:     var(--n-0);   /* ALL card surfaces        */
  --surface-sunken:   var(--n-100);
  --surface-elevated: var(--n-0);   /* raised above a card      */
  --surface-dark:     #222222;   /* Figma dark plate */

  --line:           rgba(17, 17, 17, 0.12);  /* card borders        */
  --line-strong:    rgba(17, 17, 17, 0.22);  /* tag/control borders */
  --line-soft:      rgba(17, 17, 17, 0.08);

  --accent:         var(--brand-500);
  --accent-ink:     var(--brand-700);

  /* Semantic theme roles shared by every component. */
  --surface-glass:   rgba(255, 255, 255, 0.78);
  --surface-media:   var(--n-100);
  --surface-control: var(--n-0);
  --surface-active:  #242424;
  --text-primary:    var(--ink-strong);
  --text-secondary:  var(--ink-muted);
  --text-tertiary:   var(--ink-soft);
  --text-on-active:  var(--n-0);
  --shadow-soft:     0 18px 55px rgba(17, 17, 17, 0.08);
  --focus-ring:      rgba(77, 206, 22, 0.78);
  --tag-border:      rgba(17, 17, 17, 0.30);

  /* ---- Type -------------------------------------------------------- */
  --font-display: "Geist", system-ui, -apple-system, sans-serif;
  --font-body:    "Geist", system-ui, -apple-system, sans-serif;
  /* Section titles only. Geist stays the face for body copy, labels and
     controls; the expanded cut is reserved for the display line. */
  --font-title:   "Zalando Sans Expanded", "Geist", system-ui, -apple-system, sans-serif;

  /* Figma styleguide (517:4565) type scale, five steps:
       Large Text Medium  82px   -> --fs-display
       Large Text Bold    64px   -> --fs-section-title
       Regular Text       18px   -> --fs-lede / --fs-body
       Medium             14px   -> --fs-meta
       Small              12px   -> --fs-small / --fs-tag / --fs-label
     Each clamp hits its Figma px value at the 1440px comp width and
     scales down below it; nothing between steps is invented. */
  --fs-display:       clamp(2.75rem, 5.7vw, 5.125rem);  /* 82px */
  --fs-section-title: clamp(2rem, 3.4vw, 3.25rem);  /* 32 -> 52px; the homepage's rendered value */
  --fs-card-title:    clamp(1.55rem, 2.325vw, 2.4rem);  /* between steps; no Figma value */
  --fs-lede:          1rem;  /* 16px */
  --fs-body:          1rem;  /* 16px */
  --fs-meta:          0.875rem;  /* 14px */
  --fs-small:         0.75rem;  /* 12px */
  --fs-tag:           0.75rem;  /* 12px */
  --fs-label:         0.75rem;  /* 12px */
  --fs-caption:       0.625rem;   /* 10px — collapses 9/10px drift */
  --fs-nano:          0.5rem;     /* 8px — collapses 6/7/8px drift */

  --fw-regular: 400;
  --fw-medium:  500;
  --fw-semi:    600;

  --track-tight:   -0.055em;  /* display headings */
  --track-heading: -0.035em;  /* card titles      */
  --track-body:    -0.02em;   /* universal body   */

  /* ---- Spacing ----------------------------------------------------- */
  --gutter:        20px;
  --section-space: clamp(40px, 5vw, 68px);
  --head-gap:      clamp(28px, 3.4vw, 48px);
  --card-pad:      clamp(24px, 2.4vw, 32px);
  --card-gap:      clamp(12px, 1.4vw, 20px);

  /* ---- Radius ------------------------------------------------------ */
  /* Square surfaces. Cards, panels and media all sit flush; only
     controls (pills, buttons, the nav capsule) keep a radius. */
  --radius-card:    0px;
  --radius-panel:   0px;
  --radius-media:   0px;
  --radius-control: 0px;

  /* One gap for every card grid, gallery and masonry gutter on the site,
     set by the Approach row. Change it here, not per section. */
  --grid-gap: 8px;

  /* Section rhythm. Sections that pad at section level use --section-pad;
     the ones that pad internally (Work, Expertise, Playground, Education)
     read the same value from their own stage. */
  --section-pad: var(--section-gap, clamp(3.5rem, 7vw, 6.5rem));

  /* Motion: see the easing and duration set under "Motion" below. */
  --ease-out-expo: var(--ease-out);
  --stagger:    .09s;

  /* ---- Controls ---------------------------------------------------- */
  --control-h:       40px;  /* pills, tabs, secondary   */
  --control-h-lg:    48px;  /* primary CTA, FAB         */
  --control-h-touch: 44px;  /* min under pointer:coarse */
  --control-pad-x:   18px;

  /* ---- Motion ------------------------------------------------------ */
  /* Four curves and four durations. Every cubic-bezier and every CSS
     duration up to 1s in the stylesheets points at one of these; there
     were 26 hand-typed curves and 30+ durations (.22s, .24s, .25s, .28s…)
     no one could tell apart. Durations above 1s are ambient loops and
     keep their own values. */

    --ease-out:     cubic-bezier(.16, 1, .3, 1);   /* arrivals, reveals, hovers */
  --ease-out-quart: cubic-bezier(.22, 1, .36, 1); /* softer arrival than --ease-out;
                                                     the case studies' entrance curve */
  --ease-toggle:  cubic-bezier(.34, 1.2, .4, 1); /* the theme toggle's sun/moon morph,
                                                     deliberately the one motion that
                                                     overshoots. Used once, on purpose. */
  --ease-in-out:  cubic-bezier(.65, 0, .35, 1);  /* moves that start and end at rest */
  --ease-spring:  cubic-bezier(.2, 1.5, .4, 1);  /* small UI overshoot only */
  /* --ease-linear is the keyword `linear`, used for scroll-linked motion. */
  --ease-in-out-strong: var(--ease-in-out);      /* legacy names */
  --ease-expo:          var(--ease-in-out);

  --dur-1: 150ms;   /* colour, opacity, hover */
  --dur-2: 250ms;   /* controls, thumbs, small moves */
  --dur-3: 450ms;   /* cards, panels, crossfades */
  --dur-4: 800ms;   /* section entrances, large media */
  --dur-fast: var(--dur-2);                      /* legacy names */
  --dur-base: var(--dur-3);
  --dur-mid:  var(--dur-3);
  --dur-slow: var(--dur-4);

  /* ---- Layout ------------------------------------------------------ */
  --nav-breakpoint: 1024px;  /* <=1024 menu, >=1025 links */
}

/* ---------------------------------------------------------------------
   DARK THEME
   Mirrors the light theme step for step, on the same achromatic ramp.
   Contrast on --surface-page (#0f0f0f): primary 16.2:1, secondary 8.0:1,
   tertiary 4.8:1 — all at or above WCAG AA for their roles.
   --------------------------------------------------------------------- */
:root[data-theme="dark"] {
  color-scheme: dark;
  --ink:            var(--n-75);
  --ink-strong:     #f5f5f5;
  --ink-muted:      #a8a8a8;
  --ink-soft:       #8a8a8a;
  --ink-invert:     #111111;

  --surface-page:     var(--n-950);
  --surface-card:     var(--n-850);
  --surface-sunken:   var(--n-1000);
  --surface-elevated: var(--n-800);
  --surface-dark:     #050505;

  --surface-glass:   rgba(24, 24, 24, 0.82);
  --surface-media:   var(--n-800);
  --surface-control: #1f1f1f;
  --surface-active:  var(--n-75);

  --line:           rgba(255, 255, 255, 0.10);
  --line-strong:    rgba(255, 255, 255, 0.20);
  --line-soft:      rgba(255, 255, 255, 0.06);

  --accent:         var(--brand-400);
  --accent-ink:     var(--brand-300);

  --text-primary:   var(--ink-strong);
  --text-secondary: var(--ink-muted);
  --text-tertiary:  var(--ink-soft);
  --text-on-active: #111111;
  --shadow-soft:    0 20px 64px rgba(0, 0, 0, 0.55);
  --focus-ring:     rgba(110, 228, 63, 0.85);
  --tag-border:     rgba(255, 255, 255, 0.26);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-1: 0.01ms;
    --dur-2: 0.01ms;
    --dur-3: 0.01ms;
    --dur-4: 0.01ms;
  }
}


/* =====================================================================
   STYLEGUIDE — square corners, site-wide.

   Every surface, control and media frame sits flush. This lives in
   tokens.css rather than a page bundle so it reaches the case studies
   too, and it is a universal selector because the radius values it has
   to beat are scattered across the bundles as hardcoded pixel values and
   !important declarations rather than reading the tokens above.

   Note this squares circles as well as rounded rectangles: avatars, the
   theme toggle, icon buttons and the music player's disc all become
   squares. That follows from "no rounded corners on any element" — if
   any of those should stay circular, exempt them here rather than
   reintroducing a radius at the component.
   ===================================================================== */
/* The bundles define their own radius aliases and re-declare them with
   literal pixel values (--ui-radius-card: 20px, --service-radius, and so
   on). Zeroing them here kills every rule that READS a token, including
   the ID-level `:is(#services .capability-card-v2, ...)` selectors that
   outrank any universal sweep. The sweep below then only has to catch
   the rules with hardcoded values. */
:root,
:root[data-theme],
:root[data-theme="dark"],
body {
  --ui-radius-card: 16px !important;
  --ui-radius-panel: 20px !important;
  --ui-radius-control: 999px !important;
  --service-radius: 16px !important;
  --radius-card: 16px !important;
  --radius-panel: 20px !important;
  --radius-media: 16px !important;
  --radius-control: 999px !important;
}

/* The universal square sweep that used to sit here has been removed: the
   site is rounded now, so components keep the radius they declare and the
   tokens above supply one to everything that reads them. The control
   rules below still hold — a control is a pill wherever it appears. */

/* =====================================================================
   FILTERS — one segmented control, everywhere

   The category rows were bare text buttons: the only mark of the active
   one was a slightly darker ink, which read as body copy rather than a
   control. They are now a segmented pill group — a tinted track holding
   the options, the active one filled — the same shape the mobile filters
   already use. Tokens so every row can be retuned at once.
   ===================================================================== */
:root {
  --filter-track-bg: color-mix(in srgb, var(--ink-strong) 5%, transparent);
  --filter-track-pad: 4px;
  --filter-item-gap: 2px;
  --filter-radius: 999px;
  --filter-pad-x: clamp(12px, .95vw, 16px);
  --filter-pad-y: clamp(7px, .6vw, 9px);
  --filter-fs: clamp(12.5px, .95vw, 13.5px);
  --filter-fw: 500;
  --filter-ink: var(--ink-muted);
  --filter-ink-hover: var(--ink-strong);
  --filter-ink-active: var(--surface-page);
  --filter-bg-active: var(--ink-strong);
}
[data-theme="dark"] {
  --filter-track-bg: rgba(255, 255, 255, .07);
  --filter-ink-active: #111111;
  --filter-bg-active: #f5f5f5;
}

:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs)[class] {
  display: inline-flex !important;
  align-items: center;
  gap: var(--filter-item-gap) !important;
  padding: var(--filter-track-pad) !important;
  background: var(--filter-track-bg) !important;
  border-radius: var(--filter-radius) !important;
}
:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs) > button[class],
:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs) > button {
  padding: var(--filter-pad-y) var(--filter-pad-x) !important;
  border: 0 !important;
  border-radius: var(--filter-radius) !important;
  background: transparent !important;
  color: var(--filter-ink) !important;
  font-size: var(--filter-fs) !important;
  font-weight: var(--filter-fw) !important;
  letter-spacing: -.01em;
  line-height: 1;
  white-space: nowrap;
  transition: background-color var(--dur-2) ease, color var(--dur-2) ease;
}
:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs) > button:hover {
  color: var(--filter-ink-hover) !important;
}
/* The rows' own rules set their own gap and horizontal padding — the
   Services strip spread its options across 536px of track — so the
   segmented metrics are restated at the depth those rules use. */
:root body #perspective .pg-gallery__tabs[class][class],
:root body #work .sw__pills[class][class],
:root body #expertise .xp__tabs[class][class] {
  gap: var(--filter-item-gap) !important;
  padding: var(--filter-track-pad) !important;
  column-gap: var(--filter-item-gap) !important;
}
:root body #perspective .pg-gallery__tabs > button[class][class],
:root body #work .sw__pills > button[class][class],
:root body #expertise .xp__tabs > button[class][class] {
  margin: 0 !important;
  padding: var(--filter-pad-y) var(--filter-pad-x) !important;
}

/* The travelling thumb: one element per row that slides to whichever
   option is selected. js/filter-thumb.js positions it. */
:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs)[class] {
  position: relative;
  isolation: isolate;
}
:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs) > .filter-thumb {
  position: absolute;
  z-index: 0;
  top: var(--filter-track-pad);
  left: 0;
  bottom: var(--filter-track-pad);
  width: 0;
  border-radius: var(--filter-radius) !important;
  background: var(--filter-bg-active);
  opacity: 0;
  pointer-events: none;
  transition: transform var(--dur-3) var(--ease-out),
    width var(--dur-3) var(--ease-out),
    opacity var(--dur-2) ease;
}
:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs) > button {
  position: relative;
  z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
  :root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs) > .filter-thumb {
    transition: none;
  }
}

/* The gallery's own rules sit at id + class depth and paint these
   buttons transparent, so the selected state has to out-specify them. */
:root body #perspective .pg-gallery__tabs > button[class][class]:is(.is-active, [aria-selected="true"]),
:root body #expertise .xp__tabs > button[class][class]:is(.is-active, [aria-selected="true"]),
:root body #work .sw__pills > button[class][class]:is(.is-active, [aria-selected="true"], [aria-pressed="true"]),
:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs) > button:is(.is-active, [aria-selected="true"], [aria-pressed="true"]) {
  background: var(--filter-bg-active) !important;
  color: var(--filter-ink-active) !important;
}
/* With a thumb present the buttons carry no fill of their own — the
   thumb IS the fill, and two of them would double up mid-slide. Stated
   after the active rule above, which it has to override. */
:root body #perspective .pg-gallery__tabs.has-filter-thumb > button[class][class],
:root body #expertise .xp__tabs.has-filter-thumb > button[class][class],
:root body #work .sw__pills.has-filter-thumb > button[class][class] {
  background: transparent !important;
}
:root body :is(#perspective .pg-gallery__tabs, #work .sw__pills, #expertise .xp__tabs) > button:focus-visible {
  outline: 2px solid var(--ink-strong);
  outline-offset: 2px;
}

/* =====================================================================
   GRID RHYTHM — one gap between cards, everywhere

   Card grids had drifted apart: the playground bento sat at 4px, the
   Approach steps at 8px, the toolkit at 8.7px and partnerships at 8px, so
   the same visual — a row of cards — breathed differently in every
   section. They now share one token, which scales with the viewport.
   ===================================================================== */
:root { --grid-gap: clamp(10px, .9vw, 16px); }

:root body :is(
  #perspective .pg-panel--all,
  #process .design-process__steps,
  #toolkit .stack-grid,
  #toolkit [class*="__grid"],
  #partnerships [class*="__cards"],
  #services [class*="__grid"],
  #client-value [class*="__grid"],
  #work [class*="__grid"],
  #insights [class*="__grid"]
)[class] {
  gap: var(--grid-gap) !important;
}

/* =====================================================================
   ROUNDED PASS — media and cards carry a corner everywhere

   Removing the old square sweep gave components back whatever radius they
   declared, but many declared none: their corners were square on purpose
   under the previous language. Rather than chase each one, media and card
   surfaces inside the content sections are rounded here, at the id tier
   the components use. Sections, the hero canvas and the nav are excluded —
   full-bleed backgrounds have no corners to show.
   ===================================================================== */
:root body :is(#work, #expertise, #process, #toolkit, #perspective,
  #partnerships, #experience, #education-recognition, #insights, #services,
  #client-value, #stack, #testimonials, #about, #about-me, #contact)
  :is(img, video, picture, figure, .card,
      [class*="__media"], [class*="-media"],
      [class*="__card"], [class*="-card"],
      [class*="__tile"], [class*="__thumb"], [class*="__poster"],
      /* Card-shaped blocks that do not carry "card" in their name: the
         Approach steps, panels and boxed items were square for exactly
         that reason. */
      [class*="__step"], [class*="__panel"], [class*="__box"],
      [class*="__surface"], [class*="__pane"])[class] {
  border-radius: var(--radius-media) !important;
}
/* Bare media carries no class of its own, so it needs its own rule — the
   selector above requires one. The wrappers are `overflow: visible`, so
   rounding the box alone would not clip the picture inside it. */
:root body :is(#work, #expertise, #process, #toolkit, #perspective,
  #partnerships, #experience, #education-recognition, #insights, #services,
  #client-value, #stack, #testimonials, #about, #about-me, #contact)
  :is(img, video, picture) {
  border-radius: var(--radius-media) !important;
}
/* Services (#expertise) keeps square media: its panels are full-bleed
   compositions that run edge to edge in the pinned stage, and a corner
   on them cuts into the artwork rather than framing it. Its controls are
   unaffected — the pill rules below still apply. */
/* The switching panels inside stay square: the image, the panel it sits
   in and the layer that carries them. The card pass rounds anything named
   "__panel", which is what put a corner back on these. */
:root body #expertise :is(img, video, picture, figure,
  [class*="__media"], [class*="-media"], [class*="__tile"],
  [class*="__poster"], [class*="__thumb"], [class*="__panel"],
  [class*="__layer"], [class*="__stage"],
  [class*="__frame"], [class*="__visual"])[class],
:root body #expertise :is(img, video, picture) {
  border-radius: 0 !important;
}
/* The cover is the one frame around the whole image area. It takes the
   corner, and its overflow clip carries that corner onto the square
   panels as they switch inside it. */
:root body #expertise .xp__cover[class] {
  border-radius: var(--radius-media) !important;
  overflow: hidden !important;
}

/* Product Stack logos keep their own shapes. They are brand marks, not
   media, so the rounded pass above must not clip them. */
:root body #toolkit :is(.stack-logo, .stack-card__logos)[class] {
  border-radius: 0 !important;
}

/* Anything genuinely circular stays circular. Media is excluded: the
   Playground's <video class="pg-youtube-player"> matched "play" and was
   cut into a pill, which showed as giant corners once it went fullscreen. */
:root body :is([class*="avatar"], [class*="__dot"], [class*="__ring"],
  [class*="play"], [class*="__badge"])[class]:not(video, img, picture) {
  border-radius: 999px !important;
}

/* Fullscreen media is the whole viewport: no corners on the frame or on
   the film inside it. */
:root body :is(#perspective, #work) .pg-media-expanded[class],
:root body :is(#perspective, #work) .pg-media-expanded :is(video, img, picture) {
  border-radius: 0 !important;
}

/* =====================================================================
   STYLEGUIDE — controls stay pill-shaped, and controls over media frost.

   The square sweep above deliberately hits everything, so controls have
   to be re-rounded AFTER it and at matching specificity — including a
   section-id tier, since the sweep's top tier is id-scoped.

   `button` is targeted by ELEMENT, plus a list of anchor-based controls.
   That is on purpose: several non-controls carry role="button" (the
   Approach cards, the Work list items, the Work thumbnail) and those must
   stay square.
   ===================================================================== */
:root {
  --radius-control: 999px !important;
  --ui-radius-control: 999px !important;
  /* One frost recipe for any control sitting on top of an image. */
  --control-frost-bg: rgba(16, 16, 16, .34);
  --control-frost-border: rgba(255, 255, 255, .22);
  --control-frost-filter: blur(16px) saturate(140%);
}

button,
.nav__talk,
.mobnav__talk,
.to-top,
.contact-light__cta,
.cs__back,
.cs__close,
.pg-preview__close,
.pg-frost,
.pg-aerial__more,
.pg-reels__follow,
:root body button,
:root body :is(.nav__talk, .mobnav__talk, .to-top, .contact-light__cta, .cs__back, .cs__close, .pg-preview__close, .pg-frost, .pg-aerial__more, .pg-reels__follow),
:root body :is(#hero, #about, #work, #expertise, #services, #process, #about-me,
  #toolkit, #perspective, #partnerships, #experience, #education-recognition,
  #insights, #contact, #stack, #testimonials, #client-value) :is(button, .nav__talk, .to-top, .contact-light__cta, .cs__back, .cs__close, .pg-preview__close, .pg-frost, .pg-aerial__more, .pg-reels__follow) {
  border-radius: 999px !important;
}

/* Controls that sit over an image get the frost. `.pg-frost` is the
   existing marker class for these; the container-scoped selectors catch
   the ones that never got it. */
:root body :is(.pg-frost, .pg-aerial__fullscreen, .pg-reels__follow, .pg-preview__close,
  .sw__ov-close, .cs__close),
:root body :is(.pg-frost, .pg-aerial__fullscreen, .pg-reels__follow, .pg-preview__close,
  .sw__ov-close, .cs__close) {
  background: var(--control-frost-bg) !important;
  border: 1px solid var(--control-frost-border) !important;
  color: #fff !important;
  -webkit-backdrop-filter: var(--control-frost-filter) !important;
  backdrop-filter: var(--control-frost-filter) !important;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12) !important;
}

/* Where the blur cannot render, the tint alone has to carry contrast. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root { --control-frost-bg: rgba(16, 16, 16, .72); }
}

/* Controls that live OUTSIDE the enumerated sections — the header, the
   mobile nav, the case-study overlay — are caught by the sweep's
   `[class][class]` tier, which outranks the button tiers above. Repeating
   the attribute selector lifts these past it without needing an id. */
:root body button[class][class][class][class],
:root body [class][class][class][class]:is(.nav__talk, .mobnav__talk, .to-top,
  .contact-light__cta, .cs__back, .cs__close, .pg-preview__close, .pg-frost, .pg-aerial__more, .pg-reels__follow),
:root body [id] button[class][class][class][class] {
  border-radius: 999px !important;
}

/* =====================================================================
   STYLEGUIDE — strokes off, frosted controls circular, nav on the gutter
   ===================================================================== */

/* 1 · Product Stack tiles: no stroke, no green glow. The glow was a
   radial rgba(77,206,22,…) on ::before, sitting behind each tile. */
:root body #toolkit .toolkit-tile,
:root body #toolkit .toolkit-tile::after {
  border: 0 !important;
  box-shadow: none !important;
  outline: 0 !important;
}
:root body #toolkit .toolkit-tile::before {
  background: none !important;
  filter: none !important;
  opacity: 0 !important;
}

/* 2 · Frosted controls and labels lose their hairline. The border was
   there to lift them off bright photography; the tint and blur already
   do that. */
:root body :is(.pg-frost, .pg-aerial__fullscreen, .pg-aerial__more, .pg-reels__follow,
  .pg-preview__close, .sw__ov-close, .cs__close),
:root body #expertise .xp__tags li,
:root body #expertise .xp__overlay :is(li, button, a) {
  border: 0 !important;
  outline: 0 !important;
  box-shadow: none !important;
}

/* Expertise's overlay chips are <li>, so the control rules never reached
   them — they need the pill radius stated at section-id level to clear
   the square sweep. */
:root body #expertise .xp__tags li,
:root body #expertise .xp__overlay :is(li, button, a) {
  border-radius: 999px !important;
}

/* 3 · The header capsule is fully rounded and sits on the same gutter as
   every section's content. */
:root body #nav,
:root body .nav {
  border-radius: 999px !important;
  /* The 2rem floor was set for desktop, where 3.94vw wins anyway; on a
     phone it took over and inset the capsule 32px while the page content
     sits on a 20px gutter, leaving the bar visibly narrower than
     everything under it. The floor now matches the mobile gutter. */
  left: max(3.94vw, 1.25rem) !important;
  right: max(3.94vw, 1.25rem) !important;
  width: auto !important;
}

/* 5 · The fullscreen / view control is a true circle: one size on both
   axes so the pill radius resolves to a full round rather than a
   stadium. */
:root body :is(.pg-aerial__fullscreen, .sw__ov-close, .cs__close),
:root body [id] :is(.pg-aerial__fullscreen) {
  width: var(--view-btn-size, 44px) !important;
  height: var(--view-btn-size, 44px) !important;
  min-width: var(--view-btn-size, 44px) !important;
  min-height: var(--view-btn-size, 44px) !important;
  padding: 0 !important;
  display: grid !important;
  place-items: center !important;
  aspect-ratio: 1 !important;
  border-radius: 999px !important;
}

/* 6 · View control reveals on hover over its media. js/view-hover.js tags
   the wrapping element as [data-view-host]; this is the whole behaviour. */
:root body [data-view-host] [data-pg-expand] {
  opacity: 0;
  transform: scale(.92);
  pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
}
:root body [data-view-host]:hover [data-pg-expand],
:root body [data-view-host]:focus-within [data-pg-expand],
:root body [data-view-host] [data-pg-expand]:focus-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
/* No hover to reveal it on touch, so it stays put. */
@media (hover: none), (pointer: coarse) {
  :root body [data-view-host] [data-pg-expand] {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }
}
@media (prefers-reduced-motion: reduce) {
  :root body [data-view-host] [data-pg-expand] { transition: none; }
}

/* The custom cursor is a dot, not a corner — the square sweep turned it
   into a small black square that tracked the pointer. */
:root body .portfolio-cursor[class][class][class],
:root body .portfolio-cursor[class][class][class] *,
:root body [class*="portfolio-cursor"][class][class][class],
:root body [class*="portfolio-cursor"][class][class][class] * {
  border-radius: 999px !important;
}

/* =====================================================================
   CORRECTION — the frost and pill rules were scoped by ELEMENT, so they
   swallowed media surfaces that happen to be <button>.

   Two of these are whole panels: `.pg-aerial__preview-trigger` is the
   1160x559 click target wrapping the Playground video, and `.pg-track`
   is a 498x553 Music card. Frosting them painted a giant blurred plate
   over the artwork, and the pill radius rounded the cards off entirely.

   Controls are now identified by INTENT — the `.pg-frost` marker plus a
   named list — never by "a button inside a figure".
   ===================================================================== */

/* 1 · Media surfaces are not controls: no frost, no pill. */
:root body :is(.pg-aerial__preview-trigger, .pg-media-placeholder,
  .pg-panel > button:not(.pg-track), .playground-video-player__rail),
:root body [id] :is(.pg-aerial__preview-trigger, .pg-media-placeholder),
:root body :is(.pg-aerial__preview-trigger)[class][class][class][class] {
  background: none !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  border: 0 !important;
  box-shadow: none !important;
  border-radius: var(--radius-media) !important;
  color: inherit !important;
}

/* 1b · A music card is a media surface too, but its gradient IS the
   artwork — stripping the background left three black rectangles. It
   loses the frost, border and pill radius like the others; it keeps
   whatever `.pg-track--one/two/three` paints. */
:root body .pg-track,
:root body [id] .pg-track,
:root body .pg-track[class][class][class][class] {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  border: 0 !important;
  box-shadow: none !important;
  border-radius: var(--radius-media) !important;
  color: #fff !important;
}

/* 2 · Round affordances inside a media surface stay round. */
:root body :is(.pg-track__play, .playground-music-player__controls button,
  .playground-video-player__rail span)[class][class][class][class] {
  border-radius: 999px !important;
}

/* 3 · The frost list, stated explicitly. Small overlay controls only. */
:root body :is(.pg-frost, .pg-aerial__fullscreen, .pg-aerial__more,
  .pg-reels__follow, .pg-preview__close, .sw__ov-close, .cs__close,
  .playground-music-player__controls button)[class][class] {
  background: var(--control-frost-bg) !important;
  -webkit-backdrop-filter: var(--control-frost-filter) !important;
  backdrop-filter: var(--control-frost-filter) !important;
  color: #fff !important;
  border: 0 !important;
}

/* The pill rules carry `[id] button[class]x4`, and the square sweep's top
   tier is id-scoped, so both of these need an id of their own to land. */
:root body #perspective :is(.pg-aerial__preview-trigger, .pg-track,
  .pg-media-placeholder)[class][class] {
  border-radius: var(--radius-media) !important;
}
:root body #perspective :is(.pg-track__play, .playground-music-player__controls button,
  .playground-video-player__rail span, .pg-aerial__fullscreen, .pg-preview__close)[class][class] {
  border-radius: 999px !important;
}

/* The expand control doubles as the CLOSE control once the media is
   fullscreen (playground-media.js swaps its icon to "minimize"). The
   hover-reveal above left it at opacity 0 / pointer-events:none whenever
   the pointer was not over its host — which in fullscreen meant the video
   could be opened but never closed. While a preview is open the control
   is always live. */
html.pg-media-is-open [data-pg-expand],
html.pg-media-is-open [data-view-host] [data-pg-expand],
:root body .pg-media-expanded [data-pg-expand],
:root body [data-view-host].pg-media-expanded [data-pg-expand] {
  opacity: 1 !important;
  transform: none !important;
  pointer-events: auto !important;
}

/* =====================================================================
   AUDIT PASS — spacing, type and motion consistency across sections.
   ===================================================================== */

/* 1 · Every section lede reads at one size. They had drifted to 13, 14
   and 15px because several sections set their own font-size instead of
   taking the token. */
:root body :is(
  #expertise .xp__lede,
  #process .design-process__lede,
  #services .help__sub,
  #client-value .client-value__lede,
  #toolkit .toolkit__lede,
  #perspective .pg-gallery__summary,
  #partnerships .editorial-head > p,
  #experience .exp__head-desc,
  #education-recognition .section__lede,
  #contact .contact-light__head > p
)[class] {
  font-size: var(--fs-lede) !important;
  line-height: 1.5 !important;
  letter-spacing: var(--track-body) !important;
  color: var(--text-secondary) !important;
}

/* 2 · Partnerships was the last grid off the universal gutter (17.95px). */
:root body #partnerships :is(.partnerships__cards, [class*="__cards"], [class*="__grid"])[class] {
  gap: var(--grid-gap) !important;
}

/* 3 · Experience's heading was the one section title still at weight 500
   in sentence case — every other section is the expanded cut at 600 in
   caps. */
:root body #experience > :is(h2, header > h2, .section__label)[class],
:root body #experience h2[class] {
  font-family: var(--font-title) !important;
  font-size: var(--fs-section-title) !important;
  font-weight: 600 !important;
  text-transform: uppercase !important;
  letter-spacing: -.045em !important;
  line-height: .9 !important;
  color: var(--text-primary) !important;
}

/* 4 · Contact's bottom padding was a one-off 30.77px against the 51.28px
   every other padded section uses. */
:root body #contact[id] {
  padding-block: var(--section-pad) !important;
}

/* 5 · One section rhythm. The five sections that pad at section level all
   read the same token; the four that pad internally are left alone —
   their stages own the spacing and overriding here would fight the
   pinned layouts. */
:root body :is(#process, #toolkit, #partnerships, #experience, #client-value, #services)[id] {
  padding-block: var(--section-pad) !important;
}

/* 6 · Shared motion. Anything still carrying a bespoke ease or duration
   for a simple reveal now moves on the tokens. */
:root body :is(#process .design-process__step, #expertise .xp__tab,
  #work .sw-pill, #perspective .pg-gallery__tabs button, #work .sw-li) {
  transition-timing-function: var(--ease-out-expo) !important;
  transition-duration: var(--dur-base) !important;
}

/* 7 · Mobile Work filters read as type, matching desktop and the other
   two filter rows, instead of being the site's last capsule set. */
@media (max-width: 900px) {
  :root body #work .sw__pills[class] {
    display: flex !important;
    flex-wrap: nowrap !important;
    overflow-x: auto !important;
    grid-template-columns: none !important;
    column-gap: var(--filter-item-gap) !important;
    scrollbar-width: none !important;
  }
  :root body #work .sw__pills[class]::-webkit-scrollbar { display: none !important; }
  :root body #work .sw-pill[class][class] {
    flex: 0 0 auto !important;
    width: auto !important;
    min-height: 0 !important;
    padding: 4px 2px !important;
    border: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
    font-size: .875rem !important;
    font-weight: 400 !important;
    color: var(--text-tertiary) !important;
    white-space: nowrap !important;
  }
  :root body #work .sw-pill.is-active[class][class] {
    background: transparent !important;
    color: var(--text-primary) !important;
    font-weight: 500 !important;
  }
}

/* Two stragglers whose own rules sit above the audit pass. */
:root body #partnerships .editorial-head > p[class][class],
:root body #partnerships .editorial-head > p:not([class]) {
  font-size: var(--fs-lede) !important;
}
:root body #experience h2[class][class],
:root body #experience h2.section__label[class][class] {
  font-weight: 600 !important;
  font-family: var(--font-title) !important;
}

/* The Experience heading's own weight rule sits above everything the
   audit pass could reach without matching its class depth. */
:root body #experience h2[class][class][class][class],
:root body #experience .section__label[class][class][class][class] {
  font-weight: 600 !important;
}

/* The winning rule is `:is(#work .sw__title, #services #capabilitiesTitle,
   …) { font-weight: 500 !important }` — `:is()` takes its most specific
   argument, and `#services #capabilitiesTitle` is TWO ids. Nothing with a
   single id can reach it, so this borrows main#top for the second. */
:root body main#top #experience h2[class],
:root body main#top #experience .section__label[class] {
  font-weight: 600 !important;
  font-family: var(--font-title) !important;
}

/* =====================================================================
   Auto-contrast states. js/auto-contrast.js samples the media behind
   each marked element and sets one of these. Specificity is high because
   the colours they replace are themselves !important; a transition keeps
   a cut in a video from flicking the text.
   ===================================================================== */
:root body main#top .is-on-dark,
:root body main#top .is-on-dark[class],
:root body .is-on-dark[class][class][class] {
  color: #fff !important;
  --auto-contrast-ink: #fff;
}
:root body main#top .is-on-light,
:root body main#top .is-on-light[class],
:root body .is-on-light[class][class][class] {
  color: #111 !important;
  --auto-contrast-ink: #111;
}
:root body :is(.is-on-dark, .is-on-light)[class] {
  transition: color var(--dur-base, .42s) var(--ease-out-expo, ease);
}
/* Chips and play affordances swap their plate, not just their ink, or a
   dark pill on a dark frame loses its edge. */
:root body .pg-track__play.is-on-light[class][class] {
  background: rgba(255, 255, 255, .58) !important;
}
:root body .pg-track__play.is-on-dark[class][class] {
  background: rgba(16, 16, 16, .42) !important;
}
@media (prefers-reduced-motion: reduce) {
  :root body :is(.is-on-dark, .is-on-light)[class] { transition: none; }
}

/* The play affordance's ::after is the circular progress ring — a conic
   gradient clipped by a radial mask, revealed via `.has-progress` once
   playback starts. The square sweep zeroed its border-radius, so pressing
   play painted a translucent SQUARE behind the button instead of a ring.
   Round affordances and their pseudo-elements are exempt. */
:root body #perspective .pg-track__play,
:root body #perspective .pg-track__play::before,
:root body #perspective .pg-track__play::after,
:root body #perspective :is(.playground-music-player__controls button,
  .playground-video-player__rail span)::before,
:root body #perspective :is(.playground-music-player__controls button,
  .playground-video-player__rail span)::after {
  border-radius: 50% !important;
}

/* The play / pause glyphs are lucide SVGs drawing with currentColor, so
   they inherited whatever colour .pg-track__play carried — mid-grey on a
   translucent white plate over dark artwork, which is what made them hard
   to read. Glyph pinned white, plate pinned to a dark frost so white
   always has something to read against on every card. */
:root body #perspective .pg-track__play[class][class] {
  background: rgba(16, 16, 16, .46) !important;
  color: #fff !important;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22) !important;
}
:root body #perspective .pg-track__play :is(svg, .pg-track__icon-play, .pg-track__icon-pause),
:root body #perspective .pg-track__play :is(svg, .pg-track__icon-play, .pg-track__icon-pause) * {
  color: #fff !important;
  stroke: #fff !important;
  fill: none !important;
}

/* .mobnav is a fixed, near-fullscreen panel (inset: 78px 12px 12px) with a
   30px radius, and it is `display: flex` at EVERY breakpoint — hidden only
   by opacity/visibility/clip-path. On desktop it has no reason to exist in
   the layout at all, and while it does it is the one large rounded box
   overlaying the hero. Taking it out of the render tree above the mobile
   breakpoint removes any chance of it painting there. */
@media (min-width: 901px) {
  :root body .mobnav:not(.is-open) {
    display: none !important;
  }
}

/* =====================================================================
   PREVIEW — undo three of my own overrides that broke it.

   1 · .pg-preview__backdrop is the SCRIM behind a fullscreen image, not a
       media surface. I had swept its background to none, so the preview
       opened with nothing behind the photo — the page showed through
       wherever the image did not cover, which reads exactly as "renders
       weirdly / sections disappear / huge empty space".
   2 · .pg-preview__close was forced into a fixed 44px grid box, fighting
       whatever placement the component sets for itself.
   3 · The FLIP that scales a thumbnail to fullscreen animates
       border-radius (see its `will-change`); a blanket `border-radius: 0
       !important` freezes that channel mid-morph.
   ===================================================================== */
:root body #perspective .pg-preview__backdrop[class][class],
:root body .pg-preview__backdrop[class][class] {
  background: rgba(8, 8, 8, .92) !important;
  -webkit-backdrop-filter: blur(6px) !important;
  backdrop-filter: blur(6px) !important;
}
:root body .pg-preview__image[class][class],
:root body .pg-media-expanded[class][class] {
  border-radius: inherit !important;
}

/* =====================================================================
   9 · UNIVERSAL BUTTON SHAPE — anchors count as buttons too.

   The pill rule targeted `button` by element plus a named list, so
   anchor-based CTAs that were not on that list stayed square. Any element
   that looks and behaves like a button now takes the pill, whatever tag
   it uses. Media triggers and cards are excluded by name, as before.
   ===================================================================== */
:root body main#top :is(
  .folio-footer__button, .folio-footer__button--primary, .folio-footer__button--secondary,
  [class*="__button"], [class*="__cta"], [class*="-button"], .btn
):not(.pg-track):not(.pg-aerial__preview-trigger):not([class*="__buttons"]):not([class*="__button-group"]),
:root body :is(.folio-footer__button, [class*="__button"], [class*="__cta"])[class][class][class][class] {
  border-radius: 999px !important;
}

/* =====================================================================
   4 · CARD STROKES OFF, every section.
   ===================================================================== */
:root body main#top :is(
  #process .design-process__step, #toolkit .toolkit-tile, #client-value .value-card,
  #services .capability-card-v2, #partnerships .partnership-card,
  #experience .counter, #insights .accordion, .sw-mobile-cards > *,
  [class*="-card"], [class*="__card"], [class*="-tile"], [class*="__tile"]
)[class] {
  border: 0 !important;
  outline: 0 !important;
  box-shadow: none !important;
}

/* =====================================================================
   10 · Music: timestamp sits under the genre, not beside it.
   ===================================================================== */
:root body #perspective .pg-track__meta small[class],
:root body #perspective .pg-track__meta small {
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  gap: .2rem !important;
}
:root body #perspective .pg-track__genre,
:root body #perspective .pg-track__duration {
  display: block !important;
}
:root body #perspective .pg-track__duration {
  opacity: .72;
  font-variant-numeric: tabular-nums;
}

/* =====================================================================
   1–3 · Section rhythm, head alignment, filter-to-content gap.

   One value each, so a change lands everywhere rather than per section.
   ===================================================================== */
:root {
  --section-gap: clamp(3.5rem, 7vw, 6.5rem);  /* between sections */
  --head-to-content: clamp(2rem, 4vw, 3.5rem); /* title block -> content */
  --filter-to-content: clamp(1.75rem, 3.2vw, 2.75rem); /* filters -> content */
}

@media (min-width: 901px) {
  /* 1 · One gap between every section. */
  :root body main#top > section[id] {
    padding-block: var(--section-gap) !important;
  }
  /* The four that pin or pad internally keep their own stage padding —
     forcing section padding on them would double it against the pin. */
  :root body main#top > :is(#work, #expertise, #perspective, #hero)[id] {
    padding-block: 0 !important;
  }

  /* 3 · Filters sit the same distance above their content everywhere. */
  :root body #work .sw__main { padding-top: var(--filter-to-content) !important; }
  :root body #expertise .xp__cover { margin-top: var(--filter-to-content) !important; }
  :root body #perspective .pg-gallery__stage { margin-top: var(--filter-to-content) !important; }
}

/* =====================================================================
   5 · Experience roll-in keeps its tracking from frame one.

   The heading splits into per-character spans for the roll. Each span is
   display:inline-block, which suppresses the pair kerning/tracking the
   heading normally carries — so the letters roll in loose and only snap
   tight once the split is reverted at the end. Stating the tracking on
   the generated spans as well as the heading means the animation starts
   at its final spacing instead of arriving there.
   ===================================================================== */
:root body #experience :is(h2, .exp__title, [data-char-rise])[class],
:root body #experience :is(h2, .exp__title, [data-char-rise]) > span,
:root body #experience :is(h2, .exp__title, [data-char-rise]) > span > span {
  letter-spacing: -.045em !important;
  font-kerning: normal !important;
}
/* Same for every other split heading on the site, so none of them widen
   during their reveal. */
:root body main#top [data-source] > span,
:root body main#top .sw-title-line > span,
:root body main#top #expertise .xp__title > span {
  letter-spacing: inherit !important;
}

/* Experience's lede was never in the fixed-width alignment list, so its
   left edge sat 25px inside the other five. */
@media (min-width: 901px) {
  :root body #experience .exp__head-desc[class] {
    justify-self: end !important;
    align-self: start !important;
    text-align: left !important;
    width: 21.125rem !important;
    max-width: 21.125rem !important;
    min-width: 21.125rem !important;
  }
}

/* =====================================================================
   6 + 7 · SECTION REVEAL — one rhythm for the whole site.

   Title, then the sub-text after a lead beat, then content piece by
   piece. js/section-reveal.js writes each element's --reveal-delay; this
   supplies the motion. Change the two tokens to retune every section at
   once.

   NOTE on --reveal-lead: 2s is a long hold between the title and the
   rest, and on a fast scroll a visitor can pass a section before its body
   has arrived. It is a token for exactly that reason — 600-900ms reads as
   the same deliberate beat without the risk.
   ===================================================================== */
:root {
  /* Was 2s — long enough that a normal scroll carried a section's title
     off screen before its sub-text had arrived. 700ms still reads as a
     deliberate beat between the two. */
  --reveal-lead: .16s;
  --reveal-stagger: 78ms;
  --reveal-shift: 22px;
  --reveal-duration: .7s;
  /* A short defocus on arrival: the piece resolves as it rises rather
     than sliding in already sharp, which is what separates a considered
     entrance from a plain translate. */
  --reveal-blur: 6px;
}

/* Hidden state exists ONLY while the controller is live. */
:root.reveal-ready body main#top .reveal-part {
  opacity: 0;
  transform: translateY(var(--reveal-shift));
  filter: blur(var(--reveal-blur));
  will-change: opacity, transform, filter;
}
:root.reveal-ready body main#top .is-revealed .reveal-part {
  opacity: 1;
  transform: none;
  filter: blur(0px);
  transition:
    opacity var(--reveal-duration) var(--ease-out-expo, var(--ease-out)) var(--reveal-delay, 0ms),
    transform var(--reveal-duration) var(--ease-out-expo, var(--ease-out)) var(--reveal-delay, 0ms),
    filter calc(var(--reveal-duration) * .7) ease-out var(--reveal-delay, 0ms);
}
/* A nav jump resolves entrances instantly (see js/section-reveal.js). */
:root.reveal-ready.reveal-instant body main#top .reveal-part {
  transition: none !important;
}
/* Once a part has arrived, drop the hint so it cannot cost compositing
   for the rest of the session. */
:root.reveal-ready body main#top .is-revealed .reveal-part {
  will-change: auto;
}

/* Tablet and phone: the same rhythm, scaled to the shorter viewport. A
   24px rise and a 90ms step read as sluggish when a section fills the
   screen, and the blur costs more per pixel on a phone. */
@media (max-width: 1024px) {
  :root {
    --reveal-lead: .13s;
    --reveal-stagger: 66ms;
    --reveal-shift: 16px;
    --reveal-duration: .6s;
    --reveal-blur: 4px;
  }
}
@media (max-width: 720px) {
  :root {
    --reveal-lead: .11s;
    --reveal-stagger: 58ms;
    --reveal-shift: 13px;
    --reveal-duration: .5s;
    --reveal-blur: 3px;
  }

  /* Section headings carry the page on a phone, where there is no wide
     lede beside them to give them weight. */
  :root { --fs-section-title: clamp(31px, 8.6vw, 38px); }

  /* Work sits on pure white, not a tint of it. The carousel shell paints
     its own surface inside the section, so both have to be stated or the
     section reads grey however white its own background is. */
  :root body #work[id],
  :root body #work #swRoot[id],
  :root body #work [class*="sw__"][class] { background-color: #ffffff !important; }
  :root body #work [class*="sw__"][class]:is(button, a, .sw__pill, .sw__pills *) {
    background-color: revert-layer !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  :root.reveal-ready body main#top .reveal-part {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* =====================================================================
   The last three section headings still on the old label treatment.

   FAQ, the Technology-stack label and Testimonials were authored as
   `.section__label mono-label` / `.words__label` rather than display
   titles, so they kept Geist at weight 500 in sentence case — and two of
   them at a smaller size — while every other section had moved to the
   expanded cut at 600 in caps. Same two-id specificity the Experience
   heading needed, for the same reason: the winning rule is an :is() list
   whose most specific argument carries two ids.
   ===================================================================== */
:root body main#top :is(#insights, #stack, #testimonials) :is(h2, .section__label, .words__label)[class] {
  font-family: var(--font-title) !important;
  font-size: var(--fs-section-title) !important;
  font-weight: 600 !important;
  text-transform: uppercase !important;
  letter-spacing: -.045em !important;
  line-height: .9 !important;
  color: var(--text-primary) !important;
}

/* The FAQ question sat at 23.6px/450 — well above the row-title scale the
   rest of the site settled on (Approach and Product Stack titles are
   ~17-18px at 500-600). */
:root body main#top #insights .accordion__title[class],
:root body main#top #insights .accordion__head[class] .accordion__title {
  font-size: clamp(1rem, 1.25vw, 1.2rem) !important;
  font-weight: 500 !important;
  letter-spacing: var(--track-body) !important;
}
:root body main#top #insights .accordion__body p[class],
:root body main#top #insights .accordion__body p {
  font-size: var(--fs-lede) !important;
  line-height: 1.5 !important;
  color: var(--text-secondary) !important;
}

/* =====================================================================
   Controls on the case-study pages take the site button shape.

   The pill rule matched `button` by element plus a named list, so anchor-
   based controls that live only on the case-study templates — the close
   link, the footer CTAs, the back link — were never covered and stayed
   square. Listed by name rather than by a wildcard: `fi-next-card` and
   `pg-aerial__preview-trigger` are CARDS and media triggers that happen
   to share a prefix, and rounding those is exactly the mistake that
   turned the Music cards into pills earlier.

   Four repeated attribute selectors to clear the square sweep's
   `[class][class]` tier, which is what these lose to.
   ===================================================================== */
:root body :is(
  .fi-close, .fi-theme, .fi-footer__button,
  .fi-footer__button--primary, .fi-footer__button--secondary,
  .cs__back, .cs__close, .back-link, .folio-footer__button
)[class][class][class][class],
:root body a:is([class$="__close"], [class$="-close"], [class$="__button"])[class][class][class][class] {
  border-radius: 999px !important;
}

/* A close control is a circle, not a stadium — it holds a single glyph. */
:root body :is(.fi-close, .cs__close)[class][class][class][class] {
  display: grid !important;
  place-items: center !important;
  aspect-ratio: 1 !important;
}

/* =====================================================================
   Case-study section headings join the site title style.

   The case-study templates do not load homepage-bundle.css, so every
   heading rule written there stopped at the homepage. Their section
   headings (Challenge / Approach / Solution / Impact / More Case Studies)
   were still Geist at weight 400 in sentence case.

   The page H1 is deliberately NOT included: it carries the project name,
   and project names stay in mixed case here exactly as .cs__title and
   .sw__ov-title do on the homepage.
   ===================================================================== */
body:not(:has(main#top)) h2,
body .fi-section h2,
body .cs-section h2,
body article h2 {
  font-family: var(--font-title) !important;
  font-weight: 600 !important;
  text-transform: uppercase !important;
  letter-spacing: -.045em !important;
  line-height: .92 !important;
}

/* =====================================================================
   PHONE LAYER — page inset, section rhythm, filter rows
   ---------------------------------------------------------------------
   Three things were inconsistent below 900px and are settled here, in the
   token file, rather than per section:

   1 · Page inset. The gutter was 20px, with Selected Work sitting at 26px
       because its inner shell added its own. One token now, set tight the
       way current editorial sites run it, with the safe-area inset
       respected on notched handsets.

   2 · Section rhythm. The "one gap between every section" rule in this
       file is scoped to min-width:901px, so on a phone only the six
       sections that read --section-pad were on rhythm. Playground came in
       at 32/60 and Insights at 60/60, which made the real gaps between
       section content 228 / 146 / 142 / 227 / 297px. Redefining the token
       here puts every flow section on one value; the two strays are added
       to the rule. Hero, Work and Expertise still pad from their own
       stages — forcing section padding on them doubles it against the pin.

   3 · Filter rows are handled in css/editorial-polish.css for the same
       load-order reason.
   ===================================================================== */
@media (max-width: 900px) {
  :root {
    /* The page inset is re-declared in css/editorial-polish.css: styles.css
       and homepage-bundle.css both redefine --gutter/--mobile-gutter AFTER
       this file loads, so a value set here would simply be overwritten. */
    --section-pad: clamp(60px, 17vw, 84px);
  }

  /* Playground and Insights join the rhythm the other flow sections
     already read from --section-pad. */
  :root body :is(#perspective, #insights)[id] {
    padding-block: var(--section-pad) !important;
  }

}
