/* Whiskerloop.
 *
 * The board is a room seen from above, so the palette is warm - lamplight and
 * wood rather than the cool slate a spreadsheet would use. That is not only
 * taste: casual puzzle players stay longer with emotional themes than
 * mechanical ones, and a warm room full of cats is the whole pitch.
 *
 * Every state a cat can be in is carried by shape as well as colour. Asleep is
 * a curl, awake is a cat with a visible head, a problem gets a ring and a
 * wobble. Colour alone would fail anyone who cannot separate the red from the
 * amber, and about one man in twelve cannot.
 */

:root {
  --bg: #17120f;
  --surface: #221a15;
  --surface-2: #2c221b;
  --floor: #2a201a;
  --floor-alt: #2f2420;
  --furniture: #4a382b;
  --mirror: #2c3440;
  --line: #3d2f26;
  --text: #f3e9e0;
  --muted: #a99383;
  --accent: #f0a04b;
  --accent-ink: #2a1708;
  --good: #7fc98a;
  --bad: #e8726a;
  --gaze: #6c5646;
  --dog: #e8726a;
  --glow: #2a1e16;
  --radius: 16px;
}

/* The light palette, written once and applied by two selectors: the system
   preference when the player has not chosen, and an explicit choice when they
   have. Without the second, "light" is unreachable on a device set to dark. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]):not([data-theme="light"]) {
    --bg: #fbf5ee;
    --surface: #fffaf4;
    --surface-2: #f3e7da;
    --floor: #f6ece0;
    --floor-alt: #efe2d2;
    --furniture: #d9c3a8;
    --mirror: #dbe6ee;
    --line: #e0cdb8;
    --text: #2e2119;
    --muted: #7d6754;
    --accent: #d8802a;
    --accent-ink: #fffaf4;
    --good: #2f7d47;
    --bad: #b8342b;
    --dog: #c0392b;
    /* Warm daylight rather than lamplight; near-white, or the
       gradient reads as a smudge across the top of the page. */
    --glow: #fff6e8;
  }
}

:root[data-theme="light"] {
    --bg: #fbf5ee;
    --surface: #fffaf4;
    --surface-2: #f3e7da;
    --floor: #f6ece0;
    --floor-alt: #efe2d2;
    --furniture: #d9c3a8;
    --mirror: #dbe6ee;
    --line: #e0cdb8;
    --text: #2e2119;
    --muted: #7d6754;
    --accent: #d8802a;
    --accent-ink: #fffaf4;
    --good: #2f7d47;
    --bad: #b8342b;
  --gaze: #c9b39c;
  --dog: #c0392b;
  --glow: #fff6e8;
}

:root[data-theme="dark"] {
  --bg: #17120f; --surface: #221a15; --surface-2: #2c221b;
  --floor: #2a201a; --floor-alt: #2f2420; --furniture: #4a382b; --mirror: #2c3440; --line: #3d2f26;
  --text: #f3e9e0; --muted: #a99383; --accent: #f0a04b; --accent-ink: #2a1708;
  --good: #7fc98a; --bad: #e8726a; --gaze: #6c5646; --dog: #e8726a; --glow: #2a1e16;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 ui-rounded, "SF Pro Rounded", system-ui, -apple-system, Segoe UI, sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100dvh;
  padding:
    env(safe-area-inset-top) calc(16px + env(safe-area-inset-right))
    calc(20px + env(safe-area-inset-bottom))
    calc(16px + env(safe-area-inset-left));
  -webkit-tap-highlight-color: transparent;
  /* A native shell has no address bar to scroll away, and a puzzle board that
     rubber-bands under the thumb feels broken rather than playful. */
  overscroll-behavior: none;
}

/* Text selection on a game board is never wanted and always in the way on a
   long press. The inputs opt back in. */
body { user-select: none; -webkit-user-select: none; }
input, textarea { user-select: text; -webkit-user-select: text; }

/* The lamplight lives on its own layer, behind everything.
 *
 * As a background on <body> - with or without background-attachment: fixed -
 * the Android WebView failed to invalidate it when the layout shifted, and the
 * board was left painted twice, offset, with the tools drawn over the stale
 * copy. It looked perfect in every desktop browser. A fixed pseudo-element
 * promoted to its own compositing layer does not shift with the content, so
 * there is nothing to invalidate.
 */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(120% 70% at 50% 18%,
                              color-mix(in srgb, var(--glow) 85%, transparent) 0%,
                              color-mix(in srgb, var(--glow) 0%, transparent) 62%);
  transform: translateZ(0);
}

[hidden] { display: none !important; }

/* --- header ------------------------------------------------------------ */

.bar {
  width: 100%; max-width: var(--board);
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 16px 0 10px;
}

.title {
  display: flex; align-items: center; gap: 11px; min-width: 0;
  background: none; border: none; padding: 0; color: inherit;
  font: inherit; text-align: left; cursor: pointer;
}
.logo { display: flex; flex: none; }
h1 { font-size: 20px; margin: 0; letter-spacing: -.02em; font-weight: 800; }
#subtitle { margin: 0; font-size: 12.5px; color: var(--muted); }

.bar-right { display: flex; align-items: center; gap: 8px; flex: none; }

.timer {
  font-variant-numeric: tabular-nums;
  font-size: 17px; font-weight: 700; color: var(--text);
  min-width: 46px; text-align: right;
}

/* The streak is the reason to come back tomorrow, so it lives in the header
   rather than three taps away in a stats screen. */
.chip-stat {
  font-size: 13px; font-weight: 700;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 999px; padding: 3px 9px; white-space: nowrap;
}

.pill {
  font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--accent-ink); background: var(--accent);
  border-radius: 999px; padding: 3px 8px;
}

@media (max-width: 420px) {
  #subtitle { display: none; }
  .bar { padding: 12px 0 8px; gap: 8px; }
  /* The menu button cost the wordmark its room, and a title clipped to
     "Whisk(" is worse than no title: the mark is the same mark, and the app
     has its name on the icon the player tapped to get here. */
  .title h1 { display: none; }
}

/* --- difficulty -------------------------------------------------------- */

.chips {
  width: 100%; max-width: var(--board);
  display: flex; gap: 8px; align-items: center;
  margin-bottom: 12px;
}
.segmented {
  flex: 1;
  display: grid; grid-template-columns: repeat(4, 1fr);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; padding: 3px; gap: 3px;
}
.chip {
  padding: 7px 2px; border-radius: 9px;
  border: none; background: none;
  color: var(--muted); font: inherit; font-size: 12.5px; font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.chip[aria-selected="true"] {
  background: var(--accent); color: var(--accent-ink);
}

/* --- which game is this ------------------------------------------------- */

/* Above the difficulty row and larger than it, because it is the larger
   question. Today expires and carries the streak, and a level is the same
   room for everybody forever - two different games that used to be told
   apart by a pill in the corner which was hidden for the more common one. */
.modes {
  width: 100%; max-width: var(--board);
  display: flex; margin-bottom: 8px;
}
.modes .segmented { grid-template-columns: repeat(2, 1fr); }
.mode {
  padding: 9px 2px; border-radius: 9px;
  border: none; background: none;
  color: var(--muted); font: inherit; font-size: 14px; font-weight: 800;
  cursor: pointer; white-space: nowrap;
}
.mode[aria-selected="true"] { background: var(--accent); color: var(--accent-ink); }

/* Says what this room is and what finishing it does, and keeps saying it.
   The status line under the board is a live region that every hint and wrong
   answer overwrites, so it could not be the thing that held this. */
.context {
  width: 100%; max-width: var(--board);
  display: flex; align-items: baseline; gap: 8px;
  margin: 0 0 10px; min-height: 18px;     /* no jump before the first paint */
  font-size: 13px;
}
.context b { font-weight: 800; white-space: nowrap; }
.context .sub { flex: 1; color: var(--muted); }
/* Inline here, unlike the full-width .linkish the sheets use. */
.context .linkish {
  display: inline; width: auto; margin: 0; padding: 0;
  font-size: 13px; white-space: nowrap; text-decoration: underline;
}

/* --- coach and banner -------------------------------------------------- */

.coach {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface-2); border: 1px solid var(--accent);
  border-radius: var(--radius); padding: 12px 14px; margin-bottom: 12px;
}
.coach p { margin: 0; font-size: 14px; }
.btn-sm { flex: none; padding: 7px 13px; font-size: 13.5px; }

.banner {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 11px; padding: 9px 13px; margin-bottom: 10px;
  font-size: 13.5px; color: var(--muted); text-align: center;
}
.banner.paywall { border-color: var(--accent); color: var(--text); }

/* --- the room ---------------------------------------------------------- */

/* On a narrow screen the board is limited by width, not height, so there is
   leftover space below it. Filling the column and centring in it puts the
   board at the optical centre instead of leaving a void above the tab bar. */
main {
  width: 100%;
  max-width: var(--board);
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* As large as the viewport allows, never so large the tools fall off the
   bottom. 62vh leaves room for the header, the status line and the tools; the
   560px ceiling stops it becoming a wall on a monitor. */
:root {
  --board: min(
    94vw,
    calc(62vh - env(safe-area-inset-top) - env(safe-area-inset-bottom)),
    560px
  );
}

#board-wrap { position: relative; width: 100%; }

/* The room, covered. Opening a room and starting its clock used to be the
   same event, so tapping through the four difficulties to see what today
   held left four clocks running behind you. Opaque rather than blurred:
   the point is not to see the pattern, and a blur is a thin promise. */
.cover {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 12px;
  padding: 24px; text-align: center;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.cover .btn { flex: none; min-width: 170px; }
.cover .aside { margin: 0; max-width: 22em; }
.cover-title {
  margin: 0; font-size: 20px; font-weight: 800; letter-spacing: .01em;
}
/* Armed, waiting for the second tap. Clear wipes the work; catching it on
   the way to Hint should not. */
.btn.armed { border-color: var(--accent); color: var(--accent); }
/* No hints left. Still tappable, because tapping it is how you find out that
   more can be had - a disabled button answers nothing. */
.btn.spent { opacity: .55; }
/* visibility rather than display: the board keeps its size, so the cover
   above it is exactly the shape of the room it is covering - and hidden
   cells are neither focusable nor read out. */
#board.veiled, #gazes.veiled { visibility: hidden; }

#board {
  display: grid; gap: 3px; padding: 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  touch-action: manipulation;
  box-shadow:
    0 1px 0 rgba(255, 220, 180, .06) inset,
    0 18px 44px rgba(0, 0, 0, .45),
    0 0 0 1px rgba(240, 160, 75, .07);
}

/* The gaze lines are the thing you are actually reasoning about, so they are
   never occluded. */
#gazes { position: absolute; inset: 0; pointer-events: none; overflow: visible; }

.cell {
  aspect-ratio: 1; border-radius: 10px;
  background: var(--floor); border: none; padding: 0;
  position: relative; display: grid; place-items: center;
}
.cell.alt { background: var(--floor-alt); }

/* Furniture blocks a gaze, so it is part of the puzzle and has to be obvious
   at a glance. Tone alone was not enough - it carries a drawn object too. */
.cell.furniture {
  background: var(--furniture);
  box-shadow: inset 0 -3px 0 rgba(0, 0, 0, .25);
}
.prop { width: 74%; height: 74%; display: block; opacity: .95; }

/* A mirror turns a gaze rather than stopping it, so it must never be mistaken
   for furniture: its own cool tone, and the glass drawn corner to corner in
   the slant that decides which way the gaze goes. */
.cell.mirror { background: var(--mirror); }
.prop.glass { width: 86%; height: 86%; }

/* A tunnel mouth is a hole, and its rim colour is shared with its twin. */
.prop.tunnel { width: 82%; height: 82%; }

button.cell { cursor: pointer; font: inherit; color: inherit; }
/* A tap that does not acknowledge itself feels broken on a phone, where there
   is no cursor to show anything happened. */
button.cell:active .cat { transform: var(--facing) scale(.88); }
button.cell:active { background: var(--surface-2); }
button.cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.cat { width: 82%; height: 82%; display: block; transition: transform .16s ease; }
.cat.asleep { opacity: .82; }

/* The dog has to read as a dog whatever coat a theme gives it - a black dog
   among black cats was a cat with a red line. So its square says "dog" on its
   own: a round red bed that does not turn when the dog does, and a paw print
   in the corner. No cat ever gets either. */
.cell.kennel {
  background:
    radial-gradient(circle at 50% 52%, #f3e3c3 0 27%, #c0442f 28% 40%, #7a2619 41% 44%, transparent 45%),
    var(--floor);
}
.cell.kennel::after {
  content: ""; position: absolute; top: 4%; left: 4%; width: 26%; height: 26%;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='10' fill='%23fff7e8'/%3E%3Cg fill='%236e2418'%3E%3Cellipse cx='10' cy='12.6' rx='3.6' ry='3'/%3E%3Ccircle cx='5.4' cy='8.6' r='1.7'/%3E%3Ccircle cx='8.2' cy='5.8' r='1.7'/%3E%3Ccircle cx='11.8' cy='5.8' r='1.7'/%3E%3Ccircle cx='14.6' cy='8.6' r='1.7'/%3E%3C/g%3E%3C/svg%3E") center / contain no-repeat;
  pointer-events: none;
}
.cat.dog { width: 92%; height: 92%; position: relative; z-index: 1; }

.cell.locked { box-shadow: inset 0 0 0 2px var(--accent); cursor: default; }
.cell.problem { box-shadow: inset 0 0 0 2px var(--bad); }
.cell.problem .cat { animation: wobble .5s ease-in-out infinite; }
@keyframes wobble {
  0%, 100% { transform: var(--facing) translateX(0); }
  25% { transform: var(--facing) translateX(-4%); }
  75% { transform: var(--facing) translateX(4%); }
}
.cell.solved-glow { box-shadow: inset 0 0 0 2px var(--good); }

/* Four dots around a sleeping cat, lit where it could actually look. The
   answer to "what are my options" should not cost a tap to find out. */
.pips { position: absolute; inset: 0; pointer-events: none; }
.pip { position: absolute; width: 4px; height: 4px; border-radius: 50%; background: var(--line); }
.pip.on { background: var(--accent); }
.pip-0 { top: 4px; left: 50%; transform: translateX(-50%); }
.pip-1 { right: 4px; top: 50%; transform: translateY(-50%); }
.pip-2 { bottom: 4px; left: 50%; transform: translateX(-50%); }
.pip-3 { left: 4px; top: 50%; transform: translateY(-50%); }

@media (prefers-reduced-motion: reduce) {
  .cat, .cell.problem .cat { transition: none; animation: none; }
}

/* --- status, counters, tools ------------------------------------------- */

/* How far in you are, as a bar rather than a fraction. The board already says
   which cats are placed; this only has to answer "nearly there?". */
.progress {
  height: 4px; margin: 14px 0 9px;
  background: var(--surface-2); border-radius: 999px; overflow: hidden;
}
.progress i {
  display: block; height: 100%; width: 0;
  background: var(--accent); border-radius: 999px;
  transition: width .25s ease, background-color .25s ease;
}
.progress.done i { background: var(--good); }

.status {
  min-height: 22px; margin: 0 0 12px;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  text-align: center; font-size: 14px; color: var(--muted);
}
.version-tag {
  margin: 14px 0 0; text-align: center;
  font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.status.good { color: var(--good); }
.status.bad { color: var(--bad); }

/* A count of problems appears only when there are some. An always-visible
   zero is noise, and noise is what teaches people to stop reading. */
.badge {
  flex: none;
  background: var(--bad); color: #fff;
  font-size: 11.5px; font-weight: 800;
  border-radius: 999px; padding: 2px 8px;
}

.tools { display: flex; gap: 8px; }

.btn {
  flex: 1; padding: 11px 14px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--surface); color: var(--text);
  font: inherit; font-size: 15px; font-weight: 700; cursor: pointer;
}
.btn:hover { border-color: var(--accent); }
.btn:disabled { opacity: .45; cursor: default; }
.btn-primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

.icon-btn {
  flex: none; width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--muted); font: inherit; font-size: 15px; cursor: pointer;
}
.icon-btn:hover { color: var(--text); border-color: var(--accent); }

.linkish {
  display: block; width: 100%; margin-top: 10px; padding: 4px;
  background: none; border: none; color: var(--muted);
  font: inherit; font-size: 13.5px; cursor: pointer;
}
.linkish:hover { color: var(--text); }

/* A deploy landed while this page was open. Above the tab bar rather than at
   the top of the column: it must not push the board around, and it has to be
   somewhere a thumb already is. */
.update-bar {
  position: fixed; left: 0; right: 0; z-index: 11;
  bottom: env(safe-area-inset-bottom);
  border: none; border-top: 1px solid var(--line);
  background: var(--accent); color: var(--accent-ink);
  font: inherit; font-size: 13.5px; font-weight: 700;
  padding: 10px 14px; cursor: pointer; text-align: center;
}

/* The string somebody reads out when they write to us, and the two documents
   they agreed to. Both were hard to find; both are now at the foot of
   settings, which is where people go looking. */
.player-id {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; margin-top: 18px; padding: 11px 14px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 11px;
  font: inherit; font-size: 13px; cursor: pointer;
}
.player-id b { font-weight: 800; letter-spacing: .04em; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.legal { margin-top: 10px; text-align: center; }
/* The browser's default link blue is the one colour in the app that belongs
   to no one - and these two sit under a dark panel where it is hard to read. */
.legal a { color: var(--muted); text-underline-offset: 3px; }
.legal a:hover { color: var(--text); }

/* --- home --------------------------------------------------------------- */

/* The three modes, side by side, before a clock exists. A strip of three tabs
   can say which one you are in; only this can say what the difference is. */
/* --- the menu ----------------------------------------------------------- */

/* It replaced a bar along the bottom. The bar was one tap to everything and
   that is its real advantage, but six labels across a phone is a row of
   things too small to read, and the shop was two levels down inside settings
   regardless. One list, one level, nothing hidden behind a second one. */
.menu-btn { font-size: 19px; }

.scrim {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(0, 0, 0, .55);
}

.drawer {
  position: fixed; top: 0; bottom: 0; left: 0; z-index: 41;
  width: min(86vw, 340px);
  display: flex; flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.drawer-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 14px 10px 18px;
  padding-top: calc(16px + env(safe-area-inset-top));
}
.drawer-head h2 {
  margin: 0; font-size: 15px; font-weight: 800;
  text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
}
#drawer-body { padding: 0 12px 18px; }

.drawer-item {
  display: flex; align-items: center; gap: 13px;
  width: 100%; padding: 12px 8px; border: none; background: none;
  font: inherit; text-align: left; color: var(--text); cursor: pointer;
  border-radius: 11px;
}
.drawer-item:hover { background: var(--surface-2); }
.drawer-item .glyph { font-size: 20px; line-height: 1; width: 26px; text-align: center; }
.drawer-item b { display: block; font-size: 15px; font-weight: 700; }
/* A group break, not a decoration: the things you play, the things you look
   at, and the things you set once. */
.drawer-rule { height: 1px; background: var(--line); margin: 8px 6px; }

/* The language button lives in the header rather than only inside Settings.

   If you end up in a language you cannot read - a tap by a child, a shared
   phone, a mistake - then "Settings" is written in that language too, and the
   way back is hidden behind a word you cannot parse. A globe and a two-letter
   code are readable from any of them. */
.lang-btn { display: inline-flex; align-items: center; gap: 3px; width: auto; padding: 0 9px; }
.lang-btn i { font-style: normal; font-size: 10px; font-weight: 800; letter-spacing: .04em; }

/* Tool labels are wrapped in <i> so a translation can replace the word
   without touching the icon beside it. They are not emphasis. */
.btn i { font-style: normal; }

/* --- sheets and modals -------------------------------------------------- */

.modal {
  position: fixed; inset: 0; background: rgba(0, 0, 0, .55);
  display: grid; place-items: center; padding: 20px; z-index: 20;
}
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 20px; padding: 22px; max-width: 420px; width: 100%;
  max-height: 84dvh; overflow-y: auto;
}
.card h2 { margin: 0 0 10px; font-size: 19px; }
.aside { font-size: 13px; color: var(--muted); margin: 0 0 14px; }
.empty { color: var(--muted); text-align: center; padding: 22px 0; font-size: 14px; }

.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.sheet-head h2 { margin: 0; }
.sheet-tabs { display: flex; gap: 6px; margin: 14px 0 12px; }
.sheet-tab {
  flex: 1; padding: 7px 0; border-radius: 9px;
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--muted); font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
}
.sheet-tab[aria-selected="true"] { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

.tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; margin-bottom: 14px; }
.tiles div { background: var(--surface-2); border-radius: 11px; padding: 9px 0; text-align: center; }
.tiles b { display: block; font-size: 17px; }
.tiles small { color: var(--muted); font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; }

.rows { display: flex; flex-direction: column; gap: 5px; margin-bottom: 12px; }
.row {
  display: grid; grid-template-columns: 38px 1fr auto auto; gap: 9px; align-items: center;
  background: var(--surface-2); border-radius: 10px; padding: 8px 11px; font-size: 14px;
}
.row.you { box-shadow: inset 0 0 0 1.5px var(--accent); }
.row .rank { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 13px; }
.row .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .who .avatar { margin-right: 7px; }

/* A face on a coloured disc, next to a name on the board. */
.avatar {
  display: inline-grid; place-items: center; vertical-align: middle;
  width: 22px; height: 22px; border-radius: 50%;
  font-size: 13px; line-height: 1;
}
.avatar.big { width: 48px; height: 48px; font-size: 28px; }
.avatar-picker { margin: 4px 0 14px; }
.avatar-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.faces { display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); gap: 6px; }
.face {
  aspect-ratio: 1; border-radius: 50%; border: 1px solid var(--line);
  background: var(--surface-2); font-size: 20px; cursor: pointer; padding: 0;
}
.face.on { box-shadow: 0 0 0 2px var(--accent); }
.face.locked { filter: grayscale(1); opacity: .45; }
.swatches { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.swatch {
  width: 28px; height: 28px; border-radius: 50%; border: 2px solid transparent;
  cursor: pointer; padding: 0;
}
.swatch.on { border-color: var(--text); }

.unlocked { margin: -6px 0 12px; text-align: center; font-weight: 700; color: var(--accent); }
.row .meta { color: var(--muted); font-size: 12.5px; }
.row .score { font-weight: 700; font-variant-numeric: tabular-nums; }
.gap { text-align: center; color: var(--muted); }

/* Which stretch of time a board covers, and stepping through it. */
.board-periods { margin-bottom: 8px; }
.board-nav {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  margin: 2px 0 10px; font-size: 14px;
}
.board-nav strong { min-width: 12em; text-align: center; white-space: nowrap; }
.board-nav .linkish { font-size: 22px; line-height: 1; padding: 2px 10px; }
.board-nav .linkish:disabled { opacity: .3; cursor: default; }
.sheet-sub { font-size: 14px; margin: 16px 0 8px; }

.rulelist { list-style: none; margin: 0 0 14px; padding: 0; font-size: 14.5px; }
.rulelist li { display: flex; gap: 10px; margin-bottom: 10px; align-items: flex-start; }
.ruleno {
  flex: none; width: 21px; height: 21px; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink);
  font-size: 12px; font-weight: 800; display: grid; place-items: center; margin-top: 1px;
}

/* The same list shape as the rules, without the numbered discs - these are
   three alternatives, not three steps in order. */
.modes-title {
  margin: 18px 0 8px; font-size: 14px; font-weight: 800;
  text-transform: uppercase; letter-spacing: .05em; color: var(--muted);
}
.modelist li { display: block; }

.perklist { margin: 0 0 16px; padding-left: 20px; font-size: 14.5px; }
.perklist li { margin-bottom: 6px; }

/* The ladder, as a grid you can see your progress across at a glance. The
   difficulty is carried by a left edge rather than the whole tile, so a solved
   level still reads as solved and the two facts do not fight. */
.levels {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 7px;
  margin: 14px 0;
}
.level {
  aspect-ratio: 1;
  display: grid; place-items: center;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: 10px;
  color: var(--muted);
  font: inherit; cursor: pointer;
}
.level b { font-size: 14px; font-weight: 700; }
.level:hover { border-color: var(--accent); }
.level.easy { border-left-color: #7fc98a; }
.level.medium { border-left-color: #f0a04b; }
.level.hard { border-left-color: #e8934b; }
.level.fiendish { border-left-color: #e8726a; }
.level.done {
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
}
.level.done::after { content: "✓"; position: absolute; }
.level.locked { opacity: .55; }
.level.done b { opacity: .95; }

.days { display: flex; flex-direction: column; gap: 6px; }
.day {
  display: grid; grid-template-columns: 62px 1fr auto; gap: 10px; align-items: center;
  background: var(--surface-2); border-radius: 11px; padding: 8px 11px;
}
.day.today { box-shadow: inset 0 0 0 1.5px var(--accent); }
.day.locked { opacity: .72; cursor: pointer; }
.day .date { font-variant-numeric: tabular-nums; font-size: 13.5px; }
.marks { display: flex; gap: 4px; }
.marks i {
  font-style: normal; width: 18px; height: 18px; border-radius: 5px;
  display: grid; place-items: center; font-size: 10.5px; font-weight: 800;
}
.marks .done { background: var(--good); color: var(--accent-ink); }
.marks .todo { background: var(--floor); color: var(--muted); }

.field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.field label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); }
.field input {
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  border-radius: 10px; padding: 10px 11px; font: inherit; font-size: 15px;
}
.field input:focus { outline: none; border-color: var(--accent); }
.form .btn { width: 100%; }

.divider {
  display: flex; align-items: center; gap: 10px;
  color: var(--muted); font-size: 12px; margin: 14px 0;
}
.divider::before, .divider::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}
.google-holder { display: flex; justify-content: center; min-height: 44px; }

.sheet-error { color: var(--bad); font-size: 13px; margin: 8px 0 0; }

/* --- settings ----------------------------------------------------------- */

.setting { align-items: center; }
.menu-row {
  width: 100%; text-align: left; font: inherit; color: inherit;
  border: none; cursor: pointer; margin-bottom: 6px;
}
.menu-row:hover { box-shadow: inset 0 0 0 1.5px var(--accent); }
/* The one you are already on. Marked by a tick as well as the outline, because
   the language list is the one screen where colour alone is asking a lot. */
.menu-row.on { box-shadow: inset 0 0 0 1.5px var(--accent); }
.menu-row.on .chevron { color: var(--accent); }
.chevron { color: var(--muted); font-size: 20px; line-height: 1; }

.switch {
  flex: none; min-width: 56px;
  padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--muted); font: inherit; font-size: 13px; font-weight: 700;
  cursor: pointer;
}
.switch.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

.mini-segmented {
  flex: none; display: flex; gap: 2px; padding: 2px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
}
.mini-segmented button {
  padding: 5px 9px; border-radius: 8px; border: none; background: none;
  color: var(--muted); font: inherit; font-size: 12.5px; font-weight: 700;
  cursor: pointer;
}
.mini-segmented button[aria-selected="true"] {
  background: var(--accent); color: var(--accent-ink);
}

/* A native <select>, because six languages will not fit a segmented control and
   every phone already knows how to scroll one of these. Only the closed state
   is ours to style; the open list belongs to the operating system. */
.picker {
  /* A grid "auto" track sizes a <select> to its min-content, which browsers
     put at about one character - "Nederlands" rendered as "N" until this had
     a floor. It has to hold the longest language name in the list. */
  flex: none; min-width: 124px;
  padding: 6px 10px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--ink); font: inherit; font-size: 13px; font-weight: 700;
  cursor: pointer;
}

/* --- skins -------------------------------------------------------------- */

/* Each skin shows its six cats rather than a colour swatch: six swatches tell
   you nothing about what six cats look like sitting in a room together. */
.skin {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 12px; padding: 12px; margin-bottom: 10px;
}
.skin.selected { border-color: var(--accent); }
.skin-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.skin-name { font-weight: 700; font-size: 14.5px; }
.skin-cats {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; margin-top: 10px;
  border-radius: 10px; padding: 4px;
}
.skin-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.skin-cat {
  aspect-ratio: 1; border-radius: 8px; background: var(--floor);
  display: grid; place-items: center;
}
.skin-cat .cat { width: 90%; height: 90%; }
.skin-cat .prop { width: 78%; height: 78%; }

.row.pair { grid-template-columns: 1fr auto; margin-bottom: 7px; }
.row.pair input {
  background: var(--surface); border: 1px solid var(--line); color: var(--text);
  border-radius: 9px; padding: 8px 10px; font: inherit; font-size: 14px; width: 100%;
}
.row .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 16px; font-weight: 700; letter-spacing: .08em; }
.row .sub { font-size: 11.5px; color: var(--muted); }
.btn-sm { flex: none; padding: 7px 13px; font-size: 13.5px; }

/* A guest on the leaderboard sheet: why they are not on it, and the way on. */
.board-invite {
  margin-top: 14px; padding: 14px; text-align: center;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 12px;
}
.board-invite p { margin: 0 0 10px; font-size: 13.5px; color: var(--muted); }
