/* ==========================================================================
   Nishtech 2026 — THEME LAYER
   --------------------------------------------------------------------------
   The gradient picker and the contrast switch. Loads after main-2026.css and
   landing-2026.css on the landing family only: the four section landings and
   their detail pages (services, industries, platforms, products). The homepage
   and About keep their fixed design — see SCOPE below.

   Everything here hangs off two independent attributes on <html>:

     data-gradient   blue-magenta | hot-orange | orange-magenta
                     blue-orange  | blue-green | pink-orange
     data-contrast   light | dark

   Six × two combinations resolve into one set of --th-* tokens, so there are
   twelve looks and one stylesheet rather than twelve stylesheets.

   All colour values are measured, not chosen. THEME-EXTRACTION.md records how
   each one was read out of the Figma canvas and what the readings were.
   ========================================================================== */


/* ==========================================================================
   1. THE SIX GRADIENTS
   --------------------------------------------------------------------------
   Held as space-separated channel triples rather than hex, so the same token
   can be used solid — rgb(var(--th-a-rgb)) — and at an alpha —
   rgb(var(--th-a-rgb) / .5). A hex token cannot do the second thing, and the
   wash in §4 needs both.

   Naming follows the Figma layer names exactly, including which colour is the
   `a` stop, so a value here can be checked against the file without having to
   work out an aliasing scheme first.
   ========================================================================== */
:root{
  --g-blue-magenta-a:    8   2 117;   /* #080275 blue          */
  --g-blue-magenta-b:  147   2  91;   /* #93025B magenta       */

  --g-hot-orange-a:    252   4   4;   /* #FC0404 hot orange    */
  --g-hot-orange-b:    247  99   0;   /* #F76300 orange        */

  --g-orange-magenta-a:250  57   0;   /* #FA3900 active orange */
  --g-orange-magenta-b:147   2  91;   /* #93025B magenta       */

  --g-blue-orange-a:     8   2 117;   /* #080275 blue          */
  --g-blue-orange-b:   250  57   0;   /* #FA3900 active orange */

  --g-blue-green-a:     16   2 156;   /* #10029C medium blue   */
  --g-blue-green-b:    147 181  34;   /* #93B522 green         */

  --g-pink-orange-a:   255  36  69;   /* #FF2445 pink          */
  --g-pink-orange-b:   255 120   0;   /* #FF7800 orange        */
}

/* The default is blue to magenta, which is the pair the comp draws and the
   only one left visible in the file's `color gradients` group. It is declared
   on :root as well as on the attribute so the page still themes correctly if
   the boot script in <head> never runs — no JS, no attribute, still correct. */
:root,
html[data-gradient="blue-magenta"]  {--th-a-rgb:var(--g-blue-magenta-a);   --th-b-rgb:var(--g-blue-magenta-b)}
html[data-gradient="hot-orange"]    {--th-a-rgb:var(--g-hot-orange-a);     --th-b-rgb:var(--g-hot-orange-b)}
html[data-gradient="orange-magenta"]{--th-a-rgb:var(--g-orange-magenta-a); --th-b-rgb:var(--g-orange-magenta-b)}
html[data-gradient="blue-orange"]   {--th-a-rgb:var(--g-blue-orange-a);    --th-b-rgb:var(--g-blue-orange-b)}
html[data-gradient="blue-green"]    {--th-a-rgb:var(--g-blue-green-a);     --th-b-rgb:var(--g-blue-green-b)}
html[data-gradient="pink-orange"]   {--th-a-rgb:var(--g-pink-orange-a);    --th-b-rgb:var(--g-pink-orange-b)}

:root{
  --th-a:      rgb(var(--th-a-rgb));
  --th-b:      rgb(var(--th-b-rgb));
  --th-grad:   linear-gradient(135deg, var(--th-a) 0%, var(--th-b) 100%);
  --th-grad-h: linear-gradient( 90deg, var(--th-a) 0%, var(--th-b) 100%);
  --th-grad-v: linear-gradient(180deg, var(--th-a) 0%, var(--th-b) 100%);

  /* A gradient fill deep enough to keep a white label on it. See §8 — at full
     strength four of the six gradients have no label colour that clears AA, so
     the fill is composited at 65% over --indigo-deep. Hue survives; only
     lightness changes. */
  --th-fill:   linear-gradient(135deg,
                 rgb(var(--th-a-rgb) / .65) 0%, rgb(var(--th-b-rgb) / .65) 100%);

  /* The 2px ring on the outline buttons. A raw gradient does not clear the 3:1
     non-text floor of WCAG 1.4.11 at both ends of the range: on white, green
     (blue-to-green) reaches only 2.37 and orange (pink-to-orange) 2.65; on
     #0D0B2E, blue (blue-to-magenta, the default) reaches only 2.17 — the
     default theme's buttons would have had a near-invisible outline in dark
     mode.

     So the ring is nudged toward the opposite end of the range for the surface
     it sits on: 14% toward --indigo-deep on light, 35% toward white on dark.
     Both were solved for, not picked, and the smallest resulting margin is
     3.10. The hue is untouched either way. */
  --th-ring:   linear-gradient(90deg,
                 rgb(var(--th-a-rgb) / .86) 0%, rgb(var(--th-b-rgb) / .86) 100%);
  --th-ring-under: var(--indigo-deep);

  /* The brand's own gradient tokens now follow the pick. These are what carry
     the gradient into components this file never names: --grad-line drives the
     fixed header rule and the active tab's underline, --grad-underline the
     hover wipe under every text link, --pink-gradient the outline buttons. */
  --grad-line:      var(--th-grad-h);
  --grad-underline: var(--th-grad-h);
  --pink-gradient:  var(--th-grad-h);
}


/* ==========================================================================
   2. CONTRAST
   --------------------------------------------------------------------------
   Light values are the ones already in force, so a light page renders exactly
   as it did before this file existed. That is deliberate: it means the whole
   dark treatment can be reviewed as a diff against a known-good baseline.

   The single exception is the hero wash in §4, which is new — the comp has
   always drawn it and the build had never implemented it.

   Dark values are measured off the dark comp's outer margins, where no content
   interferes. Surfaces first, then ink.
   ========================================================================== */
:root{
  --th-surface:   #FFFFFF;             /* hero, tab strip, panel, capabilities */
  --th-surface-2: #F5F5F7;             /* ecosystem grid */
  --th-ink:       var(--indigo);       /* h1, panel title, tab name */
  --th-ink-lede:  var(--indigo);       /* the hero display lede — see note */
  --th-ink-body:  var(--muted);
  --th-rule:      var(--border);

  /* Wash strength and geometry — fitted, see §4. The radii differ between the
     modes, so they are tokens rather than literals in the gradient. */
  --th-wash:      .61;
  --th-lift:      0;                   /* no white lift needed over white */
  --th-b-rx:      41%;  --th-b-ry:  91%;
  --th-a-rx:      18%;  --th-a-ry:  86%;
}

html[data-contrast="dark"]{
  color-scheme: dark;

  --th-surface:   #0D0B2E;             /* indigo-deep, exact */
  --th-surface-2: #12103A;
  --th-ink:       #FFFFFF;
  /* The hero lede is the one piece of type that changes hue rather than
     lightness: indigo in the light comp, #FF9500 in the dark one. The panel
     title next to it stays white, so this is specific to the lede and not a
     blanket "display type goes amber" rule. Both were sampled. */
  --th-ink-lede:  var(--amber);
  --th-ink-body:  rgba(255,255,255,.72);
  --th-rule:      rgba(255,255,255,.22);

  /* Dark compositing needs a white lift as well as the gradient colour. Alpha
     blending magenta (green channel 2) over #0D0B2E (green 11) cannot reach
     the measured green of 48 — the channel rises, which straight compositing
     between those two values will never do. Fitting colour and lift together
     against the dark comp lands them here.

     The dark wash is also the same artwork at 1.2x width and 1.5x height.
     Fitting the two modes separately rather than sharing one geometry dropped
     the error from 19.4/255 to 8.4/255, so that difference is real and not
     noise. See §4 and _washfit.py. */
  --th-wash:      .40;
  --th-lift:      .34;
  --th-b-rx:      49%;  --th-b-ry: 136%;
  --th-a-rx:      22%;  --th-a-ry: 129%;

  /* Ring nudged toward white instead of toward the deep indigo. */
  --th-ring:   linear-gradient(90deg,
                 rgb(var(--th-a-rgb) / .65) 0%, rgb(var(--th-b-rgb) / .65) 100%);
  --th-ring-under: #FFFFFF;
}


/* ==========================================================================
   3. SWITCHING FEEL
   --------------------------------------------------------------------------
   Flat colours cross-fade; gradients cannot, because background-image is not
   an animatable property. Rather than fake it with stacked layers, the mode
   change fades what can fade and lets the two gradient surfaces (the insights
   band and the hero wash) snap. At 350ms nobody reads that as a fault — it
   reads as the page changing state, which is what happened.

   The transition is armed by a class the script adds for the duration of a
   switch, so it never interferes with a first paint or with hover states.
   ========================================================================== */
.th-switching :where(.lp-hero,.lp-panels,.caps,.eco,.insights,.top-nav,
                     .mega-panel,.post,.lp-tab,.caps__item,.eco__item),
.th-switching :where(h1,h2,h3,p,a,span,li){
  transition: background-color .35s var(--ease),
              color            .35s var(--ease),
              border-color     .35s var(--ease);
}

@media (prefers-reduced-motion:reduce){
  .th-switching :where(*){transition:none !important}
}


/* ==========================================================================
   4. THE HERO WASH
   --------------------------------------------------------------------------
   This is the component the picker exists to drive, and it is not the band
   behind the hero that it looks like from a distance.

   It is a soft wash in the **top-right corner**: the left margin is pure white
   from the nav down to the insights boundary, and the hue travels magenta ->
   blue as x increases. The `b` stop sits inboard of the `a` stop, which is the
   gradient reading right-to-left across the corner.

   Two radial-gradient layers say that directly. The alternative — one linear
   gradient behind a radial mask-image — needs a -webkit- prefix and an extra
   compositing layer to express the same shape.

   Both stops fade to `rgb(... / 0)` rather than to `transparent`. `transparent`
   is rgba(0,0,0,0), so interpolating to it drags the midpoint toward grey; the
   same colour at zero alpha keeps the ramp clean.

   GEOMETRY IS FITTED, NOT CHOSEN
   The first version of this rule was eyeballed from a handful of samples along
   one scanline and rendered about three times too weak — at 80% across and 18%
   down, the comp reads alpha .545 and the CSS produced .172.

   _washfit.py replaces that with a least-squares fit of both blobs against a
   2D grid sampled off each comp. That is possible in closed form because both
   gradient stops have a green channel of 2 (#080275 and #93025B) and the light
   comp has no white lift, so alpha = (255 - G) / 253 exactly — no colour
   unmixing required. Light fits to an rms alpha error of 0.028, which is about
   7/255 and inside measurement noise. Dark needed its own radii and fits to
   8.4/255.

   Two things the fit found that guessing had missed:

   · Both blob centres sit ABOVE the top edge (cy -7.5% and -20%) and both are
     nearly as tall as the hero (ry 91% and 86%). The wash is strongest at the
     very top and falls away over the hero's whole height — not a small patch
     near the top, which is what made the first attempt look thin.
   · The `a` blob is narrow (rx 18%) and the `b` blob wide (rx 41%). The
     magenta does the spreading; the blue is a concentrated highlight.
   ========================================================================== */
.lp-hero{position:relative;isolation:isolate}

.lp-hero::before{
  content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    /* white lift — dead in light mode (--th-lift:0), carries the dark corner */
    radial-gradient(var(--th-b-rx) var(--th-b-ry) at 63% -7.5%,
      rgb(255 255 255 / var(--th-lift)) 0%, rgb(255 255 255 / 0) 95%),
    /* second stop, inboard and wide */
    radial-gradient(var(--th-b-rx) var(--th-b-ry) at 63% -7.5%,
      rgb(var(--th-b-rgb) / var(--th-wash)) 0%, rgb(var(--th-b-rgb) / 0) 95%),
    /* first stop, outboard and narrow */
    radial-gradient(var(--th-a-rx) var(--th-a-ry) at 82% -20%,
      rgb(var(--th-a-rgb) / var(--th-wash)) 0%, rgb(var(--th-a-rgb) / 0) 95%);
}

/* The copy has to sit above the wash. `isolation:isolate` on the section plus
   z-index:-1 on the wash keeps it behind the hero's own content without
   escaping backwards through the page's stacking order and landing behind the
   section background. */
.lp-hero__grid{position:relative;z-index:1}

/* Below the point where the hero stacks, the mark drops out and the copy takes
   the full width — so a wash sized to an empty corner is suddenly behind the
   paragraph. Both blobs move further right and lose about a third of their
   strength.

   The geometry is not eyeballed. The bar it has to clear is that the wash must
   not make any existing text pair worse than it already is on a plain surface,
   and the binding constraint is the amber eyebrow, which sits at 2.20 on white
   and is the one pair on the page already under the AA large-text floor (a
   standing creative-direction decision recorded in main-2026.css, not
   something introduced here).

   A first attempt reached alpha .114 inside the eyebrow's box and pulled it to
   1.77. Centring the stops at 78% and 102% with tighter radii puts the
   eyebrow's box outside both ramps entirely — alpha 0, so the eyebrow reads
   exactly as it does on white — while the wash still covers the top-right
   corner. Everything else on the page has 5:1 or better of headroom and is not
   what set these numbers. */
@media (max-width:1023.98px){
  .lp-hero::before{
    background:
      radial-gradient(50% 46% at 86% -4%,
        rgb(255 255 255 / calc(var(--th-lift) * .72)) 0%, rgb(255 255 255 / 0) 90%),
      radial-gradient(50% 46% at 86% -4%,
        rgb(var(--th-b-rgb) / calc(var(--th-wash) * .62)) 0%, rgb(var(--th-b-rgb) / 0) 90%),
      radial-gradient(30% 38% at 104% -8%,
        rgb(var(--th-a-rgb) / calc(var(--th-wash) * .62)) 0%, rgb(var(--th-a-rgb) / 0) 90%);
  }
}


/* ==========================================================================
   5. DARK CONTRAST — SURFACES AND INK
   --------------------------------------------------------------------------
   Scoped to the attribute, so none of it can affect a light page. Grouped by
   component rather than by property, because that is how it gets reviewed.

   What deliberately does NOT change:

     footer      sampled #1E1B56 in both comps, with the same #29265E
                 secondary block. Identical, so it is left alone.
     insight cards  sampled #FFFFFF in both comps, body copy and all. Light
                 cards on a dark band is what the comp draws.
     accents     the active-orange CTAs, the amber eyebrows, the tab
                 underline's brand ramp and the header rule are the same in
                 both frames. Colour still encodes interactivity, and it does
                 not follow the picker — the `color gradients` group sits
                 inside `banner` in the layer tree, not around the tab strip.
   ========================================================================== */

/* --- 5.1 Header ---------------------------------------------------------- */
/* .is-solid is the landing header's from-first-paint state. The scroll handler
   also adds .scrolled, which lands on the same declarations; both are
   out-specified here by the extra attribute. */
html[data-contrast="dark"] .top-nav.is-solid{
  background:rgba(13,11,46,.92);
  border-bottom-color:rgba(255,255,255,.12);
  box-shadow:0 2px 20px rgba(0,0,0,.42);
}
html[data-contrast="dark"] .top-nav.is-solid .logo-color{opacity:0}
html[data-contrast="dark"] .top-nav.is-solid .logo-white{opacity:1}
html[data-contrast="dark"] .top-nav.is-solid .nav-links>li>a{color:#fff}
html[data-contrast="dark"] .top-nav.is-solid .nav-toggle{color:#fff}

html[data-contrast="dark"] .mega-panel{
  background:#141238;box-shadow:0 24px 60px rgba(0,0,0,.55);
}
html[data-contrast="dark"] .mega-item:hover{background:rgba(255,255,255,.06)}
html[data-contrast="dark"] .mega-item-desc{color:rgba(255,255,255,.7)}
html[data-contrast="dark"] .mega-all-row{border-top-color:rgba(255,255,255,.14)}

/* --- 5.2 Hero, tab strip, panel ------------------------------------------ */
html[data-contrast="dark"] .lp-hero,
html[data-contrast="dark"] .lp-panels,
html[data-contrast="dark"] .caps{background:var(--th-surface)}

html[data-contrast="dark"] .lp-hero__title,
html[data-contrast="dark"] .lp-panel__title,
html[data-contrast="dark"] .lp-tab__name,
html[data-contrast="dark"] .lp-tab[aria-selected="true"] .lp-tab__name{color:var(--th-ink)}

html[data-contrast="dark"] .lp-hero__lede{color:var(--th-ink-lede)}

html[data-contrast="dark"] .lp-hero__body,
html[data-contrast="dark"] .lp-panel__body,
html[data-contrast="dark"] .lp-tab__for{color:var(--th-ink-body)}

/* The stacked tab strip below 1200 draws a rule under each tab. */
html[data-contrast="dark"] .lp-tab{border-bottom-color:var(--th-rule)}

/* The collage is one photograph twice: a washed backdrop and a sharp framed
   copy. On a dark ground the backdrop's job reverses — it was knocking a
   photo back toward white, now it has to knock it back toward the surface —
   so the opacity comes down and the hairline frame lightens. */
html[data-contrast="dark"] .lp-collage__back{opacity:.12}
html[data-contrast="dark"] .lp-collage__front{border-color:rgba(255,255,255,.34)}

/* --- 5.3 Capabilities card and ecosystem grid ---------------------------- */
html[data-contrast="dark"] .eco{background:var(--th-surface-2)}

html[data-contrast="dark"] .caps__title,
html[data-contrast="dark"] .eco__title,
html[data-contrast="dark"] .caps__item h3,
html[data-contrast="dark"] .eco__name{color:var(--th-ink)}

html[data-contrast="dark"] .caps__lede,
html[data-contrast="dark"] .caps__item p,
html[data-contrast="dark"] .eco__item p{color:var(--th-ink-body)}

html[data-contrast="dark"] .caps__item,
html[data-contrast="dark"] .eco__item{border-top-color:var(--th-rule)}

/* --- 5.4 Insights -------------------------------------------------------- */
/* Measured as a vertical ramp, #161342 at the top to #0E0C32 at the bottom —
   not a flat fill, and not the horizontal ramp the light band uses.

   THE MESH HAD TO COME OUT OF THE BACKGROUND.
   `insight-bg.png` is not the transparent line drawing it looks like — it is an
   opaque image with its own near-white ground, which is invisible on the light
   band because the ground matches. Left in place on the dark band it painted a
   hard-edged pale block over this gradient and the white heading became
   unreadable on it. The giveaway was the shape of the failure: a crisp vertical
   edge at 555px rather than any kind of ramp, which is an image boundary, not a
   gradient. At 1440 the image is wider than the section and swallowed the whole
   band; at 2123 it covered the right 73%.

   So the section keeps only the gradient, and the mesh returns as an overlay
   that is inverted before it is composited. Inverting turns the near-white
   ground black and the grey lines into dark grey; under `screen` a black source
   contributes nothing at all, so the ground disappears completely and only the
   lines survive as a faint lift. `brightness()` sets how faint — that is the
   one number to turn if this reads too strong or too weak. */
html[data-contrast="dark"] .insights{
  background:linear-gradient(180deg,#161342 0%,#0E0C32 100%);
  /* mix-blend-mode composites against the backdrop of the nearest stacking
     context, and position:relative with z-index:auto does not create one --
     without this the overlay would blend against whatever sits further back in
     the page rather than against this section's own gradient. */
  isolation:isolate;
}
html[data-contrast="dark"] .insights::before{
  content:'';position:absolute;inset:0;z-index:0;pointer-events:none;
  background:url('https://www.nishtech.com/-/media/Images/Home/insight-bg.png') no-repeat right top;
  filter:invert(1) brightness(2.4);
  mix-blend-mode:screen;opacity:.5;
}
html[data-contrast="dark"] .insights__title{color:var(--th-ink)}
html[data-contrast="dark"] .insights__head .lede{color:var(--th-ink-body)}
html[data-contrast="dark"] .carousel-index{color:rgba(255,255,255,.6)}
html[data-contrast="dark"] .carousel-rail{background:rgba(255,255,255,.2)}

/* --- 5.5 Platform "why" band and the two marquees ------------------------ */
/* Already dark by design in both comps, so they need nothing. Named here so
   the next person does not go looking for the rule that handles them. */


/* ==========================================================================
   6. THE CONTROLS
   --------------------------------------------------------------------------
   A swatch button that opens the six gradients, and a contrast switch, both
   seated in the header next to Talk To An Expert. Modelled on the picker the
   brief pointed at: one small circular affordance, no chrome until you touch
   it.

   The pill geometry for the contrast switch comes from the `dark-mode-button`
   component on the Figma stylesheet page — a dark track with a sun on the left
   and a moon on the right, and a round knob that travels between them.
   ========================================================================== */
.th-ctrl{display:flex;align-items:center;gap:clamp(.5rem,.7vw,.875rem);flex:none;position:relative}

/* --- 6.1 Swatch button --------------------------------------------------- */
.th-swatch{
  width:clamp(28px,1.72vw,34px);aspect-ratio:1;border-radius:50%;
  display:grid;place-items:center;flex:none;cursor:pointer;
  background:none;border:1px solid var(--border);padding:3px;
  transition:border-color .25s var(--ease),transform .25s var(--ease);
}
.th-swatch__dot{
  display:block;width:100%;height:100%;border-radius:50%;
  background:var(--th-grad);
}
.th-swatch:hover{border-color:var(--active-orange);transform:scale(1.06)}
.th-swatch[aria-expanded="true"]{border-color:var(--active-orange)}
html[data-contrast="dark"] .th-swatch{border-color:rgba(255,255,255,.28)}

/* --- 6.2 Popover -------------------------------------------------------- */
/* Anchored to the control rather than the viewport so it tracks the header at
   every width. `top:calc(100% + …)` puts it under the button; the nav is
   position:fixed, so this is fixed too and needs no scroll handling. */
.th-menu{
  position:absolute;top:calc(100% + 14px);right:0;z-index:1003;
  width:min(19rem,calc(100vw - 2rem));
  padding:1.125rem 1.125rem 1rem;
  background:#fff;border-radius:4px;
  box-shadow:0 24px 60px rgba(13,11,46,.22);
  border:1px solid var(--border);
}
.th-menu[hidden]{display:none}
html[data-contrast="dark"] .th-menu{
  background:#141238;border-color:rgba(255,255,255,.14);
  box-shadow:0 24px 60px rgba(0,0,0,.55);
}

.th-menu__title{
  font-size:var(--fs-kicker);font-weight:800;letter-spacing:var(--ls-label);
  text-transform:uppercase;color:var(--accent-title);margin:0 0 .75rem;
}
.th-menu__grid{display:flex;flex-direction:column;gap:2px}

.th-opt{
  display:flex;align-items:center;gap:.625rem;width:100%;
  padding:.5rem .5rem;border-radius:3px;cursor:pointer;
  background:none;border:0;text-align:left;
  transition:background .18s var(--ease);
}
.th-opt:hover{background:var(--bg)}
html[data-contrast="dark"] .th-opt:hover{background:rgba(255,255,255,.06)}

.th-opt__dot{
  width:22px;height:22px;border-radius:50%;flex:none;
  box-shadow:0 0 0 1px rgba(13,11,46,.12) inset;
}
html[data-contrast="dark"] .th-opt__dot{box-shadow:0 0 0 1px rgba(255,255,255,.22) inset}

.th-opt__name{
  font-size:var(--fs-small);font-weight:600;line-height:1.3;
  color:var(--muted);
}
html[data-contrast="dark"] .th-opt__name{color:rgba(255,255,255,.82)}

/* The selected option is marked with a ring rather than a tick, because the
   thing being chosen is a colour and a tick would sit on top of it. */
.th-opt[aria-checked="true"] .th-opt__dot{
  box-shadow:0 0 0 2px var(--surface) inset, 0 0 0 2px var(--active-orange);
}
html[data-contrast="dark"] .th-opt[aria-checked="true"] .th-opt__dot{
  box-shadow:0 0 0 2px #141238 inset, 0 0 0 2px var(--active-orange);
}
.th-opt[aria-checked="true"] .th-opt__name{color:var(--active-orange)}

/* Per-option preview. Each dot shows its own pair, not the active one, so the
   list reads as six choices rather than six copies of the current state. */
.th-opt[data-gradient="blue-magenta"]   .th-opt__dot{background:linear-gradient(135deg,rgb(var(--g-blue-magenta-a)),rgb(var(--g-blue-magenta-b)))}
.th-opt[data-gradient="hot-orange"]     .th-opt__dot{background:linear-gradient(135deg,rgb(var(--g-hot-orange-a)),rgb(var(--g-hot-orange-b)))}
.th-opt[data-gradient="orange-magenta"] .th-opt__dot{background:linear-gradient(135deg,rgb(var(--g-orange-magenta-a)),rgb(var(--g-orange-magenta-b)))}
.th-opt[data-gradient="blue-orange"]    .th-opt__dot{background:linear-gradient(135deg,rgb(var(--g-blue-orange-a)),rgb(var(--g-blue-orange-b)))}
.th-opt[data-gradient="blue-green"]     .th-opt__dot{background:linear-gradient(135deg,rgb(var(--g-blue-green-a)),rgb(var(--g-blue-green-b)))}
.th-opt[data-gradient="pink-orange"]    .th-opt__dot{background:linear-gradient(135deg,rgb(var(--g-pink-orange-a)),rgb(var(--g-pink-orange-b)))}

/* --- 6.3 Contrast switch ------------------------------------------------- */
.th-contrast{
  flex:none;cursor:pointer;background:none;border:0;padding:0;
  border-radius:999px;
}
.th-contrast__track{
  position:relative;display:grid;grid-auto-flow:column;place-items:center;
  width:clamp(46px,2.9vw,58px);height:clamp(24px,1.5vw,30px);
  padding:0 5px;gap:2px;border-radius:999px;
  background:var(--indigo-deep);
  transition:background .3s var(--ease);
}
.th-contrast__track svg{
  width:clamp(11px,.7vw,14px);height:auto;flex:none;
  fill:none;stroke:rgba(255,255,255,.55);stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round;
  position:relative;z-index:1;transition:stroke .3s var(--ease);
}
.th-contrast__knob{
  position:absolute;top:3px;left:3px;
  width:calc(clamp(24px,1.5vw,30px) - 6px);aspect-ratio:1;
  border-radius:50%;background:#fff;
  transition:transform .3s var(--ease),background .3s var(--ease);
}
/* Off = light mode = knob over the sun. On = dark = knob travels to the moon.
   The knob is inset 3px on both sides, so its travel is the track width less
   its own diameter less both insets. */
.th-contrast[aria-checked="true"] .th-contrast__knob{
  transform:translateX(calc(clamp(46px,2.9vw,58px) - clamp(24px,1.5vw,30px)));
  background:var(--active-orange);
}
.th-contrast[aria-checked="false"] .th-contrast__sun,
.th-contrast[aria-checked="true"]  .th-contrast__moon{stroke:transparent}
.th-contrast:hover .th-contrast__track{background:#000}
html[data-contrast="dark"] .th-contrast__track{background:rgba(255,255,255,.14)}

@media (prefers-reduced-motion:reduce){
  .th-swatch,.th-contrast__track,.th-contrast__knob,.th-opt{transition:none}
}

/* --- 6.4 The switch's second home --------------------------------------- */
/* Hidden by default: above 1200 the nav-seated switch is the one on show. */
.th-menu__mode{
  display:none;align-items:center;justify-content:space-between;gap:.75rem;
  margin-top:.875rem;padding-top:.875rem;border-top:1px solid var(--border);
}
.th-menu__mode-label{font-size:var(--fs-small);font-weight:600;color:var(--muted)}
html[data-contrast="dark"] .th-menu__mode{border-top-color:rgba(255,255,255,.14)}
html[data-contrast="dark"] .th-menu__mode-label{color:rgba(255,255,255,.82)}

/* 1024–1199 is the tight band: main-2026.css has already dropped the nav links
   to 12px and their gap to 1rem, the CTA is still present, and eight links plus
   two controls do not fit. Below 1024 the links move into a drawer and the CTA
   goes, so there is room again — but by then the swap has no cost, and keeping
   the same arrangement from 1199 down to 375 is one behaviour to reason about
   instead of three. */
@media (max-width:1199.98px){
  .th-contrast--nav{display:none}
  .th-menu__mode{display:flex}
}

/* The popover has to stop hugging the right edge of a 375px screen. */
@media (max-width:767.98px){
  .th-menu{right:auto;left:50%;transform:translateX(-50%);width:min(17rem,calc(100vw - 1.5rem))}
}


/* ==========================================================================
   7. FIRST-VISIT INTRODUCTION
   --------------------------------------------------------------------------
   Shown once, on a first visit only, then never again. It exists because a
   circular swatch in a header is not self-explanatory: without it the feature
   is discoverable only by accident.

   The markup is built by the script from the options already in the header, so
   there is one list of gradients in the DOM rather than two that can drift.
   ========================================================================== */
.th-intro{
  position:fixed;inset:0;z-index:2000;
  display:grid;place-items:center;padding:1.5rem;
  background:rgba(13,11,46,.62);backdrop-filter:blur(6px);
}
.th-intro[hidden]{display:none}

.th-intro__panel{
  width:min(34rem,100%);max-height:calc(100vh - 3rem);overflow:auto;
  background:var(--th-surface);color:var(--th-ink);
  border-radius:6px;padding:clamp(1.5rem,2.4vw,2.75rem);
  box-shadow:0 40px 90px rgba(0,0,0,.4);
  animation:th-intro-in .4s var(--ease) both;
}
@keyframes th-intro-in{from{opacity:0;transform:translateY(14px)}}

.th-intro__eyebrow{
  font-size:var(--fs-kicker);font-weight:800;letter-spacing:var(--ls-label);
  text-transform:uppercase;color:var(--accent-title);margin:0;
}
.th-intro__title{
  font-family:var(--font-display);font-weight:400;line-height:1.1;
  font-size:clamp(1.6rem,2.1vw,2.375rem);color:var(--th-ink);
  margin:.5rem 0 0;
}
.th-intro__title .dot{color:var(--accent-title)}
.th-intro__body{
  font-size:var(--fs-body);line-height:var(--lh-body);color:var(--th-ink-body);
  margin:.75rem 0 0;
}

/* Two across, so all six are visible without scrolling on a phone. */
.th-intro__grid{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:.5rem;margin-top:1.5rem;
}
.th-intro__grid .th-opt{padding:.625rem .5rem}

.th-intro__row{
  display:flex;align-items:center;justify-content:space-between;
  gap:1rem;flex-wrap:wrap;margin-top:1.5rem;
  padding-top:1.25rem;border-top:1px solid var(--th-rule);
}
.th-intro__mode{display:flex;align-items:center;gap:.625rem}
.th-intro__mode-label{
  font-size:var(--fs-small);font-weight:600;color:var(--th-ink-body);
}
.th-intro__done{
  display:inline-flex;align-items:center;gap:.5rem;cursor:pointer;
  font-size:var(--fs-btn);font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;
  padding:.75rem 1.5rem;border:1px solid var(--active-orange);
  color:var(--active-orange);background:none;border-radius:2px;
  transition:background .25s var(--ease),color .25s var(--ease);
}
.th-intro__done:hover{background:var(--active-orange);color:#fff}

@media (max-width:479.98px){
  .th-intro__grid{grid-template-columns:1fr}
}
@media (prefers-reduced-motion:reduce){
  .th-intro__panel{animation:none}
}


/* ==========================================================================
   8. GRADIENT TREATMENT ACROSS COMPONENTS
   --------------------------------------------------------------------------
   §1 already routes the brand's own gradient tokens through the pick, which
   carries it into the fixed header rule, the active tab's underline and the
   hover wipe under every text link without naming any of them here. This
   section handles the rest: the buttons, and the places that held a literal
   gradient rather than a token.

   THE FILL DEPTH IS NOT A STYLE CHOICE
   The obvious move is to fill an outline button with the gradient on hover and
   put a white label on it. That fails. Sampling twenty points along each ramp
   and testing both candidate label colours:

     gradient          white label   #0D0B2E label
     blue-magenta         8.75 ok       1.16 fail
     hot-orange           3.12 fail     4.67 ok
     orange-magenta       3.72 fail     2.17 fail
     blue-orange          3.72 fail     1.16 fail
     blue-green           2.37 fail     1.37 fail
     pink-orange          2.65 fail     5.06 ok

   Four of six have no label colour that clears the 4.5 floor for a 14px bold
   label, and the two that pass want *different* colours. Per-gradient label
   tokens would mean the same button changing its text colour as you flip
   through the picker, which is worse than the problem.

   So the fill is the gradient composited at 65% over --indigo-deep. Hue is
   untouched — blue-to-green still plainly reads as blue-to-green — and every
   ramp lands at 4.85 or better against white, blue-green being the tight one.
   0.68 was the limit; 0.65 leaves three points of margin. See _btnink.py.
   ========================================================================== */

/* --- 8.1 Outline buttons -------------------------------------------------
   The 2px gradient ring already existed as a hover state built from a
   mask-composite pseudo-element. It becomes the resting state instead, and the
   hover adds the fill underneath it.

   Both mask properties are declared. -webkit-mask-composite takes the old
   Safari keyword (`xor`); mask-composite takes the standard one (`exclude`).
   They are not synonyms and both are needed.
   ------------------------------------------------------------------------ */
.btn-outline,
.th-intro__done{border-color:transparent}

.btn-outline::before,
.th-intro__done::before{
  content:'';position:absolute;inset:0;z-index:-1;border-radius:inherit;
  padding:2px;background:var(--th-ring),var(--th-ring-under);
  -webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
          mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
  opacity:1;pointer-events:none;
}

/* The fill. A second pseudo-element rather than a background on the button,
   because the button's own background has to stay transparent for the ring's
   mask to read against the page. */
.btn-outline::after,
.th-intro__done::after{
  content:'';position:absolute;inset:0;z-index:-2;border-radius:inherit;
  background:var(--th-fill),var(--indigo-deep);
  opacity:0;transition:opacity .3s var(--ease);pointer-events:none;
}
.btn-outline:hover::after,
.btn-outline:focus-visible::after,
.th-intro__done:hover::after,
.th-intro__done:focus-visible::after{opacity:1}

/* On the fill the label is white in every combination — that is what the 65%
   buys. The rest state keeps active orange, so colour still encodes
   interactivity the way the rest of the system does. */
.btn-outline:hover,
.btn-outline:focus-visible,
.btn-outline--dark:hover,
.btn-outline--dark:focus-visible,
.th-intro__done:hover,
.th-intro__done:focus-visible{color:#fff;background:transparent}

/* The old glow was a white bloom, which only worked on a dark surface. */
.btn-outline:hover{box-shadow:0 6px 20px rgb(var(--th-a-rgb) / .28)}
.btn-outline--dark:hover{box-shadow:0 6px 20px rgb(var(--th-a-rgb) / .28)}

.th-intro__done{position:relative;z-index:0}
/* 1px border on this one, so its ring is 1px too. */
.th-intro__done::before{padding:1px}

/* --- 8.2 Header CTA ------------------------------------------------------ */
.nav-cta{position:relative;z-index:0;border-color:transparent}
.nav-cta::before{
  content:'';position:absolute;inset:0;z-index:-1;border-radius:inherit;
  padding:2px;background:var(--th-ring),var(--th-ring-under);
  -webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
          mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
  pointer-events:none;
}
.nav-cta::after{
  content:'';position:absolute;inset:0;z-index:-2;border-radius:inherit;
  background:var(--th-fill),var(--indigo-deep);
  opacity:0;transition:opacity .3s var(--ease);pointer-events:none;
}
.nav-cta:hover::after,.nav-cta:focus-visible::after{opacity:1}
/* Out-specifies both .top-nav.is-solid .nav-cta and .top-nav.scrolled .nav-cta,
   which is why the attribute selector is on the front of these two. */
html .top-nav.is-solid .nav-cta:hover,
html .top-nav.scrolled .nav-cta:hover{background:transparent;color:#fff;border-color:transparent}

/* --- 8.3 Rules and hairlines -------------------------------------------- */
/* border-image paints only the edges that carry a width, so a single
   declaration turns these top rules into gradients and leaves the other three
   sides alone. */
/* Only the capabilities card. The ecosystem grid keeps a plain rule that turns
   active orange on hover: border-image makes border-top-color a no-op, which
   would silently remove the only hover affordance those cards have. */
.caps__item{border-image:var(--th-grad-h) 1}

/* --- 8.4 Insights carousel and cards ------------------------------------ */
.carousel-rail__thumb{background:var(--th-grad-h)}
/* Was a literal five-stop ramp on the featured card's right edge. */
.post--featured::after{background:var(--th-grad-v)}

/* --- 8.5 Platform "why" band -------------------------------------------- */
/* The decorative corner glow was a fixed orange radial. */
.pw__glow{background:radial-gradient(circle,rgb(var(--th-a-rgb) / .22),transparent 70%)}

/* --- 8.6 The two logo marquees ------------------------------------------
   These carry white text and white-filtered logos, so the gradient goes on as
   a translucent wash over the existing dark indigo ramp rather than replacing
   it. The two bands keep distinct treatments — they make different claims —
   but both now answer to the picker.
   ------------------------------------------------------------------------ */
.partners--platforms{
  background:
    linear-gradient(105deg, rgb(var(--th-a-rgb) / .34) 0%, rgb(var(--th-b-rgb) / .26) 100%),
    linear-gradient(105deg,#0D0B2E 0%,#1E1B56 58%,#2A2570 100%);
}
.partners--clients{
  background:
    linear-gradient(105deg, rgb(var(--th-b-rgb) / .30) 0%, rgb(var(--th-a-rgb) / .34) 100%),
    linear-gradient(105deg,#241F63 0%,#14103F 46%,#000979 100%);
}

/* Deliberately left on the fixed brand ramp:

   .mega-item-title   gradient-clipped text in the mega menu. It already sets
                      amber at 2.20 on white, which is under the floor, and it
                      appears on the homepage and About too where there is no
                      picker to explain a change. Six more variants of an
                      existing contrast problem is not an improvement.
   .offer__icon, .pw__eyebrow, .pw__n, the eyebrows, the CTA link colour
                      All accent-semantic: active orange means interactive,
                      amber means decorative title. Both comps draw them
                      identically in light and dark, so they are furniture
                      rather than theme. */
