/**
 * Film grain.
 *
 * Not borrowed decoration: the app composites a `GrainOverlay` shader over its
 * whole shell at intensity 0.13, so the web version matches that number rather
 * than picking one by eye. The texture is generated locally
 * (assets/img/grain.png, 128px seeded noise) because §10 bans third-party
 * requests and the source resource fetched it from a CDN.
 *
 * TWO LAYERS, AND ONLY ONE OF THEM MOVES (owner, 2026-09-09).
 *
 *   1. The charcoal ground carries MOVING grain. It is painted into the body's
 *      background, so it is behind every element on the page — which means it
 *      shows through exactly where the dark shell is exposed and nowhere else.
 *      An accent zone or a card sits on top of it and hides it. That is the
 *      scoping mechanism: no masking, no per-element opt-in, no way for it to
 *      leak onto a colour field.
 *
 *   2. Everything else carries STATIC grain, from one fixed overlay above the
 *      content. Colour fields and coloured type get texture without motion.
 *
 * The result on a dark area is both layers, which is correct — the dark ground
 * is where the app puts its grain too.
 */

/* ------------------------------------------------- 1. the moving ground */

body {
  background-image: url("../../img/grain.png");
  background-size: 128px 128px;
  background-repeat: repeat;
  /* Fixed to the viewport, so the grain does not slide up the page as the
   * document scrolls. Film grain belongs to the lens, not to the scene. */
  background-attachment: fixed;
  /*
   * BLENDED WITH THE SURFACE COLOUR, not painted over it. Without this the
   * image simply replaces the background-color and the charcoal shell renders
   * as a band of grey noise — which is exactly what happened. Overlay against a
   * mid-grey tile leaves the colour where it is and adds only the deviation.
   */
  background-blend-mode: overlay;
}

/**
 * The animation is off unless the device can afford it.
 *
 * Stepping the position repaints the full viewport five times a second. On a
 * phone that is a real cost for a texture nobody is standing still to look at,
 * so it runs only where there is a fine pointer and no reduced-motion request:
 * a desktop, sitting still.
 */
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
  body { animation: too-grain 0.6s steps(1, end) infinite; }
}

@keyframes too-grain {
  0%   { background-position: 0 0; }
  20%  { background-position: -8px 5px; }
  40%  { background-position: 6px -3px; }
  60%  { background-position: -5px -6px; }
  80%  { background-position: 3px 8px; }
}

/* -------------------------------------------------- 2. the static layer */

.grain {
  position: fixed;
  inset: 0;
  z-index: var(--z-toast);
  pointer-events: none;
  background-image: url("../../img/grain.png");
  background-size: 128px 128px;
  background-repeat: repeat;
  /* Matches the app's shader intensity. Above about 0.2 it stops reading as
   * film and starts reading as a dirty screen. */
  opacity: 0.13;
  /* Overlay keeps it out of the highlights, so a light accent gets texture
   * rather than a grey wash. */
  mix-blend-mode: overlay;
  /* NEVER ANIMATED. This is the layer that lands on coloured surfaces. */
}

/**
 * Grain in the ink of the big display type.
 *
 * Only the fat chunky headings, and only where they are set in near-black on a
 * colour field — texture in the ink is the point, and doing it to white type on
 * a dark ground just dirties it.
 *
 * background-clip: text needs `color: transparent` to show, which means an
 * unsupported browser would render invisible headlines. The @supports guard is
 * doing real work, not box-ticking: without it this is a catastrophic failure
 * rather than a missing texture.
 */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .hero-zone__title,
  .dome-grid__theme,
  .feature-card--accent .feature-card__title {
    background-image: url("../../img/grain.png"),
      linear-gradient(currentColor, currentColor);
    background-size: 128px 128px, 100% 100%;
    background-repeat: repeat, no-repeat;
    background-blend-mode: overlay, normal;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }

}

@media print {
  .grain { display: none; }
  body { background-image: none; animation: none; }
}
