/* voxel.css - piece P14 (Developer voxel wave + re-writing statement). Owner: P14.
   Two canvases and nothing else: the cube field renders inside .stage__center
   (data-center="voxel") and the typewriter line inside .stage__line
   (data-line). stage.css (P2) owns WHERE those mounts sit and how big the
   centre is - every rule below that touches a mount is wrapped in :where(),
   which has zero specificity, so any stage.css declaration wins by cascade
   the moment it lands, whatever its order or specificity. Until then the
   fallback puts the lockup (centre + line) where P2's brief says it goes:
   the centre a clamp(280px, 42vh, 520px) square centred horizontally in the
   display field above the band's resting plane, the line directly beneath.
   No colours here: both canvases read the tokens at paint time. */

:root {
  --vx-size: clamp(280px, 42vh, 520px);     /* P2's centre mount, mirrored as a fallback */
  --vx-line-gap: 40px;                      /* field bottom -> statement top: P2's own gap, five tiles (FINE.gapRows), measured from the paint */
  --vx-line-h: 21px;                        /* 7 glyph rows at the 3px glyph pixel */
  --vx-field: calc(100% - var(--band-h, 30svh) - 1px); /* the stage above the band's box (body + seam hairline) */
  --vx-lockup: calc(var(--vx-size) + var(--vx-line-gap) + var(--vx-line-h));
  --vx-top: calc((var(--vx-field) - var(--vx-lockup)) / 2);
}

@media (max-width: 767px) {
  :root {
    --vx-line-gap: 30px;
    --vx-line-h: 14px;                      /* 7 rows at the 2px glyph pixel */
  }
}

/* ---- WHAT USED TO BE HERE, AND WHY IT IS NOT ANY MORE ----
   A previous round set `.stage { --stage-corner-pitch: 18px }` from this file:
   the caption's leading is stage.css's property, the caption led 24 into 18 and
   left "DEVELOPER" reading as an orphan above its own block, and P14 owns no
   file that could say so. It was declared here, named, on the same selector
   stage.css uses, applying to the Developer alone (voxel.css is linked from no
   other page) - and the twin divergence it created was reported in this comment
   rather than hidden, with "delete this block the moment P2 takes the value".
   P2 has taken it: stage.css now declares --stage-corner-pitch: 18px itself and
   has dropped the kicker the note was about. The block is therefore a duplicate
   of a value it no longer owns, so it is gone, and both twins lead from ONE
   declaration again. Nothing about the rendering changes - the number is the
   number P2 now sets. Keep this file to its three mounts. */

/* ---- fallback placement (zero specificity; stage.css overrides) ---- */
:where(.stage__center[data-center="voxel"]) {
  position: absolute;
  left: calc(50% - var(--vx-size) / 2);
  top: var(--vx-top);
  width: var(--vx-size);
  height: var(--vx-size);
  pointer-events: none;
}

:where(.stage__line[data-line]) {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(var(--vx-top) + var(--vx-size) + var(--vx-line-gap));
  height: var(--vx-line-h);
  pointer-events: none;
}

/* ---- the field ----
   THE CANVAS IS THE PAINT, and it is not the mount's box. It is as wide as the
   isometric floor needs (650px in a 384px mount at 1440) because an isometric
   field is roughly √3 as wide as it is deep, so filling a square mount's height
   means running past its sides. P2 measures a
   centrepiece's overhang on the TOP and BOTTOM edges only, so the horizontal
   spill is free and `.stage { overflow: hidden }` is the backstop at any width.
   Vertically the box is now entirely INSIDE the mount: FIELD.lift is zero, so
   the field asks the composition for no bleed above the mount's top line at
   all, and FIELD.seat holds its floor a fifth of the mount's height above the
   bottom one - which is what keeps the footprint the size the composition was
   tuned for now that a terraced column stacks two whole cubes. P2 reads the
   canvas's ALPHA, not this box, so the shortfall at the bottom reaches it as
   the negative bleed it is. voxel.js writes width, height, left and top in px
   on every measure - nothing here may set any of the four. */
.voxel__canvas {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  max-width: none;          /* base.css caps canvases at 100%; the field is wider than its mount by design */
  pointer-events: none;
}

/* ---- the line ---- */
.rewrite__canvas {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  max-width: none;          /* fills the slot; each line is typed at the origin stage.js reports */
  pointer-events: none;
}

/* ================================================================
   THE LIVE CUBE'S LINK - piece P14, added 2026-09-04 on Dan's ask
   ("put an example of the mouse onhover callout ... on top of the
   lattice cube that pops up").

   NOTHING HERE DRAWS THE LINK ON THE CANVAS, and that is the whole
   constraint: a link painted into a canvas cannot be tabbed to, read
   out, or opened in a new tab, and has no href under the cursor. So
   the anchor is real DOM sitting over the cube's projected point,
   and this sheet is only its clothes. voxel.js writes the geometry
   (transform / width / height / top / left, in px, every frame);
   nothing below may set any of those - it sets tone, type, hairline
   and what is visible when.

   FOUR COLOURS: --ink for the label, --dim for its tag and arrow,
   --hairline (which is --gray at 40%) for the one hairline, and the
   ground showing through everywhere else. The red lives on the cube
   itself, struck by the painter, and appears nowhere in this block.
   ================================================================ */

.vxl-layer {
  position: absolute;
  inset: 0;
  /* over the centrepiece's mount (z 1) and under the showcase (z 20);
     the layer is hidden outright while a piece is open anyway */
  z-index: 3;
  pointer-events: none;
}

/* The anchor's own box is the LABEL BLOCK and is never itself the hit target:
   only its parts are (the plate and the label). That keeps the clickable region
   the shape of the CUBE instead of a rectangle around it, and the ancestor
   still takes :hover and still activates on a click, because both of those
   follow the element that was actually hit. voxel.js writes the box - see the
   note there for the scroll jump a full-stage anchor caused on first focus. */
.vxl {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  /* the cube and its leader hang off this box; only the label is inside it */
  overflow: visible;
  pointer-events: none;
  color: var(--ink);
  outline: none;
}

/* THE HIT REGION IS THE CUBE'S OWN SILHOUETTE, clipped. clip-path clips
   hit-testing as well as paint, so what the pointer can press is exactly the
   shape the painter drew - no invisible corners reaching over the neighbouring
   tiles. voxel.js writes the polygon every frame (the plate at rest; the plate
   plus the flank the pop exposes while it is engaged, floor frozen so the
   target cannot slide out from under the pointer). It draws nothing: the cube
   is the visual. */
.vxl__tile {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  pointer-events: auto;
  cursor: pointer;
}

/* THE LEADER: one hairline from the plate's right vertex up to the label's row,
   drawn down the middle of a strip. Visual only - it is never a hit target. */
.vxl__lead {
  position: absolute;
  top: 0;
  left: 0;
  width: 24px;
  pointer-events: none;
}

/* THE CORRIDOR. The smallest box that holds the cube, the leader and the label,
   so a pointer travelling between them never crosses dead space and shuts the
   callout it is walking towards. It is a SIBLING of the anchor and not a part
   of it: it holds an open callout open, it can never open one (voxel.js
   ignores it unless that link is already engaged), and it is not clickable -
   the things you can press are the cube and the label, and blank sky that
   navigates because a callout happens to be open is a trap. */
.vxl-bridge {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
/* armed only while a callout is open. A data attribute rather than :has(), so
   the corridor does not depend on selector support to be inert. */
.vxl-layer[data-open="true"] .vxl-bridge { pointer-events: auto; }
.vxl__lead::before {
  content: "";
  position: absolute;
  left: calc(50% - 0.5px);
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--hairline);
  opacity: 0;
  transition: opacity var(--dur-micro) var(--ease);
}

/* THE CALLOUT. A zero-height box whose top edge IS the hairline row: the name
   hangs above that line, so the label's own underline and the leader are one
   continuous stroke. A second line (voxel.js builds it only when a cell has one
   - it carried "PLACEHOLDER LINK" before the destination was known) would hang
   below the same rule. No fill, no shadow, no rounded chrome - it is an
   annotation on the page, in the site's READOUT. */
.vxl__note {
  position: absolute;
  width: 0;
  height: 0;
}
.vxl__name,
.vxl__tag {
  position: absolute;
  white-space: nowrap;
  font-family: var(--font-readout);
  text-transform: uppercase;
  letter-spacing: var(--track-readout);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-micro) var(--ease);
}
.vxl__name {
  bottom: 0;
  left: 0;
  /* the top pad is the tap/click target growing UPWARD, so it can never move
     the hairline the whole device is drawn against */
  padding: 12px 0 6px 10px;
  font-size: var(--fs-12);
  line-height: 1;
  color: var(--ink);
  border-bottom: 1px solid var(--hairline);
  cursor: pointer;
}
.vxl__tag {
  top: 0;
  left: 10px;
  padding: 5px 0 10px;
  font-size: 11px;
  line-height: 1;
  color: var(--dim);
  cursor: pointer;
}
.vxl__arrow {
  margin-left: 10px;
  color: var(--dim);
}

/* the rule runs back over the field when the page's margin will not take it
   (the phone, where the field is the frame) - voxel.js measures and chooses */
.vxl__note--left .vxl__name {
  left: auto;
  right: 0;
  padding: 12px 10px 6px 0;
  text-align: right;
}
.vxl__note--left .vxl__tag {
  left: auto;
  right: 10px;
  text-align: right;
}

/* ---- open: on hover, on :focus-visible, and always where there is no hover ---- */
.vxl[data-live="on"] .vxl__lead::before,
.vxl[data-live="on"] .vxl__name,
.vxl[data-live="on"] .vxl__tag,
.vxl-layer[data-touch="on"] .vxl__lead::before,
.vxl-layer[data-touch="on"] .vxl__name,
.vxl-layer[data-touch="on"] .vxl__tag { opacity: 1; }

.vxl[data-live="on"] .vxl__name,
.vxl[data-live="on"] .vxl__tag,
.vxl-layer[data-touch="on"] .vxl__name,
.vxl-layer[data-touch="on"] .vxl__tag { pointer-events: auto; }

/* THE FOCUS RING IS THE LABEL'S CELL - one tile outside the whole callout block
   on all four sides, the same grammar the icon rail and the bar's own controls
   use. It is the ANCHOR's outline, because the anchor's box IS that block
   (voxel.js measures the two lines and writes it), so the ring cannot cut
   through the placeholder line the way a ring drawn round the first line alone
   did. */
.vxl:focus-visible {
  outline: 1px solid var(--ink);
  outline-offset: var(--pitch);
}

@media (prefers-reduced-motion: reduce) {
  /* the link still works and still shows its callout; it simply does not fade */
  .vxl__lead::before,
  .vxl__name,
  .vxl__tag { transition: none; }
}

@media (forced-colors: active) {
  .vxl__name { color: LinkText; }
  .vxl__tag,
  .vxl__arrow { color: CanvasText; }
  .vxl__lead::before { background: CanvasText; }
  .vxl:focus-visible { outline-color: CanvasText; }
}

@media (max-width: 767px) {
  .vxl__name {
    /* The phone has no hover, so this label is a TAP target and is padded to a
       real one on its own: 11px caps + 27 above + 6 below = 44. It used to
       reach 44 only WITH the second line under the rule carrying the rest, so
       when the placeholder line went the target quietly fell to 38 - measured
       on the served page at 390. The whole pad is on TOP because that is the
       one direction that cannot move the hairline: the rule is this element's
       own bottom border and the leader ends exactly on it, so a pad below would
       walk the two apart. */
    font-size: 11px;
    padding: 27px 0 6px 8px;
  }
  .vxl__note--left .vxl__name { padding: 27px 8px 6px 0; }
  .vxl__tag {
    font-size: 10px;
    left: 8px;
    padding: 5px 0 12px;
  }
  .vxl__note--left .vxl__tag { left: auto; right: 8px; }
  .vxl__arrow { margin-left: 8px; }
}
