/* ===========================================================================
   Glass — the surface recipe, as reusable tiers
   ---------------------------------------------------------------------------
   HAND-WRITTEN arrangement, generated tokens for every value.

   REWRITTEN after auditing the Figma components directly rather than from
   build notes. The tiers below are not invented: each one is a
   blur / fill / stroke / shadow combination that actually appears in the
   file, and the components that use it are listed. Numbers came from
   reading effects off the nodes, so "blur 26" means Figma says 26.

   TIER TABLE (fill · blur · inner · drop · stroke)

     --shell    Surface/Base    30  y0   none    solid      title bar, sidebar
     --card     Surface/Raised  26  y1   4 @y2   gradient   hub, category,
                                                            TL;DR, contact card
     --raised   Surface/Raised  20  y1   2 @y1   gradient   tool cell, metric
     --cover    Surface/Sunken  20  none none    solid      project-card cover
     --control  Surface/Raised  14  y0   2 @y1   gradient   button, secondary
     --quiet    Surface/Raised  12  y0   none    gradient   nav link, ghost hover
     --pill     Surface/Raised  10  y0   none    gradient   chip, badge
     --pill-sm  Surface/Raised   7  y0   none    gradient   company chip
     --disc     Surface/Raised  16  none none    gradient   skills mark, face tile
     --sunken   Surface/Sunken  none none none   gradient   link row, input

   Figma's inner shadows on the pill and control tiers have radius 0 AND
   offset 0, which renders nothing. They are a leftover from how the file
   was built. Rather than reproduce an invisible effect or quietly add a
   visible one, those tiers simply have no inner highlight: the gradient rim
   already carries the edge. The tiers whose inner shadow has offset y1 do
   get the 1px top hairline, because there it is real.

   CAUTION — backdrop-filter makes an element a containing block for
   position:fixed descendants. Documented trap in this repository
   (docs/lessons/backdrop-filter-creates-a-containing-block.md): a fixed
   child reports `fixed` and still scrolls with its glass ancestor. Nothing
   fixed may live inside a .glass subtree.
   =========================================================================== */

/* A plain custom property is untyped: a transition across one snaps at
   some point mid-duration rather than interpolating, because the browser
   has no declared syntax to compute in-between values from. Registering
   these two gives `.btn--secondary`'s hover/press and `.linkrow`'s hover
   (both swap `--glass-fill` and/or `--glass-sheen`, nothing else) a real
   animated crossfade through `background` below instead of an instant
   cut — which is what made Secondary's hover read as unresponsive next to
   Primary's, even after Secondary got a hover rule at all: the rule was
   there, it just wasn't animating. */
@property --glass-fill  { syntax: '<color>';  inherits: true; initial-value: transparent; }
@property --glass-sheen { syntax: '<number>'; inherits: true; initial-value: 0.04; }

.glass {
  --glass-fill:   var(--glass-surface-raised);
  --glass-sheen:  0.04;
  --glass-blur:   22px;
  --glass-lift:   0px;      /* drop-shadow offset; 0 = no drop shadow */
  --glass-hair:   1;        /* 1 = 1px top inner highlight, 0 = none */
  --glass-rim:    gradient; /* documentation only; see .glass--hairline */

  position: relative;

  /* Sheen rides on the fill in one background stack, keeping ::after free
     for the rim. */
  background:
    linear-gradient(
      to bottom,
      rgb(255 255 255 / var(--glass-sheen)) 0%,
      rgb(255 255 255 / 0) 60%
    ),
    var(--glass-fill);
  transition: background 200ms linear;

  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
}

/* Shadows are two independently switchable parts, because Figma mixes them
   freely: some tiers have the hairline and no drop, some the reverse, some
   both, some neither.

   Both switches work by multiplying a length by 0 or 1 rather than by
   touching the colour. An inset shadow with zero offset, zero blur and zero
   spread paints nothing, so --glass-hair: 0 turns the hairline off without
   needing relative colour syntax, and the same trick handles --glass-lift.
   Plain calc(), so no feature query and no fallback branch to keep in sync. */
.glass {
  box-shadow:
    inset 0 calc(1px * var(--glass-hair)) 0 var(--glass-inner-highlight),
    0 var(--glass-lift) calc(var(--glass-lift) * 2) var(--glass-contact);
}

/* The gradient rim.

   A gradient cannot be a border-color, and border-image discards
   border-radius, so this is the mask-composite ring: a 1px pseudo-element
   masked to its own border box. It inherits the radius, so a pill and a
   card are the same code, and it never affects layout. */
.glass::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(
    to bottom,
    var(--glass-edge-top) 0%,
    var(--glass-edge-mid) 45%,
    var(--glass-edge-bottom) 100%
  );
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}

/* Figma uses a flat 1px hairline rather than the gradient on a few surfaces
   (title bar, sidebar, project-card cover, TL;DR, metric). Same ring, one
   colour — but not the same colour everywhere: reading the TL;DR component's
   own stroke directly off the file (node 3:10428) gives white at 20%
   (Border/Strong), not the 12% (Border/Hairline) this shares with the
   others. TL;DR and its `.metric` sibling get the stronger variant; title
   bar, sidebar and project-card cover keep the original. */
.glass--hairline::after {
  background: var(--glass-border-hairline);
}

.glass--hairline-strong::after {
  background: var(--glass-border-strong);
}

.glass--no-rim::after { content: none; }


/* --- Tiers --------------------------------------------------------------- */

/* Title bar and sidebar. Largest surfaces, widest blur, flat hairline, and
   no lift of their own: the window shell carries the shadow. */
.glass--shell {
  --glass-fill: var(--glass-surface-base);
  --glass-sheen: 0;
  --glass-blur: 30px;
  --glass-lift: 0px;
  --glass-hair: 0;
}

/* Hub card, category card, TL;DR, contact card. */
.glass--card {
  --glass-sheen: 0.05;
  --glass-blur: 26px;
  --glass-lift: 2px;
  --glass-hair: 1;
}

/* Tool cell, metric. */
.glass--raised {
  --glass-sheen: 0.04;
  --glass-blur: 20px;
  --glass-lift: 1px;
  --glass-hair: 1;
}

/* Project-card cover. Recessed, no shadows at all. */
.glass--cover {
  --glass-fill: var(--glass-surface-sunken);
  --glass-sheen: 0.02;
  --glass-blur: 20px;
  --glass-lift: 0px;
  --glass-hair: 0;
}

/* Buttons. */
.glass--control {
  --glass-sheen: 0.04;
  --glass-blur: 14px;
  --glass-lift: 1px;
  --glass-hair: 0;
}

/* Nav link hover/active, ghost button hover. */
.glass--quiet {
  --glass-sheen: 0.03;
  --glass-blur: 12px;
  --glass-lift: 0px;
  --glass-hair: 0;
}

/* ===========================================================================
   Spotlight — one shared ambient light, not a glow per card
   ---------------------------------------------------------------------------
   Not in Figma: Saar's own addition, on the interactive tiles (hub cards,
   project covers, category cards, tool cells). `--mx`/`--my` (position
   within this card) and `--glow` (0 to 1, how close the pointer is to this
   card) are written every frame by js/app.js's pointermove handler for
   every `.spotlight` element at once, so nearby cards brighten and dim
   together as one field reacting to the same light source, instead of each
   card only ever switching itself on the instant the pointer crosses its
   own edge — see the longer note in js/app.js on why that read as rough.
   With no JS running yet, `--glow` defaults to 0: nothing shows until the
   first pointermove.

   `isolation: isolate` gives the pseudo-element its own local stacking
   context so `z-index: -1` sinks it behind this element's real content
   without also sinking it behind unrelated page content outside this box —
   the well-known trap with a negative z-index on an element that doesn't
   isolate one. Behind the text, in front of the glass tint underneath it.
   `overflow: hidden` on the element itself keeps the blur below from
   rendering past the card's own rounded corners — a `filter` is not
   clipped by `border-radius` the way a plain background already is. */
.spotlight { position: relative; isolation: isolate; overflow: hidden; }
.spotlight::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    320px circle at var(--mx, 50%) var(--my, 50%),
    var(--background-brand-25),
    transparent 70%
  );
  filter: blur(48px);
  opacity: var(--glow, 0);
  pointer-events: none;
}

/* Chips and badges. */
.glass--pill {
  --glass-sheen: 0.03;
  --glass-blur: 10px;
  --glass-lift: 0px;
  --glass-hair: 0;
}

/* Company chip: same pill, tighter blur. */
.glass--pill-sm {
  --glass-sheen: 0.03;
  --glass-blur: 7px;
  --glass-lift: 0px;
  --glass-hair: 0;
}

/* The big circles: skills marks, About face tiles. */
.glass--disc {
  --glass-sheen: 0.04;
  --glass-blur: 16px;
  --glass-lift: 0px;
  --glass-hair: 0;
}

/* Link rows and the passcode input. Recessed, no blur in Figma. */
.glass--sunken {
  --glass-fill: var(--glass-surface-sunken);
  --glass-sheen: 0;
  --glass-blur: 0px;
  --glass-lift: 0px;
  --glass-hair: 0;
}


/* ===========================================================================
   Ambient glow
   ---------------------------------------------------------------------------
   In Figma these are three heavily blurred ellipses on a locked layer per
   frame. Here they are one element with three radial gradients: one node
   instead of three, and impossible to reorder by accident.

   Sizes are percentages because Figma's were absolute per frame and there
   are three frame widths; a percentage serves all of them. Decoration only,
   so aria-hidden and pointer-events:none.
   =========================================================================== */

.ambient {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;

  background:
    radial-gradient(55% 45% at 8% -5%,   var(--glass-glow-indigo)  0%, transparent 70%),
    radial-gradient(50% 40% at 92% 30%,  var(--glass-glow-violet)  0%, transparent 70%),
    radial-gradient(45% 35% at 40% 105%, var(--glass-glow-fuchsia) 0%, transparent 70%);
}

/* ===========================================================================
   Light mode balance (Saar, 2026-09-29): "everything is too white, no
   balance like dark". The generated light tokens put a white page under
   white glass with white hairlines, so nothing separated from anything.
   These overrides give light the same layering dark has, without touching
   tokens.generated.css (regenerated from Figma):
     page   a soft lavender-grey, so the window reads as an object on it
     shell  off-white (window, sidebar), a step below the cards
     cards  near-white, the brightest layer, like dark's raised tier
     edges  faint dark hairlines instead of white ones, which vanished
   Scoped to light only: explicit data-theme="light", or no theme set on a
   system that isn't asking for dark. Dark mode is untouched.
   =========================================================================== */
:root[data-theme="light"] {
  --background-default-default: #E8E9F2;
  --glass-surface-base:   #F6F6FBD9;
  --glass-surface-raised: #FFFFFFE6;
  --glass-surface-sunken: #E6E7F2B3;
  --glass-border-hairline: #1C1D2E14;
  --glass-border-strong:   #595EE04D;
  --glass-edge-mid:        #1C1D2E14;
  --glass-edge-bottom:     #595EE026;
  --glass-contact:         #33367317;
  --glass-inner-shade:     #3336730D;
  --glass-glow-indigo:  #595EE061;
  --glass-glow-violet:  #9161E552;
  --glass-glow-fuchsia: #D63D8F3D;
}
@media not all and (prefers-color-scheme: dark) {
  :root:not([data-theme="dark"]) {
    --background-default-default: #E8E9F2;
    --glass-surface-base:   #F6F6FBD9;
    --glass-surface-raised: #FFFFFFE6;
    --glass-surface-sunken: #E6E7F2B3;
    --glass-border-hairline: #1C1D2E14;
    --glass-border-strong:   #595EE04D;
    --glass-edge-mid:        #1C1D2E14;
    --glass-edge-bottom:     #595EE026;
    --glass-contact:         #33367317;
    --glass-inner-shade:     #3336730D;
    --glass-glow-indigo:  #595EE061;
    --glass-glow-violet:  #9161E552;
    --glass-glow-fuchsia: #D63D8F3D;
  }
}
/* Discs (hub icons, fun facts, skills marks) share the cards' raised fill,
   which in light mode is white on white; a sunken lavender separates them. */
:root[data-theme="light"] .glass--disc { --glass-fill: var(--glass-surface-sunken); }
@media not all and (prefers-color-scheme: dark) {
  :root:not([data-theme="dark"]) .glass--disc { --glass-fill: var(--glass-surface-sunken); }
}

/* Dark mode only: tertiary text on cards measured 4.23:1 against the raised
   fill (a11y review, round 13); slate-400 clears 4.5:1 there. Scoped to
   card surfaces, so tertiary text on the page itself keeps its Figma value.
   Saar's call, 2026-09-29. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .glass--card,
  :root:not([data-theme="light"]) .glass--raised { --text-default-tertiary: var(--slate-400); }
}
:root[data-theme="dark"] .glass--card,
:root[data-theme="dark"] .glass--raised { --text-default-tertiary: var(--slate-400); }
