/* ==========================================================================
   ai-hub.css — /ai/ and /ai/sample/
   ==========================================================================
   A futuristic treatment built entirely from CSS transforms, gradients and
   masks. No WebGL, no Three.js, no tilt library.

   THE FOUR THINGS THAT MAKE IT READ AS "FUTURE"
   ---------------------------------------------
   1. DEPTH THAT RESPONDS. Real perspective with panels at genuinely different
      Z, and a stage that yaws to the pointer. Depth you can move is what
      separates 3D from a drop shadow.
   2. LIGHT THAT IS NOT WHITE. Every surface is lit by its own hue, carried in
      --h and set once per panel, so seven services are seven colours of glass
      rather than seven grey cards.
   3. HARDWARE ARTEFACTS. Scanlines, a noise plate, corner brackets, a sweeping
      scan bar. Cheap to draw, and they are what the eye reads as "a display"
      rather than "a web page".
   4. RESTRAINT AT THE TEXT. The chrome is loud; the type is not. Copy sits on
      near-solid panels because a holographic paragraph is an unreadable one.

   TRANSFORM DISCIPLINE
   --------------------
   Nothing in this file is animated by writing `transform` from JavaScript.
   Every moving element composes custom properties — --px/--py for parallax,
   --rx/--ry for pointer yaw — inside a single `transform` declared here. GSAP's
   reveals write `transform` on their own targets, and the two would overwrite
   each other silently. This codebase has already paid for that lesson twice.
   ========================================================================== */

.page-aix {
  /* PROMOTED FROM /ai/sample-amber/ on 2026-09-21. The hub was built on a
     blue-black ground with a cyan-violet atmosphere; it now runs on the house
     yellows, which is what the rest of the site is lit by.

     Nothing below this block changed to do it. The five sky layers already
     read their colour from --aix-h1..h4, so repointing four numbers repaints
     the whole atmosphere — and the panels are untouched because they take
     their hue from --h, which aix_render() prints per service. */
  --aix-ink:   #0d0a05;              /* warm near-black, not blue-black */
  --aix-line:  rgb(140 190 255 / .16);
  --aix-glass: rgb(26 20 9 / .72);   /* glass warms with the room it sits in */

  /* THE FOUR DECORATIVE HUES, NAMED.
     They used to be written as literals in 78 places, which meant the palette
     could not be changed without editing 78 rules — and /ai/sample-gold/
     exists precisely to change it. Defaults are the exact values that were
     hardcoded, so this page renders identically to before.

     hsl(150) is deliberately NOT in here. That one is the live-status dot and
     its chip: it means "running", not "on brand", and a green that turns gold
     with the theme stops carrying the only thing it was there to say. */
  --aix-h1: 45;    /* brand yellow — floor grid and the brightest well */
  --aix-h2: 32;    /* amber        */
  --aix-h3: 20;    /* burnt orange */
  --aix-h4: 52;    /* pale gold    */
  color-scheme: dark;
}
.page-aix .container { position: relative; z-index: 1; }

/* The page owns its own ground colour: the futuristic palette only works
   against something close to black, and the site's --surface-0 is a warm grey
   that would wash the glow out. */
body.page-aix { background: var(--aix-ink); }

/* ── THE ATMOSPHERE ─────────────────────────────────────────────────────────
   Five fixed layers. Fixed rather than absolute so each costs exactly one
   transform write per frame no matter how long the page grows. */
.aixSky { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.aixSky > * { position: absolute; inset: -25% -10%; will-change: transform; }
.aixSky__aurora,
.aixSky__floor,
.aixSky__stars { transform: translate3d(0, var(--py, 0px), 0); }

/* Aurora — four drifting wells of colour. The animation moves the gradient
   positions, not the element, so it never fights the parallax transform. */
.aixSky__aurora {
  background:
    radial-gradient(42% 38% at 14% 18%, hsl(var(--aix-h2) 85% 55% / .28), transparent 66%),
    radial-gradient(38% 34% at 84% 12%, hsl(var(--aix-h1) 90% 50% / .24), transparent 64%),
    radial-gradient(46% 42% at 70% 78%, hsl(var(--aix-h3) 80% 55% / .20), transparent 68%),
    radial-gradient(40% 40% at 26% 86%, hsl(var(--aix-h4) 90% 55% / .18), transparent 66%);
  filter: blur(60px);
  /* Held back from full. At the same value a yellow well reads hotter than
     the blue one this was tuned for, and at 1 it ate the contrast under the
     headline sitting over it. */
  opacity: .82;
  animation: aixDrift 24s ease-in-out infinite alternate;
}
@keyframes aixDrift {
  to {
    background-position: 6% -4%, -5% 5%, -4% -6%, 5% 4%;
    filter: blur(76px);
  }
}

/* The floor. A perspective-warped grid receding to a horizon, masked out
   before it reaches the top so there is no hard edge. */
.aixSky__floor {
  inset: 46% -60% -34% -60%;
  background-image:
    linear-gradient(hsl(var(--aix-h1) 90% 60% / .16) 1px, transparent 1px),
    linear-gradient(90deg, hsl(var(--aix-h1) 90% 60% / .16) 1px, transparent 1px);
  background-size: 72px 72px;
  transform: translate3d(0, var(--py, 0px), 0) perspective(300px) rotateX(62deg);
  transform-origin: 50% 0;
  mask-image: linear-gradient(to bottom, black, transparent 66%);
}
.aixSky__stars {
  background-image:
    radial-gradient(1.7px 1.7px at 12% 22%, rgb(255 255 255 / .55), transparent),
    radial-gradient(1.5px 1.5px at 68% 14%, hsl(var(--aix-h1) 90% 75% / .6), transparent),
    radial-gradient(2.1px 2.1px at 42% 72%, hsl(var(--aix-h3) 85% 78% / .5), transparent),
    radial-gradient(1.5px 1.5px at 88% 58%, rgb(255 255 255 / .4), transparent),
    radial-gradient(1.7px 1.7px at 24% 54%, hsl(var(--aix-h2) 85% 80% / .5), transparent),
    radial-gradient(1.4px 1.4px at 78% 88%, rgb(255 255 255 / .35), transparent);
}
/* Scanlines and noise. Both are what make the whole thing read as a SCREEN;
   without them the gradients just look like a gradient. */
.aixSky__scan {
  inset: 0;
  background: repeating-linear-gradient(
    to bottom, rgb(255 255 255 / .028) 0 1px, transparent 1px 3px);
  mix-blend-mode: overlay;
}
.aixSky__noise {
  inset: 0; opacity: .045;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ── ICONS ──────────────────────────────────────────────────────────────────
   Masks, not images: they inherit the panel's hue, stay sharp at any size, and
   cannot be the asset that fails to load. */
.aixArt {
  display: block; width: 1.5rem; height: 1.5rem;
  background: currentColor;
  mask-repeat: no-repeat; mask-size: contain; mask-position: center;
}
.aixArt--wave   { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'%3E%3Cpath d='M3 12h2M8 6v12M13 3v18M18 8v8M22 11h-1'/%3E%3C/svg%3E"); }
.aixArt--call   { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7'%3E%3Crect x='2' y='5' width='14' height='14' rx='3'/%3E%3Cpath d='M16 10.5 22 7v10l-6-3.5z'/%3E%3C/svg%3E"); }
.aixArt--clone  { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7'%3E%3Ccircle cx='9' cy='8' r='3.4'/%3E%3Cpath d='M3 20a6 6 0 0 1 12 0'/%3E%3Cpath d='M15.5 5.2a3.4 3.4 0 0 1 0 5.6M18 20a6 6 0 0 0-2.2-4.6' stroke-dasharray='2.4 2'/%3E%3C/svg%3E"); }
.aixArt--cap    { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7'%3E%3Cpath d='M12 4 2 9l10 5 10-5-10-5z'/%3E%3Cpath d='M6 11.5V17c0 1.7 2.7 3 6 3s6-1.3 6-3v-5.5M22 9v6'/%3E%3C/svg%3E"); }
.aixArt--spark  { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round'%3E%3Cpath d='M12 2.5 14 9l6.5 2-6.5 2-2 6.5-2-6.5L3.5 11 10 9z'/%3E%3Cpath d='M19 3v3M20.5 4.5h-3'/%3E%3C/svg%3E"); }
.aixArt--flow   { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7'%3E%3Crect x='2.5' y='3' width='6' height='5' rx='1.5'/%3E%3Crect x='15.5' y='3' width='6' height='5' rx='1.5'/%3E%3Crect x='9' y='16' width='6' height='5' rx='1.5'/%3E%3Cpath d='M5.5 8v3.5h13V8M12 11.5V16'/%3E%3C/svg%3E"); }
.aixArt--window { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7'%3E%3Crect x='2.5' y='4' width='19' height='16' rx='2.5'/%3E%3Cpath d='M2.5 9h19M5.6 6.5h.01M8.2 6.5h.01'/%3E%3C/svg%3E"); }

/* ── BUTTONS ────────────────────────────────────────────────────────────── */
.aixBtn {
  display: inline-flex; align-items: center; justify-content: center; gap: .7ch;
  padding: .85rem 1.5rem; border-radius: 999px; border: 1px solid transparent;
  font: inherit; font-size: var(--fs-sm); font-weight: 500;
  text-decoration: none; cursor: pointer; position: relative; overflow: hidden;
  transition: transform .3s cubic-bezier(.22,1,.36,1), box-shadow .35s, border-color .3s, background .3s;
}
.aixBtn--sm { padding: .55rem 1.05rem; }
.aixBtn--primary {
  background: linear-gradient(120deg, hsl(var(--aix-h1) 95% 52%), hsl(var(--aix-h2) 85% 62%));
  color: #04060c; font-weight: 600;
  box-shadow: 0 10px 34px -12px hsl(var(--aix-h1) 95% 52% / .8);
}
.aixBtn--primary:hover { transform: translateY(-2px); box-shadow: 0 16px 42px -12px hsl(var(--aix-h1) 95% 52% / .95); }
.aixBtn--ghost {
  border-color: var(--aix-line); color: var(--text-hi);
  background: rgb(255 255 255 / .04); backdrop-filter: blur(8px);
}
.aixBtn--ghost:hover { border-color: hsl(var(--aix-h1) 90% 60% / .6); transform: translateY(-2px); background: rgb(255 255 255 / .07); }
.aixBtn__play {
  width: 0; height: 0; flex: none;
  border-left: 8px solid hsl(var(--aix-h1) 95% 62%);
  border-top: 5px solid transparent; border-bottom: 5px solid transparent;
}

/* ── HERO ───────────────────────────────────────────────────────────────────
   Deliberately compact. The brief is that all seven services are visible the
   moment the page opens, and every pixel the hero copy takes is a pixel the
   console's second row loses — at the first sizing this hero was 371px tall
   and pushed row two entirely below the fold, which defeated the whole design.
   The copy is therefore sized to fit, not to fill. */
.aixHero { position: relative; z-index: 1; padding-block: clamp(var(--s-4), 1.8vw, var(--s-6)) clamp(var(--s-10), 6vw, var(--s-20)); }
/* The sample banner is temporary furniture, but while it is here it is eating
   the console's bottom row. */
.page-aix .sample-flag { margin-bottom: var(--s-2); }
/* Copy left, Yuna right. The right half was empty on a page whose headline
   product is a face that talks — an argument made in words and then not
   shown. Her column is sized to the copy's own height rather than to the
   portrait's natural ratio, because anything taller pushes the console's
   second row below the fold and the whole "all seven at once" premise with it. */
.aixHero__top {
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(var(--s-6), 4vw, var(--s-12));
}
.aixHero__copy { max-width: 44rem; }
.aixHero__eyebrow {
  display: inline-flex; align-items: center; gap: .7ch; margin: 0 0 var(--s-3);
  font-family: var(--font-mono); font-size: .64rem; color: hsl(var(--aix-h1) 80% 72%);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
}
.aixHero__pulse {
  width: .5rem; height: .5rem; border-radius: 50%; background: hsl(150 80% 55%);
  box-shadow: 0 0 0 0 hsl(150 80% 55% / .6); animation: aixPulse 2.4s ease-out infinite;
}
@keyframes aixPulse { 70%, 100% { box-shadow: 0 0 0 .7rem hsl(150 80% 55% / 0); } }

.aixHero__h1 {
  margin: 0 0 var(--s-4);
  font-size: clamp(2rem, 1.05rem + 3vw, 3.15rem);
  line-height: 1.05; letter-spacing: -.035em; color: #fff;
}
/* The decoding line. JS scrambles it into place on load; the gradient and the
   chromatic ghost behind it are what make it read as a readout rather than as
   a heading that happens to animate. */
.aixHero__decode {
  position: relative; display: inline-block;
  background: linear-gradient(100deg, hsl(var(--aix-h1) 95% 62%), hsl(var(--aix-h2) 90% 72%) 45%, hsl(var(--aix-h3) 85% 70%));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.aixHero__decode::after {
  content: attr(data-ghost); position: absolute; inset: 0;
  color: hsl(var(--aix-h3) 90% 60% / .32); transform: translate(2px, -1px);
  z-index: -1; pointer-events: none;
}
.aixHero__sub { margin: 0; max-width: 58ch; color: var(--text-mid); font-size: var(--fs-body); line-height: 1.55; }
.aixHero__cta { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-5); }

/* ── YUNA, THE GUIDE ────────────────────────────────────────────────────────
   A button, not a decorated <div>: it starts the walkthrough, so it has to be
   reachable by keyboard and announce itself. The speech bubble is part of the
   affordance rather than flavour — a portrait with a play triangle on it could
   mean "watch a showreel", and this needs to say what pressing it does. */
.aixGuide {
  position: relative; display: block; padding: 0; border: 0; background: none;
  cursor: pointer; font: inherit; text-align: left;
  width: clamp(11rem, 17vw, 15rem);
}
.aixGuide__plate {
  position: relative; display: block; overflow: hidden;
  aspect-ratio: 4 / 5; border-radius: 16px;
  border: 1px solid hsl(var(--aix-h1) 90% 62% / .4);
  background: rgb(8 12 22 / .6);
  box-shadow: 0 26px 60px -26px #000;
  transition: transform .5s cubic-bezier(.22,1,.36,1), border-color .35s, box-shadow .4s;
}
.aixGuide:hover .aixGuide__plate,
.aixGuide:focus-visible .aixGuide__plate {
  transform: translateY(-4px);
  border-color: hsl(var(--aix-h1) 95% 68% / .95);
  box-shadow: 0 0 0 1px hsl(var(--aix-h1) 95% 62% / .35), 0 34px 74px -24px hsl(var(--aix-h1) 90% 40% / .8);
}
.aixGuide:focus-visible { outline: 2px solid hsl(var(--aix-h1) 95% 65%); outline-offset: 6px; border-radius: 18px; }
.aixGuide__plate img {
  display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 12%;
  filter: saturate(.95) brightness(.92);
  transition: transform 1s cubic-bezier(.22,1,.36,1), filter .5s;
}
.aixGuide:hover .aixGuide__plate img { transform: scale(1.05); filter: saturate(1.05) brightness(1); }
.aixGuide__scan {
  position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom, hsl(var(--aix-h1) 95% 70% / .07) 0 1px, transparent 1px 4px);
}
.aixGuide__halo {
  position: absolute; inset: auto -20% -30%; height: 60%;
  background: radial-gradient(50% 100% at 50% 100%, hsl(var(--aix-h1) 90% 55% / .5), transparent 70%);
  filter: blur(24px); z-index: -1;
}
.aixGuide__live {
  position: absolute; left: .6rem; top: .6rem; z-index: 2;
  padding: .16rem .5rem .16rem 1.1rem; border-radius: 999px;
  background: rgb(4 6 12 / .82); border: 1px solid hsl(150 80% 55% / .5);
  background-image: radial-gradient(circle at .55rem 50%, hsl(150 85% 58%) 2.2px, transparent 2.4px);
  font-family: var(--font-mono); font-size: .5rem; letter-spacing: .16em; color: hsl(150 80% 70%);
}
/* Bottom-right, not centred. Centred it sat squarely on her mouth and chin —
   which on a card whose entire subject is a face is the one place it cannot
   go. In the corner it reads as a player control instead, and she stays
   looking at you. */
.aixGuide__play {
  position: absolute; right: .7rem; bottom: 3.6rem; z-index: 2;
  display: grid; place-items: center; width: 3rem; height: 3rem; border-radius: 50%;
  background: rgb(6 10 18 / .68); border: 1px solid hsl(var(--aix-h1) 95% 70% / .8);
  backdrop-filter: blur(6px);
  box-shadow: 0 0 0 0 hsl(var(--aix-h1) 95% 60% / .5);
  animation: aixRipple 2.8s ease-out infinite;
  transition: transform .35s cubic-bezier(.34,1.56,.64,1);
}
.aixGuide__play::after {
  content: ''; margin-left: 3px;
  border-left: 11px solid hsl(var(--aix-h1) 95% 75%);
  border-top: 7px solid transparent; border-bottom: 7px solid transparent;
}
.aixGuide:hover .aixGuide__play { transform: scale(1.1); }
@keyframes aixRipple { 70%, 100% { box-shadow: 0 0 0 1.3rem hsl(var(--aix-h1) 95% 60% / 0); } }
.aixGuide__foot {
  position: absolute; inset: auto 0 0; z-index: 2;
  display: grid; gap: 1px; padding: 1.6rem .8rem .7rem;
  background: linear-gradient(transparent, rgb(4 6 12 / .92) 55%);
}
.aixGuide__foot b { font-size: var(--fs-sm); color: #fff; }
.aixGuide__foot i {
  font-style: normal; font-family: var(--font-mono); font-size: .52rem;
  color: hsl(var(--aix-h1) 80% 72%); letter-spacing: .1em; text-transform: uppercase;
}
/* The bubble. Anchored to the plate's top-left and pointing at her. */
/* Clear of the plate entirely. Overlapping it by 18% put the bubble straight
   on top of the LIVE badge, so both were unreadable. */
.aixGuide__say {
  position: absolute; right: calc(100% + .7rem); top: .4rem; z-index: 3;
  width: max-content; max-width: 12rem;
  padding: .55rem .8rem; border-radius: 12px 12px 2px 12px;
  border: 1px solid hsl(var(--aix-h1) 90% 62% / .45);
  background: rgb(10 16 28 / .95); backdrop-filter: blur(8px);
  font-size: .78rem; line-height: 1.35; color: var(--text-hi);
  box-shadow: 0 14px 34px -16px #000;
  animation: aixSay 5.5s ease-in-out 1.2s infinite;
}
.aixGuide__say::after {
  content: ''; position: absolute; right: -5px; bottom: 8px;
  width: 9px; height: 9px; rotate: 45deg;
  background: rgb(10 16 28 / .95);
  border-right: 1px solid hsl(var(--aix-h1) 90% 62% / .45); border-bottom: 1px solid hsl(var(--aix-h1) 90% 62% / .45);
}
@keyframes aixSay { 0%, 88%, 100% { transform: translateY(0); } 94% { transform: translateY(-4px); } }

/* ── THE CONSOLE ────────────────────────────────────────────────────────────
   The centrepiece. Seven panels, 4 over 3, curved by each panel's signed
   distance from its row centre. The stage yaws to the pointer as one body, so
   the array feels like an object rather than seven independent hovers. */
.aixConsole {
  margin-top: clamp(var(--s-4), 2.2vw, var(--s-6));
  display: grid; gap: clamp(var(--s-3), 1.4vw, var(--s-4));
  perspective: 1500px; perspective-origin: 50% 42%;
  transform-style: preserve-3d;
  position: relative;
}
.aixConsole__row {
  display: grid; gap: clamp(var(--s-3), 1.4vw, var(--s-4));
  transform-style: preserve-3d;
  /* The pointer yaw. Written by JS as --rx/--ry, composed here so it can never
     clobber a panel's own curve transform. */
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform .6s cubic-bezier(.22,1,.36,1);
}
.aixConsole__row--1 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.aixConsole__row--2 { grid-template-columns: repeat(3, minmax(0, 1fr)); width: 76%; margin-inline: auto; }
/* A lit rail under the array — the console's "shelf". */
.aixConsole__rail {
  position: absolute; left: 8%; right: 8%; bottom: -1.6rem; height: 1px;
  background: linear-gradient(90deg, transparent, hsl(var(--aix-h1) 90% 60% / .55), hsl(var(--aix-h2) 85% 65% / .5), transparent);
  filter: blur(.5px);
}
.aixConsole__rail::after {
  content: ''; position: absolute; inset: -1.4rem 0 auto;
  height: 3rem;
  background: radial-gradient(60% 100% at 50% 0, hsl(var(--aix-h1) 90% 55% / .16), transparent 70%);
}

.aixPanel {
  position: relative; display: grid; align-content: start; gap: .35rem;
  padding: var(--s-3) var(--s-4) var(--s-4);
  border-radius: 14px; border: 1px solid var(--aix-line);
  background:
    linear-gradient(165deg, hsl(var(--h) 80% 60% / .13), transparent 58%),
    var(--aix-glass);
  backdrop-filter: blur(14px);
  text-decoration: none; color: inherit; overflow: hidden;
  transform-style: preserve-3d;
  /* THE CURVE. --o signed offset from row centre, --a its absolute value. */
  transform:
    rotateY(calc(var(--o) * -8deg))
    translateZ(calc(var(--a) * -30px))
    translateY(calc(var(--a) * 5px));
  transition: transform .55s cubic-bezier(.22,1,.36,1), border-color .3s, box-shadow .4s;
  animation: aixRise .7s cubic-bezier(.22,1,.36,1) both;
  animation-delay: calc(var(--i) * 70ms + .15s);
}
@keyframes aixRise { from { opacity: 0; transform: translateY(26px) rotateX(12deg); } }

.aixPanel:hover, .aixPanel:focus-visible {
  transform:
    rotateY(calc(var(--o) * -4deg))
    translateZ(34px)
    translateY(calc(var(--a) * 2px - 4px));
  border-color: hsl(var(--h) 90% 62% / .75);
  box-shadow:
    0 0 0 1px hsl(var(--h) 90% 62% / .35),
    0 30px 70px -24px hsl(var(--h) 90% 40% / .7);
  z-index: 3;
}
.aixPanel:focus-visible { outline: 2px solid hsl(var(--h) 90% 65%); outline-offset: 3px; }

/* An iridescent top edge — one line of light, which is most of what sells
   "glass" without turning the whole panel into a mirror. */
.aixPanel__edge {
  position: absolute; inset: 0 0 auto; height: 1px;
  background: linear-gradient(90deg, transparent, hsl(var(--h) 95% 70% / .9), transparent);
}
.aixPanel__sheen {
  position: absolute; inset: 0; opacity: 0; pointer-events: none;
  background: radial-gradient(220px circle at var(--mx, 50%) var(--my, 50%), hsl(var(--h) 95% 65% / .22), transparent 62%);
  transition: opacity .35s;
}
.aixPanel:hover .aixPanel__sheen { opacity: 1; }

.aixPanel__n {
  font-family: var(--font-mono); font-size: .58rem; color: hsl(var(--h) 80% 68% / .8);
  letter-spacing: var(--tracking-eyebrow);
}
.aixPanel__ico {
  display: grid; place-items: center; width: 2.3rem; height: 2.3rem; margin: var(--s-2) 0 var(--s-2);
  border-radius: 10px; border: 1px solid hsl(var(--h) 85% 62% / .4);
  background: linear-gradient(150deg, hsl(var(--h) 85% 58% / .28), transparent);
  color: hsl(var(--h) 95% 76%);
  transition: transform .45s cubic-bezier(.34,1.56,.64,1), box-shadow .35s;
}
.aixPanel:hover .aixPanel__ico { transform: translateZ(26px) scale(1.06); box-shadow: 0 0 0 5px hsl(var(--h) 90% 60% / .1); }
.aixPanel__kicker {
  font-family: var(--font-mono); font-size: .6rem; color: hsl(var(--h) 85% 74%);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
}
.aixPanel__title {
  font-size: .95rem; line-height: 1.28; color: #fff; font-weight: 500;
  text-wrap: balance;
}
/* The arrow sits in the top-right corner rather than on its own line at the
   foot. As a row it cost ~28px per panel, which across two rows was 56px —
   and 56px was the difference between the second row being fully above the
   fold on a laptop and being clipped. It is an affordance, not content, so it
   can live in the corner beside the index. */
.aixPanel__go {
  position: absolute; right: var(--s-4); top: var(--s-3);
  font-size: .95rem; line-height: 1; color: hsl(var(--h) 90% 72%); opacity: .55;
  transition: transform .35s cubic-bezier(.22,1,.36,1), opacity .3s;
}
.aixPanel:hover .aixPanel__go { opacity: 1; }
.aixPanel:hover .aixPanel__go { transform: translateX(5px); }

.aixConsole__foot {
  display: flex; align-items: center; justify-content: center; gap: .8ch;
  /* Centring on a <p> always has to be paired with releasing the measure cap,
     or the content is centred inside a 600px box that is itself sitting at the
     left of a 1126px row — i.e. visibly off-centre. Same trap as
     `.cta-band__actions`, `.section__more` and the rest. */
  max-width: none;
  margin: clamp(var(--s-6), 3vw, var(--s-8)) 0 0;
  font-size: var(--fs-sm); color: var(--text-lo); text-align: center;
}
.aixConsole__foot span { color: hsl(var(--aix-h1) 90% 65%); animation: aixBob 2.6s ease-in-out infinite; }
@keyframes aixBob { 50% { transform: translateY(4px); } }

/* ── SECTION FURNITURE ──────────────────────────────────────────────────── */
.aixSection { position: relative; z-index: 1; padding-block: clamp(var(--s-10), 6vw, var(--s-20)); }
.aixSection--alt { background: linear-gradient(180deg, transparent, rgb(10 14 24 / .7) 12% 88%, transparent); }
.aixHead { max-width: 44rem; margin: 0 auto clamp(var(--s-8), 4vw, var(--s-12)); text-align: center; }
.aixHead__k {
  margin: 0 0 var(--s-3); font-family: var(--font-mono); font-size: .62rem;
  color: hsl(var(--aix-h1) 80% 70%); letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
}
.aixHead h2 { margin: 0 0 var(--s-3); font-size: clamp(1.8rem, 1.2rem + 2.4vw, 2.9rem); letter-spacing: -.03em; color: #fff; }
.aixHead p { margin: 0; color: var(--text-mid); }
.aixAccent {
  background: linear-gradient(100deg, hsl(var(--aix-h1) 95% 62%), hsl(var(--aix-h2) 90% 72%));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* ── THE SEVEN ──────────────────────────────────────────────────────────── */
.aixSvc { padding-block: clamp(var(--s-8), 4vw, var(--s-12)); }
.aixSvc__in {
  display: grid; gap: clamp(var(--s-6), 4vw, var(--s-12));
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  align-items: center;
}
.aixSvc:nth-child(even) .aixSvc__media { order: 2; }

.aixSvc__media { position: relative; }
.aixSvc__frame {
  position: relative; overflow: hidden; border-radius: 16px;
  border: 1px solid var(--aix-line); aspect-ratio: 16 / 10;
  background: rgb(8 10 18 / .8);
  box-shadow: 0 34px 80px -34px #000;
  transform: translate3d(0, var(--py, 0px), 0) rotateY(7deg) rotateX(2deg);
  transition: transform .8s cubic-bezier(.22,1,.36,1);
}
.aixSvc:nth-child(even) .aixSvc__frame { transform: translate3d(0, var(--py, 0px), 0) rotateY(-7deg) rotateX(2deg); }
.aixSvc__frame:hover,
.aixSvc:nth-child(even) .aixSvc__frame:hover { transform: translate3d(0, var(--py, 0px), 0) rotateY(0deg) rotateX(0deg); }
.aixSvc__frame img, .aixSvc__frame video {
  display: block; width: 100%; height: 100%; object-fit: cover;
  /* Biased upward, not centred. Three of the seven sources are PORTRAIT video
     of a person, and a centred crop into a 16:10 frame lands on the collarbone
     — it cut Allie's head off entirely. Favouring the top keeps faces in shot
     and costs the landscape screenshots nothing. */
  object-position: 50% 24%;
  filter: saturate(.9) contrast(1.04) brightness(.86);
  transition: filter .5s, transform 1.1s cubic-bezier(.22,1,.36,1);
}
.aixSvc__frame:hover img, .aixSvc__frame:hover video { filter: saturate(1.05) brightness(.98); transform: scale(1.03); }
/* The clip is appended AFTER the readout grid and the HUD chip, so at equal
   z-index it paints over both and the frame loses its instrumentation the
   moment the video attaches. Explicit stacking instead. */
.aixSvc__frame video { position: absolute; inset: 0; z-index: 1; }
/* A faint readout grid over the footage — the cheapest possible way to say
   "this is a feed, not a photograph". */
.aixSvc__grid {
  position: absolute; inset: 0; pointer-events: none; z-index: 2;
  background-image:
    linear-gradient(hsl(var(--h) 90% 70% / .09) 1px, transparent 1px),
    linear-gradient(90deg, hsl(var(--h) 90% 70% / .09) 1px, transparent 1px);
  background-size: 42px 42px;
  mask-image: radial-gradient(120% 100% at 50% 50%, black 30%, transparent 78%);
}
.aixSvc__hud {
  position: absolute; left: var(--s-4); top: var(--s-4); z-index: 3;
  display: flex; align-items: baseline; gap: .7ch;
  padding: .3rem .7rem; border-radius: 999px;
  border: 1px solid hsl(var(--h) 85% 65% / .4);
  background: rgb(4 6 12 / .68); backdrop-filter: blur(6px);
}
.aixSvc__hud b { font-family: var(--font-mono); font-size: .68rem; color: hsl(var(--h) 95% 74%); }
.aixSvc__hud i { font-style: normal; font-size: .6rem; color: rgb(255 255 255 / .66); letter-spacing: .06em; text-transform: uppercase; }
/* The glow used to bleed 6% past the media box on each side. A blurred
   decoration extending beyond the container is layout overflow, and below
   900px that became real horizontal page scroll on every one of the seven
   rows. Kept inside the box: blur() already paints ~46px past its own edges,
   and painted overflow does not add to scrollWidth. */
/* A YouTube play button, on the two sections whose proof is a published film.
   The iframe is not built until this is pressed: a raw embed costs every
   visitor ~1MB of third-party JavaScript and a cookie whether or not they ever
   watch, and there are two of them on this page. */
.aixSvc__play {
  position: absolute; left: 50%; top: 50%; translate: -50% -50%; z-index: 3;
  display: grid; place-items: center; width: 4.6rem; height: 3.2rem;
  border: 0; border-radius: 13px; background: #ff3d3d; cursor: pointer;
  box-shadow: 0 14px 38px -10px rgb(255 61 61 / .75);
  transition: transform .3s cubic-bezier(.34,1.56,.64,1), background .3s;
}
.aixSvc__play::after {
  content: ''; margin-left: 4px;
  border-left: 16px solid #fff; border-top: 10px solid transparent; border-bottom: 10px solid transparent;
}
.aixSvc__play:hover { transform: translate(-50%, -50%) scale(1.08); background: #ff2020; }
.aixSvc__play:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.aixSvc__frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; z-index: 4; }
/* Once it is a real player the tilt has to go — a rotated iframe makes the
   YouTube controls hard to hit and the captions soft. */
.aixSvc__frame.is-live,
.aixSvc__frame.is-live:hover,
.aixSvc:nth-child(even) .aixSvc__frame.is-live,
.aixSvc:nth-child(even) .aixSvc__frame.is-live:hover { transform: none; }

.aixSvc__glow {
  position: absolute; inset: 12% 0 -10%; z-index: -1;
  background: radial-gradient(70% 60% at 50% 60%, hsl(var(--h) 90% 50% / .4), transparent 70%);
  filter: blur(46px);
}

.aixSvc__k {
  display: flex; align-items: center; gap: .8ch; margin: 0 0 var(--s-3);
  font-family: var(--font-mono); font-size: .62rem; color: hsl(var(--h) 85% 74%);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
}
.aixSvc__badge {
  display: grid; place-items: center; width: 2.2rem; height: 2.2rem; flex: none;
  border-radius: 8px; border: 1px solid hsl(var(--h) 85% 62% / .4);
  background: linear-gradient(150deg, hsl(var(--h) 85% 58% / .26), transparent);
  color: hsl(var(--h) 95% 76%);
}
.aixSvc__copy h3 {
  margin: 0 0 var(--s-4); font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.2rem);
  letter-spacing: -.028em; color: #fff; text-wrap: balance;
}
.aixSvc__items { list-style: none; margin: 0 0 var(--s-5); padding: 0; display: grid; gap: var(--s-3); }
.aixSvc__items li {
  position: relative; padding-left: 1.7rem;
  color: var(--text-mid); font-size: var(--fs-body); line-height: var(--lh-body);
}
.aixSvc__items li::before {
  content: ''; position: absolute; left: 0; top: .55em;
  width: .8rem; height: 1px; background: hsl(var(--h) 90% 65%);
  box-shadow: 0 0 8px hsl(var(--h) 90% 60% / .9);
}
.aixSvc__helps {
  margin: 0 0 var(--s-4); padding-left: var(--s-4);
  border-left: 2px solid hsl(var(--h) 85% 60% / .5);
  color: var(--text-hi); font-size: var(--fs-body-lg); line-height: var(--lh-snug);
}
.aixSvc__proof {
  display: flex; align-items: center; gap: .7ch; margin: 0 0 var(--s-5);
  font-size: var(--fs-sm); color: var(--text-lo);
}
.aixSvc__proof span { color: hsl(150 75% 55%); font-size: .6rem; }
.aixSvc__cta {
  display: inline-flex; align-items: center; gap: .7ch;
  padding: .7rem 1.3rem; border-radius: 999px;
  border: 1px solid hsl(var(--h) 85% 62% / .45);
  background: hsl(var(--h) 85% 55% / .1);
  color: hsl(var(--h) 92% 78%); font-size: var(--fs-sm); text-decoration: none;
  transition: background .3s, border-color .3s, transform .3s;
}
.aixSvc__cta span { transition: transform .35s cubic-bezier(.22,1,.36,1); }
.aixSvc__cta:hover { background: hsl(var(--h) 85% 55% / .2); border-color: hsl(var(--h) 90% 68% / .85); transform: translateY(-2px); }
.aixSvc__cta:hover span { transform: translateX(4px); }

/* ── THE PROCESS ────────────────────────────────────────────────────────── */
.aixFlow {
  list-style: none; margin: 0; padding: 0; position: relative;
  display: grid; gap: var(--s-5); grid-template-columns: repeat(4, minmax(0, 1fr));
}
.aixFlow::before {
  content: ''; position: absolute; left: 0; right: 0; top: 1.55rem; height: 1px;
  background: linear-gradient(90deg, hsl(var(--aix-h1) 90% 60%), hsl(var(--aix-h2) 85% 65%), hsl(var(--aix-h3) 85% 65%), transparent);
  transform: scaleX(var(--p, 0)); transform-origin: 0 50%;
  transition: transform 1.5s cubic-bezier(.22,1,.36,1);
}
.aixFlow__step { animation: aixRise .6s cubic-bezier(.22,1,.36,1) both; animation-delay: calc(var(--i) * 90ms); }
.aixFlow__n {
  position: relative; z-index: 1;
  display: grid; place-items: center; width: 3.1rem; height: 3.1rem;
  border-radius: 50%; border: 1px solid hsl(var(--aix-h1) 90% 60% / .45);
  background: rgb(8 10 18 / .95);
  box-shadow: 0 0 0 6px hsl(var(--aix-h1) 90% 55% / .07);
  font-family: var(--font-mono); font-size: .8rem; color: hsl(var(--aix-h1) 92% 72%);
}
.aixFlow__step h3 { margin: var(--s-4) 0 var(--s-2); font-size: var(--fs-h3); color: #fff; }
.aixFlow__step p { margin: 0; color: var(--text-mid); font-size: var(--fs-sm); line-height: var(--lh-body); }

/* ── CLOSING ────────────────────────────────────────────────────────────── */
/* THE SEAM WAS PAID FOR TWICE. The flow section above ends with 80px of its
   own padding-bottom, and this section opened with a matching 96px on top —
   176px of empty ground between the last line of copy and the headline, which
   read as a gap rather than as breathing room.

   Only the TOP is trimmed. The bottom still needs the full measure: nothing
   follows it but the footer, and a closing call to action pressed against a
   footer looks like it ran out of page. Same lesson as --section-y, which is
   applied to both sides of every .section and therefore doubles at every
   seam — fix it at the side that is doubling, not by halving both. */
.aixClose {
  position: relative; z-index: 1; text-align: center;
  padding-top: clamp(var(--s-4), 2.5vw, var(--s-8));
  padding-bottom: clamp(var(--s-12), 7vw, var(--s-24));
}
.aixClose__in { max-width: 46rem; margin-inline: auto; }
.aixClose h2 { margin: 0 0 var(--s-4); font-size: clamp(1.9rem, 1.2rem + 2.8vw, 3rem); letter-spacing: -.03em; color: #fff; }
.aixClose p { margin: 0 auto; max-width: 46ch; color: var(--text-mid); }
.aixClose .aixHero__cta { justify-content: center; margin-top: var(--s-7); }
.aixClose__note { margin-top: var(--s-6); font-size: var(--fs-sm); color: var(--text-lo); }

/* ══════════════════════════════════════════════════════════════════════════
   THE BRIEFING
   ══════════════════════════════════════════════════════════════════════════
   A visual-novel cutscene. The veil is deliberately NOT opaque: Yuna spends
   the whole sequence pointing at panels, so the panels have to stay visible.
   ========================================================================== */
.aixBrief { position: fixed; inset: 0; z-index: 900; }
.aixBrief[hidden] { display: none; }
/* The veil is TRANSPARENT whenever the reticle is up, and only paints when
   there is no target to point at.

   The reticle dims the page itself, with a box-shadow spread wide enough to
   cover any viewport; the lit rectangle is the shadow's hole. Painting a veil
   underneath as well meant the hole revealed the veil instead of the page, so
   the spotlight was a 6% brightness difference nobody could see. It exists
   here purely to catch clicks. */
.aixBrief__veil {
  position: absolute; inset: 0;
  background: transparent;
  animation: aixFade .4s ease both;
}
.aixBrief.is-blind .aixBrief__veil { background: rgb(3 4 9 / .8); backdrop-filter: blur(3px); }
@keyframes aixFade { from { opacity: 0; } }

/* The spotlight. One element moved between targets, with the hole punched by
   an enormous spread box-shadow — the same trick the notes tour uses, but the
   ring here is drawn as corner brackets and a sweep so it reads as targeting
   reticle rather than as a highlighter. */
.aixBrief__ring {
  position: absolute; border-radius: 16px; pointer-events: none;
  box-shadow: 0 0 0 100vmax rgb(3 4 9 / .8), 0 0 60px hsl(var(--aix-h1) 95% 60% / .35) inset;
  border: 1px solid hsl(var(--aix-h1) 95% 65% / .7);
  /* NO transition by default. The reticle is re-measured every frame while
     the page scrolls, and an eased position means it is permanently chasing
     the card instead of sitting on it — during a beat change it lagged far
     enough behind to be drawn around empty space. The ease is added back
     for the half-second it takes to move BETWEEN targets, which is the only
     time it is wanted. */
  transition: none;
}
.aixBrief__ring.is-moving {
  transition: left .5s cubic-bezier(.22,1,.36,1), top .5s cubic-bezier(.22,1,.36,1),
              width .5s cubic-bezier(.22,1,.36,1), height .5s cubic-bezier(.22,1,.36,1);
}
.aixBrief__ring[hidden] { display: none; }
.aixBrief__corner {
  position: absolute; width: 15px; height: 15px;
  border: 2px solid hsl(var(--aix-h1) 95% 68%);
}
.aixBrief__corner--tl { left: -2px; top: -2px; border-right: 0; border-bottom: 0; border-radius: 5px 0 0 0; }
.aixBrief__corner--tr { right: -2px; top: -2px; border-left: 0; border-bottom: 0; border-radius: 0 5px 0 0; }
.aixBrief__corner--bl { left: -2px; bottom: -2px; border-right: 0; border-top: 0; border-radius: 0 0 0 5px; }
.aixBrief__corner--br { right: -2px; bottom: -2px; border-left: 0; border-top: 0; border-radius: 0 0 5px 0; }
.aixBrief__sweep {
  position: absolute; inset: 0; overflow: hidden; border-radius: inherit;
}
.aixBrief__sweep::after {
  content: ''; position: absolute; left: 0; right: 0; height: 34%;
  background: linear-gradient(hsl(var(--aix-h1) 95% 60% / 0), hsl(var(--aix-h1) 95% 60% / .22), hsl(var(--aix-h1) 95% 60% / 0));
  animation: aixSweep 2.6s ease-in-out infinite;
}
@keyframes aixSweep { 0% { top: -34%; } 100% { top: 100%; } }

/* The stage: character on the left, dialogue box on the right. */
.aixBrief__stage {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: end; gap: 0;
  padding: 0 clamp(var(--s-4), 3vw, var(--s-10)) clamp(var(--s-4), 2.5vw, var(--s-8));
  max-width: 78rem; margin-inline: auto;
  pointer-events: none;
}
.aixBrief__stage > * { pointer-events: auto; }

.aixBrief__char {
  position: relative; margin: 0 -2.5rem 0 0; width: clamp(9rem, 15vw, 15rem);
  animation: aixEnter .55s cubic-bezier(.22,1,.36,1) both;
  filter: drop-shadow(0 18px 40px rgb(0 0 0 / .7));
}
@keyframes aixEnter { from { opacity: 0; transform: translate(-24px, 18px); } }
/* A comms plate, not a cut-out.
   The source portrait is a studio shot on a flat teal backdrop, so masking it
   to a silhouette is not possible without a matte we do not have — and a
   hard-edged rectangle floating in space just reads as a stray photograph.
   Framing it as a lit panel with a bracket and a live dot turns the same
   asset into an incoming video call, which is both honest about what it is
   and exactly the register the rest of the page is in. */
.aixBrief__char img {
  display: block; width: 100%; height: auto;
  border-radius: 14px 14px 0 0;
  mask-image: linear-gradient(to bottom, black 84%, transparent);
}
.aixBrief__char::before {
  content: ''; position: absolute; inset: 0;
  border: 1px solid hsl(var(--aix-h1) 90% 62% / .45);
  border-bottom: 0; border-radius: 14px 14px 0 0;
  mask-image: linear-gradient(to bottom, black 62%, transparent);
  pointer-events: none;
}
/* "LIVE", because she is an avatar and the page has just said so. */
.aixBrief__char::after {
  content: 'LIVE';
  position: absolute; left: .55rem; top: .55rem;
  display: flex; align-items: center;
  padding: .16rem .5rem .16rem 1.1rem;
  border-radius: 999px; background: rgb(4 6 12 / .8);
  border: 1px solid hsl(150 80% 55% / .5);
  font-family: var(--font-mono); font-size: .5rem; letter-spacing: .16em;
  color: hsl(150 80% 70%);
  background-image: radial-gradient(circle at .55rem 50%, hsl(150 85% 58%) 2.2px, transparent 2.4px);
}
.aixBrief__halo {
  position: absolute; inset: 8% -18% -6%;
  background: radial-gradient(50% 55% at 50% 55%, hsl(var(--aix-h1) 90% 55% / .38), transparent 70%);
  filter: blur(26px); z-index: -1;
}
.aixBrief__scanline {
  position: absolute; inset: 0; border-radius: 14px 14px 0 0; overflow: hidden;
  background: repeating-linear-gradient(to bottom, hsl(var(--aix-h1) 95% 70% / .07) 0 1px, transparent 1px 4px);
  mask-image: linear-gradient(to bottom, black 78%, transparent);
}
/* She "breathes" while speaking, and settles when the line is done. It is two
   pixels of movement and it is the difference between a character and a
   cut-out. */
.aixBrief.is-typing .aixBrief__char { animation: aixEnter .55s cubic-bezier(.22,1,.36,1) both, aixBreathe 2.2s ease-in-out .55s infinite; }
@keyframes aixBreathe { 50% { transform: translateY(-3px) scale(1.006); } }

.aixBrief__box {
  position: relative; align-self: end;
  border-radius: 16px; border: 1px solid hsl(var(--aix-h1) 90% 60% / .35);
  background: linear-gradient(150deg, rgb(10 16 28 / .96), rgb(6 8 16 / .96));
  backdrop-filter: blur(16px);
  box-shadow: 0 -6px 60px -18px hsl(var(--aix-h1) 95% 50% / .45), 0 30px 70px -30px #000;
  padding: var(--s-5) clamp(var(--s-5), 2.4vw, var(--s-7));
  animation: aixBox .45s cubic-bezier(.22,1,.36,1) both;
}
@keyframes aixBox { from { opacity: 0; transform: translateY(20px); } }
.aixBrief__box::before {
  content: ''; position: absolute; inset: 0 0 auto; height: 1px;
  background: linear-gradient(90deg, transparent, hsl(var(--aix-h1) 95% 70%), transparent);
}

.aixBrief__meta { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-3); }
.aixBrief__avatar {
  display: block; width: 2.4rem; height: 2.4rem; flex: none;
  border-radius: 50%; overflow: hidden; border: 1px solid hsl(var(--aix-h1) 90% 62% / .6);
  box-shadow: 0 0 0 3px hsl(var(--aix-h1) 90% 55% / .12);
}
.aixBrief__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aixBrief__id { display: grid; gap: 1px; min-width: 0; }
.aixBrief__id b { font-size: var(--fs-sm); color: #fff; letter-spacing: .02em; }
.aixBrief__id i {
  font-style: normal; font-family: var(--font-mono); font-size: .58rem;
  color: hsl(var(--aix-h1) 80% 70%); letter-spacing: .1em; text-transform: uppercase;
}
.aixBrief__count {
  margin-left: auto; font-family: var(--font-mono); font-size: .68rem; color: var(--text-lo);
}
.aixBrief__count b { color: hsl(var(--aix-h1) 92% 72%); }

.aixBrief__text {
  margin: 0 0 var(--s-4); min-height: 3.9em;
  color: var(--text-hi); font-size: var(--fs-body-lg); line-height: 1.55;
}
/* The caret that trails the typewriter. */
.aixBrief__text::after {
  content: '▍'; color: hsl(var(--aix-h1) 95% 65%); opacity: 0;
}
.aixBrief.is-typing .aixBrief__text::after { opacity: 1; animation: aixBlink .8s steps(1) infinite; }
@keyframes aixBlink { 50% { opacity: 0; } }

.aixBrief__bar { height: 2px; border-radius: 2px; background: rgb(255 255 255 / .1); overflow: hidden; }
.aixBrief__bar span {
  display: block; height: 100%; width: 0;
  background: linear-gradient(90deg, hsl(var(--aix-h1) 95% 58%), hsl(var(--aix-h2) 90% 68%));
  transition: width .35s ease;
}
.aixBrief__acts { display: flex; align-items: center; gap: var(--s-4); margin-top: var(--s-4); }
.aixBrief__nav { margin-left: auto; display: flex; align-items: center; gap: var(--s-3); }
.aixBrief__skip, .aixBrief__back {
  border: 0; background: none; font: inherit; font-size: var(--fs-sm);
  color: var(--text-lo); cursor: pointer; padding: .4rem 0;
  transition: color .25s;
}
.aixBrief__skip:hover, .aixBrief__back:hover { color: var(--text-hi); }
.aixBrief__caret {
  width: 0; height: 0;
  border-left: 5px solid currentColor; border-top: 4px solid transparent; border-bottom: 4px solid transparent;
}

/* The replay button. Yuna's face, so it is obvious what it replays. */
.aixReplay {
  position: fixed; right: clamp(.8rem, 2vw, 1.4rem); bottom: clamp(.8rem, 2vw, 1.4rem);
  z-index: 800;
  display: inline-flex; align-items: center; gap: .7ch;
  padding: .4rem 1rem .4rem .4rem; border-radius: 999px;
  border: 1px solid hsl(var(--aix-h1) 90% 60% / .4);
  background: rgb(8 12 22 / .9); backdrop-filter: blur(10px);
  color: var(--text-hi); font: inherit; font-size: var(--fs-sm); cursor: pointer;
  box-shadow: 0 12px 34px -14px #000;
  transition: transform .3s cubic-bezier(.22,1,.36,1), border-color .3s;
}
.aixReplay:hover { transform: translateY(-2px); border-color: hsl(var(--aix-h1) 95% 65% / .9); }
.aixReplay__face { width: 2rem; height: 2rem; border-radius: 50%; overflow: hidden; flex: none; }
.aixReplay__face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aixBrief:not([hidden]) ~ .aixReplay { opacity: 0; pointer-events: none; }
/* The install card (990) and push prompt (991) sit above the briefing's 900
   and covered the dialogue box and its buttons on phones. They step aside for
   the length of the briefing and come back when it ends. */
.aix-briefing .ycInst, .aix-briefing .ycPush, .aix-briefing .ycInst__toast {
  visibility: hidden; opacity: 0; pointer-events: none;
}

/* ══════════════════════════════════════════════════════════════════════════
   RESPONSIVE
   ══════════════════════════════════════════════════════════════════════════
   The console is the thing that has to be re-thought at each width, because
   "all seven visible at once" stops being possible long before the text stops
   fitting. It steps 4+3 → 3+3+1 → 2 → 1, and the CURVE IS DROPPED below the
   point where panels are wide enough for the yaw to distort the text.
   ========================================================================== */
/* Below 64rem the two rows DISSOLVE into one flat grid via display:contents.
   They have to: --o is each panel's offset from ITS OWN ROW's centre, computed
   in PHP, so the moment the panels reflow into different positions the curve
   is describing a layout that no longer exists — panel 5 would yaw as though
   it were still the left edge of a three-wide row. The curve and the
   perspective both go with it.

   Seven items in two columns leaves a seventh on its own, so it spans the
   pair rather than sitting next to a hole. */
/* Yuna turns from a column into a bar. Below this width a portrait beside the
   copy would take a third of the screen and shove the console off the fold,
   but dropping her entirely loses the one thing that says the walkthrough
   exists — so she becomes a full-width strip under the buttons instead: face,
   the same sentence, same play button. */
@media (max-width: 72rem) {
  .aixHero__top { grid-template-columns: 1fr; gap: var(--s-6); }
  .aixHero__copy { max-width: none; }
  .aixGuide { width: 100%; }
  .aixGuide__plate {
    display: flex; align-items: center; gap: var(--s-4);
    aspect-ratio: auto; padding: var(--s-3); border-radius: 999px;
  }
  .aixGuide__plate img {
    width: 3.4rem; height: 3.4rem; flex: none; border-radius: 50%;
    object-position: 50% 8%;
  }
  .aixGuide__scan, .aixGuide__live, .aixGuide__halo { display: none; }
  .aixGuide__play {
    position: static; translate: none; order: 3; margin-left: auto;
    width: 2.4rem; height: 2.4rem; animation: none;
  }
  .aixGuide__play::after { border-left-width: 9px; border-top-width: 6px; border-bottom-width: 6px; }
  .aixGuide__foot { position: static; padding: 0; background: none; }
  /* The bubble becomes the label, in flow, because there is nothing left to
     point at once the portrait is a 3rem circle. */
  .aixGuide__say {
    position: static; max-width: none; width: auto; order: 2;
    padding: 0; border: 0; background: none; box-shadow: none; animation: none;
    font-size: var(--fs-sm); color: var(--text-mid);
  }
  .aixGuide__say::after { display: none; }
  .aixGuide:hover .aixGuide__plate { transform: none; }
  .aixGuide:hover .aixGuide__plate img { transform: none; }
}
@media (max-width: 34rem) {
  .aixGuide__say { display: none; }
  .aixGuide__foot i { font-size: .48rem; }
}

@media (max-width: 64rem) {
  .aixConsole { grid-template-columns: repeat(2, minmax(0, 1fr)); perspective: none; }
  .aixConsole__row { display: contents; }
  .aixPanel, .aixPanel:hover { transform: none; }
  /* The SECOND row's last panel — not `.aixPanel:last-child`, which also
     matches panel 04. display:contents removes the row's box but :last-child
     still resolves against the real DOM parent, so each row still has its own
     last child and panel 04 was spanning the grid too. */
  .aixConsole__row--2 .aixPanel:last-child { grid-column: span 2; }
  .aixPanel:hover { box-shadow: 0 0 0 1px hsl(var(--h) 90% 62% / .35), 0 20px 44px -20px #000; }
  .aixSvc__in { grid-template-columns: 1fr; }
  .aixSvc:nth-child(even) .aixSvc__media { order: 0; }
  .aixSvc__frame,
  .aixSvc:nth-child(even) .aixSvc__frame { transform: translate3d(0, var(--py, 0px), 0); }
  .aixFlow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .aixFlow::before { display: none; }
}
/* Tablet keeps THREE across so the array is 3+3+1 rather than 2+2+2+1 — one
   row fewer, which on a 900-tall tablet is the difference between seeing five
   services on open and four.  The seventh spans the row rather than sitting
   beside two empty cells.

   This block MUST come after the 64rem one: equal specificity resolves by
   source order, so declared first it would simply be overwritten by the
   two-column rule at every width it covers. */
@media (min-width: 48.0625rem) and (max-width: 64rem) {
  .aixConsole { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .aixConsole__row--2 .aixPanel:last-child { grid-column: span 3; }
}

@media (max-width: 40rem) {
  .aixConsole { grid-template-columns: 1fr; }
  .aixConsole__row--2 .aixPanel:last-child { grid-column: auto; }
  .aixPanel { padding: var(--s-4); }
  .aixFlow { grid-template-columns: 1fr; }
  .aixHero__cta { display: grid; }
  .aixHero__cta .aixBtn { width: 100%; }
  .aixConsole__rail { display: none; }
}

/* The briefing on a phone. The character cannot sit beside the box — there is
   not room for both — so she shrinks to a head that overlaps the box's top
   corner, which is how handheld visual novels have always solved it. */
/* On a phone the floating portrait is dropped: at this width it always lands
   on top of the card being pointed at. Her face stays in the box's name row,
   enlarged slightly, so the scene still has a speaker. */
@media (max-width: 52rem) {
  .aixBrief__stage {
    grid-template-columns: 1fr;
    padding: 0 var(--s-3) max(var(--s-3), env(safe-area-inset-bottom));
  }
  .aixBrief__char { display: none; }
  .aixBrief__avatar { width: 2.75rem; height: 2.75rem; }
  .aixBrief__text { font-size: var(--fs-body); line-height: 1.5; min-height: 4.6em; margin-bottom: var(--s-3); }
  .aixBrief__box { padding: var(--s-4); max-height: 46vh; overflow-y: auto; overscroll-behavior: contain; }
  .aixBrief__acts { gap: var(--s-3); margin-top: var(--s-3); }
  .aixBrief__skip, .aixBrief__back { min-height: 44px; padding: 0 .25rem; }
  .aixBrief__nav .aixBtn { min-height: 44px; }
  .aixReplay__t { display: none; }
  .aixReplay {
    padding: .4rem;
    right: max(.8rem, env(safe-area-inset-right));
    bottom: max(.8rem, env(safe-area-inset-bottom));
  }
}
@media (max-width: 22rem) {
  .aixBrief__skip { font-size: var(--fs-xs, .75rem); }
}
/* Very short viewports — a landscape phone — cannot afford the portrait at
   all; it would cover the thing being pointed at. */
@media (max-height: 34rem) {
  .aixBrief__char { display: none; }
  .aixBrief__text { min-height: 0; }
}

/* ── REDUCED MOTION ─────────────────────────────────────────────────────────
   Everything above is decoration. With motion off the page is a heading, seven
   links, seven explanations and a call to action — which is the whole page. */
@media (prefers-reduced-motion: reduce) {
  .aixSky__aurora, .aixSky__floor, .aixSky__stars { transform: none; animation: none; }
  .aixPanel, .aixFlow__step, .aixBrief__char, .aixBrief__box, .aixBrief__veil { animation: none; }
  .aixPanel { transform: none; }
  .aixPanel:hover { transform: none; }
  .aixConsole__row, .aixPanel, .aixSvc__frame, .aixSvc__frame img,
  .aixBrief__ring, .aixBtn, .aixSvc__cta { transition: none; }
  .aixSvc__frame, .aixSvc:nth-child(even) .aixSvc__frame { transform: none; }
  .aixHero__pulse, .aixConsole__foot span, .aixBrief__sweep::after,
  .aixBrief__text::after, .aixGuide__play, .aixGuide__say { animation: none; }
  .aixGuide__plate, .aixGuide__plate img, .aixSvc__play { transition: none; }
  .aixGuide:hover .aixGuide__plate, .aixGuide:hover .aixGuide__plate img { transform: none; }
  .aixFlow::before { transform: scaleX(1); transition: none; }
}

/* THE WHOLE PANEL IS THE LINK. The rows are 3D-transformed planes (the
   console tilt), and in the corners of each tilted panel the ROW won the
   hit-test — so only the middle of a card opened its page and a click near
   an edge did nothing. The row lets clicks through; the panels take them. */
/* The console itself is also a transformed plane, and the tilted panels'
   upper halves sit "behind" it in 3D — it took those clicks too. Everything
   in the console lets clicks through except the panels themselves. The tilt
   still follows the pointer: pointermove bubbles up from the panels. */
.aixConsole, .aixConsole__row { pointer-events: none; }
.aixConsole .aixPanel { pointer-events: auto; cursor: pointer; }
