/*
  Shared tokens for every code component.

  A site-level CSS *class* never reaches inside a shadow root, but a site-level
  *variable* does (CLAUDE.md section 2) - so this file is where the components
  meet the site's own design system. Each --na-* name below resolves to the real
  Webflow variable first and to the value from the Figma file only as a fallback,
  which means retuning a colour in the Designer moves every block with it, while
  the components still render correctly in the local gallery, in Figma-faithful
  isolation, and on any site that does not define these.

  The Webflow names are the ones the Designer emits for the "Semantic Colors"
  and "Base" collections of the Nebius Academy site; every fallback here was
  checked against the live value and matches it.
*/
/* Both selectors on purpose: the CLI rewrites `:root` to `:host` when it builds
   the shadow-DOM stylesheet (section 2), but the local gallery inlines this file
   as-is, where `:root` matches nothing and every token would silently fall back.
   Naming `:host` as well makes the gallery behave like the real thing. */
:host,
:host {
  --container-max-width: 1280px;

  /* Type. Webflow holds no font-family variables - the faces are set in the
     site's typography settings - so these are ours, with the project's own
     "Inter 28" first and metric-compatible Arial behind it. */
  --na-font-sans: "Inter 28", Arial, sans-serif;
  --na-font-display: "Inter Tight", "Inter 28", Arial, sans-serif;
  --na-font-serif: "Tiempos Text", Georgia, "Times New Roman", serif;

  /* Text */
  --na-content-primary-bold: var(--_semantic-colors---content--primary-bold, #14070f);
  --na-content-primary: var(--_semantic-colors---content--primary, rgba(20, 7, 15, 0.9));
  --na-content-secondary: var(--_semantic-colors---content--secondary, rgba(20, 7, 15, 0.7));
  --na-content-tertiary: var(--_semantic-colors---content--tertiary, rgba(20, 7, 15, 0.5));
  --na-content-inverse-primary-bold: var(--_semantic-colors---content--inverse-primary-bold, #f9f8f6);
  --na-content-inverse-primary: var(--_semantic-colors---content--inverse-primary, rgba(249, 248, 246, 0.9));
  --na-content-inverse-secondary: var(--_semantic-colors---content--inverse-secondary, rgba(249, 248, 246, 0.7));
  --na-content-inverse-tertiary: var(--_semantic-colors---content--inverse-tertiary, rgba(249, 248, 246, 0.5));

  /* Surfaces */
  --na-background-primary: var(--_semantic-colors---background--primary, #f3efe8);
  --na-background-secondary: var(--_semantic-colors---background--secondary, #f9f8f6);
  --na-background-inverse: var(--_semantic-colors---background--inverse, rgba(20, 7, 15, 0.95));
  --na-background-primary-hover-subtle: var(--_semantic-colors---background--primary-hover-subtle, #eae6dc);

  /* Borders */
  --na-border-primary: var(--_semantic-colors---border-stroke--black--primary, rgba(20, 7, 15, 0.2));
  --na-border-secondary: var(--_semantic-colors---border-stroke--black--secondary, rgba(20, 7, 15, 0.1));
  --na-border-inverse-primary: var(--_semantic-colors---border-stroke--white--primary, rgba(249, 248, 246, 0.7));
  --na-color-black-alpha-20-a: var(--black--alpha--20-a, rgba(20, 7, 15, 0.2));

  /* No counterpart in the site's collections, so these stay ours - but they are
     still variables, so a page can retune them.

     Two of them deliberately do NOT use the similarly named Webflow token:
     Background/primary-hover is #ab9c8f there, and primary-hover-subtle is the
     opaque #eae6dc. Both are meant for beige cards. Over the header's glass bar
     a hover has to be a transparent wash or it reads as a solid chip, which is
     what the two "ghost" names below are for. */
  --na-background-primary-subtle: #f3efe8;
  --na-background-white: #fff;
  --na-background-glass: rgba(255, 255, 255, 0.5);
  --na-background-primary-hover: rgba(20, 7, 15, 0.06);
  --na-background-ghost-hover: rgba(20, 7, 15, 0.06);
  --na-background-ghost-hover-subtle: rgba(20, 7, 15, 0.04);
  --na-color-white-alpha-70-a: rgba(255, 255, 255, 0.7);
  --na-color-white-alpha-50-a: rgba(255, 255, 255, 0.5);
  --na-color-white-alpha-40-a: rgba(255, 255, 255, 0.4);

  /* The Problem section's "Tint" stat card (Figma node 21245-5945): a solid
     lavender card with a black-fading-to-lavender figure. No counterpart in
     the site's collections, so this stays ours the way the block above does. */
  --na-background-tint-purple: #d3d3fa;
  --na-gradient-stat-fade: linear-gradient(90deg, rgba(20, 7, 15, 0.95) 0%, rgba(20, 7, 15, 0.95) 35%, rgba(211, 211, 250, 0.9) 100%);

  /* The gradient every *emphasis* italic run in a heading is clipped to
     (Figma node 21045-20, the Hero heading). Webflow has no gradient
     variables to bridge to (same reasoning as the fonts above), so this one
     is ours outright - defined once here and referenced by every section's
     .emphasis rule instead of repeating the five stops seven times. */
  --na-gradient-emphasis: linear-gradient(213deg, #b5a9ea 18%, #8873de 54%, #2e2e7d 75%, #43425b 95%);
}

.LrziVRlNDf7w8PVeIa4P {
  width: 100%;
  box-sizing: border-box;
  background: var(--na-background-primary-subtle, #f3efe8);
  border-radius: var(--na-border-radius-24, 24px);
  padding: var(--na-spacing-56, 56px) var(--na-spacing-16, 16px);
}

.tMgvoLMJbqQfOEr89GQ8 {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--na-spacing-64, 64px);
  width: 100%;
  max-width: var(--container-max-width, 1280px);
  margin-inline: auto;
}

.zNtxOeTyh_yqurWKFpRG {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--na-spacing-24, 24px);
  width: 100%;
}

.aboN3mZiFs8y9fxx41IP {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--na-spacing-20, 20px);
  width: 100%;
}

.UTLcZc38xXsNVBh2ZK1z {
  display: flex;
  align-items: center;
  gap: 5px;
}

.xhC6wMV3ikadO9FxwxAu {
  --wordmark-width: 90px;
}

/* Figma only shows the product logo beside the wordmark on the widest frame;
   the tablet and mobile variants are the mark on its own. Height, not width,
   is what's fixed - the uploaded SVG's own aspect ratio decides how wide it
   sits next to the wordmark (CLAUDE.md section 4: an uploaded image, never a
   hard-coded asset). */
.VZlK5byXWC6d68DNbXCg {
  display: none;
  height: 12px;
  width: auto;
  object-fit: contain;
}

.sPZlKANs7vPTDnBlsvOQ {
  margin: 0;
  font-family: var(--na-font-display, "Inter Tight", "Inter 28", Arial, sans-serif);
  font-weight: 400;
  font-size: 36px;
  line-height: 40px;
  text-align: center;
  text-wrap: balance;
  overflow-wrap: break-word;
  color: var(--na-content-primary, rgba(20, 7, 15, 0.9));
}

.hy5Bn26UVDvllhr92HfJ {
  font-family: var(--na-font-serif, "Tiempos Text", Georgia, "Times New Roman", serif);
  font-style: italic;
  /* Clipped to the shared purple gradient (globals.css) rather than a
     flat color - see CLAUDE.md section 10. */
  background-image: var(--na-gradient-emphasis, linear-gradient(213deg, #b5a9ea 18%, #8873de 54%, #2e2e7d 75%, #43425b 95%));
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* Same italic serif run, without the gradient clip - for headings that should
   read as plain text emphasis (`emphasisGradient: false`). */
.t8ANPHrLbe_UUqL7Et59 {
  font-family: var(--na-font-serif, "Tiempos Text", Georgia, "Times New Roman", serif);
  font-style: italic;
  color: inherit;
}

/* The <br> a newline in the heading produces. It breaks the line at every
   width here; the class exists so this section can change that at a breakpoint
   the way ProblemSection does (CLAUDE.md section 10). */
.x063tglPzC1azMa1T7nm {
  display: inline;
}

.IVCJ2T79jnoK1HwS7Lfm {
  margin: 0;
  max-width: 780px;
  font-family: var(--na-font-sans, "Inter 28", Arial, sans-serif);
  font-weight: 400;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: -0.2px;
  text-align: center;
  color: var(--na-content-secondary, rgba(20, 7, 15, 0.7));
}

.IVCJ2T79jnoK1HwS7Lfm p {
  margin: 0;
}

.IVCJ2T79jnoK1HwS7Lfm p + p {
  margin-top: 0.75em;
}

.SJ87tG3n0PKrOQAE5BNC {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: var(--na-spacing-16, 16px);
}

.J0ukLeow0qQMLV1WSi62,
.rbQvfot_ncwvAIdjsBT6 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  height: 52px;
  padding: var(--na-spacing-12, 12px) 24px;
  border: none;
  border-radius: var(--na-border-radius-14, 14px);
  font-family: var(--na-font-sans, "Inter 28", Arial, sans-serif);
  font-weight: 400;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: -0.2px;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.J0ukLeow0qQMLV1WSi62 {
  background: var(--na-background-inverse, rgba(20, 7, 15, 0.95));
  color: var(--na-content-inverse-primary, rgba(249, 248, 246, 0.9));
}

.rbQvfot_ncwvAIdjsBT6 {
  border: 1px solid var(--na-border-primary, rgba(20, 7, 15, 0.2));
  background: transparent;
  color: var(--na-content-primary, rgba(20, 7, 15, 0.9));
}

/* ---- demo media ----------------------------------------------------------

   Two boxes on purpose: a tinted frame (.media) and the demo itself (.plate).
   The plate stays at its own fixed resting size always - it is `.media`, the
   frame behind it, that opens to fill the viewport the moment scrolling
   reaches the hero, holds there, then closes back - Calendly-style, and each
   open/close is a CSS transition, not scroll-scrubbed (CLAUDE.md section 13).
   `.mediaStage` is the layout box: it holds the frame's resting footprint in
   the flex column at rest, and on desktop HeroSection.tsx stretches its
   height to also reserve the scroll room the hold needs, so the section after
   the hero only arrives once that runs out. Everything below 992px (and
   reduced motion) leaves both elements at their stylesheet defaults - the
   static design state. */
.v9rIXp5i16EnBl4IVCgE {
  position: relative;
  width: 100%;
  max-width: 927px;
  /* Figma pins the frame at 521px tall from 743px upwards. Below that a fixed
     521px would be taller than the frame is wide, so the height eases down
     with the viewport instead of snapping at a breakpoint. HeroSection.tsx
     mirrors this exact clamp() when it computes the resting height for the
     scroll gesture. */
  height: clamp(320px, 70vw, 521px);
}

/* `top`/`left` + explicit `width`/`height`, never the `inset` shorthand: the
   scroll effect overrides `top`/`transform`/`width`/`height` inline every
   frame (and swaps `position` between `fixed` and `sticky` - CLAUDE.md
   section 13), and a stray `right`/`bottom: 0` left over from `inset` would
   over-constrain the box against those.

   Horizontal centering is a `transform: translateX()` the scroll effect
   computes every frame, not a `left` percentage - a sticky box's `left`
   percentage resolves against `.mediaStage` (its containing block) even
   while vertically stuck, not the viewport, so it drifts once the frame
   grows past the stage's own width. `transform` isn't part of sticky's inset
   system, so it applies the same way regardless of `position`.

   `expand` itself is a step (0 or 1), not a scroll-scrubbed fraction - the
   transition below turns each step into a smooth open/close, at a fixed
   duration regardless of scroll speed (CLAUDE.md section 13). It only covers
   `width`/`height`/`border-radius`, deliberately not `top` or `transform`:
   those two are also rewritten every scroll frame to track the live scroll
   position (both during the plain fixed-tracking phase and across the
   fixed/sticky handoff), and transitioning a property that's already being
   set every frame makes it lag behind the scroll position it's supposed to
   match - which reads as the frame jerking sideways once the lag catches up
   or the position mode swaps underneath it. */
.bHrSi1mFJdO2XICcmCke {
  --expand: 0;
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: calc(var(--na-border-radius-24, 24px) * (1 - var(--expand)));
  background: var(--na-background-primary-hover-subtle, #eae6dc);
  will-change: top, transform, width, height;
}

/* The `transition` lives here, not on `.media` itself, so the very first size
   this element ever gets written on mount (HeroSection.tsx) lands instantly -
   HeroSection.tsx only adds this class a couple of frames later. Without that
   gap, a size read that races the shadow root's own stylesheet `<link>` (still
   loading, so the box briefly lays out unstyled/wide - CLAUDE.md section 5)
   gets locked in as the *target* the very first write animates toward, which
   is the load-time jump this avoids. Every write after that first one is a
   real scroll-driven open/close and should animate. */
.bHrSi1mFJdO2XICcmCke.vG2u436FVpPZFC3Xozqo {
  transition:
    width 650ms cubic-bezier(0.22, 1, 0.36, 1),
    height 650ms cubic-bezier(0.22, 1, 0.36, 1),
    border-radius 650ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Optional, and edge to edge behind the plate - visible in the gutter around
   it at rest, and briefly behind it while `--expand` is still under 1. `.plate`
   is `position: relative` and comes after this in the DOM, which is what keeps
   it on top (CSS 2.1 Appendix E: a positioned descendant at z-index 0/auto
   still paints after ordinary in-flow content, so pairing "absolute" here with
   "relative" there, in that order, is what makes the stacking come out right -
   see ProblemCard for the same trick). */
.BXVZBXui82qMrEMeXoL_ {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Figma's plate is 545 x 335, a 1.63 ratio that no video is ever authored in.
   The width is kept (capped so the plate can never outgrow the frame it sits
   in - on a phone that cap is what makes the plate full width), and the height
   now comes from `aspect-ratio` instead of Figma's 335px, so a 16:9 demo fills
   the plate exactly and nothing has to be cropped or letterboxed to fit.

   `.media`, the frame around the plate, grows, holds and shrinks its own
   `width`/`height` (CLAUDE.md section 13); the plate grows the same real way
   - a `width` HeroSection.tsx's scroll effect sets on this element every
   frame, not a `transform: scale()`, with height following for free from
   `aspect-ratio` below. It targets 80% of the viewport width, capped at
   1500px, well under the frame's own edge-to-edge size at full expand - `100%
   - 32px` here is only the *resting* width's cap (this element's box against
   `.media`'s own resting width), since that part alone is expressible as a
   stylesheet ratio; the viewport-relative expanded target is not, so the JS
   effect owns the whole interpolation between the two, the same way it
   already does for `restHeight` vs. the fullscreen height on `.media`.

   No `max-height` alongside the ratio: a definite height would simply win
   over `aspect-ratio` (a box with both axes definite ignores it) and put the
   letterboxing straight back. Unlike at rest, the *expanded* plate is not
   guaranteed shorter than the frame - at 1500px wide, 16:9 makes it 844px
   tall, taller than the frame on a short viewport - so `.media`'s own
   `overflow: hidden` is what keeps that case from spilling out rather than
   any constraint here. */
.E2rV928P6ZMhndU067Cp {
  --plate-rest-width: min(545px, 100% - 32px);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: var(--plate-rest-width);
  aspect-ratio: 16 / 9;
  padding: var(--na-spacing-24, 24px);
  overflow: hidden;
  border-radius: var(--na-border-radius-16, 16px);
  background: var(--na-background-secondary, #f9f8f6);
}

/* Same reason as `.media.animated` above - kept off the base rule so the first
   write on mount doesn't animate. */
.E2rV928P6ZMhndU067Cp.vG2u436FVpPZFC3Xozqo {
  transition: width 650ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* The Media slot layer. It is always in the DOM because a props.Slot always
   compiles to a <slot>, so it sits behind the fallbacks and only shows when the
   designer has actually dropped something in (HeroSection.tsx hides the
   fallbacks then). ::slotted() is the only selector that reaches projected
   content, and it targets the ONE unclassed wrapper Webflow feeds the slot -
   sizing that wrapper is what makes a dropped video fill the plate. */
.kTM1TCa2Kqu9uJwovq3A {
  position: absolute;
  inset: 0;
}

.kTM1TCa2Kqu9uJwovq3A > slot::slotted(*) {
  display: block;
  width: 100%;
  height: 100%;
}

/* inset: 0 ignores the plate's padding, so real media bleeds to its corners
   while the placeholder caption stays inside it. */
.YltuazXzrylNf6qto_IA {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A YouTube embed, which cannot be cropped with `object-fit` - that property
   does nothing to an iframe, so a video whose ratio is not the plate's would
   letterbox against the plate's near-white background. The plate is 16:9 now,
   so for a 16:9 video this resolves to a plain 100%/100% - it is what keeps
   anything else (a 4:3 or vertical demo) filling the plate. The iframe is grown
   to whichever dimension covers the plate and centred, and this wrapper clips
   the overflow, the same way `object-fit: cover` clips the <video> above.
   It exists rather than sizing the iframe against `.plate` directly because
   container query units resolve against the container's *content* box: the
   plate's own 24px padding would come off all four sides and leave the video
   short of its corners. This box has `inset: 0` and no padding, so 100cqw/cqh
   really is the whole plate. Size containment is safe on it for the same
   reason - its size comes from `inset`, never from the iframe inside. */
.TTWX9q9VsIV1sS5iRhR3 {
  position: absolute;
  inset: 0;
  overflow: hidden;
  container-type: size;
}

/* The 100%/100% pair first is the letterboxed fallback for anything without
   container query units. */
.WMDpGS8lZ2t0avPw988Q {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  width: max(100cqw, calc(100cqh * 16 / 9));
  height: max(100cqh, calc(100cqw * 9 / 16));
  border: 0;
  transform: translate(-50%, -50%);
}

.pWRKvIg9HQ66DqPu5hxQ {
  margin: 0;
  width: 100%;
  font-family: var(--na-font-sans, "Inter 28", Arial, sans-serif);
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  letter-spacing: -0.2px;
  text-align: center;
  color: var(--na-content-tertiary, rgba(20, 7, 15, 0.5));
}

/* Tablet: wider gutters and Figma's Display/Regular heading */
@media (min-width: 768px) {
  .LrziVRlNDf7w8PVeIa4P {
    padding: var(--na-spacing-56, 56px) var(--na-spacing-48, 48px);
  }

  .xhC6wMV3ikadO9FxwxAu {
    /* --wordmark-width: 132px; */
  }

  .sPZlKANs7vPTDnBlsvOQ {
    font-size: 54px;
    line-height: 58px;
  }
}

/* Medium desktop: the larger heading, and where the scroll expansion starts */
@media (min-width: 992px) {
  .LrziVRlNDf7w8PVeIa4P {
    padding-bottom: var(--na-spacing-80, 80px);
  }

  .xhC6wMV3ikadO9FxwxAu {
    /* --wordmark-width: 140px; */
  }

  .sPZlKANs7vPTDnBlsvOQ {
    font-size: 62px;
    line-height: 66px;
  }
}

/* Large desktop: full padding, and the product name joins the wordmark */
@media (min-width: 1200px) {
  .LrziVRlNDf7w8PVeIa4P {
    padding: var(--na-spacing-80, 96px) var(--na-spacing-80, 80px);
  }

  .xhC6wMV3ikadO9FxwxAu {
    /* --wordmark-width: 146px; */
  }

  .VZlK5byXWC6d68DNbXCg {
    display: block;
  }
}

/* The Evolve pill, one image now that the design ships it as a single SVG
   instead of a bracket-icon-plus-text-run composite - see Wordmark.tsx. Every
   place it appears is that same artwork at a different width (hero 90, the
   ecosystem card 126…), so sizing is `--wordmark-width` and the artwork's own
   aspect ratio, nothing positioned by hand.

   `height: auto` is load-bearing, and so is the missing `height` attribute on
   the <svg> itself: an inline SVG's width/height attributes are *presentation
   attributes*, i.e. real CSS declarations, and a specified height makes the
   browser ignore `aspect-ratio` entirely. With `height="24"` still on the tag
   the element stayed 24px tall at every width and the artwork just letterboxed
   inside it - which is why the card's mark rendered at exactly the hero's
   size with 60px of dead space either side of it. */
.xywYA8RNsVWVsJQED9AD {
  display: block;
  width: var(--wordmark-width, 90px);
  height: auto;
  aspect-ratio: 91 / 24;
}

/* Three identical tiles stacked - the site's own recipe (see NoiseOverlay.tsx),
   not something to simplify to one: stacking the same tile compounds the
   blend into visible grain, where a single layer reads as almost nothing. */
/* No z-index here on purpose. A positioned element with z-index:0/auto still
   paints after in-flow, non-positioned content in the same stacking context
   (CSS 2.1 Appendix E) - so as long as the real content sitting over this
   overlay is itself `position: relative` (as every card content wrapper in
   this project already is) and comes later in the DOM, tree order alone keeps
   the grain under the copy. A fixed z-index here would instead force the
   overlay in front of any caller that isn't giving its content a *higher*
   one, which is one more number every caller would have to know to match. */
.VjIzBc5zC6slMhcdqEWS {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.YqJvn0ptuHfTjC_wXJLP {
  position: absolute;
  inset: 0;
  background-color: var(--na-background-secondary, #f9f8f6);
  background-image: var(--noise-url);
  background-size: var(--noise-size, 256px);
  mix-blend-mode: color-burn;
}

