/* ═══════════════════════════════════════════════════════════════
   XODE — AMBIENCE AND TOASTS

   Two small things that run on every page.

   AMBIENCE takes the dotted grid and slow orbiting dots from the About
   page's story panel and spreads them thinly across the site. The
   emphasis is on thinly: a background people notice only when they
   stop looking at the content. Nothing here sits above text, nothing
   catches a click, and it all stops for anyone who has asked for less
   motion.

   TOASTS were an editor-only feature — there was no way to confirm
   anything on the rest of the site. The editor keeps its own stack;
   this is for every other page.
   ═══════════════════════════════════════════════════════════════ */

/* ── The ambient layer ────────────────────────────────────────── */
.xam-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;      /* never intercepts a click */
  overflow: hidden;
  /* Behind everything. Sections carry their own background, so this
     shows through the gaps rather than washing over the content. */
}

/* The dotted grid, at about a third of the About page's strength —
   full-bleed it would be busy rather than atmospheric. */
.xam-dots {
  position: absolute;
  inset: -40px;
  background-image: radial-gradient(circle, rgba(124, 92, 255, .22) 1px, transparent 1px);
  background-size: 30px 30px;
  opacity: .10;
  animation: xam-drift 90s linear infinite;
}

/* Two slow motes. Not orbits — an orbit needs a centre to orbit, and
   a page has none. These drift instead. */
.xam-mote {
  position: absolute;
  border-radius: 50%;
  filter: blur(1px);
  opacity: .5;
}
.xam-mote.v {
  width: 6px; height: 6px;
  background: var(--violet, #7C5CFF);
  box-shadow: 0 0 12px var(--violet, #7C5CFF);
  animation: xam-float-a 34s ease-in-out infinite;
}
.xam-mote.a {
  width: 5px; height: 5px;
  background: var(--amber, #FFB454);
  box-shadow: 0 0 10px var(--amber, #FFB454);
  animation: xam-float-b 46s ease-in-out infinite;
}

/* One soft wash that breathes, so the page is not perfectly static. */
.xam-glow {
  position: absolute;
  width: 60vw; height: 60vw;
  max-width: 720px; max-height: 720px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(124, 92, 255, .07), transparent 68%);
  animation: xam-breathe 26s ease-in-out infinite;
}

@keyframes xam-drift {
  to { background-position: 300px 300px; }
}
@keyframes xam-float-a {
  0%, 100% { transform: translate(8vw, 18vh); }
  33%      { transform: translate(72vw, 34vh); }
  66%      { transform: translate(40vw, 74vh); }
}
@keyframes xam-float-b {
  0%, 100% { transform: translate(84vw, 72vh); }
  40%      { transform: translate(22vw, 58vh); }
  75%      { transform: translate(64vw, 14vh); }
}
@keyframes xam-breathe {
  0%, 100% { transform: translate(-12%, -8%) scale(1);    opacity: .8; }
  50%      { transform: translate(6%, 10%)   scale(1.14); opacity: 1; }
}

/* Content must sit above the ambient layer. Sections that set their
   own background already do; these are the ones that do not.

   NOT nav. ".xam-on nav" is more specific than ".nav", so it won
   the cascade and replaced the navigation bar's own
   position: fixed / z-index: 200 with relative / 1. That stopped the
   bar being fixed at all, and dropped it to the same layer as every
   section — so page content painted over the hamburger menu. The nav
   is already above the ambient layer at 200; it never needed this. */
.xam-on main,
.xam-on footer,
.xam-on section,
.xam-on .container { position: relative; z-index: 1; }

/* Phones: keep the wash, drop the moving parts. They cost battery and
   are barely visible at this size. */
@media (max-width: 640px) {
  /* The motes stay. Transform and opacity are composited, so four
     drifting dots cost close to nothing next to the scrolling. */
  .xam-mote {
    display: block;
    /* The blur is the expensive part — a genuine repaint every frame.
       The glow carries the same impression at this size. */
    filter: none;
  }
  .xam-mote.v { width: 5px; height: 5px; box-shadow: 0 0 10px var(--violet, #7C5CFF); }
  .xam-mote.a { width: 4px; height: 4px; box-shadow: 0 0 8px var(--amber, #FFB454); }

  /* The grid keeps its drift too, but faintly — it repaints a
     full-screen background image, so it is the one thing here worth
     slowing down rather than leaving at desktop speed. */
  .xam-dots {
    opacity: .07;
    animation: xam-drift 150s linear infinite;
  }
  .xam-glow { animation: xam-breathe 34s ease-in-out infinite; opacity: .8; }
}

/* Asked for less motion — hold everything still. The colour stays. */
@media (prefers-reduced-motion: reduce) {
  .xam-dots, .xam-mote, .xam-glow { animation: none !important; }
}

/* ═══════════════════════════════════════════════════════════════
   TOASTS
   ═══════════════════════════════════════════════════════════════ */
.xt-stack {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%);
  z-index: 99997;
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
  pointer-events: none;
  width: max-content;
  max-width: calc(100vw - 28px);
}

.xt {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 12px 18px 12px 14px;
  border-radius: 100px;
  background: var(--bg-2, #1A1C22);
  border: 1px solid var(--border-2, #383B46);
  box-shadow: 0 12px 34px rgba(var(--shadow-rgb), .5);
  font-family: var(--font-body, Inter, sans-serif);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text-0, #F5F5F7);
  line-height: 1.45;
  pointer-events: auto;
  opacity: 0;
  transform: translateY(14px) scale(.96);
  transition: opacity .26s cubic-bezier(.2,.8,.3,1), transform .26s cubic-bezier(.2,.8,.3,1);
}
.xt.in { opacity: 1; transform: none; }
.xt.out { opacity: 0; transform: translateY(-8px) scale(.97); }

.xt-icon {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(124, 92, 255, .16);
}
.xt-icon svg { width: 14px; height: 14px; stroke: var(--violet, #7C5CFF); }

.xt.ok    .xt-icon { background: rgba(74, 222, 128, .16); }
.xt.ok    .xt-icon svg { stroke: var(--success, #4ADE80); }
.xt.error .xt-icon { background: rgba(255, 107, 107, .16); }
.xt.error .xt-icon svg { stroke: var(--danger, #FF6B6B); }

/* The celebratory one — sign-in and publish. A gradient edge, so it
   reads as an occasion rather than a status line. */
.xt.party {
  border-color: transparent;
  background:
    linear-gradient(var(--bg-2, #1A1C22), var(--bg-2, #1A1C22)) padding-box,
    var(--grad, linear-gradient(90deg, #7C5CFF, #FFB454)) border-box;
  border: 1px solid transparent;
}
.xt.party .xt-icon {
  background: var(--grad, linear-gradient(90deg, #7C5CFF, #FFB454));
}
.xt.party .xt-icon svg { stroke: var(--bg-0); }

.xt-text { min-width: 0; }
.xt-text b { display: block; font-weight: 700; }
.xt-text span {
  display: block;
  font-size: 11.5px;
  color: var(--text-2, #8B8D98);
  font-weight: 400;
  margin-top: 1px;
}

@media (max-width: 640px) {
  .xt-stack {
    left: 12px; right: 12px;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    transform: none;
    width: auto;
    max-width: none;
  }
  .xt { width: 100%; border-radius: 14px; font-size: 13px; }
}

@media (prefers-reduced-motion: reduce) {
  .xt { transition: opacity .2s; transform: none; }
  .xt.in, .xt.out { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════
   MORE AMBIENCE

   A second, richer layer. Still background: everything here is slow
   (20s and up), low-contrast, and behind the content. Nothing pulses,
   nothing blinks — motion fast enough to notice is motion that
   competes with what you came to read.

   All of it stops on phones and under prefers-reduced-motion.
   ═══════════════════════════════════════════════════════════════ */

/* Two more motes on different paths, so the drift never looks like
   one repeating loop. */
.xam-mote.v2 {
  width: 4px; height: 4px;
  background: #4A7FE8;
  box-shadow: 0 0 10px #4A7FE8;
  opacity: .4;
  animation: xam-float-c 58s ease-in-out infinite;
}
.xam-mote.t {
  width: 5px; height: 5px;
  background: #2BB5A0;
  box-shadow: 0 0 9px #2BB5A0;
  opacity: .35;
  animation: xam-float-d 68s ease-in-out infinite;
}

@keyframes xam-float-c {
  0%, 100% { transform: translate(58vw, 8vh); }
  30%      { transform: translate(12vw, 44vh); }
  62%      { transform: translate(88vw, 62vh); }
}
@keyframes xam-float-d {
  0%, 100% { transform: translate(30vw, 88vh); }
  45%      { transform: translate(76vw, 26vh); }
  80%      { transform: translate(6vw, 66vh); }
}

/* A second wash in the warm accent, out of phase with the first, so
   the page's tint shifts slightly over a minute rather than sitting
   on one colour. */
.xam-glow2 {
  position: absolute;
  right: -10%; bottom: -14%;
  width: 52vw; height: 52vw;
  max-width: 620px; max-height: 620px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 180, 84, .05), transparent 66%);
  animation: xam-breathe2 38s ease-in-out infinite;
}
@keyframes xam-breathe2 {
  0%, 100% { transform: translate(4%, 6%)  scale(1.08); opacity: .7; }
  50%      { transform: translate(-8%, -4%) scale(1);    opacity: 1; }
}

/* ── Section accents ────────────────────────────────────────────
   The gradient rules between sections gain a slow shimmer, so the
   page has a pulse without anything new being added to it. */
.xam-on hr.grad-line {
  background-size: 220% 100%;
  animation: xam-shimmer 14s ease-in-out infinite;
}
@keyframes xam-shimmer {
  0%, 100% { background-position: 0% 50%; }
  50%      { background-position: 100% 50%; }
}

/* Cards lift a little more willingly. Transform only — cheap to
   animate, and it does not force a layout pass. */
.xam-on .pg-card,
.xam-on .why-card,
.xam-on .team-card,
.xam-on .topic-card,
.xam-on .prof-card,
.xam-on .testi-card {
  transition: transform .28s cubic-bezier(.2,.8,.3,1),
              border-color .2s,
              box-shadow .28s;
}

/* The gradient text on headings drifts across its own gradient. */
.xam-on .grad-text,
.xam-on .pg-title em {
  background-size: 220% 100%;
  animation: xam-shimmer 18s ease-in-out infinite;
}

@media (max-width: 640px) {
  /* All four drift on a phone as well. They are on different paths and
     different durations, so the field never looks like one loop. */
  .xam-mote.v2 { display: block; width: 4px; height: 4px; box-shadow: 0 0 8px #4A7FE8; filter: none; }
  .xam-mote.t  { display: block; width: 4px; height: 4px; box-shadow: 0 0 8px #2BB5A0; filter: none; }
  .xam-glow2 { animation: xam-breathe2 46s ease-in-out infinite; opacity: .7; }

  /* Text shimmer stays off. It animates background-position on a
     background-clipped gradient, which re-rasterises the glyphs every
     frame — the one effect here that genuinely stutters on a phone. */
  .xam-on hr.grad-line,
  .xam-on .grad-text,
  .xam-on .pg-title em { animation: none; background-size: 100% 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .xam-mote.v2, .xam-mote.t, .xam-glow2,
  .xam-on hr.grad-line, .xam-on .grad-text, .xam-on .pg-title em {
    animation: none !important;
  }
}

/* ═══════════════════════════════════════════════════════════════
   SIX MORE MOTES

   Four drifting dots across a whole page read as four drifting dots.
   Ten read as air.

   Every one differs in size, colour, opacity, path and duration, and
   the durations share no common factor — 41, 52, 63, 74, 87, 95 —
   so the field never falls into step and starts repeating as a group,
   which is the thing that makes an effect like this look cheap.

   The faint ones carry the depth. A dot at a fifth opacity moving
   slowly reads as far away; without those the others sit on one flat
   plane. That is why three of these six are barely there.

   Each is a small div animating transform and opacity, so the
   compositor does the work — no layout, no paint, no main thread.
   ═══════════════════════════════════════════════════════════════ */

.xam-mote.v3 {
  width: 3px; height: 3px;
  background: var(--violet, #7C5CFF);
  box-shadow: 0 0 8px var(--violet, #7C5CFF);
  opacity: .30;
  animation: xam-float-e 41s ease-in-out infinite;
}
.xam-mote.a2 {
  width: 6px; height: 6px;
  background: var(--amber, #FFB454);
  box-shadow: 0 0 12px var(--amber, #FFB454);
  opacity: .22;
  animation: xam-float-f 87s ease-in-out infinite;
}
.xam-mote.t2 {
  width: 2px; height: 2px;
  background: #2BB5A0;
  box-shadow: 0 0 7px #2BB5A0;
  opacity: .42;
  animation: xam-float-g 52s ease-in-out infinite;
}
.xam-mote.p {
  width: 4px; height: 4px;
  background: #FFB454;
  box-shadow: 0 0 10px #FFB454;
  opacity: .26;
  animation: xam-float-h 74s ease-in-out infinite;
}
.xam-mote.w {
  width: 2px; height: 2px;
  background: #FFFFFF;
  box-shadow: 0 0 6px rgba(var(--fg-rgb), .8);
  opacity: .20;
  animation: xam-float-i 63s ease-in-out infinite;
}
.xam-mote.g {
  width: 3px; height: 3px;
  background: #C9A86A;
  box-shadow: 0 0 9px #C9A86A;
  opacity: .24;
  animation: xam-float-j 95s ease-in-out infinite;
}

/* Four waypoints rather than three, so the paths curve differently
   from the originals instead of tracing the same triangle. */
@keyframes xam-float-e {
  0%, 100% { transform: translate(14vw, 62vh); }
  28%      { transform: translate(46vw, 16vh); }
  55%      { transform: translate(80vw, 40vh); }
  78%      { transform: translate(36vw, 82vh); }
}
@keyframes xam-float-f {
  0%, 100% { transform: translate(92vw, 34vh); }
  33%      { transform: translate(54vw, 74vh); }
  61%      { transform: translate(18vw, 24vh); }
  84%      { transform: translate(70vw, 8vh); }
}
@keyframes xam-float-g {
  0%, 100% { transform: translate(4vw, 12vh); }
  26%      { transform: translate(38vw, 52vh); }
  58%      { transform: translate(66vw, 90vh); }
  81%      { transform: translate(24vw, 36vh); }
}
@keyframes xam-float-h {
  0%, 100% { transform: translate(68vw, 94vh); }
  30%      { transform: translate(90vw, 48vh); }
  57%      { transform: translate(44vw, 6vh); }
  79%      { transform: translate(8vw, 56vh); }
}
@keyframes xam-float-i {
  0%, 100% { transform: translate(50vw, 4vh); }
  24%      { transform: translate(84vw, 30vh); }
  52%      { transform: translate(60vw, 70vh); }
  76%      { transform: translate(16vw, 44vh); }
}
@keyframes xam-float-j {
  0%, 100% { transform: translate(26vw, 30vh); }
  31%      { transform: translate(2vw, 76vh); }
  59%      { transform: translate(52vw, 58vh); }
  83%      { transform: translate(96vw, 18vh); }
}

@media (max-width: 640px) {
  /* All ten drift on a phone too. The blur is the only expensive part
     and it is already off there; ten composited 2–6px dots cost less
     than the scrolling does. */
  .xam-mote.v3, .xam-mote.a2, .xam-mote.t2,
  .xam-mote.p,  .xam-mote.w,  .xam-mote.g { display: block; filter: none; }

  /* Slightly smaller, since a phone screen is closer to the eye. */
  .xam-mote.a2 { width: 5px; height: 5px; }
  .xam-mote.p  { width: 3px; height: 3px; }
}

@media (prefers-reduced-motion: reduce) {
  .xam-mote.v3, .xam-mote.a2, .xam-mote.t2,
  .xam-mote.p,  .xam-mote.w,  .xam-mote.g { animation: none !important; }
}
