/* ==========================================================================
   glassmorphism-theme / tokens
   --------------------------------------------------------------------------
   This file is the single source of truth. Every literal value in the theme
   lives here exactly once. The Tailwind adapters next to it (theme.css for
   v4, preset.js for v3) hold no values of their own; they point at these
   custom properties with var().

   Each group is annotated with the creative-direction axis the choice serves.
   The four axes are tone register, aesthetic philosophy, audience
   relationship, and sensory ambition. Read them at
   https://rampstack.co/framework/creative-direction

   CONTRAST ON A TRANSLUCENT SURFACE
   A ratio quoted against an opaque background is a fact. A ratio quoted
   against a translucent one is a claim about every pixel the surface might
   ever sit over, and most of this file exists to make that claim provable.
   The method:

     1. The ground is built from a base color and a fixed set of orb colors,
        each painted at --gl-orb-alpha. Source-over compositing makes every
        rendered channel a weighted average of those inputs, so the lightest
        pixel the ground can produce is computable rather than observed.
        That ceiling is --gl-ground-peak.
     2. Glass adds white at a known alpha. Compositing white over the ceiling
        gives the lightest surface any text can land on.
     3. Every ink ratio in this file is measured against that worst case, not
        against a screenshot.

   Ratios are WCAG 2.1 relative-luminance figures. Every text pairing here
   clears AA at the worst case, not on average.
   ========================================================================== */

:root {
  /* ----------------------------------------------------------------------
     THE GROUND
     Aesthetic philosophy axis. Glass is a material that shows what is behind
     it, so a glass theme on a flat fill produces gray boxes and nothing else.
     The ground is the theme's actual subject: a deep base with three colored
     orbs bloomed across it, dark enough that light ink survives and varied
     enough that the blur has something to work on.

     The three orb hues are far enough apart on the wheel to read as separate
     light sources rather than as one gradient, and none of them is allowed
     near full luminance. Orb alpha is the single dial that trades atmosphere
     against contrast headroom; raising it lifts --gl-ground-peak, and every
     ratio below moves with it.
     ---------------------------------------------------------------------- */
  --gl-ground: #121840;         /* the deep base the orbs bloom over */
  --gl-orb-violet: #9a3ff0;     /* the warm light source */
  --gl-orb-indigo: #4a5bff;     /* the primary light source */
  --gl-orb-teal: #0e7f9c;       /* the cool light source, deliberately the dimmest */
  --gl-orb-alpha: 0.24;         /* peak alpha of any single orb */

  /* The computed ceiling: the lightest pixel the ground above can render.
     With three source-over layers at alpha a, the layer weights are a,
     (1-a)a and (1-a)^2 a, with (1-a)^3 left for the base. Every composite
     channel is a weighted average of those inputs, so the per-channel maximum
     is reached by pairing the largest orb channel with the largest weight.
     That arithmetic gives (60.3, 66.3, 154.7), rounded up to the value below.
     Blur can only average a region, never exceed its maximum, so the ceiling
     survives backdrop-filter.

     The bound is conservative: it describes three orb cores landing on one
     pixel, which the composition in depth.css avoids. Two tempting ways to
     tighten it do not work. Blending the orbs with lighten is looser rather
     than tighter, because each layer moves the result a further fraction
     toward the brightest orb instead of averaging toward its own color.
     Clipping each orb to its own region would give the tight bound and costs
     the soft falloff that is the reason to have a ground at all.

     This token is a derived value, not a free choice. Change the orbs or the
     alpha and it has to be recomputed, or every ratio in this file is a
     guess. verify/contrast.js recomputes it and fails loudly on drift, and it
     has been checked against the rendered page: no pixel of the demo's ground
     exceeds it on any channel, at either 1280 or 390. */
  --gl-ground-peak: #3d439b;    /* ink on the bare peak: 7.51:1 */

  /* ----------------------------------------------------------------------
     INK
     Audience relationship axis. Two inks, and the second one is barely muted,
     because a translucent surface spends the contrast budget that opaque
     surfaces spend on typographic hierarchy. On this ground, ink at 5.78:1
     and muted ink at 4.71:1 are the two steps available at the worst case.
     A third, dimmer step exists on paper and fails wherever an orb sits, so
     the theme does not ship one and pushes secondary emphasis into weight and
     size instead. That is the register's real cost, and it is a cost in
     typography rather than in color.
     ---------------------------------------------------------------------- */
  --gl-ink: #eef1f8;        /* worst case 5.78:1, best case 12.89:1 */
  --gl-ink-muted: #d5dbe8;  /* worst case 4.71:1, best case 10.50:1 */

  /* ----------------------------------------------------------------------
     GLASS: THE ELEVATION TIERS
     Sensory ambition axis. Three tiers, and the pairing of blur with alpha is
     the whole trick. A surface nearer the viewer occludes more of what is
     behind it and defocuses it further, so alpha and blur rise together. Move
     one without the other and the tier stops reading as a distance and starts
     reading as a style.

     Saturation is lifted alongside the blur because blurring a region pulls
     its colors toward their average, which drains them. Restoring saturation
     is what keeps a blurred orb reading as colored light rather than as haze.

     The alphas are low on purpose. Every step up costs ink contrast on the
     ground: tier 1 carries ink at 6.42:1 and tier 2 at 5.78:1, and a third
     step on the ground would put the muted ink under AA. That is why tier 3
     is scrimmed rather than merely lighter, and why there is no tier 4.
     ---------------------------------------------------------------------- */
  --gl-alpha-1: 0.06;  /* recessed: wells, table rows, inline code */
  --gl-alpha-2: 0.10;  /* the working surface: cards, panels, the nav */
  --gl-alpha-3: 0.14;  /* near: menus, popovers, anything that floats over a panel */

  --gl-blur-1: 12px;
  --gl-blur-2: 20px;
  --gl-blur-3: 32px;

  --gl-saturate: 1.6;

  /* Ratios for tiers 1 and 2 are against the lit ground. Tier 3 never sits on
     the ground, so its ratios are against the scrim over white content, which
     is the worst backdrop anything in this theme can have. */
  --gl-surface-1: rgba(255, 255, 255, 0.06);  /* ink 6.42:1, muted 5.23:1 */
  --gl-surface-2: rgba(255, 255, 255, 0.10);  /* ink 5.78:1, muted 4.71:1 */
  --gl-surface-3: rgba(255, 255, 255, 0.14);  /* ink 5.77:1, muted 4.70:1 */

  --gl-backdrop-1: blur(var(--gl-blur-1)) saturate(var(--gl-saturate));
  --gl-backdrop-2: blur(var(--gl-blur-2)) saturate(var(--gl-saturate));
  --gl-backdrop-3: blur(var(--gl-blur-3)) saturate(var(--gl-saturate));

  /* ----------------------------------------------------------------------
     THE SCRIM
     Aesthetic philosophy axis, and the token that closes the largest hole in
     everything above. Every ratio in this file is measured against the
     ground, and that is sound for a panel sitting on the ground. It says
     nothing about a surface whose backdrop is the document: a sticky bar with
     the page scrolling under it, or anything floating over another surface.
     That backdrop is not bounded by --gl-ground-peak. It contains ink.

     Measured: a nav link on a tier-2 fill with body text passing under it
     lands at 1.24:1. Not a near miss, and not visible in a screenshot of the
     top of a page, which is where this register's failures like to hide.

     The scrim is the ground color at an alpha high enough to put a floor
     under any backdrop whatsoever. Over pure white content it holds ink at
     6.47:1 and muted ink at 5.27:1. Against that same backdrop with no scrim
     at all, the two measure 1.13:1 and 1.39:1. Over the lit ground the scrim
     costs almost nothing, because it is the ground color and the ground is
     already dark.

     It is dark rather than light on purpose. Lifting a surface with white
     works when what is behind it is darker than the text; over arbitrary
     content there is no such guarantee, and the only fill that bounds an
     unknown backdrop from the correct side on a dark theme is a dark one.
     ---------------------------------------------------------------------- */
  --gl-scrim: rgba(18, 24, 64, 0.82);

  /* ----------------------------------------------------------------------
     THE ALPHA FLOOR
     Aesthetic philosophy axis, and the token that stops this register from
     failing quietly. A glass fill lets the ground's variation through to
     whatever sits on it. Large text absorbs that; a 12px uppercase label does
     not. Measured on this ground, semantic fills at a decorative alpha of
     0.20 put their label at 4.47:1 in the info case and 4.08:1 in the danger
     case, which is a failure that only appears where an orb happens to be.

     At the floor, the fill rather than the backdrop sets the label's
     background, and the same two cases measure 6.58:1 and 6.37:1. Any fill
     carrying text below --gl-text-sm takes this alpha. Nothing else does; a
     panel at 0.62 is a painted box with a blur behind it that no one can see.
     ---------------------------------------------------------------------- */
  --gl-alpha-floor: 0.62;

  /* ----------------------------------------------------------------------
     EDGES
     Aesthetic philosophy axis. On this ground a tier-1 panel measures 1.17:1
     against the darkest ground it can sit over, so a glass fill cannot carry
     its own boundary at any alpha this register permits. Reaching 3:1 on fill
     alone needs roughly 0.34 alpha, which is a painted surface. The edge is
     therefore structural rather than decorative, and it is drawn.

     Three weights. The hairline is the light catching a top edge and is
     allowed to fail contrast because it states nothing; the boundary carries
     a panel; the strong edge carries anything a pointer can act on and is the
     only one that has to clear 3:1 against the lightest panel underneath it.
     ---------------------------------------------------------------------- */
  --gl-edge: rgba(255, 255, 255, 0.16);         /* hairline, decorative only */
  --gl-edge-boundary: rgba(255, 255, 255, 0.34); /* 3.06:1 against the darkest ground */
  --gl-edge-strong: rgba(255, 255, 255, 0.60);   /* 3.49:1 against the lightest panel */
  --gl-highlight: rgba(255, 255, 255, 0.68);     /* the catch-light along a top edge */

  --gl-border-width: 1px;
  --gl-border-width-heavy: 1.5px;

  /* ----------------------------------------------------------------------
     ACTION COLORS
     Tone register axis. Both actions are opaque. A translucent call to action
     inherits the ground's variance and its label's contrast becomes a
     function of where the reader scrolled to, which is not a trade worth
     making for one more frosted rectangle. They take dark ink rather than
     white, because at the brightness that separates a button from a lit
     panel, white text is already under AA.
     ---------------------------------------------------------------------- */
  --gl-primary: #6d7cff;          /* dark ink on it: 5.57:1 */
  --gl-primary-ink: #080b1a;
  --gl-destructive: #ff6b81;      /* dark ink on it: 7.15:1 */
  --gl-destructive-ink: #080b1a;

  /* ----------------------------------------------------------------------
     THE ACCENT
     Sensory ambition axis. One cool light, used for the focus ring and for
     the single glow the theme permits per screen. The glow is the effect this
     register is most often ruined by, so it is scoped to one token with one
     intended call site rather than offered as a utility. It carries no
     information, which is why its alpha is low enough to read as a light
     source rather than as a highlight.
     ---------------------------------------------------------------------- */
  --gl-accent: #8ee6ff;  /* 4.64:1 against the lightest panel, 12.12:1 against the darkest ground */
  --gl-accent-ink: #08131a;
  --gl-glow: rgba(142, 230, 255, 0.18);

  /* ----------------------------------------------------------------------
     SEMANTIC FILLS
     Aesthetic philosophy axis. Each is a deep tint applied at the alpha floor
     with a light label in the same hue family. Deep rather than pale: a pale
     tint over a lit panel lands lighter still and takes the label with it,
     and the ratios above are what that costs.

     The fills shift hue slightly as the panel under them changes, because 38%
     of the backdrop is still coming through. That drift is the register
     behaving correctly and is why the label ratios are quoted as a range.
     ---------------------------------------------------------------------- */
  --gl-fill-neutral: #141a2e;   /* label 9.76:1 to 12.77:1 */
  --gl-fill-success: #06301f;   /* label 8.01:1 to 10.59:1 */
  --gl-fill-info: #0a2440;      /* label 6.58:1 to  8.71:1 */
  --gl-fill-warning: #3a2405;   /* label 7.54:1 to  9.95:1 */
  --gl-fill-danger: #3d0a16;    /* label 6.37:1 to  8.21:1 */

  --gl-ink-neutral: #dfe4ef;
  --gl-ink-success: #7ef0c0;
  --gl-ink-info: #93c8ff;
  --gl-ink-warning: #ffcb6b;
  --gl-ink-danger: #ff9aab;

  /* ----------------------------------------------------------------------
     SHADOW
     Sensory ambition axis. Large radius, low opacity, and offset roughly a
     third of the blur, which is the geometry of a soft area light rather than
     a point source. The shadow is what puts air between a panel and the
     ground; without it a frosted surface reads as a hole cut in the page.
     Each tier's shadow grows with its blur, so the two depth cues agree.
     ---------------------------------------------------------------------- */
  --gl-shadow-1: 0 2px 8px rgba(3, 5, 14, 0.28);
  --gl-shadow-2: 0 12px 32px rgba(3, 5, 14, 0.4);
  --gl-shadow-3: 0 24px 64px rgba(3, 5, 14, 0.52);
  --gl-shadow-glow: 0 0 48px var(--gl-glow);

  /* The same light, inverted. A well is lit from above like everything else,
     so its shadow falls on the inside of its top edge. Wells take this
     instead of the top catch-light, never both. */
  --gl-shadow-inset: inset 0 2px 8px rgba(3, 5, 14, 0.28);

  /* ----------------------------------------------------------------------
     RADIUS
     Audience relationship axis. Radius scales with the surface rather than
     sitting at one value, because a corner reads as a proportion of the edge
     it turns. Twenty pixels on a 480px panel and twelve on a 44px button are
     the same gesture at two sizes, and it is the button the reader's hand
     meets, so the button carries the relationship. Twelve is level ground:
     square would put the brand above the reader, and a pill would put it
     below.
     ---------------------------------------------------------------------- */
  --gl-radius-tight: 8px;   /* badges, chips, inline code */
  --gl-radius: 12px;        /* buttons, inputs, small controls */
  --gl-radius-lg: 20px;     /* panels, cards, popovers */
  --gl-radius-pill: 999px;  /* avatars and pill toggles only */

  /* ----------------------------------------------------------------------
     TYPE
     Tone register axis. System fonts, so the theme loads no webfont and makes
     no network request. Conversational sits one step warmer than Professional
     and the type carries that in the gaps rather than in the faces: 700
     against 400 is a narrower jump than a Playful register would take, and
     the lead size sits close enough to body that a paragraph does not
     announce itself before it is read.
     ---------------------------------------------------------------------- */
  --gl-font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --gl-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  --gl-weight-body: 400;
  --gl-weight-medium: 500;
  --gl-weight-bold: 600;
  --gl-weight-display: 700;

  --gl-text-display: 3.25rem;  /* 52px */
  --gl-text-h1: 2.5rem;        /* 40px */
  --gl-text-h2: 1.875rem;      /* 30px */
  --gl-text-h3: 1.375rem;      /* 22px */
  --gl-text-lead: 1.1875rem;   /* 19px */
  --gl-text-body: 1rem;        /* 16px, the floor for form inputs on iOS */
  --gl-text-sm: 0.875rem;      /* 14px, the floor for any body content */
  --gl-text-xs: 0.75rem;       /* 12px, labels and badges only, never prose */

  --gl-leading-tight: 1.08;
  --gl-leading-snug: 1.3;
  --gl-leading-body: 1.65;

  --gl-tracking-tight: -0.02em;
  --gl-tracking-wide: 0.06em;

  /* ----------------------------------------------------------------------
     SPACE
     Aesthetic philosophy axis. Polished Standard earns its keep at the edges
     rather than in the layout, so the scale is the conventional one and the
     craft budget goes elsewhere. Panel padding runs one step wider than an
     opaque theme would take, because a frosted surface has no hard edge to
     hold text away from and a cramped glass panel reads as a smudge.
     ---------------------------------------------------------------------- */
  --gl-space-1: 4px;
  --gl-space-2: 8px;
  --gl-space-3: 12px;
  --gl-space-4: 16px;
  --gl-space-6: 24px;
  --gl-space-8: 32px;
  --gl-space-12: 48px;
  --gl-space-16: 64px;
  --gl-space-24: 96px;

  --gl-measure: 66ch;      /* prose measure */
  --gl-container: 1120px;  /* page max width */

  /* ----------------------------------------------------------------------
     MOTION
     Sensory ambition axis. One gesture: a surface rises toward the viewer on
     hover, gaining shadow as it goes. The travel is small because the shadow
     is doing the work, and a glass panel that moves far enough to notice
     stops reading as a heavy pane. Duration is longer than a click response
     would want, since this motion is atmosphere rather than feedback.
     ---------------------------------------------------------------------- */
  --gl-lift: 3px;
  --gl-duration: 220ms;
  --gl-ease: cubic-bezier(0.2, 0.6, 0.2, 1);

  /* ----------------------------------------------------------------------
     FOCUS
     Audience relationship axis. The ring is the accent rather than the
     primary, so a focused primary button still shows a ring the reader can
     see against its own fill. It sits outside the edge so it never competes
     with the boundary the edge tokens are drawing.
     ---------------------------------------------------------------------- */
  --gl-ring: var(--gl-accent);
  --gl-ring-width: 2px;
  --gl-ring-offset: 2px;

  /* ----------------------------------------------------------------------
     DEPTH: THE GROUND LAYER
     Sensory ambition axis. Orb geometry and the veil that sits between the
     orbs and the glass. The veil is a fine grid at low opacity, and it earns
     its place at the panel boundary rather than across the panel. Blur over
     the bare gradient changes a panel's pixels by a mean of 1.86/255, which
     nobody sees; the veil takes that to 2.12/255, which nobody sees either.
     What the reader does see is the grid crisp on the open ground and absent
     under the glass, measured at a standard deviation of 0.88 outside a panel
     and 0 inside one. depth.css carries the working.
     ---------------------------------------------------------------------- */
  --gl-orb-size: 62vmax;
  --gl-orb-size-sm: 44vmax;

  --gl-veil-line: rgba(255, 255, 255, 0.05);
  --gl-veil-opacity: 0.5;
  --gl-veil-size: 44px;  /* the grid pitch, small enough to soften at blur-2 */
}

/* Anyone who has asked the operating system to reduce transparency gets the
   same theme with the material swapped out: solid surfaces, no backdrop
   filter, and a ground with no orbs on it. The tiers survive as three opaque
   values that keep their relative order, so hierarchy built out of elevation
   still reads. Ink improves on every one of them, to 13.35:1 at tier 2.

   This is a real setting on macOS, iOS and Windows, and the people who turn
   it on include readers with vestibular sensitivity and readers who simply
   cannot resolve text on a moving backdrop. Shipping glass without this
   branch is the register's most common accessibility failure. */
@media (prefers-reduced-transparency: reduce) {
  :root {
    --gl-ground: #0f1435;
    --gl-orb-alpha: 0;
    --gl-veil-opacity: 0;

    --gl-surface-1: #171c38;  /* ink 14.74:1, muted 12.01:1 */
    --gl-surface-2: #1e2444;  /* ink 13.35:1, muted 10.87:1 */
    --gl-surface-3: #262d54;  /* ink 11.71:1, muted  9.54:1 */

    --gl-backdrop-1: none;
    --gl-backdrop-2: none;
    --gl-backdrop-3: none;

    /* With no blur to soften them, the edges can come down to hairlines. */
    --gl-edge-boundary: rgba(255, 255, 255, 0.22);
    --gl-edge-strong: rgba(255, 255, 255, 0.42);
    --gl-highlight: rgba(255, 255, 255, 0.42);
  }
}

/* Asking for more contrast retires the material entirely rather than tuning
   it. Glass at any alpha puts a variable backdrop behind the text, and a
   reader who has asked for more contrast is asking for the variable to go. */
@media (prefers-contrast: more) {
  :root {
    --gl-orb-alpha: 0;
    --gl-veil-opacity: 0;

    --gl-surface-1: #0d1122;
    --gl-surface-2: #131934;
    --gl-surface-3: #1a2145;

    --gl-backdrop-1: none;
    --gl-backdrop-2: none;
    --gl-backdrop-3: none;

    --gl-ink-muted: var(--gl-ink);
    --gl-edge-boundary: rgba(255, 255, 255, 0.62);
    --gl-edge-strong: rgba(255, 255, 255, 0.86);
  }
}

/* The lift is the only motion the theme ships, so reducing motion removes it
   and leaves the shadow change behind. The hover still reads; it stops
   travelling. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --gl-lift: 0px;
    --gl-duration: 1ms;
  }
}
