/* The newsletter shelf — partials/newsletter-shelf.njk.
   Loaded with publications.css, on /school-publications/ only.

   Eighteen covers standing in a line running away from the reader, the nearest
   one square on and openable. Scrolling walks along them.

   Written as two states. The first is what is here without JavaScript: a plain
   horizontal strip of covers that scrolls, snaps and takes the keyboard on its
   own. The second, .is-live, is added by the script once it has the planes
   positioned, and is the stack, walked along by dragging it sideways. Nothing
   is hidden in either — the fallback is the same eighteen links, side by side
   instead of stacked. */

.newsletter-menu {
  /* Set in px by the script from the shelf's real height, because the band
     around it decides that height and no percentage inside a scroll container
     resolves against the part of it you can see. 100svh until then, and in the
     fallback state, where nothing reads it. */
  --menu-height: 100svh;
  /* A cover is 849x1200, so 1.41 times as tall as it is wide. Two sizes: how
     wide it would like to be, and how wide the shelf's own height leaves room
     for. A window 730 tall gives the shelf about 430 of that, where a 312 wide
     cover stands 441 and loses its head and its foot — which is what the old
     arrangement did, silently, because it sized itself off the window instead
     of off the shelf. */
  --cover-wide: clamp(224px, 26vw, 312px);
  --cover-w: min(var(--cover-wide), calc(var(--menu-height) * 0.62));
  position: relative;
  /* Fills its row of the band. Every strip of ground beside it would otherwise
     be a strip where scrolling did nothing but move on down the page. */
  height: 100%;
  min-height: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  touch-action: pan-y;
  isolation: isolate;
}
.newsletter-menu::-webkit-scrollbar { display: none; }

.menu-stage {
  width: max-content;
  height: 100%;
}
/* Set from .newsletter-menu, not on .cover-stream alone: base.css zeroes the
   padding of every ol[role="list"], and an attribute selector outranks a bare
   class, so the covers stood flush against the edges of the shelf. */
.newsletter-menu .cover-stream {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  width: max-content;
  height: 100%;
  padding: var(--sp-8);
  margin: 0;
  list-style: none;
}
/* Sized from the strip's own height rather than from --cover-w, which is the
   stack's business: here there is a definite height to fill and the width
   follows the ratio. */
.cover-plane {
  position: relative;
  flex: 0 0 auto;
  height: min(100%, calc(var(--cover-wide) * 1.4134));
  aspect-ratio: 849 / 1200;
  scroll-snap-align: center;
}
.cover-link {
  position: absolute;
  inset: 0;
  display: block;
  background: var(--shelf-ground);
  box-shadow: 0 8px 8px var(--shelf-shadow);
  text-decoration: none;
}
.cover-link img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  backface-visibility: hidden;
}
/* The shelf is dragged by its covers, so the browser must not try to drag one
   away instead. draggable="false" in the markup is what actually settles this
   in every browser; this is the WebKit-only half of the same instruction. */
.cover-link,
.cover-link img {
  -webkit-user-drag: none;
}
/* Set from .newsletter-menu rather than on .cover-link alone: .is-live
   .cover-link below holds the outline at nothing so its colour can be
   transitioned, and at equal specificity the later rule would win and there
   would be no focus ring at all. */
.newsletter-menu .cover-link:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 7px;
}
.depth-meter { display: none; }

/* ---- The stack ------------------------------------------------------- */

/* Not a scroll container. The stack is walked along by dragging it sideways —
   see newsletter-shelf.js — and the wheel is left entirely to the page, so
   that a reader on their way past this band is never caught by it. */
.newsletter-menu.is-live {
  display: block;
  overflow: hidden;
}

.is-live .menu-stage {
  position: relative;
  z-index: 2;
  width: 100%;
  height: 100%;
  overflow: hidden;
  perspective: 1550px;
  perspective-origin: 50% 46%;
  /* Something to take hold of. */
  cursor: grab;
  /* The browser keeps vertical panning, which is the page scrolling on a
     phone; sideways is ours. */
  touch-action: pan-y;
  transform-style: preserve-3d;
}
.is-live .menu-stage.is-dragging { cursor: grabbing; }
/* While the shelf is being dragged the covers must not be selected, and the
   one at the front must not light up under a pointer that is only passing
   through on its way across. */
.is-live .menu-stage.is-dragging * {
  cursor: grabbing;
  user-select: none;
}
.newsletter-menu.is-live .cover-stream {
  position: absolute;
  inset: 0;
  display: block;
  width: auto;
  height: auto;
  padding: 0;
  transform-style: preserve-3d;
}

/* Each cover's place in the line. The script writes these six values every
   frame and this element does nothing else — no layout, no transition, so the
   whole stack is one composited transform per plane. */
.is-live .cover-plane {
  --plane-x: 0px;
  --plane-y: 0px;
  --plane-z: 0px;
  --plane-ry: 0deg;
  --plane-rz: 0deg;
  --plane-scale: 1;
  --plane-opacity: 0;
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--cover-w);
  /* The strip sets an explicit height and lets the width follow; here it is the
     other way round, so hand the height back to the ratio. */
  height: auto;
  opacity: var(--plane-opacity);
  pointer-events: none;
  transform:
    translate3d(calc(-50% + var(--plane-x)), calc(-50% + var(--plane-y)), var(--plane-z))
    rotateY(var(--plane-ry))
    rotateZ(var(--plane-rz))
    scale(var(--plane-scale));
  transform-origin: center;
  transform-style: preserve-3d;
}
/* The one at the front, before the first frame is drawn. */
.is-live .cover-plane:first-child { --plane-opacity: 1; }
.is-live .cover-plane.is-near { will-change: transform, opacity; }
/* Only the cover square on to the reader can be clicked — the rest are edge on
   and a click on one would be a mis-click. */
.is-live .cover-plane.is-centered { pointer-events: auto; }

.is-live .cover-link {
  --pointer-rx: 0deg;
  --pointer-ry: 0deg;
  transform: translateZ(0) scale(1) rotateX(0deg) rotateY(0deg);
  transform-origin: center;
  transform-style: preserve-3d;
  /* The focus ring is deliberately not in here. It used to fade its colour in
     over 180ms, which means a fifth of a second where a keyboard user has
     moved and nothing has drawn yet. */
  transition:
    transform 520ms var(--ease),
    box-shadow 360ms var(--ease-scrim);
  touch-action: pan-y;
}
.is-live .cover-plane.is-centered .cover-link { cursor: pointer; }
.is-live .cover-plane.is-centered .cover-link:hover {
  box-shadow: 0 12px 8px var(--shelf-shadow-lift);
  transform:
    translateZ(58px)
    scale(1.075)
    rotateX(var(--pointer-rx))
    rotateY(var(--pointer-ry));
}
.is-live .cover-link:focus-visible { transform: translateZ(54px) scale(1.055); }

/* How far along the eighteen the reader has come. Decorative — the count is
   spoken from the live region instead. */
.is-live .depth-meter {
  position: absolute;
  top: 50%;
  right: clamp(var(--sp-4), 2.7vw, var(--sp-8));
  z-index: 3;
  display: block;
  width: 12px;
  height: min(280px, 45%);
  pointer-events: none;
  transform: translateY(-50%);
}
.is-live .depth-meter::before {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: var(--hairline-on-dark);
  content: "";
  transform: translateX(-50%);
}
.is-live .depth-thumb {
  position: absolute;
  top: 0;
  left: 50%;
  width: 5px;
  height: 34px;
  border-radius: 999px;
  background: var(--paper);
  transform: translateX(-50%);
  will-change: transform;
}

 .newsletter-menu.is-live .cover-stream { animation: shelf-arrives 900ms var(--ease) both; }
@keyframes shelf-arrives {
  from { clip-path: inset(4% 7% 4% 7%); transform: translate3d(0, 26px, -45px) scale(0.975); }
  to   { clip-path: inset(0 0 0 0);     transform: translate3d(0, 0, 0) scale(1); }
}

@media (max-width: 640px) {
  .newsletter-menu { --cover-wide: clamp(200px, 62vw, 248px); }
  .is-live .depth-meter { right: 12px; width: 10px; height: min(220px, 40%); }
  .is-live .depth-thumb { width: 4px; height: 28px; }
  .newsletter-menu .cover-link:focus-visible { outline-width: 2px; outline-offset: 5px; }
}

/* A laptop in a shallow window. The covers size themselves off the shelf and
   the drag distance off the covers, so only the depth meter needs bringing
   down by hand. */
@media (max-height: 560px) {
  .is-live .depth-meter { height: 44%; }
}

@media (prefers-contrast: more) {
  .is-live .depth-meter::before { background: var(--paper); }
}

@media print {
  .newsletter-menu,
  .is-live .menu-stage {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
    background: var(--paper);
  }
  .newsletter-menu .cover-stream,
  .newsletter-menu.is-live .cover-stream {
    position: static;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--sp-4);
    width: auto;
    padding: 0;
  }
  .newsletter-menu .cover-plane {
    position: relative;
    top: auto;
    left: auto;
    width: 100%;
    opacity: 1;
    pointer-events: auto;
    transform: none;
  }
  .is-live .cover-link { box-shadow: none; }
  .is-live .depth-meter { display: none; }
}
