/* ------------------------------------------------------------------
   A book as an actual object.

   Six faces, built from real elements so the thing can be turned over in
   the hand: boards front and back, the spine, and three edges of stacked
   paper. The same component stands on the shelf, comes forward to be
   inspected, and sits in the library.

   Nothing is ever listed about a book. A line surfaces only when that face
   of it comes round to face the reader.
   ------------------------------------------------------------------ */

.tome3d {
  --bh: 19rem;                              /* height is the one free measure */
  --bw: calc(var(--bh) * var(--book-w));    /* board, from the shared ratio   */
  --bd: calc(var(--bh) * var(--book-d));    /* thickness, likewise            */
  --hue: 168;
  --ry: 0deg;
  --rx: 0deg;

  /* The squares: how far the boards overhang the text block at the head,
     the tail and the fore-edge. Without them a book is a solid block. */
  --square: calc(var(--bh) * .017);
  /* The thickness of a board, which is what you see at those edges. */
  --board: calc(var(--bd) * .09);
  /* The thickness of the spine covering, seen end on at the head and tail. */
  --joint: calc(var(--bd) * .13);

  /* How far the whole body is pushed back, so that the first leaf -- the
     page you are actually looking at when the book lies open -- lands in the
     plane of the page rather than a half-thickness in front of it.
     Perspective magnifies anything nearer than that plane, which is why the
     object used to read larger than the spread it hands over to. The leaf, not
     the board, because the leaf is what the spread has to match. */
  --sink: calc(var(--board) - var(--bd) / 2);

  /* How far every inner surface tucks under the rim it meets. One pixel,
     and it is the difference between a joint and a hairline of background. */
  --seam: 1px;

  position: relative;
  width: var(--bw);
  height: var(--bh);
  perspective: 1500px;
  perspective-origin: 50% 46%;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  /* The journey from where it stood on the shelf to where it is held. */
  transition: transform var(--t-pull) var(--ease-out);
}

.tome3d__body {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform: translateZ(var(--sink))
             rotateX(var(--rx)) rotateY(var(--ry)) translateX(var(--slide, 0px));
  transition: transform var(--t-swap) var(--ease-out);
  cursor: grab;
}
.tome3d.is-turning .tome3d__body { transition: none; }

/* The cursor belongs to the handle, and the handle is the whole book. */
.tome3d { cursor: grab; }
.tome3d.is-turning { cursor: grabbing; }
.tome3d.is-opening, .tome3d.is-closing, .tome3d--bare { cursor: default; }
.tome3d.is-arriving .tome3d__body { transition: transform var(--t-pull) var(--ease-out); }

/* Each face is centred first, then rotated out to its own plane. */
.t3 {
  position: absolute;
  top: 50%;
  left: 50%;
  display: flex;
  overflow: hidden;
  backface-visibility: hidden;
}

/* --------------------------------------------------- the boards */

.t3--front, .t3--back {
  width: var(--bw);
  height: var(--bh);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.1rem;
  padding: 2rem 1.4rem;
  border-radius: 2px 5px 5px 2px;
  background:
    linear-gradient(100deg, rgba(0,0,0,.42) 0 4%, transparent 14%),
    linear-gradient(160deg,
      hsl(var(--hue) 16% 15%) 0%,
      hsl(var(--hue) 20% 10%) 52%,
      hsl(var(--hue) 14% 16%) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(201,168,76,.14),
    inset 0 0 50px rgba(0,0,0,.45),
    /* the turn-in along the fore-edge, where the board is doubled over */
    inset -2px 0 0 rgba(0,0,0,.4), inset -3px 0 3px rgba(255,255,255,.04);
}
/* The cover is a leaf hinged on the spine: the board on the outside of it and
   the first page on the inside. Swinging this is the book opening, and both
   faces of it are real, so nothing vanishes halfway through the turn. */
.t3__cover {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--bw);
  height: var(--bh);
  transform-style: preserve-3d;

  /* The hinge runs down the spine edge AT THE BOARD'S OWN DEPTH, not through
     the middle of the book's thickness. With the axis at the middle the whole
     board swung away from the spine as it opened, so the outside of it and the
     inside of it appeared to pivot about two different lines. */
  transform-origin: 0 50% calc(var(--bd) / 2 - var(--board) / 2);
  transform: translate(-50%, -50%) rotateY(var(--cover, 0deg));
  z-index: 3;
}

/* The thickness of the front board, seen end on at each of its three free
   edges. They live in the cover's own space, so they turn with it: the lip
   leaves the book when the book is opened, which is what a board does.

   Each strip lies at the board's inner plane and stands forward from there by
   the board's thickness, meeting the outer leather exactly at the surface. */
.t3e {
  position: absolute;
  background: linear-gradient(hsl(var(--hue) 15% 18%), hsl(var(--hue) 14% 11%));
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.3);
}
.t3e--head {
  left: 0; right: 0; top: 0; height: var(--board);
  transform-origin: top center;
  transform: translateZ(calc(var(--bd) / 2 - var(--board))) rotateX(90deg);
}
.t3e--tail {
  left: 0; right: 0; bottom: 0; height: var(--board);
  transform-origin: bottom center;
  transform: translateZ(calc(var(--bd) / 2 - var(--board))) rotateX(-90deg);
}
.t3e--fore {
  top: 0; bottom: 0; right: 0; width: var(--board);
  transform-origin: right center;
  transform: translateZ(calc(var(--bd) / 2 - var(--board))) rotateY(90deg);
}

/* The binding crosses the front board's edge as well, since it goes round the
   whole book. This piece of it belongs to the cover, with the rest of that
   board. */
.t3e--fore .t3__strap {
  top: 50%;
  left: 0;
  right: 0;
  height: calc(var(--bh) * .052);
  transform: translateY(-50%);
}
.t3--front {
  top: 0;
  left: 0;
  transform: translateZ(calc(var(--bd) / 2));
}

/* The inside of that board, facing into the book while it is shut. */
.t3__verso {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--bw);
  height: var(--bh);
  /* Turned in place and then set at the board's inner plane, so that the
     hinge above carries it to the front of the flipped board. Written the
     other way round it was placed to suit an axis through the middle of the
     book, and moving the axis would have thrown it to the wrong side. */
  transform: translateZ(calc(var(--bd) / 2 - var(--board))) rotateY(180deg);
  backface-visibility: hidden;
  background:
    linear-gradient(100deg, rgba(0,0,0,.26) 0 1.6%, transparent 7%),
    repeating-linear-gradient(94deg, rgba(0,0,0,.09) 0 1px, transparent 1px 3px),
    linear-gradient(160deg,
      hsl(var(--hue) 16% 21%) 0%,
      hsl(var(--hue) 18% 16%) 56%,
      hsl(var(--hue) 14% 22%) 100%);
  box-shadow: inset 0 0 0 1px rgba(201,168,76,.24);
}

/* The page it opens onto, lying under the cover with its words already on it. */
.t3__recto {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--bw);
  height: var(--bh);
  transform: translate(-50%, -50%) translateZ(calc(var(--bd) / 2 - var(--board)));
  background:
    linear-gradient(100deg, rgba(0,0,0,.26) 0 1.6%, transparent 7%),
    repeating-linear-gradient(94deg, rgba(0,0,0,.09) 0 1px, transparent 1px 3px),
    linear-gradient(160deg,
      hsl(var(--hue) 16% 21%) 0%,
      hsl(var(--hue) 18% 16%) 56%,
      hsl(var(--hue) 14% 22%) 100%);
  box-shadow: inset 0 0 0 1px rgba(201,168,76,.24);
  z-index: 2;
}

/* Each leaf is a page in its board, laid out by the same two measurements
   the flat spread uses, so nothing reflows when one becomes the other. */
.t3__verso .leaf, .t3__recto .leaf {
  position: absolute;
  inset: var(--leafpad);
  margin: 0;
  padding: var(--leafinner);
  overflow: hidden;
  background: var(--paper-1);
}
.t3__verso { border-radius: 3px 0 0 3px; }
.t3__recto { border-radius: 0 3px 3px 0; }

.t3__verso .leaf { right: 0; border-radius: 2px 0 0 2px; box-shadow: inset -20px 0 26px -20px rgba(27,23,16,.42); }
.t3__recto .leaf { left:  0; border-radius: 0 2px 2px 0; box-shadow: inset  20px 0 26px -20px rgba(27,23,16,.42); }

/* The grain of the paper, falling on the page in the book just as it falls
   on the page in the spread. */
.t3__verso .leaf::before, .t3__recto .leaf::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 4;
  background:
    repeating-linear-gradient(0deg, rgba(27,23,16,.02) 0 1px, transparent 1px 3px),
    radial-gradient(260% 80% at var(--lit, 50%) -10%, rgba(255,255,255,.35), transparent 55%);
  mix-blend-mode: multiply;
  pointer-events: none;
}
.t3__verso .leaf::before { --lit: 100%; }
.t3__recto .leaf::before { --lit: 0%; }
.t3--back {
  transform: translate(-50%, -50%) rotateY(180deg) translateZ(calc(var(--bd) / 2));
  border-radius: 5px 2px 2px 5px;
  box-shadow:
    inset 0 0 0 1px rgba(201,168,76,.14),
    inset 0 0 50px rgba(0,0,0,.45),
    inset 2px 0 0 rgba(0,0,0,.4), inset 3px 0 3px rgba(255,255,255,.04);
}

/* The joint: the groove pressed in beside the spine that lets a board open.
   Every hardback has one and nothing reads as bound without it. */
/* ⚠️ The groove runs the whole height of the board, head to tail, because
   that is what a groove does -- it is pressed into the board, and a board
   has no part that is not board. It was held back by two squares at each
   end for a while, to stop its ends painting a dark ledge across the lip
   while the cover tilted; the ledge is a problem of the MOVING board only,
   so the inset lives on the front cover now and the back board, which
   never moves, is whole. Measured again after: see the note on the front. */
.t3__joint {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--bd) * .34);
  width: calc(var(--bd) * .2);
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(90deg,
    rgba(255,255,255,.045) 0 14%,
    rgba(0,0,0,.52) 36%,
    rgba(0,0,0,.62) 50%,
    rgba(0,0,0,.42) 64%,
    rgba(255,255,255,.055) 92% 100%);
}
.t3--back .t3__joint { left: auto; right: calc(var(--bd) * .34); transform: scaleX(-1); }

/* ⚠️ The cover's groove runs head to tail like the back board's, and its
   ends fade instead of stopping.

   The reason it cannot simply be solid to the ends: as the board tilts
   toward the eye, the dark end of the groove projects past the head and
   the tail and paints a ledge across the lip. It used to be held two
   squares short of each end, which fixed the ledge by making it not a
   groove. Masked, the groove REACHES the ends of the board and the dark
   does not -- there is nothing solid left at the lip to be projected
   across it. The back board, which never turns, needs no mask. */
.t3--front .t3__joint {
  -webkit-mask-image: linear-gradient(180deg,
    transparent 0,
    #000 calc(var(--square) * 1.6),
    #000 calc(100% - var(--square) * 1.6),
    transparent 100%);
  mask-image: linear-gradient(180deg,
    transparent 0,
    #000 calc(var(--square) * 1.6),
    #000 calc(100% - var(--square) * 1.6),
    transparent 100%);
}

/* The first leaf, lying under the front board and revealed as it lifts. */
.t3__leaf {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(var(--bw) - var(--square));
  height: calc(var(--bh) - var(--square) * 2);
  transform: translate(calc(-50% - var(--square) / 2), -50%)
             translateZ(calc(var(--bd) / 2 - var(--board)));
  background: linear-gradient(100deg, var(--paper-2) 0 3%, var(--paper-0) 14%, var(--paper-1) 100%);
  box-shadow: inset 6px 0 14px rgba(27,23,16,.28);
}

/* The grain of the hide, on every leather face. */
.t3--front::before, .t3--back::before, .t3--spine::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(94deg, rgba(0,0,0,.14) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(2deg, rgba(255,255,255,.03) 0 1px, transparent 1px 5px);
  pointer-events: none;
}

/* A blind-tooled rule inset from the edge of the board. */
.t3--front::after, .t3--back::after {
  content: '';
  position: absolute;
  inset: .7rem;
  border: 1px solid rgba(201,168,76,.16);
  border-radius: 2px;
  pointer-events: none;
}

.t3__mark {
  position: relative;
  z-index: 1;
  font-family: var(--font-paper);
  font-size: 2.4rem;
  font-weight: 600;
  letter-spacing: .14em;
  color: var(--gilt);
  text-shadow: 0 1px 0 rgba(0,0,0,.85), 0 0 18px rgba(201,168,76,.22);
}
.t3__rule { position: relative; z-index: 1; width: 3.5rem; height: 1px; background: rgba(201,168,76,.5); }
.t3__num {
  position: relative;
  z-index: 1;
  font-family: var(--font-paper);
  font-size: .9rem;
  letter-spacing: .34em;
  color: rgba(201,168,76,.62);
}

/* --------------------------------------------------- the spine */

/* The spine carries lettering, so it is padded and laid out. The fore-edge
   is a frame around a trench and takes neither. They used to share this
   rule, which left the padding eating into the opening. */
.t3--spine {
  width: var(--bd);
  height: var(--bh);
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: calc(var(--bh) * .055) calc(var(--bd) * .07) calc(var(--bh) * .045);
}
.t3--spine { transform: translate(-50%, -50%) rotateY(-90deg) translateZ(calc(var(--bw) / 2)); }

.t3--spine {
  background:
    linear-gradient(90deg, rgba(0,0,0,.5) 0, transparent 22%, transparent 78%, rgba(0,0,0,.42) 100%),
    linear-gradient(160deg, hsl(var(--hue) 16% 16%), hsl(var(--hue) 20% 10%) 55%, hsl(var(--hue) 14% 17%));
  box-shadow: inset 0 0 0 1px rgba(201,168,76,.12);
}

/* The raised bands across a bound spine. Measured in proportion so the flat
   spine on the shelf and this one always agree. */
.t3__bands {
  position: absolute;
  inset: 32% 6% 24%;
  background: repeating-linear-gradient(
    180deg,
    transparent              0                      calc(16.666% - 3px),
    rgba(0,0,0,.52)          calc(16.666% - 3px)    calc(16.666% - 1px),
    rgba(201,168,76,.14)     calc(16.666% - 1px)    calc(16.666% + 1px),
    rgba(0,0,0,.46)          calc(16.666% + 1px)    calc(16.666% + 3px),
    transparent              calc(16.666% + 3px)    33.333%
  );
  pointer-events: none;
}

/* The headbands, sewn in at the head and tail of a bound spine. */
.t3__cap {
  position: absolute;
  left: 4%;
  right: 4%;
  height: calc(var(--bd) * .13);
  z-index: 1;
  pointer-events: none;
  background: repeating-linear-gradient(90deg,
    rgba(201,168,76,.5) 0 1px, rgba(27,23,16,.6) 1px 2px);
  box-shadow: 0 0 0 1px rgba(0,0,0,.45);
}
.t3__cap--head { top: calc(var(--bd) * .1); }
.t3__cap--tail { bottom: calc(var(--bd) * .1); }

.t3__code {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .1em;
  font-family: var(--font-paper);
  font-size: calc(var(--bd) * var(--spine-code));
  line-height: 1.04;
  font-weight: 600;
  color: var(--gilt);
  text-shadow: 0 1px 0 rgba(0,0,0,.8);
}
.t3__foot {
  position: relative;
  z-index: 1;
  font-family: var(--font-paper);
  font-size: calc(var(--bd) * var(--spine-foot));
  letter-spacing: .06em;
  color: rgba(201,168,76,.64);
}

/* --------------------------------------------------- the paper edges */
/*
   A hardback is not a solid block. The boards are larger than the pages, so
   at the head, the tail and the fore-edge they stand proud as a lip and the
   text block sits in a shallow trench behind them.

   Only the BACK board's share of that lip is drawn here. The front board is
   part of the cover and carries its own edge, so that opening the book takes
   the whole board away -- lip and all -- instead of peeling a sheet off the
   front of a block that never moves.

   Along the fore-edge there is no board at either end: that is the slot the
   covers part at, and a bar drawn across it welds the book shut.

   The text block is a brick, and these three faces are three faces of it.
   Each one stops exactly where the next one's plane begins, so they meet at
   a plain convex edge the way any three faces of a box do -- no sheet runs
   past another and nothing is drawn twice.

   What sits beyond the brick's corner, between it and the corner of the
   boards, is a small pocket. It has to be there: the boards overhang the
   pages on three sides, so the corner of the pages is set back from the
   corner of the boards in two directions at once. It is closed on both sides
   by the inside of the boards, so nothing shows through it -- it is a
   shadow, not a hole. The walls own the corner once between them: the
   fore-edge's run the full height and the head's and the tail's stop where
   they begin, because two coplanar faces in one square tear.
*/

.t3--fore, .t3--top, .t3--bottom {
  transform-style: preserve-3d;
  overflow: visible;
  box-sizing: border-box;
  background: transparent;            /* the middle of the frame is the trench */
  border-style: solid;
  border-color: hsl(var(--hue) 14% 15%);
  box-shadow: inset -1px -1px 0 rgba(0,0,0,.5);
}

/* The fore-edge. The back board is the one border it keeps; the front board's
   edge swings with the cover. */
.t3--fore {
  width: var(--bd);
  height: var(--bh);
  border-width: 0 var(--board) 0 0;
  transform: translate(-50%, -50%) rotateY(90deg) translateZ(calc(var(--bw) / 2));
}

/* The head and the tail. The spine's covering closes one end, the slot is
   open at the other, the back board runs the whole length and the front
   board, again, is the cover's.

   Their board edge is lit nearer the open book's board than the fore-edge's
   is, for the same reason the trench is: from above, these two faces take the
   light and the fore edge does not. */
.t3--top, .t3--bottom {
  width: var(--bw);
  height: var(--bd);
  border-color: hsl(var(--hue) 15% 21%);
}
.t3--top {
  border-width: var(--board) 0 0 var(--joint);
  transform: translate(-50%, -50%) rotateX(90deg)  translateZ(calc(var(--bh) / 2));
}
.t3--bottom {
  border-width: 0 0 var(--board) var(--joint);
  transform: translate(-50%, -50%) rotateX(-90deg) translateZ(calc(var(--bh) / 2));
}

/* The text block, lying on the floor of the trench. Its offsets stand in for
   the border the face no longer carries on the cover's side. */
.t3__paper {
  position: absolute;
  inset: 0;
  transform: translateZ(calc(-1 * var(--square)));
  background: repeating-linear-gradient(90deg,
    var(--paper-0) 0 1px,
    var(--paper-2) 1px 2px,
    var(--paper-1) 2px 3px);
  box-shadow: inset 0 0 10px rgba(27,23,16,.2);
}
.t3--top    .t3__paper { inset: 0 var(--square) var(--board) 0; }
.t3--bottom .t3__paper { inset: var(--board) var(--square) 0 0; }
.t3--fore   .t3__paper { inset: var(--square) 0 var(--square) var(--board); }

/* At the head and the tail you are looking down on the folded edges, so the
   lines run from the spine to the fore-edge and repeat across the thickness. */
.t3--top .t3__paper,
.t3--bottom .t3__paper {
  background-image: repeating-linear-gradient(180deg,
    var(--paper-0) 0 1px,
    var(--paper-2) 1px 2px,
    var(--paper-1) 2px 3px);
}

/* The walls of the trench: the inner faces of the boards and of the spine
   covering, standing from the paper up to the rim. */
.t3w {
  position: absolute;
  background: linear-gradient(hsl(var(--hue) 13% 11%), hsl(var(--hue) 13% 7%));
  box-shadow: inset 0 0 6px rgba(0,0,0,.6);
}
/* Each one starts a hair outside its own rim and is that much longer, so it
   tucks under the rim rather than meeting it exactly. Two perpendicular
   surfaces that meet precisely at an edge leave a hairline of background
   between them once they are antialiased, and in the trench that read as a
   thin black line running the length of the book. */
.t3w--t { left: 0; right: 0; top: calc(-1 * var(--seam));    height: calc(var(--square) + var(--seam)); transform-origin: top center;    transform: rotateX(-90deg); }
.t3w--b { left: 0; right: 0; bottom: calc(-1 * var(--seam)); height: calc(var(--square) + var(--seam)); transform-origin: bottom center; transform: rotateX(90deg); }
.t3w--l { top: 0; bottom: 0; left: calc(-1 * var(--seam));   width:  calc(var(--square) + var(--seam)); transform-origin: left center;   transform: rotateY(90deg); }
.t3w--r { top: 0; bottom: 0; right: calc(-1 * var(--seam));  width:  calc(var(--square) + var(--seam)); transform-origin: right center;  transform: rotateY(-90deg); }

/* Each wall stands where its own board does, so the ones on the cover's side
   come in by a board's thickness. The fore-edge's run the full height and own
   the corner pocket; the head's and the tail's stop where they begin. */
.t3--top    .t3w--b { bottom: calc(var(--board) - var(--seam)); }

/* The head and tail trenches take the light; the fore-edge one does not.
   Matched to the open book's board so the hand-over does not jump: the
   model's head read 14-29 against the spread's 40 before this. */
.t3--top .t3w,
.t3--bottom .t3w {
  background: linear-gradient(hsl(var(--hue) 15% 25%), hsl(var(--hue) 15% 19%));
  box-shadow: inset 0 0 5px rgba(0,0,0,.22);
}
.t3--bottom .t3w--t { top: calc(var(--board) - var(--seam)); }
.t3--fore   .t3w--l { left: calc(var(--board) - var(--seam)); }
.t3--top    .t3w--l { bottom: var(--board); }
.t3--bottom .t3w--l { top: var(--board); }

/* Nothing stands in the corner pocket, not even a wall.

   The pocket is where the head's trench and the fore-edge's meet, and a
   wall carried into it is a plane seen edge on from almost everywhere --
   which draws as a thin black line running across the gap from one lip to
   the other. Two walls, two lines. Every trench therefore stops a square
   short of the corner and the pocket is left as what it is: empty. */
.t3--fore .t3w--l, .t3--fore .t3w--r { top: var(--square); bottom: var(--square); }
.t3--top    .t3w--t, .t3--top    .t3w--b,
.t3--bottom .t3w--t, .t3--bottom .t3w--b { right: var(--square); }
/* --------------------------------------------------- the unfinished one */
/* It is the same book as the others in every way. The only differences are
   that nothing was ever stamped into it and that it is bound shut. It is not
   drawn differently, lit differently, or outlined. */

/* The clasp, which is not locked. It simply does not open yet. */
.t3__clasp {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 1.5rem;
  transform: translateY(-50%);
  background: linear-gradient(180deg, #1d2d36, #0a1115 60%, #17242c);
  border-block: 1px solid rgba(37,234,181,.28);
  box-shadow: 0 0 16px rgba(37,234,181,.12);
  z-index: 2;
}
.t3__clasp::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: .55rem;
  height: .55rem;
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--accent-deep);
  box-shadow: 0 0 10px var(--accent-glow);
}



/* ------------------------------------------------------------------
   The button stamped into the front board

   The only control on the object. Clicking the book itself does nothing,
   because the book is for turning over -- a tap and a drag would fight
   each other for the same gesture.
   ------------------------------------------------------------------ */

.t3__open {
  position: absolute;
  left: 50%;
  bottom: 2.1rem;
  z-index: 3;
  transform: translateX(-50%);
  padding: .5rem 1.15rem;
  font-family: var(--font-hud);
  font-size: .6rem;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--gilt);
  background: linear-gradient(180deg, rgba(201,168,76,.12), rgba(0,0,0,.26));
  border: 1px solid rgba(201,168,76,.42);
  border-radius: 2px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 1px 3px rgba(0,0,0,.5);
  cursor: pointer;
  transition: color var(--t-state) var(--ease-out),
              background var(--t-state) var(--ease-out),
              border-color var(--t-state) var(--ease-out),
              box-shadow var(--t-state) var(--ease-out);
}
.t3__open:hover {
  color: #f0dca0;
  border-color: rgba(201,168,76,.8);
  background: linear-gradient(180deg, rgba(201,168,76,.22), rgba(0,0,0,.2));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 0 18px rgba(201,168,76,.22);
}
.t3__open:active { transform: translateX(-50%) translateY(1px); }


/* ------------------------------------------------------------------
   The excuse, thrown off the cover

   A book that will not open says why, and it says it the way everything
   else here is shown: as light off the object. Rails run from the corners
   of the button up to the corners of the panel, with streaks travelling
   along them, so the words are visibly thrown from the button itself.
   ------------------------------------------------------------------ */

.t3__excuse {
  position: absolute;
  left: 50%;
  bottom: 30%;
  z-index: 4;
  /* As wide as the sentence and no wider. The panel is a thing the light
     falls on, so it has to have the shape of what is written on it; a fixed
     width left a short refusal sitting in a box the size of the book. The
     old width survives as the point where a long one is made to wrap. */
  width: max-content;
  max-width: 150%;
  transform-style: preserve-3d;
  transform: translateX(-50%) translateZ(calc(var(--bd) / 2 + 3.4rem));
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-swap) var(--ease-out);
}
.t3__excuse.is-said { opacity: 1; }

/* The panel the words are thrown onto: a box, with corners. */
.t3__excuse-t {
  position: relative;
  display: block;
  padding: .75rem 1.1rem;
  font-family: var(--font-paper);
  font-size: clamp(1.02rem, calc(var(--bd) * .44), 1.5rem);
  font-style: italic;
  line-height: 1.34;
  text-align: center;
  text-wrap: balance;
  color: var(--accent-pale);
  background: linear-gradient(180deg, rgba(37,234,181,.12), rgba(4,7,10,.62));
  border: 1px solid rgba(37,234,181,.55);
  text-shadow: 0 0 12px var(--accent-glow), 0 0 30px rgba(37,234,181,.3);
  box-shadow: 0 0 40px rgba(37,234,181,.16), inset 0 0 24px rgba(37,234,181,.08);
}
/* The scan of a projected surface. */
.t3__excuse-t::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(180deg,
    rgba(37,234,181,.1) 0 1px, transparent 1px 3px);
  mix-blend-mode: screen;
  pointer-events: none;
}


/* The button lights while it is throwing. */
.tome3d.is-saying .t3__open {
  color: var(--accent-pale);
  border-color: var(--accent);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 0 26px var(--accent-glow);
}
/* ------------------------------------------------------------------
   Marks on the object

   What a book has to give up is carried on the thing itself. There is no
   list and no caption: the reader turns it over and sees. The words behind
   each mark are its accessible name, for anyone who cannot see it.
   ------------------------------------------------------------------ */

.t3m { position: absolute; z-index: 2; pointer-events: none; }

/* front -- a blank panel, debossed where a title would have been stamped */
.t3m--plate {
  top: 18%;
  left: 50%;
  width: 56%;
  height: 20%;
  transform: translateX(-50%);
  border: 1px solid rgba(0,0,0,.45);
  border-radius: 1px;
  background: linear-gradient(170deg, rgba(0,0,0,.26), rgba(255,255,255,.035));
  box-shadow: inset 0 1px 2px rgba(0,0,0,.6), 0 1px 0 rgba(255,255,255,.05);
}

/* spine -- the blank label where the mark should be */
.t3m--label {
  top: 30%;
  left: 50%;
  width: 72%;
  height: 16%;
  transform: translateX(-50%);
  border-block: 1px solid rgba(0,0,0,.5);
  background: linear-gradient(180deg, rgba(0,0,0,.3), rgba(255,255,255,.04));
  box-shadow: inset 0 1px 2px rgba(0,0,0,.55);
}

/* fore-edge -- six notches cut into the paper, every one worn smooth */
.t3m--notches {
  inset: 10% 0;
  transform: translateZ(calc(-1 * var(--square)));
  background: repeating-linear-gradient(
    180deg,
    transparent 0 calc(16.666% - 3px),
    rgba(27,23,16,.5) calc(16.666% - 3px) calc(16.666% - 1px),
    rgba(255,255,255,.35) calc(16.666% - 1px) 16.666%
  );
}

/* top edge -- clean, where the others have gone dull */
.t3m--sheen {
  inset: 0;
  transform: translateZ(calc(-1 * var(--square)));
  background: linear-gradient(90deg, transparent 10%, rgba(255,255,255,.4) 50%, transparent 90%);
  mix-blend-mode: screen;
}

/* bottom edge -- a band that sits lower than it should */
.t3m--band {
  left: 12%;
  right: 12%;
  top: 50%;
  height: 2px;
  transform: translateY(-50%) translateZ(calc(-1 * var(--square)));
  background: linear-gradient(90deg, transparent, rgba(27,23,16,.6), transparent);
}

/* ⚠️ Hints do NOT share a corner. Two marks in the same place on different
   books read as the same mark: a reader turning the second book over sees
   what they already found, where they already looked. Each has its own face
   and its own corner. What they share is the faintness, and the fact that
   nothing anywhere points at any of them.

   ⚠️ And a hint has to be readable BEFORE the story explains it. A trebuchet
   is a siege engine whether or not you know who built it; a smear of soot is
   nothing to anybody. Draw the object; let knowing the story be what makes it
   land, not what makes it visible. */
/* ⚠️ NOTHING GOES OVER THE GROOVE BESIDE THE SPINE. The groove is a
   pressed-in hinge and the moment a drawing crosses it, it stops being a
   hollow in the board and becomes a line on it. It sits between 7.8% and
   12.4% of the board's width in from the spine-side edge -- bd*.34 wide
   of the edge, bd*.2 across, against a board of .72bh -- so anything on
   that side of a board clears this much and no less. */
.tome3d { --groove-clear: 15%; }

.t3m--hint { opacity: .17; }

/* The two a reader can touch sit a little stronger, because a thing that
   responds to a press has to look like it is there. */
.t3m--hourglass, /* The two a reader can touch sit a little stronger, because a thing that
   responds to a press has to look like it is there. */
.t3m--hourglass, .t3m--trebuchet { opacity: .26; }

/* ⚠️ Marks are `pointer-events: none` so they never interrupt a turn. These
   two are buttons and must take a press -- js/tome3d.js knows to let it
   through rather than start dragging the book. */
.t3m--hourglass, .t3m--trebuchet {
  pointer-events: auto;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  transition: opacity var(--t-state) var(--ease-out);
}
.t3m--hourglass:hover, .t3m--trebuchet:hover,
.t3m--hourglass:focus-visible, .t3m--trebuchet:focus-visible { opacity: .42; outline: 0; }

.t3m--hourglass svg, .t3m--trebuchet svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: none;
  stroke: rgb(214, 238, 232);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------------------------------------------------------- RNG, back board */

/* The eyes, low in the corner away from the spine. */
.t3m--eyes {
  left: 7%;
  bottom: 6.5%;
  width: calc(var(--bh) * .098);
  height: calc(var(--bh) * .0356);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 16' fill='none' stroke='rgb(214,238,232)' stroke-width='1.25' stroke-linejoin='round'%3E%3Cpath d='M1 8s2.8-5.2 8-5.2S17 8 17 8s-2.8 5.2-8 5.2S1 8 1 8z'/%3E%3Ccircle cx='9' cy='8' r='2.2'/%3E%3Cpath d='M27 8s2.8-5.2 8-5.2S43 8 43 8s-2.8 5.2-8 5.2S27 8 27 8z'/%3E%3Ccircle cx='35' cy='8' r='2.2'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* The hourglass, at the head on the fore side, clear of the groove.
   It turns itself over: the sand runs out, it rests, the glass comes round,
   and it runs the other way. Nothing about it answers to a press. */
.t3m--hourglass {
  right: var(--groove-clear);
  top: 8%;
  width: calc(var(--bh) * .062);
  height: calc(var(--bh) * .088);
}
.t3m--hourglass svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: none;
  stroke: rgb(214, 238, 232);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* ⚠️ Twice the sand's time, and ending on 360 so the loop closes. One turn
     per pass of the sand, during the rest at the end of each pass. */
  animation: hgTurn var(--hg, 18s) ease-in-out infinite;
  transform-origin: 50% 50%;
}
.t3hg__sand {
  stroke: none;
  fill: rgb(214, 238, 232);
  /* ⚠️ Stated, not assumed: without it the translations below are measured
     against each shape's own box rather than the drawing's. */
  transform-box: view-box;
}

/* ⚠️ Both of these MOVE. Neither is scaled, because scaling a chamber-shaped
   body keeps its width and pushes it out through the glass. The upper block
   slides down out of its chamber; the heap slides up into its chamber from
   below the floor. The clip does the rest. */
.t3hg__sand--top { animation: hgDrain var(--hg, 18s) linear infinite; }
.t3hg__sand--bot { animation: hgFill  var(--hg, 18s) linear infinite; }
.t3hg__fall {
  stroke-width: 1;
  stroke-dasharray: 2 2.4;
  transform-box: view-box;
  animation: hgThread .55s linear infinite,
             hgRun var(--hg, 18s) linear infinite;
}

/* The glass is drawn over the sand, so the sand can never be outside it. */
.t3hg__glass { fill: none; }

/* 0% is the chamber all but full -- a little space at the top, which is the
   sand settling after the turn. 74% is through, and the last quarter is the
   rest before the glass comes round. */
@keyframes hgDrain { 0% { transform: translateY(3px); }  70%, 100% { transform: translateY(26px); } }
@keyframes hgFill  { 0% { transform: translateY(21px); } 70%, 100% { transform: translateY(0); } }
@keyframes hgThread { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -4.4; } }
@keyframes hgRun   { 0%, 64% { opacity: 1; } 70%, 100% { opacity: 0; } }
/* ⚠️ The turn must not begin until the sand is through. 46% of two passes
   is well past 68% of one, with room to spare at both ends. */
/* ⚠️ It ends at 180 and the loop snaps it back to 0. That cannot be seen:
   a symmetric glass turned over with the full chamber up is the same
   picture as an upright one with the full chamber up, and the sand snaps
   back in the same frame. */
@keyframes hgTurn {
  0%, 82%   { transform: rotate(0deg); }
  96%, 100% { transform: rotate(180deg); }
}
@media (prefers-reduced-motion: reduce) {
  .t3hg__sand, .t3hg__fall, .t3m--hourglass svg { animation: none; }
  .t3hg__sand--top { transform: scaleY(.45); }
  .t3hg__sand--bot { transform: scaleY(.55); }
}
/* ---------------------------------------------------------- DWK, back board */
/* ⚠️ In the corner on the PAGE side of the board, facing the spine -- which
   is the side the front cover is on, and therefore where the shot has to go.
   On the back board `left` is the fore-edge, so this is `left`. */
.t3m--trebuchet {
  /* Moved in from the fore edge (2026-10-28) so DWK's gear train can come
     down that edge and drive it from behind its foot. */
  left: 14%;
  bottom: 5%;
  width: calc(var(--bh) * .21);
  height: calc(var(--bh) * .168);
}
.t3m--trebuchet svg { stroke-width: 1.4; overflow: visible; }

/* ⚠️ `.t3` clips its face, which is right for everything a face carries --
   except a stone that has just been thrown off it. The back board stops
   clipping for as long as the throw lasts, so the arc can be watched all
   the way across the spine instead of vanishing at the board's edge. */
.t3--back:has(.t3m--trebuchet.is-throwing) { overflow: visible; }
.t3tb__shot, .t3tb__flyer { fill: rgb(214, 238, 232); stroke: none; }
/* The flyer waits off-stage until the rope lets go. */
/* The release point, measured off the rope at the top of the swing. */
.t3tb__flyer { opacity: 0; --fx: 15.3px; --fy: -80.1px; }
.t3tb__weight { fill: rgba(214, 238, 232, .22); }

/* Everything that turns, turns about the axle. The counterweight hangs from
   the short arm on a pivot of its own, so it stays upright the whole way
   round -- carried rigidly it finishes the throw upside down over the axle. */
.t3tb__arm    { transform-origin: 28px 14px; }
/* ⚠️ The rope hangs off the END of the long arm, so it turns about the
   arm's TIP, not the axle -- and it lags, because a rope has no stiffness
   of its own. Its last stop is -55 degrees: the arm finishes at 145, and
   145 - 55 = 90, which is straight down. Gravity has it from then on. */
.t3tb__sling  { transform-origin: 9px 41px; }
.t3tb__weight { transform-origin: 36px 2.5px; }

/* ⚠️ A trebuchet does not stop where the throw leaves it. The weight
   overshoots, swings back, and rings down until it hangs straight below the
   axle -- which leaves the long arm pointing straight up. The settling is
   most of what makes the thing read as a machine with mass. */
@keyframes tbArm {
  0%   { transform: rotate(0deg); }
  40%  { transform: rotate(186deg); }
  56%  { transform: rotate(110deg); }
  68%  { transform: rotate(172deg); }
  78%  { transform: rotate(126deg); }
  86%  { transform: rotate(158deg); }
  92%  { transform: rotate(136deg); }
  96%  { transform: rotate(150deg); }
  100% { transform: rotate(145deg); }
}
@keyframes tbWeight {
  0%   { transform: rotate(0deg); }
  40%  { transform: rotate(-186deg); }
  56%  { transform: rotate(-110deg); }
  68%  { transform: rotate(-172deg); }
  78%  { transform: rotate(-126deg); }
  86%  { transform: rotate(-158deg); }
  92%  { transform: rotate(-136deg); }
  96%  { transform: rotate(-150deg); }
  100% { transform: rotate(-145deg); }
}
/* The sling swings round with the arm and goes slack once the shot is away. */
/* ⚠️ -55 degrees is along the beam: the arm ends at 145 and 145-55 = 90,
   straight down. So the rope passing -55 is the rope going OVER the stick,
   which is what it does at the moment of release -- it carries past the
   arm, following the shot out, before it drops back and hangs. */
/* ⚠️ The beam, seen from the tip, lies at -55 in the sling's own frame.
   So anything less than -55 is the far side of the arm: the rope was
   crossing over at a sixth of the way in and trailing from THERE, which
   is what read as it jumping sides. It is held short of -55 the whole
   sweep -- dragging, on the side it was loaded on -- and only passes the
   arm in the last few percent, which is the moment it lets go. */
@keyframes tbSling {
  0%   { transform: rotate(0deg); }     /* in the trough             */
  6%   { transform: rotate(28deg); }    /* falling behind the tip    */
  14%  { transform: rotate(36deg); }    /* trailing                  */
  24%  { transform: rotate(40deg); }
  31%  { transform: rotate(90deg); }    /* the top of its path -- go */
  34%  { transform: rotate(162deg); }   /* well over it -- let go    */
  38%  { transform: rotate(44deg); }    /* empty: it falls back      */
  44%  { transform: rotate(-32deg); }
  56%  { transform: rotate(-20deg); }
  68%  { transform: rotate(-82deg); }
  78%  { transform: rotate(-36deg); }
  86%  { transform: rotate(-68deg); }
  92%  { transform: rotate(-46deg); }
  100% { transform: rotate(-55deg); }
}
/* ⚠️ The shot is OUTSIDE the arm, and its path is the arm tip's traced out
   in the drawing's own units: dragged along the ground, up the back of the
   swing, and then away to the right once the sling lets go. Inside the arm
   group it would have to be animated in a frame that is itself spinning. */
/* ⚠️ A shot that is still climbing when it leaves the sling does not come
   down in forty units. The apex is late and the fall after it is shallow,
   which is what weight thrown hard actually looks like. */
@keyframes tbShotUnused {
  0%   { transform: translate(0, 0);            opacity: 1; }
  12%  { transform: translate(-20px, -2px);     opacity: 1; }
  20%  { transform: translate(-34px, -16px); }
  28%  { transform: translate(-36px, -38px); }
  36%  { transform: translate(-24px, -58px); }
  46%  { transform: translate(28px, -88px); }
  58%  { transform: translate(88px, -106px); }
  70%  { transform: translate(150px, -110px); }
  82%  { transform: translate(208px, -98px);   opacity: 1; }
  92%  { transform: translate(256px, -76px);   opacity: .5; }
  100% { transform: translate(292px, -52px);   opacity: 0; }
}

.t3m--trebuchet.is-throwing .t3tb__arm    { animation: tbArm 1.7s cubic-bezier(.35,0,.3,1) both; }
.t3m--trebuchet.is-throwing .t3tb__weight { animation: tbWeight 1.7s cubic-bezier(.35,0,.3,1) both; }
.t3m--trebuchet.is-throwing .t3tb__sling  { animation: tbSling 1.7s cubic-bezier(.35,0,.3,1) both; }
/* ⚠️ The shot is let go AFTER the rope has carried over the stick, not
   before: the sling peaks at 44% and the stone leaves on 46%. Until then
   it is simply part of the sling and needs no animation of its own. */
.t3m--trebuchet.is-throwing .t3tb__shot   { animation: tbHold 1.7s linear both; }
.t3m--trebuchet.is-throwing .t3tb__flyer  { animation: tbFly 1.7s cubic-bezier(.22,.5,.5,1) both; }

/* Spent: the arm straight up, the weight hanging, nothing in the sling. */
.t3m--trebuchet.is-spent .t3tb__arm    { transform: rotate(145deg); }
.t3m--trebuchet.is-spent .t3tb__weight { transform: rotate(-145deg); }
.t3m--trebuchet.is-spent .t3tb__sling  { transform: rotate(-55deg); }
.t3m--trebuchet.is-spent .t3tb__shot   { opacity: 0; }
.t3m--trebuchet.is-spent .t3tb__flyer  { opacity: 0; }

/* Hauling it back down. */
@keyframes tbHold { 0%, 30% { opacity: 1; } 32%, 100% { opacity: 0; } }
/* ⚠️ These coordinates start where the rope END is at 46%, not where the
   stone was drawn -- the sling has swung it up and to the left of the
   frame by then. Measured in the browser, not reasoned about. */
@keyframes tbFly {
  0%, 30%  { transform: translate(var(--fx), var(--fy)); opacity: 0; }
  32%      { transform: translate(var(--fx), var(--fy)); opacity: 1; }
  36%      { transform: translate(56px, -91px);  opacity: 1; }
  41%      { transform: translate(98px, -97px);  opacity: 1; }
  46%      { transform: translate(130px, -99px); opacity: 1; }
  50%      { transform: translate(157px, -98px); opacity: 1; }
  50.1%    { transform: translate(157px, -98px); opacity: 0; }
  100%     { transform: translate(157px, -98px); opacity: 0; }
}
@keyframes tbReloadArm    { from { transform: rotate(145deg); }  to { transform: rotate(0deg); } }
@keyframes tbReloadWeight { from { transform: rotate(-145deg); } to { transform: rotate(0deg); } }
@keyframes tbReloadShot   { 0%, 70% { opacity: 0; } 100% { opacity: 1; } }
.t3m--trebuchet.is-reloading .t3tb__arm    { animation: tbReloadArm .8s cubic-bezier(.4,0,.35,1) both; }
.t3m--trebuchet.is-reloading .t3tb__weight { animation: tbReloadWeight .8s cubic-bezier(.4,0,.35,1) both; }
@keyframes tbReloadSling { from { transform: rotate(-55deg); } to { transform: rotate(0deg); } }
.t3m--trebuchet.is-reloading .t3tb__sling  { animation: tbReloadSling .8s cubic-bezier(.4,0,.35,1) both; }
.t3m--trebuchet.is-reloading .t3tb__shot   { animation: tbReloadShot .8s linear both; }
.t3m--trebuchet.is-reloading .t3tb__flyer  { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .t3m--trebuchet.is-throwing .t3tb__arm,
  .t3m--trebuchet.is-throwing .t3tb__weight,
  .t3m--trebuchet.is-throwing .t3tb__shot,
  .t3m--trebuchet.is-reloading .t3tb__arm,
  .t3m--trebuchet.is-reloading .t3tb__weight { animation-duration: .01s; }
}

/* Where the shot lands: the spine-side foot of the FRONT board. The rocks are
   placed by js/tome3d.js, which bounces them off the edges and off each
   other, so nothing here says where any of them goes. */
.t3__pile {
  position: absolute;
  right: 5%;
  width: 32%;
  bottom: calc(var(--square) * 1.2);
  height: 26%;
  z-index: 3;
  pointer-events: none;
}
.t3__rock {
  position: absolute;
  left: 0;
  bottom: 0;
  width: calc(var(--bh) * .023);
  height: calc(var(--bh) * .023);
  background: hsl(var(--hue) 10% 26%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07), 0 1px 2px rgba(0,0,0,.5);
  will-change: transform;
  /* the border-radius is set per rock in js, so no two are the same lump */
}
/* ---------------------------------------------------------- VDY, back board */

/* Drawn in the page (includes/vessel.php) so each meteor can move; pressed,
   the sky falls -- js/tome3d.js, wireMeteors. */
.t3m--meteors {
  right: var(--groove-clear);
  top: 8%;
  width: calc(var(--bh) * .185);
  height: calc(var(--bh) * .144);
  padding: 0;
  border: 0;
  background: none;
  pointer-events: auto;
  cursor: pointer;
  transition: opacity var(--t-state) var(--ease-out);
}
.t3m--meteors svg { display: block; width: 100%; height: 100%; overflow: visible; }
.t3mt__head { fill: rgb(214, 238, 232); stroke: none; }
.t3m--meteors:hover, .t3m--meteors:focus-visible { opacity: .27; outline: 0; }
.t3m--meteors.is-falling { cursor: default; }
/* The shower is drawn on a canvas laid over the whole back board for as
   long as it lasts, then faded and removed. */
.t3mt__sky {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 3;
  pointer-events: none;
  transition: opacity .9s ease-out;
}
.t3mt__sky.is-out { opacity: 0; }


/* ---------------------------------------------------------- BGG, back board */

/* Falling from somewhere above the book rather than lying in a frame. */
.t3m--shards {
  left: 13%;
  top: -5%;
  width: calc(var(--bh) * .2);
  height: calc(var(--bh) * .36);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 70 126' fill='none' stroke='rgb(214,238,232)' stroke-width='.95' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M8 2 L16 6 L7 14 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M10.3 0.4 9.1 -10.2'/%3E%3Cpath stroke-width='.5' d='M9.1 5.6 11.5 10.4'/%3E%3Cpath d='M34 4 L39 2 L37 10 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M36.7 0.4 35.5 -8.0'/%3E%3Cpath d='M54 1 L63 8 L57 20 L52 10 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M56.5 -0.6 55.3 -15.1'/%3E%3Cpath stroke-width='.5' d='M55.3 6.7 57.7 14.3'/%3E%3Cpath d='M20 18 L24 16 L22 23 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M22.0 14.4 20.8 6.5'/%3E%3Cpath d='M40 16 L47 21 L38 32 L36 24 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M40.2 14.4 39.0 1.6'/%3E%3Cpath stroke-width='.5' d='M39.0 20.8 41.5 27.2'/%3E%3Cpath d='M62 22 L68 32 L58 33 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M62.7 20.4 61.5 10.3'/%3E%3Cpath stroke-width='.5' d='M61.5 25.3 63.9 29.7'/%3E%3Cpath d='M6 34 L12 33 L8 48 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M8.7 31.4 7.5 19.1'/%3E%3Cpath d='M26 40 L34 38 L37 47 L31 53 L25 48 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M30.6 36.4 29.4 24.1'/%3E%3Cpath stroke-width='.5' d='M29.4 42.5 31.8 48.5'/%3E%3Cpath d='M50 44 L56 47 L49 52 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M51.7 42.4 50.5 34.0'/%3E%3Cpath d='M12 56 L19 59 L14 74 L10 66 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M13.8 54.4 12.6 40.5'/%3E%3Cpath stroke-width='.5' d='M12.6 61.4 14.9 68.6'/%3E%3Cpath d='M40 60 L45 62 L41 67 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M42.0 58.4 40.8 50.5'/%3E%3Cpath d='M56 64 L66 70 L60 84 L53 76 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M58.8 62.4 57.5 47.4'/%3E%3Cpath stroke-width='.5' d='M57.5 70.0 60.0 78.0'/%3E%3Cpath d='M22 78 L28 80 L21 92 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M23.7 76.4 22.5 64.7'/%3E%3Cpath stroke-width='.5' d='M22.5 82.2 24.9 87.8'/%3E%3Cpath d='M42 86 L50 88 L46 99 L39 95 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M44.2 84.4 43.0 73.2'/%3E%3Cpath stroke-width='.5' d='M43.0 89.9 45.5 95.1'/%3E%3Cpath d='M10 92 L16 95 L11 101 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M12.3 90.4 11.1 81.5'/%3E%3Cpath d='M28 104 L38 108 L32 122 L25 114 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M30.8 102.4 29.6 88.5'/%3E%3Cpath stroke-width='.5' d='M29.6 109.4 31.9 116.6'/%3E%3Cpath d='M56 104 L62 107 L57 118 Z'/%3E%3Cpath stroke-width='.45' opacity='.6' d='M58.3 102.4 57.1 90.7'/%3E%3C/svg%3E") top center no-repeat;
}

/* ---------------------------------------------------------- SPR, back board */

/* Walking, in profile. Crossing the board, which is the whole of it. */
.t3m--cat {
  right: var(--groove-clear);
  bottom: 6%;
  width: calc(var(--bh) * .1);
  height: calc(var(--bh) * .104);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 46 48' fill='none' stroke='rgb(214,238,232)' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 15.4c0-4.1 3.3-7.4 7.4-7.4s7.4 3.3 7.4 7.4-3.3 7.4-7.4 7.4-7.4-3.3-7.4-7.4z'/%3E%3Cpath d='M16.6 9.6 14.8 2.2 21.2 6.2'/%3E%3Cpath d='M28.2 9.6 30 2.2 23.6 6.2'/%3E%3Cpath d='M17.4 8.2 16.8 4.6 19.6 6.6' stroke-width='.8'/%3E%3Cpath d='M27.4 8.2 28 4.6 25.2 6.6' stroke-width='.8'/%3E%3Cpath d='M19 14.4v1.6' stroke-width='1.5'/%3E%3Cpath d='M25.8 14.4v1.6' stroke-width='1.5'/%3E%3Cpath d='M21.3 17.6h2.8l-1.4 1.8z' fill='rgb(214,238,232)' stroke-width='.7'/%3E%3Cpath d='M22.7 19.4v1.1'/%3E%3Cpath d='M22.7 20.5c-.9.9-2.2.9-2.9 0'/%3E%3Cpath d='M22.7 20.5c.9.9 2.2.9 2.9 0'/%3E%3Cpath d='M16.8 17.6 11.4 16.2' stroke-width='.65'/%3E%3Cpath d='M16.8 19 11.6 19.6' stroke-width='.65'/%3E%3Cpath d='M28.6 17.6 34 16.2' stroke-width='.65'/%3E%3Cpath d='M28.6 19 33.8 19.6' stroke-width='.65'/%3E%3Cpath d='M16.4 21c-3.1 4.6-5 10.6-5.2 17.6'/%3E%3Cpath d='M28.6 21c3.1 4.6 5 10.6 5.2 17.6'/%3E%3Cpath d='M11.2 38.6c0 1.6 1.3 2.6 3 2.6h17.8c1.7 0 3-1 3-2.6'/%3E%3Cpath d='M16.7 23.4V38.4C16.7 39.5 16.0 40.1 16.0 40.8C16.0 41.2 16.3 41.5 17.1 41.5H20.5C21.2 41.5 21.6 41.2 21.6 40.8C21.6 40.1 20.9 39.5 20.9 38.4V23.4'/%3E%3Cpath d='M18.1 41.5v-1.2' stroke-width='.55'/%3E%3Cpath d='M19.5 41.5v-1.2' stroke-width='.55'/%3E%3Cpath d='M24.3 23.4V38.4C24.3 39.5 23.6 40.1 23.6 40.8C23.6 41.2 23.9 41.5 24.7 41.5H28.1C28.9 41.5 29.2 41.2 29.2 40.8C29.2 40.1 28.5 39.5 28.5 38.4V23.4'/%3E%3Cpath d='M25.7 41.5v-1.2' stroke-width='.55'/%3E%3Cpath d='M27.1 41.5v-1.2' stroke-width='.55'/%3E%3Cpath d='M34.8 39.4c5 .4 7.6-3.6 6.4-7.8-.7-2.6-3.6-2.8-4.1-.3'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* ------------------------------------------------------------------
   Bookmarks

   Not observations. An observation is one mark on one face, found by turning
   the book over; a ribbon is an object somebody left in the book, and it says
   that somebody is reading it. It comes from `series.ribbon`, beside the
   binding rather than beside the marks.

   ⚠️ The plain one sits in the MIDDLE of the block, at z = 0, which is where
   a bookmark physically is -- so it shows from either side of the book rather
   than only from behind. The frayed one has been brought over the tail lip
   and lies on the back board, so it belongs to that face.
   ------------------------------------------------------------------ */

/* LMP -- a plain ribbon at the head, out over near the spine, showing only a
   little more than the thickness of the board. It is folded over nothing. */
.t3rb--plain {
  position: absolute;
  left: 14%;
  top: calc(var(--bh) * -.042);
  width: calc(var(--bh) * .0748);
  height: calc(var(--bh) * .072);
  background: linear-gradient(90deg,
    rgb(92, 78, 54) 0 18%, rgb(126, 108, 76) 18% 82%, rgb(92, 78, 54) 82% 100%);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .5);
}

/* TBC -- one ribbon in two pieces.
 *
 * ⚠️ They must be the same width and the same gradient or they read as two
 * objects. The piece on the head edge covers only the half of the thickness
 * nearest the BACK board and stops there, because that is where it goes down
 * between the pages -- it does not run across the whole lip.
 *
 * ⚠️ And the head face's x runs the other way to the back board's, which is
 * why one is measured with `right` and the other with `left`. Equal widths
 * are what make `right: 28%` and `left: 28%` land on the same line. */
.t3rb--head {
  position: absolute;
  right: 74.6%;
  /* ⚠️ The half of the thickness nearest the BACK board, measured from the
     head face's own top -- the other half leaves a gap between the two
     pieces and they stop reading as one ribbon. */
  top: calc(var(--bh) * -.016);
  bottom: 54.5%;
  width: calc(var(--bh) * .11);
  background:
    linear-gradient(90deg,
      transparent 0 16%, rgb(78, 65, 45) 16% 26%, rgb(148, 128, 92) 28% 35%,
      rgb(126, 108, 76) 35% 74%, rgb(78, 65, 45) 74% 84%, transparent 84% 100%);
  z-index: 3;
}
/* ⚠️ The right angle into the pages. It stands in the block's own plane,
   at z = 0, so it is between the leaves rather than on a board -- the
   same place LMP's plain ribbon lives. Without it the ribbon crossed
   the lip and stopped at nothing. */
.t3rb--into {
  position: absolute;
  right: 74.61%;
  top: 0;
  width: calc(var(--bh) * .0748);
  height: calc(var(--bh) * .085);
  background: linear-gradient(90deg,
    rgb(78, 65, 45) 0 13%, rgb(148, 128, 92) 15% 24%,
    rgb(126, 108, 76) 24% 86%, rgb(78, 65, 45) 86% 100%);
}

.t3rb--frayed {
  position: absolute;
  z-index: 3;
  left: 72.16%;
  top: calc(var(--bh) * -.022);
  width: calc(var(--bh) * .11);
  height: calc(var(--bh) * .475);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 190' stroke='none'%3E%3Crect x='7' y='0' width='30' height='107' fill='rgb(126,108,76)'/%3E%3Crect x='7' y='0' width='4.4' height='107' fill='rgb(78,65,45)'/%3E%3Crect x='32.6' y='0' width='4.4' height='107' fill='rgb(78,65,45)'/%3E%3Crect x='29.6' y='0' width='3' height='107' fill='rgb(148,128,92)' opacity='.45'/%3E%3Crect x='7.0' y='101' width='8.6' height='39' fill='rgb(126,108,76)'/%3E%3Crect x='7.0' y='101' width='4.4' height='39' fill='rgb(78,65,45)'/%3E%3Crect x='17.7' y='101' width='8.6' height='39' fill='rgb(126,108,76)'/%3E%3Crect x='28.4' y='101' width='8.6' height='39' fill='rgb(126,108,76)'/%3E%3Crect x='32.6' y='101' width='4.4' height='39' fill='rgb(78,65,45)'/%3E%3Cpath fill='rgb(78,65,45)' d='M7.0 133 L10.6 133 L10.3 167.0 L8.6 172 L6.8 165.0 Z'/%3E%3Cpath fill='rgb(126,108,76)' d='M12.0 133 L15.6 133 L15.9 181.0 L14.1 186 L12.3 179.0 Z'/%3E%3Cpath fill='rgb(126,108,76)' d='M17.7 133 L21.3 133 L20.9 159.0 L19.1 164 L17.3 157.0 Z'/%3E%3Cpath fill='rgb(126,108,76)' d='M22.7 133 L26.3 133 L26.8 175.0 L25.0 180 L23.2 173.0 Z'/%3E%3Cpath fill='rgb(126,108,76)' d='M28.4 133 L32.0 133 L31.4 163.0 L29.6 168 L27.8 161.0 Z'/%3E%3Cpath fill='rgb(78,65,45)' d='M33.4 133 L37.0 133 L37.6 184.0 L35.8 189 L34.0 182.0 Z'/%3E%3C/svg%3E") top center no-repeat;

  background-size: 100% auto;}
/* Nothing is stamped into the unfinished one, and that includes the empty
   places where a stamp would have gone. A debossed panel with no title in it
   and a blank label on the spine are details, and the whole of this book is
   that it has none: a plain board, the series numeral, and the binding.

   ⚠️ Hints are exempt. A bare board is a fact about the book; a hint is
   something somebody put there, and the sealed book has the most to hide. */
.tome3d--bare .t3m:not(.t3m--hint) { display: none; }

/* The letters are not there yet, but their place is kept, so the rule and the
   numeral under it land where they land on every other book. */
.t3__mark--none { visibility: hidden; }

/* ⚠️ The buckle belongs to the BINDING, not to whatever the back board
   happens to notice. It used to be the back's clasp mark restyled, which
   tied the two together: giving a bound book any other observation on its
   back took the buckle off the strap it is buckling. The binding is a
   fact about the object; an observation is something a reader finds. */
.tome3d--bound .t3--back .t3__strap::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(var(--bh) * .03);
  height: calc(var(--bh) * .03);
  transform: translate(-50%, -50%) rotate(45deg);
  background: linear-gradient(135deg, var(--accent-pale), var(--accent-deep));
  box-shadow: 0 0 12px var(--accent-glow);
}

/* back -- the clasp, which is not locked */
.t3m--clasp {
  top: 50%;
  left: 0;
  right: 0;
  height: 1.4rem;
  transform: translateY(-50%);
  background: linear-gradient(180deg, #1d2d36, #0a1115 60%, #17242c);
  border-block: 1px solid rgba(37,234,181,.28);
  box-shadow: 0 0 16px rgba(37,234,181,.12);
}
.t3m--clasp::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: .55rem;
  height: .55rem;
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--accent-deep);
  box-shadow: 0 0 10px var(--accent-glow);
}

/* ------------------------------------------------------------------
   Held

   Just the book, enlarged, with nothing drawn around it.
   ------------------------------------------------------------------ */

.vessel__phase[data-phase="inspect"] { display: grid; place-items: center; }
.vessel__phase[hidden] { display: none; }

/* Inherited from the vessel, so the object and the spread are the same size. */
.stage .vessel__phase[data-phase="inspect"] .tome3d { --bh: inherit; }

.tome3d.is-refusing .tome3d__body { animation: refuse 360ms var(--ease-both); }
@keyframes refuse {
  0%, 100% { transform: rotateX(var(--rx)) rotateY(var(--ry)); }
  30%  { transform: rotateX(var(--rx)) rotateY(calc(var(--ry) - 6deg)) translateX(-6px); }
  65%  { transform: rotateX(var(--rx)) rotateY(calc(var(--ry) + 6deg)) translateX(6px); }
}

/* Going back to its place on the shelf. */
.tome3d.is-leaving { transition: transform var(--t-pull) var(--ease-in); }

@media (max-width: 860px) {
  .stage .vessel__phase[data-phase="inspect"] .tome3d { --bh: inherit; }
  .t3__open { bottom: 1.5rem; font-size: .55rem; padding: .4rem .9rem; }
  /* Clear of the button it was thrown from. At .4rem the panel sat straight
     on top of it, so the one control on the cover was under the words
     explaining why it had not worked. */
  .t3__excuse { bottom: 4rem; }
}

/* The same spine on the object, scaled to its thickness rather than the flat
   one's width, so a volume reads identically on the shelf and in the hand. */
.t3--spine-vol { justify-content: space-between; }
.t3__vol { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; }
.t3--spine-vol .spine-vol { font-size: calc(var(--bd) * .22); }
.t3--spine-vol .spine-no  { font-size: calc(var(--bd) * .62); }

/* The angle the front board is swung to. Registered so it can be animated as
   a number rather than snapping between two keywords. */
@property --cover {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

/* While the cover is swinging nothing may be clicked, and the book is wide
   enough to hold both leaves. */
.tome3d.is-opening, .tome3d.is-closing { pointer-events: none; }
.tome3d.is-opening .t3__recto, .tome3d.is-closing .t3__recto { z-index: 2; }

/* ------------------------------------------------------------------
   Bound shut

   A book that will not open is tied. The binding runs the whole way round
   — over the front board, across the fore-edge, over the back and down the
   spine — so it reads from the shelf as well as in the hand, and gives the
   reader something to look at when they ask why it will not open.
   ------------------------------------------------------------------ */

.t3__strap {
  position: absolute;
  z-index: 3;
  pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(37, 234, 181, .06) 0 12%,
      rgba(10, 17, 21, .96)  12% 50%,
      rgba(6, 11, 14, .98)   50% 88%,
      rgba(37, 234, 181, .06) 88% 100%);
  box-shadow:
    inset 0  1px 0 rgba(37, 234, 181, .45),
    inset 0 -1px 0 rgba(37, 234, 181, .32),
    0 0 14px rgba(37, 234, 181, .14);
}

/* Over the boards, and down the spine: a band across the middle. */
.t3--front .t3__strap,
.t3--back  .t3__strap,
.t3--spine .t3__strap {
  top: 50%;
  left: 0;
  right: 0;
  height: calc(var(--bh) * .052);
  transform: translateY(-50%);
}

/* Across the fore-edge, where it is pulled tight over the lip. It runs from
   the edge of the front board -- which belongs to the cover, and carries its
   own piece of the band -- to the outer face of the back board, and stops at
   both. It used to overhang a board's thickness at each end, which put it past
   the corner of the book on the side where the pages are. */
.t3--fore .t3__strap {
  top: 50%;
  left: 0;
  right: calc(-1 * var(--board));
  height: calc(var(--bh) * .052);
  transform: translateY(-50%) translateZ(1px);
  box-shadow:
    inset 0  1px 0 rgba(37, 234, 181, .5),
    inset 0 -1px 0 rgba(37, 234, 181, .36),
    0 0 18px rgba(37, 234, 181, .22);
}

/* The seal where the binding is drawn together, on the front board. */
.t3--front .t3__strap::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(var(--bh) * .034);
  height: calc(var(--bh) * .034);
  transform: translate(-50%, -50%) rotate(45deg);
  background: linear-gradient(135deg, var(--accent), var(--accent-deep));
  box-shadow: 0 0 14px var(--accent-glow), inset 0 0 6px rgba(4, 7, 10, .5);
}

/* The bands of a bound spine stop where the binding crosses them. */
.tome3d--bound .t3__bands { opacity: .55; }

/* The edges of the leaves still to be turned, at the outer edge of each page.
   The spread shows these, so the book must show them too, or they appear out
   of nowhere the moment one becomes the other. */
.t3__recto .leaf::after,
.t3__verso .leaf::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 3;
  pointer-events: none;
  background: repeating-linear-gradient(90deg,
    var(--paper-0) 0 1px,
    var(--paper-2) 1px 2px,
    var(--paper-1) 2px 3px);
}
/* Shut, every leaf is still ahead of the reader, so they are all on the right. */
.t3__recto .leaf::after {
  right: 0;
  width: min(calc(var(--ahead, 3) * var(--leaf-thick)), var(--stack));
  box-shadow: inset 3px 0 6px rgba(27,23,16,.22), -2px 0 5px rgba(27,23,16,.12);
  border-left: 1px solid rgba(27,23,16,.14);
}
.t3__verso .leaf::after { left: 0; width: 0; }

/* ------------------------------------------------------------------
   LMP's lettering, over something struck out

   ⚠️ This is NOT an observation. An observation is one mark on one face,
   found by turning the book over. This belongs to the LETTERS, so it shows
   everywhere they do -- the shelf, the spine of the object, the board --
   and a reader who never picks the book up still sees it.

   The scribble is a single stroke going back and forth with the turns
   overshooting unevenly, which is what somebody blacking out a line with a
   marker actually leaves: ragged at the edges, denser in the middle, and
   plainly bigger than what it was covering. The letters sit on top of it.
   ------------------------------------------------------------------ */
.tome[data-series="LMP"] .tome__code,
[data-code="LMP"] .t3__code,
[data-code="LMP"] .t3__mark {
  position: relative;
  isolation: isolate;
}
.tome[data-series="LMP"] .tome__code::before,
[data-code="LMP"] .t3__code::before,
[data-code="LMP"] .t3__mark::before {
  content: '';
  position: absolute;
  z-index: -1;
  pointer-events: none;
  /* Nobody rules a line out square to the page. */
  transform: rotate(-1.4deg);
  background: center / 100% 100% no-repeat;
}

/* Stacked letters: the marker runs down the spine. */
.tome[data-series="LMP"] .tome__code::before,
[data-code="LMP"] .t3__code::before {
  /* ⚠️ Close to the letters. It is covering a word, not painting a
     panel -- a hand works to the edge of what it is hiding. */
  inset: -.16em -.28em;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 120' preserveAspectRatio='none' fill='none' stroke='rgb(5,8,10)' stroke-width='11.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 6 L14 112 L19 115 L15 3 L19 9 L24 108 L29 113 L25 5 L29 4 L35 110'/%3E%3C/svg%3E");
}

/* A line of letters across the board: the marker runs across. */
[data-code="LMP"] .t3__mark::before {
  inset: -.13em -.2em;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 44' preserveAspectRatio='none' fill='none' stroke='rgb(5,8,10)' stroke-width='11' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 10 L114 7 L117 17 L2 15 L8 24 L112 22 L116 30 L4 28 L3 36 L110 34'/%3E%3C/svg%3E");
}

/* ------------------------------------------------------------------
   Five more hints -- 2026-10-27, second pass

   VDY's pressure gauge, SPR's coin, BGG's garden of masks, RNG's cassette
   and player, and TBC's railgun on its hull. The coin, the cassette and the
   railgun are toys, like the hourglass and the trebuchet: buttons, let
   through the drag, stronger than a plain hint.
   ------------------------------------------------------------------ */
.t3m--coin, .t3m--railgun, .t3m--cassette { opacity: .26; }
.t3m--coin, .t3m--railgun, .t3m--cassette {
  pointer-events: auto;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  transition: opacity var(--t-state) var(--ease-out);
}
.t3m--coin:hover, .t3m--railgun:hover, .t3m--cassette:hover,
.t3m--coin:focus-visible, .t3m--railgun:focus-visible, .t3m--cassette:focus-visible { opacity: .42; outline: 0; }
.t3m--dial svg, .t3m--coin svg, .t3m--railgun svg, .t3m--masks svg, .t3m--cassette svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: none;
  stroke: rgb(214, 238, 232);
  stroke-linecap: round;
  stroke-linejoin: round;
  overflow: visible;
}

/* VDY, back board, the corner on the fore side: a gauge standing off a gas
   line that runs edge to edge across the corner, the gauge angled with it.
   ⚠️ The pipe is drawn long on purpose -- the board clips it at both edges,
   which is what makes it read as one pipe that carries on. */
.t3m--dial {
  left: 0;
  bottom: 0;
  width: calc(var(--bh) * .3);
  height: calc(var(--bh) * .3);
}
.t3m--dial svg { stroke-width: 1.1; }
.t3gg__pipe { stroke-width: 1.3; }
.t3gg__case, .t3gg__fit, .t3gg__nut { fill: rgba(214, 238, 232, .12); }
.t3gg__red { stroke-width: 2; }
.t3dl__hub { fill: rgb(214, 238, 232); stroke: none; }
/* ⚠️ The gauge sits inside a rotated group, so its needle turns about its
   own hub measured off its own box -- a view-box origin would be in the
   wrong frame. The hub is 78.7% of the way down the needle's box. */
.t3dl__needle {
  transform-box: fill-box;
  transform-origin: 50% 78.7%;
  animation: dlWaver 5.3s ease-in-out infinite;
}
@keyframes dlWaver {
  0%   { transform: rotate(-24deg); }
  14%  { transform: rotate(-17deg); }
  27%  { transform: rotate(-27deg); }
  41%  { transform: rotate(-15deg); }
  55%  { transform: rotate(-22deg); }
  68%  { transform: rotate(-13deg); }
  83%  { transform: rotate(-23deg); }
  100% { transform: rotate(-24deg); }
}

/* SPR, back board, foot, fore side -- the cat has the spine side. */
.t3m--coin {
  /* High up and centred (his call, 2026-10-28), to give the couch the foot. */
  left: calc(50% - var(--bh) * .035);
  top: 9%;
  width: calc(var(--bh) * .07);
  height: calc(var(--bh) * .07);
}
.t3m--coin svg { stroke-width: 1.1; }
.t3cn__heads > circle:first-child, .t3cn__tails > circle:first-child { fill: rgba(214, 238, 232, .14); }
.t3cn__edge rect { fill: rgba(214, 238, 232, .3); }
.t3cn__shadow { fill: rgba(0, 0, 0, .35); stroke: none; }
.t3m--coin .t3cn__tails, .t3m--coin .t3cn__edge { display: none; }
.t3m--coin[data-side="tails"] .t3cn__heads { display: none; }
.t3m--coin[data-side="tails"] .t3cn__tails { display: inline; }
.t3m--coin[data-side="edge"] .t3cn__coin { display: none; }
.t3m--coin[data-side="edge"] .t3cn__edge { display: inline; }
/* while it is in the air, js/tome3d.js shows whichever face is up */
.t3m--coin.is-tossing .t3cn__heads, .t3m--coin.is-tossing .t3cn__tails { display: none; }
.t3m--coin.is-tossing[data-up="heads"] .t3cn__heads,
.t3m--coin.is-tossing[data-up="tails"] .t3cn__tails { display: inline; }
.t3m--coin.is-tossing .t3cn__edge { display: none; }
.t3m--coin.is-tossing[data-up="edge"] .t3cn__coin { display: none; }
.t3m--coin.is-tossing[data-up="edge"] .t3cn__edge { display: inline; }
.t3--back:has(.t3m--coin.is-tossing) { overflow: visible; }

/* BGG, back board, the whole foot: the Garden of Masquerades, grown wild,
   with the glass still falling above it. Pressed, the wind comes: a run of
   gusts, each arriving before the last has finished, lines tearing across
   the whole cover and the bed bending under them (js/tome3d.js). */
.t3m--masks {
  left: 3%;
  right: var(--groove-clear);
  bottom: 2.5%;
  height: calc(var(--bh) * .27);
  opacity: .22;
  pointer-events: auto;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  transition: opacity var(--t-state) var(--ease-out);
}
.t3m--masks:hover, .t3m--masks:focus-visible { opacity: .3; outline: 0; }
.t3m--masks.is-blowing { opacity: .34; }
.t3m--masks svg { stroke-width: 1; }
.t3mk__face { fill: rgba(240, 248, 245, .82); stroke-width: .5; }
.t3mk__hole, .t3mk__grin { fill: rgb(14, 18, 20); stroke-width: .4; }
.t3mk__stem { stroke-width: .7; }
.t3mk__weed { stroke-width: .5; }
.t3mk__leaf { stroke-width: .45; }
.t3mk__seed { stroke-width: .4; }
.t3mk__plant--d0 { opacity: .6; }
.t3mk__plant--d1 { opacity: .8; }
.t3mk__plant { transform-box: view-box; }
.t3mk__gust { stroke-linecap: round; fill: none; }

/* RNG, back board, above the eyes: a cassette on the fore side. The player
   waits off the board past the spine-side edge until it is called, and
   throws its picture toward the spine, fading out inside the cover -- so
   everything stays on the book, and nothing shows what is playing. */
.t3m--cassette {
  left: 2%;
  right: 0;
  top: 34%;
  height: calc(var(--bh) * .14);
}
.t3m--cassette svg { stroke-width: 1; }
.t3cs__body, .t3cs__lens { fill: rgba(214, 238, 232, .14); }
.t3cs__beam { stroke: none; opacity: 0; transition: opacity .35s ease-out; }
.t3cs__reel { transform-box: view-box; }
/* Off: the tape comes out first, then the player leaves. On: the player
   arrives first, then the tape goes in. */
.t3cs__player { transform: translateX(150px); transition: transform .65s cubic-bezier(.3,.7,.3,1) .45s; }
.t3cs__tape   { transition: transform .55s cubic-bezier(.4,0,.3,1) 0s; }
.t3m--cassette.is-on .t3cs__player { transform: none; transition-delay: 0s; }
.t3m--cassette.is-on .t3cs__tape   { transform: translate(63px, -0.8px); transition-delay: .6s; }
.t3m--cassette.is-playing .t3cs__reel { animation: csSpin 1.4s linear infinite; }
.t3m--cassette.is-playing .t3cs__beam { opacity: 1; animation: csFlicker 2.6s steps(1) infinite .35s; }
@keyframes csSpin { to { transform: rotate(360deg); } }
@keyframes csFlicker {
  0% { opacity: 1; } 31% { opacity: .82; } 33% { opacity: 1; } 58% { opacity: .9; }
  61% { opacity: .7; } 62% { opacity: 1; } 86% { opacity: .88; } 88% { opacity: 1; }
}

/* TBC, back board: where DWK's trebuchet stands, facing the same way, on the
   deck of a ship whose hull runs off the bottom of the board. */
.t3m--railgun {
  left: 5%;
  bottom: 0;
  width: calc(var(--bh) * .21);
  height: calc(var(--bh) * .168);
}
.t3m--railgun svg { stroke-width: 1.3; }
.t3rg__plate, .t3rg__cells, .t3rg__housing, .t3rg__breech { fill: rgba(214, 238, 232, .16); }
.t3rg__slug, .t3rg__flyer rect { fill: rgb(214, 238, 232); stroke: none; }
.t3rg__streak { stroke-width: 1.4; opacity: .55; }
.t3rg__flyer, .t3rg__flash { opacity: 0; }
.t3rg__coil, .t3rg__barrel, .t3rg__slug { transform-box: view-box; }

/* ⚠️ The slug is NOT let past the board's edge: the face clips it there,
   which is the point -- it ends at the edge of the book and turns up on the
   front. So, unlike the trebuchet, the back board keeps clipping.
   The slug sits at the breech and is seen running the barrel, the coils
   lighting as it passes each one, then it is the flyer from the muzzle on. */
.t3m--railgun.is-firing .t3rg__coil { animation: rgCoil .62s linear both; }
.t3m--railgun.is-firing .t3rg__coil:nth-child(2) { animation-delay: .025s; }
.t3m--railgun.is-firing .t3rg__coil:nth-child(3) { animation-delay: .05s; }
.t3m--railgun.is-firing .t3rg__coil:nth-child(4) { animation-delay: .075s; }
.t3m--railgun.is-firing .t3rg__coil:nth-child(5) { animation-delay: .1s; }
.t3m--railgun.is-firing .t3rg__slug   { animation: rgSlug .62s both; }
.t3m--railgun.is-firing .t3rg__flyer  { animation: rgFly .62s linear both; }
.t3m--railgun.is-firing .t3rg__flash  { animation: rgFlash .62s linear both; }
.t3m--railgun.is-firing .t3rg__barrel { animation: rgRecoil .62s cubic-bezier(.2,.7,.3,1) both; }
@keyframes rgCoil   { 0%, 12% { fill: rgba(214,238,232,0); } 20% { fill: rgba(214,238,232,.95); } 44%, 100% { fill: rgba(214,238,232,0); } }
@keyframes rgSlug   { 0%, 14% { opacity: 1; transform: translateX(0); animation-timing-function: cubic-bezier(.55,0,.9,.6); }
                      31% { opacity: 1; transform: translateX(34px); } 31.5%, 86% { opacity: 0; transform: translateX(0); }
                      100% { opacity: 1; transform: translateX(0); } }
@keyframes rgFly    { 0%, 30.5% { opacity: 0; transform: translateX(0); } 31% { opacity: 1; transform: translateX(0); }
                      44% { opacity: 1; transform: translateX(170px); } 45%, 100% { opacity: 0; transform: translateX(170px); } }
@keyframes rgFlash  { 0%, 30% { opacity: 0; } 32% { opacity: 1; } 42%, 100% { opacity: 0; } }
@keyframes rgRecoil { 0%, 30% { transform: translateX(0); } 36% { transform: translateX(-1.8px); } 100% { transform: translateX(0); } }

/* Where the slugs land: the foot of TBC's FRONT board, on the spine side,
   clear of the groove. Same size as the rock pile, so the same sim fits. */
.t3__pile--rg { right: auto; left: var(--groove-clear); }
.t3__slug {
  position: absolute;
  left: 0;
  bottom: 0;
  width: calc(var(--bh) * .021);
  height: calc(var(--bh) * .021);
  border-radius: 2px;
  background: linear-gradient(135deg, hsl(180 8% 62%), hsl(180 6% 34%));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 1px 2px rgba(0,0,0,.5);
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .t3dl__needle { animation: none; transform: rotate(-20deg); }
  .t3m--railgun.is-firing *, .t3m--cassette * { animation-duration: .01s !important; transition-duration: .01s !important; }
}

/* ------------------------------------------------------------------
   Four more series hints -- 2026-10-28
   ------------------------------------------------------------------ */

/* Toys share this: a mark that is a button, faint until hovered. */
.t3m--mop, .t3m--couch, .t3m--neuron {
  padding: 0;
  border: 0;
  background: none;
  pointer-events: auto;
  cursor: pointer;
  transition: opacity var(--t-state) var(--ease-out);
}
.t3m--mop:hover, .t3m--couch:hover, .t3m--neuron:hover,
.t3m--mop:focus-visible, .t3m--couch:focus-visible, .t3m--neuron:focus-visible { opacity: .28; outline: 0; }
.t3m--mop svg, .t3m--couch svg, .t3m--neuron svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* LMP, back board, the foot corner: a mop standing in its bucket. */
.t3m--mop {
  left: 0;
  bottom: 0;
  width: calc(var(--bh) * .2);
  height: calc(var(--bh) * .64);
}
.t3m--mop.is-mopping { opacity: .3; }
/* The pail is filled with the board, so the mop's head is hidden inside it. */
.t3mp__pail { fill: hsl(var(--hue) 14% 13%); }
.t3mp__mop {
  transform-box: view-box;
  transform-origin: 27px 151px;
}
/* SPR, back board, the foot: the couch. Somebody arrives on it. */
.t3m--couch {
  left: 7%;
  bottom: 3.5%;
  width: calc(var(--bh) * .26);
  height: calc(var(--bh) * .17);
}
.t3m--couch.is-sitting { opacity: .5; }
.t3sp__couch, .t3sp__seat { transform-box: fill-box; transform-origin: 50% 100%; }
.t3sp__who { fill: rgb(214, 238, 232); stroke: none; color: rgb(214, 238, 232); opacity: 0; transform-box: fill-box; transform-origin: 50% 100%; }
.t3sp__puff { fill: rgb(214, 238, 232); stroke: none; transform-box: fill-box; transform-origin: 50% 50%; }

/* RNG, back board, the head on the fore side: one nerve cell. */
.t3m--neuron {
  left: 8%;
  top: 8%;
  width: calc(var(--bh) * .085);
  height: calc(var(--bh) * .085);
}
.t3m--neuron svg { transform-origin: 50% 45%; }
.t3nr__soma { fill: rgb(214, 238, 232); stroke: none; }
.t3m--neuron.is-firing { cursor: default; }
.t3nr__net { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 3; pointer-events: none; transition: opacity .7s ease-out; }
.t3nr__net.is-out { opacity: 0; }

/* TBC, back board, above the railgun: stars to move. The field passes
   presses through; only the stars take them. */
.t3m--stars {
  top: 6%;
  left: 7%;
  right: var(--groove-clear);
  bottom: 34%;
  opacity: .34;
}
.t3st__lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.t3st__ln { stroke: rgb(214, 238, 232); stroke-width: 1; vector-effect: non-scaling-stroke; opacity: .8; }
.t3st__ln.is-new { animation: stJoin .6s ease-out; }
@keyframes stJoin { from { opacity: 0; } 30% { opacity: 1; stroke-width: 2.5; } }
.t3st__s {
  position: absolute;
  width: calc(var(--bh) * .05);
  height: calc(var(--bh) * .05);
  margin: calc(var(--bh) * -.025) 0 0 calc(var(--bh) * -.025);
  padding: calc(var(--bh) * .014);
  border: 0;
  background: none;
  pointer-events: auto;
  cursor: grab;
  touch-action: none;
  animation: stTwinkle 4.2s ease-in-out infinite;
  animation-delay: calc(var(--s) * -3s);
}
.t3st__s svg { display: block; width: 100%; height: 100%; transform: scale(var(--s)); fill: rgb(214, 238, 232); transition: transform .2s ease-out; }
.t3st__s:hover svg, .t3st__s.is-near svg { transform: scale(calc(var(--s) * 1.35)); }
.t3st__s.is-held { cursor: grabbing; animation: none; z-index: 2; }
.t3st__s.is-held svg { transform: scale(calc(var(--s) * 1.5)); }
.t3st__s.is-joined svg { animation: stFlare .7s ease-out; }
@keyframes stTwinkle { 0%, 100% { opacity: .75; } 50% { opacity: 1; } }
@keyframes stFlare { 30% { filter: drop-shadow(0 0 4px rgb(214, 238, 232)); transform: scale(calc(var(--s) * 1.9)); } }
.t3m--stars:has(.t3st__s.is-held) { opacity: .6; }
@media (prefers-reduced-motion: reduce) { .t3st__s { animation: none; } }

/* LMP: the whole board wet after the mop, drying unevenly (js/tome3d.js). */
.t3mp__wet { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
.t3sp__arm { fill: none; stroke: rgb(214, 238, 232); stroke-width: 3.2; stroke-linecap: round; }

/* ------------------------------------------------------------------
   DWK, LMP and BGG -- 2026-10-28, second round
   ------------------------------------------------------------------ */
.t3m--spintop, .t3m--helmet, .t3m--notebook {
  padding: 0;
  border: 0;
  background: none;
  pointer-events: auto;
  cursor: pointer;
  transition: opacity var(--t-state) var(--ease-out);
}
.t3m--spintop:hover, .t3m--helmet:hover, .t3m--notebook:hover,
.t3m--spintop:focus-visible, .t3m--helmet:focus-visible, .t3m--notebook:focus-visible { opacity: .3; outline: 0; }
.t3m--spintop svg, .t3m--gears svg, .t3m--helmet svg, .t3m--notebook svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* DWK, the foot on the spine side: a top that has stopped. */
.t3m--spintop { right: calc(var(--groove-clear) + 4%); bottom: 6%; width: calc(var(--bh) * .075); height: calc(var(--bh) * .075); }
.t3tp__top { transform-box: view-box; transform-origin: 20px 37px; transform: rotate(74deg) translate(0px, -3px); }
.t3tp__body { fill: hsl(var(--hue) 14% 13%); }
.t3m--spintop.is-spinning { opacity: .34; }

/* DWK, the head on the spine side: the gear train, always turning. */
.t3m--gears {
  /* The whole board: the train runs round it to the trebuchet. Only the
     wheels take a press; everything else lets it through to the book.
     Under the other marks, so it passes behind the trebuchet. */
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
.t3gr__g { transform-box: fill-box; transform-origin: 50% 50%; animation: grSpin var(--d) linear infinite; animation-direction: var(--dir); }
.t3gr__axle { fill: rgb(214, 238, 232); stroke: none; }
@keyframes grSpin { to { transform: rotate(360deg); } }
.t3m--gears.is-jammed .t3gr__g { animation-play-state: paused; }
.t3m--gears.is-jammed svg { animation: grJam .5s steps(2) 2; }
@keyframes grJam { 0% { transform: translate(.6px, 0); } 50% { transform: translate(-.6px, .4px); } 100% { transform: none; } }
@media (prefers-reduced-motion: reduce) { .t3gr__g { animation: none; } }

/* DWK, the head on the fore side: a helm with a slot in it. */
.t3m--helmet {
  left: 30%;
  top: 19%;
  width: calc(var(--bh) * .1);
  height: calc(var(--bh) * .125);
}
.t3hm__helm { transform-box: view-box; transform-origin: 20px 27px; }
.t3hm__plate { fill: hsl(var(--hue) 14% 13%); }
.t3hm__hole { fill: rgb(5, 8, 10); }
.t3hm__rivet { fill: rgb(214, 238, 232); stroke: none; }
.t3hm__slot { fill: rgb(6, 9, 11); stroke: none; }
.t3hm__ear { fill: rgb(214, 238, 232); stroke: none; transform: translate(-6px, 3px); }
/* A star let go behind the bookmark or strap drifts out (js/tome3d.js). */
.t3st__s.is-drifting { transition: left .45s cubic-bezier(.2,.8,.3,1), top .45s cubic-bezier(.2,.8,.3,1); }

/* DWK's train: shafts that visibly turn, and wheels to press. */
.t3gr__shaft { stroke-width: 1; opacity: .7; }
.t3gr__spin { stroke-dasharray: .9 1.4; animation: grShaft 1.1s linear infinite; }
@keyframes grShaft { to { stroke-dashoffset: -2.3; } }
.t3gr__brg { fill: hsl(var(--hue) 14% 13%); }
.t3gr__hit { fill: transparent; stroke: none; pointer-events: all; cursor: pointer; }
.t3m--gears.is-jammed .t3gr__spin { animation-play-state: paused; }
.t3m--gears:has(.t3gr__hit:hover) { opacity: .26; }

/* BGG: the flower grown into the garden, facing away, half smothered.

   ⭐ The pink reads as pink now (his call, 2026-10-28). At .4 over a board
   this dark the petals came out grey-mauve, and the masks beside it are
   near-white -- so the one coloured thing in the bed was the thing you did
   not see. Still half smothered, because the stems in front of it do that
   and they have not moved; it is the colour that was lost, not the hiding. */
.t3mk__bloom { fill: rgba(247, 168, 205, .72); stroke: rgb(255, 193, 221); stroke-width: .6; }
.t3mk__sepal { fill: rgba(214, 238, 232, .5); stroke: rgb(214, 238, 232); stroke-width: .35; }

/* VDY, up of centre on the fore side: the journalist's notebook. */
.t3m--notebook { left: 12%; top: 30%; width: calc(var(--bh) * .16); height: calc(var(--bh) * .11); }
.t3nb__book { transform-box: view-box; transform-origin: 22px 15px; }
.t3nb__page { fill: hsl(var(--hue) 10% 19%); }
.t3nb__cover { fill: hsl(var(--hue) 18% 9%); }
.t3nb__coil { stroke-width: .5; fill: hsl(var(--hue) 14% 13%); }
.t3nb__ink { stroke-width: .42; }
.t3m--notebook.is-burning { opacity: .5; cursor: default; }
.t3nb__fire { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 3; pointer-events: none; }
