/* Dice on the sketchbook's mat.
   ---------------------------------------------------------------------------
   A die here is a real cube: six faces on a preserve-3d box, and showing a
   number means turning the box until that face points at the viewer. At rest
   that face is square-on — one face, no lean to show the sides — and a second
   cube laid down on the paper as a drop shadow is what gives it volume. The
   obvious cheap alternative — one square that swaps its pips — cannot tumble,
   and a tumble is the whole ask. Six absolutely positioned divs and a rotation
   is not expensive: everything that moves here is transform and opacity, which
   the compositor does without touching layout or paint, and three dice cost
   the same as one.

   Colour comes from the page's own tokens rather than from white and black.
   --back-stock is the card back's stock and --ink is its writing, so a die is
   made of the same material as the cards it is lying among and takes both
   themes for free. On the light table that is a near-white die with dark pips,
   which is the die anyone pictures; on the dark one it inverts along with the
   card backs, which is right — a white block on a near-black felt would be the
   brightest thing on the page, and a die is not the point of it.

   The one exception is the shadow. It is black on the felt, and the felt is so
   different in the two palettes that a single rgba would vanish on one of them,
   so those faces are the only rules here that name a theme. */

.die {
  position: absolute;
  display: block;
  /* dice.js writes --die-w onto each die. The size cannot live here: the same
     number decides how far each face is pushed out from the centre of the cube
     and how much room a die needs on the mat, and both of those are worked out
     in script. One number, set where it is used for arithmetic, read here. */
  width: var(--die-w, 46px);
  height: var(--die-w, 46px);
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  /* A button's own font would otherwise leak into the faces' grid metrics. */
  font: inherit;
  color: inherit;
  overflow: visible;
  -webkit-user-select: none;
  user-select: none;
  /* Above every card on the table, including one being dragged. #played
     takes no z-index of its own and no transform, so it is not a stacking
     context: its cards stack against this one directly. sketchbook.js
     counts those cards up from two. The riffle climbs to 50000 so the
     pack passes over this band; idle cards stay under it. --die-z
     lives on #surface, the stacking context they all share, and chips use
     the same band. The shadow is a child of this element, so it travels
     with the die and cannot slip under a card the die itself is sitting
     on. */
  z-index: var(--die-z, 40000);
  /* The cards use this so a drag on a touch screen does not scroll the page
     out from under them. A die is the same kind of press. */
  touch-action: none;
  /* The camera. Shallow enough that a face turning away foreshortens visibly —
     without it the cube is six squares sliding over each other and reads as
     flat — and not so shallow that the near corner distorts on the way past.
     It is on the button rather than on the cube because a perspective on the
     same element as the rotation is applied after it, which is not a camera. */
  perspective: 520px;
}

/* No hand here. Over a die the page's own dot is the cursor, exactly as it is
   over a card: a die is a thing you handle, and the grab hand belongs to the
   mat underneath. Scoped to body.has-dot for the same reason the card rule is —
   a coarse pointer has no dot to fall back to. */
body.has-dot .die,
body.has-dot .die-face,
body.has-dot .die-cast-face,
body.has-dot .die-pip { cursor: none; }

.die:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* Above its neighbours while it is the one in the hand. The cube-shadow is
   a child, so it comes with it. */
.die.is-dragging { z-index: calc(var(--die-z, 40000) + 2); }

/* The cube. Its transform is written entirely by dice.js, one string, so that a
   resting pose and a frame of a tumble are built by the same code and can never
   disagree about the order the rotations compose in. */
.die-cube {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}

.die-face {
  position: absolute;
  inset: 0;
  border-radius: 22%;
  /* The faces pointing away are not drawn and do not answer the pointer, which
     is what makes asking the document what is under the middle of a die a
     truthful question: the answer is the face you can actually see. */
  backface-visibility: hidden;
  /* One stock. A baked wash per side is what made some faces darker at rest;
     that table is gone. On a mouse the lamp is the cursor: dice.js writes
     --lx/--ly/--lit/--shade so a highlight sits on the side looking at the
     pointer. A finger is not a lamp, so those stay 0 and every face is the
     same colour. */
  background-color: var(--back-stock);
  --shade: 0;
  --lit: 0;
  --lx: 50%;
  --ly: 38%;
  --sx: 50%;
  --sy: 62%;
  background-image:
    radial-gradient(circle 58% at var(--lx) var(--ly),
      rgba(255, 255, 255, var(--lit)),
      transparent 70%),
    radial-gradient(circle 78% at var(--sx) var(--sy),
      rgba(0, 0, 0, var(--shade)),
      transparent 72%);
  box-shadow: inset 0 0 0 1px var(--stock-edge);
  /* Nine cells; the pips name the ones they sit in. Padding rather than a
     margin on the pips so a face reads the same at any size. */
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  padding: 15%;
  gap: 5%;
}

/* The die's solid body — see paintCore in dice.js. The same rounded square as
   a face, never lit by the lamp and a shade under the stock, so the little
   of it that shows at a turned corner reads as the rounding going into
   shadow rather than as a flat patch. Seen from both sides, unlike a face:
   through a corner you are as likely to be looking at its back. */
.die-core {
  position: absolute;
  /* The face's own outline, half a pixel in, so no slice's edge touches a
     face's edge where the two meet at the cube's rim. */
  inset: 0.5px;
  border-radius: 22%;
  background-color: var(--back-stock);
  /* Black and not --stock-edge: on the dark table that edge is a light line,
     and a corner in shade is darker on either stock. */
  background-color: color-mix(in srgb, var(--back-stock) 93%, #000);
  pointer-events: none;
}

/* The slices just behind each face — see CORE_DEPTHS in dice.js for why a
   die square to the camera, at rest or at either end of a throw, leaves
   them out. */
.die:not(.is-airborne) .die-core[data-rim],
.die.is-square .die-core[data-rim] { display: none; }

/* The highlight is a mouse lamp. A phone has none, so this drops the wash
   there — and only there — so a stray inline --lit cannot paint one. */
html.no-mat-lamp .die-face {
  background-image: none;
}

@media (hover: none), (pointer: coarse) {
  .die-face {
    background-image: none;
  }
}

.die-pip {
  align-self: center;
  justify-self: center;
  width: 76%;
  height: 76%;
  border-radius: 50%;
  background: var(--ink);
}

/* What the die casts on the paper. A second cube, the same size, laid down
   on the table rather than an oval smudge: that is what sells a square-on
   die as a thing with volume, without showing a second face of the die
   itself. dice.js writes its transform — the lie-down and the die's small
   roll in the picture plane — and the lamp slides it through --cast-dx/dy.
   It is the resting shadow only; in the air it hands over to .die-air below.

   Softness is on the faces, not on this cube. A filter here would flatten
   the preserve-3d tree into a bitmap and the shadow would stop being a cube;
   box-shadow on each face feathers that square without touching the tree. */
.die-cast {
  position: absolute;
  inset: 0;
  transform: translate3d(var(--cast-dx, 12px), var(--cast-dy, 17px), 0);
  transform-style: preserve-3d;
  pointer-events: none;
  /* The lamp only slides the contact a few pixels; a short ease keeps it
     from ticking. The cube inside still turns on its own, with no ease. */
  transition: transform 0.12s ease-out;
}

.die-cast-cube {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}

.die-cast-face {
  position: absolute;
  inset: 0;
  /* Less than the die's own 22%: at this angle six independently-rounded
     squares do not meet as a rounded cube, they meet as a crease. A tighter
     corner plus the glow is what a contact shadow's edge looks like. */
  border-radius: 10%;
  backface-visibility: hidden;
  /* One tone. The cube-on-the-table reading comes from the lie-down, not from
     painting some sides darker than others. Shared with the chips. */
  background: var(--cast, rgba(0, 0, 0, 0.55));
  box-shadow: var(--cast-glow, 0 0 7px 2px rgba(0, 0, 0, 0.34));
  pointer-events: none;
}

/* The shadow while the die is in the air. A laid-down cube turning with a
   tumble shows a flat dark face whenever one comes square to the camera, so
   for the length of a throw that cube steps aside and dice.js draws the
   outline the die actually casts: a clip-path polygon, recomputed from the
   cube's pose each frame, starting and ending on the resting cube's own
   silhouette. Clip on the inner shape, blur and fade on the outer box — the
   other way round the clip would cut the blur off square. Only these two
   elements repaint while a die rolls, and only for the throw; at rest they
   are hidden and cost nothing. dice.js sets the box's inset, the room it
   leaves round the die for the throw and the blur. */
.die-air {
  position: absolute;
  pointer-events: none;
  visibility: hidden;
}

.die-air-shape {
  position: absolute;
  inset: 0;
  background: var(--cast, rgba(0, 0, 0, 0.55));
}

.die.is-airborne .die-air { visibility: visible; }
.die.is-airborne .die-cast { visibility: hidden; }
