/* ── Spindle ──────────────────────────────────────────────────────────────────
   Hex nuts on threaded screws. A spindle is three layers stacked back-to-front,
   and the ORDER is the whole illusion: foot plate, the thread behind, the thread
   in front, then the nuts. See sandbox-spindle.html for the full dossier.

   Nothing here is a flex column. Every nut is absolutely placed at j * --sp-step
   off the foot, and the step EQUALS a nut's band thickness, so stacked nuts sit
   flush and only the topmost shows a face — which is what real nuts do, and what
   a flex column cannot reproduce.                                              */

.spindle-play .q-stage {
  display: flex;
  align-items: center;
  justify-content: center;
}

.sp-board {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--sp-cols), 1fr);
  gap: 8px 4px;
  padding: 8px 2px 4px;
  width: 100%;
  max-width: 560px;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.sp-spindle {
  position: relative;
  /* tall enough for the nut in hand, which has to clear the TIP of the screw and
     not merely the top of the stack — taking a nut off lengthens the exposed
     thread, so a nut hovering lower is overlapped from behind by the very screw
     it came off, which reads as still being on it */
  height: calc(var(--sp-cap) * var(--sp-step) + var(--sp-art-h) * 1.35 + 10px);
  cursor: pointer;
  border-radius: var(--radius);
}

/* the foot: an ellipse, because it is a turned plate seen from the same angle as
   the nuts. WIDER than the nut, or it is simply not there — a plate narrower
   than what stands on it is hidden by the bottom nut at every stack height. */
.sp-base {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: calc(var(--sp-w) * 1.02);
  height: calc(var(--sp-w) * 0.17);
  background: var(--sp-base);
  border-radius: var(--radius-full);
  box-shadow: 0 1px 0 var(--sp-base-2), inset 0 -3px 0 var(--sp-base-2);
}

/* The thread. Two stacked background layers do the work: a repeating diagonal
   stripe for the ridges over a three-stop gradient for the cylinder. The stripe
   is slightly off-horizontal because a thread is a helix — level stripes read as
   a stack of rings — and its stops are soft, because a rolled ridge has a lit
   crest and a shaded root rather than a hard edge. */
.sp-screw,
.sp-screw-back {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--sp-w) * 0.055);
  width: var(--sp-screw-w);
  height: calc(var(--sp-cap) * var(--sp-step) + var(--sp-art-h) * 0.40);
  border-radius: calc(var(--sp-screw-w) * 0.46) calc(var(--sp-screw-w) * 0.46) 0 0;
  background-image:
    repeating-linear-gradient(171deg,
      var(--sp-crest) 0px, transparent 1.7px,
      var(--sp-thread) 3.4px, transparent 5.2px),
    linear-gradient(to right,
      transparent 20%, var(--sp-spec) 36%, transparent 54%),
    linear-gradient(to right,
      var(--sp-steel-2) 0%, var(--sp-steel) 30%,
      var(--sp-steel) 60%, var(--sp-steel-2) 100%);
}

/* Only the EXPOSED length is drawn, and it is drawn IN FRONT of the nuts. Put
   the whole screw behind the stack and the bore becomes a flat dark hole with an
   unrelated post standing behind it — the eye reads "beside", not "through". */
.sp-screw { z-index: 30; }

/* The same thread again, behind every nut. It exists for one moment: while a nut
   is lifting, the space it has vacated has to show thread, and the front copy
   cannot reach down there without also painting over the nut that is climbing.
   At rest the pair coincide and this one is entirely hidden. */
.sp-screw-back { z-index: 1; }

.sp-stack { position: absolute; inset: 0; }

/* --j is the nut's index up the screw. A higher nut must paint later: stacked
   nuts sit flush, so the one above covers the one below's top face entirely. */
.sp-nut {
  position: absolute;
  left: 50%;
  width: var(--sp-w);
  height: var(--sp-art-h);
  transform: translateX(-50%);
  bottom: calc(var(--j) * var(--sp-step) + var(--sp-w) * 0.045);
  z-index: calc(var(--j) + 2);
}

/* the nut in hand — the SAME element promoted out of its stack, never a copy, so
   nothing can desync between the two */
.sp-nut.inhand { filter: drop-shadow(0 7px 8px var(--sp-cast)); }

/* the screw a nut has just been taken off: grounded at the FOOT and fading out
   upward, because a spindle's box is tall and mostly empty and an even tint over
   it reads as a panel that has appeared behind the rack */
.sp-spindle.armed { background: linear-gradient(to top, var(--accent-dim), transparent 72%); }
.sp-spindle.armed .sp-base {
  background: var(--accent);
  box-shadow: 0 1px 0 var(--accent-hi), inset 0 -3px 0 var(--accent-hi);
}

/* a screw with no room while a nut is in hand — shown, never punished */
.sp-spindle.shut::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--sp-shut);
  border-radius: var(--radius);
  pointer-events: none;
  z-index: 50;
}

/* the refusal: tapping a screw that cannot take the nut */
.sp-spindle.nope { animation: sp-nope 0.22s var(--ease); }
@keyframes sp-nope {
  0%, 100% { transform: translateX(0); }
  35% { transform: translateX(-4px); }
  70% { transform: translateX(4px); }
}
@media (prefers-reduced-motion: reduce) { .sp-spindle.nope { animation: none; } }

.sp-spindle.done .sp-base {
  background: var(--accent);
  box-shadow: 0 1px 0 var(--accent-hi), inset 0 -3px 0 var(--accent-hi);
}

/* The cat's deduce overlay addresses whole SCREWS here, not squares — this game
   has no grid, and the unit her argument is about is a screw. deduce.js adds
   .dd-mark / .dd-focus to whatever `cellSel` selects, so the shared rules in
   deduce.css already cover the marks; these only give them a shape to sit in. */
.sp-spindle.dd-mark,
.sp-spindle.dd-focus { border-radius: var(--radius); }
