/* ==========================================================================
   ZYWIN MEDIA — GLASS + 3D LAYER
   --------------------------------------------------------------------------
   Loads AFTER zywin-ui.css and only ever ADDS. Nothing in this file is
   required for the site to work, and every effect has a floor:

     no backdrop-filter  -> surfaces fall back to solid paper/ink
     no JavaScript       -> glass is static, still correct
     reduced motion      -> nothing moves at all
     coarse pointer      -> tilt + spotlight never engage

   Same palette, same wedge geometry as the base system. The glass is
   crimson/violet tinted, never neutral grey.
   ========================================================================== */

/* ---------- G1. Tokens ---------- */
@property --zy-a { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

:root {
  /* Glass surfaces — kept fairly opaque on purpose so body text keeps its
     contrast ratio. Prettier-but-thinner glass fails WCAG on a busy backdrop. */
  /* Opacity is a legibility control, not a taste control. At .52 the mesh
     behind showed through strongly enough to drag body text under 4.5:1 in
     the darker patches. .68 still reads as glass and holds contrast. */
  --zy-glass:        rgba(255, 255, 255, .68);
  --zy-glass-strong: rgba(255, 255, 255, .84);
  --zy-glass-thin:   rgba(255, 255, 255, .40);
  --zy-glass-ink:    rgba(26, 18, 38, .38);
  --zy-glass-ink-2:  rgba(26, 18, 38, .58);

  /* Edges: a bright top rim + a violet-tinted bottom rim reads as thickness */
  --zy-glass-rim:    rgba(255, 255, 255, .85);
  --zy-glass-rim-d:  rgba(255, 255, 255, .26);

  /* Diagonal sheen/sweep disabled site-wide per request — kept as empty
     custom properties so every rule that references them (dozens, across
     cards, quotes, stats, glass panes) simply paints nothing extra. The
     blur/rim/shadow "glass" look stays; the moving diagonal band and the
     tinted diagonal gradient are gone everywhere they were used. */
  --zy-sweep: none;
  --zy-sheen: none;
  --zy-sheen-d: none;

  --zy-blur:    22px;
  --zy-blur-lg: 34px;
  --zy-sat:     190%;

  /* Depth. The two inset rims are what make a pane look like it has
     THICKNESS rather than being a flat tinted rectangle: a hard white
     line catching light on the top edge, a violet bounce on the bottom. */
  --zy-glass-shadow:
    0 24px 60px -30px rgba(78, 21, 128, .55),
    0 4px 14px -8px rgba(12, 7, 20, .18),
    inset 0 1px 0 rgba(255, 255, 255, 1),
    inset 0 2px 6px -4px rgba(255, 255, 255, .9),
    inset 0 -2px 0 rgba(120, 42, 178, .18),
    inset 0 -14px 24px -18px rgba(120, 42, 178, .35);
  /* A card that lifts this far needs a shadow to match, or it reads as
     scaling up rather than coming toward you. */
  --zy-glass-shadow-lift:
    0 60px 110px -30px rgba(78, 21, 128, .58),
    0 26px 50px -22px rgba(12, 7, 20, .32),
    0 10px 26px -12px rgba(229, 57, 70, .26),
    inset 0 1px 0 rgba(255, 255, 255, 1),
    inset 0 3px 8px -4px rgba(255, 255, 255, .95),
    inset 0 -2px 0 rgba(120, 42, 178, .24),
    inset 0 -16px 28px -18px rgba(229, 57, 70, .3);
  --zy-glass-shadow-d:
    0 28px 70px -32px rgba(0, 0, 0, .75),
    inset 0 1px 0 rgba(255, 255, 255, .34),
    inset 0 2px 8px -5px rgba(255, 255, 255, .5),
    inset 0 -1px 0 rgba(255, 255, 255, .08);

  /* 3D scene */
  --zy-depth: 1150px;   /* perspective for card grids    */
  --zy-tilt:  11deg;    /* max rotation on pointer tilt   */
  --zy-lift:  30px;     /* how far a card rises on hover  */
  --zy-pop:   1.055;    /* and how much it grows doing it */

  /* Written by zywin-glass.js. Safe, centred defaults so CSS alone is valid. */
  --mx: 50%;
  --my: 50%;
  --rx: 0deg;
  --ry: 0deg;
}

/* ==========================================================================
   G2. The light field the glass refracts
   Glassmorphism over flat white reads as nothing at all — the frosting needs
   something behind it. This is a slow gradient mesh fixed behind every page,
   plus a whisper of grain so large panes don't band.
   ========================================================================== */
body::before {
  content: '';
  position: fixed;
  inset: -12%;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(28% 30% at 10% 14%, rgba(229, 57, 70, .34), transparent 68%),
    radial-gradient(32% 32% at 88% 10%, rgba(120, 42, 178, .36), transparent 70%),
    radial-gradient(30% 32% at 62% 46%, rgba(120, 42, 178, .22), transparent 66%),
    radial-gradient(26% 28% at 24% 56%, rgba(229, 57, 70, .20), transparent 66%),
    radial-gradient(38% 40% at 74% 84%, rgba(78, 21, 128, .32), transparent 72%),
    radial-gradient(28% 28% at 18% 90%, rgba(229, 57, 70, .26), transparent 70%);
  filter: blur(30px);
  animation: zy-mesh 38s ease-in-out infinite alternate;
}
@keyframes zy-mesh {
  0%   { transform: translate3d(0, 0, 0) scale(1)    rotate(0deg); }
  50%  { transform: translate3d(2%, -2.5%, 0) scale(1.1) rotate(3deg); }
  100% { transform: translate3d(-2%, 2%, 0) scale(1.05) rotate(-2.5deg); }
}

body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .032;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* Opaque section fills would hide the mesh — let it through instead. */
.zy-section--mist { background: rgba(247, 245, 251, .5); }
body { background: var(--zy-paper); }

/* ==========================================================================
   G3. The glass surface
   .zy-glass is the reusable class; below it is mapped onto the components
   that already exist, so no page markup has to change.
   ========================================================================== */
.zy-glass,
.zy-card,
.zy-mcard,
.zy-pcard,
.zy-quote,
.zy-article,
.zy-client__box,
.zy-drop__m,
.zy-pop__card,
.zy-article__links,
.zy-empty {
  background-color: var(--zy-glass);
  background-image: var(--zy-sheen);
  -webkit-backdrop-filter: blur(var(--zy-blur)) saturate(var(--zy-sat));
  backdrop-filter: blur(var(--zy-blur)) saturate(var(--zy-sat));
  border: 1px solid var(--zy-glass-rim);
  box-shadow: var(--zy-glass-shadow);
}

/* Dark glass — same pane, seen against the ink sections */
.zy-section--ink .zy-glass,
.zy-section--ink .zy-card,
.zy-section--ink .zy-mcard,
.zy-section--ink .zy-quote,
.zy-hero .zy-glass,
.zy-stat {
  background-color: var(--zy-glass-ink);
  background-image: var(--zy-sheen-d);
  -webkit-backdrop-filter: blur(var(--zy-blur)) saturate(140%);
  backdrop-filter: blur(var(--zy-blur)) saturate(140%);
  border: 1px solid var(--zy-glass-rim-d);
  box-shadow: var(--zy-glass-shadow-d);
  color: #fff;
}
.zy-section--ink .zy-card h3,
.zy-section--ink .zy-mcard h3 { color: #fff; }
.zy-section--ink .zy-card p,
.zy-section--ink .zy-mcard p  { color: rgba(255, 255, 255, .74); }

/* The stat strip becomes a single sheet of dark glass with hairline dividers */
.zy-stats { background: rgba(255, 255, 255, .08); }

/* Media cards keep the photo crisp; only the body frosts */
.zy-mcard { background-color: var(--zy-glass-thin); background-image: none; }
.zy-mcard__body {
  background-color: var(--zy-glass);
  background-image: var(--zy-sheen);
}
.zy-mcard__img { background: rgba(247, 245, 251, .5); }

/* Body copy on a glass pane needs to be darker than it does on flat paper:
   the pane is translucent, so whatever is behind it eats into the contrast.
   --zy-slate (#6B6478) measured 5.5:1 over a clean section but dropped below
   AA over the stronger patches of the gradient mesh. */
.zy-card p, .zy-mcard p, .zy-pcard p, .zy-quote p,
.zy-glass p, .zy-drop__feature small { color: #4A4358; }

/* The uppercase micro-labels were the worst of it — --zy-mute over a light
   pane measured 2.9:1, well under AA even for large text. */
.zy-mcard__meta, .zy-client span { color: #5C5470; }

/* Long-form article panes get the heavier blur — they are the biggest sheet
   on the page and thin frosting looks like a rendering bug at that size. */
.zy-article,
.zy-pop__card {
  -webkit-backdrop-filter: blur(var(--zy-blur-lg)) saturate(var(--zy-sat));
  backdrop-filter: blur(var(--zy-blur-lg)) saturate(var(--zy-sat));
  background-color: var(--zy-glass-strong);
}

/* ==========================================================================
   G3b. The travelling reflection
   A narrow band of light crosses each pane on a loop, the way a reflection
   slides over real glass when you move past it. Every pane is offset by a
   different delay — a grid where all nine flash in unison reads as a screen
   effect, staggered it reads as light in a room.

   Implemented as an extra background layer with an animated position rather
   than a pseudo-element: .zy-card has already spent both of its pseudos on
   the corner wedge and the pointer spotlight.
   ========================================================================== */
@media (min-width: 941px) {
  .zy-card,
  .zy-mcard__body,
  .zy-pcard,
  .zy-quote,
  .zy-client__box,
  .zy-stat,
  .zy-glass {
    background-image: var(--zy-sweep), var(--zy-sheen);
    background-repeat: no-repeat, no-repeat;
    background-size: 62% 240%, auto;
    background-position: -90% 50%, 0 0;
    animation: zy-glass-sweep 7s cubic-bezier(.5, 0, .5, 1) infinite;
  }
  .zy-section--ink .zy-card,
  .zy-section--ink .zy-mcard__body,
  .zy-section--ink .zy-quote,
  .zy-stat { background-image: var(--zy-sweep), var(--zy-sheen-d); }

  /* Stagger.
     The delay has to land on whatever element actually carries the animation.
     For a plain .zy-card that's the grid child itself; for a media card the
     pane is .zy-mcard__body, two levels down. Setting it only on the grid
     child leaves every media card at delay 0 — so a grid of them sweeps in
     perfect unison and, because the band parks off-pane for part of each
     cycle, they all go blank together and the effect looks broken. */
  .zy-grid > *:nth-child(7n+1), .zy-grid > *:nth-child(7n+1) .zy-mcard__body { animation-delay: -0.0s; }
  .zy-grid > *:nth-child(7n+2), .zy-grid > *:nth-child(7n+2) .zy-mcard__body { animation-delay: -1.1s; }
  .zy-grid > *:nth-child(7n+3), .zy-grid > *:nth-child(7n+3) .zy-mcard__body { animation-delay: -2.2s; }
  .zy-grid > *:nth-child(7n+4), .zy-grid > *:nth-child(7n+4) .zy-mcard__body { animation-delay: -3.3s; }
  .zy-grid > *:nth-child(7n+5), .zy-grid > *:nth-child(7n+5) .zy-mcard__body { animation-delay: -4.4s; }
  .zy-grid > *:nth-child(7n+6), .zy-grid > *:nth-child(7n+6) .zy-mcard__body { animation-delay: -5.5s; }
  .zy-grid > *:nth-child(7n+7), .zy-grid > *:nth-child(7n+7) .zy-mcard__body { animation-delay: -6.6s; }

  .zy-stats > *:nth-child(2) { animation-delay: -1.8s; }
  .zy-stats > *:nth-child(3) { animation-delay: -3.6s; }
  .zy-stats > *:nth-child(4) { animation-delay: -5.4s; }

  /* On a media card the glass pane is the body, so the body carries the
     sweep — but the FLOAT belongs to the whole card, so it goes on the
     outer element instead. */
  .zy-mcard { animation: zy-card-float var(--fl-dur, 7.5s) ease-in-out infinite; }
}

/* ==========================================================================
   G3c. Floating cards
   Every pane drifts on its own slow cycle so a grid breathes instead of
   sitting still.

   The float is written with the `translate` PROPERTY, not with `transform`.
   That matters: the pointer tilt in G5 owns `transform`, and an animation
   always beats a normal declaration — a transform-based float would simply
   erase the tilt on hover. `translate`, `rotate` and `scale` are separate
   longhands that compose with `transform`, so the two coexist.
   ========================================================================== */
@media (min-width: 941px) {
  .zy-card,
  .zy-pcard,
  .zy-quote,
  .zy-client__box,
  .zy-glass {
    animation:
      zy-glass-sweep 7s cubic-bezier(.5, 0, .5, 1) infinite,
      zy-card-float var(--fl-dur, 7.5s) ease-in-out infinite;
  }

  /* Varied amplitude and period. Identical motion on every card reads as a
     carousel; slightly different ones read as things resting on air. */
  .zy-grid > *:nth-child(3n+1) { --fl: 10px; --fl-dur: 7.5s; }
  .zy-grid > *:nth-child(3n+2) { --fl: 7px;  --fl-dur: 9s; }
  .zy-grid > *:nth-child(3n+3) { --fl: 13px; --fl-dur: 6.5s; }

  /* Hold still while the pointer is on it — the tilt reads against the
     cursor, and a target that keeps drifting under the pointer feels loose.
     The sweep keeps running; only the float pauses. */
  .zy-card:hover, .zy-pcard:hover, .zy-quote:hover,
  .zy-client__box:hover, .zy-glass:hover { animation-play-state: running, paused; }
  .zy-mcard:hover { animation-play-state: paused; }

  /* Contents ride along: the icon lags slightly behind the pane it sits on,
     which is what gives the movement a sense of depth rather than the whole
     card sliding as one flat sticker. */
  .zy-card__icon, .zy-svc__icon {
    animation: zy-icon-float var(--fl-dur, 7.5s) ease-in-out infinite;
    animation-delay: -.9s;
  }
  .zy-card:hover .zy-card__icon,
  .zy-svc:hover .zy-svc__icon { animation-play-state: paused; }
}

@keyframes zy-card-float {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 calc(var(--fl, 9px) * -1); }
}
@keyframes zy-icon-float {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -3px; }
}

/* ==========================================================================
   G3d. Slow drift on card imagery
   The photographs are the largest things on the page; holding them perfectly
   still under panes that move makes the panes look like they are sliding
   rather than floating.

   Uses `scale`/`translate` rather than `transform` for the same reason as
   above — the base system's hover zoom is a `transform`, and this has to
   compose with it, not replace it.
   ========================================================================== */
@media (min-width: 941px) {
  .zy-mcard__img img,
  .zy-post .zy-mcard__img img {
    animation: zy-kenburns 26s ease-in-out infinite alternate;
  }
  /* let the hover zoom read cleanly on its own */
  .zy-mcard:hover .zy-mcard__img img { animation-play-state: paused; }
}
@keyframes zy-kenburns {
  from { scale: 1;    translate: 0 0; }
  to   { scale: 1.07; translate: -1.5% -1%; }
}
@keyframes zy-glass-sweep {
  /* crosses for the first 70%, rests off-pane for the last 30% — a band with
     no rest at all reads as a loading shimmer rather than a reflection */
  0%        { background-position: -90% 50%, 0 0; }
  70%, 100% { background-position: 190% 50%, 0 0; }
}

/* ==========================================================================
   G4. Specular light — what actually sells it as glass
   Two layers: a pointer-tracked spotlight inside the pane, and an animated
   rim of light around the edge.
   ========================================================================== */

/* -- the moving highlight inside the pane -- */
.zy-card::after,
.zy-pcard::after,
.zy-quote::after,
.zy-glass::after,
.zy-client__box::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 3;
  opacity: 0;
  transition: opacity .5s var(--zy-ease);
  background:
    radial-gradient(38% 46% at var(--mx) var(--my), rgba(255, 255, 255, .55), transparent 62%),
    radial-gradient(70% 70% at var(--mx) var(--my), rgba(229, 57, 70, .10), transparent 70%);
}
.zy-card:hover::after,   .zy-card.zy-tilt::after,
.zy-pcard:hover::after,  .zy-pcard.zy-tilt::after,
.zy-quote:hover::after,  .zy-quote.zy-tilt::after,
.zy-glass:hover::after,  .zy-glass.zy-tilt::after,
.zy-client__box:hover::after { opacity: 1; }

.zy-card, .zy-pcard, .zy-quote, .zy-glass, .zy-client__box,
.zy-mcard, .zy-article, .zy-stat { position: relative; }

/* -- the rim of light that travels around the border -- */
.zy-glass--rim::before,
.zy-mcard::before,
.zy-quote::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  z-index: 4;
  /* Always faintly lit, not only on hover — a pane whose edge only catches
     light when you touch it looks like a button, not like glass. */
  opacity: .4;
  transition: opacity .45s var(--zy-ease);
  background: conic-gradient(from var(--zy-a),
    transparent 0 18%,
    rgba(255, 255, 255, .95) 28%,
    var(--zy-crimson) 38%,
    var(--zy-violet) 50%,
    rgba(255, 255, 255, .8) 60%,
    transparent 72% 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: zy-rim 5s linear infinite;
}
.zy-glass--rim:hover::before,
.zy-mcard:hover::before,  .zy-mcard.zy-tilt::before,
.zy-quote:hover::before,  .zy-quote.zy-tilt::before { opacity: 1; }
@keyframes zy-rim { to { --zy-a: 360deg; } }

/* ==========================================================================
   G5. 3D — the tilt scene
   Grids become perspective scenes; zywin-glass.js adds .zy-tilt on pointer
   enter and writes --rx/--ry. CSS owns the actual transform so the two never
   fight over the style attribute.
   ========================================================================== */
.zy-grid,
.zy-grid--2,
.zy-grid--3,
.zy-stats,
.zy-logos__track,
[data-scene] {
  perspective: var(--zy-depth);
  perspective-origin: 50% 40%;
}

.zy-card, .zy-mcard, .zy-pcard, .zy-quote, .zy-client__box {
  transform-style: flat;                 /* overflow:hidden flattens anyway  */
  will-change: transform;
  transition:
    transform .75s var(--zy-ease),
    box-shadow .75s var(--zy-ease),
    border-color .75s var(--zy-ease),
    background .75s var(--zy-ease);
}

/* The tilted state. Written twice (bare + :hover) so it beats the base
   system's own `:hover { transform: translateY(-8px) }` on specificity. */
/* The doubled class is load-bearing, not a typo.

   The scroll-reveal system ends with `[data-reveal].is-in { transform: none }`
   — specificity (0,2,0). A single `.zy-tilt` is (0,1,0), so the moment a card
   scrolled into view and picked up `.is-in`, that rule cancelled the tilt AND
   the plain hover lift for good. Every card on the site is a reveal target,
   so the effect only ever worked on cards that hadn't been revealed yet.
   `.zy-tilt.zy-tilt` matches the same element at (0,2,0) and, loading after
   the base sheet, wins the tie. */
.zy-tilt.zy-tilt,
.zy-tilt:hover {
  transform:
    perspective(var(--zy-depth))
    rotateX(var(--rx))
    rotateY(var(--ry))
    translate3d(0, calc(var(--zy-lift) * -1), 0)
    scale(var(--zy-pop));
  transition: transform .12s linear, box-shadow .45s var(--zy-ease);
  box-shadow: var(--zy-glass-shadow-lift);
  border-color: rgba(255, 255, 255, .92);
  background-color: var(--zy-glass-strong);
  z-index: 5;
}
.zy-section--ink .zy-tilt,
.zy-section--ink .zy-tilt:hover {
  background-color: var(--zy-glass-ink-2);
  border-color: rgba(255, 255, 255, .3);
  box-shadow: var(--zy-glass-shadow-d);
}

/* Without JS — and for keyboard focus — the card still has to lift. The base
   system only nudged it 8px; match the tilted lift so it's the same effect
   either way.

   `:not(.zy-tilt)` is doing real work here: this rule and `.zy-tilt:hover`
   have identical specificity, so whichever came last would win. Excluding
   tilted cards makes the outcome independent of source order — the pointer
   tilt keeps its 3D transform, and this flat lift only covers the cases
   where the tilt engine isn't driving. */
@media (hover: hover) {
  .zy-card:hover:not(.zy-tilt),
  .zy-mcard:hover:not(.zy-tilt),
  .zy-pcard:hover:not(.zy-tilt),
  .zy-quote:hover:not(.zy-tilt),
  .zy-card:focus-visible,
  .zy-mcard:focus-visible {
    transform: translateY(calc(var(--zy-lift) * -1)) scale(var(--zy-pop));
    box-shadow: var(--zy-glass-shadow-lift);
    z-index: 5;
  }
}

/* Contents ride the tilt: children counter-shift against the pointer, which
   fakes real parallax depth inside a pane that cannot use preserve-3d. */
.zy-tilt .zy-card__icon,
.zy-tilt .zy-mcard__meta,
.zy-tilt .zy-pcard__n,
.zy-tilt .zy-quote__mark,
.zy-tilt h3 {
  transform: translate3d(calc(var(--ry) / 1deg * -1.1px), calc(var(--rx) / 1deg * 1.1px), 0);
  transition: transform .18s linear;
}
.zy-tilt .zy-card__icon {
  transform:
    translate3d(calc(var(--ry) / 1deg * -2.2px), calc(var(--rx) / 1deg * 2.2px), 0)
    scale(1.08);
  box-shadow: 0 14px 30px -14px rgba(120, 42, 178, .8);
}

/* The corner wedge opens further on a tilted card than a flat hover */
.zy-tilt.zy-card::before { transform: translate(0, 0) scale(1.12); opacity: 1; }

/* ==========================================================================
   G6. 3D scroll reveal
   Panels swing up out of the page instead of just sliding. Directional
   reveals (left/right/zoom) are left exactly as the base system had them.
   ========================================================================== */
[data-reveal]:not([data-reveal="left"]):not([data-reveal="right"]):not([data-reveal="zoom"]):not(.is-in) {
  transform: perspective(1100px) translate3d(0, 34px, -70px) rotateX(9deg);
  transform-origin: 50% 100%;
}
/* NOTE: no `[data-reveal].is-in { transform: none }` here on purpose — the
   base sheet already does it, and repeating it in this file would put a
   (0,2,0) rule AFTER the tilt and re-break the thing fixed above. */

/* ==========================================================================
   G7. Hero — floating glass panes
   .zy-orbs is injected by the JS; if the JS never runs the hero is unchanged.
   ========================================================================== */
.zy-orbs {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
  perspective: 900px;
}
.zy-orbs b {
  position: absolute;
  display: block;
  width: var(--s, 190px);
  aspect-ratio: 1;
  border-radius: 34%;
  /* Three layers make a slab of glass: a specular hot-spot near the top-left
     where the light source is, the body tint, and a bright edge. */
  background:
    radial-gradient(42% 38% at 26% 22%, rgba(255, 255, 255, .55), transparent 68%),
    radial-gradient(60% 55% at 82% 88%, rgba(229, 57, 70, .22), transparent 72%),
    linear-gradient(140deg, rgba(255, 255, 255, .26), rgba(255, 255, 255, .04) 45%, rgba(120, 42, 178, .20));
  border: 1px solid rgba(255, 255, 255, .38);
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  backdrop-filter: blur(10px) saturate(160%);
  box-shadow:
    0 30px 70px -30px rgba(0, 0, 0, .6),
    inset 0 2px 0 rgba(255, 255, 255, .75),
    inset 0 -18px 30px -22px rgba(120, 42, 178, .8),
    inset 0 0 40px -12px rgba(255, 255, 255, .25);
  animation: zy-float var(--d, 18s) ease-in-out infinite alternate;
  /* --px/--py come from the pointer; the multiplier is the orb's depth */
  translate: calc(var(--px, 0) * var(--z, 12px)) calc(var(--py, 0) * var(--z, 12px));
  transition: translate .5s cubic-bezier(.22, 1, .36, 1);
}
/* every third one is a wedge, so the logo geometry stays in the mix */
.zy-orbs b:nth-child(3n) {
  border-radius: 0;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  border: 0;
  box-shadow: none;                 /* clip-path crops insets into a hard band */
  background:
    radial-gradient(50% 40% at 46% 30%, rgba(255, 255, 255, .45), transparent 70%),
    linear-gradient(140deg, rgba(229, 57, 70, .34), rgba(120, 42, 178, .26));
}
@keyframes zy-float {
  from { transform: translate3d(0, 0, 0) rotateX(0deg) rotateY(0deg) rotate(0deg); }
  to   { transform: translate3d(24px, -38px, 0) rotateX(18deg) rotateY(-16deg) rotate(12deg); }
}

/* The headline block sits on its own glass shelf, lifted off the aurora */
.zy-hero__inner > .zy-eyebrow { position: relative; z-index: 4; }
.zy-hero__cta { position: relative; z-index: 4; }

/* Hero mascot gets a glass plinth + a slow 3D bob */
.zy-hero__figure { perspective: 900px; }
.zy-hero__mascot { animation: zy-bob 7s ease-in-out infinite alternate; }
@keyframes zy-bob {
  from { transform: translate3d(0, 0, 0) rotateY(-5deg); }
  to   { transform: translate3d(0, -14px, 0) rotateY(6deg); }
}

/* ==========================================================================
   G8. Navigation — a proper frosted bar
   ========================================================================== */
.zy-nav {
  background: rgba(255, 255, 255, .52);
  -webkit-backdrop-filter: blur(22px) saturate(190%);
  backdrop-filter: blur(22px) saturate(190%);
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, .55);
}
.zy-nav.is-stuck {
  background: rgba(255, 255, 255, .74);
  border-bottom-color: rgba(232, 228, 239, .8);
  box-shadow:
    0 18px 40px -30px rgba(78, 21, 128, .55),
    inset 0 1px 0 rgba(255, 255, 255, .9);
}

/* The dropdown floats over the dark hero on the homepage, and a translucent
   panel there composites down to a mid-grey that dragged the link text to
   3.8:1. Navigation has to stay readable over ANY backdrop, so this panel is
   near-opaque — it keeps the frosted edge and the blur, but not at the cost
   of the words. */
.zy-drop__m {
  background: rgba(255, 255, 255, .96);
  -webkit-backdrop-filter: blur(var(--zy-blur-lg)) saturate(var(--zy-sat));
  backdrop-filter: blur(var(--zy-blur-lg)) saturate(var(--zy-sat));
  border-color: var(--zy-glass-rim);
}
.zy-drop__m a { color: #3B3450; font-weight: 500; }
.zy-drop__m a:hover { color: var(--zy-violet); }
.zy-drop__m--mega .zy-drop__sep { color: var(--zy-violet-deep); }
/* menu opens with a small 3D swing rather than a flat fade */
.zy-drop__m { transform: perspective(800px) rotateX(-8deg) translateY(10px); transform-origin: 50% 0; }
.zy-drop.is-open .zy-drop__m { transform: perspective(800px) rotateX(0deg) translateY(0); }

/* ==========================================================================
   G9. Controls — buttons, chips, fields, overlays
   ========================================================================== */

/* Buttons pick up a glass rim and a real press */
.zy-btn { transform-style: preserve-3d; }
.zy-btn--solid {
  box-shadow:
    0 12px 28px -12px rgba(120, 42, 178, .75),
    inset 0 1px 0 rgba(255, 255, 255, .38),
    inset 0 -2px 0 rgba(78, 21, 128, .35);
}
.zy-btn--solid:active { transform: translateY(1px) scale(.985); }

/* A gloss across the top half — the single cheapest cue that a solid shape
   is meant to read as glass rather than paint. */
.zy-btn--solid, .zy-burger, .zy-avatar, .zy-card__icon, .zy-fab--call {
  background-image:
    linear-gradient(180deg, rgba(255, 255, 255, .3), rgba(255, 255, 255, .04) 48%, transparent 52%),
    var(--zy-grad);
}
.zy-nav__cta { background-image: linear-gradient(180deg, rgba(255,255,255,.34), rgba(255,255,255,.05) 48%, transparent 52%), var(--zy-grad); }

.zy-btn--ghost {
  background: rgba(255, 255, 255, .38);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  backdrop-filter: blur(12px) saturate(150%);
}
.zy-hero .zy-btn--ghost {
  background: rgba(255, 255, 255, .1);
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .45), inset 0 1px 0 rgba(255, 255, 255, .3);
}

.zy-chip {
  background: rgba(255, 255, 255, .5);
  -webkit-backdrop-filter: blur(10px) saturate(150%);
  backdrop-filter: blur(10px) saturate(150%);
  border-color: var(--zy-glass-rim);
}

/* Fields become glass wells — inset light on top, so they read as recessed */
.zy-field input,
.zy-field select,
.zy-field textarea,
.form-control, .form-select {
  background: rgba(255, 255, 255, .55) !important;
  -webkit-backdrop-filter: blur(10px) saturate(150%);
  backdrop-filter: blur(10px) saturate(150%);
  box-shadow: inset 0 2px 5px -3px rgba(12, 7, 20, .28), inset 0 1px 0 rgba(255, 255, 255, .7);
}
.zy-field input:focus,
.zy-field select:focus,
.zy-field textarea:focus {
  background: rgba(255, 255, 255, .82) !important;
  box-shadow: 0 0 0 4px rgba(120, 42, 178, .14), inset 0 1px 0 rgba(255, 255, 255, .9);
}

/* Popup: deeper scrim, and the card arrives with a 3D swing */
.zy-pop__overlay {
  background: rgba(12, 7, 20, .5);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  backdrop-filter: blur(14px) saturate(130%);
}
.zy-pop__card { transform: perspective(1200px) rotateX(12deg) translateY(38px) scale(.92); }
.zy-pop.is-open .zy-pop__card { transform: perspective(1200px) rotateX(0deg) translateY(0) scale(1); }

/* Mascot bubble */
.zy-mascot__bubble {
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
}

/* Loader mark spins in 3D instead of flat */
.zy-loader__mark { animation: zy-load3d 1.4s var(--zy-ease) infinite; }
@keyframes zy-load3d {
  0%   { transform: perspective(500px) rotateY(0deg)   rotateX(0deg)  scale(.72); opacity: .45; }
  50%  { transform: perspective(500px) rotateY(180deg) rotateX(18deg) scale(1);   opacity: 1; }
  100% { transform: perspective(500px) rotateY(360deg) rotateX(0deg)  scale(.72); opacity: .45; }
}

/* ==========================================================================
   G10. Strips in 3D — the marquee and the client wall lean into the page
   ========================================================================== */
.zy-marquee,
.zy-logos { perspective: 900px; }
/* A gentle lean only — outline type rasterises badly past ~6deg */
.zy-marquee__track { transform: rotateX(5deg); transform-origin: 50% 100%; }
.zy-marquee span { -webkit-text-stroke-color: rgba(120, 42, 178, .48); }
.zy-marquee { background: rgba(255, 255, 255, .35); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }
.zy-logos__track   { transform-style: flat; }
.zy-client__box { transition: transform .45s var(--zy-ease), box-shadow .45s var(--zy-ease), background .45s; }
.zy-client:hover .zy-client__box {
  transform: perspective(700px) rotateY(-8deg) translateY(-6px) scale(1.03);
  box-shadow: var(--zy-glass-shadow-lift);
}

/* Motion-graphic panels get a glass stage */
.zy-stage { box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 18px 40px -26px rgba(78, 21, 128, .8); }

/* Footer + CTA band pick up the rim light so they read as one system */
.zy-cta {
  box-shadow:
    0 40px 90px -46px rgba(78, 21, 128, .9),
    inset 0 1px 0 rgba(255, 255, 255, .32);
}
.zy-social a {
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  background: rgba(255, 255, 255, .05);
}
.zy-social a:hover { transform: translateY(-4px) rotateY(16deg) rotate(6deg); }

/* ==========================================================================
   G11. Floors — fallback, performance, motion, print
   ========================================================================== */

/* No backdrop-filter (older Firefox, some in-app browsers): go solid so
   nothing ends up as unreadable text on a transparent panel. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .zy-glass, .zy-card, .zy-mcard, .zy-pcard, .zy-quote, .zy-article,
  .zy-client__box, .zy-drop__m, .zy-pop__card, .zy-mcard__body,
  .zy-article__links, .zy-empty { background: var(--zy-paper); }
  .zy-section--ink .zy-card, .zy-section--ink .zy-mcard,
  .zy-section--ink .zy-quote, .zy-stat { background: var(--zy-ink-2); }
  .zy-nav { background: rgba(255, 255, 255, .97); }
  .zy-nav.is-stuck { background: #fff; }
  .zy-field input, .zy-field select, .zy-field textarea,
  .form-control, .form-select { background: var(--zy-paper) !important; }
}

/* Phones: blur is the single most expensive thing on this page. Halve the
   radius, drop it entirely from the big sheets, and skip the tilt scene. */
@media (max-width: 940px) {
  :root { --zy-blur: 10px; --zy-blur-lg: 14px; --zy-lift: 8px; }

  .zy-article, .zy-pop__card {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: rgba(255, 255, 255, .96);
  }
  .zy-grid, .zy-grid--2, .zy-grid--3, .zy-stats { perspective: none; }
  .zy-marquee__track { transform: none; }
  body::before { animation: none; }         /* no repaint loop on battery */
  .zy-orbs { display: none; }
}

/* Touch: there is no pointer to track, so no tilt and no spotlight */
@media (hover: none), (pointer: coarse) {
  .zy-card::after, .zy-pcard::after, .zy-quote::after,
  .zy-glass::after, .zy-client__box::after { display: none; }
  .zy-mcard::before, .zy-quote::before, .zy-glass--rim::before { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  body::before,
  .zy-orbs b,
  .zy-hero__mascot,
  .zy-loader__mark,
  .zy-card, .zy-mcard, .zy-mcard__body, .zy-pcard, .zy-quote, .zy-client__box,
  .zy-stat, .zy-glass, .zy-card__icon, .zy-svc__icon, .zy-mcard__img img,
  .zy-mcard::before, .zy-quote::before, .zy-glass--rim::before { animation: none !important; }
  /* park the travelling band off-pane so it doesn't freeze mid-sweep */
  .zy-card, .zy-mcard__body, .zy-pcard, .zy-quote,
  .zy-client__box, .zy-stat, .zy-glass { background-position: -90% 50%, 0 0; }
  /* and land every floating thing back on its resting position */
  .zy-card, .zy-mcard, .zy-pcard, .zy-quote, .zy-client__box, .zy-glass,
  .zy-card__icon, .zy-svc__icon, .zy-mcard__img img { translate: none; scale: none; }
  .zy-tilt, .zy-tilt:hover { transform: none !important; }
  .zy-marquee__track { transform: none; }
  /* FIX: the hero's rotating word is revealed only BY its animation, and the
     base reduced-motion rule crushes that animation to .01ms — so it ends on
     its final keyframe (opacity 0) and the headline reads "We build the /
     that grows you." with a hole in it. Pin the first word visible. */
  .zy-rotator > span:first-child { opacity: 1; transform: none; }

  .zy-drop__m { transform: translateY(10px); }
  .zy-drop.is-open .zy-drop__m { transform: none; }
  .zy-pop__card { transform: translateY(20px); }
  .zy-pop.is-open .zy-pop__card { transform: none; }
  [data-reveal] { transform: none !important; }
}

/* ==========================================================================
   G12. Nav restructure — single-image logo, three-column Services menu,
   an Insights group, and dropdowns that open on hover.
   ========================================================================== */

/* -- Brand: one horizontal lockup replaces the mark + wordmark pair -- */
.zy-nav__logo { height: 46px; width: auto; display: block; transition: height .4s var(--zy-ease); }
.zy-nav.is-stuck .zy-nav__logo { height: 37px; }
.zy-nav__brand { gap: 0; }
@media (max-width: 940px) { .zy-nav__logo { height: 40px; } }
@media (max-width: 380px) { .zy-nav__logo { height: 34px; } }

/* -- The dropdown opens on hover as well as click (JS adds .is-open in both
      cases). The panel sits 10px below the button, which would break a hover
      chain — this transparent bridge keeps the pointer "inside" the group
      while it crosses the gap. -- */
.zy-drop::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 14px;
}

/* -- Services: three columns of five, plus a full-width Courses row -- */
.zy-drop__m--mega {
  display: grid;
  grid-template-columns: repeat(3, minmax(210px, 1fr));
  gap: 4px 18px;
  padding: 20px 22px 16px;
  width: max-content;
  max-width: min(94vw, 860px);
  /* wide menus hang off the right edge of the viewport if left-anchored */
  left: 50%;
  transform: perspective(800px) rotateX(-8deg) translate(-50%, 10px);
  transform-origin: 50% 0;
  max-height: none;
  overflow: visible;
}
.zy-drop.is-open .zy-drop__m--mega { transform: perspective(800px) rotateX(0deg) translate(-50%, 0); }

.zy-drop__col { display: flex; flex-direction: column; min-width: 0; }
.zy-drop__m--mega .zy-drop__sep {
  margin: 0 0 .5rem;
  padding: 0 0 .55rem .85rem;
  border-top: 0;
  border-bottom: 1px solid var(--zy-line);
  color: var(--zy-violet);
}
.zy-drop__m--mega a { white-space: normal; line-height: 1.35; }

/* Courses — the one item that isn't a service, so it reads as its own thing.
   Selector is doubled up on purpose: the base system's `.zy-drop__m a` sets
   display:block and would otherwise out-specify a single class here. */
.zy-drop__m .zy-drop__feature {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: .9rem;
  margin-top: 12px; padding: .85rem 1rem;
  border-radius: 14px;
  background-image: var(--zy-grad-soft);
  border: 1px solid var(--zy-line);
  white-space: normal;
  transition: border-color .3s var(--zy-ease), transform .3s var(--zy-ease);
}
.zy-drop__m .zy-drop__feature:hover {
  border-color: transparent;
  padding-left: 1rem;                 /* base rule nudges padding on hover */
  transform: translateX(3px);
}
.zy-drop__feature > span:not(.zy-drop__feature-i) { flex: 1 1 auto; min-width: 0; }
.zy-drop__feature-i {
  flex: none; width: 38px; height: 38px; display: grid; place-items: center;
  border-radius: 11px; background: var(--zy-grad); color: #fff; font-size: 1.05rem;
}
.zy-drop__feature strong {
  display: block; font-family: var(--zy-display); font-size: 1rem; color: var(--zy-ink);
}
.zy-drop__feature small { display: block; font-size: .8rem; color: var(--zy-slate); line-height: 1.4; }
.zy-drop__feature-go { margin-left: auto; color: var(--zy-violet); transition: transform .3s var(--zy-ease); }
.zy-drop__feature:hover .zy-drop__feature-go { transform: translateX(4px); }

/* With only five top-level items there is room to breathe again */
@media (min-width: 941px) {
  .zy-nav__links > a, .zy-drop__t { padding: .5rem .9rem; font-size: .95rem; }
}

/* -- Mobile: the mega menu flattens back into a single column in the panel -- */
@media (max-width: 940px) {
  .zy-drop::after { display: none; }

  /* The dropdown flattens INTO the dark slide-in panel here, so it must stop
     being a glass sheet. The base system already resets this, but that reset
     lives in zywin-ui.css and this file loads after it — so it has to be
     undone again on our side rather than relying on the earlier rule. */
  .zy-drop__m {
    background: none;
    background-image: none;
    border: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .zy-drop__m--mega {
    display: block;
    width: auto; max-width: none;
    padding: 0; left: auto;
    transform: none;
  }
  .zy-drop.is-open .zy-drop__m--mega { transform: none; }
  .zy-drop__m--mega .zy-drop__sep {
    margin: 1.1rem 0 .2rem; padding: 0 0 .5rem;
    color: rgba(255, 255, 255, .5);
    border-bottom-color: rgba(255, 255, 255, .14);
  }
  .zy-drop__feature {
    margin: 1rem 0 .4rem; padding: .8rem .9rem;
    background-image: none;
    background-color: rgba(255, 255, 255, .08);
    border-color: rgba(255, 255, 255, .16);
  }
  .zy-drop__feature strong { color: #fff; }
  .zy-drop__feature small { color: rgba(255, 255, 255, .6); }
  .zy-drop__feature-go { color: rgba(255, 255, 255, .6); }
}

@media (prefers-reduced-motion: reduce) {
  .zy-drop__m--mega { transform: translate(-50%, 10px); }
  .zy-drop.is-open .zy-drop__m--mega { transform: translate(-50%, 0); }
}

/* ==========================================================================
   G13. Footer — services grouped like the header, and a Find-us block whose
   icons actually line up.
   ========================================================================== */

/* The Services column now holds three groups, so it needs the room. */
.zy-footer__grid { grid-template-columns: 1.1fr 2.6fr 1fr 1.4fr; gap: 40px; }
.zy-footer__svc-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 22px; }

.zy-footer__group {
  display: flex;
  align-items: flex-end;
  /* Two of the three names wrap to a second line at this width. Reserving
     the space keeps all three columns starting on the same baseline instead
     of stepping down wherever a heading happened to break. */
  min-height: 2.6em;
  margin-bottom: .7rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid rgba(255, 255, 255, .12);
  font-family: var(--zy-mono);
  font-size: .62rem;
  line-height: 1.3;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .5);
}
.zy-footer__svc-grid a { font-size: .88rem; }

/* Find us: a two-track grid pins every icon to the same column, so the text
   starts at one x no matter how wide each glyph happens to be. Flex + a
   margin (what this was) leaves the labels a few pixels out of line. */
.zy-footer__contact li {
  display: grid;
  grid-template-columns: 22px 1fr;
  align-items: start;
  gap: 0 .7rem;
  margin-bottom: 1rem;
}
.zy-footer__contact i {
  width: 22px;
  margin-top: .18em;          /* optical centring on the first line, not the box */
  font-size: 1rem;
  line-height: 1;
  text-align: center;
  color: var(--zy-crimson);
}
.zy-footer__contact a { padding: 0; display: inline; }
.zy-footer__contact li:last-child { margin-bottom: 0; }

/* The fixed corner mascot sits exactly where the footer's social row lands,
   covering two of the four icons once you reach the bottom of the page. */
body.zy-footer-visible .zy-mascot {
  opacity: 0;
  visibility: hidden;
  transition: opacity .35s var(--zy-ease), visibility .35s;
}

@media (max-width: 1180px) {
  .zy-footer__grid { grid-template-columns: 1fr 1fr; }
  .zy-footer__services { grid-column: 1 / -1; order: 3; }
}
@media (max-width: 760px) {
  .zy-footer__svc-grid { grid-template-columns: 1fr; gap: 0; }
  .zy-footer__group { margin-top: 1.4rem; }
  .zy-footer__svc-grid > div:first-child .zy-footer__group { margin-top: 0; }
}

@media print {
  body::before, body::after, .zy-orbs { display: none !important; }
  .zy-glass, .zy-card, .zy-mcard, .zy-quote, .zy-article {
    background: #fff !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    box-shadow: none !important;
  }
}