/* ═══════════════════════════════════════════════════════════
   FILM GRAIN
   The same texture and weight as the ahmed.roleos.co hero: a
   256x256 noise tile at 6% opacity, tiled, jumping in steps.

   Stepped, not sliding, and that is the whole trick. A smooth
   translate reads as a texture being dragged across the surface.
   Jumping roughly one tile per frame re-randomises which part of
   the noise sits over any given pixel, which is what makes it read
   as grain in the emulsion rather than a layer on top of it.

   Framer drives its version from JS. Nothing here needs to: steps()
   does the same job on the compositor, with no scripting and no work
   when the tab is in the background.
   ═══════════════════════════════════════════════════════════ */
.grain,
.grain-bg{
  /* One knob. 8 steps across this duration, so 2.4s is 300ms a frame,
     about 3 per second. At 1.2s it was 150ms a frame and read as
     flicker: fast enough to catch the eye while you read past it. */
  --grain-speed:2.4s;
  position:relative;isolation:isolate;
}

/* Two placements, one texture, chosen by depth rather than by whichever
   pseudo-element happens to be free:

     .grain     z-index 2, over everything. For photographs, where the
                grain has to sit in the image.
     .grain-bg  z-index -1, over the element's own fill but under its
                content. For a coloured band.

   -1 rather than 0 is the point. Inside a stacking context a positioned
   pseudo at z-index 0 paints after in-flow content, so it would veil the
   type. At -1 it paints after the background and before the content.
   isolation keeps that -1 from escaping the section.

   Both use ::after, so a section that already spends ::before on
   something else can still opt in by overriding --grain-z. */
.grain    {--grain-z:2}
.grain-bg {--grain-z:-1}

.grain::after,
.grain-bg::after{
  content:"";position:absolute;
  /* Oversized so the stepped translate never exposes an edge. The layer
     has to be bigger than its travel. */
  inset:-150%;
  background-image:url(/assets/images/grain.png);
  background-repeat:repeat;
  opacity:.06;
  pointer-events:none;
  z-index:var(--grain-z);
  will-change:transform;
  animation:grainShift var(--grain-speed) steps(8) infinite;
}

/* The overhang has to be clipped, and how matters.

   A photograph is a rounded box with nothing sticky inside, so
   overflow:hidden is right and also honours the border radius.

   A full-width band is not. overflow:hidden makes the section a scroll
   container, and the sticky .tag eyebrow inside then sticks to the
   section instead of the viewport, which reads as the label scrolling
   away with the content it is meant to label. clip-path clips the same
   overhang without creating a scroll container. The engage-section
   already used clip-path for exactly this reason. */
.grain{overflow:hidden}
.grain-bg{clip-path:inset(0)}

/* Eight positions, none a multiple of the 256px tile, so the pattern
   never lands back on itself and repeat as a visible cycle. */
@keyframes grainShift{
  0%   {transform:translate3d(0,0,0)}
  12.5%{transform:translate3d(-6%,-3%,0)}
  25%  {transform:translate3d(-11%,4%,0)}
  37.5%{transform:translate3d(3%,-8%,0)}
  50%  {transform:translate3d(-4%,9%,0)}
  62.5%{transform:translate3d(8%,2%,0)}
  75%  {transform:translate3d(-9%,-5%,0)}
  87.5%{transform:translate3d(5%,7%,0)}
  100% {transform:translate3d(0,0,0)}
}

/* Motion is the point of grain, but it is also the part that can make
   people ill. Reduced motion keeps the texture and drops the flicker,
   which still does the work of breaking up a flat fill. */
@media (prefers-reduced-motion:reduce){
  .grain::after,
  .grain-bg::after{animation:none}
}
