:root{
  --paper:#F6F4F0; --paper0:#F6F4F0; --card:#FFFFFF; --card2:#EFECE6;
  --ink:#1A1815; --ink-2:#57534B; --ink-3:#8A847A; --rule:#E2DED6;
  --accent:#2B4A7D; --on-accent:#FDFBF7; --good:#5A6B4F; --tint:#EFECE6;
  --on-good:#FDFBF7; --on-bad:#FDFBF7; --ch-good:#3F7A3F; --ch-mid:#C9BFA9;
  --ch-bad:#A3342A; --ch-bar:#6B655C;
  --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua","Hoefler Text",Georgia,serif;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Inter,Arial,sans-serif;
  --syms:"STIX Two Math","Cambria Math","DejaVu Sans",Symbola,Georgia,serif;
  --fast:.16s cubic-bezier(.3,.7,.4,1);
}
@media (prefers-color-scheme:dark){
:root{
  --paper:#131211; --paper0:#131211; --card:#1B1917; --card2:#221F1B;
  --ink:#ECE8E1; --ink-2:#B5AFA5; --ink-3:#7E786E; --rule:#2E2B26;
  --accent:#7FA3D8; --on-accent:#131211; --good:#6E8A5E; --tint:#221F1B;
  --on-good:#131211; --on-bad:#FDFBF7; --ch-good:#77A868; --ch-mid:#CCC2B0;
  --ch-bad:#C05248; --ch-bar:#A69E92;
}
}
:root[data-theme="dark"]{
  --paper:#131211; --paper0:#131211; --card:#1B1917; --card2:#221F1B;
  --ink:#ECE8E1; --ink-2:#B5AFA5; --ink-3:#7E786E; --rule:#2E2B26;
  --accent:#7FA3D8; --on-accent:#131211; --good:#6E8A5E; --tint:#221F1B;
  --on-good:#131211; --on-bad:#FDFBF7; --ch-good:#77A868; --ch-mid:#CCC2B0;
  --ch-bad:#C05248; --ch-bar:#A69E92;
}
:root[data-theme="light"]{
  --paper:#F6F4F0; --paper0:#F6F4F0; --card:#FFFFFF; --card2:#EFECE6;
  --ink:#1A1815; --ink-2:#57534B; --ink-3:#8A847A; --rule:#E2DED6;
  --accent:#2B4A7D; --on-accent:#FDFBF7; --good:#5A6B4F; --tint:#EFECE6;
  --on-good:#FDFBF7; --on-bad:#FDFBF7; --ch-good:#3F7A3F; --ch-mid:#C9BFA9;
  --ch-bad:#A3342A; --ch-bar:#6B655C;
}

*{box-sizing:border-box;margin:0;}
[hidden]{display:none !important;}
/* THE 16px FLOOR, STATED ONCE FOR EVERY FIELD THAT CAN TAKE A CARET.
   Mobile Safari zooms the page when a focused field computes under 16px
   and does not zoom back on blur — fatal under `body.pane`, which is
   locked to one viewport. The named rules below each set their own size;
   this is the floor for a field that has NO rule, because an unstyled
   <input> inherits the UA default of 13.33px and would zoom silently.
   An element selector loses to every class selector in this sheet, so
   this changes nothing that already decided for itself.
   Checkboxes, radios, colour and range are excluded: they take no caret,
   so they never trigger the zoom, and forcing type on them would only
   resize the controls. `selftest.ios_never_zooms_an_input` pins the
   explicit half of this rule. */
input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=range]),
textarea,select{font-size:16px;}
html{font-size:17px;}
/* svh, NOT vh, AND NOT dvh. On iOS `vh` is the LARGEST viewport — the height
   the page would have if the browser chrome retracted — so a full-height
   layout runs underneath the address bar and the bottom bar, and the last
   thing on the page is the thing you cannot reach. `dvh` is the opposite
   mistake: it RE-MEASURES as that chrome hides and shows, which resizes the
   board mid-solve, and `window.onresize` is wired to hideSel() — so the
   square a solver had just picked would clear itself while they scrolled.
   `svh` is the small viewport: the one that does not move. The plain `vh`
   line stays in front of it as the fallback for browsers without svh. */
body{background:var(--paper);color:var(--ink);font-family:var(--sans);
  font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased;
  min-height:100vh;min-height:100svh;display:flex;flex-direction:column;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='0.9'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.015'/%3E%3C/svg%3E");}
/* THE ROOM SAYS WHERE YOU ARE — the paper itself is the fifth channel, and
   it carries exactly one idea: home or the stacks. The daily and the open
   week keep the site's own paper (the landing page, /today and the ritual
   all share one ground); an ARCHIVE deal shifts it six percent toward the
   ink — the reading-room grey of older stock — so leaving today is felt
   before it is read. It is deliberately below announcement threshold: the
   deal caption states the fact, the paper only agrees with it. Mixed from
   --paper0 (the unshifted paper) because a custom property cannot reference
   itself; mixed with --ink-3 rather than a hex so both themes shift the
   same way without a second palette. The slow transition is what keeps the
   change a room and not an event. */
body{transition:background-color .9s ease;}
body.md-mix{--paper:color-mix(in srgb,var(--ink-3) 6%,var(--paper0));}
a{color:var(--accent);}
button{font:inherit;}
/* ONE FOCUS RING, ON EVERYTHING THAT TAKES FOCUS. `a` and `summary` were
   missing, and `.ctl` is the reason that mattered: the same component is a
   <button> in the app and an <a> on the landing page (see the note on .ctl),
   so the site's own ring appeared on a control in one place and the browser's
   default on the identical-looking control in another. The offset is what
   keeps it legible on `.ctl` itself — an accent ring sits OUTSIDE an accent
   fill, on the paper. */
button:focus-visible,select:focus-visible,input:focus-visible,
a:focus-visible,summary:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--accent);outline-offset:2px;}

/* ---- shell ---- */
/* THE RAIL IS GONE, AND ITS BUTTONS ARE IN THE BAR. A 56px column of unlabelled
   glyphs cost permanent width from a layout whose two columns already need
   760px before they collapse, and it split navigation across two edges of the
   screen — worst of all, its ◦ button signed you out on one click of an
   unlabelled dot while the name chip in the bar asked you to confirm. One
   destructive action, two controls, opposite corners, different rules. Now
   there is one place, and the confirm is the only path. */
.shell{min-height:100vh;min-height:100svh;}
.main{padding:0 26px 44px;min-width:0;max-width:1180px;
  margin:0 auto;width:100%;}
/* min-height, NOT height. It used to be `height:56px` with `flex-wrap:wrap`,
   which is a contradiction: between about 980 and 1100px the bar wrapped to a
   second row that the fixed height then clipped, straight through the board
   beneath it. Wrapping is the correct behaviour — the bar just has to be
   allowed to grow when it does. */
.top{display:flex;align-items:center;gap:9px;row-gap:7px;min-height:56px;
  padding:8px 0;border-bottom:1px solid var(--rule);margin-bottom:22px;
  flex-wrap:wrap;position:sticky;top:0;z-index:40;background:var(--paper);
  transition:background-color .9s ease;}
/* The wordmark doubles as "back to the board", which is what the rail's ♘ did.
   A brand that is already in the corner does not need a second button beside
   it to mean "home". */
.brandbtn{display:inline-flex;align-items:center;gap:7px;border:0;
  background:none;padding:0;cursor:pointer;font:inherit;color:inherit;
  border-radius:8px;transition:opacity var(--fast);}
.brandbtn:hover{opacity:.7;}
/* THE MARK IS ITS OWN FRAME. This held a serif "C" inside a rounded accent
   box until 2026-08-13. The box is gone with the letter: □ is already a
   frame, and drawing a frame inside a frame gives two rectangles where
   neither reads as the mark. See page.mark() for what the square means. */
.brandbtn .mono{color:var(--accent);width:21px;height:21px;display:block;
  flex:none;}
/* The drawn square, everywhere. It fills whatever box it is given and takes
   its colour from the box, so one function serves the header, the gate, the
   landing nav and the hero without a variant per site. */
.mk{display:block;width:100%;height:100%;}
/* THE WEEK AS A ROW (page.week_strip). Squares are all the same BOX — the
   stroke is inset, so weight changes without the row losing its baseline.
   Weight is the difficulty band and fill is "you solved it"; the two never
   swap jobs. A rest day keeps its slot as a dotted ghost, because the row
   is the schedule and a rest day is part of the schedule. */
.wkstrip{display:flex;gap:9px;align-items:flex-end;flex-wrap:wrap;}
.wkstrip .wd{display:flex;flex-direction:column;align-items:center;gap:6px;
  cursor:default;}
.wkstrip .wsq{width:30px;height:30px;color:var(--accent);display:block;}
.wkstrip .wd.rest .wsq{color:var(--ink-3);opacity:.55;}
.wkstrip .wd i{font-style:normal;font-family:var(--sans);font-size:.56rem;
  font-weight:600;letter-spacing:.15em;text-transform:uppercase;
  color:var(--ink-3);}
.wkstrip .wd.done i{color:var(--accent);}
@media (max-width:640px){.wkstrip{gap:6px;} .wkstrip .wsq{width:25px;height:25px;}}

/* THE STRIP IN THE APP, where every open square is a door. Sizes down to fit
   a 56px bar beside the wordmark, the beta chip and four chips. */
.wkstrip.app{gap:7px;}
.wkstrip.app .wd{border:0;background:none;padding:0;font:inherit;
  border-radius:5px;}
.wkstrip.app .wsq{width:22px;height:22px;}
.wkstrip.app .wd i{font-size:.5rem;letter-spacing:.1em;}
button.wd{cursor:pointer;transition:transform var(--fast),opacity var(--fast);}
button.wd:hover{transform:translateY(-1px);}
button.wd:hover .wsq{opacity:.75;}
button.wd:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
/* TODAY IS RINGED, not recoloured. Colour already means band weight and
   fill already means "committed"; a third colour would collide with both.
   A ring sits outside the square and cannot be confused with its stroke. */
.wkstrip.app .wd.now .wsq{box-shadow:0 0 0 2px var(--paper),
  0 0 0 3.5px var(--accent);border-radius:1px;}
.wkstrip.app .wd.now i{color:var(--accent);font-weight:700;}
/* THE RING BREATHES WHILE TODAY'S IS STILL OPEN, and settles the moment the
   square fills. It is the betabar dot's cadence applied to the one standing
   question the header carries — "have you done today's?" — expressed as the
   ring dimming and returning, never as motion or growth. The selector IS the
   statement: today, not committed, not a rest day. Commit fills the square,
   /api/week repaints, and the row goes still — stillness earned is the
   quiet celebration. Honours prefers-reduced-motion like the betabar. */
@keyframes ringask{
  0%,100%{box-shadow:0 0 0 2px var(--paper),0 0 0 3.5px var(--accent);}
  50%{box-shadow:0 0 0 2px var(--paper),
      0 0 0 3.5px color-mix(in srgb,var(--accent) 40%,var(--paper));}}
.wkstrip.app .wd.now:not(.done):not(.rest) .wsq{
  animation:ringask 2.6s ease-in-out infinite;}
@media (prefers-reduced-motion: reduce){
  .wkstrip.app .wd.now:not(.done):not(.rest) .wsq{animation:none;}}
/* THE DAY YOU ARE ON, which is not the same fact as which day it is — and
   the three channels that could have said it are all spoken for: stroke is
   the band, fill is committed, the ring is today. So this is the fourth,
   POSITION: a rule under the day, the way a selected tab is marked. A member
   solving Friday now sees Friday underlined and Thursday still ringed, which
   is two true statements instead of one misleading one. */
.wkstrip.app .wd.here i{box-shadow:0 2px 0 0 var(--accent);}
.wkstrip.app .wd.here{opacity:1;}
/* Still to come: visible, because the week's shape is worth knowing before
   it happens, but plainly not yours yet. */
.wkstrip.app .wd.soon{opacity:.34;}
.wkstrip.app .wd.rest{opacity:.4;}
.wkslot{display:flex;align-items:center;min-height:34px;}
@media (max-width:980px){
  .wkslot{overflow-x:auto;max-width:100%;scrollbar-width:none;}
  .wkslot::-webkit-scrollbar{display:none;}}

/* The relocated band picker. `.seg` is unchanged — this is the same control,
   in the place where its question gets asked. */
.keepgo{display:flex;align-items:center;gap:11px;flex-wrap:wrap;
  margin-top:14px;padding-top:13px;border-top:1px solid var(--rule);}
.keepgo .kglab{font-size:12px;color:var(--ink-3);}
/* The send row. Same shape as .keepgo — it is the other half of "what now" —
   and every control is `.ctl sec`, so nothing here competes with the primary
   action. The label carries the site's uppercase-label treatment. */
/* The ritual line: two quiet facts at the verdict — the company and the
   appointment. Reads as a colophon line, not a banner. */
.ritual{margin-top:10px;font-size:13.5px;color:var(--ink-3);}
.ritual b{color:var(--ink-2);font-weight:600;}
.sendbar{display:flex;align-items:center;gap:9px;flex-wrap:wrap;
  margin-top:12px;padding-top:13px;border-top:1px solid var(--rule);}
.sendbar .sblab{font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);font-weight:600;margin-right:2px;}
.sendbar .ctl{padding:6px 13px;font-size:13px;}
/* The network row: one icon per place a club actually talks. Drawn in
   currentColor — the site's ink, not six brands' paint — and sized to the
   same 34px the .ctl buttons land at, so the row reads as one instrument.
   .net covers <a> and <button> alike, which is why it resets both. */
.sendbar .netrow{display:inline-flex;gap:7px;align-items:center;}
.sendbar .net{display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border:1px solid var(--rule);border-radius:6px;
  background:var(--card);color:var(--ink-2);cursor:pointer;padding:0;
  text-decoration:none;}
.sendbar .net:hover{border-color:var(--ink-3);color:var(--accent);}
.sendbar .net svg{width:15px;height:15px;fill:currentColor;display:block;}
/* Instagram is a picture, not a link (doc/growth.md §9), so its affordance
   is quieter: two footnote links to the ready-drawn shapes. */
.sendbar .imgrow{display:inline-flex;gap:10px;align-items:center;}
.sendbar .sbimg{font-size:12px;color:var(--ink-3);text-decoration:underline;
  text-underline-offset:2px;white-space:nowrap;}
.sendbar .sbimg:hover{color:var(--accent);}
/* THE WEEK'S BLOCK, shown exactly as it will paste — the solver reads what
   they are about to say before they say it. flex-basis:100% drops it onto
   its own line inside the sendbar's wrap; the row scrolls rather than the
   page (the standing overflow rule). */
.weekshare .wsblock{flex-basis:100%;margin:2px 0 0;white-space:pre;
  overflow-x:auto;font:13.5px/1.6 var(--sans);color:var(--ink-2);
  background:var(--card2);border:1px solid var(--rule);border-radius:6px;
  padding:10px 12px;}
/* A confirmation, not an announcement: it says what happened and goes away.
   No tick, no toast, no animation — the coda rules out celebrating. */
.sendbar .shdone{font-size:12px;color:var(--ch-good);}
/* The two-line lockup. Sized in em against whatever slot it sits in, so the
   hero and the gate share one rule. text-indent puts back the optical space
   the trailing letter-space steals, so both lines align left. */
.wordmark{display:inline-block;font-family:var(--serif);font-weight:600;
  letter-spacing:-.015em;line-height:1.02;text-align:left;}
.wordmark .wm2{display:block;font-family:var(--sans);font-weight:600;
  font-size:.315em;letter-spacing:.34em;text-transform:uppercase;
  color:var(--ink-2);border-top:1px solid var(--rule);
  margin-top:.2em;padding-top:.22em;text-indent:.17em;}
/* the app header is a nav bar, not a title page: one line, short under 560px */
.top h1 .wmshort{display:none;}
@media (max-width:560px){
  .top h1 .wmfull{display:none;} .top h1 .wmshort{display:inline;}}
.top h1{font-family:var(--serif);font-size:17px;font-weight:600;
  white-space:nowrap;}
.betachip{display:inline-block;font-family:var(--sans,inherit);
  font-size:11px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--accent);
  border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);
  border-radius:99px;padding:2px 8px;vertical-align:3px;margin:0 4px;
  cursor:help;}
.top .sp{flex:1;}
.chip{display:inline-flex;align-items:baseline;gap:5px;background:var(--card2);
  border:1px solid var(--rule);border-radius:99px;padding:3px 11px;
  font-size:12px;color:var(--ink-2);font-variant-numeric:tabular-nums;
  white-space:nowrap;transition:border-color var(--fast);}
.chip b{color:var(--ink);font-size:12.5px;}
.chipwho{cursor:pointer;font:inherit;font-size:12px;align-items:center;}
.chipwho:hover{border-color:var(--ink-3);color:var(--ink);}
.chipwho .cv{font-size:9px;color:var(--ink-3);margin-left:3px;
  transition:transform var(--fast);display:inline-block;}
.chipwho[aria-expanded="true"]{border-color:var(--ink-3);color:var(--ink);
  background:var(--card);}
.chipwho[aria-expanded="true"] .cv{transform:rotate(180deg);}
.chip:hover{border-color:var(--ink-3);}
/* ---- the tool menu ----
   Settings, help, theme, sound and sign out live here. They are all
   set-once-and-forget controls, which is exactly what earns a menu: the cost
   is one click on things nobody touches twice in a session, and the saving is
   five permanent glyphs off a bar that has real work to do. */
.umwrap{position:relative;display:inline-block;}
/* THE RACK: the bell and the card behind it. The dot is the accent and it
   is STILL — no pulse, no count; the channel discipline in the header
   comment. The card is the umenu's own geometry so the two corners of the
   bar match. */
.bellwrap{position:relative;display:inline-flex;}
.chipbtn.bell{display:inline-flex;align-items:center;position:relative;}
.belldot{position:absolute;top:4px;right:4px;width:7px;height:7px;
  border-radius:50%;background:var(--accent);}
.bellcard{position:absolute;right:0;top:calc(100% + 7px);z-index:45;
  width:min(330px,88vw);background:var(--card);border:1px solid var(--rule);
  border-radius:12px;padding:10px 14px;box-shadow:0 10px 28px
  rgba(0,0,0,.10);}
.bn{display:block;padding:8px 0;border-bottom:1px solid var(--rule);
  font-size:13.5px;line-height:1.5;color:var(--ink-2);text-decoration:none;}
.bn:last-child{border-bottom:0;}
.bn:hover{color:var(--ink);}
.bn.unseen{color:var(--ink);}
.bn .bwhen{display:block;font-size:11px;color:var(--ink-3);margin-top:1px;}
.bellempty{font-size:13px;line-height:1.55;color:var(--ink-3);
  padding:4px 0;}
.umenu{position:absolute;right:0;top:calc(100% + 7px);z-index:45;
  min-width:212px;background:var(--card);border:1px solid var(--rule);
  border-radius:12px;padding:5px;
  box-shadow:0 10px 30px -12px rgba(0,0,0,.35),0 2px 6px -3px rgba(0,0,0,.2);}
.umenu[hidden]{display:none;}
/* `a.mi` and `button.mi` are the same item — Your account is a real address
   so it is an anchor, which brings a default underline a button never had. */
.mi{display:flex;align-items:center;gap:9px;width:100%;border:0;
  background:none;cursor:pointer;font:inherit;font-size:13.5px;
  color:var(--ink-2);text-align:left;padding:7px 9px;border-radius:8px;
  text-decoration:none;box-sizing:border-box;
  transition:background var(--fast),color var(--fast);}
.mi:hover,.mi:focus-visible{background:var(--card2);color:var(--ink);
  outline:none;}
.mi .mi-i{font-family:var(--syms);font-size:14px;width:1.2em;flex:none;
  text-align:center;color:var(--ink-3);}
.mi:hover .mi-i{color:var(--ink-2);}
/* the current value, right-aligned: a menu that toggles something must say
   what it is toggling FROM, or the click is a guess */
.mi .mi-v{margin-left:auto;font-size:12px;color:var(--ink-3);
  font-variant-numeric:tabular-nums;}
.mi-sep{height:1px;background:var(--rule);margin:5px 9px;}
/* Sign out is the one destructive item, so it is the one item that changes
   colour — and it still asks twice. --ch-bad, never --accent: this is not
   brand chrome. */
.mi.danger:hover,.mi.danger:focus-visible{color:var(--ch-bad);}
.mi.danger.arm{color:var(--ch-bad);background:color-mix(in srgb,
  var(--ch-bad) 12%,transparent);font-weight:600;}
.mi.danger.arm .mi-i{color:var(--ch-bad);}
.chipbtn{background:var(--card2);border:1px solid var(--rule);
  border-radius:99px;width:28px;height:24px;font-size:13px;cursor:pointer;
  color:var(--ink-2);font-family:var(--syms);line-height:1;
  transition:border-color var(--fast),transform var(--fast);}
.chipbtn:hover{border-color:var(--ink-3);transform:translateY(-1px);}
.seg{display:inline-flex;background:var(--card2);border:1px solid var(--rule);
  border-radius:9px;padding:2px;font-size:12px;user-select:none;}
.seg span{padding:2.5px 10px;border-radius:7px;color:var(--ink-3);
  cursor:pointer;transition:color var(--fast),background var(--fast);}
.seg span:hover{color:var(--ink);}
.seg span.on{background:var(--card);color:var(--ink);
  box-shadow:0 0 0 1px var(--rule);}

/* ---- cards + columns ---- */
.cols{display:grid;
  grid-template-columns:minmax(440px,1.4fr) minmax(320px,1fr);gap:28px;
  align-items:start;}
@media (max-width:980px){.cols{grid-template-columns:1fr;}
  .main{padding:0 16px 44px;}
  /* The bar carries everything now, so a phone reaches the diary, settings
     and help from the same place a desktop does — no fixed bottom bar, and
     none of the padding the page used to reserve for one. */
  .top{gap:7px;}
  /* The week strip took the slot the band picker used to hold, and inherits
     its scroll rule for the same reason: it is the widest thing in the bar,
     and it should scroll on its own rather than force the whole bar to wrap
     around it. */
  .seg span{white-space:nowrap;}}
@media (max-width:560px){
  /* ONE ROW, AND WHAT IS ON IT IS WHAT A SOLVER USES WHILE SOLVING.
     The bar carried a wordmark, a beta tag, the week strip, four record
     chips, the diary button and the tool menu — about 460px of content at
     a width of 390. It wrapped, and `.wkslot{flex-basis:100%}` gave the
     strip a row of its own, which cost the board ~34px of a screen that
     has none to spare. The strip keeps its slot on the bar instead and
     scrolls sideways in it, which it has done above 980px all along.

     What comes off is the RECORD, not the schedule: `#todayline` reads
     "3 solved · 12 pts · run 5", which is the training diary's own
     subject, and #navdiary is still on this bar, one tap away. (#runline
     has been permanently hidden in paintRecord since the run moved into
     #todayline.) The clock stays, because it is the only chip that is
     about the position in front of you.

     AND THE SCHEDULE COMES OFF TOO, 2026-08-22 (owner's call). The
     paragraph above is the record of what was tried first and it stood
     for eight days: the strip kept its slot and scrolled sideways in it.
     What that missed is that the strip is not competing with the bar, it
     is competing with the ANALYSIS — it holds `.top` at its 56px
     min-height all by itself (a 22px square plus its day letter and the
     6px between them), and on a 390x844 phone the move list is the thing
     paying for it. The bar is 43px without it.

     WHAT IS LOST, said plainly rather than waved away: on a phone the
     app itself no longer draws the week anywhere. `.wkstrip.app .wd` is
     a button that deals that day's position, so a tap-target per day
     goes with it. What remains is NOT on this screen — the Monday
     letter's own strip (`review_mail.week_cells`), the landing page's
     (which a signed-in solver never sees) and `/daily.ics`. The daily
     is still what the app opens on (`puzzles.next_auto`), so the DAY is
     never unreachable; the WEEK, on a phone, is now a thing you read in
     the letter rather than on the board screen. That is the trade, and
     it is the owner's to revisit.

     None of this is conditional on `body.pane`. A bar that changed height
     at Commit would jump, and the solver would have watched it happen —
     which is exactly why the strip goes at a WIDTH and not at a state. */
  .top h1{order:1;}
  .wkslot{display:none;}
  #navdiary{order:3;}
  .chip{order:4;font-size:11.5px;padding:3px 9px;}
  .umwrap{order:5;margin-left:auto;}
  .top .sp{display:none;}
  .betachip,#ratingline,#todayline{display:none;}
  /* SEVEN BANDS DO NOT FIT ACROSS A PHONE, AND UNTIL 2026-08-24 THEY
     TOOK THE WHOLE PAGE WITH THEM. `.seg` is an inline-flex row of
     `nowrap` spans, which at a coarse pointer's tap sizes has a
     min-content width of about 425px — wider than the card on every
     handset — so when #keepgo appeared at the verdict it widened the
     LAYOUT: on a 360px screen the document became 441px and the browser
     zoomed the whole page out to fit it, mid-read, having just scrolled
     the reader to the verdict. Nothing reported an overflow, because
     `innerWidth` had grown with it — the same trap `overflow-x:hidden`
     set on the landing nav, one storey up: the number you would check
     is the number that moved. Wrapping is the honest answer here rather
     than a sideways scroll: there is room for two rows, and a band a
     solver cannot see is a band they will not ask for. */
  .seg{flex-wrap:wrap;}
  /* 56 was the strip's height and nothing else asks for it: the tallest
     thing left on this bar is a 30px control. Padding comes down with it.
     40 AND NOT 43 (2026-08-24): 43 still had three pixels of the strip's
     measure in it, and on the pane the bar is the one row the diagram is
     always paying for. The control clears it either way. */
  .top{min-height:40px;padding:5px 0;}}
@media (max-width:420px){
  /* The mark alone. §3 of doc/design.md already takes the lockup down to
     "Calculation" under 560px, on the argument that the app header is a
     nav bar and not a title page; this is that argument one step further,
     at the width where the word and the week cannot both be had. The mark
     IS the brand and is already the "back to the board" target, so what
     is lost here is letters and not identity.

     ONLY WHERE THE WEEK IS ACTUALLY THERE TO COMPETE. A signed-out
     visitor has no week strip (.shell.stranger hides it), so dropping
     the word bought that reader nothing and cost them the one place the
     product says its own name — a bare square in an otherwise empty bar.
     The trade is real when the row is full and pure loss when it is
     not. */
  .shell:not(.stranger) .top h1 .wmshort{display:none;}}
/* The board sticks BELOW the bar, not under it — 56px of bar plus the 16px
   the board always had. */
.boardcol{position:sticky;top:78px;}
@media (max-width:980px){.boardcol{position:static;}}

/* ======================================================================
   THE SOLVE IS A PANE; THE REVIEW IS A PAGE.

   Below 980px `.cols` collapses to one column, which is right for a
   document and wrong for the one screen a solver actually works on. The
   board went above the fold and the line being written went below it, so
   the loop stopped being "click two squares, read the line, click two
   more" and became "click, scroll down, read, scroll up, click". Nothing
   was broken; everything was simply somewhere else.

   While a dealt position is uncommitted the client puts `pane` on <body>
   and the phone becomes one fixed screen: the bar, the board, and the
   analysis, with THE LINE AS THE ONLY THING THAT SCROLLS. The annotation
   rows and Commit are pinned in view — an assessment you have to go
   looking for is the same mistake as an annotation behind a toggle, and
   #nbox above records why that was reversed.

   At Commit the class comes off and the page scrolls normally again: the
   verdict, the solution, the room and the diary are a document, and a
   document must scroll. The product's own shape decides where the lock
   ends — an analysis, a commitment, and a verdict.

   PORTRAIT ONLY. A phone held sideways is about 390px tall and cannot
   stack these at all; it gets two columns instead, further down.

   AND ONLY WHERE THERE IS ROOM. A pane that does not fit is strictly
   worse than the scrolling page it replaces, because `overflow:hidden`
   would clip Commit off the bottom with no way to reach it at all.
   Modelled against the sizes below — board card 112px of chrome around
   the diagram, bar 69, and an analysis card that cannot go under about
   215 once the two annotation rows and the toolbar have their coarse-
   pointer targets — the sums stop working somewhere around 620px of
   viewport height. Under that the phone keeps today's scrolling layout,
   which is exactly as good as it is now; a lock is only an improvement
   while it holds everything it locked.

   THE TWO NUMBERS HERE (620px, and the 36svh below) ARE THE ONLY THINGS
   ON THIS PAGE MEASURED ON PAPER RATHER THAN ON GLASS. They want one
   pass on a real handset, and they are deliberately the two easiest
   things in this block to move. */
@media (max-width:980px) and (orientation:portrait) and (min-height:620px){
  body.pane{height:100svh;overflow:hidden;}
  /* display:flex IS LOAD-BEARING. .shell is a plain block everywhere else,
     and `flex:1` on .main inside a block parent does exactly nothing — the
     column then takes its content height, the pane silently stops
     constraining anything, and the page scrolls again while every rule
     below still looks applied. The whole chain from here to .sheet has to
     be flex containers with min-height:0, or none of it holds. */
  body.pane .shell{height:100%;min-height:0;display:flex;
    flex-direction:column;}
  body.pane .main{flex:1;min-height:0;display:flex;flex-direction:column;
    overflow:hidden;padding-bottom:0;}
  /* sticky is what you do to a thing inside a scrolling document. In a
     pane the bar is simply the first row, and it never moves. */
  /* 6 AND NOT 12, AND THE SAME BELOW THE BAR AS BETWEEN THE CARDS. A
     pane has no scrolling to separate its rows and every card here draws
     its own border, so these gaps are doing less work than the same
     numbers do in a document — and every one of them is a millimetre off
     the diagram. Twelve pixels between the two of them, which is the
     margin that keeps the widest handset's move list growing rather than
     shrinking when the board takes the card's full width. */
  body.pane .top{position:static;flex:none;margin-bottom:6px;}
  /* `align-items:stretch` UNDOES SOMETHING THE GRID RULE MEANT ONLY FOR
     THE GRID. `.cols` is `display:grid` with `align-items:start` above —
     right there, where the axis is vertical and it stops two columns of
     different heights from stretching to match. Here `.cols` is a flex
     COLUMN, so that same declaration is about WIDTH, and it sizes each
     child to fit-content instead of to the column.
     Invisible for as long as every child was narrower than the column;
     the moment the header row became two `nowrap` halves its max-content
     width was 425px and `.boardcol` grew to 467 on a 390px screen, with
     `body.pane{overflow:hidden}` clipping the difference in silence. */
  body.pane .cols{flex:1;min-height:0;display:flex;flex-direction:column;
    gap:6px;align-items:stretch;}
  body.pane .boardcol{flex:none;}
  /* THE FRAME ROUND THE PLATE IS THE ENTRY CARD'S FRAME. `.card` is
     18px 20px everywhere, which is a measure chosen for TEXT — it is the
     white space that keeps a paragraph off a rule. What sits in this one
     is a diagram, which brings its own border and its own margin, so the
     card's padding is being paid twice; and on a phone the 40px of it
     across the middle is the WIDTH CAP on the board itself, because past
     about 40svh the diagram stops being limited by the height it may
     have and starts being limited by the card it sits in. It takes the
     analysis card's own 12/14 (and two less under the caption than over
     the citation, because the caption is 12.5px type and the citation is
     a line of names), so the two cards on the pane are framed alike —
     fourteen pixels of the board's height and twelve of its width, both
     of which go straight to the diagram. */
  body.pane .boardcol > .card{padding:12px 14px 10px;}
  /* overflow-y here is the SAFETY VALVE, not the feature — .sheet below is
     the scroller a solver is meant to use. If the annotation rows and the
     toolbar together want more than the remainder (a very short screen, a
     toolbar wrapped to three rows), this lets the column scroll instead of
     `body.pane{overflow:hidden}` clipping Commit off the bottom with no way
     to reach it. Reachable-by-scrolling beats unreachable. */
  body.pane .sidecol{flex:1;min-height:0;display:flex;flex-direction:column;
    overflow-y:auto;overscroll-behavior:contain;}
  /* THE ANALYSIS IS THE ONLY THING IN THIS COLUMN THAT MAY GROW. .sidecol
     also holds the verdict, the review, the room and the diary — all
     hidden during a solve — plus two that are NOT: `.controls` and
     `.colo`. The colophon's keyboard-shortcut list was taking ~85px off
     the bottom of the column, which came straight out of #entry and
     collapsed the move list to ZERO HEIGHT: the solver could enter moves
     and never see one. A phone has no keyboard for those shortcuts to
     describe, so it is not merely noise here — it is noise that was
     eating the feature. */
  body.pane .sidecol > *{flex:none;}
  body.pane .colo{display:none;}
  body.pane #entry{flex:1;min-height:0;display:flex;}
  /* AND IN THE REVEAL THE PRINTED SOLUTION IS THE ONE THAT MAY GROW.
     #entry is hidden from the commit onward and #chwrap takes its place
     as the only thing in this column with anything in it — #report and
     the room and the controls are all held until the verdict lands,
     which is also when this class comes off. The card is the scroller
     rather than the wrapper, so the border stays around the moving text
     and not around a box the text slides out of; and it takes the
     entry card's own 12/14, because it is now the same kind of object.
     THIS IS WHAT MAKES `showFrame`'s scrollIntoView SAFE: with a real
     inner scroller here, lighting a ply moves the line and not the
     page — see setPane for the whole argument. */
  body.pane #chwrap{flex:1;min-height:0;display:flex;}
  body.pane #chwrap .chapters{flex:1;min-height:0;overflow-y:auto;
    overscroll-behavior:contain;padding:12px 14px;}
  /* the plate costs the analysis its own padding twice over on a phone —
     take it back, or the two lines stop reading as a column */
  body.pane .chapters .myline{padding:8px 9px;}
  /* WHAT THE PANE GIVES UP, AND WHY EACH ONE IS CHEAP. Measured on a
     390x844 handset, the line was left with 5px — legible to nobody —
     while these four took 96px between them:

     - the hint fold (29px). "How it works" is reference, and the tool
       menu already carries it as a real page. A fold nobody opens
       mid-calculation is not worth a third of the move list.
     - the event and date under the players (17px). #ghead wrapped to
       THREE lines on a phone; the players are the citation a solver
       recognises, and the event is on screen again at the reveal.
     - the status line when it has nothing to say (27px). It reserved
       1.3em permanently for a message that is usually absent; :empty
       gives the space back and takes it again the moment it speaks.
     - the keyboard shortcut list, above.

     None of them is information removed from the product — each is
     information moved off the one screen where the board and the line
     have to fit together. */
  body.pane #hintbox{display:none;}
  body.pane .gamehead .gev{display:none;}
  /* THE CITATION ON ONE ROW (2026-08-22, owner's call). The two header
     rows cost 63px between them — `.caphead` 31 and `.gamehead` 32 — and
     both are orientation rather than calculation. They become one 27px
     row, and the 36px goes to the move list.

     WHAT IS DROPPED IS WHAT THE REVEAL SAYS AGAIN, which is `.gev`'s own
     argument one line up, applied twice more. The position NUMBER indexes
     a list the phone is not showing. The side-to-move mark is already
     under the diagram, spelled ("White to move"), where a caption belongs.
     The ECO names an opening a solver is not being asked about. All three
     come back at Commit with the rest of the byline, because `.bhead` is
     only a flex row while `body.pane` is on.

     THE PLAYERS AND THE DEAL BOTH STAY. The players are the citation a
     solver recognises — the one part of this block that is about THIS
     position — and `.deal` is load-bearing for a different reason:
     `selftest.deal_label_never_lies` exists because a private archive
     deal captioned as shared sends the reader to an empty room. It is
     the last thing that may be dropped for space, not the first. */
  /* `min-width:0` HERE IS LOAD-BEARING, and leaving it out did not look
     like a bug. A flex row of two `nowrap` children has a min-content
     width equal to both of them laid end to end — 426px for this one —
     and a flex/grid item may not go under its min-content width unless
     it is told it may. So the card grew to 467px on a 390px screen, and
     `body.pane{overflow:hidden}` clipped the difference in silence: the
     deal caption simply ran off the card with no ellipsis, because
     nothing was ever narrow enough to need one. The same trap
     `overflow-x:hidden` set on the landing nav, one card over. */
  body.pane .bhead{display:flex;justify-content:space-between;
    align-items:baseline;gap:10px;padding-bottom:6px;min-width:0;}
  /* THE FLEX ITEM IS `.caphead`, NOT `#dealsay`. Ellipsis on the child
     does nothing while its parent is free to be as wide as it likes:
     the text simply ran off the card and was clipped by the card's own
     overflow, with no ellipsis to show for it. The item has to be the
     thing that shrinks, and `min-width:0` is what lets a flex item go
     narrower than its content in the first place. */
  body.pane .caphead{padding:0;order:2;min-width:0;flex:0 1 auto;
    flex-shrink:5;display:flex;justify-content:flex-end;}
  body.pane .caphead .num,body.pane .caphead .stm{display:none;}
  /* ONE LINE, WHICH TAKES AN ELLIPSIS RATHER THAN A WRAP. Every
     `deal_say` is a sentence — "Today's position — the same one for every
     solver" is 48 characters — so allowed to wrap it takes the row to
     46px and gives back half of what this block just saved. Both halves
     of the row shrink and both end in an ellipsis; what survives a
     truncation is the FIRST clause, which is the one carrying the
     distinction the caption exists to make (today's / this week's /
     from the archive), and `.deal.shared`'s accent colour carries it a
     second time. The full sentence is on screen again at the reveal. */
  body.pane .caphead .deal{font-size:11px;min-width:0;flex:0 1 auto;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  /* WHICH HALF GIVES WAY, and it is not a toss-up. Both cannot fit on a
     390px screen — the citation is ~204px and the deal sentence ~212 —
     so the row is a priority, stated as a shrink ratio. The PLAYERS are
     the one part of this block about THIS position and the thing a
     solver recognises, so they shrink last (1); the deal sentence
     shrinks five times as fast, and what a truncation takes from it is
     the elaboration rather than the distinction — "From the archive —
     dealt t…" still says archive, "Today's position — the sam…" still
     says today, and `.deal.shared`'s accent says it a second time in
     colour. Left equal, the row cut the opponent's name to "Ale…". */
  body.pane .gamehead{padding:0;order:1;min-width:0;flex:0 1 auto;
    flex-shrink:1;overflow:hidden;white-space:nowrap;
    text-overflow:ellipsis;}
  body.pane .gamehead .eco{display:none;}
  body.pane .gamehead .players{font-size:13px;color:var(--ink-2);}
  body.pane .gamehead .elo{font-size:11px;}
  body.pane .flash:empty{display:none;}
  /* #evcap and #nbcap name the symbol under the pointer. There is no
     pointer here — `(hover:none)` above turns that whole channel off —
     so on a phone they were 31px of permanently reserved space for a
     caption that has nothing to caption. They keep their slot the
     instant they have something to say. */
  body.pane .evcap:empty,body.pane .nbcap:empty{display:none;}
  /* .sidecol > * + * puts 20px between the cards. During a solve every
     sibling after #entry is hidden but still a box, so the column was
     handing 32px of margin to things with no height. */
  body.pane .sidecol > * + *{margin-top:0;}
  /* THE TOOLBAR ON ONE ROW, AND NOT BY SHORTENING A LABEL. At 375px it
     wrapped to two, which cost 50px — a quarter of the analysis card and
     more than the move list had. The words stay: page.py records why
     "Delete move" and "Clear all" are spelt out (a bare ⌫ beside a bare
     ⟲ is only legible to someone who already knows which is which, and
     getting it wrong destroys ten minutes of work). What gives is the
     TYPE — a step down in size and padding, which is a setting decision
     and not an information one — and min-height keeps the tap target
     honest at 40px while the row itself gets shorter.

     IT DID NOT FIT, AND THAT WAS INVISIBLE FOR AS LONG AS NOBODY
     MEASURED IT (2026-08-24). One step down was not enough: the three
     buttons wanted 333px of a 328px row on a 390px handset — over by
     five — and of 313 at 375 and 298 at 360. So the row wrapped on the
     three commonest phones and went on costing the 46px this paragraph
     says it saved, while reading, in the source, exactly like a
     solved problem. The narrow-width block below is that measurement
     paid for; every number in it comes off the harness. */
  body.pane .toolbar{gap:6px;margin-top:8px;}
  body.pane .toolbar .ctl{padding:7px 11px;font-size:13.5px;min-height:40px;}
  body.pane #entry > .card{flex:1;min-height:0;display:flex;
    flex-direction:column;padding:12px 14px;}
  /* WHAT SCROLLS INSIDE THE BOX DEPENDS ON HOW MUCH BOX THERE IS, and
     this is the one place the pane degrades rather than refuses.

     `.anno` (#nbox — the assessment and glyph rows) is a CHILD of .sheet,
     not a sibling: the line is the document and the strip under it is
     that document's controls, deliberately one box. It is also not a
     fixed cost. Idle it is ~52px; the moment a move is selected the two
     button rows gain their coarse-pointer targets and it grows to ~128.
     Measured on a 375x667 handset, board and line and both rows and the
     toolbar all pinned needs about 333px of a 241px column. It does not
     fit, and no amount of tuning makes it fit.

     So: THE SHEET SCROLLS, carrying the line and the rows together, and
     the toolbar stays pinned. The line is at the top of that box, so it
     is always the part you see; the annotation rows are a short scroll
     inside a box that is itself entirely on screen. That is not the
     failure #nbox was un-toggled to prevent — nothing is behind a
     control you have to find, and nothing has left the screen. On a tall
     phone the override below pins the rows outright and none of it
     scrolls at all.

     overscroll-behavior stops a flick past the end from chaining into
     the document underneath and giving the lock away. */
  body.pane .sheet{flex:1;min-height:0;overflow-y:auto;
    overscroll-behavior:contain;margin-top:0;}
  body.pane .toolbar{flex:none;}
  /* Commit must clear the home indicator, not sit under it. */
  body.pane .toolbar{padding-bottom:env(safe-area-inset-bottom);}
  /* The heading named the box for a reader looking straight at a board
     with a variation under it. The page states a fact once. */
  body.pane #entry > .card > .lab{display:none;}
  /* A colophon is a document's footer, and this is not a document yet.
     It returns with the rest of the page at the verdict. */
  body.pane .footbar{display:none;}

  /* THE BOARD IS SIZED BY THE HEIGHT IT CAN HAVE, AND IT TAKES A SHARE
     OF THE SCREEN RATHER THAN WHAT IS LEFT OVER.

     A FRACTION, NOT A SUBTRACTION. The first draft subtracted a
     hand-counted reserve — bar, caption, byline, the analysis card's own
     minimum — from 100svh. Every term in that sum is a guess that goes
     stale: the toolbar wraps to a second row at 375px and does not at
     390, so one constant is right on one phone and 40px wrong on the
     next, and the failure is silent because the pane just clips. A share
     of the screen cannot go stale, and it states a decision the
     arithmetic never did: the diagram gets a share of the phone and the
     analysis gets the rest. Measured in a browser at 36%: a 640px screen
     drew 222px (28px squares), 667 drew 232 (29px), 844 drew 296 (37px),
     932 drew 300 (38px, width-capped) — a paperback diagram's square,
     and every one of them well past a fingertip.

     42% SINCE 2026-08-24, AND IT WAS PAID FOR IN CHROME RATHER THAN
     TAKEN FROM THE ANALYSIS. The owner asked for a bigger diagram
     WITHOUT a smaller analysis, which is only answerable by finding
     space that was doing no work: the toolbar's silent second row (46px
     on every handset under about 400px — see the block below), the
     board card's text-sized padding (14 down the middle and 12 across),
     three pixels of the bar's old strip measure, and the gaps and
     separations of a pane where nothing scrolls (12 between the cards,
     11 above the annotation rows). Sixty-six pixels on the commonest
     phones, twenty on the widest, and the share was raised to spend it
     on the board. Measured before -> after, board and then the analysis
     card, with a six-move line entered by clicking:

       360x640   222 -> 261 (28 -> 33px squares)   sheet 123 -> 165
       375x667   232 -> 272 (29 -> 34)             sheet 136 -> 180
       390x844   296 -> 320 (37 -> 40)             sheet 254 -> 309
       412x915   321 -> 342 (40 -> 43)             sheet 345 -> 358
       430x932   328 -> 360 (41 -> 45)             sheet 356 -> 357

     THE SHARE NOW BINDS ONLY ON SHORT PHONES, which is the honest
     reading of that table and worth knowing before anyone raises it
     again: at 42% a 844px screen wants 355px of diagram and the card is
     328px wide, so from about 780px of height upward the board is
     limited by the CARD and not by the share, and raising the number
     further would change nothing there. What would is the gutter — 16px
     of `.main` and 14 of the card on each side — and that is a decision
     about the page and not about the board.

     SIZED BY WIDTH, WHICH IS NOT AN ACCIDENT. `height:auto` on a square
     viewBox is what makes the SVG's ELEMENT box square, and sqFromClick
     divides by r.width and r.height INDEPENDENTLY — so a box that is not
     square maps every click to the wrong file or rank while the drawing
     inside still looks perfect, because preserveAspectRatio letterboxes
     the content and says nothing about the box. Never give this board a
     height; give it a width and let the ratio follow.

     A FIXED SIZE FOR THE WHOLE SOLVE, which a flex-sized board would not
     be. If it grew and shrank as the line got longer, .selmark — placed
     in absolute pixels by markAt() from a measurement taken when it was
     drawn — would sit over the wrong square, and window.onresize does
     not fire for a layout change. Nothing here moves the board once the
     position is dealt.

     The analysis takes the remainder and gives way from the top: .sheet
     shrinks and scrolls while the annotation rows and Commit stay
     pinned. That is the right thing to lose on a small phone — the line
     you can scroll, the button you cannot.

     THE PLATE AND ITS CAPTION CARRY THE WIDTH, NOT THE FIGURE. Until
     2026-08-24 the rule sat on `.pz` itself, so that the stipulation
     ("White to play") lined up with the diagram's own left edge rather
     than the card's — a plate and its caption are one object, and that
     is still exactly what these two lines say. What changed is that the
     figure ALSO holds the reveal's controls, and a transport pill plus
     the button that ends the route do not fit inside the width of a
     diagram on a 360px phone. They are not part of the plate, so they
     get the card. */
  body.pane .pz{width:100%;}
  body.pane .boardwrap,body.pane .under{width:min(100%,42svh);
    margin-inline:auto;}
  /* THE REVEAL'S FURNITURE, ON AS FEW ROWS AS IT HAS MEANINGS. From the
     commit to the verdict this column carries three more things than a
     solve does — the caption that names the move, the transport, and the
     one button that ends the route — and stacked at their document
     measures they are 152px, which on a 640px phone is the printed line's
     whole box. The two CONTROLS share a row (the pill left, the button
     right: they are the same kind of thing, and neither is reading
     matter), and the caption keeps its own, because it is the sentence
     the board is illustrating and a question is asked in it.

     A GRID AND NOT A FLOAT, so the row is declared rather than
     discovered. The three rows above it span both columns; during a
     solve all three of these are `hidden` and the grid has nothing but
     the plate and its caption in it, which is the layout that was here
     before. */
  body.pane .pz{display:grid;grid-template-columns:auto 1fr;
    align-items:center;}
  body.pane .boardwrap,body.pane .under,body.pane .playcap{
    grid-column:1/-1;}
  body.pane .playcap{font-size:15px;margin-top:6px;min-height:2.4em;}
  body.pane .playcap .quizq{font-size:17px;}
  body.pane .transport{grid-column:1;justify-self:start;margin-top:6px;}
  body.pane .ctarow{grid-column:2;justify-self:end;margin-top:6px;}
  /* AND THE COUNTER COMES OFF THE PILL. "5 / 23" is where you are in the
     line, and on this screen the LINE is saying it better: the printed
     solution above is written ply by ply as the board reaches it, so the
     lit end of it is the same fact with the moves attached. Fifty pixels,
     which is what the row needed to hold both controls at 360px. */
  /* the counter this hid is gone entirely — see .transport above */
  /* A CAPTION BELONGS TO ITS PLATE AND A RULE DOES ITS OWN SEPARATING.
     `.under` is 8px off the diagram and `.anno` carries 21px of clear
     space above a border it already draws — both are measures for a
     DOCUMENT, where a reader's eye has scrolled to arrive at them. In a
     pane nothing has moved and nothing is between them, so the same
     separation reads at two thirds of it, and the eleven pixels are a
     third of a move line back in the list. */
  body.pane .under{margin-top:6px;}
  body.pane .anno{margin-top:6px;padding-top:8px;}}

/* THE ROW FITS AT 360, AND ONLY BECAUSE IT WAS MEASURED THERE
   (2026-08-24). The pane's own step down (13.5px type, 11px of padding)
   leaves the three buttons over the row's width on every phone narrower
   than about 400px, so the row wrapped and took 46px off the move list —
   the single largest piece of chrome on the two commonest handsets, and
   the whole of what the diagram grew by below.

   WHAT GIVES IS THE PADDING FIRST AND THE UTILITIES' TYPE SECOND. Delete
   move and Clear all are the two the solver reads once and recognises
   afterwards; COMMIT KEEPS ITS SIZE, because it is the button the screen
   is built around and the one a thumb goes to without looking. Measured
   on the harness with a six-move line entered by clicking: 360 needs
   292.4 of 298, 375 needs 292.4 of 313, 390 of 328. The words stay — the
   paragraph above is still the rule, and this is the same trade one step
   further, at the width where one step was not enough.

   A WRAP HERE IS SAFE AND THAT IS WHY THE MARGIN MAY BE THIS THIN. If a
   real device's metrics differ by more than the 5.6px of slack at 360,
   the row wraps and the pane is exactly as good as it was this morning:
   the sheet scrolls, Commit is still pinned and still on screen. Nothing
   is clipped and nothing is unreachable. */
@media (max-width:430px) and (orientation:portrait) and (min-height:620px){
  body.pane .toolbar{gap:4px;}
  body.pane .toolbar .ctl{padding:7px 7px;}
  body.pane .toolbar #undo,body.pane .toolbar #clearb{font-size:12.5px;}}

@media (max-width:980px) and (orientation:portrait) and (min-height:880px){
  /* ABOVE 880px THE ROWS ARE PINNED AND ONLY THE MOVES SCROLL — the
     arrangement the short band cannot afford. .sheet stops scrolling and
     becomes the column; the line inside it takes the slack and is the
     only scroller left, so the assessment and glyph rows hold still
     under a moving list of moves.

     880 AND NOT 800, WHICH IS A NUMBER SIX REAL MOVES CORRECTED. The
     first cut put the boundary at 800 on a measurement taken with an
     EMPTY line — the harness had been clicking fixed coordinates that
     never made a legal move, so #commit sat disabled and every figure
     described a board nobody had played on. With a six-move line and a
     move selected, .anno carries both button rows at their coarse-
     pointer size and an 844px screen has nothing left: the line goes to
     zero. 915px keeps 58px of moves and 932px keeps 75px. Anything
     shorter is better served by the scrolling sheet below. */
  body.pane .sheet{display:flex;flex-direction:column;overflow:hidden;}
  body.pane .sheet > .line{flex:1;min-height:0;overflow-y:auto;
    overscroll-behavior:contain;}
  body.pane .anno{flex:none;}}

/* A phone held sideways is short and wide, which is the one shape the
   collapsed grid gets plainly wrong rather than merely cramped: stacking
   guarantees neither the board nor the line is whole, while the desktop's
   two columns fit it exactly. Board left, line right, both in view. The
   page still scrolls — there is no pane here, because at 390px tall
   there is nothing a pane could hold. */
@media (max-width:980px) and (orientation:landscape){
  .cols{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);
    gap:16px;align-items:start;}
  .pz{width:min(100%,calc(100svh - 150px));margin-inline:auto;}}

/* ---- touch ------------------------------------------------------------
   :hover ON A TOUCHSCREEN IS NOT A HOVER. It is what the browser leaves
   behind after a tap, and it stays there until something else is tapped.
   Every lift-and-tint below was drawn to answer a pointer that moves, so
   on a phone they read as stuck state — worst on `.tok`, where the last
   move tapped keeps the tint that means "the cursor is here" and two
   moves appear selected at once.

   (hover:none) is the honest test: it asks about the input device rather
   than inferring one from the width, so a touchscreen laptop keeps its
   hovers and a phone in landscape still loses them. Only the resting
   values are restored — :focus-visible is untouched, because a keyboard
   still needs to say where it is. */
@media (hover:none){
  .tok:hover:not(.cur):not(.now){background:none;}
  .ctl:hover{transform:none;filter:none;}
  .ctl.sec:hover{border-color:var(--rule);}
  .gb:hover,.evb:hover{transform:none;border-color:var(--rule);}
  .chipbtn:hover{transform:none;border-color:var(--rule);}
  .chip:hover{border-color:var(--rule);}
  .promo button:hover{border-color:var(--rule);}
  .mi:hover{background:none;color:var(--ink-2);}
  .mi:hover .mi-i{color:var(--ink-3);}
  /* The board's hover square is drawn from mousemove, and a tap fires one
     — so a finger left a marker sitting on the last square touched, on a
     board where .selmark is the thing that means "selected". */
  .hovmark{display:none !important;}}

/* Tap targets. THE TYPE DOES NOT CHANGE — only the box around it — so
   doc/design.md §5's 11px badge floor is untouched and nothing on the
   card is re-set for the phone. A 24px-tall evaluation button with six
   more beside it is a mis-tap waiting to happen, and mis-tapping this
   particular row costs a point. */
@media (pointer:coarse){
  .anno .evb,.gb{min-height:38px;min-width:38px;padding:3px 10px;}
  .promo button{min-height:38px;min-width:38px;}
  .seg span{padding:9px 12px;}
  .chipbtn{min-height:34px;}}
.card{background:var(--card);border:1px solid var(--rule);border-radius:14px;
  padding:18px 20px;}
.card + .card,.sidecol > * + *{margin-top:20px;}
.lab{font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);font-weight:600;}

/* ---- board card: the print diagram ---- */
.caphead{display:flex;justify-content:space-between;align-items:baseline;
  padding-bottom:8px;}
.caphead .num{font-family:var(--serif);font-size:17px;font-weight:600;}
.caphead .stm{font-family:var(--syms);font-size:15px;margin-left:6px;}
/* WHAT THIS POSITION IS, at the other end of the caption bar from what it
   is numbered. Quiet — it is orientation, not a headline, and the board is
   the thing being looked at. `.shared` is the daily and the open week: the
   two everybody else is also solving, and therefore the two where the room
   below the board has anybody in it. */
.caphead .deal{font-family:var(--sans);font-size:11.5px;color:var(--ink-3);
  letter-spacing:.02em;text-align:right;}
.caphead .deal.shared{color:var(--accent);}
.bhead{display:contents;}
.gamehead{padding:0 0 10px;line-height:1.45;min-height:1em;}
.gamehead .eco{font-size:12px;letter-spacing:.14em;color:var(--ink-3);
  margin-right:10px;font-variant-numeric:tabular-nums;}
.gamehead .players{font-family:var(--serif);font-size:15px;font-weight:600;
  font-variant-caps:small-caps;letter-spacing:.02em;}
.gamehead .elo{font-family:var(--sans);font-size:12px;color:var(--ink-3);
  font-weight:400;vertical-align:1px;margin-left:2px;}
.gamehead .gev{display:block;font-size:12px;color:var(--ink-3);}
/* touch-action:manipulation removes the ~300ms wait a mobile browser holds
   every tap for, in case it turns out to be the first half of a double-tap
   zoom. On a board where the whole interaction is tap-a-square-tap-a-square
   that delay is the difference between a control that answers and one that
   lags, and there is nothing on this board a double-tap should zoom.
   The tap highlight goes for a different reason: iOS paints its own grey
   rectangle over the square you touched, which is a SECOND feedback channel
   arguing with .selmark and .flashmark — the two this product designed and
   which say more (selected / illegal). One idea, one channel. */
.boardwrap{position:relative;border:1px solid var(--ink);
  padding:3px;-webkit-user-select:none;user-select:none;
  touch-action:manipulation;-webkit-tap-highlight-color:transparent;}
.board svg{display:block;width:100%;height:auto;cursor:pointer;}
/* THE SELECTION RING IS LAYERED BECAUSE THE GROUND IS UNKNOWN. The board
   squares are a skin the solver chose and never flip with the theme;
   --accent does — so an accent band alone always has a losing combo
   (dark-theme light blue on the print skin's cream is ~1.6:1). --on-accent
   sits at the opposite end of the lightness scale from --accent in BOTH
   themes, so an accent band between two on-accent hairlines guarantees
   one layer reads on any square in any theme. Inset shadows rather than
   border+outline: every pixel stays inside the cell markAt() measured. */
.selmark{position:absolute;border-radius:3px;
  pointer-events:none;display:none;
  box-shadow:inset 0 0 0 1px var(--on-accent),
    inset 0 0 0 4px var(--accent),
    inset 0 0 0 5px var(--on-accent);
  background:color-mix(in srgb,var(--accent) 14%,transparent);
  transition:left var(--fast),top var(--fast),width var(--fast),height var(--fast);}
.selmark.pop{animation:selpop .3s cubic-bezier(.3,.7,.4,1);}
@keyframes selpop{
  0%{transform:scale(1.04);
    background:color-mix(in srgb,var(--accent) 30%,transparent);}
  100%{transform:scale(1);
    background:color-mix(in srgb,var(--accent) 14%,transparent);}}
.flashmark{position:absolute;pointer-events:none;border-radius:3px;
  display:none;background:color-mix(in srgb,var(--ink) 35%,transparent);}
.flashmark.go{display:block;animation:sqfade .5s ease-out forwards;}
@keyframes sqfade{from{opacity:.85}to{opacity:0}}
/* THE CLICK IS ANSWERED BEFORE THE SERVER IS. Every move is a round trip
   (page.handle_line is 0.8ms; the trip is 230-600ms depending on which
   ocean the solver is on), and until 2026-08-18 the screen said NOTHING in
   that window — the selection vanished on the second click and the flash,
   the sound and the move in the sheet all waited for the reply. A solver
   in the US reported it as "a bit of a delay", which is the charitable
   reading of an action that visibly undoes itself.
   Steady and unanimated, because it is a STATE and not an event: it holds
   until the answer arrives, then `go` fades it (accepted) or `bad` shakes
   it red (refused). Lighter than `go`'s wash so the two never read alike —
   this one means "heard you", not "done". */
.flashmark.wait{display:block;opacity:.5;}
/* An illegal move is "you got this wrong", so this is --ch-bad — never
   --accent: a 45% accent wash on the destination square read as "this
   square is now selected", on top of pointing an error at the brand. */
.flashmark.bad{display:block;
  background:color-mix(in srgb,var(--ch-bad) 50%,transparent);
  animation:sqdeny .5s ease-out forwards;}
@keyframes sqdeny{
  0%{opacity:.9;transform:translateX(0)}
  25%{transform:translateX(-5px)}
  50%{transform:translateX(5px)}
  70%{transform:translateX(-3px)}
  100%{opacity:0;transform:translateX(0)}}
.hovmark{position:absolute;border:1.5px solid var(--ink-3);border-radius:3px;
  pointer-events:none;display:none;
  transition:left .1s ease-out,top .1s ease-out;}
.under{display:flex;justify-content:space-between;gap:1em;
  align-items:baseline;font-size:12.5px;margin-top:8px;}
.under .stipx{font-weight:600;white-space:nowrap;}
.under .pts{font-family:var(--syms);color:var(--ink-3);letter-spacing:.05em;
  font-variant-numeric:tabular-nums;}
.playcap{font-size:16px;color:var(--ink-2);margin-top:10px;min-height:2.6em;
  line-height:1.5;-webkit-user-select:none;user-select:none;}
.playcap .line{font-family:var(--syms);color:var(--ink);}
/* WHICH LINE THE BOARD IS PLAYING — `.seclab`'s twin, deliberately, because
   it names the same object and must not read as a second kind of label. */
.playcap .eyebrow{font-family:var(--sans);font-size:12px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3);font-weight:600;line-height:1.4;}
.playcap .quizq{font-family:var(--serif);font-size:18px;font-weight:600;
  color:var(--ink);margin-top:3px;line-height:1.4;}
.transport{display:inline-flex;gap:2px;background:var(--card2);
  border:1px solid var(--rule);border-radius:99px;padding:3px;margin-top:12px;
  align-items:center;}
.transport button{border:0;background:none;width:36px;height:28px;
  border-radius:99px;color:var(--ink-2);cursor:pointer;
  transition:background var(--fast),transform var(--fast);}
.transport button:hover{background:var(--card);}
.transport button:active{transform:scale(.92);}
/* `.fctr` — the frame counter — was retired on 2026-08-27 and the rule went
   with it. It read `7 / 41`: one index across all nine chapters of a reveal,
   running backwards on the second leg and naming none of them. The caption
   above the board now says the chapter and the place in it, which is the same
   two numbers with the name attached — and the pill had already dropped this
   on a phone in 2026-08-24, for exactly the reason that now retires it
   everywhere: "the line above is saying it better". */
.ctarow{display:flex;gap:12px;align-items:center;margin-top:12px;
  flex-wrap:wrap;}

/* ---- analysis entry ---- */
/* No ruled-paper lines. They imitated a scoresheet, but a scoresheet records
   moves that were played and this records analysis that was not — and the
   rules cut through the variation brackets and the evaluation symbols, which
   are the things worth reading. line-height stays at 33px: it is sized for
   the click target on a move token, not for the ruling that used to be here. */
.sheet{background:var(--card2);border:1px solid var(--rule);border-radius:10px;
  padding:12px 15px;margin-top:10px;}
.line{font-family:var(--syms);font-size:19px;line-height:33px;
  font-variant-numeric:tabular-nums;}
.sheet .line{min-height:33px;}
.sheet .line:empty::before{content:"click two squares to enter a move";
  color:var(--ink-3);font-family:var(--sans);font-size:13px;font-style:italic;}
.line b{font-weight:700;}
.line .mv{font-weight:700;}
/* The game's line is quoted material, not the solution: same type, one step
   back in the ink so the eye never mistakes it for the answer above it. It
   carries no heading — it runs on from the solution the way a book does — so
   `glead` is what tells you whose moves these are. */
.line.gline{margin-top:.1em;text-indent:1.4em;}
.line.gline,.line.gline b,.line.gline .mv{font-weight:400;color:var(--ink-2);}
.line .glead{font-family:var(--sans);font-size:12.5px;color:var(--ink-3);
  font-style:italic;margin-right:.3em;}
.line .gtail{display:block;text-indent:1.4em;font-family:var(--sans);
  font-size:12.5px;color:var(--ink-3);}
.line .gtail .lichess{margin-left:.7em;}
.line .var{color:var(--ink-2);}
.line .var b,.line .var .mv{font-weight:400;}
/* THE THREAT IS NOT THE NEXT MOVE. "△♕e8+" says what the key move plays if
   the defence does nothing — it is an annotation on the key move, not a
   continuation of the line — so in the main line's own bold it read as the
   solution's second move. Demoted exactly as a bracket is: roman, one step
   back in the ink. The two are never confused with each other because the
   bracket carries its delimiters and this carries △. */
.line .thr{color:var(--ink-2);}
.line .thr b,.line .thr .mv{font-weight:400;}
/* A WORD INSIDE A LINE OF MOVES. Yusupov's brackets carry two or three words
   that say what kind of branch this is — "not", "if … then", "is as good:" —
   and without a face of their own they read as notation. Same treatment as
   `.glead`, the site's only other prose inside a `.line`: the sans face, a
   step back in the ink, and never the figurine face, so the eye sorts words
   from moves before it reads either. It is NOT `.pt`: a word may not carry a
   price (solution/brackets.py). */
.line .pw{font-family:var(--sans);font-size:13px;color:var(--ink-3);
  font-style:italic;}
.line .sym{font-family:var(--syms);white-space:nowrap;}
.line .game{display:block;margin-top:.4em;color:var(--ink-2);}
.line .game b,.line .game .mv{font-weight:400;}
.tok{cursor:pointer;border-radius:3px;
  transition:background var(--fast),box-shadow var(--fast);}
.tok:hover{background:var(--card);}
/* TWO THINGS WEAR THE ACCENT ON A MOVE AND THEY ARE NOT THE SAME THING
   (2026-08-27, from a solver). `.cur` is "you are annotating this move" and
   `.now` is "the board is on this move"; both were the identical 2.5px
   underline, and `.now`'s only extra was `background:var(--card)` — which on
   the card IS what `:hover` paints, so on the printed solution the playhead
   was no mark at all. One channel, three meanings, on the exact element a
   solver reported being lost in. doc/design.md §13 says what to do about it:
   change its SHAPE, not its hue. So the playhead fills and the cursor stays
   a rule. A WASH OF THE ACCENT rather than a card colour, because the
   solver's own line sits on --card2 now and a --card chip would vanish into
   it; the flat declaration in front is the fallback where color-mix is not
   understood. */
.tok.cur{box-shadow:0 2.5px 0 var(--accent);}
.tok.now{box-shadow:0 2.5px 0 var(--accent);background:var(--card2);
  background:color-mix(in srgb,var(--accent) 14%,transparent);
  padding:1px 3px;margin:0 -1px;}
.tok.start{color:var(--ink-3);}
/* ---- the solver's own annotation -------------------------------------
   A GLYPH IS NOT A VERDICT. The grader's mark is glued to the move by
   inf._tok, in the move's own weight, exactly as Informant sets it; this is
   the solver's opinion sitting after it, and it has to be visibly a different
   voice without becoming a second scoring language. Lighter weight, muted
   ink, never --accent and never --ch-bad: those two mean "the brand" and
   "you got this wrong", and taste is neither. */
.line .myg{font-family:var(--syms);color:var(--ink-3);font-weight:400;
  margin-left:1px;cursor:help;}
/* THE NOTE SITS IN THE LINE, not in a column beside it. display:block inside
   the run-in means the moves stop, the note is set under them, and the moves
   resume — a printed annotated game, which is the thing this is imitating.
   Sans-serif at a smaller size so prose never competes with notation.

   DURING and AFTER, and the difference is the whole feature: one is evidence
   of what was seen with the answer hidden, the other is a lesson written
   once it was not. Carried by ink weight, a rail and a word — never by
   colour alone, which would be unreadable to some solvers and would have to
   borrow a hue that already means something else here. */
.line .nt{display:block;font-family:var(--sans);font-size:13.5px;
  line-height:1.55;margin:5px 0 5px 4px;padding:1px 0 1px 9px;
  border-left:2px solid var(--rule);cursor:pointer;}
.line .nt.dur{border-left-style:solid;color:var(--ink);}
.line .nt.aft{border-left-style:dashed;color:var(--ink-2);}
.line .nt:hover{border-left-color:var(--ink-3);}
.line .nt .ntw{font-size:10.5px;letter-spacing:.05em;color:var(--ink-3);
  margin-right:7px;white-space:nowrap;}
/* the note inside a bracket is a note on a sideline: quieter still */
.line .var .nt{font-size:13px;opacity:.92;}
/* THE HEADNOTE — what you saw looking at the diagram, before a move existed
   to hang anything on. It sits above the line rather than in it, because
   that is what it is about, and most of the calculation happens here. */
.line .nt.root{margin:0 0 10px 0;padding-left:11px;border-left-width:3px;}
/* THE ANNOTATION PANEL, and it is always open. It saves on Enter and on the
   way out, so nothing a solver typed is ever lost to a stray click. */
/* Inside the sheet now, not a second card beside it: a hairline instead of
   a border, and the sheet's own ground instead of its own. */
.anno{margin-top:11px;padding-top:10px;border-top:1px solid var(--rule);
  transition:opacity var(--fast);}
/* No move selected yet: still there, still readable, plainly not live —
   rather than vanishing and reappearing, which reads as a bug. */
.anno.idle{opacity:.5;pointer-events:none;}
.arow{display:flex;gap:8px;align-items:center;flex-wrap:wrap;
  margin-bottom:7px;}
.arow .alab{font-size:11.5px;color:var(--ink-3);min-width:5.6em;
  cursor:help;letter-spacing:.02em;}
.arow .abtns{display:inline-flex;gap:4px;flex-wrap:wrap;flex:1;}
/* Which row costs a point and which does not, said on the row itself. This
   is the one thing about the panel a solver must not have to guess. */
.arow .apay{font-size:11px;color:var(--ink-3);white-space:nowrap;}
.arow .apay.pays{color:var(--ink-2);font-weight:600;}
/* A LINE TO WRITE ON, NOT A FIELD TO FILL IN. The border and the fill are
   gone: what is left is the caret, the text, and a rule under it. */
/* THE WRITER IN THE LINE. It borrows .nt's geometry exactly — same rail,
   same indent, same type — so the note you are writing sits where the note
   you have written will sit, and the line does not jump when you finish.
   Parked (not placed in a line) it takes no space at all. */
.nwrite{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;
  font-family:var(--sans);font-size:13.5px;line-height:1.55;
  margin:5px 0 5px 4px;padding:1px 0 1px 9px;
  border-left:2px solid var(--accent);cursor:text;}
.nwrite.parked{display:none;}
.line .nwrite.root{margin:0 0 10px 0;padding-left:11px;border-left-width:3px;}
/* The note being written is the one node whose rendered copy is suppressed:
   the input already holds that text, and two of them is the duplication
   this whole arrangement exists to remove. */
.line .nt.editing{display:none;}
/* flex-basis 12em, so a narrow slot drops the field to its own row
   rather than clipping the placeholder mid-word.
   16px IS A FLOOR, NOT A PREFERENCE — see the note above `.crow input`. */
.nwrite input{flex:1 1 12em;min-width:0;font:inherit;font-size:16px;padding:0;
  border:0;background:none;color:var(--ink);}
.nwrite input:focus{outline:none;}
.nwrite input::placeholder{color:var(--ink-3);font-style:italic;}
/* The invitation. Blinks only when the field is empty and unfocused —
   two carets on one line would read as a rendering fault. */
.ncaret{flex:0 0 auto;width:2px;height:1.15em;background:var(--ink);
  border-radius:1px;animation:ncar 1.15s steps(1, end) infinite;}
.ncaret.off{display:none;}
@keyframes ncar{50%{opacity:0;}}
/* the eval palette lives in the panel now, not in a popover off the toolbar */
.anno .evb{font-size:14px;padding:3px 8px;}
.anno .evb.on{border-color:var(--ink);color:var(--ink);font-weight:700;}
.gb{font-family:var(--syms);font-size:14px;padding:3px 8px;
  background:var(--card);border:1px solid var(--rule);border-radius:8px;
  color:var(--ink-2);cursor:pointer;
  transition:border-color var(--fast),transform var(--fast);}
.gb:hover{border-color:var(--ink-3);transform:translateY(-1px);}
.gb.on{border-color:var(--ink);color:var(--ink);font-weight:700;}
.nbcap{font-size:12.5px;color:var(--ink-3);margin-top:6px;min-height:1.2em;}
.pt{font-family:var(--sans);font-size:11px;font-weight:700;line-height:1;
  color:var(--on-good);background:var(--ch-good);border-radius:4px;
  padding:2.5px 5px;margin-left:3px;vertical-align:6px;cursor:help;}
.pt.off{background:none;border:1px solid var(--ch-bad);color:var(--ch-bad);}
/* a PRICE on the printed solution, never a receipt: dashed and unfilled so it
   cannot be read as something you were awarded */
.pt.tag{background:none;border:1px dashed var(--ink-3);color:var(--ink-3);
  font-weight:600;}
.pt.tag.muted{border-style:dotted;opacity:.65;}
/* past the fair stop: shown, never demanded, never charged */
.chapters .line .tok.past{opacity:.5;}
.toolbar,.controls{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap;
  align-items:center;-webkit-user-select:none;user-select:none;}
/* Both a <button> and an <a> wear this: the landing page's calls to action
   are real links now, because a stranger's first move is to solve rather
   than to sign up, and a link is what survives JS being off. */
.ctl{padding:8px 16px;font-size:14.5px;font-weight:600;border:0;
  border-radius:9px;background:var(--accent);color:var(--on-accent);
  cursor:pointer;
  display:inline-block;text-align:center;text-decoration:none;
  transition:transform var(--fast),filter var(--fast),border-color var(--fast);}
.ctl:hover{transform:translateY(-1px);filter:brightness(1.08);}
.ctl:active{transform:translateY(0) scale(.97);}
.ctl.sec{background:var(--card);border:1px solid var(--rule);
  color:var(--ink-2);}
.ctl.sec:hover{border-color:var(--ink-3);filter:none;}
.ctl:disabled{opacity:.35;cursor:default;transform:none;filter:none;}

/* ---- no account ------------------------------------------------------
   The doors that need a profile row are ABSENT rather than disabled. A
   greyed-out control a stranger cannot use is an advertisement for a wall;
   a missing one is simply a shorter page — and the account is offered once,
   after the verdict, where it has just been earned. */
.shell.stranger .wkslot,
.shell.stranger #keepgo,
.shell.stranger #ratingline,
.shell.stranger #todayline,
.shell.stranger #navdiary,
.shell.stranger #navacct,
.shell.stranger #navset,
.shell.stranger #next{display:none !important;}
.card.offer{border-color:var(--accent);}
.offer h3{margin:.2em 0 .4em;font-size:1.15rem;}
.offer p{margin:0 0 14px;color:var(--ink-2);max-width:54ch;}
.offerrow{display:flex;gap:10px;flex-wrap:wrap;}
.offernote{margin-top:12px;font-size:12.5px;color:var(--ink-3);}
/* THE INLINE JOIN. Its own copy of the gate's controls rather than a shared
   class: _GATE_CSS belongs to the landing and login shells and is not loaded
   here, and importing it for four rules would drag a whole page's styling
   into the app. */
.offer .ofrow{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;}
.offer .ofrow input{flex:1 1 220px;font:inherit;font-size:1rem;
  padding:.55rem .7rem;border:1px solid var(--rule);border-radius:8px;
  background:var(--paper);color:var(--ink);min-width:0;}
.offer .ofrow input:focus{outline:2px solid var(--accent);outline-offset:1px;}
.offer .ck{display:flex;gap:9px;align-items:flex-start;text-align:left;
  font-size:13px;line-height:1.5;color:var(--ink-2);margin:0 0 9px;
  max-width:54ch;cursor:pointer;}
.offer .ck input{margin-top:2px;flex:none;accent-color:var(--accent);}
.offer .ck a{color:var(--ink);}
/* the error line is the one place --ch-bad belongs on this card; the rest of
   it is brand chrome and must never borrow the error colour */
.offer .oerr{color:var(--ch-bad);font-size:.85rem;min-height:1.2em;}
.offer .lnk{background:none;border:0;font:inherit;font-size:.8rem;
  color:var(--ink-3);text-decoration:underline;cursor:pointer;padding:0;}
.offer .lnk:hover{color:var(--ink);}
.offer .osent{font-size:.95rem;line-height:1.6;color:var(--ink-2);}
.offer .osent b{color:var(--ink);}
/* signin mode: the same form, minus the two things only a signup needs */
.offer #ojoin.signin .ck{display:none;}
/* The confirmation receipt. A left rule in the accent rather than a box:
   the solver has what they came for and this is a note about what is still
   outstanding, not another thing being asked of them. */
.confirm{margin:14px 0 0;padding:10px 0 10px 14px;
  border-left:3px solid var(--accent);font-size:.9rem;line-height:1.6;
  color:var(--ink-2);max-width:60ch;}
.confirm b{color:var(--ink);}
.ctl.commit{margin-left:auto;font-family:var(--serif);letter-spacing:.02em;
  padding:9px 22px;}
/* 1-5 stars. The star row is the accent's job: it is the site asking for
   something, not an error state, so --ch-bad never appears here. Unfilled
   stars are outlines rather than absent, so the row reads as a five-point
   scale before it is touched and the target is the same size either way. */
.rate .rrow{display:flex;gap:12px;align-items:center;flex-wrap:wrap;
  margin-top:4px;}
.rstars{display:inline-flex;gap:2px;-webkit-user-select:none;user-select:none;}
.rstars .st{font-family:var(--syms);font-size:25px;line-height:1;
  padding:2px 3px;border:0;background:none;cursor:pointer;color:var(--ink-3);
  opacity:.45;transition:color var(--fast),opacity var(--fast),
  transform var(--fast);}
.rstars .st:hover{transform:translateY(-2px);}
.rstars .st.on{color:var(--accent);opacity:1;}
.rstars .st:focus-visible{outline:2px solid var(--accent);outline-offset:2px;
  border-radius:5px;}
/* while nothing is cast, a slow breath on the row — the one place the page
   asks for something back. It stops the moment a star is cast, and honours
   a solver who has asked the OS for less movement. */
.rate.unrated .rstars .st{animation:rpulse 3.4s ease-in-out infinite;}
.rate.unrated .rstars .st:nth-child(2){animation-delay:.09s;}
.rate.unrated .rstars .st:nth-child(3){animation-delay:.18s;}
.rate.unrated .rstars .st:nth-child(4){animation-delay:.27s;}
.rate.unrated .rstars .st:nth-child(5){animation-delay:.36s;}
@keyframes rpulse{0%,72%,100%{opacity:.45;}82%{opacity:.85;}}
@media (prefers-reduced-motion: reduce){
  .rate.unrated .rstars .st{animation:none;}
  .rstars .st:hover{transform:none;}
}
.rmeta{font-size:13px;color:var(--ink-3);}
.rmeta b{color:var(--ink-2);font-variant-numeric:tabular-nums;}
/* the nudge: why bothering is worth it, in one line, never twice */
.rwhy{font-size:12.5px;line-height:1.5;color:var(--ink-3);margin-top:7px;}
.rwhy:empty{display:none;}
.rwhy b{color:var(--ink-2);}
.ctl kbd{font-family:var(--sans);font-size:.8em;opacity:.6;font-weight:400;}
.asx{display:inline-flex;gap:5px;align-items:center;flex-wrap:wrap;
  width:100%;padding-top:6px;}
.evb{font-family:var(--syms);font-size:15px;padding:4px 9px;
  background:var(--card);border:1px solid var(--rule);border-radius:8px;
  color:var(--ink);cursor:pointer;
  transition:border-color var(--fast),transform var(--fast);}
.evb:hover{border-color:var(--ink-3);transform:translateY(-1px);}
.evb:active{transform:scale(.95);}
.promo{display:flex;gap:8px;margin-top:10px;align-items:center;
  font-size:13px;color:var(--ink-2);}
.promo button{font-family:var(--syms);font-size:18px;padding:3px 10px;
  background:var(--card);border:1px solid var(--rule);border-radius:8px;
  color:var(--ink);cursor:pointer;transition:border-color var(--fast);}
.promo button:hover{border-color:var(--ink-3);}
.hint{font-size:15px;color:var(--ink-3);margin-top:9px;line-height:1.55;}
/* The one-time note under the sheet. It is a hint and reads like the rest
   of them; the rule beneath ties it to the line it is explaining, and the
   size stays at 15px because this is prose a beginner is reading, not a
   caption. No caret goes in it, so the 16px iOS floor does not apply. */
.frozehint{margin:8px 0 0;padding-top:8px;border-top:1px solid var(--rule);
  font-size:14.5px;}
/* The glyph inside a labelled control: the mark you learn the button by,
   set in the symbol face and a shade quieter than its own word. */
.ctl .bg{font-family:var(--syms);font-weight:400;margin-right:5px;
  opacity:.75;}
/* ---- the help panel ----
   It was one 250-word paragraph, open by default, carrying nine unrelated
   rules in a single breath — so it filled the screen under the board and
   was unreadable in exactly the moment somebody needed it. Now it is shut
   until asked for, and inside it the rules are grouped under headings and
   set as a list, because that is what a reference gets read as. Nothing was
   cut: every rule the paragraph carried is still here, findable. */
.hint h4{font-family:var(--sans);font-size:11.5px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--ink-2);font-weight:700;
  margin:14px 0 5px;}
.hint h4:first-child{margin-top:2px;}
.hint p{margin:0 0 2px;}
.hint p:last-child{margin-top:12px;font-size:14px;}
.hint ul{margin:0;padding-left:17px;}
.hint li{margin:3.5px 0;}
.hint b{color:var(--ink-2);font-weight:600;}
.hint .k{font-family:var(--syms);color:var(--ink-2);}
.flash{color:var(--accent);font-size:15px;min-height:1.3em;margin-top:8px;}
.flash.busy{animation:pulse 1.2s ease-in-out infinite;}
@keyframes pulse{50%{opacity:.45}}
details.fold{margin-top:10px;}
details.fold summary{font-size:12px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3);cursor:pointer;font-weight:600;
  transition:color var(--fast);}
details.fold summary:hover{color:var(--ink);}
details.fold[open] summary{margin-bottom:6px;}

/* ---- verdict + analysis sheet ---- */
.report .sol{background:var(--card);border:1px solid var(--rule);
  border-radius:14px;padding:18px 20px;}
.report h3{font-family:var(--serif);font-size:19px;font-weight:600;
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 4px;}
.scorestamp{display:inline-block;font-family:var(--serif);font-weight:700;
  color:var(--accent);border:2.5px solid var(--accent);border-radius:7px;
  padding:2px 12px;transform:rotate(-2deg);font-size:19px;
  font-variant-numeric:tabular-nums;
  box-shadow:inset 0 1px 3px rgba(0,0,0,.16);
  animation:stampIn .38s cubic-bezier(.2,1.4,.4,1) both;}
@keyframes stampIn{0%{opacity:0;transform:scale(1.6) rotate(-7deg)}
  70%{opacity:1;transform:scale(.95) rotate(-1deg)}
  100%{transform:scale(1) rotate(-2deg)}}
.report h3.cleanline{position:relative;padding-bottom:6px;}
.report h3.cleanline::after{content:"";position:absolute;left:0;bottom:0;
  height:2.5px;background:var(--accent);
  animation:drawRule .7s ease-out .35s both;}
@keyframes drawRule{from{width:0}to{width:100%}}
@media (prefers-reduced-motion:reduce){
  .scorestamp,.report h3.cleanline::after{animation:none;opacity:1;}
  .chapters .ply.lit{animation:none;}   /* outranks the rule below it */
  .selmark,.hovmark,.ctl,.evb,.mi,.brandbtn{transition:none;}
  .selmark.pop,.flashmark.go{animation:none;}
  /* the caret stays — it is the affordance, not decoration — but it stops
     blinking and just sits there */
  .ncaret{animation:none;}
  .flashmark.bad{animation:none;display:none;}}
.classnote{font-family:var(--serif);font-style:italic;color:var(--ink-2);
  font-size:15px;margin:2px 0 6px;line-height:1.6;}
.sealline{font-size:12px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink-3);font-variant-numeric:tabular-nums;}
.sealline .fplus{color:var(--ch-good);font-weight:700;}
.sealline .fminus{color:var(--ch-bad);font-weight:700;}
.chapters{background:var(--card);border:1px solid var(--rule);
  border-radius:14px;padding:18px 20px;}
.seclab{font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);font-weight:600;margin:12px 0 5px;}
.seclab:first-child{margin-top:0;}
/* THE HEADING HOLDS WHILE ITS LINE IS BEING READ. These two words are the
   only thing on the card that says which line you are looking at, and they
   scrolled away — on a phone inside `.chapters`' own scroller during the
   route, and up the document after it. Sticky, scoped to this box so the
   site's other eyebrows are untouched, and on --card because that is the
   ground it lifts off. */
.chapters .seclab{position:sticky;top:0;z-index:2;background:var(--card);
  padding-bottom:3px;}
/* AND THE SOLVER'S OWN LINE KEEPS ITS PLATE. `.myline` had no rule anywhere
   in this file: the printed solution and the analysis were one face, one
   size, one ink, on one ground, and the two `.seclab`s above were the whole
   of the distinction. This is not a new device — it is `.sheet`, the recessed
   plate the line was written on before the commit, which the line simply
   stopped wearing when it moved into this box. The same object keeps its own
   surface. `.critline` is a SIBLING of `.myline`, not a child, so the
   critical test stays outside it: that is the position's material, not the
   solver's. */
.chapters .myline{background:var(--card2);border:1px solid var(--rule);
  border-radius:10px;padding:10px 14px;}
.chapters .line{font-size:17px;line-height:1.75;}
/* THE LINE WRITES ITSELF (2026-08-22). Each printed ply is a `.ply`
   carrying the frame that produces it; while the reveal is building, a
   ply the board has not reached is `hidden` — the line GROWS, which is the
   reward — and the one just written settles with the site's one duration
   and curve: it genuinely moved, from unwritten to written. A STOP in the
   reveal holds the next ply back and puts a blank in its place: the
   tally's own slot, an underlined space, which the move fills when the
   reveal resumes. It turns `--accent` for the moment the answer was right,
   and never `--ch-bad` — a wrong recall is a sentence in the caption, not
   an error state on the line. Everything after the solution — the game's
   line, the analysis, the tally, the controls — is held (`hidden`, marked
   `data-held`) until the verdict lands, because every one of them can
   name the answer. */
.ply.lit{animation:plyIn var(--fast) both;}
@keyframes plyIn{from{opacity:0;transform:translateY(2px)}
  to{opacity:1;transform:none}}
.chapters .line .slot{display:inline-block;min-width:2.2em;height:1em;
  border-bottom:2px solid var(--ink);vertical-align:-2px;margin:0 .15em;
  transition:border-color var(--fast);}
.chapters .line .slot.got{border-color:var(--accent);}
.playcap .quizq .slot{display:inline-block;min-width:2.2em;height:.9em;
  border-bottom:2px solid var(--ink);vertical-align:-2px;margin-left:.2em;}
.playcap .quizq .slot.got{border-color:var(--accent);}
.chapters .tally{display:flex;justify-content:flex-end;align-items:baseline;
  gap:8px;margin-top:10px;padding-top:8px;border-top:1px solid var(--rule);
  font-size:12px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-3);}
.chapters .tally .slot{display:inline-block;min-width:2.2em;
  border-bottom:2px solid var(--ink);text-align:center;font-weight:700;
  color:var(--ink);font-size:16px;letter-spacing:0;}
.chapters .tally .of{font-variant-numeric:tabular-nums;font-weight:700;
  color:var(--ink);letter-spacing:0;}
.boxmeta{font-size:12px;color:var(--ink-3);margin-top:10px;padding-top:8px;
  border-top:1px solid var(--rule);line-height:1.55;}
/* The field's line runs on from the category and the links rather than
   opening a second rule under them: it is a footnote to the sheet, not a
   section of it. Its <b> is the only emphasis, which is the number. */
.fieldline{border-top:0;margin-top:2px;padding-top:0;}
.fieldline b{color:var(--ink-2);font-variant-numeric:tabular-nums;}
.chip.mile{border-color:var(--accent);color:var(--accent);font-weight:700;
  animation:stampIn .38s cubic-bezier(.2,1.4,.4,1) both;}
/* One small uppercase link, two names. `.lichess` is older and is on the
   game citations all over the site; `.boxlink` is what the box foot's own
   links use, because a PGN download wearing a class called "lichess" is a
   name that has stopped being true. */
.lichess,.boxlink{font-size:12px;letter-spacing:.05em;text-transform:uppercase;
  font-weight:700;color:var(--accent);text-decoration:none;}
.lichess:hover,.boxlink:hover{text-decoration:underline;}
.critline{margin:8px 0 2px;}
.critline .line{display:inline;}
.critlab{display:inline;margin-right:.6em;font-size:12px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3);font-weight:600;}
.evok{margin-left:.12em;font-size:.8em;color:var(--ch-good);}
.evx{margin:0 .15em;color:var(--ch-bad);font-weight:700;}
.done{font-family:var(--serif);font-size:17px;margin-top:24px;}

/* ---- the training diary ---- */
.focus{font-family:var(--serif);font-style:italic;color:var(--ink-2);
  font-size:16px;margin:4px 0 10px;}
.ldg{max-width:520px;}
.ltile{display:flex;gap:28px;align-items:baseline;margin-bottom:10px;}
.ltile .big{font-size:24px;font-weight:700;font-variant-numeric:tabular-nums;}
.ltile .big.up{color:var(--ch-good);}
.ltile .big.dn{color:var(--ch-bad);}
.ltile .tlab{font-size:12px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink-3);}
.lchart{margin:6px 0 12px;position:relative;}
.lchart svg{display:block;width:100%;height:auto;}
.lchart .tip{position:absolute;pointer-events:none;background:var(--card);
  border:1px solid var(--rule);border-radius:6px;padding:2px 8px;
  font-size:12px;font-variant-numeric:tabular-nums;display:none;}
.brow{display:flex;align-items:center;gap:8px;margin:4px 0;font-size:13px;}
.brow .bl{flex:0 0 11em;color:var(--ink-2);text-align:right;}
.brow .btrack{flex:1;height:12px;background:var(--card2);border-radius:4px;
  position:relative;overflow:hidden;}
.brow .bfill{position:absolute;left:0;top:0;bottom:0;
  background:var(--ch-bar);border-radius:4px;
  transition:width .5s cubic-bezier(.3,.7,.3,1);}
.brow:hover .bfill{filter:brightness(1.15);}
.brow .bv{flex:0 0 3em;font-weight:700;font-variant-numeric:tabular-nums;}
.stk{display:flex;gap:2px;height:24px;margin:4px 0 2px;max-width:360px;}
.stk span{display:flex;align-items:center;justify-content:center;
  color:var(--on-good);font-size:12px;font-weight:700;border-radius:4px;
  min-width:2em;
  transition:filter var(--fast);}
.stk span:hover{filter:brightness(1.12);}
.stk .s1{background:var(--ch-good);}
.stk .s2{background:var(--ch-mid);color:var(--ink);}
.stk .s3{background:var(--ch-bad);color:var(--on-bad);}
.lsec{font-size:12px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink-3);font-weight:600;margin:12px 0 4px;}
.diarytab{border-collapse:collapse;font-size:13px;min-width:240px;
  margin-top:6px;}
.diarytab td{padding:2px 0;}
.diarytab td:last-child{text-align:right;font-variant-numeric:tabular-nums;
  font-weight:700;padding-left:1.2em;}

/* ---- colophon ---- */
.overlay{position:fixed;inset:0;z-index:60;display:flex;align-items:center;
  justify-content:center;
  background:color-mix(in srgb,var(--ink) 26%,transparent);}
.setcard{width:340px;}
.introcard{width:400px;max-width:92vw;}
.introcard h2{font-family:var(--serif);font-size:22px;margin:4px 0 10px;}
.introcard p{font-size:16px;color:var(--ink-2);line-height:1.55;
  margin:8px 0;}
.ikicker{font-size:12px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--accent);}
.iboard{margin:12px auto;max-width:250px;}
.iboard svg{width:100%;height:auto;display:block;border:1px solid
  var(--rule);border-radius:4px;}
.isheet{font-family:var(--syms);background:var(--tint);border:1px solid
  var(--rule);border-radius:8px;padding:9px 12px;color:var(--ink);}
.isheet span{color:var(--ink-3);}
.inav{display:flex;align-items:center;justify-content:space-between;
  margin-top:16px;}
.idots{display:flex;gap:6px;}
.idots span{width:6px;height:6px;border-radius:50%;background:var(--rule);}
.idots span.on{background:var(--accent);}
/* intro guided demo */
.introcard{max-height:92vh;overflow-y:auto;}
.iwrap{position:relative;margin:12px auto;max-width:250px;}
.iwrap .iboard{margin:0;max-width:none;}
.igrid{position:absolute;inset:3.846%;display:grid;
  grid-template:repeat(8,1fr)/repeat(8,1fr);}
.icell{cursor:pointer;}
.icell.hot{outline:3px solid var(--accent);outline-offset:-3px;
  border-radius:6px;animation:ipulse 1.1s ease-in-out infinite;}
@keyframes ipulse{0%,100%{opacity:.35}50%{opacity:1}}
.idsheet{min-height:1.5em;}
.idsheet .ibr{cursor:pointer;border-bottom:2px solid var(--accent);
  animation:ipulse 1.1s ease-in-out infinite;}
.ihint{min-height:1.5em;}
.ityper::after,.ityping::after{content:'▍';color:var(--accent);
  animation:iblink 1s steps(1) infinite;}
@keyframes iblink{50%{opacity:0}}
.ishake{animation:ishake .3s;}
@keyframes ishake{25%{transform:translateX(-4px)}75%{transform:translateX(4px)}}
#isealbox{text-align:center;margin:14px 0;}
.ivrow{opacity:0;transform:translateY(6px);
  transition:opacity .45s,transform .45s;
  font-size:15.5px;line-height:1.5;padding:7px 10px;border-radius:8px;
  margin:6px 0;border:1px solid var(--rule);color:var(--ink-2);}
.ivrow b,.ivrow i{color:var(--ink);}
.ivrow.show{opacity:1;transform:none;}
.ivrow.good{background:color-mix(in srgb,var(--ch-good) 10%,transparent);}
.ivrow.info{background:var(--tint);}
.evcap{display:block;font-size:13.5px;color:var(--ink-3);margin-top:4px;
  min-height:1.2em;}
.symteach{color:var(--ink-3);font-size:.88em;margin:0 .25em 0 .12em;}
/* A PREFIX SYMBOL GLOSSES ROUND ITS MOVE, so the gloss arrives in two
   halves and the inner edges must not carry the standing gap:
   "△(with the idea ♕e8+)", never "△(with the idea ♕e8+ )". */
.symteach.op{margin-right:0;}
.symteach.cl{margin-left:0;}
.swrow{display:flex;gap:8px;margin-top:10px;align-items:center;}
.sw{width:30px;height:30px;border-radius:8px;border:1px solid var(--rule);
  cursor:pointer;transition:transform var(--fast),border-color var(--fast);}
.sw:hover{transform:translateY(-1px);border-color:var(--ink-3);}
.swsep{width:1px;height:22px;background:var(--rule);margin:0 2px;}
.swrow input[type=color]{width:34px;height:30px;border:1px solid var(--rule);
  border-radius:8px;background:var(--card2);padding:2px;cursor:pointer;}
.setfoot{display:flex;justify-content:flex-end;margin-top:18px;}
/* the one setting that needs a sentence: it changes what you are dealt,
   not how it looks, so the panel says what will happen and what is waiting */
.sethint{font-size:12.5px;line-height:1.5;color:var(--ink-3);margin-top:7px;}
.sethint b{color:var(--ink-2);}
.seg.segwrap{flex-wrap:wrap;max-width:100%;}
.crow{display:flex;gap:8px;margin-top:10px;}
/* NO INPUT ON THIS SITE MAY BE SMALLER THAN 16px, AND IT IS NOT A TYPE
   DECISION. Mobile Safari ZOOMS THE WHOLE PAGE when a field with a computed
   font-size under 16px takes focus, and it does not zoom back out. On the
   solve pane below (`body.pane`) that is fatal: the layout is locked to one
   viewport, and a zoom that survives the blur leaves the board cropped and
   the toolbar off-screen for the rest of the session. 14.5px bought nothing
   — this text sits beside a 19px chess line either way.
   `selftest.ios_never_zooms_an_input` fails the run if one creeps back. */
/* `min-width:0` FOR THE REASON `.caphead` HAS ONE. A flex item may not go
   under its min-content width unless it is told it may, and an <input>'s
   min-content width is its `size` — twenty characters it has never been
   asked for. So this row demanded about 356px, wider than the card on
   every phone, and at the verdict it widened the whole document rather
   than the field giving way. */
.crow input{flex:1;min-width:0;font:inherit;font-size:16px;padding:8px 12px;
  border:1px solid var(--rule);border-radius:9px;background:var(--card2);
  color:var(--ink);}
.cmt{display:flex;gap:10px;align-items:baseline;padding:7px 0;
  border-bottom:1px solid var(--rule);font-size:15px;}
.cmt:last-child{border-bottom:0;}
.cmt .who{font-family:var(--serif);font-weight:600;
  font-variant-caps:small-caps;white-space:nowrap;}
.cmt .when{font-size:12px;color:var(--ink-3);white-space:nowrap;}
.cmt .txt{flex:1;color:var(--ink-2);line-height:1.5;}
.cmt .cv{display:flex;gap:2px;align-items:center;white-space:nowrap;}
.cmt .cv button{border:0;background:none;color:var(--ink-3);cursor:pointer;
  font-size:12px;padding:2px 4px;border-radius:4px;
  transition:color var(--fast),transform var(--fast);}
.cmt .cv button:hover{color:var(--ink);transform:translateY(-1px);}
.cmt .cv button.on{color:var(--accent);}
.cmt .cv .cflag{font-size:12px;opacity:.55;}
.cmt .cv .cflag:hover{opacity:1;}
.cmt .cv .sc{font-weight:700;font-variant-numeric:tabular-nums;
  min-width:1.4em;text-align:center;font-size:12.5px;}
/* A shared line needs a second row under the byline, so the row wraps rather
   than the line being squeezed into the flex column beside the vote arrows. */
.cmt.hasline{flex-wrap:wrap;}
.cmt .shared{flex-basis:100%;margin-top:6px;}
.cmt .shared .line{font-size:17px;line-height:30px;}
/* THE RECEIPT, and it is why nothing here is re-graded on read: this line was
   scored once, when its author committed it, and that number came along with
   the copy. Grading is serial behind one engine lock — scoring shared lines
   on read would put every reader in that queue. */
.crec{font-family:var(--sans);font-size:11px;font-weight:700;line-height:1;
  color:var(--ink-2);background:var(--tint);border:1px solid var(--rule);
  border-radius:4px;padding:2.5px 5px;white-space:nowrap;cursor:help;}
/* THE AUTHOR, beside what their line scored. Same chip family as the receipt
   and deliberately one step quieter: the receipt prices the line, this only
   places the person who wrote it, and the line is the thing being read. It is
   frozen at share time like everything else on this row, and it is absent
   entirely rather than hedged when the deviation says the number is noise —
   see rating.byline. */
.crat{font-family:var(--sans);font-size:11px;font-weight:600;line-height:1;
  color:var(--ink-3);border:1px solid var(--rule);border-radius:4px;
  padding:2.5px 5px;white-space:nowrap;cursor:help;
  font-variant-numeric:tabular-nums;}
/* Name, rating and receipt is three chips on one 15px line; on a phone that
   wraps rather than being clipped to an ellipsis. */
.cmt .who{white-space:normal;}
/* The label row and its tagline: the admission rule, stated where the old
   word ("kibitzers") denied it. */
.labrow{display:flex;justify-content:space-between;align-items:baseline;
  gap:12px;flex-wrap:wrap;}
.roomtag{font-family:var(--serif);font-style:italic;font-size:12px;
  color:var(--ink-3);}
/* THE RANK MARK: the author's piece, served by /img/piece — a standing
   earned in this room (rank.py), never a decoration a member picked. The
   ghosted pawn is an anonymous share: the piece every solver starts as,
   belonging to nobody. White pieces on the chip for the same reason the
   route serves them: a black silhouette drowns in the dark theme. */
.cmt .pm{width:22px;height:22px;flex:none;align-self:center;
  background:var(--card2);border:1px solid var(--rule);border-radius:6px;
  padding:2px;}
.cmt .pm.ghost{opacity:.45;}
.cmt .who.anon{font-variant-caps:normal;font-style:italic;
  color:var(--ink-2);}
/* THE MEDAL IS A DISC BECAUSE □ IS NOT AVAILABLE — see page.medal for the
   full argument. Colour lives here rather than on the SVG because a
   fill="var(--accent)" presentation attribute does not resolve. Never
   --ch-bad: third place is not an error. */
.md{display:block;width:100%;height:100%;}
.md circle{stroke:currentColor;fill:none;}
.md text{font-family:var(--sans);font-weight:700;font-size:15px;
  fill:currentColor;}
.md1{color:var(--accent);}
.md1 circle{fill:currentColor;stroke:none;}
.md1 text{fill:var(--on-accent);}
.md2{color:var(--accent);}
.md3{color:var(--ink-3);}
.shelf{display:inline-flex;gap:8px;align-items:center;vertical-align:-2px;}
.shelf .mw{display:inline-flex;gap:3px;align-items:center;}
.shelf .mw i{display:inline-block;width:17px;height:17px;}
.shelf b{font-family:var(--sans);font-size:11px;font-weight:700;
  color:var(--ink-2);font-variant-numeric:tabular-nums;}
/* WHAT SOMEBODY HOLDS IS A REVEAL, NOT A PERMANENT ROW. The byline is the
   name and the rating; the shelf sits under the cursor so a decorated
   regular does not out-shout the line they wrote. Focusable so it opens
   from a keyboard and from a tap, which is the only way it works at all on
   a phone — :hover alone would make it desktop-only. */
.cmt .who.holds{cursor:help;text-decoration:underline dotted var(--ink-3);
  text-underline-offset:3px;}
.cmt .who.holds .shelf{display:none;margin-left:6px;}
.cmt .who.holds:hover .shelf,
.cmt .who.holds:focus-visible .shelf,
.cmt .who.holds:focus-within .shelf{display:inline-flex;}
/* On the account page it is the reader's own record, so it is always open
   and drawn at a size the numeral can actually be read at. */
.acct .shelf .mw i{width:28px;height:28px;}
.acct .shelf{gap:14px;}
.acct .shelf b{font-size:14px;}
/* The share control, under the line it shares. A tick-box beside the comment
   input was invisible: that is where you look to write a sentence, not where
   you look at the analysis you just finished marking up. */
.sharebar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;
  margin-top:12px;padding:10px 12px;background:var(--card2);
  border:1px solid var(--rule);border-radius:10px;}
.sharebar .shwhy{flex:1;min-width:12em;font-size:12.5px;line-height:1.5;
  color:var(--ink-3);}
.sharebar .shdone{font-size:12.5px;color:var(--ink-2);}
.sharebar button[disabled]{opacity:.5;cursor:default;}
/* "Publish as a solver" — the whole row is the label, so the click target
   is a sentence and not a 14px box. */
.sharebar .shanon{flex-basis:100%;display:flex;gap:7px;align-items:baseline;
  font-size:12.5px;color:var(--ink-2);cursor:pointer;}
.sharebar .shanon input{accent-color:var(--accent);}
.shhint{font-size:11.5px;color:var(--ink-3);margin-top:6px;}
.colo{margin-top:24px;border-top:1px solid var(--rule);padding-top:10px;
  display:flex;justify-content:space-between;gap:18px;align-items:baseline;
  flex-wrap:wrap;}
.colo details.fold{margin:0;flex:1 1 auto;}
.keys{display:grid;grid-template-columns:repeat(2,1fr);gap:3px 14px;
  font-size:14px;color:var(--ink-2);}
.keys span b{font-family:var(--syms);color:var(--ink);
  display:inline-block;min-width:2.2em;font-weight:400;}
.colophon{font-size:12px;color:var(--ink-3);letter-spacing:.1em;
  text-transform:uppercase;white-space:nowrap;}
/* A horizontal bottom bar spanning the whole shell. The grid-column span is
   left over from the two-column shell the rail used to create — harmless on
   today's single-column shell, and the rule that keeps this honest if a
   sibling column is ever reintroduced. */
.footbar{grid-column:1 / -1;border-top:1px solid var(--rule);
  padding:12px 16px calc(12px + env(safe-area-inset-bottom));
  text-align:center;white-space:normal;}
.colophon a{color:var(--ink-3);text-decoration:none;}
.colophon a:hover{color:var(--ink);text-decoration:underline;}
.timer{font-variant-numeric:tabular-nums;}
