/* ============================================================================
   3D SUDOKU — GAME BOARD STYLES
   Builds on unified-3d-game.css (framed stage, sidebar, buttons, modals).
   Only board chrome lives here: the stage layout, the face rail, the tool row,
   the digit pad and the in-stage overlays. The site top bar and the game
   sidebar are deliberately untouched.
   ========================================================================== */

#cube-viewport {
  /* The shared .game-canvas-container centres its child; the board needs a
     column instead: rail on top, WebGL stage in the middle, pad underneath. */
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  padding: 0;
  /* Digital tabletop copied from the word scramble stage: pale paper air
     with the dark lit-table glow rising from the bottom of the frame. */
  background:
    radial-gradient(120% 90% at 50% 12%, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0) 62%),
    radial-gradient(120% 110% at 50% 108%, rgba(59, 53, 72, 0.55), rgba(0, 0, 0, 0) 60%),
    linear-gradient(180deg, #e6dfd3 0%, #e2dacb 50%, #3b3548 100%);
}

html[data-theme='dark'] #cube-viewport {
  /* Same environment in dark: violet air, lighter lit table at the bottom. */
  background:
    radial-gradient(120% 90% at 50% 10%, rgba(111, 103, 143, 0.14), rgba(0, 0, 0, 0) 62%),
    radial-gradient(120% 110% at 50% 108%, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0) 58%),
    linear-gradient(180deg, #14121a 0%, #241f2e 100%);
}

/* The mat around the framed stage would otherwise colour over the gradient. */
html[data-theme='light'] #cube-viewport,
html[data-theme='dark'] #cube-viewport {
  background-color: transparent;
}

/* The viewport has no paper mat (padding: 0), so the gridded floor fills it. */
#cube-viewport > .ws3d-grid-floor {
  left: 0;
  right: 0;
  bottom: 0;
}

/* ---------------------------------------------------------------------------
   Stage geometry: rail / board / pad as real flex rows so the renderer can be
   sized to the leftover box and the cube is never covered by its own HUD.
   ------------------------------------------------------------------------- */

.s3d-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  overflow: hidden;
}

.s3d-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
  touch-action: none;
}

.s3d-stage canvas:active {
  cursor: grabbing;
}

/* ---------------------------------------------------------------------------
   Face rail — jump the camera to any side and read its progress at a glance.
   ------------------------------------------------------------------------- */

.s3d-hud-top {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 6px 8px 2px;
}

.s3d-face-rail {
  display: flex;
  gap: 5px;
  align-items: stretch;
  flex-wrap: wrap;
  justify-content: center;
}

#cube-viewport .s3d-face-chip {
  position: relative;
  border-width: 1.5px;
  border-style: solid;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-width: 40px;
  padding: 4px 7px;
  font-family: var(--news-font-body);
  color: var(--news-ink);
  background: var(--news-btn-bg);
  border: 1.5px solid var(--news-rule);
  border-radius: 0;
  box-shadow: 1.5px 1.5px 0 rgba(0, 0, 0, 0.14);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease, color 0.15s ease, transform 0.12s ease;
}

#cube-viewport .s3d-face-chip:hover {
  background: var(--news-btn-bg-hover);
  color: var(--news-paper);
}

#cube-viewport .s3d-face-chip:active {
  transform: translate(1px, 1px);
  box-shadow: none;
}

#cube-viewport .s3d-face-chip.active {
  background: var(--news-active-bg);
  border-color: var(--news-active-bg);
  color: #fff;
}

#cube-viewport .s3d-face-chip.done {
  border-color: var(--news-correct);
  color: var(--news-correct);
}

#cube-viewport .s3d-face-chip.done.active {
  background: var(--news-correct);
  color: #fff;
}

#cube-viewport .s3d-face-chip:focus-visible {
  outline: 3px solid var(--news-accent);
  outline-offset: 2px;
}

.s3d-face-key {
  font-size: 0.68rem;
  font-weight: 900;
  letter-spacing: 0.06em;
  line-height: 1;
}

.s3d-face-count {
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}

/* ---------------------------------------------------------------------------
   Bottom bar: tools on the left, digit pad on the right.
   ------------------------------------------------------------------------- */

#cube-viewport .s3d-tools {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
}

#cube-viewport .s3d-tool {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 52px;
  min-height: 46px;
  padding: 4px 6px;
  font-family: var(--news-font-body);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--news-ink);
  background: var(--news-btn-bg);
  border: 1.5px solid var(--news-rule);
  border-radius: 0;
  box-shadow: 1.5px 1.5px 0 rgba(0, 0, 0, 0.14);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease, color 0.15s ease, transform 0.12s ease;
}

#cube-viewport .s3d-tool svg {
  width: 17px;
  height: 17px;
}

#cube-viewport .s3d-tool:hover:not(:disabled) {
  background: var(--news-btn-bg-hover);
  color: var(--news-paper);
}

#cube-viewport .s3d-tool:active:not(:disabled) {
  transform: translate(1px, 1px);
  box-shadow: none;
}

#cube-viewport .s3d-tool.on {
  background: var(--news-active-bg);
  border-color: var(--news-active-bg);
  color: #fff;
}

#cube-viewport .s3d-tool:disabled {
  opacity: 0.42;
  cursor: not-allowed;
  /* Keep the same shadow geometry: dropping it shifted a disabled button 1.5px
     out of line with its neighbours. */
  box-shadow: 1.5px 1.5px 0 rgba(0, 0, 0, 0.05);
}

#cube-viewport .s3d-tool:focus-visible {
  outline: 3px solid var(--news-accent);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   Wide screens: tools become a left column and the digit pad a right column,
   so the stage gives its full height to the board. A cube's silhouette is
   square, so on a letterboxed stage it is always height-bound — the width it
   cannot use now carries the controls instead of sitting empty.
   ------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   Digit pad — every digit shows how many of it are still unplaced, so the
   board can be read at a glance without counting.
   ------------------------------------------------------------------------- */

#cube-viewport .s3d-pad {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(46px, 1fr);
  gap: 5px;
}

#cube-viewport .s3d-pad-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  min-width: 46px;
  padding: 2px 4px 2px;
  font-family: var(--news-font-body);
  font-size: 1.32rem;
  font-weight: 800;
  color: var(--news-ink);
  background: var(--news-btn-bg);
  border: 1.5px solid var(--news-rule);
  border-radius: 0;
  box-shadow: 1.5px 1.5px 0 rgba(0, 0, 0, 0.14);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease;
}

#cube-viewport .s3d-pad-btn:hover:not(:disabled) {
  background: var(--news-btn-bg-hover);
  color: var(--news-paper);
}

#cube-viewport .s3d-pad-btn:active:not(:disabled) {
  transform: translate(1px, 1px);
  box-shadow: none;
}

#cube-viewport .s3d-pad-btn.active {
  background: var(--news-active-bg);
  border-color: var(--news-active-bg);
  color: #fff;
}

#cube-viewport .s3d-pad-btn.done {
  color: var(--news-correct);
  border-color: var(--news-correct);
}

#cube-viewport .s3d-pad-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: 1.5px 1.5px 0 rgba(0, 0, 0, 0.05);
}

#cube-viewport .s3d-pad-btn:focus-visible {
  outline: 3px solid var(--news-accent);
  outline-offset: 2px;
}

#cube-viewport .s3d-pad-digit {
  line-height: 1;
}

#cube-viewport .s3d-pad-count {
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: 0.64rem;
  font-weight: 700;
  line-height: 1;
  /* Full opacity on a token that already clears AA: at 0.62 alpha these
     numbers measured 3.56:1, and they are the whole point of the pad. */
  color: var(--news-ink-light);
  font-variant-numeric: tabular-nums;
}

#cube-viewport .s3d-pad-erase {
  font-size: 1.1rem;
}

/* ---------------------------------------------------------------------------
   In-stage overlays
   ------------------------------------------------------------------------- */

.s3d-toast {
  position: absolute;
  left: 50%;
  bottom: 14px;
  z-index: 20;
  max-width: min(92%, 460px);
  padding: 9px 16px;
  font-family: var(--news-font-body);
  font-size: 0.88rem;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  color: var(--news-paper);
  background: var(--news-ink);
  border: 1px solid var(--news-ink);
  box-shadow: var(--shadow-hard);
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, 10px);
  transition: opacity 0.22s ease, transform 0.22s ease, visibility 0.22s;
  pointer-events: none;
}

.s3d-toast.show {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

.s3d-toast-warn {
  background: #8a5a00;
  border-color: #8a5a00;
}

.s3d-toast-error {
  background: var(--news-incorrect, #c44d4d);
  border-color: var(--news-incorrect, #c44d4d);
  color: #fff;
}

.s3d-toast-success {
  background: var(--news-correct, #5a8a4a);
  border-color: var(--news-correct, #5a8a4a);
  color: #fff;
}

/* A real layout ROW inside .s3d-hud-top, not an overlay: an absolutely
   positioned banner sat on top of the very cells the hint was describing.
   Showing it shrinks the stage by its own height, and the renderer re-frames
   the cube to fit, so nothing is ever occluded. */
.s3d-hint-banner {
  display: none;
  align-items: center;
  gap: 10px;
  width: min(100%, 760px);
  margin: 6px auto 0;
  padding: 8px 12px;
  font-family: var(--news-font-body);
  font-size: 0.88rem;
  line-height: 1.4;
  color: var(--news-ink);
  background: var(--news-paper);
  border: 2px solid var(--news-ink);
  border-left-width: 6px;
  box-shadow: var(--shadow-hard);
}

.s3d-hint-banner.show {
  display: flex;
}

.s3d-hint-icon {
  flex: 0 0 auto;
  font-size: 1.1rem;
}

.s3d-hint-text {
  flex: 1 1 auto;
}

#cube-viewport .s3d-hint-btn {
  flex: 0 0 auto;
  padding: 6px 10px;
  font-family: inherit;
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--news-paper);
  background: var(--news-ink);
  border: 1px solid var(--news-ink);
  cursor: pointer;
}

#cube-viewport .s3d-hint-btn:focus-visible {
  outline: 3px solid var(--news-ink);
  outline-offset: 2px;
}

#cube-viewport .s3d-hint-close {
  flex: 0 0 auto;
  padding: 0 4px;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--news-ink-light);
  background: none;
  border: none;
  cursor: pointer;
}

.s3d-live {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.s3d-coach {
  position: absolute;
  left: 50%;
  bottom: 22px;
  z-index: 19;
  padding: 8px 14px;
  font-family: var(--news-font-body);
  font-size: 0.86rem;
  font-weight: 600;
  /* Ink, not the accent: the accent already means "this mode is on" everywhere
     else in this HUD, so a welcome message must not borrow it. */
  color: var(--news-paper);
  background: var(--news-ink);
  border: 1px solid var(--news-ink);
  box-shadow: var(--shadow-hard);
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, 8px);
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s;
  cursor: pointer;
  text-align: center;
  max-width: 90%;
}

.s3d-coach.show {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

/* Loading / dealing overlay */
.s3d-loading {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--news-bg) 82%, transparent);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0.2s;
}

.s3d-loading.show {
  opacity: 1;
  visibility: visible;
}

.s3d-loading-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 20px 26px;
  background: var(--news-paper);
  border: 2px solid var(--news-ink);
  box-shadow: var(--shadow-hard-lg);
}

.s3d-loading-text {
  font-family: var(--news-font-display);
  font-size: 0.98rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--news-ink);
}

.s3d-loading-track {
  width: 210px;
  height: 6px;
  background: var(--news-rule-light);
  border: 1px solid var(--news-rule-light);
  overflow: hidden;
}

.s3d-loading-bar {
  width: 0;
  height: 100%;
  background: var(--news-accent);
  transition: width 0.25s ease;
}

.s3d-loading-cube {
  position: relative;
  width: 42px;
  height: 42px;
  animation: s3d-spin 3.4s linear infinite;
  transform-style: preserve-3d;
}

.s3d-loading-cube span {
  position: absolute;
  inset: 0;
  border: 2px solid var(--news-accent);
  background: color-mix(in srgb, var(--news-accent) 12%, transparent);
}

.s3d-loading-cube span:nth-child(1) { transform: rotateY(0deg) translateZ(21px); }
.s3d-loading-cube span:nth-child(2) { transform: rotateY(90deg) translateZ(21px); }
.s3d-loading-cube span:nth-child(3) { transform: rotateY(180deg) translateZ(21px); }
.s3d-loading-cube span:nth-child(4) { transform: rotateY(-90deg) translateZ(21px); }
.s3d-loading-cube span:nth-child(5) { transform: rotateX(90deg) translateZ(21px); }
.s3d-loading-cube span:nth-child(6) { transform: rotateX(-90deg) translateZ(21px); }

@keyframes s3d-spin {
  from { transform: rotateX(-22deg) rotateY(0deg); }
  to { transform: rotateX(-22deg) rotateY(360deg); }
}

/* ---------------------------------------------------------------------------
   Landscape phones. A 9x9 face on a 390px-tall screen gives cells a few pixels
   each: the honest answer is to ask for the device upright rather than ship a
   board nobody can tap. The game stays fully laid out underneath, so the prompt
   is a nudge, not a wall.
   ------------------------------------------------------------------------- */

.s3d-rotate,
.s3d-toosmall {
  display: none;
}

@media (orientation: landscape) and (max-height: 560px) and (pointer: coarse) {
  /* Fixed, not absolute: on a landscape phone the shared page chrome reflows
     the stage below the fold, so an in-stage prompt would be off-screen too. */
  .s3d-rotate {
    position: fixed;
    inset: 0;
    z-index: 10060;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: color-mix(in srgb, var(--news-bg) 88%, transparent);
  }

  .s3d-rotate-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    max-width: 340px;
    padding: 18px 20px;
    text-align: center;
    color: var(--news-ink);
    background: var(--news-paper);
    border: 2px solid var(--news-ink);
    box-shadow: var(--shadow-hard-lg);
  }

  .s3d-rotate-card svg {
    width: 34px;
    height: 34px;
    color: var(--news-accent);
    animation: s3d-tilt 2.4s ease-in-out infinite;
  }

  .s3d-rotate-card strong {
    font-family: var(--news-font-display);
    font-size: 1.05rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }

  .s3d-rotate-card span {
    font-size: 0.82rem;
    line-height: 1.45;
    color: var(--color-text-secondary);
  }
}

@keyframes s3d-tilt {
  0%, 40%, 100% { transform: rotate(90deg); }
  60%, 90% { transform: rotate(0deg); }
}

/* ---------------------------------------------------------------------------
   Victory / confirm modals (game chrome; the shared .modal-overlay does the
   lifting, these only add the recap styling).
   ------------------------------------------------------------------------- */

.s3d-victory-title {
  margin: 0 0 6px;
  font-family: var(--news-font-display);
  font-size: 1.75rem;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-primary);
}

.s3d-victory-sub {
  margin: 0 0 14px;
  font-size: 0.98rem;
  color: var(--color-text-secondary);
}

.s3d-stars {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin: 2px 0 10px;
}

.s3d-star {
  font-size: 2rem;
  line-height: 1;
  color: var(--news-rule-light);
  transform: scale(0.7);
  opacity: 0.45;
  transition: transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.32s ease, opacity 0.32s ease;
}

.s3d-star.on {
  color: #e0a800;
  opacity: 1;
  transform: scale(1.12);
}

html[data-theme='dark'] .s3d-star.on {
  color: #ffc454;
}

.s3d-recap {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin: 4px 0 16px;
}

.s3d-recap-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid var(--news-rule-light);
  background: var(--news-paper);
}

.s3d-recap-label {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.s3d-recap-value {
  font-size: 1.05rem;
  font-weight: 800;
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
}

.s3d-modal-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------------------
   Responsive
   ------------------------------------------------------------------------- */

/* The side-column grid needs height as well as width: on a wide but SHORT
   window (1440x400) this rule used to apply alongside the compact block, and a
   three-column grid inside a 400px frame resolved the stage to zero width —
   an empty board with the pad below the fold. */
@media (min-width: 900px) and (min-height: 621px) {
  /* CSS GRID, not flex. With flex-wrap the line's cross size is derived from
     its items' content, and the stage's content IS the canvas, which three.js
     sizes from the stage — so every resize grew the canvas, which grew the
     stage, which triggered the ResizeObserver again. On a 1600x900 window that
     ran away to an 823px stage inside a 701px frame and pushed the bottom of
     the board below the fold. A `1fr` grid track is sized from the container's
     definite height and is never grown by its content. */
  #cube-viewport {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas:
      "rail  rail  rail"
      "tools stage pad";
    align-items: start;
  }

  .s3d-hud-top { grid-area: rail; width: 100%; }

  .s3d-stage {
    grid-area: stage;
    min-width: 0;
    min-height: 0;
    height: 100%;
    overflow: hidden;
  }

  #cube-viewport .s3d-tools {
    grid-area: tools;
    align-self: start;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    gap: 5px;
    padding: 4px 2px 8px 8px;
  }

  #cube-viewport .s3d-pad {
    grid-area: pad;
    align-self: start;
    display: grid;
    grid-auto-flow: row;
    grid-template-columns: 62px;
    grid-auto-rows: 44px;
    align-content: start;
    gap: 5px;
    padding: 4px 8px 8px 2px;
  }

  /* Uniform width: the labels are different lengths and the row used to spread
     1.54x, which read as a ragged column. */
  #cube-viewport .s3d-tool {
    flex: 0 0 auto;
    width: 100%;
    min-width: 86px;
    min-height: 44px;
    flex-direction: row;
    justify-content: flex-start;
    gap: 7px;
    padding: 0 8px;
  }

  #cube-viewport .s3d-tool svg { flex: 0 0 auto; }
  #cube-viewport .s3d-tool-label { display: inline; }

  #cube-viewport .s3d-pad-btn {
    min-height: 44px;
    min-width: 0;
  }
}

/* ---------------------------------------------------------------------------
   Compact HUD
   Applied to phones (narrow viewports) AND to short landscape screens. The
   landscape case matters: a phone turned sideways is 844px wide, which misses
   every max-width breakpoint, so the site's desktop rules applied and the
   control rows ate the whole stage — the WebGL area measured 0px tall.
   ------------------------------------------------------------------------- */

@media (max-width: 768px), (max-height: 620px) and (orientation: landscape) {
  #cube-viewport {
    padding: 0;
  }

  /* Every rule below is anchored on #cube-viewport on purpose: the shared
     chrome stylesheet sets `html[data-ui-touch] body button { min-width: 44px;
     padding: 10px 14px }`, and a bare `.s3d-tool` selector loses to it — which
     is why the rail and tool rows could not shrink and ran off the edge of a
     320px screen. The id makes these rules win. */

  /* The rail moves BELOW the board: the mobile timer pill floats over the
     top-right of the stage, and a rail up there would sit under it. The tools
     and the pad are direct children of the stage box, so they need their own
     place in the stack — leaving them on the old wrapper orders interleaved
     them with the rail and pushed the tool row over the canvas. */
  .s3d-stage { order: 1; }
  .s3d-hud-top { order: 2; }

  .s3d-hud-top {
    /* Reclaim the rail's own gutter: six chips plus five gaps is the whole
       width at 320, and every pixel here is chip width. */
    padding: 2px 0 0;
    justify-content: center;
  }

  .s3d-face-rail {
    flex-wrap: nowrap;
    gap: 2px;
    width: 100%;
    justify-content: center;
  }

  #cube-viewport .s3d-face-chip {
    flex: 1 1 0;
    min-width: 0;
    min-height: 44px;
    max-width: 58px;
    padding: 1px 2px;
  }

    .s3d-face-key { font-size: 0.66rem; }
  .s3d-face-count { font-size: 0.7rem; }

  /* The help FAB floats over the bottom-right corner (68px square, 28px from
     each edge). Reserving that column horizontally — rather than a 76px band
     underneath, which cost 18% of the board's height on every phone — keeps
     every control clear of it while the pad keeps the full width minus one
     FAB column. */
  .s3d-tools,
  #cube-viewport .s3d-pad {
    padding-left: 6px;
    padding-right: calc(96px + env(safe-area-inset-right, 0px));
  }

  .s3d-tools { padding-top: 4px; }
  #cube-viewport .s3d-pad { padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }

  #cube-viewport .s3d-pad {
    width: 100%;
    margin-right: 0;
  }

  /* Dragging a control must not scroll the page. */
  .s3d-hud-top,
    .s3d-face-rail,
  #cube-viewport .s3d-tools,
  #cube-viewport .s3d-pad {
    touch-action: none;
  }

  /* Icon-only tool row: seven controls fit one thumb-reachable line. */
  #cube-viewport .s3d-tools {
    order: 3;
    width: 100%;
    justify-content: flex-start;
    gap: 4px;
    /* Two lines of four, fully visible. A scrolling single line kept 44px
       targets but hid Hint and Sound — and those have no other entry point on a
       phone. The board is width-bound at every phone size, so the second line's
       46px costs the focused cell nothing (measured: 36.9px at 390, 37.0 at 360,
       38.1 at 320, all unchanged or better). */
    flex-wrap: wrap;
    overflow: visible;
  }

  #cube-viewport .s3d-tool {
    flex: 1 1 calc(25% - 3px);
    width: auto;
    min-width: 44px;
    max-width: none;
    min-height: 46px;
    padding: 0;
  }

  #cube-viewport .s3d-tool-label { display: none; }
  #cube-viewport .s3d-tool svg { width: 20px; height: 20px; }

  /* Two-row thumb pad (one row on 4x4 and 6x6 cubes). */
  #cube-viewport .s3d-pad {
    order: 4;
    grid-auto-flow: row;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    width: 100%;
    gap: 5px;
  }

  #cube-viewport .s3d-pad-btn {
    min-height: 46px;
    min-width: 0;
    padding: 2px 4px;
    font-size: 1.24rem;
  }

  /* Below ~340px the two-line tool row costs more board than it is worth:
     46px out of a 203px stage. There the tools go back to one scrollable line
     with a fade and snap points, which keeps every 44px target and every
     control reachable while giving the board the height back. */
  @media (max-width: 340px) {
    #cube-viewport .s3d-tools {
      flex-wrap: nowrap;
      overflow-x: auto;
      overflow-y: hidden;
      touch-action: pan-x;
      scrollbar-width: none;
      -webkit-overflow-scrolling: touch;
      scroll-snap-type: x proximity;
      -webkit-mask-image: linear-gradient(90deg, #000 0, #000 88%, transparent 100%);
      mask-image: linear-gradient(90deg, #000 0, #000 88%, transparent 100%);
    }

    #cube-viewport .s3d-tools::-webkit-scrollbar { display: none; }

    #cube-viewport .s3d-tool {
      flex: 0 0 46px;
      max-width: 46px;
      scroll-snap-align: start;
    }
  }

  /* Very narrow phones (320-360px): trim the rail a little so the board keeps
     as much height as possible. */
  @media (max-width: 360px) {
        .s3d-face-count { font-size: 0.6rem; }

    /* Five columns beside the FAB column leave ~34px keys on a 320px screen.
       Auto-fit keeps every key at 44px and lets the row wrap instead. The pad's
       erase key is dropped here: the Erase tool does the same job and tapping a
       placed digit already clears it. */
    #cube-viewport .s3d-pad {
      grid-template-columns: repeat(auto-fit, minmax(44px, 1fr));
    }

    #cube-viewport .s3d-pad-btn { font-size: 1.12rem; }
  }

  .s3d-toast {
    bottom: 8px;
    font-size: 0.84rem;
    max-width: calc(100% - 16px);
  }

  /* The banner is a row in the bottom HUD on a phone, so it never covers the
     board and never needs the page to re-layout around an overlay. */
  .s3d-hint-banner {
    flex-wrap: wrap;
    gap: 6px;
    width: 100%;
    margin: 4px 0 0;
    font-size: 0.84rem;
    padding: 8px 10px;
  }

  .s3d-hint-text {
    flex: 1 1 100%;
    order: 3;
  }

  .s3d-coach {
    /* Top of the stage, where the framing leaves clearance: at the bottom it
       sat on the cube, which is now large enough for that to matter. */
    top: 8px;
    bottom: auto;
    font-size: 0.8rem;
    max-width: 92%;
    transform: translate(-50%, -8px);
  }

  .s3d-coach.show {
    transform: translate(-50%, 0);
  }
}

/* ---------------------------------------------------------------------------
   Short screens (landscape phones, small windows): squeeze the chrome hard so
   the board keeps as much of the stage as possible. The rail and the pad each
   collapse to a single compact line.
   ------------------------------------------------------------------------- */

@media (max-height: 620px) {
  /* The shared framed stage floors at 320px. On a window shorter than that
     (plus the header) the frame itself ran past the fold and clipped the
     control rows inside an `overflow: hidden` box that could not be scrolled.
     Let the frame shrink to the space that exists, and allow it to scroll as a
     last resort. */
  #cube-viewport {
    min-height: 190px;
    overflow-y: auto;
  }
}

@media (max-height: 620px) and (orientation: landscape) {
  /* Short LANDSCAPE windows (a laptop on a squat browser, a phone on its side)
     get a
     single compact line for the rail and a single line for the controls, both
     forbidden to wrap: a wrapped row used to grow taller than the framed stage
     itself, and because that box clips its overflow the rail and the digit pad
     ended up hundreds of pixels below the fold and unreachable. */
  .s3d-hud-top { padding: 2px 6px 0; }

  .s3d-face-rail {
    flex-wrap: nowrap;
    width: 100%;
    justify-content: center;
    gap: 4px;
  }

  #cube-viewport .s3d-face-chip { flex: 0 1 auto; min-height: 34px; max-width: 44px; padding: 2px 4px; }
  .s3d-face-key { font-size: 0.6rem; }
  .s3d-face-count { font-size: 0.62rem; }
  #cube-viewport .s3d-face-home svg { width: 14px; height: 14px; }

  .s3d-hud-bottom {
    padding: 2px 6px 4px;
    gap: 6px;
    flex-wrap: nowrap;
    align-items: center;
  }

  #cube-viewport .s3d-tools { flex-wrap: nowrap; }
  #cube-viewport .s3d-tool { width: 38px; min-width: 38px; min-height: 38px; padding: 0; }
  #cube-viewport .s3d-tool-label { display: none; }
  #cube-viewport .s3d-tool svg { width: 17px; height: 17px; }

  #cube-viewport .s3d-pad {
    grid-auto-flow: column;
    grid-template-columns: none;
    grid-auto-columns: minmax(34px, 52px);
    width: auto;
    gap: 4px;
  }

  #cube-viewport .s3d-pad-btn { min-height: 38px; min-width: 34px; font-size: 1.08rem; }
  #cube-viewport .s3d-pad-count { font-size: 0.6rem; top: 1px; right: 3px; }
}

/* Landscape phones are wide: give the controls a right-hand column instead of
   stacking them under a very short stage. */
@media (max-height: 620px) and (orientation: landscape) and (min-width: 620px) and (pointer: coarse) {
  #cube-viewport {
    flex-direction: row;
    flex-wrap: wrap;
    align-content: stretch;
  }

  .s3d-stage {
    order: 1;
    flex: 1 1 auto;
    min-width: 0;
  }

  .s3d-hud-top {
    order: 2;
    flex: 0 0 62px;
    flex-direction: column;
    justify-content: flex-start;
    padding: 6px 6px 0 0;
  }

  .s3d-face-rail {
    flex-direction: column;
    flex-wrap: nowrap;
    width: auto;
  }

  #cube-viewport .s3d-face-chip {
    flex: 0 0 auto;
    width: 56px;
    max-width: none;
    min-height: 34px;
  }

  .s3d-hud-bottom {
    order: 4;
    flex: 0 0 100%;
    padding: 2px 6px 6px;
    justify-content: center;
  }

  #cube-viewport .s3d-pad {
    grid-auto-flow: column;
    grid-template-columns: none;
    grid-auto-columns: minmax(44px, 60px);
    width: auto;
  }

  #cube-viewport .s3d-tools { width: auto; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .s3d-toast,
  .s3d-hint-banner,
  .s3d-coach,
  #cube-viewport .s3d-face-chip,
  #cube-viewport .s3d-tool,
  #cube-viewport .s3d-pad-btn,
  .s3d-star {
    transition: none;
  }

  .s3d-loading-cube {
    animation: none;
  }
}
