/* ==========================================================================
   glassmorphism-theme / depth layer
   --------------------------------------------------------------------------
   The part of this register that tokens cannot carry: the ground the glass is
   translucent against, the three elevation tiers as usable classes, and the
   stacking rules that keep a frosted surface legible over a lit backdrop.

   Every value here is a var() reference to tokens.css. There is not one color
   literal in this file.

   Requires tokens.css. Load it, then components.css, then this:

     <link rel="stylesheet" href="tokens/tokens.css" />
     <link rel="stylesheet" href="components/components.css" />
     <link rel="stylesheet" href="components/depth.css" />

   HOW SEPARABLE THIS IS
   Mechanically, completely: nothing in tokens.css or components.css refers to
   anything here, so deleting this file and assets/ breaks no rule and throws
   no error. Visually it is the opposite of the neobrutalism sibling's play
   layer, which could be deleted and leave a quieter version of the same
   register behind. Delete this one and there is no register left. Glass is
   the only material whose appearance is entirely a function of what is behind
   it, and with a flat fill behind it what you have is gray boxes with light
   borders. CUSTOMIZE.md treats that honestly rather than as an axis move.

   The SVGs in assets/ are meant to be inlined into your markup rather than
   loaded through <img>. An SVG in an <img> is an isolated document that
   cannot read the page's custom properties, so it would carry its own colors
   and the single source of truth would be gone. Inlined, the paint rules
   below reach them.
   ========================================================================== */

/* --------------------------------------------------------------------------
   THE GROUND
   A fixed layer behind everything, so the light stays put while the page
   scrolls over it. Fixed rather than scrolling because a moving light source
   states that the reader is travelling past the lights rather than that the
   content is travelling past the reader, and the second reading is the one a
   page wants.

   isolation: isolate rather than a z-index race. The ground establishes a
   stacking context so its children cannot be interleaved with page content by
   accident.
   -------------------------------------------------------------------------- */
.gl-ground {
  position: fixed;
  inset: 0;
  z-index: -1;
  isolation: isolate;
  overflow: hidden;
  background: var(--gl-ground);
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   THE ORBS
   Three light sources, each an inlined SVG taking its hue from the `color`
   property set here. They are absolutely positioned as a percentage of the
   ground, so the composition survives every viewport without a breakpoint.

   Positioned by where the core lands rather than by where the box starts.
   left and top name the bright point and the translate pulls the box back
   around it, so moving an orb means naming the place you want lit. Hanging
   orbs off the corners by their bounding boxes puts the light in the margins
   and leaves every panel sitting on flat black, which is the commonest way a
   glass page ends up looking like gray boxes.

   The transform is safe here, and safe for a duller reason than rule 4 might
   suggest: the orbs are not ancestors of anything that carries a
   backdrop-filter, so nothing downstream can be affected either way.
   -------------------------------------------------------------------------- */
.gl-orb {
  position: absolute;
  display: block;
  width: var(--gl-orb-size);
  height: auto;
  transform: translate(-50%, -50%);
}

/* The core stop is the ceiling the contrast math depends on. Overriding it
   here rather than in the file means the SVGs stay honest when opened alone
   and the token stays the single source of the bound. */
.gl-orb .gl-orb-core {
  stop-color: currentColor;
  stop-opacity: var(--gl-orb-alpha);
}

/* The bent falloff: 40% of the core, so the ramp reads as a bloom rather than
   as a disc. calc() on a unitless token, so it tracks the ceiling. */
.gl-orb .gl-orb-mid {
  stop-color: currentColor;
  stop-opacity: calc(var(--gl-orb-alpha) * 0.4);
}

.gl-orb .gl-orb-inner {
  stop-color: currentColor;
  stop-opacity: calc(var(--gl-orb-alpha) * 0.25);
}

.gl-orb .gl-orb-edge {
  stop-color: currentColor;
  stop-opacity: 0;
}

/* The three hues, and three cores that all land inside the frame. Warm high
   and to the right, where the fold's working surface sits; primary low and
   left, under the body copy; cool at the bottom so a footer is not sitting on
   flat black. Warm light above cool light is the arrangement the eye reads as
   depth rather than as two stains, because it is the one the sky supplies. */
.gl-orb-drift {
  color: var(--gl-orb-violet);
  left: 76%;
  top: 26%;
  width: var(--gl-orb-size);
}

.gl-orb-bloom {
  color: var(--gl-orb-indigo);
  left: 14%;
  top: 68%;
  width: var(--gl-orb-size);
}

.gl-orb-halo {
  color: var(--gl-orb-teal);
  left: 62%;
  top: 96%;
  width: var(--gl-orb-size-sm);
}

/* --------------------------------------------------------------------------
   THE VEIL
   A fine grid between the orbs and the glass, and what makes the frost
   visible at the panel boundary.

   Blur is a local average, so blurring a smooth gradient returns something
   close to the gradient. Measured on this theme's own ground, turning
   backdrop-filter on over the bare gradient changes the pixels inside a panel
   by a mean of 1.86/255, which is not a difference anyone will see.

   Adding the veil barely moves that whole-panel figure, to 2.12/255, so the
   honest version of this rule is not that the veil makes the blur "work".
   What it does is local to the edge: scanned across the ground the grid
   reads at a standard deviation of 0.88, and inside a panel it measures 0.
   The lines are crisp on the open ground and gone under the glass, and it is
   that discontinuity at the boundary, rather than any change in the average,
   that the eye reads as a frosted surface.

   The pitch is a token because it has to stay under the blur radius. At
   --gl-veil-size 44px and --gl-blur-2 20px the lines are visibly softened but
   not erased. Push the pitch far below the blur radius and the grid
   dissolves into a flat tint, which returns you to blurring a gradient.

   This is CSS rather than a fifth file in assets/ for a specific reason: the
   pitch cannot be a token inside an SVG. A <pattern> takes its tile size from
   its width and height attributes. SVG2 does promote those to CSS geometry
   properties, so `#p { width: var(--pitch) }` parses, and getComputedStyle
   will report the new value, which makes this look like it works.

   It does not. Measured on the rendered output rather than on the computed
   style: with the attribute at 10 and CSS setting 40px, the painted tile
   stays at a 10px pitch. The declaration is accepted and ignored, which is a
   worse failure than being rejected, because the check most people would run
   reports success.

   So making the pitch a token and the veil an asset are mutually exclusive,
   and the token is worth more.
   -------------------------------------------------------------------------- */
.gl-veil {
  position: absolute;
  inset: 0;
  opacity: var(--gl-veil-opacity);
  background-image:
    repeating-linear-gradient(
      to right,
      var(--gl-veil-line) 0,
      var(--gl-veil-line) var(--gl-border-width),
      transparent var(--gl-border-width),
      transparent var(--gl-veil-size)
    ),
    repeating-linear-gradient(
      to bottom,
      var(--gl-veil-line) 0,
      var(--gl-veil-line) var(--gl-border-width),
      transparent var(--gl-border-width),
      transparent var(--gl-veil-size)
    );

  /* The grid runs to the edges of a screen and a grid that reaches a hard
     edge reads as a texture applied to the page. Fading it out at the corners
     leaves it reading as something the light happens to fall across. */
  -webkit-mask-image: radial-gradient(ellipse at 50% 38%, black 0%, transparent 78%);
  mask-image: radial-gradient(ellipse at 50% 38%, black 0%, transparent 78%);
}

/* --------------------------------------------------------------------------
   THE ELEVATION TIERS
   The same three steps components.css uses, as classes, for the surfaces the
   five components do not cover.

   Blur and alpha rise together because they are two readings of one fact.
   Something nearer the viewer occludes more of what is behind it, which is
   the alpha, and throws it further out of focus, which is the blur. Raise one
   alone and the surface stops reading as a distance: more alpha at the same
   blur is a tinted pane rather than a nearer one, and more blur at the same
   alpha is a surface that is somehow both far away and unfocused.

   Saturation rises with the blur for a mechanical reason. Averaging a region
   of colors pulls them toward the mean, and the mean of a colorful region is
   grayer than any of it. Lifting saturation puts back what the averaging took
   and keeps a blurred orb reading as colored light rather than as haze.
   -------------------------------------------------------------------------- */
.gl-elev-1 {
  background: var(--gl-surface-1);
  -webkit-backdrop-filter: var(--gl-backdrop-1);
  backdrop-filter: var(--gl-backdrop-1);
  border: var(--gl-border-width) solid var(--gl-edge);
  border-radius: var(--gl-radius-lg);
  box-shadow: var(--gl-shadow-inset);
}

.gl-elev-2 {
  background: var(--gl-surface-2);
  -webkit-backdrop-filter: var(--gl-backdrop-2);
  backdrop-filter: var(--gl-backdrop-2);
  border: var(--gl-border-width) solid var(--gl-edge-boundary);
  border-radius: var(--gl-radius-lg);
  box-shadow:
    inset 0 var(--gl-border-width) 0 0 var(--gl-highlight),
    var(--gl-shadow-2);
}

/* Tier 3 carries the scrim without being asked. The tier exists for surfaces
   that float over other surfaces, and rule 3 below says a surface over content
   takes a scrim. Making it conditional would mean shipping a tier whose
   correct use is always the modified one, so the modifier is folded in. The
   consequence is worth stating plainly: tier 3 is the only tier whose contrast
   does not depend on the ground at all. */
.gl-elev-3 {
  background-color: var(--gl-scrim);
  background-image: linear-gradient(var(--gl-surface-3), var(--gl-surface-3));
  -webkit-backdrop-filter: var(--gl-backdrop-3);
  backdrop-filter: var(--gl-backdrop-3);
  border: var(--gl-border-width) solid var(--gl-edge-boundary);
  border-radius: var(--gl-radius-lg);
  box-shadow:
    inset 0 var(--gl-border-width) 0 0 var(--gl-highlight),
    var(--gl-shadow-3);
}

/* --------------------------------------------------------------------------
   THE STACKING RULES
   Three rules. The first two are enforced here; the third cannot be.

   RULE 1. One backdrop-filter per stack.
   A filtered surface inside a filtered surface blurs its parent's finished
   output rather than the page, so the alphas multiply instead of adding and
   the blur is applied twice to something already blurred. The result is a
   milky panel with no depth in it and twice the compositing cost. A nested
   surface keeps its fill and drops its filter, which is what the rule below
   does. It reads as nearer because its fill is lighter than its parent's,
   which is the cue that was doing the work anyway.
   -------------------------------------------------------------------------- */
.gl-elev-1 .gl-elev-1,
.gl-elev-1 .gl-elev-2,
.gl-elev-1 .gl-elev-3,
.gl-elev-2 .gl-elev-1,
.gl-elev-2 .gl-elev-2,
.gl-elev-2 .gl-elev-3,
.gl-elev-3 .gl-elev-1,
.gl-elev-3 .gl-elev-2,
.gl-elev-3 .gl-elev-3 {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* --------------------------------------------------------------------------
   RULE 2. A tier cannot sit on itself.
   Two surfaces at the same alpha, one inside the other, produce a boundary
   the reader can only find by its border. Nesting is a statement that one
   thing is above another, so the inner surface has to be a tier up. This
   catches the case in the browser rather than in review: a same-tier nest
   gets the next tier's fill, and the composition still reads.

   The pair is listed rather than generated because a tier-3 surface inside a
   tier-3 surface has nowhere to go. Three tiers is the ceiling this ground
   can carry, so that nest is a design error the CSS cannot fix; the outline
   below makes it visible instead of letting it pass. */
.gl-elev-1 > .gl-elev-1 {
  background: var(--gl-surface-2);
}

.gl-elev-2 > .gl-elev-2 {
  background: var(--gl-surface-3);
}

.gl-elev-3 > .gl-elev-3 {
  outline: var(--gl-border-width-heavy) dashed var(--gl-destructive);
}

/* --------------------------------------------------------------------------
   RULE 3. A surface over content takes a scrim, not a tier.

   The three tiers are measured against the ground, and the ground is bounded
   by --gl-ground-peak. A surface whose backdrop is the document rather than
   the ground has no such bound behind it, because the document contains ink.
   Two cases, and a page of any size has both: a sticky bar with content
   scrolling under it, and any surface that floats over another surface.

   Measured on this theme, a nav link on a tier-2 fill with body text passing
   underneath lands at 1.24:1. The same link over the scrim holds 5.27:1 with
   pure white behind it, which is a backdrop no page in this theme can
   actually produce.

   The tier's fill stays on top of the scrim as a background-image, so the
   surface keeps the elevation it was carrying. What changes is what is
   underneath: a bounded dark fill instead of whatever happened to scroll
   past. The blur is still doing its work, and doing it on something the
   reader can no longer read through, which is the point.
   -------------------------------------------------------------------------- */
.gl-over-content {
  background-color: var(--gl-scrim);
}

.gl-elev-1.gl-over-content {
  background-image: linear-gradient(var(--gl-surface-1), var(--gl-surface-1));
}

.gl-elev-2.gl-over-content {
  background-image: linear-gradient(var(--gl-surface-2), var(--gl-surface-2));
}

.gl-elev-3.gl-over-content {
  background-image: linear-gradient(var(--gl-surface-3), var(--gl-surface-3));
}

/* RULE 4, which CSS cannot enforce: nothing between a surface and the ground
   may carry `opacity` below 1 or a `filter`. Either establishes a backdrop
   root, the backdrop stops there, and every panel underneath quietly loses
   its blur while keeping its fill. There is no selector for "my ancestor has
   an opacity", so this one lives in a comment.

   Measured rather than assumed, because the usual phrasing of this rule
   includes `transform` and that part did not reproduce. Chromium, striped
   backdrop, standard deviation of the stripes read through the panel, where a
   low number means the blur is working:

     no backdrop-filter at all       115   (negative control)
     backdrop-filter, no wrapper       2   (positive control)
     ancestor transform: translateX    2   blur survived
     ancestor transform: translateZ    2   blur survived
     ancestor will-change: transform   2   blur survived
     ancestor opacity: 0.99          115   BLUR LOST
     ancestor filter: blur(0px)      115   BLUR LOST

   So `opacity` and `filter` are the two to hunt for. A transform on an
   ancestor is not the culprit here, whatever the folklore says, though it
   does create a containing block and will move a fixed-position ground out
   from under your page, which is its own way of producing flat panels.
   Engines have differed on this historically; the figures above are one
   engine on one day, which is more than the folklore has. Check ancestors for
   opacity and filter first. */

/* --------------------------------------------------------------------------
   WHEN THE MATERIAL IS SWITCHED OFF
   tokens.css already swaps the surfaces for solids and zeroes the blur under
   prefers-reduced-transparency and prefers-contrast. What it cannot do from a
   token is remove the geometry, so the ground layer retires its orbs here.

   The orbs go rather than dim. A reader who has asked for reduced
   transparency has asked not to have text on a variable backdrop, and a faint
   orb is still a variable backdrop. The veil goes with them, since its only
   job was to give a blur something to soften and there is no longer a blur.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .gl-orb,
  .gl-veil {
    display: none;
  }
}

/* Nothing in this file animates. The ground is fixed rather than parallaxed,
   and the orbs hold still, so prefers-reduced-motion has nothing to switch
   off here. */
