/* Bonyo: the mono-forward, warm, minimal design system.
   Every page (landing, auth, home) uses this sheet. */

:root{
  --paper:#F0E8D7; --paper-2:#F6F0E2; --honey:#FBE7B8;
  --ink:#211C15; --soft:#4A4233; --dim:#8C7E68; --line:rgba(33,28,21,.14);
  --sun:#F2A929; --sun-deep:#D2841A; --dusk:#3A3B4E; --sage:#74894F; --ember:#B5471B; --red:#C8161A;
  --mono:'IBM Plex Mono',ui-monospace,'SF Mono',Menlo,Consolas,monospace;
  /* Two type roles (V3). --display is DotGothic16's dot-matrix — Bonyo's
     personality, for BIG / short / glanceable text only (headlines, big
     numbers, spoken lines, banner shouts, nav + section labels). Weight 400
     ONLY: the face has no bold and faux-bold smears the pixel grid.
     --mono (IBM Plex Mono) is the CLARITY layer — anything you READ or parse:
     body copy, hints, leaderboard rows, form values, messages. DotGothic16 is
     the V3 placeholder voice; the permanent pick lands later. */
  --display:'DotGothic16',var(--mono);
}

*{ box-sizing:border-box; }
html,body{ margin:0; }
/* paper behind everything: overscroll bounce and mobile URL-bar collapse must
   never show white. overscroll-behavior kills pull-to-refresh (iOS 16+). */
html{ background:var(--paper); }
html,body{ overscroll-behavior-y:none; }
/* hidden must always win: any author display rule (.btn's inline-flex, .villain's
   flex) otherwise beats the UA's [hidden]{display:none} and the element shows. */
[hidden]{ display:none !important; }
body{
  background:radial-gradient(120% 92% at 50% 26%, var(--honey), var(--paper-2) 46%, var(--paper) 78%);
  color:var(--ink); font-family:var(--mono); -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* no accidental text selection when dragging on/near Bonyo; inputs re-enable below */
.hero{ position:relative; min-height:100vh; min-height:100dvh; overflow:hidden;
  user-select:none; -webkit-user-select:none; }
.hero input{ user-select:text; -webkit-user-select:text; }
#sun{ position:absolute; inset:0; z-index:1; display:block; width:100%; height:100%; }
/* warm sun-glow wash + faint paper grain (no cyberpunk scanlines).
   --sun-x / --sun-y track Bonyo's actual centre (set by landing-sun.js on every
   resize, from the same box the ASCII sun canvas centres on), so the bright wash
   sits ON Bonyo instead of a fixed point he can drift below (captain: "Bonyo well
   below the glow centre" on /bonyo). Falls back to 50% 26% with no JS. */
.hero::before{ content:""; position:absolute; inset:0; z-index:2; pointer-events:none;
  background:radial-gradient(90% 72% at var(--sun-x,50%) var(--sun-y,26%), rgba(255,211,107,.26), rgba(255,211,107,0) 60%);
  mix-blend-mode:soft-light; }
.hero::after{ content:""; position:absolute; inset:0; z-index:5; pointer-events:none; opacity:.05;
  background-image:radial-gradient(var(--ink) .5px, transparent .6px); background-size:5px 5px; }

.hero-in{ position:relative; z-index:7; min-height:100vh; min-height:100dvh;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; gap:clamp(9px,1.9vh,18px);
  padding:clamp(64px,15vh,200px) clamp(16px,5vw,40px) clamp(20px,4vh,44px); }

.bonyo-wrap{ position:relative; width:clamp(124px,18vh,200px);
  display:flex; flex-direction:column; align-items:center; }
.bonyo-wrap::before{ content:""; position:absolute; left:50%; top:48%; width:340%; height:340%;
  transform:translate(-50%,-50%); z-index:-1; pointer-events:none;
  background:radial-gradient(circle, rgba(255,206,94,.55) 0%, rgba(255,206,94,.14) 40%, rgba(255,206,94,0) 66%); }
/* aspect-ratio matches the mascot PNGs' native 512x640: reserves the box at
   layout time instead of after decode, so the page doesn't jump when Bonyo
   loads in or gets redecoded (e.g. after a background-tab redecode). Every
   frame in mascot/ shares that size, so swapping #bonyo.src for the rep
   animation never changes the box. */
#bonyo{ width:100%; height:auto; aspect-ratio:512/640; display:block;
  filter:drop-shadow(3px 5px 0 rgba(33,28,21,.16)); user-select:none; -webkit-user-drag:none;
  -webkit-touch-callout:none; touch-action:manipulation; }

/* --- display role: DotGothic16 headlines (weight 400 only) --- */
h1.head{ font-family:var(--display); font-weight:400; color:var(--ink);
  font-size:clamp(33px,6.6vw,64px); line-height:1.08; letter-spacing:.005em; text-wrap:balance;
  margin:.05em 0 0; max-width:16ch;
  text-shadow:0 0 18px var(--paper), 0 0 9px var(--paper); }
.sub{ margin:0; color:var(--soft); font-size:clamp(14px,2.1vw,17px); max-width:36ch;
  text-shadow:0 0 12px var(--paper), 0 0 6px var(--paper); }

.goal{ display:flex; flex-direction:column; align-items:center; gap:5px; margin-top:8px; }
.goal-num{ font-family:var(--display); font-weight:400; font-variant-numeric:tabular-nums;
  font-size:clamp(42px,8.8vw,84px); line-height:1; letter-spacing:.01em; color:var(--ink); }
.goal-cry{ font-family:var(--display); font-weight:400; color:var(--red);
  font-size:clamp(18px,3.2vw,30px); letter-spacing:.06em; line-height:1; }
/* goal caption pair: label is a display kicker, sub stays clarity-mono */
.goal-label{ font-family:var(--display); font-weight:400; font-size:12px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim); }
.goal-sub{ font-family:var(--mono); font-size:12px; letter-spacing:.02em; color:var(--soft); }

#bonyo:focus-visible{ outline:3px solid var(--sage); outline-offset:6px; border-radius:12px; }

/* ---------------------------------------------------------------------------
   P1 — sign-up + auth pages. Same mono-forward language, same sun.
   --------------------------------------------------------------------------- */
.sr-only{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0,0,0,0); white-space:nowrap; border:0; }

/* buttons: display face, short action words as pixel type (weight 400) */
.btn{ font-family:var(--display); font-weight:400; font-size:15px; letter-spacing:.04em;
  color:var(--paper); background:var(--ink); border:none; border-radius:2px; padding:13px 20px;
  cursor:pointer; text-decoration:none; display:inline-flex; align-items:center; justify-content:center;
  gap:8px; transition:transform .08s ease; }
.btn:hover{ transform:translateY(-1px); }
.btn:focus-visible{ outline:3px solid var(--sage); outline-offset:3px; }
.btn:disabled{ opacity:.55; cursor:default; transform:none; }
.btn.ghost{ color:var(--ink); background:transparent; box-shadow:inset 0 0 0 1.5px var(--line); }

/* reveal-on-tap sign-up on the landing */
.signup{ display:flex; flex-direction:column; align-items:center; gap:10px; margin-top:8px; }
.signup-form{ display:flex; gap:8px; align-items:stretch; width:min(360px,86vw); }
.signup-form input{ flex:1; min-width:0; font-family:var(--mono); font-size:15px; color:var(--ink);
  background:var(--paper-2); border:1.5px solid var(--line); border-radius:2px; padding:12px 14px; }
.signup-form input:focus-visible{ outline:none; border-color:var(--sage); }
.signup-go{ padding:12px 16px; font-size:16px; }
.signup-msg{ margin:0; font-size:13px; color:var(--soft); max-width:32ch;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.signup-msg.err{ color:var(--ember); }

/* auth pages grow with content instead of clipping the hero */
.hero.hero--page{ overflow-y:auto; }

/* /bonyo + /about-bonyo are SHORT ambient pages. With the default
   justify-content:center their small content block floats to the vertical
   middle, dropping Bonyo well below where the app pages (tall, top-aligned) seat
   him — so he visibly jumps DOWN when you navigate onto them, and reads as
   sitting low in the sun (captain phone review, items 5 + 6). Top-align them so
   Bonyo lands in the SAME top band as every other hero page; the quiet footer
   link stays pinned to the bottom regardless. */
.hero--ambient .hero-in{ justify-content:flex-start; }

/* centered card in the hero (check-inbox, confirm, error, home) */
.card{ position:relative; z-index:7; width:min(440px,90vw); text-align:center;
  display:flex; flex-direction:column; align-items:center; gap:14px; }
.card .kicker{ font-family:var(--display); font-size:12px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--dim); }
.card h1{ font-family:var(--display); font-weight:400; color:var(--ink); margin:0;
  font-size:clamp(27px,5.4vw,42px); line-height:1.12; text-wrap:balance;
  text-shadow:0 0 18px var(--paper),0 0 9px var(--paper); }
.card p{ margin:0; color:var(--soft); font-size:15px; line-height:1.55; max-width:34ch;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.card .email{ font-weight:700; color:var(--ink); word-break:break-all; }

/* live global count (fed by /api/cycle, hidden until real data arrives).
   Body-embedded numerals stay CLARITY-mono; the big hero figure is .goal-num. */
.live-count{ margin:0; font-size:clamp(13px,2vw,15px); color:var(--soft); letter-spacing:.04em;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
/* embedded tally/count NUMBERS take the display face (big, glanceable — and
   DotGothic16's zero has no dot to misread); their labels stay clarity-mono */
.live-count b, .tally-num{ font-family:var(--display); font-weight:400; font-size:17px;
  color:var(--ink); }
[data-count].flick, b[data-count].flick{ animation:flick .45s ease-out; }
@keyframes flick{ 0%{ color:var(--sun-deep); } 100%{ color:var(--ink); } }

/* rep logging (home): "i did … pull-ups" is Bonyo's voice (display); the number
   the user types takes the display face too, so every number shares one voice */
.logrow{ display:flex; align-items:center; justify-content:center; gap:10px; flex-wrap:wrap; }
.log-say{ font-family:var(--display); font-size:clamp(17px,3vw,22px); color:var(--ink);
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.logrow input{ width:5.5ch; font-family:var(--display); font-weight:400; font-size:20px;
  font-variant-numeric:tabular-nums; text-align:center;
  color:var(--ink); background:var(--paper-2); border:1.5px solid var(--line); border-radius:2px;
  padding:10px 6px; -moz-appearance:textfield; }
.logrow input::-webkit-outer-spin-button, .logrow input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
.logrow input:focus-visible{ outline:none; border-color:var(--sage); }
/* the tally: two stacked lines ("12 pull-ups today" / "45,208 all of us") —
   a number never sits beside a separator dot (it read as a decimal) */
.tally{ position:relative; margin:0; display:flex; flex-direction:column; align-items:center;
  gap:3px; font-size:13px; color:var(--soft); letter-spacing:.04em;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.tally-line{ display:flex; align-items:baseline; gap:7px; }
.plus{ position:absolute; right:-26px; top:-2px; font-family:var(--mono); font-weight:700;
  font-size:15px; color:var(--sun-deep); opacity:0; pointer-events:none; }
.plus.go{ animation:floatUp .9s ease-out forwards; }
@keyframes floatUp{ 0%{ opacity:1; transform:translateY(0); } 100%{ opacity:0; transform:translateY(-30px); } }

/* community progress counter (home): the flat "N all of us" line becomes a
   feature element, reusing the landing .goal-num display-number language at
   a smaller scale, with a live fraction against the community goal (single
   source of truth: GOAL in count.js — change it there, this reads it). */
.comm{ display:flex; flex-direction:column; align-items:center; gap:6px; width:min(280px,84vw); }
.comm-label{ font-family:var(--display); font-weight:400; font-size:11px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--dim); }
.comm-num{ margin:0; display:flex; align-items:baseline; justify-content:center; gap:.2em; }
.comm-count{ font-family:var(--display); font-weight:400; font-variant-numeric:tabular-nums;
  font-size:clamp(30px,7vw,44px); line-height:1; color:var(--ink); }
.comm-sep{ font-family:var(--display); font-size:15px; color:var(--dim); }
.comm-target{ font-family:var(--display); font-weight:400; font-variant-numeric:tabular-nums;
  font-size:15px; color:var(--dim); }
.comm-bar{ width:100%; height:6px; border-radius:3px; background:var(--paper-2);
  box-shadow:inset 0 0 0 1.5px var(--line); overflow:hidden; }
.comm-fill{ height:100%; width:0%; min-width:3px; background:var(--sun-deep); border-radius:3px;
  transition:width .6s ease; }

/* share kit (home) */
.share{ display:flex; flex-direction:column; align-items:center; gap:12px; width:100%; }
.share-label{ font-family:var(--display); font-size:12px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--dim); }
.share-link{ display:flex; gap:8px; width:min(400px,88vw); }
.share-link input{ flex:1; min-width:0; font-family:var(--mono); font-size:13px; color:var(--ink);
  background:var(--paper-2); border:1.5px solid var(--line); border-radius:2px; padding:11px 12px; text-align:center; }
#qr{ width:184px; height:184px; background:var(--paper-2); padding:10px; border-radius:2px;
  box-shadow:0 0 0 1.5px var(--line); }
#qr svg{ width:100%; height:100%; display:block; }

/* signed-in page nav (log / board / plan / share / account): display-face
   wayfinding, pinned as a bar at the top of the viewport (P6). No bold on the
   active link — DotGothic16 has none; ink colour + underline mark it. */
.pagenav{ display:flex; gap:16px; justify-content:center; }
.pagenav--top{ position:fixed; top:0; left:0; right:0; z-index:8;
  padding:calc(14px + env(safe-area-inset-top)) 12px 12px;
  transition:transform .18s ease, background-color .18s ease, border-color .18s ease;
  border-bottom:1.5px solid transparent; }
/* scroll down → the bar slides away; any scroll up brings it back (home.js) */
.pagenav--hidden{ transform:translateY(-100%); }
/* once the page is scrolled, links need a backdrop to stay readable */
.pagenav--solid{ background:rgba(246,240,226,.92); border-bottom-color:var(--line); }
@media (prefers-reduced-motion:reduce){ .pagenav--top{ transition:none; } }
.pagenav a{ font-family:var(--display); font-size:13px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--dim); text-decoration:none; padding-bottom:2px; border-bottom:1.5px solid transparent; }
.pagenav a:hover{ color:var(--ink); }
.pagenav a[aria-current="page"]{ color:var(--ink); border-bottom-color:var(--ink); }
.pagenav a:focus-visible{ outline:2px solid var(--sage); outline-offset:3px; }

/* "more" overflow disclosure: adds the 7th+ destination (info) without crowding
   the phone row. Native <details>, so it opens/closes with NO JS. home.js
   enhances it (close on outside-click / Escape / nav-hide, with focus recovery)
   and, at wide widths where every link fits, sets .pagenav--wide + opens it so
   the panel dissolves into the row and desktop shows every destination inline. */
.navmore{ position:relative; display:flex; }
.navmore > summary{ list-style:none; cursor:pointer; font-family:var(--display); font-size:13px;
  letter-spacing:.1em; text-transform:uppercase; color:var(--dim); padding-bottom:2px;
  border-bottom:1.5px solid transparent; }
.navmore > summary::-webkit-details-marker{ display:none; }
.navmore > summary::after{ content:" \25BE"; }                 /* ▾ caret (glyph swap, no motion) */
.navmore > summary:hover{ color:var(--ink); }
.navmore > summary:focus-visible{ outline:2px solid var(--sage); outline-offset:3px; }
.navmore[open] > summary{ color:var(--ink); }
.navmore[open] > summary::after{ content:" \25B4"; }           /* ▴ when open */
.navmore--current > summary{ color:var(--ink); border-bottom-color:var(--ink); }
/* the drop panel: board-furniture double-rule frame on warm paper, right-aligned
   and viewport-bounded so it can never run off a narrow screen. */
.navmore-panel{ position:absolute; top:calc(100% + 8px); right:0; z-index:8;
  display:flex; flex-direction:column; gap:2px; padding:8px 10px;
  max-width:min(220px, calc(100vw - 24px - env(safe-area-inset-right)));
  background:radial-gradient(120% 120% at 50% 0, #FBF6EA, var(--paper-2));
  border:1px solid var(--dim); box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--dim);
  border-radius:2px; }
.navmore-panel a{ font-family:var(--display); font-size:13px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--dim); text-decoration:none; min-height:44px; display:flex; align-items:center;
  padding:0 6px; border-bottom:1.5px solid transparent; }
.navmore-panel a:hover{ color:var(--ink); }
.navmore-panel a[aria-current="page"]{ color:var(--ink); border-bottom-color:var(--ink); }
.navmore-panel a:focus-visible{ outline:2px solid var(--sage); outline-offset:3px; }
/* wide viewports: home.js sets .pagenav--wide and opens the <details>; we
   dissolve the disclosure into the row so every destination shows inline. */
.pagenav--wide .navmore{ display:contents; }
.pagenav--wide .navmore > summary{ display:none; }
.pagenav--wide .navmore-panel{ position:static; display:contents; }
.pagenav--wide .navmore-panel a{ min-height:0; display:inline-block; padding:0 0 2px; }

/* AFTER the base .pagenav a rule: equal specificity, so source order decides
   (codex branch-F4). The collapsed phone row is SIX primary links + the "more"
   summary (log·bounties·ranks·plan·share·account · more▾, after the captain
   promoted `account` out of "more"), which MUST NOT break at 390px. Verified at
   this tuning (DotGothic16 loaded, headless Chrome over CDP): the row sits on one
   line with comfortable slack at 390px and still fits at 360px. `flex-wrap:wrap`
   is a zero-cost graceful backstop — if a future longer label or a user's
   text-scaling ever pushes past one row, the nav wraps to a second centered row
   instead of overflowing or colliding. */
@media (max-width:420px){
  .pagenav--top{ gap:7px; row-gap:4px; flex-wrap:wrap; }
  .pagenav--top a, .pagenav--top .navmore > summary{ font-size:11.5px; letter-spacing:.005em; }
}
.logout{ margin-top:4px; font-family:var(--mono); font-size:12px; letter-spacing:.05em; color:var(--dim);
  background:none; border:none; cursor:pointer; text-decoration:underline; text-underline-offset:3px; }

/* villain bounty banner (home): display shout head, clarity-mono detail line */
.villain{ display:flex; flex-direction:column; align-items:center; gap:4px; width:min(400px,88vw);
  padding:12px 14px; border:1.5px solid var(--ember); border-radius:2px; background:var(--paper-2); }
.villain-head{ font-family:var(--display); font-weight:400; font-size:14px; letter-spacing:.1em;
  color:var(--red); text-align:center; }
.villain-sub{ font-family:var(--mono); font-size:12.5px; color:var(--ember); text-align:center; }
.villain.won{ border-color:var(--sage); }
.villain.won .villain-head{ color:var(--sage); letter-spacing:.05em; text-transform:none; }

/* covered banner (home, P4): the kind mirror of the villain — sage, not ember */
.villain.covered{ border-color:var(--sage); }
.villain.covered .villain-head{ color:var(--sage); letter-spacing:.05em; text-transform:none; }
.villain.covered .villain-sub{ color:var(--soft); }

/* crew + board rosters (home, P4): CLARITY-mono rows with dot leaders — names
   and numbers must stay legible, so these never take the pixel face */
.roster{ display:flex; flex-direction:column; align-items:stretch; gap:8px; width:min(400px,88vw); }
.roster .share-label{ text-align:center; }
.rows{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.rows li{ display:flex; align-items:baseline; gap:8px; font-family:var(--mono); font-size:13px; color:var(--soft); }
.row-name{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:60%; }
.row-name.you{ color:var(--ink); font-weight:700; }
.row-dots{ flex:1; border-bottom:1.5px dotted var(--line); transform:translateY(-3px); }
/* rep count takes the display face (all numbers share one voice); the #N rank
   prefix stays clarity-mono inside .row-name */
.row-reps{ font-family:var(--display); font-weight:400; color:var(--ink);
  font-variant-numeric:tabular-nums; }
/* Phone: pirate names are long and the 60%-cap ellipsis hid them. Let the name
   wrap to the full row width instead of truncating; the dot leader can't span a
   wrapped name, so drop it on mobile and pin the rep count to the top-right.
   Scoped to /leaderboard so the /home crew mini-roster is untouched. */
@media (max-width:559px){
  .leaderboard .rows li{ align-items:flex-start; }
  .leaderboard .row-name{ white-space:normal; overflow:visible; text-overflow:clip;
    max-width:none; flex:1 1 auto; min-width:0; }
  .leaderboard .row-dots{ display:none; }
}

/* community milestone callout (P5): one quiet display line under the count */
.milestone{ margin:0; font-family:var(--display); font-size:12px; letter-spacing:.12em;
  color:var(--soft); text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }

/* info-eye: reusable "what is this?" affordance, a quiet mono [?] placed AFTER
   a .share-label header. Tap/click (home.js wireEye) is the source of truth;
   hover/focus also reveal. The glyph is plain mono text, no icon/SVG. */
.eye{ display:inline-flex; align-items:center; justify-content:center; vertical-align:middle;
  margin-left:6px; padding:2px; background:none; border:none; cursor:pointer;
  color:var(--dim); font-size:12px; line-height:1; }
.eye:hover, .eye:focus-visible{ color:var(--ink); }
.eye:focus-visible{ outline:2px solid var(--sage); outline-offset:2px; }
.eye-glyph{ font-family:var(--mono); letter-spacing:.02em; }
/* same double-rule frame recipe as .poster/.selfpost, in --dim (a quiet aside,
   not a primary card) so the note reads warm-paper instead of flat white-grey */
.explain{ font-family:var(--mono); font-size:13px; line-height:1.5; color:var(--soft); text-align:left;
  max-width:34ch; margin:4px auto 0; padding:11px 14px;
  background:radial-gradient(130% 160% at 28% -18%, rgba(251,231,184,.4), transparent 65%), var(--paper-2);
  border:1px solid var(--dim); box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--dim);
  border-radius:2px; }

/* open bounties (V3): wanted-poster cards that pop onto the board.
   The board section breaks out of the 440px content column (spec option A):
   .card is a centered flex column, so a wider child centers itself and
   overflows the column symmetrically. On this page the board IS the content.
   Widened for the split (spec 2026-07-20, ask 3): the auto-fill poster grid can
   only get denser if its container can grow, so at desktop widths the sheet
   spans wider (up to ~5 posters per row at dozens of open bounties). The text
   furniture (status plaque, your-bounties summary) is capped narrower + centered
   so it stays readable on the wider sheet; the grid + carousel span full width. */
.bounties{ display:flex; flex-direction:column; gap:10px; width:min(1000px,94vw); align-items:center; }
/* the grabs/carrying status is a plaque, capped at a readable width and centered
   on the wider sheet; same double-rule frame recipe as .poster/.selfpost. */
.bounties #bountyStatus{ box-sizing:border-box; width:100%; max-width:600px;
  margin:0 auto; padding:9px 12px;
  background:radial-gradient(130% 160% at 28% -18%, rgba(251,231,184,.4), transparent 65%), var(--paper-2);
  border:1px solid var(--ink); box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--ink);
  border-radius:2px; letter-spacing:.06em; }
/* the your-bounties summary row is furniture too: cap + centre it; the strip
   and the tab panels below span the full sheet width. */
.bounties .yours{ max-width:600px; }
.bounties .board-panel, .bounties .board-tabs, .bounties .explain, .bounties .yours-strip{ width:100%; }

/* the leaderboard (board page): mono dot-leader rows, its own titled section
   distinct from the WANTED-poster grid above (item 5) */
.leaderboard{ display:flex; flex-direction:column; gap:10px; width:min(400px,88vw); }
.leaderboard .share-label{ text-align:center; }
/* Cap the ranked list and scroll it inside the card so a long board (up to the
   top-25) does not stretch the page forever. ~400px ≈ 16 rows at the 25px row
   pitch; taller boards scroll. The cap is on .rows (the <ol>), so the pinned
   "you" note (#boardNote, a sibling below) stays visible when you rank outside
   the visible span; your inline row is reached by scrolling. The .roster CLS
   reservation below is retuned to this capped height so the two never fight.
   RETUNE together if the row pitch or this cap changes (CLAUDE.md). */
.leaderboard .rows{ max-height:400px; overflow-y:auto; overscroll-behavior:contain;
  scrollbar-width:thin; padding-right:6px; }

/* CLS: reserve height for the async board regions while the fetch is in flight
   (carried from the /board CLS fix #56 onto the split pages, spec
   2026-07-20-board-split). The two regions now live on separate pages —
   `.bounties` (poster grid) on /bounties, `.roster` (leaderboard) on
   /leaderboard — but share the one `.card.board-loading` mechanism: each page
   carries only its own region, so only the matching rule applies there.
   `board-loading` (set in the page shell, removed on the first paint in home.js)
   holds the space during the gap; a taller region overrides the min-height, a
   short/empty one collapses cleanly once the class is removed.
   RETUNE if the poster-card height, grid breakpoint, or leaderboard row size
   changes (CLAUDE.md). Values track the split layout: /bounties shows the status
   plaque + tabs + ~2 poster rows on desktop, and on phone a SINGLE-row peek-
   carousel (so phone reserves LESS than desktop, unlike the old 2-col grid). */
.card.board-loading .bounties{ min-height:760px; }
/* matches the loaded roster: the ranked list is capped at 400px (.leaderboard
   .rows) so a populated board settles at ~this height instead of 24+ rows. */
.card.board-loading .roster{ min-height:400px; }
@media (max-width:559px){
  .card.board-loading .bounties{ min-height:500px; }
}
/* Returning user whose board had no OPEN bounties last visit (bounties.html head
   script): skip the poster-grid reservation so an empty board does not reserve-
   then-collapse. Only /bounties carries `.bounties` + the head script. */
.board-skip-reserve .card.board-loading .bounties{ min-height:0; }
.bounty-cards{ display:flex; flex-direction:column; gap:8px; }
.bounty-card{ display:flex; flex-direction:column; align-items:center; gap:6px;
  padding:12px 14px; border:1.5px solid var(--ember); border-radius:2px;
  background:var(--paper-2); animation:bounty-pop .4s cubic-bezier(.2,1.4,.4,1) both; }
.bounty-head{ font-family:var(--display); font-weight:400; font-size:14px;
  letter-spacing:.18em; text-transform:uppercase; color:var(--red); }
.bounty-line{ font-family:var(--mono); font-size:12.5px; color:var(--ember); text-align:center; }
.bounty-line a{ color:var(--ember); text-underline-offset:3px; }
.bounty-card .btn{ padding:9px 16px; font-size:14px; }
.bounty-card.grabbed{ border-color:var(--sage); animation:none; }
.bounty-card.grabbed .bounty-head{ color:var(--sage); }
.bounty-card.grabbed .bounty-line{ color:var(--soft); }
.bounty-card.gone{ opacity:.25; transition:opacity .5s ease; }
@keyframes bounty-pop{ 0%{ transform:scale(.88); opacity:0; } 100%{ transform:scale(1); opacity:1; } }
@media (prefers-reduced-motion:reduce){ .bounty-card{ animation:none; } }
#bountyStatus{ text-align:center; }
#bountyStatus a{ color:var(--ember); text-underline-offset:3px; }

/* the bounty board page (P5): display-face toggles, pagenav language. Active =
   ink colour + underline (no bold — the pixel face has none). */
.board-toggles{ display:flex; flex-direction:column; gap:8px; align-items:center; }
.tog-group{ display:flex; gap:14px; justify-content:center; }
.tog{ font-family:var(--display); font-size:13px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--dim); background:none; border:none; cursor:pointer; padding:0 0 2px 0;
  border-bottom:1.5px solid transparent; }
.tog:hover{ color:var(--ink); }
.tog[aria-pressed="true"]{ color:var(--ink); border-bottom-color:var(--ink); }
.tog:focus-visible{ outline:2px solid var(--sage); outline-offset:3px; }

/* badges (account, P5): display-face text chips, same language as .plan-day */
.badge-chips{ display:flex; gap:6px; flex-wrap:wrap; justify-content:center; }
.badge-chip{ display:inline-block; padding:9px 12px; text-align:center;
  font-family:var(--display); font-weight:400; font-size:13px; letter-spacing:.04em;
  background:var(--paper-2); border:1.5px solid var(--line); border-radius:2px; }
.badge-chip.earned{ color:var(--ink); border-color:var(--ink); }
.badge-chip.locked{ color:var(--dim); }
.badge-chip.mystery{ color:var(--dim); border-style:dashed; }
.badge-chip.more{ color:var(--dim); border:none; background:none; padding:9px 4px; }
/* earned-first wall: earned chips render first; the locked + mystery chips sit
   in a collapsed group behind a "show all" toggle so the wall stays compact. */
.badge-empty{ font-family:var(--mono); font-size:12.5px; color:var(--dim); }
.badge-showall{ flex-basis:100%; margin-top:2px; font-family:var(--mono); font-size:12.5px;
  letter-spacing:.02em; color:var(--dim); background:none; border:none; cursor:pointer;
  text-decoration:underline; text-underline-offset:3px; }
.badge-showall:hover{ color:var(--ink); }
.badge-showall:focus-visible{ outline:2px solid var(--sage); outline-offset:2px; }
.badge-locked{ display:flex; flex-wrap:wrap; gap:6px; justify-content:center; flex-basis:100%; }

/* personal goal (account, P5): six digits need more room than the plan target */
#goalInput{ width:8ch; }

/* nickname editor (account, P5): same language as the share-link row */
.namerow{ display:flex; gap:8px; width:min(400px,88vw); }
.namerow input{ flex:1; min-width:0; font-family:var(--mono); font-size:14px; color:var(--ink);
  background:var(--paper-2); border:1.5px solid var(--line); border-radius:2px; padding:11px 12px; }
.namerow input:focus-visible{ outline:none; border-color:var(--sage); }

/* plan editor (home) */
.plan{ display:flex; flex-direction:column; align-items:center; gap:12px; width:100%; }
.plan-days{ display:flex; gap:6px; flex-wrap:wrap; justify-content:center; }
.plan-day input{ position:absolute; width:1px; height:1px; opacity:0; }
.plan-day span{ display:inline-block; min-width:38px; padding:9px 0; text-align:center;
  font-family:var(--display); font-weight:400; font-size:13px; letter-spacing:.04em; color:var(--dim);
  background:var(--paper-2); border:1.5px solid var(--line); border-radius:2px; cursor:pointer;
  user-select:none; -webkit-user-select:none; }
.plan-day input:checked + span{ color:var(--paper); background:var(--ink); border-color:var(--ink); }
.plan-day input:focus-visible + span{ outline:2px solid var(--sage); outline-offset:2px; }
.plan-target{ display:flex; align-items:center; justify-content:center; gap:10px; flex-wrap:wrap; }
.plan-target input{ width:5.5ch; font-family:var(--display); font-weight:400; font-size:18px;
  font-variant-numeric:tabular-nums; text-align:center;
  color:var(--ink); background:var(--paper-2); border:1.5px solid var(--line); border-radius:2px;
  padding:9px 6px; -moz-appearance:textfield; }
.plan-target input::-webkit-outer-spin-button, .plan-target input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
.plan-target input:focus-visible{ outline:none; border-color:var(--sage); }
/* "pull-ups a day" is Bonyo's voice → display */
.plan-say{ font-family:var(--display); font-size:14px; letter-spacing:.02em; color:var(--soft); }

/* ---------------------------------------------------------------------------
   P6 — the log page (/home) + the plan page (/plan).
   --------------------------------------------------------------------------- */

/* empty vs filled, unmistakably: placeholders are dim italic examples;
   typed values stay upright ink. (There was no ::placeholder rule before —
   the UA grey read as a filled value.) */
::placeholder{ color:var(--dim); opacity:.55; font-style:italic; }

/* a submit that failed client validation marks its input until the next edit;
   the focused pairs outrank the :focus-visible sage border (codex branch-F5) */
input.field-err{ border-color:var(--ember); }
.logrow input.field-err:focus-visible, .plan-target input.field-err:focus-visible,
.namerow input.field-err:focus-visible, .signup-form input.field-err:focus-visible{
  border-color:var(--ember); }

/* bonyo's set suggestion + quiet hints under the owe line (clarity-mono) */
.owe-hint{ margin:0; font-size:12.5px; color:var(--soft); letter-spacing:.04em;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.owe-hint a{ color:var(--ember); text-underline-offset:3px; }

/* day selector (log page): display weekday label, display rep count (glanceable,
   not parsed). ember dot = planned + missed */
.daysrow{ display:flex; gap:6px; justify-content:center; flex-wrap:wrap; }
.daychip{ display:flex; flex-direction:column; align-items:center; gap:1px;
  min-width:40px; padding:6px 0 5px; font-family:var(--display); font-weight:400;
  font-size:12px; letter-spacing:.04em; color:var(--dim);
  background:var(--paper-2); border:1.5px solid var(--line); border-radius:2px;
  cursor:pointer; }
.daychip small{ font-family:var(--display); font-weight:400; font-size:13px; letter-spacing:0;
  font-variant-numeric:tabular-nums; }
.daychip .dot{ width:5px; height:5px; border-radius:50%; background:var(--ember); }
.daychip .dot.none{ background:transparent; }
/* rest-day marker: a cute ASCII "zzz" that rises z -> zz -> zzz on a slow loop,
   then clears and rises again (gentle snoring, not a flash). All three z glyphs
   are always in the DOM (opacity-only), so the width never reflows. */
.daychip .zzz{ display:inline-flex; font-family:var(--display); font-weight:400;
  font-size:10px; line-height:5px; height:5px; align-items:center; color:var(--dim); }
.daychip .zzz i{ font-style:normal; opacity:.15; }
.daychip .zzz i:nth-child(1){ animation:zzz-1 2.4s steps(1,end) infinite; }
.daychip .zzz i:nth-child(2){ animation:zzz-2 2.4s steps(1,end) infinite; }
.daychip .zzz i:nth-child(3){ animation:zzz-3 2.4s steps(1,end) infinite; }
@keyframes zzz-1{ 0%,74%{ opacity:.85; } 75%,100%{ opacity:.15; } }
@keyframes zzz-2{ 0%,24%{ opacity:.15; } 25%,74%{ opacity:.85; } 75%,100%{ opacity:.15; } }
@keyframes zzz-3{ 0%,49%{ opacity:.15; } 50%,74%{ opacity:.85; } 75%,100%{ opacity:.15; } }
@media (prefers-reduced-motion:reduce){
  .daychip .zzz i{ opacity:.85; animation:none; }
}
.daychip[aria-pressed="true"]{ color:var(--paper); background:var(--ink); border-color:var(--ink); }
.daychip[aria-pressed="true"] .zzz{ color:var(--paper); }
.daychip:focus-visible{ outline:2px solid var(--sage); outline-offset:2px; }

/* quick-add chips (log page): display face; tap instead of type; typing still works */
.quickrow{ display:flex; gap:6px; justify-content:center; flex-wrap:wrap; }
.chip{ font-family:var(--display); font-weight:400; font-size:13px; letter-spacing:.02em;
  color:var(--ink); background:var(--paper-2); border:1.5px solid var(--line);
  border-radius:2px; padding:8px 12px; cursor:pointer; }
.chip:hover{ border-color:var(--ink); }
.chip:focus-visible{ outline:2px solid var(--sage); outline-offset:2px; }

/* self-bounty (log page): the container stacks each postable grace-day
   candidate; the already-posted confirmation reuses it as a plain text line. */
.slackline{ margin:0; display:flex; flex-direction:column; align-items:center; gap:10px;
  font-size:12.5px; color:var(--soft); max-width:36ch;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }

/* section divider (plan page): three quiet dots, ASCII-quiet separation */
.divider{ font-family:var(--mono); font-size:13px; letter-spacing:.4em;
  color:var(--dim); opacity:.7; user-select:none; margin:2px 0; }

/* ── Bounty Board v3 (portrait One Piece-style posters) ──────────────────
   Each poster is a portrait-orientation pixel wanted poster, top to bottom:
   pixel-serif WANTED banner (SVG art from poster-art.js), framed ASCII
   Bonyo mugshot, DEAD OR ALIVE line, the outlaw's alias in display caps,
   the "reward: N pull-ups" line, then the functional age/status content.
   The printed text never changes with state: resolution is the COVERED
   stamp, not a text swap. Corner nails + torn bottom edge + honey wash and
   the state colors (ember expiring, dim grabbed, sage covered, sun-deep
   yours) carry over from the v2 redesign unchanged.
   The double-rule frame (box-shadow ring) stays on `.poster` itself, always
   a clean rectangle: `clip-path` silently drops box-shadow in every browser,
   so the torn edge + honey wash live on a separate `.poster-paper` backdrop
   (negative z-index, paints behind real text, ahead of the ring) instead of
   being applied to `.poster` directly. Real content is never clipped — only
   this decorative backdrop is — so the torn edge can never hide information,
   only the empty space behind it. */
/* a denser wall at dozens of open bounties (spec Q5): auto-fill columns pack
   more posters per row as width allows (min 200px keeps the outlaw name + reward
   legible). Keep the server BOARD_CAP, no pagination yet. */
.poster-grid{ display:grid; gap:14px; width:100%;
  grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); align-items:stretch; }
/* phone: the SAME grid becomes a horizontal peek-carousel (spec item 4) — one
   poster centered with the next peeking, native CSS scroll-snap so it scrolls,
   snaps and peeks with zero JS. home.js enhances with dots + an "n / total"
   counter + prev/next arrow buttons + arrow-key paging + an aria-live position
   announce. No markup fork: the poster DOM is identical to desktop. */
@media (max-width:559px){
  .poster-grid{ display:flex; grid-template-columns:none; gap:12px;
    overflow-x:auto; scroll-snap-type:x mandatory; scroll-padding-left:9%;
    -webkit-overflow-scrolling:touch; scrollbar-width:none; }
  .poster-grid::-webkit-scrollbar{ display:none; }
  .poster-grid > .poster{ flex:0 0 82%; scroll-snap-align:center; }
}
@media (prefers-reduced-motion:reduce){ .poster-grid{ scroll-behavior:auto; } }
.poster{ --tilt:0deg; position:relative; display:flex; flex-direction:column;
  align-items:center; gap:2px; padding:10px 8px 16px; background:none;
  color:var(--ink); border:1px solid var(--ink);
  box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--ink);
  font-family:var(--mono); text-align:center;
  container-type:inline-size;
  animation:cardIn .28s ease both; }
/* the torn-paper backdrop: the card's actual paper fill lives here (not on
   `.poster` itself), so the jagged clip-path below genuinely tears it away —
   the ring/border stay a clean rectangle (clip-path silently drops
   box-shadow in every browser, so it can never live on `.poster` itself),
   and the torn notches expose the page background behind the card, not a
   second flat fill in the same colour. z-index:-1 paints behind real text,
   ahead of nothing (poster has no other background). Percentage clip-path,
   so it scales with the fluid grid at any column width, no extra breakpoint. */
.poster-paper{ position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:radial-gradient(130% 160% at 28% -18%, rgba(251,231,184,.55), transparent 65%),
    var(--paper-2);
  clip-path:polygon(0% 0%,100% 0%,100% 88%,97% 100%,93% 90%,89% 98%,85% 89%,81% 100%,
    77% 91%,73% 97%,69% 88%,65% 100%,61% 90%,57% 96%,53% 89%,49% 100%,45% 91%,41% 97%,
    37% 88%,33% 100%,29% 90%,25% 98%,21% 89%,17% 100%,13% 91%,9% 97%,5% 88%,0% 96%); }
/* corner nail glyphs: pinned-to-corkboard read, colour-neutral (a real nail,
   not a state colour) and above everything, including the COVERED stamp. */
.poster::before, .poster::after{ content:""; position:absolute; top:-5px; z-index:4;
  width:7px; height:7px; border-radius:50%;
  background:radial-gradient(circle at 35% 30%,var(--paper-2),var(--dim) 55%,var(--ink) 100%);
  box-shadow:0 1px 1px rgba(33,28,21,.35); }
.poster::before{ left:13px; }
.poster::after{ right:13px; }
/* per-card tilt: baked into the entrance animation's own end keyframe (not a
   plain static transform, which the animation's fill-mode would immediately
   overwrite). Only above 480px — a full-bleed single-column card would
   overflow the viewport if tilted — and only without reduced motion. */
@media (prefers-reduced-motion:no-preference) and (min-width:480px){
  .poster:nth-child(odd){ --tilt:-1.1deg; }
  .poster:nth-child(even){ --tilt:1deg; }
}
/* the WANTED head: pixel-serif SVG lettering from poster-art.js, scaled by
   viewBox to the card width. fill:currentColor, so the state recolors below
   (ember/dim/sage/deep) keep working untouched. The font declaration only
   styles the plain-text fallback used if poster-art.js failed to load. */
.poster-head{ display:block; width:90%; margin-top:2px;
  font-family:var(--display); font-weight:400; font-size:24px; }
.poster-head svg{ display:block; width:100%; height:auto; }
/* the framed mugshot: the real hand-drawn Bonyo, printed black and white.
   The frame's aspect-ratio matches the mascot PNGs (256x320, 4:5) and the
   img carries explicit width/height attributes, so the box is reserved
   before the PNG decodes (PR #42 CLS discipline). grayscale + multiply
   against the paper reads like a photo on a period poster; the portrait is
   printed art and never recolors with poster state. */
.poster-portrait{ box-sizing:border-box; width:86%; aspect-ratio:4/5;
  margin-top:7px; border:1.5px solid var(--ink); background:rgba(33,28,21,.04);
  display:flex; align-items:center; justify-content:center; overflow:hidden;
  padding:4%; }
.poster-portrait img{ width:100%; height:100%; object-fit:contain;
  filter:grayscale(1) contrast(1.06); mix-blend-mode:multiply;
  /* zoom so the art's own pull-up bar (already the widest thing in the
     frame) bleeds past the padding and gets clipped flush at the frame's
     inner border, instead of floating with a mat gap on both sides */
  transform:scale(1.26); transform-origin:50% 0; }
/* DEAD OR ALIVE: same pixel-serif SVG treatment, printed ink (no state
   recolor), small like the reference line */
.poster-doa{ display:block; width:74%; margin-top:6px; color:var(--ink); }
.poster-doa svg{ display:block; width:100%; height:auto; }
/* the outlaw's name is the poster's identity line: display caps, wraps
   rather than truncates (it is the one thing a wanted poster must say) */
.poster-name{ font-family:var(--display); font-weight:400; color:var(--ink);
  font-size:17px; font-size:min(17px,8.6cqi); line-height:1.2;
  letter-spacing:.05em; text-transform:uppercase; margin-top:4px;
  max-width:96%; overflow-wrap:anywhere; }
/* the reward line: pull-ups demoted from the old headline role to a small
   "N pullup bounty" line, secondary to WANTED. */
.poster-reward{ display:flex; align-items:baseline; justify-content:center;
  gap:5px; margin-top:3px; font-size:11px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--dim); }
.poster-num{ font-family:var(--display); font-weight:400; font-size:20px;
  line-height:1.05; color:var(--ink); }
.poster-meta{ font-size:11px; color:var(--soft); margin-top:5px; }
.poster-meta b{ font-weight:600; color:var(--ink); }
.poster-hunter{ font-size:11px; color:var(--soft); margin-top:5px; }
.poster-grab{ margin-top:7px; font-family:var(--mono); font-size:13px; letter-spacing:.08em;
  color:var(--ink); background:none; border:1px solid var(--ink); border-radius:2px;
  padding:6px 13px; cursor:pointer; }
.poster-grab:hover{ background:var(--ink); color:var(--paper-2); }
.poster-grab:focus-visible{ outline:2px solid var(--sage); outline-offset:2px; }
/* ornamental footer echoing the reference posters' barcode + issuer mark;
   margin-top:auto pins it to the card bottom so a row of posters keeps a
   shared baseline whatever each card's status line wrapped to */
.poster-footer{ display:flex; align-items:center; gap:7px; width:86%;
  margin-top:auto; padding-top:9px; opacity:.7; }
.poster-barcode{ flex:1 1 0; height:9px; background-image:
  repeating-linear-gradient(90deg,var(--ink) 0 1px,transparent 1px 3px,var(--ink) 3px 5px,transparent 5px 6px,var(--ink) 6px 8px,transparent 8px 9px); }
.poster-footer small{ font-size:8px; letter-spacing:.3em; color:var(--ink); }
/* the COVERED stamp (spec ask 2, Direction A — legibility fix): the old
   translucent multiply overprint sat at top:40% OVER the dark B&W mugshot and
   was illegible (sage ink multiplied into near-black art). Replace it with a
   solid cream BAND across the poster's lower third, CLEAR of the portrait: sage
   ink on opaque cream, no multiply blend, full opacity. Reads as a real printed
   overprint band and stays legible against the dark art. Still aria-hidden (the
   covered status is also in the `.poster-hunter` text line). Rendered only on
   covered posters — the "recently covered" tab and your own covered poster. */
.poster-stamp{ position:absolute; left:-4px; right:-4px; top:60%; z-index:5;
  transform:rotate(-8deg); pointer-events:none; text-align:center;
  font-family:var(--display); font-weight:400; font-size:20px; letter-spacing:.24em;
  text-transform:uppercase; color:var(--sage); background:var(--paper-2);
  border-top:2px solid var(--sage); border-bottom:2px solid var(--sage);
  padding:3px 0; white-space:nowrap; box-shadow:0 1px 4px rgba(33,28,21,.25); }
.poster--expiring{ box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--ember); }
.poster--expiring .poster-head, .poster--expiring .poster-num{ color:var(--ember); }
.poster--grabbed{ opacity:.6; box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--dim); }
.poster--grabbed .poster-head{ color:var(--dim); }
.poster--covered{ box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--sage); }
.poster--covered .poster-head, .poster--covered .poster-num{ color:var(--sage); }
/* your own poster: a sun-deep ownership ring, declared after the state colours
   so it wins whatever the poster's state (it's never grabbable) */
.poster--mine{ box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--sun-deep); }
.poster--mine .poster-head, .poster--mine .poster-num{ color:var(--sun-deep); }
.poster--gone{ opacity:.2; transition:opacity .5s ease; }
@keyframes cardIn{ from{ opacity:0; transform:translateY(6px) rotate(var(--tilt)); }
  to{ opacity:1; transform:rotate(var(--tilt)); } }
@media (prefers-reduced-motion:reduce){ .poster{ animation:none; } }

/* ── Bounty page split furniture (spec 2026-07-20-board-split) ──────────────
   "your bounties" region, the to-grab / recently-covered tabs, and the mobile
   peek-carousel controls. All within the design system: display-face labels,
   mono body, the double-rule frame recipe, no new colour tokens. */

/* your bounties: your OWN posters (every state), collapsed behind an expander so
   the top of the board stays focused on grabbable posters. */
.yours{ display:flex; flex-direction:column; gap:8px; width:100%; }
.yours-sum{ display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; }
.yours-toggle{ font-family:var(--mono); font-size:12px; color:var(--ember); background:none;
  border:none; cursor:pointer; text-decoration:underline; text-underline-offset:3px; white-space:nowrap; }
.yours-toggle:focus-visible{ outline:2px solid var(--sage); outline-offset:2px; }
/* the revealed strip: your own mini-posters, a horizontal scroller (not the
   grid/carousel rules below — those key on .poster-grid only) */
.yours-strip{ display:flex; gap:12px; width:100%; overflow-x:auto; padding:2px 0 8px;
  scroll-snap-type:x proximity; scrollbar-width:none; }
.yours-strip::-webkit-scrollbar{ display:none; }
.yours-strip > .poster{ flex:0 0 158px; scroll-snap-align:start; }

/* to-grab / recently-covered tabs: reuse the display-face toggle look of the
   leaderboard's .tog (ink + underline for active; DotGothic16 has no bold). */
.board-tabs{ display:flex; gap:16px; justify-content:center; align-items:center; flex-wrap:wrap; }
.tab{ font-family:var(--display); font-size:13px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--dim); background:none; border:none; cursor:pointer; padding:0 0 3px 0;
  border-bottom:1.5px solid transparent; }
.tab:hover{ color:var(--ink); }
.tab[aria-selected="true"]{ color:var(--ink); border-bottom-color:var(--ink); }
.tab:focus-visible{ outline:2px solid var(--sage); outline-offset:3px; }
.tab-cnt{ font-family:var(--mono); font-size:11px; letter-spacing:0; color:var(--dim); }
.board-panel{ display:flex; flex-direction:column; gap:10px; width:100%; }

/* mobile peek-carousel controls (phone only; the carousel itself is the
   .poster-grid flex/scroll-snap rules above). Hidden on desktop where the grid
   is a normal wall. */
.swipe-ctl, .swipe-count{ display:none; }
@media (max-width:559px){
  .swipe-ctl{ display:flex; align-items:center; justify-content:center; gap:14px; }
  .swipe-count{ display:block; text-align:center; font-family:var(--display);
    font-size:11px; letter-spacing:.12em; color:var(--dim); margin:0; }
}
/* the prev/next controls read as board furniture, not generic circular icons:
   the same paper-framed rounded rectangle as the log/self-post buttons
   (var(--paper-2) fill + 1px ink border + 2px radius), with mono chevrons. */
.swipe-arrow{ font-family:var(--mono); font-size:16px; line-height:1; color:var(--ink);
  background:var(--paper-2); border:1px solid var(--ink); border-radius:2px; width:34px; height:34px;
  cursor:pointer; display:flex; align-items:center; justify-content:center; }
.swipe-arrow:hover{ background:var(--ink); color:var(--paper-2); }
.swipe-arrow:focus-visible{ outline:2px solid var(--sage); outline-offset:2px; }
.swipe-arrow[disabled]{ opacity:.3; cursor:default; }
.swipe-arrow[disabled]:hover{ background:var(--paper-2); color:var(--ink); }
.swipe-dots{ display:flex; gap:6px; align-items:center; }
.swipe-dots i{ display:block; width:7px; height:7px; border-radius:50%; background:var(--line); }
.swipe-dots i.on{ background:var(--ink); }

/* self-post block (log page) — "wanted slips" (captain pick 2026-07-21, Variant
   B): each grace-day is a little framed slip with a honey day-tab and a
   poster-style "N pull-up bounty" reward line, plus a segmented scope toggle
   with a [?] explainer disclosure (replacing the bare native crew-only checkbox
   and its standing hint line). Warm radial
   fill + double-rule frame like the /bounties plaque. The single full card and
   the group's rows share the slip layout (.selfpost-slip).
   See docs/specs/2026-07-20-compact-missed-days.md. */
.selfpost{ position:relative; display:flex; flex-direction:column; align-items:center; gap:10px;
  padding:14px 16px; color:var(--ink);
  background:radial-gradient(130% 160% at 28% -18%, rgba(251,231,184,.5), transparent 62%), var(--paper-2);
  border:1px solid var(--ink); box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--ink);
  border-radius:2px; font-family:var(--mono); text-align:center; animation:cardIn .28s ease both; }

/* the wanted-slip layout: honey day-tab (corner) + a top line (reward | button)
   + full-width scope, [?] explain panel, and message rows below. padding-top
   clears the tab. The explain row is empty (collapsed) until the [?] is opened. */
.selfpost-slip{ display:grid; grid-template-columns:1fr auto; align-items:center; gap:6px 12px;
  text-align:left; padding-top:20px;
  grid-template-areas:"reward btn" "scope scope" "explain explain" "msg msg"; }
.selfpost-tab{ position:absolute; top:-1px; left:-1px; font-family:var(--display); font-weight:400;
  font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--ink);
  background:var(--honey); border:1px solid var(--dim); border-radius:2px 0 2px 0; padding:2px 10px; }
.selfpost-reward{ grid-area:reward; display:flex; align-items:baseline; gap:7px; white-space:nowrap;
  font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--dim); }
.selfpost-num{ font-family:var(--display); font-weight:400; font-size:25px; line-height:1; color:var(--ink); }

/* post button: outlined ghost ("put it up"), pinned to the top-right of the slip */
.selfpost-slip > .selfpost-btn, .selfpost-slip > .selfpost-row-btn{ grid-area:btn; justify-self:end; }
.selfpost-btn, .selfpost-row-btn{ font-family:var(--mono); font-size:13px; letter-spacing:.06em;
  color:var(--ink); background:none; border:1px solid var(--ink); border-radius:2px;
  min-height:44px; padding:8px 16px; cursor:pointer; white-space:nowrap; }
.selfpost-btn:hover, .selfpost-row-btn:hover{ background:var(--ink); color:var(--paper-2); }
.selfpost-btn:focus-visible, .selfpost-row-btn:focus-visible{ outline:2px solid var(--sage); outline-offset:2px; }
.selfpost-btn:disabled, .selfpost-row-btn:disabled{ opacity:.5; cursor:default; }
.selfpost-btn:disabled:hover, .selfpost-row-btn:disabled:hover{ background:none; color:var(--ink); }

/* scope control: segmented "grabbable by [anyone | my crew]" (two radios styled
   as pills) + a live hint. checked+sibling styling (no :has()) so the selected
   pill reads even on older engines. */
.scope{ grid-area:scope; display:flex; align-items:center; flex-wrap:wrap; justify-content:flex-start;
  gap:8px; border:0; margin:0; padding:0; min-width:0; }
.scope-q{ font-family:var(--mono); font-size:11px; color:var(--soft); }
.seg{ display:inline-flex; border:1px solid var(--ink); border-radius:2px; overflow:hidden; }
.seg input{ position:absolute; width:1px; height:1px; opacity:0; }
/* .slackline sets a paper-colored text-shadow (legibility over the sun) that
   inherits down here; on the selected pill (paper text on ink) it haloed into a
   "glow". Kill it so selection reads as a clean flat inverted-ink fill, no glow. */
.seg .pill{ display:inline-flex; align-items:center; font-family:var(--mono); font-size:11.5px;
  letter-spacing:.02em; padding:6px 12px; cursor:pointer; color:var(--soft); white-space:nowrap;
  text-shadow:none; }
.seg label + label .pill{ border-left:1px solid var(--ink); }
.seg input:checked + .pill{ background:var(--ink); color:var(--paper-2); }
.seg input:focus-visible + .pill{ outline:2px solid var(--sage); outline-offset:-2px; }
/* the scope explainer, revealed by the [?] (reuses the board's .explain frame);
   full width + left-aligned inside the slip grid (the base .explain is centered). */
.selfpost-explain{ grid-area:explain; max-width:none; margin:2px 0 0; text-shadow:none; }
.selfpost-explain-line{ margin:0; }
.selfpost-explain-line + .selfpost-explain-line{ margin-top:5px; }
.selfpost-explain-line b{ font-weight:400; color:var(--ink); }
.selfpost-msg{ grid-area:msg; margin:0; font-family:var(--mono); font-size:12px; color:var(--ember); }

/* Compact multi-missed-days group (3+ days): ONE framed card holding a header
   (count + clamped total) + a wanted-slip per day. Widen past .slackline's 36ch
   cap via .slackline--grouped on the host. */
.slackline--grouped{ max-width:none; }
.selfpost-group{ display:flex; flex-direction:column; align-items:stretch; gap:0;
  width:min(460px,94vw); padding:16px 18px 16px; }
.selfpost-group-head{ font-family:var(--display); font-weight:400; font-size:14px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--soft); text-align:center; }
.selfpost-group-head b{ font-weight:400; color:var(--ink); }
.selfpost-group-sub{ display:block; font-family:var(--mono); font-size:12px; color:var(--soft);
  text-align:center; margin:3px 0 10px; }
.selfpost-group-sub b{ color:var(--ink); }
/* The rows scroll inside a capped viewport so a full owed-days stack (up to 4)
   never stretches the page; header + total stay pinned above. 400px mirrors the
   leaderboard's .rows cap and lands mid-row at both widths (≈3.3 compact rows on
   desktop / ≈2.3 taller stacked rows on phone), so the next row always peeks — a
   scroll cue that survives idle-hidden overlay scrollbars. Retune with the row
   heights below. The 4px padding keeps each row's 3px frame ring off the scroll
   edge (overflow clips otherwise); the negative margin re-widens so rows still
   fill the group. No CLS reservation to mind on /home (the slip lives in the
   hero, which grows with content), and max-height caps without reserving, so a
   short stack collapses cleanly. */
.selfpost-rows{ max-height:400px; overflow-y:auto; overflow-x:hidden; padding:4px; margin:0 -4px; }
/* each row is a wanted-slip in the quieter --dim frame (the group's --ink frame
   is the primary one); .selfpost-slip supplies the tab + reward + act layout */
.selfpost-row{ position:relative; border:1px solid var(--dim);
  box-shadow:0 0 0 2px var(--paper-2),0 0 0 3px var(--dim); border-radius:2px;
  background:radial-gradient(130% 170% at 24% -30%, rgba(251,231,184,.55), transparent 60%), var(--paper-2);
  padding:9px 14px 10px 16px; }
/* the row's shorthand padding above would reset the slip's padding-top; this
   compound selector (higher specificity) restores the tab clearance. */
.selfpost-row.selfpost-slip{ padding-top:20px; }
.selfpost-row + .selfpost-row{ margin-top:9px; }
@media (max-width:559px){
  /* Single column so the button + segmented scope never clip at 375px, and pills
     keep a >=44px tap target. */
  .selfpost-slip{ grid-template-columns:1fr;
    grid-template-areas:"reward" "btn" "scope" "explain" "msg"; }
  .selfpost-slip > .selfpost-btn, .selfpost-slip > .selfpost-row-btn{ justify-self:stretch; }
  .seg .pill{ min-height:44px; padding:0 14px; }
}
.slackline-noscript{ max-width:36ch; }
.slackline-noscript a{ color:var(--ember); text-underline-offset:3px; }
@media (prefers-reduced-motion:reduce){ .selfpost, .selfpost-group{ animation:none; } }

/* pirate papers (account page) */
.papers{ display:flex; flex-direction:column; align-items:center; gap:6px; width:min(400px,88vw); }
.papers .share-label{ text-align:center; }
.pirate-alias{ font-family:var(--display); font-weight:400; font-size:22px; letter-spacing:.04em; }
.pirate-rank{ font-family:var(--mono); font-size:12.5px; color:var(--soft); }
.nick-toggle{ font-family:var(--mono); font-size:12.5px; color:var(--soft);
  display:inline-flex; align-items:center; gap:7px; margin-top:4px; cursor:pointer; }
.nick-toggle input{ accent-color:var(--sage); }
.nick-toggle:has(input:disabled){ color:var(--dim); cursor:default; }
.nick-hint{ margin:2px 0 0; font-family:var(--mono); font-size:11.5px;
  color:var(--dim); max-width:36ch; }

/* scar + revealed-hidden badge chips */
.badge-chip.scar.earned{ color:var(--ember); border-color:var(--ember); }
.badge-chip.hidden.earned{ color:var(--sage); border-color:var(--sage); }

/* settings (account page, last block): relocated nickname toggle + sign-out,
   plus a read-only plan line linking out to /plan (editors stay there). */
.settings{ display:flex; flex-direction:column; align-items:center; gap:6px; width:min(400px,88vw); }
.settings .share-label{ text-align:center; }
.settings-plan-line{ font-family:var(--mono); font-size:12.5px; color:var(--soft);
  text-decoration:underline; text-underline-offset:3px; }
.settings-plan-line:hover{ color:var(--ink); }

/* in-app feedback button (feedback.js injects the FAB + <dialog> on signed-in
   pages). z-index 7 = above page content (.hero-in) but below the top nav
   (z-index 8); they never overlap spatially anyway. */
.feedback-fab{ position:fixed; right:16px; bottom:calc(16px + env(safe-area-inset-bottom)); z-index:7;
  font-family:var(--display); font-weight:400; font-size:14px; letter-spacing:.06em;
  color:var(--paper); background:var(--ink); border:none; border-radius:999px;
  padding:12px 18px; min-height:44px; cursor:pointer; box-shadow:0 3px 14px rgba(33,28,21,.28);
  display:inline-flex; align-items:center; justify-content:center;
  transition:transform .18s ease; }
.feedback-fab:hover{ transform:translateY(-1px); }
.feedback-fab:focus-visible{ outline:3px solid var(--sage); outline-offset:3px; }
/* desktop shows the word; the speech-bubble glyph is the phone form (below) */
.feedback-fab .fab-ico{ display:none; width:20px; height:20px; }
/* hide-on-scroll: feedback.js toggles this on scroll-down / reveals on scroll-up
   (mirrors the top-nav pattern in home.js), so the FAB steps out of the way
   while reading. Declared after :hover so it wins when hidden. */
.feedback-fab--hidden{ transform:translateY(calc(100% + 28px)); }
@media (prefers-reduced-motion:reduce){ .feedback-fab{ transition:none; } }
/* mobile (spec added-scope): the FAB must never permanently obscure content.
   (1) shrink to a compact 44x44 round glyph button (no word pill, no emoji — an
   inline-SVG speech bubble, consistent with the poster-art inline-SVG approach);
   (2) the page reserves bottom scroll space (below) so the last content clears
   it; (3) hide-on-scroll (class above). aria-label keeps the name at every width. */
@media (max-width:559px){
  .feedback-fab{ padding:0; width:48px; height:48px; border-radius:50%; }
  .feedback-fab .fab-word{ display:none; }
  .feedback-fab .fab-ico{ display:block; }
  /* reserve room so the FAB never sits on top of the last content with no way to
     scroll it clear (signed-in pages only: the FAB is injected there). */
  .hero--page .hero-in{ padding-bottom:calc(84px + env(safe-area-inset-bottom)); }
}

.feedback-modal{ width:min(420px,92vw); border:1.5px solid var(--line); border-radius:4px;
  background:var(--paper-2); color:var(--ink); padding:20px; }
.feedback-modal::backdrop{ background:rgba(33,28,21,.42); }
.feedback-form{ display:flex; flex-direction:column; gap:10px; text-align:left; }
.feedback-title{ font-family:var(--display); font-weight:400; font-size:20px; margin:0 0 4px; color:var(--ink); }
.feedback-label{ font-family:var(--mono); font-size:12.5px; color:var(--soft); letter-spacing:.02em; }
.feedback-text, .feedback-select{ font-family:var(--mono); font-size:15px; color:var(--ink);
  background:var(--paper); border:1.5px solid var(--line); border-radius:2px; padding:10px 12px; width:100%; }
.feedback-text{ resize:vertical; min-height:96px; }
.feedback-text:focus-visible, .feedback-select:focus-visible{ outline:none; border-color:var(--sage); }
.feedback-msg{ margin:2px 0 0; }
.feedback-actions{ display:flex; justify-content:flex-end; gap:8px; margin-top:6px; }

/* schema-out-of-date banner (deploy/migration gate): shown by home.js when
   /api/me returns a schema_out_of_date 503, so a mid-deploy skew reads as a
   visible "updating" state instead of a blank/logged-out page. Top-fixed, above
   everything, mono copy on the ink slab. */
.schema-banner{ position:fixed; top:0; left:0; right:0; z-index:9;
  font-family:var(--mono); font-size:13px; letter-spacing:.02em; text-align:center;
  color:var(--paper); background:var(--ink); padding:calc(10px + env(safe-area-inset-top)) 16px 10px;
  box-shadow:0 2px 12px rgba(33,28,21,.28); }

/* ---------------------------------------------------------------------------
   /bonyo — the ambient countdown page. The /home hero with controls removed:
   just Bonyo, the big DOWN-number (remaining = target - community total), and
   the speech bubble tapping Bonyo raises. Reuses the hero/sun/card/comm-bar
   CSS above; only the big-number sizing and the bubble are new here.
   --------------------------------------------------------------------------- */
.bonyo-count{ display:flex; flex-direction:column; align-items:center; gap:8px;
  width:min(360px,88vw); margin-top:6px; }
/* the countdown figure: the display face, a size step above .goal-num so it is
   unmistakably the hero of the page (tabular so digits don't jitter as it rolls) */
.bonyo-num{ font-family:var(--display); font-weight:400; font-variant-numeric:tabular-nums;
  font-size:clamp(56px,14vw,150px); line-height:.98; letter-spacing:.01em; color:var(--ink);
  text-shadow:0 0 18px var(--paper),0 0 9px var(--paper); }
.bonyo-num.tick{ animation:flick .5s ease-out; }   /* reuse the count flick keyframe */

/* Speech bubble: the paper callout Bonyo speaks through (double-rule frame, so
   it "belongs to the paper"). THIS BLOCK IS THE ONE SHARED DEFINITION — /bonyo,
   the signed-in pages (#bonyo + [data-say-bubble]) and the /welcome sibling all
   reuse it, so the fix lives here, never per page.

   Two things it must get right at EVERY viewport (captain phone review):
   (1) WIDTH BAND, never a one-word tower. The box is position:absolute inside
       the narrow (~140-200px) .bonyo-wrap containing block. `width:max-content`
       sizes it to the line on well-behaved engines, but iOS Safari mis-resolves
       max-content to MIN-content for an abs box in a narrow block and wraps it
       one word per line (the "skinny tower" the captain still sees on his phone).
       `min-width` is the insurance: even fully collapsed the box stays ~3 words
       wide, so it wraps naturally; max-width then caps + wraps the long quips.
   (2) TAIL points at Bonyo, which means anchoring to his HEAD, not to the top of
       the box. This is where the rule was wrong (captain saw it detached at
       ~980px, and it was equally wrong at EVERY width above the phone
       breakpoint): `top:-4px` pins the bubble to the top of `.bonyo-wrap`, but
       that box is the whole 512x640 art, whose top is the BAR — his head sits
       ~55% DOWN it. So the callout floated ~100-120px above his head with the
       tail aimed into the empty space under the bar, reading as a detached
       label. The anchor is now a PERCENTAGE of the wrap (`top:46%`), so it
       tracks his head at every size instead of the art box's top edge, and the
       tail sits near the bubble's TOP-left (so a two-line quip grows downward
       and the tail stays level with his face). Percentages are what make this
       hold: the wrap is sized `clamp(124px,18vh,200px)` off viewport HEIGHT, so
       a px offset drifts off his head as the art scales. */
.saybubble{ position:absolute; top:46%; left:82%; z-index:8;
  width:max-content; min-width:15ch; max-width:230px; text-align:left;
  opacity:0; transform:translateY(-6px) scale(.9); transition:opacity .16s ease, transform .16s ease;
  background:var(--paper-2); border:1px solid var(--ink);
  box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--ink); border-radius:3px; padding:9px 12px;
  font-family:var(--display); font-weight:400; font-size:14px; line-height:1.25; color:var(--ink); }
.saybubble.show{ opacity:1; transform:translateY(-6px) scale(1); }
.saybubble.shout{ color:var(--sun-deep); font-size:16px; letter-spacing:.02em; }
/* wide-viewport tail: near the bubble's TOP-left, so it stays level with Bonyo's
   face (which the % anchor above parks it beside) no matter how many lines the
   quip wraps to — the box grows downward, away from the tail. border-left +
   border-bottom rotated 45deg puts the wedge's apex on the LEFT, pointing
   straight at him. */
.saybubble::after{ content:""; position:absolute; left:-9px; top:15px; bottom:auto; width:14px; height:14px;
  background:var(--paper-2); border-left:1px solid var(--ink); border-bottom:1px solid var(--ink);
  transform:rotate(45deg); }
/* Narrow / phone: no room to Bonyo's right (the number sits below him), so the
   bubble pops straight ABOVE him and the tail points straight DOWN into his face.
   Held up to 719px so the side layout only renders where it has room to grow. */
@media (max-width:719px){
  .saybubble{ left:50%; top:auto; bottom:calc(100% - 6px);
    transform:translate(-50%,-4px) scale(.9); min-width:14ch; max-width:80vw; text-align:center; }
  .saybubble.show{ transform:translate(-50%,-4px) scale(1); }
  .saybubble::after{ left:50%; top:auto; bottom:-8px; width:15px; height:15px; margin-left:-7.5px;
    border-left:none; border-top:none; border-right:1px solid var(--ink); border-bottom:1px solid var(--ink); }
}

/* account page — the challenge note. The old flat .goal block becomes a quiet
   "belongs to the paper" aside (double-rule frame at --dim) with the live
   fraction and a days-remaining readout. */
.challenge{ position:relative; width:min(320px,86vw); margin-top:8px; padding:15px 18px;
  display:flex; flex-direction:column; align-items:center; gap:5px;
  background:radial-gradient(120% 150% at 14% 0%, var(--honey), var(--paper-2) 62%);
  border:1px solid var(--dim); box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--dim);
  border-radius:3px; }
.challenge-by{ font-family:var(--display); font-weight:400; font-size:13px; letter-spacing:.02em;
  color:var(--sun-deep); }
.challenge .comm-bar{ margin-top:8px; }
.challenge-stats{ display:flex; justify-content:space-between; width:100%; gap:10px; margin-top:8px;
  font-family:var(--mono); font-size:12px; color:var(--soft); }
.challenge-stats b{ font-family:var(--display); font-weight:400; color:var(--ink); }
.challenge-stats .days{ text-align:right; }
.challenge-stats .days b{ color:var(--sun-deep); }

@media (prefers-reduced-motion:reduce){
  .bonyo-num.tick{ animation:none; }
  .saybubble{ transition:none; }
  .comm-fill{ transition:none; }
}

/* landing → /bonyo: a small, unobtrusive link to the ambient countdown screen */
.watch-line{ margin:0; font-family:var(--mono); font-size:13px; letter-spacing:.02em; }
.watch-line a{ color:var(--dim); text-decoration:underline; text-underline-offset:3px;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.watch-line a:hover{ color:var(--soft); }

/* /bonyo back affordance: a quiet top-left link out of the ambient page. Pinned
   absolutely so it never disturbs the centered countdown; same faint --display
   treatment as /info's .info-home. */
.bonyo-back{ position:absolute; z-index:8; text-decoration:none;
  left:calc(12px + env(safe-area-inset-left)); top:calc(14px + env(safe-area-inset-top));
  font-family:var(--display); font-size:13px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--dim); text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.bonyo-back:hover{ color:var(--ink); }
.bonyo-back:focus-visible{ outline:2px solid var(--sage); outline-offset:3px; }

/* /bonyo → /about-bonyo: an even quieter link, pinned to the very bottom of the
   hero so it never competes with the countdown. Faint --dim mono, no underline
   until hover. z-index above the sun canvas (z1) + grain overlay (z5). */
.bonyo-about{ position:absolute; left:0; right:0; z-index:8; text-align:center;
  bottom:calc(14px + env(safe-area-inset-bottom));
  font-family:var(--mono); font-size:12px; letter-spacing:.05em; }
.bonyo-about a{ color:var(--dim); opacity:.72; text-decoration:none;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.bonyo-about a:hover{ color:var(--soft); opacity:1;
  text-decoration:underline; text-underline-offset:3px; }

/* /about-bonyo: the captain's short stacked lines under the mascot. Static copy
   (no live data). Headline is the display role; the lines are the clarity role
   (--mono, like the landing .sub), each on its own line, comfortably spaced. */
.about{ display:flex; flex-direction:column; align-items:center; gap:12px;
  width:min(600px,92vw); text-align:center; }
.about-line{ margin:0; font-family:var(--mono); font-size:clamp(15px,2.4vw,18px);
  line-height:1.5; color:var(--soft); max-width:34ch;
  text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.about-line:first-of-type{ margin-top:2px; }

/* --- /info: the public explainer. Reuses the hero/paper shell but as a
   deliberate, centered long-form reading column: wider than the app's action
   cards, with the prose FILLING the measure (the base `.card p` 34ch cap is
   lifted here, which is what made the desktop copy hug a narrow left column),
   and selectable text restored (.hero sets user-select:none, poor for reading). --- */
.info-top{ position:relative; z-index:7; width:min(620px,92vw); margin:0 auto;
  padding:calc(12px + env(safe-area-inset-top)) 4px 0; }
.info-home{ font-family:var(--display); font-size:13px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--dim); text-decoration:none; }
.info-home:hover{ color:var(--ink); }
.info-home:focus-visible{ outline:2px solid var(--sage); outline-offset:3px; }
.card.info{ width:min(620px,92vw); text-align:left; align-items:stretch; gap:18px;
  user-select:text; -webkit-user-select:text; }
.card.info .bonyo-wrap{ align-self:center; }
.card.info h1{ text-align:center; }
/* centered intro, held to a comfortable measure (beats the .card p 34ch cap) */
.card.info .info-lede{ font-family:var(--mono); font-size:15px; line-height:1.6; color:var(--soft);
  text-align:center; margin:0 auto; max-width:52ch; }
.info-sec{ width:100%; }
.info-sec h2{ font-family:var(--display); font-weight:400; font-size:clamp(17px,4.2vw,20px);
  letter-spacing:.02em; color:var(--ink); margin:0 0 7px; }
/* max-width:none lifts the base .card p 34ch cap so body copy fills the column */
.info-sec p{ font-family:var(--mono); font-size:14.5px; line-height:1.62; color:var(--soft);
  margin:0 0 4px; max-width:none; }
.info-sec a{ color:var(--ember); text-decoration:underline; text-underline-offset:2px; }
.info-sec a:hover{ color:var(--red); }
.info-sec em{ font-style:normal; color:var(--ink); }   /* ink-weight emphasis, not italic (house style) */
/* short scannable list inside a section: same body type as .info-sec p, em-dash-free
   bullet drawn as a paper-toned marker rather than the UA disc */
.info-list{ font-family:var(--mono); font-size:14.5px; line-height:1.62; color:var(--soft);
  margin:2px 0 6px; padding-left:1.15em; list-style:none; }
.info-list li{ position:relative; }
.info-list li::before{ content:"·"; position:absolute; left:-1.15em; color:var(--dim); }
.card.info .challenge{ margin:2px auto 0; }   /* center the fixed-width widget in the wide column */
.info-cta{ align-self:center; margin-top:4px; }
/* embedded app illustrations: framed like board furniture (double-rule frame),
   width/height on the <img> reserves the box (no CLS). */
.info-shot{ margin:12px 0 2px; }
.info-shot img{ display:block; width:100%; height:auto; border-radius:2px;
  border:1px solid var(--dim); box-shadow:0 0 0 3px var(--paper-2),0 0 0 4px var(--dim); }
.info-shot figcaption{ margin-top:9px; font-family:var(--mono); font-size:12.5px; color:var(--dim);
  text-align:center; }
/* a portrait poster shot shouldn't span the full reading column */
.info-shot--poster{ max-width:300px; margin-left:auto; margin-right:auto; }

/* /welcome — the first-login intro (spec 2026-07-21, presentation re-specced
   round 3): the /bonyo hero (animated ASCII sun + Bonyo + the real .saybubble,
   all from landing-sun.js + the shared rules above) plus a name reveal and one
   CTA. The pirate NAME is the hero: INLINE on one line, CRISP solid ink with no
   glow (captain round 4 rejected an earlier honey halo as fuzzy), sized from a
   cqi baseline (poster-text precedent: px-first fallback, .card.welcome is the
   container) and fit-shrunk by welcome.js; .wrap is the two-balanced-lines
   escape hatch for an alias that cannot fit at phone width.

   The name is THIS page's hero figure exactly as the countdown number is
   /bonyo's, and it earns the same licence to outgrow the narrow card. The
   shared `.card` is min(440px,90vw), an auth-FORM measure, which pinned even a
   short alias to ~37px on a 1280 desktop and read timid next to /bonyo. Widen
   ONLY the welcome card so the cqi baseline has room to scale; the phone value
   stays 90vw, so narrow layout is unchanged. `.card p` keeps its 34ch cap, so
   the mission line still sets to a comfortable measure in the wider column. */
.card.welcome{ gap:9px; container-type:inline-size; width:min(720px,90vw); }
.card.welcome .welcome-namelabel{ margin:0 0 -4px; font-family:var(--mono); font-size:13px;
  letter-spacing:.06em; color:var(--dim); text-shadow:0 0 12px var(--paper),0 0 6px var(--paper); }
.card.welcome .welcome-name{ margin:0; width:100%; max-width:100%;
  font-family:var(--display); font-weight:400; color:var(--ink); white-space:nowrap;
  font-size:40px; font-size:min(12cqi,92px); line-height:1.12; letter-spacing:.01em;
  /* crisp solid ink, no glow (captain round 4). The paper-coloured backwash is
     the sitewide .card h1 recipe: invisible as a glow, it only knocks the ASCII
     sun back so the glyph edges stay clean. */
  text-shadow:0 0 18px var(--paper),0 0 9px var(--paper); }
.card.welcome .welcome-name.wrap{ white-space:normal; text-wrap:balance;
  overflow-wrap:break-word; font-size:26px; font-size:min(8cqi,34px); line-height:1.14; }
/* Rhythm: the name and its lead-in are ONE unit (the label sits tight under
   Bonyo and hugs the name), then a clear beat before the mission line, then a
   wider beat before the CTA so it reads as the page's single terminal action
   rather than a fourth line of copy. */
/* The mission line outgrew the base `.card p` 34ch measure when the cycle copy
   became "…by 20 july 2027." (#71), which broke the DATE across two lines
   ("20 july" / "2027."). Widen just this line's measure so it sets on one row
   wherever there is room; the words themselves belong to #71, so only the
   measure is touched here.
   MUST stay scoped under `.card.welcome`: a bare `.welcome-pitch` (0-1-0) loses
   to `.card p` (0-1-1), so both this max-width and the margin below are silently
   dropped if the scope is removed. */
.card.welcome .welcome-pitch{ margin:6px 0 0; max-width:46ch; }
.welcome-cta{ margin-top:18px; }
.card.welcome .welcome-noscript{ margin:0; font-size:13px; color:var(--soft); }
/* /welcome intentionally has NO bubble override: it uses the ONE shared
   `.saybubble` treatment (width band + tail) so it lands identical to /bonyo
   (captain: make /welcome conform to the canonical shared bubble). An earlier
   welcome-scoped anchor was removed here on merge — its tight max-width also
   fought the shared min-width floor. Any future bubble tuning belongs in the
   shared `.saybubble` block, not a per-page override. */
