/* ==========================================================================
   AstroPanda, service.css
   Layout for individual service detail pages (e.g. /services/custom-software/,
   /es/servicios/software-a-medida/): a two-column hero (copy + floating
   visual), a feature grid, a stats strip, and the shared CTA band at the
   close. Shared chrome (header/footer/typography/buttons/CTA band) lives in
   global.css, this file only covers what's exclusive to this page template.

   /services/web-design-development/ and /es/servicios/diseno-y-desarrollo-web/
   reuse this exact hero skeleton (.ap-svc-hero/-visual/-layer-parallax/-float)
   but swap the floating layer for a composited "monitor" (see the dedicated
   section below) instead of the ship image, same JS hooks (service.js),
   same entrance/float/parallax animations, just a different asset inside.
   ========================================================================== */

/* ---------- hero ----------
   Fills the viewport the same way the home hero does. The visual is NOT
   boxed into a 50/50 grid column, this ship image is wide (~1.7:1), so a
   half-width column left it tiny with dead space around it no matter how
   tall the box was. Instead it's an absolutely-positioned layer bleeding
   to the right edge of the viewport, sized by width alone (its own aspect
   ratio then decides the height), the same trick .ap-arc uses elsewhere
   to bleed full-bleed content past the shell's max-width. */
.ap-svc-hero{
  position:relative; min-height:100vh; padding-top:0px; overflow:hidden;
  display:flex; align-items:center;
}
.ap-svc-hero .ap-shell{position:relative; z-index:2}
.ap-svc-hero-copy{position:relative; max-width:55%; text-align:left; padding-left:0;}
/* the monitor now sits on the LEFT of this hero (its perspective swing
   reaches well past center), so the copy column flips to hug the right
   edge of the shell instead, margin-left:auto on a block narrower than
   its container pushes it there, and being %/auto-based like the monitor's
   own sizing, the gap between them holds at any viewport width instead of
   needing a breakpoint-specific fix. Text inside the box stays left-aligned
   (only the box moves, not the reading direction). */
.ap-web-hero .ap-svc-hero-copy{margin-left:auto; margin-right:0;
  /* the devices reach to ~48vw, the copy box starts at ~45.7vw: this
     is what keeps the headline off the phone. */
  padding-left:10%}
.ap-svc-hero-copy .ap-eyebrow{text-indent:0}
.ap-svc-hero-copy .ap-display{text-align:left;
  /* the column stops growing when .ap-shell does; the headline has to
     stop with it or it wraps where no <br> asked it to */
  font-size:clamp(34px,4vw,66px)}
/* left, like the eyebrow and the headline above it. Growth is the one hero
   that is centred by design and keeps its own override further down. */
.ap-svc-hero-copy .ap-lead{margin-inline:0; text-align:left}
.ap-svc-hero-copy .ap-buttons{justify-content:flex-start; margin-top:34px}

/* ---------- entrance ----------
   A simple, one-time "boot"-style intro, same spirit as the home hero
   (fade + rise, staggered) but short and plain CSS, this page has no
   async component to wait for, so there's no need for the JS-gated
   .ap-boot/.ap-booted dance the home page uses. */
@keyframes apSvcRise{
  from{opacity:0; transform:translateY(26px)}
  to{opacity:1; transform:none}
}
.ap-svc-hero-copy .ap-eyebrow{opacity:0; animation:apSvcRise .7s cubic-bezier(.19,.82,.22,1) .05s forwards}
/* the title rises without fading: Chrome does not count an element as the
   Largest Contentful Paint while its opacity is 0, and the headline is the
   LCP element on every service and industry hero. The fade with its 180ms
   delay put the measured LCP 1.2 to 1.7s after the first paint. The rest of
   the copy keeps the fade, so the stagger still reads. */
.ap-svc-hero-copy .ap-display{opacity:1; animation:apSvcTitleRise .8s cubic-bezier(.19,.82,.22,1) both}
@keyframes apSvcTitleRise{
  from{transform:translateY(22px)}
  to{transform:none}
}
.ap-svc-hero-copy .ap-lead{opacity:0; animation:apSvcRise .8s cubic-bezier(.19,.82,.22,1) .32s forwards}
.ap-svc-hero-copy .ap-buttons{opacity:0; animation:apSvcRise .8s cubic-bezier(.19,.82,.22,1) .44s forwards}

.ap-svc-hero-visual{ 
  position:absolute;
  z-index:1;
  top:0;
  bottom:0;
  right:0;
  width:min(64vw,1300px);

  display:flex;
  align-items:flex-end;
  justify-content:center;
  pointer-events:none;

  opacity:0;
  transform:translate3d(70px,30px,0) scale(1.12);

  animation:apSvcShipEnter 1.25s cubic-bezier(.19,.82,.22,1) .15s forwards;
}

@keyframes apSvcShipEnter{
  0%{
    opacity:0;
    transform:translate3d(70px,30px,0) scale(1.12);
  }

  60%{
    opacity:1;
  }

  100%{
    opacity:1;
    transform:translate3d(0,0,0) scale(1);
  }
}
.ap-svc-visual-glow{
  position:absolute; inset:10% -15% 0 -15%; z-index:0; pointer-events:none; border-radius:50%;
  background:radial-gradient(ellipse 55% 55% at 50% 65%, rgba(255,90,20,.18) 0%, rgba(255,90,20,0) 70%);
}
/* the visual sits in its own stacking area, two nested wrappers so a
   constant float (CSS animation) and mouse-driven parallax (JS, via custom
   properties) never fight over the same `transform` */
.ap-svc-layer-parallax{
  position:relative; z-index:1; width:100%;
  transform:translate3d(var(--px,0px),var(--py,0px),0);
  will-change:transform;
}
.ap-svc-layer-float{
  width:100%;
  animation:apSvcFloat 7s ease-in-out infinite;
  will-change:transform;
}
@keyframes apSvcFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-16px)}}
.ap-svc-layer{
  display:block; width:100%; height:auto;
  filter:drop-shadow(0 35px 60px rgba(0,0,0,.55));
  pointer-events:none; user-select:none;
}

/* ---------- web design hero (monitor composite) ----------
   web-bg.webp is the full-bleed scene (ship interior, console on the right);
   web-monitor.webp is a separate frame asset with a transparent screen hole.
   The two are combined purely in CSS/markup, not baked into one image:
     .ap-web-hero-bg, the scene, full-bleed background-image
     .ap-web-hero-scrim, gradient so hero copy stays legible over it
     .ap-monitor-screen, clipped to the screen hole, holds the slider
     .ap-monitor-slides, the auto-cycling website screenshots (behind)
     .ap-monitor-frame-img, web-monitor.webp itself, on top, as the real frame
   The screen-hole rectangle below (11%/17%/69.4%/56.3%) was measured
   directly off web-monitor.webp's alpha channel via a flood fill from its
   center, so the slider lines up with the actual cutout at any size. */
.ap-web-hero-bg{
  position:absolute; inset:0; z-index:0;
  background-size:cover; background-position:64% center;
}
.ap-web-hero-scrim{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(100deg, rgba(3,3,5,.95) 0%, rgba(3,3,5,.8) 28%, rgba(3,3,5,.34) 54%, rgba(3,3,5,.14) 70%, rgba(3,3,5,.5) 100%),
    linear-gradient(180deg, rgba(3,3,5,.55) 0%, rgba(3,3,5,0) 22%, rgba(3,3,5,0) 76%, rgba(3,3,5,.68) 100%);
}
.ap-web-hero .ap-svc-hero-visual{
  width:100%;
}
/* out-of-flow (.ap-monitor is position:absolute), so these wrappers need an
   explicit height for its top/right percentages to resolve against, normally .ap-svc-layer-float/-parallax get their height from the (in-flow)
   ship image; here nothing in flow would give them one otherwise. */
.ap-web-hero .ap-svc-layer-parallax,
.ap-web-hero .ap-svc-layer-float{
  height:100%;
}

/* Mounted on the console wall at a real angle (not floating, not flat-pasted
   on top), a single perspective(...) rotateY(...) on the whole frame+screen
   unit, hinged on its left edge, so the near (right) side reads bigger and
   the far (left) side recedes into the panel exactly like the reference
   crop. No transform-style:preserve-3d needed: screen and frame have no
   transforms of their own, so they flatten into that same tilted plane as
   one rigid unit, the screenshots inside inherit the panel's perspective
   for free instead of needing their own skew. */
.ap-monitor{
  position:absolute;
  top:20%;
  right:7%;
  width:42%;
  aspect-ratio:1440/1000;

  transform-origin:left center;

  opacity:0;
  transform:
    perspective(1000px)
    translate3d(-120px, 20px, 0)
    rotateY(-165deg)
    scale(.88);

  filter:drop-shadow(0 40px 70px rgba(0,0,0,.65));

  animation:apMonitorEnter 1.45s cubic-bezier(.16,.84,.24,1) .18s forwards;
}

@keyframes apMonitorEnter{
  0%{
    opacity:0.5;
    transform:
      perspective(1000px)
      translate3d(-120px,20px,0)
      rotateY(-165deg)
      scale(.88);
  }

  100%{
    opacity:1;
    transform:
      perspective(1000px)
      translate3d(10px,-2px,0)
      rotateY(-133deg)
      scale(1);
  }
}

.ap-monitor-screen{
  position:absolute;
  left:10.98%; top:16.76%; width:69.41%; height:56.35%;
  overflow:hidden; background:#0a0703;
}
.ap-monitor-slides{
  position:absolute;
  inset:0;
  transform:scaleX(-1);
}
.ap-monitor-slide{
  position:absolute; inset:0;
  background-size:cover; background-position:center 12%;
  opacity:0;
  transition:opacity .25s ease;
}
.ap-monitor-slide.is-active{
  opacity:1; z-index:1;
  animation:apMonitorKen 2s ease-out forwards;
}
@keyframes apMonitorKen{
  from{transform:scale(1.09) translate3d(1.2%,-1%,0)}
  to{transform:scale(1) translate3d(0,0,0)}
}
/* faint always-on scanlines sell the CRT read of the screen even at rest */
.ap-monitor-scan{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:repeating-linear-gradient(0deg, rgba(255,255,255,.07) 0 1px, transparent 1px 3px);
  mix-blend-mode:overlay; opacity:.55;
}
/* old-TV channel-change: a quick brightness/contrast flicker + vertical
   roll, timed so its brightest spike (35%) lands right on the slide swap
   (triggered by service.js) and masks the cut, see the JS for the timing. */
.ap-monitor-screen.is-switching{animation:apTvSwitch .5s steps(2,end)}
@keyframes apTvSwitch{
  0%{filter:brightness(1) contrast(1); transform:translateY(0)}
  12%{filter:brightness(2.4) contrast(1.5); transform:translateY(-1.6%)}
  28%{filter:brightness(.25) contrast(1.2); transform:translateY(1.2%)}
  40%{filter:brightness(2) contrast(1.4); transform:translateY(-.8%)}
  58%{filter:brightness(.5); transform:translateY(.5%)}
  100%{filter:brightness(1) contrast(1); transform:translateY(0)}
}
.ap-monitor-frame-img{
  position:absolute; inset:0; width:100%; height:100%;
  display:block; z-index:2; pointer-events:none; user-select:none;
}
/* static by design (no float, no drift), the panel is mounted, not
   drifting through the cabin like the ship image elsewhere on this file */
.ap-monitor-float{animation:none}

/* ---------- brand & product design hero (hall + bear) ----------
   hall.webp is the full-bleed empty workshop/hall scene; bear.webp is a
   real-alpha cutout (not a flattened white box) of the AstroPanda mascot
   at a workbench. It floats bottom-right through the exact same chain as
   the ship on the custom-software hero, .ap-svc-hero-visual/-layer-
   parallax/-layer-float/-layer, untouched, just with a background layer
   behind it and a different image in it.

   Bear position/size lives ENTIRELY in the two lines below: width is its
   size, right is how far it sits from the hero's right edge (top/bottom/
   align-items/justify-content are inherited from .ap-svc-hero-visual's
   base rule above, which already floats it bottom-right). Both language
   pages share this one rule, so there's nothing to keep in sync by hand
   when nudging it. */
.ap-brand-hero-bg{
  position:absolute; inset:0; z-index:0;
  background-size:cover; background-position:center;
  transform:scale(1.15);
  filter:blur(128px);
  animation:apBrandBgIn 1.7s cubic-bezier(.19,.82,.22,1) forwards;
}
/* the hall reveals itself on load, starts zoomed in and blurred, settles
   to sharp and at rest. .ap-svc-hero already has overflow:hidden, so the
   1.15 scale never shows a gap at the edges as it eases back down to 1. */
@keyframes apBrandBgIn{
  from{transform:scale(1.15); filter:blur(18px)}
  to{transform:scale(1); filter:blur(0)}
}
.ap-brand-hero-scrim{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(100deg, rgba(3,3,5,.9) 0%, rgba(3,3,5,.6) 34%, rgba(3,3,5,.16) 62%, rgba(3,3,5,.4) 100%),
    linear-gradient(180deg, rgba(3,3,5,.5) 0%, rgba(3,3,5,0) 22%, rgba(3,3,5,0) 74%, rgba(3,3,5,.6) 100%);
}
.ap-brand-hero .ap-svc-hero-visual{
  width:min(540vw,1220px); /* ← bear size */
  right:-6%;               /* ← bear horizontal position (from the right edge) */
  bottom:-3%;
}
/* The bear is the brightest thing in this hero and it sits at z-index 1,
   the same as .ap-brand-hero-scrim but after it in source order, so the
   scrim never darkens it: between roughly 1180 and 1600px the copy column
   and the bear's lit shoulder land on each other and the headline is read
   against near-white. Measured mean luminance under the text ran 79-108
   there, against 5-25 on every other service hero.

   Shifting the artwork only moves which widths break (measured: -14%
   fixes 1440 and breaks 1280; -20% fixes 1366 and breaks 1180), because
   what passes under the text is a highlight, not an edge. So the veil goes
   on the copy column itself, where it travels with the text at every width
   instead of being aimed at one.

   z-index:-1 stays inside .ap-shell's stacking context (it is positioned
   with z-index:2), so this paints under the copy and still over the bear.
   The ellipse has to reach full transparency INSIDE the box: a gradient
   still carrying alpha at the box edge is cut there, and that straight cut
   reads as a seam across the bear. Hence extents under 100% rather than
   the usual oversized ones, and the generous bleed on all four sides so
   the fade has room to finish before the box runs out. */
.ap-brand-hero .ap-svc-hero-copy::before{
  content:''; position:absolute; z-index:-1; pointer-events:none;
  inset:-34% -30% -34% -44%;
  background:radial-gradient(76% 62% at 20% 50%,
    rgba(3,3,5,.86) 0%, rgba(3,3,5,.78) 34%, rgba(3,3,5,.5) 58%,
    rgba(3,3,5,.2) 80%, rgba(3,3,5,0) 100%);
}
/* no float for this one, the bear stays put except for the mouse
   parallax (data-depth on .ap-svc-layer-parallax in the page markup),
   unlike the ship on custom-software which bobs via .ap-svc-layer-float's
   base animation (apSvcFloat, defined near the top of this file). */
.ap-brand-hero .ap-svc-layer-float{
  animation:none;
}

/* ---------- brand hero floating nuts ---------- */

.ap-tuer{
  position:absolute;
  z-index:1;
  pointer-events:none;
}

.ap-tuer--1{
  top:16%;
  left:4%;
  width:110px;
}

.ap-tuer--2{
  top:66%;
  left:9%;
  width:60px;
}

.ap-tuer--3{
  top:45%;
  left:10%;
  width:110px;
}

.ap-tuer--4{
  top:36%;
  left:15%;
  width:60px;
}

.ap-tuer--5{
  top:46%;
  left:19%;
  width:60px;
}

/* ---------- ecommerce hero (door opening) ----------
   Three images share one 1672x941 coordinate system, stacked and animated
   so the doorway slab slides open to reveal the scene (and the panda)
   already sitting behind it, ported from door-effect-demo.html (kept at
   the project root as the reference/playground for this effect) with
   class names swapped for the site's ap- prefix and IDs swapped for data
   attributes (service.js queries by those, see the door-opening block
   there). The percentages below are NOT arbitrary, they're measured
   against ecommerce-door.png's actual doorway position, so don't touch
   them without re-measuring against that asset.
     .ap-door-stage, isolated stacking context, full-bleed
     .ap-door-scene, the shared 1672x941 canvas, "cover"-sized via
                              width:max(100vw, 100svh * its own aspect
                              ratio) since aspect-ratio+width alone can't
                              express "at least this tall too"
     .ap-door-base, ecommerce-base.png, the scene already fully
                              lit/open (bottom layer, always visible)
     .ap-door-pocket, clips to the doorway rectangle; the slab
                              lives here and slides right into the wall
     .ap-door-slab, ecommerce-door.png, closed at rest
     .ap-door-edge, pure-CSS glow riding the slab's moving edge
     .ap-door-floor, ecommerce-reflex-blur.png used twice (left/
                              right); an animated clip-path opens a wedge
                              between them to fake the light spilling onto
                              the floor as the door slides */
.ap-ecommerce-hero{background:#050505}
.ap-door-stage{position:absolute; inset:0; z-index:0; overflow:hidden; isolation:isolate}
.ap-door-scene{
  position:absolute; top:50%; left:50%;
  width:max(100vw, calc(100svh * 1.7768331562));
  aspect-ratio:1672/941;
  transform:translate(-50%,-50%);
  overflow:hidden;
}
.ap-door-layer{
  position:absolute; inset:0; display:block; width:100%; height:100%;
  user-select:none; pointer-events:none;
}
.ap-door-base{z-index:1}
.ap-door-floor{
  z-index:10; opacity:1; visibility:visible; filter:none; mix-blend-mode:normal; will-change:clip-path;
}
.ap-door-floor--left{clip-path:polygon(0 0, 59.35% 0, 59.35% 80.35%, 59.35% 100%, 0 100%)}
.ap-door-floor--right{clip-path:polygon(59.35% 0, 100% 0, 100% 100%, 59.35% 100%, 59.35% 80.35%)}
.ap-door-pocket{
  position:absolute; inset:0; z-index:4; overflow:hidden;
  clip-path:polygon(59.15% 10.1%, 84.75% 10.1%, 84.75% 80.35%, 59.15% 80.35%);
}
.ap-door-slab{will-change:transform, filter; filter:brightness(.98)}
.ap-door-edge{
  position:absolute; z-index:5; left:59.35%; top:11.2%;
  width:clamp(2px,.22vw,5px); height:67.7%;
  transform:translateX(0); opacity:0;
  background:linear-gradient(to bottom, transparent, rgba(218,233,244,.72) 12%, rgba(189,211,226,.85) 75%, transparent);
  filter:blur(clamp(2px,.25vw,5px));
  box-shadow:0 0 1.2vw rgba(186,214,232,.34);
  will-change:transform, opacity;
  pointer-events:none;
}
/* keeps the doorway (and the panda behind it) in frame instead of a
   centered crop losing it off-screen on tall/narrow phones. The demo this
   was ported from shipped `left:61%` here, but that's the wrong direction
   on an actual phone aspect ratio (~9:19.5): with this cover-sizing
   technique, INCREASING `left%` shifts the visible crop toward the
   scene's own LEFT side (the shelving wall), not toward the doorway on
   the right, the math only works out close to the 4:5 boundary this
   query is keyed off, not at the much narrower ratios real phones use.
   A negative value is what actually centers the doorway at typical phone
   widths (~360-430px), verified against a 390x844 viewport. */
@media (max-aspect-ratio: 4/5){
  .ap-door-scene{left:-28%}
}
/* copy sits over the LEFT side of the scene (mostly bare wall/shelving, the doorway itself is off to the right, ~59%-85%), so the scrim only
   needs to bite there; it fades out well before the doorway so the door
   itself, the light beam and the panda all stay clean and unfiltered. */
.ap-ecommerce-hero-scrim{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(100deg, rgba(5,5,5,.92) 0%, rgba(5,5,5,.62) 32%, rgba(5,5,5,.12) 56%, rgba(5,5,5,0) 70%),
    linear-gradient(180deg, rgba(5,5,5,.4) 0%, rgba(5,5,5,0) 22%, rgba(5,5,5,0) 78%, rgba(5,5,5,.5) 100%);
}

/* ---------- growth & performance hero (cockpit warp) ----------
   speed.webp is the panda's cockpit, full-bleed, but it's not a plain
   background: the "windshield" is a real transparent cutout in the PNG
   alpha (measured the same way as the monitor screen, flood fill from
   its center, landing at roughly 7.6%-92.4% x / 8.8%-72.6% y of the
   1672x941 canvas), so whatever renders behind it shows straight through
   as the view out the window. That's a canvas doing a continuous
   warp-speed starfield (JS in service.js, guarded by
   [data-growth-canvas]) plus the occasional planet, drawn the exact same
   way (grows outward from the center, same acceleration curve) instead of
   a separate DOM element sweeping across, unlike the ecommerce door,
   this one runs for as long as the hero is on screen, not once on load.
     .ap-growth-stage, isolated stack: canvas, nebula tint, cockpit
                             image on top
     .ap-growth-canvas, stars AND planets, both drawn every frame, object-fit:cover fills whatever height the
                             hero actually renders at (not a fixed 100svh
                             assumption, see the responsive note below)
     .ap-growth-nebula, static warm radial tint, same orange as
                             .ap-glow--a in global.css (the corner glow
                             every page has), just scoped in here since
                             that one paints behind this hero's own opaque
                             background and would never show through
   Copy is centered here (not the two-column left split the other service
   heroes use), same treatment as the home hero. */
.ap-growth-hero{background:#050505}
.ap-growth-stage{position:absolute; inset:0; z-index:0; overflow:hidden; isolation:isolate}
.ap-growth-canvas{position:absolute; inset:0; width:100%; height:100%; display:block; z-index:0}
/* subtle, off-center so it reads as a distant nebula rather than a halo
   centered on the panda, same rgba(255,90,20,...) as .ap-glow--a. Parked
   in the upper-right, away from the radial scrim's darkest point (dead
   center) which otherwise ate almost all of it, needed more strength
   than .ap-glow--a gets away with since it's fighting that scrim too. */
.ap-growth-nebula{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:radial-gradient(ellipse 55% 46% at 76% 24%, rgba(49, 37, 223, 0.22) 0%, rgba(43, 20, 255, 0.1) 42%, rgba(255,90,20,0) 74%);
  mix-blend-mode:screen;
}
.ap-growth-frame-img{
  /* object-fit:cover instead of the door scene's width:max(100vw,...)
     trick: that formula assumes the hero is exactly 100svh tall, which
     breaks if the copy ever needs more room than that and pushes the
     hero taller (happened on some phones, the image undersized and left
     gaps). cover always fills the element's REAL box, whatever that
     ends up being, cropping the sides on a narrow/tall screen instead, which is exactly what was asked for: the full window height stays
     in frame no matter what, sides are fair game to lose. */
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; object-position:center;
  z-index:2; display:block; pointer-events:none; user-select:none;
}
.ap-growth-hero-scrim{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    radial-gradient(ellipse 62% 58% at 50% 48%, rgba(5,5,5,.74) 0%, rgba(5,5,5,.3) 55%, rgba(5,5,5,0) 82%),
    linear-gradient(180deg, rgba(5,5,5,.55) 0%, rgba(5,5,5,0) 24%, rgba(5,5,5,0) 72%, rgba(5,5,5,.55) 100%);
}

.ap-growth-hero .ap-svc-hero-copy{
  max-width:auto; margin:0 auto; padding-left:0; text-align:center;
}
.ap-growth-hero .ap-svc-hero-copy .ap-eyebrow,
.ap-growth-hero .ap-svc-hero-copy .ap-display,
.ap-growth-hero .ap-svc-hero-copy .ap-lead{text-align:center}
.ap-growth-hero .ap-svc-hero-copy .ap-buttons{justify-content:center}
/* desktop only (this hero's mobile min-height override lives in the
   @media block below, alongside the same fix for the ecommerce door), pulls the copy up off dead-center so less of it sits over the panda. */
.ap-growth-hero .ap-shell{align-self:flex-start; margin-top:15vh}

/* ---------- feature grid ---------- */
.ap-svc-section{position:relative; padding:90px 0}
.ap-svc-head{max-width:640px; margin-bottom:56px}
.ap-svc-head .ap-eyebrow{text-indent:0}
.ap-svc-head .ap-display{text-align:left}

.ap-svc-grid{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:22px}
.ap-svc-card{
  padding:30px 26px; border:1px solid var(--line); border-radius:16px;
  background:rgba(238,240,246,.03);
  transition:border-color .25s, transform .25s, background .25s;
}
.ap-svc-card:hover{border-color:rgba(255,90,20,.5); background:rgba(238,240,246,.05); transform:translateY(-3px)}
.ap-svc-card-num{
  font-family:'Space Mono',monospace; font-size:11px; letter-spacing:.2em;
  color:var(--orange); margin-bottom:16px;
}
.ap-svc-card h3{
  font-family:'Anton',sans-serif; font-weight:400; margin:0 0 12px;
  font-size:20px; line-height:1.15; letter-spacing:.005em; color:#f2f3f8;
}
.ap-svc-card p{margin:0; font-size:14.5px; line-height:1.65; color:var(--muted); text-wrap:pretty}

/* ---------- stats strip ---------- */
.ap-svc-stats{
  display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:22px;
  padding:44px 0; border-top:1px solid var(--line); border-bottom:1px solid var(--line);
}
.ap-svc-stat{text-align:center}
.ap-svc-stat b{
  display:block; font-family:'Anton',sans-serif; font-weight:400;
  font-size:clamp(24px,3vw,42px); color:var(--orange-soft); line-height:1;
  /* same rule as the case band: a figure shrinks to fit, it never breaks */
  white-space:nowrap;
}
.ap-svc-stat span{
  display:block; margin-top:10px; font-family:'Space Mono',monospace;
  font-size:10.5px; letter-spacing:.14em; color:var(--muted); text-transform:uppercase;
}

/* ---------- responsive ---------- */
@media (max-width:1024px){
  .ap-svc-hero{min-height:auto; padding:120px 0 50px; flex-direction:column; justify-content:center}
  /* every other hero here wants to shrink to fit its (now stacked) content,
     but the door scene's cover-sizing math (width:max(100vw, 100svh *
     ratio)) assumes it has close to a full viewport to fill, collapsed to
     min-height:auto, the hero was only a few hundred px tall, so the scene
     rendered as a thin cropped sliver with the doorway (and the panda)
     scrolled out of it entirely. This hero has no .ap-svc-hero-visual to
     stack below the copy anyway, so there's no flow-height downside to
     keeping it near full height here. */
  .ap-ecommerce-hero{min-height:100svh}
  /* same reasoning as the door hero right above, no .ap-svc-hero-visual
     to stack below the copy, so it stays a full-bleed background with the
     (already-centered) copy over it, same as ecommerce on mobile. */
  .ap-growth-hero{min-height:100svh}
  /* the desktop-only upward nudge (align-self:flex-start/margin-top) means
     something different once .ap-svc-hero switches to flex-direction:
     column right above, align-self now controls the HORIZONTAL axis, so
     left it as-is it would shove the copy to the left edge instead of
     nudging it up. Centered copy, no offset, same as every other mobile
     hero here. */
  .ap-growth-hero .ap-shell{align-self:center; margin-top:0}
  .ap-svc-hero .ap-shell{height:auto; min-height:0; grid-template-columns:1fr; gap:50px; text-align:center}
  /* the base rule's max-width:55%/padding-left:10% (further up this file)
     is a desktop-only split, sized for sitting next to the visual in a two-
     column layout, with nothing resetting it here, that same 55% width
     was carrying straight into mobile, squeezing the copy into a narrow
     column that wrapped after two or three words instead of using the
     available line length. Full width here, side to side. */
  .ap-svc-hero-copy{max-width:100%; padding-left:0; text-align:center}
  /* the desktop-only right-hug (margin-left:auto/margin-right:0) would keep
     the copy jammed against the right edge here too, under a monitor that's
     back to being centered and full-width, re-center it like every other
     service hero on mobile. */
  .ap-web-hero .ap-svc-hero-copy{margin-left:auto; margin-right:auto}
  .ap-svc-hero-copy .ap-display,
  .ap-svc-hero-copy .ap-lead{text-align:center}
  .ap-svc-hero-copy .ap-buttons{justify-content:center}

  /* every hero's main image (ship / monitor / bear) drops BELOW the copy
     and buttons on mobile now, across all three hero variants. .ap-shell
     keeps the default order (0) and .ap-svc-hero-visual gets order:2, so
     it always sorts after regardless of which comes first in the markup.
     position:static is what actually matters, though, desktop bleeds this
     layer to an edge via position:absolute, which order can't touch (an
     absolutely-positioned element isn't part of flex flow at all, so the
     old order:-1 here was a silent no-op and the image just sat on top of
     the copy underneath it). Static gives it real flow space instead. */
  .ap-svc-hero-visual{
    position:static; inset:auto; order:2;
    width:min(80vw,440px); height:auto; margin:28px auto 0;
  }
  /* .ap-web-hero .ap-svc-hero-visual (width:100%) and .ap-brand-hero
     .ap-svc-hero-visual (width:min(540vw,1220px), meant for desktop's
     wall-mounted/bottom-right framing) are both two-class selectors, more specific than the single-class rule right above, so they'd keep
     winning on `width` even in here and blow the visual out to ~full
     viewport width (or worse, a flat 1220px on brand-hero, overflowing
     the screen since a static element ignores its `right`/`bottom` but
     not its `width`). Match their specificity to actually override it. */
  .ap-web-hero .ap-svc-hero-visual,
  .ap-brand-hero .ap-svc-hero-visual{
    width:min(80vw,440px);
  }
  /* the bear is in flow below the copy from here down, so nothing bright
     passes under the text and the veil would just be a dark smudge */
  .ap-brand-hero .ap-svc-hero-copy::before{display:none}
  .ap-web-hero{padding-top:150px}
  /* the desktop wall-mount perspective reads as noise at this size, and its
     absolute top/right/width are relative to .ap-svc-hero-visual, which is
     no longer a full-height positioned box on mobile, so drop back to a
     plain, flat, in-flow frame instead of a differently-broken tilt.
     animation:none is the part that actually matters: apMonitorEnter is
     still assigned otherwise, and a running animation overrides a plain
     transform/opacity declaration on the same element regardless of
     specificity, so without this, the monitor was finishing that
     animation's rotateY(-133deg) end state and rendering edge-on, which is
     why it was invisible on mobile before this fix (opacity:0 is that same
     animation's starting value, stuck as soon as the animation itself is
     gone). */

     
  .ap-monitor{
    position:static; width:100%; transform:none; opacity:1; animation:none;
    filter:drop-shadow(0 20px 40px rgba(0,0,0,.55));

    
  }
  /* the desktop screen content is mirrored (scaleX(-1)) to read correctly
     through the rotateY'd frame, with the frame flat on mobile, that mirror
     has nothing to counteract and just flips the screenshots backwards. */
  .ap-monitor-slides{transform:none}

  /* the door scene reads as a background here, not a showcase, copy is
     centered on top of it (not stacked below, unlike the other three
     heroes) so it needs a heavier, uniform scrim rather than the desktop
     version's side-biased gradient that leaves the doorway clean. */
  .ap-ecommerce-hero-scrim{
    background:
      linear-gradient(180deg, rgba(5,5,5,.55) 0%, rgba(5,5,5,.72) 45%, rgba(5,5,5,.55) 100%),
      rgba(5,5,5,.45);
  }

  .ap-svc-grid{grid-template-columns:1fr 1fr; gap:16px}
  .ap-svc-stats{grid-template-columns:1fr 1fr; gap:28px 16px}

  .ap-tuer--1{
      top:10%;
      left:3%;
      width:80px;
    }

    .ap-tuer--2{
      top:55%;
      left:5%;
      width:48px;
    }

    .ap-tuer--3{
      top:32%;
      left:auto;
      right:4%;
      width:75px;
    }

    .ap-tuer--4{
      display:none;
    }

    .ap-tuer--5{
      top:67%;
      left:auto;
      right:8%;
      width:45px;
    }
}
@media (max-width:560px){
  .ap-svc-grid{grid-template-columns:1fr}
  .ap-tuer--1{
  top:7%;
  left:2%;
  width:55px;
}

.ap-tuer--2,
.ap-tuer--4,
.ap-tuer--5{
  display:none;
}

.ap-tuer--3{
  top:29%;
  left:auto;
  right:2%;
  width:55px;
}
}

@media (prefers-reduced-motion:reduce){
  .ap-svc-hero-copy .ap-eyebrow, .ap-svc-hero-copy .ap-display,
  .ap-svc-hero-copy .ap-lead, .ap-svc-hero-copy .ap-buttons, .ap-svc-hero-visual{
    opacity:1; animation:none; transform:none;
  }
  .ap-svc-layer-float{animation:none}
  .ap-monitor{transform:none}
  .ap-monitor-slide.is-active{animation:none}
  .ap-monitor-screen.is-switching{animation:none}
  .ap-brand-hero-bg{animation:none; transform:none; filter:none}
  /* service.js also bails out of openDoor() itself on this query, so this
     just sets the frame it leaves showing: door parked open, light-beam
     floor covers and the edge glow hidden rather than frozen mid-clip. */
  .ap-door-slab{transform:translateX(26%)}
  .ap-door-floor, .ap-door-edge{opacity:0}
  /* service.js skips the rAF loop for the warp canvas on this query (draws
     one still frame instead, and never spawns planets on it). */
}


/* ==========================================================================
   Web Design hero v2: floating device mockups + planet/ring scene
   ========================================================================== */
.ap-web2-visual{position:absolute; inset:0; pointer-events:none}

/* the planet limb hugs the bottom-left corner, bleeding off both edges */
.ap-web2-planet{
  position:absolute; left:max(-90px,-5vw); bottom:-2vh;
  width:min(46vw,900px); max-width:none;
  pointer-events:none;
}

/* Big orbital arc behind the devices. Same geometry on both layers so the
   orb rides exactly the drawn ring; only the ring carries the mask that
   thins its stroke to nothing along one stretch and swells it back. */
.ap-web2-halo,
.ap-web2-orbit{
  /* anchored to the hero's left half so the arc stays behind the devices and
     never crosses the copy column on the right */
  position:absolute; left:0; top:50%;
  width:min(1180px,64vw); aspect-ratio:1/1;
  transform:translate(-26%,-52%) rotate(-10deg);
  pointer-events:none;
}
.ap-web2-halo{
  border:2px solid var(--orange);
  border-radius:50%;
  -webkit-mask-image:conic-gradient(from 150deg at 50% 50%,
    #000 0deg, #000 150deg, transparent 205deg, transparent 220deg, #000 285deg, #000 360deg);
  mask-image:conic-gradient(from 150deg at 50% 50%,
    #000 0deg, #000 150deg, transparent 205deg, transparent 220deg, #000 285deg, #000 360deg);
}

.ap-web2-orb{
  position:absolute; inset:0;
  width:11px; height:11px; border-radius:50%;
  background:#ffd9b8;
  box-shadow:0 0 20px 8px rgba(255,110,30,.8), 0 0 6px 2px rgba(255,210,160,1);
  offset-path:ellipse(50% 50% at 50% 50%);
  offset-rotate:0deg;
  animation:apWeb2Orb 26s linear infinite;
}
@keyframes apWeb2Orb{
  from{offset-distance:0%}
  to{offset-distance:100%}
}

/* device mockups: native-pixel stage scaled to rendered width by service.js */
.ap-mock{position:absolute}
.ap-mock--desktop{
  left:3vw; top:15vh;
  width:min(40vw,800px); aspect-ratio:1106/920;
}
.ap-mock--phone{
  left:calc(3vw + min(31vw,620px)); top:33vh;
  width:min(14vw,280px); aspect-ratio:700/1284;
}
.ap-mock-native{transform-origin:0 0; position:absolute; left:0; top:0}
.ap-mock--desktop .ap-mock-native{width:1106px; height:920px}
.ap-mock--phone   .ap-mock-native{width:700px; height:1284px}
.ap-mock-native > img{position:absolute; inset:0; width:100%; height:100%}

/* the screen overlay: unit rectangle warped onto the frame's baked screen
   quad by a homography precomputed offline from the PNG (see the commit) */
.ap-mock-screen{
  position:absolute; left:0; top:0; transform-origin:0 0; overflow:hidden;
}
/* Each overlay BLEEDS a few rect-space pixels past the screen quad. The
   frame PNGs have a light website baked into their screen, and at the exact
   quad its edge peeked around the overlay as a thin white rim (invisible
   under light slides, obvious under a dark one). The bleed is applied as a
   pre-translate: transforms compose right to left, so translate3d runs
   first, in the rect's own space, and the homography maps the enlarged rect. */
/* Homographies solved from the green-screen guides. The corners come from
   fitting a line to each of the four edges and intersecting them, NOT from
   the extreme green pixels: the screens are rounded rectangles, so extreme
   points sit on the arcs, well inside the true corners, and that inset was
   what let the frame's baked screenshot peek out as a white rim. */
.ap-mock--desktop .ap-mock-screen{
  width:1052px; height:732px;
  transform:matrix3d(1.017357,0.111031,0,0.00000516,-0.029206,1.007809,0,-0.00000224,0,0,1,0,38.8,66.7,0,1)
            translate3d(-6px,-6px,0);
  border-radius:10px;
}
.ap-mock--phone .ap-mock-screen{
  width:448px; height:1168px;
  transform:matrix3d(1.058767,0.101537,0,-0.00006790,-0.146590,0.977719,0,-0.00001942,0,0,1,0,185,17.8,0,1)
            translate3d(-4px,-4px,0);
  border-radius:58px;
}
.ap-mock-screen .ap-monitor-slide{
  position:absolute; inset:0;
  /* cover, anchored top: the captures are 1.92 wide but the screen rect is
     1.44, so fitting by width left a blank strip under them. Covering reads
     as a browser viewport showing the top of the page, which is the point. */
  /* The captures are full-page and pre-cropped to exactly what each rect
     reveals, so cover scales by width and shows the top of the page with no
     side crop at all: a real browser viewport. A capture that happens to be
     shorter than the rect (an above-the-fold export) gets a mild centred
     side crop instead of a blank strip. */
  background-size:cover; background-position:top center;
  background-repeat:no-repeat;
  opacity:0; transition:opacity .6s ease;
}
.ap-mock-screen .ap-monitor-slide.is-active{opacity:1}


/* The typed URL rides the SAME plane as the screen: the browser frame's
   address pill inverse-maps to x 78..573 / y -68..-3 in screen-rect space,
   so this element shares the screen's homography with a rect-space offset
   composed after it (transforms apply right to left). */
.ap-mock-url{
  position:absolute; left:0; top:0;
  width:430px; height:58px;
  transform:matrix3d(1.017357,0.111031,0,0.00000516,-0.029206,1.007809,0,-0.00000224,0,0,1,0,38.8,66.7,0,1)
            translate3d(168px,-62px,0);
  transform-origin:0 0;
  display:flex; align-items:center;
  font-size:16px; letter-spacing:.01em;
  color:rgba(238,240,246,.85);
  white-space:nowrap; overflow:hidden;
}
.ap-mock-url::after{
  content:''; width:1.5px; height:17px; margin-left:3px;
  background:rgba(238,240,246,.7);
  animation:apCaret 1.1s steps(1) infinite;
}
@keyframes apCaret{0%,49%{opacity:1}50%,100%{opacity:0}}

.ap-web2-scroll{
  position:absolute; right:clamp(24px,3.5vw,60px); bottom:44px; z-index:3;
  display:flex; flex-direction:column; align-items:center; gap:8px;
  font-family:'Space Mono',monospace; font-size:10.5px; letter-spacing:.3em;
  color:rgba(238,240,246,.45);
}
.ap-web2-scroll i{font-style:normal; color:var(--orange); animation:apPulse 2.4s ease-in-out infinite}

/* this hero's primary action goes back to loud solid orange (the site-wide
   .ap-btn voice stays outlined; mockup calls for a strong primary here) */
.ap-web2-copy-primary,
.ap-web-hero .ap-btn--solid{
  background:var(--orange); border-color:transparent; color:#fff;
}
.ap-web-hero .ap-btn--solid span{color:#fff}
.ap-web-hero .ap-btn--solid:hover{background:#ff7d2e; color:#fff}

@media (prefers-reduced-motion: reduce){
  .ap-web2-orb{animation:none}
}
@media (max-width:1024px){
  .ap-web2-scene,.ap-web2-scroll{display:none}
  .ap-mock{position:relative; left:auto; top:auto; margin:0 auto}
  .ap-mock--phone{display:none}
}


/* ==========================================================================
   Industries: a carousel you can actually scroll
   ========================================================================== */
/* padding gives way before the cards do, so the whole section clears a
   short laptop screen */
.ap-ind{overflow:hidden; padding-block:clamp(64px,8vh,90px)}
.ap-ind-head{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:40px; flex-wrap:wrap; margin-bottom:38px;
}
.ap-ind-headings{max-width:760px}
.ap-ind .ap-eyebrow{display:flex; align-items:center; gap:14px}
.ap-ind .ap-eyebrow i{display:block; width:46px; height:2px; background:var(--orange); flex:none}
.ap-ind-aside{display:flex; flex-direction:column; align-items:flex-start; gap:12px; max-width:34ch}
.ap-ind-lead{margin:0; font-size:16px; font-weight:300; line-height:1.5; color:var(--muted)}
.ap-ind-all{
  font-size:15px; font-weight:400; color:var(--orange);
  border-bottom:1px solid color-mix(in srgb,var(--orange) 45%,transparent);
  padding-bottom:2px;
}
.ap-ind-all span{display:inline-block; transition:translate .25s ease}
.ap-ind-all:hover span{translate:2px -2px}

/* The track is full bleed and padded to the shell, so the first card lines
   up with the headline while the last one runs off the right edge. */
.ap-ind-track{
  width:100vw; margin-left:calc(50% - 50vw);
  overflow-x:auto; overflow-y:hidden;
  scroll-snap-type:x mandatory;
  scrollbar-width:none;
  --gut:calc((100vw - min(var(--shell),100vw - 88px)) / 2);
  /* vertical room for the hover lift: the track clips its overflow-y, so at
     4px the card's -6px travel was being cut off the top */
  padding:18px var(--gut) 20px;
  /* without this the browser snaps the first card flush to the scrollport
     and swallows the left gutter on load */
  scroll-padding-inline:var(--gut);
}
.ap-ind-track::-webkit-scrollbar{display:none}
/* .is-grab lands on pointerdown, .is-dragging only once the pointer has
   actually moved, so a plain click on a card never looks like a drag */
.ap-ind-track.is-grab{cursor:grab}
.ap-ind-track.is-dragging{cursor:grabbing; scroll-snap-type:none; user-select:none}
.ap-ind-track.is-dragging .ap-ind-card{pointer-events:none}
@media (hover:hover) and (pointer:fine){ .ap-ind-track{cursor:grab} }
.ap-ind-track:focus-visible{outline:2px solid var(--orange); outline-offset:6px}
.ap-ind-rail{display:flex; gap:22px; width:max-content}

/* One portrait panel per industry: the art fills the card and the copy sits
   on top of it, so a card with a photograph and a card with the fallback
   artwork are the same object rather than two different layouts. */
.ap-ind-card{
  scroll-snap-align:start;
  position:relative; display:block;
  /* short enough that the headline, the row and the controls all fit in one
     screen on a 900px-tall laptop */
  width:clamp(248px,21vw,340px); aspect-ratio:1/1.24; flex:none; cursor:pointer;
  border:1px solid var(--line); border-radius:14px; overflow:hidden;
  background:#07080c;
  transition:border-color .35s ease, translate .35s ease;
}
/* the hover reads on the card whether or not it is a link yet, so the set
   behaves the same the day the industry pages go live */
.ap-ind-card:hover{border-color:var(--orange); translate:0 -6px}

.ap-ind-media{position:absolute; inset:0; display:block; overflow:hidden}
.ap-ind-media img{
  width:100%; height:100%; object-fit:cover; object-position:center; display:block;
  transition:scale .6s cubic-bezier(.2,.7,.3,1);
}
.ap-ind-card:hover .ap-ind-media img{scale:1.05}
/* Two scrims rather than one animated gradient: gradients with different
   stops do not interpolate, but cross-fading two layers does. The resting
   one only darkens the foot; the hover one covers the whole card. */
.ap-ind-media::after,.ap-ind-media::before{
  content:""; position:absolute; inset:0; transition:opacity .45s ease;
}
.ap-ind-media::after{
  background:linear-gradient(180deg,rgba(3,3,5,0) 34%,rgba(3,3,5,.55) 62%,rgba(3,3,5,.93) 100%);
}
.ap-ind-media::before{
  z-index:1; opacity:0;
  background:linear-gradient(180deg,rgba(3,3,5,.62) 0%,rgba(3,3,5,.76) 46%,rgba(3,3,5,.96) 100%);
}
.ap-ind-card:hover .ap-ind-media::before{opacity:1}

/* Fallback artwork, for industries with no photograph yet. Not a grey box:
   a lit horizon plus a slice of orbit, the same two shapes the rest of the
   page is built from. --hue and --i tilt each one so the row is not six
   copies of the same picture. */
.ap-ind-card--art{
  background:
    radial-gradient(135% 85% at 12% 108%, hsl(var(--hue) 95% 54% / .62) 0%, hsl(var(--hue) 90% 30% / .28) 34%, transparent 66%),
    radial-gradient(80% 60% at 88% -6%, hsl(calc(var(--hue) + 16deg) 90% 58% / .2) 0%, transparent 62%),
    #06070b;
}
.ap-ind-glow{
  position:absolute; left:-58%; bottom:-96%;
  width:186%; aspect-ratio:1/1; border-radius:50%;
  border:1px solid rgba(255,255,255,.14);
  rotate:calc(var(--i) * -7deg);
  box-shadow:0 -26px 70px -30px hsl(var(--hue) 95% 55% / .75);
}
/* a second, tighter arc so the motif reads as an orbit and not as a ring */
.ap-ind-glow::after{
  content:""; position:absolute; inset:13%;
  border-radius:50%; border:1px solid hsl(var(--hue) 95% 58% / .3);
  -webkit-mask-image:linear-gradient(200deg,#000 26%,transparent 62%);
  mask-image:linear-gradient(200deg,#000 26%,transparent 62%);
}
.ap-ind-mark{
  position:absolute; right:.1em; top:.02em;
  font-family:'Anton',sans-serif; font-size:clamp(120px,13vw,190px); line-height:1;
  color:rgba(255,255,255,.07); letter-spacing:-.03em; user-select:none;
}

.ap-ind-body{
  position:absolute; left:0; right:0; bottom:0; z-index:2;
  display:flex; flex-direction:column; align-items:flex-start; gap:7px;
  padding:24px 24px 22px;
  transition:translate .45s cubic-bezier(.2,.7,.3,1);
}
.ap-ind-card:hover .ap-ind-body{translate:0 -10px}
.ap-ind-title{font-size:21px; font-weight:500; letter-spacing:.01em; color:var(--paper)}
/* two lines reserved, so the titles line up across the row even when one
   description wraps and its neighbour does not */
.ap-ind-desc{font-size:14.5px; font-weight:300; line-height:1.5; color:var(--muted); text-wrap:pretty; min-height:3em}
.ap-ind-cta{margin-top:6px; font-size:14.5px; color:var(--orange)}
.ap-ind-cta span{display:inline-block; transition:translate .25s ease}
.ap-ind-card:hover .ap-ind-cta span{translate:2px -2px}

/* Controls. JS unhides them, so a page without scripting never shows arrows
   that cannot do anything. */
.ap-ind-controls{display:flex; align-items:center; gap:24px; margin-top:34px}
.ap-ind-hint{font-size:14px; font-weight:300; color:var(--muted)}
.ap-ind-bar{
  position:relative; flex:0 1 520px; max-width:520px; height:2px;
  background:var(--line); margin-left:auto;
}
.ap-ind-bar i{
  position:absolute; left:0; top:0; height:100%; width:var(--p,20%);
  background:var(--orange); transition:width .25s ease;
}
.ap-ind-arrows{display:flex; gap:10px}
.ap-ind-arrow{
  width:46px; height:46px; border-radius:50%; flex:none;
  border:1px solid var(--line); background:none; color:var(--paper);
  font-size:17px; line-height:1; cursor:pointer;
  transition:border-color .25s ease, color .25s ease, background .25s ease;
}
.ap-ind-arrow:hover:not(:disabled){border-color:var(--orange); color:var(--orange)}
.ap-ind-arrow:disabled{opacity:.3; cursor:default}
.ap-ind-arrow--next:not(:disabled){border-color:var(--orange); color:var(--orange)}

@media (max-width:1024px){
  .ap-ind-track{--gut:calc((100vw - min(var(--shell),100vw - 48px)) / 2)}
}
@media (max-width:640px){
  .ap-ind-track{--gut:16px; scroll-snap-type:x proximity}
  .ap-ind-rail{gap:16px}
  .ap-ind-controls{flex-wrap:wrap; gap:16px; margin-top:26px}
  /* the hint is for pointer users; on touch, swiping is the obvious move */
  .ap-ind-hint{display:none}
  .ap-ind-bar{order:3; flex-basis:100%; max-width:none; margin-left:0}
  .ap-ind-arrows{margin-left:auto}
}
@media (prefers-reduced-motion:reduce){
  .ap-ind-card,.ap-ind-media img{transition:none}
}


/* ==========================================================================
   FAQ: copy and panda on the left, the questions on the right
   ========================================================================== */
.ap-faq{overflow:hidden}
.ap-faq-grid{
  display:grid; grid-template-columns:minmax(0,1.04fr) minmax(0,1.1fr);
  gap:clamp(30px,5vw,80px); align-items:start;
}
.ap-faq-aside{position:relative; display:flex; flex-direction:column; align-items:flex-start}
/* capped so the headline keeps the two lines its <br> is written for */
.ap-faq-aside .ap-display{font-size:clamp(32px,3.7vw,60px); margin-top:14px}
.ap-faq-lead{
  margin:18px 0 0; max-width:30ch;
  font-size:16px; font-weight:300; line-height:1.55; color:var(--muted);
}
/* Bleeds off the left edge of the shell, the way it would off the page.
   The mask is back because this artwork is cropped at the bottom: without
   it the leg ends on a flat horizontal line in the middle of the section. */
.ap-faq-panda{
  display:block; width:min(148%,800px); max-width:none;
  margin:clamp(14px,2.6vw,34px) 0 0 min(-30%,-190px);
  -webkit-mask-image:linear-gradient(180deg,#000 70%,transparent 97%);
  mask-image:linear-gradient(180deg,#000 70%,transparent 97%);
  /* the same drift the hero mockups have, a touch slower: it is scenery,
     not a thing to look at */
  animation:apSvcFloat 7.5s ease-in-out infinite;
}
/* The reveal is an animation now, and it would otherwise replace the drift
   above for good. Both, in order: reveal first, drift once it has landed. */
.ap-faq-panda.is-in{
  animation:apReveal .7s cubic-bezier(.22,.61,.36,1) both,
            apSvcFloat 7.5s ease-in-out .7s infinite;
}

/* the questions */
.ap-faq-list{display:flex; flex-direction:column}
.ap-faq-item{border-top:1px solid var(--line)}
.ap-faq-item:last-of-type{border-bottom:1px solid var(--line)}
.ap-faq-q{
  position:relative; width:100%; cursor:pointer;
  display:flex; align-items:center; justify-content:space-between; gap:24px;
  padding:clamp(18px,2.4vh,26px) 0 clamp(18px,2.4vh,26px) clamp(18px,2vw,28px);
  border:0; background:none; text-align:left; font-family:inherit;
  font-size:clamp(16px,1.35vw,20px); font-weight:400; color:var(--paper);
  transition:color .25s ease;
}
.ap-faq-q:hover{color:var(--orange)}
/* the bar that marks the open question, drawn on the item so it spans the
   answer as well as the question */
.ap-faq-item::before{
  content:""; position:absolute; left:0; width:2px; height:0;
  background:var(--orange); transition:height .45s cubic-bezier(.3,.8,.35,1);
}
.ap-faq-item{position:relative}
.ap-faq-item.is-open::before{height:calc(100% - 2px)}

.ap-faq-sign{
  position:relative; flex:none; width:34px; height:34px; border-radius:50%;
  border:1px solid var(--line);
  transition:border-color .25s ease, rotate .35s cubic-bezier(.3,.8,.35,1);
}
.ap-faq-sign::before,.ap-faq-sign::after{
  content:""; position:absolute; left:50%; top:50%; background:currentColor;
  translate:-50% -50%;
}
.ap-faq-sign::before{width:11px; height:1.5px}
.ap-faq-sign::after{width:1.5px; height:11px; transition:opacity .25s ease, scale .35s ease}
.ap-faq-item.is-open .ap-faq-sign{border-color:var(--orange); color:var(--orange); rotate:180deg}
.ap-faq-item.is-open .ap-faq-sign::after{opacity:0; scale:1 0}
.ap-faq-q:hover .ap-faq-sign{border-color:var(--orange)}

/* 0fr → 1fr animates a height nobody has to measure */
.ap-faq-a{
  display:grid; grid-template-rows:0fr;
  transition:grid-template-rows .45s cubic-bezier(.3,.8,.35,1);
}
.ap-faq-item.is-open .ap-faq-a{grid-template-rows:1fr}
.ap-faq-a > div{overflow:hidden}
.ap-faq-a p{
  margin:0; padding:0 56px clamp(20px,2.6vh,28px) clamp(18px,2vw,28px);
  font-size:clamp(14.5px,1.1vw,16.5px); font-weight:300; line-height:1.6;
  color:var(--muted); text-wrap:pretty;
}

.ap-faq-more{
  display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  margin:clamp(26px,3.4vh,40px) 0 0;
  font-size:15px; font-weight:300; color:var(--muted);
}
.ap-faq-link{
  display:inline-flex; align-items:center; gap:9px; cursor:pointer;
  color:var(--orange); font-size:16px;
  border-bottom:1px solid color-mix(in srgb,var(--orange) 45%,transparent);
  padding-bottom:2px;
}
.ap-faq-link .ap-arw{transition:translate .25s ease}
.ap-faq-link:hover .ap-arw{translate:2px -2px}

@media (max-width:1000px){
  .ap-faq-grid{grid-template-columns:1fr; gap:34px}
  .ap-faq-aside{align-items:flex-start}
  .ap-faq-panda{width:min(112%,520px); margin-left:0; align-self:center}
}
@media (prefers-reduced-motion:reduce){
  .ap-faq-a,.ap-faq-item::before,.ap-faq-sign{transition:none}
  /* has to live after the rule it cancels: the shared override further up
     the file loses to a later declaration of equal specificity */
  .ap-faq-panda{animation:none}
}


/* ==========================================================================
   Scope & investment: three glass cards over the planet limb
   ========================================================================== */
.ap-scope{position:relative; overflow:hidden; padding-block:clamp(90px,12vh,150px)}
/* the photograph, faded to alpha at both ends so it joins the sections
   around it the same way the rest of the page's artwork does */
.ap-scope::before{
  content:''; position:absolute; inset:0; z-index:0; pointer-events:none;
  background:url('../../uploads/pricing-bg.webp') center / cover no-repeat;
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 16%,#000 82%,transparent 100%);
  mask-image:linear-gradient(180deg,transparent 0,#000 16%,#000 82%,transparent 100%);
}
.ap-scope .ap-shell{position:relative; z-index:1}

.ap-scope-head{
  display:flex; flex-direction:column; align-items:center; text-align:center;
  gap:16px; margin-bottom:clamp(40px,6vh,72px);
}
.ap-scope .ap-eyebrow{display:flex; align-items:center; gap:14px; text-indent:0}
.ap-scope .ap-eyebrow i{display:block; width:46px; height:2px; background:var(--orange); flex:none}
.ap-scope .ap-display{text-align:center; font-size:clamp(30px,3.4vw,56px); max-width:20ch}
.ap-scope-lead{margin:0; max-width:52ch; font-size:16px; font-weight:300; line-height:1.55; color:var(--muted)}

/* The middle card is taller: align-items:center on the row lets the other
   two centre against it instead of stretching to match. */
.ap-scope-grid{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(18px,2vw,30px); align-items:center;
}
.ap-scope-card{
  position:relative; display:flex; flex-direction:column; align-items:flex-start;
  gap:14px; padding:clamp(26px,2.6vw,38px) clamp(22px,2.2vw,32px);
  border:1px solid rgba(238,240,246,.16); border-radius:20px;
  background:rgba(12,10,16,.42);
  -webkit-backdrop-filter:blur(18px) saturate(1.1);
  backdrop-filter:blur(18px) saturate(1.1);
  --glow:0 0 0 0 rgba(255,90,20,0);
  box-shadow:0 30px 70px -40px rgba(0,0,0,.9), var(--glow);
  transition:border-color .4s ease, background .4s ease, box-shadow .45s ease;
  /* Irregular on purpose: three durations that do not divide into each
     other, so the cards never line up into one synchronised bob. */
  animation:apScopeFloat var(--dur,8.3s) ease-in-out var(--delay,0s) infinite;
}
.ap-scope-card:nth-child(1){--dur:7.2s; --delay:-1.4s}
.ap-scope-card:nth-child(2){--dur:9.7s; --delay:-4.1s}
.ap-scope-card:nth-child(3){--dur:8.3s; --delay:-2.6s}
@keyframes apScopeFloat{0%,100%{translate:0 0}50%{translate:0 -13px}}

/* The glow is an outer box-shadow, not a pseudo-element behind the card.
   These cards are translucent, so anything painted behind one shows
   THROUGH it and tints the inside; an outer shadow is defined as not
   painted within the border box, so it stays strictly outside no matter
   how see-through the card is. Carried in a variable so both the plain and
   the featured card can add it to their own shadow stack. */
.ap-scope-card:hover{
  --glow:0 0 150px 34px rgba(255,90,20,.30);
  border-color:color-mix(in srgb,var(--orange) 55%,transparent);
}

.ap-scope-card.is-featured{
  padding-block:clamp(40px,4.6vh,62px);
  border-color:color-mix(in srgb,var(--orange) 55%,transparent);
  background:rgba(22,12,8,.5);
  box-shadow:0 40px 90px -46px rgba(0,0,0,.95), 0 0 60px -30px rgba(255,90,20,.45), var(--glow);
}
.ap-scope-flag{
  position:absolute; top:0; left:clamp(22px,2.2vw,32px); translate:0 -50%;
  padding:6px 13px; border-radius:999px; background:var(--orange); color:#120c07;
  font-family:'Space Mono',monospace; font-size:10px; letter-spacing:.16em; text-transform:uppercase;
}

.ap-scope-title{
  margin:0; font-family:'Anton',sans-serif; font-weight:400;
  font-size:clamp(24px,2.2vw,34px); line-height:1.05; letter-spacing:.01em;
  text-transform:uppercase; color:var(--paper);
}
/* The figure, between the tier name and what it covers. Tabular digits so
   three cards stacked beside each other line their numbers up, and the
   "From" and the unit stay quiet: the number is the thing being read. */
.ap-scope-price{
  display:flex; align-items:baseline; gap:7px; flex-wrap:wrap;
  margin:-4px 0 2px; font-variant-numeric:tabular-nums;
}
.ap-scope-price small{
  font-family:'Space Mono',monospace; font-size:10.5px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--muted);
}
.ap-scope-price b{
  font-family:'Anton',sans-serif; font-weight:400; letter-spacing:.01em;
  font-size:clamp(26px,2.4vw,36px); line-height:1; color:var(--orange);
}
.ap-scope-price span{font-size:14px; font-weight:300; color:var(--muted)}
/* One note under the three, not an asterisk on every number. */
.ap-scope-note{
  margin:clamp(26px,4vh,40px) auto 0; max-width:74ch; text-align:center;
  font-size:13.5px; font-weight:300; line-height:1.6; color:var(--muted);
  text-wrap:pretty;
}

.ap-scope-desc{margin:0; font-size:15px; font-weight:300; line-height:1.55; color:var(--muted); text-wrap:pretty}
.ap-scope-list{list-style:none; margin:6px 0 0; padding:0; display:flex; flex-direction:column; gap:11px; width:100%}
.ap-scope-list li{
  display:flex; align-items:flex-start; gap:11px;
  font-size:14.5px; font-weight:300; line-height:1.5; color:rgba(238,240,246,.86);
}
.ap-scope-list i{
  flex:none; font-style:normal; font-family:'Space Mono',monospace;
  font-size:14px; line-height:1.5; color:var(--orange);
}
.ap-scope-cta{
  margin-top:auto; padding-top:8px;
  display:inline-flex; align-items:center; gap:10px; cursor:pointer;
  font-size:12.5px; font-weight:700; letter-spacing:.2em; color:var(--paper);
  border-bottom:1px solid color-mix(in srgb,var(--orange) 50%,transparent);
  padding-bottom:6px;
  transition:color .25s ease, border-color .25s ease;
}
.ap-scope-cta:hover{color:var(--orange); border-bottom-color:var(--orange)}
.ap-scope-cta .ap-arw{transition:translate .25s ease}
.ap-scope-cta:hover .ap-arw{translate:2px -2px}

@media (max-width:900px){
  .ap-scope-grid{grid-template-columns:1fr; max-width:460px; margin-inline:auto}
  .ap-scope-card.is-featured{padding-block:clamp(34px,4vh,48px)}
  /* stacked, a floating card just wobbles against its neighbours */
  .ap-scope-card{animation:none}
}
@media (prefers-reduced-motion:reduce){
  .ap-scope-card{animation:none}
}

/* ======================================================================
   WEB DESIGN, TABLET (761 to 1024)
   ----------------------------------------------------------------------
   Between the phone block and the desktop layout the copy is centred and
   stacked, but the two device mockups kept their desktop placement:
   top:15vh / 33vh and widths in vw, measured against the hero. On a tablet
   that drops the browser frame straight onto the headline, and at 1024 the
   title sat entirely behind the screenshot (1.1:1). The 1024 block above
   was written for an older .ap-monitor element and no longer reaches the
   mockups at all.

   Here the visual becomes a real box in the flow under the copy, with its
   own aspect ratio, and the devices are placed in percentages of that box,
   so they cannot reach the copy whatever the tablet's height. The frame
   PNGs and service.js's homography still apply at this width: only the
   stage moves, not how the screens are mapped.
   ====================================================================== */
@media (min-width:761px) and (max-width:1024px){
  .ap-web-hero .ap-svc-hero-visual{
    position:relative; order:2; inset:auto;
    width:min(88vw,760px); height:auto; aspect-ratio:1.5;
    margin:40px auto 0; overflow:visible;
  }
  .ap-web-hero .ap-svc-layer-parallax,
  .ap-web-hero .ap-svc-layer-float{
    position:absolute; inset:0; transform:none; animation:none;
  }
  /* the 1024 block further up makes .ap-mock relative and centred, a plain
     stack, and hides the phone; inside the box they go back to absolute */
  .ap-web-hero .ap-mock--desktop,
  .ap-web-hero .ap-mock--phone{position:absolute; margin:0}
  .ap-web-hero .ap-mock--desktop{left:0; top:4%; width:76%}
  .ap-web-hero .ap-mock--phone{display:block; left:auto; right:3%; top:20%; width:24%}
  .ap-web2-halo, .ap-web2-orbit{display:none}
  .ap-web2-planet{left:-12%; bottom:-14%; width:72%}
}

/* ======================================================================
   THE PHONE HERO
   ----------------------------------------------------------------------
   One shape for all six: copy at the top, left aligned, artwork full bleed
   underneath filling the rest of the screen. The max-width:1024px block
   further up still owns tablets, where the copy stays centred and the art
   is a small block under it; everything here is keyed to 760px so a phone
   gets the comped layout and nothing else moves.

   The artwork framing is per hero, because the six pictures have nothing
   in common: two of them are cutouts that can be placed anywhere, three
   are full-frame photographs whose subject sits somewhere other than the
   middle, and one is painted to a canvas by JavaScript.
   ====================================================================== */
@media (max-width:760px){

  /* ---------- the shared frame ---------- */
  .ap-svc-hero{
    min-height:100svh; padding:0;
    display:flex; flex-direction:column; justify-content:flex-start;
  }
  /* the copy takes the height it needs and no more; the art gets the rest */
  .ap-svc-hero .ap-shell{
    flex:0 0 auto; height:auto; min-height:0;
    padding-top:92px; padding-bottom:20px;
    text-align:left; gap:0;
  }
  .ap-svc-hero-copy,
  .ap-svc-hero-copy .ap-display,
  .ap-svc-hero-copy .ap-lead{text-align:left}
  /* the eyebrow repeats the breadcrumb and the headline right under it, and
     on a phone it is a third line of small caps in the way */
  .ap-svc-hero-copy .ap-eyebrow{display:none}
  /* the headline is the hero at this size; the lead gives back the room */
  .ap-svc-hero-copy .ap-display{font-size:clamp(42px,12vw,62px); line-height:1.02}
  .ap-svc-hero-copy .ap-lead{font-size:14.5px; line-height:1.55; margin-top:14px}
  .ap-svc-hero-copy{max-width:100%; padding-left:0; margin:0}
  .ap-web-hero .ap-svc-hero-copy{margin:0; padding-left:0}
  /* The one hero that keeps its desktop arrangement: the cockpit frames the
     copy, so the copy is centred inside the window rather than ranged left
     against a structural edge, and it needs room on all three sides to stay
     off the frame. The window's inner edge is at about 8% of the width. */
  .ap-growth-hero .ap-shell{align-self:stretch; margin-top:0; padding-inline:34px}
  .ap-growth-hero .ap-svc-hero-copy{padding-top:22px}
  .ap-growth-hero .ap-svc-hero-copy .ap-display,
  .ap-growth-hero .ap-svc-hero-copy .ap-lead{text-align:center}
  .ap-growth-hero .ap-svc-hero-copy .ap-display{font-size:clamp(34px,9.5vw,52px)}
  .ap-growth-hero .ap-svc-hero-copy .ap-buttons{justify-content:center}
  /* the two actions fit side by side at this size and the comps keep them
     that way, unlike the home hero where the labels are longer */
  .ap-svc-hero-copy .ap-buttons{justify-content:flex-start; gap:10px; flex-wrap:nowrap}
  /* both have to live inside the shell's 358px, gap included, so they are
     measured to fit rather than left at the desktop padding, which ran the
     second one 22px past the right edge */
  .ap-svc-hero-copy .ap-btn{
    padding:13px 13px; font-size:11.5px; letter-spacing:.06em;
    white-space:nowrap; flex:0 1 auto; min-width:0;
  }
  .ap-svc-hero-copy .ap-btn .ap-arw{width:11px}

  /* ---------- the art band ----------
     Overrides the 1024 block's width:min(80vw,440px) on all three hero
     variants at once; those are two-class selectors there, so the plain
     one would lose to them even from inside this later query. */
  .ap-svc-hero-visual,
  .ap-web-hero .ap-svc-hero-visual,
  .ap-brand-hero .ap-svc-hero-visual{
    position:relative; order:2; flex:1 1 0;
    width:100%; max-width:none; min-height:34svh; height:auto;
    margin:0; inset:auto; overflow:hidden;
  }
  /* the wrappers carried the artwork's own height on desktop; in the band
     they have to fill it instead so an absolutely placed cutout has
     something to resolve its percentages against */
  .ap-svc-hero-visual .ap-svc-layer-parallax,
  .ap-svc-hero-visual .ap-svc-layer-float{
    position:absolute; inset:0; width:100%; height:100%;
  }

  /* ======================================================================
     THE ARTWORK
     Six portrait renders, one per hero, composed for this screen, so the
     framing is the image rather than a pile of offsets on the desktop
     layers. Those layers are switched off below; what is left on is only
     what has to show THROUGH the art.
     ====================================================================== */

  /* the four that are a single opaque layer */
  .ap-svc-hero{background-position:center bottom; background-size:cover; background-repeat:no-repeat}
  .ap-software-hero{background-image:url('../../uploads/svc-mobile/custom-software.webp')}
  /* the only one of the four plain ones with nothing between the copy and
     the picture: the Spanish lead wraps onto the lit hull at 360 and read
     1.43:1 there */
  .ap-software-hero::before{
    content:''; position:absolute; inset:0; z-index:0; pointer-events:none;
    background:linear-gradient(180deg,
      rgba(5,5,8,.92) 0%, rgba(5,5,8,.86) 38%, rgba(5,5,8,.64) 52%,
      rgba(5,5,8,.26) 64%, rgba(5,5,8,0) 80%);
  }
  /* anchored to the top and oversized, so the panda drops down the screen
     instead of being pinned to the bottom by cover */
  .ap-ecommerce-hero{
    background-image:url('../../uploads/svc-mobile/ecommerce.webp');
    background-size:122% auto; background-position:50% 0;
  }
  .ap-brand-hero{
    background-image:url('../../uploads/svc-mobile/brand.webp');
    background-size:120% auto; background-position:50% 0;
  }
  /* the bear's lit shoulder runs straight behind the buttons; the scrim has
     to hold past them rather than fading out above them */
  .ap-brand-hero::before{
    content:''; position:absolute; inset:0; z-index:0; pointer-events:none;
    background:linear-gradient(180deg,
      rgba(5,5,8,.9) 0%, rgba(5,5,8,.84) 40%, rgba(5,5,8,.7) 56%,
      rgba(5,5,8,.42) 68%, rgba(5,5,8,.12) 80%, rgba(5,5,8,0) 92%);
  }
  .ap-maint-hero{background-image:url('../../uploads/svc-mobile/maintenance.webp')}

  /* the desktop machinery those four no longer need */
  .ap-software-hero .ap-svc-hero-visual,
  .ap-brand-hero .ap-svc-hero-visual,
  .ap-door-stage,
  .ap-brand-hero-bg,
  .ap-brand-hero-scrim,
  .ap-maint-bg,
  .ap-maint-scene,
  .ap-maint-hero .load-status,
  .ap-planet,
  .ap-tuer{display:none}

  /* ---------- web design: two holes over the live screenshots ----------
     The visual becomes a cover-sized box carrying the render's own aspect
     ratio, so the holes keep their place whatever the phone's shape is, and
     the mockup screens are positioned in percentages of that same box. The
     art rides on top of them; the device frames in it replace the frame
     PNGs entirely, which is why those images are dropped. */
  .ap-web-hero{background-image:none}
  /* Stretched to the hero rather than cover-cropped, because that is what
     keeps the holes under the screens: with cover, the crop changes with
     the phone's shape and the screens drift off the holes. The render is
     0.4626 wide to tall and the phones this runs on are 0.45 to 0.465, so
     the stretch is under 3% and nothing in the picture reads as distorted,
     while the alignment is exact by construction. */
  .ap-web-hero .ap-svc-hero-visual{
    position:absolute; inset:0;
    order:0; flex:none; z-index:0; overflow:hidden;
    width:auto; height:auto; min-height:0; aspect-ratio:auto;
  }
  .ap-web-hero .ap-svc-hero-visual::after{
    content:''; position:absolute; inset:0; z-index:2; pointer-events:none;
    background:url('../../uploads/svc-mobile/web-design.webp') 0 0 / 100% 100% no-repeat;
  }
  /* The render puts the devices at 47% of its height. Our lead wraps to one
     line more than the comp did at its own width, so the copy reaches about
     59% here and the last line lands on the desktop mockup. Every other hero
     already carries a scrim for exactly this; this one needed one too. It is
     inside the visual, which is its own stacking context at z-index 0, so it
     stays under the copy without competing with it. */
  .ap-web-hero .ap-svc-hero-visual::before{
    content:''; position:absolute; inset:0; z-index:3; pointer-events:none;
    background:linear-gradient(180deg,
      rgba(5,5,6,.95) 0%, rgba(5,5,6,.9) 40%, rgba(5,5,6,.72) 54%,
      rgba(5,5,6,.36) 64%, rgba(5,5,6,.1) 74%, rgba(5,5,6,0) 84%);
  }
  .ap-web2-planet, .ap-web2-halo, .ap-web2-orbit, .ap-bottom-fade{display:none}
  /* animation:none is the part that matters: apSvcFloat was still running and
     a running animation beats a plain transform declaration whatever the
     specificity, so the stage sat 14px above where the holes are. The devices
     are painted into the render now, so there is nothing left to bob. */
  .ap-web-hero .ap-svc-layer-parallax,
  .ap-web-hero .ap-svc-layer-float{
    position:absolute; inset:0; transform:none; animation:none;
  }
  /* The holes are not rectangles. Both devices are drawn in perspective, so
     a screenshot dropped into the hole's bounding box reads dead flat
     against a tilted frame. They need the same treatment the desktop hero
     gives its frame PNGs: a homography mapping a plain rect onto the quad.

     The quads below were solved from the keyed render's own alpha channel,
     in its own 853x1844 pixel space, which is why both stages are that size
     and service.js scales them to the rendered width from there. The source
     rects carry a 3px bleed so the keyed edge never shows a seam. */
  .ap-mock{position:absolute; z-index:1; aspect-ratio:auto; transform:none}
  /* .ap-mock--desktop/--phone carry their desktop width as two-class
     selectors, so a plain .ap-mock{inset:0} left the stage at 40vw and the
     screens landed nowhere near their holes */
  .ap-web-hero .ap-mock--desktop,
  .ap-web-hero .ap-mock--phone{
    display:block; left:0; top:0; right:auto; bottom:auto;
    width:100%; height:100%;
  }
  .ap-web-hero .ap-mock-native{
    position:absolute; left:0; top:0;
    width:853px !important; height:1844px !important;
  }
  .ap-web-hero .ap-mock-native > img{display:none}
  .ap-web-hero .ap-mock-screen{
    position:absolute; left:0; top:0; transform-origin:0 0; border-radius:0;
    overflow:hidden;
  }
  /* The render draws the screen taller than a browser viewport is: the quad
     unwarps to 1.20 wide to tall against the captures' 1.44, so cover has to
     lose something off the sides. Anchored left, it keeps the page's own left
     edge, where the logo and the first column are, and loses the empty right
     instead of biting into both. */
  .ap-web-hero .ap-mock-screen .ap-monitor-slide{background-position:top left}
  .ap-web-hero .ap-mock--desktop .ap-mock-screen{
    width:573px !important; height:479px !important;
    transform:matrix3d(1.120217,0.210745,0,0.00007237,-0.028056,1.586050,0,0.00043171,0,0,1,0,88.148,880.066,0,1) !important;
  }
  .ap-web-hero .ap-mock--phone .ap-mock-screen{
    width:168px !important; height:486px !important;
    transform:matrix3d(0.787481,-0.153443,0,-0.00016569,-0.248846,0.669500,0,-0.00019215,0,0,1,0,645.306,1063.687,0,1) !important;
  }

  /* ---------- growth: the window is a hole over the starfield ----------
     The one hero that had no portrait source and was holding. Same
     arrangement the desktop has: the canvas paints the stars, the frame
     sits over it with the window cut out. */
  .ap-growth-hero{background-image:none}
  .ap-growth-frame-img{display:none}
  .ap-growth-nebula{display:none}
  /* same reasoning as the web hero: stretched, not cover-cropped, so the
     window stays exactly over the canvas area it is meant to reveal */
  .ap-growth-stage::after{
    content:''; position:absolute; inset:0; z-index:2; pointer-events:none;
    background:url('../../uploads/svc-mobile/growth.webp') 0 0 / 100% 100% no-repeat;
  }

  /* ---------- the scrims ----------
     Every render keeps its top quiet and dark on purpose, so these only
     have to carry the copy, not rescue it. */
  /* both heroes put something bright right where the buttons land, and an
     outlined button on a lit surface has no edge left; the scrim holds its
     weight past them and only then fades */
  .ap-ecommerce-hero-scrim,
  .ap-growth-hero-scrim{
    background:linear-gradient(180deg,
      rgba(5,5,6,.82) 0%, rgba(5,5,6,.72) 38%, rgba(5,5,6,.56) 54%,
      rgba(5,5,6,.34) 66%, rgba(5,5,6,.1) 78%, rgba(5,5,6,0) 90%);
  }
  .ap-maint-hero .ap-shell{height:auto; padding-bottom:18px}
}
