/* The shell. Colors all come from the --ui-* block in theme.css, so this file
   follows whatever palette you set there.

   Every tunable this file invents is a named custom property, gathered at the
   top of the block it belongs to. Retune the look here; nothing in shell.js
   reads a number that is not one of these. */

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  background: var(--ui-bg);
  color: var(--ui-text);
}

/* 2026-09-13: the `font:` shorthand used to sit on `html, body` together, which
   pinned the ROOT font-size to 14px. Two things were wrong with that. It threw
   away the reader's own browser text-size setting in this document — the shell
   is the only page that loaded this file, so the shell chrome was the one part
   of the app that could never be made bigger. And it made `rem` mean 14px here
   while it meant 16px inside every iframe (build/repertoire/lessons/... each
   set the font on `body` only), so the same rem value would have rendered at
   two different sizes on the same screen.

   Moving the shorthand down to `body` changes nothing visible — `html` has no
   text of its own and `body` still says 14px — but it lets the root scale with
   the reader's setting and makes 1rem = 16px everywhere in the app, which is
   the divisor every size in these files is now written against. */
body {
  display: flex;
  flex-direction: column;
  font: 0.875rem/1.5 var(--ui-font);   /* 14px at the default root size */
}

/* ── top bar ────────────────────────────────────────────────────────────── */

.shell-bar {
  display: flex;
  align-items: center;
  gap: 1.125rem;
  padding: 0 1rem;
  height: 2.875rem;
  flex: 0 0 auto;
  min-width: 0;
  background: var(--ui-panel);
  border-bottom: 1px solid var(--ui-line);
}

/* ── the mark ───────────────────────────────────────────────────────────────
   A black pawn in a purple cloud. There are two of every file — see
   tools/make-logo.js — because the pawn is black and this app is dark, so the
   dark theme gets the one with the pawn inverted. The swap is here rather than
   in the markup: a stylesheet already knows which theme is on, and doing it in
   script would flash the wrong one on every page load.

   `--logo-mark` is set on :root by theme.css, light and dark.

   A <span> with a background rather than an <img>, deliberately. `content:` on
   an <img> would do the same job in one line, but a browser that ignores it
   falls back to whatever `src` says — and whichever file that is, it is the
   wrong one on half the themes. A background-image has no wrong fallback: it
   either draws or it does not. */
.shell-mark {
  width: 1.625rem;
  height: 1.625rem;
  flex: 0 0 auto;
  background: var(--logo-mark) center / contain no-repeat;
}

.shell-brand {
  font-weight: 700;
  letter-spacing: 0.14em;
  font-size: 0.75rem;
  color: var(--ui-dim);
}

/* The wordmark is a label again, not a control. How To and About hung off it as
   a dropdown from 2026-09-04; they are in the drawer with everything else now,
   because two menus doing the same job is what "the nav has outgrown its
   space" actually looked like. */
.shell-wordmark {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex: 0 0 auto;
}

/* Bigger, and above the wordmark: on the sign-in screen this is the only thing
   on the page that says whose app it is. */
.signin-mark {
  width: 6rem;
  height: 6rem;
  background: var(--logo-mark) center / contain no-repeat;
}

.grow { flex: 1; }

/* ── the hamburger ──────────────────────────────────────────────────────────
   EVERY KNOB FOR THE TOGGLE AND THE DRAWER IS IN THIS ONE BLOCK.

   --drawer-breakpoint is the width at which the tab strip goes away and this
   button becomes the only navigation. It is written twice on purpose and is the
   only value in this file that is: CSS custom properties cannot be used inside
   a @media query, so the number here is documentation and the two @media rules
   further down are what the browser obeys. Change all three together — they are
   marked DRAWER BREAKPOINT so a search finds them. */
:root {
  --drawer-breakpoint: 900px;   /* also in the two @media rules below */

  --drawer-width: 19rem;
  --drawer-max-width: 86vw;
  --drawer-duration: 0.22s;     /* 0s under prefers-reduced-motion, see below */
  --drawer-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --drawer-bg: var(--ui-panel);
  --drawer-line: var(--ui-line);
  --drawer-shadow: 0 18px 48px rgba(0, 0, 0, 0.45);
  --drawer-scrim: rgba(0, 0, 0, 0.5);
  --drawer-pad: 0.625rem;
  --drawer-group-gap: 1.125rem;
  --drawer-item-height: 2.375rem;
  --drawer-item-gap: 2px;
  /* Above the panes and the drawn caret, below the sign-in card (50), which is
     the one thing allowed to cover this app. */
  --drawer-z: 45;

  --nav-toggle-size: 2.125rem;
  --nav-bar-width: 1.0625rem;
  --nav-bar-height: 2px;
  --nav-bar-gap: 0.3125rem;
  --nav-due-size: 0.4375rem;
}

/* An accessibility setting, not a style choice — theme.css says the same about
   its own animations. Retune --drawer-duration above; leave this alone. */
@media (prefers-reduced-motion: reduce) {
  :root { --drawer-duration: 0s; }
}

.navtoggle {
  position: relative;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: var(--nav-toggle-size);
  height: var(--nav-toggle-size);
  padding: 0;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--ui-radius);
  color: var(--ui-dim);
  cursor: pointer;
}
/* ── why every :hover in this app is wrapped, 2026-09-13 ──────────────────
   This is the first of them, so the explanation lives here; the other sixty-odd
   across the ten stylesheets are the same wrapper and nothing else.

   A touch browser has no pointer to take away, so it fakes one: it applies
   :hover on tap and leaves it applied until you touch something else. That is
   the "why is that button still highlighted" bug — the last thing tapped keeps
   an accent border for the rest of the session. `@media (hover: hover)` says
   "only where a real pointer can hover", which is the fix, and it changes
   nothing at all on a mouse.

   Two rules when you add a hover rule from here on:

   1. Do not put :hover and :focus-visible in the same selector list and wrap
      the pair. The guard would take the keyboard's focus ring away too. Split
      them — see `.promobtn` in repertoire.css.
   2. A guard is not a fix for a rule that REVEALS something. If the only way
      to see a control is to hover it, guarding the rule just states in CSS
      that touch users cannot have it. Give those a second rule under
      `@media (hover: none)` — see `.course .foldertools` in repertoire.css,
      which is the one place in this app that needed it. */
@media (hover: hover) { .navtoggle:hover { border-color: var(--ui-line); color: var(--ui-text); } }
.navtoggle[aria-expanded="true"] { border-color: var(--ui-line); color: var(--ui-text); }
.navtoggle:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 1px; }

/* Three bars drawn rather than typed. "☰" renders at a different size and
   baseline in every font this app might fall back to — the same reason the old
   caret was drawn with borders. */
.navtoggle-bars {
  position: relative;
  display: block;
  width: var(--nav-bar-width);
  height: var(--nav-bar-height);
  background: currentColor;
  border-radius: 1px;
}
.navtoggle-bars::before,
.navtoggle-bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: 100%;
  background: currentColor;
  border-radius: inherit;
}
.navtoggle-bars::before { top: calc(-1 * var(--nav-bar-gap)); }
.navtoggle-bars::after { top: var(--nav-bar-gap); }

/* Something is due for review, said on the closed hamburger. Below the
   breakpoint the tab badge and the drawer badge are both behind this button, and
   a review you have to open a menu to discover is a review you miss. */
.navtoggle-due {
  position: absolute;
  top: 0.1875rem;
  right: 0.1875rem;
  width: var(--nav-due-size);
  height: var(--nav-due-size);
  border-radius: 50%;
  background: var(--due-now);
}

/* ── the drawer ─────────────────────────────────────────────────────────── */

.navscrim {
  position: fixed;
  inset: 0;
  z-index: calc(var(--drawer-z) - 1);
  background: var(--drawer-scrim);
  opacity: 0;
  transition: opacity var(--drawer-duration) var(--drawer-ease);
}
.navscrim.is-open { opacity: 1; }

.navdrawer {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: var(--drawer-z);
  display: flex;
  flex-direction: column;
  width: min(var(--drawer-width), var(--drawer-max-width));
  background: var(--drawer-bg);
  border-right: 1px solid var(--drawer-line);
  box-shadow: var(--drawer-shadow);
  transform: translateX(-100%);
  transition: transform var(--drawer-duration) var(--drawer-ease);
}
.navdrawer.is-open { transform: translateX(0); }

/* `hidden` alone loses to `display: flex`, which is the bug that kept an empty
   "Lines built" panel on the Openings & Middlegames screen for a fortnight.
   Said here on purpose rather than relied upon — and it matters more here than
   anywhere, because a drawer that is merely off-screen is still tabbable. */
.navdrawer[hidden],
.navscrim[hidden] { display: none !important; }

.navdrawer-head {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex: 0 0 auto;
  padding: var(--drawer-pad) var(--drawer-pad) var(--drawer-pad) 0.875rem;
  border-bottom: 1px solid var(--drawer-line);
}
.navdrawer-head .grow { flex: 1; }

.navclose {
  flex: 0 0 auto;
  width: var(--nav-toggle-size);
  height: var(--nav-toggle-size);
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--ui-radius);
  color: var(--ui-dim);
  font: 1.125rem/1 var(--ui-font); /* 18px */
  cursor: pointer;
}
@media (hover: hover) { .navclose:hover { border-color: var(--ui-line); color: var(--ui-text); } }
.navclose:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 1px; }

.navdrawer-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--drawer-pad);
}

.navgroup { margin-bottom: var(--drawer-group-gap); }
.navgroup:last-child { margin-bottom: 0; }

.navgroup-title {
  margin: 0 0 0.375rem 0.625rem;
  font: 700 max(0.65625rem, var(--text-min))/1.6 var(--ui-font); /* 10.5px */
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ui-dim);
  opacity: 0.8;
}

.navgroup-items {
  display: flex;
  flex-direction: column;
  gap: var(--drawer-item-gap);
}

.navitem {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  min-height: var(--drawer-item-height);
  padding: 0 0.625rem;
  text-align: left;
  background: none;
  border: none;
  /* A left marker rather than a bottom border: the tab strip reads horizontally
     and this reads vertically, and a 2px underline on a list item looks like a
     mistake. */
  border-left: 2px solid transparent;
  border-radius: calc(var(--ui-radius) - 1px);
  color: var(--ui-dim);
  font: inherit;
  font-size: 0.84375rem; /* 13.5px */
  cursor: pointer;
}
@media (hover: hover) { .navitem:hover { background: var(--ui-btn); color: var(--ui-text); } }
.navitem.is-active {
  color: var(--ui-text);
  border-left-color: var(--ui-accent);
  background: var(--ui-btn);
  font-weight: 600;
}
.navitem:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: -2px; }

/* Same treatment as the tab strip: announced, visibly not pressable, saying
   why. See `data-coming-soon` in shell.js, which is what refuses to open it. */
.navitem.is-soon { opacity: 0.55; cursor: default; }
@media (hover: hover) { .navitem.is-soon:hover { background: none; color: var(--ui-dim); } }

/* A badge or a dot in a drawer row sits at the right-hand end, not against the
   label. */
.navitem .duebadge,
.navitem .dot,
.navitem .soon { margin-left: auto; }

/* ── tabs ───────────────────────────────────────────────────────────────── */

.tabs {
  display: flex;
  gap: 0.25rem;
  height: 100%;
  align-items: stretch;
  /* The strip takes the leftover width and is its own horizontal scroller, so
     nothing can be pushed past the right-hand edge of the window — which is
     what happened to Sign out the day the seventh and eighth tabs arrived. That
     ladder of "what do we give up next" is retired; the drawer is the answer on
     a narrow screen and the scroller is the answer in between. */
  flex: 1 1 0;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}

.tab {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  flex: 0 0 auto;
  white-space: nowrap;
  background: none;
  border: none;
  /* The active marker is a bottom border that lines up with the bar's own
     border, so the selected tab reads as connected to the pane below. A
     scroller clips at its own edge, so the 1px by which it used to bleed past
     the bar is not spent — the marker sits on top of the border, not over it. */
  border-bottom: 2px solid transparent;
  margin-bottom: 0;
  color: var(--ui-dim);
  font: inherit;
  font-size: 0.84375rem; /* 13.5px */
  padding: 0 0.5625rem;
  cursor: pointer;
}

@media (hover: hover) { .tab:hover { color: var(--ui-text); } }

.tab.is-active {
  color: var(--ui-text);
  border-bottom-color: var(--ui-accent);
  font-weight: 600;
}

/* A tab that is announced but switched off — the Binance Pay treatment, on the
   tab bar: still there, visibly not pressable, and saying why. */
.tab.is-soon { opacity: 0.55; cursor: default; }
@media (hover: hover) { .tab.is-soon:hover { color: var(--ui-dim); } }
.tab .soon,
.navitem .soon {
  font-size: max(0.625rem, var(--text-min)); /* 10px */
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 1px 0.3125rem;
  border: 1px solid var(--ui-dim);
  border-radius: 62.4375rem;
  white-space: nowrap;
}

/* Traffic light on the App tab: is the app process reachable? */
.dot {
  width: 0.4375rem;
  height: 0.4375rem;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--ui-dim);
  transition: background 0.2s linear;
}

.dot[data-state="up"] { background: var(--status-up); }
.dot[data-state="down"] { background: var(--status-down); }

/* ── header tools ───────────────────────────────────────────────────────── */

.tools {
  display: flex;
  gap: 0.5625rem;
  align-items: center;
  flex: 0 0 auto;
  min-width: 0;
}

/* ── panes ──────────────────────────────────────────────────────────────── */

.panes {
  flex: 1 1 auto;
  position: relative;
  min-height: 0;
}

.pane {
  position: absolute;
  inset: 0;
  /* Hidden rather than unmounted: switching tabs must not reload the board or
     throw away a run log that is scrolling in the app. */
  visibility: hidden;
  opacity: 0;
}

.pane.is-active {
  visibility: visible;
  opacity: 1;
}

.pane iframe {
  width: 100%;
  height: 100%;
  border: none;
  display: block;
  background: var(--ui-bg);
}

/* The browser's own [hidden] rule is display:none, but ANY display declaration
   in a stylesheet beats it — which is how a card with display:grid stayed on
   screen for hours while its .hidden property read true. Restate it as a
   backstop so setting .hidden always actually hides. */
[hidden] { display: none !important; }

/* ── narrowing down ──────────────────────────────────────────────────────── */

/* The wordmark goes, the mark stays. It is 26px against the wordmark's 124 and
   it says the same thing. */
@media (max-width: 1240px) {
  .shell-brand { display: none; }
  /* Not in the drawer, where there is room for it and it is the only thing
     saying whose menu this is. */
  .navdrawer-head .shell-brand { display: inline; }
}

/* DRAWER BREAKPOINT — keep in step with --drawer-breakpoint at the top.
   Under this width the tab strip goes away entirely and the hamburger is the
   navigation. The strip was a sideways scroller of eight entries on a phone,
   which is a list you cannot see the end of; the drawer groups the same eight
   and shows all of them at once. */
@media (max-width: 900px) {
  .shell-bar > .tabs { display: none; }
  .shell-bar { gap: 0.625rem; }
}

/* DRAWER BREAKPOINT — the other half. Above it the hamburger is still offered,
   because the grouped menu is the only place How To, About, Announcements and
   Admin sit together, but the due dot on it would be a second copy of a badge
   already on the Repertoire tab. */
@media (min-width: 901px) {
  .navtoggle-due { display: none; }
}

/* Below this the username goes and Sign out stands alone. Sign out is the one
   control that must survive, because an account you cannot get out of is a trap
   on a shared machine. */
@media (max-width: 620px) {
  .accountwho { display: none; }
}

/* ── the sign-in screen ──────────────────────────────────────────────────── */

/* Off by default in the stylesheet, on only by class. The one card that ever
   covers this app must not be able to do it by accident — see the coverage
   checks in test-shell.html. */
.signin { display: none; }

.signin.on {
  display: grid;
  place-items: center;
  position: fixed;
  inset: 0;
  background: var(--ui-bg);
  z-index: 50;
}

.signin-card {
  display: grid;
  justify-items: center;
  gap: 0.875rem;
  padding: 2rem 2.5rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-panel);
  text-align: center;
}

.signin-card p { margin: 0; color: var(--ui-dim); font-size: 0.875rem; /* 14px */ }

/* The card is the first thing anybody sees, and until 2026-09-01 it said "Sign
   in to continue." — which tells somebody who has just arrived nothing about
   what they would be signing in to. */
.signin-title {
  margin: 0;
  font: 700 1.25rem/1.3 var(--ui-font);
  color: var(--ui-text);
  max-width: 22ch;
}
.signin-sell { max-width: 40ch; line-height: 1.6; }

.signin-note { font-size: 0.75rem; max-width: 34ch; }
.signin-note.bad { color: var(--learn-wrong); }
.signin-note[hidden] { display: none; }

/* Terms and privacy, which have to be readable BEFORE there is an account —
   so they are links out of this card, and /about.html is not behind the gate. */
.signin-legal {
  font-size: max(0.71875rem, var(--text-min)); /* 11.5px */
  max-width: 40ch;
  line-height: 1.7;
  opacity: .75;
}
.signin-legal a { color: var(--ui-dim); text-decoration: underline; }
@media (hover: hover) { .signin-legal a:hover { color: var(--ui-text); } }

/* The operator's way back in, on a local server only. The sentence is built in
   shell.js rather than written in the markup — see paintSignInNotes. */
.signin-note.local { opacity: .6; }

/* When the sign-in screen is up, the chrome behind it is not merely covered —
   it is gone, so nothing behind can take focus or be tabbed into. The toggle
   and the drawer are on that list: a menu that opens over the sign-in card
   would be a way to walk around the gate with the Tab key. */
body.signed-out .panes,
body.signed-out .tabs,
body.signed-out .tools,
body.signed-out .navtoggle,
body.signed-out .navdrawer,
body.signed-out .navscrim { display: none; }

/* The Repertoire tab's "due now" count. Hidden entirely at zero rather than
   shown as a 0 — a badge that is always there stops being a signal. */
.duebadge {
  min-width: 1.0625rem;
  padding: 0 0.3125rem;
  border-radius: 0.5625rem;
  background: var(--due-now);
  color: var(--ui-accent-text);
  font: 700 max(0.625rem, var(--text-min))/1.0625rem var(--ui-mono); /* 10px */
  text-align: center;
}
