/* platform-scrolly.css — built from the prototype's platform.css by
   webflow/build.py. Do not edit here; edit the prototype and rebuild.
   Loaded only on /low-code-platform, from that page's head custom code. */

/* The prototype page carried its own reset; on Webflow the only parts of
   it that matter are the heading/paragraph margins, scoped to the scene so
   nothing else on the site moves. */
   :not([class]) keeps it OUT of the way of native Designer classes: a
   type selector inside a class beats a plain class (0,1,1 vs 0,1,0), so
   without it this reset would silently win over .hero-title's margin.
   Anything in the scene that carries a class owns its own margin. */
.platform-scrolly :is(h1, h2, p):not([class]) { margin: 0; }
/* Webflow's base stylesheet sets img { max-width: 100% }. Several images
   in the scene hang off ZERO-SIZE anchors — .slab-mark and the four shadow
   layers sit on .platform-slab, which is a 0x0 transform origin by design —
   so a percentage max-width resolves against nothing and collapses them to
   width 0 while the explicit height still applies. That is the logo and its
   shadow disappearing. The prototype had no such rule; undo it for the
   scene only. */
.platform-scrolly img { max-width: none; }
/* …and it styles the bare p tag (color #252b37, 1.25rem). A type selector
   beats inheritance whatever the specificity of the ancestor, so every
   paragraph in the scene came out dark grey on the dark purple stage —
   barely legible. The scene sets the colour on the CONTAINERS
   (.platform-hero, .platform-copy, .platform-card are all #fff), which is
   how the prototype does it, so put the paragraphs back on inheritance. */
.platform-scrolly p { color: inherit; }
/* Same again for headings: the site colours them with var(--titles)
   (#1a1b25) through a type selector, so the step headlines came out
   near-black on the dark stage. */
.platform-scrolly :is(h1, h2, h3) { color: inherit; }

/* ── ROOM FOR THE FIXED HEADER ────────────────────────────────────
   The prototype had the stage to itself, so everything is centred in the
   full viewport. On the site a fixed .section-header covers the top 104 px
   (24 px offset + 80 px tall), which ate the top of the tower. So the
   scene is centred in the space BELOW the header instead, and its square
   box is measured against that same reduced height — shifting alone would
   push the bottom of the tower off screen, because 92vh + half the header
   is taller than the viewport on anything under ~1300 px.
   One variable drives both, and the brand's perspective has to keep using
   the SAME expression as the box or the step-7 pose changes with the
   viewport (see .platform-brand). */
.platform-scrolly {
  --platform-head: 104px;
  --platform-box: min(60rem, (100vh - var(--platform-head)) * 0.92);
}
.platform-scrolly .platform-tower {
  width: var(--platform-box);
  height: var(--platform-box);
  top: calc(50% + var(--platform-head) / 2);
}
.platform-scrolly .platform-copy { top: calc(50% + var(--platform-head) / 2); }
.platform-scrolly .platform-brand { perspective: calc(var(--platform-box) * 1.0417); }

/* ── FIRST PAINT ──────────────────────────────────────────────────
   The scene's resting state is normally established by the gsap.set()
   calls at the top of build(), which cannot run before the HTML paints.
   On a local file that gap is invisible; over the network it is most of a
   second of the raw markup — all four copy slides are position:absolute on
   the same line, so they land on top of each other, and every layer, chip
   and the brand lock-up show at once. Mirrored here so the FIRST frame is
   already the resting state and JS only takes over from it.
   GSAP writes autoAlpha as an inline style, which outranks all of this the
   moment it runs — so these are start values, not a fight. */
.platform-scrolly .platform-slide,
.platform-scrolly .platform-intro,
.platform-scrolly .platform-brand,
.platform-scrolly .platform-solo-inner,
.platform-scrolly .platform-icons .platform-chip,
.platform-scrolly .platform-layer > .layer-blur {
  opacity: 0;
  visibility: hidden;
}
/* …and the art the CSS slabs replace. It is normally hidden by
   .platform-layer.has-slab > img, but JS adds that class, so until it runs
   these are <img src=""> — which Webflow emits for an Image element with no
   asset, and which the browser paints as a broken-image square. In slab
   mode none of them is ever shown, so hide them without waiting. */
[data-platform-slabs="css"] .platform-layer > img { display: none; }

/* the КРОК-7 down-line continues inside the next section, which therefore
   has to be a positioning context for the absolutely placed connector */
.platform-after-host { position: relative; }

/* ── SEAM INTO THE NEXT SECTION ─────────────────────────
   The stage ends on a radial glow anchored to the BOTTOM of the viewport
   (#8e07ff at 50% 100%), and the stage bottom is exactly the top edge of
   the section that follows — which starts on its own flat gradient. Bright
   purple meeting #293395 in one pixel row is the hard band you see.
   The prototype solves it (.platform-after::before) with a MIRRORED copy of
   the same radial, pinned to the top edge of the next section and masked
   away downwards, so the glow flows across the boundary and dissolves into
   that section's own colour. Ported here onto whatever section follows the
   scene — this stylesheet only loads on /low-code-platform, so it cannot
   reach any other page.
   z-index -1 (the house .section-cover pattern) keeps it above the
   section background but under the content; the section already makes a
   stacking context of its own, so -1 cannot escape it.
   Desktop only: below 992 the scene is display:none, so there is no glow
   above the boundary to carry on with — the mirrored copy would read as a
   purple wash arriving from nowhere at the top of the page.
   The phone-only opening scene (.section-mobile-scrolly, display:none up
   here) now sits BETWEEN the two, so a bare '+ section' landed on that
   hidden block and the seam came back as a hard edge — hence the second
   selector, which skips over it. */
@media (min-width: 992px) {
.platform-scrolly + section,
.platform-scrolly + .section-mobile-scrolly + section { position: relative; }
.platform-scrolly + section::before,
.platform-scrolly + .section-mobile-scrolly + section::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  /* 62vh -> 22.5rem (2026-09-23): the glow used to fade over ~560px, which
     washed the whole headline and the first row of cards purple — far
     brighter than Figma. It now dissolves exactly by the bottom of the
     title (measured 22.3rem below the section top at 1280, 1440 and 1920 —
     rem, not vh, because the layout it has to meet is rem), and from there
     the section's own Figma gradient takes over. */
  height: 22.5rem;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
    radial-gradient(
      100% 100% at 50% 0%,
      #8e07ff 5%,
      #4e06c0 45%,
      rgba(18, 20, 51, 0) 100%
    );
  -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, transparent 100%);
}
}

/* platform.css — Platform page scrollytelling scene (local prototype).
   The stage is CSS-sticky (like the homepage scrolly); GSAP only scrubs.

   Layer anatomy: every tower floor is
       .platform-layer[data-platform-spread]  ← wrapper: hover/click SPREAD only
         img                                  ← inner: scrub owns enter/fade
   so the scroll scrub and the interactive spread never animate the same node.

   All tower art shares one 1600×1600 canvas → every layer is inset:0 and
   pre-registered; icons are placed in % of that same canvas via --x/--y. */

.platform-scrolly {
  position: relative;
  /* track height is set from JS: 100vh + data-platform-length */
}

.platform-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  background:
    linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
    radial-gradient(100% 100% at 50% 100%, #8e07ff 5%, #4e06c0 45%, #121433 100%);
}

/* ── solo box: lid (base-first) + cube that extrudes out of it ─────────
   The lid is registered over the cube's TOP FACE: its plate centre (at
   45.8% / 53.4% of the lid image) is pinned to the face centre (50% /
   30.6% of the tower canvas), and its width makes the plate match the
   face width. Tune the three numbers below if the seat is off. */
.platform-solo-inner {
  position: absolute;
  inset: 0;
}
.solo-cube {
  position: absolute;
  inset: 0;
  z-index: 2; /* the tower paints ABOVE the lid */
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* the lid is BAKED onto the shared 1600 canvas (base-first-reg) — a plain
   full-size layer, scale is always 1, no separate registration needed */
.solo-lid-wrap {
  position: absolute;
  inset: 0;
  z-index: 1;
  /* the copies inside ADD (plus-lighter) inside this isolated group, so a
     cross-fade between two of them is an exact linear mix — see .solo-lid-x */
  isolation: isolate;
}
.solo-lid-wrap > img {
  mix-blend-mode: plus-lighter;
}
.solo-lid,
.solo-lid-x {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* The lid's exit blur used to be a CSS filter: blur(0 -> 22px) tweened on this
   1466 px image — a full gaussian over 2.1 Mpx (8.6 in retina) on every frame of
   the hand-over, the single heaviest effect on the page. Now the ramp is SIX
   copies with the blur baked in (base-first-blur-x1..x6.webp, 3.7 px steps,
   blurred premultiplied at the canvas' own scale), stacked over the crisp one
   and cross-faded one into the next while the wrap carries the fade-out. The
   crisp lid is stop 0; at any point exactly two neighbouring stops are visible,
   with weights that sum to 1, and they are composited with plus-lighter inside
   the wrap's isolated group — so the result is EXACTLY w·A + (1-w)·B in
   premultiplied space. Plain source-over cannot do that for a translucent
   image: stacking copies compounds their soft alpha (1-(1-a)^n — the lid, all
   soft edge, came out far too opaque), and fading both dips the coverage
   mid-way. Why six: a crossfade between two blur radii is NOT the blur in between
   (the wider copy's tails show up early). Simulated on the lid itself, the worst
   frame is 24/255 with two stops, 19 with three, 6 with seven — below what a
   moving gradient can show. Stops 2-6 are stored at 800² (a sigma >= 7 px image
   has no detail past half-res), so the six weigh less than the one they replace.
   The legacy page keeps the filter (no wrap there). */
.solo-lid-x {
  opacity: 0;
}

/* ── the tower ─────────────────────────────────────────────────────────── */
.platform-tower {
  position: absolute;
  z-index: 2; /* above the mountains */
  left: 31%;
  top: 50%;
  /* MUST stay square: chips are positioned in % of this box while the cube art
     is object-fit:contain inside it — a non-square box letterboxes the art and
     the chips drift off it. Height is set to the SAME expression (not just
     aspect-ratio) so a host stylesheet that forces a height can't break the
     square. */
  width: min(60rem, 92vh);
  height: min(60rem, 92vh);
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
}

.platform-layer {
  position: absolute;
  inset: 0;
  pointer-events: none; /* the tower hit-area handles hover/click */
}
.platform-layer img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* ONLY the frosted floors need their crisp cube art lifted above the blur:
   the img directly after a .layer-blur is made positioned (z-index auto) so it
   paints AFTER — i.e. ON TOP of — the absolutely-positioned .layer-blur before
   it. No numeric z-index → no new stacking context, so the blur still reads the
   cross-layer backdrop. Scoped this way it never touches .solo-cube/.solo-lid
   (which set their own absolute positions + z-index). */
.layer-blur + img {
  position: relative;
}
/* the Foundation "-last" art overlays the base exactly and cross-fades in on
   focus (JS animates its opacity) — a smooth swap instead of a hard src change */
.platform-layer img.foundation-last {
  position: absolute;
  inset: 0;
  opacity: 0;
}
/* frosted glass: backdrop-blur clipped to the cube silhouette (Figma's
   background-blur can't bake into a transparent PNG, so we recreate it here).
   Sits UNDER the cube art; blurs the lower cubes + gradient within the shape.
   The silhouette is a clip-path polygon (per cube, below) — geometry only, so
   no external mask asset is fetched (works on file:// with no CORS). Traced
   from each cube's own alpha at ~1600² canvas %, so it registers exactly. */
.layer-blur {
  position: absolute;
  inset: 0;
  /* blur is ALWAYS on by default — the cubes are always visible, so the frost
     should never be off. (On desktop the scrub still hides it during the intro
     assembly and fades it in with its cube; without JS it just stays on.) */
  opacity: 1;
  /* clip-path + backdrop-filter on the SAME element is fine cross-browser (the
     clip just trims the blurred result). Chrome/Firefox/Safari 16.4+. */
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}
.layer-blur.is-wf-big {
  clip-path: polygon(50.06% 14%, 55.88% 15.5%, 73.5% 27.62%, 75.19% 29.5%, 75.69% 31.87%, 75.69% 55.25%, 73.56% 58.5%, 55.06% 70.75%, 52.12% 71.88%, 49% 72.12%, 44.75% 71%, 24.88% 56.88%, 24.31% 55.25%, 24.25% 31.25%, 24.88% 29.25%, 26.25% 27.75%, 45.12% 15.25%);
}
.layer-blur.is-wf-flat {
  clip-path: polygon(49.25% 29.25%, 53.12% 29.5%, 55.88% 30.63%, 73.69% 42.88%, 75.75% 46.25%, 75.81% 55.25%, 74.5% 58%, 55.31% 70.88%, 51.25% 72.25%, 48.19% 72.25%, 44.94% 71.25%, 26.44% 58.63%, 24.44% 55.62%, 24.38% 46.25%, 25.5% 43.75%, 45.12% 30.5%);
}
.layer-blur.is-ops {
  clip-path: polygon(50.12% 8.88%, 55.5% 10.12%, 74.62% 23.5%, 75.69% 25.87%, 75.75% 35.25%, 73.62% 38.62%, 55% 51%, 52.44% 52%, 48.5% 52.25%, 44.44% 51%, 25.37% 37.62%, 24.38% 35.5%, 24.31% 26%, 25% 24%, 26.81% 22.25%, 44.75% 10.38%);
}

/* transparent hit-area over the tower silhouette for hover/click */
.platform-hit {
  position: absolute;
  inset: 12%;
  /* no cursor:pointer — the floors are not clickable (see platform.js) */
}

/* ── icon chips on a top face ──────────────────────────────────────────── */
.platform-icons {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* stacking: layers z1, wf icons ride BETWEEN wf art (z1) and ops art (z3)
   so the closed 3-floor stack naturally covers them; ops icons on top */
[data-platform="icons-wf"],
[data-platform="icons-fnd"] {
  z-index: 2;
}
[data-platform="ops"] {
  z-index: 3;
}
[data-platform="icons-ops"] {
  z-index: 4;
}
.icons-inner {
  position: absolute;
  inset: 0;
}
/* focus gating: once the full stack is live, only the ACTIVE layer's chips
   are clickable (before assembly there is no data-focus → all default) */
.platform-tower[data-focus] .platform-chip {
  pointer-events: none;
}
.platform-tower[data-focus="operations"] [data-platform="icons-ops"] .platform-chip,
.platform-tower[data-focus="workflows"] [data-platform="icons-wf"] .platform-chip,
.platform-tower[data-focus="foundation"] [data-platform="icons-fnd"] .platform-chip {
  pointer-events: auto;
}
/* …but a floor on its way out (JS marks the layer at the START of the
   transition) stops being clickable, even if it is the focused one: a
   half-faded, half-blurred card must not still hit-test. Has to sit on the CHIP
   — .platform-chip's own pointer-events:auto beats a `none` inherited from the
   layer — and must come AFTER the rules above, which it ties with on
   specificity. */
.platform-tower .platform-icons[data-inert="1"] .platform-chip {
  pointer-events: none;
}
.platform-chip {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 8.6%;
  /* Centring lives here so it also holds without JS. GSAP re-states it as
     xPercent/yPercent AND explicitly zeroes x/y — otherwise GSAP keeps the px it
     parsed out of this very rule (a computed transform is always resolved to
     pixels) and centres the chip twice, half a chip height too high. */
  transform: translate(-50%, -50%);
  pointer-events: auto;
  cursor: pointer;
  /* NOT transform: GSAP owns this element's transform and rewrites it inline on
     every scrub frame (the reveal stagger, the focus poses). A CSS transition on
     the same property re-interpolates each of those writes over 0.3s, so the
     chips permanently trail the scroll and only reach their target a third of a
     second after it stops. The hover lift is done on the CHILDREN, not here. */
  transition: filter 0.36s ease;
}
.platform-chip.is-logo {
  opacity: 0 !important;
  pointer-events: none !important;
}
.platform-chip img {
  display: block;
  width: 100%;
  height: auto;
}
/* liftable workflows chip: colored hover icon + a stand that grows under it */
.platform-chip .chip-base {
  position: relative;
  z-index: 2;
}
.platform-chip .chip-hover {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  opacity: 0;
}
.platform-chip .chip-stand {
  position: absolute;
  left: 0;
  bottom: 0; /* stand bottom sits at the icon's rest bottom */
  z-index: 1;
  /* revealed via a V-shaped clip that follows the chevron's own top edge
     (NOT scaleY — that squashes the perspective riser). JS animates the
     polygon so it extrudes out along its contour. Start fully hidden. */
  clip-path: polygon(0% 200%, 50% 200%, 100% 200%, 100% 200%, 0% 200%);
}
/* NOTE: there used to be a `.platform-chip:not(.has-lift):hover` fallback lift
   here. It was dead twice over — every real chip carries .has-lift (and is
   driven by JS), the only match left was .is-logo, which is
   `opacity:0 !important; pointer-events:none !important`. Its `transform` could
   never have applied anyway: GSAP writes transform as an INLINE style, which
   outranks any stylesheet rule. */

/* ── chip hover: dashed connectors + floating tag + glass card ─────────── */
.platform-connect {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
}
/* the КРОК-7 down-line under the brand lock-up — same dashed treatment as the
   chip connectors; JS owns the reveal, so it starts invisible */
.platform-brandline {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
}
.after-connect {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* above the ::before transition glow (the radial gradient at the section top
     was painting over the connector) */
  z-index: 2;
}
.after-connect path,
.platform-brandline path,
.platform-connect path {
  fill: none;
}
/* visible dashed segments (stroke = a fade gradient set inline per segment);
   the .conn-mask paths draw them on and stay solid (their dash/stroke are
   set inline by JS, so these rules don't touch them) */
.conn-seg {
  fill: none;
  stroke-width: 0.75;
  stroke-dasharray: 1.5 3.5;
  stroke-linecap: round;
}
.conn-mask {
  fill: none;
  stroke-linecap: round;
}
.platform-chip-tag {
  position: absolute;
  z-index: 4;
  transform: translate(-50%, -100%);
  color: #fff;
  font-weight: 600;
  font-size: 0.95rem;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
}
/* ── THE CARD THAT OPENS ON A CHIP (Figma 6273:32785 "Integrations Block") ─
   390 px wide (24.375rem), 24 px of air on ALL four sides (the older 6242
   frame had 32 on top), 28 px radius, one 18/1.5 paragraph. At two lines of
   copy it is 390x102. The wash is the design's three-stop diagonal at the
   angle Figma resolves for that 102-tall box (-23.99deg; the 224-tall card
   of the old frame came out at -44.4), and the blur is 16 px. Its LEFT edge lines up with the copy column
   above it — the column is 422 and the card 390, so its right inset is the
   column's plus that 32 px difference. */
.platform-card {
  position: absolute;
  z-index: 4;
  /* only the resting place before platform.js measures and positions it
     against the copy column — kept in step with .platform-copy above */
  right: calc(50% - 34.375rem);
  top: 56%;
  width: 24.375rem;
  max-width: 34vw;
  padding: 1.5rem;
  border-radius: 1.75rem;
  /* The glass panel is the card's OWN background, and the slab behind it is a
     separate box (.platform-card-deco) painted just before it — NOT a child.
     As a child it could not go under this background, and moving the glass to
     a ::before above it did not work either: Chrome's backdrop-filter did not
     pick up a sibling painted in the same paint chunk, so the plate came
     through crisp at full strength ("блюру немає") until some later repaint
     forced the compositor to redo it, which is why clicking the card a few
     times made the blur appear. Measured at the plate's edge under the glass:
     as a child, a 41/255 step over 18 px; as a sibling, 21 over 27 px —
     Figma's own render is 19 over ~30. */
  /* two layers: the design's glass gradient, and on top of it the light the
     wedge used to throw down into the card. That light HAS to belong to the
     card — see .card-bevel's clip-path for why. Tuned against the unclipped
     render: +11/255 at the top edge decaying to 0 by 46 px, which reproduces
     it to within 1/255 at every depth. */
  background:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.055) 0,
      rgba(255, 255, 255, 0.03) 1rem,
      rgba(255, 255, 255, 0) 2.875rem
    ),
    linear-gradient(
      -23.993deg,
      rgba(222, 203, 255, 0.3) 8.62%,
      rgba(153, 94, 255, 0.2) 39.96%,
      rgba(123, 56, 240, 0.3) 84.68%
    );
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  color: #fff;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.platform-card.is-shown {
  pointer-events: auto;
}
/* The design's CTA button (6242:31171) is gone on the client's word — that
   Figma frame is out of date — so the text is the card's only content. */
.platform-card .card-text {
  margin: 0;
  font-size: 1.125rem;
  line-height: 1.5;
  font-weight: 500;
}

/* ── the slab the card sits on (Figma 6242:31167 + 6242:31168) ───────────
   Its own box, sized and placed onto the card by platform.js and faded with
   it. z-index 3 puts it directly under the card (4), which is what lets the
   card's backdrop-filter blur it. */
.platform-card-deco {
  position: absolute;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
}
/* Rear plate ("Base", 322x111). Off the 390x102 design card: 33 px in from
   the left, 35 from the right, 8.96 ABOVE the card's top and flush with its
   bottom — insets, not a fixed height, so it follows the card when a longer
   blurb wraps to a third line. Figma
   paints the layer at fill-opacity .5 over stops that are themselves .5,
   hence .25. Its own 12 px backdrop-blur is deliberately NOT reproduced: an
   element with backdrop-filter is a backdrop root and Chrome then leaves it
   out of the card's backdrop, killing the blur that actually matters. */
.platform-card-deco .card-plate {
  position: absolute;
  left: 2.0625rem; /* 33px */
  right: 2.1875rem; /* 35px */
  top: -0.56rem; /* -8.96px */
  bottom: 0;
  border-radius: 1.7156rem; /* 27.45px */
  background: linear-gradient(
    180deg,
    rgba(222, 203, 255, 0.25) 0%,
    rgba(197, 164, 253, 0.25) 40.13%,
    rgba(182, 143, 252, 0.25) 97.4%
  );
}
/* The glossy wedge between plate and glass ("3d") — a trapezoid, so it stays
   an SVG. Fixed 13.37 px tall, hung from the same -8.96 px line; its bottom
   4.41 px tuck behind the card. */
.platform-card-deco .card-bevel {
  position: absolute;
  left: 0.8081rem; /* 12.93px */
  top: -0.56rem; /* -8.96px */
  width: 22.7404rem; /* 363.847px */
  height: 0.8356rem; /* 13.3696px */
  overflow: visible;
  /* CLIPPED at the card's top edge (the design lets the wedge tuck 4.41 px
     under the glass). Anything bright sitting BEHIND translucent glass stops
     dimming with it: fade the pair and the glass's coverage drops faster than
     the wedge does, so that ~8 px band hung on at full strength and then
     vanished at the end — "полоска зверху різко зникає". That is plain alpha
     maths, not a browser quirk, so no compositor hint fixes it (will-change,
     translateZ and isolation all measured identical). The band's light is
     reproduced INSIDE .platform-card's background instead, where it fades with
     the card: band-minus-body then holds at +1.7/255 through the whole fade,
     against a swing of +2 → +17 before. */
  clip-path: inset(0 0 0.2757rem 0); /* 4.41px */
}

/* ── КРОК 2 — intro line ──────────────────────────────────
   .platform-intro itself is styled NATIVELY in the Designer (Figma
   5901:35400: Manrope 700 64/1.1, −2% tracking, 980 px, centred).
   Only its gradient span is left below, because the Designer refuses
   -webkit-background-clip. */
.platform-intro .accent {
  background: linear-gradient(90deg, #a25eff 0%, #05c3cd 50.484%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* ── right copy column, one slide per scene ────────────────────────────── */
/* ── TYPE FOR THE RIGHT-HAND COPY (Figma 6242:30983) ───────────────────
   The frame gives the column and its rhythm: the copy block is 422 px wide
   (26.375rem) with 24 px between the headline and the body, the headline is
   Manrope Bold 48/1.1 at -0.96px tracking (-0.02em at this size), the body is
   Manrope Medium 18/1.5, both pure white — the body was carrying opacity .85,
   which the design does not have. Nodes 6242:31160 (headline) and 6242:31165
   (body). Kept in rem so the whole scene still scales with the root size. */
.platform-copy {
  position: absolute;
  /* SYMMETRIC ABOUT THE CENTRE LINE. It used to be right: 13%, which measured
     (at 1222 css, and the proportions hold at any width) 63 px from the tower's
     art to the left edge, 159 px from the copy to the right edge, and — the
     part that showed — the copy starting further from the connector than the
     tower sits from it. THE GAPS EITHER SIDE OF THE DOTTED LINE ARE WHAT HAS
     TO MATCH (client, marking both on a screenshot): the line's vertical run
     is the stage's centre and the copy starts a gutter past it, while the
     tower's art ends a gutter short of it on КРОК 4.
     Anchored to the MIDDLE rather than to the right edge — left = 100% − right
     − width = 50% + 8rem — so the two gaps stay tied to each other at any
     width instead of drifting with the viewport.
     The gutter is 8rem, not the 6rem that made the two BOXES equal: the
     tower's art is not flush with its box — the lit bevel fades out well
     before the edge — so matching the boxes still read as lopsided on the
     canvas. 8rem matches what the eye lands on. */
  right: calc(50% - 34.375rem); /* 26.375rem column + 8rem gutter */
  top: 50%;
  transform: translateY(-50%);
  width: 26.375rem;
  max-width: 34vw;
  color: #fff;
}
/* Slides hang from the column's line and are NOT self-centred here: the copy
   and the card are centred TOGETHER by platform.js (centreCopy) — with a card
   open the pair has to sit in the middle, not the headline alone. A CSS
   translateY(-50%) also fought GSAP, which owns this element's transform for
   the per-slide y tween and resolved the percentage inconsistently. */
.platform-slide {
  position: absolute;
  inset: auto 0 auto 0;
  top: 50%;
}
.platform-slide h2 {
  font-size: 3rem;
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 1.5rem;
}
.platform-slide p {
  font-size: 1.125rem;
  line-height: 1.5;
  font-weight: 500;
  margin: 0;
}

/* ── КРОК 6–7: brand lock-up ─────────────────────────────────────────────
   ONE frontal mark that first LIES on the block's top face (КРОК 6) and then
   turns to face the viewer (КРОК 7). All geometry is read off Figma and mapped
   onto the 1600² tower canvas (block node 412 px wide ↔ block silhouette
   24.19–75.75 % of the canvas → 2.002 canvas px per Figma px):
     · lying "logo" (6055:27144): 119.7×84.1 Figma px, centre 50.70 % / 64.03 %
     · frontal "logo 1" (6055:27440): 215.2×134, centre 49.97 % / 62.39 %,
       width 26.93 % of the box — SAME x as the lying mark, so it stands up in
       place with no sideways drift;
     · wordmark (6055:27441): 241.7×37 = 112 % of the mark, 30 px gap below.
   The element is the FRONTAL mark at its КРОК-7 size; the КРОК-6 pose
   (platform.js BRAND_LIE — rotateX 67° on .brand-mark-wrap; rotate(-45deg)
   scale(.8) on .brand-mark, under perspective 1000 px) lays it onto the baked
   mark on the top face (apex toward the block's right corner). Centre origin
   so it pivots about its own centre; the parent owns the perspective. See
   .brand-mark-wrap for WHY two layers. */
.platform-brand {
  position: absolute;
  left: 49%;
  top: 62.8%;
  z-index: 3; /* above the foundation art */
  width: 25.6%;
  /* Centring is GSAP's (xPercent/yPercent −50, set in platform.js), NOT a CSS
     translate: GSAP owns this element's transform for the scrub lift, and it
     resolves a CSS translate(-50%) to px on first touch — so the same rule
     here would centre it twice and jump it, exactly the .platform-chip trap. */
  /* proportional to the tower box (same expression as .platform-tower width) so
     the lying pose is viewport-independent: the fit assumed d = 1.0417 × box */
  perspective: calc(min(60rem, 92vh) * 1.0417);
  pointer-events: none;
  --brand-glow: 0; /* JS drives 0→1 on КРОК 7; read by ::before */
}
.platform-brand .brand-mark-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 352 / 219;
  transform-style: preserve-3d;
  /* TILT layer. The lying pose needs rotateX applied AFTER rotateZ (the CSS
     string `rotateX() rotateZ()`), but GSAP always composes a single element's
     rotations as rotateZ·rotateX — the opposite order — which turns the mark
     "on edge" instead of laying it flat. So the two axes live on two elements:
     this wrap takes rotateX, the mark inside takes rotateZ + scale. Nested
     transforms compose outer·inner = rotateX·(rotateZ·scale), exactly the CSS
     order the pose was fitted in. Verified pixel-identical in headless Chrome. */
  transform-origin: 50% 50%;
}
.platform-brand .brand-mark {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  transform-origin: 50% 50%;
  will-change: transform;
  backface-visibility: hidden;
}
/* soft glow behind the mark (the two blurred ellipses in the design) — a
   gradient, not an asset; hidden while the mark is lying on the block */
.platform-brand::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 220%;
  height: 220%;
  transform: translate(-50%, -50%);
  background: radial-gradient(
    50% 50% at 50% 50%,
    rgba(126, 74, 255, 0.5) 0%,
    rgba(96, 60, 214, 0.22) 45%,
    rgba(83, 74, 255, 0) 72%
  );
  opacity: var(--brand-glow); /* 0 while lying on the block, 1 on КРОК 7 */
  pointer-events: none;
}
/* wordmark: 112 % of the mark, hanging 30 Figma px (= 22.4 % of the mark's
   134 px height) under it. Absolutely placed so it never moves the mark. */
.platform-brand .brand-word {
  position: absolute;
  left: 50%;
  top: 100%;
  display: block;
  width: 112%;
  height: auto;
  /* Measured off 6233:30906: the wordmark is 1.125x the logo's width (112% here
     already matches) and its INK sits 0.151 of that width below the logo's ink.
     7.1%, not 15.1%: the margin is measured from the mark's box to the wordmark's
     box, and the wordmark SVG carries ~15 px of its own empty top on this scale,
     so the visible gap is that much larger than the margin. Set from the rendered
     ink, not from the boxes. */
  margin-top: 0.3%;
  transform: translateX(-50%);
  opacity: 0; /* JS reveals on КРОК 7 */
}

/* ── hero "mountains": rounded diamonds with a GRADIENT blur ───────────
   Wrapper carries GSAP transforms; inside: a sharp shape + a heavily
   blurred copy masked top→down, so the peak stays crisp and the blur
   builds towards the middle. Left one is smaller and nearer the centre;
   the right one is larger, further right, peak higher in the viewport. */
.platform-mount {
  position: absolute;
  z-index: 1; /* always UNDER the tower/lid (tower is z-index 2) */
  pointer-events: none;
}
.platform-mount.is-left {
  width: 60vw;
  height: 50vw;
  left: -6vw; /* nearer the centre */
  bottom: -30vw; /* raised ~20% */
}
.platform-mount.is-right {
  width: 85vw;
  height: 85vw;
  right: -26vw; /* nearer the centre */
  bottom: -50vw; /* raised ~20% */
}
/* the pre-blurred diamond PNG fills its wrapper; no filter/mask (the soft edge
   is baked in). object-fit: fill stretches it to the wrapper box so the peak
   stays at the box's top-centre — the geometry the GSAP peak math assumes. */
.mount-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

/* ── hero copy INSIDE the stage (scene 0 — no section break) ──────────── */
/* ── КРОК 1 — HERO ────────────────────────────────────────────
   Deliberately empty here. The hero is styled with NATIVE Designer
   classes (.platform-hero, .hero-label, .hero-title, .hero-text,
   .hero-actions) and uses the site's button components, so nothing
   about it belongs in this file. Edit it in the Designer.
   The prototype's own copy of these rules lives in platform.css. */

.platform-after {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Content sits near the TOP, not centred: centring in a 100vh section pushed
     the pill 304 px down, and everything above it is bare dashed line — added
     to the run below the lock-up that was already ~0.8 of a screen of empty
     connector between КРОК 7 and 8. 16vh roughly halves it and scales with the
     viewport, unlike a rem value. */
  justify-content: flex-start;
  padding: 16vh 1.5rem 6rem;
  gap: 2rem;
  /* the dark gradient of the section itself… */
  background: linear-gradient(135.79deg, #1e256d 0%, #0d102f 75.34%);
  color: #fff;
  text-align: center;
  overflow: hidden;
}
/* …plus the TRANSITION: a mirrored copy of the scrolly's radial, anchored
   to this section's top edge, dissolving downwards — so the purple glow
   flows across the boundary and melts into the dark. */
.platform-after::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 62vh;
  pointer-events: none;
  background:
    linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
    radial-gradient(
      100% 100% at 50% 0%,
      #8e07ff 5%,
      #4e06c0 45%,
      rgba(18, 20, 51, 0) 100%
    );
  -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, transparent 100%);
}
/* The ::before glow is absolutely positioned, so it paints ABOVE static
   children — fine while the copy sat centred, well below it, but the copy now
   starts at 16vh, inside the glow's 90vh band, and was being washed over by it.
   Same fix .after-connect already needed: give the content its own layer. */
.platform-after .after-pill,
.platform-after h2 {
  position: relative;
  z-index: 2;
}
.platform-after .after-pill {
  padding: 0.7rem 1.4rem;
  border-radius: 3rem;
  background: rgba(140, 90, 255, 0.35);
  box-shadow: 0 0 34px rgba(140, 90, 255, 0.55);
  font-weight: 600;
}
.platform-after h2 {
  font-size: 3.6rem;
  line-height: 1.15;
  max-width: 22ch;
}
.platform-after .grad {
  background: linear-gradient(90deg, #b18cff 0%, #5aa8ff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

@media (max-width: 991px) {
  /* THE SCENE IS DESKTOP-ONLY, AND BELOW 992 IT IS OFF — not trimmed.
     The JS gate (gsap.matchMedia in init) already refuses to build here, so
     what used to be left was the raw markup: a 100vh sticky stage with the
     tower art and an unstyled hero whose headline overflowed the viewport.
     Hiding the section is also what makes the JS gate honest — a scene that
     is not running should not be occupying a screen either.
     display:none keeps the copy in the document for crawlers while taking it
     out of the layout; the teardown clears the inline height that build() set,
     so nothing of the desktop track survives down here. */
  .platform-scrolly { display: none; }
}

/* ── DEBUG: big semi-transparent step number (centre-right) ──────────── */
.platform-stepno {
  position: absolute;
  right: 8%;
  top: 50%;
  transform: translateY(-50%);
  z-index: 50;
  font-size: 22rem;
  font-weight: 800;
  line-height: 1;
  color: rgba(255, 255, 255, 0.12);
  pointer-events: none;
  user-select: none;
}
.platform-stepno.is-static {
  position: absolute;
}

/* ── CSS-3D slab floors (opt-in: [data-platform-slabs="css"] on the wrap) ─────
   Replaces the baked small-card arts (foundation-base / workflows-base /
   operations-base) with a real 3D solid, so the block can be TURNED towards the
   camera instead of cross-faded to another picture. Built and measured in
   css3d/slab.html; the numbers below are that sandbox's, re-expressed in % of
   the tower box (which is square and IS the 1600 art canvas, so one % works on
   both axes). Geometry fitted to the arts it replaces — plate centre, side,
   radius and thickness all come from a silhouette fit, not from taste.

   --slab-px is 1% of the tower box in pixels, set by JS. It is not only for the
   thickness (translateZ has no percentage form): .platform-slab is a ZERO-SIZE
   anchor, so a percentage width or radius on its children would resolve against
   0 and collapse. Everything inside is therefore sized in --slab-px units; only
   the anchor's own left/top stay in %, since those resolve against the layer.

   ART POSE vs VIEW POSE: the wall labels and the logo are flat vectors drawn at
   the art's angles, so anything lying on a face carries the INVERSE of that pose
   (--slab-inv) and re-projects correctly at any view. See css3d/slab.html. */
/* The scrub fades and blurs the art. Doing that ON the element that carries
   transform-style: preserve-3d FLATTENS it — opacity < 1 and filter != none are
   grouping properties, so every rim slice loses its z and collapses onto the
   face plane, and the solid falls apart halfway through its own fade. A plain
   ancestor is fine: it composites the already-rendered 3D result. So the scrub
   targets THIS wrapper and never the slab itself. */
.platform-slab-wrap {
  position: absolute;
  inset: 0;
}
.platform-slab {
  position: absolute;
  left: var(--slab-cx);
  top: var(--slab-cy);
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  transform: rotateX(var(--slab-rx)) rotateZ(var(--slab-rz));
}
.slab-plate {
  /* the plates are only ever seen from the front, so let the compositor skip the
     back faces entirely */
  backface-visibility: hidden;
  position: absolute;
  left: calc(var(--slab-side) * -0.5);
  top: calc(var(--slab-side) * -0.5);
  width: var(--slab-side);
  height: var(--slab-side);
  border-radius: var(--slab-radius);
}
/* the fills are Figma's 50% stops over an opaque ground — a solid is not
   see-through, and translucent slices would compound through the rim stack */
.slab-face {
  /* No halo. The old tower art had one (its alpha runs to 957 canvas px against
     the block's own 823) and it was briefly reproduced here as a box-shadow —
     dropped on request. The block itself already matches the art to within a
     pixel, so nothing else depends on it. */
  /* Figma's own fill for the top face is
       linear-gradient(180deg, rgba(222,203,255,.5), rgba(197,164,253,.5) 40.14%,
                       rgba(182,143,252,.5) 97.41%)
     and these are those three stops PRE-COMPOSITED at their own 50 % over the
     ground the tower sits on, rgb(73, 28, 143) — the page background sampled
     behind the plates. Two reasons not to paste the rgba() straight in:
     1. The plates OVERLAP. At 50 % the plate below shows through the one above,
        which is the washed-out look the client already rejected once.
     2. The page ground is itself a gradient, so a translucent face would drift
        in colour as the section scrolls, and re-blend every frame.
     The ANGLE stays var(--slab-grad), not Figma's 180deg: the face is a tilted
     plane, and --slab-grad is that screen-vertical angle mapped into face space. */
  background: linear-gradient(
    var(--slab-grad),
    rgb(148, 116, 199) 0%,
    rgb(135, 96, 198) 40.14%,
    rgb(128, 86, 198) 97.41%
  );
}
/* --slab-thick is a unitless number of --slab-px, so it can be TWEENED: the same
   block is the tall tower on КРОК 3, a shorter one on КРОК 4 and a flat slab on
   КРОК 5, and 4→5 plays that as a shrink instead of swapping two pictures. */
/* The tall tower is nearly WHITE in Figma (6175:30310: white .31 → white) while
   the flat slab is purple. Rather than swap gradients — CSS cannot interpolate
   between two of them — the light one rides ON TOP of the face as its own plate
   and --slab-light fades it out as the block comes down. The rim changes with
   --slab-rim-color, which IS tweenable because the slices paint Figma's white
   over an opaque ground. */
.slab-face-light {
  /* EXACTLY coplanar with the face — no nudge. A 0.02px translateZ was meant to
     avoid z-fighting but causes it: coplanar elements in a 3D context sort by
     paint order, which is deterministic, whereas a sub-pixel depth difference
     rounds inconsistently once the subtree is scaled (the intro blows it up
     2.08x) and the two planes start swapping — the banding and the ghosted mark
     on КРОК 2. This sits after .slab-face in the DOM, so it paints over it. */
  opacity: var(--slab-light, 0);
  /* Figma: linear-gradient(180.06deg, #836CB5 11.19%, #FFFFFF 99.95%) — the
     КРОК 3 tower's own top. Opaque, so at --slab-light: 1 it simply IS the top
     face and the purple one beneath does not tint it; as --slab-light falls on
     the way down, it cross-fades to that purple. Angle mapped as above. */
  background: linear-gradient(
    var(--slab-grad),
    #836cb5 11.19%,
    #ffffff 99.95%
  );
}
/* THE RIM, without a stack.
   An extruded rounded square is a curved surface, so it cannot be one plane —
   which is why this used to be N copies of the plate at descending translateZ.
   That cost one composited layer per slice (107 across three floors at its worst)
   and was the stutter, worst on КРОК 2→3 where the intro scales the whole subtree
   2.08x and every layer has to re-rasterise.
   But the rim never has to BE 3-D geometry — only its PROJECTION has to be right.
   The plate's outline under the pose is a convex polygon, and sweeping it straight
   down by `thick * sin(rx)` px is simply the convex hull of that polygon and its
   shifted copy. So the rim is ONE flat element with a computed clip-path,
   repainted only when the pose actually changes, and it stays exact through the
   КРОК 7 turn because the polygon is rebuilt from the live angles.
   Draw order matches Figma: sweep (rim colour), the under-plate over its lower
   part, then the real 3-D group on top. */
/* the tall tower is near-white in Figma, and --slab-light only reached the top
   face — this whitens the BODY (rim + under-plate) by the same amount, so the
   whole block goes light together and comes back to purple as it shrinks */
.slab-sweep,
.slab-body-light {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.slab-sweep {
  background: linear-gradient(
    90deg,
    rgb(212, 203, 231) 0%,
    rgb(116, 84, 178) 23.36%,
    rgb(212, 203, 231) 49.11%,
    rgb(151, 128, 197) 85%,
    rgb(255, 255, 255) 100%
  );
}
/* the tall tower is near-white in Figma while the flat slab is purple, and
   --slab-light only reaches the top face — this whitens the BODY by the same
   amount, so the whole block goes light together and returns to purple as it
   shrinks. Shares the sweep's clip-path. */
.slab-body-light {
  opacity: var(--slab-light, 0);
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.82) 0%,
    rgba(255, 255, 255, 0.55) 100%
  );
}
/* the lower-left wall, stood up on the face's +y edge; the label is a child of
   it, so it takes the perspective by itself instead of being skewed by hand */
.slab-wall {
  position: absolute;
  left: calc(var(--slab-side) * -0.5);
  top: calc(var(--slab-side) * 0.5);
  width: var(--slab-side);
  height: calc(var(--slab-px) * var(--slab-thick));
  transform-origin: 50% 0;
  transform: rotateX(-90deg);
}
.slab-label {
  position: absolute;
  /* CENTRED on the wall. A fixed left edge was fitted for "Platform" and left the
     longer labels ("Shared Services", "Applications") starting in the same place
     but running much further along the face, so the three never lined up. */
  left: 50%;
  top: 40.4%;
  /* Un-lean the text. The wall is a real plane, so its local x lands on screen at
     33.54° (measured: 0.7171, 0.4753) while its local y still falls straight down
     (0, 0.7315) — strictly correct perspective, and it makes the labels read as a
     squeezed parallelogram leaning 33.54° off their own baseline (client: "в
     перспективі вони сильно зажаті"). skewX puts the stems back square to the
     baseline: local (x,y) → (x + y·tanθ, y), so the rendered down-vector becomes
     u·tanθ + v, and ⊥ u gives tanθ = −(u·v)/|u|² = −0.46972, i.e. −25.16° would
     put them exactly square to the baseline. That was too much (client: "занадто
     сильно повернув, потрібно було на 10% тільки"), so this takes 10% of it —
     −2.52°: the labels keep the wall's perspective and only lose a tenth of the
     lean. Height is unchanged either way (0.6098 — the skew only shears), so the
     text still sits on the wall. Dial the fraction here: 0 is the strict
     perspective, −25.16deg is fully upright to the baseline. */
  transform: translateX(-50%) skewX(var(--slab-label-skew, -2.52deg));
  color: #fff;
  /* 17px on the 412-wide artboard → x2.0015 onto the 1600 canvas → 2.13 canvas
     % , i.e. 2.13 --slab-px. It was half that and read as fine print. */
  font-size: calc(var(--slab-px) * 2.13);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  opacity: 0.9;
}
/* The mark lies ON the face: --slab-inv undoes the ART pose, so it reproduces
   the drawing at rest and unsquashes by itself as the slab turns.
   NB --slab-inv is NOT just "the tilt undone" — it is anisotropic (a rotation
   AND a 1/cos stretch), so the lying art and the upright logo are different
   SHAPES, not the same one rotated. Removing it and feeding in the frontal
   vector was tried: the logo then lies on the plate visibly turned on its side,
   because it inherits the card's own 44.188° heading. The art has to be the
   logo ALREADY PROJECTED onto the face — which is what img/platform/slab-logo.svg
   now is (see the matrix baked into that file). */
.slab-mark {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  transform:
    scale(var(--slab-mark-scale, 1))
    rotate(var(--slab-mark-spin, 0deg))
    var(--slab-inv)
    translate(calc(var(--slab-px) * -5.730), calc(var(--slab-px) * -4.358));
}
.slab-mark img {
  display: block;
  /* Sized off the КРОК 7 lock-up in 6233:30906, where the mark's ink is 215.38
     wide against a 241.73 wordmark (1.1224). This is what puts the rendered ink
     on that ratio; it also lands the mark at 0.28 of the block's width on
     КРОК 3, against 0.29 in the client's own block art.
     The file's viewBox is TIGHT TO THE INK — the projected bounds of its frontal
     33 x 24 canvas would carry 40% slack, and the shadow's geometry is expressed
     as fractions of THIS box, so that slack put the fall in the wrong band. */
  width: calc(var(--slab-px) * 15.634);
  height: calc(var(--slab-px) * 11.197);
}
/* The mark's shadow (Figma "mindworks 3d", 6055:26480). The layer is the logo's
   3-D EXTRUSION — its white side walls — and Figma blurs it (stdDeviation 26) to
   make the shadow. Two copies of that one export, with the mark on top:
   .slab-glow is the file as exported (the wide, gentle bloom that lifts the face),
   .slab-haze / .slab-smoke / .slab-shade the same paths re-blurred at their own
   radii. Why not Figma's one blur: its 26 is measured in the layer's own 232-unit
   space, so drawn at ~218 px it spreads the white so thin it lifts the face by
   ~11/255 — on the artboard's near-white plate that reads, on our darker one it
   vanishes.
   ONE blurred copy is still wrong, though: it reads as a flat smudge, while the
   art's shadow is a lit VOLUME with height — the glyph caps a glowing column that
   dissipates downward, and it gets softer the further from the glyph it falls
   (client: "обємне світіння яке падає як дим", "до низу більш сильний блюр").
   A single radius cannot do that, so the crisp export is drawn THREE times, each
   at its own radius and each masked to the band of the fall it owns — and since
   2026-09-20 those three blurs and masks are BAKED INTO THE SVGs (slab-haze /
   slab-smoke / slab-shade.svg, same numbers as the CSS filter + mask-image that
   used to sit here), because a CSS blur on a live element re-runs the gaussian on
   every frame the slab moves — three passes, all through the assembly, the
   collapse and the turn. A pre-blurred image costs one texture upload. Radii in
   the files are the slab-px values below x (232.031 / 30.535) canvas units: .slab-shade
   is nearly sharp and lives right under the glyph, .slab-smoke takes over below it,
   .slab-haze is the widest and only exists at the bottom. Where the bands overlap
   they cross-fade, so the blur ramps instead of stepping.
   The bands come from the export's own geometry, not from taste: the crisp
   extrusion occupies y 0.259-0.746 of the layer box and the glyph covers it down
   to ~0.63, so everything visible happens between 0.60 and 0.75 — that is the
   range the three masks divide. (Figma's own gradient on these paths runs
   HORIZONTALLY, 171.03,88.52 -> 52,82.50, i.e. across the glyph's width; there is
   no vertical falloff in the file to copy, which is why the depth has to be built
   from the blur.)
   THE ART IS THE ORIGINAL EXPORT, untouched, even though the logo on the face is
   now a different mark. It was rebuilt from the new glyph once and the client
   rejected it on sight ("відрізняється поворот і форма") — heavily blurred and
   masked to the lower band, this wall reads as the mark's own and any
   reconstruction reads as someone else's. What HAD been wrong was the scale: it
   was first put back at x1.76, the factor the mark's BOX grew by, and at that
   size it spills off the glyph as a foreign white blob. The mark's INK grew
   x1.045, and that is the only factor that applies here.
   NB this painted stack, not a drop-shadow() stack on .slab-mark: six
   drop-shadows on an element that is transformed every frame is six blur passes
   per frame, and the client measured the FPS drop.
   They LIE ON THE FACE like the mark, so they carry the same --slab-inv, and they
   track --slab-mark-scale so the three can never drift apart. Sizes come from the
   art: the layer box is 1.952x the logo box wide and 2.431x tall, and its centre
   sits (+0.575, +1.939) slab-px off the mark's — the extrusion falls down-right.
   Every number here is that original set x1.045.
   PLACEMENT IS THE BUILD'S, NOT THE DESIGN'S, and deliberately so. Figma hangs
   this wall tighter: off 6175:30228 and its sibling `logo` 6175:30220 the canvas
   is 1.9609x the logo's box with the logo at (0.2587, 0.2624) of it, against the
   (0.2242, 0.2184) here, so the design shows about 40% less of the fall. That was
   built and rejected (client: "поверни як було") — the looser placement is what
   the page has always had and what was signed off. */
.slab-glow,
.slab-haze,
.slab-smoke,
.slab-shade {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  transform:
    scale(var(--slab-mark-scale, 1))
    rotate(var(--slab-mark-spin, 0deg))
    var(--slab-inv)
    translate(calc(var(--slab-px) * -12.575), calc(var(--slab-px) * -10.190));
}
/* ── THE MARK'S SHADOW, STRAIGHT FROM FIGMA ────────────────────────────
   One image: node 6175:30228 "mindworks 3d" exported as it is drawn, blur and
   all, instead of the four painted layers that tried to rebuild it. The stack
   was never quite the shape the design has — the client kept seeing it sit
   wrong on the logo — and there is no reason to rebuild what Figma will hand
   over whole.
   EVERY NUMBER BELOW IS THE DESIGN'S. In the frame the logo is 119.742 x 84.074
   at (152.00, 89.02) and the shadow node is 129.074 x 101.206 at (151.695,
   95.167); the export carries the blur's spill on top of that (Figma's own
   inset -40.29% x / -51.38% y, i.e. 52 units each way for a 26 blur), so the
   file is 233.074 x 205.206 starting at (99.691, 43.167). Scaled by the mark's
   own box (15.634 / 119.742 across, 11.197 / 84.074 down — the mark's art is
   ink-tight, exactly like the Figma node) that lands at:
       30.4321 x 27.3293 slab-px, offset (-12.5597, -10.4647)
   The old stack used 30.535 x 26.700 at (-12.575, -10.190): 2.4% too short and
   a quarter of a slab-px too low, which is the misfit that was visible.
   Re-derive these four numbers if either node is ever re-exported. */
.slab-fall {
  /* the box from the design (see above), with --slab-fall-k scaling it about
     its own centre: the PNG export is framed differently from the SVG one, so
     the shape inside it needs a factor to land on the mark. centre is
     (2.6564, 3.2000) slab-px — i.e. -12.5597 + 30.4321/2, -10.4647 + 27.3293/2 */
  /* THREE KNOBS, live-editable in DevTools (they are plain custom properties):
       --slab-fall-k   size, 1 = the design's box
       --slab-fall-dx  move right (+) / left (-), in slab-px
       --slab-fall-dy  move down (+) / up (-), in slab-px
     1 slab-px = 1% of the tower's width, so 0.5 nudges it by half a percent of
     the block. The same three are on the URL: ?fallscale= ?falldx= ?falldy= */
  --slab-fall-k: 1;
  --slab-fall-dx: 0;
  --slab-fall-dy: 0;
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  transform:
    scale(var(--slab-mark-scale, 1))
    rotate(var(--slab-mark-spin, 0deg))
    var(--slab-inv)
    translate(
      calc(var(--slab-px) * (2.6564 - 15.21605 * var(--slab-fall-k) + var(--slab-fall-dx))),
      calc(var(--slab-px) * (3.2000 - 13.66465 * var(--slab-fall-k) + var(--slab-fall-dy))));
  opacity: var(--slab-shadow-alpha, 1);
}
.slab-fall img,
.slab-fall svg {
  display: block;
  width: calc(var(--slab-px) * 30.4321 * var(--slab-fall-k));
  height: calc(var(--slab-px) * 27.3293 * var(--slab-fall-k));
  overflow: visible;
}

/* Opacity is a VARIABLE, never an inline fade: --slab-shadow-alpha scales the
   whole shadow (1 on КРОК 3's tower, 0.35 once it lies down as the base, a
   quarter less again on КРОК 6, 0 as the card turns on КРОК 7) and
   --slab-shadow-own keeps each layer's own weight. */
.slab-glow,
.slab-haze,
.slab-smoke,
.slab-shade {
  opacity: calc(var(--slab-shadow-alpha, 1) * var(--slab-shadow-own, 1));
}
.slab-smoke { --slab-shadow-own: 0.8; }
/* the blur + band of each layer lives in its SVG now (see above):
     haze   blur 3.072 slab-px  mask  transparent 60% -> .55 74% -> #000 86%
     smoke  blur 1.254 slab-px  mask  .35 0-55% -> #000 70-80% -> transparent 94%
     shade  blur 0.136 slab-px  mask  #000 0-60% -> .5 67% -> transparent 75%
   Keep these in sync if a radius or a band is ever retuned. */
.slab-glow img,
.slab-haze img,
.slab-smoke img,
.slab-shade img {
  display: block;
  width: calc(var(--slab-px) * 30.535);
  height: calc(var(--slab-px) * 26.700);
}
/* the floors that get a CSS slab hide the art they replace. wf-big carries the
   has-slab class WITHOUT a slab of its own: with CSS slabs the tall Workflows
   block and the flat one are the SAME slab at two thicknesses, so its separate
   art (and its frost, which would sit on nothing) is retired. */
[data-platform-slabs="css"] .platform-layer.has-slab > img { display: none; }
[data-platform-slabs="css"] .platform-layer.has-slab > .layer-blur { display: none; }
/* КРОК 3's tower is the foundation slab itself, so the cube art is gone for good
   — no picture, nothing to hand over to */
[data-platform-slabs="css"] .solo-cube { display: none; }

/* With a CSS slab the Platform block carries its own mark ON the face, so the
   legacy lock-up's mark would double it: the slab's turns WITH the card, which
   is the point. The logo chip keeps its hit area (the "Explore the Platform"
   card still opens) but stops drawing a second copy, and the lock-up keeps only
   its wordmark and glow. */
[data-platform-slabs="css"] .platform-chip.is-logo > img { opacity: 0; }
/* Hidden again: with the frontal vector on the slab itself there is no hand-over
   to a second image, so this legacy lock-up contributes only its wordmark. The
   glow below therefore still needs its own size — see there. */
[data-platform-slabs="css"] .brand-mark-wrap { display: none; }
/* …and with the mark gone the wordmark and the glow have nothing left in
   here to line up with: the mark they now sit under is the slab's, whose
   anchor is the slab cx (50.0938% of the tower). The legacy 49% was tuned
   to the old art and puts the wordmark 8 px left of it. */
[data-platform-slabs="css"] .platform-brand { left: 50.0938%; }
/* THE GLOW BELONGS BEHIND THE MARK. .platform-brand is a child of the tower at
   z-index 3, while the mark is painted by the slab through layers that are all
   z-index auto — so the ::before bloom, which reaches 240% of the lock-up's
   width and is at full strength exactly where the card lands on КРОК 7, was
   being laid OVER the logo. Measured: the mark's mean saturation on screen was
   0.669 against 0.744 in the file, which reads as a logo that has lost its
   opacity (client). A negative z-index puts the whole lock-up behind the
   layers: the bloom goes where it was meant to go, the mark renders exactly as
   drawn, and the wordmark — the only other thing in here in slab mode — still
   shows, because the layers above it are transparent wherever they have no ink.
   Slab mode only: without the slabs the lock-up IS the mark, and it has to
   stay above. */
[data-platform-slabs="css"] .platform-brand { z-index: -1; }
[data-platform-slabs="css"] .platform-brand::before {
  /* 2.19x the mark's rendered width, against 2.03x in 6233:30906 — this box has
     collapsed to zero height (its only child is the absolutely placed wordmark),
     so a % height or a % `top` resolves against nothing and the glow has to be
     sized and shifted in units of ITSELF. */
  width: 240%;
  height: auto;
  aspect-ratio: 1;
  top: var(--brand-glow-top, 0);
  /* …and with `top` dead, the base translate would centre the glow on the
     wordmark's top edge, 54 px below the lock-up. −10% of its own height puts
     it back on the lock-up's middle, a touch low as the design has it. */
  transform: translate(-50%, -60%);
}
