/* ==========================================================================
   Dots band  —  Figma "Component 452 / Dots - Animate" (705:185044)
   1920 x 600 at y 863. Five staggered rows of 19x19 dot cells:
     rows 217 / 383 / 549  start at x 59
     rows 300 / 466        start at x 16
   23 cells per row, pitch 83 (a 19px cell plus a 64px gap).

   Each cell is a real element rather than a painted background, because it has
   a hover state — Figma "Component 425", variants Default and Pulse
   (705:100716):
     Default  transparent cell, 3px dot in Mono/Grey
     Pulse    Accent/Yellow cell, radius 6, and the dot flips to Mono/Dark

   Section reveal: opacity .15 -> 1.
   ========================================================================== */

.dots {
  top: calc(863 * var(--s));
  height: calc(600 * var(--s));
  background: var(--c-mono-dark);
  overflow: hidden;
  z-index: 1;
}

.dots__grid {
  position: absolute;
  inset: 0;
  opacity: 0.15;
  transition: opacity calc(var(--rv-dur) * 1.2) var(--rv-ease);
}
.dots.is-in .dots__grid { opacity: 1; }

.dots__row {
  position: absolute;
  display: flex;
  gap: calc(64 * var(--s));          /* 83 pitch - 19 cell */
}

.dots__dot {
  position: relative;
  flex: none;
  width: calc(19 * var(--s));
  height: calc(19 * var(--s));
  border-radius: calc(6 * var(--s));
  background: transparent;
  /* Quick to light, slow to fade — moving the cursor across the grid leaves a
     brief trail rather than a hard on/off. */
  transition: background-color 420ms var(--ui-ease);
}

/* --- hit target ----------------------------------------------------------
   The visible cell is 19 on an 83 pitch, so aiming at it means aiming at 23%
   of the row — you have to hunt for each dot. This invisible box extends the
   hover area to ~40% of the pitch in every direction (33.2 from the centre,
   so 66.4 across), which is what makes the grid feel responsive to a cursor
   that is merely near a dot rather than on it.

   It is a pseudo-element, so hovering it hovers .dots__dot and the existing
   :hover rules light up unchanged — the cell and the dot keep their Figma
   sizes. 66.4 on an 83 lattice still leaves a ~17 alley between neighbours,
   so no two dots ever claim the same pointer.

   Both dot fields use the same 83x83 lattice — the separator band and the
   Technology Compatibility field, which reuses these classes — so this covers
   both. --dot-hit is the inset: (66.4 - 19) / 2. */
.dots__dot {
  --dot-hit: 23.7;
}

.dots__dot::before {
  content: "";
  position: absolute;
  inset: calc(var(--dot-hit) * -1 * var(--s));
  border-radius: calc(12 * var(--s));
  /* Transparent, but still hit-tested — this is the whole point. */
  background: transparent;
}

.dots__dot::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(3 * var(--s));
  height: calc(3 * var(--s));
  margin: calc(-1.5 * var(--s)) 0 0 calc(-1.5 * var(--s));
  border-radius: 50%;
  background: var(--c-mono-grey);
  transition: background-color 420ms var(--ui-ease);
}

.dots__dot:hover,
.dots__dot.is-lit {
  background: var(--c-accent-yellow);
  transition-duration: 120ms;
}
.dots__dot:hover::after,
.dots__dot.is-lit::after {
  background: var(--c-mono-dark);
  transition-duration: 120ms;
}

@media (prefers-reduced-motion: reduce) {
  .dots__grid { opacity: 1; transition: none; }
}

@media (max-width: 1023px), (max-width: 1200px) and (orientation: portrait) {
  .dots { height: 220px; }
  .dots__row { gap: 26px; }
  /* Pitch drops to 36 here, so the hit box has to come down with it or
     neighbours would overlap and fight for the same pointer. */
  .dots__dot { width: 10px; height: 10px; border-radius: 3px; --dot-hit: 8; }
  .dots__dot::before { border-radius: 6px; }
  .dots__dot::after { width: 2px; height: 2px; margin: -1px 0 0 -1px; }
  /* Rows are laid out on the 1920 grid; scale the whole band down instead of
     recomputing every position. */
  .dots__grid { transform: scale(0.5); transform-origin: 0 0; width: 200%; height: 200%; }
}

/* --- the repeat ----------------------------------------------------------
   The phone frame (1135:54801) puts a second band between the testimonial
   and the FAQ. The desktop canvas has none, and both bands would land on
   the same absolute `top` there, so the repeat is off by default and only
   the phone layout switches it on. This matches the second .dots only --
   the first one precedes .testimonial, so the sibling combinator skips it. */
.testimonial ~ .dots { display: none; }

/* The contact page's second band is the phone frame's only; the desktop
   canvas places one band at a fixed y and both would land on it. */
.o-page--contact .dots + .dots { display: none; }

/* The About page's second band belongs to the phone frame only -- the
   desktop canvas places one band at a fixed y and both would land on it. */
.about-std ~ .dots { display: none; }
