/* 识字 — iOS-style treatment: system type, large title, inset grouped cards, hairline separators, filled buttons,
   translucent bars. Light and dark follow the phone. The seal red stays as the tint; big characters keep the serif. */
:root{
  /* v591 (H: "Bitte Cards und Camera dem Fotoformat der Lernkarten anpassen", then "In der cards Vorschau nur
     quadratische Foto thumbnails anzeigen"): ONE shape for every box that shows a photo small — the square the
     learning card's own photo area already is (the cue is as tall as the pad, v561/v580), with the picture FITTED
     inside it and the subdued blurred copy behind it (v232/v234). v595 brought the CUT to the same square
     (CARD_RATIO), so the two bands of blur beside a picture became the photo itself; v596 gave the last box that
     was still 3:2 — the page front of a card made from a multicard — this token as well, so every box that shows
     a photo small in this app reads --photo-ar and nothing else. */
  --photo-ar:1;
  --bg:#F2F2F7; --card:#FFFFFF; --card2:#F2F2F7; --fill:#E9E9EE;
  --label:#000000; --label2:#626268; --label3:#AEAEB2; --sep:rgba(60,60,67,.24);
  --tint:#C8372D; --tint-soft:rgba(200,55,45,.12); --tint-ink:#B52F26; --tint-fill:#C8372D; --ok:#2FA36B; --ok-soft:rgba(47,163,107,.12); --warn:#B5730B; --warn-soft:rgba(214,148,30,.14);
  --lock:#2F6BD6; --lock-soft:rgba(47,107,214,.12); /* v528: a locked character (§ 7) — its own colour, so red = in the pad, blue = locked, green = written never share a word */
  --bar:rgba(249,249,249,.86); --shadow:0 1px 2px rgba(0,0,0,.05), 0 6px 20px rgba(0,0,0,.05);
  --hanzi:'Songti SC','STSong','Noto Serif CJK SC','Source Han Serif SC','SimSun',serif;
  --sans:-apple-system,BlinkMacSystemFont,'SF Pro Text','PingFang SC','Helvetica Neue',Roboto,system-ui,'Noto Sans SC',sans-serif;
  --mono:ui-monospace,'SF Mono',Menlo,'Roboto Mono',Consolas,monospace;
  --r:12px; --rc:16px; --nav:64px;
  color-scheme:light dark;
}
@media (prefers-color-scheme:dark){
  :root{ --bg:#000000; --card:#1C1C1E; --card2:#2C2C2E; --fill:#3A3A3C;
    --label:#FFFFFF; --label2:#B3B3BA; --label3:#6E6E73; --sep:rgba(84,84,88,.6);
    --tint:#E0483E; --tint-soft:rgba(224,72,62,.18); --tint-ink:#FF8178; --tint-fill:#C8372D; /* v816 (H: "go"): text in the tint reads 5:1 on the soft fill and the card, white on a filled button 5.2:1 — #E0483E gave 3.5 and 4.1 */ --ok:#3DBE7A; --ok-soft:rgba(61,190,122,.16); --warn:#E8A93C; --warn-soft:rgba(232,169,60,.18);
    --lock:#6B9BF0; --lock-soft:rgba(107,155,240,.18);
    --bar:rgba(22,22,24,.82); --shadow:none; }
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;padding:0;height:100%}
/* the pull-to-refresh is off on the VIEWPORT'S scroller, which is html, not body (v415, H: "nach unten swipen hat manchmal einen
   refresh mit zurueckspringen auf die Learn Seite zur Folge"): overscroll-behavior is not propagated from body the way overflow is,
   so the rule below — on body since v82 — never reached the scroller and Chrome reloaded the page on a downward swipe at the top. */
html{background:var(--bg);overscroll-behavior-y:contain}
body{background:var(--bg);color:var(--label);font-family:var(--sans);font-size:17px;line-height:1.45;-webkit-font-smoothing:antialiased;touch-action:manipulation;overscroll-behavior-y:contain}
button{font:inherit;color:inherit}
/* the browser's own touch behaviours kept off the app (v249, H after the image menu of v248: "Find all such useless behaviours and remove them"): no long-press image menu, drag ghost or selection on any picture or canvas; no selection toolbar on buttons, tabs, chips, strips, tiles, the picker, the drawing sheet and the crop layer; no double-tap zoom anywhere (touch-action on body, the crop layer and the pad keep their own stricter values); the callout is Safari's, for the iPhone */
img,canvas{-webkit-touch-callout:none;-webkit-user-drag:none;user-select:none;-webkit-user-select:none}
button,.tab,.chip,.chipset,.pill,.grade,.grades,.ck,.cstrip,.ckpick,.ckhead,.seg,.drawsheet,.croplayer,.croprect,.reticle,.picbox,.thumbbox,.shotzoom,.linked,.badge,label,.topline,.mrow .t,.field>label{user-select:none;-webkit-user-select:none;-webkit-touch-callout:none} /* the pills too since v494 — a finger on the reference pill under the meaning raised the phone's selection toolbar and a Google search instead of doing anything (H); the meaning stayed selectable until v694 took every selection away. The badges and the forms' labels since v272 — a long press on the Edit badge opened the phone's dictionary card for "edit" (H) */
/* v694 (H, a screenshot of Chrome's Google search panel after tapping 京 on an open card: "Bitte nicht diese Google popups
   zulassen"): Chrome raises that panel on a tap or long press on any selectable text, and v249 had kept prose selectable so
   a meaning could be copied. No text is selectable now; only what you type in stays so */
html{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
input,textarea,[contenteditable="true"]{-webkit-user-select:text;user-select:text;-webkit-touch-callout:default}
:focus-visible{outline:2px solid var(--tint);outline-offset:2px}
#app{min-height:100%;display:flex;flex-direction:column}

/* top bar: large title, compact stat capsules */
#hdr{position:sticky;top:0;z-index:5;display:flex;justify-content:space-between;align-items:flex-end;gap:10px;
  padding:calc(8px + env(safe-area-inset-top)) 20px 8px;
  background:var(--bar);backdrop-filter:saturate(180%) blur(20px);-webkit-backdrop-filter:saturate(180%) blur(20px);border-bottom:1px solid var(--sep)}
.brand{display:flex;align-items:baseline;gap:8px;white-space:nowrap}
.logo{font-family:var(--hanzi);font-size:30px;font-weight:700;letter-spacing:1px;line-height:1.1}
.ver{display:none} /* the PWA vN label stays in the page for the shell check, the mirror and About, but is not shown in the header (v225, H's "Go" on the design review: a developer's line, not a user's) */
.stats{display:flex;gap:6px;font-size:13px;white-space:nowrap;font-variant-numeric:tabular-nums;padding-bottom:3px}
.stats>span,.stats>button{display:inline-flex;align-items:baseline;gap:5px;padding:5px 11px;border-radius:999px;background:var(--fill);border:0;font:inherit;color:inherit;cursor:pointer}
.stats b{font-weight:600;font-size:11px;color:var(--label2)} /* no capitalize since v402 (H: "Ebenso Vietnamesisch, Thai, Indonesisch"): the rule title-cased every word of the label, which is right for no language that needs two of them — "Jatuh Tempo", "Đến Hạn", and French's "À Faire" since v255. The three labels are written in the case they should show. */
.stats .v{color:var(--label);font-weight:600}
#stat-filter:empty{display:none} #stat-filter .chip.fpill{min-height:44px;padding:4px 11px;max-width:54vw} #stat-filter .fpill>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} /* v516: Learn's filter pill in the top bar where Due and Done stood; a long name ends in an ellipsis instead of pushing the logo */

/* main */
#main{position:relative;flex:1;display:flex;flex-direction:column;align-items:center;
  padding:16px 16px calc(var(--nav) + 28px + env(safe-area-inset-bottom));overflow-y:auto}
#main.center{justify-content:center}
/* Learn: the chip row stays at the top edge, the card is centred in the space below it (v158, H: "chips should always be on the upper edge, to avoid distraction from the cards") */
/* the line for a page inside WeChat's browser (v219): at the top edge like the chips, the card centred below */
.wxnote{width:100%;max-width:440px;margin:0 0 14px;padding:10px 14px;border-radius:var(--r);background:var(--fill);color:var(--tint-ink);font-size:14px;line-height:1.4}
#main.center>.wxnote{margin-bottom:auto} #main.center>.wxnote~.card,#main.center>.wxnote~.done{margin-bottom:auto}

/* bottom tab bar */
#tabs{position:fixed;left:0;right:0;bottom:0;z-index:5;display:grid;grid-template-columns:repeat(4,1fr);gap:0;
  padding:6px 8px calc(6px + env(safe-area-inset-bottom));
  background:var(--bar);backdrop-filter:saturate(180%) blur(20px);-webkit-backdrop-filter:saturate(180%) blur(20px);border-top:1px solid var(--sep)}
.tab{background:none;border:none;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:2px;
  padding:4px 2px 2px;font-family:var(--sans);font-size:12px;font-weight:500;color:var(--label2);min-height:44px}
.tab svg{width:26px;height:26px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.tab.on{color:var(--tint-ink)}
.tab:active{opacity:.6}

/* study */
.card{width:100%;max-width:440px;text-align:center;background:var(--card);border-radius:var(--rc);padding:22px 18px 18px;box-shadow:var(--shadow)}
/* the page detail's body lives in a .card so the swipe of v445 can find and move it, but it brings its own surfaces — the photo block and the list of texts — so the card's own box is taken off (v460) */
.card.bare{background:none;padding:0;box-shadow:none;border-radius:0;text-align:start;max-width:none}
.tags{display:flex;justify-content:center;gap:8px;margin-bottom:16px;font-size:13px;font-weight:500}
.reticle{position:relative;width:100%;height:150px;max-width:100%;margin:0 auto;background:var(--card2);border-radius:var(--rc);overflow:hidden} /* the size comes inline from frontBox (the wide box, FRONT_H per photo line); the 260 px square default of v82–v222 went in v233 */
.reticle svg{position:absolute;inset:0}
.reticle .glyph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-family:var(--hanzi);line-height:1.15;color:var(--label);user-select:none;white-space:nowrap;text-align:center;
  padding:0 12px;word-break:keep-all;overflow-wrap:anywhere}
/* a card saved before its reading is done (v237): the reading bar sits in the box, the text follows when it is done */
.reticle .glyph .wait{display:block;width:58%;max-width:220px;font-family:var(--sans);font-size:14px;font-weight:500;color:var(--label2);white-space:normal;line-height:1.3}
.reticle .glyph .wait .reading{padding:0} .reticle .glyph .wait .badge{font-size:14px;font-weight:500;text-align:center}
.crow .c .lbl{font-size:16px;font-weight:400;margin:0}
.reticle .glyph .wait.failed{width:auto}
.pin{font-size:21px;font-weight:600;color:var(--label);letter-spacing:.3px}
.mean{font-size:18px;color:var(--label);margin-top:6px;line-height:1.45}
/* three grades in the traffic light's own colours (v421): red = Hard, amber = Medium, green = Easy — the soft surface with the
   strong label of .btn.danger, so the colour is unmistakable and the text still reads on both grounds; a filled amber tile with
   white on it does not (measured against the same text on the soft ground). No interval line under the label any more. */
.grades{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:22px}
/* the two-button vote on the Learn back (v497; the markup and these rules are v487's sheet vote, which v488 removed): a red
   cross Not yet and a green check Got it, the app's own two states (v461), in the two states' soft colours */
/* .vote/.vt — the two-button vote of v497 — went with the write pad (v512) */
.grade{display:flex;align-items:center;justify-content:center;min-height:62px;background:var(--card2);
  color:var(--label);cursor:pointer;border:none;border-radius:var(--r);padding:8px 4px}
.grade[data-g="again"]{background:var(--fill);color:var(--tint-ink)}
.grade[data-g="good"]{background:var(--warn-soft);color:var(--warn)}
.grade[data-g="easy"]{background:var(--ok-soft);color:var(--ok)}
.grade:active{filter:brightness(.94)}
.grade .lbl{font-size:17px;font-weight:600;color:inherit;margin:0} /* the tile carries the colour; the page-wide .lbl rule (label2, margin-right 8px) would otherwise win over the inherited one and push the label off centre */

/* buttons: primary = filled tint · default = tinted soft · danger = red on soft · del = plain text */
.btn{background:var(--fill);color:var(--tint-ink);border:none;border-radius:var(--r);
  cursor:pointer;font-family:var(--sans);font-size:17px;font-weight:600;min-height:50px;padding:12px 18px}
.btn:active{opacity:.7}
.front.tap{cursor:pointer} .front.tap:active .reticle{opacity:.75}
/* the closed card is swiped sideways to pick another card of the session (v414): pan-y so a horizontal stroke reaches the handler while the page
   still scrolls; .swiping drops the transition so the card follows the thumb, and the next card comes in from the side the stroke went to. */
/* the carousel of v417: the card follows the thumb, the neighbour rides beside it and the pair slides one card width so the next one snaps into place. No tilt and no fade — H: "Die muessen nicht so zur Seite wegkippen wie bei Tinder." */
.card.swipe{touch-action:pan-y}
.card.peer{position:absolute;pointer-events:none}
/* the peer is a sibling of .pane and paints above it, so a taller neighbour slid over the detail's buttons, its SRS line
   and its linked row — none of which move with the gesture (measured: 527 px of overlap, the six buttons visibly sliced).
   Everything under the card steps aside while a card is being pushed and comes back with the render. (v445) */
.pane.swiping>.detailacts,.pane.swiping>.dmain,.pane.swiping>.dacts,.pane.swiping>.badge,.pane.swiping>.linked{visibility:hidden}
.card.sliding{transition:transform .22s cubic-bezier(.22,.61,.36,1)} /* app.js SW_MS waits for this .22s */
@media (prefers-reduced-motion:reduce){.card.sliding{transition:none}}
.hint{margin-top:18px;font-size:13px;color:var(--label2)}
.btn.block{width:100%}
/* the Camera tab's shutter (v466): the big card IS the Take photo button — a tap opens the phone's own camera, as it always
   did — and From album sits full width under it. #shots only gets its gap when there is something under the buttons. */
.shutter{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;width:100%;aspect-ratio:var(--photo-ar);max-height:42vh;padding:0;border:0;border-radius:var(--rc);background:var(--card);box-shadow:var(--shadow);color:var(--tint-ink);font:inherit;cursor:pointer}
.shutter .sh-ic{display:flex}
/* four corner brackets, the crop frame's own language, so the card reads as a camera surface and not as an empty card */
.shutter .sh-f{position:absolute;inset:16px;pointer-events:none;opacity:.32;
  background:linear-gradient(currentColor 0 0) 0 0/2.5px 28px,linear-gradient(currentColor 0 0) 0 0/28px 2.5px,
    linear-gradient(currentColor 0 0) 100% 0/2.5px 28px,linear-gradient(currentColor 0 0) 100% 0/28px 2.5px,
    linear-gradient(currentColor 0 0) 0 100%/2.5px 28px,linear-gradient(currentColor 0 0) 0 100%/28px 2.5px,
    linear-gradient(currentColor 0 0) 100% 100%/2.5px 28px,linear-gradient(currentColor 0 0) 100% 100%/28px 2.5px;
  background-repeat:no-repeat}
.shutter .sh-t{font-size:17px;font-weight:600}
.shutter:active{background:var(--card2)}
.snaprow{display:grid;grid-template-columns:1fr;gap:10px;margin-top:10px}
#shots:not(:empty){margin-top:24px} /* the Inbox head used to carry this gap (v471) */
.btn.primary{background:var(--tint-fill);color:#fff}
.btn.danger{background:var(--fill);color:var(--tint-ink);box-shadow:inset 0 0 0 1.5px var(--tint)} /* v551: the declarations were byte-identical to .btn, so the class said "destructive" and drew nothing — on the open card Delete card was the fifth of five identical tinted pills. The accent IS the red here, so the difference cannot be the colour (a filled red would out-shout .btn.primary, the thing the screen wants you to do): it is the ring. */
.btn.mini{min-height:44px;padding:10px 14px;font-size:15px;white-space:nowrap}
/* a .btn.mini on its own line under a field may wrap: the nowrap above was written for a two-word action
   ("Not here? Draw it"), and Meanings puts a whole sentence there — "Traduire toutes les cartes en Français"
   stood 5 px past the card's own edge at 390 px and 35 px at 360 (v432; the v413 lesson, a class written for
   one shape is not a free ride for another) */
.fieldacts .btn.mini{white-space:normal;text-align:center;line-height:1.25}
.btn:disabled{opacity:.4;cursor:not-allowed}
.btn.on{background:var(--tint-fill);color:#fff}

/* done screen */
.done{text-align:center;max-width:380px;background:var(--card);border-radius:var(--rc);padding:30px 16px;box-shadow:var(--shadow)}
.done .mark{font-family:var(--hanzi);font-size:64px;color:var(--ok);margin-bottom:14px}
.done h2{font-size:22px;margin:0 0 8px;font-weight:700;letter-spacing:-.2px}
.done p{color:var(--label2);font-size:15px;line-height:1.55;margin:0 0 22px}

/* forms / add + inbox + more */
.pane{width:100%;max-width:460px}
.lead{font-size:15px;color:var(--label2);line-height:1.5;margin:0 0 16px}
.field{margin-bottom:14px}
/* a form is one card (v124, H: the Edit screen "doesn't look like one thing"): fields on the card surface, inputs and strip tiles on --card2 */
.form{background:var(--card);border-radius:var(--rc);padding:16px 16px 16px;box-shadow:var(--shadow);margin-bottom:4px}
.form .field:last-of-type{margin-bottom:14px} .form .field input,.form .field textarea,.form .field select{background:var(--card2);box-shadow:none} .form .field input:focus,.form .field textarea:focus{box-shadow:inset 0 0 0 2px var(--tint)}
.form .signed .ck{background:var(--card2)} .form .ckpick{background:var(--fill)} .form .ckpick .ck{background:var(--card)} .form .field label{margin-left:2px}
.field label{display:block;font-size:13px;font-weight:500;color:var(--label2);margin:0 0 6px 4px}
.field input,.field textarea{width:100%;background:var(--card);border:none;border-radius:10px;
  color:var(--label);font-family:var(--sans);font-size:17px;padding:12px 14px;outline:none;box-shadow:inset 0 0 0 1px var(--sep)}
.field input:focus,.field textarea:focus{box-shadow:inset 0 0 0 2px var(--tint)}
.field input.hanzi,.field textarea.hanzi{font-family:var(--hanzi);font-size:20px;line-height:1.4;resize:vertical}
.field input.hanzi.big{font-size:24px}
.field input.mono,.field textarea.mono{font-family:var(--mono)}
.field textarea.grow{resize:none;overflow:hidden;line-height:1.35;min-height:46px;display:block}
.row{display:flex;gap:10px}
.row .field{flex:1} .row .field.row input{flex:1;min-width:0}
.err{font-size:14px;color:var(--tint-ink);margin-bottom:12px}
.ok{font-size:14px;color:var(--ok);margin-top:12px}
/* the note under a photo on the Camera tab (v535): no selection, or a long press on it raises the phone's selection
   toolbar and a Google search over the app (H, with a screenshot of exactly that); and a note that says the reading
   FAILED is not written in the success green - .bad is the same line in the ordinary label colour. */
.shot .ok,.shot .ok.bad{user-select:none;-webkit-user-select:none;-webkit-touch-callout:none}
.shot .ok.bad{color:var(--label2)}
.badge{font-size:14px;color:var(--label2);line-height:1.45}
.listhead{font-size:13px;font-weight:500;color:var(--label2);margin:26px 0 8px 4px}
.more .listhead{margin-left:16px} /* v551: the head sat at x=20 over rows whose titles sit at 32 — the one text on More that lined up with nothing. Scoped to More, since the Camera tab's marking head stands over photo tiles at 16, not over a card's inner padding. */
.del{background:none;border:none;cursor:pointer;font-size:15px;font-weight:500;color:var(--tint-ink);padding:10px 6px;min-height:44px;border-radius:8px}
.del:active{opacity:.6}
.more .mofold{display:block;padding:6px 0;min-height:44px;text-align:left} .more .mofold i{font-style:normal;color:var(--label3)} /* v717: the fold under a More row — What is sent, About the app — a link with a chevron, the text under it only while open */
.more .mrow{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;background:var(--card);border-bottom:1px solid var(--sep)} /* v589 flex-wrap: at 360 px the German Import button hung 8.6 px out of its own card and the Russian Flagged-cards row
   ran 11.6 px past it, "Поделиться" sliced by the screen edge — the text div cannot shrink below its longest word and pushed
   the .btnrow (flex:none) out. Wrapping drops the buttons onto their own line only when both cannot fit, so a row that
   fits is untouched; v432 measured and rejected min-width:0 here, which reflowed the Mirror row at 390 px instead. */
.more .mrow:first-of-type{border-top-left-radius:var(--r);border-top-right-radius:var(--r)}
.more .mrow:last-of-type,.more .mrow:has(+.listhead){border-bottom:none;border-bottom-left-radius:var(--r);border-bottom-right-radius:var(--r)} /* the last row of every group, not only the page's last (v282, H: "warum sind die Ecken der weißen Boxen unten nicht rund?" — the heads are divs too, so :last-of-type matched only About's row) */
.more .listhead+.mrow{border-top-left-radius:var(--r);border-top-right-radius:var(--r)}
/* v589: at 360 px the German Import button hung 8.6 px out of its own card and the Russian Flagged-cards row ran 11.6 px
   past it with "Поделиться" sliced by the screen edge — the text column could not shrink below its longest word and pushed the
   .btnrow (flex:none) out. A plain flex-wrap wraps on MAX-content, so it would have moved every row's button under its text in
   every language (+200…300 px of More in en, ja, ko, th); a flex-basis of min-content wraps only when the two really cannot
   share a line, so no row changes shape where it fitted. The wrapped button is right-aligned like .fieldacts (v257).
   v432 measured and rejected min-width:0 here (it reflowed the Mirror row at 390 px) and overflow-wrap (it broke seven
   ordinary Russian words mid-letter, measured at v589). */
.more .mrow>div:first-child{flex:1 1 min-content}
.more .mrow>.btnrow,.more .mrow>button{margin-left:auto}
.more .mrow .t{font-size:17px}
.more .mrow .s{font-size:14px;color:var(--label2);margin-top:2px;line-height:1.4}
.more .warn{color:var(--tint-ink)}

/* cards library */
.topline{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
/* v574: the study screen is the one place a topline is not inside a .pane — as a flex child of the centred #main it would
   shrink to its own content, so it takes the card's own width and sits squarely above it */
#main>.topline{width:100%;max-width:440px}
.topline .del{padding-left:0;flex:none;white-space:nowrap} /* v589: neither child was nowrap, so a long status badge
   (ru «Не проверено, перевод ещё не готов», id "Belum diperiksa, menunggu terjemahan") shrank the BACK BUTTON below its
   own content and ← stacked over "Карточки" — the way out of a card rendering as a mistake, 41.8 → 63.5 px tall. The badge
   is prose and wraps; the button does not. */
.cardsbar{display:flex;gap:8px;margin-bottom:10px}
.nudge{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 10px;padding:10px 14px;background:var(--card);border-radius:var(--r);font-size:14px;line-height:1.35;color:var(--label2)} .nudge .btn.mini{flex:none} /* v717: the one backup line on the Cards tab, from BACKUP_AT flashcards until the first export */
.cardsbar input{flex:1;min-width:0;background:var(--fill);border:none;border-radius:10px;color:var(--label);
  font-family:var(--sans);font-size:17px;padding:10px 14px;outline:none;-webkit-appearance:none}
.cardsbar input:focus{box-shadow:inset 0 0 0 2px var(--tint)}
.field select{width:100%;background:var(--card);border:none;box-shadow:inset 0 0 0 1px var(--sep);border-radius:10px;color:var(--label);font-family:var(--sans);font-size:17px;padding:11px 14px;outline:none;-webkit-appearance:none;appearance:none}
.chips{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px} .chips #cnt{white-space:nowrap;flex:none}
/* the chip rows stay on one line and scroll sideways (v157, H: "tag list is now 2 lines, takes too much space") */
.chips .chipset{flex-wrap:nowrap;overflow-x:auto;min-width:0;scrollbar-width:none;-webkit-overflow-scrolling:touch;padding-bottom:2px} .chips .chipset::-webkit-scrollbar{display:none} .chips .chip{flex:none;white-space:nowrap}
.chipset{display:flex;gap:6px;flex-wrap:wrap} .orderchips{margin-top:8px}
.btnrow{display:flex;gap:6px;flex:none}
.chip{background:var(--fill);border:none;border-radius:999px;color:var(--label);font-size:13px;font-weight:500;padding:8px 14px;min-height:44px;cursor:pointer}
.chip.on{background:var(--tint-fill);color:#fff}
.tagchips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px} .tagchips .chip{min-height:44px;padding:6px 11px;font-size:13px}
.pill.tag{background:var(--fill);color:var(--tint-ink)}
.say{position:relative;background:var(--fill);border:none;border-radius:999px;width:38px;height:38px;margin-left:10px;vertical-align:middle;cursor:pointer;padding:0;display:inline-flex;align-items:center;justify-content:center} .say::before{content:"";position:absolute;inset:-3px} /* v711: a 44 px hit area around the 38 px disc */
.say svg{width:20px;height:20px;fill:none;stroke:var(--tint);stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round} .say:active{opacity:.6}
.crow{display:grid;grid-template-columns:124px 1fr auto;gap:12px;align-items:center;width:100%;background:var(--card);border:none;
  border-bottom:1px solid var(--sep);padding:10px 12px;text-align:left;cursor:pointer;color:var(--label)}
.crow:first-of-type{border-top-left-radius:var(--rc);border-top-right-radius:var(--rc)} /* v711: a list of rows is a card — 16, not the buttons' 12 */
.crow:last-of-type{border-bottom:none;border-bottom-left-radius:var(--rc);border-bottom-right-radius:var(--rc)}
.crow:active{background:var(--card2)}
/* the star (v425, H: "waehrend des Durchscrollens ganz schnell und unkompliziert Karten markieren"): one tap, no mode. It sits
   at the head of the row's status column, which is right-aligned and auto-wide — so every star lines up at the same edge and, on
   a row that already carries a pill or a due date, costs no width at all. A column of its own was measured and dropped: it took
   52 px from the text and clipped the card's own characters (大闸蟹我选蟹状元 → 大闸蟹我选蟹…), which is the one thing in the row
   that must not be cut. The negative margins keep the 44 px touch area while the box lays out as 33 × 32. */
.star{display:flex;align-items:center;justify-content:center;width:44px;height:44px;margin:-6px -11px -6px 0;cursor:pointer;color:var(--label3)}
.star svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linejoin:round}
.star.on{color:var(--tint-ink)} .star.on svg{fill:currentColor}
.star:active{opacity:.55}
.thumb{width:83px;height:83px;border-radius:8px;object-fit:contain;object-position:center;background:var(--card2)}
/* the list's thumbnail like the front's photo box (v232, H: "in der Cards Liste auch"): a darkened blurred copy of the crop fills the tile behind the fitted picture */
.thumbbox{position:relative;display:block;width:83px;height:83px;border-radius:8px;overflow:hidden;background:var(--card2);box-shadow:inset 0 0 0 1px var(--sep)}
.thumbbox .thumbbg{position:absolute;left:-10%;top:-10%;width:120%;height:120%;object-fit:cover;filter:blur(10px) saturate(.55) brightness(.85);opacity:.55;pointer-events:none} /* the same subdued fill as the front (v234) */
.thumbbox .thumb{position:relative;display:block;background:transparent;border-radius:0}
.thumb.glyph{display:flex;align-items:center;justify-content:center;font-family:var(--hanzi);font-size:26px;color:var(--label3)}
.ct{display:flex;flex-direction:column;min-width:0}
.ct .c{font-family:var(--hanzi);font-size:19px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .ct .pills{display:flex;flex-wrap:wrap;gap:4px;margin:2px 0} .ct .pills .pill{margin-left:0;font-size:10px;padding:1px 7px} /* the pills have their own line under the pinyin, the text keeps its room (v126/v134) */
.ct .p{font-size:13px;color:var(--label2)}
.ct .m{font-size:14px;color:var(--label2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ptags{display:flex;flex-wrap:wrap;gap:6px;margin:6px 2px 4px} .ptags .pill{margin-left:0}
.ct .d{font-size:14px;line-height:19px;color:var(--label2);margin-top:3px;white-space:normal}
/* v699: a multicard's rows show the name and the meaning whole, and a dish's price at the right */
.crow.inpage{align-items:start;grid-template-columns:83px 1fr auto} /* the picture's own width, not the list's 124 px column */ .crow.inpage .ct .c,.crow.inpage .ct .m{white-space:normal;overflow:visible;text-overflow:clip}
.dprice{font-size:17px;font-weight:600;color:var(--label);text-align:center;margin:2px 0 6px} .lookup .dprice{text-align:start;margin:0 0 4px} /* the sheet's own lines start at the left */ /* v712: a dish's price on its own line under the characters, on the text's own screen and in the look-up */
.crow .price{display:block;font-size:17px;font-weight:600;color:var(--label);white-space:nowrap;text-align:end;margin-bottom:6px} /* v696: a multicard text's description in its row; since v698 its short one, shown whole */
.cs{display:flex;flex-direction:column;align-items:flex-end;gap:5px}
/* the six buttons of the card detail. minmax(0,1fr) and a label that may wrap, since v472: `1fr` is minmax(auto,1fr),
   so a nowrap label made the track's min-content its own width and the whole grid overflowed the card — measured at
   360 px in Indonesian +21 px, German +6, Spanish +1, which is the v427 fault (a class written for one shape borrowed
   by another) in its grid form. A two-line label inside a button is the right failure, an overflowing grid is not. */
.detailacts{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:8px;margin-top:16px}
.detailacts .primary{grid-column:1/-1} .detailacts .btn{font-size:15px;padding:12px 8px;white-space:normal;line-height:1.25}
.pill{font-size:11px;font-weight:600;border-radius:999px;padding:2px 8px;color:var(--label2);background:var(--card2);margin-left:6px;vertical-align:middle;white-space:nowrap;display:inline-block}
.pill.flagged{color:var(--tint-ink);background:var(--tint-soft)}
.pill.ai{color:var(--ok);background:var(--ok-soft)}
.crow .simpref{display:block;font-size:14px;color:var(--label2);margin:-2px 0 2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .crow .simpref .lbl{font-size:12px;margin-right:6px} .crow .simpref .hanzi{font-family:var(--hanzi);font-size:15px;color:var(--label2)}
.crow .pill{font-family:var(--sans);font-weight:600}
.linked{margin-top:16px} .linked .lbl{font-size:13px;color:var(--label2);margin-bottom:6px}
/* one line that scrolls sideways, as the tag chips since v157 — four photos used to wrap 3 + 1 (v345); the tile is 74x44 so four fit the card at 390 px and the touch target keeps its 44 px */
.linked .thumbs{display:flex;gap:8px;flex-wrap:nowrap;overflow-x:auto;min-width:0;scrollbar-width:none;-webkit-overflow-scrolling:touch;padding-bottom:2px} .linked .thumbs::-webkit-scrollbar{display:none} .card .linked .thumbs{justify-content:safe center}
.linked .lnk{position:relative;flex:none;width:72px;height:72px;padding:0;border:1px solid var(--sep);border-radius:10px;background:var(--card2);overflow:hidden;display:flex;align-items:center;justify-content:center} .linked .lnk.on{border:2px solid var(--tint)} /* 72x72 since v591 (96x54 from v422, 74x44 from v345, H: "Vielleicht sind sie einfach zu klein"): the app's one photo-box shape, and exactly the 5184 px2 of v422's 96x54, so four fit the card's width on one line where three and a peek did */
/* the Cards list's box look (v345): the crop fitted, a subdued blurred copy behind it as on the front (v232/v234) */
.linked .lnk .thumb{position:relative;width:100%;height:100%;object-fit:contain} .linked .lnk .thumbbg{position:absolute;left:-10%;top:-10%;width:120%;height:120%;object-fit:cover;filter:blur(10px) saturate(.55) brightness(.85);opacity:.55;pointer-events:none} .linked .glyph{font-size:26px;color:var(--label)}
.scriptline{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-top:8px}
.field .btnrow{display:flex;gap:8px;align-items:center} .field .btnrow input{flex:1;min-width:0}
.fieldacts{display:flex;justify-content:flex-end;gap:8px;margin-top:8px} /* an action under a field, as a soft button like the picker's (v160) */
.seg{display:inline-flex;background:var(--fill);border-radius:999px;padding:3px;margin-left:auto}
/* the Cards scope bar (v477): the same component full width under the search field, its two halves equal, so a long label in one language cannot squeeze the other */
.seg.scope{display:flex;width:100%;margin:0 0 10px} .seg.scope .segbtn{flex:1;min-width:0;padding:6px 8px;min-height:44px;white-space:normal;overflow-wrap:anywhere} .segbtn{background:none;border:none;border-radius:999px;color:var(--label);font-size:13px;font-weight:500;padding:6px 13px;min-height:44px;cursor:pointer} .segbtn.on{background:var(--tint-fill);color:#fff} .segbtn:disabled{color:var(--label3);cursor:default}
.lbl{font-family:var(--sans);font-size:13px;font-weight:500;color:var(--label2);margin-right:8px}
.scriptref{display:inline-flex;align-items:baseline;gap:0}
.scriptref .hanzi{font-family:var(--hanzi);font-size:18px;color:var(--label)}
.script{margin:10px 0 0;text-align:center} .script .pill{margin-left:0} .script.back{margin:0 0 10px}
.flag{display:inline-block;font-size:13px;font-weight:500;padding:4px 10px;border-radius:999px;color:var(--tint-ink);background:var(--tint-soft);margin-top:12px}
.aibar{display:flex;align-items:center;gap:8px;margin:0 0 10px;padding:8px 8px 8px 14px;border-radius:var(--r);background:var(--ok-soft);color:var(--ok);font-size:14px;font-weight:600}
.aibar .aiacts2{display:flex;align-items:center;gap:8px;margin-left:auto} /* align-items, or the two buttons take each glyph's own baseline and sit 12 px apart */
/* the two bulk actions are the app's own tick and cross (v480): measured over ten languages, no layout with both words
   spelled out fits one line — German alone wants 494px of the 358 the card has — and the bar wrapping to two or three
   lines is what H rejected. The words ride on title/aria-label, and both actions are undoable from More. */
.aibar .aibtn{width:44px;height:44px;flex:0 0 44px;display:grid;place-items:center;border:none;border-radius:999px;background:var(--card);color:var(--label2);cursor:pointer;padding:0}
.aibar .aibtn.prim{background:var(--ok);color:#fff} /* NOT .ok — the page already has a bare .ok rule (v82) that would add its own margin-top and font-size, the v421 class-collision lesson */
.aibar .aibtn svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.aibar .aibtn:active{opacity:.7}
.aibar .aibtn:disabled{opacity:.4}

/* character parts on the back */
.chars{display:flex;flex-wrap:wrap;justify-content:center;gap:6px;margin-top:16px}
.chars .ch{font-family:var(--hanzi);font-size:22px;min-width:44px;height:44px;padding:0 8px;border:none;border-radius:10px;background:var(--card2);color:var(--label);cursor:pointer}
.chars .ch.on{background:var(--tint-fill);color:#fff}
.chinfo{margin-top:10px;font-size:15px;color:var(--label2)}
.chline{display:flex;align-items:baseline;justify-content:center;flex-wrap:wrap;gap:12px}
.chars.sub{margin-top:10px} .chars.sub .ch{min-width:38px;height:38px;font-size:19px}
.chinfo .hanzi{font-family:var(--hanzi);font-size:26px;color:var(--label)} .chinfo .mono{font-weight:600}

/* inbox photos */
.shot.pending{padding:26px 14px;text-align:center;border:1px dashed var(--sep);border-radius:var(--rc);animation:pulse 1.2s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:.55}50%{opacity:1}}
.shot{border-radius:var(--rc);padding:10px;margin-bottom:14px;background:var(--card);box-shadow:var(--shadow)}
.shot img{width:100%;border-radius:10px;display:block;margin-bottom:8px}
.shot .meta{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px} /* v739: three buttons beside the timestamp do not fit at 360 px in German or Russian — the row wraps and the buttons stand under the time, at the right */
.shot .ts{font-family:var(--mono);font-size:12px;color:var(--label3)}
.shot .ts{flex:1 1 auto;min-width:0} .shot .acts{display:flex;gap:6px;margin-left:auto;white-space:nowrap} .shot .acts button{white-space:nowrap} /* v739: a button never breaks inside */
.ocr-btn{background:var(--tint-soft);border:none;border-radius:999px;cursor:pointer;font-size:13px;font-weight:600;color:var(--tint-ink);padding:9px 14px;min-height:38px}
.ocr-btn:active{opacity:.6}
.ocr:not(:empty){margin-top:10px}
.ocr .field input,.ocr .field textarea{background:var(--card2);box-shadow:none} .ocr .field input:focus,.ocr .field textarea:focus{box-shadow:inset 0 0 0 2px var(--tint)} /* the preview's fields sit on the photo's card like the form's (v134) */
#shots .aibox{margin-top:8px}

.shotwrap{position:relative;margin-bottom:8px}
.shotwrap img{margin-bottom:0;border-radius:10px}
/* a marked photo (v448, redrawn in v461): every card's own frame drawn permanently on the photo as a NEUTRAL outline —
   a white core with a dark flank on each side, so the boundary is carried by the EDGE between the runs and survives a
   white sign, a black appliance panel and a red shop front without a colour of its own. Nothing else is painted on the
   picture: no dot, no colour, no cross and no check, because a frame that says "you know this one" gives the answer
   away before it was asked (v461). .ff IS the frame and is its own size container, so a word only a few pixels tall —
   a panel button label renders about 16 x 9 px — thins its stroke instead of drowning in it. */
.shotwrap .regions{position:absolute;inset:0;border-radius:10px}
.region{position:absolute;padding:0;margin:0;background:none;border:0;cursor:pointer;transform-origin:center;box-sizing:border-box;min-width:0;min-height:0;container-type:size}
.region .ff{position:absolute;inset:0;border-radius:6px;box-shadow:inset 0 0 0 1.7px rgba(255,255,255,.95), inset 0 0 0 3px rgba(0,0,0,.38), 0 0 0 1px rgba(0,0,0,.45)}
@container (max-height:14px) or (max-width:26px){ .region .ff{border-radius:3.5px;box-shadow:inset 0 0 0 1.3px rgba(255,255,255,.95), 0 0 0 1px rgba(0,0,0,.5)} }
/* nothing is framed until a text is tapped (v467, H: "in der Default Ansicht nichts eingerahmt … und wenn ich auf ein
   Textelement drauftippe, dann erscheint der Rahmen"). Only the ink goes — the .region buttons keep their place and their
   size, so every tap target and v448's 44 px nearest-region rule are exactly where they were. Learn keeps its frames:
   there the lit one with its spotlight says which word the card is, and there is no tap to ask with. */
.regions:not(.learn) .region .ff{opacity:0}
.regions:not(.learn) .region.on .ff{opacity:1}
.regline{font-size:13px;color:var(--label2);margin:0 2px 4px}
.taphint{font-size:13px;color:var(--label2);margin:0 2px 8px}
/* the sheet over the photo — not modal: no scrim, the page scrolls, the next dot can be tapped while it stands */
.ask.lookup{background:none;pointer-events:none}
.ask .sheet.lookup{pointer-events:auto;position:relative;padding:18px 16px 14px;box-shadow:0 -2px 24px rgba(0,0,0,.28),0 0 0 1px var(--sep)}
.lookup .x{position:absolute;right:3px;top:1px;width:44px;height:44px;border:none;background:none;color:var(--label2);font-size:24px;line-height:44px;cursor:pointer;padding:0}
.lookup .zh{font-family:var(--hanzi);font-size:40px;line-height:1.25;color:var(--label);padding-right:36px;word-break:keep-all;overflow-wrap:anywhere} .lookup .zh.long{font-size:30px} /* v741: a text longer than the line wraps (keep-all alone cut 启用蓝牙摇一摇或将二维码靠近扫描器 at the sheet's edge) and, past 8 characters, stands a size smaller — two lines, not three */
.lookup .script{margin:4px 0 0}
.lookup .pin{margin-top:8px}
.pin .nw{white-space:nowrap} /* v711: the speaker is bound to the pinyin's last syllable, so it never wraps onto a line of its own at the left */ .lookup .mean{margin-top:4px}
.lookup .grades{margin-top:16px} .lookup .grade{min-height:54px}
.lookup .lkacts{display:flex;justify-content:center;margin-top:8px} .lookup .lkacts .del{padding:8px 14px}
.ask .sheet.lookup{max-height:78vh;overflow-y:auto;overscroll-behavior:contain} /* v733: an open Details fold scrolls inside the sheet, never the page under it */
#q::placeholder{font-size:15px} /* v800: the search placeholder names three things and the Russian field beside "+ Новая" is 194 px wide — a point smaller than the typed text, which stays at the field's own size (the 16 px floor is the input's, against the zoom on focus) */ .lkspace{display:block;width:100%;height:0;pointer-events:none} /* v793: the room under the page while a look-up sheet is open, sized by lkRoom */ .lookup .lkbar{margin-top:10px;margin-bottom:-4px} .lookup .lknote{margin-top:12px;font-size:14px;font-weight:600;color:var(--ok);text-align:center;animation:askup .18s ease-out} .lookup .lkbar .tbtn.on svg{fill:none} /* v794: the line that says the flashcard was made, and the made state in the tint — the card icon keeps its outline (a filled rect would hide the plus) */ .lookup .flagbox,.lookup .aibox{margin-top:12px} /* v792: the open card's toolbar closes the sheet — + Flashcard, Flag, Edit (v790's lone text button went into it); the flag note and the AI box stand over the fold */
.lookup .lkfold{margin-top:12px} .pnodot{margin:14px 16px 6px;font-size:13px;color:var(--label2)} /* v792: the label over the one row a text no dot reaches keeps */ .lookup .lkfold .ans{padding-top:4px;text-align:start} .lookup .lkfold .desc{margin-top:8px;max-width:none} .lookup .lkfold .desc.dsh{color:var(--label)} /* v733/v741: the Details fold, closed by default — the short description reads first and darker, the long one in the grey of every description */
.pill.multi{background:var(--fill);color:var(--label2)}
button.pill.multi{border:0;font:inherit;cursor:pointer} /* the reference is a way back to the multicard, not only a label */
/* under the picture the reference carries a whole headline ("Meituan — order confirmation, 颐堤港店"), where .pill was
   written for a word (v489): it may take the card's width and ends in an ellipsis rather than pushing past it — the
   whole title stays in the title attribute, and in the detail the pill is the button that opens the multicard */
.script .pill.multi{max-width:100%;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}
/* the card made by itself (v325, H: "no frame during the first scan, just the magic wobbling over the image, and then the result is the finished card"): a soft band of light sweeps the photo while the app reads — nothing on the photo but light, no frame; still under reduced motion (a faint veil) */
.shotwrap .scan{position:absolute;inset:0;border-radius:10px;overflow:hidden;pointer-events:none}
.shotwrap .scan::before{content:"";position:absolute;top:-30%;bottom:-30%;left:0;width:45%;transform:translateX(-150%) skewX(-14deg);will-change:transform;background:linear-gradient(90deg,rgba(255,255,255,0),rgba(255,255,255,.28) 35%,rgba(255,255,255,.55) 50%,rgba(255,255,255,.28) 65%,rgba(255,255,255,0));animation:scan 1.7s linear infinite} /* v332 (H: "the scanning effect stutters at the top left, it must run smoothly"): the band moves by transform alone, so the compositor drives it while the reader's copies keep the main thread busy (until v331 it animated left — a layout property, drawn by the main thread and frozen with it); linear, and it starts well outside the box, so the skewed band's top corner no longer pops in at the top left on every loop */
@keyframes scan{to{transform:translateX(250%) skewX(-14deg)}}
@media (prefers-reduced-motion:reduce){.shotwrap .scan::before{animation:none;left:0;width:100%;transform:none;background:rgba(255,255,255,.12)}}
/* the finished card in the photo's place (v325): the front's boxes and the back as in the detail, on the photo's own card */
.shot .result{text-align:center;padding:6px 4px 2px} .shot .result img{margin-bottom:0;border-radius:0} /* the photo box's own rules size its two pictures (v328 — a width:auto here had left the crop at the box's left edge and the blurred fill short), the linked row's its own since v345 */ .shot .result .picbox{border-radius:var(--rc)} .shot .result .signimg.full{width:100%;border-radius:10px;margin-bottom:10px}
.shot .result .back{margin-top:14px} .shot .result .linked .thumbs{justify-content:safe center} .shot .result .flagbox{text-align:left}
.shot:has(.result){padding:12px 12px 14px} .shot .detailacts{margin-top:16px}
.shot .reshead{margin:2px 0 10px 2px} .shot .result+.detailacts+.result{margin-top:22px;padding-top:22px;border-top:1px solid var(--sep)} /* several labels of one photo, one card each (v357): a head line and a hairline between the cards */
/* the finished card carries the detail's own action row (v344): Edit and Delete card as two equal buttons with the detail's room above them, no photo timestamp — the row had three type sizes and touched the parts row */
.signed .badge.ai{color:var(--ok)}
.signed .badge.bad,.smean.bad{color:var(--tint-ink)} .aibox.bad{background:var(--tint-soft)} .aibox.bad .aihead{color:var(--tint-ink)}
.cropacts .del{padding:10px 6px}

/* cropping: the frame must be visible on any photo colour (red on red!): bright dashed edge with a dark outline */
.croplayer{position:absolute;inset:0;touch-action:none;cursor:crosshair}
/* once a frame exists no new one can be drawn, so a swipe outside the frame scrolls the page; the frame itself and its corners keep taking the finger (v131/v132) */
.shotzoom{width:100%;border-radius:10px;display:block;margin-bottom:8px;background-repeat:no-repeat;background-color:var(--fill)} .croplayer.zoomed{touch-action:pan-y;cursor:pointer}
.croplayer.framed{touch-action:pan-y;cursor:default} .croplayer.framed .croprect{pointer-events:auto;touch-action:none;cursor:move} .croplayer.framed .croprect .h{cursor:nwse-resize}
.croprect{position:absolute;display:none;transform-origin:center center;border:2px dashed #fff;
  box-shadow:0 0 0 1px rgba(0,0,0,.8), inset 0 0 0 1px rgba(0,0,0,.6);pointer-events:none}
.croprect .h{position:absolute;width:18px;height:18px;background:#fff;border:1px solid rgba(0,0,0,.8);border-radius:4px}
/* the frame's dashes travel along its edge while the reading and the AI work on it (v295, H: "I just want the user to know that something's happening with the crop frame" — the colour swirls of v294 did not please): the static white dashes give way to an SVG rectangle on the same edge whose dashes march; the dark outline and the handles stay, nothing lies on the picture; still under reduced motion */
.croprect.working{border-color:transparent}
.croprect .work{position:absolute;inset:-2px;pointer-events:none}
.croprect .work svg{display:block;width:100%;height:100%;overflow:visible}
.croprect .work rect{fill:none;stroke:#fff;stroke-width:2px;stroke-dasharray:7 5;x:1px;y:1px;width:calc(100% - 2px);height:calc(100% - 2px);animation:march .8s linear infinite}
@keyframes march{to{stroke-dashoffset:-12px}}
@media (prefers-reduced-motion:reduce){.croprect .work rect{animation:none}}
.croprect .h.tl{left:-9px;top:-9px} .croprect .h.tr{right:-9px;top:-9px}
.croprect .h.bl{left:-9px;bottom:-9px} .croprect .h.br{right:-9px;bottom:-9px}
.croprect .h.rot{left:calc(50% - 9px);top:-43px;border-radius:50%;cursor:grab} .croprect .h.rot::after{content:"";position:absolute;left:8px;top:18px;width:1px;height:23px;background:#fff;box-shadow:0 0 0 1px rgba(0,0,0,.6)} /* the turn handle above the top edge with its stem (v185) */
.croppreview img{width:auto;max-width:100%;max-height:120px;display:block;border-radius:8px}
.cropacts{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px;align-items:center}
.form>.cropacts{display:flex;flex-wrap:wrap;gap:8px} .form>.cropacts>.btn{flex:1 1 auto} .form>.cropacts>.del{flex:0 0 auto} /* v811: the Edit form's Save takes its row when its label is long, Cancel drops under it — the two-column grid cut "Änderungen spei" (the labels are short now, this is the guard) */

/* sign editor (Read preview, Edit form) */
.signed .sline{margin-bottom:8px}
.signed .sline input{width:100%;background:var(--card2);border:none;border-radius:10px;color:var(--label);font-family:var(--hanzi);font-size:20px;padding:10px 12px;outline:none}
.signed .sline input:focus{box-shadow:inset 0 0 0 2px var(--tint)}
.signed .sp{font-size:14px;color:var(--label2);margin-top:3px}
.smean{margin-top:8px;font-size:16px}
.cstrip{display:flex;flex-wrap:wrap;gap:4px;margin:6px 0} .cstrip.selecting .ck{touch-action:none} /* a stroke along the strip marks, it does not scroll (v205) */
.ck{font-family:var(--hanzi);font-size:22px;min-width:44px;height:44px;padding:0 6px;border:none;border-radius:8px;background:var(--card);box-shadow:inset 0 0 0 1px var(--sep);color:var(--label);cursor:pointer}
.ck.low{box-shadow:inset 0 0 0 1.5px var(--tint)} .ck.on{background:var(--tint-fill);color:#fff} .ck.ai{box-shadow:inset 0 0 0 1.5px var(--ok)}
.ck.draw{box-shadow:inset 0 0 0 1.5px var(--label2)}
.ckpick{margin:6px 0 10px;padding:12px;border-radius:var(--r);background:var(--card2)}
.ckpick .ck{background:var(--card)} .signed .ck{background:var(--card2)} .pane .signed .ck{background:var(--card)}
.signed .ck.on,.ckpick .ck.on{background:var(--tint-fill);color:#fff} /* the chosen character stays filled whatever the surface */
.ckpick .cands{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}
.ckhead{display:flex;justify-content:space-between;align-items:center;gap:8px} .ckx{flex:none;width:30px;height:30px;border-radius:50%;border:none;background:var(--fill);color:var(--label2);font-size:20px;line-height:30px;padding:0;cursor:pointer;font-family:var(--sans)} .form .ckpick .ckx{background:var(--card)} .ckx:active{opacity:.6}
.ckacts{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:8px} .ckacts .grow{flex:1}
.ckacts.ckadd{gap:4px} .ckadd .del{padding:6px 3px;font-size:14px;min-height:36px} .ckadd .badge{margin-right:2px}
.ckhint{margin:2px 0 8px}
.labelrow{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:4px 10px;margin-bottom:6px} .labelrow label{margin-bottom:0;flex:1 1 60px} .labelrow .selrow{margin:4px 0 0;flex:1 1 100%;display:flex;flex-wrap:wrap;gap:8px}
.labelrow .selrow .btn{flex:1 1 130px;min-width:0;min-height:44px;white-space:normal;line-height:1.3}
.labelrow .selrow .badge{flex:1 1 130px;align-self:center} /* Select (or Remove and Done) at the right end of the Characters label, for all lines at once (v272) */
.reading{display:flex;flex-direction:column;gap:8px;padding:4px 0}
.diag{user-select:text;-webkit-user-select:text;font-family:var(--mono);font-size:12px;line-height:1.4;white-space:pre-wrap;word-break:break-word;background:var(--card);color:var(--label2);border-radius:var(--rc);padding:12px 14px;margin:0 0 10px;max-height:50vh;overflow:auto}
.reading .readrow{display:flex;align-items:center;justify-content:space-between;gap:10px} /* the reading's text with Save now at the right (v237) */
.reading .bar{position:relative;height:4px;border-radius:2px;background:var(--fill);overflow:hidden}
.reading .bar i{position:absolute;top:0;bottom:0;width:40%;border-radius:2px;background:var(--tint);animation:slide 1.2s ease-in-out infinite}
@keyframes slide{0%{left:-40%}100%{left:100%}}

/* Character correction uses the shared .ask > .sheet dialog shell (v852). */
.drawsheet{--ds-square:240px}
.drawsheet .dshead,.drawsheet .dsfooter,.drawsheet .dsbody{width:100%;min-width:0}
.drawsheet .del{min-height:44px;white-space:normal;line-height:1.3}
.drawsheet #ds-back{min-width:44px;font-size:24px;padding:4px}
.drawsheet .dsbody{overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;gap:10px;align-items:center;padding-bottom:4px}
.drawsheet .dsbody>*{flex-shrink:0}
.drawsheet .ckref,.drawsheet .pad,.drawsheet #ds-preview{display:block;width:var(--ds-square);height:var(--ds-square);max-width:100%;margin:0 auto;border-radius:var(--rc)}
.drawsheet .ckref{background:var(--fill);touch-action:none;cursor:grab}
.drawsheet .ds-photo{text-align:center;max-width:100%}
.drawsheet .ds-photo .badge{font-size:12px;margin-top:5px}
.drawsheet .pad{background:var(--card2);box-shadow:inset 0 0 0 1px var(--sep);touch-action:none}
.drawsheet #ds-preview{display:flex;align-items:center;justify-content:center;background:var(--card2);color:var(--label);font-size:calc(var(--ds-square)*.76);line-height:1}
.drawsheet .hanzi{font-family:var(--hanzi)}
.drawsheet #ds-context{font-size:26px;text-align:center;overflow-wrap:anywhere;line-height:1.4}
.drawsheet mark{background:var(--fill);color:var(--tint-ink);padding:3px 6px;border-radius:6px}
.drawsheet #ds-st{text-align:center;line-height:1.4;min-height:20px;max-width:100%}
.drawsheet .cands{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:0;width:100%}
.drawsheet .ck.draw{min-width:46px;height:52px;font-size:30px;background:var(--card);border:1px solid var(--sep);border-radius:10px;box-shadow:none;flex:0 0 auto}
.drawsheet .ck.draw.on{background:var(--tint-fill);color:#fff;box-shadow:inset 0 0 0 2px var(--tint-ink)}
.drawsheet .dsfooter{display:flex;flex-direction:column;gap:4px;border-top:1px solid var(--sep);padding-top:8px;background:var(--card)}
.drawsheet[data-stage="choose"] .dsfooter{border-top:0;padding-top:6px}
.drawsheet .ds-tools{display:flex;justify-content:space-between;gap:6px}
.drawsheet .ds-tools .del{flex:1;min-width:0;font-size:14px;padding:8px 4px}
.drawsheet.erasing #ds-erase{background:var(--fill);border-radius:var(--r);box-shadow:inset 0 0 0 1px var(--tint-ink)}
.drawsheet.erasing .pad{cursor:crosshair;box-shadow:0 0 0 2px var(--tint-ink)}
.drawsheet .btn.primary{width:100%;min-height:50px;white-space:normal;line-height:1.3}
.drawsheet #ds-st:empty,.drawsheet #ds-cands:empty{display:none}
.drawsheet #ds-edit-template{font-size:14px;color:var(--label2)}
.drawsheet [hidden],.drawsheet[data-stage="choose"] .pad,.drawsheet[data-stage="choose"] #ds-preview,
.drawsheet[data-stage="choose"] .ds-tools,.drawsheet[data-stage="choose"] #ds-improve,.drawsheet[data-stage="choose"] #ds-edit-template,
.drawsheet[data-stage="write"] #ds-preview,.drawsheet[data-stage="write"] #ds-context,
.drawsheet[data-stage="write"] #ds-cands,.drawsheet[data-stage="write"] #ds-improve,.drawsheet[data-stage="write"] #ds-edit-template,
.drawsheet[data-stage="results"] .pad,.drawsheet[data-stage="results"] #ds-context,.drawsheet[data-stage="results"] .ds-tools{display:none}
.char-undobar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:6px;font-size:14px;color:var(--label2)}
.char-undobar .del{min-height:44px}
.ck.corrected{box-shadow:inset 0 0 0 2px var(--ok)}
body.noscroll{overflow:hidden}
/* the app's own question sheet (v222): a dimmed backdrop, the card slides up from the bottom — a bold title, one line of consequence, a neutral Cancel beside the action */
.ask{position:fixed;inset:0;z-index:30;background:rgba(0,0,0,.45);display:flex;align-items:flex-end;justify-content:center;padding:10px;padding-bottom:calc(10px + env(safe-area-inset-bottom))}
.ask .sheet{width:100%;max-width:440px;background:var(--card);border-radius:var(--rc);padding:20px 16px 16px;box-shadow:0 10px 32px rgba(0,0,0,.28);animation:askup .18s ease-out}
@keyframes askup{from{transform:translateY(24px);opacity:0}to{transform:none;opacity:1}}
.ask .t{font-size:19px;font-weight:600;color:var(--label);line-height:1.3;letter-spacing:-.01em}
.sheet-head{display:flex;align-items:center;gap:10px;min-width:0}
.sheet-head .t{flex:1;min-width:0;margin:0;overflow-wrap:anywhere}
.sheet-head>.del{min-width:44px;min-height:44px;white-space:normal;line-height:1.3}
.ask.drawsheet,.ask.editing-modal{padding-top:calc(10px + env(safe-area-inset-top));padding-left:calc(10px + env(safe-area-inset-left));padding-right:calc(10px + env(safe-area-inset-right))}
.ask .sheet.draw-dialog,.ask .sheet.editing-dialog{padding:16px;max-height:100%;display:grid;grid-template-rows:auto minmax(0,1fr) auto;gap:12px;overflow:hidden;outline:1px solid var(--sep)}
.drawsheet[data-stage="choose"] .draw-dialog{grid-template-rows:auto minmax(0,1fr)}

.ask .s{font-size:15px;color:var(--label2);line-height:1.45;margin-top:6px}
.ask .row{display:flex;gap:10px;margin-top:18px} .ask .row .btn{flex:1;min-height:50px;font-size:17px}
.ask .btn.plain{background:var(--fill);color:var(--label)}
/* the filter pill and its sheet (v365): one line in place of the chip row, the whole list grouped in the sheet */
.chip.fpill{display:inline-flex;align-items:center;gap:6px} .ficon{width:16px;height:16px;flex:none;fill:none;stroke-width:1.7;stroke-linecap:round}
.ask .sheet.filter{padding:16px 0 12px;max-height:78vh;display:flex;flex-direction:column}
.filter .fhead{padding:0 16px 12px}
.filter .flist{overflow-y:auto;min-height:0;-webkit-overflow-scrolling:touch}
.filter .fgroup+.fgroup{margin-top:16px}
.filter .fgh{font-size:13px;color:var(--label2);padding:0 16px 6px}
.filter .frow{display:flex;align-items:center;gap:12px;width:100%;min-height:48px;padding:11px 16px;background:none;border:none;text-align:left;font-size:16px;color:var(--label);cursor:pointer}
.filter .frow+.frow{border-top:1px solid var(--sep)}
.filter .fl{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.6} /* 1.6, not the page's 1.45, since v402: Thai stacks a vowel and a tone over the letter (ทั้, ชื่), and at 16 px the taller stack stood 1 px above a line box this row clips — the only slot in the app where any ink left its box. The row's min-height 48 absorbs the 2.4 px, so no filter row grows. */
.filter .fn{color:var(--label2);font-size:15px;flex:none} .filter .fc{width:16px;flex:none}
.filter .frow.on .fl{color:var(--tint-ink);font-weight:600}
.filter .frow.on .fc::after{content:"";display:block;width:6px;height:11px;margin:0 auto;border:2px solid var(--tint);border-top:0;border-left:0;transform:rotate(45deg)}
.filter .row{margin-top:12px;padding:12px 16px 0;border-top:1px solid var(--sep)}

/* sign card front */
.signfront{width:100%;max-width:440px;margin:0 auto}
/* the crop in a fixed box inside the card at --pr, which every caller sets to CARD_RATIO (v224, 16:9 then, 3:2 at v519 and the square since v595; the edge-to-edge box of v228 went in v229 — H: "Bild in der Box gefiel mir besser"), with the text box's hairline edge, and a blurred copy of the crop filling the box behind it (v229); the copy carries the photo's own colours (v230, H: "match the soft blurred fill with the color of the photo"; v229 had it at 45 % over the grey, which washed a red crop to pink), darkened the way photo apps letterbox a picture — the blurred image at full opacity, brightness 65 %, slightly enlarged so the blur's edges never show (v231, H: "Dimming would be OK. Please do it how it's usually done.") */
.picbox{position:relative;width:100%;aspect-ratio:var(--pr,1);background:var(--card2);border-radius:var(--rc);margin:0 auto 10px;overflow:hidden;box-shadow:inset 0 0 0 1px var(--sep)}
.picbox .picbg{position:absolute;left:-10%;top:-10%;width:120%;height:120%;object-fit:cover;filter:blur(24px) saturate(.55) brightness(.85);opacity:.55;pointer-events:none} /* subdued: the photo's colour only hints through the card's grey (v234, H: "die Hintergründe bitte dezenter" — v231 had it at full opacity, brightness 65 %) */
.picbox .signimg{position:relative;width:100%;height:100%;object-fit:contain;margin:0;max-height:none;border-radius:0}
.signimg{max-width:100%;max-height:220px;display:block;margin:0 auto 10px;border-radius:10px} /* outside the box only the whole-photo view (.full) is left, in the photo's own shape */
.signimg.full{max-height:420px}
.signtext{position:relative;font-family:var(--hanzi);line-height:1.3;color:var(--label);padding:16px 14px;white-space:nowrap;background:var(--card2);border-radius:var(--r);text-align:center}

/* online AI review */
.aibox{margin-top:14px;padding:12px 14px;border-radius:var(--r);text-align:left;font-size:15px;line-height:1.5;background:var(--ok-soft)}
.aihead{font-size:13px;font-weight:600;color:var(--ok);margin-bottom:6px}
.aibox .hanzi{font-size:20px} .aibox .mono{color:var(--label2)}
.ainote{color:var(--label2);font-size:14px;margin-top:6px}
.aiacts{display:flex;gap:8px;margin-top:10px}
.aiform{background:var(--card);border-radius:var(--r);padding:12px 14px 14px;margin:4px 0 10px;box-shadow:var(--shadow)}
/* review flag */
.flagbox{margin-top:16px;padding:10px 14px;border-radius:var(--r);color:var(--tint-ink);font-size:14px;line-height:1.45;text-align:left;background:var(--tint-soft)}
/* three actions since v427 (the star joined Flag and Edit): the row wraps as whole buttons and a label never breaks
   mid-phrase — without nowrap the German row came out as three ragged two-line columns with the glyph stranded above its word.
   The gap is 14 px and the padding 2 px since v431: with 22 px the three buttons fitted (287 px of 322 in Russian) and the two
   gaps pushed the row over anyway, so it wrapped on the labels' account when it was the spacing */
.backacts{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:4px;margin-top:14px;padding-top:6px;border-top:.5px solid var(--sep)} /* v655 (H chose B): one toolbar of equal columns under a hairline, all three grey and equally quiet */
.tbtn{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;min-height:54px;padding:6px 2px;background:none;border:none;border-radius:10px;cursor:pointer;color:var(--label2);font:inherit;font-size:12px;font-weight:500;line-height:1.2;text-align:center} .tbtn:active{background:var(--fill)}
.tbtn svg{width:23px;height:23px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linejoin:round;stroke-linecap:round} .tbtn.on{color:var(--tint-ink)} .tbtn.on svg{fill:currentColor} .tbtn#edit-card svg{fill:none} /* on: a starred or flagged card's own button in the tint, its icon filled */
/* a text box inside a More row (the Feedback row, v218): the form field's look on the row's secondary surface, full width, growing with its text */
.mrow textarea{width:100%;background:var(--card2);border:none;border-radius:10px;color:var(--label);font-family:var(--sans);font-size:17px;padding:12px 14px;outline:none;box-shadow:none;margin-top:10px;resize:none;overflow:hidden;line-height:1.35;min-height:46px;display:block}
.mrow textarea:focus{box-shadow:inset 0 0 0 2px var(--tint)} .mrow textarea::placeholder{color:var(--label3)}
/* the screenshot staged under the Feedback box (v511): a thumbnail with its size and a × to take it off again */
.fbthumb{position:relative;display:inline-block;margin-top:10px}
.fbthumb img{max-width:140px;max-height:120px;border-radius:10px;display:block;box-shadow:inset 0 0 0 1px var(--sep)}
.fbthumb .x{position:absolute;top:-8px;right:-8px;width:28px;height:28px;border-radius:999px;border:0;background:var(--fill);color:var(--label);font-size:17px;line-height:1;cursor:pointer}
.fbthumb .sz{display:block;margin-top:4px;font-size:12px;color:var(--label2)}
/* and the pictures under the owner's Feedback report */
.fbpics{display:flex;flex-wrap:wrap;gap:10px;margin:10px 16px 0}
.fbpics figure{margin:0;max-width:150px} .fbpics img{max-width:150px;border-radius:10px;display:block;cursor:pointer;box-shadow:inset 0 0 0 1px var(--sep)}
.fbpics figcaption{font-size:11px;color:var(--label2);font-family:var(--mono);word-break:break-all}
.mrow .inrow{display:flex;gap:8px;align-items:center;margin-top:10px;min-width:0}
#mirror-status{overflow-wrap:anywhere} /* the mirror address is one unbreakable token, and its min-content pushed the owner's row 6 px past a 360 px screen (v472) */ /* an input with its button inside a More row, in the Feedback box's field look (v282) */
.mrow .inrow input{flex:1;min-width:0;background:var(--card2);border:none;border-radius:10px;color:var(--label);font-family:var(--sans);font-size:17px;padding:10px 14px;outline:none;box-shadow:none}
.mrow .inrow.admin{flex-wrap:wrap} .mrow .inrow.admin input{flex:1 1 120px;min-width:120px} /* v812: the password field wraps under its label rather than shrinking to 25 px in Spanish */
.mrow .inrow input.mono{font-family:var(--mono);font-size:14px} .mrow .inrow input:focus{box-shadow:inset 0 0 0 2px var(--tint)} .mrow .inrow input::placeholder{color:var(--label3)}
.mrow .err{margin:8px 0 0}
.mrow .inrow.admin{margin-top:0;gap:10px;flex-wrap:wrap} .mrow .inrow.admin .s.quiet{flex:0 1 auto;min-width:0;margin:0;font-size:13px} /* v537: the label may shrink and the row may wrap. With flex:none on the label the German row ran to x=428 on a 393 px screen and 42 % of "Anmelden" was off the right edge - the button, not just the row, and scrolling to it shifted the whole page sideways and cut every section head. CLAUDE.md recorded the row overflow as structural and accepted; a clipped BUTTON is not. */ .mrow .inrow.admin input{padding:10px 12px;font-size:16px;min-height:44px} .mrow .inrow.admin .del{flex:none;padding:8px 6px;min-height:44px} /* the admin log-in row: label, field and a plain Log in on one line, nothing prominent (v283–v284) */
.field label.check,.mrow label.check{display:flex;align-items:flex-start;gap:10px;font-size:15px;color:var(--label);margin-bottom:8px;min-height:44px;padding-top:11px} /* v589: the box sits level with the FIRST line of its label, not in the middle of the block. With align-items:center a
    four-line label (de@360 "Fotos einschließlen (etwa 0.0 MB mehr)") put the tick box 31.5 px down, level with line 3 of 4,
    so the control a learner must tap was a third of the way down the sentence. The 11 px keeps a one-line label's box
    centred in its own 44 px touch target, which is what align-items:center bought. */
.field label.check input,.mrow label.check input{width:22px;height:22px;accent-color:var(--tint-ink);margin:0;flex:none;box-shadow:none}

/* card image (crop) */
.pimg img{max-width:100%;display:block;border-radius:10px;margin-bottom:6px}
.pimg .imgacts{display:flex;gap:14px}
.recrop .shotwrap img{width:100%;display:block} .recrop .imgacts{justify-content:flex-end;flex-wrap:wrap;margin:-2px 0 8px} /* Cancel directly under the photo at the right, as the Camera tab's row (v245) */ /* the Edit form's Crop again (v239): the whole photo with the Camera tab's frame, Cancel at the right like the inbox */
.recrop #e-cropview{margin-inline-end:auto;min-height:44px}
.pimg .del.on{color:var(--ok)}
/* the picture-read mark at the right end of the Characters label (v176–v180): the green AI capsule of the Cards list */
.picmark{display:inline-flex;align-items:center;gap:4px;color:var(--ok);background:var(--ok-soft);border-radius:999px;padding:2px 8px 2px 6px;font-size:11px;font-weight:600;letter-spacing:.02em;float:right;margin-top:-2px;line-height:16px} .picmark svg{width:13px;height:13px}

/* How to use the app (v259): one white card, a section per topic with a hairline between */
.guide{background:var(--card);border-radius:var(--rc);padding:2px 16px 6px;box-shadow:var(--shadow);margin-top:4px} .guide section+section{border-top:1px solid var(--sep)} .guide h2{font-size:17px;font-weight:600;margin:14px 0 6px} .guide p{margin:0 0 10px;font-size:16px;line-height:1.45;color:var(--label)}
/* the guide's figures (v549): drawn by the app on its own tokens, so they follow the language (no UI prose inside, only
   Chinese characters and the fixed endonyms) and light/dark, and cannot go stale the way a screenshot does. */
.guide .gfig{margin:0 0 12px;padding:0;background:var(--card2);border-radius:12px;box-shadow:inset 0 0 0 1px var(--sep);overflow:hidden}
.guide .gf{display:block;width:100%;height:auto}
/* a figure that is a real crop of the app (v598): the <picture> is the block, the img carries its own width and
   aspect ratio inline (min(100%, h x ar)), so a wide crop fills the column and the square write pad sits centred.
   The radius follows the app's own small-box radius, so a crop reads as a piece of the app rather than a poster. */
.guide .gfp{display:block}
.guide .gfs{width:auto;height:auto;margin:5px auto;border-radius:10px}
/* a real crop placed INSIDE a drawn figure (v599, gfimg): <picture> does not exist inside SVG, so light and dark are
   two <image> elements and the media query hides one. Not scoped to .guide — the empty deck's example card uses the
   same pair on the Learn tab. The app has no theme switch, so a plain media query is the whole rule. */
.gfd-d{display:none}
@media(prefers-color-scheme:dark){ .gfd-l{display:none} .gfd-d{display:inline} }
.guide .gf .gfh{font-family:var(--hanzi)}

.done .hint{margin:18px 0 6px} /* the pointer to the guide under Take a photo (v266) */
/* v569 (H: "ich möchte eigentlich quasi eine Beispielkarte haben und dann mit Pfeilen kurz erklärt, handschriftlich am besten auch noch ... Wie als ob da eine Karte wäre und jemand da was hinkritzelt"): the drawn card on the left, three notes beside it, each with its own hand-drawn arrow pointing back into the card. v568's numbered list is gone. The notes are spread over the card's own height (space-around), so each sits at about the height of the part it points at — a scribble points approximately, which is the whole look. */
.done .intro{display:grid;grid-template-columns:38% 1fr;gap:8px;align-items:stretch;margin:24px 0 4px;text-align:start}
.done .intro .mock{width:100%;height:auto;align-self:start}
.done .intro .notes{display:flex;flex-direction:column;justify-content:space-around;gap:10px;padding:6px 0}
.done .intro .note{display:flex;align-items:flex-start;gap:4px}
.done .intro .note .ar{width:34px;min-width:34px;height:19px;margin-top:3px}
.done .intro .note span{font-family:'Bradley Hand','Segoe Script','Noteworthy','Comic Sans MS',var(--sans);font-size:14px;line-height:1.35;color:var(--tint-ink);display:block} /* whatever handwriting face the phone has; with none it falls back to the UI font and the arrows carry the look */
.done .intro .n1{transform:rotate(-1.5deg)} .done .intro .n2{transform:rotate(.8deg)} .done .intro .n3{transform:rotate(-1deg)}
@media(prefers-reduced-motion:reduce){.done .intro .note{transform:none}}

/* the Undo line after a deletion (v268): above the tab bar, the card's surface with the hairline and a shadow, the text and a tint Undo */
.undo{position:fixed;left:50%;transform:translateX(-50%);bottom:calc(var(--nav) + 14px + env(safe-area-inset-bottom));z-index:20;display:flex;align-items:center;gap:12px;width:calc(100% - 32px);max-width:408px;padding:6px 6px 6px 16px;border-radius:var(--r);background:var(--card);color:var(--label);font-size:15px;line-height:1.35;box-shadow:0 0 0 1px var(--sep),0 8px 24px rgba(0,0,0,.18);animation:undoup .18s ease-out}
.undo .t{flex:1;min-width:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.3} /* v812: two lines, so the verb that ends the sentence in de, fr, es, ja, ko and id is not cut off */
/* the update note (v408) is a toast, not an action bar: it has no button, so it hugs its text, wraps instead of
   clipping it, and pads evenly. Until v412 it borrowed .undo whole and every note was cut at about half (v413). */
#updated{display:flex;align-items:center;gap:8px;left:0;right:0;margin-inline:auto;transform:none;width:fit-content;max-width:min(calc(100% - 32px),408px);padding:8px 8px 8px 16px;animation:noteup .18s ease-out;cursor:pointer}
#updated .t{flex:1 1 auto;min-width:0;overflow:visible;text-overflow:clip;white-space:normal;display:block;text-align:center;text-wrap:balance}
/* it waits until it is tapped away (v416), so it needs a way out: the whole line takes the tap, the × says so */
#updated .x{flex:none;width:34px;height:34px;display:flex;align-items:center;justify-content:center;border:none;background:none;color:var(--label2);font-size:20px;line-height:1;border-radius:999px;cursor:pointer;padding:0}
#updated .x:active{background:var(--fill)}
/* marking many cards or photos (v351): the mark at the right end of the row, off the picture (v355), and the bar of the Undo line with Delete N and Done */
.crow.pick,.shot.pick{cursor:pointer}
.crow.pick{grid-template-columns:124px 1fr auto 22px}
.crow.pick.on,.shot.pick.on{background:var(--tint-soft)}
.tick{position:relative;flex:none;width:22px;height:22px;border-radius:999px;box-shadow:inset 0 0 0 1.5px var(--label3)}
.crow.on .tick,.shot.pick.on .tick{background:var(--tint);box-shadow:inset 0 0 0 1.5px var(--tint)}
.crow.on .tick::after,.shot.pick.on .tick::after{content:"";position:absolute;left:8px;top:4px;width:5px;height:10px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg)}
.pickbar{padding-left:6px}
.pickbar .sp{flex:1}
.pickbar #pick-del{color:var(--tint-ink)}
.pickbar #pick-del[disabled]{color:var(--label3)}
.pickbar #pick-done{color:var(--label2)}
.cend{display:flex;align-items:center;gap:10px;flex:none}
.chips .del{padding:6px 8px;min-height:44px;min-width:44px;font-size:14px} /* v812: All / None were 21 × 32 */
/* the inbox as tiles, two photos a row (v462): the grid is on #shots, and anything that is not a plain tile — the head,
   the batch line, the placeholder, and every photo that is busy or was opened from the grid — spans both columns, so one
   photo being read sits full width among the tiles without a second layout. */
#shots.tiles{display:grid;grid-template-columns:1fr 1fr;gap:10px;align-items:start}
#shots.tiles>.listhead,#shots.tiles>.batchline,#shots.tiles>.shot,#shots.tiles>.badge{grid-column:1/-1}
.tile{display:block;padding:0;border:0;background:var(--card);border-radius:var(--rc);overflow:hidden;box-shadow:var(--shadow);text-align:start;font:inherit;color:inherit;width:100%;cursor:pointer}
.tile .tw,.ctile .tw{position:relative;display:block;aspect-ratio:var(--photo-ar);background:var(--card2);overflow:hidden}
/* v591: the Camera tile shows the photo the way the learning card does — fitted, with the subdued blurred copy behind it
   (v232/v234) — where until v590 it was object-fit:cover in a 4:3 box: measured at 390 px, a 3:1 sign lost 55.6 % of itself,
   a portrait photo 43.8 % and an ordinary 3:2 camera photo 11.1 %, none of it reachable, on the one screen whose job is to
   let H find a photo he took. The Cards tile and the Camera tile are one box, so the two grids cannot drift apart again. */
.tile .tw .tim,.ctile .tim{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;display:block}
.tile .tw .tbg,.ctile .tbg{position:absolute;inset:-10%;width:120%;height:120%;object-fit:cover;filter:blur(10px) saturate(55%) brightness(85%);opacity:.55;pointer-events:none}
.tile .tw .wt{position:absolute;left:6px;top:6px;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:999px;background:rgba(0,0,0,.62);color:#fff} /* a photo still waiting for its turn (v466) */
.tile .tw .cnt{position:absolute;right:6px;bottom:6px;min-width:20px;height:19px;padding:0 6px;border-radius:999px;background:rgba(0,0,0,.62);color:#fff;font-family:var(--sans);font-size:11px;font-weight:600;line-height:19px;text-align:center}
.tile .tmeta{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:6px 9px 8px}
.tile .ts{font-family:var(--mono);font-size:11px;color:var(--label3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tile:active{background:var(--card2)}
/* with nothing being worked on the Camera tab is the camera: the shutter and From album sit in the middle of what is
   left of the screen, and the lead line goes with them rather than pinning them to the top (v470). The pane is inside
   a column #main, so centring is one justify-content once the pane is allowed to take the height. */
.pane.camonly{flex:1;display:flex;flex-direction:column;justify-content:center;padding-bottom:8vh}
.pane.camonly>.lead{margin-bottom:18px}
.pane>.lead.relaynote,.pane.camonly>.lead.relaynote{margin:16px 0 0;font-size:14px} /* v817: why the free AI waits, under From album */
.s.relaynote{margin-top:6px}
.relaynote{margin:12px 0;padding:12px;border:1px solid var(--sep);border-radius:12px;color:var(--label2);font-size:14px;line-height:1.5;overflow-wrap:anywhere}
.relaynote[hidden],.relaynote [hidden]{display:none!important}
.relayactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
.relayactions .btn{min-height:44px;max-width:100%;white-space:normal}
.relaynote p{margin:10px 0 0}
.pane.camonly>#shots{display:none}
/* the Cards list as photo tiles, two in a row (v465): the card's own picture with its heading under it. The grid lives
   on the list, so the empty badge and anything else that is not a tile spans both columns. */
.clist.tiles{display:grid;grid-template-columns:1fr 1fr;gap:10px;align-items:stretch;border-radius:0;overflow:visible;box-shadow:none;background:none}
.ctile{display:block;width:100%;height:100%;padding:0;border:0;background:var(--card);border-radius:var(--rc);overflow:hidden;box-shadow:var(--shadow);text-align:start;font:inherit;color:inherit;cursor:pointer}
.ctile .tglyph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:46px;color:var(--label3);text-align:center;line-height:1.15;white-space:nowrap;padding:0 6px}
/* the glyph tile carries the card's whole text (v506): .tw is a size container so the font can be fitted to the tile's
   own width and height with cqw/cqh (the inline style on .tglyph), capped at the 46 px a single character always had */
.ctile .tw.glyph{container-type:size} .ctile .tw.glyph .tglyph{color:var(--label2)} /* v812: the tile's only identification since v593 took the headings away — label3 read at 2:1 */
/* the tile of a flashcard generated from a multicard (v490): the multicard's whole photo with this card's own text
   framed and lit on it, the Learn front in miniature. .tw becomes a size container so the photo can be fitted with
   cqw/cqh — a percentage max-height would resolve against an auto height and collapse — and .tpw shrink-wraps the
   fitted photo, so the regions' percent coordinates land on the picture and not on the box beside it. */
.ctile .tw.src{display:flex;align-items:center;justify-content:center;container-type:size}
.ctile .tpw{position:relative;display:block;line-height:0}
.ctile .tpw .tpi{display:block;width:auto;height:auto;max-width:100cqw;max-height:100cqh}
.ctile .tpw .regions{position:absolute;inset:0;pointer-events:none;overflow:hidden}
/* v791: once fitTileCovers has placed it, the wrap is the cover-fitted photo pinned by its own left/top, free of the contain
   fit's cap — the open card's cut, centred on the card's own text, in the square */
.ctile .tpw.cov{position:absolute;max-width:none;max-height:none}
.ctile .tpw.cov .tpi{max-width:none;max-height:none}
/* v593 (H: "Cards preview bitte als quadratische Foto Tiles ohne text", then "Two lines are okay for Multicards"):
   an ordinary tile carries no text at all, so .th is the multicard's title alone and .ts2 is gone with cardStatus —
   and with it the three .st rules, whose only producer it was (the v307 rule). The title is clamped to two lines
   again, which is H's own number: v592 had unclamped it because a two-line cut lost the shop of "Meituan — order
   confirmation, 颐堤港店", and that cost is real and is accepted here — a title that needs a third line is read on
   the multicard itself, one tap away.
   The line-height is a whole 18 px and not 1.25: at 14 px that ratio gives a 17.5 px line, so the clamped box is 35.0 px
   and the clip edge falls mid device pixel — measured at 3x, a sliver of the third line's ink was painted under the
   second. An integer line box clips clean. */
.ctile .th{margin:8px 10px 0;font-family:var(--sans);font-size:14px;font-weight:600;line-height:18px;overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis} /* the ellipsis is what says a title goes on — a -webkit-box clamps without one, and a title that simply stops mid-word reads as a mistake rather than as "there is more" (the v413 lesson) */
/* the star on a tile must RESET the row layout's negative margin (v469, H: "das Sternchen zu weit rechts an der
   Kante. Wird leicht angeschnitten"). The base .star carries margin:-6px -11px -6px 0, written to pull its 44 px
   touch target into a list row without widening it; the tile rule never undid it, so right:2px resolved to -9px and
   top:2px to -4px, and .ctile is overflow:hidden — three pixels of the glyph were cut off every tile. Placed so the
   glyph lands exactly where .ctile.pick .tick does, 6 px in, and nothing moves when the marking starts. */
.tile-wrap>.star{position:absolute;right:0;top:0;margin:0;z-index:3;width:44px;height:44px;display:grid;place-items:center;filter:drop-shadow(0 1px 2px rgba(0,0,0,.55))}
.tile-wrap>.star svg{stroke:#fff} .tile-wrap>.star.on svg{stroke:#fff;fill:var(--tint)} /* v812: a white edge, so a starred red sign shows its star */ .tile-wrap:has(.tw.glyph)>.star:not(.on) svg{stroke:var(--label2)} /* a text-only tile is light: a white outline was invisible */
.ctile .cnt{position:absolute;right:6px;bottom:6px;z-index:3;min-width:20px;height:19px;padding:0 6px;border-radius:999px;background:rgba(0,0,0,.62);color:#fff;font-family:var(--sans);font-size:11px;font-weight:600;line-height:19px;text-align:center}
.ctile .tmarks{position:absolute;left:6px;bottom:6px;z-index:3;display:flex;gap:4px}
.ctile .tm{display:inline-grid;place-items:center;min-width:19px;height:19px;padding:0 5px;border-radius:999px;font-family:var(--sans);font-size:10px;font-weight:700;font-style:normal;color:#fff}
.ctile .tm.flag{background:var(--tint)} .ctile .tm.ai{background:var(--ok)}
.ctile .tm.trad{background:var(--lock)} .ctile .tmarks.ttrad{top:6px;bottom:auto;right:40px} /* v603: the card is in traditional characters — the tile's top-left corner, clear of the star */
.tradmark.simp{background:var(--fill);color:var(--label)} /* v604: switched to simplified — the same chip, quieter, since simplified is the app's default script */
.ctile .tm.new{background:rgba(0,0,0,.62);color:#fff} /* v812: on the photo, the overlay look of .cnt — the tint on tint-soft vanished on a red sign */ /* v515: a page whose text is not yet checked */
/* the marking's tick, on a tile (v490, H: "Im markiermodus sind an der Sternposition nun kreise. Das scheint falsch."):
   the filled state has been scoped to .crow and .shot.pick since v355, so on a tile — the Cards list since v465 — a
   marked card's ring never filled and every tile carried a grey circle that could not change. It fills like the row's
   now, and it takes the star's own legibility treatment (v425), since on a tile it sits on the picture and not on the
   card's own surface, where a grey ring vanishes over a light photo. */
.ctile.pick .tick{position:absolute;right:6px;top:6px;z-index:3;box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.95);filter:drop-shadow(0 1px 2px rgba(0,0,0,.55))}
.ctile.pick.on .tick{background:var(--tint);box-shadow:inset 0 0 0 1.5px var(--tint)}
.ctile.pick.on .tick::after{content:"";position:absolute;left:8px;top:4px;width:5px;height:10px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg)}
.ctile.pick.on{box-shadow:0 0 0 2px var(--tint),var(--shadow)}
/* a Multicard tile carries v461's two tells: the picture on a stack of plates with the count, and a bar for how far
   through it you are (v465, H: "Man muss sofort sehen, dass das eine Multicard ist"). The ghosts are inside the tile's
   own margin, so no tile grows and the grid's rhythm does not move; no negative z-index, or they paint behind the tile. */
/* the deck is ONE grid of squares (v597, H: "Make the deck square"): the multicard's tile was the only tile that was
   not — 174x239 against every other tile's 174x174 — because its progress bar and its two-line title stood UNDER the
   picture, which is the very thing v593 took off an ordinary tile ("quadratische Foto Tiles ohne text"). Both move ON
   to it: the bar is the picture's own bottom edge, the title sits over a gradient scrim, still clamped to two lines
   with its ellipsis (v593, H: "Two lines are okay for Multicards"). The arithmetic then falls out with nothing to
   tune — the stack's 9 px of top margin plus the square picture ARE the tile's 174 — so padding-bottom goes to 0.
   What it costs, named: the title's text column is 145 px instead of 154, about one character a line, so a long
   multicard name is cut a little sooner; and the scrim covers the lower quarter of the picture. The count chip and
   the marks move to the picture's top corners, out of the band's way; while the marking runs the tick owns the
   top-right, so the count stands down rather than sitting under it. */
.ctile.page{padding-bottom:0}
.ctile.page .tstack{position:relative;display:block;margin:9px 9px 0 0;isolation:isolate}
.ctile.page .tstack::before,.ctile.page .tstack::after{content:"";position:absolute;inset:0;border-radius:8px;background:var(--card2);box-shadow:inset 0 0 0 1px var(--sep)}
.ctile.page .tstack::before{transform:translate(4px,-4px);z-index:1}
.ctile.page .tstack::after{transform:translate(9px,-9px);z-index:0}
.ctile.page .tw{position:relative;z-index:2;border-radius:8px}
.ctile.page .cnt{top:6px;bottom:auto} .ctile.page.pick .cnt{display:none}
.ctile.page .tmarks{top:6px;bottom:auto}
.ctile.page .tcap{position:absolute;left:0;right:0;bottom:0;z-index:3;padding:24px 10px 9px;background:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,.5) 45%,rgba(0,0,0,.76) 100%);pointer-events:none}
.ctile.page .tcap .th{margin:0;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.55)}
.ctile.page .prog{position:absolute;left:0;right:0;bottom:0;z-index:4;display:block;height:3px;border-radius:0;background:rgba(255,255,255,.3);margin:0;overflow:hidden}
.ctile.page .prog i{display:block;height:100%;border-radius:0;background:var(--ok)}
.ctile:active{background:var(--card2)}
.listhead.pickhead{display:flex;align-items:center;justify-content:space-between;gap:8px} .listhead.pickhead .del{padding:2px 2px;min-height:0;font-size:14px}
.undo button{background:none;border:none;font-family:var(--sans);font-size:16px;font-weight:600;color:var(--tint-ink);padding:0 10px;min-height:44px;cursor:pointer;flex:none}
@keyframes undoup{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
@keyframes noteup{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
/* the Progress dashboard (v274): four tiles on the row's grey surface, the 30-day strip in the tint, the deck bar in grey, tint and green */
.prog{margin-top:8px}
.ptiles{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.ptiles>.ptile:last-child:nth-child(odd){grid-column:1/-1} /* v551: five tiles in two columns left a 160 px hole beside the fifth. A rule about odd counts, not about five, so a sixth tile changes nothing and a seventh fills its row the same way. */
.ptile{background:var(--card2);border-radius:var(--r);padding:10px 12px}
.ptile .n{font-size:28px;font-weight:700;color:var(--label);line-height:1.1;letter-spacing:-.02em}
.ptile .l{font-size:12px;color:var(--label2);margin-top:2px;line-height:1.25}
.prog .pl{font-size:13px;color:var(--label2);margin-top:12px}
.pdots{display:flex;gap:3px;margin-top:6px}
.pdots i{flex:1;height:10px;border-radius:3px;background:var(--fill)}
.pdots i.d1{background:var(--tint);opacity:.3} .pdots i.d2{background:var(--tint);opacity:.5} .pdots i.d3{background:var(--tint);opacity:.75} .pdots i.d4{background:var(--tint)}
.pbar{display:flex;height:10px;border-radius:5px;overflow:hidden;background:var(--fill);margin-top:14px;gap:2px}
.pbar i{display:block;min-width:0}
/* v551: the deck bar is a progression — grey, amber, green — and it read grey, ALERT RED, green, because "Still learning"
   was painted in the tint. The app's own three states are the traffic light of v421 (hard red, medium amber, easy green), so the
   middle of three now takes the middle colour; the 30-day strip above keeps the tint, since activity is not a state. */
.pbar i.new,.plegend i.new{background:var(--label3)} .pbar i.learn,.plegend i.learn{background:var(--warn)} .pbar i.known,.plegend i.known{background:var(--ok)}
.plegend{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:8px;font-size:13px;color:var(--label2)}
.plegend i{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px;vertical-align:-1px}

/* the batch of photos working itself through, and the one thing the user has to know about it (v411) */
/* the batch line is the only text left on the Camera tab (v471): it says something rather than naming something, so it
   gets colour and weight instead of reading as a stray grey caption (H: "der kleine Text da in der Mitte sieht total
   unsexy aus"). v471 made it a PILL with the waiting tiles' clock in it, and v473's audit measured what that cost:
   v412 spent a whole version getting this sentence onto ONE line in all 30 language × count combinations at 360 px,
   and the pill's 22 px of side padding plus the 19 px icon broke that in 9 to 13 of the 30 (fr, es, ko, ru) — the same
   ground on which v412 itself dropped a candidate line. Measured over every variant: only 4 px of side padding and no
   icon fits everywhere, so the line keeps v412's own geometry and takes its presence from the tint and weight 500.
   The clock stays where it belongs, on the waiting tiles themselves. And NO background (v475): a full-width tinted
   rounded band sitting directly under From album — itself a full-width tinted rounded button — reads as a second
   button and invites a tap that does nothing. Left-aligned tint at 13 px is unmistakably a caption, costs no width,
   and is still far more present than the grey it replaced. */
.batchline{display:block;margin:0 2px 12px 4px;font-size:13px;font-weight:500;line-height:1.4;color:var(--tint-ink);text-align:start}
/* v452: a card that is one of several from one photo shows the page itself on the closed Learn front and in the detail — the whole picture in its own shape (the one exception to v223's "both boxes the same shape"), the blurred fill around it; the dots as on the Camera tab, the card's own lit; the dots take no tap there. The cap keeps the closed front — page, text box, pill and hint — above the tab bar: 100vh minus what the rest of the screen takes (measured 500 px at 390×844 with the hint, 344 px of page there, 240 at 360×740), never over 56vh on a tall screen; the revealed card shows the card's own cut, since the grades then own the screen (v421). The 16 px frame keeps a dot at the picture's own edge whole (a title at y 0, a label at the right edge — measured clipped by 14 px without it). */
.picbox.page{--pageh:clamp(180px,calc(100vh - 500px),56vh);aspect-ratio:auto;height:auto;max-height:var(--pageh);display:flex;justify-content:center;align-items:center}
.picbox.page .pagewrap{position:relative;display:block;margin:16px;max-width:calc(100% - 32px);max-height:calc(var(--pageh) - 32px);line-height:0}
.picbox.page .pagewrap .signimg{position:static;width:auto;height:auto;max-width:100%;max-height:calc(var(--pageh) - 32px);display:block}
.picbox.page .regions{position:absolute;inset:0;pointer-events:none}
/* Learn: a spotlight, and it is still (v461 — the pulsing dot of v452 is gone). One extra shadow on the studied word's
   own frame darkens the whole photo around it, clipped by the regions box; no second element and no animation. It says
   WHICH word, never whether you know it. */
.picbox.page .regions{overflow:hidden}
.regions.learn .region{cursor:default;z-index:1}
.regions.learn .region.me{z-index:3}
.regions.learn .region.me .ff{box-shadow:inset 0 0 0 2px #fff, inset 0 0 0 3.4px rgba(0,0,0,.45), 0 0 0 9999px rgba(0,0,0,.34)}
.script .pill+.pill{margin-left:6px} /* .pill.page went with v452's count pill (v510) */
/* the page card (v453): one row for a whole screen — its title in the UI font, the photo with the dots in its detail, and each text's dot state before its status in the item list */
.ptitle{font-size:15px;font-weight:600;line-height:1.3;margin:0 2px 2px;color:var(--label)}
.pagecard .shotwrap img{width:100%;display:block;border-radius:var(--rc)}
.pagecard .shotwrap{overflow:hidden;border-radius:10px} .pagecard .shotwrap .zwrap{position:relative;transform-origin:0 0} .pagecard .shotwrap.zoomed .zwrap{will-change:transform} /* v634: the multicard's photo zooms with its regions inside the frame, as the open card's picbox does */
.pagecard+.clist{margin-top:0}
/* the two states, where a card is listed or opened (v461): a red cross for one still to learn, a green check for one
   ticked off. Colour is never the only channel — the glyph is, which is what the owner asked for. */
/* the Multicard's row: the thumbnail is a stack of plates with the count on it, and a bar says how far through it you
   are. The three plates together are exactly the 124 x 70 the row's first column already reserves, so no row grows. */

/* ---------- the write layout of Learn (v512, SPEC-flashcard-layout.md phase 1): the photo with the flag in its corner, the
   character buttons grouped by word, the folded answer block, the square pad and the line under it ---------- */
.card.study{padding-top:12px;padding-bottom:14px} /* v559: 16/18 until then — the two smallest of the six gaps the pad pays for on a wrapped card */
.card.study .zone1{position:relative}
.card.study .zone1 .picbox{margin-bottom:0}
/* THE CHARACTERS ARE ONE ROW (v517, H: "Die Felder für Bild und Zeichenfläche dürfen sich nicht in der Größe verändern.
   Das müssen Konstanten sein."). Until v516 there was one row per photo line, so a three-line sign gave three rows, 196 px
   against a one-line card's 60, and the pad paid the difference. The rows are laid side by side now with the photo's own
   line breaks kept as a rule between them. The row never scrolls (v519): its buttons shrink to fit (--chs, chrowFit) and
   only a text that does not fit at CH_MIN wraps onto a second row. */
.chrow{margin-top:8px;position:relative;max-width:100%;overflow:hidden;--chs:56px} /* v560: the viewport; the flow is .chin */
.chrow .chin{position:relative;display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:safe center;gap:14px;transition:transform .22s cubic-bezier(.22,.61,.36,1)}
.chrow .chline{display:flex;flex-wrap:nowrap;flex:0 0 auto}
/* wrapped, the photo's line groups dissolve and the word tiles flow — a photo line wider than the card would otherwise be
   clipped at both edges (measured on the 美团 badge's two lines of 7 and 8 targets); the hairline between lines goes with them */
.chrow.wrap .chin{flex-wrap:wrap;row-gap:8px;justify-content:center} /* the row gap is STRIP_GAP in app.js: the strip slides by whole rows */
.chrow.wrap .chline{display:contents}
.chrow .chline+.chline{border-left:1px solid var(--sep);padding-left:14px}
.chline{display:flex;flex-wrap:wrap;justify-content:center;gap:10px} /* one row per photo line; a line wider than the card wraps here */
.chw{display:inline-flex;gap:0;padding:0;border-radius:12px;box-shadow:inset 0 0 0 1px var(--sep);background:none;overflow:hidden} /* v525: one hairline outline around the word, nothing filled (H: "nur Außenlinie und Trennlinie zwischen den Characters") */
.chw.on{box-shadow:inset 0 0 0 2px var(--tint)} /* the word a tap lit up (v517): the outline in the tint, no fill since v525 */
.chw.lockw{box-shadow:inset 0 0 0 1.5px var(--lock)} /* v526: the word that holds the locked character — in the lock colour, like the chip; where it is also the pad's word the lock colour wins, and the chip's tint glyph says the pad is here. 1.5 px and blue since v528 (H: "Das Grün knallt zu sehr raus", "Muss nicht grün sein") */
.chrow .ch{position:relative;font-family:var(--hanzi);font-size:calc(var(--chs,56px) * 0.54);width:var(--chs,56px);height:var(--chs,56px);padding:0;border:none;border-radius:0;background:none;color:var(--label);box-shadow:none;cursor:pointer;user-select:none}
.chw .ch+.ch{border-left:1px solid var(--sep)} /* the divider between the characters of one word (v525); border-box, so the tile keeps its width */
.chrow .ch.cur{color:var(--tint-ink)} /* the one in the pad: the glyph in the tint, no fill (v525) */
.chrow .ch.num{font-family:var(--sans);font-size:17px;font-weight:600;width:auto;padding:0 10px;color:var(--label2)} /* not scaled: its width is its text's */ /* a number with its unit: one button, not writable */
.fold{margin-top:8px;text-align:left}
.foldbtn{display:flex;flex-wrap:wrap;align-items:center;gap:2px 8px;width:100%;min-height:44px;border:none;background:var(--card2);border-radius:var(--r);padding:8px 12px;font:400 15px var(--sans);color:var(--label);cursor:pointer;text-align:left} /* v589: var(--ui) was never defined in this repo, and an undefined var kills the WHOLE font shorthand — measured, the label computed 17px/400, the card's inherited body text, instead of 15px/500 on every study card and open card */
.foldbtn i{margin-left:auto;font-style:normal;font-size:18px;line-height:1;transition:transform .15s} .fold.open .foldbtn i{transform:rotate(180deg)}
.fold .ans{padding-top:12px;text-align:center}
.fold .ans .anshanzi{font-family:var(--hanzi);font-size:28px;line-height:1.3;color:var(--label);margin-bottom:6px} /* v527: the whole card at the head of the block at the card's foot (H: "komplett hanzi, Pinyin, Bedeutung ganz nach unten") */
/* the description under the meaning (v529): prose reads left-aligned inside a centred block, one measure wide. The button's state
   is .desc.want, NOT .desc.ask — .ask is the question sheet's fixed full-screen backdrop (v222), and the first cut borrowed the
   word and laid an invisible sheet over the whole card, which took every tap (the v421 class-collision lesson). */
.desc{margin:10px auto 0;max-width:34em;font-size:15px;line-height:1.45;color:var(--label2);text-align:left}
.desc.want{text-align:center} .desc.want .del{font-size:15px;padding:6px 10px} .desc.want .hint{margin-top:2px} .desc.want .reading{justify-content:center}
.desc.want:empty{display:none} /* v585: a card with no description yet renders the empty landing place for refreshDesc and nothing else */
.pill.again{margin-left:0;background:var(--fill);color:var(--tint-ink)} /* the repeat pass of a written card (§ 8.1) */
.padwrap{position:relative;margin-top:8px;display:flex;justify-content:center;width:fit-content;margin-left:auto;margin-right:auto} /* v612 (H on the unfolded screen: "show me is in the wrong place"): the wrap is exactly the pad's width, so Show me, Skip and the note are placed on the pad itself — they sat against the card's full width, which on a wide screen is 200 px wider than the pad (fit() caps the pad by height). On a phone the two widths are the same and nothing moves */
.wpad{display:block;border-radius:var(--rc);background:var(--card2);touch-action:none;box-shadow:inset 0 0 0 1px var(--sep)} /* the pad takes the finger: a stroke is never a swipe */
.wpad.ghost{width:100%;aspect-ratio:1} /* the carousel's neighbour carries an empty pad; since v530 sized inline to the standing pad's own side (ghostSize), this square is the fallback for a card without one */
.wpad.shake{animation:padshake .24s}
/* v523: the recap over the pad after a finished card — the card's own reading and meaning stand in the pad's square
   (v577 took the characters out: the tiles two rows above carry them, green, and the learner has just drawn them).
   v535 (H: "sollte das graue Pad nicht mehr sichtbar sein, da sonst zu unruhig"): the pad goes altogether rather than
   stepping back to a tenth — the grey template behind the recap's characters was the restless part. */
/* v555 (H: "Fade out des pinyin überlappt mit folgendem character. Nicht gut."): the transition belongs to the
   pad, not to the recap's class — under .recapping it vanished WITH the class, so removing it snapped the canvas from 0
   to 1 in one frame (measured) while v553's entry claimed the two fades crossed. It is the base rule now, so the pad
   really does fade both ways. */
.padwrap .wpad{transition:opacity .2s} /* v555: every fade around the recap is now .2–.26 s — the reading in, the pad out, the pad back, the reading out */
.padwrap.recapping .wpad{opacity:0}
/* v555: one entrance and one exit for BOTH recaps — the app's own curve (the swipe's since v417) with the rise v553
   gave the one-character reading. The whole-card recap had a plain scale in and no way out at all (it was removed in a
   single frame), which is the half of H's "In/Out" the final translation never got. */
.recap{position:absolute;top:0;bottom:0;left:50%;aspect-ratio:1;container-type:size;box-sizing:border-box;padding:8px 12px;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;gap:4px;text-align:center;overflow:hidden;opacity:0;translate:-50% 8px;scale:.96;transition:opacity .22s cubic-bezier(.22,.61,.36,1),translate .26s cubic-bezier(.22,.61,.36,1),scale .26s cubic-bezier(.22,.61,.36,1)}
.recap.in{opacity:1;translate:-50% 0;scale:1}
.recap.out{opacity:0;translate:-50% -6px;scale:1;transition:opacity .2s ease-in,translate .2s ease-in,scale .2s ease-in}
/* v555: green, not the tint — the app's scheme is red = in the pad, blue = locked, green = written (v528), and at the
   moment this stands there every tile of the card IS green; the tint was the colour of the thing the card had just
   stopped being. v553 made that argument for the one-character reading and it holds for the whole card. */
/* v775 (H: "Und die Übersetzung muss prominent dastehen. Das Pinyin nur als kleine Zusatzinformation."): the MEANING is the
   big line now — the headline, in the label colour, measured by recapFit — and the reading stands small and green under
   it (the green stays the colour of the written character's reading, v553/v555). A card without a meaning keeps its
   reading large (.rp.alone). The line-height of 1.25 leaves the descenders their room, so the big line needs no bottom
   padding and the clamp cannot show the top of a cut line (v554/v673). */
.recap .rm{font-size:21px;font-weight:600;color:var(--label);line-height:1.25;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.recap .rp{font-size:18px;font-weight:600;color:var(--ok);letter-spacing:.4px;line-height:1.25;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.recap .rp.alone{font-size:21px;-webkit-line-clamp:3}
.recap .rg{font-family:var(--hanzi);font-size:min(40cqw,120px);line-height:1.08;color:var(--label);flex:none;padding:0;margin:0} /* v777: the character just written, printed at the head of its reading — large enough to be looked at, two fifths of the square, the meaning and the syllable measured into what is left (recapFit) */
/* v550: with the glyph gone the reading is the one thing on the pad, so it is set large and given its own line height.
   v553 (H: "Die pinyin Darstellung polieren: In/Out, Größe, Font, Farbe, etc."): it is GREEN, not the tint. The app's
   own scheme since v528 is red = in the pad, blue = locked, green = written — and this reading stands there at the very
   moment its character's tile turns green, so the tint would be the colour of the thing it has just stopped being. The
   font is the UI font semibold as every pinyin in the app is; the tracking is opened a little, since at this size the
   tone marks want the room. It comes up and settles on the app's own curve and it leaves by lifting away while the pad
   fades back under it, where until v552 it was removed in a single frame. */
/* v554 (H, two screenshots: "Pinyin wird abgeschnitten"): a -webkit-box with overflow:hidden clips at its PADDING edge,
   and a line box of 1.12 em is shorter than the font's own content area — measured 65.0 px against 68.0 — so the ink that
   legitimately sticks out of the line was sliced off: the descender of g (H's gōng, flat-bottomed on his phone), the left
   hook of j, the acute of í, the umlaut of ü. The room is given as PADDING and taken straight back as a negative MARGIN,
   so the clip box grows while the margin box — and with it the flex layout, the centring and the 143/n cqw fit — is exactly
   what it was; a line-height alone would not do, since the shortfall is the font's and a phone runs Roboto (descender
   .271 em) where this box measures DejaVu. */
.recap .rp,.recap .rm{padding:.22em .14em;margin:-.22em -.14em;max-width:100%} /* v600: max-width so a nowrap word wider than the square overflows .rp, where recapFit can SEE it — without it the block itself grew past the box and was cut sideways (measured: 蜜雪冰城's one word 475 px wide in a 286 px square) */
.recap .rp .w{white-space:nowrap} /* v555: the reading wraps between words, not inside one */
/* v555: the reading keeps its own height and the MEANING is the one row that may give way — a flex item shrinks before
   its container overflows, and set large the reading was losing its second line to a long meaning (measured 87 px of
   reading against 69). It is v540's rule on the line under the pad, one screen along. */
.recap .rm,.recap .rp.alone{flex:none} /* v775: the big line keeps its height, the small one gives way */
.recap .rp:not(.alone){flex:0 1 auto;min-height:0}
.recap.one .rp.alone{-webkit-line-clamp:2} /* v577: the whole card's reading may take three lines now that no glyph stands over it; one character's is one line and never needs them */
.recap.one.in{transition-delay:.2s} /* v668 (H: "Der grün ausgeschriebene Charakter und das grüne Pinyin überlappen sich leicht … unsauber"): the character's reading comes in once the pad, green ink and all, has faded out (.padwrap .wpad, .2 s) — the two fades crossed; the way out (.in removed) keeps no delay */
.recap .rm,.recap .rp{padding-bottom:0;margin-bottom:0} /* v775: both lines are clamped boxes; the 1.25 line-height holds the descenders */ /* v673 (H, a screenshot of 阿: "Und hier wird der Text abgeschnitten"): the clamp clips at the padding box, and v554's .22em of bottom room showed the top of the line it had clamped away — the rule of v593: a clamped box ends at its own last line */
.recap.one .rm{-webkit-line-clamp:2} .recap.one .rp:not(.alone){-webkit-line-clamp:1} /* v775: one character's reading is one syllable */ /* v571: the character's own meaning under its reading — a 900 ms breath, so two lines, where the whole card's recap allows three */
.recap.py .rp.alone{line-height:1.12;letter-spacing:.6px} /* v555: the entrance, the exit and the green are the base rules now — one language for both recaps */
/* v775: the 18 px grey meaning of v571 is the big line above now; see .recap .rm */
@media(prefers-reduced-motion:reduce){.recap,.recap.out{transition:none;scale:1;translate:-50% 0} .padwrap .wpad{transition:none}}
@keyframes padshake{0%,100%{transform:none}25%{transform:translateX(-3px)}75%{transform:translateX(3px)}}
@media(prefers-reduced-motion:reduce){.wpad.shake{animation:none}}
/* the chevrons of § 3.2 left at v520 (H's "A"): the swipe is the way, and the count says where you are — in the fold row
   since v522 (H: "So unwichtig und braucht so viel Platz"), where it costs no height; under the pad from v520 to v521 */
.foldbtn>span{white-space:nowrap} /* the label and the Again pill never break inside; a row that has no room wraps its tail onto a second line as whole items (German at 360 px) */
.foldbtn .tail{margin-left:auto;display:inline-flex;align-items:center;gap:8px} .foldbtn .tail i{margin-left:0}
.foldbtn .fmarks{display:inline-flex;gap:4px;color:var(--tint-ink)} .foldbtn .fmarks svg{width:15px;height:15px;fill:currentColor;stroke:currentColor;stroke-width:1.6;stroke-linejoin:round;stroke-linecap:round} /* v667: the star and flag of the card, off the photo */
.foldbtn .pos{font-size:13px;font-weight:400;color:var(--label2);white-space:nowrap}
/* the locked character lit on the photo (v520): v461's shadow around the character's own rectangle, faded in and out */
/* the word being written, marked on the photo (v533, H: "Bitte ausgewählte Worte dezent im Bild highlighten"): a tint ring
   with a thin white flank outside it, so it holds on a white sign and on a black panel alike, and a breath of tint inside.
   It only marks — the dimming of everything else belongs to the locked character's spotlight below, and H asked for dezent;
   a first cut drew a 2 px ring with a dark flank as well and read as a label rather than a hint (seen on the screenshot). */
/* everything drawn ON the photo lives in one layer the zoom transforms beside the picture (v541), so a mark travels with a
   pinch or a pan. --wz is 1/scale, so a ring keeps its width instead of growing fivefold when the picture is enlarged. */
.spotlayer{position:absolute;inset:0;pointer-events:none;transform-origin:0 0;z-index:2}
.picbox.zgl>.spotlayer,.picbox.zgl .signimg,.picbox.zgl .pagewrap{transition:transform .38s cubic-bezier(.22,.61,.36,1)} /* = app.js GLIDE_MS 380 */ /* v541: the picture follows the pad onto the next word on a glide, never a jump; the finger's own pan is never on a transition */
@media(prefers-reduced-motion:reduce){.picbox.zgl>.spotlayer,.picbox.zgl .signimg,.picbox.zgl .pagewrap{transition:none}}
/* v541 (H: "Highlight character auf rotem Hintergrund muss besser sichtbar sein"): the ring was a tint line, which a red
   shop front swallows. It is a white core between two dark flanks now — v461's own finding, that the boundary is carried by
   the EDGE between the runs and so survives a white sign, a black panel and a red one alike — with the tint kept as the wash
   inside, where it still says whose mark this is on an ordinary photo. */
.wspot{position:absolute;border-radius:calc(6px*var(--wz,1));box-shadow:inset 0 0 0 calc(1.6px*var(--wz,1)) rgba(255,255,255,.96),inset 0 0 0 calc(3.2px*var(--wz,1)) rgba(0,0,0,.42),0 0 0 calc(1.4px*var(--wz,1)) rgba(0,0,0,.42);background:transparent;opacity:0;transition:opacity .28s ease;pointer-events:none;z-index:2}
.wspot.on{opacity:1;background:var(--tint-soft)}
@media(prefers-reduced-motion:reduce){.wspot{transition:none}}
.spot{position:absolute;border-radius:calc(5px*var(--wz,1));box-shadow:0 0 0 calc(1.5px*var(--wz,1)) rgba(255,255,255,.92),0 0 0 9999px rgba(0,0,0,0);transition:box-shadow .32s ease;pointer-events:none;z-index:3}
.spot.on{box-shadow:0 0 0 calc(1.5px*var(--wz,1)) rgba(255,255,255,.92),0 0 0 9999px rgba(0,0,0,.42)}
.spot.more,.spot.more.on{box-shadow:0 0 0 calc(1.5px*var(--wz,1)) rgba(255,255,255,.92)}
.card.study .zone1.spotting{overflow:hidden;border-radius:var(--rc)} /* the whole-photo view has no box to clip the shadow */
@media(prefers-reduced-motion:reduce){.spot{transition:none}}
/* ===== the praise after a finished word (v517): the counter in the bar, the star that flies into it, the ring =====
   Design tokens only, so both themes come out of the same set. */
#stat-star{color:var(--tint-ink);gap:5px;padding:4px 11px;min-height:44px;box-sizing:border-box;align-items:center}
/* the points sheet (v546): what a point is, and the last seven days — the counter is the only place the points are shown, so it is where they are explained */
.pts{text-align:start}
.pts .ptsnow{display:flex;align-items:baseline;gap:8px;margin:2px 0 12px}
.pts .ptsnow .n{font-size:34px;font-weight:700;color:var(--tint-ink);font-variant-numeric:tabular-nums}
.pts .ptsnow .l{font-size:14px;color:var(--label2)}
.pts .ptsrule{margin:0 0 14px;padding:0 0 0 18px;font-size:15px;line-height:1.45}
.pts .ptsrule li{margin:0 0 5px}
.pts .ptshead{font-size:13px;font-weight:600;color:var(--label2);margin:0 0 6px}
.pts .ptsdays{display:flex;flex-direction:column;gap:5px}
.pts .pd{display:flex;align-items:center;gap:8px;font-size:13px}
.pts .pd .d{flex:none;width:44px;color:var(--label2)}
.pts .pd .b{flex:1 1 auto;height:8px;border-radius:999px;background:var(--fill);overflow:hidden}
.pts .pd .b i{display:block;height:100%;border-radius:999px;background:var(--tint);min-width:2px}
.pts .pd .n{flex:none;width:34px;text-align:end;font-variant-numeric:tabular-nums;color:var(--label)}
#stat-star .prs{display:inline-flex;width:15px;height:15px}
#stat-star .prs svg{width:15px;height:15px;fill:currentColor}
#stat-star .v{color:inherit;font-weight:700}
#stat-star.gold{color:var(--warn);background:var(--warn-soft)}
/* the counter takes room in the bar, so the filter pill gives some back (measured at 390 and 360 px) */
.stats:has(#stat-star:not([hidden])) #stat-filter .chip.fpill{max-width:40vw}
.prstar{position:fixed;left:0;top:0;width:34px;height:34px;color:var(--tint-ink);pointer-events:none;z-index:40;will-change:transform,opacity}
.prstar.gold{color:var(--warn)}
.prstar svg{width:100%;height:100%;fill:currentColor;filter:drop-shadow(0 2px 7px rgba(0,0,0,.28))}
.prring{position:fixed;left:0;top:0;pointer-events:none;z-index:39;border-radius:999px;box-shadow:0 0 0 3px var(--warn);will-change:transform,opacity}
/* the milestone burst (v545): sparks out of the counter for a second, once */
.prspark{position:fixed;left:0;top:0;width:8px;height:8px;border-radius:50%;background:var(--tint);pointer-events:none;z-index:41;opacity:0;will-change:transform,opacity}
.prspark.gold{background:var(--warn)}

.padline{margin-top:6px;min-height:61px;font-size:15px;color:var(--label2);display:flex;flex-direction:column;align-items:center;gap:3px} /* AT LEAST two rows tall whatever the word (v518), so the pad is the same size on a one-character word; a word that needs more takes more since v600, and splitFit measures it rather than assuming LINE_H */
.padline[hidden]{display:none}
.card.study.detail .padline{min-height:0} /* v711: the two-row reservation is for the pad, which the open card has not — it left a 40 px band over "Cards with 行" */
.padline .plrow{display:flex;flex-wrap:wrap;justify-content:center;gap:2px 10px;align-items:baseline;max-width:100%} /* the character, and under it its word (v540; the other way round v517–v539) */
/* v600 (H, on a screenshot of 中华人民共和国地理标志: "Hier wird die Übersetzungszeile abgeschnitten. In so einem Fall bitte
   zweizeilig werden bzw. mehrzeilig werden."): the row WRAPS instead of being cut. v540 held it to one line and let the
   MEANING shrink to an ellipsis, but the meaning is the only part allowed to shrink — so on a word whose characters and
   pinyin alone are wider than the card the meaning went to nothing AND the row was still sliced off both ends (measured at
   390 px: the word row 375 px wide in a 322 px box, the meaning 0 px, 中华 gone from the left and the last syllable from
   the right). flex-wrap moves whichever part does not fit onto a line of its own; max-width and overflow-wrap let a part
   that is wider than the card on its own break inside itself. Nothing is cut any more, so no ellipsis is needed — and the
   row gap is 2px, not the 10px between the parts, so a wrapped row reads as one thing. */
.padline .plrow>*{flex:none;min-width:0;max-width:100%;overflow-wrap:anywhere}
.padline .hanzi{font-family:var(--hanzi);font-size:20px;color:var(--label)} .padline .hanzi b{color:var(--tint-ink);font-weight:400} .padline .mono{font-weight:600}
.padline .hanzi b.lock{color:var(--lock)} .padline .plock{display:inline-block;vertical-align:-1px;margin-right:4px;color:var(--lock)} .padline .plock svg{width:13px;height:13px;fill:currentColor} /* v526: the locked character in the lock colour in the line, the padlock before its word (blue since v528) */
.padacts{display:flex;justify-content:center;gap:2px 14px;flex-wrap:wrap;margin-top:6px} .padacts .del{font-size:15px;padding:8px 6px;min-height:40px}
/* the multicard's picture on the study card and on the open card (D5, v512): cover-fitted in the ordinary box around the
   card's own text; the pagewrap is sized and offset by fitPageCover, so the regions' percent coordinates still land on the
   picture. v596: the carousel's neighbour is on the same three rules — the Learn peer carried .study and so took them by
   accident, while the Cards peer (v445) did not and slid its page in CONTAINED with a 16 px margin against the card's cover
   fit; naming .card.peer here is what makes the peer and the card one shape, which is v530's rule for every other row.
   THE SHAPE IS --photo-ar, the app's one photo-box token (v591/v595): a page front is a photo shown small in a box, like
   every other, and it was the last one left at 3:2. In the cue the photo ROW decides instead, exactly as it does for an
   ordinary card's box, so the two are the same rect to the pixel (322 x 311 at 390 px; 292 x 209 on a short screen, where
   neither is square and the frame is what decides — v560). */
.card.study .picbox.page,.card.peer .picbox.page{aspect-ratio:var(--photo-ar);height:auto;max-height:none;display:block}
.cue .zone1 .picbox.page{height:100%;aspect-ratio:auto} /* the cue's own photo row, as for .picbox:not(.page) */
.card.study .picbox.page .pagewrap,.card.peer .picbox.page .pagewrap{position:absolute;left:0;top:0;margin:0;max-width:none;max-height:none}
.card.study .picbox.page .pagewrap .signimg,.card.peer .picbox.page .pagewrap .signimg{max-width:none;max-height:none}
/* a locked character (v513, § 7) on every card of its walk: since v528 a SOFT chip in the lock's own blue inside its own
   cell, with the glyph in that blue — a colour of its own, so red says "in the pad", blue "locked" and green "written" and
   no two of them share a tile (v525 had a locked and a written character both green) — where v525–v527 filled the whole cell
   in saturated green with a white glyph (H: "Das Grün knallt zu sehr raus und das grüne Feld ist auch quadratisch, obwohl die
   Ecken abgerundet sind"): a cell is a rectangle, so its fill met the word's rounded outline square at the divider. The chip is
   a ::before inset 3 px with its own radius, so the cell's geometry (--chs, the dividers, chrowFit) is untouched and the chip
   reads as one rounded thing inside the outline. The one also in the pad keeps the tint glyph every current character has —
   a soft fill and a tint glyph can both be seen, so the v525 ring is gone. */
.chrow .ch.lock{color:var(--lock);isolation:isolate}
.chrow .ch.lock::before{content:"";position:absolute;inset:3px;border-radius:9px;background:var(--lock-soft);z-index:-1} /* isolation:isolate keeps the -1 inside the button (the v461 lesson: a negative z-index paints under the card) */
.chrow .ch.lock.cur{color:var(--tint-ink)}
.chrow .ch.done,.chrow .ch.done.cur{color:var(--ok)} /* v827: completion wins over the active/locked glyph colour too, including the last tile redrawn during the card recap; no fill or badge (v525) */
/* and the padlock in its corner (v518, H: "Locked on/off should be more obvious, maybe with a padlock icon?") — a small glyph in the lock colour in the chip's corner since v528, no disc */
.chrow .ch .lockmark{position:absolute;right:6px;top:5px;width:10px;height:10px;color:var(--lock);display:inline-flex;align-items:center;justify-content:center;pointer-events:none}
.chrow .ch .lockmark svg{width:10px;height:10px;fill:currentColor}
/* the open card in the study card's layout (v518): the actions where the pad would stand */
.card.study.detail .detailacts{margin-top:14px}
.dback{margin-top:10px;padding-top:12px;border-top:.5px solid var(--sep);text-align:center} /* v736: the block the fold held, always open — a hairline under the word line instead of the "Details" bar */
.dmain{margin-top:16px} .dmain .btn{font-size:16px;min-height:48px} /* v736: the open card's one action */
.backacts.dacts{margin-top:12px}
.detail .padline.whole .plrow:last-child{display:none} .detail .padline.whole:not(:has(.plrow+.plrow)){display:none} /* v736: the word row repeats the block when the word is the whole card; a one-character card's line goes altogether */ /* v736: the study card's toolbar under it — Star, Flag, Edit */
/* the detail's line sits under the row at the study card's own 10 px since v531 (it stood under the fold at 12 until v530) */
/* v596: the second copy of the page front's aspect-ratio stood here — the same selector list set to the same 1.5 as the
   rule above it (v518 added it for the carousel's neighbour, which the rule above already covered). One number written
   twice is the v401 trap in CSS: the next hand changes one of them. It is one rule now, above. */
.picbox.zoomed{touch-action:none} .picbox.zoomed .signimg,.picbox.zoomed .pagewrap,.picbox.zauto .signimg,.picbox.zauto .pagewrap{will-change:transform} /* v617: .zauto is the pad's own zoom — no touch-action:none, so one finger still swipes */


/* THE FRAME (v560, H: "immer gleich, immer symmetrisch, die Proportionen immer gleich"; v561, H: "drei verschiedene Abschnitte
   ... symmetrisch gleiche Größe das Schreibfeld und darunter dann halt nochmal ganz schmal die Details"): three panels. The CUE
   — the photo edge to edge at its top, the character strip and the word line under it on the pad's own grey with the pad's
   hairline and corners — as tall as the PAD, which is as wide as the card; the fold row closes the card. --cueh is computed
   once per device in mountPad, and the photo inside the cue takes what the strip and the line leave. The neighbour of the
   carousel is the same grid with the same --cueh. Show me, Skip, Undo, Clear and Done live inside the pad's square, so a
   button appearing never moves the frame. The hint of the first twenty reviews is one more row, out of the two panels. */
.card.study:not(.detail){display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:var(--cueh,auto) auto auto;grid-auto-rows:auto;row-gap:8px;box-sizing:border-box}
.card.study:not(.detail)>*{margin-top:0}
/* v564 (H: "den oberen Teil nochmal in zwei Hälften trennt ... eine Hälfte ist das Bild ... Die zweite Hälfte ist der ganze Text"): the cue — as tall as the pad, on no panel of its own since v563 — is two halves, the picture over the whole text; --ph, --th and --cg are written by splitFit (app.js), and a tap anywhere in the upper zone swaps the two, on a slide. v580 left only these two states — the halves, where both stood on screen at once, are gone, so there is no "other half" to close */
.cue{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:var(--ph,minmax(0,1fr)) var(--th,auto);row-gap:var(--cg,8px);min-height:0;box-sizing:border-box}
/* app.js cueBig removes .gl after 320 ms, past this .22s */ .cue.gl{transition:grid-template-rows .22s cubic-bezier(.22,.61,.36,1),row-gap .22s cubic-bezier(.22,.61,.36,1)} /* v565: the slide only on a tap (cueBig sets .gl for its duration) — a render never animates the halves into place */
.cue .zone1 .picbox:not(.page):not(.sized) .signimg{opacity:0} /* v565: the picture shows only once splitFit knows its shape — fitted or clipped — so a picture that decodes late never appears in the wrong one and jumps (the carousel's neighbour did) */
.cue .txt{position:relative;min-height:0;display:flex;flex-direction:column;justify-content:center;align-items:stretch;overflow:hidden;cursor:pointer} /* the text half: the tiles' rows over the word line, centred in the half */
.cue .zone1{min-height:0;width:min(100%,var(--ph,100%));justify-self:center;display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:var(--rc);cursor:pointer} /* v580: the whole upper zone takes the tap that swaps the two states, as the text half already did */
/* v607 (H with a screenshot of Test this card: "Warum ist dieses Bild ... nicht quadratisch? Das sollte bitte quadratisch sein ... wie bei den ganz normalen Karten"): the picture half is as WIDE as it is tall (--ph, the pad's own size, v561) and centred, so photo and pad are two equal squares on every screen. Until v606 it took the card's whole inner width, 322 px at 390, and was square only where the pad happened to be 322 wide — a single-card test's topline takes 16 px, and the photo stood 322 × 286 */
/* v577: a card with no photo of its own draws its word in the picture half instead of leaving it empty — the Cards tile's
   own treatment (v506), so the two screens say the same thing about such a card. The box is a size container, as the tile's
   is, and the glyph is sized against it by cqw/cqh. */
.cue .zone1 .cglyphbox{container-type:size;width:100%;height:100%;position:relative}
.cue .zone1 .cglyphbox .tglyph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--label2);text-align:center;line-height:1.15;white-space:nowrap;padding:0 8px}
.cue .zone1 .picbox:not(.page){aspect-ratio:auto;height:100%;flex:1;display:flex;align-items:center;justify-content:center;margin:0}
.cue .zone1 .picbox:not(.page) .signimg:not(.full){flex:none;width:100%;height:100%;object-fit:contain;margin:0}
.cue .zone1 .picbox.clip .signimg:not(.full){height:auto} /* a picture near the row's own shape keeps its size and is clipped, centred on its text (v329 centres the window on the frame) */
.cue .zone1 .signimg.full{width:100%;height:100%;object-fit:contain;margin:0;border-radius:var(--rc)} /* the whole photo, a deliberate tap, fitted into the same row */
.cue .chrow{height:auto;overflow:hidden;margin:0;max-width:none;flex:none} /* v564: every row shows; the tiles' size is the half's to give (splitFit) */
.cue .chin{gap:10px} /* v562: the word gap 14 → 10 inside the panel, so four single characters share a row */
@media(prefers-reduced-motion:reduce){.chrow .chin{transition:none} .cue{transition:none}}
.cue .padline{height:auto;min-height:61px;overflow:hidden;flex:none;margin-top:6px;justify-content:center}
/* v589: in the uncovered state the answer takes the room the tiles leave — splitFit writes --plz, and the line is never
   scaled past what its widest row can take, so a long meaning simply does not grow. v600: the rows wrap rather than
   ending in an ellipsis, and splitFit reads the line's own height off the element instead of assuming LINE_H. */
.card.study.bigtxt .cue .padline{gap:calc(3px*var(--plz,1))}
.card.study.bigtxt .cue .padline .plrow{font-size:calc(15px*var(--plz,1));gap:calc(2px*var(--plz,1)) calc(10px*var(--plz,1))} /* v600: the row gap stays the small one when the row wraps — a single `gap` here put 10px between a word and its own pinyin */
.card.study.bigtxt .cue .padline .hanzi{font-size:calc(20px*var(--plz,1))}
.padwrap .padacts{position:absolute;left:0;right:0;bottom:8px;margin:0;display:flex;justify-content:space-between;padding:0 10px;pointer-events:none} .padwrap .padacts>*{pointer-events:auto}
.padwrap .padacts:not(:has(:not([hidden]))){display:none} /* an empty row takes no taps */
.padwrap .padacts{gap:2px 6px} .padwrap .padacts .del{white-space:nowrap;font-size:clamp(12px,calc(var(--padw,300px)*.068),15px);padding-inline:4px} /* v812: one row at 360 px in every language — the pair stacked over the template in de and ru */
.padwrap .padacts button{touch-action:none} /* v717: a touch that starts on Show me or Skip and moves becomes a stroke on the pad, so the browser must not take it for a scroll */
.padwrap .hint#pad-note{position:absolute;left:12px;right:12px;top:8px;margin:0;text-align:center;pointer-events:none}
/* v603: a card in traditional characters, named in the picture's top-left corner (the review flag has the right) — on the study card, its neighbour and the open card. Over any photo, so it carries its own fill; pointer-events off, so a tap on it is still the tap on the picture */
.tradmark{position:absolute;left:8px;top:8px;z-index:3;height:24px;line-height:24px;padding:0 10px;border:none;border-radius:999px;background:var(--lock);color:#fff;font-family:var(--sans);font-size:12px;font-weight:600;white-space:nowrap;cursor:pointer;user-select:none;-webkit-user-select:none;box-shadow:0 1px 3px rgba(0,0,0,.25)}
.tradmark::before{content:"";position:absolute;inset:-10px -6px} /* v604: the chip is the script switch, and the 24 px chip gets a 44 px target */
.cue .txt .tradmark{position:relative;left:auto;top:auto;flex:none;align-self:center;margin:0 0 10px;box-shadow:none} /* v603: in the text half the chip heads the tiles rather than lying on them; splitFit gives the tiles what it leaves. v604: 10 px under it, so its 44 px target ends where the first tile begins */
/* v609: the update notes under About as a list — a dot per note (grey since v610), the text hanging clear of it, a breath between notes */
.more .mrow .s.wnhead{margin-top:10px;font-weight:600;color:var(--label)}
.wnlist{list-style:none;margin:4px 0 0;padding:0}
.wnlist li{position:relative;padding-left:16px;margin-top:6px;font-size:14px;color:var(--label2);line-height:1.4}
.wnlist li::before{content:"";position:absolute;left:4px;top:.6em;width:5px;height:5px;border-radius:50%;background:var(--label3)} /* v610 (H: "die Bullet-Punkte bitte nicht in Rot. Das ist ja viel zu auffällig"): the quiet grey of the app's tertiary text */
/* v691 the character pages: the open card's link under its line, and the page — the character, its readings in your cards, the cards that hold it */

/* v812 (the audit's layout sweep): Korean breaks at spaces, as Korean UIs do — Chrome broke between Hangul syllables 150 times
   over the screens ("넘어가/요"); Chinese text keeps breaking between characters */
html:lang(ko) body{word-break:keep-all;overflow-wrap:break-word}
html:lang(ko) :is(.hanzi,.zh,.anshanzi,.chrow,.tglyph,.padline,.c){word-break:normal}
/* v812: a More row's text column keeps 9em before its buttons wrap under it — "Markierte / Karten" stood beside its buttons over five lines */
.more .mrow>div:first-child{min-width:min(100%,9em)}
/* v812: the add form's "filled in automatically, unverified" is a caution, not an error */
#f-pinhint{color:var(--warn)}

/* v830: insertion belongs beside selection, and every gap is a full touch target. */
.cstrip.inserting{gap:4px;align-items:center}
.gap-pair{display:inline-flex;align-items:center;gap:4px}
.insert-gap{width:44px;min-width:44px;height:44px;color:var(--tint-ink);background:var(--fill);font-family:inherit;font-size:26px;box-shadow:inset 0 0 0 1px var(--sep)}
.gap-char{display:inline-flex;align-items:center;justify-content:center;min-width:36px;box-shadow:none;background:transparent!important;color:var(--label2);cursor:default}

/* The conversion action stays quiet in Edit; the preview uses the saved card's own body. */
.type-switch{display:block;min-height:44px;margin:14px auto;color:var(--label2);font:inherit;font-size:14px;padding:10px 14px;white-space:normal}
.typechange .err:empty{display:none}

/* Reserve the chooser's asynchronous content before either dictionary or AI replies. */
.drawsheet[data-stage="choose"] #ds-st{display:block;width:100%}
.drawsheet[data-stage="choose"] #ds-cands{display:grid;grid-template-columns:repeat(6,minmax(0,46px));grid-template-rows:52px;gap:6px;align-items:start}
.drawsheet[data-stage="choose"] #ds-cands .ck{width:100%;min-width:0;padding:0}
.typechange{padding:0;background:var(--bg);align-items:stretch}
.type-preview{width:100%;max-width:492px;margin:0 auto;display:flex;flex-direction:column;min-height:0;padding:calc(12px + env(safe-area-inset-top)) 16px calc(12px + env(safe-area-inset-bottom));gap:12px}
.type-preview>.topline{gap:12px;flex-shrink:0;margin:0;text-align:left}
.type-preview>.topline>div{min-width:0;font-weight:600;line-height:1.3}
.type-preview>.topline .badge{font-weight:400}
.type-preview>.topline .del{min-height:44px}
.type-preview>.type-body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;text-align:left}
.type-preview>.btn{flex-shrink:0}
.type-preview .pane{padding:0;margin:0 auto}
.type-body{--page-check-room:64px}
.type-body .pagecard .shotwrap{margin-inline:auto;max-width:100%}
.type-preview>.type-tools{display:flex;gap:10px;flex-shrink:0}
.type-tools>.btn{flex:1;min-width:0}
.flash-preview .field{margin:0;text-align:left;flex-shrink:0}
.flash-preview select{display:block;width:100%;min-height:44px;min-width:0;font:inherit;font-size:16px;color:var(--label);background:var(--card);border:1px solid var(--sep);border-radius:10px;padding:10px}
.flash-preview .btn{white-space:normal}
.type-error{min-height:20px;margin:0;font-size:14px;line-height:20px;color:var(--label2)}
.type-error.err{color:var(--tint-ink)}
.page-unplaced{margin:12px 0 0;background:var(--card);border-radius:var(--rc);overflow:hidden}
.page-unplaced>summary{min-height:48px;box-sizing:border-box;padding:14px 16px;font-size:15px;font-weight:500;cursor:pointer;color:var(--tint-ink)}
.page-unplaced .clist{margin:0;padding:0 10px 10px}
.page-meta{margin-bottom:14px}
.form-actions{display:flex;flex-wrap:wrap;gap:10px;margin:16px 0}
.form-actions>.btn{flex:1;min-width:130px}
.draft-edit{padding:0;background:var(--bg);align-items:stretch;z-index:31}
.draft-edit~.drawsheet{z-index:32}
.draft-edit~.ask:not(.draft-edit){z-index:33}
.draft-body{width:100%;overflow-y:auto;overscroll-behavior:contain;padding:calc(12px + env(safe-area-inset-top)) 16px calc(20px + env(safe-area-inset-bottom));text-align:left}
.draft-body>.pane{padding:0;margin:0 auto}
.draft-body .topline{position:sticky;top:-1px;background:var(--bg);z-index:2;padding:4px 0}
.draft-body .cropacts{flex-wrap:wrap}
.draft-body .cropacts .btn{min-height:44px}
.locate-target{font-size:24px;line-height:1.45;overflow-wrap:anywhere}
.locate-hint{margin:6px 0 14px;line-height:1.4}
.locate-edit .recrop .shotwrap{margin-inline:auto;max-width:100%}
.locate-edit .recrop .cropacts{margin:4px 0 12px}
.locate-edit .recrop .cropacts>button{min-height:44px;white-space:normal}

/* v843: discreet lookup navigation, with full-sized touch targets. */
.pagecard .shotwrap{touch-action:pan-y}
.pagecard .shotwrap.zoomed{touch-action:none}
.lookup .lk-nav{display:flex;align-items:center;justify-content:center;gap:4px;margin:-10px 32px 8px 0;min-height:44px}
.lookup .lk-nav button{width:44px;height:44px;flex:0 0 44px;border:0;border-radius:var(--r);background:none;color:var(--label2);font:28px var(--sans);cursor:pointer}
.lookup .lk-nav button:disabled{opacity:.3;cursor:default}
.lookup .lk-nav span{min-width:48px;text-align:center;font:14px var(--sans);color:var(--label2);font-variant-numeric:tabular-nums}
.crop-neighbours{position:absolute;inset:0;overflow:hidden;pointer-events:none}
.crop-neighbour{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.7);box-shadow:0 0 0 1px rgba(0,0,0,.45);border-radius:3px;transform-origin:center}

/* v847: all saved-card editors share actions; grid controls are siblings, not nested buttons. */
.tile-wrap{position:relative;min-width:0;aspect-ratio:var(--photo-ar)}
.tile-wrap>.star{border:0;border-radius:10px;background:transparent;padding:0}
.form-actions>.btn{white-space:normal;overflow-wrap:anywhere}
.save-retry{position:absolute;inset:0;z-index:4;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:12px;padding:16px;background:var(--card);color:var(--label2);font-size:15px;text-align:center;border-radius:var(--r)}
:where(button,a,input,textarea,select,summary,[tabindex]):focus-visible{outline:2px solid var(--tint-ink);outline-offset:3px}

.clist.tiles.windowed{display:block;position:relative;overflow-anchor:none}
.clist.windowed>.tile-wrap{position:absolute}

/* Shared editing-dialog content; the crop frame retains the original photo's geometry. */
.editing-modal{z-index:31}
.editing-modal.page-crop{z-index:35}
.page-draft-edit~.draft-edit{z-index:32}
.page-draft-edit~.drawsheet{z-index:34}
.editing-body{min-height:0;overflow-y:auto;overscroll-behavior:contain}
.editing-body>.form-actions{margin:0}
.editing-body .page-meta{margin-top:0}
.page-crop .crop-stage{aspect-ratio:1;display:flex;align-items:center;justify-content:center;padding-top:18px;background:var(--fill);border-radius:10px}
.page-crop .shotwrap{margin:0;max-width:100%;max-height:100%;width:100%}
.page-crop .shotwrap img{width:100%;height:auto;margin:0}
.page-crop .shotwrap{width:fit-content;margin-inline:auto}
.page-crop #page-crop-preview{margin-top:12px}
.pagecard .regions{overflow:hidden}
.page-outside{padding:12px 16px;color:var(--label2)}
.page-outside>summary{min-height:44px;display:flex;align-items:center;cursor:pointer}
[data-page-crop]{margin-block:10px}
