/* ═══════════════════════════════════════════════════════════════════════════
   MARBLELENS V3 · LENS — brass and glass, above the paper

   The material for js/lens.js. Authored against a fixed 100px box and only
   ever scaled with `transform`, so rim weight, highlight position and shadow
   throw stay proportional at every diameter — the instrument survives
   movement and scale instead of being redrawn per size.

   MATERIAL DECISIONS
   · The rim is an ANNULUS (masked ring), never a filled disc — the paper
     must stay visible through the glass or the object floats.
   · Brass is aged: the conic sheen tops out at a muted #e2c489, never white.
     A polished-mirror highlight is exactly the glossy-futuristic read the
     brief forbids.
   · The glass carries only a soft key-light and an edge darkening; the real
     optical work (magnified developing content) is the hero's develop layer
     clipped to this circle. backdrop-filter here is a whisper — brightness
     and a hair of blur for edge softness — not glassmorphism frost.
   · The contact shadow is an offset ellipse UNDER the object. It is what
     keeps the lens ON the sheet.
   ═══════════════════════════════════════════════════════════════════════════ */

.v3-lens {
  position: absolute;
  top: 0;
  left: 0;
  width: 100px;
  height: 100px;
  transform-origin: 50% 50%;
  will-change: transform, opacity;
  pointer-events: none;
  z-index: 40;
  opacity: 0;
}

/* ── ambient body shade ───────────────────────────────────────────────────
   Softer than before: this is only the body's general shade — the DARK of
   the contact travels in `.v3-press` below, following the load. Keeping
   them separate is what distinguishes rolling from a decal glued to a
   gliding object. */
.v3-lens-shadow {
  position: absolute;
  left: 4%;
  right: 4%;
  top: 66%;
  height: 30%;
  border-radius: 50%;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(28, 20, 10, 0.20), rgba(28, 20, 10, 0.07) 55%, rgba(28, 20, 10, 0) 74%);
  filter: blur(2.5px);
  transform: translateY(26%);
}
.v3-lens[data-mode="docked"] .v3-lens-shadow {
  top: 72%;
  transform: translateY(14%);
  filter: blur(1.5px);
  background: radial-gradient(ellipse at 50% 50%,
    rgba(28, 20, 10, 0.26), rgba(28, 20, 10, 0.09) 55%, rgba(28, 20, 10, 0) 74%);
}

/* ── glass ── */
.v3-lens-glass {
  position: absolute;
  inset: 11%;
  border-radius: 50%;
  border: 1px solid rgba(58, 44, 20, 0.42);
  background:
    /* edge fall-off — the thickness of the glass, not a tint. The key
       light lives in .v3-lens-glint so it can lag the roll. */
    radial-gradient(circle at 50% 50%,
      rgba(255, 252, 245, 0.02) 52%, rgba(46, 38, 20, 0.07) 82%,
      rgba(46, 38, 20, 0.16) 97%, rgba(46, 38, 20, 0.22) 100%);
  -webkit-backdrop-filter: brightness(1.045) contrast(1.03) saturate(1.05) blur(0.4px);
  backdrop-filter: brightness(1.045) contrast(1.03) saturate(1.05) blur(0.4px);
}

/* the key light on the glass — its own layer so movement can drag it a
   few pixels behind the travel (a glint sliding on moving convex glass).
   It yields to the brass core as the full stop fills (JS sets opacity). */
.v3-lens-glint {
  position: absolute;
  inset: 11%;
  border-radius: 50%;
  will-change: transform, opacity;
  background: radial-gradient(circle at 32% 26%,
    rgba(255, 252, 242, 0.30), rgba(255, 252, 242, 0.06) 36%, rgba(255, 252, 242, 0) 54%);
}

/* ── aged brass rim ── */
.v3-lens-rim {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    /* top-light across the metal */
    radial-gradient(circle at 30% 22%, rgba(255, 244, 214, 0.42), rgba(255, 244, 214, 0) 46%),
    /* the turned brass itself — uneven on purpose; aged, not mirror */
    conic-gradient(from 208deg,
      #6a5226, #937143 11%, #e2c489 21%, #b08c4c 33%, #755c2d 47%,
      #8a6c35 60%, #634c22 72%, #d3b378 84%, #6a5226 100%);
  /* closest-side, so 100% is the box's own radius — with the default
     farthest-corner the ring rasterises at 70% of its intended radius,
     detaches from the edge stroke and slides under the glass */
  -webkit-mask: radial-gradient(circle closest-side,
    transparent 0 77%, #000 80% 99%, transparent 100%);
  mask: radial-gradient(circle closest-side,
    transparent 0 77%, #000 80% 99%, transparent 100%);
}
/* outer edge stroke — the machined lip that keeps the ring crisp once the
   mask has taken box-shadow away from us */
.v3-lens::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid rgba(50, 38, 16, 0.5);
}
.v3-lens::after {
  content: "";
  position: absolute;
  inset: 10%;
  border-radius: 50%;
  border: 1px solid rgba(240, 222, 178, 0.28);
}

/* ── the docked full stop ─────────────────────────────────────────────────
   Same object, parked. At ~11px an annulus and a clear glass read as
   nothing at all, so docking FILLS the glass with brass: the core fades in
   while the lens shrinks — the instrument setting the period. The ring and
   glass stay in the stack, which is what keeps the dot reading as turned
   metal rather than a flat circle. */
.v3-lens-core {
  position: absolute;
  inset: 10%;
  border-radius: 50%;
  /* driven by the model through lens.set(); no CSS clock of its own.
     CAPPED below full: the docked stop is now §15-visible (0.55em of the
     headline, not a period dot), so the set brass must stay a warm wash
     the glass survives — rim, glass centre and depth all remain read-able
     on the parked instrument. */
  opacity: calc(var(--core, 0) * 0.55);
  /* weighted dark: at period size the highlight must be a glint on solid
     metal, or the full stop reads as a pale bead against the type's ink */
  background:
    radial-gradient(circle at 32% 26%,
      rgba(255, 246, 222, 0.85), rgba(230, 200, 138, 0.32) 26%, rgba(0, 0, 0, 0) 48%),
    radial-gradient(circle at 50% 58%, #8a6c35 22%, #63491d 62%, #3f2f10 100%);
}
.v3-lens[data-mode="docked"] .v3-lens-glass {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.v3-lens[data-mode="hidden"] { visibility: hidden; }

/* ── the rolling evidence ─────────────────────────────────────────────────
   Machining and age on the rim: a hairline seam, one filled reference
   notch with its machined light edge, and two broad wear variations. This
   layer ROTATES — angle written by JS from the model's rolling odometry
   (θ ∝ distance / circumference), so a mark meets the paper exactly once
   per circumference and the contact point reads true. The conic sheen of
   the rim beneath does NOT rotate: highlights belong to the light, wear
   belongs to the object.
   Same annulus mask law as the rim: closest-side, or the ring rasterises
   at 70% radius (the farthest-corner trap). */
.v3-lens-marks {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  will-change: transform;
  pointer-events: none;
  background:
    /* broad aged-brass wear, two arcs, opposite temperatures */
    conic-gradient(from 208deg,
      transparent 0 12deg, rgba(46, 34, 12, 0.15) 22deg 52deg, transparent 64deg 360deg),
    conic-gradient(from 16deg,
      transparent 0 6deg, rgba(255, 240, 208, 0.09) 12deg 36deg, transparent 44deg 360deg),
    /* hairline seam */
    conic-gradient(from 118deg,
      rgba(52, 40, 16, 0.5) 0 0.5deg, transparent 0.9deg 360deg),
    /* reference notch + its machined bright edge */
    conic-gradient(from 296deg,
      rgba(38, 28, 10, 0.55) 0 1.7deg, rgba(238, 219, 172, 0.4) 1.9deg 2.5deg,
      transparent 3deg 360deg);
  -webkit-mask: radial-gradient(circle closest-side,
    transparent 0 78%, #000 81% 98.5%, transparent 100%);
  mask: radial-gradient(circle closest-side,
    transparent 0 78%, #000 81% 98.5%, transparent 100%);
}

/* ── the paper under the load ─────────────────────────────────────────────
   The sheet's half of the contact: authored at 130×36 for a 100px lens,
   scaled with the body, translated to the tangent point every frame. All
   the compression is LIGHTING — a darker contact core, a shaded concave
   top wall, recovering fibres catching light on the lower lip, and a flat
   tint that quietly mutes the grain (pressed fibres reflect evenly). No
   geometry moves; the sheet stays rigid.
     --load   1 while rolling, briefly >1 as the stop sets (::before firms)
     --trail  normalised travel speed — the short recovery smear behind the
              contact (::after); zero when stopped, so a parked lens leaves
              a clean, firm shadow and NOTHING springs back. */
.v3-press {
  position: absolute;
  top: 0;
  left: 0;
  width: 130px;
  height: 36px;
  z-index: 35;               /* above the sheets and the develop circle,
                                below the instrument itself */
  pointer-events: none;
  will-change: transform, opacity;
  opacity: 0;
  background:
    /* travelling contact core — the darkest point rides the tangent */
    radial-gradient(ellipse 26% 40% at 50% 46%,
      rgba(28, 20, 10, 0.28), rgba(28, 20, 10, 0.09) 55%, transparent 76%),
    /* concave top wall in the page's top light */
    radial-gradient(ellipse 46% 42% at 50% 33%,
      rgba(31, 28, 20, 0.09), transparent 70%),
    /* recovering fibres catch the light along the lower lip */
    radial-gradient(ellipse 52% 34% at 50% 79%,
      rgba(255, 250, 238, 0.13), transparent 72%),
    /* flattened fibre — compressed sheet reflects flatter, muting grain */
    radial-gradient(ellipse 60% 62% at 50% 50%,
      rgba(236, 230, 218, 0.34), transparent 82%);
}
/* the settle firming — only exists while load exceeds its resting 1 */
.v3-press::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 30% 44% at 50% 48%,
    rgba(24, 17, 8, 0.26), transparent 70%);
  opacity: clamp(0, (var(--load, 1) - 1) * 3.2, 1);
}
/* the recovery — a short shaded smear where the sheet was a moment ago,
   fading toward "fully passed: normal paper". Speed-keyed: no movement,
   no smear. */
.v3-press::after {
  content: "";
  position: absolute;
  top: 24%;
  bottom: 20%;
  right: 56%;
  width: 52%;
  border-radius: 50%;
  background: linear-gradient(90deg,
    transparent, rgba(31, 28, 20, 0.07) 72%, rgba(31, 28, 20, 0.1));
  opacity: var(--trail, 0);
}

/* ── the replay control ───────────────────────────────────────────────────
   The docked lens is NARRATIVE interaction only (replay / inspect — never a
   product CTA). The control is a separate, honest button positioned over
   the full stop with a ≥44px hit area; the lens beneath is its visual. */
.v3-replay {
  position: absolute;
  z-index: 41;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transform: translate(-50%, -50%);
}
.v3-replay:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
}
.v3-replay[hidden] { display: none; }

/* §17 — the parked instrument acknowledges the visitor's approach: a
   restrained brightening, no scaling, no glow. The lens lives on the fixed
   page stage (not inside the pin), so the replay control toggles this class
   from JS; hover remains an ENHANCEMENT — the control itself is a plain
   focusable button. */
.v3-lens.v3-lens-approach {
  filter: brightness(1.07);
}


/* ── the instrument OFFERS ITSELF at the handover ──────────────────────────
   Its own class, not a louder .v3-lens-approach: the docked full stop uses that
   one and must stay as quiet as it is. Here the visitor has to FIND the lens
   before it will follow their hand, so "within reach" needs to be visible or
   the wait reads as a dead page. A brass ring breathing out from the rim --
   no bounce, no colour change, nothing the rest of the page would not do. */
.v3-lens.v3-lens-offer { filter: brightness(1.12); }
.v3-lens.v3-lens-offer::after {
  content: "";
  position: absolute;
  inset: -14%;
  border-radius: 50%;
  border: 1px solid var(--brass, #9c7a42);
  opacity: 0.55;
  animation: ml-offer 2.1s ease-out infinite;
  pointer-events: none;
}
@keyframes ml-offer {
  0%   { transform: scale(0.94); opacity: 0.55; }
  70%  { transform: scale(1.16); opacity: 0; }
  100% { transform: scale(1.16); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .v3-lens.v3-lens-offer::after { animation: none; opacity: 0.5; }
}
