/* ═══════════════════════════════════════════════════════════════════════
   SURFACE — the material system

   The previous glass was one blur and a wash. Real glass does more than
   blur: it has thickness, it catches light along its edges, it refracts
   differently at the rim than in the middle, and it is never perfectly
   smooth. Plastic is smooth. That is why cheap glass UI looks like plastic.

   Five things stacked, each doing one job:

     1. backdrop      blur + saturate + a slight brightness lift
     2. body tint     a thin wash, weighted to a vignette, never flat
     3. grain         a real noise texture, ~2% opacity — kills the plastic
     4. rim light     a bright top edge and a dark bottom edge = thickness
     5. specular      a soft highlight that tracks the pointer

   Radii are large and consistent. Nothing in this app has a sharp corner.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Ink ─────────────────────────────────────────────────────────────
     The secondary inks are derived, not fixed. js/contrast.js measures the
     footage and, when washing the scrim cannot reach the contrast the type
     needs, raises --ink-shift; the inks then pull toward the primary. The
     display sizes hardly need it — the 0.55rem labels are why it exists.

     If color-mix is unavailable the custom property is invalid at
     computed-value time and the colour inherits, which lands on --ink-1:
     darker than intended, still perfectly readable. A safe way to fail. */
  --ink-shift: 0;

  /* These were tuned as decoration and were far too pale to READ. The
     0.55rem tracked uppercase labels are the worst case in the whole
     system — small, letterspaced, low mass — and they were being set in
     what amounted to light grey. Every step is darker now, and --ink-4,
     which carries every eyebrow and every vital label, most of all. */
  --ink-1: #171110;
  --ink-2-base: #4a3c34;
  --ink-3-base: #716056;
  --ink-4-base: #907d72;
  --ink-5-base: #b6a599;

  --ink-2: color-mix(in oklab, var(--ink-2-base), var(--ink-1) calc(var(--ink-shift) * 70%));
  --ink-3: color-mix(in oklab, var(--ink-3-base), var(--ink-1) calc(var(--ink-shift) * 68%));
  --ink-4: color-mix(in oklab, var(--ink-4-base), var(--ink-1) calc(var(--ink-shift) * 64%));
  --ink-5: color-mix(in oklab, var(--ink-5-base), var(--ink-1) calc(var(--ink-shift) * 52%));

  /* ── Palette ─────────────────────────────────────────────────────── */
  --paper:      #faf7f3;
  --paper-warm: #f4ece2;
  --paper-deep: #e7dacb;

  --rose:       #c47870;
  --rose-deep:  #9e5550;
  --rose-soft:  rgba(196, 120, 112, 0.13);
  --sage:       #7d9a78;
  --amber:      #b8894f;

  --ok:     #5c9b64;
  --warn:   #b8894f;
  --danger: #bf5852;

  /* ── Radii — nothing is square ───────────────────────────────────── */
  --r-xs:   12px;
  --r-sm:   16px;
  --r-md:   22px;
  --r-lg:   28px;
  --r-xl:   34px;
  --r-2xl:  44px;
  --r-pill: 999px;

  /* ── Type ────────────────────────────────────────────────────────── */
  --font-ui: -apple-system, "SF Pro Display", "SF Pro Text", "Inter",
             system-ui, "Segoe UI", sans-serif;
  --font-num: ui-monospace, "SF Mono", "JetBrains Mono", monospace;
  --font-display: "Cormorant Garamond", Georgia, "Times New Roman", serif;

  /* ── Motion ──────────────────────────────────────────────────────────
     Three curves, each with a job. Mixing more than this makes an app feel
     like several apps stapled together.
       glide  — the default; decelerates hard, never overshoots
       spring — for things that gain presence (sheets, cards arriving)
       snap   — for things that must feel instant (toggles, taps)          */
  --glide:  cubic-bezier(0.22, 0.68, 0, 1);
  --spring: cubic-bezier(0.34, 1.46, 0.54, 1);
  --snap:   cubic-bezier(0.4, 0, 0.2, 1);

  /* Halved. The old figures were chosen as "elegant" and read as slow —
     a hover that takes a third of a second to acknowledge you feels
     broken, not refined. Refinement is in the curve, not the duration. */
  --t-fast: 0.10s;
  --t-med:  0.18s;
  --t-slow: 0.32s;

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
}

/* ═══ The display face ════════════════════════════════════════════════
   Self-hosted, latin subset, one 37 KB variable file covering 300–700.
   Served from our own origin so the largest text on screen does not wait
   on a third-party DNS lookup and TLS handshake, and so it works with no
   network at all. swap, not block: a verdict in the fallback face beats no
   verdict. Reserved for display sizes — body text stays in SF, where the
   hinting and the optical sizes are doing real work.                      */
@font-face {
  font-family: 'Cormorant Garamond';
  src: url('/assets/fonts/cormorant-latin.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F,
                 U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
}

/* ═══ Grain ═══════════════════════════════════════════════════════════
   Fractal noise as a data URI. The single highest-value detail here: it is
   what separates "glass" from "translucent plastic". Kept at 2–4% or it
   reads as dirt rather than texture. */
:root {
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%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='140' height='140' filter='url(%23n)' opacity='0.42'/%3E%3C/svg%3E");
}

/* ═══ Layer 0 — the footage ═══════════════════════════════════════════ */
#videobg {
  position: fixed; inset: 0; z-index: 0; overflow: hidden;
  background:
    radial-gradient(120% 90% at 20% 10%, #f6ece0 0%, transparent 60%),
    radial-gradient(110% 90% at 85% 85%, #efdfd4 0%, transparent 62%),
    linear-gradient(165deg, #f7f2ea 0%, #ecdfd2 55%, #e2d2c2 100%);
}
#videobg video {
  position: absolute; top: 50%; left: 50%;
  min-width: 100%; min-height: 100%; width: auto; height: auto;
  transform: translate(-50%, -50%) scale(1.06);
  object-fit: cover; opacity: 0;
  transition: opacity 2.6s ease;      /* = FADE_MS in video-bg.js */
  will-change: opacity;
}
#videobg video.on { opacity: 1; }

/* ═══ Layer 1 — the scrim ═════════════════════════════════════════════
   Blur destroys detail, saturate stops the smear going muddy, and the wash
   ties an arbitrary clip into the palette. No brightness push: that is what
   turns every clip into the same yellow haze. */
#scrim {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
}
/* The adaptive wash. One clip plays per day and nobody gets to check the
   luminance behind the type first, so js/contrast.js measures the live
   frame and solves for the alpha that lifts it to a backdrop the ink is
   known to read against. The transition is deliberately long: this should
   never be something you catch happening. The fallback value is the fixed
   wash this replaced, so the surface is still correct with JS disabled. */
#scrim::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: var(--paper);
  opacity: var(--veil, 0.14);
  transition: opacity 1.8s var(--glide);
}
.tex-liquid #scrim {
  background:
    linear-gradient(180deg,
      rgba(250,246,238,0.22) 0%, rgba(250,246,238,0.08) 28%,
      rgba(248,242,232,0.10) 70%, rgba(226,213,190,0.30) 100%),
    radial-gradient(116% 92% at 50% 42%,
      rgba(255,253,247,0.03) 0%, rgba(92,78,54,0.15) 100%);
  -webkit-backdrop-filter: blur(6px) saturate(1.45) contrast(1.03);
          backdrop-filter: blur(6px) saturate(1.45) contrast(1.03);
}
.tex-frosted #scrim {
  background:
    linear-gradient(180deg,
      rgba(250,247,241,0.26) 0%, rgba(249,244,236,0.12) 32%,
      rgba(246,240,230,0.14) 68%, rgba(228,216,197,0.32) 100%),
    radial-gradient(120% 96% at 50% 40%,
      rgba(255,253,248,0.03) 0%, rgba(88,74,52,0.14) 100%);
  /* A full-screen backdrop blur is the single most expensive thing on this
     page — it runs every frame the video advances. 26px was both costlier
     than it needed to be and enough to erase the footage entirely. */
  -webkit-backdrop-filter: blur(9px) saturate(1.3) contrast(1.02);
          backdrop-filter: blur(9px) saturate(1.3) contrast(1.02);
}

/* ═══════════════════════════════════════════════════════════════════════
   THE GLASS COMPONENT
   One class. Depth is a variable, not a separate component.
   ═══════════════════════════════════════════════════════════════════════ */
/* THE NUMBERS HERE WERE WRONG. 34px of blur under a 0.56 white tint is not
   glass, it is frosted plastic: nothing survives it, so the panel reads as
   a solid card that happens to be slightly warm. You should be able to make
   out what is behind a pane of glass — that is most of what tells the eye
   it IS glass and not paint. Blur and tint are both roughly halved, and the
   rim and shadow do correspondingly more of the work of separating the
   panel from the footage. */
.g {
  --blur: 15px;
  --sat: 1.5;
  --tint: 0.26;
  --rim: 0.95;
  --mx: 50%;            /* pointer position, set by motion.js */
  --my: 0%;

  position: relative;
  isolation: isolate;
  border-radius: var(--r-lg);
  background:
    linear-gradient(157deg,
      rgba(255,255,255,0.42) 0%,
      rgba(255,250,245,0.08) 46%,
      rgba(252,242,234,0.16) 100%),
    rgba(255, 252, 249, var(--tint));
  -webkit-backdrop-filter: blur(var(--blur)) saturate(var(--sat));
          backdrop-filter: blur(var(--blur)) saturate(var(--sat));

  /* Thickness. A bright top rim and a darker bottom rim is the whole trick:
     it implies an edge catching light from above and a body with depth. */
  border: 1px solid rgba(255,255,255,0.34);
  border-top-color: rgba(255,255,255, var(--rim));
  border-bottom-color: rgba(178,152,138,0.20);

  box-shadow:
    0 1px 1px  rgba(120,78,66,0.03),
    0 4px 12px rgba(120,78,66,0.05),
    0 16px 42px rgba(120,78,66,0.07),
    inset 0 1px 0 rgba(255,255,255,0.86),
    inset 0 -1px 0 rgba(160,132,118,0.07);

  /* NOT transform. Several .g surfaces have their transform written every
     frame by JavaScript — the phone's sheet follows a finger, the desktop's
     plate grows out of what you clicked — and a CSS transition on transform
     smears each of those writes over 340ms. The result is a sheet that lags
     a third of a second behind the thumb dragging it, which is the exact
     opposite of the point. Transform transitions belong on .g-lift, where
     the browser is the only thing setting them. */
  transition:
    box-shadow var(--t-med) var(--glide),
    border-color var(--t-med) var(--glide);
}

/* Grain + refraction. Sits under the content, above the backdrop. */
.g::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit; pointer-events: none;
  background-image: var(--grain),
    linear-gradient(140deg,
      rgba(255,255,255,0.26) 0%, rgba(255,238,231,0.05) 34%,
      transparent 58%, rgba(255,214,204,0.05) 100%);
  background-size: 140px 140px, 100% 100%;
  opacity: 0.55;
  mix-blend-mode: overlay;
}

/* Specular. Follows the pointer, so the surface reads as curved rather than
   as a flat pane with a highlight painted on it. */
.g::after {
  content: ''; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit; pointer-events: none;
  background: radial-gradient(58% 42% at var(--mx) var(--my),
    rgba(255,255,255,0.30) 0%, rgba(255,255,255,0.10) 38%, transparent 72%);
  opacity: 0;
  transition: opacity var(--t-slow) var(--glide);
}
.g:hover::after { opacity: 1; }

/* ── Depth variants ─────────────────────────────────────────────────── */
.g.g-thin  { --blur: 10px; --sat: 1.4; --tint: 0.16; --rim: 0.8; }
.g.g-deep  { --blur: 20px; --sat: 1.6; --tint: 0.36; --rim: 0.98;
             border-color: rgba(255,255,255,0.5);
             border-bottom-color: rgba(150,124,110,0.3);
             box-shadow:
               0 2px 4px   rgba(120,78,66,0.06),
               0 12px 30px rgba(120,78,66,0.11),
               0 40px 80px rgba(120,78,66,0.14),
               inset 0 1px 0 rgba(255,255,255,0.98); }
.g.g-flush { box-shadow: inset 0 1px 0 rgba(255,255,255,0.6); border-radius: var(--r-md); }

/* Liquid runs thinner and wetter; frosted runs calmer. Both stay see-through:
   the difference is the character of the blur, not how much it hides. */
.tex-liquid  .g { --blur: 13px; --sat: 1.7; --tint: 0.24; }
.tex-frosted .g { --blur: 17px; --sat: 1.45; --tint: 0.28; }
.tex-liquid  .g.g-deep { --blur: 19px; --tint: 0.34; }
.tex-frosted .g.g-deep { --blur: 22px; --tint: 0.38; }

/* ── Interactive lift ───────────────────────────────────────────────── */
.g-lift { cursor: pointer; transition: transform var(--t-med) var(--glide); }
.g-lift:hover {
  transform: translateY(-2px);
  border-top-color: #fff;
  box-shadow:
    0 2px 3px   rgba(120,78,66,0.04),
    0 8px 20px  rgba(120,78,66,0.07),
    0 26px 58px rgba(120,78,66,0.10),
    inset 0 1px 0 rgba(255,255,255,0.95);
}
.g-lift:active { transform: translateY(0) scale(0.994); transition-duration: 0.08s; }

/* ═══ Focus ═══════════════════════════════════════════════════════════ */
:focus-visible {
  outline: 2px solid var(--rose);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

/* ═══ Reduced motion ══════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  #videobg video { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   The phone used to alias .pane onto this material so that its 22 existing
   call sites did not have to be touched. The phone has since been rebuilt on
   .g directly, so the alias is gone. glass.css still defines .pane for
   desktop.html, the old fallback shell, which is on tex-frosted and was
   never reached by the aliased rules anyway.
   ═══════════════════════════════════════════════════════════════════════ */
