/* =============================================================================
   Turn card — one object, seen from both sides.

   The storefronts page opens with the sentence "the front of the business,
   built like the back of it". This is that sentence as an object: a card whose
   front is a shop and whose back is the order the shop produced. It rotates on
   its own Y axis as the page is scrolled, so the reader turns it over rather
   than watching two pictures cross-fade.

   Real 3D, not a fake: a perspective on the stage, preserve-3d on the pivot,
   and two faces with backface-visibility so the browser hides whichever one is
   pointing away. Which means the swap happens exactly at the edge, for free,
   at whatever frame rate the compositor is running — there is no moment to get
   wrong.

   Four numbers come from storefront-page.js, and only the first is the scroll:

     --turn    0 → 1, eased and damped. 1 is a half turn.
     --edge    0 → 1 → 0, peaking as the card passes edge-on. Everything that
               should react to "being side-on" reads this rather than
               recomputing the same curve.
     --tilt    a few degrees of rotateX, settling to level as the turn finishes.
     --lift    0 → 1, how far the card has risen into place on arrival.
   ========================================================================== */

.turncard {
  /* Deep enough to be dimensional, shallow enough that the far edge does not
     shear. Origin slightly above centre so the card is read from just above,
     the way something on a desk is. */
  perspective: 1600px;
  perspective-origin: 50% 42%;
  inline-size: 100%;
}

.turncard__inner {
  position: relative;
  /*
   * A grid with both faces in the one cell, rather than a flow front and an
   * absolute back.
   *
   * The card has to be as tall as its TALLER face. With the back positioned
   * absolutely the card took the front's height, and on a phone — where the
   * shop is three small tiles and the record is a header, three rows and a
   * footnote — the record's last line was cut off by the face's own rounding.
   * One cell sizes to the taller and stretches the shorter, at every width,
   * with nothing measured.
   */
  display: grid;
  transform-style: preserve-3d;
  transform:
    translateY(calc((1 - var(--lift, 1)) * 2.5rem))
    rotateX(var(--tilt, 0deg))
    rotateY(calc(var(--turn, 0) * 180deg))
    /* Recedes very slightly as it turns through the edge, which is what sells
       the rotation as an object moving in space rather than a texture being
       squashed horizontally. */
    scale(calc(1 - var(--edge, 0) * 0.055));
  opacity: var(--lift, 1);
}

/*
 * The faces. Both hide their reverse, so exactly one is ever painted — which
 * means the swap happens precisely at the edge, for free, with no moment for
 * the code to get wrong.
 */
.turncard__face {
  grid-area: 1 / 1;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border-radius: var(--r-md);
  /* Promotes each face to its own layer, which keeps the text on them from
     being re-rasterised every frame of the turn. */
  will-change: transform;
}

.turncard__face--back {
  transform: rotateY(180deg);
  display: flex;
}

/* The shorter face fills the cell rather than sitting short inside it. */
.turncard__face--front > .shopframe {
  block-size: 100%;
}

/*
 * The shadow the card casts, on its own plane behind it.
 *
 * Drawn rather than applied as a box-shadow, because a box-shadow rotates with
 * the element and a card turning edge-on would carry its shadow round with it.
 * This one stays flat on the ground and narrows as the card presents less of
 * itself, which is what a real one does.
 */
.turncard__shadow {
  position: absolute;
  inset-inline: 8%;
  inset-block-end: -6%;
  block-size: 14%;
  transform: translateZ(-60px) scaleX(calc(1 - var(--edge, 0) * 0.55));
  background: radial-gradient(
    50% 50% at 50% 50%,
    rgb(0 0 0 / 55%),
    transparent 70%
  );
  filter: blur(18px);
  opacity: calc((1 - var(--edge, 0) * 0.6) * var(--lift, 1));
  pointer-events: none;
}

/*
 * The sheen.
 *
 * A single band of light that sweeps across whichever face is showing, timed to
 * the turn. It is the detail that makes the card read as a surface with a sheen
 * rather than a flat rectangle being transformed — and it is strongest exactly
 * where the rotation is fastest, which is where the eye is looking.
 */
.turncard__face::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(
    105deg,
    transparent 35%,
    rgb(255 255 255 / 12%) 50%,
    transparent 65%
  );
  background-size: 300% 100%;
  background-position: calc((1 - var(--turn, 0)) * 100%) 0;
  opacity: var(--edge, 0);
}

/* The lit edge. At rest it is invisible; as the card turns it catches the light
   the way a real card's cut edge does. */
.turncard__face--front::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: 1px;
  border-radius: inherit;
  background: var(--c-accent);
  opacity: calc(var(--edge, 0) * 0.7);
  pointer-events: none;
}

/* ------------------------------------------------------------ the reverse */

/*
 * The back is the record the shop produced. It is deliberately the same object
 * language as the rail on the home page — a code, a state, rows of fact — so a
 * reader who has come from there recognises it as the same system rather than
 * as a second illustration.
 */
.turnrecord {
  inline-size: 100%;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--c-accent);
  border-radius: var(--r-md);
  background: var(--c-canvas-sunk);
  overflow: hidden;
}

.turnrecord__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border-block-end: 1px solid var(--c-accent);
  background: var(--c-accent-soft);
}

.turnrecord__code {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-micro);
  color: var(--c-accent);
}

.turnrecord__body {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-5);
  flex: 1;
  justify-content: center;
}

.turnrecord__title {
  font-size: var(--t-lead);
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: 0;
}

.turnrecord__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  margin: 0;
  padding-block-end: var(--s-3);
  border-block-end: var(--border);
}

.turnrecord__row:last-of-type {
  border-block-end: 0;
  padding-block-end: 0;
}

.turnrecord__row dt {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
}

.turnrecord__row dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  text-align: end;
}

.turnrecord__row--key dd {
  color: var(--c-accent);
}

.turnrecord__foot {
  padding: var(--s-4) var(--s-5);
  border-block-start: var(--border);
  color: var(--c-muted);
  font-size: var(--t-small);
}

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

/*
 * No turn, and no half-turned card frozen at an angle either — that would be a
 * still of an animation rather than a composition. Both faces simply stack, so
 * the reader gets the whole argument in one look, which is what the rotation
 * was there to deliver in the first place.
 */
@media (prefers-reduced-motion: reduce) {
  .turncard {
    perspective: none;
  }

  .turncard__inner {
    transform: none;
    opacity: 1;
    display: flex;
    flex-direction: column;
    gap: var(--s-5);
  }

  .turncard__face,
  .turncard__face--back {
    grid-area: auto;
    transform: none;
    backface-visibility: visible;
    will-change: auto;
  }

  .turncard__face::after,
  .turncard__face--front::before,
  .turncard__shadow {
    display: none;
  }
}
