/* ============================================================================
   Orb Workday — the field
   ---------------------------------------------------------------------------
   The page's ground. It takes its cue from ampcode.com: a very dark (or in the
   light theme, very pale) plane ruled by faint dotted vertical columns, with
   just enough depth that the middle of the page sits forward of the edges.
   It is decoration in the strict sense — aria-hidden, pointer-events: none,
   never scrolls, never overflows, and never costs a repaint on scroll.

   Expected markup (the shell emits it; nothing here requires the drift layer
   to be present):

     <div class="field" aria-hidden="true">
       <div class="field__drift"></div>
     </div>

   ---------------------------------------------------------------------------
   CUSTOM PROPERTY API
   ---------------------------------------------------------------------------
   --field-col      <length>   96px    Horizontal pitch of the column rules.
   --field-row      <length>   7px     Vertical pitch of the dots in a rule.
   --field-dot      <length>   0.5px   Dot radius.
   --field-depth    <number>   1       Strength of the vignette. 0 disables it.
   --field-drift-op <number>   1       Particle plane opacity. 0 hides them.
   --field-ground   <color>    var(--bg)  The plane's own colour. Set to
                                          transparent if the shell paints the
                                          page background itself.

   Motion: the drift planes are gated on --motion-off from motion.css, so they
   are completely still under prefers-reduced-motion or [data-motion="reduced"]
   — not slowed, stopped.
   ========================================================================= */

.field {
  --field-col: 96px;
  --field-row: 7px;
  --field-dot: 0.5px;
  --field-depth: 1;
  --field-drift-op: 1;
  --field-ground: var(--bg);

  /* --- Local tokens (NOT in tokens.css — declared here, owned here) --------
     The two colours the atmosphere is made of. They are lighting, not
     interface: --field-warm is the light the page's own subject throws into
     the room, --field-cool is what the room returns at the edges. Keeping the
     two apart is what gives the ground a direction; a single neutral wash
     would only make it grey.
     Both are used at single-digit alpha over the page ground, so neither can
     move any text or state indicator's contrast ratio measurably. */
  --field-warm: #4e6f52;
  --field-cool: #26405f;

  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  /* THE ROOM. Three gradients composited once into the plane's own paint —
     no extra element, no extra layer, no repaint on scroll:
       1. a low horizon glow, warm, sitting under the middle of the page, as
          if the light on the page came from the objects on it;
       2. a cool wash falling from the top, so the page has a top and a bottom
          rather than being an even field;
       3. cool corners, which is what makes the middle of the plane read as
          nearer than its edges.
     This is the depth the orbs sit IN rather than on. */
  background-color: var(--field-ground);
  background-image:
    radial-gradient(ellipse 78% 44% at 50% 78%,
      color-mix(in oklab, var(--field-warm) 26%, transparent) 0%,
      color-mix(in oklab, var(--field-warm) 9%, transparent) 42%,
      transparent 76%),
    linear-gradient(180deg,
      color-mix(in oklab, var(--field-cool) 20%, transparent) 0%,
      transparent 46%),
    radial-gradient(ellipse 118% 90% at 50% 44%,
      transparent 0%,
      transparent 52%,
      color-mix(in oklab, var(--field-cool) 22%, transparent) 100%);
  /* strict containment: the field can never contribute to page scroll size,
     and its repaints can never escape its own box */
  contain: strict;
}

/* --- The dotted column rules ---------------------------------------------
   One tile: a single dot, repeated on a --field-col by --field-row grid. The
   horizontal pitch is wide and the vertical pitch is tight, so the eye reads
   columns of dots rather than a dot field. */

.field::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(
    circle at center,
    var(--grid-dot) 0 var(--field-dot),
    transparent calc(var(--field-dot) + 0.4px));
  background-size: var(--field-col) var(--field-row);
  background-position: center top;
}

/* --- Depth ----------------------------------------------------------------
   A single soft vignette, drawn as a gradient rather than a shadow or a blur,
   so it composites once and never repaints. Slightly above centre, which is
   where the hero specimen sits. */

.field::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: var(--field-depth);
  background-image:
    /* the lift under the middle of the page — the plane's own key light */
    radial-gradient(ellipse 62% 46% at 50% 34%,
      color-mix(in oklab, var(--fg) 5%, transparent) 0%,
      color-mix(in oklab, var(--fg) 2%, transparent) 40%,
      transparent 68%),
    /* a second, much tighter pool right where the hero specimen stands, so the
       showpiece has a floor under it instead of standing on nothing */
    radial-gradient(ellipse 30% 22% at 50% 30%,
      color-mix(in oklab, var(--fg) 4%, transparent) 0%,
      transparent 74%),
    /* the fall-off at the edges */
    radial-gradient(ellipse 88% 72% at 50% 42%,
      transparent 0%,
      transparent 46%,
      color-mix(in oklab, var(--bg-sunken) 82%, transparent) 100%),
    /* and the very bottom, dropping away */
    linear-gradient(180deg,
      transparent 0%,
      transparent 72%,
      color-mix(in oklab, var(--bg-sunken) 60%, transparent) 100%);
}

/* --- Drift ----------------------------------------------------------------
   Three parallax planes of distant particles. Each plane is one element and
   one background-image of a handful of dots, moved with translate3d only —
   no filters, no shadows, no background-position animation, so each plane is
   a single composited layer and scrolling is untouched.

   The planes alternate, so nothing ever leaves the viewport and there is no
   restart jump. Periods are mutually prime-ish so the pattern never repeats
   visibly. */

.field__drift::before,
.field__drift::after {
  content: "";
}

.field__drift,
.field__drift::before,
.field__drift::after {
  position: absolute;
  inset: -8%;
  opacity: var(--field-drift-op);
  background-repeat: no-repeat;
  animation-name: var(--motion-off, field-drift);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

/* near plane — brightest, largest, moves most */
.field__drift {
  --drift-x: 1.6vw;
  --drift-y: -2.2vh;
  animation-duration: 71s;
  background-image:
    radial-gradient(circle 1.6px at 12% 22%, color-mix(in oklab, var(--fg) 26%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 1.4px at 78% 14%, color-mix(in oklab, var(--fg) 22%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 1.5px at 62% 71%, color-mix(in oklab, var(--fg) 20%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 1.3px at 31% 86%, color-mix(in oklab, var(--fg) 24%, transparent) 0 100%, transparent 100%);
}

/* mid plane */
.field__drift::before {
  --drift-x: -1.1vw;
  --drift-y: 1.7vh;
  animation-duration: 97s;
  background-image:
    radial-gradient(circle 1.1px at 47% 9%,  color-mix(in oklab, var(--fg) 16%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 1px   at 88% 44%, color-mix(in oklab, var(--fg) 14%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 1.2px at 21% 57%, color-mix(in oklab, var(--fg) 15%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 1px   at 69% 93%, color-mix(in oklab, var(--fg) 13%, transparent) 0 100%, transparent 100%);
}

/* far plane — faintest, slowest, almost subliminal */
.field__drift::after {
  --drift-x: 0.6vw;
  --drift-y: 0.9vh;
  animation-duration: 139s;
  background-image:
    radial-gradient(circle 0.8px at 6% 62%,  color-mix(in oklab, var(--fg) 11%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 0.8px at 39% 33%, color-mix(in oklab, var(--fg) 10%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 0.9px at 93% 76%, color-mix(in oklab, var(--fg) 11%, transparent) 0 100%, transparent 100%),
    radial-gradient(circle 0.8px at 55% 50%, color-mix(in oklab, var(--fg) 9%, transparent)  0 100%, transparent 100%);
}

/* --- Light theme ----------------------------------------------------------
   On a pale ground the same construction reads as smudge unless the vignette
   is pulled right back and the particles become dark specks rather than
   bright ones. --grid-dot and --fg already flip in tokens.css; what has to
   change here is the amount. */

:root[data-theme="light"] .field {
  /* On a pale ground the same two lights have to be paler and cooler or the
     page yellows. Same construction, a third of the strength. */
  --field-warm: #b9c9ae;
  --field-cool: #9fb0c6;
}
:root[data-theme="light"] .field::after {
  opacity: calc(var(--field-depth) * 0.62);
}
:root[data-theme="light"] .field__drift,
:root[data-theme="light"] .field__drift::before,
:root[data-theme="light"] .field__drift::after {
  opacity: calc(var(--field-drift-op) * 0.55);
}

/* --- Narrow viewports -----------------------------------------------------
   Tighter columns on a phone, so the rules still read as a rhythm rather than
   as two stray lines, and the vignette eases off because there is no width
   left for it to work in. */

@media (max-width: 40rem) {
  .field {
    --field-col: 56px;
    --field-depth: 0.7;
  }
}
