/* ============================================================
   design-kit/glass.css — the liquid-glass material layer
   Built 2026-07-25. OPT-IN: link it after components.css, or
   don't link it at all. Nothing in the kit changes until a
   .dk-glass class is actually applied to something.

   WHAT THIS IS
   Apple's Liquid Glass, ported honestly to CSS. Apple describes
   the material as three named layers, and so does this file:
     Illumination = backdrop blur+saturate+brightness, + the tint
     Highlight    = the 1px specular rim (::before) + the sheen
     Shadow       = the inner depth band + the cast shadow
   Apple's optical model is LENSING, not scattering — which is why
   naive glassmorphism (blur + white box) never reads as iOS 26.
   We can't lens on the open web (SVG displacement in
   backdrop-filter is Chromium-only), so the read comes from the
   rim, the saturate, and the scroll-edge effect instead.

   WHERE IT GOES — this is doctrine, not taste.
   Apple HIG, Materials, verbatim: "Don't use Liquid Glass in the
   content layer." Glass is the floating navigation/control layer.
     YES → sticky header, tab/segmented bars, floating pills,
            toasts, modals, sheets, command palette, FAB, and the
            :active state of an in-content control.
     NO  → cards, tables, forms, code, prose, list rows, media
            frames. Those stay flat: panel + 1px --line + radius.
   That is AGENT-GUIDE.md hard rule #4 unchanged. Rule #4 already
   carves out "shadows ONLY on overlays/FAB" — glass is the same
   exemption, expressed as translucency instead of a drop shadow.
   See AGENT-GUIDE.md hard rule #4 for the full reconciliation.

   PERF BUDGET — hard ceiling, not a suggestion.
   Max 3 composited glass surfaces per viewport, of which AT MOST
   ONE may be persistently visible while the page scrolls.
   Mozilla bug 1731965 measured 24ms GPU/frame with a single
   backdrop-filter during gentle scroll vs 2ms without — one
   surface already blows the 16.7ms/60fps budget. Apple's own
   guidance (GlassEffectContainer) is to COMBINE glass into one
   container rather than instantiate many. Put the blur on the
   tray, never on the tokens inside it.

   SELF-CONTAINED
   Consumes exactly one host token, --accent, and only if it
   exists (falls back to folio mint). Everything else — edge,
   tint, alpha, radius, depth — is defined here as --glass-*.
   Deliberately does NOT touch --panel/--txt/--dim/--line:
   --line is opaque #262b33 in :root but rgba(255,255,255,.08)
   in folio, and --line2/--faint don't exist in every theme.
   This file therefore drops into design-kit OR straight into
   szczleon-portfolio's inline <style> unchanged.
   ============================================================ */


/* ============================================================
   1 · TOKENS
   ============================================================ */

/* @property first. THE trap this defends against, verified in
   Chromium 148: a var() that fails to resolve inside
   backdrop-filter is Invalid At Computed-Value Time — it does
   NOT fall back to an earlier declaration, it resets the whole
   property to `none`. A misspelled token would therefore give
   you a fully transparent panel with unreadable text on top,
   in exactly the case you thought you had covered.
   Registering the primitives means a bad token falls back to
   initial-value instead. Engines without @property ignore this
   block and get the plain :root defaults below — belt+braces. */
@property --glass-tint   { syntax: "<percentage>"; inherits: true; initial-value: 10%;  }
@property --glass-r      { syntax: "<length>";     inherits: true; initial-value: 16px; }
@property --glass-pad    { syntax: "<length>";     inherits: true; initial-value: 0px;  }
@property --glass-rim-hi { syntax: "<number>";     inherits: true; initial-value: 0.55; }
@property --glass-rim-lo { syntax: "<number>";     inherits: true; initial-value: 0.05; }
@property --glass-depth  { syntax: "<number>";     inherits: true; initial-value: 0.85; }
@property --glass-cast   { syntax: "<number>";     inherits: true; initial-value: 0.42; }
@property --glass-grain-o{ syntax: "<number>";     inherits: true; initial-value: 0;    }

:root {
  /* --- Raw material. Space-separated channels so alpha can be
         varied per-use with rgb(var(--x) / <a>) without redeclaring
         a colour. Never reference host text/panel vars here. --- */
  --glass-ink: 8 10 14;      /* near-black; the body of the glass */
  --glass-hi:  255 255 255;  /* the light */

  /* --- The accent hook. This is the ONLY host token consumed.
         The portfolio rewrites --accent on <html> per hiring lane
         and .bgfx crossfades over 600ms, so a color-mix tint
         re-themes for free and animates with it. Hard-coded rgba
         would desync four ways (the existing .pcard::after ring
         is the cautionary example — it never re-themes). --- */
  --glass-accent: var(--accent, #74e0c4);

  /* how much accent bleeds into the ink. 10% is the ceiling that
     keeps every lane inside the contrast numbers in §3. */
  --glass-tint: 10%;

  /* --glass-base / --glass-solid are ALSO re-declared on the glass
     classes themselves — see the note directly below §1. Do not
     delete either copy. */
  --glass-base: color-mix(in srgb, var(--glass-accent) var(--glass-tint), rgb(var(--glass-ink)));

  /* NOTE: fill alpha is NOT a token. Apple's size rule inverts the
     naive instinct — larger glass is MORE opaque, not airier — and
     small glass additionally flips light/dark against its backdrop
     in iOS, which CSS cannot do. Opacity is therefore doing the job
     the OS does live, and it is tuned per tier against measured
     contrast in §3 rather than left as a knob to guess at. */

  /* the no-backdrop-filter / reduced-transparency / high-contrast
     collapse target. 97% alpha = readable over anything. */
  --glass-solid: color-mix(in srgb, var(--glass-base) 97%, transparent);

  /* --- Blur scale. DOCUMENTED HERE, but MIRRORED AS LITERALS in
         the filter chains in §3. Two reasons the chain can't be
         var()-driven: (a) the IACVT trap above, (b) MDN BCD #25914
         reports Safari 18.3 rejecting custom properties inside
         -webkit-backdrop-filter entirely. Literals cost one line
         and remove both risks. IF YOU CHANGE THESE, CHANGE THE
         LITERALS IN §3 TOO — they are the ones that render. --- */
  --glass-blur-chrome: 24px;  /* default tier: bars, toasts, pills */
  --glass-blur-panel:  32px;  /* thick tier: modals, sheets, palette */
  --glass-blur-thin:   20px;  /* field tier: over a bounded dark page */
  --glass-blur-clear:   6px;  /* clear variant: over photo/video */
  --glass-sat:        150%;

  /* --- Highlight. Conic, so the light has an ANGULAR position:
         bright at top-left, dead at bottom-right, bounce at ~60%.
         A linear gradient can only do one axis and reads as a
         gradient border, not as a lit edge. --- */
  --glass-rim-hi: .55;
  --glass-rim-lo: .05;

  /* --- Shadow. Inner band = material thickness. Cast = separation.
         Apple scales both with size and makes them adaptive; we
         scale them per tier and leave it at that. --- */
  --glass-depth: .85;
  --glass-cast:  .42;

  /* --- Shape. Apple's default glass shape is a CAPSULE, because
         a half-height radius is self-concentric at any nesting
         depth. We default to 16px because kit chrome is mostly
         rectangular, and ship .dk-glass-capsule for the rest. --- */
  --glass-r: 16px;
  --glass-pad: 0px;           /* set alongside --glass-r for concentric children */

  /* --- Grain. Off by default: it costs a compositing layer and
         only earns its keep on large hero-scale surfaces. --- */
  --glass-grain-o: 0;

  /* the scroll-edge strip height (see §7) */
  --glass-edge-h: 44px;
  --glass-edge-top: 0px;
}

/* ---- RE-DECLARED ON THE ELEMENT. This is not redundancy. -------
   A custom property that contains var() is resolved WHERE IT IS
   DECLARED, and descendants then inherit the already-resolved
   value. So with --glass-base declared only at :root, overriding
   --glass-accent or --glass-tint further down the tree does
   NOTHING — the mix has already been computed against :root's
   accent. Verified in the harness: `.cvok{--glass-accent:var(--live)}`
   produced the unchanged mint tint, and `.dk-toast.ok/.bad` were
   silently dead for the same reason.
   Re-declaring the two derived tokens on the glass classes makes
   them resolve against THAT element's accent and tint, so
   per-component retinting works — while the :root copies above
   stay as the page-wide default and keep --glass-solid available
   to descendant rules (the banned-elements block in §6). */
.dk-glass,
.dk-glass-faux {
  --glass-base:  color-mix(in srgb, var(--glass-accent) var(--glass-tint), rgb(var(--glass-ink)));
  --glass-solid: color-mix(in srgb, var(--glass-base) 97%, transparent);
}


/* ============================================================
   2 · THE MATERIAL — base rule
   Everything here is engine-agnostic and renders identically
   with or without backdrop-filter. The fallback background lives
   HERE, in the base rule, so a non-supporting browser keeps a
   readable panel; §3 only upgrades it. Never invert this into
   `@supports not (...)` — a mis-evaluated condition would then
   leave you with a transparent box and no text contrast.
   ============================================================ */
/* POSITIONING — :where() on purpose, and this is load-bearing.
   The rim/grain pseudos need a positioned ancestor, so the glass
   element must not be static. But a naive `.dk-glass{position:relative}`
   has the SAME specificity as `.dk-header{position:sticky}` /
   `.dk-toast{position:fixed}` in components.css and, because glass.css
   loads later, silently CLOBBERS them — the sticky header stops
   sticking and the toast/FAB/overlay stop being fixed. Verified in
   the harness: the header vanished on scroll.
   :where() is specificity ZERO, so it applies only when the element
   has no position of its own, and every real component keeps
   whatever components.css (or the host page's `nav {}`) declared. */
:where(.dk-glass, .dk-glass-faux) { position: relative; }

.dk-glass,
.dk-glass-faux {
  /* own stacking context: keeps the rim/grain/sheen pseudos from
     escaping, and stops a parent's blend modes reaching in.
     isolation does NOT create a backdrop root — verified. */
  isolation: isolate;
  border-radius: var(--glass-r);
  /* the rim REPLACES the 1px border. Apple: hierarchy comes from
     layout and grouping, not from added backgrounds and borders. */
  border: 0;

  /* SOLID FALLBACK. Overridden in §3 where blur is available. */
  background-color: var(--glass-solid);

  box-shadow:
    /* SHADOW · inner depth band. Big negative spread pulls it into
       a tight strip at the bottom = the look of material thickness. */
    inset 0 -18px 26px -24px rgb(var(--glass-ink) / var(--glass-depth)),
    /* SHADOW · faint internal bounce so the middle isn't dead flat */
    inset 0 0 22px 0 rgb(var(--glass-hi) / .05),
    /* SHADOW · cast. Rule #4 restricts shadows to overlays/FAB —
       glass IS only ever chrome/overlay, so this stays inside it. */
    0 14px 38px -12px rgb(0 0 0 / var(--glass-cast));

  /* Only cheap, compositor-friendly properties. NEVER put
     backdrop-filter in a transition: it re-rasterises the whole
     backdrop every frame, and Chromium issue 40175472 documents
     the blur vanishing outright mid-transition. */
  transition: background-color .22s ease, box-shadow .22s ease;
}

/* HIGHLIGHT · the 1px specular rim.
   Built as a padding ring double-masked with mask-composite.
   Two mask layers — the full box and the content box — XORed
   leave exactly the 1px padding ring, and because the pseudo
   inherits border-radius the ring tracks the curve perfectly at
   any radius including 50% circles and 999px capsules.
   Why not the alternatives:
     border-image  → ignores border-radius entirely.
     padding-box/border-box two-background trick → the border-box
       layer paints UNDER the padding-box layer, so it cannot
       coexist with a translucent fill. Fatal for glass.
   This is already the house idiom: szczleon-portfolio's
   .pcard::after hover ring uses the same XOR. */
.dk-glass::before,
.dk-glass-faux::before {
  content: "";
  position: absolute;
  inset: 0;
  box-sizing: border-box;      /* explicit: inset:0 + padding needs it */
  border-radius: inherit;
  padding: 1px;                /* = rim thickness */
  pointer-events: none;
  z-index: 2;                  /* a 1px ring over the padding edge —
                                  never lands on text */
  background-image: conic-gradient(from 200deg at 50% 50%,
    rgb(var(--glass-hi) / var(--glass-rim-hi)),
    rgb(var(--glass-hi) / .06) 20%,
    rgb(var(--glass-hi) / 0)   40%,
    rgb(var(--glass-hi) / .28) 58%,   /* bounce light off the surface below */
    rgb(var(--glass-hi) / var(--glass-rim-lo)) 76%,
    rgb(var(--glass-hi) / var(--glass-rim-hi)));
  -webkit-mask-image: linear-gradient(#000 0 0), linear-gradient(#000 0 0);
          mask-image: linear-gradient(#000 0 0), linear-gradient(#000 0 0);
  -webkit-mask-clip: content-box, border-box;
          mask-clip: content-box, border-box;
  -webkit-mask-composite: xor;      /* WebKit spelling */
          mask-composite: exclude;  /* standard spelling — both required */
  transition: background-image .22s ease;
}

/* SHADOW/texture · optional grain. z-index:-1 puts it above the
   fill and below content, so it textures the material without
   sitting on the type. Off unless .dk-glass-grain is applied. */
.dk-glass::after,
.dk-glass-faux::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: -1;
  opacity: var(--glass-grain-o);
  mix-blend-mode: overlay;
  /* feTurbulence fractalNoise, inline. No external request.
     baseFrequency .85 = UI-scale grain (not film grain).
     stitchTiles='stitch' is MANDATORY or the 160px tile seams show.
     feColorMatrix saturate 0 kills turbulence's coloured speckle. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}
.dk-glass-grain { --glass-grain-o: .16; }  /* >.20 reads as JPEG artifacting */


/* ============================================================
   3 · ILLUMINATION — the backdrop tiers
   THE FILTER ORDER IS LOAD-BEARING: blur() → saturate() → brightness().
     blur()       averages neighbouring pixels, which mathematically
                  DESATURATES the backdrop (averaging drifts toward
                  grey). This is why DIY glassmorphism looks like
                  muddy frosted plastic.
     saturate()   AFTER the blur restores the chroma the blur
                  destroyed. Before the blur it does nothing useful.
     brightness()  LAST, and it is the entire accessibility story.
                  blur() is a convex combination — it preserves the
                  local mean and CANNOT raise contrast; over a white
                  photo, blurred white is still white. brightness(k)
                  is multiplicative, so it BOUNDS any backdrop at k
                  regardless of content. That bound is what makes
                  the tier auditable: there is a WORST case, and it
                  is the one quoted per tier below, so text that
                  passes there passes at every scroll offset.
                  It BOUNDS contrast — it does not equalise it.
                  (Measured on szczleon.com: the same nav text runs
                  ~8:1 over the hero photo and ~15:1 over the dark
                  field. Design for the floor, never the average.)
                  It must be last, because saturate() can push
                  channels back up after it.

   Values below are literals on purpose (see the note in §1).
   Contrast is computed against the worst possible backdrop —
   pure white — with #e8eaef text, in every lane.
   ============================================================ */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {

  /* ---- DEFAULT · chrome tier ------------------------------------
     The safe default for anything that can travel over unknown
     content: toasts, floating pills, sticky bars, the FAB.
     ⚠ Re-measured 2026-07-25 — the figures previously here dropped
     the 10% accent tint (see .dk-glass-bar). Over a WHITE backdrop:
       worst-case composited surface ≈rgb(101,105,106)
       #e8eaef 4.59:1 ✅ body — but that clears AA by 0.09, not by
                half a point. Do not dim the text or raise the fill
                on this tier without re-measuring.
       #ffffff 5.52:1 ✅
       --dim/--muted #9aa1ad 2.12:1 ❌ — BANNED on this tier.      */
  .dk-glass {
    background-color: color-mix(in srgb, var(--glass-base) 32%, transparent);
    -webkit-backdrop-filter: blur(24px) saturate(150%) brightness(.55);
            backdrop-filter: blur(24px) saturate(150%) brightness(.55);
  }

  /* ---- .dk-glass-thin · FIELD tier ------------------------------
     Cheapest and most refractive — no brightness clamp, so the
     backdrop reads through almost undamaged.
     ⚠ VALID ONLY over a backdrop that is bounded BY DESIGN: the
     page's own dark field (.dk-bgfx + .dk-gridfield, §8). Over
     that the worst composite is ≈rgb(42,50,75) and #e8eaef is
     ~12:1 — even --dim clears 5.5:1. Over a white photo the
     identical tier is 1.4:1. Never let this tier scroll over
     media or user content.                                        */
  .dk-glass-thin {
    --glass-depth: .60;
    --glass-cast: .30;
    background-color: color-mix(in srgb, var(--glass-base) 22%, transparent);
    -webkit-backdrop-filter: blur(20px) saturate(160%);
            backdrop-filter: blur(20px) saturate(160%);
  }

  /* ---- .dk-glass-bar · DARKEST chrome tier ----------------------
     For navigation bars, which are full of 13-14px secondary links
     the default tier cannot carry.

     ⚠ NUMBERS CORRECTED 2026-07-25 — the figures previously here
     (rgb(53,53,53) / 4.69:1) were derived from --glass-ink ALONE
     and dropped the 10% accent tint, which lifts G/B. Re-measured
     against the shipped fill .66 / brightness(.55):
       worst-case surface (over WHITE) ≈rgb(60,68,69)
       #e8eaef  8.25:1 ✅ body
       #9aa1ad  3.82:1 ❌ FAILS AA — do NOT put --dim/--muted here
                unless the backdrop is bounded below ~mid-grey
                (4.5:1 from there down; 7.05:1 over the page's own
                dark field). Over media or a photo, use --txt.

     And the clamp BOUNDS the worst case — it does not equalise it.
     Contrast still swings ~8:1 → 15:1 across a scroll. Design for
     the floor, not the average.

     Still LESS opaque than the 72%-black bar it replaces on
     szczleon.com, because the clamp does work the alpha used to.  */
  .dk-glass-bar {
    background-color: color-mix(in srgb, var(--glass-base) 66%, transparent);
    -webkit-backdrop-filter: blur(24px) saturate(150%) brightness(.55);
            backdrop-filter: blur(24px) saturate(150%) brightness(.55);
  }

  /* ---- .dk-glass-thick · PANEL tier -----------------------------
     Modals, sheets, sidebars, the command palette. Apple: larger
     surfaces are MORE opaque, and glass grows more opaque still
     as engagement deepens (a half sheet dragged to full height).
     ⚠ Re-measured 2026-07-25: worst-case surface ≈rgb(71,76,77).
       #e8eaef 7.22:1 ✅  ·  #ffffff 8.69:1 ✅
       #9aa1ad 3.34:1 ⚠ large text / UI only — never body.        */
  .dk-glass-thick {
    --glass-depth: 1;
    --glass-cast: .58;
    --glass-rim-hi: .65;
    background-color: color-mix(in srgb, var(--glass-base) 46%, transparent);
    -webkit-backdrop-filter: blur(32px) saturate(140%) brightness(.45);
            backdrop-filter: blur(32px) saturate(140%) brightness(.45);
  }

  /* ---- .dk-glass-clear · Apple's SECOND VARIANT -----------------
     Apple ships exactly two variants, Regular and Clear, and says
     they must NEVER be mixed in one interface. Everything above is
     Regular. Clear is highly translucent with NO adaptive
     behaviour, for controls floating over photo/video.
     Apple gates it on three simultaneous conditions:
       1. it sits over media-rich content,
       2. the content tolerates a dimming layer,
       3. the labels on it are BOLD and BRIGHT.
     Because it has no adaptivity it REQUIRES that dim — Apple
     specifies 35% dark. We bake the dim into the background stack
     as its own layer so the two can never be separated by a
     careless author, and we add a brightness(.75) clamp Apple
     doesn't need (the OS samples the backdrop live; CSS can't).
     With the clamp: #ffffff ≈4.96:1 ✅ · #e8eaef ≈4.09:1 ⚠ large.
     Use white 600+ weight, ≥17px. Nothing else.                   */
  .dk-glass-clear {
    --glass-rim-hi: .90;      /* on media the rim is the only edge cue */
    --glass-depth: .40;
    --glass-cast: .34;
    background-color: transparent;
    background-image:
      linear-gradient(rgb(var(--glass-hi) / .12), rgb(var(--glass-hi) / .12)),
      linear-gradient(rgb(0 0 0 / .35), rgb(0 0 0 / .35));   /* the mandatory dim */
    -webkit-backdrop-filter: blur(6px) saturate(180%) brightness(.75);
            backdrop-filter: blur(6px) saturate(180%) brightness(.75);
  }
  /* keep the grain layer working when .dk-glass-clear owns background-image */
  .dk-glass-clear.dk-glass-grain::after { mix-blend-mode: soft-light; }
}


/* ============================================================
   4 · .dk-glass-faux — the no-blur escape hatch
   Same rim, same depth, same tint, ZERO GPU cost, and it works
   in places where a real backdrop-filter is physically dead:
     · inside any ancestor that is a BACKDROP ROOT (opacity<1,
       filter, mask, clip-path, mix-blend-mode, isolation… no:
       isolation is safe, but the rest are not),
     · over an opaque scrim, where there is nothing to sample,
     · on elements small enough (<32px tall) that a 20px blur
       mostly samples its own edge and reads flat anyway,
     · anywhere you are already at the 3-surface perf budget.
   Layered gradients fake the top-lit falloff that the backdrop
   would otherwise supply. Use it freely — it is not rationed. */
.dk-glass-faux {
  --glass-depth: .55;
  --glass-cast: .28;
  background-color: color-mix(in srgb, var(--glass-base) 34%, transparent);
  background-image: linear-gradient(
    180deg,
    rgb(var(--glass-hi) / .085) 0%,
    rgb(var(--glass-hi) / .022) 42%,
    rgb(var(--glass-hi) / 0)    72%,
    rgb(var(--glass-hi) / .035) 100%);   /* bottom bounce */
}
/* faux over a genuinely dark, bounded field can afford to be airier */
.dk-glass-faux.dk-glass-thin {
  background-color: color-mix(in srgb, var(--glass-base) 20%, transparent);
}


/* ============================================================
   5 · SHAPE + NESTING
   ============================================================ */

/* Apple's default glass shape. A capsule's radius is always half
   its height, so it is self-concentric at any nesting depth and
   never needs recomputing — which is exactly why the system
   defaults to it. Use for pills, statusbars, floating tabs. */
.dk-glass-capsule { --glass-r: 999px; }

/* CONCENTRIC CORNERS, as Apple's formula:
   inner radius = outer radius − padding (WWDC25 s356).
   max(0px, …) reproduces Apple's documented edge case — a child
   far from the container corner resolves to SQUARE, which is
   correct, not a bug. calc() can't read a parent's computed
   radius, so declare --glass-r and --glass-pad together on the
   container and children inherit both. */
.dk-glass > .dk-glass-inner,
.dk-glass-faux > .dk-glass-inner {
  border-radius: max(0px, calc(var(--glass-r) - var(--glass-pad)));
}
/* standalone fallback: the component keeps its own capsule when
   it is NOT nested (Apple's "concentric shape + fallback radius") */
.dk-glass-inner { border-radius: 999px; }

/* ---- NEVER GLASS ON GLASS ------------------------------------
   WWDC25 s219, categorical: "always avoid glass on glass."
   Stacking the material makes the interface read as clutter, and
   Apple's positive instruction is to use FILLS, TRANSPARENCY and
   VIBRANCY for the top element so it feels like a thin overlay
   that is PART OF the material.
   It is also physically pointless: an element with backdrop-filter
   is itself a backdrop root, so a nested blur samples only the
   parent's own paint, never the page — a guaranteed flat result
   at full GPU price.
   Enforced structurally, because a later author WILL re-nest it. */
.dk-glass :is(.dk-glass, .dk-glass-faux, .dk-glass-thin, .dk-glass-thick,
              .dk-glass-bar, .dk-glass-clear) {
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
  background-color: rgb(var(--glass-hi) / .07);
  background-image: none;
  box-shadow: none;
}
.dk-glass :is(.dk-glass, .dk-glass-faux)::before { opacity: .45; }  /* thinner rim inside */

/* Any element with a backdrop-filter also creates a CONTAINING
   BLOCK for abs/fixed descendants — verified: a position:fixed
   child with top:0/left:0 lands at the host's box, not the
   viewport. So modals, dropdowns, tooltips and command palettes
   must be portalled to <body>, never rendered inside glass chrome. */


/* ============================================================
   6 · INTERACTION
   Apple's model: THE LIGHT MOVES, THE PANEL DOESN'T. The material
   "illuminates from within" on touch; it does not change shape and
   it does not recolour its border.
   This is the ONE place AGENT-GUIDE rule #4's "state = recolor the
   border" bends — because glass has no border to recolour; the rim
   IS the border, and Apple brightens it instead. Geometry never
   changes, so the spirit of the rule survives.
   ============================================================ */
.dk-glass-hover { cursor: pointer; }

/* hover/focus: fill lifts, rim catches more light. Both are cheap
   compositor-side changes. backdrop-filter is deliberately NOT
   touched — animating blur re-rasterises the backdrop every frame,
   drops mid-range phones to ~15fps, and the resulting scroll jank
   is itself a vestibular trigger. */
.dk-glass-hover:is(:hover, :focus-visible, :focus-within) {
  --glass-rim-hi: .95;
  --glass-rim-lo: .12;
  background-color: color-mix(in srgb, var(--glass-base) 42%, transparent);
}
.dk-glass-thin.dk-glass-hover:is(:hover, :focus-visible, :focus-within) {
  background-color: color-mix(in srgb, var(--glass-base) 30%, transparent);
}
.dk-glass-bar.dk-glass-hover:is(:hover, :focus-visible, :focus-within) {
  background-color: color-mix(in srgb, var(--glass-base) 72%, transparent);
}

/* press: the panel SINKS toward the surface. Not a scale — Apple
   changes the light and the elevation, never the geometry. */
.dk-glass-hover:active { --glass-cast: .22; --glass-rim-hi: .70; }

/* ---- the sheen sweep -------------------------------------------
   Needs a real child, not a pseudo: ::before is the rim and
   ::after is the grain, and a component that collides with either
   is a bug you find in production.
   Markup:  <div class="dk-glass dk-glass-hover"><i class="dk-glass-sheen"></i> … </div>
   z-index:-1 keeps the band under the text — a .55-alpha white
   band sweeping ACROSS type is a legibility failure, not a style. */
.dk-glass-sheen {
  position: absolute;
  inset: -40%;
  z-index: -1;
  pointer-events: none;
  background-image: linear-gradient(115deg,
    transparent 38%,
    rgb(var(--glass-hi) / .14) 50%,
    transparent 62%);
  transform: translateX(-70%);
  opacity: 0;
  transition: transform .7s cubic-bezier(.32,.72,0,1), opacity .3s ease;
}
.dk-glass-hover:is(:hover, :focus-visible, :focus-within) .dk-glass-sheen {
  transform: translateX(70%);
  opacity: 1;
}

/* Marketing-hero only. NEVER on app chrome — an idle animation on
   a persistently-visible blurred surface is the worst combination
   of GPU cost and distraction in this whole file. */
@keyframes dkGlassSweep { to { transform: translateX(70%); } }
.dk-glass-sheen-auto { opacity: 1; animation: dkGlassSweep 2.8s cubic-bezier(.45,0,.55,1) infinite; }

/* ---- focus ----------------------------------------------------
   A single-colour ring cannot satisfy SC 1.4.11 / 2.4.13 on glass,
   because the ring is judged against BOTH the glass surface and
   the unbounded content one pixel outside it. Measured on the
   chrome tier: a white ring is 6.9:1 on the glass but 1.0:1 on a
   white photo; a black halo is 19.8:1 on white but 2.9:1 on the
   glass. Neither passes alone — together one always wins. */
.dk-glass :focus-visible,
.dk-glass-faux :focus-visible,
.dk-glass:focus-visible,
.dk-glass-faux:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgb(0 0 0 / .85);   /* restored under forced-colors in §9 */
}

/* ---- text law on glass ----------------------------------------
   Glass has exactly ONE text colour. Measured on the chrome tier:
     #ffffff 6.1:1 ✅ · #e8eaef 5.0:1 ✅ · #d2d2d2 4.5:1 (the floor)
     #9aa1ad 2.2:1 ❌ · #7a828e 1.7:1 ❌
     accent as TEXT: 2.1–3.7:1 depending on lane — fails in three
     of the portfolio's four lanes, so it can never be validated once.
   The lightest passing grey is visually indistinguishable from
   primary, so a "dim tier on glass" is a fiction. Express hierarchy
   with size, weight and letterspacing. Accent survives only as a
   non-text element — a rim, a dot, an underline — at 3:1.
   :where() = zero specificity, so any deliberate override wins. */
.dk-glass :where(p, li, span, small, td, dd, figcaption, .sub, .dim, .muted, .faint) {
  color: inherit;
  /* opacity on text over DARK glass blends the glyph TOWARD the
     dark surface — it LOWERS contrast. It is a bug, not a style. */
  opacity: 1;
}
.dk-glass :where(a) {
  color: inherit;
  text-decoration-color: var(--glass-accent);   /* accent as a 3:1 non-text cue */
  text-underline-offset: 3px;
}
/* Grayscale antialiasing renders thinner than subpixel and eats
   the contrast margin a translucent surface doesn't have to spare.
   Opt-in, because switching it mid-page is visible on 1x monitors. */
.dk-glass-crisp { -webkit-font-smoothing: auto; }

/* HARD BAN — these never sit on glass. Long-form prose (NN/g: if
   the page's purpose is detailed written information, glass is not
   its background), dense/tabular data (comparing two cells means
   comparing two different backdrops), code and mono (thin strokes),
   and FORM INPUTS (SC 1.4.11 needs a 3:1 field boundary a
   translucent rim cannot guarantee against unknown content, and
   translucency collapses filled/empty and value/placeholder).
   Correct modal pattern: OPAQUE body + glass HEADER. */
.dk-glass :where(table, input, select, textarea, pre, code, article, .prose) {
  background-color: var(--glass-solid);
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}


/* ============================================================
   7 · SCROLL EDGE EFFECT
   The single thing that makes web glass read as Apple glass.
   The common web pattern — "nav gains a solid background on
   scroll" — is the opposite of what iOS does. Apple keeps the
   MATERIAL constant and dissolves the CONTENT passing underneath,
   blurring it and reducing its opacity so the bar visually floats
   above a moving plane instead of clipping it.

   ⚠ IT CANNOT BE .dk-header::after. The header has a
   backdrop-filter, which makes it a backdrop root for its own
   descendants — a pseudo-element hanging below the header would
   sample only the header's paint (nothing) and render dead.
   The strip must therefore be a BODY-LEVEL element, positioned
   fixed just under the bar:
       <div class="dk-scrolledge" aria-hidden="true"></div>
   as a direct child of <body>, before the header.

   ONE PER VIEW. Apple: scroll edge effects are NOT decorative,
   they clarify where UI and content meet, and must not be used
   where there are no floating UI elements. No section-boundary
   fades, no bottom-of-viewport fades, no card edge fades.
   ============================================================ */
.dk-scrolledge {
  position: fixed;
  left: 0; right: 0;
  top: calc(var(--glass-edge-top) + env(safe-area-inset-top));
  height: var(--glass-edge-h);
  z-index: 19;                  /* under .dk-header (20), over content */
  pointer-events: none;
  -webkit-backdrop-filter: blur(10px);
          backdrop-filter: blur(10px);
  /* soft style (Apple's default): a gradual fade, so there is no
     hard cut line where the blur stops. The hard style is macOS-only. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
}


/* ============================================================
   8 · BACKDROP PRIMITIVES
   Glass over a FLAT background is invisible — blurring flat black
   yields flat black, and you pay full GPU price for nothing.
   No kit theme ships a backdrop: .dk-base is a solid var(--bg),
   which is exactly why .dk-header's existing blur(10px) shows no
   frost in components.html.
   These are szczleon-portfolio's .bgfx and .grid-field, normalised.
   Put both at the top of <body>. They are FIXED SIBLINGS at
   negative z-index — never wrappers — so nothing they do can
   become a backdrop root for the glass above them.
   ============================================================ */
.dk-bgfx {
  position: fixed; inset: 0; z-index: -2; pointer-events: none;
  transition: background-image .6s ease;   /* re-theme crossfade */
  background-color: var(--bg, #07080a);
  background-image:
    radial-gradient(900px 500px at 78% -8%,
      color-mix(in srgb, var(--accent2, #6aa3ff) 12%, transparent), transparent 60%),
    radial-gradient(800px 520px at 8% 12%,
      color-mix(in srgb, var(--accent, #74e0c4) 10%, transparent), transparent 60%);
}
.dk-gridfield {
  position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: .5;
  background-image:
    linear-gradient(rgb(var(--glass-hi) / .08) 1px, transparent 1px),
    linear-gradient(90deg, rgb(var(--glass-hi) / .08) 1px, transparent 1px);
  background-size: 46px 46px;
  /* radial mask keeps the grid from tiling to the viewport edges */
  -webkit-mask-image: radial-gradient(circle at 50% 30%, #000 0%, transparent 78%);
          mask-image: radial-gradient(circle at 50% 30%, #000 0%, transparent 78%);
}


/* ============================================================
   9 · COMPONENT RECIPES
   RE-SKIN ONLY. Every rule below changes surface, never layout —
   sizes, paddings, positions and z-indexes all come from
   components.css and are left alone.
   Usage: add .dk-glass (+ a tier) alongside the existing class.
   ============================================================ */

/* ---- sticky header --------------------------------------------
   .dk-header carries 13-14px --dim nav links, so it needs the
   secondary-text-safe tier, not the default one. Keeps sticky,
   z-index 20, the safe-area padding and the bottom hairline. */
.dk-header.dk-glass {
  --glass-r: 0px;                        /* full-bleed bar: no corners */
  --glass-cast: 0;                       /* a hairline separates it, not a shadow */
  border-bottom: 1px solid rgb(var(--glass-hi) / .10);
}
.dk-header.dk-glass::before { content: none; }   /* no rim on a full-bleed bar */
/* nested chips inside the bar: fill + hairline, never a second blur */
.dk-header.dk-glass .dk-nav button {
  background: rgb(var(--glass-hi) / .06);
  border-color: rgb(var(--glass-hi) / .12);
}
.dk-header.dk-glass .dk-nav button.active {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent, #05130f);
}

/* ---- modal ------------------------------------------------------
   PICK ONE — you cannot have both:
   (a) .dk-ov.dk-glass-scrim → the iOS sheet idiom, blur the whole
       page behind the scrim. The nested guard in §5 then correctly
       forces .dk-box opaque, because .dk-ov is now a backdrop root.
   (b) .dk-box.dk-glass.dk-glass-thick → a glass panel; drop the
       scrim alpha so there is something left to see through. */
.dk-ov.dk-glass-scrim {
  background: rgb(0 0 0 / .38);
  -webkit-backdrop-filter: blur(18px) saturate(120%) brightness(.62);
          backdrop-filter: blur(18px) saturate(120%) brightness(.62);
  /* dkFade animates the overlay's OPACITY 0→1; while opacity<1 the
     overlay is a backdrop root for its children, so a glass .dk-box
     inside would render flat and then visibly snap on at the end.
     Fade the box instead. */
  animation: none;
}
.dk-ov:has(> .dk-box.dk-glass) { background: rgb(0 0 0 / .34); animation: none; }
@keyframes dkGlassIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.dk-box.dk-glass {
  --glass-r: 16px;                      /* matches components.css */
  --glass-pad: 18px;                    /* = its padding → concentric children */
  border: 0;
  box-shadow:
    inset 0 -18px 26px -24px rgb(var(--glass-ink) / 1),
    inset 0 0 22px 0 rgb(var(--glass-hi) / .05),
    var(--shadow-overlay, 0 24px 60px -30px rgb(0 0 0 / .8));
  /* the element's own opacity is safe — it only roots its
     DESCENDANTS' backdrops, not its own filter. */
  animation: dkGlassIn .22s cubic-bezier(.32,.72,0,1);
}
/* Apple's zero-cost modal pattern: opaque body, glass header. */
.dk-box .dk-glass-head {
  margin: -18px -18px 12px; padding: 14px 18px;
  border-bottom: 1px solid rgb(var(--glass-hi) / .10);
}

/* ---- command palette --------------------------------------------
   Keeps max-width 560, radius 14, overflow:hidden, the shadow.
   .res is a SCROLL CONTAINER — never blur it, or the backdrop is
   resampled on every scroll frame inside an already-blurred panel. */
.dk-cmd.dk-glass {
  --glass-r: 14px;
  border: 0;
  box-shadow:
    inset 0 -18px 26px -24px rgb(var(--glass-ink) / 1),
    var(--shadow-overlay, 0 24px 60px -30px rgb(0 0 0 / .8));
}
.dk-cmd.dk-glass input { border-bottom-color: rgb(var(--glass-hi) / .12); }
.dk-cmd.dk-glass .res { -webkit-backdrop-filter: none; backdrop-filter: none; }
/* the default hover fill is an OPAQUE var(--panel2) — on glass that
   reads as a solid bar sliding across frosted glass. Translucent. */
.dk-cmd.dk-glass .it:hover,
.dk-cmd.dk-glass .it.sel { background: rgb(var(--glass-hi) / .09); }

/* ---- toast ------------------------------------------------------
   Transient and small, so it can afford the thick tier. Position,
   transform-centring and the show/hide transition are untouched. */
.dk-toast.dk-glass { --glass-r: 11px; --glass-cast: .5; border: 0; }
.dk-toast.dk-glass.ok  { --glass-accent: var(--accent2, #46d27a); }
.dk-toast.dk-glass.bad { --glass-accent: var(--bad, #ff5f57); }

/* ---- FAB --------------------------------------------------------
   The gradient IS the FAB's job, so full glass would erase it.
   Correct form: a glass ring around a gradient core. Keeps 52px,
   the fixed position, the safe-area offset and the :active scale. */
.dk-fab.dk-glass {
  --glass-r: 50%;
  --glass-rim-hi: 1;
  background-color: transparent;
  background-image:
    radial-gradient(circle at 50% 50%,
      var(--accent, #74e0c4) 0 58%, transparent 62%);   /* the core */
  color: var(--on-accent, #05130f);
}
/* the badge's 2px opaque var(--bg) ring instantly betrays glass */
.dk-fab.dk-glass .badge { border-color: rgb(var(--glass-hi) / .22); }

/* ---- pills + statusbar ------------------------------------------
   Both are ~22-30px tall. A 20px blur on a 28px object mostly
   samples its own edge and reads as a flat lighter pill for full
   GPU price — so these get FAUX glass, capsule shape, no filter.
   This is a deliberate downgrade, not an oversight. */
.dk-pill.dk-glass,
.dk-statusbar.dk-glass {
  --glass-r: 999px;
  --glass-cast: .18;
  --glass-depth: .40;
  border: 0;
  background-color: color-mix(in srgb, var(--glass-base) 28%, transparent);
  background-image: linear-gradient(180deg,
    rgb(var(--glass-hi) / .085), rgb(var(--glass-hi) / .015) 60%, rgb(var(--glass-hi) / .03));
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}
.dk-pill.dk-glass::before,
.dk-statusbar.dk-glass::before { padding: .5px; }   /* hairline rim at pill scale */

/* ---- segmented control ------------------------------------------
   The iOS liquid-glass signature: a translucent trough with a
   specular travelling lozenge. Apple's performance rule and the
   no-glass-on-glass rule agree — the material goes on the TRAY,
   never on the buttons. --glass-pad makes the lozenge concentric. */
.dk-seg.dk-glass {
  --glass-r: 12px;
  --glass-pad: 2px;                     /* = its padding, per components.css */
  border: 0;
  background-color: color-mix(in srgb, var(--glass-base) 30%, transparent);
  background-image: linear-gradient(180deg,
    rgb(var(--glass-hi) / .05), rgb(var(--glass-hi) / .01));
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}
.dk-seg.dk-glass button { border-radius: max(0px, calc(var(--glass-r) - var(--glass-pad))); }
.dk-seg.dk-glass button.active {
  background: var(--accent);
  box-shadow: 0 1px 2px rgb(0 0 0 / .35), inset 0 1px 0 rgb(var(--glass-hi) / .35);
}

/* explicit class equivalent of the :has() rule above, for engines
   without :has() and for authors who prefer to be told, not sniffed */
.dk-ov.dk-glass-ov { background: rgb(0 0 0 / .34); animation: none; }


/* ============================================================
   10 · GUARDS — none of these are optional
   The collapse chain is SUBTRACTIVE: glass is the default and
   each signal overrides it. Do NOT invert it into the additive
   `@media (prefers-reduced-transparency: no-preference){ .glass{…} }`
   pattern that Chrome's own launch post recommends — Safari does
   not know that feature, so the query never matches there and
   glass silently disappears on every Apple device.
   ============================================================ */

/* ---- 10a · no backdrop-filter ---------------------------------
   MANDATORY, not a graceful degrade. Without the filter there is
   no brightness clamp, and a 32% tint over white is 1.7:1 — an
   unreadable page, not a plainer one. The base rule in §2 already
   holds the solid fallback; this only strips decoration that
   assumed a backdrop, and covers hardware-acceleration-off.
   The `or` form is REQUIRED: Chromium does not support the
   -webkit- property AT ALL (verified — CSS.supports false, the
   declaration is rejected outright), and Safari <18 supports only
   the -webkit- one. Either alone excludes an engine. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .dk-glass {
    background-color: var(--glass-solid);
    background-image: none;
    border: 1px solid rgb(var(--glass-hi) / .12);
  }
  .dk-glass::after { content: none; }
  .dk-scrolledge { display: none; }
}

/* ---- 10b · reduced transparency -------------------------------
   Chrome/Edge 118+ only, ~73% global. WebKit DECLINED to
   implement it as a fingerprinting vector — which is the exact
   inverse of where the OS setting lives (macOS/iOS ▸ Accessibility
   ▸ Display ▸ Reduce Transparency). Hence 10c and 10d. */
@media (prefers-reduced-transparency: reduce) {
  :is(.dk-glass, .dk-glass-faux) {
    -webkit-backdrop-filter: none !important;
            backdrop-filter: none !important;
    background-color: var(--glass-solid) !important;
    background-image: none !important;
    border: 1px solid rgb(var(--glass-hi) / .20) !important;
    box-shadow: none !important;
  }
  :is(.dk-glass, .dk-glass-faux)::before,
  :is(.dk-glass, .dk-glass-faux)::after { content: none !important; }
  .dk-glass-sheen, .dk-scrolledge { display: none !important; }
  .dk-ov.dk-glass-scrim { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; background: rgb(0 0 0 / .8) !important; }
}

/* ---- 10c · increased contrast ---------------------------------
   THE SIGNAL THAT COVERS SAFARI. 93% global (Safari 14.1+, iOS
   14.5+, Chrome 96+, Firefox 101+). macOS "Increase Contrast" also
   puts the system into reduced transparency, so treating `more` as
   a transparency signal is correct, not a workaround — and a user
   asking for contrast should not be handed a 5:1 surface when a
   15:1 one is available.
   Note the border goes SOLID: a 12%-white hairline is ~1.3:1 and
   is invisible to precisely the person who asked for contrast.
   This also lands the component back on rule #4's flat grammar. */
@media (prefers-contrast: more) {
  :is(.dk-glass, .dk-glass-faux) {
    -webkit-backdrop-filter: none !important;
            backdrop-filter: none !important;
    background-color: var(--bg, #07080a) !important;
    background-image: none !important;
    border: 1px solid #fff !important;
    box-shadow: none !important;
  }
  :is(.dk-glass, .dk-glass-faux)::before,
  :is(.dk-glass, .dk-glass-faux)::after { content: none !important; }
  .dk-glass-sheen, .dk-scrolledge { display: none !important; }
  .dk-glass :where(a) { text-decoration-thickness: 2px; }
}
/* `less` means less contrast, not more glass. Softening a 5:1
   surface further is how you arrive at 3:1. Deliberately empty. */

/* ---- 10d · the user toggle ------------------------------------
   Given WebKit's non-support of 10b, this is the ONLY mechanism
   that reaches 100% of users. NN/g recommends it explicitly and
   Apple shipped a glass-intensity slider in iOS 27 after nine
   months of Liquid Glass legibility complaints.
   Wire a control to html[data-glass="off"] + localStorage, and set
   the attribute from an inline <script> in <head> so it does not
   flash. No JS ships in this file. */
html[data-glass="off"] :is(.dk-glass, .dk-glass-faux) {
  -webkit-backdrop-filter: none !important;
          backdrop-filter: none !important;
  background-color: var(--glass-solid) !important;
  background-image: none !important;
  border: 1px solid rgb(var(--glass-hi) / .20) !important;
  box-shadow: none !important;
}
html[data-glass="off"] :is(.dk-glass, .dk-glass-faux)::before,
html[data-glass="off"] :is(.dk-glass, .dk-glass-faux)::after { content: none !important; }
html[data-glass="off"] .dk-glass-sheen,
html[data-glass="off"] .dk-scrolledge { display: none !important; }

/* ---- 10e · forced colors (Windows High Contrast) ---------------
   WHAT THE UA DOES FOR YOU: remaps color / background-color
   (including rgba and transparent) / border-color / outline-color
   to system colours, forces box-shadow AND text-shadow to none,
   and forces background-image — i.e. EVERY GRADIENT — to none.
   WHAT IT DOES NOT DO: backdrop-filter, filter and opacity are not
   in the adjustment list. The blur keeps computing, keeps costing
   GPU, and keeps creating a containing block. Kill it yourself.
   Two consequences already designed around: the rim is a
   background-image and therefore vanishes here (fine, borders
   survive), and anything that MEANT something via box-shadow has
   to re-say it with an outline. */
@media (forced-colors: active) {
  :is(.dk-glass, .dk-glass-faux) {
    -webkit-backdrop-filter: none !important;
            backdrop-filter: none !important;
    background: Canvas !important;
    color: CanvasText !important;
    border: 1px solid ButtonBorder !important;
    box-shadow: none !important;
  }
  :is(.dk-glass, .dk-glass-faux)::before,
  :is(.dk-glass, .dk-glass-faux)::after { content: none !important; }
  .dk-glass-sheen, .dk-scrolledge { display: none !important; }
  /* the two-tone focus ring loses its halo (box-shadow is stripped);
     a system-coloured ring is guaranteed to contrast because the
     whole palette is now the user's */
  .dk-glass :focus-visible, .dk-glass:focus-visible,
  .dk-glass-faux :focus-visible, .dk-glass-faux:focus-visible {
    outline: 3px solid Highlight !important;
    outline-offset: 2px;
    box-shadow: none !important;
  }
  .dk-glass a { color: LinkText !important; text-decoration: underline; }
}
/* ⚠ NEVER `forced-color-adjust: none` to preserve the look. It also
   disables the UA text backplate — the thing keeping text legible
   over images in high contrast — producing a worse result than
   doing nothing. Surrender to Canvas/CanvasText/ButtonBorder. */

/* ---- 10f · reduced motion -------------------------------------
   Kill the sweep and every transition. Do NOT remove the blur:
   blur is a static visual property, not motion, and stripping it
   also strips the brightness clamp — you would trade away the
   contrast guarantee to satisfy a motion preference. */
@media (prefers-reduced-motion: reduce) {
  :is(.dk-glass, .dk-glass-faux),
  :is(.dk-glass, .dk-glass-faux)::before,
  .dk-glass-sheen, .dk-bgfx {
    transition: none !important;
    animation: none !important;
  }
  .dk-glass-sheen, .dk-glass-sheen-auto { display: none !important; }
  .dk-box.dk-glass { animation: none !important; }
}

/* ---- 10g · rounded-corner + sticky guards ----------------------
   Honest status of the folklore:
   · The classic WebKit bug (bugzilla 158807 — blur leaking square
     past a rounded corner) was RESOLVED in 2016. Do not cargo-cult
     `transform: translateZ(0)` for it; it buys a compositing layer
     and fixes nothing.
   · What IS still open is Mozilla 1803813: in Firefox, a parent
     with BOTH border-radius AND overflow != visible containing a
     position:sticky child kills that child's backdrop-filter
     entirely. It renders fine in Chrome, so it passes local review
     and fails only in Firefox.
   · Old iOS can still bleed the filtered backdrop a hair past the
     curve. clip-path re-cuts the exact rounded box and hard-stops
     it. Opt-in, because clip-path CLIPS — never put it on anything
     with an overflowing child (the FAB badge, dropdown menus). */
.dk-glass-clip { clip-path: inset(0 round var(--glass-r)); }
/* Firefox sticky fix: keep the rounded+overflow combination off the
   ANCESTOR of a sticky glass bar; clip the bar itself instead. */
.dk-glass-sticky { position: sticky; top: 0; border-radius: var(--glass-r); overflow: clip; }

/* ---- 10h · the ancestor contract ------------------------------
   NOT ENFORCEABLE IN CSS, and NOT DETECTABLE AT RUNTIME —
   getComputedStyle still reports blur(24px) on an element that is
   rendering completely unblurred, so this class of bug is invisible
   to tests and only catchable by eye. It has to be a convention.

   NEVER put any of these on an ANCESTOR of a glass surface:
     opacity < 1  ·  filter  ·  mask / mask-image / mask-border
     clip-path    ·  mix-blend-mode  ·  backdrop-filter
     will-change naming any of the above
   Each establishes a BACKDROP ROOT, which truncates what the glass
   can sample. The panel still paints — it just blurs nothing, so it
   degrades to a flat translucent rectangle rather than visibly
   breaking. `filter: saturate(1)` and `opacity: .999` are visual
   no-ops that still kill it.
   The classic trap: a scroll-reveal wrapper that animates opacity.
   Put the glass ON the revealing element, never inside it — or use
   .dk-glass-faux there, which is immune.

   SAFE, contrary to widespread advice: transform, will-change:
   transform, isolation: isolate, overflow: hidden. All verified
   non-breaking. */
