/* ========================================================================
   "Anchor Drop" page intro (design handoff, 2026-09-10)
   A full-screen overlay that plays once per session on first load: an
   anchor on a navy rope drops from above the viewport and lands in a
   field of notebook rules, which ripple outward from the point of impact
   like water; the wordmark fades up; the lines settle; the overlay lifts
   off the top of the screen to reveal the page.

   It must never block the page. The lift animation uses fill-mode `both`,
   so even with JS disabled the overlay ends translated off-screen and the
   page beneath is usable; landing-intro.js removes it from the DOM on
   animationend. `html.intro-seen` (set by the inline head script from the
   sessionStorage flag, before first paint) suppresses it for the rest of
   the session. Timings and easings are the handoff's canonical values;
   `--intro-speed` scales every one of them together.
   Shared by /welcome (landing.css @imports this) and the library home
   (public_library.html links it directly); the token fallbacks are what
   let it work on a page that does not load tokens.css.
   ======================================================================== */
:root {
  --intro-speed: 1;
  --intro-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --intro-ease-sine: cubic-bezier(0.37, 0, 0.63, 1);
  --intro-rope: #2c4066;
}
.intro-seen .intro { display: none; }
.intro {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; background: var(--sand, #f8f6f2);
  will-change: transform;
  animation: intro-lift calc(0.95s * var(--intro-speed)) var(--intro-ease-out)
             calc(3.15s * var(--intro-speed)) both;
}
/* Notebook-line "water": one full-width SVG per row; --a (amplitude) and
   --d (delay) grow/shrink with the row's distance from the impact row so
   the ripple reads as spreading outward from where the anchor hit. */
.intro__waves {
  position: absolute; inset: 0;
  display: grid; grid-template-rows: repeat(14, 1fr);
  animation: intro-dim calc(0.5s * var(--intro-speed)) linear
             calc(2.95s * var(--intro-speed)) both;
}
.intro__wave { width: 100%; height: 100%; overflow: visible; }
.intro__wave path {
  fill: none; stroke-width: 1.25; vector-effect: non-scaling-stroke;
  transform-box: fill-box; transform-origin: center;
  transform: scaleY(0.015);
  animation: intro-ripple calc(1.7s * var(--intro-speed)) var(--intro-ease-sine)
             calc(var(--d) * var(--intro-speed)) both;
}
.intro__anchor-group {
  position: relative; display: grid; justify-items: center; gap: 26px;
  animation: intro-drop calc(1.25s * var(--intro-speed)) var(--intro-ease-out)
             calc(0.15s * var(--intro-speed)) both;
}
/* The rope's lower end sits 23px INTO the anchor image (≈19.5% of its
   height) so it is hidden behind the solid upper band of the eyelet ring
   rather than showing through the hole. Re-derive if the anchor resizes. */
.intro__rope {
  position: absolute; left: 50%; bottom: calc(100% - 23px);
  width: 7px; height: 100vh; transform: translateX(-50%); border-radius: 4px;
  background-color: var(--intro-rope);
  background-image:
    repeating-linear-gradient(-34deg,
      rgba(255,255,255,0.26) 0px, rgba(255,255,255,0.26) 1.5px,
      rgba(0,0,0,0) 1.5px, rgba(0,0,0,0) 3px,
      rgba(10,18,34,0.5) 3px, rgba(10,18,34,0.5) 5px,
      rgba(0,0,0,0) 5px, rgba(0,0,0,0) 7px),
    linear-gradient(90deg,
      rgba(10,18,34,0.5) 0%, rgba(255,255,255,0.2) 34%,
      rgba(255,255,255,0.04) 62%, rgba(10,18,34,0.55) 100%);
  box-shadow: 0 0 0 0.5px rgba(15,26,46,0.35);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 26%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 26%);
}
.intro__anchor {
  display: block; width: 118px; height: 118px;
  transform-origin: 50% 16%;   /* pivot at the eyelet, so the sway hangs from the rope */
  animation: intro-sway calc(3.6s * var(--intro-speed)) var(--intro-ease-sine)
             calc(1.3s * var(--intro-speed)) infinite;
}
.intro__wordmark-block {
  display: grid; justify-items: center; gap: 10px;
  animation: intro-fade-up calc(0.7s * var(--intro-speed)) var(--intro-ease-out)
             calc(1.35s * var(--intro-speed)) both;
}
.intro__wordmark {
  font-family: var(--font-sans, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); font-size: clamp(2.75rem, 7vw, 4.25rem);
  font-weight: 700; letter-spacing: -0.04em; line-height: 1; color: var(--navy, #1a2744);
}
.intro__eyebrow {
  font-family: var(--font-sans, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); font-size: clamp(0.875rem, 1.5vw, 1.0625rem);
  font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; color: var(--teal, #1a7a6d);
}
@keyframes intro-drop {
  0%   { transform: translateY(-62vh) rotate(-7deg); opacity: 0; }
  10%  { opacity: 1; }
  100% { transform: translateY(0) rotate(0deg); opacity: 1; }
}
@keyframes intro-sway {
  0%, 100% { transform: rotate(-1.6deg); }
  50%      { transform: rotate(1.6deg); }
}
/* A damped oscillation, not a one-way pulse — the sign flips are what
   make it read as water rather than a stretch. */
@keyframes intro-ripple {
  0%   { transform: scaleY(0.015); }
  16%  { transform: scaleY(var(--a, 1)); }
  34%  { transform: scaleY(calc(var(--a, 1) * -0.58)); }
  52%  { transform: scaleY(calc(var(--a, 1) * 0.33)); }
  70%  { transform: scaleY(calc(var(--a, 1) * -0.18)); }
  86%  { transform: scaleY(calc(var(--a, 1) * 0.08)); }
  100% { transform: scaleY(0.015); }
}
@keyframes intro-fade-up {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes intro-lift {
  from { transform: translateY(0); }
  to   { transform: translateY(-102%); }
}
@keyframes intro-dim { to { opacity: 0; } }
/* Reduced motion: skip the intro entirely. Collapsing the durations is not
   enough — the library home's stylesheet disables every animation under
   this query, so the overlay was left as a frozen still of the finished
   scene until the JS teardown timer removed it (seen on Android, where
   "Remove animations" reports this preference to every browser). A brand
   moment with no motion is just a wall in front of the page. */
@media (prefers-reduced-motion: reduce) {
  .intro { display: none !important; }
}
