/* LansoChat — dark by default (the brief), light only when the device asks. */
:root{
  --bg:#0f1115;--panel:#171a21;--text:#e8e9ec;--muted:#9aa0ab;--line:#262a33;
  --accent:#5b9cff;--accent-text:#0b1220;--bad:#ff6b6b;--radius:12px;--them:#34c759;
  --spoken-bg:#cfd3db;--spoken-text:#0f1115;
}
:root[data-theme="light"]{
  --bg:#ffffff;--panel:#f5f6f8;--text:#111;--muted:#5b6170;--line:#e3e5ea;
  --accent:#2b6fe6;--accent-text:#fff;--spoken-bg:#d9dce3;--spoken-text:#111;
}
*{box-sizing:border-box}

html,body{margin:0;background:var(--bg);color:var(--text);font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans","Noto Sans JP","Noto Sans KR","Noto Sans SC","Noto Sans Thai","Noto Sans Arabic",sans-serif}
a{color:var(--accent)}
.sr{position:absolute;left:-9999px}
.top{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.75rem 16px;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg)}
.brand{font-weight:700;font-size:1.15rem;text-decoration:none;color:var(--text)}
.actions{display:flex;align-items:center;gap:.5rem}
.btn{display:inline-block;background:var(--accent);color:var(--accent-text);border:0;border-radius:999px;padding:.45rem .95rem;font-weight:600;text-decoration:none;cursor:pointer;font-size:.95rem}
.btn.ghost{background:transparent;color:var(--text);border:1px solid var(--line)}
.btn[disabled]{opacity:.5;cursor:not-allowed}
.who{color:var(--muted);font-size:.9rem;max-width:40vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.langbtn{background:transparent;border:1px solid var(--line);border-radius:999px;padding:.3rem .55rem;font-size:1.2rem;line-height:1;cursor:pointer;color:var(--text)}
.page{max-width:720px;margin:0 auto;padding:24px 16px 48px}
.hero h1{font-size:2.4rem;margin:.2rem 0 .3rem}
.tagline{font-size:1.25rem;margin:0 0 .75rem}
.designed{color:var(--muted);margin:0}
.how h2{margin:2rem 0 .75rem;font-size:1.3rem}
.steps{list-style:none;padding:0;margin:0;display:grid;gap:.9rem}
.steps li{display:flex;gap:.9rem;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:.9rem}
.stepn{flex:0 0 2rem;height:2rem;border-radius:50%;background:var(--accent);color:var(--accent-text);display:flex;align-items:center;justify-content:center;font-weight:700}
.steps h3{margin:0 0 .2rem;font-size:1.05rem}
.steps p{margin:0;color:var(--muted)}
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:1.25rem}
.card h1{margin:0 0 .75rem;font-size:1.5rem}
.form{display:grid;gap:.9rem}
.form label{display:grid;gap:.3rem;font-size:.95rem;color:var(--muted)}
.form input{font:inherit;padding:.6rem .7rem;border-radius:8px;border:1px solid var(--line);background:var(--bg);color:var(--text)}
.notice{background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:.6rem .8rem;color:var(--muted)}
.notice.bad{color:var(--bad);border-color:var(--bad)}
.foot{color:var(--muted);font-size:.85rem;text-align:center;padding:1rem 16px 2rem}
/* The language modal (E-006). */
/* THE POP-UP BACKDROP, and it is the same in every pop-up in the app: the page DIMS and BLURS
   behind it, so what is on top is obviously on top and the page behind is obviously not reachable.
   Tuned for DARK, which is this app's default — a scrim tuned on a light page turns a dark one to
   mud, so the dim is modest and the blur does the separating. [R-015] */
/* EVERY POP-UP IS AN INSET CARD (Cam, 21 Sep 2026): standing clear of both edges, capped well
   short of the screen's height, all four corners rounded. A pop-up used to be a sheet rising from
   the bottom that took the full width and 85% of the height, whatever it held — a four-line
   dialog got the same footprint as a fifty-language list. Only a pop-up that says so takes more. */
.modal{position:fixed;inset:0;background:rgba(0,0,0,.5);-webkit-backdrop-filter:blur(6px) saturate(.9);backdrop-filter:blur(6px) saturate(.9);display:flex;align-items:center;justify-content:center;padding:16px;z-index:50}
.modal[hidden]{display:none}
.modalbox{position:relative;background:var(--panel);border:1px solid var(--line);border-radius:16px;width:100%;max-width:420px;max-height:70vh;display:flex;flex-direction:column}
/* a list inside a card scrolls; the card does not grow to fit it */
.modalbox > .pane,.modalbox > .langgroup{overflow-y:auto;min-height:0}
.modal.centre{align-items:center;padding:16px}
.modal.centre .modalbox{border-radius:16px;max-width:360px}
.modalbox.small{max-width:320px}
.modaltitle{margin:0;padding:1.1rem 1rem .6rem;font-size:1.15rem}
.modalactions{display:flex;gap:.6rem;justify-content:flex-end;padding:.4rem 1rem 1rem}
@media (min-width:600px){.modal{align-items:center}.modalbox{border-radius:16px}}
.modalhead{display:flex;align-items:center;justify-content:space-between;padding:.9rem 1rem .3rem}
.modalhead h2{margin:0;font-size:1.15rem}
.closex{background:transparent;border:0;color:var(--muted);font-size:1.6rem;line-height:1;cursor:pointer}
.hint{margin:0 1rem .5rem;color:var(--muted);font-size:.9rem}
.langlist{overflow:auto;padding:0 .5rem 1rem}
.langtop{border-bottom:1px solid var(--line);margin-bottom:.4rem;padding-bottom:.4rem}
.langscript{color:var(--muted);font-size:.78rem;text-transform:uppercase;letter-spacing:.04em;padding:.5rem .6rem .2rem}
.langrow{display:flex;align-items:center;gap:.7rem;padding:.55rem .6rem;border-radius:8px;text-decoration:none;color:var(--text)}
.langrow:hover{background:var(--bg)}
.langrow.selected{background:var(--bg);border:1px solid var(--accent)}
.langrow .flag{font-size:1.3rem;width:1.6rem;text-align:center}
.langrow .lname{font-weight:600}
.langrow .lenglish{color:var(--muted);font-size:.85rem;margin-inline-start:auto}
.langrow .tick{color:var(--accent);font-weight:700}
/* ── the side panel and the hamburger that becomes an X ── */
.burger{width:40px;height:40px;border:1px solid var(--line);border-radius:10px;background:transparent;cursor:pointer;position:relative;padding:0}
.burger span{position:absolute;left:10px;right:10px;height:2px;background:var(--text);border-radius:2px;transition:transform .2s,opacity .2s,top .2s}
.burger span:nth-child(1){top:12px}.burger span:nth-child(2){top:19px}.burger span:nth-child(3){top:26px}
.burger.open span:nth-child(1){top:19px;transform:rotate(45deg)}
.burger.open span:nth-child(2){opacity:0}
.burger.open span:nth-child(3){top:19px;transform:rotate(-45deg)}
.panelback{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:40;opacity:0;visibility:hidden;transition:opacity .25s ease,visibility 0s linear .25s}
.panelback.open{opacity:1;visibility:visible;transition:opacity .25s ease}
.panel{position:fixed;top:0;bottom:0;inset-inline-end:0;width:min(82vw,320px);background:var(--panel);border-inline-start:1px solid var(--line);z-index:45;display:flex;flex-direction:column;padding:64px 0 16px;box-shadow:-12px 0 32px rgba(0,0,0,.35);transform:translateX(100%);visibility:hidden;transition:transform .28s cubic-bezier(.2,.8,.2,1),visibility 0s linear .28s}
[dir="rtl"] .panel{transform:translateX(-100%)}
.panel.open{transform:translateX(0);visibility:visible;transition:transform .28s cubic-bezier(.2,.8,.2,1)}
.panelclose{position:absolute;top:12px;inset-inline-end:12px;width:40px;height:40px;border:1px solid var(--line);border-radius:10px;background:transparent;color:var(--text);font-size:1.6rem;line-height:1;cursor:pointer}
.panelclose:hover{background:var(--bg)}
@media (prefers-reduced-motion:reduce){.panel,.panelback{transition:none}}
.panelnav{display:flex;flex-direction:column;padding:0 8px}
/* the listener's persistent notice: requested to join as a talker (Cam, 21 Sep 2026) */
.topnote{display:flex;align-items:center;gap:.6rem;background:color-mix(in srgb,var(--accent) 18%,var(--panel));border:1px solid var(--accent);border-radius:10px;padding:.5rem .5rem .5rem .9rem;margin:0 0 .5rem;font-weight:600}
.topnote .closex{margin-inline-start:auto;position:static}
/* THE CONVERSATION ENDED UNDER YOU (Cam, 22 Sep 2026): the notice sits clear of the top controls,
   its New conversation button at the right, and everything else on the page fades behind it. [R-042] */
.endednote{margin:76px .5rem .5rem;padding:.7rem .6rem .7rem 1rem;gap:.8rem;position:relative;z-index:6}
.endednote .endedtext{flex:1;min-width:0}
.endednote .endednew{margin:0;flex:0 0 auto}
.endednote .endednew .btn{white-space:nowrap}
.convo.ended>:not(.endednote){opacity:.22;transition:opacity .7s ease}
/* beneath Create account, quieter: what an account gets you */
.grantnote{text-align:center;font-size:.85rem;margin:.45rem 0 0}
/* the recordings page's one-line totals (Cam, 21 Sep 2026) */
.rectotals{color:var(--muted);margin:0 0 .8rem}
/* the incident count past fifty [R-041] */
.navitem-hot{color:var(--bad)}
.inccat{font-size:1rem;margin:1.1rem 0 .4rem}.inccat .pcount{color:var(--muted);font-weight:400}
.inc{padding:.6rem .8rem;background:var(--bg);border:1px solid var(--line);border-radius:10px;margin-bottom:.4rem}.incwhen{font-size:.8rem}.incmsg{font-weight:600;margin:.15rem 0}.incdetail summary{cursor:pointer;color:var(--muted);font-size:.85rem}.incdetail pre{white-space:pre-wrap;font-size:.78rem;max-height:14em;overflow:auto;margin:.3rem 0}.incua{font-size:.75rem}
.navitem{display:block;padding:.8rem .9rem;border-radius:10px;color:var(--text);text-decoration:none;font-size:1.05rem}
.navitem:hover{background:var(--bg)}
.navitem.soon{color:var(--muted)}
.navitem.soon small{font-size:.75em;margin-inline-start:.3rem}
.navsection{color:var(--muted);font-size:.78rem;text-transform:uppercase;letter-spacing:.04em;padding:1rem .9rem .2rem}
.panelfoot{margin-top:auto;padding:12px 16px 0;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:.6rem}
.panelemail{color:var(--muted);font-size:.9rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Profile sits with Logout at the foot, not up with the navigation (Cam, 20 Sep 2026) */
.panelprofile{padding-inline:0}
/* ── the profile's languages: a row each, with the level as coloured buttons ── */
.langfield{display:grid;gap:.5rem}
/* the language just chosen, at the top of the picker, with its level to choose */
/* centred on the page, not a sheet from the bottom (Cam, 21 Sep 2026); wide enough for a list */
#pickmodal.centre .modalbox{max-width:420px;max-height:80vh}
/* the level step: one large button a row, none chosen for them; the colour arrives on press */
.chosen .options{padding:0}
.lvlbig{padding:.85rem .9rem;font-weight:600}
.lvlbig.on.lvl-beginner{background:#6aa9ff;border-color:#6aa9ff;color:#0b1220}
.lvlbig.on.lvl-intermediate{background:#f2b53c;border-color:#f2b53c;color:#0b1220}
.lvlbig.on.lvl-fluent{background:#42c46b;border-color:#42c46b;color:#0b1220}
.lvlbig.on.lvl-native{background:#b287ff;border-color:#b287ff;color:#0b1220}
.langsection{display:grid;gap:.5rem}
.langsection .langscript{padding:.4rem 0 0}
.kindpick{padding:1rem 1rem 1.1rem}
.kindpick[hidden]{display:none}
#pickmodal .pane[hidden]{display:none}
.chosen{padding:.8rem 1rem .9rem;border-bottom:1px solid var(--line);display:grid;gap:.55rem;background:var(--bg)}
.chosen[hidden]{display:none}
.chosen .langline-name b{font-size:1.05rem}
.langfield-head{display:flex;align-items:center;justify-content:space-between;gap:.6rem}
.langfield-title{font-weight:600}
.langlines{list-style:none;margin:0;padding:0;display:grid;gap:.6rem}
/* THE LANGUAGE READS FIRST, ITS LEVEL ANSWERS IT ON THE SAME LINE, and the × sits outermost
   (Cam, 21 Sep 2026). Pressing the level opens the other three, and they then take a line of
   their own — four pills do not fit beside a language name on a phone. [R-031] */
.langline{background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:.6rem .7rem;display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}
.langline-head{display:flex;align-items:center;gap:.5rem;flex:1 1 8rem;min-width:0}
.langno{flex:none;min-width:1.4rem;height:1.4rem;border-radius:50%;background:var(--line);color:var(--text);font-size:.78rem;font-weight:700;display:flex;align-items:center;justify-content:center}
.langno[hidden]{display:none}
.langline-name{display:flex;align-items:center;gap:.45rem;flex:1;min-width:0;flex-wrap:wrap}
.langline-rm{flex:none;background:none;border:0;color:var(--muted);font-size:1.3rem;line-height:1;cursor:pointer;padding:0 .2rem}
.levels{display:flex;flex-wrap:wrap;gap:.35rem;flex:0 0 auto;margin-inline-start:auto}
.langline.picking .levels{flex-basis:100%;margin-inline-start:0}
/* in a row, only the CHOSEN level shows, above the flag; press it and the other three appear */
.langline .levels .lvl:not(.on){display:none}
.langline.picking .levels .lvl{display:inline-block}
.langline .levels .lvl.on{cursor:pointer}
/* one colour a rung, climbing — and Native/primary set apart, because it is not on the ladder */
.lvl{border:1px solid var(--line);background:transparent;color:var(--muted);border-radius:999px;padding:.28rem .6rem;font:inherit;font-size:.8rem;font-weight:600;cursor:pointer}
.lvl.on{color:#0b1220}
.lvl-beginner.on{background:#6aa9ff;border-color:#6aa9ff}
.lvl-intermediate.on{background:#f2b53c;border-color:#f2b53c}
.lvl-fluent.on{background:#42c46b;border-color:#42c46b}
.lvl-native.on{background:#b287ff;border-color:#b287ff}
.pickrow{width:100%;text-align:start;background:none;border:0;font:inherit;cursor:pointer}
/* a LANGUAGE's mark: its own code, not somebody's flag. English is not Australian. */
.langmark{display:inline-flex;align-items:center;justify-content:center;width:var(--lm,27px);height:var(--lmh,18px);border-radius:4px;background:var(--panel);border:1px solid var(--line);color:var(--muted);font-size:.66rem;font-weight:700;letter-spacing:.02em;flex:none;line-height:1}
.langrow.selected .langmark{background:transparent;border-color:currentColor;color:inherit}
/* ── dashboard ── */
.dash{text-align:center;padding-top:0}
.dash .hello{text-align:start;font-size:1.45rem;margin:.5rem 0 0}
.dashmid{padding-top:12vh}
.btn.big{font-size:1.15rem;padding:.85rem 1.6rem}
.btn .bang{display:inline-block;font-weight:800}
/* LANguage SOcial CHAT — the name, spelled out. The three letter-groups carry the colour; the
   rest of each word is there to be read once and then ignored. */
.namesource{display:flex;justify-content:flex-start;flex-wrap:wrap;gap:.6rem;margin:.2rem 0 .9rem;
  font-size:.95rem;letter-spacing:.01em;color:var(--muted)}
.namesource .np b{color:var(--text);font-weight:700}
/* ── the flag marquee: small, slow, and going nowhere in particular ── */
.marquee{overflow:hidden;width:100%;margin:0 auto;padding:.5rem 0;
  /* it fades out at both ends rather than being cut off by an edge */
  -webkit-mask-image:linear-gradient(to right,transparent,#000 12%,#000 88%,transparent);
  mask-image:linear-gradient(to right,transparent,#000 12%,#000 88%,transparent)}
.mtrack{display:flex;gap:.55rem;width:max-content;animation:drift 70s linear infinite}
/* the top band drifts LEFT, the bottom band drifts RIGHT (Cam, 21 Sep 2026 — settled after going
   the other way twice; leave it) */
.marquee.back .mtrack{animation-direction:reverse}
.mtrack .flag{flex:none;line-height:0}
.mtrack .flagtile{width:24px;height:auto;border-radius:3px;opacity:.85}
/* half the track, because the run of flags is printed twice — so it restarts where it began */
@keyframes drift{from{transform:translateX(0)}to{transform:translateX(-50%)}}
/* a drifting band is exactly the thing somebody who asked for less motion does not want */
@media (prefers-reduced-motion:reduce){.mtrack{animation:none}}
.flagframe{display:grid;grid-template-columns:auto 1fr auto;grid-template-rows:auto auto auto;align-items:center;justify-items:center;gap:.6rem .8rem;width:max-content;max-width:100%;margin:0 auto 1.2rem}
.flagframe .row{grid-column:1 / -1;display:flex;gap:.5rem;justify-content:center}
.flagframe .col{display:flex;flex-direction:column;gap:.5rem}
.flagframe .btn.big{margin:0 .2rem}
.dashmid .btn.big{display:block;margin:1.4rem auto}
/* said under the button when the conversation could not be started, so nothing on the page is lost */
#exitmodal .leavebad{margin:.7rem 1rem 0;text-align:center;font-size:.85rem}
.dashmid .startfail{margin:1.1rem auto 0;max-width:20rem;text-align:center;font-size:.95rem}
/* ── conversation mode ── */
.page.fill{display:flex;flex-direction:column;height:calc(100dvh - 65px);padding-bottom:12px;overflow:hidden}
body.noscroll{overflow:hidden;height:100dvh}
.convo{display:flex;flex-direction:column;flex:1;min-height:0}
.bubbles{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:.5rem;padding:.5rem 0;-webkit-overflow-scrolling:touch;overscroll-behavior-y:auto}
body.bare{overscroll-behavior:none}   /* the page itself never rubber-bands; the list does */
.bubbles > :first-child{margin-top:auto}
.bubbles .empty{color:var(--muted);text-align:center;margin:auto}
.bar{display:flex;gap:.6rem;align-items:center;justify-content:space-between;padding-top:.6rem;border-top:1px solid var(--line)}
.btn.talk{flex:1;font-size:1.1rem;padding:.8rem 1rem}
.qricon{font-size:1.1em;margin-inline-end:.2rem}
.soonline{color:var(--muted);font-size:.85rem;text-align:center;margin:.4rem 0 0}
.credits{color:var(--muted);font-size:.9rem;text-align:end}
.modalbox.invite{padding:.6rem 0 1rem}
/* ── the invite as a LINK instead of a QR code (Cam, 19 Sep 2026) ── */
.invitelink{display:flex;gap:.5rem;align-items:center;padding:1.2rem 1rem .2rem}
.invitelink[hidden]{display:none}
.linkbox{flex:1;min-width:0;background:var(--bg);color:var(--text);border:1px solid var(--line);border-radius:999px;padding:.55rem .9rem;font:inherit;font-size:.9rem}
.copybtn{flex:none}
.linknote{color:var(--muted);font-size:.85rem;text-align:center;margin:.7rem 1rem 0;line-height:1.35}
.linkbad{margin:.7rem 1rem 0;text-align:center;font-size:.85rem}
/* the caption line IS the control: it swaps the code for the address and back */
.linkswap{background:none;border:0;padding:.2rem;color:var(--accent);font:inherit;font-size:.9rem;font-weight:600;cursor:pointer;text-decoration:underline}
.linkswap.pressed{opacity:.6}
.linkswap[disabled]{cursor:not-allowed}
.roles{display:flex;gap:.5rem;padding:1.4rem 1rem .9rem}
.rolebtn{flex:1;padding:.6rem;border-radius:999px;border:1px solid var(--line);background:var(--bg);color:var(--text);font-weight:600;cursor:pointer;font-size:.95rem}
.rolebtn.on{background:var(--accent);color:var(--accent-text);border-color:var(--accent)}
.floatx{position:absolute;top:6px;inset-inline-end:8px}
.qr{margin:0 1rem;display:flex;justify-content:center}
.qr[hidden]{display:none}   /* a rule that sets display beats [hidden]; same pattern as .modal[hidden] */
.qr svg{width:100%;max-width:300px;height:auto;border-radius:12px}
/* the same footprint as the code it stands in for: square, 300 wide at most, so nothing shifts */
/* the placeholder: a DASHED border in the text colour, on the page's own background — dark on
   dark, white on light — so it reads as a space being held, not as a code (Cam, 21 Sep 2026) */
.qrwait{width:100%;max-width:300px;aspect-ratio:1;border-radius:12px;background:var(--bg);color:var(--text);border:2px dashed var(--text);display:flex;align-items:center;justify-content:center;font-weight:600;font-size:1.05rem;box-sizing:border-box}
.landing .invited{font-size:1.25rem}
.landing .gift{color:var(--muted)}
.chatlist{list-style:none;padding:0;margin:0;display:grid;gap:.5rem}
.chatlist li a{display:block;background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:.7rem .9rem;text-decoration:none;color:var(--text)}
.muted{color:var(--muted)}

/* ── push-to-talk (conversation mode) ── */
.talkbar{position:fixed;left:0;right:0;bottom:0;align-items:flex-end;justify-content:space-between;border-top:0;padding:6px 16px calc(12px + env(safe-area-inset-bottom))}
.talkbar::before{content:"";position:absolute;left:0;right:0;bottom:0;top:-56px;z-index:-1;pointer-events:none;background:color-mix(in srgb,var(--bg) 68%,transparent);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);-webkit-mask-image:linear-gradient(to top,#000 45%,rgba(0,0,0,.6) 70%,transparent);mask-image:linear-gradient(to top,#000 45%,rgba(0,0,0,.6) 70%,transparent)}
.talkbar.pos-left::before,.talkbar.pos-right::before{display:none}
.convo{position:relative}
.convo.tut .talkbar::before{display:none}
.convo.tut .talkbar.pos-left,.convo.tut .talkbar.pos-right{z-index:62}
.bubbles{padding-bottom:140px}
.convo.pos-left .bubbles,.convo.pos-right .bubbles{padding-bottom:12px}
.talkstatus{display:none}
.talkstatus.on{opacity:1}
.pttwrap{position:relative;display:flex;flex-direction:column;align-items:center;gap:.3rem}
/* the talk button: one solid disc in the accent with a soft halo outside it, the mark dead centre.
   Idle: blue with a black mark; recording: dark blue with a white mark (Cam, 19 Sep 2026) */
.ptt{width:104px;height:104px;border-radius:50%;border:0;padding:0;color:#0b1220;cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;
  display:flex;align-items:center;justify-content:center;background:var(--accent);
  box-shadow:0 10px 30px color-mix(in srgb,var(--accent) 35%,transparent);
  transition:transform .08s,box-shadow .15s,background .15s,color .15s}
.ptt .ico{display:block}
.ptt.muted{background:#8b1e2b;color:var(--bg);box-shadow:0 8px 26px rgba(139,30,43,.45)}   /* dark red, the mark in the background colour (Cam, 19 Sep 2026) */
.ptt .off,.ptt .help{display:none}
.ptt.muted .live{display:none}
.ptt.muted .off{display:block}
.ptt.denied{background:#5a606b;color:#fff}
.ptt.denied .live,.ptt.denied .off{display:none}
.ptt.denied .help{display:block}
.michelp{padding:0 1rem;font-size:.95rem}
.michelp p{margin:.4rem 0}
.ptt .live,.ptt .off{line-height:0}
.ptt{position:relative}
.pttlabel{color:var(--muted);font-size:.82rem;font-weight:500;letter-spacing:.01em;margin-top:.15rem}
/* the state word sits ABOVE the button: Muted while muted, nothing while live (Cam, 19 Sep 2026) */
.pttstate{color:var(--muted);font-size:.85rem;font-weight:600;min-height:1.1rem;margin-bottom:.15rem}
.latch{display:flex;flex-direction:column;align-items:center;gap:.35rem;background:transparent;border:0;color:var(--muted);font-size:.75rem;cursor:pointer;width:84px}
.latchicon{width:44px;height:26px;border-radius:13px;background:var(--line);position:relative;transition:background .15s}
.latchicon::after{content:"";position:absolute;top:3px;left:3px;width:20px;height:20px;border-radius:50%;background:var(--text);transition:transform .15s}
.latch[aria-pressed="true"] .latchicon{background:var(--bad)}
.latch[aria-pressed="true"] .latchicon::after{transform:translateX(18px)}
.latch[aria-pressed="true"]{color:var(--text)}
.pillbtn{display:inline-flex;align-items:center;gap:.45rem;height:48px;padding:0 1rem;border-radius:999px;border:1px solid var(--line);background:var(--panel);color:var(--text);font-size:.95rem;font-weight:600;cursor:pointer}
.bubble{max-width:85%;padding:.55rem .8rem;border-radius:14px;background:var(--panel);border:1px solid var(--line);align-self:flex-end;font-size:.9rem;color:var(--muted)}

/* ── the bar's layout follows the position setting ── */
.roundbtn{width:52px;height:52px;border-radius:50%;border:1px solid var(--line);background:var(--panel);color:var(--text);font-size:1.3rem;cursor:pointer;display:flex;align-items:center;justify-content:center}
.talkbar{display:grid;grid-template-columns:1fr auto 1fr;align-items:end;gap:.5rem}
.talkbar #settingsbtn{justify-self:start}
.talkbar .addbtn{justify-self:end}
/* Left and Right: no bottom bar. Everything sits in a rail on that side — the talk button with its
   centre 30% down the screen, then settings, Admin and Invite beneath it (Cam, 19 Sep 2026) */
.talkbar.pos-left,.talkbar.pos-right{position:fixed;top:0;bottom:0;left:auto;right:auto;width:132px;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:.9rem;padding:calc(30dvh - 52px) 0 0;background:color-mix(in srgb,var(--bg) 68%,transparent)}
.talkbar.pos-left{inset-inline-start:0;border-inline-end:1px solid var(--line)}
.talkbar.pos-right{inset-inline-end:0;border-inline-start:1px solid var(--line)}
.talkbar.pos-left .pttwrap,.talkbar.pos-right .pttwrap{order:-1}
.talkbar.pos-left .rightcol,.talkbar.pos-right .rightcol,.talkbar.pos-left .leftcol,.talkbar.pos-right .leftcol{align-items:center;justify-self:auto}
.talkbar.pos-left #settingsbtn,.talkbar.pos-right #settingsbtn{justify-self:auto}
.talknote{color:var(--bad);font-size:.85rem;text-align:center;min-height:0}
.talknote:empty{display:none}
.settings{padding:.4rem 1rem 1rem}
.settings .k{color:var(--muted);font-size:.85rem;margin:.2rem 0 .4rem}
.choices{display:grid;gap:.3rem;margin-bottom:.8rem}
.menu{margin-bottom:.5rem}
.menu .choices{margin:.3rem 0 .2rem}
.menu .pane{display:grid;gap:.3rem}
.pane[hidden]{display:none}
.pane.sub{animation:slidein .18s ease-out}
@keyframes slidein{from{transform:translateX(24px);opacity:0}to{transform:none;opacity:1}}
.reporow,.backrow{width:100%;justify-content:space-between;cursor:pointer;color:var(--text);font:inherit;-webkit-user-select:none;user-select:none}
.backrow{justify-content:flex-start;gap:.5rem;color:var(--muted)}
.chev{width:.6rem;height:.6rem;border-right:2px solid var(--muted);border-bottom:2px solid var(--muted)}
.chev.right{transform:rotate(-45deg);margin-inline-end:.3rem}
.chev.left{transform:rotate(135deg);margin-inline-start:.2rem}
.choice{display:flex;align-items:center;gap:.6rem;padding:.55rem .6rem;border-radius:8px;background:var(--bg);border:1px solid var(--line)}
.switchrow{justify-content:space-between}
/* ── first-run tutorial ── */
.tutorial{position:fixed;inset:0;background:rgba(0,0,0,.62);z-index:60;display:flex;align-items:flex-end;justify-content:center;padding:16px 16px 175px;overflow:auto}
.tutorial[hidden]{display:none}
/* the box moves to the top when the thing it rings is in the lower half of the screen, so it
   never sits on what it is pointing at (Cam, 21 Sep 2026); 72px clears the two corner buttons */
.tutorial.attop{align-items:flex-start;padding:72px 16px 16px}
/* while the tutorial runs the real bubbles stand aside and the sample ones stand in [R-024] */
.convo.tut .phrase:not(.tutrow),.convo.tut .topics,.convo.tut .sysnote{display:none}
.editbtn.spot{position:absolute}
.tutbox{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:1.1rem 1.2rem;max-width:360px;width:100%;display:grid;gap:.6rem;text-align:start}
.tuttitle{margin:0;font-size:1.25rem}
.tutpos{margin:.2rem 0 0}
.tutpos[hidden]{display:none}
.tutpos .menu{margin-bottom:0}
.tutpos .choices{grid-template-columns:1fr 1fr}
.spot{position:relative;z-index:62;animation:spot 1.2s ease-in-out infinite}
.talkbar .pttwrap.spotwrap{z-index:62}
@keyframes spot{0%,100%{box-shadow:0 0 0 4px var(--accent),0 0 0 10px color-mix(in srgb,var(--accent) 35%,transparent)}50%{box-shadow:0 0 0 4px var(--accent),0 0 0 16px color-mix(in srgb,var(--accent) 12%,transparent)}}
.tutcount{color:var(--muted);font-size:.85rem}
/* Back beside Next, Back first so the pair reads left to right (Cam, 21 Sep 2026) */
.tutactions{display:flex;justify-content:flex-end;align-items:center;gap:.55rem}
.tutactions .btn{min-height:44px;display:inline-flex;align-items:center}
.tutactions #tutback[hidden],.tutactions #tutlater[hidden],.tutcount[hidden]{display:none}
.tuttext{margin:0;font-size:1.05rem}

/* ── conversation mode is bare: no header, one exit control ── */
body.bare .page.fill{height:100dvh;padding-top:0}
.exitbtn{position:fixed;top:12px;inset-inline-end:12px;z-index:30;background:color-mix(in srgb,var(--panel) 72%,transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}
.bubbles{padding-top:64px}
.roundbtn.small{width:48px;height:48px}
.navitem .ico{vertical-align:-4px;margin-inline-end:.5rem}
.small{font-size:.8rem}

/* ── admin: costs and servers ── */
.admin .sub{color:var(--muted);margin:-.4rem 0 1rem}
.total{display:flex;align-items:baseline;gap:.5rem;font-size:1.05rem}
.total strong{font-size:2rem}
.meter{position:relative;height:10px;border-radius:5px;background:var(--bg);border:1px solid var(--line);margin:.6rem 0 1.2rem}
.meter .fill{height:100%;border-radius:5px;background:var(--accent);max-width:100%}
.meter.warn .fill{background:#e0a030}
.meter.over .fill{background:var(--bad)}
.meter .mark{position:absolute;top:-4px;bottom:-4px;left:75%;width:2px;background:var(--muted)}
.tbl{width:100%;border-collapse:collapse;font-size:.92rem}
.tbl th{text-align:start;color:var(--muted);font-weight:500;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;padding:.3rem .4rem}
.tbl td{padding:.5rem .4rem;border-top:1px solid var(--line);vertical-align:top}
.tbl .num{text-align:end;white-space:nowrap}
.machines{list-style:none;padding:0;margin:0 0 1rem;display:grid;gap:.6rem}
.machine{background:var(--bg);border:1px solid var(--line);border-radius:12px;padding:.8rem .9rem;display:grid;gap:.4rem}
.mhead{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}
.pill{font-size:.75rem;padding:.15rem .55rem;border-radius:999px;background:var(--line);color:var(--text);white-space:nowrap}
.pill.started{background:#1f7a3a;color:#fff}
.pill.over{background:var(--bad);color:#fff}
.mactions{display:flex;gap:.5rem}
.btn.small{padding:.35rem .8rem;font-size:.85rem}
.alloff{margin:0 0 .8rem}
.alloff .ico{vertical-align:-4px;margin-inline-end:.3rem}
.stats{display:grid;grid-template-columns:repeat(2,1fr);gap:.5rem;margin-bottom:1rem}
.stats div{background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:.5rem .7rem}
.stats .k{display:block;color:var(--muted);font-size:.75rem}
.admin h2{font-size:1.05rem;margin:1.2rem 0 .5rem}
.panelnew{padding:.2rem .9rem .8rem}
.panelnew .btn{width:100%;text-align:center}
/* the page you are on: drawn as current, not pressable */
.navitem.current{background:color-mix(in srgb,var(--accent) 18%,transparent);color:var(--text);cursor:default;font-weight:700;border-inline-start:3px solid var(--accent)}
a.btn.pressed{opacity:.6;transform:scale(.96);pointer-events:none}
.qrcaption{margin:.8rem 1.2rem 0;text-align:center;color:var(--muted);font-size:.9rem}
/* it belongs to the TITLE, not to the code: it says what the pop-up is for before you see it */
.qrwhat{margin:.35rem 1.2rem .2rem;text-align:start}

/* ── the bar's right column: Admin (admin only) above Invite ── */
.rightcol{display:flex;flex-direction:column;align-items:flex-end;gap:.4rem;justify-self:end}
.leftcol{display:flex;flex-direction:column;align-items:flex-start;gap:.4rem;justify-self:start}
.adminbtn{height:40px;font-size:.85rem;padding:0 .85rem}
.options{display:grid;gap:.3rem;padding:0 1rem}
.optbtn{width:100%;text-align:start;color:var(--text);font:inherit;cursor:pointer;text-decoration:none}
/* an option may underline ONE word of its label — the verb, marked in the catalogue (Cam, 21 Sep 2026) */
.optbtn u{text-decoration:underline;text-underline-offset:3px}
/* ── a phrase: the spoken line inverted, the translation fading in beneath (R-002) ── */
/* A BUBBLE BEING SPOKEN INTO SNAPS BETWEEN TWO WIDTHS, it does not breathe (Cam, 20 Sep 2026).
   It used to open at the full 86% with the waiting dots in it and then reflow on every word, which
   is what made the text jump about. It opens at HALF that, and goes to the full width once — and
   only once — the words need the room. Two states, so there is one step instead of a hundred. */
.phrase{max-width:86%;flex-shrink:0;overflow:hidden;transition:height .35s ease,opacity .3s ease,width .3s ease}
.phrase .inner{display:grid;gap:.25rem}
.phrase.live{width:43%}
.phrase.live.wide{width:86%}
.phrase.live .spoken{width:100%;box-sizing:border-box}
.spoken{box-sizing:border-box;transition:height .25s ease}
.spoken:empty{min-height:2.35rem;min-width:3.4rem}
.phrase .bubble{align-self:auto}
.phrase.mine{align-self:flex-end}
.phrase.mine .inner{justify-items:end}
.phrase.theirs{align-self:flex-start}
.phrase.theirs .inner{justify-items:start}
/* spoken lines: blue for me, green for the other person, white text, as a phone's messages
   (Cam, 19 Sep 2026: the white-on-black was jarring); the translation beneath in the theme's colours */
.spoken{padding:.5rem .8rem;border-radius:14px;font-size:1rem;line-height:1.35;color:#0b1220;text-align:start}
.phrase.mine .spoken{background:var(--accent)}
.phrase.theirs .spoken{background:var(--them)}
.phrase.mine .spoken{border-bottom-right-radius:4px}
.phrase.theirs .spoken{border-bottom-left-radius:4px}
.translation{padding:0 .5rem;color:var(--text);font-size:.95rem;line-height:1.35;min-height:1.2em;width:100%;box-sizing:border-box;text-align:start}
.translation .w{opacity:0;transition:opacity .4s}
.translation .w.in{opacity:1}
.who{font-size:.72rem;color:var(--muted);padding:0 .5rem;display:flex;align-items:center;gap:.35rem}
.who .flag{font-size:1rem;line-height:1}
/* the side positions carve the button's column out of the text, so lines scroll past it */
.convo.pos-left .bubbles{padding-inline-start:144px}
.convo.pos-right .bubbles{padding-inline-end:144px}
.qrtitle{margin:.2rem 2.6rem 0 1rem;font-size:1.35rem;line-height:1.2}
.flag .flagtile{display:inline-block;vertical-align:-3px;border-radius:3px;box-shadow:0 0 0 1px rgba(0,0,0,.25)}
.who .flag .flagtile{vertical-align:-2px}
.subhead{font-weight:600;padding:.1rem .2rem .2rem}
.w{cursor:pointer;border-radius:4px}
.w.hl{background:color-mix(in srgb,#ffd166 45%,transparent);color:inherit}
.spoken .w.hl{background:rgba(255,255,255,.6)}
/* THE TAUGHT WORD: one per bubble, the same amber on both lines so the eye pairs them (Cam, 22 Sep
   2026). An underline as well as a tint, because the spoken line is already a coloured bubble and
   a tint alone gets lost on it; the mark sits inside the word's span so the timing still lights
   the whole word around it. [R-072] */
mark.teach{background:color-mix(in srgb,#ffd166 55%,transparent);color:inherit;border-radius:3px;padding:0 .1em;margin:0 -.1em;box-decoration-break:clone;-webkit-box-decoration-break:clone;text-decoration:underline;text-decoration-color:#e0a800;text-decoration-thickness:2px;text-underline-offset:2px}
.spoken mark.teach{background:rgba(255,255,255,.55)}
.w.speaking mark.teach{background:transparent}
/* detached from automatic scrolling: a narrow button on the left returns to it */
/* ── things to talk about, while the conversation is empty [R-025] ── */
.topics{margin:auto 0;align-self:center;width:min(100%,360px);padding:1rem 1.1rem;border:1px solid var(--line);border-radius:14px;background:color-mix(in srgb,var(--panel) 80%,transparent);text-align:start}
.topicshead{display:flex;align-items:center;justify-content:space-between;color:var(--muted);font-size:.78rem;text-transform:uppercase;letter-spacing:.04em;margin-bottom:.4rem}
/* the countdown to the next three, and the mark that brings them now (Cam, 21 Sep 2026) */
.topicrefresh{display:inline-flex;align-items:center;gap:.3rem;background:none;border:0;color:var(--muted);font:inherit;font-size:.8rem;letter-spacing:0;text-transform:none;cursor:pointer;padding:.2rem .3rem;border-radius:6px}
.topicrefresh:active{background:var(--bg)}.topicrefresh .ico{width:14px;height:14px}
.topic .tsite{display:block;color:var(--muted);font-size:.85rem;margin-bottom:.15rem}.topic .ttarget{display:block}
.topiclist{list-style:none;margin:0;padding:0;display:grid;gap:.5rem}
/* each starter in a box of its own, so three read as three (Cam, 21 Sep 2026) */
.topic{font-size:1.02rem;line-height:1.35;padding:.6rem .75rem;border:1px solid var(--line);border-radius:10px;background:var(--bg);opacity:0;transform:translateY(4px);transition:opacity .45s ease,transform .45s ease}
.topic:nth-child(2){transition-delay:.08s}.topic:nth-child(3){transition-delay:.16s}
.topic.in{opacity:1;transform:none}
/* the card's one button: Enable microphone, or Start conversation! once it is allowed [R-026] */
.ideasgo{margin-top:.9rem;width:100%;min-height:44px;font-size:1rem}
.followbtn{position:fixed;inset-inline-start:12px;bottom:170px;width:42px;height:42px;border-radius:50%;border:1px solid var(--line);background:color-mix(in srgb,var(--panel) 85%,transparent);color:var(--text);display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:25;opacity:0;visibility:hidden;transition:opacity .2s,visibility 0s linear .2s}
.followbtn.on{opacity:1;visibility:visible;transition:opacity .2s}
.convo.pos-left .followbtn{inset-inline-start:140px}
.convo.pos-left .followbtn,.convo.pos-right .followbtn{bottom:24px}
/* the listener's join bubble and the language page */
.joinbar{justify-content:center;border-top:0;padding-bottom:12px}
.joinbubble{border-radius:999px}
.langpage .langlist{padding:0;max-height:none}
.form{display:grid;gap:.8rem}
.form label{display:grid;gap:.3rem;font-size:.9rem;color:var(--muted)}
.form input,.form select{font:inherit;padding:.6rem .7rem;border-radius:8px;border:1px solid var(--line);background:var(--bg);color:var(--text)}
.form label.choice{display:flex;color:var(--text);font-size:.9rem}

/* ── recordings: the conversation's layout in greyscale, a play button for the audio ── */
.replay .phrase.mine .spoken{background:#b9bec8;color:#0b1220}
.replay .phrase.theirs .spoken{background:#5a606b;color:#fff}
.replay .phrase{height:auto;overflow:visible}
.replay .phrase.now .spoken{outline:3px solid var(--accent);outline-offset:2px}
.replay .translation{color:var(--muted)}
.playbtn{background:#8a8f99;color:#0b1220;box-shadow:0 10px 30px rgba(0,0,0,.35)}
.playbtn.playing{background:#3a3f4b;color:#fff}
.playbtn .off{display:none}.playbtn.playing .live{display:none}.playbtn.playing .off{display:block}
.recinfo{align-self:end;padding-bottom:6px}
.recinfo .recwho{font-weight:600}
.reclist .reclink{display:grid;gap:.15rem}
.reclink .recwhen{color:var(--muted);font-size:.85rem}
.reclink .recmeta{color:var(--muted);font-size:.8rem}
/* a pressed button says so: every button dips on press; Reload also dims and reads Reloading… */
.btn:active,.pillbtn:active,.roundbtn:active{transform:scale(.96)}
.btn.pressed{opacity:.6;transform:scale(.96);transition:opacity .12s,transform .12s}
.btn{transition:transform .08s}
.dots{display:inline-flex;gap:.3rem;align-items:center;height:1.35em;padding:0 .1rem}
.dots i{width:.42rem;height:.42rem;border-radius:50%;background:currentColor;opacity:.35;animation:dotpulse 1.2s ease-in-out infinite}
.dots i:nth-child(2){animation-delay:.2s}.dots i:nth-child(3){animation-delay:.4s}
@keyframes dotpulse{0%,80%,100%{opacity:.35;transform:translateY(0)}40%{opacity:1;transform:translateY(-2px)}}
/* Edit sits OUTSIDE the bubble, to its left, as a pencil (Cam, 20 Sep 2026). It is positioned
   against the bubble's place in the scrolling list, so it travels with it. */
.bubbles{position:relative}
.editbtn{position:absolute;z-index:5;display:flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border-radius:50%;border:1px solid var(--line);background:var(--panel);color:var(--text);cursor:pointer}
.editbtn .ico{width:18px;height:18px}
.editbtn:active{transform:scale(.94)}
.wedit{font:inherit;padding:0 .25rem;border-radius:6px;border:2px solid rgba(0,0,0,.35);background:rgba(255,255,255,.85);color:#0b1220;max-width:80%}
/* the whole line, in a box beneath the translation; the translation dims because it was made
   from the words being changed and is about to be wrong */
/* the bubble's inner grid is justify-items:end for your own lines, which shrinks every child to
   its content — the edit box has to opt out of that or it comes up half-width */
.fullwrap{margin-top:.45rem;justify-self:stretch;width:100%}
.fulledit{display:block;width:100%;box-sizing:border-box;font:inherit;line-height:1.35;padding:.45rem .6rem;border-radius:8px;border:2px solid rgba(0,0,0,.35);background:rgba(255,255,255,.92);color:#0b1220;resize:none;overflow:hidden}
.translation.stale{opacity:.28;transition:opacity .25s}
/* Confirm and Cancel beneath the box */
.editactions{display:flex;justify-content:flex-end;gap:.55rem;margin-top:.55rem}
/* big enough to hit standing up holding a phone, which is the only way this is ever used (Cam, 21 Sep 2026) */
.editactions .btn{padding:.6rem 1.25rem;font-size:1rem;min-height:44px}
/* what changed, live: a word taken out stays struck through; a word put in arrives green. The
   green is chosen against the SPEAKER's light blue bubble, the only ground it lands on. */
/* a corrected line carries a green tick, on the same side the pencil offered to correct it:
   LEFT of the speaker's own bubble, and mirrored to the right for somebody else's */
.phrase.edited{padding-inline-start:34px;position:relative}
.phrase.theirs.edited{padding-inline-start:0;padding-inline-end:34px}
.editedtick{position:absolute;inset-inline-start:0;top:6px;display:flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;background:color-mix(in srgb,#42c46b 22%,transparent);color:#42c46b}
.phrase.theirs .editedtick{inset-inline-start:auto;inset-inline-end:0}
.editedtick .ico{width:16px;height:16px}
/* the pencil sits exactly here, so the tick stands down while it is out (Cam, 21 Sep 2026) */
.editedtick[hidden]{display:none}
.w.wgone{text-decoration:line-through;opacity:.45}
.w.wnew{color:#0a6b2e;font-weight:700}
.phrase.theirs .w.wnew{color:#0b3d1c}
/* who is here */
.people{list-style:none;margin:0;padding:0 1rem;display:grid;gap:.3rem}
.person{display:flex;align-items:center;gap:.5rem;padding:.5rem .6rem;border-radius:8px;background:var(--bg);border:1px solid var(--line)}
.pnum{width:1.4rem;height:1.4rem;border-radius:50%;background:var(--accent);color:var(--accent-text);font-size:.75rem;font-weight:700;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}
.pname{font-weight:600}
.crown{color:#f6b40e}
.pnote{margin-inline-start:auto;color:var(--muted);font-size:.78rem}
.peoplehead{margin:.9rem 1rem .3rem;font-size:.78rem;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
.people.listeners{margin-bottom:.4rem}
.statsgrid{padding:0 1rem}
.statsgrid .wide{grid-column:1 / -1}
.notice.ok{color:#34c759;border-color:#34c759}
.form select[multiple]{min-height:9rem}
.person .flag .flagtile{vertical-align:-3px}
.pact{margin-inline-start:auto;padding:.25rem .65rem;font-size:.8rem}
.person .pnote:empty{display:none}
.pwarn{color:#e0a030;font-size:.85rem;padding:.2rem .6rem .4rem}
.pcount{color:var(--muted);font-weight:500}
@keyframes bump{0%{color:var(--accent)}100%{color:inherit}}
.stats strong.bump{animation:bump 1.2s ease-out}
/* The people button is the ONE round button that sizes to its contents, and the selector has to
   out-rank .roundbtn.small (two classes) or the 48px pins it shut. It was losing that fight, so
   the button never grew past 48px and the flex row inside it squashed the person mark to SEVEN
   pixels wide against a 20px height — a sliver, not an icon (Cam, twice, 19 Sep 2026). */
.roundbtn.peoplebtn{width:auto;min-width:48px;padding:0 .85rem 0 .7rem;border-radius:999px;gap:.4rem}
.ptotal{font-size:1.15rem;font-weight:700;font-variant-numeric:tabular-nums;line-height:1}
/* ── listen mode: the speakers' audio plays and each word lights as it is heard ── */
.ptt .ears{display:none}
.ptt.listenmode{background:#1f7a3a;color:#fff;box-shadow:0 10px 30px rgba(31,122,58,.4)}
.ptt.listenmode .live,.ptt.listenmode .off,.ptt.listenmode .help{display:none}
.ptt.listenmode .ears{display:block;line-height:0}
.convo.listening #pttlabel{color:#34c759}
/* ── the person card: who this is, where they are from, and what you can do about it ── */
/* the card is opened FROM Who's here, so it has to sit above it — and the one behind keeps its
   own backdrop, so the page dims twice and the card is plainly the thing in front */
#personmodal{z-index:55}
#personmodal .modalbox{box-shadow:0 18px 50px rgba(0,0,0,.55)}
.personcard{text-align:center}
.pcardname{margin:.2rem 1rem .6rem}
.pcardflag{display:flex;justify-content:center;margin:.2rem 0 .5rem}
.pcardflag .flagtile,.pcardflag svg{width:64px;height:auto;border-radius:8px}
.pcardcountry{color:var(--muted);margin:0 1rem 1rem;font-size:.95rem}
.pcardbad{margin:.8rem 1rem 0;text-align:center;font-size:.85rem}
.person.tappable{cursor:pointer}
/* icon PLUS word on the card's buttons: the word carries the meaning, the mark makes it scannable */
.personcard .optbtn{display:flex;align-items:center;gap:.6rem;text-align:start}
.personcard .optbtn .ico{width:20px;height:20px;flex:none;opacity:.9}
.personcard .optbtn.pressed{opacity:.6}
.personcard .optbtn[disabled]{cursor:not-allowed}
.personcard .options[hidden]{display:none}
/* the flag of the language you are speaking: a circle like the others, the tile filling it */
/* the flag sits at the top right, immediately LEFT of the X (Cam, 20 Sep 2026) — the X is 48px
   wide and 12px from the edge, so this clears it by the same gap */
/* a PILL, not a circle: TWO flags — what you speak, then what it is translated into — so it
   reads as a different setting from the plain flag on the site header, and says at a glance
   which way the translation runs (Cam, 21 Sep 2026) */
.roundbtn.speakbtn{position:fixed;top:12px;inset-inline-start:12px;z-index:30;overflow:hidden;padding:0 .55rem 0 .5rem;width:auto;min-width:48px;border-radius:999px;gap:.35rem;background:color-mix(in srgb,var(--panel) 72%,transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}
.speakbtn .flag{display:flex;line-height:0}
/* TOP LEFT, opposite the exit button (Cam, 21 Sep 2026). It used to sit beside Leave on the
   right; the two flags say what you are speaking and what it becomes, which is about the
   conversation rather than about leaving it, so it gets its own corner. */
.roundbtn.speakbtn{inset-inline-start:12px}
/* the talk bar becomes a full-height column down that side, so the pill steps clear of it,
   exactly as the follow button already does */
.convo.pos-left .roundbtn.speakbtn{inset-inline-start:140px}
.speakbtn .pair{display:flex;align-items:center;gap:.3rem}
.speakbtn .pair .to:empty{display:none}
.sitelang{border-top:1px solid var(--line);margin-top:.9rem;padding-top:.9rem}
.speakbtn .flagtile{width:30px;height:auto;border-radius:4px;box-shadow:0 0 0 1px rgba(0,0,0,.35)}
/* ── the speaking-language pop-up ── */
/* SHORTER, AND INSET FROM THE SIDES (Cam, 21 Sep 2026). It was a full-width sheet allowed 85% of
   the screen; it is now a card standing clear of both edges, capped lower, with all four corners
   rounded so it reads as a card rather than a drawer. */

/* THE LIST SCROLLS. .modalbox is a flex column capped at 85vh, and a flex child will not scroll
   unless it is told it may shrink — without min-height:0 it grows to its content and the fifty-odd
   languages simply ran off the bottom of the screen with no way to reach them (Cam, 21 Sep 2026). */
#speakmodal .pane{padding:0 1rem;overflow-y:auto;min-height:0;flex:1;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
#speakmodal .langgroup{padding:0}
#speakmodal .langrow{border-radius:10px;padding:.6rem .7rem}
/* the one you are on is a filled box, not a bare outline */
#speakmodal .langrow.selected{background:color-mix(in srgb,var(--accent) 20%,transparent);border:1px solid var(--accent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--accent) 35%,transparent)}
#speakmodal .langrow.selected .lname{font-weight:700}
#speakmodal .modalactions{padding:1.1rem 1rem 1.2rem;display:flex;justify-content:center}
/* the two settings, each a box you press to change it */
.langsetting{padding:.9rem 0 .2rem}
.langbox{display:flex;align-items:center;gap:.55rem;width:100%;padding:.75rem .85rem;margin-top:.35rem;
  background:var(--bg);border:1px solid var(--line);border-radius:12px;color:var(--text);font:inherit;cursor:pointer;text-align:start}
.langbox:active{transform:scale(.99)}
/* a row being saved dims; one that failed flashes red and comes back */
#speakmodal .langrow.saving{opacity:.6}
#speakmodal .langrow.failed{box-shadow:inset 0 0 0 2px var(--bad)}
.langbox .lname{font-weight:700}
.langbox .lenglish{margin-inline-start:auto;color:var(--muted);font-size:.9rem}
.langbox-go{display:flex;color:var(--muted);margin-inline-start:.35rem}
.langbox-go .ico{width:16px;height:16px;transform:rotate(-90deg)}
.backline{display:flex;align-items:center;gap:.6rem}
#speakmodal .btn.ghost.small{padding:.3rem .7rem;border:1px solid var(--line);color:var(--muted)}
.leftrow{display:flex;align-items:center;gap:.4rem}
.peoplebtn .ico{width:1.6em;height:1.6em;flex:none}   /* flex:none — an svg in a flex row is shrinkable, and was being shrunk */
.peoplebtn{font-size:1.05rem}
.listenbtn[aria-pressed="true"],.listenpill[aria-pressed="true"]{background:#1f7a3a;color:#fff;border-color:#1f7a3a}
.w.speaking{background:rgba(255,255,255,.55);color:#0b1220;border-radius:4px}
.translation .w.speaking{background:color-mix(in srgb,#34c759 40%,transparent);color:inherit}
.phrase.playing .spoken{outline:2px solid #34c759;outline-offset:2px}
.listenpill{gap:.4rem}

/* ── ON A WIDE SCREEN THE APP IS A COLUMN, and the rest is wallpaper (Cam, 20 Sep 2026) ──
   THIS BLOCK LIVES LAST ON PURPOSE. A media query adds no specificity, so when it sat at the top
   of the file every later rule beat it: the bar kept left:0;right:0 and stayed stuck to the window
   rather than the column, and the X stayed 12px from the window edge instead of the column edge.
   A phone IS the column, so nothing here applies below the breakpoint. On a laptop the app would
   otherwise stretch to whatever width the window happens to be, which reads as a page that has
   not finished loading. It sits in 1000px and the space either side carries a motif themed to the
   language, at an opacity chosen so you notice it only if you look for it. */
:root{--app-w:1000px}
.wallpaper{display:none}
@media (min-width:1060px){
  .wallpaper{display:block;position:fixed;inset:0;width:100%;height:100%;z-index:0;pointer-events:none;color:var(--text);opacity:.055}
  :root[data-theme="light"] .wallpaper{opacity:.075}
  /* the column itself, and the fixed furniture that has to line up with it */
  .top,.page,.talkbar{width:min(100%,var(--app-w));margin-inline:auto}
  .top{position:sticky;z-index:2}
  .page{max-width:var(--app-w)}
  .talkbar{left:50%;right:auto;transform:translateX(-50%)}
  .exitbtn{inset-inline-end:max(12px,calc((100vw - var(--app-w))/2 + 12px))}
  .speakbtn{inset-inline-start:max(12px,calc((100vw - var(--app-w))/2 + 12px))}
  /* ONLY the page column is lifted above the wallpaper. NOT .modal or .panel: both are
     position:fixed, and forcing them to relative dropped every pop-up out of its overlay and into
     the bottom of the page. They already sit at z-index 40-50, far above the wallpaper's 0, so
     they never needed touching at all. */
  main,.top{position:relative;z-index:1}
  /* the column reads as the page; the wallpaper is what it sits on */
  .top,main{background:var(--bg)}
}

/* ── CREDITS A TALKER EARNS (R-027) ──────────────────────────────────────────────────────── */
/* HOW MANY CREDITS ARE LEFT, bottom left beside the settings button, where Cam asked for it. The
   number sits beside an abstract credit mark rather than the word "left" — which also spares every
   catalogue a translation. It is a button: pressing it opens the rates. [R-044] */
.tokenbtn{font-variant-numeric:tabular-nums;font-size:.85rem;display:inline-flex;align-items:center;gap:.28rem;white-space:nowrap}
.tokenbtn .ico,.credits .ico,.tokenbig .ico{flex:none;opacity:.75}
/* the listener's readout, top, is the same thing and opens the same pop-up */
.creditsbtn{background:none;border:0;font:inherit;cursor:pointer;padding:.35rem .85rem;display:inline-flex;align-items:center;gap:.35rem}
/* RUNNING LOW IS SAID IN COLOUR (Cam, 21 Sep 2026): orange is a warning, red is almost out. It
   colours the mark as well as the number, so it reads at a glance and not only on inspection. */
.low,.low .ico{color:#f2b53c}
.verylow,.verylow .ico{color:#ff5a5a}

/* THE POP-UP: the balance large, then the two rates as CHOICES with the one in force lit, then
   the ways to get more. */
.tokenbig{font-size:2rem;font-weight:700;margin:.2rem 0 0;text-align:center;font-variant-numeric:tabular-nums;display:flex;align-items:center;justify-content:center;gap:.4rem}
.tokenearned{margin:.1rem 0 .6rem;text-align:center;color:#34c759;font-size:.9rem}
.tokenearned[hidden]{display:none}
.rates{margin:.4rem 0 0;display:grid;gap:.4rem}
.rate{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;
  padding:.62rem .7rem;border:1px solid var(--line);border-radius:10px;color:var(--muted);
  background:none;font:inherit;text-align:start;cursor:pointer}
.rate b{font-variant-numeric:tabular-nums;font-weight:600}
.rate.now{border-color:#5b9cff;color:var(--fg)}
.rate.now b{color:#5b9cff}
.rate[data-locked]{opacity:.5;cursor:not-allowed}
/* a warning the person brought on themselves by pressing: yellow, under the thing they pressed */
.ratewarn{margin:.45rem 0 0;color:#f2b53c;font-size:.82rem;text-align:center}
.ratewarn[hidden]{display:none}
.getmore{margin-top:1rem;border-top:1px solid var(--line);padding-top:.8rem}
.getmore .langscript{margin-bottom:.5rem}
.getbtn{width:100%;margin-top:.2rem}
.earnbracket{margin:.3rem 0 .7rem;text-align:center;color:var(--muted);font-size:.82rem}

/* THE EARN LINE under Get as link: smaller, in brackets, and it must stay INSIDE the box — it was
   overflowing (Cam, 21 Sep 2026). */
.earnnote{margin:.5rem 0 0;color:var(--muted);font-size:.78rem;line-height:1.35;text-align:center;
  max-width:100%;overflow-wrap:anywhere}

/* ── WHO IS READING THIS FILE ────────────────────────────────────────────────────────────────
   A built page is one file for everybody, so it carries both the signed-in and the signed-out
   header and shows one. The switch is an attribute on <html>, set by a script in the head before
   the page paints — the same trick the theme already uses — so a signed-in person never sees
   "Log in" flash past on the way to their own name. Without JavaScript the page stays as it was
   built, which is the signed-out state, and every link on it still works. [R-051] */
[data-signed="out"] [data-when="in"],
[data-signed="in"] [data-when="out"] { display: none !important; }
[data-signed="out"] .panel,
[data-signed="out"] .panelback { display: none !important; }
/* The admin section of the panel is in every built page and shown to an admin only. It is not a
   permission — the service checks every admin call regardless, and always did. [R-051] */
.adminonly[hidden] { display: none !important; }

/* HEAR THIS LINE: the mark sits in the same column as the pencil and the corrected tick — outside
   the bubble's edge. It is positioned from script rather than by the cascade, because which side
   it lands on depends on whose line it is, and that is mirrored. [R-055] */
/* `display:flex` beats the `hidden` attribute's own `display:none`, so hiding it by setting
   `hidden` left it on screen — the same trap the admin panel section fell into. */
.saybtn[hidden]{display:none !important}
.saybtn{position:absolute;z-index:5;display:flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border-radius:50%;border:1px solid var(--line);background:var(--panel);color:var(--text);cursor:pointer}
.saybtn .ico{width:18px;height:18px}
.saybtn:active{transform:scale(.94)}
/* while it is reading, the mark pulses — the only sign anything is happening on a silent phone */
.saybtn.saying{border-color:var(--accent);color:var(--accent);animation:saypulse 1.1s ease-in-out infinite}
@keyframes saypulse{0%,100%{opacity:1}50%{opacity:.55}}

/* LET'S GO! above the conversation ideas — the line that starts the thing, in enlarged characters
   (Cam, 22 September 2026). It sits at the top of the ideas card, above the card's own quiet
   uppercase heading, so the card reads as an invitation rather than a list. */
.letsgo{font-size:1.7rem;line-height:1.1;font-weight:800;letter-spacing:-.01em;text-align:center;margin:.1rem 0 .7rem}

/* WHAT YOU EARN SITS ON THE BUTTON, under its words, rather than floating beneath it — it is a
   fact about pressing that button, so it belongs inside it (Cam, 22 September 2026). */
.getbtn.stacked{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.12rem;line-height:1.2;padding-block:.6rem}
.getbtn.stacked .earnin{font-size:.78rem;font-weight:600;opacity:.75}

/* NOTHING CHOSEN TO LEARN YET: a question mark that takes exactly a flag's space, so the pair in
   the speak button always reads as two things — one of which is unanswered — rather than one.
   Its size is tied to the flag beside it; change one and change both. */
.speakbtn .flagq{display:flex;align-items:center;justify-content:center;width:30px;height:20px;border-radius:4px;
  box-shadow:0 0 0 1px rgba(0,0,0,.35);background:color-mix(in srgb,var(--text) 14%,transparent);
  color:var(--text);font-size:.8rem;font-weight:800;line-height:1}

/* THE "or" BETWEEN THE TWO WAYS IN. A rule across the card with the word sitting in a gap in it,
   so the link button above and the password below read as two alternatives rather than two steps
   of one form. The line is drawn by a border on the pseudo-elements either side, which is what
   lets the word keep the card's own background without knowing what colour that is. */
.or{display:flex;align-items:center;gap:.75rem;margin:1.1rem 0;color:var(--muted);font-size:.85rem}
.or::before,.or::after{content:"";flex:1;height:1px;background:var(--line)}

/* THE SAME SHAPE AS THE BUTTON ABOVE IT, AND PLAINLY NOT ONE. It carries a fact rather than an
   action, so it keeps the button's size, radius and typography — the two read as a pair of ways to
   earn — but takes the panel surface instead of the accent fill, and neither the pointer nor the
   press. A filled button that cannot be pressed is a trap; this is the honest half of the
   likeness. [R-059] */
.getinfo{display:inline-block;width:100%;margin-top:.2rem;border-radius:999px;border:1px solid var(--line);
  background:color-mix(in srgb,var(--panel) 60%,transparent);color:var(--muted);
  font-size:1.15rem;font-weight:600;text-align:center;padding:.85rem 1.6rem;cursor:default;user-select:none}
.getinfo.stacked{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.12rem;line-height:1.2;padding-block:.6rem}
.getinfo .earnin{font-size:.78rem;font-weight:600;opacity:.8}

/* ROOM AT THE SIDES INSIDE THE CREDITS POP-UP. The title and the actions carried their own, so
   everything between them ran to the edges of the box and the rows looked wedged in (Cam, 22
   September 2026). Scoped to this pop-up rather than added to every modal, because the others lay
   their own contents out. */
#tokenmodal .tokenbig,
#tokenmodal .tokenearned,
#tokenmodal .rates,
#tokenmodal .ratewarn,
#tokenmodal .getmore{padding-inline:1rem}

/* AN INCIDENT, CLOSED: how long ago, who it happened to, and what happened. Everything else — the
   page, the conversation, the category and the whole browser string — is behind the fold, because
   twenty rows each carrying a Mozilla/5.0 is a wall with the sentences that matter buried in it.
   [R-067] */
.inc .incwhen{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
.whotag{display:inline-block;padding:.05rem .45rem;border-radius:999px;border:1px solid var(--line);font-size:.7rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase}
/* A fault hit while building is expected; one hit by somebody at a language night is the app
   failing in the world. The two should not look the same at a glance. */
.whotag.dev{color:var(--muted)}
.whotag.user{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 45%,transparent)}
.incfacts{display:grid;grid-template-columns:auto 1fr;gap:.2rem .7rem;margin:0 0 .6rem;font-size:.82rem}
.incfacts dt{color:var(--muted)}
.incfacts dd{margin:0;overflow-wrap:anywhere}

/* THE TUTORIAL'S X, top corner of its box, and the question it asks before closing — asked in the
   box itself because nothing may open over the tutorial. */
.tutbox{position:relative}
.tutclose{position:absolute;top:.35rem;inset-inline-end:.5rem;z-index:1;width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:50%}
.tutclose:hover{color:var(--text)}
.tutstep{display:grid;gap:.6rem}
.tutstep[hidden],.tutconfirm[hidden]{display:none}
.tutconfirm{display:grid;gap:.6rem;padding-top:.6rem}
