/* Loose Ends — table, controls and dialogs.
   Sized up from the card games' chrome for the same reason jigsaw was: the
   people this is for are mostly over seventy, often on a laptop trackpad, and a
   0.92rem button they have to aim at is the difference between a game and a
   chore. Nothing here is smaller than 1rem or shorter than 2.6rem. */

.loose-ends-page {
  margin: 0;

  /* The table. Unlike jigsaw — which forces a dark surround because a
     photograph is what the eye should be drawn to — there is no picture to
     protect here, so the board simply follows whichever of the eight themes is
     on. board.js reads these through getComputedStyle, fresh every frame, so
     dragging a colour in the theme editor repaints the board live. */
  --le-board: var(--surface-alt);
  --le-grid: var(--rule);

  /* The six line colours.

     Fixed values rather than theme tokens, and deliberately so. Derived
     colours would collide the moment a theme's accent landed near its info
     colour — the teal theme would have handed us two teals — and a game where
     two lines look alike is a broken game, whereas a game whose lines do not
     match the site's hue is merely a game.

     Spread right around the wheel: blue, green, gold, purple, pink,
     terracotta. An earlier set had a teal in it, wedged between the blue and
     the green, and on a packed board the three of them were genuinely hard to
     tell apart — which is the one thing this palette has to get right.

     No red. Red is what a blocked line's culprit flashes, and a palette that
     also contained one would make the single most important signal on the
     board ambiguous.

     Two flat sets rather than one light-dark() pair, and the reason is that
     board.js reads these with getComputedStyle. A custom property computes to
     its token stream, NOT to a resolved colour — so light-dark(a, b) comes back
     to JavaScript as the literal string "light-dark(a, b)", which canvas cannot
     parse and silently ignores, leaving every line painted in whatever colour
     happened to be set last. CSS would have been perfectly happy; the canvas
     would not.

     So the dark set gets its own names and board.js chooses between them off
     the computed color-scheme — which every theme in siteTheme-Tokens.css sets,
     and which resolves properly because it is a real property rather than a
     custom one. Still no list of theme names to maintain.

     And because colour carries no rule whatsoever — a blue line blocks a green
     one exactly as a grey line blocks a grey one — none of this is information
     a colour-blind player can be denied. It is paint. */
  --le-line-1: #2E6FA8;   /* blue       */
  --le-line-2: #3E8B4A;   /* green      */
  --le-line-3: #B98A1B;   /* gold       */
  --le-line-4: #7A4E9E;   /* purple     */
  --le-line-5: #B3417A;   /* pink       */
  --le-line-6: #C2593C;   /* terracotta */

  --le-line-1-dark: #6FA3D4;
  --le-line-2-dark: #6FBE72;
  --le-line-3-dark: #D4B057;
  --le-line-4-dark: #A98BC8;
  --le-line-5-dark: #D97CAA;
  --le-line-6-dark: #DE8A6E;
}

/* Full-height chain, as the card games and jigsaw do it: the stage takes
   whatever is left after the header, nav, bars and footer. min-height:0 on the
   flex children is what lets them shrink instead of shoving the footer off. */
.loose-ends-page .Master {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.loose-ends-page .Master > section.le-shell {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 10px 10px;
}

.loose-ends-page footer {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 8px;
  padding: 2px 10px;
  margin: 0;
}

.loose-ends-page .build-tag,
.loose-ends-page .page-hits {
  font-family: 'Trebuchet MS', 'Segoe UI', sans-serif;
  font-size: var(--footer-size);
  letter-spacing: 0.04em;
  opacity: 0.45;
  margin: 0;
}

.loose-ends-page .page-hits::before { content: '· '; }


/* ------------------------------------------------------------- the bars

   Two rows on purpose, the same split jigsaw settled on. The top row is what
   you change between boards; the bottom row is what you use while playing one.
   Keeping them apart means the buttons reached for every minute never move
   because a level name got longer. */

.le-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--surface);
}

.le-bar button,
.le-bar select,
.le-bar input {
  font-family: inherit;
  font-size: 1rem;
  min-height: 2.6rem;
  padding: 0 0.85rem;
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
  background: var(--button-bg, var(--surface));
  color: var(--button-ink, var(--ink));
  cursor: pointer;
}

.le-bar input { cursor: text; }

.le-bar button:hover:not(:disabled),
.le-bar select:hover {
  background: var(--button-hover-bg, var(--accent-soft));
  border-color: var(--accent);
}

.le-bar button:disabled {
  color: var(--button-disabled-ink, var(--ink-faint));
  cursor: default;
  opacity: 0.55;
}

.le-primary {
  background: var(--accent) !important;
  border-color: var(--accent) !important;
  color: var(--on-accent) !important;
  font-weight: 700;
}

.le-primary:hover:not(:disabled) { filter: brightness(1.12); }

.le-field {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.le-seed { width: 7.5rem; font-variant-numeric: tabular-nums; }

.le-group {
  display: flex;
  gap: 4px;
  padding-left: 8px;
  border-left: 1px solid var(--rule);
}

.le-group button { min-width: 2.9rem; padding: 0 0.6rem; }

/* Pushed to the far end so the way out of full screen is always in the same
   corner, whichever row wrapped. */
.le-fs { margin-left: auto; }
.le-fs.fs-on {
  background: var(--accent) !important;
  border-color: var(--accent) !important;
  color: var(--on-accent) !important;
}

.le-menu {
  padding: 6px;
  border: 1px solid var(--rule-strong);
  border-radius: 10px;
  background: var(--raised, var(--surface));
  color: var(--ink);
  box-shadow: 0 6px 20px var(--shadow, rgba(0, 0, 0, 0.25));
}

.le-menu button,
.le-menu__link {
  display: block;
  width: 100%;
  min-height: 2.6rem;
  padding: 0 0.9rem;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  text-decoration: none;
  line-height: 2.6rem;
  cursor: pointer;
}

.le-menu button:hover,
.le-menu__link:hover { background: var(--accent-soft); }

.le-menu__rule { border: 0; border-top: 1px solid var(--rule); margin: 5px 2px; }


/* ------------------------------------------------------------- readout */

.le-readout {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 18px;
  margin: 0;
  padding: 0 4px;
  font-size: 0.95rem;
  color: var(--ink-muted);
}

.le-readout__item {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
}

.le-readout b {
  font-size: 1.15rem;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Free right now is the number that actually says how hard a board is, so it
   is the one allowed to shout. */
.le-readout__free b { color: var(--accent); }

.le-readout__par {
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}


/* --------------------------------------------------------------- stage */

.le-stage {
  position: relative;
  flex: 1;
  /* The board is the only thing on this page that flexes, so on a short window
     it was handed whatever the two bars and the readout left over — which on a
     463px-tall window was about 160px, and a board squeezed into 160px is a
     board nobody can play. It used to be min-height:0, which let it collapse
     to exactly that.

     A floor instead. Below this the page scrolls, which is a far better
     outcome than a sliver of board: the bars go off the top, the board stays
     a usable size, and full screen is right there for anybody who wants the
     whole window back. */
  min-height: 17rem;
  border: 1px solid var(--rule-strong);
  border-radius: 12px;
  background: var(--ground);
  overflow: hidden;
}

.le-stage canvas {
  /* Absolute, not height:100%.

     The stage is a flex item sized by flex-grow and min-height, so its own
     `height` stays auto — and a percentage height resolves against the
     parent's height property, not against the box flex actually gave it. So
     height:100% quietly did nothing, the canvas fell back to the pixel height
     in its width/height attributes, and board.js then measured THAT and wrote
     it straight back. A 150px canvas nailed itself to 150px inside a 272px
     stage and stayed there.

     inset:0 has no such dependency: it sizes to the padding box whatever gave
     the parent its size. */
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  /* Every gesture on the board is ours — a browser that decides a drag was a
     scroll makes the board unplayable on a phone. */
  touch-action: none;
}

.le-stage canvas:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
}

/* The tutorial's running commentary. A band under the board, not a panel over
   it: as an overlay it covered the very cells it was talking about. */
.le-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
  padding: 11px 14px;
  border: 1px solid var(--accent-line, var(--rule-strong));
  border-left: 4px solid var(--accent);
  border-radius: 10px;
  background: var(--accent-soft);
}

/* Hidden means hidden. display:flex above outranks the browser's own rule for
   the hidden attribute, so main.js hiding the note on a real board left it on
   the screen: an empty band under every board, and the tutorial's last words
   carried on into the next one. */
.le-note[hidden] { display: none; }

.le-note__words { flex: 1 1 18rem; min-width: 0; }

.le-note h2 {
  margin: 0;
  font-size: 1.02rem;
  font-family: 'Trebuchet MS', 'Segoe UI', sans-serif;
}

.le-note p { margin: 2px 0 0; font-size: 0.95rem; }
.le-note button { min-height: 2.6rem; padding: 0 1rem; border-radius: 8px; border: 0; font: inherit; cursor: pointer; }


/* ------------------------------------------------------------- dialogs */

.le-dialog {
  width: min(32rem, calc(100vw - 32px));
  padding: 0 18px 18px;
  border: 1px solid var(--rule-strong);
  border-radius: 12px;
  background: var(--raised, var(--surface));
  color: var(--ink);
}

.le-dialog::backdrop { background: var(--backdrop, rgba(0, 0, 0, 0.55)); }

.le-dialog__head {
  display: flex;
  align-items: center;
  gap: 12px;
  position: sticky;
  top: 0;
  padding: 14px 0 10px;
  background: inherit;
  border-bottom: 1px solid var(--rule);
}

.le-dialog__head h2 { flex: 1; margin: 0; font-size: 1.25rem; }

.le-dialog__head button {
  min-width: 2.6rem;
  min-height: 2.6rem;
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font-size: 1.15rem;
  cursor: pointer;
}

.le-dialog__note {
  margin: 14px 0 0;
  font-size: 0.85rem;
  color: var(--ink-faint);
}

.le-dialog__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
}

.le-dialog__actions button {
  flex: 1 1 8rem;
  min-height: 2.8rem;
  padding: 0 1rem;
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.le-dialog__actions button:hover { background: var(--accent-soft); }

/* The finished dialog has no close cross in its corner — it has three buttons
   along the bottom instead — so its heading stands on its own. */
.le-dialog__title {
  margin: 16px 0 0;
  font-size: 1.25rem;
}

.le-done__headline {
  margin: 16px 0 4px;
  font-size: 1.1rem;
  font-weight: 700;
  text-wrap: balance;
}

.stats-list,
.le-done__detail {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0;
  margin: 12px 0 0;
}

.stats-list dt,
.le-done__detail dt {
  padding: 7px 0;
  border-bottom: 1px solid var(--rule);
  color: var(--ink-muted);
}

.stats-list dd,
.le-done__detail dd {
  margin: 0;
  padding: 7px 0;
  border-bottom: 1px solid var(--rule);
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

#stats-body h3 {
  margin: 20px 0 0;
  font-size: 1rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
}


/* ---------------------------------------------------------- full screen

   Only .le-shell is handed to requestFullscreen, so the bars — and with them
   the way back out — go full screen along with the board. */

.le-shell:fullscreen,
.le-shell.is-focus {
  padding: 10px 12px 12px;
  background: var(--ground);
}


/* ----------------------------------------------------------- short windows

   A laptop window dragged down to a few hundred pixels, or a phone held
   sideways. Width is not the problem here — height is, and the chrome is what
   is eating it, so the chrome gets out of the way rather than the board.

   Everything below only trims: nothing is hidden that cannot be worked out
   from what is left, and every control keeps a target big enough to hit. The
   uppercase words beside the pickers go, because a select showing "Normal —
   11 × 11" does not need the word SIZE in front of it to be understood. */

@media (max-height: 700px) {
  .loose-ends-page .Master > section.le-shell { gap: 6px; padding: 6px 8px 8px; }

  .le-bar { padding: 5px 7px; gap: 6px; }
  .le-bar button,
  .le-bar select,
  .le-bar input { min-height: 2.25rem; padding: 0 0.7rem; }

  .le-field { font-size: 0; gap: 0; }
  .le-field select,
  .le-field input,
  .le-field button { font-size: 0.95rem; }

  .le-readout { font-size: 0.85rem; gap: 2px 14px; }
  .le-readout b { font-size: 1rem; }

  .le-note { padding: 8px 11px; }
  .le-note p { font-size: 0.88rem; }

  .loose-ends-page footer { padding: 0 10px; }
}


/* ---------------------------------------------------------- small screens

   Below this the two bars would wrap to four rows and leave the board a couple
   of inches tall, which is worse than losing a few labels. The readout stays:
   it is the only thing that says how close the board is to being finished. */

@media (max-width: 760px) {
  .loose-ends-page .Master > section.le-shell { padding: 6px 7px 8px; gap: 6px; }

  .le-bar { gap: 6px; padding: 6px; }
  .le-bar button, .le-bar select, .le-bar input { padding: 0 0.6rem; }

  /* The words beside each picker go, not the pickers. font-size:0 collapses
     the label text; every control inside has to be given its size back or the
     puzzle-number box disappears along with the word in front of it. */
  .le-field { font-size: 0; gap: 0; }
  .le-field select,
  .le-field input,
  .le-field button { font-size: 1rem; }

  .le-group { padding-left: 6px; }
  .le-fs { margin-left: 0; }

  .le-readout { gap: 2px 12px; font-size: 0.88rem; }
}

/* ======================================================================
   HANDHELD MODE

   css/games/handheld.css lays the shell out: the numbers along the top
   (.hh-top), then the board, the tutorial's note and the dock (.hh-main),
   and the screens before play over the board. These are Loose Ends' own
   parts of it. The bars above stay in this file: admin/mazeEditor.html
   still uses them.
   ====================================================================== */

/* No floor under the board in app view: the shell is the whole window, and
   the board takes what the numbers, the note and the dock leave it. */
.hh-app .le-stage { min-height: 0; }

.hh-app .le-readout {
  flex: none;
  justify-content: center;
}

/* A screen before play is a page of its own: no frame round it, and no
   tutorial note under it. */
.hh-app:has(.ar-screen:not([hidden])) .le-stage { border-color: transparent; }
.hh-app:has(.ar-screen:not([hidden])) .le-note { display: none; }

/* Zoom, in the board's corner, the way a map has it. */
.le-zoom {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.le-zoom button {
  width: 2.9rem;
  height: 2.9rem;
  padding: 0;
  border: 1px solid var(--rule-strong);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink);
  font-size: 1.05rem;
  box-shadow: 0 2px 8px var(--shadow, rgba(0, 0, 0, 0.2));
  cursor: pointer;
  touch-action: manipulation;
}

.le-zoom button:hover { border-color: var(--accent); }

/* How to play: the one dialog that is mostly words. */
.le-rules { margin: 4px 0 0; }

.le-rules h3 {
  margin: 16px 0 4px;
  font-size: 1rem;
  font-family: 'Trebuchet MS', 'Segoe UI', sans-serif;
  color: var(--accent);
}

.le-rules p { margin: 0 0 6px; line-height: 1.5; }

/* The board's focus ring is for a keyboard, and a phone has none. */
@media (pointer: coarse) {
  .hh-app .le-stage canvas:focus-visible { outline: none; }
}

/* A phone: the par goes from the line of numbers (the cleared board's
   dialog says it), and the tutorial's note is a little tighter. */
@media (pointer: coarse) and (max-width: 720px) {
  .hh-app .le-readout__par { display: none; }

  .hh-app .le-note { padding: 8px 11px; }
  .hh-app .le-note p { font-size: 0.9rem; }
}
