/* ═══════════════════════════════════════════════════════════════════
   LUSTRE: the lighting layer
   ═══════════════════════════════════════════════════════════════════

   Loaded after blatt.css. It adds no new layout and no new colours. It
   only changes how light behaves on surfaces that already exist.

   WHY THIS EXISTS

   blatt.com/start was taken apart on 2026-08-07 to work out why its
   tier cards read as physical objects while the portal cards read as
   coloured rectangles. The answer was not 3D. Real 3D transforms appear
   in exactly one place on that whole page, the departure board flip.
   Everything else that looks dimensional does it with two techniques:

     1. A specular stop. A gradient that goes dark and then jumps
        straight back to light at its midpoint, which the eye reads as
        the line of light along a machined edge.

     2. A three-part shadow. An ambient shadow that puts the object in a
        room, a tighter contact shadow that puts it on a surface, and an
        inset highlight along the top edge that gives it a lip.

   That second one is the whole trick. `inset 0 1px 0` is one
   declaration and it is the difference between a card and a rectangle.

   WHAT IS DELIBERATELY NOT HERE

   No spinning. /start has a keyframe named `holographic` that is just
   rotate(0deg) to rotate(360deg). Things that spin look cheap. The
   sheen here travels in one direction across a surface and stops,
   because that is what light does when an object tilts.

   TWO SKINS, ONE ENGINE

   Every recipe below is driven by --lu-* variables. The founder pages
   set them to gold on black, which is Momentum. The registry sets them
   to platinum, titanium and gold, which is its product language. The
   physics do not change between them, only the substance being lit.

   ═══════════════════════════════════════════════════════════════════ */

/* ── Registered properties ─────────────────────────────────────────
   CSS cannot interpolate a gradient. It CAN interpolate a registered
   custom property that a gradient is built from. Registering these is
   what lets the sheen glide instead of snapping between keyframes, and
   it is the single reason the motion below reads as expensive rather
   than as a CSS animation. @supports-guarded: where @property is
   missing the sheen simply does not animate, and nothing breaks. */

@property --lu-sheen {
  syntax: '<percentage>';
  inherits: false;
  initial-value: -40%;
}

/* The tilt itself is NOT registered here and is not reimplemented here.
   blatt.css:617 already rotates by --px/--py and blatt.css:633 already
   places a cursor highlight at --mx/--my, both written by litSurfaces()
   in blatt.js. This file deliberately adds no second tilt system: two
   implementations writing transform on the same element is a fight, not
   a feature. What changed on 2026-08-07 is that litSurfaces() now
   selects .b-card as well as .b-portal, so the venture and practice
   cards on the founder pages are lit by the same engine that already
   lit the launchpad cards. */

/* ── The substance ────────────────────────────────────────────────
   Four stops, and the third one is the whole point: it is LIGHTER than
   the second, at the midpoint. That reversal is the specular line.

   Gold values are Momentum's own: #fabd59 with #c9902f as its shadow
   side. The highlight is not white, it is a pale warm gold, because
   white on gold reads as plastic. */

:root {
  --lu-lit:        #ffe0a3;   /* highlight, the lit face */
  --lu-mid:        #fabd59;   /* the body of the metal */
  --lu-shade:      #c9902f;   /* the turned-away face */
  --lu-deep:       #8a5f18;   /* the edge in shadow */

  --lu-ground:     #0b0b0c;
  --lu-surface:    #14141a;

  /* Shadow strengths. Tuned as a set: raising the ambient without
     raising the contact makes an object look like it is floating. */
  --lu-ambient:    0 30px 80px -28px rgba(0,0,0,0.95);
  --lu-contact:    0 10px 30px -12px rgba(0,0,0,0.85);
  --lu-lip:        inset 0 1px 0 rgba(255,255,255,0.10);
  --lu-lip-warm:   inset 0 1px 0 rgba(255,224,163,0.22);
  --lu-underlip:   inset 0 -1px 0 rgba(0,0,0,0.55);

  --lu-glide:      cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ── Recipe 1: brushed surface ────────────────────────────────────
   The specular stop applied to a dark surface. 148deg rather than 145
   is not fussiness: at 145 the highlight on a wide card lands exactly
   on the horizontal centre line and reads as a seam. Three degrees
   moves it off centre and it reads as light instead. */

.lu-brushed {
  background-image: linear-gradient(
    148deg,
    rgba(255,255,255,0.055) 0%,
    rgba(255,255,255,0.012) 44%,
    rgba(255,255,255,0.075) 50%,   /* the specular reversal */
    rgba(255,255,255,0.000) 58%,
    rgba(0,0,0,0.30) 100%
  );
}

/* ── Recipe 2: the lip ────────────────────────────────────────────
   Applied to anything that should read as a machined object rather
   than a div. Cards, panels, buttons. */

.lu-object {
  box-shadow: var(--lu-ambient), var(--lu-contact), var(--lu-lip), var(--lu-underlip);
}

/* ── Recipe 3: the sheen sweep ────────────────────────────────────
   A narrow band of light dragged across a surface, masked to the
   surface so it cannot spill past the corners. Driven by the
   registered --lu-sheen so it interpolates.

   The band is 18% wide and travels 180%, which means it enters, cross-
   es and fully leaves. A band that stops mid-surface reads as a bug. */

@supports (background: linear-gradient(in oklab, red, red)) {
  .lu-sheen { position: relative; overflow: hidden; }

  .lu-sheen::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    background: linear-gradient(
      104deg,
      transparent calc(var(--lu-sheen) - 9%),
      rgba(255,224,163,0.13) var(--lu-sheen),
      rgba(255,255,255,0.20) calc(var(--lu-sheen) + 2%),
      rgba(255,224,163,0.13) calc(var(--lu-sheen) + 4%),
      transparent calc(var(--lu-sheen) + 13%)
    );
    opacity: 0;
    transition: opacity 0.25s;
  }

  .lu-sheen:hover::after,
  .lu-sheen:focus-visible::after,
  .lu-sheen:focus-within::after {
    opacity: 1;
    animation: luSheen 1.15s var(--lu-glide) 1 forwards;
  }
}

@keyframes luSheen {
  from { --lu-sheen: -40%; }
  to   { --lu-sheen: 140%; }
}

/* ── Recipe 4: the lit card ───────────────────────────────────────
   .b-card now takes the tilt and the cursor highlight that .b-portal
   already had. The rotation and the highlight position both come from
   litSurfaces() in blatt.js via --px/--py/--mx/--my, so this only has
   to establish the 3D context and supply the highlight layer.

   Kept at the same 7 degrees .b-portal uses, so the two card types
   behave identically. Past about 8 it stops reading as a heavy object
   catching light and starts reading as a web page doing a trick.

   perspective on the element rather than the parent is deliberate: the
   cards sit in a grid, and a shared parent perspective makes cards at
   the edges skew differently from cards in the middle, which reads as a
   rendering fault rather than as depth. */

/* ── Card rhythm ──────────────────────────────────────────────────
   Anric called these boxes unsymmetrical. The grid change in blatt.css
   makes every card in a row the same HEIGHT; this makes their CONTENTS
   agree, which is the half people actually notice.

   A column layout with the meta row pushed down by margin-top:auto means
   the last line of every card in a row sits on the same baseline no
   matter how much body copy sits above it. Without this, equal-height
   cards just have their text floating at different depths, which looks
   worse than ragged bottoms did.

   Fixed min-heights on the label and heading stop a one-line title and a
   two-line title from shifting everything below them out of alignment.
   That is the difference between "Consulting" and "Keynote Speaking"
   knocking a whole row out of register. */

.b-card {
  display: flex;
  flex-direction: column;
  transform-style: preserve-3d;
  will-change: transform;
}

.b-card > .b-card-key { min-height: 2.4em; margin-bottom: 2px; }
.b-card > h3 { min-height: 2.3em; display: flex; align-items: flex-end; }
.b-card > p:not(.b-card-key):not(.b-card-meta) { flex: 0 0 auto; }
.b-card > .b-points { flex: 1 1 auto; }
.b-card > .b-card-meta { margin-top: auto; }

/* ── The points list ──────────────────────────────────────────────
   The specifics that used to be a paragraph. Gold rule as the marker
   rather than a bullet character or a tick glyph: a rule is a squared
   mark, which the brand allows, and it does not import a shape from
   somebody else's icon set.

   Rules that are 8px wide and sit on the cap height read as a
   deliberate typographic mark. Centred vertically they read as an
   accident, so `top` is set against the line box rather than using
   flex centring. */

.b-points {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.b-points li {
  position: relative;
  padding-left: 18px;
  font-size: var(--b-step-micro);
  letter-spacing: 0.04em;
  line-height: 1.5;
  color: var(--b-temp-body);
}

.b-points li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 8px;
  height: 1px;
  background: var(--mv-gold);
  opacity: 0.75;
}

.b-card[data-lit] {
  transform: perspective(900px)
             rotateX(var(--px, 0deg))
             rotateY(var(--py, 0deg))
             translateY(-5px);
  transition: transform 0.12s linear, box-shadow 0.4s var(--lu-glide);
}

/* The highlight follows the cursor across the surface. This is what
   separates a tilt from a tilt that looks lit. */
.b-card[data-lit]::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(
    440px circle at var(--mx, 30%) var(--my, 0%),
    rgba(255,224,163,0.10),
    transparent 62%
  );
}

/* ── Applying it to what already exists ───────────────────────────
   No new markup required for these. The classes above are for opting
   in deliberately; the rules below upgrade the existing components. */

.b-card {
  background-image: linear-gradient(
    148deg,
    rgba(255,255,255,0.050) 0%,
    rgba(255,255,255,0.010) 44%,
    rgba(255,255,255,0.068) 50%,
    rgba(255,255,255,0.000) 58%,
    rgba(0,0,0,0.28) 100%
  );
  box-shadow: var(--lu-contact), var(--lu-lip), var(--lu-underlip);
  transition:
    border-color 0.3s,
    transform 0.4s var(--lu-glide),
    box-shadow 0.4s var(--lu-glide);
}

/* Lifting an object should deepen its ambient shadow and brighten its
   lip, because both are what happens when something rises toward a
   light. Changing only translateY is why the old hover felt flat.

   No transform here on purpose. The lift lives in the [data-lit] rule
   above, together with the rotation, so the two cannot overwrite each
   other. Declaring translateY here as well would mean whichever rule
   came last silently won and the tilt would vanish on hover. */
.b-card:hover,
.b-card:focus-within {
  box-shadow: var(--lu-ambient), var(--lu-contact), var(--lu-lip-warm), var(--lu-underlip);
}

/* Reduced-motion and coarse pointers never get [data-lit], because
   litSurfaces() returns early, so those visitors would get no lift at
   all. Give them the plain one. */
@media (prefers-reduced-motion: reduce) {
  .b-card:hover, .b-card:focus-within { transform: none; }
}
@media (hover: hover) and (pointer: fine) {
  .b-card:hover:not([data-lit]) { transform: translateY(-5px); }
}

/* The gold button becomes a struck object rather than a gold rectangle:
   four stops with the specular reversal, and a lip in gold rather than
   white so the highlight belongs to the metal. */
.b-btn--gold {
  background-image: linear-gradient(
    148deg,
    var(--lu-lit) 0%,
    var(--lu-mid) 42%,
    #ffd487 50%,
    var(--lu-mid) 58%,
    var(--lu-shade) 100%
  );
  box-shadow:
    0 14px 34px -14px rgba(250,189,89,0.50),
    0 3px 10px -4px rgba(0,0,0,0.70),
    inset 0 1px 0 rgba(255,255,255,0.45),
    inset 0 -1px 0 rgba(138,95,24,0.55);
}
.b-btn--gold:hover,
.b-btn--gold:focus-visible {
  box-shadow:
    0 20px 46px -14px rgba(250,189,89,0.72),
    0 4px 12px -4px rgba(0,0,0,0.75),
    inset 0 1px 0 rgba(255,255,255,0.60),
    inset 0 -1px 0 rgba(138,95,24,0.55);
}

/* Glass panels get the lip too. Without it a backdrop-filter panel
   reads as a blurred hole rather than a pane sitting above the page. */
.b-panel,
.b-quote,
.b-door {
  box-shadow: var(--lu-contact), var(--lu-lip), var(--lu-underlip);
}

/* ── Book titles in prose ─────────────────────────────────────────
   Anric: "don't you think you should hyperlink discreetly with maybe a
   faint yellow shimmer on the text and make the destination clickable?"

   Discreet is the hard part. His voice paragraph names five books, so five
   loud links in one paragraph would shred it. The resolution: the LINE
   carries the gold at rest, the TEXT carries the shimmer only on hover.
   That way a reader scanning the paragraph sees five quiet gold rules
   rather than five gold sentences, and gets the reward on approach.

   text-decoration rather than a border-bottom, so the rule breaks
   correctly across a line wrap and skips descenders instead of striking
   through the tail of a g or a y. */

.b-tlink {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(250, 189, 89, 0.42);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: text-decoration-color 0.25s, color 0.25s;
}

.b-tlink:hover,
.b-tlink:focus-visible {
  text-decoration-color: var(--lu-mid);
  /* The shimmer. A wide gold gradient clipped to the glyphs and slid
     across them once. background-size beyond 100% is what gives it
     somewhere to travel; without it the gradient has no slack and the
     animation does nothing. */
  background-image: linear-gradient(
    100deg,
    var(--lu-mid) 0%,
    #ffe9c2 42%,
    #fff6e2 50%,
    #ffe9c2 58%,
    var(--lu-mid) 100%
  );
  background-size: 260% 100%;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: luTitleShimmer 1.1s var(--lu-glide) 1 forwards;
}

@keyframes luTitleShimmer {
  from { background-position: 100% 0; }
  to   { background-position: 0 0; }
}

/* Reduced motion still gets the gold, just no travel. Removing the colour
   as well would leave the link indistinguishable on hover. */
@media (prefers-reduced-motion: reduce) {
  .b-tlink:hover,
  .b-tlink:focus-visible {
    animation: none;
    background-image: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    color: var(--lu-mid);
  }
}

/* Forced-colours mode strips background-image, which would leave
   `color: transparent` on visible text and make the title vanish. */
@media (forced-colors: active) {
  .b-tlink:hover,
  .b-tlink:focus-visible {
    background-image: none;
    color: LinkText;
    -webkit-background-clip: border-box;
    background-clip: border-box;
  }
}

/* ── Accessibility and restraint ──────────────────────────────────
   Every effect above is decorative. All of it goes away, and what is
   left still has full contrast and visible focus. */

@media (prefers-reduced-motion: reduce) {
  .lu-sheen:hover::after,
  .lu-sheen:focus-visible::after,
  .lu-sheen:focus-within::after { animation: none; opacity: 0; }

  .b-card[data-lit] { transform: none; transition: box-shadow 0.3s; }
  .b-card[data-lit]::before { display: none; }
}

/* Coarse pointers have no cursor to track. Serving the tilt to a phone
   costs a repaint per touch and buys nothing. litSurfaces() already
   bails, so this is belt and braces for the case where a device reports
   a fine pointer and then gets touched. */
@media (hover: none), (pointer: coarse) {
  .b-card[data-lit] { transform: none; }
  .b-card[data-lit]::before { display: none; }
}
