/* :where() keeps this at zero specificity — a generic fallback size for any container
   using this module standalone, deliberately losing to a more specific override like
   .hero__dot-field's width:58% (layout.css) regardless of stylesheet load order. Without
   this, both rules have equal (single-class) specificity and whichever file loads last
   wins by source order alone — which is exactly what silently reintroduced full-width
   dots after js/dot-field.js started adding this class. */
:where(.dot-field) {
  width: 100%;
  height: 100%;
}

.hero__dot-field {
  /* Top- and bottom-edge fade (both were a hard cutoff where the field's box simply
     started/ended — most visible on mobile, where the confined box is short enough
     that the top edge sits close to the focal point and the natural per-dot falloff
     alone doesn't soften it). The left-edge fade lives on .hero__dot-field-inner below
     — two separately-masked nested elements, not one element with two mask layers +
     mask-composite: intersect. Browser support/behaviour for mask-composite turned out
     inconsistent (Chrome's own prefixed vs. unprefixed versions didn't reliably agree,
     confirmed via computed style). A masked parent naturally fades its already-masked
     child on its own, so nesting gets the same result without needing composite modes
     anywhere. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 75%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 75%, transparent 100%);
}

.hero__dot-field-inner {
  /* Left-edge fade, into the text column (the "overlapping but fading" look). The right
     edge is deliberately left unfaded — it runs flush to the true viewport edge (see
     .hero__dot-field's width/position in layout.css) and should read as the pattern
     continuing past what's visible, not stopping politely short of it. */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, black 30%);
  mask-image: linear-gradient(to right, transparent 0%, black 30%);
}

/* FINAL REVIEW (F-1): the previous confined composition (height:42%/56% of the
   hero's own min-height, width up to 42%/34vw) still read as a large desktop
   graphic that had merely been cropped/scaled down — at mobile it visibly
   overlapped the heading's own text column (.hero's <64rem tier is vertically
   *centred*, not pinned to the top, so the heading's actual top edge shifts
   with content/viewport height; a percentage-height field tall enough to
   "reach" toward the heading at one height reliably collided with it at
   another — confirmed by screenshot at 390 and 820). Replaced with small,
   FIXED pixel dimensions instead of percentages: a genuinely different,
   deliberately compact corner accent, not the desktop composition scaled
   down by degrees. Small enough that even the tallest realistic heading
   (three wrapped lines on a narrow phone) still starts well below it. */
@media (max-width: 64rem) {
  .hero__dot-field {
    top: 20px;
    height: 112px;
    width: 112px;
  }
}

/* Tablet tier: a little more breathing room than mobile, still a compact
   square corner accent — not a stretched/taller version of mobile's box. */
@media (min-width: 50.0625rem) and (max-width: 64rem) {
  .hero__dot-field {
    top: 28px;
    height: 168px;
    width: 168px;
  }
}

.dot-field svg {
  display: block;
  width: 100%;
  height: 100%;
}

.dot-field circle {
  fill: var(--cb-color-ink);
}

/* v1 entrance: a single container-level fade, not a per-dot stagger (that belongs to the
   deferred scroll-driven phase). Reduced-motion users see the final state immediately. */
@media (prefers-reduced-motion: no-preference) {
  .dot-field {
    opacity: 0;
    animation: dot-field-fade-in var(--cb-duration-slow) var(--cb-ease-standard) forwards;
    animation-delay: 100ms;
  }
}

@keyframes dot-field-fade-in {
  to {
    opacity: 1;
  }
}

/* The ambient per-dot size pulse (a random subset gently growing and settling back to
   their own original size) is driven from js/dot-field.js via requestAnimationFrame,
   setting each dot's `r` attribute directly every frame — not a CSS animation. A CSS
   @keyframes animation of `r` via calc()/var() was tried and rejected: some engines
   failed to resolve it mid-animation (confirmed via getComputedStyle returning a literal
   unresolved "calc(...)" string) and rendered affected dots at radius 0 — dots blinking
   out. See the comment above animatePulses() in dot-field.js for the full reasoning,
   including why transform: scale() was rejected first for a different failure (drift). */
