/* Cat Grid - inked cats on warm paper. The board is the page; the menus sit over it. */
:root {
  --paper: #f4ead8; --paper2: #e9dcc4; --card: #fffaf0; --line: #d8c6a4; --ink: #3a2e1f;
  --muted: #8a7555; --gold: #c9892f; --red: #c0392b; --green: #4f7a3f;
  --safe-t: env(safe-area-inset-top, 0px); --safe-b: env(safe-area-inset-bottom, 0px);
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; min-height: 100%; color: var(--ink);
  font: 16px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
/* A desk lamp over paper: one warm light above the board, the corners falling away. Two gradients
   and no image file - the whole portfolio draws its own light (GRAPHICS.md 2b). */
body { background:
    radial-gradient(118% 62% at 50% -6%, #fdf6e8 0%, rgba(253, 246, 232, 0) 62%),
    radial-gradient(135% 96% at 50% 46%, var(--paper) 44%, #e2d3b6 100%);
  background-attachment: fixed; }
#catgrid { min-height: 100vh; }
.cscreen { min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: calc(12px + var(--safe-t)) 12px calc(12px + var(--safe-b)); }

.card { width: min(440px, 100%); max-height: 100%; overflow-y: auto;
  background: linear-gradient(176deg, #fffdf7 0%, var(--card) 38%, #fbf3e4 100%);
  border: 1px solid var(--line); border-radius: 18px; padding: 16px; text-align: center;
  /* a hairline of light along the top edge, then the shadow the card casts on the desk */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 2px 4px rgba(80, 60, 25, 0.07),
    0 14px 38px rgba(80, 60, 25, 0.17); }
.card.small { width: min(360px, 100%); }
.card.board { width: min(480px, 100%); }
.card h1 { font-size: clamp(30px, 9vw, 40px); margin: 2px 0 4px; font-weight: 800; letter-spacing: -0.01em; }
.card h2 { font-size: 22px; margin: 2px 0 8px; font-weight: 800; }
.tag { color: var(--muted); font-size: 13.5px; margin: 0 0 12px; }
.kick { display: block; font-size: 11px; font-weight: 800; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--muted); }
.muted { color: var(--muted); font-size: 13.5px; }
.gold { color: var(--gold); font-weight: 700; }
.warn { color: var(--red); font-weight: 700; }
.good { color: var(--green); font-weight: 700; }
.fine { font-size: 11.5px; color: var(--muted); margin-top: 10px; }
.fine a { color: inherit; }
.big-time { font-size: 38px; font-weight: 800; margin: 4px 0; font-variant-numeric: tabular-nums; }

.b { display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; border: 1px solid var(--line); background: var(--paper2); color: var(--ink);
  border-radius: 12px; padding: 10px 12px; font: inherit; font-weight: 700; cursor: pointer; }
.b small { font-weight: 500; font-size: 11.5px; color: var(--muted); }
.b.wide { display: flex; width: 100%; margin-top: 8px; }
.b.big { padding: 14px; font-size: 17px; }
.b.gold { background: linear-gradient(#f0c06a, var(--gold)); border-color: var(--gold); color: #2e1c04; }
.b.gold small { color: #58360a; }
.b.ghost { background: transparent; }
.b.slim { padding: 5px 10px; font-size: 12.5px; }
.b.due { box-shadow: 0 0 0 3px rgba(201, 137, 47, 0.22); }
.b:active { transform: translateY(1px); }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
/* a single button in a .row spans both columns instead of leaving a hole beside itself */
.row.one { grid-template-columns: 1fr; }

/* the seven cats across the top of the menu: the cast, and the key to the markings */
.cast { display: flex; justify-content: center; gap: 3px; margin: 2px 0 12px; }
.face { flex: 0 1 46px; max-width: 46px; }
.face .cat { filter: drop-shadow(0 2px 2px rgba(80, 55, 20, 0.25)); }
/* a gentle arc, so seven heads in a row read as a group and not as a toolbar */
.face:nth-child(1), .face:nth-child(7) { transform: translateY(5px) scale(0.9); }
.face:nth-child(2), .face:nth-child(6) { transform: translateY(2px) scale(0.96); }
.face:nth-child(4) { transform: translateY(-2px) scale(1.04); }
.tally { font-size: 12px; color: var(--muted); margin: 10px 0 0; font-weight: 600; }

/* ---- the ladder: 120 numbered levels ------------------------------------------------------
   One button per level, four per row, in a card that scrolls. Cleared levels keep their stars so
   the map is also the record; the next one is the only lit thing on the page. */
.lvmap { max-height: min(86vh, 760px); }
.lvgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; }
.lv { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  aspect-ratio: 1; border: 1px solid var(--line); border-radius: 12px; padding: 4px;
  background: linear-gradient(#fffdf8, #f6edda); color: var(--ink); font: inherit; cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 1px 2px rgba(90, 65, 25, 0.1); }
.lv b { font-size: 17px; font-weight: 800; font-variant-numeric: tabular-nums; }
.lv.done { background: linear-gradient(#f3e7cd, #e9dcc0); }
/* where the player is. The only gold on the map, so the eye lands on it and nowhere else */
.lv.now { background: linear-gradient(#f6cd83, var(--gold)); border-color: var(--gold); color: #2e1c04;
  box-shadow: 0 0 0 3px rgba(201, 137, 47, 0.25), 0 4px 10px rgba(150, 100, 30, 0.3); }
.lv.shut { background: #efe6d3; color: #9d8e74; opacity: 0.5; cursor: default; box-shadow: none; }
.lv:not(.shut):active { transform: translateY(1px); }
.stars { display: inline-flex; gap: 1px; line-height: 1; }
.star { font-style: normal; font-size: 9px; color: #cbbb9c; }
.star.on { color: var(--gold); }
.lv.now .star { color: rgba(46, 28, 4, 0.35); }
.lv.now .star.on { color: #2e1c04; }
/* the four chapters, named where they begin */
.band { grid-column: 1 / -1; margin: 10px 0 2px; font-size: 11px; font-weight: 800;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); text-align: left; }
.band.first { margin-top: 0; }
.bigstars { margin: 2px 0 6px; }
.bigstars .star { font-size: 30px; }
.shelf { margin: 14px 0 0; font-size: 11px; font-weight: 800; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted); }

/* the four rungs */
.ladder { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px; }
.rung { display: flex; flex-direction: column; align-items: flex-start; gap: 0;
  border: 1px solid var(--line); background: var(--paper2); border-radius: 12px;
  padding: 9px 11px; font: inherit; cursor: pointer; text-align: left; color: inherit; }
.rung b { font-size: 14.5px; }
.rung small { color: var(--muted); font-size: 11.5px; }
.rung em { font-style: normal; font-size: 11px; color: var(--gold); font-weight: 700; margin-top: 2px; }
.rung:active { transform: translateY(1px); }

/* ---- the board ------------------------------------------------------------------------- */
.bar { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; }
.chip { display: inline-flex; flex-direction: column; align-items: flex-start; line-height: 1.1;
  background: var(--paper2); border: 1px solid var(--line); border-radius: 10px; padding: 4px 9px; }
.chip small { font-size: 9px; font-weight: 800; letter-spacing: 0.1em; color: var(--muted); }
.chip b { font-size: 15px; font-variant-numeric: tabular-nums; }
.bar .b { margin-left: auto; }

/* The grid sits IN the page rather than on it: the frame is darker than its own cells and holds
   an inner shadow, so the squares read as cut out of the board. Flat before, and a flat grid of
   flat cells is the look of a spreadsheet. */
/* ---- paws, undo and hints ---------------------------------------------------------------
   Three paws you can lose, and the two things that keep losing them fair. The row sits directly
   over the board because that is where the eyes already are when a square turns red. */
.pawbar { display: flex; align-items: center; gap: 8px; margin: 8px 2px 0; }
.paws { display: inline-flex; gap: 5px; }
.paw { display: inline-flex; width: 26px; height: 26px; color: var(--gold);
  transition: transform 0.18s ease-out; }
.pawsvg { width: 100%; height: 100%; fill: currentColor; display: block;
  filter: drop-shadow(0 1px 0.5px rgba(80, 55, 20, 0.3)); }
/* a spent paw stays in its place and goes hollow: a paw that vanished would leave the player
   counting what is left instead of seeing what is gone */
.paw.gone { color: #cbbb9c; transform: scale(0.84); }
.paw.gone .pawsvg { filter: none; opacity: 0.75; }
.pawbar .tools { margin-left: auto; display: inline-flex; gap: 6px; }
/* one line, not two: .b stacks its label over its sub-label by default, which turned a count
     of three hints into a second line and a button twice as tall as the bar it sits in */
.pawbar .b { flex-direction: row; gap: 5px; padding: 7px 12px; }
.pawbar .b em { font-style: normal; font-weight: 800; color: var(--gold); }
.pawbar .b[disabled] { opacity: 0.42; cursor: default; }
/* the three spent paws, on the card that says they are spent. A card of three buttons and a
   sentence said nothing about WHAT ran out; this is the picture of it. */
.lostpaws { display: flex; justify-content: center; gap: 10px; margin: 6px 0 2px; }
.lostpaws .pawsvg { width: 38px; height: 38px; fill: #cbbb9c; }
.lostpaws .pawsvg:nth-child(2) { transform: rotate(-9deg); }
.lostpaws .pawsvg:nth-child(3) { transform: rotate(7deg); }

.grid { display: grid; grid-template-columns: repeat(var(--n), 1fr); gap: 4px;
  background: linear-gradient(#d2bd97, #c9b088); border: 3px solid #c3a87c; border-radius: 13px;
  padding: 4px; box-shadow: inset 0 2px 5px rgba(90, 65, 25, 0.25), 0 1px 0 rgba(255, 255, 255, 0.6); }
.cell { aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(#fffdf8, #fbf4e6); border: 0; border-radius: 7px; padding: 5%;
  cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95), 0 1px 2px rgba(90, 65, 25, 0.1); }
.cell { position: relative; }
.cell.given { background: linear-gradient(#ece0c8, #e2d4b6); cursor: default;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55), inset 0 0 0 1px rgba(150, 120, 70, 0.14); }
/* a given cat is the one you cannot change, so it is quieter, not louder */
.cell.given .cat { opacity: 0.85; }
/* ...but quieter was not ENOUGH. paper2 against card is a few percent of luminance: on a dim phone
   the two are one colour, and a player cannot tell the cats they were given from the ones they put
   there themselves (2026-11-09). So a given now carries a folded corner - a shape, not a tint,
   which is the same rule the cats are drawn under. */
.cell.given::after { content: ""; position: absolute; left: 0; top: 0;
  border-top: 12px solid var(--ink); border-right: 12px solid transparent; opacity: 0.55; }
.cell.same { box-shadow: inset 0 0 0 3px rgba(201, 137, 47, 0.55), 0 0 10px rgba(201, 137, 47, 0.25); }
/* a square the game filled in on a hint. Gold corner, mirroring the given cell's dark one: both
   say "this one was not your deduction", and both are a shape rather than a tint. */
.cell.gift::after { content: ""; position: absolute; right: 0; bottom: 0;
  border-bottom: 11px solid var(--gold); border-left: 11px solid transparent; opacity: 0.75; }
/* a square the chosen cat is ALLOWED in - the margin pencil mark, not an answer */
/* 0.32 gold on cream was a dot you had to look for; the pencil mark is the one piece of help
     on Medium and it has to be visible at arm's length on a dim phone */
.cell.can::before { content: ""; position: absolute; left: 50%; top: 50%; width: 24%; height: 24%;
  margin: -12% 0 0 -12%; border-radius: 50%; background: var(--gold); opacity: 0.5;
  box-shadow: 0 0 0 2px rgba(201, 137, 47, 0.14); }
.cell.bad { background: linear-gradient(#fbe3de, #f4d0c9); box-shadow: inset 0 0 0 3px var(--red);
  animation: cellWrong 0.26s ease-out; }
/* a wrong cat costs a paw, so the square says so with its whole body rather than just a colour */
@keyframes cellWrong {
  0% { transform: translateX(0); } 25% { transform: translateX(-3px); }
  55% { transform: translateX(3px); } 100% { transform: translateX(0); }
}
/* only while Stuck is held open: this one really does give the answer away */
.cell.right { background: #dff0d6; box-shadow: inset 0 0 0 3px var(--green); }
.cell:active { transform: scale(0.96); }
/* One small shadow under every cat, so it stands on the square instead of being printed into it.
   A filter and not a second element: the cats are SVG with transparent corners, and a box-shadow
   would draw a rectangle around a round head. Blur is kept to 1px - this runs on up to 49 cells. */
.cat { width: 100%; height: auto; display: block;
  filter: drop-shadow(0 1px 1px rgba(80, 55, 20, 0.28)); }

/* the tray doubles as a tally: the number is how many of that cat are still to place */
.tray { display: grid; grid-template-columns: repeat(auto-fit, minmax(42px, 1fr)); gap: 6px; margin-top: 10px; }
.pick { position: relative; display: flex; align-items: center; justify-content: center;
  background: var(--paper2); border: 2px solid var(--line); border-radius: 11px;
  padding: 5px; cursor: pointer; }
.pick { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 1px 2px rgba(90, 65, 25, 0.12); }
/* the chosen cat is the one thing on the screen that is allowed to come forward */
.pick.on { border-color: var(--gold); background: linear-gradient(#fff6e3, #fbe7c4);
  box-shadow: 0 0 0 3px rgba(201, 137, 47, 0.22), 0 4px 10px rgba(150, 100, 30, 0.25);
  transform: translateY(-2px); }
.pick.full { opacity: 0.45; }
.pick em { position: absolute; right: 2px; bottom: 1px; font-style: normal; font-size: 10px;
  font-weight: 800; color: var(--muted); background: var(--card); border-radius: 5px; padding: 0 3px; }
.pick:active { transform: translateY(1px); }
.b.slim.on { background: var(--paper2); border-color: var(--green); color: var(--green); }
/* Stuck is a crutch, and a crutch should not be the loudest thing under the board. It was the
   widest, heaviest button on the screen, competing with the tray a player actually plays with. */
.row.one .b.slim.wide { background: transparent; border-style: dashed; color: var(--muted);
  font-weight: 600; font-size: 12px; padding: 7px 10px; }
.row.one .b.slim.wide.on { background: #eef5e9; border-style: solid; }
/* TWO lines reserved, always. The hint swaps between a long line and a short warning, and the
   card is flex-centred, so a one-line min-height made the whole grid jump 9px up the instant a
   player placed a cat wrongly and back down when they undid it - jank at the exact moment they
   are learning the twist. Measured 2026-11-09: grid top 228 vs 237. */
.hint { font-size: 12.5px; color: var(--muted); margin: 9px 0 0; min-height: 2.9em; }

/* the twist, drawn: two 3x3 boards side by side, the same cat twice in each */
.rulepic { display: flex; gap: 12px; justify-content: center; margin: 12px 0 2px; }
.rulepic figure { margin: 0; flex: 1 1 0; max-width: 142px; text-align: center; }
.rulepic .grid { border-width: 2px; }
.rulepic figcaption { font-size: 11.5px; font-weight: 800; margin-top: 6px; line-height: 1.3; }
.rulepic .no { color: var(--red); }
.rulepic .ok { color: var(--green); }

/* a tall card keeps its way out on the screen, never below the fold */
.card.tall { text-align: left; }
.card.tall h2 { text-align: center; }
.card.tall ol { margin: 8px 0; padding-left: 20px; font-size: 14px; }
.card.tall li { margin-bottom: 7px; }
.card.tall .pin { position: sticky; bottom: -16px; margin: 0 -16px -16px;
  padding: 10px 16px 16px; background: linear-gradient(rgba(255, 250, 240, 0), var(--card) 42%); }

.fx-enter > * { animation: fxEnter 0.24s cubic-bezier(0.2, 0.8, 0.3, 1); }
@keyframes fxEnter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .cell:active, .pick:active, .b:active { transform: none; }
  .fx-enter > * { animation: none; } }
:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; border-radius: 6px; }

/* ---- today's three goals -------------------------------------------------------------------
   The one thing on this screen that is gone at midnight. It sits between the ladder and free
   play - directly under the two buttons that satisfy it - because a goal you have to scroll to
   is not a goal. Ticks are a shape (circle -> check) as well as a colour, like every other
   state in this game, so it still reads without colour vision. */
.goals { margin: 14px 0 0; padding: 10px 12px 8px; text-align: left;
  background: linear-gradient(#fffdf7, #f7eedb);
  border: 1px solid var(--line); border-radius: 14px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9); }
.goals .shelf { margin: 0 0 6px; display: flex; align-items: baseline; gap: 6px; }
.goals .shelf em { font-style: normal; margin-left: auto; color: var(--gold); letter-spacing: 0; }
/* the whole card goes gold once all three are in: the reward has to be visible from the menu */
.goals.all { border-color: var(--gold); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 0 0 2px rgba(201, 137, 47, 0.18); }

.goal { display: flex; align-items: flex-start; gap: 8px; margin: 0 0 4px;
  font-size: 13.5px; line-height: 1.35; color: var(--ink); }
.goal i { font-style: normal; width: 16px; flex: 0 0 16px; text-align: center;
  color: var(--line); font-size: 13px; line-height: 1.5; }
.goal span { flex: 1 1 auto; }
.goal b { font-variant-numeric: tabular-nums; color: var(--muted); font-size: 12px; }
.goal.on { color: var(--muted); }
.goal.on i { color: var(--green); }
.goal.on span { text-decoration: line-through; text-decoration-color: rgba(138, 117, 85, 0.5); }

.boon { margin: 6px 0 0; font-size: 11.5px; line-height: 1.35; color: var(--muted); }
.goals.all .boon { color: var(--gold); font-weight: 700; }

/* The one advert (menu only). Empty = no height. Kept well clear of the buttons above so a thumb
   aiming at "Help" cannot land on it, and labelled so it is never mistaken for part of the game. */
.adslot:empty { display: none; }
.adslot { margin-top: 28px; padding-top: 12px; border-top: 1px solid rgba(0, 0, 0, 0.08); min-height: 0; }
.adbox { max-width: 100%; overflow: hidden; }
.adtag { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 0 0 6px; text-align: center; }
/* Google marks a unit it could not fill; an unfilled one must not leave a labelled hole. */
.adbox:has(ins.adsbygoogle[data-ad-status="unfilled"]) { display: none; }
