/* Liquid Glass — MATERIAL layer.
 *
 * The glass itself: the scheme tokens, the `.lg` panel styling every tier
 * shares, and the proxy/flat fallbacks. This is the design-system half — it has
 * no opinion about what a Button or a Sidebar is, and a project can adopt it on
 * its own and build its own components on top.
 *
 * Restyling the whole library means overriding the tokens below on `:root`.
 * They are named for their ROLE, never for a colour, so an override never ends
 * up fighting its own name.
 *
 * Pairs with `components.css`. Import both, or import this alone if you only
 * want the material.
 */

/* The rim as one shadow list, so `.lg` and the two knobs cannot drift from it
 * or from each other. Offsetting on ONE axis at a time is what puts each
 * hairline on two opposite edges and lets it fade around the corners the way
 * the reference does; a `1px 1px` offset puts it on a corner pair instead.
 *
 * **Declared twice on purpose, and it is not a duplicate.** A custom property's
 * `var()` references are substituted where the property is DECLARED, and the
 * result is what inherits. Declared only on `:root`, every panel in the library
 * inherited a list frozen at the root defaults — 0.45 white and 0.22 black —
 * and the per-instance `--lg-rim-light`/`--lg-rim-dark` the engine writes on
 * each host were substituted into nothing. It rendered, it looked plausible,
 * and every variant's `rim` knob was silently doing nothing. Re-declaring on
 * `.lg` re-substitutes against that element's own values; the `:root` copy
 * stays as the fallback for anything outside a panel. */
:root, .lg {
  --lg-rim:
    inset 0 1px 0 var(--lg-rim-light),
    inset 0 -1px 0 var(--lg-rim-light),
    inset 1px 0 0 var(--lg-rim-dark),
    inset -1px 0 0 var(--lg-rim-dark);
}

/* Liquid Glass — panel styling shared by all tiers. */

:root {
  /* The proxy tier refracts THIS. Must match the page backdrop.
     background-attachment:fixed locks it to the viewport, so the slice
     showing through a panel lines up with what is actually behind it. */
  --lg-page-bg: none;
  --lg-page-bg-color: #0b0d12;

  /* The edge, and it is TWO features of opposite polarity on opposite axes —
     not one hairline with an alpha.

     Measured off `ref/image-1786231405608.webp`, a home screen widget over flat
     sky, 391x388 at r=60, light scheme (`npm run probe:edge -- --size panel`):

                        bright, over the interior   dark, under the backdrop
       top / bottom              +58 / +52                   -5 / -1
       left / right               +4 / ~0                   -56 / -59

     So the horizontal edges carry a bright specular hairline and the vertical
     ones carry a dark contour, each fading around the corners into the other.
     This is Apple's own description of Golden Gate — "darkened edges and
     brighter specular highlights" — and only the second half used to be here:
     both tokens were white, offset to top-left and bottom-right, which is a
     white line on all four sides. On a light backdrop that reads as a drawn
     outline rather than a lit edge, and no alpha fixes it, because the missing
     term is not dim white, it is dark. That is issue #20.

     Variants raise both via the `rim` knob; these are the Regular/Clear values.
     The dark one inverts by scheme — a dark hairline on a dark page is
     invisible, and there the far edge becomes a second, dimmer highlight. */
  --lg-rim-light: rgba(255, 255, 255, 0.45);
  --lg-rim-dark: rgba(0, 0, 0, 0.22);
  --lg-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);

  /* How translucent the glass is allowed to be. 1 = the material as designed,
   * 0 = fully opaque. Every `.lg` blends toward `--lg-surface` by the shortfall.
   *
   * **This is a USER setting, not a design token**, and that is the whole point.
   * Apple shipped Liquid Glass in iOS 26 and then spent a year walking it back:
   * a Clear/Tinted toggle in 26.1, a Lock Screen Glass/Solid choice plus a
   * transparency slider in 26.2, and at WWDC26 a continuous opacity slider for
   * iOS 27 running from fully opaque to completely clear. The lesson is not
   * "less glass" — it is that how much of it a person can tolerate is theirs to
   * decide, and no amount of tuning by us substitutes for the control.
   *
   * `material: 'solid'` is the per-component version of the same idea. This is
   * the global one: wire it to a slider in your own settings and the whole
   * library follows, live, with no rebuild and no JS.
   *
   *   document.documentElement.style.setProperty('--lg-translucency', 0.4);
   *
   * At 0 the engine stops rendering as well as showing: every panel drops to
   * the `none` tier — no map, no SVG filter, no backdrop-filter — because an
   * opaque surface paints over all of it anyway. It watches the root's `style`
   * attribute for exactly the write above, so the slider works with no other
   * wiring; change the value from a stylesheet instead and call `refresh()`.
   */
  --lg-translucency: 1;
}

/* Reduce Transparency is a stronger statement than the slider, and it is the
 * OS asking on the user's behalf. The engine separately forces the `flat` tier
 * here (no refraction at all); this makes the tint itself nearly opaque, which
 * is the part the tier cannot do. */
@media (prefers-reduced-transparency: reduce) {
  :root { --lg-translucency: 0.08; }
}

/* ------------------------------------------------------------------- motion */
/* Springs, as CSS. Generated from `motion.js` — a test regenerates these and
 * fails if they drift, because a hand-edited spring is not a spring.
 *
 * A cubic-bézier cannot BE a spring: four control points describe a cubic, and
 * a spring is a decaying exponential times a sinusoid that overshoots its
 * target. `linear()` takes arbitrary samples and accepts values above 1
 * (verified in Chromium, not assumed), so the real solution fits.
 *
 * **The curve and the duration are independent.** A spring's step response
 * scales exactly in time with its duration at a fixed damping ratio, so
 * playing one of these faster is not an approximation of a shorter spring — it
 * IS the shorter spring. Pick a shape and a speed separately; every
 * combination is physical.
 *
 *   smooth  bounce 0     no overshoot at all. Apple's default, and the right
 *                        answer unless you can say why not.
 *   snappy  bounce 0.15  0.6% overshoot. Reads as crisp rather than bouncy.
 *   bouncy  bounce 0.3   4.6% overshoot. Noticeably springy — for something
 *                        arriving, never for something leaving.
 *
 * Above bounce 0.4 it stops being motion and becomes an effect (WWDC23). */
:root {
  --lg-dur-press: 180ms;
  --lg-dur-fast: 320ms;
  --lg-dur-base: 520ms;
  --lg-dur-slow: 720ms;
  --lg-ease-smooth: linear(0,0.0086,0.0315,0.0649,0.1057,0.1514,0.2001,0.2503,0.3008,0.3507,0.3993,0.4461,0.4909,0.5332,0.5731,0.6104,0.6452,0.6775,0.7073,0.7348,0.7601,0.7832,0.8044,0.8237,0.8412,0.8572,0.8717,0.8848,0.8967,0.9074,0.9171,0.9258,0.9336,0.9407,0.947,0.9527,0.9578,0.9624,0.9664,0.9701,0.9734,0.9763,0.9789,0.9813,0.9833,0.9852,0.9869,0.9883,0.9896,0.9908,0.9918,0.9928,0.9936,0.9943,0.995,0.9955,0.996,0.9965,0.9969,0.9973,0.9976,0.9979,0.9981,0.9983,1);
  --lg-ease-snappy: linear(0,0.008,0.0295,0.0617,0.1018,0.1478,0.1976,0.2498,0.3031,0.3564,0.409,0.4601,0.5094,0.5563,0.6007,0.6423,0.6812,0.7171,0.7503,0.7806,0.8083,0.8334,0.856,0.8763,0.8945,0.9107,0.925,0.9376,0.9486,0.9583,0.9666,0.9738,0.98,0.9852,0.9896,0.9933,0.9964,0.9989,1.0009,1.0025,1.0038,1.0047,1.0054,1.0059,1.0061,1.0063,1.0063,1.0062,1.006,1.0058,1.0055,1.0052,1.0049,1.0046,1.0042,1.0039,1.0036,1.0033,1.003,1.0027,1.0024,1.0021,1.0019,1.0017,1);
  --lg-ease-bouncy: linear(0,0.0108,0.0403,0.0841,0.1387,0.2008,0.2677,0.337,0.4069,0.4758,0.5423,0.6057,0.6651,0.7201,0.7703,0.8157,0.8563,0.892,0.9231,0.9499,0.9725,0.9914,1.0068,1.0191,1.0287,1.0358,1.0408,1.0439,1.0456,1.046,1.0453,1.0438,1.0417,1.0391,1.0362,1.0331,1.0299,1.0267,1.0236,1.0205,1.0177,1.015,1.0125,1.0102,1.0081,1.0063,1.0047,1.0033,1.0021,1.0011,1.0003,0.9996,0.999,0.9986,0.9983,0.9981,0.998,0.9979,0.9979,0.9979,0.998,0.9981,0.9982,0.9984,1);
}

/* One rule, and every token-driven transition in the library stops.
 *
 * Apple's guidance and the WCAG position agree: REMOVE the motion, do not
 * soften it — a gentler version of a vestibular trigger is still a trigger.
 * 1ms rather than 0 so `transitionend` still fires and nothing that waits on
 * it hangs. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --lg-dur-press: 1ms;
    --lg-dur-fast: 1ms;
    --lg-dur-base: 1ms;
    --lg-dur-slow: 1ms;
  }
}

/* ---------------------------------------------------------------- content */
/* Ink and fills, per resolved scheme.
 *
 * Everything above this line is the MATERIAL — the glass itself, which the
 * engine drives. Everything below is what sits ON the glass, and it was the
 * library's biggest gap: every component hardcoded `color: #fff`, so the whole
 * set was dark-mode-only. In light scheme the sidebar rendered white text on a
 * pale panel with the section headings essentially invisible.
 *
 * These are deliberately alpha-on-currentColor rather than fixed greys: glass
 * is translucent, so a solid fill reads as a sticker on it (the same lesson the
 * rim taught). Alpha lets whatever is behind keep showing through.
 *
 * A consumer restyles the library by overriding these six on :root — that is
 * the whole theming contract, and it is why they are named for their ROLE
 * rather than for a colour. */
:root,
.lg[data-lg-scheme='light'],
[data-scheme='light'] {
  --lg-ink: #10121a;
  /* Both dims FIRM UP as translucency falls — the second half of Tinted mode,
     which "increases opacity while adding more contrast". Opacity alone already
     buys most of it, because an opaque surface is a predictable one: measured,
     a light panel goes from 3.17:1 over a mid backdrop at full glass to 5.18:1
     at zero. The ramp is what closes the middle, where the surface is neither
     glass nor paper and the ratio dips under 4.5 on the way through.

     Declared on `:root` only, deliberately. These tokens are documented as the
     consumer's to override there, and repeating them on `.lg` — as `--lg-rim`
     has to, for a different reason — would beat any override and silently take
     the restyling hook away. The cost is that a subtree carrying its own
     `--lg-translucency` keeps the root's ink, which is the same scope the
     setting is documented at. */
  --lg-ink-dim: rgb(16 18 26 / calc(0.62 + (1 - var(--lg-translucency)) * 0.14));
  /* 0.50 and not the 0.42 this shipped with. `--lg-ink-faint` draws scrollbar
     thumbs and separators, which WCAG counts as UI components at 3:1, and 0.42
     measured 2.75:1 against a fully OPAQUE surface — the most favourable case
     there is. It never met the floor at any translucency, on any backdrop.
     0.50 measures 3.49:1 there, and still sits clear of `--lg-ink-dim` at 0.62
     so the hierarchy survives. `npm run probe:contrast`. */
  --lg-ink-faint: rgb(16 18 26 / calc(0.5 + (1 - var(--lg-translucency)) * 0.12));
  --lg-fill: rgba(16, 18, 26, 0.07);
  --lg-fill-strong: rgba(255, 255, 255, 0.55);
  --lg-hairline: rgba(16, 18, 26, 0.14);

  /* ── Type ───────────────────────────────────────────────────────────────
     Apple's four system faces, reached the only correct way: by GENERIC, not
     by file. `ui-sans-serif` resolves to SF Pro on Apple platforms, `ui-serif`
     to New York, `ui-monospace` to SF Mono, `ui-rounded` to SF Pro Rounded —
     the real thing, already installed, correctly hinted, and with the optical
     sizing (SF Text below 20pt, SF Display above) that only the system can do.

     **Nothing here may ever become an @font-face.** Apple's fonts are licensed
     for designing and developing for Apple platforms; self-hosting the files to
     serve them to every visitor is not that, and this is a product that gets
     sold. Naming a family a browser may already have is not distribution.

     The named fallbacks after each generic are for older engines that do not
     know `ui-*` yet, and for Windows and Android, where these names simply do
     not resolve and the platform's own face is the right answer anyway. */
  --lg-font: ui-sans-serif, system-ui, -apple-system, 'SF Pro Text', 'SF Pro Display',
    'Segoe UI Variable Text', 'Segoe UI', Roboto, sans-serif;
  --lg-font-mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas,
    'Liberation Mono', monospace;
  --lg-font-serif: ui-serif, 'New York', Georgia, 'Times New Roman', serif;
  --lg-font-rounded: ui-rounded, 'SF Pro Rounded', var(--lg-font);
  /* SF Compact is watchOS's face and has no `ui-*` generic — it resolves only
     where it happens to be installed, which on the web is almost nowhere. It is
     here so a watch-like or very tight layout can ask for it by name and fall
     back cleanly, not because it will usually land. */
  --lg-font-compact: 'SF Compact Text', 'SF Compact Display', var(--lg-font);
  /* Tabular figures for anything that counts or ticks — a clock whose digits
     change width jitters, and it is the single most common numeric bug in a UI. */
  --lg-numeric: tabular-nums;
  /* A recessed channel — the switch and slider tracks. It is NOT glass, so it
     does not follow the tint; it follows the scheme like any other surface.
     Left dark in light scheme it swallowed its own labels, which is how the
     first pass at these tokens shipped unreadable "Decline / Accept". */
  --lg-groove: rgba(16, 18, 26, 0.13);
  --lg-groove-shadow: rgba(16, 18, 26, 0.16);
  /* The one OPAQUE token. Everything else here is alpha-on-ink, because glass
     is translucent — but a library that replaces a project's styling has to be
     able to say "not translucent" and mean it, and an alpha fill cannot: over a
     photo it still shows the photo. See `.lg-surface[data-lg-variant='none']`. */
  --lg-surface: #fbfbfd;
  /* Brand. The two a consumer is most likely to override, and the only tokens
     in the set that carry a hue — everything else is neutral by construction so
     that changing these two changes the product's colour and nothing else. */
  --lg-accent: #0a84ff;
  --lg-danger: #ff3b30;
  --lg-on-accent: #fff;
}
.lg[data-lg-scheme='dark'],
[data-scheme='dark'] {
  --lg-ink: #fff;
  --lg-ink-dim: rgb(255 255 255 / calc(0.66 + (1 - var(--lg-translucency)) * 0.12));
  --lg-ink-faint: rgb(255 255 255 / calc(0.45 + (1 - var(--lg-translucency)) * 0.12));
  --lg-fill: rgba(255, 255, 255, 0.14);
  --lg-fill-strong: rgba(255, 255, 255, 0.22);
  --lg-hairline: rgba(255, 255, 255, 0.18);
  --lg-groove: rgba(8, 9, 12, 0.52);
  --lg-groove-shadow: rgba(0, 0, 0, 0.55);
  --lg-surface: #1c1d22;
  --lg-accent: #0a84ff;
  --lg-danger: #ff453a;
  --lg-on-accent: #fff;
}

.lg {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: 28px;
  /* The tint, pulled toward opaque by however much translucency the user has
     given up. At 1 this is exactly `var(--lg-tint)` — a no-op by construction,
     so the material is untouched unless somebody asks. */
  background: color-mix(in srgb,
    var(--lg-surface) calc((1 - var(--lg-translucency)) * 100%),
    var(--lg-tint, rgba(255, 255, 255, 0.1)));
  box-shadow: var(--lg-shadow), var(--lg-rim);
  color: var(--lg-ink);
  -webkit-font-smoothing: antialiased;
}

/* Solid — the opt-out from translucency, and the honest fallback.
 *
 * `Surface` mounts no engine for `variant: 'none'`, which means no `.lg` class
 * — so until this rule existed a solid surface painted *nothing at all*: no
 * background, no rim, transparent, with the docstring one file over claiming it
 * "paints a solid material". Exactly the failure the glass path had with
 * `tier: undefined`, and invisible for the same reason: nothing errors.
 *
 * It is one rule rather than a `solid` branch in each component, so `Card`,
 * `Popover`, `Sheet`, `Sidebar` and every control get it at once and cannot
 * drift apart. Deliberately mirrors `.lg`: same box model, same radius (written
 * inline by `Surface` either way), same ink — so swapping the material changes
 * only the material.
 *
 * No `backdrop-filter`, no SVG, no map. This is the cheapest surface in the
 * library and the only one that is legible over anything at all.
 *
 * **The `:where()` is load-bearing.** Written as a plain
 * `.lg-surface[data-lg-variant='none']` this rule is specificity (0,2,0), which
 * beats every `.lgtip`/`.lgpop`/`.lgmenu` (0,1,0) — so `position: relative`
 * silently won over their `position: fixed` and a solid tooltip rendered 2100px
 * down the document while reporting itself as shown. `.lg` itself is (0,1,0)
 * and has always relied on later single-class rules overriding it; this has to
 * match that, or it is a different rule wearing the same name. Same trap as the
 * `.lg > *` opt-out below, and the fourth time it has cost something here. */
.lg-surface:where([data-lg-variant='none']) {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--lg-surface);
  box-shadow:
    var(--lg-shadow),
    inset 0 0 0 1px var(--lg-hairline);
  color: var(--lg-ink);
  -webkit-font-smoothing: antialiased;
}

/* A scroller that belongs to the system rather than to the OS.
 *
 * Put this on anything of your own that scrolls; the library's own lists, menus
 * and dialog bodies carry it. A platform scrollbar inside a glass panel is the
 * same problem an OS `<select>` popup was — it is the one part of the surface
 * that ignores every token you set, and it is opaque grey in both schemes.
 *
 * Both spellings on purpose. `scrollbar-width`/`scrollbar-color` are the
 * standard and win wherever they are supported (Chrome 121+, Firefox, Safari
 * 18.2+); the `::-webkit-` block is what older Safari actually reads, and is
 * ignored rather than doubled where the standard properties apply.
 *
 * The thumb is an ink alpha, never a fixed grey — it has to work on a surface
 * whose colour the consumer chose. */
.lg-scroll {
  scrollbar-width: thin;
  scrollbar-color: var(--lg-ink-faint) transparent;
}
.lg-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.lg-scroll::-webkit-scrollbar-track { background: transparent; }
.lg-scroll::-webkit-scrollbar-thumb {
  background-color: var(--lg-ink-faint);
  border-radius: 999px;
  /* A transparent border with `background-clip: padding-box` is the only way to
     inset a WebKit thumb — there is no margin on it. */
  border: 3px solid transparent;
  background-clip: padding-box;
}
.lg-scroll::-webkit-scrollbar-thumb:hover { background-color: var(--lg-ink-dim); }
.lg-scroll::-webkit-scrollbar-corner { background: transparent; }

/* Proxy backdrop — only visible on the 'proxy' tier. */
.lg-proxy {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  display: none;
  background-color: var(--lg-page-bg-color);
  background-image: var(--lg-page-bg);
  /* Size and position are set from JS to the viewport box. `background-attachment:
     fixed` looks like the obvious way to do this and works in Chromium, but a
     `filter` on the element makes it a containing block, so WebKit resolves the
     background against the element instead of the viewport and every panel shows
     a whole copy of the page background rather than the slice behind it. */
  background-attachment: scroll;
  background-repeat: no-repeat;
}
.lg[data-lg-tier='proxy'] .lg-proxy { display: block; }

/* Flat tier: no refraction available (or reduced-transparency). Lean on blur,
   a heavier tint and the rim so the shape still reads as a material. */
.lg[data-lg-tier='flat'] {
  backdrop-filter: blur(max(14px, var(--lg-blur))) saturate(var(--lg-saturate, 1.6));
  -webkit-backdrop-filter: blur(max(14px, var(--lg-blur))) saturate(var(--lg-saturate, 1.6));
  background: color-mix(in srgb,
    var(--lg-surface) calc((1 - var(--lg-translucency)) * 100%),
    var(--lg-tint, rgba(255, 255, 255, 0.1)));
}

/* Dim layer — Apple's rule for `clear` glass over bright media (~35% max). */
.lg::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: rgba(0, 0, 0, var(--lg-dim, 0));
}

/* Grain — the 'frost' tell. Etched glass scatters, it does not just blur. */
.lg::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--lg-grain, 0);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}

/* Lift real children above the ::before (dim) and ::after (grain) layers.
 *
 * `.lg-abs` is the opt-out, and it exists because this rule is a trap: at
 * specificity (0,2,0) it beats any single-class selector, so an absolutely
 * positioned child of a glass panel is silently forced back to `position:
 * relative` and collapses into the flow. It has now caught the TabBar
 * indicator, the Sidebar's resize grip and the Button's tone fill — three
 * times, each debugged from scratch, because the failure is invisible: nothing
 * errors, the element is simply in the wrong place.
 *
 * CONTRACT: any absolutely positioned direct child of a `.lg` carries
 * `lg-abs`.
 *
 * The opt-out is wrapped in `:where()` deliberately. Written as a plain
 * `:not(.lg-abs)` it raises this rule to (0,4,0) — which then beats the
 * `.lgtb > .lgtb-ind` and `.lgsb > .lgsb-grip` workarounds that exist precisely
 * because of it, and breaks them instead. `:where()` contributes no
 * specificity, so the rule stays at (0,2,0) and everything that already fought
 * it to a draw keeps winning on source order. */
.lg > *:not(.lg-proxy):where(:not(.lg-abs)) { position: relative; z-index: 1; }
.lg > .lg-abs { z-index: 1; }
