/* PAINT SHOP — scoped to [data-app="paint"] / .win.w-paint.
   Replaces COLORING + COLOR BY NUMBER (css/cbn.css, retired 2026-08-03).

   ══════════════════════════════════════════════════════════════════════
   v2 STYLE PIVOT (2026-08-04) — pixel-arcade direction, his own art + his
   own approved mock. Visual contract: pbn/_mockups/paint-shop-v2.html
   (gallery) and pbn/_mockups/paint-shop-v2-board.html (board).
   Plan: _plans/2026-08-04-paint-shop-v2.md.

   TASK 1 (this section + the rail): the shared vocabulary every v2 surface
   is built from, plus the persistent left rail (js/paint/app.js owns it —
   built once, survives every view change). Every color below is SAMPLED
   from his reference render, not guessed — see the plan's Global
   Constraints for the sample list. The gallery (.pg-*) and board (.pb-*)
   component styles further down are STILL direction-D (dark museum wall) —
   Tasks 2 and 6 restyle those onto this vocabulary; until then they render
   transitionally, inside the new indigo shell.
   ══════════════════════════════════════════════════════════════════════ */

.win.w-paint {
  width: 1100px; height: 700px;
  pointer-events: auto;   /* #windows is pointer-events:none — CLAUDE.md gotcha #0 */
}
.w-paint .win-body {
  padding: 0; overflow: hidden;
  display: flex; flex-direction: column;
}

/* ── v2 sampled color vocabulary — do not re-pick by eye ──────────────── */
[data-app="paint"] {
  --ps-ink: #0b0733;
  --ps-page: #13106F;       /* indigo, NOT violet — the v1 mock used violet by mistake */
  --ps-page2: #0d0a52;
  --ps-panel: #2A1C8C;
  --ps-panel-lt: #5B45D8;
  --ps-panel-dk: #150F5E;
  --ps-gold: #FFC531;

  /* ── FOCUS / SELECTION RINGS FOR CHAMFERED CONTROLS (2026-08-06 fix) ────
     `outline` never renders on an element that also carries `.ps-ch` —
     clip-path restricts ALL of an element's own painting (fills, borders,
     outline, non-inset box-shadow, and — verified by pixel-sampling a
     focused card, NOT by trusting the doc's claim about the "sit-on-the-
     page" shadow elsewhere in this file — `filter: drop-shadow()` too) to
     strictly inside its own [0%,100%] box. Anything painted OUTSIDE that
     box is removed everywhere along the edge, not just at the chamfered
     corners: the clip polygon's coordinates never exceed the box, so there
     is nothing to clip differently corner vs. edge. This was reported as
     computed-style correct / rendered-pixels ZERO — the outline computed
     fine, but a pixel crop of a focused card showed no ring anywhere. It
     silently broke every such ring in the app, not just the gallery: the
     rail's own gold "which tier am I on" ring (`.ps-rb.on`, below) turned
     out to be exactly as invisible.
     An INSET box-shadow never leaves the box, so clip-path cannot remove
     it — confirmed by pixel-sampling a real card (a solid white band
     appeared exactly in the element's own 3px .ps-ink padding gap, the
     same gap that normally shows as the chamfer's black frame). Depth is
     capped at that 3px padding on purpose: any deeper and the opaque inner
     fill layer (e.g. `.pg-card-in`) paints over the rest of the ring,
     since children paint on top of their parent's own box-shadow. */
  --ps-focusring: inset 0 0 0 3px #fff;
  --ps-selectring: inset 0 0 0 3px var(--ps-gold);
}

/* ── THE CHAMFER — the one shape idea the whole v2 design is built from ──
   Two-layer pattern everywhere it's used: the outer box (this class) is the
   black outline, an inner box (also carrying .ps-ch) is the fill, so the
   outline follows the cut instead of framing a square corner. */
[data-app="paint"] .ps-ch {
  --n: 7px;
  clip-path: polygon(var(--n) 0, calc(100% - var(--n)) 0, 100% var(--n), 100% calc(100% - var(--n)),
    calc(100% - var(--n)) 100%, var(--n) 100%, 0 calc(100% - var(--n)), 0 var(--n));
}

/* ── THE BEVEL — the button/card "pressed pixel" gradient. Consumers set
   --c/--lt/--dk (the per-item color triad, e.g. .ps-c-easy below). ── */
[data-app="paint"] .ps-bev {
  background: linear-gradient(180deg, var(--lt) 0%, var(--c) 22%, var(--c) 62%, var(--dk) 100%);
}

/* ── OUTLINED PIXEL TEXT — 8-direction, ZERO blur (a blurred outline reads
   as fuzz; same trick the OS desktop icon labels use, CLAUDE.md gotcha #4). ── */
[data-app="paint"] .ps-px {
  font-family: 'Minecraft', monospace;
  color: #fff;
  text-shadow:
    -2px -2px 0 var(--ps-ink), 0 -2px 0 var(--ps-ink), 2px -2px 0 var(--ps-ink),
    -2px  0   0 var(--ps-ink),                          2px  0   0 var(--ps-ink),
    -2px  2px 0 var(--ps-ink), 0  2px 0 var(--ps-ink), 2px  2px 0 var(--ps-ink),
     0    4px 0 rgba(0, 0, 0, .45);
}

/* ── SIT-ON-THE-PAGE — the flat drop shadow every chamfered surface floats
   on. A plain filter, not a box-shadow, so it follows the chamfer's cut
   shape instead of boxing it. ── */
[data-app="paint"] .ps-drop {
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
}

/* ── the app shell: indigo page + persistent rail + swappable main column ─ */
[data-app="paint"] .ps {
  flex: 1; min-height: 0; min-width: 0;
  display: flex; gap: 14px; padding: 12px; position: relative;
  background: radial-gradient(130% 90% at 50% -15%, #241C9E 0%, var(--ps-page) 42%, var(--ps-page2) 100%);
}
[data-app="paint"] .ps::before {
  content: '';
  position: absolute; inset: 0; pointer-events: none; opacity: .4;
  background-image:
    repeating-linear-gradient(0deg, rgba(255, 255, 255, .03) 0 2px, transparent 2px 4px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, .03) 0 2px, transparent 2px 4px);
}

/* ── left rail — built once by js/paint/app.js, never rebuilt on nav ────
   Order: HOME (nav) / divider / EASY·MEDIUM·HARD·FREE COLOR (filters,
   these alone ever take .on) / divider / MY ART·HELP (nav). The dividers
   are an addition beyond his mock (his rail has none) — added so the two
   kinds of button read as separate groups instead of seven identical
   buttons; see CLAUDE.md-style plan note in app.js. ── */
[data-app="paint"] .ps-rail {
  width: 196px; flex-shrink: 0;
  display: flex; flex-direction: column; gap: 9px;
  position: relative; z-index: 1;
}
/* ⚠ REQUIRED: display:flex above is an author rule and beats the UA's
   [hidden]{display:none}, so app.js's `railEl.hidden = view === 'board'`
   would do nothing without this. Same trap as .pb-btn / .pb-rb below. */
[data-app="paint"] .ps-rail[hidden] { display: none; }
[data-app="paint"] .ps-raildiv {
  flex-shrink: 0; height: 2px; margin: 2px 5px;
  background: var(--ps-panel-lt); opacity: .35;
}
[data-app="paint"] .ps-rb {
  flex: 1; background: var(--ps-ink); padding: 3px; cursor: pointer; border: 0; display: block;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
}
[data-app="paint"] .ps-rb-in {
  height: 100%; display: flex; align-items: center; gap: 10px;
  padding: 4px 8px 4px 7px;
}
[data-app="paint"] .ps-rb-ico {
  width: 38px; height: 38px; flex-shrink: 0; object-fit: contain; image-rendering: pixelated;
  filter: drop-shadow(0 2px 0 rgba(0, 0, 0, .35));
}
[data-app="paint"] .ps-rb-lbl {
  font-size: 15px; line-height: 1.05; letter-spacing: .3px; text-align: left;
  min-width: 0; flex: 1;
}
/* ⚠ ONLY tier buttons ([data-tier]) ever receive .on — app.js never toggles
   it on a [data-nav] button. A nav button that could look selected but never
   latches is the same confusion as the star badge he removed. */
/* ⚠ .ps-rb carries .ps-ch (clip-path) — outline would be silently clipped
   away, see the --ps-focusring/--ps-selectring note above. box-shadow is a
   separate property from the button's own `filter: drop-shadow(...)`
   sit-on-page shadow, so that stays untouched — no need to recombine it. */
[data-app="paint"] .ps-rb.on { outline: none; box-shadow: var(--ps-selectring); }
[data-app="paint"] .ps-rb:hover .ps-rb-in { filter: brightness(1.08); }
[data-app="paint"] .ps-rb:focus-visible { outline: none; box-shadow: var(--ps-focusring); }
[data-app="paint"] .ps-rb:active .ps-rb-in { filter: brightness(.94); }

/* sampled from his reference — one triad per rail button/color group */
[data-app="paint"] .ps-c-home { --c: #0196EC; --lt: #63C6FF; --dk: #0166A8; }
[data-app="paint"] .ps-c-easy { --c: #69C901; --lt: #B4F55E; --dk: #3E8300; }
[data-app="paint"] .ps-c-med  { --c: #02A3F0; --lt: #6FD2FF; --dk: #0170AC; }
[data-app="paint"] .ps-c-hard { --c: #8433F0; --lt: #C08CFF; --dk: #5A16AE; }
/* EXTREME: the only triad NOT sampled from his reference -- the tier did not exist when he
   drew it. Red is the one 'beyond hard' hue his palette had left (help owns the pink).
   Swap freely if he picks another. */
[data-app="paint"] .ps-c-extreme { --c: #E63329; --lt: #FF8079; --dk: #9E1710; }
[data-app="paint"] .ps-c-free { --c: #F0A012; --lt: #FFD470; --dk: #B05C00; }
[data-app="paint"] .ps-c-art  { --c: #02BA9E; --lt: #6DF0DB; --dk: #01806C; }
[data-app="paint"] .ps-c-help { --c: #F83273; --lt: #FF8FB4; --dk: #B00840; }

/* ── the logo, top of the rail — the STACKED lockup (his own art, 807×493),
   not the inline one; at 196px the inline lockup's letterforms fall under
   10px tall. See img/paint/logo.webp. ── */
[data-app="paint"] .ps-logo {
  flex-shrink: 0; display: flex; align-items: center; justify-content: center; padding: 2px 0 8px;
}
[data-app="paint"] .ps-logo img {
  width: 100%; height: auto; image-rendering: pixelated;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
}

/* ── the main column — only this swaps between views; the rail above it
   never rebuilds ── */
[data-app="paint"] .ps-main {
  flex: 1; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; position: relative; z-index: 1;
}

/* ── the chamfered panel — the frame every view's content sits in. Used
   directly by the Task 1 HOME/MY ART placeholders below; Tasks 2 and 6
   wrap the gallery/board content in it too. ── */
[data-app="paint"] .ps-panel {
  flex: 1; min-height: 0; background: var(--ps-ink); padding: 3px;
  filter: drop-shadow(0 4px 0 rgba(0, 0, 0, .5)); position: relative;
}
[data-app="paint"] .ps-panel-in {
  height: 100%;
  background: linear-gradient(180deg, var(--ps-panel) 0%, var(--ps-panel) 70%, var(--ps-panel-dk) 100%);
  border-top: 3px solid var(--ps-panel-lt); border-left: 3px solid var(--ps-panel-lt);
  padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 9px;
}
[data-app="paint"] .ps-banner {
  align-self: center; background: var(--ps-ink); padding: 3px; margin-top: -1px;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .45));
}
[data-app="paint"] .ps-banner-in {
  background: linear-gradient(180deg, #4A32B8 0%, #2B1B7E 45%, #1D1160 100%);
  padding: 5px 34px; border-top: 2px solid #6B52D8;
}
[data-app="paint"] .ps-banner span { font-size: 19px; letter-spacing: 2.5px; }

/* ── HOME / MY ART placeholders (Task 1 only — Tasks 4/5 build the real
   views on this same .ps-panel frame) ── */
[data-app="paint"] .ps-placeholder-body {
  margin: auto; max-width: 46ch; text-align: center;
  font-family: 'JetBrains Mono', monospace; font-weight: 600; font-size: 13.5px;
  color: #cfc9f0;
}

.w-paint #paint-root {
  flex: 1; min-height: 0; min-width: 0;
  display: flex; flex-direction: column;
  /* transparent — the indigo .ps shell shows through a chamfered panel's cut
     corners; an opaque background here painted as a grey box in those cuts. */
  background: transparent;
  color: #f2f2ec;
}
.w-paint #paint-root[hidden] { display: none; }

[data-app="paint"] .paint-placeholder {
  margin: auto;
  max-width: 60ch;
  text-align: center;
  padding: 24px;
}
[data-app="paint"] .paint-placeholder h2 {
  font-family: 'Geologica', sans-serif;
  font-weight: 900;
  font-size: 28px;
  letter-spacing: .04em;
  color: var(--accent, #C6F542);
  margin: 0 0 10px;
}
[data-app="paint"] .paint-placeholder p {
  font-family: 'JetBrains Mono', monospace;
  font-weight: 600;
  font-size: 14px;
  color: #9c9ba8;
  margin: 0;
  word-break: break-word;
}
[data-app="paint"] .paint-placeholder-error h2 { color: var(--hover, #FF2E7E); }

/* ══════════════════════════════════════════════════════════════════════
   GALLERY — v2 pixel-arcade. Visual contract: pbn/_mockups/paint-shop-v2.html.
   No masthead here — the rail (js/paint/app.js) owns the ONE logo. No tab
   row here either — the rail's gold ring is the ONE tier selector; gallery.js
   is handed the active tab at mount time and renders only that tab's grid,
   so the two controls can never disagree about which tier is showing.
   ══════════════════════════════════════════════════════════════════════ */

[data-app="paint"] .pg-root {
  flex: 1; min-height: 0; min-width: 0;
  display: flex; flex-direction: column;
}

/* ── the card grid ── the mock's exact `.ps-grid` sizes to a fixed 12-card
   layout (grid-auto-rows:1fr) because its demo never scrolls. Real tabs run
   10-28 boards and must scroll, so rows are sized to CONTENT (`auto`) and
   `.pg-art` below carries a fixed aspect-ratio instead — that gives every
   card a stable intrinsic height regardless of how many rows exist, without
   the fractional-1fr-row collapse this file's other grids have hit before. */
[data-app="paint"] .pg-grid {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: auto;
  align-content: start;
  gap: 11px; overflow-y: auto; overflow-x: hidden;
  /* same scrollport rule as .pb-swatches: a card's focus ring is an `outline`
     (3px + 2px offset) drawn OUTSIDE its box, and this container clips at its
     padding box — 2px left/top cut the ring off on the first row and column. */
  padding: 6px;
}
[data-app="paint"] .pg-empty {
  grid-column: 1 / -1; margin: 20px 0; text-align: center;
  font-family: 'JetBrains Mono', monospace; font-weight: 600; font-size: 13px;
  color: #cfc9f0;
}

[data-app="paint"] .pg-card {
  appearance: none; -webkit-appearance: none; border: 0; margin: 0; padding: 3px;
  display: block; width: 100%; text-align: left; font: inherit; color: inherit;
  cursor: pointer; background: var(--ps-ink);
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
  transition: filter .1s, transform .05s;
}
[data-app="paint"] .pg-card:hover .pg-card-in { filter: brightness(1.07); }
[data-app="paint"] .pg-card:active { transform: translateY(1px); }
/* ⚠ .pg-card carries .ps-ch — outline is clipped away, see --ps-focusring
   note in the sampled-color block above. This was the audit's finding: a
   focused card's outline reported correctly via getComputedStyle while
   rendering ZERO visible pixels, verified by a 2x pixel crop. */
[data-app="paint"] .pg-card:focus-visible { outline: none; box-shadow: var(--ps-focusring); }

/* card frame — the bevel gradient, sampled per-slot color triad (f1-f7) */
[data-app="paint"] .pg-card-in {
  height: 100%; display: flex; flex-direction: column; padding: 5px 5px 0;
  background: linear-gradient(180deg, var(--lt) 0%, var(--c) 20%, var(--c) 60%, var(--dk) 100%);
}
/* the art sits in a light inner border, as in his reference */
[data-app="paint"] .pg-art {
  position: relative; overflow: hidden; background: #fff;
  aspect-ratio: 3 / 2;
  box-shadow: 0 0 0 2px var(--lt), 0 0 0 4px var(--ps-ink);
}
[data-app="paint"] .pg-art img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  /* ⚠ full color, no filter — an unstarted picture is not "disabled".
     grayscale(75%) shipped once here and read as broken/off-limits. */
}
/* Progress lives on the bottom edge of the artwork — no badge, and it reads
   at a glance because it's a length, not a symbol a child has to decode. A
   FINISHED board needs no marker: its thumbnail swaps from outline to the
   painted version (src swap above) — the picture IS the reward. */
[data-app="paint"] .pg-prog {
  position: absolute; left: 0; right: 0; bottom: 0; height: 7px;
  background: rgba(11, 7, 51, .55);
}
[data-app="paint"] .pg-prog i {
  display: block; height: 100%; background: var(--lt);
  box-shadow: 0 0 0 1px var(--ps-ink) inset;
}
/* ⚠ padding-top is 9px, not the 4px it looks like it needs: .pg-art's outer
   ring is a BOX-SHADOW (0 0 0 4px), which paints outside the art's border box
   and therefore straight over the first 4px of this padding. At 4px the name
   sat flush against the black ring with no visible gap (his review, 2026-08-05).
   Any change here has to budget those 4px before the gap starts. */
[data-app="paint"] .pg-name {
  flex-shrink: 0; text-align: center;
  padding: 9px 8px 6px; font-size: 12px; letter-spacing: .5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* card frame colors — cycled f1-f7, sampled from his reference, same rhythm */
[data-app="paint"] .pg-f1 { --c: #2E8BE6; --lt: #8CC6FF; --dk: #14549E; }
[data-app="paint"] .pg-f2 { --c: #4CBB3C; --lt: #9DEE8C; --dk: #22781A; }
[data-app="paint"] .pg-f3 { --c: #F0407F; --lt: #FF9BBE; --dk: #A3134B; }
[data-app="paint"] .pg-f4 { --c: #F5A020; --lt: #FFD48A; --dk: #A96400; }
[data-app="paint"] .pg-f5 { --c: #E63B2E; --lt: #FF9A8F; --dk: #9C1710; }
[data-app="paint"] .pg-f6 { --c: #21B39B; --lt: #8AEDDA; --dk: #0E7360; }
[data-app="paint"] .pg-f7 { --c: #8C52E0; --lt: #C7A2FF; --dk: #5A28A0; }

/* ── narrow ladder — ResizeObserver classes on .win.w-paint (which is also
   the [data-app="paint"] element itself — see index.html), never @media
   (CLAUDE.md gotcha #0b: these are draggable windows, not the viewport) ── */
.win.w-paint.paint-narrow .pg-grid { grid-template-columns: repeat(3, 1fr); }
.win.w-paint.paint-tiny .pg-grid { grid-template-columns: 1fr; gap: 9px; }

/* ══════════════════════════════════════════════════════════════════════
   HOME — the hub (js/paint/home.js). No mock of his to match: built on the
   Task 1 vocabulary so it reads as the same app as the gallery.

   ⚠ ONE SCREEN AT 1366×768, NO SCROLLING (the plan's rule — a hub that
   scrolls is a menu). Every row is a flex child with min-height:0 and its
   own overflow:hidden, so a short window SHRINKS the rows instead of
   growing past the panel. Only .paint-tiny — a window far below the
   Chromebook target — is allowed to scroll.
   ══════════════════════════════════════════════════════════════════════ */

[data-app="paint"] .ph-root {
  flex: 1; min-height: 0; min-width: 0;
  display: flex; flex-direction: column;
}
[data-app="paint"] .ph-root .ps-panel-in { overflow: hidden; }

[data-app="paint"] .ph-top {
  flex: 1.35 1 0; min-height: 0;
  display: flex; gap: 11px;
}

/* ── CONTINUE — first, biggest, and the only gold surface in the app, so a
   returning child's eye lands on it before anything else ── */
[data-app="paint"] .ph-cont {
  --c: #F5B31A; --lt: #FFE08A; --dk: #A96400;
  flex: 1.5 1 0; min-width: 0;
  appearance: none; -webkit-appearance: none; border: 0; margin: 0; padding: 3px;
  text-align: left; font: inherit; cursor: pointer; background: var(--ps-ink);
  filter: drop-shadow(0 4px 0 rgba(0, 0, 0, .5));
  transition: filter .1s, transform .05s;
}
[data-app="paint"] .ph-cont:hover .ph-cont-in { filter: brightness(1.06); }
[data-app="paint"] .ph-cont:active { transform: translateY(1px); }
/* .ph-cont carries .ps-ch — outline clipped away, same fix as .pg-card */
[data-app="paint"] .ph-cont:focus-visible { outline: none; box-shadow: var(--ps-focusring); }
[data-app="paint"] .ph-cont-in {
  height: 100%; display: flex; align-items: stretch; gap: 12px; padding: 10px;
  background: linear-gradient(180deg, var(--lt) 0%, var(--c) 20%, var(--c) 62%, var(--dk) 100%);
}
/* ⚠ The thumbnail keeps the boards' own 3:2 and is CENTRED, never stretched to
   the card's height: filling a ~250px-tall slot with object-fit:cover crops a
   landscape picture down to a meaningless middle strip, which reads as a broken
   image rather than as the picture they were painting. */
[data-app="paint"] .ph-cont-art {
  flex: 0 0 44%; min-width: 0; align-self: stretch;
  background: #fff; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 2px var(--lt), 0 0 0 4px var(--ps-ink);
}
/* ⚠ contain, NOT cover: the hero slot is much taller than a board's 3:2, and
   cover crops a landscape picture to a meaningless middle strip that reads as a
   broken image. The boards' paper is white, so the letterboxing is invisible. */
[data-app="paint"] .ph-cont-art img {
  display: block; width: 100%; height: 100%; object-fit: contain;
}
/* the empty state shows the EASY paint-can icon, which must not be cropped
   to fill a 3:2 box the way a board thumbnail is */
[data-app="paint"] .ph-cont-empty .ph-cont-art { background: var(--ps-panel); }
[data-app="paint"] .ph-cont-empty .ph-cont-art img {
  width: auto; height: 62%; object-fit: contain; image-rendering: pixelated;
}
[data-app="paint"] .ph-cont-txt {
  flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 7px;
}
[data-app="paint"] .ph-kicker { font-size: 12px; letter-spacing: 2.5px; }
[data-app="paint"] .ph-cont-name {
  font-size: 21px; line-height: 1.15;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
[data-app="paint"] .ph-bar {
  display: block; height: 11px; background: rgba(11, 7, 51, .55);
  box-shadow: 0 0 0 2px var(--ps-ink);
}
[data-app="paint"] .ph-bar i { display: block; height: 100%; background: var(--lt); }
[data-app="paint"] .ph-cont-meta {
  font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 11.5px;
  color: var(--ps-ink); line-height: 1.35;
}
[data-app="paint"] .ph-go { font-size: 16px; letter-spacing: 1px; margin-top: 2px; }

/* ── YOUR PROGRESS ── */
[data-app="paint"] .ph-box {
  flex: 1 1 0; min-width: 0; background: var(--ps-ink); padding: 3px;
  filter: drop-shadow(0 4px 0 rgba(0, 0, 0, .5));
}
[data-app="paint"] .ph-box-in {
  height: 100%; display: flex; flex-direction: column; gap: 7px; padding: 9px 11px 11px;
  background: linear-gradient(180deg, #2B1D91 0%, #1D1266 100%);
  border-top: 3px solid var(--ps-panel-lt); border-left: 3px solid var(--ps-panel-lt);
}
[data-app="paint"] .ph-h { margin: 0; font-size: 13px; letter-spacing: 2px; }
[data-app="paint"] .ph-big {
  margin: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
[data-app="paint"] .ph-big b { font-size: 30px; line-height: 1; }
[data-app="paint"] .ph-big span {
  font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 11.5px; color: #cfc9f0;
}
[data-app="paint"] .ph-tiers { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
[data-app="paint"] .ph-trow {
  display: grid; grid-template-columns: 88px 1fr 42px; align-items: center; gap: 8px;
}
[data-app="paint"] .ph-tname {
  font-family: 'Pixeloid Sans', monospace; font-weight: 700; font-size: 11px;
  letter-spacing: .5px; color: var(--lt);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[data-app="paint"] .ph-tbar { height: 9px; }
[data-app="paint"] .ph-tbar i { background: var(--c); }
[data-app="paint"] .ph-tnum {
  font-family: 'Pixeloid Mono', monospace; font-size: 11px; color: #cfc9f0; text-align: right;
}
[data-app="paint"] .ph-artlink {
  align-self: flex-start; margin-top: auto;
  appearance: none; -webkit-appearance: none; border: 2px solid var(--ps-ink);
  background: #02BA9E; color: #fff; font-size: 12px; letter-spacing: 1px;
  padding: 10px 14px; min-height: 44px; cursor: pointer;
}
[data-app="paint"] .ph-artlink:hover { background: #6DF0DB; }
[data-app="paint"] .ph-artlink:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
[data-app="paint"] .ph-hint {
  margin: auto 0 0; font-family: 'JetBrains Mono', monospace; font-weight: 600;
  font-size: 11px; color: #a9a1d8;
}

/* ── TWO KINDS OF PAGES — the explainer he asked for. The chip strips SHOW
   the difference; the copy alone never lands with a 7-year-old. ── */
[data-app="paint"] .ph-kinds {
  flex: .9 1 0; min-height: 0; display: flex; flex-direction: column; gap: 6px;
}
[data-app="paint"] .ph-h-wide { align-self: center; }
[data-app="paint"] .ph-kindrow {
  flex: 1; min-height: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 11px;
}
[data-app="paint"] .ph-kind {
  appearance: none; -webkit-appearance: none; border: 0; margin: 0; padding: 3px;
  text-align: left; font: inherit; cursor: pointer; background: var(--ps-ink);
  min-width: 0; filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
  transition: filter .1s, transform .05s;
}
[data-app="paint"] .ph-kind:hover .ph-kind-in { filter: brightness(1.07); }
[data-app="paint"] .ph-kind:active { transform: translateY(1px); }
/* .ph-kind carries .ps-ch — outline clipped away, same fix as .pg-card */
[data-app="paint"] .ph-kind:focus-visible { outline: none; box-shadow: var(--ps-focusring); }
[data-app="paint"] .ph-kind-in {
  height: 100%; display: flex; align-items: center; gap: 11px; padding: 9px 11px;
  background: linear-gradient(180deg, var(--lt) 0%, var(--c) 20%, var(--c) 62%, var(--dk) 100%);
}
[data-app="paint"] .ph-kind-ico {
  width: 44px; height: 44px; flex-shrink: 0; object-fit: contain; image-rendering: pixelated;
  filter: drop-shadow(0 2px 0 rgba(0, 0, 0, .35));
}
[data-app="paint"] .ph-kind-txt { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
[data-app="paint"] .ph-kind-name { font-size: 15px; letter-spacing: 1px; }
[data-app="paint"] .ph-kind-desc {
  font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 11px; line-height: 1.35;
  color: var(--ps-ink);
}
[data-app="paint"] .ph-chips { display: flex; gap: 5px; }
[data-app="paint"] .ph-chip {
  width: 21px; height: 21px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid var(--ps-ink);
  font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 11px; color: var(--ps-ink);
}
[data-app="paint"] .ph-chip-1 { background: #FF6FA8; }
[data-app="paint"] .ph-chip-2 { background: #6FD2FF; }
[data-app="paint"] .ph-chip-3 { background: #B4F55E; }
[data-app="paint"] .ph-chip-4 { background: #FFD470; }
/* the free-color strip sits on ORANGE, so it needs its own set — the numbered
   strip's pale yellow chip all but disappears against that card */
[data-app="paint"] .ph-chip-5 { background: #FF6FA8; }
[data-app="paint"] .ph-chip-6 { background: #6FD2FF; }
[data-app="paint"] .ph-chip-7 { background: #B4F55E; }
[data-app="paint"] .ph-chip-8 { background: #C08CFF; }

/* ── JUMP TO A TIER ── */
[data-app="paint"] .ph-jump {
  flex: 0 0 auto; display: grid; grid-template-columns: repeat(4, 1fr); gap: 11px;
}
[data-app="paint"] .ph-jb {
  appearance: none; -webkit-appearance: none; border: 0; margin: 0; padding: 3px;
  background: var(--ps-ink); cursor: pointer; min-width: 0;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
  transition: filter .1s, transform .05s;
}
[data-app="paint"] .ph-jb:hover .ph-jb-in { filter: brightness(1.08); }
[data-app="paint"] .ph-jb:active { transform: translateY(1px); }
/* .ph-jb carries .ps-ch — outline clipped away, same fix as .pg-card */
[data-app="paint"] .ph-jb:focus-visible { outline: none; box-shadow: var(--ps-focusring); }
[data-app="paint"] .ph-jb-in {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  padding: 8px 6px;
}
[data-app="paint"] .ph-jb-in .ps-px { font-size: 14px; letter-spacing: 1px; text-align: center; }
[data-app="paint"] .ph-jb-n {
  font-family: 'Pixeloid Mono', monospace; font-size: 10px; color: var(--ps-ink);
}

/* ── narrow ladder — ResizeObserver classes, never @media (gotcha #0b) ── */
.win.w-paint.paint-narrow .ph-cont-art { flex-basis: 36%; }
.win.w-paint.paint-narrow .ph-cont-name { font-size: 16px; }
.win.w-paint.paint-narrow .ph-big b { font-size: 24px; }
.win.w-paint.paint-narrow .ph-kind-desc { font-size: 10px; }
/* ⚠ the label column stays 88px at every width — "FREE COLOR" in Pixeloid Sans
   700 at 11px measures ~84px, and a narrower column ellipsises it to "FREE CO…" */
.win.w-paint.paint-narrow .ph-trow { grid-template-columns: 88px 1fr 38px; }
/* Below ~620px there is no honest way to keep four columns and a hero card on
   one screen, so the hub stacks and becomes scrollable — the no-scroll rule is
   about the 1366×768 Chromebook, not about a window dragged to a sliver. */
.win.w-paint.paint-tiny .ph-root .ps-panel-in { overflow-y: auto; }
.win.w-paint.paint-tiny .ph-top { flex-direction: column; flex: 0 0 auto; }
.win.w-paint.paint-tiny .ph-cont-in { flex-direction: column; }
.win.w-paint.paint-tiny .ph-cont-art { flex-basis: 130px; }
.win.w-paint.paint-tiny .ph-kindrow { grid-template-columns: 1fr; }
.win.w-paint.paint-tiny .ph-kinds { flex: 0 0 auto; }
.win.w-paint.paint-tiny .ph-jump { grid-template-columns: repeat(2, 1fr); }

/* ══════════════════════════════════════════════════════════════════════
   MY ART — the finished-pictures gallery (js/paint/myart.js). Same
   chamfered card frame and f1-f7 cycle as the main gallery, so the two
   screens read as one app; only the actions differ.
   ══════════════════════════════════════════════════════════════════════ */

[data-app="paint"] .pm-root {
  flex: 1; min-height: 0; min-width: 0;
  display: flex; flex-direction: column;
}
[data-app="paint"] .pm-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
[data-app="paint"] .pm-loading {
  margin: auto; font-family: 'JetBrains Mono', monospace; font-weight: 600;
  font-size: 13px; color: #cfc9f0;
}
[data-app="paint"] .pm-grid {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: auto;
  align-content: start; gap: 11px; overflow-y: auto; overflow-x: hidden;
  padding: 6px;   /* focus-ring room — see .pg-grid */
}
[data-app="paint"] .pm-card {
  padding: 3px; background: var(--ps-ink); min-width: 0;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
}
[data-app="paint"] .pm-card-in {
  height: 100%; display: flex; flex-direction: column; padding: 5px 5px 6px;
  background: linear-gradient(180deg, var(--lt) 0%, var(--c) 20%, var(--c) 62%, var(--dk) 100%);
}
[data-app="paint"] .pm-art {
  background: #fff; overflow: hidden; aspect-ratio: 3 / 2;
  box-shadow: 0 0 0 2px var(--lt), 0 0 0 4px var(--ps-ink);
}
[data-app="paint"] .pm-art img { display: block; width: 100%; height: 100%; object-fit: contain; }
/* same 4px box-shadow ring budget as .pg-name above */
[data-app="paint"] .pm-name {
  flex-shrink: 0; text-align: center; padding: 10px 6px 3px; font-size: 12px; letter-spacing: .5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[data-app="paint"] .pm-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  padding: 0 2px 6px;
  font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 9.5px;
  color: var(--ps-ink);
}
[data-app="paint"] .pm-when { opacity: .8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
[data-app="paint"] .pm-acts { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: auto; }
[data-app="paint"] .pm-btn {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  border: 2px solid var(--ps-ink); background: rgba(255, 255, 255, .82); color: var(--ps-ink);
  font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 9.5px; letter-spacing: .02em;
  padding: 7px 4px; min-height: 44px;
}
[data-app="paint"] .pm-btn:hover { background: #fff; }
[data-app="paint"] .pm-btn:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
/* armed = tap 2 of 2. House destructive color, and it stays fully readable —
   the same language the board's START OVER uses. */
[data-app="paint"] .pm-btn.pm-armed {
  background: var(--hover, #FF2E7E); color: #fff; border-color: var(--ps-ink);
}

/* ── the empty state — the screen every child sees FIRST ── */
[data-app="paint"] .pm-body-empty { align-items: center; justify-content: center; }
[data-app="paint"] .pm-empty {
  margin: auto; max-width: 42ch; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
[data-app="paint"] .pm-empty-ico {
  width: 76px; height: 76px; object-fit: contain; image-rendering: pixelated;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .4));
}
[data-app="paint"] .pm-empty h3 { margin: 0; font-size: 17px; letter-spacing: 2px; }
[data-app="paint"] .pm-empty p {
  margin: 0; font-family: 'JetBrains Mono', monospace; font-weight: 600; font-size: 12.5px;
  line-height: 1.5; color: #d3cdf2;
}
[data-app="paint"] .pm-empty p b { color: #fff; }
[data-app="paint"] .pm-cta {
  appearance: none; -webkit-appearance: none; border: 0; margin: 4px 0 0; padding: 3px;
  background: var(--ps-ink); cursor: pointer;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
}
[data-app="paint"] .pm-cta-in { display: block; padding: 9px 20px; }
[data-app="paint"] .pm-cta-in .ps-px { font-size: 15px; letter-spacing: 1px; }
[data-app="paint"] .pm-cta:hover .pm-cta-in { filter: brightness(1.08); }
[data-app="paint"] .pm-cta:active { transform: translateY(1px); }
/* .pm-cta carries .ps-ch — outline clipped away, same fix as .pg-card */
[data-app="paint"] .pm-cta:focus-visible { outline: none; box-shadow: var(--ps-focusring); }

.win.w-paint.paint-narrow .pm-grid { grid-template-columns: repeat(3, 1fr); }
.win.w-paint.paint-tiny .pm-grid { grid-template-columns: 1fr; gap: 9px; }

/* ══════════════════════════════════════════════════════════════════════
   BOARD — v2 pixel-arcade. Visual contract:
   pbn/_mockups/paint-shop-v2-board.html (both board types).

   ⚠ CHROME ONLY. Every behaviour this styles is unchanged: zoom pill,
   wrong-color refusal shake, hint camera fly, win banner, confetti,
   two-tap START OVER. The direction-D sheet this replaced is GONE — one
   system per file, never two.

   ⚠ THE BOARD OWNS THE RAIL. js/paint/app.js hides the app rail
   (HOME / tiers / MY ART / HELP) while a board is open, exactly as the
   mock shows, and this rail takes its place: GALLERY, then the palette in
   the flexible middle where a child's hand keeps returning, then the
   actions, then START OVER alone at the bottom — a destructive control
   must not sit in the row being tapped repeatedly.
   ══════════════════════════════════════════════════════════════════════ */

[data-app="paint"] .pb-root {
  flex: 1; min-height: 0; min-width: 0;
  display: flex; gap: 12px; color: #f2f2ec;
  overflow: hidden;
}

/* a tighter chamfer for the small parts (swatch tiles, zoom keys) — a 7px
   notch on a 38px tile eats the whole corner */
[data-app="paint"] .ps-ch-sm {
  --n: 4px;
  clip-path: polygon(var(--n) 0, calc(100% - var(--n)) 0, 100% var(--n), 100% calc(100% - var(--n)),
    calc(100% - var(--n)) 100%, var(--n) 100%, 0 calc(100% - var(--n)), 0 var(--n));
}

/* ── the board's own rail ── */
/* ⚠ 232px, not the mock's 212: a 4-wide palette of 44px-minimum touch targets
   plus the scrollbar does not fit in 212, and shrinking the swatches is the one
   thing this rail must not do — they are the control a child taps hundreds of
   times. The stage gives up 20px of ~840 for it. */
[data-app="paint"] .pb-rail {
  width: 232px; flex-shrink: 0;
  display: flex; flex-direction: column; gap: 8px;
}
[data-app="paint"] .pb-railgap { flex: 1; min-height: 0; }
[data-app="paint"] .pb-rb {
  flex-shrink: 0; background: var(--ps-ink); padding: 3px; cursor: pointer; border: 0; display: block;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5));
  transition: filter .1s, transform .05s;
}
/* ⚠ THE [hidden] TRAP AGAIN. `display:block` above is an AUTHOR rule and beats
   the UA's [hidden]{display:none} — (0,2,0) vs (0,1,0). Without this line,
   setting hidden on HINT / FILL REST does nothing and both stay visible on
   free-color boards, where FILL REST floods the whole picture in one tap and
   saves it immediately. Same trap already logged for the pet dock, .cc-pcard
   and booth.css. */
[data-app="paint"] .pb-rb[hidden] { display: none; }
[data-app="paint"] .pb-rb-in {
  display: flex; align-items: center; gap: 9px; padding: 8px 8px 8px 10px;
  min-height: 38px;   /* + the 3px outer chamfer padding = a 44px touch target */
  background: linear-gradient(180deg, var(--lt) 0%, var(--c) 22%, var(--c) 62%, var(--dk) 100%);
}
[data-app="paint"] .pb-rb-ico { font-size: 18px; line-height: 1; filter: drop-shadow(0 2px 0 rgba(0, 0, 0, .3)); }
[data-app="paint"] .pb-rb-lbl {
  font-size: 14px; line-height: 1.05; letter-spacing: .3px; flex: 1; min-width: 0; text-align: left;
}
[data-app="paint"] .pb-rb-sm { margin-top: 6px; }
[data-app="paint"] .pb-rb-sm .pb-rb-in { padding: 6px 8px; min-height: 38px; }
[data-app="paint"] .pb-rb-sm .pb-rb-lbl { font-size: 12px; }
[data-app="paint"] .pb-rb:hover .pb-rb-in { filter: brightness(1.08); }
[data-app="paint"] .pb-rb:active { transform: translateY(1px); }
/* .pb-rb carries .ps-ch — outline clipped away, same fix as .pg-card */
[data-app="paint"] .pb-rb:focus-visible { outline: none; box-shadow: var(--ps-focusring); }
[data-app="paint"] .pb-rb:disabled { cursor: default; filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .5)) grayscale(.55) brightness(.82); }
[data-app="paint"] .pb-rb:disabled:hover .pb-rb-in { filter: none; }

[data-app="paint"] .pb-c-back { --c: #0196EC; --lt: #63C6FF; --dk: #0166A8; }
[data-app="paint"] .pb-c-hint { --c: #F0A012; --lt: #FFD470; --dk: #B05C00; }
[data-app="paint"] .pb-c-fill { --c: #69C901; --lt: #B4F55E; --dk: #3E8300; }
[data-app="paint"] .pb-c-undo { --c: #7A6BB8; --lt: #B9AEE8; --dk: #453A80; }
[data-app="paint"] .pb-c-over { --c: #F83273; --lt: #FF8FB4; --dk: #B00840; }
/* armed = tap 2 of 2. Not disabled-styled: it stays fully readable, and only
   the confirm tap is destructive. */
[data-app="paint"] .pb-reset.pb-reset-armed { --c: #FF0F5B; --lt: #FF6E9C; --dk: #8E0033; }
[data-app="paint"] .pb-reset.pb-reset-armed .pb-rb-in { animation: pb-armpulse 1.1s ease-in-out infinite; }
@keyframes pb-armpulse { 50% { filter: brightness(1.18); } }

/* ── the palette, in the rail (his call) ──────────────────────────────
   4-wide GRID, not a strip: clawmachine ships 55 swatches and macaw 53,
   which as a horizontal strip was about four screens of SIDEWAYS scrolling
   (pbn/PLAN.md §0's open palette-size item — this closes it). A grid shows
   ~40 at once and overflows VERTICALLY, the gesture every wheel and
   trackpad already makes. */
[data-app="paint"] .pb-palbox {
  flex: 0 1 auto; min-height: 0; background: var(--ps-ink); padding: 3px;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .45)); display: flex;
}
[data-app="paint"] .pb-palbox-in {
  flex: 1; min-height: 0; max-height: 100%;
  background: linear-gradient(180deg, var(--ps-panel) 0%, var(--ps-panel-dk) 100%);
  border-top: 2px solid var(--ps-panel-lt); border-left: 2px solid var(--ps-panel-lt);
  /* ⚠ 5px sides, not 8: the horizontal breathing room moved INTO .pb-swatches
     below (see its padding note). Total inset is unchanged — it just has to sit
     inside the scrollport to stop clipping the selected swatch's ring. */
  padding: 8px 5px; display: flex; flex-direction: column; gap: 7px;
}
[data-app="paint"] .pb-pal-h { font-size: 11px; letter-spacing: 1px; flex-shrink: 0; }
/* ⚠ THE PADDING IS THE SELECTION RING'S ONLY ROOM. This is a scroll container,
   so it CLIPS AT ITS PADDING BOX — and .pb-sw.on draws its gold ring with an
   `outline` (3px + 1px offset = 4px OUTSIDE the tile), which is not part of the
   tile's box and gets no room from `gap`. With no padding the top row lost the
   top of its ring, the left and right columns lost their side, and the focus
   ring (3px + 2px offset = 5px) lost the same edges — his review, 2026-08-05.
   6px sides/top clears both rings; 8px bottom also clears the count badge's
   3px overhang, which is what the old padding-bottom was for (without it the
   last row pushes the container past its own height and a palette that fits
   comfortably shows a scrollbar).
   ⚠ Any new padding here comes straight off the swatch width — 4 columns must
   stay ≥44px (the touch minimum, and the one thing this rail must not shrink).
   Current sums: 232px rail → 44.2px columns. The 5px gap (was 6) pays for it. */
[data-app="paint"] .pb-swatches {
  min-height: 0; display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 5px; align-content: start; padding: 6px 6px 8px;
  overflow-x: hidden; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: #6B52D8 #1B1060;
}
/* a 15px classic scrollbar eats ~4px off every column; 8px keeps all four
   swatches above the touch minimum */
[data-app="paint"] .pb-swatches::-webkit-scrollbar { width: 8px; }
[data-app="paint"] .pb-swatches::-webkit-scrollbar-track { background: #1B1060; }
[data-app="paint"] .pb-swatches::-webkit-scrollbar-thumb { background: #6B52D8; }
/* ⚠ free-color boards used to widen this to gap:7px ("no counts, so the tiles
   can breathe"). Removed 2026-08-05: the ring padding above already gives them
   the breathing room, and 7px + that padding drops the columns to 42.8px —
   under the 44px touch minimum, on the very boards with the biggest palettes
   (clawmachine 55, macaw 53). The gap is now 5px on every board. */
[data-app="paint"] .pb-sw {
  position: relative; background: var(--ps-ink); padding: 2px; cursor: pointer; border: 0;
  appearance: none; -webkit-appearance: none;
}
/* ⚠ the chamfer lives on the INNER TILE, never on .pb-sw — clipping the button
   clips the count badge that deliberately overhangs its corner. */
[data-app="paint"] .pb-sw-in {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 40px;   /* + 2px padding top and bottom = 44px */
  font-family: 'Minecraft', monospace; font-size: 14px;
}
[data-app="paint"] .pb-sw.on { outline: 3px solid var(--ps-gold); outline-offset: 1px; z-index: 2; }
[data-app="paint"] .pb-sw:focus-visible { outline: 3px solid #fff; outline-offset: 2px; z-index: 2; }
[data-app="paint"] .pb-sw .pb-rem {
  position: absolute; right: -2px; bottom: -3px; z-index: 3;
  background: var(--ps-ink); color: #fff;
  font-family: 'Pixeloid Mono', monospace; font-size: 9px; font-weight: 400;
  padding: 0 3px; line-height: 1.35;
}
[data-app="paint"] .pb-sw.done .pb-sw-in { opacity: .3; }
[data-app="paint"] .pb-sw.done .pb-rem { background: #3E8300; }
/* free-color leaves the badge empty — an empty black nub in every corner reads
   as damage, not as "no count" */
[data-app="paint"] .pb-sw .pb-rem:empty { display: none; }

/* ── "+" PAD CELLS — what makes every row full (his rule, 2026-08-05) ──────
   A free-color palette is 15-55 colors and almost never divides by the column
   count, so the grid used to end in a ragged part-row. The tail is padded with
   MIX buttons, not blanks: the row is genuinely full, no color was dropped from
   his art, and the gap becomes the way in to the mixer. Styled as a CONTROL —
   panel-colored and dashed, never a flat square that reads as a 56th color. */
[data-app="paint"] .pb-sw-add { background: transparent; }
[data-app="paint"] .pb-sw-add .pb-sw-in {
  background: rgba(255, 255, 255, .07);
  box-shadow: inset 0 0 0 2px var(--ps-panel-lt);
  color: #cfc9f0; font-size: 19px; line-height: 1;
}
[data-app="paint"] .pb-sw-add:hover .pb-sw-in { background: rgba(255, 255, 255, .16); color: #fff; }
[data-app="paint"] .pb-sw-add:active { transform: translateY(1px); }
/* the child's own mixed/picked colors sit after the board's, marked with a thin
   light rim so "mine" is findable without a badge (he removed the star badge —
   a symbol a child has to decode is worse than none) */
[data-app="paint"] .pb-sw-custom { background: var(--ps-panel-lt); }

/* ── FILLER CELLS — numbered boards' half of the even-rows rule ────────────
   (2026-08-06, his open item.) A numbered board has no legitimate color to
   put in FREE COLOR's "+" slot — it has a fixed, correct-answer palette — so
   the tail is padded with a quiet, INERT tile instead: same row-completing
   job, but styled to read as the RAIL's own texture, never as a swatch or a
   button. No glyph, no hover state, default cursor, `<span>` not `<button>`
   (see makeFillerCell in board.js). */
[data-app="paint"] .pb-sw-filler { cursor: default; }
[data-app="paint"] .pb-sw-filler .pb-sw-in {
  background: repeating-linear-gradient(135deg,
    var(--ps-panel-lt) 0 3px, transparent 3px 7px);
  opacity: .3;
}

/* ── MIX / PICK — free-color only, a row of CONTROLS under the color grid ──
   Deliberately outside .pb-swatches: a control among the colors would break the
   even-rows rule and would read as a color a child could paint with. */
[data-app="paint"] .pb-palacts {
  flex-shrink: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
}
/* the [hidden] trap again — display:grid is an author rule and beats the UA's
   [hidden]{display:none}, so `palActsEl.hidden = !freeColor` would do nothing
   and MIX/PICK would show up on numbered boards. Same as .pb-rb / .pb-btn. */
[data-app="paint"] .pb-palacts[hidden] { display: none; }
[data-app="paint"] .pb-pa {
  appearance: none; -webkit-appearance: none; border: 0; margin: 0; padding: 2px;
  background: var(--ps-ink); cursor: pointer; min-width: 0;
  transition: filter .1s, transform .05s;
}
[data-app="paint"] .pb-pa-in {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  min-height: 40px;   /* + 2px padding top and bottom = a 44px touch target */
  padding: 4px 6px;
  background: linear-gradient(180deg, #6B52D8 0%, #4A32B8 45%, #2B1B7E 100%);
}
[data-app="paint"] .pb-pa-ico { font-size: 14px; line-height: 1; flex-shrink: 0; }
[data-app="paint"] .pb-pa-lbl { font-size: 12px; letter-spacing: .5px; min-width: 0; }
[data-app="paint"] .pb-pa:hover .pb-pa-in { filter: brightness(1.12); }
[data-app="paint"] .pb-pa:active { transform: translateY(1px); }
[data-app="paint"] .pb-pa:focus-visible { outline: 3px solid #fff; outline-offset: 1px; }
/* PICK is a MODE, so it latches — the same gold ring the selected swatch wears,
   because they mean the same thing: "this is what a tap will do right now" */
[data-app="paint"] .pb-drop.on { outline: 3px solid var(--ps-gold); outline-offset: 1px; }
[data-app="paint"] .pb-drop.on .pb-pa-in {
  background: linear-gradient(180deg, #FFD470 0%, #F0A012 45%, #B05C00 100%);
}
[data-app="paint"] .pb-drop.on .pb-pa-lbl { color: #fff; }
/* eyedropper armed: the cursor is the only thing telling a mouse user that the
   next click samples instead of paints. Beats .pb-canvas's `grab` on specificity. */
[data-app="paint"] .pb-stage.pb-picking .pb-canvas { cursor: crosshair; }

/* ⚠ VISUALLY hidden, not display:none — Chrome refuses to open the native color
   dialog for a display:none input, so MIX would silently do nothing. */
[data-app="paint"] .pb-mixinput {
  position: absolute; width: 1px; height: 1px; padding: 0; border: 0;
  opacity: 0; pointer-events: none;
}

/* ── main column: the title strip + the picture, full height ── */
[data-app="paint"] .pb-main {
  flex: 1; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; gap: 10px;
}
[data-app="paint"] .pb-bar {
  background: var(--ps-ink); padding: 3px; flex-shrink: 0;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .45));
}
[data-app="paint"] .pb-bar-in {
  background: linear-gradient(180deg, #4A32B8 0%, #2B1B7E 45%, #1D1160 100%);
  border-top: 2px solid #6B52D8; padding: 6px 12px;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
[data-app="paint"] .pb-title {
  margin: 0; font-size: 17px; letter-spacing: 1px; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 30ch;
}
[data-app="paint"] .pb-pill {
  flex-shrink: 0; font-size: 11px; padding: 3px 9px;
  background: var(--tc, #6c6a80); box-shadow: 0 0 0 3px var(--ps-ink);
}
[data-app="paint"] .pb-diff-easy   { --tc: #69C901; }
[data-app="paint"] .pb-diff-medium { --tc: #02A3F0; }
[data-app="paint"] .pb-diff-hard   { --tc: #8433F0; }
[data-app="paint"] .pb-diff-extreme { --tc: #E63329; }
[data-app="paint"] .pb-diff-free   { --tc: #8C52E0; }

[data-app="paint"] .pb-zoomgrp { display: flex; gap: 5px; margin-left: auto; flex-shrink: 0; }
[data-app="paint"] .pb-zb {
  background: var(--ps-ink); padding: 2px; cursor: pointer; border: 0;
  appearance: none; -webkit-appearance: none;
}
[data-app="paint"] .pb-zb-in {
  display: block; background: linear-gradient(180deg, #6B52D8, #3B2699);
  padding: 7px 12px; font-size: 14px; min-width: 40px; min-height: 40px; text-align: center;
  display: flex; align-items: center; justify-content: center;
}
[data-app="paint"] .pb-zb:hover .pb-zb-in { filter: brightness(1.12); }
[data-app="paint"] .pb-zb:active { transform: translateY(1px); }
/* .pb-zb carries .ps-ch — outline clipped away, same fix as .pg-card.
   ⚠ .pb-zb's own padding is 2px, not the usual 3px, so the ring uses its
   own 2px inset here rather than --ps-focusring — any deeper and the
   opaque .pb-zb-in fill would paint over the extra depth (see the ring
   note in the sampled-color block: the ring can only live in the gap
   between the outer box edge and where the inner fill begins). */
[data-app="paint"] .pb-zb:focus-visible { outline: none; box-shadow: inset 0 0 0 2px #fff; }

[data-app="paint"] .pb-track {
  width: 190px; height: 20px; background: var(--ps-ink); padding: 3px; flex-shrink: 0;
}
[data-app="paint"] .pb-trackin { height: 100%; background: #1B1060; }
[data-app="paint"] .pb-fill {
  height: 100%; width: 0%;
  background: linear-gradient(180deg, #B4F55E, #69C901);
  transition: width .25s ease;
}
[data-app="paint"] .pb-ptext {
  font-family: 'Pixeloid Mono', monospace; font-size: 13px; color: #C9BCF7;
  min-width: 66px; text-align: right; flex-shrink: 0;
}

/* ── the stage ── */
[data-app="paint"] .pb-stagewrap {
  flex: 1; min-height: 0; background: var(--ps-ink); padding: 3px;
  filter: drop-shadow(0 4px 0 rgba(0, 0, 0, .5));
}
/* ⚠ this background MUST equal STAGE_BG in js/paint/board.js — the canvas
   paints its own backdrop every frame, so a mismatch shows up as a rectangle
   of a different colour sitting inside the panel. */
[data-app="paint"] .pb-stage {
  position: relative; height: 100%; overflow: hidden;
  background: #241873; touch-action: none;
  border-top: 3px solid var(--ps-panel-lt); border-left: 3px solid var(--ps-panel-lt);
}
[data-app="paint"] .pb-canvas { position: absolute; inset: 0; display: block; cursor: grab; }
[data-app="paint"] .pb-stage.pb-dragging .pb-canvas { cursor: grabbing; }
[data-app="paint"] .pb-stage.pb-shake { animation: pb-shake .35s; }
@keyframes pb-shake {
  10%,90% { transform: translateX(-1px); }
  20%,80% { transform: translateX(2px); }
  30%,50%,70% { transform: translateX(-5px); }
  40%,60% { transform: translateX(5px); }
}
@keyframes pb-fall { to { transform: translateY(110vh) rotate(600deg); opacity: .2; } }

[data-app="paint"] .pb-load {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(19, 16, 111, .93); text-align: center; padding: 20px; z-index: 3;
  font: 700 13px/1.4 'JetBrains Mono', monospace; color: #d8d2f5;
}
[data-app="paint"] .pb-load[hidden] { display: none; }

[data-app="paint"] .pb-toast {
  position: absolute; left: 50%; bottom: 18px; transform: translate(-50%, 10px);
  background: var(--ps-ink); color: #fff; padding: 9px 16px; border: 2px solid #6B52D8;
  font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 12px;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; z-index: 6;
}
[data-app="paint"] .pb-toast.show { opacity: 1; transform: translate(-50%, 0); }

[data-app="paint"] .pb-win {
  position: absolute; left: 50%; top: 14%; transform: translate(-50%, -16px) scale(.94);
  background: linear-gradient(180deg, #4A32B8 0%, #2B1B7E 55%, #1D1160 100%);
  box-shadow: 0 0 0 3px var(--ps-ink), 0 12px 40px rgba(0, 0, 0, .75);
  border-top: 3px solid #6B52D8;
  padding: 24px 30px; text-align: center; opacity: 0; pointer-events: none;
  transition: opacity .3s, transform .3s; z-index: 7; max-width: 88%;
}
[data-app="paint"] .pb-win.show { opacity: 1; transform: translate(-50%, 0) scale(1); pointer-events: auto; }
[data-app="paint"] .pb-win h3 { font: 900 24px/1.2 'Geologica', sans-serif; margin: 0 0 8px; color: #fff; }
[data-app="paint"] .pb-win h3 .hl { color: var(--ps-gold); }
[data-app="paint"] .pb-win p { margin: 0 0 18px; font-size: 13px; color: #d3cdf2; }
[data-app="paint"] .pb-winrow { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

/* the win banner's two buttons and the zoom hint are the only plain buttons
   left on this screen — kept quiet so the celebration reads first */
[data-app="paint"] .pb-btn {
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-weight: 700;
  font-size: 11px; letter-spacing: .03em;
  padding: 9px 14px; min-height: 44px;
  background: rgba(255, 255, 255, .9); color: var(--ps-ink); border: 2px solid var(--ps-ink);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  white-space: nowrap; cursor: pointer;
  transition: background .1s, transform .05s;
}
[data-app="paint"] .pb-btn[hidden] { display: none; }
[data-app="paint"] .pb-btn:hover { background: #fff; }
[data-app="paint"] .pb-btn:active { transform: translateY(1px); }
[data-app="paint"] .pb-btn:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }

[data-app="paint"] .pb-zoomhint {
  position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
  background: var(--ps-ink); color: #fff; border: 2px solid #6B52D8;
  padding: 9px 16px; font: 700 12px 'JetBrains Mono', monospace; cursor: pointer; z-index: 6;
  display: inline-flex; align-items: center; gap: 6px; max-width: 92%;
}
[data-app="paint"] .pb-zoomhint b { color: var(--ps-gold); }
/* same [hidden] trap as .pb-btn — display:inline-flex is an author rule */
[data-app="paint"] .pb-zoomhint[hidden] { display: none; }

[data-app="paint"] .pb-live {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0);
}

/* ── narrow ladder — ResizeObserver classes, never @media (gotcha #0b) ── */
.win.w-paint.paint-narrow .pb-rail { width: 190px; }
.win.w-paint.paint-narrow .pb-title { max-width: 20ch; font-size: 15px; }
.win.w-paint.paint-narrow .pb-track { width: 130px; }
.win.w-paint.paint-narrow .pb-swatches { grid-template-columns: repeat(3, 1fr); }
.win.w-paint.paint-tiny .pb-rail { width: 132px; }
.win.w-paint.paint-tiny .pb-rb-lbl { font-size: 12px; }
.win.w-paint.paint-tiny .pb-rb-ico { font-size: 15px; }
.win.w-paint.paint-tiny .pb-rb-in { padding: 7px 6px; gap: 6px; }
.win.w-paint.paint-tiny .pb-title { max-width: 14ch; font-size: 14px; }
.win.w-paint.paint-tiny .pb-swatches { grid-template-columns: repeat(2, 1fr); }
/* a 132px rail cannot pay for both the ring padding and 44px columns out of the
   same 8px panel inset — the panel gives up 2px a side so the ring room (which
   is what stops the selection outline clipping) survives at every width */
.win.w-paint.paint-tiny .pb-palbox-in { padding: 8px 3px; }
/* two labelled buttons will not sit side by side in a 132px rail — "PICK" alone
   ellipsises. They stack instead of shrinking below the touch minimum. */
.win.w-paint.paint-tiny .pb-palacts { grid-template-columns: 1fr; }
.win.w-paint.paint-tiny .pb-track { width: 90px; }
.win.w-paint.paint-tiny .pb-ptext { font-size: 11px; min-width: 52px; }

/* ⚠ prefers-reduced-motion is one of the FEW correct @media queries in this
   project — it is about the person, not the window size (CLAUDE.md gotcha #0b).
   Covers every v2 surface, not just the board: the hover-brighten and 1px press
   on cards and rail buttons are motion too. board.js's REDUCED constant
   separately suppresses confetti and the fill flashes. */
@media (prefers-reduced-motion: reduce) {
  [data-app="paint"] .pb-stage.pb-shake,
  [data-app="paint"] .pb-win,
  [data-app="paint"] .pb-toast,
  [data-app="paint"] .pb-fill,
  [data-app="paint"] .pb-reset.pb-reset-armed .pb-rb-in,
  [data-app="paint"] .pb-rb,
  [data-app="paint"] .pg-card,
  [data-app="paint"] .ph-cont,
  [data-app="paint"] .ph-kind,
  [data-app="paint"] .ph-jb {
    animation: none !important; transition-duration: .001ms !important;
  }
  [data-app="paint"] .pg-card:active,
  [data-app="paint"] .ph-cont:active,
  [data-app="paint"] .ph-kind:active,
  [data-app="paint"] .ph-jb:active,
  [data-app="paint"] .pb-rb:active,
  [data-app="paint"] .pb-zb:active,
  [data-app="paint"] .pm-cta:active { transform: none; }
}
