/* ==========================================================================
   GLASS + 3D - the liquid-glass surface and the floating card system
   ========================================================================== */

/* --------------------------------------------------------------------------
   .glass - a single translucent pane.

   Three layers make it read as glass rather than a grey box:
     1. a diagonal tint gradient   (the body of the pane)
     2. backdrop-filter            (bends/blurs the ambient blooms behind it)
     3. a masked 1px rim gradient  (the specular edge where light catches)
   -------------------------------------------------------------------------- */
.glass {
  position: relative;
  isolation: isolate;
  border-radius: var(--r-lg);
  background:
    linear-gradient(158deg,
      rgba(255, 255, 255, 0.085) 0%,
      rgba(255, 255, 255, 0.028) 42%,
      rgba(255, 255, 255, 0.014) 68%,
      rgba(255, 255, 255, 0.055) 100%);
  backdrop-filter: blur(var(--glass-blur)) saturate(175%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(175%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 -1px 0 rgba(255, 255, 255, 0.045),
    0 22px 60px -26px rgba(0, 0, 0, 0.9);
}

/* The rim. A gradient stroke drawn only in the 1px border ring. */
.glass::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(140deg,
    rgba(255, 255, 255, 0.5) 0%,
    rgba(255, 255, 255, 0.09) 26%,
    rgba(255, 255, 255, 0.02) 52%,
    rgba(255, 255, 255, 0.1) 74%,
    rgba(255, 255, 255, 0.34) 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;
  pointer-events: none;
}

/* Specular sheen sitting just inside the top-left corner. */
.glass::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  background: radial-gradient(120% 80% at 12% 0%,
    rgba(255, 255, 255, 0.13) 0%,
    rgba(255, 255, 255, 0.03) 34%,
    transparent 62%);
  pointer-events: none;
}

.glass > * { position: relative; z-index: 3; }

/* Heavier pane for the two big panels (pricing, contact). */
.glass--strong {
  --glass-blur: 34px;
  background:
    linear-gradient(158deg,
      rgba(255, 255, 255, 0.1) 0%,
      rgba(255, 255, 255, 0.035) 44%,
      rgba(255, 255, 255, 0.02) 70%,
      rgba(255, 255, 255, 0.07) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    inset 0 -1px 0 rgba(255, 255, 255, 0.05),
    0 40px 100px -40px rgba(0, 0, 0, 1);
}

.glass--pill { border-radius: 999px; --glass-blur: 22px; }
.glass--pill::after { border-radius: 999px; }

/* Cheaper variant for the many small cards in grids - no backdrop-filter,
   so a wall of 24 of them still scrolls at 60fps. */
.glass--flat {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background:
    linear-gradient(158deg,
      rgba(255, 255, 255, 0.062) 0%,
      rgba(255, 255, 255, 0.022) 46%,
      rgba(255, 255, 255, 0.04) 100%);
}

/* Backdrop-filter is the single most expensive thing on this page.
   Below the tablet breakpoint, dial it down hard. */
@media (max-width: 860px) {
  :root { --glass-blur: 16px; }
  .glass--strong { --glass-blur: 20px; }
}

/* Safari on iOS drops frames badly when a backdrop-filter layer is also being
   transformed. Anything that moves gets the flat treatment there. */
@supports (-webkit-touch-callout: none) {
  .card3d .glass,
  .card3d.glass {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* ==========================================================================
   FLOATING 3D CARDS
   ========================================================================== */

/* --------------------------- A single card --------------------------- */
.card3d {
  position: absolute;
  left: var(--l, 50%);
  top: var(--t, 50%);
  width: var(--w, 44%);
  transform-style: preserve-3d;
  transform:
    translate(-50%, -50%)
    translateZ(var(--z, 0px))
    rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg))
    rotateZ(var(--rz, 0deg))
    scale(var(--s, 1));
  z-index: var(--layer, 1);
}

/* ---- Portrait -------------------------------------------------------------
   The hero's right-hand column, opposite the copy. It fills its grid track,
   and the track is capped at 400px because that is what the source can serve
   sharp: the largest original is 800 square, which is exactly 400 at 2x.
   Going wider would only soften it.

   A square with rounded corners that turns towards the cursor. `.portrait` is
   the camera and never moves; `.portrait__tilt` is the thing that turns, and
   the sheen is inside it so the highlight turns with the picture rather than
   sliding across a flat pane. motion.js writes --tilt-x / --tilt-y / --gx /
   --gy and nothing else. */
.portrait {
  --r-portrait: 22px;
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  margin: 0;
  perspective: 1000px;
  /* The float is on this element and the turn is on the one inside, because a
     CSS animation replaces the whole `transform` - run both on one element and
     the keyframes wipe the tilt every frame. */
  animation: portrait-float 7.5s ease-in-out infinite;
}

@keyframes portrait-float {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -13px, 0); }
}

/* The warm pool behind it. Outside the tilt on purpose: it reads as light in
   the room rather than as part of the object, so it should stay put while the
   picture turns in front of it. */
.portrait::before {
  content: '';
  position: absolute;
  inset: -12% -9% -16%;
  background: radial-gradient(56% 52% at 50% 46%,
    rgba(255, 138, 61, 0.26) 0%,
    rgba(255, 138, 61, 0.10) 46%,
    transparent 76%);
  filter: blur(18px);
  pointer-events: none;
}

.portrait__tilt {
  /* The angle it rests at, before the cursor is anywhere near. Negative yaw
     turns the right edge towards you and the left edge away, so the face
     points back into the page rather than out past the edge of the window -
     it is the far column, and a picture out there squared up to the viewer
     reads as pasted on. motion.js adds its own delta on top of these. */
  --rest-x: 2.5deg;
  --rest-y: -8deg;

  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--r-portrait);
  /* Clips the picture and the sheen to the rounded corners. This is why there
     is no `transform-style: preserve-3d` here: overflow forces flattening
     anyway, and nothing inside needs its own 3D space - both children are
     flat on the surface and turn with it. */
  overflow: hidden;
  transform:
    rotateX(calc(var(--rest-x) + var(--tilt-x, 0deg)))
    rotateY(calc(var(--rest-y) + var(--tilt-y, 0deg)))
    scale(var(--tilt-s, 1));
  /* Only for the settle back to square when the pointer leaves. motion.js
     clears it while tracking, or the picture lags the cursor. */
  transition: transform 0.5s var(--ease);
  will-change: transform;
  box-shadow:
    0 26px 50px -22px rgba(0, 0, 0, 0.95),
    0 0 0 1px rgba(255, 255, 255, 0.08) inset;
}

/* `picture` is an inline box that would otherwise sit between the wrapper and
   the image and swallow the 100% height. */
.portrait picture { display: contents; }

/* Not `border-radius: inherit`. `picture` sits between this and the wrapper
   and, being `display: contents`, is still what `inherit` resolves against -
   it has no radius of its own, so the corners came out square. */
.portrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--r-portrait);
}

/* The highlight. Its centre follows the pointer, which is what sells the turn:
   a fixed gradient on a tilting plane reads as a sticker, a moving one reads
   as a surface catching the light. Fades out with --sheen when the pointer
   leaves, so a touch device never sees it stuck on. */
.portrait__sheen {
  position: absolute;
  inset: 0;
  border-radius: var(--r-portrait);
  pointer-events: none;
  background: radial-gradient(
    38% 38% at var(--gx, 50%) var(--gy, 50%),
    rgba(255, 226, 196, 0.30) 0%,
    rgba(255, 168, 96, 0.10) 42%,
    transparent 72%);
  opacity: var(--sheen, 0);
  transition: opacity 0.4s var(--ease);
  mix-blend-mode: screen;
}

/* Shown only if the photo is missing (JS sets .is-empty). */
.portrait__hint { display: none; }
.portrait.is-empty {
  display: grid;
  place-items: center;
  text-align: center;
  padding: 10%;
  background: linear-gradient(150deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
}
.portrait.is-empty img { display: none; }
.portrait.is-empty .portrait__hint {
  display: block;
  font-size: 0.56rem;
  line-height: 1.4;
  color: var(--ink-3);
}

/* Separate layer so the idle float animation never fights the hover lift. */
.card3d__float {
  transform-style: preserve-3d;
  animation: float var(--float-dur, 9s) var(--ease-soft) infinite alternate;
  animation-delay: calc(var(--i, 0) * -1.7s);
}
@keyframes float {
  from { transform: translate3d(0, -7px, 0); }
  to   { transform: translate3d(0, 9px, 0); }
}
/* On the helix the orbit is the movement; an idle bob on top of it just reads
   as the card wobbling loose. */
.helix .card3d__float { animation: none; }

.card3d__face {
  display: block;
  position: relative;
  border-radius: var(--r-lg);
  padding: 7px;
  transform-style: preserve-3d;
  transition: transform 0.7s var(--ease), box-shadow 0.7s var(--ease);
  will-change: transform;
}

/* Hover / keyboard focus: the card lifts forward and turns to face you. */
.card3d:hover  .card3d__face,
.card3d:focus-within .card3d__face {
  transform: translateZ(70px) rotateY(calc(var(--ry, 0deg) * -0.62));
  box-shadow: 0 50px 90px -40px rgba(0, 0, 0, 1);
}

.card3d__shot {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: calc(var(--r-lg) - 7px);
  background: #0b0b0f;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
}

.card3d__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.9s var(--ease), filter 0.6s var(--ease);
  filter: saturate(1.02) contrast(1.02);
}
.card3d:hover .card3d__shot img { transform: scale(1.045); }

/* Play glyph */
.card3d__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.card3d__play span {
  display: grid;
  place-items: center;
  width: 54px; height: 54px;
  border-radius: 999px;
  background: rgba(10, 10, 14, 0.5);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.32);
  transform: scale(0.85);
  transition: transform 0.45s var(--ease);
}
.card3d__play svg { margin-left: 3px; }
.card3d:hover .card3d__play,
.card3d:focus-within .card3d__play { opacity: 1; }
.card3d:hover .card3d__play span { transform: scale(1); }

/* Caption under the thumbnail */
.card3d__meta {
  padding: 12px 8px 5px;
}
.card3d__title {
  font-size: 0.9rem;
  font-weight: 550;
  line-height: 1.32;
  letter-spacing: -0.015em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card3d__sub {
  margin-top: 5px;
  font-size: 0.76rem;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: 7px;
}
.card3d__sub b { font-weight: 500; color: var(--ink-2); }
.card3d__sub i {
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--ink-4);
}

/* Entrance */
.card3d {
  opacity: 0;
  animation: card-in 1.1s var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * 110ms + 180ms);
}
@keyframes card-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .card3d { animation: none; opacity: 1; }
  .card3d__float { animation: none; }
  .card3d__face { transition: none; }
  .card3d:hover .card3d__face,
  .card3d:focus-within .card3d__face { transform: none; }

  /* The drift goes; the resting angle stays. One is motion, the other is just
     the shape the picture is, and motion.js never starts the cursor tracking
     under reduced motion anyway. */
  .portrait { animation: none; }
  .portrait__tilt { transition: none; }
}
