/* ARTIFACT — entrance
 *
 * The name at architectural scale with the archive dealing through a card in front
 * of it. Reference: the stacked-card hero on onmark.webflow.io.
 *
 * Two deliberate departures from that reference:
 *
 *   1. The card OCCLUDES the name rather than sitting beside it. While the archive
 *      runs, "JULIEN DIAZ" is never whole — the evidence covers the man. That is
 *      §59/§60 given an interface, and it is the reason this is not a wordmark hero.
 *
 *   2. No mark, no ©, no tagline, no CTA. The reference is a brand template and its
 *      furniture is exactly what PRINCIPLES prohibits.
 *
 * Luxury-cliché watch (§57): giant grotesk in bone-on-graphite goes fashion campaign
 * very easily. Countermeasures — the type is full-bleed and edge-anchored rather than
 * centred and precious, and every other element on the page is utilitarian mono.
 */

:root {
  --ground: #0b0b0c;
  --rule: #242428;
  --ink: #e8e6e2;
  --ink-dim: #86858a;
  --ink-faint: #4a4a4f;

  --pad: clamp(16px, 3vw, 34px);
  --sans: "Helvetica Neue", ui-sans-serif, -apple-system, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --deal: 900ms;
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  overflow-x: hidden;
}

/* The entrance is exactly one screen; the scope begins below the fold. */
.stage { min-height: 100vh; }

button { font: inherit; color: inherit; background: none; border: 0; padding: 0; }
:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

.stage {
  position: relative;
  height: 100vh;
  display: grid;
  place-items: center;
}

/* ------------------------------------------------------------------- name */

/* Full bleed, edge to edge, split across two lines so the card can sit in the
   channel between them and cover the inner letters of both. */
.name {
  position: absolute;
  inset: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  padding: 0 var(--pad);
  font-weight: 700;
  line-height: .82;
  color: var(--ink);
  user-select: none;
  z-index: 1;
}

/* Each line is distributed letter by letter to the full measure. Justify cannot do
   this — it breaks at spaces, and a single word has none — so the letters are laid
   out as flex children instead. */
.name .line {
  display: flex;
  justify-content: space-between;
}
.name .line > span { display: block; }

/* The two lines are set at different sizes so that six letters and four letters each
   fill the same measure at close to natural tracking. Setting them at one size and
   forcing the gap open turns DIAZ into spaced-out lettering, which stops reading as a
   name — and the occlusion only works if there is a legible name to occlude. Both are
   also height-bounded so the pair never clips the viewport. */
.name .line:nth-child(1) { font-size: min(20.5vw, 40vh); }
.name .line:nth-child(2) { font-size: min(29vw, 52vh); }

/* ---------------------------------------------------------------- rolodex */

/* A Rolodex drum, not a flip.
 *
 * Measured off onmark.webflow.io: eight cards on a shared spindle, each rotateX at
 * k × 45° about a pivot just below its own bottom edge, with the whole drum turned
 * rotateY(45°) and scaled 0.9 under perspective.
 *
 * The oblique spindle is the whole trick. A fan of rotateX cards alone projects to a
 * flat vertical fan — every card on one line. Turning the axle about Y maps the fan's
 * depth axis partly onto screen X, so card centres trace an ELLIPSE. That is what
 * reads as a pinwheel rather than a stack seen edge-on.
 */

.rolodex {
  position: relative;
  z-index: 2;
  /* Proportions taken from the reference, not estimated.
     onmark's card is 246 × 371 with transform-origin y = 384 — the spindle sits 13px
     past the bottom edge, giving a radius of ~198, which is 0.535 of the card's radial
     dimension. That is a TIGHT hub: the leaves overlap heavily near the centre, and
     that crowding is most of the look.
     The leaf is the frame at its native ratio — 3:2, per the EXIF (9520 × 6336). No
     crop anywhere in the entrance. That costs some separation: landscape leaves are
     stubby radially and overlap far more than the reference's portrait ones, so the
     wheel is denser than onmark's. Uncropped photographs are worth the density. */
  --cw: min(33vw, 50vh, 480px);        /* card width */
  --ch: calc(var(--cw) / 1.5);         /* 3:2 native — the radial dimension */

  /* Exactly half the radial dimension puts the spindle ON the card's inner edge, so
     all eight leaves touch at the centre of the spin. This is the reference's own
     tightness — onmark sits at 0.535, barely past its card edge. */
  --hub: calc(var(--ch) * .5);

  /* How far the axle is turned from face-on. The ellipse's horizontal semi-axis is
     hub · sin(tilt), so opening this past the reference's 45° buys lateral separation
     without either shrinking the frames or cropping them. */
  --tilt: 55deg;

  /* The box only has to contain the wheel; the lift below does the centring. */
  width: calc(var(--cw) * 1.5);
  height: calc(var(--hub) * 2 + var(--ch) * 1.2);
  perspective: 2400px;

  /* The eye has to look down the axle. Left at the box centre, the projection is
     asymmetric about a ring that is centred a hub lower — near leaves get pushed out
     and far ones pulled in, which drags the apparent centre of the wheel off the
     spindle no matter what the lift is set to. */
  perspective-origin: 50% calc(50% + var(--hub));

  cursor: pointer;
}

.deck {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;

  /* The deck must turn about the SAME axis the leaves are mounted on. Left at the
     default 50% 50% it rotates about its own box centre, which sits a hub above the
     spindle — so the whole ring orbited that higher point instead of spinning on its
     own hub. The axle is where the frames meet; put the origin there. */
  transform-origin: 50% calc(50% + var(--hub));

  /* Brings the axle itself to the middle of the screen. The wheel is centred on the
     spindle, which is laid out a hub below the box centre, so without this the lower
     half of the drum hangs off the bottom.
     Measure this against a marker placed ON the axle, never against the average of the
     leaves' bounding boxes — perspective magnifies near leaves and shrinks far ones, so
     that average sits a full hub away from the real centre and will send you chasing a
     lift that is already correct. */
  --lift: calc(var(--hub) * -1);

  transform: translateY(var(--lift)) rotateY(var(--tilt)) rotateX(var(--turn, 0deg)) scale(.9);
}

.card {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--cw);
  height: var(--ch);
  margin-left: calc(var(--cw) / -2);
  margin-top: calc(var(--ch) / -2);

  /* No mount, no border, no rounding — the photograph is the whole leaf. */
  background: #17171a;
  overflow: hidden;
  border-radius: 0;

  transform-origin: 50% calc(50% + var(--hub));
  transform: rotateX(var(--a, 0deg));

  /* Sprung open, then held. The overshoot is the spring; the long tail is the hold. */
  transition: transform 1150ms cubic-bezier(.16, 1.06, .3, 1) var(--delay, 0ms),
              opacity 500ms ease var(--delay, 0ms);

  box-shadow: 0 18px 50px -20px rgba(0,0,0,.9);
}

/* Before the spring: every card collapsed onto the same plane. */
.deck:not(.is-open) .card { --a: 0deg; }

.card.is-lifted { filter: brightness(1.15); }

.card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: grayscale(1) contrast(1.04);
}

/* With the mount gone the number sits on the photograph itself, as on the sheet. */
.card::after {
  content: attr(data-id);
  position: absolute;
  left: calc(var(--cw) * .025);
  bottom: calc(var(--cw) * .018);
  font-family: var(--mono);
  font-size: calc(var(--cw) * .028);
  letter-spacing: .06em;
  color: var(--ink);
  opacity: .55;
  text-shadow: 0 0 6px rgba(0,0,0,.9);
}

/* The drum turns by whole detents when asked. It is otherwise held. */
.deck.is-turning { transition: transform 900ms cubic-bezier(.22, .61, .36, 1); }

/* -------------------------------------------------------------- caption */

/* Fixed to the corner rather than tethered under the card. The name occupies the
   whole field, so anything positioned relative to the card lands on top of a letter. */
.caption {
  position: fixed;
  z-index: 6;
  top: 0;
  left: 0;
  margin: 0;
  padding: 13px var(--pad);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .06em;
  color: var(--ink-faint);
  text-shadow: 0 0 10px var(--ground), 0 0 4px var(--ground);
}
.caption .k { color: var(--ink); }

/* ------------------------------------------------------------------ foot */

.foot {
  position: fixed;
  z-index: 4;
  left: 0; right: 0; bottom: 0;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 20px;
  padding: 13px var(--pad);
  border-top: 1px solid var(--rule);
  background: color-mix(in srgb, var(--ground) 86%, transparent);
  backdrop-filter: blur(6px);
}

.statement {
  margin: 0;
  font-size: 12.5px;
  color: var(--ink-dim);
}

.lnk {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  cursor: pointer;
}

.tick {
  margin: 0;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .06em;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* --------------------------------------------------------------- scope */

/* A 2.35:1 letterbox below the black, acting as a divider.
 *
 * The entrance stops being a fixed viewport once this exists — the page scrolls, and
 * the wheel only captures scroll while the pointer is actually on the drum, so the two
 * gestures never fight for the same input.
 *
 * 2.35is a CROP, not the frames' native ratio (they are 3:2). Each frame therefore
 * carries its own object-position, because a centred crop cuts faces in half — the
 * portrait at 05 needs the top of the frame, the ground-level shots need the bottom.
 */

/* The section is tall — its height comes from the manifest, total clip seconds times a
   viewport-heights-per-second dial. The BAND inside is sticky, so it pins while all
   that height scrolls past underneath and the camera appears to move on its own. */
.scope {
  position: relative;
  z-index: 3;
  background: var(--ground);
}

.scope-pane {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  place-items: center;
}

.scope-band {
  position: relative;
  width: 100%;
  aspect-ratio: 2.35 / 1;   /* CinemaScope */
  overflow: hidden;
  background: #000;
}

.scope-band video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;        /* 16:9 source, 2.35 band — the crop is the letterbox */
  filter: grayscale(1) contrast(1.03);
  opacity: 0;
}
.scope-band video.is-active { opacity: 1; }

/* ONE element, never two meeting in the middle. Two gradients butted together show a
   seam no matter how carefully the stops are matched. */
.scope-fade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    var(--ground) 0%,
    rgba(0,0,0,0) 9%,
    rgba(0,0,0,0) 91%,
    var(--ground) 100%);
}

.scope-meta {
  position: absolute;
  left: var(--pad);
  bottom: 12px;
  margin: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  color: var(--ink);
  opacity: .5;
  text-shadow: 0 0 8px rgba(0,0,0,.9);
  pointer-events: none;
}

/* Without script, or with motion reduced, the section collapses to a single band
   height and the first frame stands as a still. Eight screens of dead scroll would
   otherwise be the failure mode. */
.scope.is-static { height: auto !important; }
.scope.is-static .scope-pane { position: relative; height: auto; padding: 8vh 0; }

/* ----------------------------------------------------------- shadow box */

.shadowbox { position: fixed; inset: 0; z-index: 50; }

.shadowbox-scrim {
  position: absolute;
  inset: 0;
  background: rgba(6, 6, 7, .88);
  opacity: 0;
  transition: opacity 380ms ease;
}
.shadowbox.is-open .shadowbox-scrim { opacity: 1; }

/* Positioned in px from script. Open and close are a FLIP: the box is always laid out
   at its final rect and transformed back onto the leaf, so the move runs on the
   compositor rather than animating width and height. */
.shadowbox-frame {
  position: fixed;
  transform-origin: top left;
  will-change: transform;
  background: #17171a;
}

.shadowbox-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: grayscale(1) contrast(1.03);
}

/* Just outside the photograph, clear of the image itself. */
.shadowbox-close {
  position: absolute;
  top: -34px;
  right: -2px;
  padding: 4px 6px;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  color: var(--ink-dim);
  cursor: pointer;
  opacity: 0;
  transition: opacity 260ms ease 220ms, color 160ms ease;
}
.shadowbox-close:hover { color: var(--ink); }
.shadowbox.is-open .shadowbox-close { opacity: 1; }

@media (max-width: 700px) {
  .shadowbox-close { top: -30px; font-size: 15px; }
}

@media (prefers-reduced-motion: reduce) {
  .shadowbox-scrim,
  .shadowbox-close { transition: none; }
}

/* ----------------------------------------------------------------- grain */

.grain {
  position: fixed;
  inset: -50%;
  z-index: 5;
  pointer-events: none;
  opacity: .03;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/></filter><rect width='140' height='140' filter='url(%23n)'/></svg>");
}

/* ---------------------------------------------------------------- mobile */

@media (max-width: 700px) {
  .rolodex { --cw: min(80vw, 40vh); }

  /* The name is the one thing that should still feel architectural at this size.
     Tightening the leading pulls the two lines into a single block. */
  .name { line-height: .86; }

  .caption {
    font-size: 9.5px;
    max-width: 70vw;
    line-height: 1.5;
  }

  .foot { flex-direction: column; align-items: flex-start; gap: 6px; }
  .statement { font-size: 12px; }
}

/* -------------------------------------------------------- reduced motion */

/* The archive stops running. The card still changes, but it cuts instead of
   hinging, and nothing advances unless the visitor asks it to. */
@media (prefers-reduced-motion: reduce) {
  .card.is-gone,
  .card.is-settling { animation: none; transition: none; }
  .grain { display: none; }
}

/* ------------------------------------------------------------------- panel */

/* The section after the photographs. Held empty on purpose: what goes here is not
 * decided, and dropping in placeholder furniture would decide it by accident. So it is
 * a full screen carrying two links and nothing else.
 *
 * WHITE, against a site that is otherwise entirely dark. This is the only inversion
 * anywhere in the piece, and it does more work than a black panel did: after the wheel
 * and a full sequence of photographs on near-black, the page opening onto white reads
 * as coming up for air — the sequence has ended, and the surface says so before a
 * single word does. A black panel had to be told apart from the ground by a hairline.
 *
 * The grain overlay sits above this at 3% and is tuned for dark ground; it is switched
 * off over the panel rather than left to dirty a white field.
 */
.panel {
  position: relative;
  z-index: 6;                 /* above .grain (5), so the noise does not sit on white */
  min-height: 100vh;
  background: #fff;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: var(--pad);
}

/* ------------------------------------------------------------- shoe stack */

/* One box, upper-left, holding all twenty-one shoes on top of one another. Only the
 * current one is visible — nothing slides, so the eye never tracks a moving object and
 * the shoe simply becomes the next shoe.
 */
.shoes {
  align-self: flex-start;
  margin-top: clamp(16px, 7vh, 78px);
  position: relative;
  width: min(52vw, 780px);
  aspect-ratio: 1600 / 1140;   /* the catalogue frame, so every shoe registers the same */
  max-height: 56vh;
  cursor: ns-resize;           /* the affordance is vertical, because the gesture is */
}

.shoes img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;

  /* The catalogue shots are on pure white. Multiply drops that white into the panel
     without any cutting — and it only works because this panel is white. On the dark
     study the same images needed a paper card behind them. */
  mix-blend-mode: multiply;
  visibility: hidden;
}

.shoes img.is-current { visibility: visible; }

.shoes:focus-visible { outline: 1px solid #0b0b0c; outline-offset: 12px; }

.shoes-meta {
  align-self: flex-start;
  margin: 18px 0 0;
  display: flex;
  gap: 18px;
  align-items: baseline;
  flex-wrap: wrap;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #7c7c82;
}

.shoes-meta .shoes-name { color: #0b0b0c; }
.shoes-meta .idx { font-variant-numeric: tabular-nums; }

/* Bottom row. margin-top:auto pins it regardless of how tall the stack above runs, so
   a short window cannot push it off. Baseline-aligned so the small marks sit on the
   same line as the base of the large words rather than floating against their centre. */
.panel-foot {
  margin-top: auto;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
}

/* ----------------------------------------------------------------- social */

/* Inline SVG rather than an icon font or a sprite: one fewer request, and it inherits
   `color`, which is the whole trick — the same markup is dark on this white panel and
   light on the still pages, with nothing swapped but a colour.
 *
 * Drawn as outlines with selected parts filled, so both marks read at 22px. currentColor
 * on both stroke and fill means a single declaration inverts the pair. */
.social {
  display: flex;
  gap: 16px;
  align-items: center;
}

.social a {
  display: block;
  line-height: 0;
  color: inherit;
}

.social svg {
  width: 22px;
  height: 22px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  opacity: .55;
  transition: opacity 300ms ease;
}

.social svg .fill { fill: currentColor; stroke: none; }

.social a:hover svg,
.social a:focus-visible svg { opacity: 1; }

/* On the white panel the marks are the ground's own black. */
.panel .social { color: #0b0b0c; }

/* Set like the name rather than like navigation: the same bold sans, uppercase, tight
   tracking and heavy weight as JULIEN DIAZ on the entrance. On an otherwise empty white
   field these two words are the only thing on the surface, so treating them as small
   mono furniture wasted the panel — at this scale they ARE the panel.

   Sized in vw so they scale with the name they echo. Halved on 2026-08-17 — 22px floor,
   52px ceiling, about 47px on a 1280 display. Still display type rather than furniture,
   but it no longer competes with the name it is echoing. */
.panel-links {
  display: flex;
  align-items: baseline;
  gap: clamp(16px, 2.4vw, 34px);
  font-family: var(--sans);
  font-weight: 700;
  font-size: clamp(22px, 3.7vw, 52px);
  line-height: .9;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

/* Black, and black at rest — not a grey that resolves on hover. At this weight and
   size, starting dim would read as a disabled control rather than as restraint. The
   hover moves the underline instead, so attention is still answered. */
.panel-links a {
  color: #0b0b0c;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  padding-bottom: 2px;
  transition: border-color 320ms ease;
}

.panel-links a:hover,
.panel-links a:focus-visible {
  border-bottom-color: #0b0b0c;
}

/* Kept from when these were twice this size, where the pair needed 377px against a
   343px phone panel and broke out of its corner. At the 22px floor they need roughly
   190px and fit side by side, so this now only guards the narrowest devices — the
   threshold is dropped to match, rather than stacking phones that have the room. */
@media (max-width: 380px) {
  .panel-links {
    flex-direction: column;
    align-items: flex-end;
    gap: clamp(8px, 1.6vh, 16px);
  }
}

/* Stood down over the panel. pointer-events must go too — a transparent fixed bar
   still swallows clicks, which is exactly how the panel's links got covered. */
.foot.is-stood-down {
  opacity: 0;
  pointer-events: none;
  transition: opacity 420ms ease;
}

.foot { transition: opacity 420ms ease; }
