/**
 * /pricing/ — Bundles & Pricing page skin.
 *
 * Enqueued only on the pricing page (see functions.php). Lives here rather than
 * in an Elementor HTML widget on purpose: widget content is run through the
 * stack's sanitizer, which escapes bare ">" (killing child combinators) and
 * strips style-attribute declarations that use rgba(). A real stylesheet has
 * neither limitation, so this file uses normal CSS.
 *
 * Card/section boxes (background, border, padding, radius) still come from the
 * Elementor document — its generated rules out-specify anything reasonable here.
 * This file owns everything inside the boxes plus the carousel and the buttons.
 *
 * Companion JS: js/pricing.js (Flickity carousel + gradient-button blobs).
 */

.oa-pricing {
  --oa-orange: #FE7C10;
  --oa-ink: #030304;
  --oa-card: #111218;
  --oa-card-lift: #1A1B22;
  --oa-hair: rgba(255, 255, 255, 0.10);
  --oa-hair-soft: rgba(255, 255, 255, 0.07);
  /* Three text colours, not six. Body copy was #A6ACBF at 13.5px, which is
     legible but tiring on a near-black page; it is brighter and larger now. */
  --oa-txt: #FFFFFF;      /* headings, prices, emphasis */
  --oa-sub: #D3D9E6;      /* body copy */
  --oa-faint: #99A1B5;    /* meta, prices in rows, hints */
  /* One type scale. Nothing on this page may invent a size outside it. */
  --oa-fs-micro: 11px;    /* uppercase labels */
  --oa-fs-meta: 13px;     /* hints, secondary lines */
  --oa-fs-body: 14.5px;   /* body copy and list items */
  --oa-fs-lead: 17px;     /* section intros */
  --oa-lh-body: 1.6;
  /* The hero's dot pitch. Hoisted out of .oa-hero::before so the spark field
     and the beam lanes can key off the same lattice instead of guessing it. */
  --oa-grid: 26px;
  --oa-sans: 'Libre Franklin', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --oa-serif: 'Source Serif 4', Georgia, serif;
  /* Card corner radius, mirrored from the Elementor document (gen_pricing.py,
     plan_card) so the decoration layers can round to match it. */
  --oa-card-r: 14px;
}

/* Hosts for absolutely-positioned decoration (orbs, bookmark ribbons). The
   widget itself must not take part in layout. */
.elementor-widget-html.oa-style-host { display: none; }
.elementor-widget-html.oa-fx-host {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  margin: 0;
}
/* Same idea, one layer down: decoration that belongs BEHIND the copy (the card
   spotlight, the border beam). A negative-z child paints above its parent's own
   background and border but below the parent's in-flow content — which is
   exactly the layer wanted — provided the parent is a stacking context, hence
   the `isolation` on .oa-plan further down.
   `inset: 0` is already the whole card interior: an absolutely positioned box
   resolves against its ancestor's padding box, whose edge is the inside of the
   border — the card's padding is inside that box, not outside it. */
.elementor-widget-html.oa-glow-host {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  margin: 0;
  border-radius: calc(var(--oa-card-r) - 1px);
}

/* Sections 1 (gradient buttons) and 03 (the package finder) are not missing —
   they MOVED to css/chooser.css, which this file names as an enqueue
   dependency so it still loads first and the cascade order is unchanged. They
   left because the finder now has a second home: a site-wide popup opened by
   every `#quiz-` link, rendered outside this page entirely. The numbering below
   is the original's, kept so the section names still match spec.json's order.
   =========================================================================== */

/* ===========================================================================
   2. Hero
   ---------------------------------------------------------------------------
   The lamp is the hero, and everything else defers to it. Back to front:
     .oa-aurora  — 21st.dev "Aurora Background" (@manuarora700, #1120) ported to
                   plain CSS: two repeating-linear-gradients (canvas bands over a
                   colour ribbon), a second copy on ::after travelling at another
                   rate, composited with mix-blend-mode: difference so the two
                   interfere into a slow shimmer. Their light/dark pair is
                   dropped (this page is dark only) and so is their
                   background-attachment: fixed — it repaints on scroll, it is
                   broken on iOS, and inside an overflow:hidden band it buys
                   nothing. Held at low opacity: it is a warm wash behind the
                   lamp, not a second light source.
     .oa-lamp    — the composition. See its own block below.
     .oa-orbs    — one slow warm breath under the headline.
   A twinkling spark lattice and five "Shooting Stars Grid" beam runners lived
   here until 2026-08 and were cut with their CSS: against the lamp they read as
   clutter, and the source this hero follows has neither.
   Everything is CSS. With JS off the hero still animates; with reduced motion
   asked for it holds as a composed static field (see the block at the foot of
   this file).
   =========================================================================== */
/* --px/--py are the pointer bias pricing.js writes. They default to 0, so with
   JS off nothing shifts and the hero is simply its own animation. */
.oa-hero { position: relative; overflow: hidden; --px: 0; --py: 0; }
.oa-hero .e-con { position: relative; z-index: 2; }
.oa-heroback { position: absolute; inset: 0; }
/* The band ended on a hard line against the packages section below it, which
   undid the sense of a lit space. ::after was free (only ::before was taken). */
.oa-hero::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 200px;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, #030304 88%);
}

/* Offscreen the whole backdrop holds still. None of it is visible once the
   packages are in view, and five beams plus two gradient layers plus three
   blurred orbs are cheap rather than free. */
.oa-hero.is-paused .oa-aurora::after,
.oa-hero.is-paused .oa-orbs span,
.oa-hero.is-paused .oa-lamp .glow,
.oa-hero.is-paused::before { animation-play-state: paused; }

/* A dot field drifting behind everything — magicui's grid pattern, masked to an
   ellipse so it fades out well before the band edges instead of ending on a
   hard line. Its pitch is the lattice the sparks and beams key off. */
.oa-hero::before {
  content: "";
  position: absolute;
  inset: -30px 0;
  z-index: 0;
  background-image: radial-gradient(rgba(255, 255, 255, .055) 1px, transparent 1px);
  background-size: var(--oa-grid) var(--oa-grid);
  -webkit-mask-image: radial-gradient(ellipse 62% 58% at 50% 42%, #000 12%, transparent 72%);
  mask-image: radial-gradient(ellipse 62% 58% at 50% 42%, #000 12%, transparent 72%);
  animation: oaGridDrift 30s linear infinite;
  pointer-events: none;
}
@keyframes oaGridDrift {
  from { background-position: 0 0; }
  to   { background-position: var(--oa-grid) calc(var(--oa-grid) * 2); }
}

/* --- aurora ribbon ------------------------------------------------------- */
.oa-aurora {
  /* Their --dark-gradient / --aurora pair, our palette: the brand ramp already
     used by the button blobs. */
  --oa-au-veil: repeating-linear-gradient(100deg,
      #030304 0%, #030304 7%, transparent 10%, transparent 12%, #030304 16%);
  --oa-au-band: repeating-linear-gradient(100deg,
      #FE7C10 10%, #FFB454 15%, #B3550A 20%, #FFD9A8 25%, #FF4D14 30%);
  position: absolute;
  inset: -10px;
  z-index: 0;
  pointer-events: none;
  /* Pulled back from .40: the lamp is now the hero's light source and the
     ribbon sits behind it, so at full strength the two orange fields muddied
     each other across the top of the band. */
  opacity: .26;
  filter: blur(10px);
  will-change: transform;
  background-image: var(--oa-au-veil), var(--oa-au-band);
  background-size: 300%, 200%;
  background-position: 50% 50%, 50% 50%;
  /* Theirs hangs off the top-right. This hero is centre-aligned, so the ribbon
     hangs off the top edge and fades out before it reaches the copy. */
  -webkit-mask-image: radial-gradient(ellipse 95% 72% at 50% -6%, #000 8%, transparent 68%);
  mask-image: radial-gradient(ellipse 95% 72% at 50% -6%, #000 8%, transparent 68%);
  /* Pointer bias. will-change above promotes this to its own layer, so the
     blur is rasterised once and only the layer moves. */
  transform: translate3d(calc(var(--px) * 14px), calc(var(--py) * 10px), 0);
  transition: transform .9s cubic-bezier(.2, .8, .2, 1);
}
/* Guarded: without mix-blend-mode this layer paints opaque over the base and
   the ribbon becomes a solid slab across the top of the page. */
@supports (mix-blend-mode: difference) {
  .oa-aurora::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: var(--oa-au-veil), var(--oa-au-band);
    background-size: 200%, 100%;
    mix-blend-mode: difference;
    animation: oaAurora 60s linear infinite;
  }
}
@keyframes oaAurora {
  from { background-position: 50% 50%, 50% 50%; }
  to   { background-position: 350% 50%, 350% 50%; }
}

/* --- lamp ---------------------------------------------------------------- */
/* After 21st.dev "Lamp" (@manuarora700). Their cone is two conic-gradient boxes
   with overlay masks; transcribed literally onto this markup it rendered as a
   lit rectangle rather than thrown light, and washed the eyebrow out — so the
   cone here is built the way the effect actually works instead: an ellipse of
   light anchored ON the filament, spreading and fading downward, with a second
   hotter pass over the middle. Same composition, far fewer moving parts, and it
   holds its shape at any width instead of depending on two 480px boxes meeting.

   Every layer is placed against --lamp-y, the filament. The hero's top padding
   is set to clear it so the copy reads UNDER the light — that relationship is
   the whole composition; without it the lamp is a stripe behind a headline. */
.oa-lamp {
  position: absolute;
  inset: 0 0 auto 0;
  height: 620px;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* The filament. It used to sit at 196px, which on a phone put it 145px below
     a 51px header — a lamp hung so low it read as a stripe across the middle of
     the copy rather than a light above it (owner, 2026-08: "the line ... needs
     to be higher"). The mobile override is in the max-767 block at the foot of
     this file, together with the matching hero padding-top. */
  --lamp-y: 150px;          /* the filament */
  --lamp-w: 620px;          /* filament length */
}
/* The hero's top padding exists to clear the filament (see .oa-lamp above), so
   it moves with it. Elementor's own rule for this container is
   `.elementor-23142 .elementor-element.elementor-element-00b0009` — three
   classes, the same weight as the selector below, and it is printed earlier in
   the head (elementor-post-23142 at 21, oversee-pricing at 25), so the tie
   resolves here. The doubled `.e-con` is what buys the third class. */
.oa-hero.e-con.e-con { --padding-top: 244px; }
/* The spread. */
.oa-lamp .spread {
  position: absolute;
  top: var(--lamp-y);
  left: 50%;
  width: 1180px;
  margin-left: -590px;
  height: 380px;
  background: radial-gradient(ellipse 40% 100% at 50% 0%,
              rgba(254, 124, 16, .42) 0%, rgba(254, 124, 16, .13) 44%, transparent 74%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 88%);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 88%);
  animation: oaLampWedge 1.1s cubic-bezier(.4, 0, .2, 1) .15s both;
}
/* The hot centre of it, tighter and brighter. */
.oa-lamp .hot {
  position: absolute;
  top: var(--lamp-y);
  left: 50%;
  width: 620px;
  margin-left: -310px;
  height: 260px;
  background: radial-gradient(ellipse 46% 100% at 50% 0%,
              rgba(255, 180, 84, .40) 0%, rgba(254, 124, 16, .10) 52%, transparent 78%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 84%);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 84%);
  animation: oaLampWedge 1.1s cubic-bezier(.4, 0, .2, 1) .25s both;
}
/* The bloom sitting on the line. */
.oa-lamp .glow {
  position: absolute; top: var(--lamp-y); left: 50%; width: 560px; height: 120px;
  margin-left: -280px; transform: translateY(-50%);
  border-radius: 50%; background: #FE7C10; opacity: .30; filter: blur(72px); z-index: 2;
}
.oa-lamp .core {
  position: absolute; top: var(--lamp-y); left: 50%; width: 260px; height: 76px;
  margin-left: -130px; transform: translateY(-50%);
  border-radius: 50%; background: #FFB454; opacity: .5; filter: blur(38px); z-index: 3;
  animation: oaLampCore 1.1s cubic-bezier(.4, 0, .2, 1) .15s both;
}
/* The filament itself: the one genuinely bright thing in the band. */
.oa-lamp .line {
  position: absolute; top: var(--lamp-y); left: 50%; width: var(--lamp-w); height: 2px;
  margin-left: calc(var(--lamp-w) / -2);
  background: linear-gradient(90deg, transparent, rgba(255, 217, 168, .55) 14%,
              #FFD9A8 38%, #FFFFFF 50%, #FFD9A8 62%, rgba(255, 217, 168, .55) 86%, transparent);
  z-index: 6;
  box-shadow: 0 0 26px rgba(255, 180, 84, .55);
  animation: oaLampLine 1.1s cubic-bezier(.4, 0, .2, 1) .15s both;
}
/* The lamp keeps breathing after it has arrived. A hero that finishes its
   entrance and then holds perfectly still reads as a screenshot; this is slow
   and small enough to register as presence rather than motion. */
.oa-lamp .glow { animation: oaLampBreathe 9s ease-in-out 1.4s infinite; }
.oa-lamp .line { will-change: opacity; }
@keyframes oaLampBreathe {
  0%, 100% { opacity: .30; transform: translateY(-50%) scale(1); }
  50%      { opacity: .44; transform: translateY(-50%) scale(1.06); }
}

/* pricing.js drops this class on the frame after the hero is on screen, which
   restarts every entrance animation from zero. See initHeroEntrance(). */
.oa-hero.oa-replay .oa-h1 .w i,
.oa-hero.oa-replay .oa-lamp .spread,
.oa-hero.oa-replay .oa-lamp .hot,
.oa-hero.oa-replay .oa-lamp .line,
.oa-hero.oa-replay .oa-lamp .core,
.oa-hero.oa-replay .oa-lamp .glow,
.oa-hero.oa-replay .elementor-widget:not(.oa-fx-host) { animation: none !important; }

@keyframes oaLampWedge { from { opacity: 0; transform: scaleX(.55); } to { opacity: 1; transform: scaleX(1); } }
@keyframes oaLampLine  { from { width: 260px; margin-left: -130px; } to { width: var(--lamp-w); margin-left: calc(var(--lamp-w) / -2); } }
@keyframes oaLampCore  { from { width: 130px; margin-left: -65px; } to { width: 260px; margin-left: -130px; } }

/* --- orbs ---------------------------------------------------------------- */
.oa-orbs { position: absolute; inset: 0; z-index: 1; }
.oa-orbs span { position: absolute; border-radius: 50%; filter: blur(80px); opacity: .32; }
/* .o1 and .o2 lived here with an oaDrift keyframe of their own and were dead:
   gen_pricing.py emits `<div class="oa-orbs"><span class="o3"></span></div>`
   and never has emitted anything else. Removed rather than left as a trap for
   whoever next wonders why editing them changes nothing. */
.oa-orbs .o3 {
  width: 520px; height: 300px; background: #FF4D14; top: 34%; left: 50%;
  opacity: .13; filter: blur(120px);
  animation: oaBreathe 12s ease-in-out infinite;
  /* .o3's transform is owned by oaBreathe, so its pointer bias rides on the
     margin instead — the two would otherwise overwrite each other. */
  margin-left: calc(-260px + var(--px) * 26px);
  transition: margin-left .9s cubic-bezier(.2, .8, .2, 1);
}
@keyframes oaBreathe {
  0%, 100% { transform: scale(1)    translateY(0);     opacity: .10; }
  50%      { transform: scale(1.18) translateY(-14px); opacity: .18; }
}

/* --- eyebrow pill -------------------------------------------------------- */
.oa-eyerow { display: flex; justify-content: center; margin: 0 0 16px; }
.oa-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px 6px 11px;
  border-radius: 999px;
  border: 1px solid rgba(254, 124, 16, .30);
  background: rgba(254, 124, 16, .08);
  color: var(--oa-orange);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-micro);
  font-weight: 800;
  letter-spacing: 2px;
  text-transform: uppercase;
  line-height: 1.4;
}
.oa-eyebrow .pulse {
  position: relative;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--oa-orange);
  flex: none;
}
.oa-eyebrow .pulse::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--oa-orange);
  animation: oaPing 2.4s cubic-bezier(0, 0, .2, 1) infinite;
}
@keyframes oaPing {
  0%        { transform: scale(1);   opacity: .8; }
  70%, 100% { transform: scale(2.8); opacity: 0; }
}

/* --- headline: word-by-word rise ---------------------------------------- */
.oa-h1 {
  margin: 0 0 18px;
  text-align: center;
  color: #fff;
  font-family: var(--oa-sans);
  font-weight: 800;
  font-size: 56px;
  line-height: 1.08;
  letter-spacing: -1.4px;
}
/* The source clips a gradient to its headline text. That cannot be done on this
   markup: background-clip:text paints only the element's OWN text, and every
   word here is inside a .w > i pair for the rise animation — so the descendants
   inherited a transparent fill with no gradient behind them and the whole line
   vanished, leaving only the orange reel word. Verified in a browser before
   reverting. The gradient goes on the words themselves instead. */
.oa-h1 .w i {
  background: linear-gradient(to bottom, #FFFFFF 30%, #B4BBCC 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .oa-h1 .w i { background: none; -webkit-text-fill-color: #fff; color: #fff; }
}
/* The clip window must not eat descenders: pad the box down and pull the same
   amount back off the margin, so the line box is unchanged. */
.oa-h1 .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: .14em;
  margin-bottom: -.14em;
}
.oa-h1 .w i {
  display: inline-block;
  font-style: inherit;
  transform: translateY(118%);
  animation: oaWordUp .82s cubic-bezier(.16, 1, .3, 1) both;
}
.oa-h1 .w:nth-child(1) i { animation-delay: .06s; }
.oa-h1 .w:nth-child(2) i { animation-delay: .13s; }
.oa-h1 .w:nth-child(3) i { animation-delay: .20s; }
.oa-h1 .w:nth-child(4) i { animation-delay: .27s; }
.oa-h1 .w:nth-child(5) i { animation-delay: .34s; }
.oa-h1 .w:nth-child(6) i { animation-delay: .41s; }
.oa-h1 .w:nth-child(7) i { animation-delay: .48s; }
.oa-h1 .w:nth-child(8) i { animation-delay: .55s; }
@keyframes oaWordUp {
  from { transform: translateY(118%); }
  to   { transform: translateY(0); }
}

/* --- headline: slot reel ------------------------------------------------- */
.oa-h1 .oa-rot {
  position: relative;
  display: inline-block;
  overflow: hidden;
  height: 1.18em;
  vertical-align: bottom;
  text-align: left;
}
.oa-h1 .oa-reel {
  display: grid;                       /* one column: width = widest word */
  animation: oaSlot4 14s cubic-bezier(.83, 0, .17, 1) .9s infinite;
}
.oa-h1 .oa-reel i {
  height: 1.18em;
  line-height: 1.18em;
  font-family: var(--oa-serif);
  font-style: italic;
  font-weight: 700;
  color: var(--oa-orange);
  /* Opts out of the h1's clipped gradient — otherwise this word inherits a
     transparent fill and disappears. */
  -webkit-text-fill-color: var(--oa-orange);
  letter-spacing: -0.5px;
  white-space: nowrap;
}
/* Four spec words plus the repeated first = five rows, so the reel travels
   4 x 1.18em and lands on a frame identical to its start. The oa-nN class the
   generator emits is what makes a spec of a different length fail loudly:
   add oaSlot3/5/6 to match. */
@keyframes oaSlot4 {
  0%,   18%  { transform: translateY(0); }
  22%,  40%  { transform: translateY(-1.18em); }
  44%,  62%  { transform: translateY(-2.36em); }
  66%,  84%  { transform: translateY(-3.54em); }
  88%, 100%  { transform: translateY(-4.72em); }
}
/* A soft bar that flares on each swap, so the roll reads as a mechanism. */
.oa-h1 .oa-reelglow {
  position: absolute;
  left: 0; right: 0; bottom: .06em;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--oa-orange), transparent);
  transform: scaleX(.2);
  opacity: 0;
  animation: oaReelFlare 14s linear .9s infinite;
}
@keyframes oaReelFlare {
  0%, 17%   { transform: scaleX(.2); opacity: 0; }
  20%       { transform: scaleX(1);  opacity: .9; }
  24%, 39%  { transform: scaleX(.2); opacity: 0; }
  42%       { transform: scaleX(1);  opacity: .9; }
  46%, 61%  { transform: scaleX(.2); opacity: 0; }
  64%       { transform: scaleX(1);  opacity: .9; }
  68%, 83%  { transform: scaleX(.2); opacity: 0; }
  86%       { transform: scaleX(1);  opacity: .9; }
  90%, 100% { transform: scaleX(.2); opacity: 0; }
}

/* --- primary CTA sheen (::before and ::after on .oa-gbtn were both free) -- */
.oa-gbtn.is-live::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 22;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 42%, rgba(255, 255, 255, .34) 50%,
              transparent 58%);
  background-size: 260% 100%;
  background-repeat: no-repeat;
  animation: oaSheen 4.6s cubic-bezier(.4, 0, .2, 1) infinite;
}
@keyframes oaSheen {
  0%, 55%  { background-position: 190% 0; }
  100%     { background-position: -90% 0; }
}

/* --- scroll cue ---------------------------------------------------------- */
.oa-scrollcue {
  display: block;
  width: 22px;
  margin: 30px auto 0;
  opacity: .7;
  transition: opacity .25s ease;
}
.oa-scrollcue:hover, .oa-scrollcue:focus-visible { opacity: 1; }
.oa-scrollcue .m {
  display: block;
  height: 34px;
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 12px;
  position: relative;
}
.oa-scrollcue .m i {
  position: absolute;
  top: 6px; left: 50%;
  width: 3px; height: 6px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: var(--oa-orange);
  animation: oaWheel 1.8s ease-in-out infinite;
}
@keyframes oaWheel {
  0%        { transform: translateY(0);    opacity: 0; }
  25%       { opacity: 1; }
  75%, 100% { transform: translateY(14px); opacity: 0; }
}

.oa-heroctas { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }

/* Trust strip under the hero CTAs. */
.oa-trust {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px 26px;
  margin: 26px 0 0;
  padding: 0;
  list-style: none;
  font-family: var(--oa-sans);
}
.oa-trust li {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--oa-faint);
  font-size: 13px;
  line-height: 1.4;
}
.oa-trust li svg { flex: none; }

/* Staggered entrance, same blur-fade as the scroll reveals. */
/* :not(.oa-fx-host) — the backdrop is itself an .elementor-widget, a sibling of
   the copy column rather than a child of it, so the bare selector was sliding
   and un-blurring the entire hero background in alongside the headline. */
.oa-hero .elementor-widget:not(.oa-fx-host) {
  animation: oaBlurFadeIn .85s cubic-bezier(.22, .65, .3, 1) both;
}
.oa-hero .oa-fx-host { animation: oaFxIn 1.4s ease both; }
@keyframes oaFxIn { from { opacity: 0; } to { opacity: 1; } }
.oa-hero .elementor-widget:nth-child(2) { animation-delay: .09s; }
.oa-hero .elementor-widget:nth-child(3) { animation-delay: .18s; }
.oa-hero .elementor-widget:nth-child(4) { animation-delay: .27s; }
.oa-hero .elementor-widget:nth-child(5) { animation-delay: .36s; }
/* 6 = the scroll cue. Without this it inherits the base rule's 0s delay and
   arrives before the headline it is meant to follow. */
.oa-hero .elementor-widget:nth-child(6) { animation-delay: .45s; }
@keyframes oaBlurFadeIn {
  from { opacity: 0; filter: blur(8px); transform: translateY(22px); }
  to   { opacity: 1; filter: blur(0); transform: translateY(0); }
}

/* ===========================================================================
   3. Plan cards + bookmark ribbon
   ---------------------------------------------------------------------------
   Ribbon geometry is iconsax's .svg-pro, ported: a 50x60 tab hanging over the
   card's top edge, a fold wedge on its left, and a 45deg rotated square in the
   card's own fill notching the bottom so it reads as a bookmark, not a chip.
   =========================================================================== */
/* `isolation` makes the card a stacking context so .oa-glow-host's negative
   z-index resolves against the card rather than escaping behind the section. */
.oa-plan { position: relative; isolation: isolate; }

.oa-plan .oa-bm {
  position: absolute;
  top: -20px;
  right: 22px;
  width: 50px;
  height: 60px;
  z-index: 12;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: var(--oa-orange);
  box-shadow: 0 14px 28px -12px rgba(254, 124, 16, .6);
}
.oa-plan .oa-bm .oa-bm-body {
  position: relative;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  height: 60px;
  border-radius: 8px;
  background: var(--oa-orange);
}
.oa-plan .oa-bm .oa-bm-body svg {
  width: 22px;
  height: 22px;
  transform: translateY(-9px);
}
/* The notch: the card's own fill, rotated 45deg, biting into the tab's foot. */
.oa-plan .oa-bm::after {
  content: "";
  position: absolute;
  bottom: -14px;
  width: 26px;
  height: 26px;
  z-index: 11;
  border-radius: 8px 0 0 0;
  border-left: 2px solid rgba(0, 0, 0, .22);
  border-top: 1px solid rgba(0, 0, 0, .22);
  background: var(--oa-card);
  transform: rotate(45deg);
}
.oa-plan.oa-feat .oa-bm::after { background: var(--oa-card-lift); }
/* The fold wedge on the tab's left edge. */
.oa-plan .oa-bm .oa-bm-fold {
  position: absolute;
  left: -13px;
  top: 2px;
  width: 18px;
  height: 18px;
  z-index: 1;
}

/* Card interior ------------------------------------------------------------ */
.oa-plan-head { font-family: var(--oa-sans); }
/* The mobile block that used to sit here was entirely dead, and had been since
   it was written. A media query adds NO specificity: every one of its
   declarations tied at one class with an unqualified counterpart written LATER
   in this same file — `.oa-titlerow { min-height: 58px }` at :861,
   `.oa-bestfor { min-height: 3.2em }` at :959, and the `.oa-tip` width at
   :1139 — so source order handed the win to the desktop rule every time. The
   phone was carrying every desktop alignment floor (pure wasted height on a
   carousel showing one card at a time) and a 264px tooltip on a 390px screen.
   All of it now lives in the max-767 block at the FOOT of this file, after
   everything it has to beat. Nothing may be added here. */
.oa-stage {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  /* The badge is taller than a bare stage label; without a floor the card
     carrying it pushed every block beneath it 8px out of line with its
     neighbours. The right inset clears the bookmark ribbon, which hangs over
     this row and nothing below it. */
  min-height: 30px;
  margin: 0 0 8px;
  padding-right: 62px;
}

/* Name on the left, price on the right, on one row. Stacked they ran the card's
   opening four blocks ~150px down a 340px column with the right half of every
   line empty; side by side they cost one row and put the two facts a reader is
   actually comparing next to each other. */
.oa-titlerow {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}
.oa-titlecol { flex: 1 1 auto; min-width: 0; }
/* All six title rows are levelled so the "Best for" line and the list under it
   start at the same height in every card — they are read across, not down.
   58px is the name (28px) + its 6px margin + a one-line category (~20px), which
   is what all six are at the 448px of content width these cards have; 82px was
   reserving a second category line none of them needs, on all of them.
   This is the floor, not the answer: levelTitles() in pricing.js measures the
   real rows and raises them all to the tallest, so a category that does wrap at
   some width cannot pull one card out of step. */
.oa-titlerow { min-height: 58px; }
.oa-pricecol {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  padding-top: 2px;
}
.oa-stage-num {
  color: var(--oa-orange);
  font-weight: 800;
  font-size: var(--oa-fs-micro);
  letter-spacing: 1.6px;
  text-transform: uppercase;
}
.oa-badge {
  display: inline-block;
  padding: 4px 11px;
  border-radius: 999px;
  background: var(--oa-orange);
  color: #fff;
  font-weight: 800;
  font-size: 9.5px;
  letter-spacing: 1.3px;
}
.oa-plan-name {
  display: block;
  margin: 0;
  color: #fff;
  font-family: var(--oa-sans);
  font-weight: 800;
  font-size: 25px;
  line-height: 1.12;
  letter-spacing: -0.6px;
}
.oa-plan-cat {
  display: block;
  margin-top: 6px;
  color: var(--oa-orange);
  font-size: var(--oa-fs-body);
  font-weight: 700;
  line-height: 1.35;
}

.oa-price {
  display: block;
  margin: 0;
  font-family: var(--oa-sans);
  line-height: 1;
  white-space: nowrap;
}
.oa-price .amt {
  font-weight: 800;
  font-size: 34px;
  letter-spacing: -1.2px;
  color: #fff;
  /* Fixed-width digits: the count-up ticker would otherwise reflow the row on
     every frame as glyph widths changed. */
  font-variant-numeric: tabular-nums;
}
.oa-price .per { font-weight: 600; font-size: 14px; color: var(--oa-faint); margin-left: 2px; }

/* Bundle pricing: what the same services cost bought separately, struck
   through, and the saving under it. The block is present on every card (empty on
   the ones with no discount) so the rows below start at a matching height. */
.oa-was {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  min-height: 24px;
  margin: 8px 0 0;
  line-height: 1.2;
  font-family: var(--oa-sans);
}
.oa-was .strike {
  color: var(--oa-faint);
  font-size: var(--oa-fs-meta);
  text-decoration: line-through;
}
.oa-was .save {
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(254, 124, 16, .14);
  color: var(--oa-orange);
  font-size: 11.5px;
  font-weight: 800;
  line-height: 1.2;
  white-space: nowrap;
}

/* Cards sit side by side and get read across, so the same block has to start at
   the same height in each one. Without these the shortest "Best for" pulled its
   card's whole stack a line up and nothing aligned.
   .oa-plan-head carried a 96px floor of its own until 2026-08; it could never
   bind, because .oa-stage's 30px plus its 8px margin plus .oa-titlerow's floor
   already exceed it on every card. Only "Best for" needs one now. */
.oa-bestfor { min-height: 3.2em; }   /* two lines */

/* "Best for" — the qualifier, so a reader can self-select in one line. */
/* "Best for" and "Move up when" share one treatment: an inline bold label and
   a line of body copy. Previously one was a grey box and the other loose text,
   which made two equivalent facts look like different kinds of thing. */
.oa-bestfor,
.oa-next {
  display: block;
  margin: 14px 0 0;
  color: var(--oa-sub);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-body);
  line-height: var(--oa-lh-body);
}
/* This line is the card's claim now, not a qualifier, so it carries the weight
   of one: full-strength text at the card's own scale. The audience sentence it
   replaced moved into the tooltip on the package name. */
.oa-bestfor {
  color: #fff;
  font-size: var(--oa-fs-lead);
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: -0.2px;
}
.oa-next { margin-top: 12px; font-size: var(--oa-fs-meta); color: var(--oa-faint); }
.oa-bestfor .k,
.oa-next .k {
  color: var(--oa-txt);
  font-weight: 800;
  margin-right: 6px;
}
.oa-next .k { color: var(--oa-sub); }
.oa-bestfor .k::after,
.oa-next .k::after { content: ":"; }

/* "The play" — what the money is actually doing. */
.oa-play {
  margin: 14px 0 0;
  color: var(--oa-sub);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-body);
  line-height: var(--oa-lh-body);
}

/* One flat list of priced rows. The uppercase discipline labels that used to
   sit in a left rail beside these rows are gone (owner, 2026-08): they cost a
   96px column on every card to name something the rows already said, and the
   rows have that width back now, so fewer of them wrap onto a second line. */
.oa-inc {
  display: flex;
  flex-direction: column;
  margin: 13px 0 0;
  padding: 11px 0 0;
  border-top: 1px solid rgba(255, 255, 255, .09);
  list-style: none;
  font-family: var(--oa-sans);
}
.oa-inc .oa-inc-item {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 2.5px 0;
}
.oa-inc .oa-inc-item .lbl {
  flex: 1;
  min-width: 0;
  color: var(--oa-sub);
  font-size: var(--oa-fs-body);
  line-height: 1.45;
}
.oa-inc .oa-inc-item .amt {
  color: var(--oa-faint);
  font-weight: 700;
  font-size: var(--oa-fs-meta);
  white-space: nowrap;
  margin-top: 2px;
}
.oa-inc .oa-inc-item svg { flex: none; margin-top: 1px; }

/* The label's last word and its info mark, welded together. Left to itself the
   mark is an inline box the browser will happily push onto a line of its own at
   the end of a wrapping row, which is what made it read as a stray character
   sitting in the margin. */
.oa-nb { white-space: nowrap; }

/* A build that ENDS says so on the card, not in a tooltip. This is the one
   thing buyers were getting wrong about the page, so it gets a tinted strip of
   its own directly under the line items it qualifies. */
.oa-payoff {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 12px 0 0;
  padding: 10px 12px;
  border: 1px solid rgba(254, 124, 16, .28);
  border-radius: 10px;
  background: rgba(254, 124, 16, .07);
  color: var(--oa-sub);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-meta);
  line-height: 1.5;
}
.oa-payoff svg { flex: none; margin-top: 2px; }

/* An optional extra, priced but NOT in the package total. It has to read as an
   offer rather than a line item, so: dashed rule instead of the solid tinted
   strip the payoff note uses, no check mark, and an "Optional" tag carrying the
   distinction in words rather than only in styling. */
.oa-addon {
  display: block;
  margin: 11px 0 0;
  padding: 10px 0 0;
  border-top: 1px dashed rgba(255, 255, 255, .16);
  font-family: var(--oa-sans);
}
.oa-addon .hd {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 7px;
}
.oa-addon .k {
  display: block;
  margin-bottom: 5px;
  color: var(--oa-orange);
  font-size: var(--oa-fs-micro);
  font-weight: 800;
  letter-spacing: 1.2px;
  text-transform: uppercase;
}
.oa-addon .l {
  flex: 1;
  min-width: 0;
  color: var(--oa-sub);
  font-size: var(--oa-fs-body);
  line-height: 1.45;
}
.oa-addon .amt {
  flex: none;
  color: var(--oa-faint);
  font-weight: 700;
  font-size: var(--oa-fs-meta);
  white-space: nowrap;
}
.oa-addon .n {
  display: block;
  margin-top: 5px;
  color: var(--oa-faint);
  font-size: var(--oa-fs-meta);
  line-height: 1.45;
}

/* ---------------------------------------------------------------------------
   Tooltips. Detail that only a considering reader wants — the strategy behind a
   package, what a line item actually includes — lives here instead of in the
   card, which is what made the cards tall and ragged. CSS only: :hover for a
   mouse, :focus-within for the keyboard and for a tap on a touch screen, since
   the trigger is focusable.
   --------------------------------------------------------------------------- */
.oa-info {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: 6px;
  color: var(--oa-faint);
  cursor: help;
  vertical-align: middle;
  transition: color .2s ease;
}
.oa-info:hover, .oa-info:focus, .oa-info:focus-within { color: var(--oa-orange); outline: none; }
.oa-info svg { flex: none; }
/* Inside a line item the mark is an aside, not a control: smaller, dimmer, and
   tucked right up against its word so the row reads as one phrase. It brightens
   to full strength on hover, which is when it matters. */
.oa-inc-item .oa-info {
  margin-left: 3px;
  color: rgba(255, 255, 255, .26);
}
.oa-inc-item .oa-info svg { width: 11px; height: 11px; }
.oa-inc-item .oa-info:hover,
.oa-inc-item .oa-info:focus-within { color: var(--oa-orange); }
/* The labelled variant ("Why this package") reads as a control, not an aside. */
.oa-info-lbl {
  margin-left: 0;
  color: var(--oa-sub);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-meta);
  font-weight: 700;
}
.oa-info-lbl .t { border-bottom: 1px dotted rgba(255, 255, 255, .3); }
.oa-info-lbl:hover .t, .oa-info-lbl:focus-within .t { border-bottom-color: var(--oa-orange); }

.oa-tip {
  position: absolute;
  left: 0;
  bottom: calc(100% + 10px);
  z-index: 40;
  width: 264px;
  max-width: 264px;
  /* The panel lives inside .oa-info, which lives inside the .oa-nb span that
     glues the mark to its word — so it inherited `white-space: nowrap` and laid
     its whole paragraph out on one line, straight through the side of the box.
     Wrapping is reasserted here rather than scoping .oa-nb, because the mark and
     its word genuinely must not break; only this panel must. */
  white-space: normal;
  overflow-wrap: anywhere;
  padding: 12px 14px;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 12px;
  background: #171821;
  box-shadow: 0 20px 44px -14px rgba(0, 0, 0, .85);
  color: var(--oa-sub);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-meta);
  font-weight: 400;
  line-height: 1.55;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity .18s ease, transform .18s ease, visibility .18s;
  pointer-events: none;
}
/* Line-item tips sit further right in the card, so they hang leftward instead. */
.oa-inc-item .oa-tip { left: auto; right: -8px; }
/* The package tip's trigger rides on the card's name, near the top edge. There
   is nothing above it but the Flickity viewport, which clips at overflow, so
   this one opens downward. */
.oa-tip-down { top: calc(100% + 10px); bottom: auto; transform: translateY(-4px); }
.oa-info:hover .oa-tip,
.oa-info:focus .oa-tip,
.oa-info:focus-within .oa-tip { opacity: 1; visibility: visible; transform: translateY(0); }
.oa-tip .h {
  display: block;
  margin-bottom: 6px;
  color: var(--oa-txt);
  font-weight: 800;
  font-size: var(--oa-fs-body);
}
.oa-tip .lab {
  display: block;
  margin: 10px 0 3px;
  color: var(--oa-orange);
  font-size: var(--oa-fs-micro);
  font-weight: 800;
  letter-spacing: 1.4px;
  text-transform: uppercase;
}
.oa-tip .p { display: block; }
/* The name carries the tip trigger, so it must not be dragged onto the icon's
   baseline. */
/* On the baseline, not above it. Raising this mark turned an information
   affordance into a footnote marker, which is not what it is (owner, 2026-08:
   no superscripts). Optical centring against the name's x-height is done with
   translateY so the line box is left alone, and 12px keeps it an aside beside a
   25px name rather than a second headline. */
.oa-plan-name .oa-info {
  margin-left: 7px;
  vertical-align: baseline;
  transform: translateY(-1px);
}
.oa-plan-name .oa-info svg { width: 12px; height: 12px; }

/* The body widget is a plain block. It used to be a flex column so a foot block
   could be bottom-anchored inside it; that block is gone, and leaving the flex
   in place sized the widget's container to max-content, which let the
   two-column list run out past the card's right edge. The CTA still pins to the
   card foot on its own — it is a sibling widget with margin-top:auto inside the
   card's own column flex. */
.oa-plan .oa-planbody-host { flex: 1 1 auto; min-width: 0; }
.oa-plan-body { width: 100%; min-width: 0; }

/* CTA pinned to the card foot so every button lines up across the row. */
.oa-plan .oa-ctafoot { margin-top: auto !important; padding-top: 18px; }
.oa-plan .oa-ctafoot .oa-gbtn { width: 100%; }

/* ---------------------------------------------------------------------------
   Card motion, ported from magicui (the registry 21st.dev serves):

   MagicCard — a spotlight and a rim light that track the pointer. pricing.js
   writes --mx/--my on the card; everything else is CSS. Both live in
   .oa-glow-host at z-index -1, i.e. above the card's own fill and border but
   under its copy, so nothing tints the text.

   BorderBeam — a short bright segment travelling the card's border, on the
   featured card only. Theirs rides an offset-path rect() with the border drawn
   by a mask-composite:intersect pair; that is what the @supports block does.
   Where rect() paths are unsupported the same segment is a rotating conic
   sweep, which looks the same and works everywhere.
   --------------------------------------------------------------------------- */
.oa-glow {
  position: absolute;
  inset: 0;
  /* Not `inherit`: the parent here is Elementor's own .elementor-widget-container,
     which has no radius of its own. */
  border-radius: calc(var(--oa-card-r) - 1px);
  opacity: 0;
  transition: opacity .35s ease;
  background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 0%),
                              rgba(254, 124, 16, .20), transparent 72%);
}
/* The rim: the same radial, clipped to a 1px band by masking the padding box
   out of the border box. */
.oa-glow::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: radial-gradient(220px circle at var(--mx, 50%) var(--my, 0%),
                              rgba(254, 124, 16, .95), transparent 65%);
  -webkit-mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}
.oa-plan:hover .oa-glow,
.oa-plan:focus-within .oa-glow { opacity: 1; }
/* A card lifts slightly under the pointer — the only transform on the cell, so
   it cannot fight Flickity, which drives the track and not the cells. */
.oa-plan { transition: transform .35s cubic-bezier(.22, .65, .3, 1); }
.oa-plan:hover { transform: translateY(-4px); }

/* The card you are on is the one you are reading, so it stands at full size and
   its neighbours sit back. Shrinking the others rather than growing the
   selected one: neither reflows, but scaling UP would push the featured card's
   glow past even the viewport's clip margin.

   This uses the individual `scale` and `translate` properties, NOT `transform`.
   Flickity positions each cell with an inline `transform: translateX(...)`, and
   an inline style beats any stylesheet rule — measured: the rule matched and
   its opacity applied while its transform was silently discarded. `scale` and
   `translate` are separate properties that COMPOSE with transform instead of
   competing for it. (The same inline transform is why the hover lift below has
   never worked inside the carousel either; it moves to `translate` here too.)

   Only while Flickity is driving. The no-JS scroll-snap rail has no concept of
   a selected cell, so every card there must read equally. */
@media (min-width: 768px) {
  /* NO `scale` here, and none may be added. Flickity positions each cell with an
     inline `transform: translateX(548px)`, and the CSS Transforms spec applies
     the individual `scale` property BEFORE `transform` — so a scale multiplies
     Flickity's own translation. Measured on the live page: at .90 the 548px
     step rendered as 493, at 1.04 as 570, giving pitches of 570/416/494 and a
     145px overlap of one card onto the next. Flickity's internal model was
     correct throughout (every cell 548 wide at x = 0, 548, 1096 …); only the
     paint was wrong, which is why re-measuring could never fix it.
     The focused card is distinguished by clearing it while its neighbours are
     pushed back — no geometry involved, so nothing can drift. Making it
     genuinely larger needs the card's visuals moved onto an inner element that
     is free to scale; the cell itself is not. */
  .oa-rail > .e-con-inner.is-flkty .oa-plan {
    /* Blur and dim, NOT opacity. Fading a card to .74 made the card itself
       translucent, so its neighbours and the page behind showed straight
       through it. blur() + brightness() push a card back while it stays solid. */
    filter: blur(2.5px) brightness(.6);
    transition: filter .45s ease, translate .35s cubic-bezier(.22, .65, .3, 1);
  }
  .oa-rail > .e-con-inner.is-flkty .oa-plan.is-selected {
    filter: none;
    /* Cells are absolute and paint in DOM order, so the focused card leads. */
    z-index: 3;
  }
  /* Hovering a card you have not selected clears it: the tooltips inside it are
     descendants, so a blurred card would hand you a blurred explanation. */
  .oa-rail > .e-con-inner.is-flkty .oa-plan:hover {
    translate: 0 -4px;
    filter: none;
  }
}

.oa-beam {
  position: absolute;
  inset: 0;
  border-radius: calc(var(--oa-card-r) - 1px);
  overflow: hidden;
}
.oa-beam::before {
  content: "";
  position: absolute;
  inset: -60%;
  background: conic-gradient(from 0deg, transparent 0 72%, #FFD9A8 86%,
                             #FE7C10 93%, transparent 100%);
  animation: oaBeamSpin 6s linear infinite;
}
/* The inner plate that turns the sweep into a border rather than a fill. It has
   to repaint the card's own gradient, because this layer sits above it. */
.oa-beam::after {
  content: "";
  position: absolute;
  inset: 1.5px;
  border-radius: inherit;
  background: linear-gradient(180deg, var(--oa-card-lift), #101117);
}
@keyframes oaBeamSpin { to { transform: rotate(1turn); } }

@supports (offset-path: rect(0 auto auto 0 round 1px)) {
  .oa-beam {
    overflow: visible;
    border: 1.5px solid transparent;
    -webkit-mask: linear-gradient(transparent, transparent), linear-gradient(#000, #000);
    mask: linear-gradient(transparent, transparent), linear-gradient(#000, #000);
    -webkit-mask-clip: padding-box, border-box;
    mask-clip: padding-box, border-box;
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
  .oa-beam::after { content: none; }
  .oa-beam::before {
    inset: auto;
    width: 120px;
    aspect-ratio: 1;
    background: linear-gradient(to left, #FFE2BC, #FE7C10, transparent);
    offset-path: rect(0 auto auto 0 round 120px);
    animation: oaBeamRun 7s linear infinite;
  }
  @keyframes oaBeamRun {
    from { offset-distance: 0%; }
    to   { offset-distance: 100%; }
  }
}

/* Featured card: a slow breathing ring under the beam. */
.oa-plan.oa-feat { animation: oaBreath 4.5s ease-in-out infinite; }
@keyframes oaBreath {
  0%, 100% { box-shadow: 0 24px 70px -20px rgba(254, 124, 16, .22), 0 0 0 1px rgba(254, 124, 16, .35); }
  50%      { box-shadow: 0 24px 84px -14px rgba(254, 124, 16, .50), 0 0 0 1px rgba(254, 124, 16, .65); }
}

/* The badge catches a highlight every few seconds — magicui's shine, on the one
   element whose whole job is to be noticed. */
.oa-badge { position: relative; overflow: hidden; }
.oa-badge::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(104deg, transparent 25%, rgba(255, 255, 255, .55) 50%, transparent 75%);
  transform: translateX(-130%);
  animation: oaShine 4s ease-in-out infinite;
}
@keyframes oaShine {
  0%, 62%   { transform: translateX(-130%); }
  88%, 100% { transform: translateX(130%); }
}

/* ===========================================================================
   4. Carousel
   ---------------------------------------------------------------------------
   Progressive enhancement. With no JS the track is a scroll-snap rail and
   everything still works; pricing.js upgrades it to Flickity (the carousel
   library the theme already ships) and adds .is-flkty, which hands scrolling
   over to the library.
   =========================================================================== */
.oa-rail {
  position: relative;
  padding: 0 !important;
  /* The rail runs the full width of the screen and sets its own left inset to
     line the first card up with the text column. Boxed to 1180 it was sliced
     mid-card at the container's edge, which read as a mistake; now the row
     simply continues off the screen, the way a carousel should. 566 = half the
     1180 column minus its 24px gutter. */
  width: 100vw !important;
  max-width: 100vw !important;
  margin-left: calc(50% - 50vw) !important;
  margin-right: calc(50% - 50vw) !important;
  --oa-rail-inset: max(20px, calc(50vw - 566px));
  /* Cell margins are load-bearing, so they are named once here: the top margin
     is the headroom the bookmark ribbon hangs into (it would otherwise be
     clipped by the viewport), the bottom margin is room for the featured
     card's glow, and the equal-height rule subtracts both. */
  /* Symmetric, deliberately. These were 26/34, and the 8px difference meant a
     card sat 8px high in its own viewport — measured as 51px of spare room
     above and 59px below. Scale grows a card evenly about its centre, so an
     off-centre card grows evenly into an uneven frame, which is what read as a
     lopsided zoom. The room is also what the focused card scales up into. */
  --oa-cell-mt: 34px;
  --oa-cell-mb: 34px;
  --oa-cell-gap: 24px;
  /* Breathing room between the row and the window edge, applied to the
     scrollport (see .is-flkty) because Flickity's `contain` eats cell margins
     at both ends. Small on purpose: the row is meant to run off the screen. */
  --oa-rail-gutter: 24px;
}
.oa-rail > .e-con-inner {
  /* Elementor clamps a boxed container's inner to --content-width and centres
     it, so bleeding only the outer element left the track sitting at 1180 and
     the inset pushing the first card a further 154px in. The inner has to span
     the bleed too. */
  max-width: none !important;
  width: 100% !important;
  margin-left: 0 !important;
  margin-right: 0 !important;
  flex-wrap: nowrap !important;
  justify-content: flex-start !important;
  align-items: stretch !important;
  padding: 40px 0 20px var(--oa-rail-inset) !important;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 4px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--oa-orange) #1A1B20;
}
.oa-rail > .e-con-inner::-webkit-scrollbar { height: 6px; }
.oa-rail > .e-con-inner::-webkit-scrollbar-track { background: #1A1B20; border-radius: 99px; }
.oa-rail > .e-con-inner::-webkit-scrollbar-thumb { background: var(--oa-orange); border-radius: 99px; }

.oa-rail .oa-plan {
  flex: 0 0 auto;
  width: 500px;
  margin-right: var(--oa-cell-gap);
  /* Centre, matching the Flickity config. With JS off this rail is the whole
     carousel, and snapping to `start` left the last card pinned at the right
     edge with nothing able to bring it in — the same problem the JS path had. */
  scroll-snap-align: center;
}
/* The end cards need room on their outside edge to reach the middle at all;
   without it the scroll simply runs out before they get there. 50% of the
   viewport less half a card puts the first and last on the centre line exactly
   as the middle ones do. */
.oa-rail > .e-con-inner::before,
.oa-rail > .e-con-inner::after {
  content: "";
  flex: 0 0 auto;
  width: max(0px, calc(50vw - 250px - var(--oa-rail-inset)));
}
/* Flickity positions cells absolutely and measures the track from them, so the
   spacers would be counted as cells and throw the geometry off. */
.oa-rail > .e-con-inner.is-flkty::before,
.oa-rail > .e-con-inner.is-flkty::after { display: none; }

/* Flickity has taken over: it owns the scrolling, so drop the native rail.
   The bottom margin (not padding) reserves space for the dots while keeping the
   track's box equal to the viewport, so the arrows' top:50% lands on the cards'
   centre rather than below it.
   The side margins are the row's only breathing room. They cannot live on the
   cells: `contain` explicitly subtracts the first cell's left margin and the
   last cell's right margin from its bounds, precisely so a contained row has no
   slack at either end — put the inset there and Flickity cancels it and parks
   the first card against the window edge. Insetting the VIEWPORT instead is
   outside its arithmetic, so it survives. */
.oa-rail > .e-con-inner.is-flkty {
  overflow: visible;
  scroll-snap-type: none;
  padding: 0 !important;
  width: calc(100% - 2 * var(--oa-rail-gutter)) !important;
  margin: 0 var(--oa-rail-gutter) 46px !important;
}
.oa-rail > .e-con-inner.is-flkty::-webkit-scrollbar { display: none; }
/* The gutter lives on the cell, not the track: a selected card then sits the
   same distance from the window edge wherever it is in the run, and the row can
   travel the entire width instead of stopping at an interior padding edge. */
.oa-rail > .e-con-inner.is-flkty .oa-plan {
  margin: var(--oa-cell-mt) var(--oa-cell-gap) var(--oa-cell-mb) var(--oa-cell-gap);
  scroll-snap-align: none;
}

/* Minimal Flickity base, scoped — the theme's copy of these rules is partial. */
.oa-rail .flickity-enabled { position: relative; }
.oa-rail .flickity-enabled:focus { outline: 0; }
/* `clip` with a margin, not `hidden`. The featured card's glow is a box-shadow
   spreading ~85px past its own edge, and hidden cut it off square against the
   viewport — the card looked trimmed. Plain `visible` is not the answer either:
   the comment below is why. `clip` still creates no scroll container, and the
   margin lets the glow out without letting a distant cell in. */
.oa-rail .flickity-viewport {
  position: relative;
  overflow: clip;
  overflow-clip-margin: 110px;
  height: 100%;
  transition: height .25s ease;
}
/* Hub ships `.flickity-enabled.is-draggable .flickity-viewport{overflow:visible}`
   so its own carousels can bleed. That out-specifies a two-class selector, and
   without this override the off-screen cells escape the rail and give the whole
   page a horizontal scrollbar. */
.oa-rail .flickity-enabled.is-draggable .flickity-viewport {
  overflow: clip;
  overflow-clip-margin: 110px;
}
/* Safari below 16 and Firefox below 102 have no `clip`; they fall back to the
   square cut rather than to a horizontal scrollbar. */
@supports not (overflow: clip) {
  .oa-rail .flickity-viewport,
  .oa-rail .flickity-enabled.is-draggable .flickity-viewport { overflow: hidden; }
}
/* No masks. The scrollport spans the whole screen, so a card leaving it is
   leaving the window — there is no interior edge to soften. The gutter lives on
   the cells, which is what lets the row travel the entire width. */
.oa-rail .flickity-slider { position: absolute; width: 100%; height: 100%; }
.oa-rail .flickity-enabled.is-draggable { -webkit-tap-highlight-color: transparent; user-select: none; }
.oa-rail .flickity-enabled.is-draggable .flickity-viewport { cursor: grab; }
.oa-rail .flickity-enabled.is-draggable .flickity-viewport.is-pointer-down { cursor: grabbing; }
/* Cells are absolutely positioned once Flickity runs, so equal height has to be
   asked for explicitly. pricing.js measures with this class off, then adds it.
   Flickity sizes the viewport from each cell's OUTER height, margins included,
   so the two margins come back off here. */
.oa-rail > .e-con-inner.is-equal .oa-plan {
  height: calc(100% - var(--oa-cell-mt) - var(--oa-cell-mb));
}
/* Equalising to the tallest card (Warpspeed, 8 rows) leaves a short one
   (Launchpad, 3) with the difference, and .oa-ctafoot's margin-top:auto dumps
   all of it into one gap above the button. Capping that gap keeps a short card
   reading as finished — the overflow becomes card padding instead of a hole. */
.oa-rail > .e-con-inner.is-equal .oa-plan .oa-ctafoot { max-height: 92px; }

/* Arrows — brand circles, hidden where dragging is the natural gesture. */
.oa-rail .flickity-button {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 50%;
  background: rgba(12, 13, 17, .92);
  color: #fff;
  cursor: pointer;
  z-index: 20;
  transition: background-color .25s ease, border-color .25s ease, opacity .25s ease;
}
.oa-rail .flickity-button:hover { background: var(--oa-orange); border-color: var(--oa-orange); }
.oa-rail .flickity-button:focus-visible { outline: 2px solid var(--oa-orange); outline-offset: 3px; }
.oa-rail .flickity-button:disabled { opacity: .3; cursor: default; pointer-events: none; }
.oa-rail .flickity-button svg { width: 15px; height: 15px; }
.oa-rail .flickity-button .arrow { fill: currentColor; }
/* Hard against the window edges: the cards now start at the edge too, so an
   arrow inset to the old text column sat on top of the first card's copy. */
.oa-rail .flickity-prev-next-button.previous { left: 10px; }
.oa-rail .flickity-prev-next-button.next { right: 10px; }

/* Dots — absolute, so they never join the track's flex row as an extra column.
   They live in the track's bottom margin. */
.oa-rail .flickity-page-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -32px;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.oa-rail .flickity-page-dots .dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .22);
  cursor: pointer;
  transition: width .3s ease, background-color .3s ease;
}
.oa-rail .flickity-page-dots .dot.is-selected { width: 26px; background: var(--oa-orange); }

/* Track filter chips. With no JS every card is on the rail and the chips are
   inert, which is why they are buttons rather than links to nowhere. */
.oa-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin: 26px 0 0;
}
.oa-chip {
  appearance: none;
  padding: 10px 20px;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 999px;
  background: rgba(255, 255, 255, .04);
  color: var(--oa-sub);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-meta);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: background-color .2s ease, border-color .2s ease, color .2s ease;
}
.oa-chip:hover { border-color: rgba(254, 124, 16, .5); color: #fff; }
.oa-chip:focus-visible { outline: 2px solid var(--oa-orange); outline-offset: 2px; }
.oa-chip.is-on {
  background: var(--oa-orange);
  border-color: var(--oa-orange);
  color: #fff;
}
/* Filtered out: left in the DOM, taken out of the carousel by cellSelector. */
.oa-rail .oa-plan.is-off { display: none !important; }

.oa-railhint {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 14px 0 0;
  color: var(--oa-faint);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-meta);
}
.oa-railhint svg { flex: none; }

/* What every stage carries — one line under the rail instead of the same two
   rows repeated inside all six cards. */
.oa-allinc {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 10px;
  max-width: 720px;
  margin: 22px auto 0;
  padding: 14px 20px;
  border: 1px solid rgba(255, 255, 255, .09);
  border-radius: 12px;
  background: rgba(255, 255, 255, .03);
  color: var(--oa-sub);
  font-family: var(--oa-sans);
  font-size: var(--oa-fs-body);
  line-height: var(--oa-lh-body);
  text-align: left;
}
.oa-allinc svg { flex: none; margin-top: 1px; }

/* ===========================================================================
   5. Comparison table
   ---------------------------------------------------------------------------
   The theme zebra-stripes tables white, which would put white text on white,
   so every cell sets its own background. The label column is sticky so the row
   meaning survives horizontal scrolling on a phone.
   =========================================================================== */
.oa-cmp-wrap {
  position: relative;
  overflow-x: auto;
  border: 1px solid var(--oa-hair);
  border-radius: 18px;
  background: var(--oa-card);
  -webkit-overflow-scrolling: touch;
}
/* Wide enough for the whole table, so the scroller is not needed — and an
   overflow-x scroller clips on BOTH axes, which would cut every row tooltip in
   half. This is the same breakpoint that hides the "scroll sideways" hint. */
@media (min-width: 1260px) {
  .oa-cmp-wrap { overflow: visible; }
}
/* Row-label marks: an aside beside the label, same weight as the ones in the
   cards. The panel hangs left because the label column is the leftmost, and
   opens downward because a row near the top has the table header above it. */
.oa-cmp .oa-cmp-lbl .oa-info { margin-left: 3px; color: rgba(255, 255, 255, .3); }
.oa-cmp .oa-cmp-lbl .oa-info svg { width: 11px; height: 11px; }
.oa-cmp .oa-cmp-lbl .oa-info:hover,
.oa-cmp .oa-cmp-lbl .oa-info:focus-within { color: var(--oa-orange); }
.oa-cmp .oa-cmp-lbl .oa-tip { left: 0; right: auto; }
/* Below 1260px the wrapper is still a scroller, which clips on both axes, so a
   downward panel on one of the last rows would be cut off at its bottom edge.
   Those rows open upward instead — there is always table above them. */
@media (max-width: 1259px) {
  .oa-cmp tbody tr:nth-last-child(-n+5) .oa-cmp-lbl .oa-tip.oa-tip-down {
    top: auto;
    bottom: calc(100% + 10px);
  }
}
.oa-cmp {
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
  border: 0;
  background: transparent !important;
  font-family: var(--oa-sans);
}
.oa-cmp tr, .oa-cmp td { background: transparent !important; border: 0; }
.oa-cmp td {
  padding: 14px 16px;
  font-size: var(--oa-fs-body);
  border-top: 1px solid var(--oa-hair-soft);
}
.oa-cmp .oa-cmp-lbl {
  position: sticky;
  left: 0;
  z-index: 2;
  min-width: 210px;
  background: var(--oa-card) !important;
  color: var(--oa-sub) !important;
}
.oa-cmp .oa-cmp-val { text-align: center; color: #fff; }
.oa-cmp .oa-cmp-val .v { font-weight: 700; color: #fff; }
.oa-cmp .oa-cmp-val svg { vertical-align: middle; }
.oa-cmp .oa-cmp-feat { background: #1E1612 !important; }
.oa-cmp .oa-cmp-head td { padding: 20px 16px 14px; border-top: 0; }
.oa-cmp .oa-cmp-head .name { display: block; color: #fff; font-weight: 800; font-size: 15px; }
.oa-cmp .oa-cmp-head .price { display: block; margin-top: 3px; font-weight: 800; font-size: 21px; letter-spacing: -0.5px; color: #fff; }
.oa-cmp .oa-cmp-head .oa-cmp-feat .price { color: var(--oa-orange); }
.oa-cmp .oa-cmp-head .pill {
  display: inline-block;
  margin-bottom: 7px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--oa-orange);
  color: #fff;
  font-weight: 800;
  font-size: 9.5px;
  letter-spacing: 1.3px;
}
.oa-cmp .oa-cmp-grp td {
  padding: 22px 16px 8px;
  color: var(--oa-orange) !important;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}
.oa-cmp .oa-cmp-cta td { padding: 20px 16px; }
.oa-cmpscroll {
  margin: 10px 0 0;
  color: var(--oa-faint);
  font-family: var(--oa-sans);
  font-size: 12.5px;
  text-align: center;
}
/* The grid only stops scrolling once the band is wide enough for all four
   columns (~1138px of table inside the 1140 boxed band). */
@media (min-width: 1260px) { .oa-cmpscroll { display: none; } }

/* ===========================================================================
   6. Service lists, steps, FAQ
   =========================================================================== */
.oa-svc { margin: 12px 0 0; padding: 0; list-style: none; font-family: var(--oa-sans); }
.oa-svc li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
  color: var(--oa-sub);
  font-size: var(--oa-fs-body);
  line-height: 1.45;
}
.oa-svc li:last-child { border-bottom: 0; }
.oa-svc li .amt { font-weight: 700; color: #fff; white-space: nowrap; }

/* --- 06 How it works -------------------------------------------------------
   Three stages, not three paragraphs. Each card carries its own timing pill and
   mark, a ghost numeral bled off the top-right corner, and — on desktop — a
   connector drawn in the grid gap so the row reads left to right as a sequence.
   The connector lives on the gap rather than behind the cards: the cards are
   opaque, so a line drawn behind them would only ever show in the gaps anyway,
   and drawing it there directly means no z-index fight with the card fill. */
.oa-steps {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  margin: 40px 0 0;
  padding: 0;
  list-style: none;
}

/* The two connectors hang off the ROW, not off the cards. A card clips its own
   overflow so the ghost numeral can bleed past its corner, which also clipped
   a connector drawn on the card — it was in the markup and invisible on the
   page. Drawn here they sit in the grid gaps, which is where they belong.
   Column width is (100% - 2*24px)/3, so gap 1 starts at one column and gap 2 at
   two columns plus the first gap; each line is inset 2px from both card edges. */
.oa-steps::before,
.oa-steps::after {
  content: "";
  position: absolute;
  top: 50px;
  width: 20px;
  height: 1px;
  background: linear-gradient(90deg, rgba(254, 124, 16, 0), rgba(254, 124, 16, .6));
}
.oa-steps::before { left: calc((100% - 48px) / 3 + 2px); }
.oa-steps::after  { left: calc((100% - 48px) / 3 * 2 + 26px); }
.oa-step {
  position: relative;
  padding: 26px 26px 28px;
  border: 1px solid var(--oa-hair);
  border-radius: 18px;
  background: linear-gradient(180deg, #111218 0%, #0A0B0E 100%);
  overflow: hidden;
  transition: border-color .3s ease, transform .3s ease, box-shadow .3s ease;
}
.oa-step:hover {
  border-color: rgba(254, 124, 16, .34);
  transform: translateY(-3px);
  box-shadow: 0 18px 44px -26px rgba(0, 0, 0, .9);
}

/* Ghost numeral. Decoration only — the <ol> already carries the real order, and
   the span is aria-hidden — so it can sit this low in contrast. */
.oa-step-num {
  position: absolute;
  top: -14px;
  right: 6px;
  font-family: var(--oa-sans);
  font-weight: 800;
  font-size: 78px;
  line-height: 1;
  letter-spacing: -4px;
  color: rgba(255, 255, 255, .045);
  pointer-events: none;
  user-select: none;
}

.oa-step-head { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.oa-step-ic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  border-radius: 13px;
  background: #241306;
  border: 1px solid #5B2D08;
  color: var(--oa-orange);
}
.oa-step-ic svg { width: 20px; height: 20px; }
.oa-step-when {
  font-family: var(--oa-sans);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--oa-orange);
}
.oa-step-t {
  margin: 0 0 8px;
  font-family: var(--oa-sans);
  font-size: 20px;
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -0.3px;
  color: #fff;
}
.oa-step-b {
  margin: 0;
  font-family: var(--oa-sans);
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--oa-sub);
}

/* --- 07 FAQ ----------------------------------------------------------------
   A sticky rail on the left holds the heading and the one CTA; the questions
   sit beside it in three labelled sets. Native <details name="oa-faq">, so the
   open/close state, the keyboard, and one-open-at-a-time are the browser's job
   and the answers stay in the DOM either way. */
.oa-faq-rail { align-self: flex-start; }
/* 1025px, not 1000px: the two columns are told to stack from Elementor's
   tablet breakpoint down (gen_pricing.py sets width_tablet/width_mobile to
   100%, and Elementor's default tablet query is max-width:1024px). Between
   1000 and 1024 the rail was therefore full-width AND sticky — a heading that
   pinned itself over the questions it introduces. */
@media (min-width: 1025px) {
  .oa-faq-rail { position: sticky; top: 116px; }
}
.oa-faq-ask { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.oa-faq-note {
  font-family: var(--oa-sans);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--oa-faint);
}

/* Scaled up 2026-08 (owner: "make the question section bigger"). Everything in
   this block moved together — question, answer, row padding, the plus and the
   spacing between sets — because enlarging the type alone just makes a cramped
   row look crowded. */
.oa-faq-set + .oa-faq-set { margin-top: 46px; }
.oa-faq-g {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 10px;
  font-family: var(--oa-sans);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--oa-faint);
}
.oa-faq-g::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--oa-hair);
}

.oa-q {
  border-bottom: 1px solid rgba(255, 255, 255, .08);
  transition: background-color .25s ease, box-shadow .25s ease, border-radius .25s ease;
}
.oa-q:last-child { border-bottom: 0; }
/* A closed row is a divider, so it stays square — rounding it would bow the
   hairline up at both ends. The corners arrive with the card, on open. */
.oa-q[open] {
  border-radius: 14px;
  background: #111218;
  border-bottom-color: transparent;
  box-shadow: inset 0 0 0 1px rgba(254, 124, 16, .26);
}
.oa-q > summary {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  padding: 22px 22px;
  cursor: pointer;
  list-style: none;
  /* The row is the hit target, not the plus. 56px keeps a one-line question
     comfortably over the 44px minimum on a phone. */
  min-height: 56px;
}
.oa-q > summary::-webkit-details-marker { display: none; }
.oa-q > summary:focus-visible { outline: 2px solid var(--oa-orange); outline-offset: 2px; border-radius: 14px; }
.oa-q-t {
  flex: 1 1 auto;
  font-family: var(--oa-sans);
  font-size: 19px;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: -0.2px;
  color: #fff;
}
.oa-q:hover > summary .oa-q-t { color: var(--oa-orange); }
.oa-q[open] > summary .oa-q-t { color: var(--oa-orange); }

/* Plus that turns into a cross on open. Two bars and one rotation, so there is
   no icon font to load and no second glyph to keep in step with the state. */
.oa-q-i {
  position: relative;
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  margin-top: 0;
  border-radius: 50%;
  border: 1px solid var(--oa-hair);
  transition: transform .28s ease, border-color .28s ease, background-color .28s ease;
}
.oa-q-i::before,
.oa-q-i::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 11px;
  height: 1.6px;
  border-radius: 2px;
  background: var(--oa-sub);
  transform: translate(-50%, -50%);
  transition: background-color .28s ease;
}
.oa-q-i::after { transform: translate(-50%, -50%) rotate(90deg); }
.oa-q:hover > summary .oa-q-i { border-color: rgba(254, 124, 16, .5); }
.oa-q[open] > summary .oa-q-i {
  transform: rotate(135deg);
  border-color: var(--oa-orange);
  background: rgba(254, 124, 16, .12);
}
.oa-q[open] > summary .oa-q-i::before,
.oa-q[open] > summary .oa-q-i::after,
.oa-q:hover > summary .oa-q-i::before,
.oa-q:hover > summary .oa-q-i::after { background: var(--oa-orange); }

.oa-a { padding: 0 22px 22px; }
.oa-a p {
  margin: 0 0 14px;
  font-family: var(--oa-serif);
  font-size: 17.5px;
  line-height: 1.72;
  color: var(--oa-sub);
}
.oa-a p:last-child { margin-bottom: 0; }

/* Open animation. `details` has no transitionable height, so the panel fades
   and lifts on open instead; where the browser can interpolate the content box
   (::details-content + interpolate-size) it also slides, and closing animates
   too. Progressive: everywhere else simply opens. */
.oa-q[open] > .oa-a { animation: oaFaqIn .3s cubic-bezier(.22, .61, .36, 1) both; }
@keyframes oaFaqIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
@supports (interpolate-size: allow-keywords) and selector(details::details-content) {
  .oa-q { interpolate-size: allow-keywords; }
  .oa-q::details-content {
    block-size: 0;
    overflow: hidden;
    transition: block-size .32s cubic-bezier(.22, .61, .36, 1), content-visibility .32s allow-discrete;
  }
  .oa-q[open]::details-content { block-size: auto; }
  .oa-q[open] > .oa-a { animation: none; }
}

/* ===========================================================================
   7. Motion, reveals, responsive
   =========================================================================== */
/* Scroll reveal, scrubbed against the element's own entry — magicui's BlurFade
   (opacity + a short lift + a blur that resolves) driven by a view timeline
   instead of an observer, so it costs no JS and no layout reads. */
@supports (animation-timeline: view()) {
  .oa-rise,
  .oa-reveal {
    animation: oaBlurFade 1ms linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 60%;
  }
  /* Never starts at 0. A view() timeline only advances with scroll, so anything
     that renders the page without scrolling it — print, a full-page screenshot,
     a crawler measuring a tall viewport — sees every section below the fold in
     its `from` state. At 0 that is invisible content on a page we want indexed;
     the blur and the lift carry the effect on their own. */
  @keyframes oaBlurFade {
    from { opacity: .25; filter: blur(7px); transform: translateY(26px); }
    to   { opacity: 1; filter: blur(0); transform: translateY(0); }
  }
}

@media (max-width: 1024px) {
  .oa-h1 { font-size: 44px; }
  /* Two up, then the third card starts a row of its own — the connectors are
     positioned for a three-column row, so they go with it. */
  .oa-steps { grid-template-columns: repeat(2, 1fr); }
  .oa-steps::before,
  .oa-steps::after { display: none; }
}
/* --- GUTTER -----------------------------------------------------------------
   1199px, not 767px: that is where the theme swaps to the mobile bar
   (data-mobile-nav-breakpoint="1200"). Measured at 900px, the bar's inner
   padding is 20px and the logo sits at x=20 while the content column began at
   x=48 — sect() in gen_pricing.py nests two Elementor containers and gives both
   24px. The gutter follows the bar, not the phone.

   Box padding belongs to the Elementor document by the rule at the top of this
   file; it is here because sect() has no per-breakpoint padding, so authoring
   it there would mean rebuilding the document to move two numbers. Both
   selectors are three classes — the same weight as Elementor's generated rule —
   and this file prints after it, so the tie resolves here. */
@media (max-width: 1199px) {
  .oa-pricing.e-con.e-con { --padding-left: 20px; --padding-right: 20px; }
  .oa-pricing > .e-con.e-con { --padding-left: 0px; --padding-right: 0px; }
}

@media (max-width: 767px) {
  /* --- LAMP -----------------------------------------------------------------
     Higher, and short enough to have ends. At 620px the filament ran 115px past
     both edges of a 390px screen and read as a rule across the page rather than
     a lit line; clamped to the gutter it is a lamp again. */
  .oa-lamp { --lamp-y: 128px; --lamp-w: min(620px, calc(100vw - 40px)); }
  .oa-hero.e-con.e-con { --padding-top: 208px; }
  .oa-lamp .spread { height: 300px; }
  .oa-lamp .hot { height: 210px; }
  /* The aurora is masked to the top of the band, which is where the lamp now
     is. At .26 on a 390px screen the two orange fields merge into one smear and
     the filament — the one genuinely bright thing in the hero — stops reading
     as brighter than its own surroundings. The ribbon stays; it just goes back
     to being a wash behind the light instead of a second light. */
  .oa-aurora { opacity: .13; }

  .oa-h1 { font-size: 32px; letter-spacing: -0.8px; }
  .oa-heroctas { flex-direction: column; align-items: stretch; }
  .oa-heroctas .oa-gbtn,
  .oa-heroctas .oa-ghostbtn { width: 100%; }
  .oa-trust { gap: 8px 18px; }
  .oa-trust li { font-size: 12px; }

  /* One card per swipe, with a sliver of the next showing so the carousel
     reads as a carousel without an arrow to prove it. */
  .oa-rail { --oa-cell-gap: 14px; --oa-cell-mt: 26px; --oa-cell-mb: 26px; --oa-rail-gutter: 16px; }
  /* Card + its two gutters + the viewport inset, sized so a sliver of the next
     card still shows past the selected one. */
  /* --- CARD WIDTH ------------------------------------------------------------
     Measured at 390px: the card was 296px with 26px of padding each side, so
     the copy had 256px — two thirds of the screen — and eight things inside it
     wrapped, including the price. A wider card with tighter padding gives the
     content 296px instead, a 40px gain that costs nothing but the sliver of the
     next card shrinking from 47px to 28px, which is still plainly a carousel.
     The Elementor document owns the card box, so the padding override needs the
     same three-class weight as its generated rule; this file prints later. */
  .oa-rail .oa-plan { width: calc(100vw - 56px); max-width: 360px; }
  .oa-plan.e-con.e-con {
    --padding-left: 18px;
    --padding-right: 18px;
    --padding-top: 24px;
    --padding-bottom: 22px;
  }
  .oa-rail > .e-con-inner { padding: 34px 0 12px !important; }
  .oa-rail .flickity-button { display: none; }
  .oa-plan-name { font-size: 22px; }
  .oa-price .amt { font-size: 30px; }
  /* "$530/mo" was breaking across two lines. A price is one token; the title
     row can give it whatever width it needs and wrap the name instead. */
  .oa-price { white-space: nowrap; }
  .oa-inc li:first-child { padding-top: 0; }
  /* 11.5px over six lines is a paragraph pretending to be a caption — and this
     is the line that explains a build is ten payments rather than forever, so
     it is the one note on the card that has to be read. */
  .oa-payoff { font-size: 12.5px; padding: 8px 11px; line-height: 1.45; }

  /* The line item's price cell is nowrap, and on a ~300px row it was spending
     its width out of the label's: "Website build, up to 5 pages" broke across
     THREE lines so that "$500 for 10 months" could hold one. The label is the
     thing being bought and the qualifier is the aside, so the aside wraps. */
  .oa-inc .oa-inc-item .amt { white-space: normal; text-align: right; }
  .oa-inc .oa-inc-item .lbl { font-size: 14px; }
  .oa-cmp { min-width: 620px; }
  .oa-cmp td { padding: 11px 12px; font-size: 13px; }
  .oa-cmp .oa-cmp-lbl { min-width: 168px; }
  .oa-svc li { font-size: 14px; }

  .oa-steps { grid-template-columns: 1fr; gap: 16px; margin-top: 30px; }
  .oa-step { padding: 22px 20px 24px; }
  .oa-step-num { font-size: 62px; top: -10px; }
  .oa-step-t { font-size: 18.5px; }
  .oa-step-b { font-size: 15px; }

  .oa-quiz-form,
  .oa-quiz-out { padding: 22px 18px 20px; border-radius: 16px; }
  .oa-qstep-q { font-size: 18.5px; margin-bottom: 14px; }
  .oa-qopt-box { padding: 13px 14px; gap: 12px; }
  .oa-qopt-l { font-size: 15px; }
  .oa-qopt:hover .oa-qopt-box { transform: none; }
  .oa-quiz-nav { flex-wrap: wrap; }
  .oa-quiz-hint { flex: 1 0 100%; }
  .oa-qres-name { font-size: 25px; }
  .oa-qres-price .amt { font-size: 27px; }
  .oa-qres-why { font-size: 16px; }
  .oa-qres-ctas { gap: 14px; }
  .oa-qres-ctas .oa-gbtn { width: 100%; }

  /* FAQ, scaled with the desktop block above. */
  .oa-q > summary { padding: 18px 16px; gap: 14px; min-height: 54px; }
  .oa-q-t { font-size: 17px; }
  .oa-a { padding: 0 16px 18px; }
  .oa-a p { font-size: 16.5px; }
  .oa-faq-set + .oa-faq-set { margin-top: 36px; }
  .oa-faq-ask .oa-ghostbtn { width: 100%; }

  /* --- TOOLTIPS -------------------------------------------------------------
     A 264px panel anchored to a mark inside a 296px card hangs off the screen
     whichever way it opens — measured at 390px: one tip spanning x 186..450,
     another x -107..157. Making .oa-info static hands the panel's containing
     block to .oa-plan (already position:relative for the glow layer), so left
     and right clamp to the card and the panel can no longer leave the viewport.
     It docks at the card's foot rather than beside its mark: on a phone there
     is no hover to follow, the tip opens on tap, and a fixed place to look for
     it beats a panel that lands somewhere different for every row. */
  .oa-plan .oa-info { position: static; }
  .oa-plan .oa-tip,
  .oa-plan .oa-inc-item .oa-tip,
  .oa-plan .oa-tip.oa-tip-down {
    top: auto;
    bottom: 12px;
    left: 12px;
    right: 12px;
    width: auto;
    max-width: none;
  }
  /* The compare table scrolls horizontally in its own box, so its tips keep the
     beside-the-mark behaviour; they only need to stop being wider than it. */
  .oa-cmp .oa-tip { width: 240px; max-width: 240px; }

  /* --- TOUCH TARGETS --------------------------------------------------------
     The info marks are 11-12px SVGs sitting in running text, so they cannot be
     grown with min-width without opening a hole in the line. Padding grows the
     hit box; the matching negative margin puts the surrounding text back where
     it was, which leaves the mark drawn at 12px and tappable at ~32px. */
  .oa-plan .oa-info {
    padding: 10px;
    margin-top: -10px;
    margin-bottom: -10px;
    margin-right: -10px;
  }
  .oa-plan .oa-inc-item .oa-info { margin-left: -4px; }

  /* The arrows are hidden below 768px, so the dots are the only carousel
     control besides dragging — and they were 8px wide on an 8px gap, a 16px
     pitch for six packages. The dot keeps its drawn size; the padding is what
     the thumb lands on. */
  /* The hit area is a pseudo-element, NOT padding on the dot. The dot is
     `width: 8px; box-sizing: border-box`, so `padding: 14px 6px` left it a
     content box of zero and `background-clip: content-box` then had nothing to
     paint — the dots vanished while the row they sat in grew. The overlay
     leaves the dot drawn exactly as designed and spans its own gap, so
     neighbouring targets meet without overlapping. */
  .oa-rail .flickity-page-dots { gap: 18px; }
  .oa-rail .flickity-page-dots .dot { position: relative; }
  .oa-rail .flickity-page-dots .dot::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(100% + 18px);
    height: 44px;
    transform: translate(-50%, -50%);
  }

  /* The only way to advance the package finder on a phone, at ~34px. */
  .oa-quiz-next,
  .oa-quiz-back { min-height: 44px; }

  /* --- CARD ALIGNMENT FLOORS ------------------------------------------------
     Relocated from the dead block near the top of this file (see the note at
     :803). One card fills the screen here and pricing.js runs Flickity with
     adaptiveHeight, so nothing is being levelled across a row — every desktop
     floor is height the reader has to scroll past for no reason. */
  .oa-plan-head, .oa-plan-cat, .oa-bestfor,
  .oa-titlerow, .oa-was, .oa-stage { min-height: 0; }

  /* --- PAINT COST -----------------------------------------------------------
     Two forever-animations that cannot be composited, both running on a phone
     GPU behind a carousel:
       .oa-aurora::after animates background-position — a paint property — over
       a full-viewport layer carrying mix-blend-mode:difference and blur(10px),
       so every frame is a full-band repaint on the main thread. At the .13
       opacity this block sets, the shimmer it produces is not visible anyway.
       .oa-plan.oa-feat animates box-shadow, which repaints an 84px-blurred
       shadow around the card every frame, forever, outside .is-paused's reach.
     The featured card keeps the ring, held at the bright end of its own
     keyframes, so the card is still marked as the featured one. */
  .oa-aurora::after { animation: none; }
  .oa-plan.oa-feat {
    animation: none;
    box-shadow: 0 24px 70px -20px rgba(254, 124, 16, .30),
                0 0 0 1px rgba(254, 124, 16, .45);
  }
}
@media (max-width: 400px) {
  .oa-h1 { font-size: 28px; }
  /* This used to be `100vw - 80px`, which is why the card measured 310px on a
     390px phone even after the rule above widened it — same one class, later in
     the file. The narrower screen is exactly where the card can least afford to
     give away 80px, so it now gives away less than the block above, not more. */
  .oa-rail .oa-plan { width: calc(100vw - 44px); }
}

@media (prefers-reduced-motion: reduce) {
  .oa-gbtn .c,
  .oa-gbtn .g2,
  .oa-gbtn.is-live::after,
  .oa-plan.oa-feat,
  .oa-hero::before,
  .oa-hero .oa-orbs span,
  .oa-aurora, .oa-aurora::after,
  .oa-lamp .spread, .oa-lamp .hot, .oa-lamp .core, .oa-lamp .line,
  .oa-eyebrow .pulse::after,
  .oa-h1 .w i,
  .oa-h1 .oa-reel,
  .oa-h1 .oa-reelglow,
  .oa-scrollcue .m i,
  .oa-beam::before,
  .oa-badge::after,
  .oa-rise,
  .oa-reveal,
  .oa-plan,
  .oa-step,
  .oa-q,
  .oa-q-i,
  .oa-q[open] > .oa-a,
  .oa-quiz-steps,
  .oa-quiz-out,
  .oa-qstep.is-in,
  .oa-qstep.is-out,
  .oa-qstep.is-in .oa-qopt,
  .oa-qopt-box,
  .oa-qopt-tick,
  .oa-qopt input:checked + .oa-qopt-box .oa-qopt-tick svg,
  .oa-hero .oa-fx-host,
  .oa-hero .elementor-widget { animation: none !important; transition: none !important; }
  /* The step still changes and the tick still appears — that is the answer,
     not the decoration. What goes is the travel between the two. */
  .oa-qopt input:checked + .oa-qopt-box .oa-qopt-tick svg { opacity: 1; transform: none; }
  .oa-qopt:hover .oa-qopt-box { transform: none; }
  .oa-quiz-next:hover:not(:disabled) { transform: none; }
  .oa-plan:hover { transform: none; }
  .oa-step:hover { transform: none; }
  /* The plus still becomes a cross — that is the state, not the motion. */
  .oa-q[open] > summary .oa-q-i { transform: rotate(135deg); }
  /* Reduced motion keeps the selected/unselected distinction — it is
     information, not decoration — but drops the movement. */
  .oa-rail > .e-con-inner.is-flkty .oa-plan { filter: brightness(.6); }
  .oa-rail > .e-con-inner.is-flkty .oa-plan.is-selected { filter: none; }
  .oa-rail > .e-con-inner.is-flkty .oa-plan:hover { translate: none; filter: none; }
  .oa-badge::after { display: none; }
  .oa-plan.oa-feat { box-shadow: 0 24px 70px -20px rgba(254, 124, 16, .22), 0 0 0 1px rgba(254, 124, 16, .35); }

  /* Motion off, composition intact: the ribbon, sparks and orbs stay as a
     static gradient field. What goes entirely is anything that only reads as
     movement — the beam runners, the CTA sheen and the scroll cue's dot. */
  .oa-scrollcue .m i,
  .oa-h1 .oa-reelglow,
  .oa-gbtn.is-live::after { display: none; }
  .oa-h1 .w i     { transform: none; }
  .oa-h1 .oa-reel { transform: translateY(0); }   /* the first word, held */
}
