/* ── Ice key — the site's blue action button ─────────────────────────
   Ported from totp-decoder demo/ice-key-button.html. Two variants:
     .ice-key        V5 "engraved grotesk · main" — frosted slab with
                     fracture artwork, inclusions and a hover-swept
                     reflection band (the homepage hero CTA).
     .ice-key.pure   V5 "pure + engraved grotesk" — clean solid slab,
                     no fractures/reflection/sweep. Every blue
                     rectangular button on the site is this variant;
                     the header auth pill (index.html #auth-btn) is its
                     reference instance, so a change here restyles all
                     of them together.
   Size grades: default ≈46px tall (hero CTA, form submits), .mini
   ≈32px (console chrome, the auth pill). The cap sits in-flow on the
   key's grid and sizes from its label, so each locale's text sets its
   own width; as a flex/grid child the key stretches with its column
   (login/reset submit rows) exactly like a padded pill would.
   Works on <a> and <button> alike — the paired ice-key.js drives
   --press/--tilt-* motion and owns the <a>-specific click hazards.
   The demo composes variants from four classes (rimless +
   no-pointer-light + highlight-sweep + engraved-label); these rules
   are the merged final values. */
.ice-key{
  --press:0;
  --tilt-x:0deg;
  --tilt-y:0deg;
  --ice-hairline:1.5px;
  --ice-edge:oklch(72% .115 226 / .74);
  --ice-deep:oklch(58% .145 232 / .78);
  --ice-shadow:oklch(41% .08 235 / .24);
  --ice-white:oklch(100% 0 0 / .86);
  position:relative;
  display:inline-grid;
  flex:0 0 auto;
  margin:0;
  padding:0;
  border:0;
  border-radius:12px;
  background:transparent;
  appearance:none;
  -webkit-appearance:none;
  cursor:pointer;
  outline:none;
  text-decoration:none;
  user-select:none;
  -webkit-user-select:none;
  touch-action:manipulation;
  -webkit-tap-highlight-color:transparent;
  perspective:780px;
}
/* The grid display above would defeat the UA's [hidden]{display:none}
   (no site-wide guard exists — every JS-toggled element needs its own),
   and console.html toggles `hidden` on ice keys (#twofa-confirm). */
.ice-key[hidden]{display:none}
/* Busy/locked keys (form submits while a request is in flight): dim the
   whole slab and park the cap flat — pointer events stop reaching a
   disabled <button>, so without the transform reset a key disabled
   mid-hover would freeze at a stale tilt. */
.ice-key:disabled{opacity:.6;cursor:not-allowed}
.ice-key:disabled .key-cap{transform:none}
.ice-key:focus-visible::after{
  content:"";
  position:absolute;
  inset:-6px;
  border:1.5px solid oklch(58% .16 235 / .72);
  border-radius:17px;
  box-shadow:0 0 0 4px oklch(66% .12 230 / .12);
}
/* Both under-glow layers hang below the key's border box, and as <a>
   descendants they'd extend its hit target over whatever wraps beneath
   — dead zones on the demo's <button>, live link surface here. */
.ice-key .key-shadow{
  position:absolute;
  left:7px;right:7px;bottom:-10px;
  height:14px;
  border-radius:50%;
  background:radial-gradient(ellipse,var(--ice-shadow),transparent 70%);
  filter:blur(4px);
  opacity:calc(.9 - var(--press) * .22);
  transform:scaleX(calc(1 - var(--press) * .06));
  pointer-events:none;
}
.ice-key .key-caustic{
  position:absolute;
  left:19px;right:19px;bottom:-7px;
  height:8px;
  border-radius:50%;
  background:
    radial-gradient(ellipse at 45% 45%,oklch(99% .018 220 / .94),transparent 44%),
    radial-gradient(ellipse,oklch(73% .12 224 / .66),transparent 72%);
  filter:blur(1.5px);
  opacity:calc(.72 + var(--press) * .28);
  pointer-events:none;
}
/* Dark mode kills the caustic: the bright pool under the key reads as
   light refracted through ice onto paper, and a dark page has no light
   to refract — the white puddle just floats (user call 2026-07-13).
   The key-shadow stays: lost against dark paint, still correct there.
   [data-ice-mode='dark'] lets a locally dark panel (the demo's dark
   section) opt into the dark treatment without the site theme. */
html[data-mode='dark'] .ice-key .key-caustic,
[data-ice-mode='dark'] .ice-key .key-caustic{display:none}
/* Ambient frost (user calls 2026-07-13): snow crumbs and tiny ice
   shards scatter around the key's feet on BOTH themes — bright dust on
   dark paint, pale blue-grey dust on light (ice-key.js picks the
   palette per key from data-ice-mode/data-mode and regenerates on
   theme or Motion flips). Dark additionally gets crystal sparkles on
   SMIL twinkle loops and the faint cold haze below (the gradient layer
   is white — self-erasing on light paper). Reaches past the key's box
   to hold the scatter and paints beneath every key layer (first child
   box, no z-index needed). Randomized per load like all the artwork
   here; main variant only — ice-key.js fills nothing for pure keys. */
.ice-key::before{
  content:"";
  position:absolute;
  inset:-18px -26px -22px;
  background-image:
    var(--frost,none),
    radial-gradient(58% 26% at 50% 82%,oklch(82% .05 228 / .09),transparent 72%);
  background-size:100% 100%;
  background-repeat:no-repeat;
  pointer-events:none;
}
/* Frost v2.1 — the ice hole is an idle easter egg (user spec
   2026-07-13): 9m20s without any input anywhere on the page and a hole
   opens in the sheet beside each pond-class key, at a random spot on
   its free flank (.pond-left/.pond-right constrain the side, bare
   .pond picks one per appearance) with a fresh random shape every
   time; the first input starts a slow refreeze (the longer fade-out)
   and rearms the timer. ice-key.js injects this span as the key's
   first element child so it paints beneath every slab layer. Dark
   surfaces only — light keeps the crumbs, never the hole. */
.ice-key .key-pond{
  position:absolute;
  inset:-24px -100px -50px;
  display:none;
  background-size:100% 100%;
  background-repeat:no-repeat;
  opacity:0;
  transition:opacity 2.4s ease;
  pointer-events:none;
}
html[data-mode='dark'] .ice-key .key-pond,
[data-ice-mode='dark'] .ice-key .key-pond{display:block}
.ice-key .key-pond.on{opacity:1;transition-duration:1.4s}
html[data-anim='off'] .ice-key .key-pond{transition:none}
/* Side-face continuation of the impact web: rays that punch through the
   cap's bottom edge hand their exit x/heading to ice-key.js, which
   draws their run down the exposed lip here (--shatter-base; 'none'
   when a strike leaves no through-rays, so a stale lip from an earlier
   hit can't outlive its cap web). Same latch as the cap overlay. */
.ice-key .key-base::after{
  content:"";
  position:absolute;
  inset:0;
  border-radius:inherit;
  background-image:var(--shatter-base,none);
  background-size:100% 100%;
  background-repeat:no-repeat;
  opacity:var(--shattered,0);
  pointer-events:none;
}
/* Tactile side wall without the pale sandwich-like band (V5 key-base). */
.ice-key .key-base{
  position:absolute;
  inset:4px 2px 0;
  border-radius:10px;
  transform:translateY(3px);
  background:
    linear-gradient(90deg,
      oklch(77% .09 222 / .72),
      oklch(69% .12 231 / .78) 15%,
      oklch(74% .105 227 / .76) 50%,
      oklch(66% .13 234 / .8) 87%,
      oklch(76% .09 222 / .72)),
    linear-gradient(180deg,oklch(76% .1 225 / .78),oklch(66% .13 233 / .84));
  box-shadow:
    inset 0 -2px 3px oklch(54% .12 237 / .22),
    inset 5px 0 9px oklch(84% .06 218 / .2),
    inset -5px 0 9px oklch(57% .12 236 / .16),
    0 5px 10px -6px var(--ice-shadow);
}
/* Fractures enter at points matching the cap's, kink on the perpendicular
   side face and pick up a faint refracted shadow instead of extending
   straight. ice-key.js regenerates this artwork per load via
   --fracture-base (randomized composition, same recipe); the inline URI
   is the no-JS fallback. */
.ice-key .key-base::before{
  content:"";
  position:absolute;
  inset:0;
  border-radius:inherit;
  background-image:var(--fracture-base,url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20256%2089'%3E%3Cg%20fill='none'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M65%2077C66%2079%2065%2081%2067%2083C68%2085%2067%2087%2069%2089M197%2077C195%2079%20197%2081%20195%2083C194%2085%20195%2086%20193%2088'%20stroke='%233d85ae'%20stroke-opacity='.09'%20stroke-width='.85'/%3E%3Cpath%20d='M64%2077C65%2079%2064%2081%2066%2083'%20stroke='%23effcff'%20stroke-opacity='.42'%20stroke-width='.65'/%3E%3Cpath%20d='M66%2083C67%2085%2066%2087%2068%2089M65.5%2082.5C64%2083%2063.5%2084%2062.5%2085'%20stroke='%23effcff'%20stroke-opacity='.19'%20stroke-width='.58'/%3E%3Cpath%20d='M196%2077C194.8%2079%20196%2080.5%20194.5%2082.5'%20stroke='%23effcff'%20stroke-opacity='.32'%20stroke-width='.62'/%3E%3Cpath%20d='M194.5%2082.5C193.8%2084%20194.5%2085.5%20192.5%2087.5M194.7%2082C196%2083%20196.5%2084%20197.3%2084.8'%20stroke='%23effcff'%20stroke-opacity='.13'%20stroke-width='.54'/%3E%3C/g%3E%3Ccircle%20cx='115'%20cy='84'%20r='1'%20fill='%23f5fdff'%20fill-opacity='.72'/%3E%3Ccircle%20cx='184'%20cy='81'%20r='.8'%20fill='%23f5fdff'%20fill-opacity='.58'/%3E%3Ccircle%20cx='215'%20cy='86'%20r='.7'%20fill='%23f5fdff'%20fill-opacity='.46'/%3E%3C/svg%3E"));
  background-position:center;
  background-size:100% 100%;
  background-repeat:no-repeat;
  opacity:.72;
  pointer-events:none;
}
/* Ice is rigid: V5 sinks straight into its base — no rubber-like scale.
   Travel is 7px, tuned by feel rather than the footprint's ~0.48 scale
   of the demo's 12px: bottoming-out is read from the overshoot past the
   base reveal — the proportional 6px left 2px (imperceptible), 8px gave
   4px (~9% of the key, read as punching through), 3px sits right. The
   four hairline highlights and the edge ring live in this box-shadow
   stack and fade with --press; painting below the negative-z backdrop
   child they render soft, not crisp — deliberate (the crisp version sat
   on a ::after overlay for one commit and was rejected as too harsh). */
.ice-key .key-cap{
  grid-area:1/1;
  position:relative;
  z-index:1;
  margin:0 2px 4px;
  padding:12px 20px;
  display:grid;
  place-items:center;
  overflow:hidden;
  border-radius:10px;
  transform-style:preserve-3d;
  will-change:transform;
  transform:
    rotateX(var(--tilt-x))
    rotateY(var(--tilt-y))
    translateY(calc(var(--press) * 7px));
  transform-origin:50% 90%;
  background:
    radial-gradient(120% 125% at 50% 42%,
      oklch(91% .035 220 / calc(.24 - var(--press) * .08)) 24%,
      oklch(78% .095 226 / calc(.31 - var(--press) * .10)) 71%,
      oklch(65% .145 234 / calc(.58 - var(--press) * .18)) 100%);
  box-shadow:
    inset 0 var(--ice-hairline) 0 oklch(100% 0 0 / calc(.86 - var(--press) * .86)),
    inset var(--ice-hairline) 0 0 oklch(100% 0 0 / calc(.54 - var(--press) * .54)),
    inset calc(0px - var(--ice-hairline)) 0 0 oklch(100% 0 0 / calc(.38 - var(--press) * .38)),
    inset 0 -1px 0 oklch(100% 0 0 / calc(.55 - var(--press) * .55)),
    inset 0 -5px 9px -6px var(--ice-deep),
    inset 7px 0 11px -10px oklch(100% 0 0 / .52),
    inset -7px 0 11px -10px oklch(49% .13 239 / .24),
    inset 0 0 0 1px oklch(72% .115 226 / calc(.74 - var(--press) * .74)),
    0 7px 12px -7px var(--ice-shadow);
}
/* The demo puts backdrop-filter on .key-cap itself, but Chromium bleeds
   the backdrop blur past the rounded edge when nothing clips the element
   (invisible over the demo's paper background, an obvious grey halo over
   dark mode). Hosted on a child instead, the bleed gets clipped by the
   cap's own overflow:hidden. Trade-offs: the child's backdrop also
   samples the cap's own gradient (no visible difference), and the cap's
   inset lines paint below this layer, softened by its blur (kept — see
   the cap comment). Never display:none this — the halo comes back. */
.ice-key .key-cap::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:-1;
  backdrop-filter:blur(calc(3.2px - var(--press) * 2.4px)) saturate(145%) brightness(calc(1.02 + var(--press) * .05));
  -webkit-backdrop-filter:blur(calc(3.2px - var(--press) * 2.4px)) saturate(145%) brightness(calc(1.02 + var(--press) * .05));
}
/* A broad reflection band sells polished ice without a neon glow. Static
   diagonal (no pointer tracking); hover sweeps --reflection-sweep via JS.
   Two bands riding the same sweep variable: the bright one and a fainter
   echo ~20% ahead of it along the travel direction (demo's V5 values). */
.ice-key .reflection{
  --reflection-sweep:36%;
  position:absolute;
  inset:-35% -24%;
  background:
    linear-gradient(120deg,
      transparent calc(var(--reflection-sweep) + 17%),
      oklch(100% 0 0 / .015) calc(var(--reflection-sweep) + 21%),
      oklch(100% 0 0 / .22) calc(var(--reflection-sweep) + 24%),
      oklch(100% 0 0 / .045) calc(var(--reflection-sweep) + 29%),
      transparent calc(var(--reflection-sweep) + 34%)),
    linear-gradient(120deg,
      transparent calc(var(--reflection-sweep) - 7%),
      oklch(100% 0 0 / .03) calc(var(--reflection-sweep) - 2%),
      oklch(100% 0 0 / .62) var(--reflection-sweep),
      oklch(100% 0 0 / .08) calc(var(--reflection-sweep) + 5%),
      transparent calc(var(--reflection-sweep) + 10%));
  opacity:calc(.86 - var(--press) * .12);
  pointer-events:none;
}
/* Tiny fixed imperfections. The demo pins these to a 256×101 tile so
   fracture entry points stay put on its fixed-size key; here the box
   itself is the (locale-dependent) size reference, which keeps the
   composition intact at any width. Dots only — the cap's fracture lines
   moved to the ::before below. */
.ice-key .inclusions{
  position:absolute;
  inset:0;
  background:
    radial-gradient(circle at 19% 68%,oklch(100% 0 0 / .72) 0 1.1px,transparent 1.7px),
    radial-gradient(circle at 76% 27%,oklch(100% 0 0 / .62) 0 1.5px,transparent 2.1px),
    radial-gradient(circle at 84% 71%,oklch(100% 0 0 / .52) 0 .8px,transparent 1.4px);
  opacity:.64;
  pointer-events:none;
}
/* Cap fracture lines, on their own layer so the JS-generated SVG can
   stretch edge-to-edge (a fixed-ratio SVG dropped into the multi-layer
   background above would letterbox — background-size must pair with the
   var()'s single layer, and the layer count differs between fallback
   and generated values). ice-key.js sets --fracture-cap per load; the
   fallback keeps the original two static lines for no-JS visits.
   Inherits the parent's pointer-events:none and .64 dimming. */
.ice-key .inclusions::before{
  content:"";
  position:absolute;
  inset:0;
  background:var(--fracture-cap,
    linear-gradient(67deg,transparent 67.1%,oklch(100% 0 0 / .48) 67.4%,transparent 67.8%),
    linear-gradient(118deg,transparent 35%,oklch(55% .1 233 / .14) 35.4%,oklch(100% 0 0 / .46) 35.7%,transparent 36.1%));
  background-size:100% 100%;
  background-repeat:no-repeat;
}
/* Press-to-shatter overlay. ice-key.js drops a freshly generated impact
   web into --shatter on every press (grown by SMIL inside the SVG while
   motion is on; static when off) and latches --shattered to 1 — damage
   is permanent for the page's lifetime, no healing on release (user
   call 2026-07-13); each further press regenerates denser artwork, so
   hits accumulate as escalating damage rather than stacked layers.
   Tree order puts this above reflection/inclusions and below the z:2
   label. Pure keys never get --shatter set, so it stays transparent
   there. */
.ice-key .key-cap::after{
  content:"";
  position:absolute;
  inset:0;
  background-image:var(--shatter,none);
  background-size:100% 100%;
  background-repeat:no-repeat;
  opacity:var(--shattered,0);
  pointer-events:none;
}
/* ── .pure variant — demo's "V5 pure + engraved grotesk" (side branch).
   A clean slab: solid base, no fractures, no reflection band, no sweep —
   its markup simply omits .reflection/.inclusions and the script skips
   sweeping when no band exists. Same skeleton, press physics and
   lettering as the main key. */
.ice-key.pure .key-base{
  background:oklch(64% .14 233 / .82);
  box-shadow:
    inset 5px 0 9px oklch(79% .08 220 / .42),
    inset -5px 0 9px oklch(49% .13 239 / .25),
    inset 0 -1px 1px oklch(45% .11 237 / .32),
    inset 0 1px 1px oklch(100% 0 0 / .64),
    0 5px 10px -6px var(--ice-shadow);
}
/* Three faint bubbles along the base's bottom lip replace the fracture
   artwork (the demo's pure ::before override). */
.ice-key.pure .key-base::before{
  background-image:none;
  background:
    radial-gradient(circle at 45% 94%,oklch(100% 0 0 / .7) 0 1px,transparent 1.6px),
    radial-gradient(circle at 72% 91%,oklch(100% 0 0 / .56) 0 .8px,transparent 1.4px),
    radial-gradient(circle at 84% 96%,oklch(100% 0 0 / .44) 0 .7px,transparent 1.3px);
  opacity:.66;
}
/* Pure cap: no rimless side glows, a slightly shallower depth bloom —
   hairlines and edge ring as on the main cap. (The demo also hides its
   V1 inner frame here; this port never carried that frame — our
   cap::before is the backdrop-filter host and must survive.) */
.ice-key.pure .key-cap{
  box-shadow:
    inset 0 var(--ice-hairline) 0 oklch(100% 0 0 / calc(.86 - var(--press) * .86)),
    inset var(--ice-hairline) 0 0 oklch(100% 0 0 / calc(.54 - var(--press) * .54)),
    inset calc(0px - var(--ice-hairline)) 0 0 oklch(100% 0 0 / calc(.38 - var(--press) * .38)),
    inset 0 -1px 0 oklch(100% 0 0 / calc(.55 - var(--press) * .55)),
    inset 0 -4px 8px -5px var(--ice-deep),
    inset 0 0 0 1px oklch(72% .115 226 / calc(.74 - var(--press) * .74)),
    0 7px 12px -7px var(--ice-shadow);
}
/* Product-mark lettering at the wordmark's .12em tracking — white and
   shadowless on both keys (user call; the demo's engraved dark-and-
   relief treatment is retired). The demo bakes uppercase into the text;
   here the baked-in translations stay title-case and the transform does
   the work (no-op for CJK locales). Leading icons (console's plus/top-up
   glyphs) and the trailing arrow are stroke-drawn SVGs on currentColor. */
.ice-key .label{
  position:relative;
  z-index:2;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  color:oklch(100% 0 0);
  font-family:'Manrope Wordmark','Avenir Next','Helvetica Neue',Arial,sans-serif;
  font-size:11px;
  font-weight:700;
  letter-spacing:.12em;
  text-transform:uppercase;
  white-space:nowrap;
  /* +2px: optical centring, not press math — the key reads as cap face
     PLUS the 3D body revealed below it, so eyes centre the label against
     the whole silhouette, whose centre sits below the cap's geometric
     one; uppercase (no descenders) pushes the same way. Dialled in two
     user-reviewed steps to exactly 2px on 2026-07-13 — every ice key
     site-wide, all size grades. A transform constant keeps layout (and
     the key's label-driven sizing) untouched. */
  transform:translateY(calc(var(--press) * .5px + 2px));
}
.ice-key .label svg{width:11px;height:11px;flex-shrink:0}
.ice-key .arrow{
  opacity:.7;
  transition:transform .22s cubic-bezier(.22,.8,.3,1);
}
.ice-key:hover .arrow{transform:translateX(2px)}
/* ── .mini size grade — 32px tall: console chrome and the header auth
   pill (index.html). Same layer recipe compressed: 3px base reveal,
   5px press travel (2px overshoot), hairline details unchanged
   (they're material, not geometry). */
.ice-key.mini{
  border-radius:11px;
}
.ice-key.mini:focus-visible::after{
  inset:-5px;
  border-radius:15px;
}
.ice-key.mini .key-cap{
  margin:0 1px 3px;
  padding:6px 13px;
  border-radius:9px;
  transform:
    rotateX(var(--tilt-x))
    rotateY(var(--tilt-y))
    translateY(calc(var(--press) * 5px));
}
.ice-key.mini .key-base{
  inset:3px 1px 0;
  border-radius:9px;
  transform:translateY(2px);
}
.ice-key.mini .key-shadow{
  left:5px;right:5px;bottom:-7px;
  height:10px;
  filter:blur(3px);
}
.ice-key.mini .key-caustic{
  left:13px;right:13px;bottom:-5px;
  height:6px;
  filter:blur(1px);
}
.ice-key.mini .label{
  font-size:10.5px;
  letter-spacing:.08em;
}

/* ── Gecko & WebKit: bake the frost in ───────────────────────────────
   Chromium reads the cap's inset hairlines through the ::before
   backdrop blur — that softness is a design constant (the crisp
   variant was rejected) — because Blink's backdrop sampling includes
   the host cap's own paint. Gecko and WebKit both exclude it (and
   include what sits behind the cap), so the same stylesheet produced
   the opposite reading: raw full-alpha hairlines as a harsh thick
   outline, plus the blur smearing foreign paint into the cap's edges —
   on Firefox the key-base's bright top lit the rim (user report
   2026-07-13, dark hero), on Safari/WebKit the page's dark backdrop
   bled in as a truncated-looking top band (user report same day,
   reproduced on WebKitGTK 26.5). The Chromium composition can't exist
   under those semantics, so both engines bake the glass instead:
   backdrop-filter off, cap gradient opacity raised to carry the frost
   itself (press still clears it a step), hairlines pre-softened to
   what they measure through Chromium's blur. Gates verified lime-test:
   -moz-appearance hits Gecko only (FF 152), -webkit-hyphens hits
   WebKit only (Blink never carried that prefix). */
@supports (-moz-appearance:none) or (-webkit-hyphens:none){
  .ice-key .key-cap::before{backdrop-filter:none;-webkit-backdrop-filter:none}
  .ice-key .key-cap{
    background:
      radial-gradient(120% 125% at 50% 42%,
        oklch(91% .035 220 / calc(.6 - var(--press) * .1)) 24%,
        oklch(78% .095 226 / calc(.66 - var(--press) * .12)) 71%,
        oklch(65% .145 234 / calc(.82 - var(--press) * .16)) 100%);
    box-shadow:
      inset 0 var(--ice-hairline) 0 oklch(100% 0 0 / calc(.42 - var(--press) * .42)),
      inset var(--ice-hairline) 0 0 oklch(100% 0 0 / calc(.26 - var(--press) * .26)),
      inset calc(0px - var(--ice-hairline)) 0 0 oklch(100% 0 0 / calc(.19 - var(--press) * .19)),
      inset 0 -1px 0 oklch(100% 0 0 / calc(.27 - var(--press) * .27)),
      inset 0 -5px 9px -6px var(--ice-deep),
      inset 7px 0 11px -10px oklch(100% 0 0 / .38),
      inset -7px 0 11px -10px oklch(49% .13 239 / .2),
      inset 0 0 0 1px oklch(72% .115 226 / calc(.36 - var(--press) * .36)),
      0 7px 12px -7px var(--ice-shadow);
  }
  /* pure's own stack outranks the block above ((0,3,0) vs (0,2,0)) —
     restate it pre-softened or pure keys keep the raw hairlines */
  .ice-key.pure .key-cap{
    box-shadow:
      inset 0 var(--ice-hairline) 0 oklch(100% 0 0 / calc(.42 - var(--press) * .42)),
      inset var(--ice-hairline) 0 0 oklch(100% 0 0 / calc(.26 - var(--press) * .26)),
      inset calc(0px - var(--ice-hairline)) 0 0 oklch(100% 0 0 / calc(.19 - var(--press) * .19)),
      inset 0 -1px 0 oklch(100% 0 0 / calc(.27 - var(--press) * .27)),
      inset 0 -4px 8px -5px var(--ice-deep),
      inset 0 0 0 1px oklch(72% .115 226 / calc(.36 - var(--press) * .36)),
      0 7px 12px -7px var(--ice-shadow);
  }
}

/* ── Low-density displays: pixel-snap edges, preserve the material ───
   Physical PPI is deliberately not exposed reliably by browsers, but
   resolution:dppx describes the raster grid CSS is actually painted onto
   (and follows browser zoom). At <=1.25dppx a 1.5px hairline straddles the
   physical grid and can read as a jagged double edge, so only those four
   edge offsets are quantized to 1px.

   Crucially this branch does NOT change the cap gradient, backdrop filter,
   tilt, reflection or fracture artwork. An earlier revision borrowed the
   opaque Gecko/WebKit baked-frost background here (.60/.66/.82 instead of
   the original .24/.31/.58 alphas), which made low-DPI keys visibly less
   translucent. Each engine now keeps exactly its high-DPI material recipe;
   this query changes raster geometry only. */
@media (max-resolution:1.25dppx){
  .ice-key{--ice-hairline:1px}
}

/* Demo-family curvature (2026-07-12): the key adopts the same continuous-corner
   family as the text fields (superellipse(1.53), corner box = 0.4255 x part
   height) so login forms read as one geometry. Radii scale per layer; the
   circular focus ring keeps the site-wide convention. Non-supporting engines
   keep the circular radii above. */
@supports (corner-shape: superellipse(1.53)){
  .ice-key{border-radius:18.3px;corner-shape:superellipse(1.53)}
  /* cap margin(0 2px 4px) and base inset(4px 2px 0) yield the SAME box —
     one radius for both; the artwork ::before needs its own corner-shape
     (not inherited, border-radius:inherit alone keeps it circular) */
  .ice-key .key-base,.ice-key .key-cap{border-radius:16.6px;corner-shape:superellipse(1.53)}
  .ice-key .key-base::before,.ice-key .key-base::after{corner-shape:superellipse(1.53)}
  .ice-key:focus-visible::after{border-radius:20px}
  .ice-key.mini{border-radius:13.6px}
  .ice-key.mini .key-cap,.ice-key.mini .key-base{border-radius:11.1px}
  .ice-key.mini:focus-visible::after{border-radius:16px}
}
