/* ── stage ──────────────────────────────────────────────────────────── */
.stage {
  position: fixed; inset: 0;
  display: grid; place-items: center;
  perspective: 2400px;
  perspective-origin: 50% 46%;
  transition: perspective 620ms cubic-bezier(.28,.66,.24,1), opacity 420ms ease;
}
/* the closed magazine is the subject, so it gets a shorter lens and reads with
   real depth; the open spread wants the flatter one so pages stay square */
/* A long lens. At 1500px against a 595px-wide cover the object converged hard
   enough to read as a cheap 3D demo; product photography uses a long lens for
   exactly this reason. Nothing here is a wide-angle shot. */
body[data-state="landing"] .stage { perspective: 3600px; perspective-origin: 50% 42%; }

/* The landing is a page you scroll, so the magazine belongs to the first screen
   of it rather than being pinned over the whole thing. Pinned, it stayed put
   while the bio ran up over it and the section list underneath it collided with
   the text. In flow, the whole hero leaves together. Reading puts it back to
   fixed, which is the rule above. */
body[data-state="landing"] .stage {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: auto;
  height: 100dvh;
  /* the contact shadow is wider than the cover and used to be clipped by the
     viewport when this was fixed. `clip` on one axis only, so it neither
     becomes a scroll container nor touches the perspective it carries. */
  overflow-x: clip;
}

/* The shadow is a plain 2D shape sitting in the stage, not a plane inside the
   book's 3D context. A rotated plane there is either hidden behind the cover or
   grazed to nothing depending on the sign of the tilt, and it cannot be
   blurred — a filter would flatten it out of the 3D space. This is placed from
   the same measurements the contents list uses, so it tracks exactly. */
.stage__ground {
  position: absolute;
  left: 50%; top: 50%;
  width: calc(var(--book-w) * 1.02);
  height: calc(var(--book-w) / var(--spread-ratio) * .34);
  translate: -50% calc(var(--book-w) / var(--spread-ratio) * .43);
  background:
    radial-gradient(38% 42% at 46% 34%, rgba(6,3,4,.46), rgba(6,3,4,0) 70%),
    radial-gradient(62% 60% at 52% 46%, rgba(10,6,8,.24), rgba(10,6,8,0) 74%);
  filter: blur(18px);
  opacity: 0;
  transition: opacity 700ms ease;
  pointer-events: none;
}

/* The shadow an object standing on a surface throws: a tight dark contact line
   right under the trim, widening and softening as it runs away from the base.
   The old one was offset a third of its own width and rolled to match a Z
   rotation that no longer exists, so it read as a smudge lying near the
   magazine rather than the magazine sitting on anything. */
body[data-state="landing"] .stage__ground {
  opacity: 1;
  left: var(--cover-cx, 28vw);
  top: var(--cover-bottom, 70vh);
  width: calc(var(--cover-w, 20vw) * 1.34);
  height: calc(var(--cover-w, 20vw) * 0.30);
  translate: -50% -22%;
  rotate: none;
  background:
    /* the contact: narrow, dark, hugging the foot of the cover. On a light
       ground a shadow has to carry real density or the object floats — the
       first version peaked at 29 levels of darkening and read as nothing. */
    radial-gradient(33% 10% at 50% 13%, rgba(4,2,3,.78), rgba(4,2,3,0) 70%),
    /* the near falloff */
    radial-gradient(50% 30% at 50% 21%, rgba(8,4,6,.52), rgba(8,4,6,0) 76%),
    /* the ambient pool it sits in */
    radial-gradient(82% 68% at 50% 32%, rgba(14,8,11,.30), rgba(14,8,11,0) 80%);
  filter: blur(13px);
}
body[data-state="landing"] .stage__ground::after {
  content: "";
  position: absolute; left: 50%; top: 6%;
  width: 62%; height: 26%;
  translate: -50% 0;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(214,176,178,.16), rgba(214,176,178,0) 72%);
}
/* the landing has its own contact shadow, inside the object, so it can lean */
body[data-state="opening"] .stage__ground { opacity: 0; }

/* ── frame ──────────────────────────────────────────────────────────── */
:root {
  --gap-v: calc(var(--chrome-h) * 2 + 1.6rem);
  --book-w: min(94vw, calc((100dvh - var(--gap-v)) * var(--spread-ratio)));
  --land-scale: .60;
  --read: 0;                                  /* 0 unopened, 1 finished */
  --depth: clamp(6px, calc(var(--book-w) * .022), 30px);
  /* photographed stock rather than fractal noise: the old turbulence read as
     digital speckle up close, this has the fibre and the faint mottle paper
     actually has. Centred on white, so multiplying it never dims the artwork. */
  --paper-noise: url("../assets/tex/paper-grain.webp");
  --paper-noise-size: 300px;
}

.bookframe {
  position: relative;
  width: var(--book-w);
  aspect-ratio: var(--spread-ratio);
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
  will-change: transform;
  transition: transform 620ms cubic-bezier(.28,.66,.24,1);
}

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

/* ── pages ──────────────────────────────────────────────────────────── */
/* page-flip owns the geometry of the turn. Everything here is about making
   its pages look like paper and sit correctly inside the book furniture. */

.sheets {
  position: absolute; inset: 0;
  display: grid; place-items: center;
}
.flipmount { line-height: 0; }

.page {
  background: #f4efe9;
  overflow: hidden;
}
.page > .pg { position: absolute; inset: 0; }

/* paper grain over every page */
.page::before {
  content: "";
  position: absolute; inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: .62;
  mix-blend-mode: multiply;
  background-image: var(--paper-noise);
  background-size: var(--paper-noise-size) var(--paper-noise-size);
}

/* the gutter each facing page carries, independent of the turn */
.page::after {
  content: "";
  position: absolute; top: 0; bottom: 0;
  width: 15%;
  z-index: 4;
  pointer-events: none;
  opacity: .9;
}
.page--left::after {
  right: 0;
  background: linear-gradient(to left,
    rgba(40,28,25,.50) 0%, rgba(40,28,25,.27) 15%, rgba(40,28,25,.11) 40%,
    rgba(40,28,25,.03) 70%, rgba(40,28,25,0) 100%);
}
.page--right::after {
  left: 0;
  background: linear-gradient(to right,
    rgba(40,28,25,.56) 0%, rgba(40,28,25,.32) 13%, rgba(40,28,25,.14) 36%,
    rgba(40,28,25,.04) 66%, rgba(40,28,25,0) 100%);
}
/* the closed magazine already has its fold; a second gutter on the cover
   would double it, and the back board has nothing to be bound to */
body:not([data-state="reading"]) .page::after,
body[data-mode="single"] .page::after { opacity: 0; }
.page--back::after { opacity: 0; }
/* page-flip decides whether a click is a page turn by reading tagName on the
   exact element clicked, so a click on the label inside a button reads as a
   SPAN and turns the page. Making the contents of interactive elements
   click-transparent keeps the button itself as the target. */
.page button > *, .page a > * { pointer-events: none; }

/* page-flip's shadows, warmed to the paper rather than neutral black */
.stf__outerShadow, .stf__innerShadow { mix-blend-mode: multiply; }

/* ── page-edge stack ────────────────────────────────────────────────── */
/* thickness tracks progress: the unread block visibly thins */
.edges {
  position: absolute; top: 1.2%; bottom: 1.2%;
  width: var(--edge-w, 0px);
  pointer-events: none;
  z-index: 400;
  opacity: 0;
  transition: opacity 420ms ease, width 700ms var(--turn-ease);
  border-radius: 2px;
  background:
    repeating-linear-gradient(to right,
      rgba(255,252,246,.95) 0 1px,
      rgba(203,190,175,.85) 1px 2px,
      rgba(240,232,220,.92) 2px 3px);
}
.edges::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
}
body[data-state="reading"] .edges { opacity: 1; }

body[data-mode="single"] .edges { top: .9%; bottom: .9%; }

.edges--l {
  right: 100%;
  --edge-w: calc(var(--read) * 1.15rem + .06rem);
  box-shadow: -1px 0 2px rgba(40,28,24,.22), inset -2px 0 4px rgba(40,28,24,.18);
}
.edges--l::after { background: linear-gradient(to left, rgba(40,28,24,.22), rgba(40,28,24,0) 60%); }

.edges--r {
  left: 100%;
  --edge-w: calc((1 - var(--read)) * 1.15rem + .06rem);
  box-shadow: 1px 0 2px rgba(40,28,24,.22), inset 2px 0 4px rgba(40,28,24,.18);
}
.edges--r::after { background: linear-gradient(to right, rgba(40,28,24,.22), rgba(40,28,24,0) 60%); }

.edges--l, .edges--r { top: 1.2%; bottom: 1.2%; }
/* nothing is stacked on the outside of either cover */
body[data-edge="start"] .edges--l,
body[data-edge="end"]   .edges--r { opacity: 0; }
.edges { width: var(--edge-w); }

/* ── the magazine on the landing screen ─────────────────────────────── */
/* A saddle-stitched magazine: a few sheets thick, folded at the spine, trimmed
   flush on the other three sides, printed on stock with a sheen. The cover
   artwork itself is page 1 — this is only the object around it.

   Nothing here uses `filter`. Inside a preserve-3d context a filter forces the
   element onto a flattened plane, which is what turned the contact shadow into
   a smudge lying across the cover. Every soft edge below is a gradient. */

:root {
  --depth: clamp(14px, calc(var(--book-w) * .042), 54px);
}

.closed {
  position: absolute;
  left: 50%; right: 0; top: 0; bottom: 0;
  pointer-events: none;
  transform-style: preserve-3d;
  z-index: 300;
  transition: opacity 380ms ease;
}
body[data-mode="single"] .closed { left: 0; }
body[data-state="reading"] .closed,
body[data-state="opening"] .closed { opacity: 0; }

/* the shadow it throws, thrown down and away from a light at upper left */
/* The block of pages standing off the fore edge. Thicker than a strict scale
   would give, because at this angle a true few-millimetre edge collapses to a
   couple of pixels and the object stops reading as something with pages in it. */
.mag__block {
  position: absolute;
  left: 100%; top: -.15%; bottom: -.15%;
  width: var(--depth);
  transform-origin: left center;
  /* Not 90°. A plane at exactly 90° lands edge-on in this camera and projects
     to nothing at all — the old block was invisible, and what read as a grey
     tile beside the cover was the contact shadow showing through. Turned a few
     degrees back it catches the light as a trimmed block of paper, which is
     also what a saddle-stitched magazine really does: the sheets splay. */
  transform: rotateY(-76deg);
  border-radius: 0 2px 2px 0;
  /* The striations are a photographed block of paper edges, not a gradient
     pretending to be one — a repeating-linear-gradient at the width this
     actually projects to came out as three or four hard bands. The image
     supplies the sheets; the gradient over it supplies the modelling, dark in
     the cover's shadow at the seam and catching light out at the trim. It
     tiles vertically, so the block is as tall as the magazine needs. */
  background:
    linear-gradient(to right,
      rgba(64,44,38,.40) 0%, rgba(64,44,38,.13) 10%,
      rgba(255,255,252,0) 34%, rgba(255,255,252,.24) 72%, rgba(255,255,252,.40) 100%),
    url("../assets/tex/fore-edge.webp") left top / 100% 210px repeat-y,
    linear-gradient(to right, #efe7d8 0%, #f7f1e5 46%, #fdf9f0 100%);
}
/* the seam where the cover folds over the block */
.mag__block::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: rgba(48,32,26,.55);
}

/* the head trim, which the downward tilt catches */
.mag__head {
  position: absolute;
  left: 0; right: -.05%; top: 0;
  height: var(--depth);
  transform-origin: center top;
  transform: rotateX(-78deg);
  /* Near-upright, this projects to about twelve pixels. The head-edge photograph
     was being squeezed from 688px of striation into those twelve, which aliased
     into one flat pale band — the thing that made the top of the magazine look
     painted on rather than cut. At this size the sheets are genuinely
     sub-perceptual, so what sells it is tone, not texture: dark in the cover's
     shadow at the spine, warming toward the fore edge, and a real seam where
     the cover sits down onto the block. */
  background:
    /* the gap between the cover and the first page */
    linear-gradient(to bottom, rgba(26,15,14,.62) 0 1px, rgba(26,15,14,0) 1px),
    /* along the length: the spine end is in shadow, the fore edge catches light */
    linear-gradient(to right,
      rgba(38,22,20,.52) 0%, rgba(38,22,20,.22) 22%,
      rgba(38,22,20,.04) 54%, rgba(255,252,246,.10) 84%, rgba(255,252,246,.20) 100%),
    /* through the depth: the back of the block is darker than its front */
    linear-gradient(to bottom, #9d8f7c 0%, #c3b7a1 38%, #ded3bd 74%, #ece2cf 100%);
}

/* the spine: a fold, not a slab — the stock wraps round and creases */
.mag__fold {
  position: absolute;
  left: 0; top: 0; bottom: 0; width: 4.2%;
  background: linear-gradient(to right,
    rgba(52,34,32,.42) 0%,
    rgba(52,34,32,.22) 22%,
    rgba(255,250,246,.16) 56%,
    rgba(52,34,32,.05) 80%,
    rgba(52,34,32,0) 100%);
}
.mag__fold::after {
  content: "";
  position: absolute; left: 8%; top: 0; bottom: 0; width: 1px;
  background: linear-gradient(to bottom,
    rgba(46,28,26,0), rgba(46,28,26,.30) 5%, rgba(46,28,26,.30) 95%, rgba(46,28,26,0));
}
/* the rim the light from the upper left catches along the spine fold */
.mag__fold::before {
  content: "";
  position: absolute; left: 0; top: 2%; bottom: 2%; width: 2px;
  background: linear-gradient(to bottom,
    rgba(255,246,242,0), rgba(255,246,242,.42) 12%,
    rgba(255,246,242,.30) 72%, rgba(255,246,242,0));
}

/* the bow a soft cover takes, and a crisp trim on the three cut edges */
.mag__bow {
  position: absolute; inset: 0;
  border-radius: 0 3px 3px 0;
  background:
    linear-gradient(to right,
      rgba(38,24,22,.15) 0%,
      rgba(38,24,22,.02) 14%,
      rgba(255,252,247,.18) 40%,
      rgba(255,252,247,.08) 62%,
      rgba(38,24,22,.04) 84%,
      rgba(38,24,22,.11) 96%,
      rgba(38,24,22,.20) 100%),
    /* the cover lifts a little off the block at the outer corners */
    radial-gradient(52% 26% at 100% 0%, rgba(255,252,247,.24), rgba(255,252,247,0) 70%),
    radial-gradient(52% 26% at 100% 100%, rgba(38,24,22,.14), rgba(38,24,22,0) 70%);
  box-shadow:
    inset -1px 0 0 rgba(64,44,38,.42),
    inset 0 1px 0 rgba(255,253,248,.34),
    inset 0 -1px 0 rgba(64,44,38,.34),
    /* the cover's edge against a pale room, and the light it catches on top */
    0 1px 1px rgba(0,0,0,.4),
    0 12px 26px -12px rgba(0,0,0,.6);
}

/* coated stock: a broad specular sweep that travels as the cover turns */
/* Coated stock under a soft box. The old version was two hard diagonal bands
   swept across the cover, which at this angle read as a stripe rather than a
   reflection; a real soft box is a broad ellipse with a bright core and a long
   irregular falloff, brightest where the sheet faces the light and dying off
   toward the fore edge as the cover curves away. */
.mag__gloss {
  position: absolute; inset: 0;
  background-image:
    radial-gradient(70% 52% at 18% 6%, rgba(255,252,250,.30), rgba(255,252,250,0) 72%),
    radial-gradient(38% 30% at 34% 20%, rgba(255,255,255,.16), rgba(255,255,255,0) 76%),
    radial-gradient(90% 70% at 96% 92%, rgba(28,14,20,.20), rgba(28,14,20,0) 68%),
    linear-gradient(104deg, rgba(255,255,255,.07) 0%, rgba(255,255,255,0) 38%);
  mix-blend-mode: soft-light;
  transition: opacity 700ms ease, transform 900ms cubic-bezier(.3,.7,.25,1);
}
/* the reflection travels a little as the object turns under the pointer */
body[data-state="landing"] .bookframe:hover .mag__gloss,
body[data-state="landing"] .bookframe:focus-within .mag__gloss {
  transform: translateX(4%);
}


/* ── reading vs landing placement ───────────────────────────────────── */
/* At rest the magazine sits back and to one side; opening walks it to the
   centre, which is simply its untransformed place. Listing "opening" in the
   rule below as well would be pointless — the next rule overrides it. */
/* The old pose was rotateX(-28°) rotateY(-22°) rotateZ(-10°). Negative rotateX
   tips the top of the object toward the camera, and at 28° the cover's top edge
   projected wider than its bottom — the magazine was measurably falling
   forward. The Z roll added a lean on top of that, so three simultaneous
   rotations read as something tumbling rather than something presented.

   This is a magazine standing up, seen a little from above and turned just far
   enough to show the spine and the block of pages. One small X, one modest Y,
   no roll at all. */
body[data-state="landing"] .bookframe {
  transform:
    translateX(calc(var(--land-x) - var(--book-w) * var(--land-scale) / 4))
    translateY(-14vh)
    rotateX(-7deg)
    rotateY(-17deg)
    scale(var(--land-scale));
}

body[data-state="landing"] {
  --land-x: 0vw;
  /* Where the cover lands, used only if a measurement has not arrived. The
     shift and the cover's own half-width cancel, so it is centred on
     50vw + --land-x; perspective then pushes it a little further out and makes
     it taller than the flat arithmetic says, so both terms carry a factor
     fitted against the rendered position at several viewport sizes. */
  --cover-cx-calc: calc(50vw + var(--land-x) * 1.115);
  --cover-bottom-calc: calc(44vh + var(--book-w) / var(--spread-ratio) * var(--land-scale) * .49);
}
body[data-state="opening"] .bookframe { transform: none; }

body[data-state="reading"] .stage__ground { opacity: .38; }

body[data-state="landing"] .bookframe { cursor: pointer; }
/* Hover lifts it and opens the turn a few degrees. The old hover exaggerated
   every angle, which made the worst part of the pose worse the moment you
   touched it. */
body[data-state="landing"] .bookframe:hover,
body[data-state="landing"] .bookframe:focus-within {
  transform:
    translateX(calc(var(--land-x) - var(--book-w) * var(--land-scale) / 4))
    translateY(-15.1vh)
    rotateX(-5.5deg)
    rotateY(-13deg)
    scale(calc(var(--land-scale) + .018));
}

/* single-page mode */
body[data-mode="single"] {
  --book-w: min(94vw, calc((100dvh - var(--gap-v)) * var(--page-ratio)));
}
body[data-mode="single"] .bookframe { aspect-ratio: var(--page-ratio); }

/* ── stacked landing: the magazine sits in a band at the top ────────── */
/* Sized so the cover is about a third of the viewport height whichever mode
   the book is in — in a spread the cover is half the box, in single it is the
   whole of it, so the two are scaled from the same target. */
@media not all and (min-width: 1024px) and (min-aspect-ratio: 5 / 4) {
  body[data-state="landing"] .stage { height: 48dvh; }
  body[data-state="landing"] { --land-scale: 1; }

  /* 33dvh left the cover at about 164px on a 390px phone — small enough to read
     as a thumbnail rather than the thing the page is about. The list and the
     button beneath it still clear the fold with room to spare at 38. */
  body[data-mode="single"][data-state="landing"] {
    --book-w: min(70vw, calc(38dvh * var(--page-ratio)));
  }
  body[data-mode="spread"][data-state="landing"] {
    --book-w: min(94vw, calc(38dvh * var(--spread-ratio)));
  }
  body[data-state="landing"] .bookframe {
    transform: rotateX(-6deg) rotateY(-15deg);
  }
  body[data-state="landing"] .bookframe:hover,
  body[data-state="landing"] .bookframe:focus-within {
    transform: rotateX(-5deg) rotateY(-11deg) scale(1.015);
  }
}

/* ── two columns: the magazine moves off to the left ────────────────── */
/* The bio reads left, the magazine stands right. Moving the text off the right
   edge and into the wider half is what lets the whole section sit in one
   screen instead of running past the fold. */
@media (min-width: 1024px) and (min-aspect-ratio: 5 / 4) {
  /* the centrepiece earns its scale: it is the thing the page is about */
  body[data-state="landing"] { --land-x: 30.2vw; --land-scale: .74; }
}
@media (min-width: 1024px) and (min-aspect-ratio: 5 / 4) and (max-height: 830px) {
  body[data-state="landing"] { --land-scale: .60; }
}

/* ── short windows give the chrome less of the height ───────────────── */
@media (max-height: 640px) { :root { --chrome-h: 2.6rem; } }
@media (max-height: 480px) { :root { --chrome-h: 2.2rem; } }

/* ── reduced motion: the turn becomes a crossfade ───────────────────── */
@media (prefers-reduced-motion: reduce) {
  /* A static pose is not motion. Flattening the landing transform here left the
     magazine at full size and pushed the contents list off the bottom of the
     screen, so the placement stays and only the movement goes. */
  .bookframe { transition: none; }
  .stage__ground { transition: opacity 260ms ease; }
  .edges { transition: opacity 260ms ease; }
  .mag__gloss, .mag__shadow { transition: none; }

  body[data-state="landing"] .bookframe:hover,
  body[data-state="landing"] .bookframe:focus-within {
    transform:
      translateX(calc(var(--land-x) - var(--book-w) * var(--land-scale) / 4))
      translateY(-14vh)
      rotateX(-7deg)
      rotateY(-17deg)
      scale(var(--land-scale));
  }
  body[data-state="opening"] .bookframe { transform: none; }

  .stf__outerShadow, .stf__innerShadow { display: none; }
}



/* ── the binding ────────────────────────────────────────────────────── */
/* The gutter shading lives on the pages, so it folds away with them. What the
   pages cannot carry is the seam itself — the place the two halves meet — so
   that sits in the book, over the settled spread, and stands down while a page
   is in the air rather than drawing a line across it. */
.binding {
  position: absolute;
  left: 50%; top: 1.5%; bottom: 1.5%;
  width: 10px;
  translate: -50% 0;
  z-index: 5;
  pointer-events: none;
  opacity: 0;
  transition: opacity 260ms ease;
  background: linear-gradient(to right,
    rgba(26,17,15,0) 0%, rgba(26,17,15,.30) 34%,
    rgba(26,17,15,.52) 50%,
    rgba(26,17,15,.30) 66%, rgba(26,17,15,0) 100%);
}
/* the crease itself, and the sliver of light where the paper climbs back out */
.binding__seam {
  position: absolute; left: 50%; top: 0; bottom: 0;
  width: 2px; translate: -50% 0;
  background: linear-gradient(to bottom,
    rgba(20,12,11,.42), rgba(20,12,11,.86) 8%, rgba(20,12,11,.86) 92%, rgba(20,12,11,.42));
  box-shadow:
    -3px 0 5px -1px rgba(20,12,11,.5),
     3px 0 5px -1px rgba(20,12,11,.5);
}

body[data-state="reading"][data-mode="spread"] .book[data-spread="open"] .binding { opacity: 1; }
body[data-flipping="1"] .binding { opacity: 0; transition-duration: 90ms; }

/* ── full-screen reading ────────────────────────────────────────────── */
/* Open, the magazine is the whole window. The chrome stops reserving height
   and floats over the page on its own scrim instead. */
body[data-state="reading"],
body[data-state="opening"] {
  --book-w: min(99vw, calc((100dvh - .6rem) * var(--spread-ratio)));
}
body[data-state="reading"][data-mode="single"],
body[data-state="opening"][data-mode="single"] {
  --book-w: min(96vw, calc((100dvh - .6rem) * var(--page-ratio)));
}

/* ── hover the edge, the page goes over ─────────────────────────────── */
/* Purely a light. The pointer arithmetic is in main.js and no element here
   ever takes a pointer event, because anything that did would be competing
   with page-flip for the same clicks and drags. */
.turnzone {
  position: fixed; top: 0; bottom: 0;
  width: clamp(52px, 6.5vw, 116px);
  z-index: 55;
  pointer-events: none;
  opacity: 0;
  transition: opacity 240ms ease;
  display: grid; place-items: center;
}
.turnzone--l { left: 0;  background: linear-gradient(to right, rgba(201,166,167,.22), rgba(201,166,167,0) 88%); }
.turnzone--r { right: 0; background: linear-gradient(to left,  rgba(201,166,167,.22), rgba(201,166,167,0) 88%); }
.turnzone.is-armed { opacity: 1; }

/* a page can be any colour at all, so the chevron carries its own ground */
.turnzone::before {
  content: "";
  grid-area: 1 / 1;
  width: 2.5rem; height: 2.5rem;
  border-radius: 50%;
  background: rgba(16,10,14,.62);
  border: 1px solid rgba(236,226,225,.20);
  backdrop-filter: blur(8px) saturate(1.1);
  -webkit-backdrop-filter: blur(8px) saturate(1.1);
  box-shadow: 0 6px 18px -8px rgba(0,0,0,.8);
}

.turnzone::after {
  content: "";
  grid-area: 1 / 1;
  place-self: center;
  width: 1.35rem; height: 1.35rem;
  background: var(--ink);
  opacity: .9;
  -webkit-mask: var(--chev) center / contain no-repeat;
          mask: var(--chev) center / contain no-repeat;
  animation: nudge 1.5s ease-in-out infinite;
}
.turnzone--l { --chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 5l-7 7 7 7'/%3E%3C/svg%3E"); }
.turnzone--r { --chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 5l7 7-7 7'/%3E%3C/svg%3E"); }
@keyframes nudge {
  0%, 100% { translate: 0 0 }
  50% { translate: var(--nudge, 3px) 0 }
}
.turnzone--l::after { --nudge: -3px; }

@media (prefers-reduced-motion: reduce) {
  .turnzone, .binding { transition: none; }
  .turnzone::after { animation: none; }
}
