/* Shared site chrome (header/nav/footer) + base layout. One copy, one place
   to change, instead of duplicating this block into every page. */

/* ---- Theme tokens ------------------------------------------------------
   :root is the LIGHT theme: it is the one the site is designed for, and it is
   what a visitor gets by default and what shows if theme.js never runs.
   :root[data-theme="dark"] below is the alternative, reached only through the
   toggle. It wins by specificity wherever the attribute is set, so the order
   of the two blocks in this file does not matter.

   This used to be the other way round - dark in the plain :root - which meant
   a page that lost theme.js fell back to a theme nobody designs for any more.

   Pages must reference these tokens instead of hex literals: a raw #2a2a2a in
   a page is a square that stays dark when the rest of the site turns light. */
:root {
  color-scheme: light;

  --bg: #FAFAF8;
  --surface: #FFFFFF;
  --panel: #FFFFFF;
  --panel-alt: #F3F2EE;
  --panel-alt-hover: #E9E7E1;
  --sunken: #F7F6F2;
  --sunken-alt: #FCFCFA;
  --sunken-hover: #EFEDE7;
  --track: #E8E6E0;

  --border: #E5E3DD;
  --border-strong: #D4D1C9;
  --border-mid: #DEDBD4;
  --border-faint: #F0EEE9;
  --fill-muted: #CFCCC4;
  --fill-neutral: #8E8B84;
  --shadow: rgba(60, 50, 30, 0.10);
  --shadow-warm: rgba(146, 92, 22, 0.12);

  --text: #3C3A37;
  --text-bright: #22211F;
  --text-soft: #4A4845;
  --text-dim: #5E5B57;
  --text-muted: #8A8781;
  --text-faint: #ABA8A1;
  --text-ghost: #C9C6BF;

  --accent: #F97B16;
  --accent-edge: #CB5D07;
  --accent-hover: #FF8C2E;
  --accent-deep: #A94B03;
  --accent-link: #C65D08;
  --accent-fg: #FFFFFF;
  --accent-tint: #FFF0E0;
  --accent-tint-fg: #9A4A07;
  --accent-glow: rgba(249, 123, 22, 0.4);
  --accent-glow-fade: rgba(249, 123, 22, 0);

  --second: #14B8A6;
  --second-edge: #0B8A7C;
  --second-hover: #19CFBB;
  --second-deep: #086258;
  --second-link: #0B8A7C;
  --second-fg: #FFFFFF;
  --second-tint: #E0F7F4;
  --second-tint-fg: #0A6B60;

  --good: #58CC02;
  --good-edge: #46A302;
  --good-text: #3E8E02;
  --good-bright: #58CC02;
  --good-tint: #EDFADF;
  --good-tint-fg: #3B7D06;
  --good-border: #BCE79A;

  --bad: #FF4B4B;
  --bad-edge: #DC2F2F;
  --bad-text: #D83A3A;
  --bad-tint: #FFECEC;
  --bad-tint-fg: #B82B2B;
  --bad-border: #FFC2C2;

  --warn: #C79000;
  --warn-bright: #FFC800;
  --warn-fg: #4A3703;
  --warn-edge: #D9A400;
  --warn-tint: #FFF7DC;
  --warn-tint-fg: #8A6400;
  --warn-border: #F2DE9B;

  /* A fifth colour, for a category that is none of the above. Verdicts own
     green, red and yellow, so a palette with only orange and teal left runs
     out of ways to tell six things apart. */
  --plum: #CE82FF;
  --plum-edge: #A855F7;
  --plum-tint: #F6ECFF;
  --plum-tint-fg: #7A3BB5;
}

/* The dark theme. Kept working and kept in step, but no longer the theme
   decisions are made against. */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* Warm charcoal, not neutral grey: the same hue family as the cream in the
     light theme, so the two themes read as one site at different times of day. */
  --bg: #15120F;             /* page background */
  --surface: #1C1813;        /* header/footer chrome */
  --panel: #211C16;          /* cards sitting on the page background */
  --panel-alt: #2C251C;      /* controls on a card: buttons, tags, chips */
  --panel-alt-hover: #382F23;
  --sunken: #191511;         /* inset blocks: code, nested rows, banners */
  --sunken-alt: #241F18;     /* list rows, and the hover state of a panel */
  --sunken-hover: #2E2719;
  --track: #332B20;          /* the empty part of a progress/score bar */

  --border: #2E2820;
  --border-strong: #463C2E;  /* input and button outlines */
  --border-mid: #393127;
  --border-faint: #262019;   /* table rules */
  --fill-muted: #463C2E;     /* disabled fills, inactive dots */
  --fill-neutral: #7A7064;   /* a bar fill with no verdict attached */
  --shadow: rgba(0, 0, 0, 0.45);
  --shadow-warm: rgba(0, 0, 0, 0.5);

  /* Text, brightest to faintest. Body copy is --text; anything below
     --text-muted is for metadata that should recede. */
  --text: #F3EEE6;
  --text-bright: #FFFFFF;
  --text-soft: #E2DACE;
  --text-dim: #C6BCAD;
  --text-muted: #9A9084;
  --text-faint: #786F63;
  --text-ghost: #564F45;

  /* Orange. --accent is the fill; --accent-link is the same idea as text,
     pushed brighter because orange type on a dark ground loses more than an
     orange fill does. */
  --accent: #F0801E;
  --accent-hover: #FF9538;
  --accent-deep: #C25C08;    /* the dark end of an accent gradient */
  --accent-link: #FBA75A;
  --accent-fg: #1B0F03;      /* text on an accent fill */
  --accent-tint: #33210F;    /* accent as a quiet background */
  --accent-tint-fg: #FBB878;
  /* The expanding ring on a highlighted card; the -fade pair is the same
     colour at zero alpha, because a gradient/shadow to transparent must not
     pass through black on the way out. */
  --accent-edge: #B85A0A;
  --accent-glow: rgba(240, 128, 30, 0.5);
  --accent-glow-fade: rgba(240, 128, 30, 0);

  /* Teal, the secondary. Same five roles as the accent, so anything built on
     one can be re-skinned to the other by swapping the prefix. */
  --second: #19A89A;
  --second-edge: #0C6F65;
  --second-hover: #22C3B2;
  --second-deep: #0B7268;
  --second-link: #5EEAD4;
  --second-fg: #04201D;
  --second-tint: #10302D;
  --second-tint-fg: #6FE3D2;

  --good: #58CC02;
  --good-edge: #3C8A02;
  --good-text: #7BD43C;
  --good-bright: #34D399;
  --good-tint: #1B3A1F;
  --good-tint-fg: #93E39B;
  --good-border: #3D6B3F;

  --bad: #FF4B4B;
  --bad-edge: #B52E2E;
  --bad-text: #FF8686;
  --bad-tint: #44201E;
  --bad-tint-fg: #FF9A9A;
  --bad-border: #6B3D3D;

  /* A true yellow, not amber: amber beside an orange accent is one colour. */
  --warn: #F2C319;
  --warn-edge: #B38C06;
  --warn-bright: #FFD84D;
  --warn-fg: #4A3703;
  --warn-tint: #332B10;
  --warn-tint-fg: #E8CE72;
  --warn-border: #6B5D20;

  --plum: #C078F0;
  --plum-edge: #7C3FAF;
  --plum-tint: #2C1E3A;
  --plum-tint-fg: #D9A9FF;
}

/* ---- Type ---------------------------------------------------------------
   Two families. Baloo 2 is the display face: chunky, rounded and built for
   large sizes, which is what gives the headings and the uppercase buttons
   their character. Nunito carries everything else, because Baloo 2 at 13px
   body copy loses its definition and gets tiring to read - that is the whole
   reason this is a pair rather than one family doing both jobs.

   Baloo 2 stops at weight 800, so nothing on a display element may ask for
   more than that or the browser fakes it and the letterforms go lumpy. The
   heaviest display weight in use is .pressBtn at 800.

   The system stack sits behind both as the fallback while the web fonts load
   or if they never arrive. */
:root {
  --font-body: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-display: 'Baloo 2', 'Nunito', -apple-system, BlinkMacSystemFont, sans-serif;
}
h1, h2, h3,
.pressBtn, .accountBtn, .siteLogo, .eyebrow, .statTile b {
  font-family: var(--font-display);
}

/* ---- Gradients, shadows, radii -----------------------------------------
   A flat fill and a gradient fill of the same colour are not interchangeable:
   the gradient is what makes a button look pressable and an icon tile look
   like an object. Defined once so every tile and button on the site catches
   the light from the same direction (top-left). */
:root {
  --grad-accent: linear-gradient(135deg, #FFA345 0%, #E8720F 55%, #CE5D06 100%);
  --grad-second: linear-gradient(135deg, #38CBB8 0%, #0E9488 55%, #0A7067 100%);
  --grad-sun: linear-gradient(135deg, #FFD469 0%, #F7A51C 60%, #E8720F 100%);
  --grad-good: linear-gradient(135deg, #7BDD3A 0%, #46A302 100%);
  --grad-plum: linear-gradient(135deg, #DEA3FF 0%, #A855F7 100%);
  --grad-bad: linear-gradient(135deg, #FF8A7A 0%, #D6453A 100%);
  /* A warm wash for the top of a hero, over the page background. */
  --grad-wash: radial-gradient(1150px 560px at 74% -10%, rgba(249, 110, 0, 0.55), transparent 68%),
               radial-gradient(800px 440px at 2% 0%, rgba(255, 170, 0, 0.34), transparent 66%);

  --shadow-sm: 0 1px 2px var(--shadow);
  --shadow-md: 0 6px 18px -6px var(--shadow), 0 2px 5px -2px var(--shadow);
  --shadow-lg: 0 20px 44px -16px var(--shadow-warm), 0 6px 14px -6px var(--shadow);
  /* The lift a coloured tile casts, tinted with its own colour rather than
     black - a black shadow under a saturated tile reads as dirt. */
  --shadow-accent: 0 8px 18px -6px rgba(226, 110, 13, 0.45);
  --shadow-second: 0 8px 18px -6px rgba(14, 148, 136, 0.42);

  /* Rounder than before. Nothing on a friendly page should have a 4px corner. */
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 18px;
  --r-xl: 24px;
  --r-pill: 999px;
}

/* The dark theme needs a cooler, weaker wash: the light one's chroma over a
   near-black ground reads as a lens flare. */
:root[data-theme="dark"] {
  --grad-wash: radial-gradient(1100px 520px at 72% -8%, rgba(240, 128, 30, 0.22), transparent 62%),
               radial-gradient(760px 420px at 8% 4%, rgba(25, 168, 154, 0.13), transparent 60%);
}

/* ---- Chesster's furniture: paws and board squares -----------------------
   Decoration, sampled off the logo's fur. This used to be a separate warm
   layer kept deliberately away from the blue accent; now that the accent IS
   orange, --cat is simply the warmest member of that family and the paws sit
   in the same palette as the buttons rather than beside it. This lives here
   rather than in a page's own <style> because index.html, puzzles.html and
   patterns.html all draw on it, and three copies is three oranges waiting to
   drift apart. Nothing here applies to a page that does not ask for it. */
:root {
  --cat: #f0913c;
  --cat-deep: #d2761f;
  /* Orange over near-white carries less than orange over near-black, so the
     light values are the stronger pair. --cat itself is theme-independent and
     stays in the base block. */
  --checker: rgba(210, 118, 31, 0.20);  /* a board square, at rule scale */
  --board: rgba(210, 118, 31, 0.11);    /* the same square, hero-sized */
  --paw-ink: 0.13;                      /* watermark paws */
}
:root[data-theme="dark"] {
  --checker: rgba(240, 145, 60, 0.13);
  --board: rgba(240, 145, 60, 0.08);
  --paw-ink: 0.09;
}

/* One paw, drawn once: pad plus four toes, each toe splayed by its own
   rotation. The same data URI does two jobs.
     - As a background it is a watermark, and the baked-in orange is what you
       see - a data URI cannot read a CSS variable, so opacity does the
       theme-dependent work instead.
     - As a mask (.pawIcon below) only its alpha matters, so the shape takes
       currentColor and can sit inside a coloured line of text.
   Which is why there is no <symbol> sprite for pages to keep in step. */
:root {
  --paw: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='%23f0913c'%3E%3Cellipse cx='12' cy='16.4' rx='6.2' ry='5.1'/%3E%3Cellipse cx='5.4' cy='9.6' rx='2.5' ry='3.1' transform='rotate(-22 5.4 9.6)'/%3E%3Cellipse cx='9.9' cy='6.3' rx='2.4' ry='3.3' transform='rotate(-8 9.9 6.3)'/%3E%3Cellipse cx='14.5' cy='6.4' rx='2.4' ry='3.3' transform='rotate(8 14.5 6.4)'/%3E%3Cellipse cx='18.8' cy='9.8' rx='2.5' ry='3.1' transform='rotate(22 18.8 9.8)'/%3E%3C/g%3E%3C/svg%3E");
}

/* A paw as content: <span class="pawIcon"></span>, empty on purpose - the mask
   paints it, so it needs no markup inside and no fill attribute. */
.pawIcon {
  display: block; width: 18px; height: 18px;
  background: currentColor;
  -webkit-mask: var(--paw) center / contain no-repeat;
  mask: var(--paw) center / contain no-repeat;
}
/* The same paw inline in a line of small caps, sized and baseline-nudged to
   sit with the text rather than on top of it. */
.pawInline {
  display: inline-block; width: 13px; height: 13px;
  vertical-align: -2px; margin-right: 7px;
  background: var(--cat);
  -webkit-mask: var(--paw) center / contain no-repeat;
  mask: var(--paw) center / contain no-repeat;
}

/* The same paw again, as a pseudo-element, for a label whose text is written
   with textContent - which replaces every child but leaves a ::before alone.
   Use .pawInline where the markup is yours to write, this where it is not. */
.pawBefore::before {
  content: ''; display: inline-block; width: 12px; height: 12px;
  vertical-align: -2px; margin-right: 7px;
  background: var(--cat);
  -webkit-mask: var(--paw) center / contain no-repeat;
  mask: var(--paw) center / contain no-repeat;
}

/* Paw prints approaching the reader, growing and solidifying left to right, so
   the eye is walked into what follows - always a single call to action. */
.pawTrail {
  display: flex; justify-content: center; align-items: flex-end;
  gap: 16px; color: var(--cat);
}
.pawTrail .pawIcon:nth-child(1) { width: 13px; height: 13px; opacity: 0.22; transform: rotate(-20deg) translateY(-7px); }
.pawTrail .pawIcon:nth-child(2) { width: 16px; height: 16px; opacity: 0.38; transform: rotate(-8deg) translateY(2px); }
.pawTrail .pawIcon:nth-child(3) { width: 19px; height: 19px; opacity: 0.55; transform: rotate(4deg) translateY(-5px); }
.pawTrail .pawIcon:nth-child(4) { width: 22px; height: 22px; opacity: 0.72; transform: rotate(14deg) translateY(3px); }
.pawTrail .pawIcon:nth-child(5) { width: 25px; height: 25px; opacity: 0.9; transform: rotate(24deg) translateY(-4px); }

/* A rank of board squares, used where a divider should be an ornament rather
   than structure. One row of squares: the conic tile is twice the rule's
   height, so only its top half ever gets painted. */
.checkerRule {
  height: 8px;
  background-image: repeating-conic-gradient(var(--checker) 0% 25%, transparent 0% 50%);
  background-size: 16px 16px;
}

/* ---- The playful kit ---------------------------------------------------
   Shared pieces that give a page energy without each page inventing its own.
   Everything here is opt-in: nothing applies to a page that does not ask for
   it by class. Shapes and colours live here; WHERE a decoration sits is the
   page's business, so a page positions .confetti marks in its own <style>.

   Collisions to know about: index.html and learning.css define their own
   .eyebrow and .pill. Both load after site.css, so the page wins - these are
   the defaults for pages that have not written their own. */

/* A micro-label above a heading. Uppercase, tracked out, accent-coloured. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent-link);
  margin: 0 0 12px;
}

/* A small rounded label. Plain is neutral; the modifiers tint it. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: 0.02em;
  background: var(--panel-alt);
  color: var(--text-dim);
  white-space: nowrap;
}
.pill--accent { background: var(--accent-tint); color: var(--accent-tint-fg); }
.pill--second { background: var(--second-tint); color: var(--second-tint-fg); }
.pill--good { background: var(--good-tint); color: var(--good-tint-fg); }
.pill--bad { background: var(--bad-tint); color: var(--bad-tint-fg); }
.pill--warn { background: var(--warn-tint); color: var(--warn-tint-fg); }
/* The loud one: a filled gradient badge, for a single label per screen. */
.pill--solid {
  background: var(--grad-accent);
  color: #fff;
  box-shadow: var(--shadow-accent);
}

/* ---- The press button --------------------------------------------------
   The one control that carries the site's personality. A flat fill with a
   HARD bottom edge in the darker twin of its own colour, so it reads as a
   physical thing sitting on the page; pressing it drops the face onto the
   edge, which is the whole trick. No gradient and no blur - a soft drop
   shadow under a chunky button makes it look like it is floating away.

   Deliberately NOT called .btn: coaches.html, index.html, lesson.html and
   today.html each define their own .btn, and a shared rule of that name would
   half-apply to all four. Pages opt in by using this class instead.

   The label is uppercase and tracked out because at this weight and size
   sentence case reads as a form control rather than as an invitation. */
.pressBtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  font: inherit;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  padding: 14px 26px;
  border-radius: var(--r-xl);
  border: 2px solid var(--border-strong);
  background: var(--panel);
  color: var(--text-dim);
  box-shadow: 0 4px 0 var(--border-strong);
  text-decoration: none;
  cursor: pointer;
  user-select: none;
  transition: filter 0.12s ease;
}
.pressBtn:hover { filter: brightness(0.97); }
/* The press: the face travels exactly the height of its own edge, and the
   edge goes to zero, so the button ends flush with the page. */
.pressBtn:active {
  transform: translateY(4px);
  box-shadow: 0 0 0 var(--border-strong);
}
.pressBtn:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.pressBtn:disabled, .pressBtn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: default;
  transform: none;
}

.pressBtn--primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-fg);
  box-shadow: 0 4px 0 var(--accent-edge);
}
.pressBtn--primary:active { box-shadow: 0 0 0 var(--accent-edge); }
.pressBtn--second {
  background: var(--second); border-color: var(--second); color: var(--second-fg);
  box-shadow: 0 4px 0 var(--second-edge);
}
.pressBtn--second:active { box-shadow: 0 0 0 var(--second-edge); }
.pressBtn--good {
  background: var(--good); border-color: var(--good); color: #fff;
  box-shadow: 0 4px 0 var(--good-edge);
}
.pressBtn--good:active { box-shadow: 0 0 0 var(--good-edge); }
/* On a coloured ground (the closing band), where a white face is the loud one. */
.pressBtn--onColor {
  background: #fff; border-color: #fff; color: var(--accent-deep);
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.18);
}
.pressBtn--onColor:active { box-shadow: 0 0 0 rgba(0, 0, 0, 0.18); }
.pressBtn--ghost {
  background: transparent; border-color: rgba(255, 255, 255, 0.55); color: #fff;
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.18);
}
.pressBtn--ghost:active { box-shadow: 0 0 0 rgba(0, 0, 0, 0.18); }

.pressBtn--lg { font-size: 17px; padding: 17px 32px; }
.pressBtn--sm { font-size: 13px; padding: 10px 18px; border-radius: var(--r-lg); }

@media (prefers-reduced-motion: reduce) {
  .pressBtn { transition: none; }
}

/* ---- Icon tiles --------------------------------------------------------
   A rounded square carrying one line-drawn glyph. The gradient is what makes
   it read as an object rather than a coloured rectangle, and the shadow is
   tinted with the tile's own colour - a black shadow under a saturated tile
   reads as dirt. Give the <svg> inside no fill and no stroke colour of its
   own; it inherits currentColor from the tile. */
.iconTile {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  border-radius: 17px;
  background: var(--grad-accent);
  color: #fff;
  box-shadow: var(--shadow-accent);
}
.iconTile svg {
  width: 27px;
  height: 27px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.1;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.iconTile--second { background: var(--grad-second); box-shadow: var(--shadow-second); }
.iconTile--sun { background: var(--grad-sun); color: #4A2703; box-shadow: 0 8px 18px -6px rgba(247, 165, 28, 0.45); }
.iconTile--good { background: var(--grad-good); box-shadow: 0 8px 18px -6px rgba(70, 163, 2, 0.42); }
.iconTile--plum { background: var(--grad-plum); box-shadow: 0 8px 18px -6px rgba(168, 85, 247, 0.42); }
.iconTile--bad { background: var(--grad-bad); box-shadow: 0 8px 18px -6px rgba(214, 69, 58, 0.42); }
/* The quiet variant: a tint rather than a fill, for a row of many tiles where
   a row of many gradients would be a fairground. */
.iconTile--soft { background: var(--accent-tint); color: var(--accent-tint-fg); box-shadow: none; }
.iconTile--soft.iconTile--second { background: var(--second-tint); color: var(--second-tint-fg); }
.iconTile--sm { width: 38px; height: 38px; border-radius: 12px; }
.iconTile--sm svg { width: 20px; height: 20px; }
.iconTile--lg { width: 72px; height: 72px; border-radius: 23px; }
.iconTile--lg svg { width: 36px; height: 36px; stroke-width: 1.9; }

/* ---- Cards -------------------------------------------------------------
   A panel that sits ON the page rather than being drawn into it: big radius,
   soft warm shadow, border only as a hairline to hold the edge in light mode
   where the card and the shadow are both pale. */
.softCard {
  background: var(--panel);
  border: 2px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: none;
}
/* Lifts toward the reader on hover. Use on cards that are links. */
.softCard--lift {
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.softCard--lift:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
}

/* A headline phrase in the brand gradient. Wrap only the words that should
   catch - a whole gradient headline is a headline nobody can read. */
.gradText {
  background: var(--grad-accent);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.gradText--second { background: var(--grad-second); -webkit-background-clip: text; background-clip: text; }

/* ---- Confetti ----------------------------------------------------------
   The little marks scattered around a hero: sparkles, dashes, dots, arcs.
   Drawn in CSS rather than as images so they take currentColor and cost no
   request. The page positions them - put them in a .confettiField, which
   clips them and keeps them out of the way of the pointer. */
.confettiField {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.confetti { position: absolute; display: block; }
.confetti.spark {
  width: 18px; height: 18px; background: currentColor;
  clip-path: polygon(50% 0%, 59% 41%, 100% 50%, 59% 59%, 50% 100%, 41% 59%, 0% 50%, 41% 41%);
}
.confetti.dash { width: 17px; height: 5px; border-radius: var(--r-pill); background: currentColor; }
.confetti.dot { width: 9px; height: 9px; border-radius: 50%; background: currentColor; }
.confetti.ring { width: 15px; height: 15px; border-radius: 50%; border: 3px solid currentColor; }
/* A quarter of a ring: one stroked edge, the rest transparent. */
.confetti.arc {
  width: 22px; height: 22px; border-radius: 50%;
  border: 3px solid transparent; border-top-color: currentColor; border-left-color: currentColor;
}
.confetti.blob {
  width: 26px; height: 22px; background: currentColor;
  border-radius: 62% 38% 44% 56% / 55% 50% 50% 45%;
}
.inkAccent { color: var(--accent); }
.inkSecond { color: var(--second); }
.inkSun { color: var(--warn-bright); }
.inkCat { color: var(--cat); }

/* ---- Clouds ------------------------------------------------------------
   Drawn the same way as the paw: one shape as a data-URI MASK, so the element
   paints it with its own background and the colour comes from CSS rather than
   from the file. That is what lets one cloud be white over a cream sky and a
   warm grey over a night one without a second asset.

   The shape is three circles and a rounded slab - as a mask only the alpha
   matters, so there is no fill to keep in step with a theme. Pages position
   them and set their own colour; sizes are set per cloud, because a sky of
   identically sized clouds reads as wallpaper. */
:root {
  --cloud: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 60'%3E%3Cg fill='%23000'%3E%3Ccircle cx='34' cy='31' r='19'/%3E%3Ccircle cx='62' cy='23' r='25'/%3E%3Ccircle cx='90' cy='33' r='17'/%3E%3Crect x='14' y='32' width='92' height='19' rx='9.5'/%3E%3C/g%3E%3C/svg%3E");
}
.cloud {
  position: absolute;
  display: block;
  width: 120px;
  height: 60px;
  background: currentColor;
  -webkit-mask: var(--cloud) center / contain no-repeat;
  mask: var(--cloud) center / contain no-repeat;
  pointer-events: none;
}

/* ---- Wave edge ---------------------------------------------------------
   A soft wave where a section meets the next one, instead of a hairline. The
   shape is a mask, so the element's own background shows through it - which
   means one shape works for a flat token, a gradient, or either theme. Set
   the background to whatever the section BELOW the wave is filled with. */
:root {
  --wave: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 90' preserveAspectRatio='none'%3E%3Cpath d='M0 46c140-38 280-54 420-32s250 62 390 60 250-48 390-56c90-5 160 6 240 24v48H0z' fill='%23000'/%3E%3C/svg%3E");
}
/* index.html masks this shape onto the top edge of its closing band; nothing
   uses it as a standalone element any more, so there is no .waveEdge class. */

/* ---- Progress ----------------------------------------------------------
   One bar, used for lesson progress, XP, anything out of a total. */
.meter {
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--track);
  overflow: hidden;
}
.meter > span {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--grad-accent);
}
.meter--second > span { background: var(--grad-second); }

/* A number with a label under it and a tile beside it: streak, level, gain. */
.statTile {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 16px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
}
.statTile b {
  display: block;
  font-size: 20px;
  font-weight: 750;
  line-height: 1.1;
  color: var(--text-bright);
}
.statTile span { font-size: 12px; color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
  .softCard--lift { transition: none; }
  .softCard--lift:hover { transform: none; }
}

* { box-sizing: border-box; }
body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text);
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
#siteHeader {
  --header-pad: 28px;
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 18px var(--header-pad);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
  gap: 8px;
}
.siteLogo {
  color: var(--text);
  font-weight: 700;
  font-size: 19px;
  text-decoration: none;
  letter-spacing: 0.2px;
  display: inline-flex;
  align-items: center;
  gap: 11px;
}
/* Height-driven, so the full mark keeps its aspect ratio: the artwork is taller
   than it is wide because of the king sitting on Chesster's head. */
.siteLogoMark { height: 54px; width: auto; display: block; }
/* The nav and the theme toggle travel together, so the toggle stays pinned to
   the right edge instead of being spaced out as a third top-level flex child. */
.siteHeaderRight { display: flex; align-items: center; gap: 12px; flex-wrap: nowrap; }

/* The nav is a drop-down panel at every width, not only on a phone: the links
   are one tap away everywhere and the bar is the same two controls on a laptop
   and a phone, instead of a row that reflows into a menu at some width.
   It hangs under the right edge of the header, lined up with the button that
   opens it - hence --header-pad, which the header's own padding also uses so
   the two cannot drift apart. */
.siteNav {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: var(--header-pad);
  left: auto;
  z-index: 60;
  flex-direction: column;
  gap: 0;
  min-width: 212px;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: 0 14px 30px var(--shadow);
}
#siteHeader.navOpen .siteNav { display: flex; }
.siteNav a {
  color: var(--text-dim);
  text-decoration: none;
  font-size: 15px;
  padding: 10px 12px;
  border-radius: 10px;
}
.siteNav a:hover { color: var(--text); background: var(--panel-alt); }
/* The desktop row marked the current page with an underline. Under a row that
   is itself a rounded block that reads as a divider, and a bar down the left
   edge gets clipped into a crescent by the same rounding - so the row takes the
   accent tint instead, the way .accountBtn does on its own page. */
.siteNav a.active {
  background: var(--accent-tint);
  color: var(--accent-tint-fg);
  font-weight: 700;
}
.siteNav a.active:hover { background: var(--accent-tint); color: var(--accent-tint-fg); }

.accountBtn {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 1rem;
  border-radius: var(--r-lg);
  border: 2px solid var(--accent);
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  flex-shrink: 0;
  box-shadow: 0 3px 0 var(--accent-edge);
  transition: filter 0.12s;
}
.accountBtn:hover { filter: brightness(0.97); }
.accountBtn:active { transform: translateY(3px); box-shadow: 0 0 0 var(--accent-edge); }
/* On /auth itself. Everywhere else this button is the chrome's one call to
   action and carries the gradient; on its own page that would be an invitation
   to go where you already are, so it drops back to a quiet tint. */
.accountBtn.active {
  background: var(--accent-tint);
  color: var(--accent-tint-fg);
  border-color: var(--accent-tint-fg);
  box-shadow: 0 3px 0 var(--accent-tint-fg);
}
.accountBtn.active:hover { filter: none; }
.accountBtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The menu button. Permanent chrome - it opens .siteNav at every width. */
.navToggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--panel-alt);
  color: var(--text);
  cursor: pointer;
  flex-shrink: 0;
}
.navToggle:hover { background: var(--panel-alt-hover); }
.navToggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Three bars from one element: the middle is the span, the outer two are its
   pseudo-elements, so the open state can fade the middle and cross the others
   without any extra markup. */
.navToggleBars, .navToggleBars::before, .navToggleBars::after {
  display: block;
  width: 17px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transition: transform 0.18s, opacity 0.18s;
}
.navToggleBars { position: relative; }
.navToggleBars::before, .navToggleBars::after { content: ''; position: absolute; left: 0; }
.navToggleBars::before { top: -6px; }
.navToggleBars::after { top: 6px; }
#siteHeader.navOpen .navToggleBars { background: transparent; }
#siteHeader.navOpen .navToggleBars::before { transform: translateY(6px) rotate(45deg); }
#siteHeader.navOpen .navToggleBars::after { transform: translateY(-6px) rotate(-45deg); }
@media (prefers-reduced-motion: reduce) {
  .navToggleBars, .navToggleBars::before, .navToggleBars::after { transition: none; }
}

/* On a phone a 212px card floating under the button looks like a tooltip, and
   the rows are needlessly narrow, so it spans the bar instead. */
@media (max-width: 560px) {
  .siteNav {
    left: var(--header-pad);
    right: var(--header-pad);
    min-width: 0;
  }
  .siteNav a { font-size: 16px; padding: 12px; }
}

/* Every page opens with this same block, so the title, the subtitle and the
   content below them land on identical y positions on every page. Pages must
   not restyle the title or subtitle locally - per-page h1 sizes and hint
   margins are exactly what made the top of the page jump when switching
   between them.

   The intro is left-aligned with its emblem beside it, which is the layout
   puzzles.html has always used and is now the pattern for every page but the
   landing page. Left alignment only works if the title sits on the same
   vertical line as the content below it, so a page declares the width of its
   own content column with --page-width (and --page-pad, when that column pads
   itself). Set them next to that page's #wrap - the two numbers have to be the
   same number, and keeping them apart is how they drift. A page that sizes its
   column at runtime (puzzles.html) sets the width on <main> instead and leaves
   --page-width alone. */
.pageIntro {
  width: 100%;
  max-width: var(--page-width, 100%);
  padding: 0 var(--page-pad, 0px);
  box-sizing: border-box;
  margin: 0 auto 24px;
  text-align: left;
  display: flex;
  align-items: center;
  gap: 16px;
}
.pageIntro h1 {
  font-size: 22px;
  line-height: 1.25;
  margin: 0 0 6px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.pageIntro .pageSubtitle {
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1.5;
  margin: 0;
}

/* ---- Page emblems ------------------------------------------------------
   A drawn mark beside the title, one per page: whatever that page is for,
   drawn rather than named. Each sits in the <div class="pageIntro"> ahead of
   the block holding the h1 and the subtitle.

   The emblem is kept shorter than that block is tall (h1 + subtitle is about
   55px) so that it never decides how tall the intro is - that is the whole
   reason .pageIntro is shared, see the note above. A page that shrinks its own
   intro has to shrink or drop its emblem to match.

   The classes below are the shared vocabulary, so the same idea is the same
   colour on every page: --text-faint for what is behind you or is only
   context, --cat for the one thing the page is about, a dash for a line that
   never actually happened. */
.pageEmblem { flex: none; display: block; }
.pageEmblem .emStroke {
  fill: none; stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.pageEmblem .emPast { stroke: var(--text-faint); }
.pageEmblem .emLive { stroke: var(--cat); }
.pageEmblem .emDrop { stroke: var(--bad); }
/* The line you did not play: dashed, because it never happened. */
.pageEmblem .emMissed { stroke: var(--good); stroke-dasharray: 3 3.5; }
/* Same idea with no verdict attached: a thing that is not real (a bot clone),
   or a branch you have never been down. */
.pageEmblem .emGhost { stroke: var(--text-faint); stroke-dasharray: 3 3.5; }
.pageEmblem .emMark { fill: var(--cat); }
.pageEmblem .emPastMark { fill: var(--text-faint); }

/* On a phone the title needs the whole width more than the page needs a
   picture, and the flex row would otherwise squeeze it into three lines. */
@media (max-width: 560px) {
  .pageIntro { display: block; }
  .pageEmblem { display: none; }
}

main {
  flex: 1;
  padding: 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

#siteFooter {
  text-align: center;
  padding: 16px 24px;
  color: var(--text-faint);
  font-size: 12px;
  border-top: 1px solid var(--border);
}
#siteFooter p { margin: 0; }
.footerSignoff {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--text-muted);
}

/* One row of shortcuts to every page, wrapping to as many rows as the screen
   needs. The separators are drawn on the links themselves so a wrapped row
   never starts or ends with a stray dot. */
.footerLinks {
  margin-top: 8px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 4px 14px;
}
.footerLinks a { position: relative; }
.footerLinks a + a::before {
  content: '\00b7';
  position: absolute;
  left: -8px;
  color: var(--text-faint);
}
/* Scoped under #siteFooter on purpose: the `#siteFooter a` rule further down
   is an ID selector, so an unscoped `.footerLinks a.active` loses to it on
   specificity no matter what order they are in - which is why the footer's
   "you are here" marker never actually rendered on any page. */
#siteFooter .footerLinks a.active { color: var(--text-muted); }
.footerHeart { color: #f0913c; }
.footerFace { height: 18px; width: auto; display: block; }
#siteFooter a { color: var(--accent-link); text-decoration: none; }
#siteFooter a:hover { text-decoration: underline; }

/* Below this, side-by-side fixed-width columns stop fitting phone screens.
   Page-specific rules (index/spar #layout etc.) hook into this breakpoint
   too - see each page's own <style> block. */
@media (max-width: 720px) {
  main { padding: 16px; }
  #siteHeader { --header-pad: 16px; padding: 12px var(--header-pad); }
}

/* ---------------------------------------------------------------- demo ----
   A signed-out visitor gets the owner's own games rather than a "sign in to
   train" card, so the demo is a state every data page can be in, and its one
   piece of extra chrome lives here with the header and the nav rather than
   being pasted into each page. ChessterData.renderDemoBanner() draws it.

   Accent tint rather than a warning colour: this is an invitation, not a
   problem with the page. */
.demoBanner {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 0 0 18px;
  padding: 12px 14px;
  background: var(--accent-tint);
  color: var(--accent-tint-fg);
  border: 1px solid var(--accent);
  border-radius: var(--r-md);
}
.demoBanner .demoTag {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-fg);
  flex-shrink: 0;
}
.demoBanner .demoCopy { flex: 1 1 240px; line-height: 1.5; font-size: 14px; }

/* The sticky strip under the header (ChessterData.renderDemoBar). Solid accent
   so it can't be read past, and sticky so it is still there three screens down. */
.demoBar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--accent);
  color: var(--accent-fg, #fff);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .12);
}
.demoBarInner {
  max-width: var(--page-width, 1040px);
  margin: 0 auto;
  padding: 10px 24px;
  display: flex;
  align-items: center;
  gap: 8px 14px;
  flex-wrap: wrap;
  font-size: 14px;
  line-height: 1.4;
}
.demoBarLabel {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .22);
  border: 1px solid rgba(255, 255, 255, .55);
  white-space: nowrap;
}
.demoBarText { font-weight: 600; flex: 1 1 220px; }
.demoBarCta {
  color: var(--accent);
  background: #fff;
  font-weight: 700;
  font-size: 13px;
  padding: 6px 14px;
  border-radius: 999px;
  text-decoration: none;
  white-space: nowrap;
}
.demoBarCta:hover { text-decoration: none; filter: brightness(.96); }
@media (max-width: 560px) {
  .demoBarInner { padding: 9px 16px; font-size: 13px; }
  .demoBarText { flex-basis: calc(100% - 140px); }
}
.demoBanner .bannerBtn { flex-shrink: 0; }

/* Was local to puzzles.html, which was the only page with a banner to put a
   button in. Shared now, because the demo banner is on every data page. */
.bannerBtn, a.bannerBtn {
  padding: 5px 12px; font-size: 12px; white-space: nowrap;
  background: var(--accent); border: 1px solid var(--accent); color: var(--accent-fg);
  border-radius: 8px; font-weight: 600; text-decoration: none; cursor: pointer;
  display: inline-block; font-family: inherit;
}
.bannerBtn:hover:not(:disabled), a.bannerBtn:hover {
  background: var(--accent-hover); border-color: var(--accent-hover); text-decoration: none;
}

/* spar.html signed out. Sparring is the one feature with no demo: the bots are
   server-side Stockfish, so a game anybody can start without an account is CPU
   anybody can spend. The page says so and points at the trainer demo. */
.sparGate {
  max-width: 52ch;
  padding: 1.25rem 1.4rem;
  border: 1px solid var(--border-strong);
  background: var(--panel);
  border-radius: 12px;
}
.sparGate .sparGateTitle { font-weight: 600; font-size: 1.05rem; margin-bottom: 0.5rem; }
.sparGate p { color: var(--text-dim); line-height: 1.6; margin: 0 0 0.75rem; }
.sparGate a:not(.bannerBtn) { color: var(--accent-link); }

/* Persistent analysis toast, below the account controls. */
.analysisProgress {
  position: fixed;
  top: calc(88px + env(safe-area-inset-top, 0px));
  right: max(24px, env(safe-area-inset-right, 0px));
  z-index: 50;
  width: min(352px, calc(100vw - 32px));
  box-sizing: border-box;
  padding: 16px 18px;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, .18);
  font-size: 13px;
}
.analysisProgressHeading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.analysisProgressTitle { font-size: 15px; }
.analysisProgress button {
  flex-shrink: 0; display: grid; place-items: center; width: 28px; height: 28px;
  color: var(--text-dim); background: transparent; border: 0; border-radius: 6px;
  font: inherit; font-size: 22px; cursor: pointer;
}
.analysisProgress button:hover { color: var(--text); background: var(--track); }
.analysisProgress button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.analysisProgress p { margin: 8px 0 14px; color: var(--text-dim); line-height: 1.5; }
.analysisProgress progress {
  display: block; appearance: none; width: 100%; height: 7px;
  border: 0; border-radius: 999px; overflow: hidden; background: var(--track); accent-color: var(--accent);
}
.analysisProgress progress::-webkit-progress-bar { background: var(--track); border-radius: 999px; }
.analysisProgress progress::-webkit-progress-value { background: var(--accent); border-radius: 999px; transition: width .3s ease; }
.analysisProgress progress::-moz-progress-bar { background: var(--accent); border-radius: 999px; }
.analysisProgress progress:indeterminate { opacity: .55; }
.analysisProgressStats { display: flex; justify-content: space-between; margin-top: 8px; color: var(--text-dim); font-size: 12px; font-variant-numeric: tabular-nums; }
.analysisProgressStats span:last-child { color: var(--accent-link); font-weight: 700; }
.analysisProgress [hidden] { display: none; }
@media (max-width: 560px) {
  .analysisProgress { top: calc(76px + env(safe-area-inset-top, 0px)); right: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .analysisProgress progress::-webkit-progress-value { transition: none; }
}
