/* ==========================================================================
   RATTLESNAKE — marketing.css
   Slater › CSS tab. Paste without <style> tags.

   House rules
   - Presentation (colour, type, spacing, borders) lives in Webflow Designer.
   - This file holds only what Designer cannot express: structural state,
     attribute-driven behaviour, and the motion contracts the JS tab reads.
   - New components are namespaced by data-attribute rather than class, so
     renaming a class in Designer can never break a script.
   - Sections 12 and 13 are the documented exception. Their marks are drawn
     in script, so there is no Designer element to hold their colour or
     type; both expose the values as custom properties instead, which is the
     nearest equivalent.
   - Sections 5 and 15 are class-based rather than attribute-based, because
     the elements they style are built in Designer and named there. Both read
     their behaviour off data-attributes, so the classes carry no logic.

   Contents
     1.  Tokens
     2.  Base reset
     3.  Accordion          [data-acc]
     4.  Dot grid           [data-dots]
     5.  Marquee            .showcase_marquee_*
     6.  Odometer           [data-odometer-element]
     7.  Pulse dot          .rsnake-pulse-*
     8.  Rotating text      [data-rotating-title]
     9.  Stacking cards     [data-stacking-card]
     10. Swiper
     11. Journey steps      [data-acc-style="journey"]
     12. Chart, two curves  .rsg
     13. Chart, with and without an AI answer   .rsd
     14. Reduced motion
     15. Reveal             [data-reveal]
     16. CTA button         .div-cta-button
     17. Clamp              [data-clamp]
     18. Underline          [data-underline]
     19. Sticky CTA         [data-sticky]
   ========================================================================== */


/* ==========================================================================
   1. TOKENS
   Shared values only. Component-local variables stay with their component.
   ========================================================================== */
:root {
  --ease-emphasis: cubic-bezier(.625, .05, 0, 1);

  /* easeOutQuint, the curve Webflow calls "Out Quint". Used by both chart
     sections and by the accordion's sibling dim, so it is named once and
     referenced rather than retyped. */
  --ease-out-quint: cubic-bezier(.22, 1, .36, 1);
  --ease-chart:     var(--ease-out-quint);

  /* Lightspark's reveal curve and duration. Shared by section 15 and by the
     sticky CTA in section 19, so the two arrive on the same motion. */
  --ease-reveal:   cubic-bezier(.27, .09, .24, 1);
  --time-reveal:   .7s;

  /* One curve for the interface, three durations for it. The curve decides
     the character, the duration decides how far the thing is travelling, and
     those are two different questions. --ease-emphasis starts very slowly: on
     a 600ms transition it has covered 4.8 per cent of its path at the 100ms
     mark, which is right for a panel opening and wrong for a button answering
     the cursor. On 200ms the same curve is 84 per cent done by then. Hence a
     scale rather than a single number. */
  --time-quick:    200ms;   /* answering the pointer: colour, opacity, a small icon */
  --time-base:     400ms;   /* an underline, a chevron, a short move */
  --time-slow:     600ms;   /* panels, arrivals, anything crossing distance */

  /* Dot grid defaults — overridable per section by [data-dots] modifiers. */
  --dot-gap:   32px;
  --dot-size:  1px;
  --dot-color: rgba(20, 20, 20, .18);
}


/* ==========================================================================
   2. BASE RESET
   ========================================================================== */
html {
  scroll-behavior: initial;
  scrollbar-width: none;              /* Firefox */
}
body {
  -ms-overflow-style: none;           /* legacy Edge / IE */
}
body::-webkit-scrollbar {
  display: none;                      /* Chrome, Safari, Opera */
}
html,
body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overscroll-behavior: none;
}
*,
*::before,
*::after { box-sizing: border-box; }

/* Webflow ships max-width:100% on svg, which crushes inline icons. */
svg {
  max-width: none;
  height: auto;
  box-sizing: border-box;
  vertical-align: middle;
}
a {
  color: inherit;
  text-decoration: none;
}
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}


/* ==========================================================================
   3. ACCORDION  [data-acc]
   --------------------------------------------------------------------------
   Markup contract
     [data-acc]           wrapper. Space-separated flags: multi first merge debug
     [data-acc-item]      one row
     [data-acc-head]      the click target
     [data-acc-body]      the collapsing panel
     [data-acc-chevron]   optional icon wrapper, rotated on open

   Tuning attributes, read once at init by the JS tab
     data-acc-gap  data-acc-radius  data-acc-inner
     data-acc-spring  data-acc-ease  data-acc-dur
     data-acc-dim     hovering one row fades the others. Optional value is the
                      opacity they fade to, 0.4 by default. Desktop only.

   The script owns height, border-radius and item offsets. Card background,
   padding, borders and typography stay in Designer.
   ========================================================================== */

/* Collapsed by default, so there is no flash of open content before init. */
[data-acc-body] {
  overflow: hidden;
  height: 0;
}
[data-acc-head] {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Strip UA styling when the head is a real <button> (preferred for a11y). */
:where(button[data-acc-head]) {
  display: block;
  width: 100%;
  margin: 0;
  border: 0;
  background: none;
  text-align: left;
  color: inherit;
  font: inherit;
}
[data-acc-head]:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -3px;
}

/* Content resolves slightly after the panel height, so the row reads as one
   movement rather than two. Deliberately applied to the children, not the
   panel: a transform on [data-acc-body] is counted into its own scroll
   height and inflates the measurement the script animates to. */
[data-acc-body] > * {
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity   var(--time-base) var(--ease-emphasis),
    transform 420ms var(--acc-spring, ease);
}
[data-acc-item][data-acc-open="true"] > [data-acc-body] > * {
  opacity: 1;
  transform: none;
  transition-delay: 60ms;
}

/* Chevron.
   inline-flex shrinks the box to the icon. With inline-block the box is
   wider than the glyph, so the rotation pivots off-centre and the chevron
   appears to swing sideways. line-height:0 removes the leftover line box. */
[data-acc-chevron] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  transform-origin: 50% 50%;
  transition: transform var(--acc-dur, 520ms) var(--acc-spring, ease);
}
[data-acc-chevron] > svg { display: block; }
[data-acc-item][data-acc-open="true"] [data-acc-chevron] {
  transform: rotate(180deg);
}


/* ── dim the siblings on hover   [data-acc-dim] ──
   Pointing at one row fades the rest of them. The value on the attribute is
   the opacity they fade to; the JS tab reads it and publishes --acc-dim.

   Gated behind a real pointer, and that is not politeness. On a touch screen
   :hover latches after a tap and stays latched, so the row somebody just
   opened would hold every other row at 40 per cent until they tapped
   somewhere else. hover:hover with pointer:fine is the pair that means a
   mouse; the width query keeps it to Webflow's desktop breakpoint, since the
   effect needs a list tall enough to read as a list in the first place.

   The timings are asymmetric, and that asymmetry is the whole character of
   it: the fade settles slowly, the recovery snaps back. Carried on a custom
   property rather than on the transition shorthand, because in merge mode the
   script writes an inline transition for margin and radius, and an inline
   shorthand cannot be overridden by a later rule. A variable inside that
   shorthand can. */
@media (hover: hover) and (pointer: fine) and (min-width: 992px) {
  [data-acc][data-acc-dim] [data-acc-item] {
    --acc-dim-t: 300ms;
    transition: opacity var(--acc-dim-t) var(--ease-out-quint, cubic-bezier(.22, 1, .36, 1));
  }
  [data-acc][data-acc-dim]:hover [data-acc-item]:not(:hover) {
    --acc-dim-t: 600ms;
    opacity: var(--acc-dim, .4);
  }
}


/* ==========================================================================
   4. DOT GRID  [data-dots]
   --------------------------------------------------------------------------
   Background dot field for any section. Painted on ::before at z-index:-1
   inside an isolation context, so it sits behind the section's own content
   without escaping into sibling sections.

   Values (space-separated, combinable)
     Density   dense | sparse
     Contrast  soft | strong | dark
     Shape     fade | edges | halo | sides
     Utility   debug
   ========================================================================== */
[data-dots] {
  position: relative;
  isolation: isolate;
}
[data-dots]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: radial-gradient(circle,
    var(--dot-color) var(--dot-size), transparent var(--dot-size));
  background-size: var(--dot-gap) var(--dot-gap);
  background-position: center;
}

/* Density */
[data-dots~="dense"]  { --dot-gap: 18px; }
[data-dots~="sparse"] { --dot-gap: 56px; }

/* Contrast — "dark" is for sections sitting on a dark background. */
[data-dots~="soft"]   { --dot-color: rgba(20, 20, 20, .10); }
[data-dots~="strong"] { --dot-color: rgba(20, 20, 20, .30); }
[data-dots~="dark"]   { --dot-color: rgba(255, 255, 255, .18); }

/* Shape — masks deciding where the field is allowed to show. */
[data-dots~="fade"]::before {
  -webkit-mask-image: radial-gradient(115% 90% at 50% 50%, #000 35%, transparent 100%);
          mask-image: radial-gradient(115% 90% at 50% 50%, #000 35%, transparent 100%);
}
[data-dots~="edges"]::before {
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 18%, #000 82%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0, #000 18%, #000 82%, transparent 100%);
}

/* halo — clears the centre so dots never sit under body copy. */
[data-dots~="halo"]::before {
  --dot-clear-x: 64%;
  --dot-clear-y: 70%;
  --dot-core: 38%;
  -webkit-mask-image: radial-gradient(ellipse var(--dot-clear-x) var(--dot-clear-y) at 50% 50%,
    transparent var(--dot-core), #000 100%);
          mask-image: radial-gradient(ellipse var(--dot-clear-x) var(--dot-clear-y) at 50% 50%,
    transparent var(--dot-core), #000 100%);
}

/* sides — keeps two vertical bands at the outer edges only. */
[data-dots~="sides"]::before {
  --dot-band: 24%;
  -webkit-mask-image: linear-gradient(90deg,
    #000 0, transparent var(--dot-band),
    transparent calc(100% - var(--dot-band)), #000 100%);
          mask-image: linear-gradient(90deg,
    #000 0, transparent var(--dot-band),
    transparent calc(100% - var(--dot-band)), #000 100%);
}

@media (max-width: 900px) {
  [data-dots~="halo"]::before  { --dot-clear-x: 96%; --dot-core: 30%; }
  [data-dots~="sides"]::before { --dot-band: 12%; }
}

/* debug — high-contrast dots with every mask removed, to confirm the
   attribute is reaching the element. Must stay last in this section so it
   wins over the shape rules above. */
[data-dots~="debug"] {
  --dot-color: #ff0055;
  --dot-size: 2.5px;
  --dot-gap: 24px;
}
[data-dots~="debug"]::before {
  -webkit-mask-image: none;
          mask-image: none;
}


/* ==========================================================================
   5. MARQUEE   .showcase_marquee_*
   --------------------------------------------------------------------------
   Rebuilt on the Rejouice component, read from their bundle rather than
   guessed: /_nuxt/R078RQd0.js is the Marquee, /_nuxt/BdSAQIjB.js holds the
   entrance, /_nuxt/BayDiI7K.js the maths helpers.

   The one idea worth stealing is how they loop. Two copies of the sequence,
   both given the SAME percentage transform, sweeping -100% to 0%. At -100%
   copy two stands exactly where copy one started, so the loop is seamless with
   no pixel measuring at all, and it survives a resize without recomputing
   anything. The trick that makes it work is that the gap between sequences
   lives INSIDE the sequence as padding-right, so the width of one sequence is
   the length of the loop. Get that wrong and the strip jumps by one gap per
   cycle.

   Their component in full is four declarations,
     .marquee__container { display:flex; flex-wrap:nowrap; overflow:hidden;
                           position:relative; touch-action:pan-y }
     .marquee__container.drag { cursor:grab }
     .marquee__container .marquee-item { display:flex; user-select:none }
   and the rest is script. touch-action:pan-y is load-bearing: horizontal
   gestures go to the script, vertical scrolling stays with the browser.

   Markup contract, exactly as Designer builds it
     .showcase_marquee_wrapper   the window. Clips, and owns the edge fade
     .showcase_marquee_track     the flex row holding the sequences
     .showcase_marquee_list      one sequence. Keep ONE in Designer; the script
                                 clones it as many times as the width needs
     .showcase_marquee_flex      the row inside a sequence
     .div-marquee-item-v2        one tile
     .marquee-img-v2             the image

   Knobs, set on .showcase_marquee_wrapper or a parent
     --marquee-gap          space between tiles, and between sequences
     --marquee-fade         fade at each edge. 0 turns it off
     --marquee-runway       how far below the row the tiles may start without
                            being clipped. Their trick, their numbers
     --marquee-perspective  only read with the class has-depth on the wrapper,
                            which turns the entrance from Rejouice's skew into
                            a true 3D tilt
     --marquee-h            tile height, only with the class is-sized
   ========================================================================== */

.showcase_marquee_wrapper {
  --marquee-gap:      1rem;
  --marquee-fade:     0rem;
  --marquee-runway:   8rem;

  position: relative;
  width: 100%;
  overflow: hidden;          /* the crop belongs here, not to an ancestor */
  touch-action: pan-y;       /* horizontal to the script, vertical to the page */

  /* The runway. overflow:hidden clips at the padding edge, so a tall bottom
     padding lets the tiles arrive from below the row without being sliced off,
     and the matching negative margin cancels the space so nothing underneath
     moves. This is Rejouice's own fix, verbatim from their stylesheet:
       .marquee__container { margin-bottom:-25rem; padding-bottom:25rem }
     with 50rem above 1024px. Theirs is oversized for a wall of widgets. The
     number you need is 40% of the tile height, because that is the translateY
     and the rotation pivots on the bottom edge; 8rem covers a tile up to 20rem
     tall. Keep it no larger than it has to be: the wrapper is positioned, so
     the runway sits over whatever follows. */
  padding-bottom: var(--marquee-runway);
  margin-bottom: calc(var(--marquee-runway) * -1);

  /* The runway overlaps whatever follows, and the wrapper is positioned, so
     without this it would swallow clicks in that band. The track takes the
     pointer back; enter, leave and the drag all still reach the wrapper,
     because they travel the path through it. */
  pointer-events: none;
}
.showcase_marquee_track { pointer-events: auto; }

/* The fade is painted only when asked for. A mask with two fully opaque stops
   is a no-op, so the default costs nothing and cannot dim the row. */
.showcase_marquee_wrapper {
  -webkit-mask-image: linear-gradient(to right,
    transparent 0, #000 var(--marquee-fade),
    #000 calc(100% - var(--marquee-fade)), transparent 100%);
  mask-image: linear-gradient(to right,
    transparent 0, #000 var(--marquee-fade),
    #000 calc(100% - var(--marquee-fade)), transparent 100%);
}

/* Only once the script has taken over, so a no-JS visitor is not offered a
   grab cursor on something that cannot be grabbed. */
.showcase_marquee_wrapper.is-draggable { cursor: grab; }
.showcase_marquee_wrapper.is-dragging  { cursor: grabbing; }

.showcase_marquee_track {
  display: flex;
  flex-wrap: nowrap;
  width: max-content;
}

.showcase_marquee_list {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  -webkit-user-select: none;
  user-select: none;

  /* The gap between sequences, carried inside the sequence. This is what makes
     one sequence width equal one loop length. */
  padding-right: var(--marquee-gap);
  will-change: transform;
}

.showcase_marquee_flex {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: var(--marquee-gap);
}

.div-marquee-item-v2 {
  flex: 0 0 auto;            /* without this the tiles compress and the images crop */
  display: flex;
}

/* Webflow ships a global img{max-width:100%}. Inside a flex row that is what
   squashes a tile down to a sliver, which reads as cropping. Rejouice hit the
   same wall and answer it the same way, with max-width:unset on the image.

   Nothing here sets width or height: whatever you gave .marquee-img-v2 in
   Designer stays in charge. To drive the height from CSS instead, put the
   class is-sized on the wrapper and set --marquee-h. */
.marquee-img-v2 {
  display: block;
  max-width: none;
  /* Theirs. Their whole stylesheet uses three radii and nothing else: 0,
     0.3rem on mobile, 0.6rem from 1024px. On a card 44rem wide that reads as a
     screenshot rather than an app tile, and that restraint is a real part of
     why the row looks expensive. Raise it if the block has to speak the same
     language as the rest of your site. */
  border-radius: .6rem;
  -webkit-user-drag: none;
  pointer-events: none;      /* or the browser starts its own image drag */
}
.showcase_marquee_wrapper.is-sized .marquee-img-v2 {
  height: var(--marquee-h, 13rem);
  width: auto;
}

/* --------------------------------------------------------------------------
   ENTRANCE
   Rejouice fly the tiles up from below with a tilt, staggered, while the strip
   itself spins up from a standstill. Their numbers, taken from the bundle:
   rotateX 45deg, rotateY 12deg, yPercent 40, opacity 0, one second each,
   0.1s apart, fired once when the block reaches 90% down the viewport.

   Their tween carries ease:"expo.inOut" inside the FROM object, where GSAP
   ignores it, so what actually runs is the default power1.inOut. This uses an
   out curve instead, which is what a fly-in wants, and it is a curve already
   on their site.
   -------------------------------------------------------------------------- */

/* GSAP's default curve, power1.inOut, because that is what actually runs on
   their site: their tween puts ease:"expo.inOut" inside the FROM object, where
   GSAP ignores it. It matters more here than it looks. A hard out-curve is 15%
   through the move in the first 20ms, so the tilt is gone before the eye finds
   it; a symmetric in-out keeps the tile leaning long enough to be seen, and
   that lean is the effect. */
.showcase_marquee_wrapper .div-marquee-item-v2 {
  --marquee-enter-ease: cubic-bezier(.45, 0, .55, 1);
  transition:
    opacity   1s var(--marquee-enter-ease) var(--enter-delay, 0s),
    transform 1s var(--marquee-enter-ease) var(--enter-delay, 0s);
}

/* No perspective, and that is the point rather than an omission. Rejouice set
   none anywhere, and GSAP only applies one when you pass transformPerspective,
   which they do not. rotateX(45) rotateY(12) with no perspective is not a tilt
   at all: it flattens to the 2D matrix

     a 0.9781   b 0.1470   c 0   d 0.7071

   A non-zero shear term is what makes it read as a lean rather than a fade,
   and that lean is the thing that looks like theirs. Adding perspective turns
   it into an honest 3D card flip, which looks more correct and less like the
   original. Opt into that with the class has-depth if you want it.

   THE ORDER IS NOT ARBITRARY. GSAP renders its transform string as
     translate3d() rotate() rotateY() rotateX() skew() scale()
   so rotateY comes before rotateX, and matrix multiplication does not commute.
   Written the other way round the shear lands on the other axis:

     rotateX(45) rotateY(12)   a .9781  b .1470  c 0      d .7071
     rotateY(12) rotateX(45)   a .9781  b 0      c .1470  d .7071

   b shears vertically, c shears horizontally. Both are 45-and-12 degrees, and
   they lean in visibly different directions. Theirs is the second. */
.showcase_marquee_wrapper.is-entering .div-marquee-item-v2 {
  opacity: 0;
  transform: translateY(40%) rotateY(12deg) rotateX(45deg);
  transform-origin: bottom;

  /* Arming must be instant. With the transition above still in force, adding
     is-entering animates the tiles OUT over a second first, and the entrance
     then starts from wherever that got to. */
  transition: none;
}
.showcase_marquee_wrapper.has-depth.is-entering .div-marquee-item-v2 {
  transform: perspective(var(--marquee-perspective, 60rem))
             translateY(40%) rotateY(12deg) rotateX(45deg);
}

/* Set by the script once it has measured and cloned. Before that the track is
   invisible, so the reader never sees the un-cloned sequence jump. */
.showcase_marquee_track { opacity: 0; }
.showcase_marquee_wrapper.is-ready .showcase_marquee_track { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .showcase_marquee_list { transform: none !important; }
  .showcase_marquee_wrapper .div-marquee-item-v2 { transition: none; }
  .showcase_marquee_wrapper.is-entering .div-marquee-item-v2 {
    opacity: 1; transform: none;
  }
  .showcase_marquee_wrapper.is-draggable { cursor: auto; }
}


/* ==========================================================================
   6. ODOMETER  [data-odometer-element]
   --------------------------------------------------------------------------
   The roller is translated vertically inside a clipped mask. The padding /
   negative-margin pair gives glyph ascenders and descenders room to render
   without widening the clip box.
   ========================================================================== */
[data-odometer-element] {
  display: inline-flex;
  align-items: center;
  font-variant-numeric: tabular-nums;
}
[data-odometer-part="mask"],
[data-odometer-part="static"] {
  display: inline-block;
  overflow: clip;
  padding: 0.05em;
  margin: -0.05em;
}
[data-odometer-part="roller"] {
  display: block;
  white-space: pre;
  text-align: center;
  will-change: transform;
}


/* ==========================================================================
   7. PULSE DOT  (live indicator)
   ========================================================================== */
.rsnake-pulse-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.rsnake-pulse-ring {
  position: absolute;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: #b3ddff;
  opacity: 0.5;
  animation: rsnake-pulse 1.6s ease-out infinite;
  will-change: transform, opacity;
}
.rsnake-pulse-dot {
  position: relative;
  z-index: 2;
  width: 7.5px;
  height: 7.5px;
  border-radius: 50%;
  background: #b3ddff;
}
@keyframes rsnake-pulse {
  0%   { transform: scale(1);    opacity: 0.5; }
  100% { transform: scale(1.75); opacity: 0;   }
}


/* ==========================================================================
   8. ROTATING TEXT  [data-rotating-title]
   --------------------------------------------------------------------------
   Words are absolutely stacked so the line does not reflow between swaps.
   The mask clips vertically only — horizontal overflow must stay visible or
   wide words are cut off mid-transition.
   ========================================================================== */
[data-rotating-title] { width: 100%; }
[data-rotating-words] {
  display: inline-block;
  position: relative;
}
.rotating-text__inner { display: inline-block; }
.rotating-text__word {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  white-space: nowrap;
}
.rotating-line {
  padding-bottom: 0.1em;
  margin-bottom: -0.1em;
  white-space: nowrap;
}
.rotating-line-mask {
  overflow-x: visible !important;
  overflow-y: clip !important;
}


/* ==========================================================================
   9. STACKING CARDS  [data-stacking-card]
   Set `top` in Designer to match the header height.
   ========================================================================== */
[data-stacking-card] {
  position: sticky;
  top: 0;
}
[data-stacking-card-target] {
  transform-origin: center top;
  will-change: transform;
}


/* ==========================================================================
   10. SWIPER
   --------------------------------------------------------------------------
   Swiper reads its own custom properties off the element, so these stay
   scoped to body rather than :root — that is where Swiper's own stylesheet
   expects to inherit them from.
   ========================================================================== */
body {
  --swiper-pagination-color: currentColor;
  --swiper-pagination-bottom: auto;
  --swiper-pagination-bullet-size: 0.5em;
  --swiper-pagination-bullet-inactive-color: currentColor;
  --swiper-pagination-bullet-inactive-opacity: 0.15;
  --swiper-pagination-bullet-horizontal-gap: 0.25em;
  --swiper-wrapper-transition-timing-function: var(--ease-emphasis);
}

/* Gutter lives on the slide; the last slide reclaims it so the track ends
   flush with the container. */
.swiper-slide {
  --gap: 1.25em;
  padding-right: var(--gap);
}
.swiper-slide:last-of-type {
  margin-right: calc(-1 * var(--gap));
}
.swiper-navigation__button { transition: opacity var(--time-quick) var(--ease-emphasis); }
.swiper-button-disabled {
  opacity: 0;
  pointer-events: none;
}


/* ==========================================================================
   11. JOURNEY STEPS   [data-acc-style="journey"]
   --------------------------------------------------------------------------
   A marker at the left of every row: either a numbered node or a coloured
   tile. BOTH ARE OFF BY DEFAULT. Plain rows, flush left, are what you get from
   data-acc-style="journey" on its own, and that is the right default: a marker
   nobody asked for is a marker somebody has to remove.

   Turn one on with a SECOND attribute on the same wrapper:

       data-acc-numbers        01, 02, 03 in a hairline ring
       data-acc-icons = seo    stone tiles, the Traditional SEO column
       data-acc-icons = ai     coloured tiles, the AI search column

   THE MARKER HANGS OFF THE HEADER, NOT THE ROW, and that is the whole reason
   this section was rewritten. Pinned to the row it needed a hand-set distance
   from the top, and a row is only as tall as Designer's padding makes it: at
   46px a marker centred on 30px sits 7px low and pushes its own bottom edge
   past the row, where overflow:hidden slices it. The header is exactly the
   closed-row box and never grows when the panel opens, so centring on it is
   both correct at any row height and stable while opening. Nothing here needs
   a magic number any more.
   ========================================================================== */
[data-acc-style="journey"] {
  --node:   30px;      /* marker diameter                                   */
  --inset:  12px;      /* SAME gap above the marker and to its left          */
  --node-gap: 14px;    /* marker to title                                   */
  --glyph:  15px;      /* icon inside the tile                              */
  --ring:   #e4e1dc;   /* hairline around a resting node                    */
  --ring-2: #cfccc7;   /* dashed ring on a skipped node                     */

  counter-reset: rsj-step;
}
[data-acc-style="journey"] [data-acc-item] {
  position: relative;
  overflow: hidden;
  counter-increment: rsj-step;
}

/* ── the marker box ──
   Geometry only, and shared, so the numbered node and the icon tile occupy
   exactly the same square. Switching between them can then never move the
   title by a pixel. Declared for whichever opt-in is present; with neither,
   ::before is never generated and the row has no marker at all. */
[data-acc-style="journey"]:is([data-acc-numbers], [data-acc-icons]) [data-acc-head]::before {
  content: "";
  position: absolute;
  z-index: 2;
  left: var(--inset);
  top: 50%;
  transform: translateY(-50%);
  width: var(--node);
  height: var(--node);
  pointer-events: none;
}

/* ── room for it ──
   Also opt-in. A 60px indent reserved for a marker that is not there is the
   most visible way to get this wrong.

   min-height is what makes the marker sit EVENLY. The gap above it is half of
   whatever is left over in the row, so with Designer's own padding the row came
   to 46px, the marker to 30px, and the gap above it to 8px against 20px on the
   left. Two different numbers on two edges of the same object, which is exactly
   the sort of thing that reads as wrong before anyone can say why.

   Giving the row a floor of marker plus twice the inset makes the leftover
   exactly two insets, so the gap above equals the gap to the left by
   construction rather than by a value picked to match. A row whose title wraps
   grows past the floor and the marker centres in it, which is what the eye
   expects once there is more than one line to centre against. */
[data-acc-style="journey"]:is([data-acc-numbers], [data-acc-icons]) [data-acc-head] {
  min-height: calc(var(--node) + var(--inset) * 2);
}
[data-acc-style="journey"]:is([data-acc-numbers], [data-acc-icons]) [data-acc-head],
[data-acc-style="journey"]:is([data-acc-numbers], [data-acc-icons]) [data-acc-body] {
  position: relative;
  z-index: 1;
  padding-left: calc(var(--inset) + var(--node) + var(--node-gap));
}

/* ── numbers   data-acc-numbers ──
   A hairline ring, not a filled disc. With the digit at 10px it reads as a
   quiet marker rather than a badge, which is the whole difference between
   elegant and busy here. No background, so it inherits whatever surface
   Designer gives the card. */
[data-acc-style="journey"][data-acc-numbers] [data-acc-head]::before {
  content: counter(rsj-step, decimal-leading-zero);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px var(--ring);
  font-size: 10px;
  line-height: 1;
  letter-spacing: .06em;
  color: #a8a5a0;
  font-variant-numeric: tabular-nums;
  transition:
    box-shadow var(--time-base) var(--ease-emphasis),
    color      var(--time-base) var(--ease-emphasis),
    background var(--time-base) var(--ease-emphasis);
}
[data-acc-style="journey"][data-acc-numbers] [data-acc-item][data-acc-open="true"] [data-acc-head]::before {
  background: #141414;
  box-shadow: inset 0 0 0 1px #141414;
  color: #fff;
}
[data-acc-style="journey"][data-acc-numbers] [data-acc-item][data-acc-skip] [data-acc-head]::before {
  background: transparent;
  box-shadow: none;
  border: 1px dashed var(--ring-2);
  color: #c4c1bc;
}

/* ── icons   data-acc-icons ──
   Read from OpenAI rather than Google: not a coloured line on a pale chip, but
   a saturated iridescent wash with the glyph knocked out white. Three radial
   blobs over a base gradient make a mesh cheaply; a real mesh gradient needs
   SVG filters and buys nothing at 30 pixels.

   The glyph is the TOP BACKGROUND LAYER of the same box, not a second
   pseudo-element. One box means one thing to position and one thing to centre,
   and it leaves the header's ::after free for the "skipped" pill.

   Colour is the argument this block exists to make, so the two columns are the
   same object in two states: the AI column is lit, the SEO column is the
   identical tile drained to stone. Do not give the left column its own shape
   or its own size. Same tile, life removed.

   Geometry is Lucide, ISC licence, free commercially. Nothing here uses an
   OpenAI mark: their logo is a trademark, a soft iridescent wash is not. */
[data-acc-style="journey"][data-acc-icons] [data-acc-head]::before {
  border-radius: .625rem;
  background:
    var(--ico, none) no-repeat center / var(--glyph) var(--glyph),
    radial-gradient(115% 115% at 14% 6%,   var(--m1) 0%, transparent 60%),
    radial-gradient(115% 115% at 88% 20%,  var(--m2) 0%, transparent 58%),
    radial-gradient(140% 140% at 46% 112%, var(--m3) 0%, transparent 64%),
    linear-gradient(145deg, var(--m1), var(--m3));

  /* Inset only. A drop shadow says "this object floats above the row", and at
     30px inside a flat card that reads as a sticker rather than as depth. The
     lit top edge and the hairline of light are what give the tile its form,
     and they cost nothing in the row's own rendering. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .5),
    inset 0 0 0 1px rgba(255, 255, 255, .18);

  transition: transform var(--time-slow) var(--ease-emphasis, cubic-bezier(.625,.05,0,1));
}
[data-acc-style="journey"][data-acc-icons] [data-acc-item][data-acc-open="true"] [data-acc-head]::before {
  transform: translateY(-50%) scale(1.06);
}

/* The Traditional SEO column. The glyph is grey because white on stone is a
   smudge, and the hairline is dark rather than light because a pale tile on a
   pale row has nothing else to define its edge. */
[data-acc-icons="seo"] [data-acc-item] { --m1: #EDEAE5; --m2: #E0DCD6; --m3: #D3CEC7; }
[data-acc-icons="seo"] [data-acc-head]::before {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .7),
    inset 0 0 0 1px rgba(17, 17, 16, .06);
}
[data-acc-icons="seo"] [data-acc-item]:nth-child(1) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238b857c'%3E%3Cpath fill-rule='evenodd' d='M10.5 3.75a6.75 6.75 0 1 0 0 13.5 6.75 6.75 0 0 0 0-13.5ZM2.25 10.5a8.25 8.25 0 1 1 14.59 5.28l4.69 4.69a.75.75 0 1 1-1.06 1.06l-4.69-4.69A8.25 8.25 0 0 1 2.25 10.5Z' clip-rule='evenodd'/%3E%3C/svg%3E"); }
[data-acc-icons="seo"] [data-acc-item]:nth-child(2) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238b857c'%3E%3Cpath d='M6 3a3 3 0 0 0-3 3v1.5a.75.75 0 0 0 1.5 0V6A1.5 1.5 0 0 1 6 4.5h1.5a.75.75 0 0 0 0-1.5H6ZM16.5 3a.75.75 0 0 0 0 1.5H18A1.5 1.5 0 0 1 19.5 6v1.5a.75.75 0 0 0 1.5 0V6a3 3 0 0 0-3-3h-1.5ZM12 8.25a3.75 3.75 0 1 0 0 7.5 3.75 3.75 0 0 0 0-7.5ZM4.5 16.5a.75.75 0 0 0-1.5 0V18a3 3 0 0 0 3 3h1.5a.75.75 0 0 0 0-1.5H6A1.5 1.5 0 0 1 4.5 18v-1.5ZM21 16.5a.75.75 0 0 0-1.5 0V18a1.5 1.5 0 0 1-1.5 1.5h-1.5a.75.75 0 0 0 0 1.5H18a3 3 0 0 0 3-3v-1.5Z'/%3E%3C/svg%3E"); }
[data-acc-icons="seo"] [data-acc-item]:nth-child(3) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238b857c'%3E%3Cpath fill-rule='evenodd' d='M2.25 6a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3V6Zm18 3H3.75v9a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5V9Zm-15-3.75A.75.75 0 0 0 4.5 6v.008c0 .414.336.75.75.75h.008a.75.75 0 0 0 .75-.75V6a.75.75 0 0 0-.75-.75H5.25Zm1.5.75a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75H7.5a.75.75 0 0 1-.75-.75V6Zm3-.75A.75.75 0 0 0 9 6v.008c0 .414.336.75.75.75h.008a.75.75 0 0 0 .75-.75V6a.75.75 0 0 0-.75-.75H9.75Z' clip-rule='evenodd'/%3E%3C/svg%3E"); }
[data-acc-icons="seo"] [data-acc-item]:nth-child(4) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238b857c'%3E%3Cpath fill-rule='evenodd' d='M7.502 6h7.128A3.375 3.375 0 0 1 18 9.375v9.375a3 3 0 0 0 3-3V6.108c0-1.505-1.125-2.811-2.664-2.94a48.972 48.972 0 0 0-.673-.05A3 3 0 0 0 15 1.5h-1.5a3 3 0 0 0-2.663 1.618c-.225.015-.45.032-.673.05C8.662 3.295 7.554 4.542 7.502 6ZM13.5 3A1.5 1.5 0 0 0 12 4.5h4.5A1.5 1.5 0 0 0 15 3h-1.5Z' clip-rule='evenodd'/%3E%3Cpath fill-rule='evenodd' d='M3 9.375C3 8.339 3.84 7.5 4.875 7.5h9.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V9.375ZM6 12a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75H6.75a.75.75 0 0 1-.75-.75V12Zm2.25 0a.75.75 0 0 1 .75-.75h3.75a.75.75 0 0 1 0 1.5H9a.75.75 0 0 1-.75-.75ZM6 15a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75H6.75a.75.75 0 0 1-.75-.75V15Zm2.25 0a.75.75 0 0 1 .75-.75h3.75a.75.75 0 0 1 0 1.5H9a.75.75 0 0 1-.75-.75ZM6 18a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75H6.75a.75.75 0 0 1-.75-.75V18Zm2.25 0a.75.75 0 0 1 .75-.75h3.75a.75.75 0 0 1 0 1.5H9a.75.75 0 0 1-.75-.75Z' clip-rule='evenodd'/%3E%3C/svg%3E"); }
[data-acc-icons="seo"] [data-acc-item]:nth-child(5) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238b857c'%3E%3Cpath d='M12.75 12.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM7.5 15.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM8.25 17.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM9.75 15.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM10.5 17.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12 15.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM12.75 17.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM14.25 15.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM15 17.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM16.5 15.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM15 12.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM16.5 13.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z'/%3E%3Cpath fill-rule='evenodd' d='M6.75 2.25A.75.75 0 0 1 7.5 3v1.5h9V3A.75.75 0 0 1 18 3v1.5h.75a3 3 0 0 1 3 3v11.25a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3V7.5a3 3 0 0 1 3-3H6V3a.75.75 0 0 1 .75-.75Zm13.5 9a1.5 1.5 0 0 0-1.5-1.5H5.25a1.5 1.5 0 0 0-1.5 1.5v7.5a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5v-7.5Z' clip-rule='evenodd'/%3E%3C/svg%3E"); }

/* The AI column. One colour for all five tiles, taken from Designer rather
   than written here: --rsnake-blue-light is your own variable, so retuning the
   brand blue in Webflow retunes these with it and this file never disagrees
   with the palette. The hex after the comma is only a fallback for the case
   where the variable is missing.

   Three values are derived from that one so the tile keeps some depth. A flat
   swatch at 30px reads as a placeholder; a wash with a lit corner reads as an
   object. color-mix does the deriving, which means there is exactly one colour
   to change and no set of hand-matched hexes to keep in step.

   The glyphs are Heroicons 24 Solid, MIT licence, filled rather than stroked.
   That is not only a style choice: a filled shape is mass and a 1.6px outline
   is a line, and mass survives on a pale ground where a line disappears. Which
   matters here, because white on this blue is a low ratio, and the fill is
   what makes it hold.
   ========================================================================== */
[data-acc-icons="ai"] [data-acc-item] {
  --tile: var(--rsnake-blue-light, #8DC5F6);

  /* A mesh, not a wash. Three stops that drift ACROSS the blues rather than up
     and down one of them: cyan in the lit corner, the brand blue through the
     middle, indigo settling at the foot. That drift is what iridescence is, and
     it is the whole reason the tile reads as a small object catching light
     instead of a coloured square.

     Blue the whole way, though, and that is the constraint the earlier set
     broke. Teal to amber was not a mesh, it was five colours, and five colours
     turn one argument into decoration.

     Every stop is mixed FROM the token, so Designer still owns the colour: move
     --rsnake-blue-light and the cyan, the blue and the indigo all move with it,
     keeping their relationship. The three hexes are directions to travel in,
     not colours in their own right.

     It also fixes the contrast. White on the flat token was 1.83:1; across this
     mesh the glyph crosses 2.72 in the middle and 5.08 at the foot, so the
     filled shape has something to sit on wherever it lands. */
  --m1: color-mix(in oklab, var(--tile) 62%, #8FE9E4);
  --m2: color-mix(in oklab, var(--tile) 40%, #4C7EFF);
  --m3: color-mix(in oklab, var(--tile) 28%, #4038C7);
}

[data-acc-icons="ai"] [data-acc-item]:nth-child(1) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath fill-rule='evenodd' d='M4.848 2.771A49.144 49.144 0 0 1 12 2.25c2.43 0 4.817.178 7.152.52 1.978.292 3.348 2.024 3.348 3.97v6.02c0 1.946-1.37 3.678-3.348 3.97a48.901 48.901 0 0 1-3.476.383.39.39 0 0 0-.297.17l-2.755 4.133a.75.75 0 0 1-1.248 0l-2.755-4.133a.39.39 0 0 0-.297-.17 48.9 48.9 0 0 1-3.476-.384c-1.978-.29-3.348-2.024-3.348-3.97V6.741c0-1.946 1.37-3.68 3.348-3.97ZM6.75 8.25a.75.75 0 0 1 .75-.75h9a.75.75 0 0 1 0 1.5h-9a.75.75 0 0 1-.75-.75Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H7.5Z' clip-rule='evenodd'/%3E%3C/svg%3E"); }
[data-acc-icons="ai"] [data-acc-item]:nth-child(2) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath fill-rule='evenodd' d='M9 4.5a.75.75 0 0 1 .721.544l.813 2.846a3.75 3.75 0 0 0 2.576 2.576l2.846.813a.75.75 0 0 1 0 1.442l-2.846.813a3.75 3.75 0 0 0-2.576 2.576l-.813 2.846a.75.75 0 0 1-1.442 0l-.813-2.846a3.75 3.75 0 0 0-2.576-2.576l-2.846-.813a.75.75 0 0 1 0-1.442l2.846-.813A3.75 3.75 0 0 0 7.466 7.89l.813-2.846A.75.75 0 0 1 9 4.5ZM18 1.5a.75.75 0 0 1 .728.568l.258 1.036c.236.94.97 1.674 1.91 1.91l1.036.258a.75.75 0 0 1 0 1.456l-1.036.258c-.94.236-1.674.97-1.91 1.91l-.258 1.036a.75.75 0 0 1-1.456 0l-.258-1.036a2.625 2.625 0 0 0-1.91-1.91l-1.036-.258a.75.75 0 0 1 0-1.456l1.036-.258a2.625 2.625 0 0 0 1.91-1.91l.258-1.036A.75.75 0 0 1 18 1.5ZM16.5 15a.75.75 0 0 1 .712.513l.394 1.183c.15.447.5.799.948.948l1.183.395a.75.75 0 0 1 0 1.422l-1.183.395c-.447.15-.799.5-.948.948l-.395 1.183a.75.75 0 0 1-1.422 0l-.395-1.183a1.5 1.5 0 0 0-.948-.948l-1.183-.395a.75.75 0 0 1 0-1.422l1.183-.395c.447-.15.799-.5.948-.948l.395-1.183A.75.75 0 0 1 16.5 15Z' clip-rule='evenodd'/%3E%3C/svg%3E"); }
[data-acc-icons="ai"] [data-acc-item]:nth-child(3) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M5.625 3.75a2.625 2.625 0 1 0 0 5.25h12.75a2.625 2.625 0 0 0 0-5.25H5.625ZM3.75 11.25a.75.75 0 0 0 0 1.5h16.5a.75.75 0 0 0 0-1.5H3.75ZM3 15.75a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75a.75.75 0 0 1-.75-.75ZM3.75 18.75a.75.75 0 0 0 0 1.5h16.5a.75.75 0 0 0 0-1.5H3.75Z'/%3E%3C/svg%3E"); }
[data-acc-icons="ai"] [data-acc-item]:nth-child(4) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath fill-rule='evenodd' d='M12.516 2.17a.75.75 0 0 0-1.032 0 11.209 11.209 0 0 1-7.877 3.08.75.75 0 0 0-.722.515A12.74 12.74 0 0 0 2.25 9.75c0 5.942 4.064 10.933 9.563 12.348a.749.749 0 0 0 .374 0c5.499-1.415 9.563-6.406 9.563-12.348 0-1.39-.223-2.73-.635-3.985a.75.75 0 0 0-.722-.516l-.143.001c-2.996 0-5.717-1.17-7.734-3.08Zm3.094 8.016a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z' clip-rule='evenodd'/%3E%3C/svg%3E"); }
[data-acc-icons="ai"] [data-acc-item]:nth-child(5) { --ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z'/%3E%3C/svg%3E"); }

/* A skipped step goes flatter still, and its glyph fades with it. */
[data-acc-style="journey"][data-acc-icons] [data-acc-item][data-acc-skip] {
  --m1: #F3F1EE; --m2: #EBE8E4; --m3: #E4E1DC;
}
[data-acc-style="journey"][data-acc-icons] [data-acc-item][data-acc-skip] [data-acc-head]::before {
  box-shadow: inset 0 0 0 1px rgba(17, 17, 16, .05);
  opacity: .75;
}

/* ── skipped rows ──
   Add data-acc-skip to the step in Designer. No value needed. Independent of
   the marker, so it works with numbers, with icons and with neither.
   ========================================================================== */

/* background-image, not a pseudo-element: the header's ::before is the marker
   and its ::after is the pill, and layering the hatch as an image keeps
   whatever background-colour Designer set. */
[data-acc-style="journey"] [data-acc-item][data-acc-skip] {
  background-image: repeating-linear-gradient(-45deg,
    rgba(20, 20, 20, .04) 0 1px, transparent 1px 7px);
}
[data-acc-style="journey"] [data-acc-item][data-acc-skip] [data-acc-head] {
  opacity: .62;
  text-decoration: line-through;
  text-decoration-color: #cfccc7;
  text-decoration-thickness: 1px;
}

/* The "skipped" pill.
   A pseudo-element, not a Text Block: text-decoration set on an ancestor is
   drawn through every in-flow descendant, and neither text-decoration:none nor
   display:inline-block on the child removes it, because a flex item is
   blockified and inline-block never takes effect. An absolutely positioned box
   is out of flow, and the strike does not reach it. */
[data-acc-style="journey"] [data-acc-item][data-acc-skip] [data-acc-head]::after {
  content: "skipped";
  position: absolute;
  right: 46px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 9.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #8c8983;
  border: 1px solid #ddd9d4;
  border-radius: 999px;
  padding: 3px 8px;
  white-space: nowrap;
  pointer-events: none;
}

/* ── tuning ──
   --node is the tile, --glyph the icon inside it, --inset the gap that appears
   on BOTH edges, --node-gap the space between marker and title. Nothing here is
   a position: the vertical placement comes from the header and the left indent
   is derived, so there is no coordinate left to get wrong.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  [data-acc-style="journey"] [data-acc-head]::before { transition: none; }
}

/* ==========================================================================
   12. CHART, TWO CURVES   .rsg
   --------------------------------------------------------------------------
   Two panels, never one plot. The series are counted in different units,
   weekly people against a share of searches, so a shared pair of axes would
   invent a relationship the numbers do not contain. Side by side, each with
   its own scale and its own zero, the reader compares direction and shape,
   which is the only comparison the data supports.

   Markup contract
     .rsg                  wrapper, id="rsg"
     .rsg__panel           one panel
     .rsg__cap             caption above the plot
     .rsg__plot            id="rsg-plot-up" / "rsg-plot-down"
     .rsg__readout         id="rsg-readout-up" / "rsg-readout-down"
     .rsg__note            the source line under the plot

   Transparent by design: no background, no border, no radius. It fills the
   div you put it in.

   --bg MUST match that div's background. The dots on the lines are punched
   out with a ring in this colour and the area fill fades into it.
   ========================================================================== */

.rsg{
  --bg:      #1a1a1a;     /* your section background */
  --h:       15rem;       /* height of one plot      */

  --ink:     #ffffff;
  --ink-2:   #c3c3c3;
  --dim:     #6b6b6b;
  --rule:    #2b2b2b;

  --up:      #4c7eff;     /* Rsnake Blue, the layer that is growing */
  --up-2:    #8dc5f6;
  --down:    #a1a1a1;     /* neutral, the click that is shrinking   */
  --link:    #8dc5f6;     /* Rsnake Blue [light]                    */
  --link-2:  #b3ddff;     /* Rsnake Blue [extra light], on hover    */

  /* On a light section:
       --bg:#ffffff; --ink:#111111; --ink-2:#5a5a5a; --dim:#6b6b6b;
       --rule:#e5e5e5; --up:#4c7eff; --up-2:#4c7eff; --down:#6b6b6b;  */

  font-family: inherit;
  width: 100%;
  color: var(--ink);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2.5rem;
}
.rsg *, .rsg *::before, .rsg *::after { box-sizing: border-box; }

/* Relume wins wherever it is present, because :where() has no specificity.
   These are fallbacks for the day a class is missing, not overrides. */
.rsg :where(.text-size-tiny)     { font-size: .75rem;  line-height: 1.5; }
.rsg :where(.text-size-small)    { font-size: .875rem; line-height: 1.5; }
.rsg :where(.text-size-regular)  { font-size: 1rem;    line-height: 1.5; }
.rsg :where(.heading-style-h6)   { font-size: 1.25rem; line-height: 1.4; }
.rsg :where(.text-style-allcaps) { text-transform: uppercase; letter-spacing: .1em; }

.rsg__panel { display: flex; flex-direction: column; min-width: 0; }

/* One series per panel, so no legend box: the caption names what is plotted
   and a swatch of a single colour would only restate it.

   Sentence case, not caps. A caption that has to carry a product name reads
   badly shouted, and CHATGPT loses the shape the eye recognises. */
.rsg__cap {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 .125rem;
  margin: 0 0 .875rem;
  color: var(--dim);
}
.rsg__cap b { color: var(--ink-2); font-weight: 400; }

/* Product marks.
   They live here rather than inline in the Embed for one blunt reason: the
   Google mark alone is 8.3KB, and Webflow caps an HTML Embed at 10,000
   characters. In the CSS tab there is no cap, and the Embed keeps an empty
   span.

   The two are built differently on purpose. Google is a full-colour mark with
   nine gradients, so it is a background-image and keeps its own colours. The
   OpenAI mark is monochrome, so it is a mask and takes its colour from the
   element, which means it follows the theme instead of being baked in.

   Ids inside a data URI are scoped to that image, so neither mark can collide
   with any other SVG on the page.

   The near-invisible blur filters from the original export were removed: at
   17px a stdDeviation of 0.06 on a 70px viewBox is 0.015px. */
.rsg__mark {
  display: inline-block;
  width: 1.0625rem;
  height: 1.0625rem;
  flex: 0 0 1.0625rem;
  margin-right: .4375rem;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}
.rsg__mark svg { width: 100%; height: 100%; display: block; }

.rsg__mark--google {
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 70 71' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cmask id='rsg-g-mask0' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='0' y='0' width='70' height='71'%3E%3Cpath d='M68.627 28.8213H35.3845V42.4264H54.4859C54.1787 44.3519 53.4893 46.2461 52.4796 47.973C51.3227 49.952 49.8927 51.4585 48.4268 52.6057C44.0355 56.0422 38.9165 56.7449 35.3612 56.7449C26.3799 56.7449 18.7064 50.8164 15.7355 42.7604C15.6157 42.4679 15.5362 42.1659 15.4392 41.8676C14.7671 39.7774 14.4244 37.5901 14.424 35.3886C14.424 33.0397 14.8124 30.7914 15.5206 28.6678C18.314 20.2923 26.1609 14.0369 35.3676 14.0369C37.2194 14.0369 39.0027 14.262 40.6938 14.7111C43.7838 15.53 46.6271 17.1267 48.9676 19.3574L59.0754 9.24693C52.9269 3.48898 44.9118 0 35.3508 0C27.7075 0 20.6508 2.43216 14.8679 6.54312C10.1783 9.87654 6.33216 14.3401 3.73627 19.5237C1.32208 24.3302 0 29.6566 0 35.3835C0 41.1106 1.3243 46.4923 3.7385 51.2543V51.2863C6.2886 56.3419 10.0179 60.6947 14.5503 64.0131C18.5101 66.9119 25.6101 70.7729 35.3508 70.7729C40.9525 70.7729 45.9173 69.7415 50.2956 67.8083C53.4542 66.4137 56.2525 64.5948 58.7863 62.2571C62.1342 59.1684 64.7561 55.3477 66.5457 50.9521C68.3354 46.5565 69.2926 41.5855 69.2926 36.1964C69.2926 33.6866 69.0456 31.1376 68.627 28.8213Z' fill='white'/%3E%3C/mask%3E%3Cg mask='url(%23rsg-g-mask0)'%3E%3Cg%3E%3Cpath d='M-0.507812 35.623C-0.471177 41.2598 1.10141 47.0755 3.48169 51.7705V51.8029C5.2018 55.2124 7.55238 57.9056 10.2295 60.5742L26.4001 54.5479C23.3406 52.9605 22.8737 51.988 20.6808 50.2132C18.4397 47.905 16.7693 45.2554 15.7292 42.1483H15.6871L15.7292 42.116C15.0447 40.0645 14.9774 37.8864 14.9519 35.623H-0.507812Z' fill='url(%23rsg-g-paint0_radial)'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M35.3869 -0.256836C33.7889 5.4782 34.3998 11.0526 35.3869 14.2961C37.2328 14.2977 39.0108 14.5222 40.6965 14.97C43.7866 15.7886 46.6299 17.3854 48.9701 19.6164L59.3368 9.24775C53.1955 3.49642 45.8049 -0.247925 35.3869 -0.256836Z' fill='url(%23rsg-g-paint1_radial)'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M35.352 -0.302734C27.5126 -0.302734 20.2747 2.1918 14.3435 6.40816C12.1481 7.96805 10.1281 9.77403 8.32054 11.7929C7.84924 16.3104 11.8496 21.8624 19.7719 21.8163C23.6157 17.2499 29.3008 14.295 35.628 14.295L35.6453 14.2956L35.3869 -0.301717L35.352 -0.302734Z' fill='url(%23rsg-g-paint2_radial)'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M61.2272 37.2582L54.2299 42.1681C53.923 44.0934 53.2329 45.9876 52.2232 47.7147C51.0662 49.6935 49.6364 51.2001 48.1703 52.3474C43.7884 55.7768 38.6828 56.483 35.1285 56.4858C31.4546 62.8767 30.8105 66.0777 35.3867 71.2356C41.0495 71.2315 46.0695 70.1874 50.4976 68.2324C53.6985 66.8191 56.5345 64.9756 59.1021 62.6066C62.4948 59.4763 65.1523 55.6044 66.966 51.1497C68.7797 46.6951 69.7495 41.6579 69.7495 36.1963L61.2272 37.2582Z' fill='url(%23rsg-g-paint3_radial)'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M34.8691 28.3057V42.9444H68.5352C68.8313 40.9397 69.8105 38.3454 69.8105 36.1976C69.8105 33.6878 69.564 30.6222 69.1454 28.3057H34.8691Z' fill='%233086FF'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M8.4805 11.2773C6.4027 13.599 4.62788 16.1977 3.22065 19.0079C0.806453 23.8144 -0.515625 29.6577 -0.515625 35.3845C-0.515625 35.4652 -0.509189 35.5442 -0.508447 35.6249C0.560899 37.7187 14.2609 37.3177 14.9515 35.6249C14.9505 35.5459 14.9419 35.4688 14.9419 35.3896C14.9419 33.0407 15.3305 31.3095 16.0384 29.1859C16.9122 26.5663 18.2801 24.1543 20.0294 22.076C20.426 21.559 21.4837 20.4474 21.7921 19.7806C21.9097 19.5268 21.5787 19.3842 21.5602 19.2948C21.5396 19.1948 21.0958 19.2752 20.9965 19.2006C20.6809 18.9641 20.0559 18.8406 19.6762 18.7309C18.865 18.4964 17.5207 17.9791 16.7739 17.4426C14.4134 15.7475 10.7301 13.7225 8.4805 11.2773Z' fill='url(%23rsg-g-paint4_radial)'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M16.827 19.3037C22.3005 22.6901 23.8746 17.5944 27.5138 15.9998L21.1834 2.5918C18.8739 3.58322 16.6717 4.82087 14.6131 6.28446C11.5645 8.45158 8.87228 11.0963 6.64844 14.1057L16.827 19.3037Z' fill='url(%23rsg-g-paint5_radial)'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M19.053 53.5124C11.7055 56.2216 10.5552 56.3189 9.87891 60.9696C11.1754 62.2622 12.5649 63.4523 14.0355 64.5298C17.995 67.4287 25.6117 71.2896 35.3526 71.2896L35.3865 71.2886V56.2275L35.3633 56.228C31.7156 56.228 28.8006 55.2496 25.8122 53.5478C25.0753 53.1283 23.7383 54.2549 23.0588 53.7513C22.1217 53.0565 19.8657 54.3496 19.053 53.5124Z' fill='url(%23rsg-g-paint6_radial)'/%3E%3C/g%3E%3Cg opacity='0.5'%3E%3Cpath d='M31.082 55.7539V71.0287C32.445 71.1916 33.8633 71.2904 35.3512 71.2904C36.8429 71.2904 38.2858 71.2122 39.688 71.0684V55.8568C38.2586 56.1046 36.8115 56.229 35.3619 56.2287C33.8935 56.2287 32.4657 56.0541 31.082 55.7539Z' fill='url(%23rsg-g-paint7_linear)'/%3E%3C/g%3E%3C/g%3E%3Cdefs%3E%3CradialGradient id='rsg-g-paint0_radial' cx='0' cy='0' r='1' gradientUnits='userSpaceOnUse' gradientTransform='translate(26.0763 60.0428) rotate(-92.3394) scale(35.2741 51.8136)'%3E%3Cstop offset='0.142' stop-color='%231ABD4D'/%3E%3Cstop offset='0.248' stop-color='%236EC30D'/%3E%3Cstop offset='0.312' stop-color='%238AC502'/%3E%3Cstop offset='0.366' stop-color='%23A2C600'/%3E%3Cstop offset='0.446' stop-color='%23C8C903'/%3E%3Cstop offset='0.54' stop-color='%23EBCB03'/%3E%3Cstop offset='0.616' stop-color='%23F7CD07'/%3E%3Cstop offset='0.699' stop-color='%23FDCD04'/%3E%3Cstop offset='0.771' stop-color='%23FDCE05'/%3E%3Cstop offset='0.861' stop-color='%23FFCE0A'/%3E%3C/radialGradient%3E%3CradialGradient id='rsg-g-paint1_radial' cx='0' cy='0' r='1' gradientUnits='userSpaceOnUse' gradientTransform='translate(58.3669 18.8664) scale(24.4532 31.58)'%3E%3Cstop offset='0.408' stop-color='%23FB4E5A'/%3E%3Cstop offset='1' stop-color='%23FF4540'/%3E%3C/radialGradient%3E%3CradialGradient id='rsg-g-paint2_radial' cx='0' cy='0' r='1' gradientTransform='matrix(-34.261 18.9758 25.7501 46.4919 45.0122 -4.87415)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.231' stop-color='%23FF4541'/%3E%3Cstop offset='0.312' stop-color='%23FF4540'/%3E%3Cstop offset='0.458' stop-color='%23FF4640'/%3E%3Cstop offset='0.54' stop-color='%23FF473F'/%3E%3Cstop offset='0.699' stop-color='%23FF5138'/%3E%3Cstop offset='0.771' stop-color='%23FF5B33'/%3E%3Cstop offset='0.861' stop-color='%23FF6C29'/%3E%3Cstop offset='1' stop-color='%23FF8C18'/%3E%3C/radialGradient%3E%3CradialGradient id='rsg-g-paint3_radial' cx='0' cy='0' r='1' gradientTransform='matrix(-62.1336 -81.108 -29.9391 22.935 35.8953 66.6551)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.132' stop-color='%230CBA65'/%3E%3Cstop offset='0.21' stop-color='%230BB86D'/%3E%3Cstop offset='0.297' stop-color='%2309B479'/%3E%3Cstop offset='0.396' stop-color='%2308AD93'/%3E%3Cstop offset='0.477' stop-color='%230AA6A9'/%3E%3Cstop offset='0.568' stop-color='%230D9CC6'/%3E%3Cstop offset='0.667' stop-color='%231893DD'/%3E%3Cstop offset='0.769' stop-color='%23258BF1'/%3E%3Cstop offset='0.859' stop-color='%233086FF'/%3E%3C/radialGradient%3E%3CradialGradient id='rsg-g-paint4_radial' cx='0' cy='0' r='1' gradientUnits='userSpaceOnUse' gradientTransform='translate(32.3494 6.38265) rotate(96.6178) scale(38.1537 52.7543)'%3E%3Cstop offset='0.366' stop-color='%23FF4E3A'/%3E%3Cstop offset='0.458' stop-color='%23FF8A1B'/%3E%3Cstop offset='0.54' stop-color='%23FFA312'/%3E%3Cstop offset='0.616' stop-color='%23FFB60C'/%3E%3Cstop offset='0.771' stop-color='%23FFCD0A'/%3E%3Cstop offset='0.861' stop-color='%23FECF0A'/%3E%3Cstop offset='0.915' stop-color='%23FECF08'/%3E%3Cstop offset='1' stop-color='%23FDCD01'/%3E%3C/radialGradient%3E%3CradialGradient id='rsg-g-paint5_radial' cx='0' cy='0' r='1' gradientUnits='userSpaceOnUse' gradientTransform='translate(26.169 5.98809) rotate(132.119) scale(18.9503 53.4505)'%3E%3Cstop offset='0.316' stop-color='%23FF4C3C'/%3E%3Cstop offset='0.604' stop-color='%23FF692C'/%3E%3Cstop offset='0.727' stop-color='%23FF7825'/%3E%3Cstop offset='0.885' stop-color='%23FF8D1B'/%3E%3Cstop offset='1' stop-color='%23FF9F13'/%3E%3C/radialGradient%3E%3CradialGradient id='rsg-g-paint6_radial' cx='0' cy='0' r='1' gradientTransform='matrix(-34.261 -18.9758 25.7501 -46.4919 45.0119 75.6459)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.231' stop-color='%230FBC5F'/%3E%3Cstop offset='0.312' stop-color='%230FBC5F'/%3E%3Cstop offset='0.366' stop-color='%230FBC5E'/%3E%3Cstop offset='0.458' stop-color='%230FBC5D'/%3E%3Cstop offset='0.54' stop-color='%2312BC58'/%3E%3Cstop offset='0.699' stop-color='%2328BF3C'/%3E%3Cstop offset='0.771' stop-color='%2338C02B'/%3E%3Cstop offset='0.861' stop-color='%2352C218'/%3E%3Cstop offset='0.915' stop-color='%2367C30F'/%3E%3Cstop offset='1' stop-color='%2386C504'/%3E%3C/radialGradient%3E%3ClinearGradient id='rsg-g-paint7_linear' x1='31.082' y1='63.5221' x2='39.688' y2='63.5221' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%230FBC5C'/%3E%3Cstop offset='1' stop-color='%230CBA65'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}

.rsg__mark--openai {
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 71 71' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M65.1982 28.9553C66.7988 24.135 66.2445 18.8585 63.6787 14.477C59.8195 7.75918 52.0637 4.30317 44.49 5.92678C37.8882 -1.41739 26.5854 -2.01843 19.2418 4.58375C16.9113 6.67942 15.1725 9.34955 14.2007 12.3295C9.22466 13.3507 4.93064 16.4663 2.41672 20.8797C-1.48377 27.587 -0.598356 36.0481 4.60629 41.8033C3.00036 46.6209 3.54942 51.8987 6.11253 56.2815C9.9758 63.002 17.7369 66.458 25.3145 64.8317C28.6859 68.6282 33.5277 70.789 38.6047 70.7597C46.3685 70.7664 53.2468 65.7532 55.6172 58.3599C60.5918 57.3373 64.8858 54.2217 67.4011 49.8096C71.2551 43.1144 70.3656 34.6984 65.1982 28.9566V28.9553ZM38.6047 66.1296C35.5059 66.1349 32.504 65.0485 30.1257 63.0605L30.5444 62.8238L44.6296 54.6911C45.3422 54.2723 45.7822 53.509 45.7875 52.6819V32.8182L51.7419 36.2635C51.8018 36.2941 51.843 36.3513 51.8536 36.4165V52.876C51.8376 60.1883 45.9151 66.1123 38.6047 66.1283V66.1296ZM10.1287 53.9664C8.57459 51.283 8.01623 48.1368 8.55331 45.0811L8.97206 45.3324L23.0705 53.4638C23.7804 53.8813 24.6618 53.8813 25.3717 53.4638L42.5942 43.5333V50.4107C42.5903 50.4825 42.5544 50.549 42.4972 50.5915L28.2313 58.8213C21.8913 62.4741 13.7925 60.3026 10.13 53.9664H10.1287ZM6.41963 23.2786C7.98435 20.5766 10.4544 18.5168 13.3924 17.4623V34.2011C13.3817 35.0242 13.8191 35.7888 14.5357 36.1957L31.6744 46.085L25.72 49.5304C25.6549 49.565 25.5764 49.565 25.5113 49.5304L11.2733 41.3139C4.94661 37.6465 2.77704 29.551 6.41963 23.2081V23.2786ZM55.3393 34.6466L38.1447 24.6589L44.0845 21.2281C44.1497 21.1936 44.2281 21.1936 44.2932 21.2281L58.5312 29.4579C64.8685 33.116 67.0408 41.2208 63.3836 47.5584C61.8441 50.2272 59.4233 52.275 56.5371 53.3521V36.6133C56.5118 35.7915 56.0559 35.0442 55.338 34.6466H55.3393ZM61.2659 25.7333L60.847 25.482L46.7766 17.2801C46.0613 16.8613 45.1759 16.8613 44.462 17.2801L27.2541 27.2107V20.3332C27.2475 20.2627 27.2794 20.1923 27.3379 20.1524L41.5759 11.9359C47.9225 8.27911 56.032 10.4599 59.6878 16.8081C61.2326 19.4888 61.791 22.6257 61.2672 25.6748V25.7307L61.2659 25.7333ZM24.0051 37.9244L18.0506 34.4936C17.9908 34.4577 17.9496 34.3952 17.939 34.3261V17.9078C17.9483 10.5809 23.8948 4.65023 31.2185 4.65954C34.3094 4.66353 37.3032 5.74727 39.6802 7.7246L39.2614 7.9613L25.1763 16.094C24.4637 16.5129 24.0237 17.2761 24.0184 18.1032L24.0051 37.9244ZM27.2395 30.9499L34.9089 26.5285L42.5929 30.9499V39.7927L34.9368 44.2154L27.2541 39.7927L27.2409 30.9499H27.2395Z' fill='black'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 71 71' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M65.1982 28.9553C66.7988 24.135 66.2445 18.8585 63.6787 14.477C59.8195 7.75918 52.0637 4.30317 44.49 5.92678C37.8882 -1.41739 26.5854 -2.01843 19.2418 4.58375C16.9113 6.67942 15.1725 9.34955 14.2007 12.3295C9.22466 13.3507 4.93064 16.4663 2.41672 20.8797C-1.48377 27.587 -0.598356 36.0481 4.60629 41.8033C3.00036 46.6209 3.54942 51.8987 6.11253 56.2815C9.9758 63.002 17.7369 66.458 25.3145 64.8317C28.6859 68.6282 33.5277 70.789 38.6047 70.7597C46.3685 70.7664 53.2468 65.7532 55.6172 58.3599C60.5918 57.3373 64.8858 54.2217 67.4011 49.8096C71.2551 43.1144 70.3656 34.6984 65.1982 28.9566V28.9553ZM38.6047 66.1296C35.5059 66.1349 32.504 65.0485 30.1257 63.0605L30.5444 62.8238L44.6296 54.6911C45.3422 54.2723 45.7822 53.509 45.7875 52.6819V32.8182L51.7419 36.2635C51.8018 36.2941 51.843 36.3513 51.8536 36.4165V52.876C51.8376 60.1883 45.9151 66.1123 38.6047 66.1283V66.1296ZM10.1287 53.9664C8.57459 51.283 8.01623 48.1368 8.55331 45.0811L8.97206 45.3324L23.0705 53.4638C23.7804 53.8813 24.6618 53.8813 25.3717 53.4638L42.5942 43.5333V50.4107C42.5903 50.4825 42.5544 50.549 42.4972 50.5915L28.2313 58.8213C21.8913 62.4741 13.7925 60.3026 10.13 53.9664H10.1287ZM6.41963 23.2786C7.98435 20.5766 10.4544 18.5168 13.3924 17.4623V34.2011C13.3817 35.0242 13.8191 35.7888 14.5357 36.1957L31.6744 46.085L25.72 49.5304C25.6549 49.565 25.5764 49.565 25.5113 49.5304L11.2733 41.3139C4.94661 37.6465 2.77704 29.551 6.41963 23.2081V23.2786ZM55.3393 34.6466L38.1447 24.6589L44.0845 21.2281C44.1497 21.1936 44.2281 21.1936 44.2932 21.2281L58.5312 29.4579C64.8685 33.116 67.0408 41.2208 63.3836 47.5584C61.8441 50.2272 59.4233 52.275 56.5371 53.3521V36.6133C56.5118 35.7915 56.0559 35.0442 55.338 34.6466H55.3393ZM61.2659 25.7333L60.847 25.482L46.7766 17.2801C46.0613 16.8613 45.1759 16.8613 44.462 17.2801L27.2541 27.2107V20.3332C27.2475 20.2627 27.2794 20.1923 27.3379 20.1524L41.5759 11.9359C47.9225 8.27911 56.032 10.4599 59.6878 16.8081C61.2326 19.4888 61.791 22.6257 61.2672 25.6748V25.7307L61.2659 25.7333ZM24.0051 37.9244L18.0506 34.4936C17.9908 34.4577 17.9496 34.3952 17.939 34.3261V17.9078C17.9483 10.5809 23.8948 4.65023 31.2185 4.65954C34.3094 4.66353 37.3032 5.74727 39.6802 7.7246L39.2614 7.9613L25.1763 16.094C24.4637 16.5129 24.0237 17.2761 24.0184 18.1032L24.0051 37.9244ZM27.2395 30.9499L34.9089 26.5285L42.5929 30.9499V39.7927L34.9368 44.2154L27.2541 39.7927L27.2409 30.9499H27.2395Z' fill='black'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat;   mask-repeat: no-repeat;
  -webkit-mask-position: center;    mask-position: center;
  -webkit-mask-size: contain;       mask-size: contain;
}

/* The OpenAI mark is a logo, so it wears ink rather than the series colour:
   a recognisable mark tinted an arbitrary hue reads as a mistake. */
.rsg__panel .rsg__mark--openai { color: var(--ink-2); }

.rsg__plot      { position: relative; width: 100%; height: var(--h); }
.rsg__plot svg  { display: block; width: 100%; height: 100%; overflow: visible; }
.rsg__plot text { font-family: inherit; fill: var(--dim); }

/* margin-top:auto pushes the note to the foot of its panel, so the two rules
   line up across the pair even when one note wraps to an extra line. Matching
   the copy length is not enough on its own: a narrower viewport rewraps it. */
.rsg__note {
  margin: 1rem 0 0;
  /* Pushed to the foot of its panel so the two source rules line up across the
     pair even when one body wraps to an extra line. Matching the copy length is
     not enough on its own: a narrower viewport rewraps it. */
  margin-top: auto;
  padding-top: .875rem;
  border-top: 1px solid var(--rule);
  color: var(--dim);
}

/* Smaller and slower than the first pass. A readout that snaps in at 150ms
   reads as a tooltip; at 600ms on the emphasis curve it reads as part of the
   chart. Sentence case throughout, so the label sits beside the value rather
   than shouting over it. */
/* The hover readout.
   Glass rather than a card. The plot behind it stays faintly visible through
   the panel, which is what keeps it reading as part of the chart instead of a
   tooltip parked on top of one. No drop shadow anywhere: on a dark ground a
   shadow is invisible, so it was paying for a composited layer and buying
   nothing. What separates the panel from the plot is the hairline and the
   blur, both of which the eye reads as depth without any dark under it.

   Three lines, three weights, one hierarchy: the date sits back, the value is
   the only thing at full contrast, the unit explains it and sits back again.
   Nothing is bold, nothing is uppercase. At this size weight is a blunter
   instrument than contrast, and contrast is doing the whole job already.

   The motion is 0.6s on the emphasis curve, the same pair the accordion,
   the CTA and the marquee use, so the page has ONE way of arriving. It rises
   6px and settles: no scale, because a panel that grows reads as a popup, and
   a popup is the opposite of what this is. */
.rsg__readout {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  pointer-events: none;

  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity   var(--time-slow) var(--ease-emphasis),
    transform var(--time-slow) var(--ease-emphasis);
  will-change: opacity, transform;

  min-width: 7.5rem;
  padding: .625rem .75rem .6875rem;
  border-radius: .75rem;

  /* color-mix rather than a second variable: the panel is always the section's
     own background at 72 per cent, so it follows --bg into any theme without a
     matching value that has to be remembered and kept in step. */
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
          backdrop-filter: blur(14px) saturate(140%);
  box-shadow: inset 0 0 0 1px var(--rule);
}
.rsg__readout.is-on { opacity: 1; transform: translateY(0); }

.rsg__readout u {
  display: block;
  text-decoration: none;
  color: var(--dim);
  margin-bottom: .3125rem;
  /* Stated rather than assumed: a global rule on the site was uppercasing
     this, and the label is a date, which reads badly shouted. */
  text-transform: none;
  letter-spacing: normal;
}
.rsg__readout b {
  display: block;
  color: var(--ink);
  font-weight: 400;          /* contrast is the hierarchy here, not weight */
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  margin-bottom: .1875rem;
}
.rsg__readout span { display: block; color: var(--dim); }

/* Where the panel cannot be translucent, it goes solid rather than washing the
   plot out through a half-transparent ground. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .rsg__readout { background: var(--bg); }
}
@media (prefers-reduced-motion: reduce) {
  .rsg__readout { transition: opacity .01ms; transform: none; }
  .rsg__readout.is-on { transform: none; }
}

/* Source links.
   Dashed rather than solid, because these are citations rather than
   navigation: the reader should see that they lead somewhere without the
   line competing with the sentence. */
.rsg a,
.rsd a {
  color: var(--link);
  text-decoration-line: underline;
  text-decoration-style: dashed;
  text-decoration-thickness: 1px;
  text-underline-offset: .2em;
  transition:
    color                  var(--time-quick) var(--ease-emphasis),
    text-decoration-color  var(--time-quick) var(--ease-emphasis);
}
.rsg a:hover,
.rsd a:hover { color: var(--link-2); }
.rsg a:focus-visible,
.rsd a:focus-visible {
  outline: 2px solid var(--link-2);
  outline-offset: 2px;
  border-radius: .125rem;
}

@media (max-width: 52rem) {
  .rsg { grid-template-columns: 1fr; gap: 2rem; --h: 13rem; }
}


/* ==========================================================================
   13. CHART, WITH AND WITHOUT AN AI ANSWER   .rsd
   --------------------------------------------------------------------------
   Two sources measuring the same effect on different scales, 15 per cent
   against 3.8. Each pair is therefore scaled to its own first bar and there
   is deliberately no shared axis, because a shared axis would invite a
   comparison between Pew and Seer that neither dataset supports. The reader
   compares inside a row only.

   Markup contract
     .rsd            wrapper, id="rsd"
     .rsd__group     one source
     .rsd__src       the source line
     .rsd__row       one bar. .is-ai marks the AI answer row
     .rsd__track     the rail
     .rsd__bar       the bar, width set by script
     .rsd__val       data-to holds the value, data-dec the decimals
     .rsd__drop      the fall, computed by script from the two values

   Transparent by design. --bg only matters if you want the readout chips to
   punch cleanly through the section behind them.
   ========================================================================== */

.rsd{
  --bg:      #1a1a1a;
  --ink:     #ffffff;
  --ink-2:   #c3c3c3;
  --dim:     #6b6b6b;
  --rule:    #2b2b2b;

  --plain:   #a1a1a1;   /* no AI answer on the page */
  --ai:      #4c7eff;   /* an AI answer on the page */
  --ai-2:    #8dc5f6;
  --link:    #8dc5f6;   /* Rsnake Blue [light]                   */
  --link-2:  #b3ddff;   /* Rsnake Blue [extra light], on hover    */

  font-family: inherit;
  width: 100%;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  gap: 2rem;
}
.rsd *, .rsd *::before, .rsd *::after { box-sizing: border-box; }

.rsd :where(.text-size-tiny)     { font-size: .75rem;  line-height: 1.5; }
.rsd :where(.text-size-small)    { font-size: .875rem; line-height: 1.5; }
.rsd :where(.heading-style-h6)   { font-size: 1.25rem; line-height: 1.4; }
.rsd :where(.text-style-allcaps) { text-transform: uppercase; letter-spacing: .1em; }

.rsd__group { display: flex; flex-direction: column; gap: .875rem; }
.rsd__group + .rsd__group {
  padding-top: 2rem;
  border-top: 1px solid var(--rule);
}
.rsd__src { margin: 0; color: var(--dim); }

.rsd__row {
  display: grid;
  align-items: center;
  gap: .75rem 1.25rem;
  grid-template-columns: minmax(9rem, 13rem) minmax(0, 1fr) auto;
}
.rsd__name              { color: var(--ink-2); }
.rsd__row.is-ai .rsd__name { color: var(--ink); }

/* The rail runs the full width of the row, so the shorter bar reads as a
   shortfall rather than merely a shorter bar. */
.rsd__track { position: relative; height: .625rem; }
.rsd__track::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: var(--rule);
}
.rsd__bar {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--w);
  background: var(--plain);
  border-radius: .125rem;
  transform: scaleX(var(--s, 1));
  transform-origin: left center;
  transition: transform 760ms var(--ease-chart) var(--d, 0ms);
}
.rsd__row.is-ai .rsd__bar { background: var(--ai); }

.rsd__end { display: flex; align-items: baseline; gap: .75rem; justify-self: end; }
.rsd__val {
  color: var(--ink);
  letter-spacing: -.025em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
.rsd__row.is-ai .rsd__val { color: var(--ai-2); }

/* The fall is the point of the row, so it is printed rather than left to be
   worked out from two numbers. The script computes it, so it can never
   contradict the values beside it. */
.rsd__drop {
  color: var(--ai-2);
  white-space: nowrap;
  border: 1px solid rgba(76, 126, 255, .35);
  background: rgba(76, 126, 255, .10);
  border-radius: 999px;
  padding: .1875rem .5rem;
  font-variant-numeric: tabular-nums;
  opacity: var(--o, 1);
  transition: opacity var(--time-slow) var(--ease-emphasis) .9s;
}

@media (max-width: 40rem) {
  .rsd { gap: 1.5rem; }
  .rsd__row   { grid-template-columns: 1fr auto; gap: .5rem .75rem; }
  .rsd__track { grid-column: 1 / -1; order: 3; }
  .rsd__end   { order: 2; }
}


/* ==========================================================================
   14. REDUCED MOTION
   One block for the whole file. Add future components here rather than
   repeating the query per section.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  [data-acc-body],
  [data-acc-body] > *,
  [data-acc-chevron],
  [data-acc-item],
  .rsg *,
  .rsd * {
    transition-duration: 1ms !important;
  }
  .rsnake-pulse-ring { animation: none; }
}


/* ==========================================================================
   15. REVEAL   [data-reveal]
   --------------------------------------------------------------------------
   Titles and subtitles arriving the way Lightspark do it. Their numbers, read
   out of /_next/static/chunks/1568-3624c8da939b3e31.js rather than eyeballed:

     from      opacity 0, translateY(10px), blur(14px)
     to        opacity 1, translateY(0),    blur(0)
     duration  0.7s, the same for every element
     ease      cubic-bezier(.27, .09, .24, 1)
     delays    0.10s title, 0.25s subtitle, 0.35s buttons
     trigger   on mount for the hero, no observer

   Two things about their build are worth knowing before copying it. The h1 is
   split into <span> lines, but the lines do NOT stagger; they are line-break
   wrappers and the whole heading animates as one. And the blur is interpolated
   as a filter string on the same curve as everything else, not on a timing of
   its own. What reads as a stagger is only those three hand-written delays.

   Their second preset, for lists and cards, comes from chunk 6261: opacity 0
   with a 4px rise, 0.24s ease-out, 40ms per item, fired by an observer at
   threshold 0.2, once. That is `rise` below.

   Markup contract
     [data-reveal]              the group. Default fires when it scrolls in
     [data-reveal="load"]       fires on page load instead, for a hero
     [data-reveal="rise"]       the small list preset
     [data-reveal="tilt"]       Rejouice's card fly-in, for grids of cards
     [data-reveal-item]         each element that moves. Delays are assigned in
                                document order unless you set one yourself
     [data-reveal-delay="0.4"]  override for one item, in seconds

   The script removes the filter entirely once the run finishes. blur(0px) is
   not free: a filter makes the element a containing block for anything fixed
   or absolute inside it, gives it its own stacking context and keeps it on a
   composited layer. Leaving it on a heading forever is a real cost.
   ========================================================================== */

[data-reveal] {
  --reveal-ease:  var(--ease-reveal, cubic-bezier(.27, .09, .24, 1));
  --reveal-time:  var(--time-reveal, .7s);
  --reveal-rise:  10px;
  --reveal-blur:  14px;

  /* The whole start transform, as one value, so a preset can replace it
     outright instead of every preset having to know about every axis. */
  --reveal-from:  translateY(var(--reveal-rise));
  --reveal-origin: 50% 50%;
}

/* Nothing happens until the script says so, so a visitor without JS reads the
   page in full rather than a blank column. */
[data-reveal].is-armed [data-reveal-item] {
  opacity: 0;
  transform: var(--reveal-from);
  transform-origin: var(--reveal-origin);
  filter: blur(var(--reveal-blur));
  will-change: opacity, transform, filter;
}

/* The transition is declared ONLY on the finished state, and that is
   deliberate. A transition lives on the computed style the element ends up
   with, so putting it here means hiding the item is instant and revealing it
   animates. Declared on the armed rule instead, arming itself would animate:
   the item would fade out over 0.7s on page load, fire transitionend, and the
   cleanup below would then strip the transition before the reveal ever ran.
   That is a silent failure, and the reason a scroll-triggered group would snap
   into place with no animation at all. */
[data-reveal].is-in [data-reveal-item] {
  opacity: 1;
  transform: none;
  filter: blur(0);
  transition:
    opacity   var(--reveal-time) var(--reveal-ease) var(--reveal-delay, 0s),
    transform var(--reveal-time) var(--reveal-ease) var(--reveal-delay, 0s),
    filter    var(--reveal-time) var(--reveal-ease) var(--reveal-delay, 0s);
}

/* Applied per item when its own transition ends. Drops the compositing cost
   and the containing-block side effect of a filter that is doing nothing.
   Written long enough to outweigh the .is-in rule above, which is what actually
   holds blur(0) on the element. */
[data-reveal].is-in [data-reveal-item].is-done {
  filter: none;
  will-change: auto;
  transition: none;   /* or dropping the filter tweens blur(0) to none for nothing */
}

/* The list preset: a shorter, smaller move with no blur, 40ms apart. */
[data-reveal="rise"] {
  --reveal-time: .24s;
  --reveal-ease: cubic-bezier(0, 0, .58, 1);   /* ease-out */
  --reveal-rise: 4px;
  --reveal-blur: 0px;
}

/* The card preset, Rejouice's. The same values their marquee tiles use, from
   /_nuxt/BdSAQIjB.js: 40% down, rotateY 12 then rotateX 45, one second,
   power1.inOut, 0.1s apart, pivoting on the bottom edge.

   No perspective, deliberately. Without one the two rotations flatten into a
   squash with a horizontal shear, and that lean is what reads as theirs; add
   perspective and it becomes a 3D card flip, which looks more correct and less
   like the original. The order is not interchangeable either: rotateY before
   rotateX puts the shear on the horizontal axis, the other way round puts it
   on the vertical.

   Use it on a grid of cards, not on a heading. On text the squash reads as a
   rendering fault for the first few frames. */
[data-reveal="tilt"] {
  --reveal-time: 1s;
  --reveal-ease: cubic-bezier(.45, 0, .55, 1);   /* GSAP power1.inOut */
  --reveal-blur: 0px;
  --reveal-from: translateY(40%) rotateY(12deg) rotateX(45deg);
  --reveal-origin: bottom;
}

/* A tilted card starts below its own box, so a section that clips will slice
   it. Same runway the marquee uses, and the same reason. Only needed where an
   ancestor has overflow hidden; harmless everywhere else. */
[data-reveal="tilt"].has-runway {
  padding-bottom: var(--reveal-runway, 8rem);
  margin-bottom: calc(var(--reveal-runway, 8rem) * -1);
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal].is-armed [data-reveal-item] {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
    will-change: auto;
  }
}

/* ==========================================================================
   16. CTA BUTTON   .div-cta-button
   --------------------------------------------------------------------------
   The booking pill. At rest the arrow points up and to the right inside a
   translucent disc; on hover it swings down to horizontal and the disc fills
   white, with the arrow flipping to ink so it stays readable on it.

   Two states of one control, not two effects. The disc going white is what
   makes the button look pressable, and the arrow straightening is what says
   where it goes. Either alone reads as decoration.

   Driven from the button, not the disc. The reader is aiming at the label, so
   a reaction that only fires when the pointer finds a 32px circle reads as
   broken.

   Written against the class names already in Designer, so it reaches every
   instance of the component without touching any of them:

     .div-cta-button        the pill
     .div-cta-button-icon   the disc at the right end
     .icon-1x1-xxsmall      the Embed holding the arrow

   The rotation lives here rather than in the SVG. It has to: it is the thing
   being animated. Your arrow stays a plain right-pointing glyph and this file
   decides which way it faces in each state, which also means the same Embed
   can be reused anywhere without carrying a hard-coded angle.

   Knobs, on .div-cta-button or any parent
     --cta-time          duration for the whole group
     --cta-rest          arrow angle at rest.  -45deg is up and right
     --cta-hover         arrow angle on hover.  0deg is straight right
     --cta-arrow-size    the square the arrow is drawn in. Default 1rem
     --cta-disc          disc at rest
     --cta-disc-hover    disc on hover
     --cta-arrow         arrow colour at rest
     --cta-arrow-hover   arrow colour on hover
     --cta-pill / --cta-pill-hover / --cta-ring
   ========================================================================== */

.div-cta-button {
  --cta-time: var(--time-base);

  --cta-rest:  -45deg;
  --cta-hover:   0deg;

  --cta-pill:       rgba(255, 255, 255, .06);
  --cta-pill-hover: rgba(255, 255, 255, .10);
  --cta-ring:       rgba(255, 255, 255, .14);

  --cta-disc:        rgba(255, 255, 255, .11);
  --cta-disc-hover:  #ffffff;
  --cta-arrow:       #ffffff;
  --cta-arrow-hover: #141414;

  transition:
    background-color var(--cta-time) var(--ease-emphasis),
    box-shadow       var(--cta-time) var(--ease-emphasis);
}

/* A ring rather than a drop shadow. The pill sits on a dark ground where a
   shadow is invisible, and a ring reads as the control lighting up rather than
   lifting off the page. Drawn with box-shadow so it costs no layout and cannot
   shift the text by a pixel the way a border would. */
.div-cta-button:hover,
.div-cta-button:focus-within,
a:hover > .div-cta-button {
  background-color: var(--cta-pill-hover);
  box-shadow: 0 0 0 1px var(--cta-ring);
}

.div-cta-button-icon {
  background-color: var(--cta-disc);
  /* The SVG is stroke="currentColor", so the arrow colour is just the colour
     of this element and needs no separate rule or second selector. */
  color: var(--cta-arrow);
  transition:
    background-color var(--cta-time) var(--ease-emphasis),
    color            var(--cta-time) var(--ease-emphasis);
}

/* The Embed wrapper turns, not the SVG. Keeping the glyph plain means the same
   Embed drops in anywhere, and it keeps this file the only place an angle is
   written down.

   Rotating a wrapper only looks right if the glyph sits at that wrapper's
   centre, and out of the box it does not. Webflow gives every .w-embed a
   clearfix, `content:" "; display:table; grid-area:1/1/2/2`, and the SVG is an
   inline box with vertical-align:middle. A block wrapper therefore puts the
   pseudo on its own line and lands the arrow about 3px low. Straight on that is
   almost invisible. Turned 45 degrees it is not: the offset rotates with the
   element, so 3px down becomes 2.1px down AND 2.1px right, and the arrow reads
   as sitting crooked in the disc.

   Three lines fix it. Grid with place-items centres the SVG on the wrapper's
   own centre, which is the point the rotation pivots on. content:none retires
   the clearfix, which has nothing to clear here. line-height:0 stops the
   wrapper inheriting a text line box it has no text to put in. */
.div-cta-button .icon-1x1-xxsmall {
  display: grid;
  place-items: center;

  /* A definite square, so the box is not whatever the SVG's intrinsic sizing
     happens to produce. Rotated 45 degrees a square shows its diagonal, 1.41x
     its side, so 1rem inside a 2rem disc reads as 1.41rem and fills it the way
     1.2rem would if it never turned. Size it here, not in Designer. */
  width:  var(--cta-arrow-size, 1rem);
  height: var(--cta-arrow-size, 1rem);
  line-height: 0;

  transform: rotate(var(--cta-rest));
  transition: transform var(--cta-time) var(--ease-emphasis);
}
.div-cta-button .icon-1x1-xxsmall::before,
.div-cta-button .icon-1x1-xxsmall::after { content: none; }

/* Webflow's global svg rule is max-width:none; height:auto, which leaves the
   size to the SVG's own attributes. Yours carries none, so say it here. */
.div-cta-button .icon-1x1-xxsmall > svg {
  display: block;
  width: 100%;
  height: 100%;
}

:is(.div-cta-button:hover, .div-cta-button:focus-within, a:hover) .div-cta-button-icon {
  background-color: var(--cta-disc-hover);
  color: var(--cta-arrow-hover);
}

:is(.div-cta-button:hover, .div-cta-button:focus-within, a:hover) .icon-1x1-xxsmall {
  transform: rotate(var(--cta-hover));
}

/* On a light ground. Same structure, different values: the disc goes to ink
   instead of white, and the arrow inverts the other way. */
.div-cta-button.is-light {
  --cta-pill:        rgba(17, 17, 16, .05);
  --cta-pill-hover:  rgba(17, 17, 16, .09);
  --cta-ring:        rgba(17, 17, 16, .12);
  --cta-disc:        rgba(17, 17, 16, .08);
  --cta-disc-hover:  #141414;
  --cta-arrow:       #141414;
  --cta-arrow-hover: #ffffff;
}

@media (prefers-reduced-motion: reduce) {
  .div-cta-button,
  .div-cta-button-icon,
  .div-cta-button .icon-1x1-xxsmall { transition: none; }
}


/* ==========================================================================
   17. CLAMP   [data-clamp]
   --------------------------------------------------------------------------
   Long copy cut to a fixed number of LINES, with a fade at the cut and a
   Read more that opens it on the accordion's own spring.

   Lines, not characters, and that is the whole point of the component. A
   hundred characters is two lines in one column and one line in another, so a
   character threshold leaves a three-column row uneven at exactly the widths
   where it mattered. Lines are what the eye is levelling, so lines are what the
   component counts, and it re-counts them on resize because the same copy
   rewraps at every breakpoint.

   Markup contract, all in Designer, no Embed
     data-clamp="5"            the text block. The value is the line count
     data-clamp-more="..."     button label when closed  (default "Read more")
     data-clamp-less="..."     button label when open    (default "Read less")
     data-clamp-class="..."    classes put on the button
                               (default "text-color-black text-size-xsmall";
                                set it to "" for no class at all)

   The button is created by the script and only when the text ACTUALLY
   overflows. A Read more that opens nothing is worse than no Read more, and at
   some widths the same paragraph fits.

   Motion is the accordion's, not a copy of it: both read the curve out of
   rsSpring() in the JS tab, so retuning one retunes the other.
   ========================================================================== */

/* The fade has to animate, and a custom property only animates if its type is
   declared. Without this the mask would jump from faded to clear in one frame
   while the height glides, which reads as two separate things happening. */
@property --clamp-fade {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

[data-clamp] {
  --clamp-fade: 0px;
  overflow: hidden;

  /* Set by the script from the element's own measured line-height, so the cut
     lands on a baseline rather than part-way through a line of text. */
  max-height: var(--clamp-h, none);

  /* Alpha, not a coloured overlay. A gradient to the card colour would need to
     know the card colour, and would show as a smear the moment the same
     component appears on the dark sections. A mask fades to transparent and
     therefore works on any ground. */
  -webkit-mask-image: linear-gradient(to bottom,
    #000 calc(100% - var(--clamp-fade)), transparent 100%);
          mask-image: linear-gradient(to bottom,
    #000 calc(100% - var(--clamp-fade)), transparent 100%);

  transition:
    max-height    var(--clamp-dur, 520ms) var(--clamp-ease, ease),
    --clamp-fade  var(--clamp-dur, 520ms) var(--clamp-ease, ease);
}

/* Closed. The fade is a little over two lines so it reads as text receding
   rather than as a band laid across the last one. */
[data-clamp].is-clamped { --clamp-fade: 2.25em; }

/* Open and settled. The script releases the height to none once the transition
   ends, so a resize or a late font swap cannot leave the panel clipped at the
   height it happened to measure. */
[data-clamp].is-open { --clamp-fade: 0px; }

/* Nothing is hidden before the script has measured, so a visitor with no JS
   reads the whole paragraph instead of a permanently truncated one. */
[data-clamp]:not(.is-clamped):not(.is-open) {
  max-height: none;
  -webkit-mask-image: none;
          mask-image: none;
}

/* ── the control ──
   A real button: it is operated by keyboard and read by screen readers, and a
   div with a click handler is neither.

   Everything to do with type lives inside :where(), which carries zero
   specificity. That is the point. The label has to read as part of the copy it
   belongs to, so the script measures the real font off the clamped text and
   feeds it in through the custom properties below; because the rule has no
   specificity, any class you put on the button in Designer still wins over it.

   Colour and size are deliberately left alone here and handed to Designer
   classes instead: data-clamp-class, "text-color-black text-size-xsmall"
   unless you say otherwise. A Webflow class beats a zero-specificity rule
   whichever order the stylesheets load in, which a plain
   .rs-clamp-toggle { color: ... } would not.

   Letter-spacing is measured off the copy but handed over in em rather than
   px, so it stays in proportion when a size class makes the label smaller than
   the paragraph it belongs to. Leading is set to 1 rather than measured: the
   label is one line, and the paragraph's leading would only pad the button. */
:where(.rs-clamp-toggle) {
  font-family:    var(--clamp-ff, inherit);
  font-size:      var(--clamp-fs, inherit);
  font-weight:    var(--clamp-fw, inherit);
  font-style:     var(--clamp-fi, inherit);
  line-height:    1;
  letter-spacing: var(--clamp-ls, inherit);
  text-transform: var(--clamp-tt, inherit);
  color: inherit;
}
.rs-clamp-toggle {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  margin-top: .625rem;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: opacity var(--time-quick) var(--ease-emphasis, ease);
}

/* Full strength at rest, so the colour on the page is exactly the colour the
   class sets. The dip belongs to the hover, not to the resting state. */
.rs-clamp-toggle:hover { opacity: .6; }
.rs-clamp-toggle:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

/* The chevron turns rather than the label changing shape, which is the same
   gesture the accordion makes and on the same curve. */
.rs-clamp-toggle svg {
  width: .875em;
  height: .875em;
  display: block;
  transition: transform var(--clamp-dur, 520ms) var(--clamp-ease, ease);
}
.rs-clamp-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
  [data-clamp],
  .rs-clamp-toggle svg { transition: none; }
}


/* ==========================================================================
   18. UNDERLINE  [data-underline]
   --------------------------------------------------------------------------
   An underline that draws itself on hover. No library and no JS: the whole
   component is two pseudo-elements and a transform, which is why it costs
   nothing and cannot fall out of sync with anything else on the page.

   Markup contract, all in Designer
     data-underline            wipe in from the left   (the default)
     data-underline="centre"   grow out from the middle
     data-underline="swap"     resting line leaves right, new one enters left
     data-underline="out"      starts underlined, wipes away on hover
     data-underline="through"  in from the left, out to the right (uses the JS tab)
     data-underline="wrap"     for a link inside a paragraph, see below

   Tunable on the element itself or on any ancestor, so a whole footer can be
   set once:
     --ul-size    thickness            default 1px  (try .06em to scale with type)
     --ul-colour  line colour          default currentColor
     --ul-gap     distance below text  default .14em
     --ul-dur     duration             default 420ms

   Transforms, not width, so the work happens on the compositor and a row of
   twenty links costs the same as one.

   Why there are two kinds. The first four variants are a pseudo-element, which
   is a single box and therefore cannot break across lines: right for a nav
   item, a button or a standalone link, wrong for a link in the middle of a
   paragraph, where it would draw one line under both halves. "wrap" paints the
   line as a background instead, so each line fragment gets its own and each
   draws from its own left edge.

   Every :not() below is wrapped in :where(), which contributes no specificity.
   Without that, the base rule would outrank the variant rules that are meant
   to override it and "out" would resolve to the default.
   ========================================================================== */

[data-underline] {
  --ul-size:   1px;
  --ul-colour: currentColor;
  --ul-gap:    .14em;
  --ul-dur:    var(--time-base);
  --ul-ease:   var(--ease-emphasis, cubic-bezier(.625, .05, 0, 1));

  position: relative;
  display: inline-block;
  color: inherit;

  /* The component IS the underline, so the browser's own one would double it.
     Removed here rather than in Designer so the two cannot disagree. */
  text-decoration: none;
}

[data-underline]:where(:not([data-underline="wrap"]))::before,
[data-underline]:where(:not([data-underline="wrap"]))::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--ul-gap) * -1);
  height: var(--ul-size);
  background: var(--ul-colour);
  pointer-events: none;

  /* X only, so a 1px line stays 1px however far it stretches. Scaling a box
     and its border together is what makes most versions of this effect go
     soft at the ends. */
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--ul-dur) var(--ul-ease);
}

/* ::before belongs to "swap" alone. Kept out of the way everywhere else so the
   default variant draws one line and not two stacked. */
[data-underline]:where(:not([data-underline="swap"]))::before { content: none; }

/* Keyboard gets what the mouse gets. A hover-only effect is an effect some of
   your visitors never see. */
[data-underline]:hover::after,
[data-underline]:focus-visible::after { transform: scaleX(1); }

/* ── centre ── */
[data-underline="centre"]::after { transform-origin: center; }

/* ── out ──
   The inverse: underlined at rest, and the line retreats the way the cursor
   came. Reads as the link stepping forward rather than reacting. */
[data-underline="out"]::after {
  transform: scaleX(1);
  transform-origin: right center;
}
[data-underline="out"]:hover::after,
[data-underline="out"]:focus-visible::after { transform: scaleX(0); }

/* ── swap ──
   Two lines, handed off. The delay sits on the incoming line while hovering
   and on the returning line while leaving, so the exchange runs the same way
   round in both directions instead of the two crossing in the middle. */
[data-underline="swap"]::before {
  transform: scaleX(1);
  transform-origin: right center;
  transition-delay: calc(var(--ul-dur) * .35);
}
[data-underline="swap"]:hover::before,
[data-underline="swap"]:focus-visible::before {
  transform: scaleX(0);
  transition-delay: 0ms;
}
[data-underline="swap"]:hover::after,
[data-underline="swap"]:focus-visible::after {
  transition-delay: calc(var(--ul-dur) * .35);
}

/* ── wrap ──
   Stays display:inline so the line can break, and the line is a background
   whose width animates. box-decoration-break:clone is what gives each fragment
   its own background box; without it the first line takes the whole gradient
   and the second gets nothing. */
[data-underline="wrap"] {
  --ul-gap: .06em;
  display: inline;
  background-image: linear-gradient(var(--ul-colour), var(--ul-colour));
  background-repeat: no-repeat;
  background-size: 0% var(--ul-size);
  background-position: 0 calc(100% - var(--ul-gap));
  transition: background-size var(--ul-dur) var(--ul-ease);
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
}
[data-underline="wrap"]:hover,
[data-underline="wrap"]:focus-visible { background-size: 100% var(--ul-size); }

/* ── through ──
   Comes in from the left, leaves to the right. Two states, two different end
   points, which is more than CSS hover can express on its own: :hover has one
   resting state, and the resting state is where the line must both start and
   finish.

   The pure-CSS answer is to flip transform-origin instead of animating it. At
   rest the origin is the right edge and the line is scaled to nothing, so the
   flip to the left edge on hover happens at zero width and cannot be seen;
   the flip back happens at full width, where it cannot be seen either. The
   line therefore grows rightward and shrinks rightward.

   That version is correct as long as the animation is allowed to finish. Pull
   the cursor away halfway and the origin flips while the line is half drawn,
   which moves it bodily across the word. This is the jump you were looking at.

   So this block is the baseline, and initUnderline() in the JS tab upgrades it
   for browsers that can do better. No JS, no upgrade, no jump you would notice
   unless you were hunting for it. */
[data-underline="through"]::after { transform-origin: right center; }
[data-underline="through"]:hover::after,
[data-underline="through"]:focus-visible::after { transform-origin: left center; }

/* The upgrade. The script appends a real element, because a pseudo-element is
   not addressable from script and the only way to reach one is through an
   inherited custom property, which would mean putting a transition shorthand
   on the link itself and quietly killing whatever colour transition Designer
   has on it.

   clip-path rather than a transform, so both edges of the line are independent
   values. That is the whole trick: growing animates the right edge, leaving
   animates the left edge, and an interrupted animation resumes from wherever
   the two edges currently are instead of being snapped to a new origin. */
[data-underline="through"][data-ul-js]::after { content: none; }

[data-underline="through"] > .rs-ul {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--ul-gap) * -1);
  height: var(--ul-size);
  background: var(--ul-colour);
  pointer-events: none;

  /* Closed, collapsed against the right edge. */
  clip-path: inset(0 0 0 100%);
  transition: clip-path var(--ul-dur) var(--ul-ease);
}

@media (hover: none) {
  [data-underline="through"] > .rs-ul { clip-path: inset(0 0 0 0); }
}
@media (prefers-reduced-motion: reduce) {
  [data-underline="through"] > .rs-ul { transition: none; }
}

/* ── touch ──
   There is no hover on a phone, so the line would simply never appear. Shown
   at rest instead, which is also the accessible answer for a link sitting in a
   paragraph: without it, nothing but colour marks it as a link. Delete this
   block if you are using the component on nav items only. */
@media (hover: none) {
  [data-underline]:where(:not([data-underline="wrap"]))::after { transform: scaleX(1); }
  [data-underline="swap"]::before { content: none; }
  [data-underline="out"]:hover::after,
  [data-underline="out"]:focus-visible::after { transform: scaleX(1); }
  [data-underline="wrap"] { background-size: 100% var(--ul-size); }
}

@media (prefers-reduced-motion: reduce) {
  [data-underline]::before,
  [data-underline]::after,
  [data-underline="wrap"] { transition: none; }
}


/* ==========================================================================
   19. STICKY CTA  [data-sticky]
   --------------------------------------------------------------------------
   A fixed button that rises into place once the reader has got somewhere, and
   drops back out once they have arrived somewhere else.

   The motion is section 15's, not an imitation of it: both read --ease-reveal
   and --time-reveal out of the tokens, so retuning the reveal retunes this and
   the two can never drift apart.

   Markup contract, all in Designer
     data-sticky              the fixed button, or its wrapper
     data-sticky-from         appears once this element is behind you, that
                              is once its bottom edge clears the top of the
                              screen. Put it on the hero.
     data-sticky-until        leaves once this element is reached, that is
                              once its top edge crosses the trigger line. Put
                              it on the footer or the contact block.
     data-sticky-line="0.9"   where that line sits in the viewport, as a
                              fraction of its height. 0.9 is the line
                              [data-reveal] fires on. Optional, affects
                              "until" only.

   A trigger can go on any element, including an empty Div dropped at the exact
   point you want the button to appear. For a zero-height marker the two rules
   above are the same rule, which makes it the most predictable placement of
   the three.

   To run two of them on one page, give each a name and repeat the name on its
   triggers: data-sticky="pricing" with data-sticky-from="pricing".

   Both triggers are optional. No "from" and it is eligible from the top of the
   page; no "until" and it never leaves.

   One difference from section 15 worth stating. The reveal fades in through a
   blur and this does not, deliberately. A filter makes an element a containing
   block for anything fixed inside it and pins it to its own composited layer,
   which is a fair price for a heading that animates once and is then cleaned
   up, and a poor one for an element that sits on screen for the whole visit.
   ========================================================================== */

[data-sticky] {
  --sticky-ease: var(--ease-reveal, cubic-bezier(.27, .09, .24, 1));
  --sticky-time: var(--time-reveal, .7s);

  /* Further than the reveal's 10px, because this one is travelling from
     outside the viewport edge rather than from just under its final spot. */
  --sticky-rise: 1.5rem;

  opacity: 0;
  transform: translateY(var(--sticky-rise));

  /* Hidden properly, not merely transparent: a 0-opacity button still takes
     keyboard focus and still reads out to a screen reader. visibility is not
     interpolated, it is switched at the end of the timing it is given, which
     is why it is held for the length of the fade on the way out and released
     immediately on the way in. */
  visibility: hidden;
  pointer-events: none;

  transition:
    opacity    var(--sticky-time) var(--sticky-ease),
    transform  var(--sticky-time) var(--sticky-ease),
    visibility 0s linear var(--sticky-time);
}

[data-sticky].is-on {
  opacity: 1;
  transform: none;
  visibility: visible;
  pointer-events: auto;
  transition:
    opacity    var(--sticky-time) var(--sticky-ease),
    transform  var(--sticky-time) var(--sticky-ease),
    visibility 0s;
}

/* Nothing is hidden until the script has decided what the state should be, so
   a visitor with no JS keeps a working button rather than losing it. */
[data-sticky]:not([data-sticky-ready]) {
  opacity: 1;
  transform: none;
  visibility: visible;
  pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
  [data-sticky] { transition: opacity .2s linear, visibility 0s linear .2s; }
  [data-sticky].is-on { transition: opacity .2s linear, visibility 0s; }
  [data-sticky],
  [data-sticky].is-on { transform: none; }
}