/* Billiards. The table is a canvas that fills whatever is left after the
   header, the turn bar and (on Master only) the spin control — so the layout
   gives the canvas a flexible box and lets it measure itself, never the
   reverse. */
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; overflow: hidden; overscroll-behavior: none; }
body {
  background: #0d2b1d; color: #eaf5ee;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  height: 100vh; height: 100dvh;      /* fallback first: dvh is Safari 15.4+ */
  user-select: none; -webkit-user-select: none; touch-action: none;
}
#app { display: flex; flex-direction: column; height: 100%; }
/* The content region. `position: relative` is the whole point: it is what
   an overlay is absolute AGAINST, which is what keeps the header out from
   under the scrim. */
#main { flex: 1 1 auto; min-height: 0; position: relative; display: flex; flex-direction: column; }

/* [hidden] loses to display:flex/grid, and a help sheet shipping open is the
   bug that fact causes. Settle it once, globally. */
[hidden] { display: none !important; }

#hdr {
  flex: 0 0 auto; display: flex; align-items: center; gap: 6px;
  padding: calc(6px + env(safe-area-inset-top)) 10px 6px;
  background: #082015; font-weight: 700; font-size: 14px;
}
#hdrTitle { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pill {
  flex: 0 0 auto; min-width: 44px; min-height: 44px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 12px; background: #16452f; color: inherit;
  font-size: 17px; cursor: pointer;
}
.pill:active { background: #1d5b3d; }

/* ---- screens ---- */
.scr { display: none; flex: 1 1 auto; min-height: 0; flex-direction: column; }
.scr.on { display: flex; }
/* R-C15b: these screens CENTRE and SCROLL, which is the one combination that
   puts content out of reach. When the content is taller than the box,
   `justify-content: center` pushes the first child ABOVE the top edge and
   scrollTop cannot go negative. Measured at 375x620 — a phone with its URL bar
   showing — word-sleuth's help began at y = -585: the first three cards could
   not be read or scrolled to, and had not been since it shipped.
   Auto margins on the end children centre identically while the content fits
   and collapse to zero once it does not. */
#scr-menu, #scr-how {
  align-items: center; gap: 10px;
  padding: 16px calc(16px + env(safe-area-inset-left)) calc(16px + env(safe-area-inset-bottom));
  overflow-y: auto; overscroll-behavior: contain;
}
#scr-menu > :first-child, #scr-how > :first-child { margin-top: auto; }
#scr-menu > :last-child,  #scr-how > :last-child  { margin-bottom: auto; }
h1 { font-size: 30px; }
h2 { font-size: 22px; margin-bottom: 6px; }
.sub { opacity: .75; font-size: 14px; text-align: center; max-width: 30ch; }
#statsLine { opacity: .7; font-size: 13px; text-align: center; margin-top: 4px; }

.big {
  width: min(420px, 100%); min-height: 52px;
  border: 0; border-radius: 14px; background: #16452f; color: inherit;
  font-size: 16px; font-weight: 600; cursor: pointer;
}
.big:active { transform: translateY(1px); }
.big.primary { background: #2fa06a; color: #04160e; }
.mini {
  min-height: 44px; padding: 0 14px; border: 0; border-radius: 10px;
  background: #16452f; color: inherit; font-size: 13px; cursor: pointer;
}

#tierRow { display: flex; gap: 8px; width: min(420px, 100%); }
.tierCard {
  /* flex-basis 0 with min-width 0: the auto minimum of a flex item is its
     content, which overflows the page on a narrow phone. */
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 10px 6px; border: 2px solid transparent; border-radius: 14px;
  background: rgba(18, 56, 38, .38); color: inherit; cursor: pointer; font-size: 12px;
}
.tierCard.sel { border-color: #2fa06a; background: #16452f; }
.tierCard .te { font-size: 20px; }
.tierCard b { font-size: 13px; }
.tierCard small { opacity: .65; font-size: 10px; line-height: 1.25; text-align: center; }

.how { max-width: 46ch; display: flex; flex-direction: column; gap: 10px; font-size: 14px; line-height: 1.5; }
.how p { opacity: .9; }
.how b { color: #7fe0ac; }

/* ---- play ---- */
#turnBar {
  flex: 0 0 auto; display: flex; justify-content: space-between; align-items: stretch;
  gap: 6px; padding: 4px 8px; font-size: 13px; font-weight: 600; background: #0a2418;
}
/* Both players, always. The active one is not merely brighter — a dim/bright
   pair is a difference you have to have SEEN the other state to read. It gets
   a filled background and a rail, so "mine" is legible on its own. */
.pcard {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 5px 8px; border: 0; border-radius: 10px;
  background: transparent; color: #cfe6d8; opacity: .55;
  font: inherit; font-size: 13px; cursor: pointer;
  border-bottom: 3px solid transparent;
}
.pcard.on {
  background: #14402f; color: #fff; opacity: 1;
  border-bottom-color: #2fa06a;
}
.pname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pcount { font-weight: 700; opacity: .85; font-variant-numeric: tabular-nums; }
/* The suit, drawn rather than spelled. Both chips are the same yellow so the
   ONLY difference between them is the band — which is the only difference
   between the balls, too. An unassigned table shows a hollow ring: "not yet"
   has to look different from both answers, not like one of them. */
.suit {
  flex: 0 0 auto; width: 17px; height: 17px; border-radius: 50%;
  background: #F2C230; box-shadow: inset 0 0 0 1px rgba(0,0,0,.35);
}
.suit.stripe {
  background: linear-gradient(#F5F1E6 0 27%, #F2C230 27% 73%, #F5F1E6 73% 100%);
}
.suit.open {
  background: none; box-shadow: inset 0 0 0 2px rgba(207, 230, 216, .5);
}
.suit.done { background: #1A1A1A; box-shadow: inset 0 0 0 1px rgba(255,255,255,.4); }
#soloBar { display: none; flex: 1; justify-content: space-between; align-items: center; }
#turnGroup { opacity: .7; font-weight: 500; }
#turnBar.solo .pcard { display: none; }
#turnBar.solo #soloBar { display: flex; }

/* ---- the tally ---- */
#status {
  position: absolute; inset: 0; display: none; align-items: center; justify-content: center;
  background: rgba(4, 22, 14, .78); padding: 20px; z-index: 45;
}
#status.on { display: flex; }
#status .card {
  display: flex; flex-direction: column; gap: 12px;
  background: #12382e; padding: 20px; border-radius: 18px;
  width: min(360px, 100%);
}
#statusTitle { font-size: 20px; font-weight: 700; text-align: center; }
#statusNote { font-size: 13px; opacity: .75; text-align: center; }
.srow {
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px; border-radius: 12px; background: rgba(0,0,0,.18);
}
.srow.on { background: #14402f; box-shadow: inset 0 0 0 2px #2fa06a; }
.shead { display: flex; align-items: center; gap: 7px; font-size: 14px; font-weight: 700; }
.shead .tag { margin-left: auto; font-weight: 600; opacity: .8; font-size: 12px; }
.sballs { display: flex; flex-wrap: wrap; gap: 5px; min-height: 26px; align-items: center; }
.sempty { font-size: 13px; opacity: .7; }
/* A numbered chip, built the way the table draws a ball: a coloured body, a
   white disc in the middle, the number on the disc. Stripes get the band.

   The size is not cosmetic. At 26px with a 43% number disc the disc spanned
   7.4-18.6px and the band 6.8-19.2px, so the stripe showed as two 0.6px
   slivers and a striped chip was very nearly a solid one. 32px with a 30%
   disc leaves the band visible around it — and the white CAPS, which are the
   signal that actually carries at this size, are 8px each. */
.chip {
  width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; color: #111;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.35);
  background:
    radial-gradient(circle at 50% 50%, #F5F1E6 0 30%, transparent 30%),
    var(--c);
}
.chip.stripe {
  background:
    radial-gradient(circle at 50% 50%, #F5F1E6 0 30%, transparent 30%),
    linear-gradient(#F5F1E6 0 26%, var(--c) 26% 74%, #F5F1E6 74% 100%);
}

/* No padding here, deliberately. fit() sizes the canvas from clientWidth,
   which INCLUDES padding, so any padding makes the canvas overflow its own
   content box — and then every screen->table coordinate is offset by exactly
   that much, which shows up as the cue ball refusing to be grabbed where it
   is drawn. Breathing room comes from the rail the table draws for itself. */
#tableWrap {
  flex: 1 1 auto; min-height: 0; position: relative;
  padding: 0;
}
#cv { display: block; }
#hint {
  position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%);
  background: rgba(4, 22, 14, .8); padding: 6px 12px; border-radius: 20px;
  font-size: 12px; opacity: .85; pointer-events: none; white-space: nowrap;
}

/* Aim / power / fire. One row, because three stacked rows would take the
   height back off the table — and the table is the game. */
/* R-C7: the padding rides on the shot controls, not on the footer, so the
   menu's chip-only state is 44px rather than 58px. Height follows contents. */
#footer { padding: 0 12px env(safe-area-inset-bottom); background: #0a2418; }
#shotBar {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 10px;
  padding: 6px 0;
}
#spinPad { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 2px; }
#spinCv { display: block; touch-action: none; cursor: pointer; width: 56px; height: 56px; }
#spinLabel { font-size: 10px; opacity: .7; line-height: 1; }

/* min-width:0 so the slider can actually shrink: a flex item's automatic
   minimum is its content, and a range input reports a wide intrinsic size. */
#powerWrap { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
#powerLabel { font-size: 11px; opacity: .75; }
#powerVal { opacity: 1; font-weight: 700; }

#powerRange {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 22px; background: transparent; touch-action: none; cursor: pointer;
}
#powerRange::-webkit-slider-runnable-track {
  height: 8px; border-radius: 4px;
  background: linear-gradient(90deg, #2fa06a, #ffd666 62%, #ff7a59);
}
#powerRange::-moz-range-track {
  height: 8px; border-radius: 4px;
  background: linear-gradient(90deg, #2fa06a, #ffd666 62%, #ff7a59);
}
/* 26px thumb: below ~24px a moving target is genuinely hard for small hands,
   and this one gets dragged on every single shot. */
#powerRange::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 26px; height: 26px; margin-top: -9px;
  border-radius: 50%; background: #eaf5ee; border: 3px solid #0a2418;
}
#powerRange::-moz-range-thumb {
  width: 26px; height: 26px;
  border-radius: 50%; background: #eaf5ee; border: 3px solid #0a2418;
}
#powerRange:focus-visible { outline: 2px solid #7fe0ac; outline-offset: 2px; }

.shoot {
  flex: 0 0 auto; min-width: 84px; min-height: 52px; padding: 0 16px;
  border: 0; border-radius: 14px; background: #2fa06a; color: #04160e;
  font-size: 16px; font-weight: 800; cursor: pointer;
}
.shoot:active { transform: translateY(1px); }
.shoot:disabled { opacity: .4; }

/* ---- overlay ---- */
#ovl {
  position: absolute; inset: 0; display: none; align-items: center; justify-content: center;
  background: rgba(4, 22, 14, .78); padding: 20px; z-index: 40;
}
#ovl.on { display: flex; }
#ovl .card {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  background: #12382e; padding: 22px; border-radius: 18px;
  width: min(360px, 100%); text-align: center;
}
#ovlTitle { font-size: 22px; font-weight: 700; }
#ovlSub { font-size: 14px; opacity: .8; white-space: pre-line; }

/* ---- toasts ---- */
#toast-host {
  position: fixed; left: 50%; transform: translateX(-50%);
  top: calc(54px + env(safe-area-inset-top));
  display: flex; flex-direction: column; gap: 6px; z-index: 50;
  pointer-events: none; width: max-content; max-width: 92vw;
}
.toast {
  background: rgba(4, 22, 14, .92); border: 1px solid rgba(47, 160, 106, .35);
  padding: 8px 14px; border-radius: 20px; font-size: 13px; text-align: center;
  opacity: 0; transform: translateY(-6px); transition: opacity .2s, transform .2s;
  pointer-events: auto;
}
.toast.in { opacity: 1; transform: none; }

/* A landscape phone has almost no vertical room, and the spin pad is the first
   thing that can afford to give some back. */
@media (max-height: 480px) {
  #spinCv { width: 44px; height: 44px; }
  #spinLabel { display: none; }
  #turnBar { padding: 3px 12px; font-size: 12px; }
  .shoot { min-height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  .toast { transition: none; }
}
