/* ===========================================================================
   METAL v17 — the two rules the engine needs.           branched 2026-09-14

   Everything that varies (the image, its size, the shadow) is set by
   metal.js on the element. What lives here is only what cannot vary: the
   geometry of the layer and the two clip properties.

   Load this before metal.js, or the first sheet paints against unstyled
   layout and the measurement it takes is of the wrong box.
   =========================================================================== */

/* THE SURFACE LAYER.

   Absolute, so it is not a flex or grid item and cannot shove the section's
   content sideways. v16 learned that one the hard way: .panel-grain is a
   child of a flex container, and in flow it would have been a fourteenth
   letter.

   z-index -1 is the whole trick. Positioned descendants paint ABOVE in-flow
   text, so at z-index 0 this layer would cover the section's own copy. At -1
   it paints above the section's background and below everything else, with no
   requirement that the content be positioned. The engine sets
   isolation:isolate on the host so the -1 cannot fall behind that background.

   image-rendering:pixelated is not a nicety. The sheet is generated at one
   image pixel per CSS pixel; on a 2x display the browser upscales it, and
   smooth interpolation would average adjacent noise pixels together and halve
   the grain. Pixelated keeps each noise pixel a crisp 2x2 block, so the grain
   reads at the same scale on every display instead of getting softer as the
   screen gets better. */
.metal-surface{
  position:absolute;
  inset:0;
  z-index:-1;
  pointer-events:none;
  image-rendering:pixelated;

  /* Serves both layer modes. A <canvas> has an intrinsic size (its bitmap)
     separate from its CSS size, and without width/height it lays out at the
     default 300x150 and fights the inset:0. A <div> ignores all three of
     these and takes its background-image instead. The engine sets the canvas
     bitmap to the host's exact pixel size, so it resolves 1:1 and nothing is
     resampled except by devicePixelRatio, which is what pixelated is for. */
  display:block;
  width:100%;
  height:100%;
  background-repeat:no-repeat;

  /* THE HOST'S CORNERS, OR IT EATS THEM. This layer is a rectangle painted
     over the element's own background, so on anything with a border-radius
     -- a card, a button, a rounded panel -- its square corners cover the
     rounded ones and the element goes back to being a box. Worse where
     something moves behind it: on xpertloop the rotating triangles pass
     under .apply-card and the square corner showed as a hard point.

     inherit rather than a value, so one rule serves every radius on the page
     and none of them is written down twice. The layer is the padding box and
     the radius is the border box, so they differ by the border width -- 1px
     against a 16px radius, which is not visible. */
  border-radius:inherit;
}

/* THE TEXT TIER.

   One sheet for the element, clipped to its glyphs — not one per letter. Each
   letter therefore clips its own window out of one continuous sheet, which is
   what a word cut from a single plate does.

   -webkit-text-fill-color as well as color: WebKit honours the fill colour
   over the colour property, and without it the glyphs paint solid and hide
   the background entirely.

   box-decoration-break:clone for the fragmented case. An inline that wraps is
   several boxes, and sliced (the default) lays the background out across a
   single hypothetical box and then cuts it up, which puts the top of the run
   on line one and the bottom on line two — correct for a block, wrong for an
   inline whose fragments should each read as cut from the plate. Clone gives
   each fragment the whole run. Neither is right for every case; a multi-line
   metal heading wants display:inline-block or a block element, where the
   engine measures the real box and spans the run across all of it. */
.metal-text{
  color:transparent;
  -webkit-text-fill-color:transparent;
  -webkit-background-clip:text;
  background-clip:text;

  /* NO image-rendering HERE, AND THAT IS THE WHOLE BUG.

     It carried image-rendering:pixelated, copied from .metal-surface without
     asking whether text wanted it. Found 2026-09-14 by bisection on the live
     page -- pausing the tri animation: still broken. Removing the hero's
     canvas as well: still broken. Turning off pixelated: FIXED.

     Pixelated is nearest-neighbour sampling. If the layer is ever rastered at
     a scale other than 1 -- and a composited layer is, during scroll -- the
     background comes back HARD STRETCHED rather than merely soft. Every
     description of this bug used the word "stretched", and pixelated is the
     only property in play that turns a scale error into a stretch instead of
     a blur. With auto the same scale error is a barely-visible softening.

     It was never worth having here. On a SURFACE the grain covers a large
     flat area and crispness is the point; clipped to glyphs it is four levels
     of noise inside letterforms, where nobody can tell interpolated from
     nearest-neighbour. .metal-surface keeps it and should.

     If the grain ever does read too soft on a HiDPI display, the answer is to
     generate the text sheet at devicePixelRatio and set background-size in
     CSS pixels -- crisp by having the right pixels rather than by refusing to
     interpolate the wrong ones.

     Also gone: display:inline-block and transform:translateZ(0), added to
     force this onto its own layer. They did not help, and promoting it was
     arguably making the scale problem more likely rather than less. What is
     left is the minimum that works. */

  -webkit-box-decoration-break:clone;
  box-decoration-break:clone;

  /* background-repeat is set by the engine, not here: flat text repeats so a
     stale measurement cannot leave glyph bottoms unpainted, ramped text does
     not because where its run starts is the point. See repaint() in metal.js.
}

/* A text-shadow is painted behind the glyph, and background-clip:text makes
   the glyph a window onto the sheet — so on a metal-text element the shadow
   would show THROUGH its own letters. Anything wearing both needs the shadow
   on a wrapper, not on the clipped element. Stated here because it is the one
   combination that looks broken rather than merely wrong. */
.metal-text.metal-shadowed{
  text-shadow:none;
}
