/* Trap Phone — the approved olive theme (mockups/trapphone-theme.css), adopted
   by the app on 08/14/2026 so the homepage and the app are one product.
   Palette verbatim from the theme sheet; every pairing there clears WCAG AA.
   Gold is the primary action and appears once per screen, never as a ground.
   Red appears only on an action that ends something. */
:root{
  --bg:#F4F3EE; --panel:#FFFFFF; --ink:#232B24; --ink2:#4E574F;
  --mute:#616A64; --line:#E2E2DB; --accent:#4F6350; --accent-ink:#F4F7F3;
  --deep:#3F5140; --gold:#D9B437; --gold-ink:#232B24; --pale:#EDF1EA;
  /* gold lifted just enough to clear AA on dark grounds (theme sheet) */
  --gold-on-deep:#E3C255;
  --accent-lift:#7FB48C; --accent-deep:#1B4A32;
  --mine:#E7EDE6; --mine-line:#D3DED2; --flag:#9A3B2E;
  --serif: ui-serif,"New York",Georgia,"Iowan Old Style","Noto Serif",serif;
  --sans: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
  --mono: ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}
/*
 * Dark, as a set of tokens swapped on <html data-theme="dark">.
 *
 * NOT @media (prefers-color-scheme: dark), and that is deliberate. There was a
 * media query here for about ten minutes on 08/20/2026 covering the sender
 * colours alone, and it made every group message unreadable: the bubbles went
 * dark on a phone set to dark, the page stayed cream, the ink stayed dark. A
 * theme has to move EVERY token or none, and it has to be something the person
 * chose — the phone's own switch is one decision for everything they own, and
 * this app is read in bed more than most.
 *
 * Three grounds are kept here because the choice between them is a real one and
 * changing it is a single attribute. Black is the shipped default.
 */
:root[data-theme="dark"]{
  --bg:#000000; --panel:#0C0C0D; --ink:#F4F4F3; --ink2:#B9BCBA;
  --mute:#8A8E8C; --line:#26282A; --accent:#8FB894; --accent-ink:#08120C;
  --deep:#0F1A13; --gold:#E3C255; --gold-ink:#241E07; --pale:#131514;
  --gold-on-deep:#E3C255; --accent-lift:#7FB48C; --accent-deep:#8FB894;
  --mine:#1C2A22; --mine-line:#31473A; --flag:#E08A7C;
}
:root[data-theme="forest"]{
  --bg:#0A1A12; --panel:#102A1D; --ink:#E2F2E8; --ink2:#A2C4AF;
  --mute:#7EA48D; --line:#1E4835; --accent:#7FD69B; --accent-ink:#06170E;
  --deep:#0C2318; --gold:#E6CB63; --gold-ink:#241E07; --pale:#143024;
  --gold-on-deep:#E6CB63; --accent-lift:#9FE3B5; --accent-deep:#7FD69B;
  --mine:#17402B; --mine-line:#276048; --flag:#E89B8C;
}
:root[data-theme="ember"]{
  --bg:#1A1512; --panel:#241D18; --ink:#F3EAE0; --ink2:#C4B4A5;
  --mute:#9C8C7D; --line:#3B2E26; --accent:#C9A97A; --accent-ink:#1A1209;
  --deep:#2A211B; --gold:#E8C87C; --gold-ink:#241E07; --pale:#2A211B;
  --gold-on-deep:#E8C87C; --accent-lift:#DCC198; --accent-deep:#C9A97A;
  --mine:#332720; --mine-line:#4C3B30; --flag:#E5A18F;
}
/* The sender colours travel too. A wash is mixed toward whatever ground is on
   rather than lightened, so none of them glow. Without this block the eight
   washes stay pale-on-cream and the text on them is unreadable — which is
   exactly the bug that made this a data-theme and not a media query. */
:root[data-theme="dark"] .mk0{--w:#F0A472;--wash:#2A1F18}
:root[data-theme="dark"] .mk1{--w:#6FD3E0;--wash:#0F2A31}
:root[data-theme="dark"] .mk2{--w:#D6A6E8;--wash:#26192E}
:root[data-theme="dark"] .mk3{--w:#DCD06A;--wash:#2A2714}
:root[data-theme="dark"] .mk4{--w:#7FD69B;--wash:#12291A}
:root[data-theme="dark"] .mk5{--w:#F294AB;--wash:#2E1820}
:root[data-theme="dark"] .mk6{--w:#93B0F0;--wash:#171F35}
:root[data-theme="dark"] .mk7{--w:#E0C088;--wash:#2A2216}
:root[data-theme="forest"] .mk0{--w:#F5B084;--wash:#2A2018}
:root[data-theme="forest"] .mk1{--w:#7FE0EA;--wash:#0F3038}
:root[data-theme="forest"] .mk2{--w:#DDB0EE;--wash:#2A2038}
:root[data-theme="forest"] .mk3{--w:#E4D877;--wash:#2C2A18}
:root[data-theme="forest"] .mk4{--w:#8FE3A9;--wash:#123322}
:root[data-theme="forest"] .mk5{--w:#F79FB5;--wash:#331C26}
:root[data-theme="forest"] .mk6{--w:#A3BDF7;--wash:#1A2440}
:root[data-theme="forest"] .mk7{--w:#EACC95;--wash:#2C2618}
:root[data-theme="ember"] .mk0{--w:#F0A96F;--wash:#33261C}
:root[data-theme="ember"] .mk1{--w:#8ED2DC;--wash:#1B2E32}
:root[data-theme="ember"] .mk2{--w:#D6ACE0;--wash:#2E2430}
:root[data-theme="ember"] .mk3{--w:#DCCE7B;--wash:#302B1B}
:root[data-theme="ember"] .mk4{--w:#94CFA6;--wash:#1E2E22}
:root[data-theme="ember"] .mk5{--w:#EDA0B0;--wash:#331F26}
:root[data-theme="ember"] .mk6{--w:#A3B6E4;--wash:#20263A}
:root[data-theme="ember"] .mk7{--w:#E2C490;--wash:#302818}

*{box-sizing:border-box}
/* The page itself must never scroll. Everything is a fixed, full-height view
   with its own internal scroller (.thread). Without this, iOS Safari scrolls
   the whole document and you get the header OR the composer, never both --
   which is exactly what happened on a real iPhone (08/05/2026). */
html,body{height:100%;overflow:hidden;overscroll-behavior:none}
body{
  margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);
  font-size:16px;line-height:1.5;-webkit-text-size-adjust:100%;
  overscroll-behavior-y:none;
}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}

/* position:fixed + inset:0 is measured against iOS Safari's LARGE viewport --
   the size the page would be if the URL bar and toolbar were hidden. With the
   chrome actually on screen, the bottom of the view sits below the visible
   area, so the last flex child (the composer) was invisible on a real iPhone:
   the thread rendered, and there was no way to type.

   dvh was tried first and was NOT enough: iOS 26 Safari's floating bottom bar
   still overlapped the composer, and the page scrolled so you saw the header
   or the composer but never both. svh is the SMALL viewport -- the height with
   browser chrome fully expanded -- so it is the only value guaranteed to be
   visible at all times. The cost is a little unused space when the chrome
   hides; the benefit is that the composer can never be unreachable. The vh
   line stays first as a fallback for anything that does not know svh. */
/* --kb is how much of that small viewport the on-screen keyboard and Apple's
   form bar are covering, measured live from visualViewport. Without it the
   page stayed sized for a screen with no keyboard on it, so the composer sat
   where it would have sat anyway and left a band of dead white between itself
   and the keyboard. Zero whenever nothing is focused, so the reasoning above
   is untouched the rest of the time. */
.view{position:fixed;inset:0;height:100vh;height:100svh;height:calc(100svh - var(--kb, 0px));
  display:flex;flex-direction:column;background:var(--bg);overflow:hidden}
.view[hidden]{display:none}
.pad{padding:20px;flex:1;display:flex;flex-direction:column;overflow-y:auto}
.pad.center{justify-content:flex-start}
.grow{flex:1}

.display{font-family:var(--serif);font-size:clamp(28px,8vw,34px);line-height:1.05;
  letter-spacing:-.03em;font-weight:600;margin:0}
.say{color:var(--ink2);font-size:14px;line-height:1.55;margin:14px 0 0}
.say.sm{font-size:13px}
.fine{color:var(--mute);font-size:12px;line-height:1.5;margin:12px 0 0;text-align:center}
.reqs{list-style:none;padding:0;margin:22px 0 0;display:flex;flex-direction:column;gap:9px}
.reqs li{display:flex;align-items:center;gap:10px;font-size:13.5px;color:var(--ink2)}
.reqs .x{width:18px;height:18px;border-radius:50%;border:1px solid var(--line);flex:none;
  display:grid;place-items:center;font-size:9px;color:var(--mute)}
.reqs s{text-decoration:line-through;text-decoration-color:var(--mute);opacity:.8}

/* The primary action is gold, per the theme's rule and the onboarding
   contract's frames: gold once per screen, dark ink on it, never a ground. */
.btn{margin-top:22px;background:var(--gold);color:var(--gold-ink);border:0;border-radius:12px;
  padding:14px;font-size:15px;font-weight:700;font-family:inherit;cursor:pointer;width:100%}
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink);margin-top:10px;font-weight:600}
.btn:active{transform:translateY(1px)}
.btn:focus-visible,.icon:focus-visible,.tab:focus-visible,.input:focus-visible,
.field:focus-visible,.send:focus-visible,.attach:focus-within{outline:2px solid var(--accent);outline-offset:2px}

.bar{flex:none;display:flex;align-items:center;gap:10px;padding:14px 16px 10px;
  border-bottom:1px solid var(--line);background:var(--panel);
  padding-top:calc(14px + env(safe-area-inset-top))}
.title{font-family:var(--serif);font-size:24px;letter-spacing:-.025em;font-weight:600;margin:0;flex:1}
.title.sm{font-size:17px}
.icon{background:none;border:0;color:var(--accent);font-size:22px;line-height:1;cursor:pointer;
  padding:4px 8px;font-family:inherit}
.who{flex:1;min-width:0;background:none;border:0;padding:0;text-align:left;
  font-family:inherit;color:inherit;cursor:pointer}
.who-n{display:block;font-size:15px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chip{background:none;border:1px solid var(--mine-line);color:var(--accent);border-radius:99px;
  padding:4px 9px;font-family:var(--mono);font-size:10px;cursor:pointer}

.idcard{flex:none;margin:12px 16px;padding:12px 13px;border:1px solid var(--line);border-radius:12px;
  background:var(--panel);display:flex;align-items:center;gap:10px}
.idname{font-size:13px;font-weight:600}
/* Where the address used to be rendered. It is a sentence now, not a value:
   nothing on this card can be screenshotted and handed to anybody. */
.idsub{font-size:11.5px;color:var(--mute);margin-top:2px}
.idcard>div:first-child{flex:1;min-width:0}
.pill{font-family:var(--mono);font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;
  padding:4px 8px;border-radius:99px;background:var(--line);color:var(--mute);flex:none}
.pill.on{background:var(--mine);color:var(--accent)}

/* The chats screen is now two lists plus their section headers, so the
   scroller is the wrapper and the lists themselves size to their content.
   min-height:0 for the same reason as .hold below: without it a flex child
   refuses to shrink under its content and .view's overflow:hidden slices it. */
.chatscroll{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
  position:relative;z-index:1;background:var(--bg);will-change:transform;
  /* The browser's own bounce is ours to draw now (see the pull in app.js);
     without this iOS rubber-bands the page underneath the gesture. */
  overscroll-behavior-y:contain}
.list{list-style:none;margin:0;padding:0}
.list li{display:flex;gap:12px;padding:12px 16px;align-items:center;
  border-bottom:1px solid var(--line);cursor:pointer}
/* An invite nobody has answered yet: a person, greyed, so four invites out at
   once are four names rather than four identical rows. */
.list li.wait .av{background:var(--line);border-color:var(--line);color:var(--mute)}
.list li.wait .l1 b{color:var(--ink2)}

.list .av{width:36px;height:36px;border-radius:50%;flex:none;display:grid;place-items:center;
  background:var(--mine);border:1px solid var(--mine-line);font-family:var(--serif);
  font-size:14px;font-weight:600;color:var(--accent)}
/* display:block, and that is the whole of the fix.
   .c and .l2 are spans, so both defaulted to display:inline — and an inline box
   cannot be clipped: overflow:hidden and text-overflow:ellipsis below are
   declared on .l2 and were doing NOTHING. A long group preview ran 265px past
   the right edge of a 390px phone and straight under the unread pill, which is
   what a screenshot on 08/20/2026 showed. The column was always the right
   width (276px); the line inside it just refused to stop. */
.list .c{flex:1;min-width:0;display:block;overflow:hidden}
.list .l1{display:flex;justify-content:space-between;gap:8px;align-items:baseline}
.list .l1 b{font-size:14.5px;font-weight:600}
.list .l1 em{font-style:normal;font-size:10.5px;color:var(--mute)}
.list .l2{display:block;font-size:12.5px;color:var(--mute);margin-top:1px;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
/* The per-chat unread COUNT (mockups/unread-count, 08/17/2026): an olive pill
   where the 8px dot used to sit, and the row goes bold, which is how every
   messenger says new without needing to be told. Zero unread draws nothing. */
.cnt{flex:none;min-width:22px;height:22px;border-radius:11px;background:var(--accent);
  color:var(--accent-ink);font-size:12px;font-weight:800;display:grid;place-items:center;padding:0 6px}
.list li.new .l1 b{font-weight:700}
.list li.new .l2{color:var(--ink2);font-weight:600}
.empty{color:var(--mute);font-size:13.5px;text-align:center;padding:28px 20px;margin:0}
.empty[hidden]{display:none}

/* The tabs used to be text characters: <i>◉</i> and <i>⚙</i>. iOS renders
   U+2699 as Apple's full colour 3D emoji gear, so a shiny cog was sitting in a
   flat sage and dove interface. They are drawn strokes now. Never put a glyph
   in here that a font is free to reinterpret as emoji. */
.tabbar{flex:none;display:flex;border-top:1px solid var(--line);background:var(--panel);
  padding:9px 0 10px;padding-bottom:calc(10px + env(safe-area-inset-bottom))}
.tab{flex:1;background:none;border:0;padding:0;color:var(--mute);font-size:11px;letter-spacing:-.01em;
  font-family:inherit;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:5px;
  position:relative}
/* The Chats tab's total: red, because it mirrors the home screen icon's badge,
   which is the platform's red. Inside the rows the count is olive, so it
   belongs to this product; the two reds agree with each other instead. */
.tabcnt{position:absolute;top:-2px;left:50%;margin-left:6px;min-width:18px;height:18px;border-radius:9px;
  background:var(--flag);color:#FFF4F1;font-size:11px;font-weight:800;display:grid;place-items:center;padding:0 5px}
.tabcnt[hidden]{display:none}
.tab svg{width:23px;height:23px;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.tab.is-on{color:var(--accent);font-weight:700}
.tab.is-on svg{stroke-width:2}

/* ---------------------------------------------------------------- settings */
.setwrap{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding-bottom:12px}
.sect{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--mute);
  font-weight:800;padding:20px 18px 7px}
.srow{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:13px 18px;background:var(--panel);border:0;border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);font-family:inherit;text-align:left;color:var(--ink)}
.srow[hidden]{display:none}
.srow + .srow{border-top:0}
.srow .sc{display:block;min-width:0}
.srow .k{display:block;font-size:14.5px}
.srow .v{display:block;font-size:12px;color:var(--mute);margin-top:2px}
/* A fixed column, so an EMPTY chevron still holds its place. The phrase row
   blanks its own arrow once a phrase is set (app.js sets textContent to ''),
   which collapsed the element to nothing and dragged that row's ON pill 60px
   further right than the ON pill above it. Two pills on one screen at two
   different x positions reads as a mistake, because it is one. */
.srow .go{color:var(--mute);font-size:17px;flex:none;width:10px;text-align:center}
/* And the pill always sits against that column rather than wherever the text
   above it happens to end. */
.srow .pill{margin-left:auto}
.srow.tap{cursor:pointer}
.srow.tap:active{background:var(--bg)}
.srow.danger .k{color:var(--flag);font-weight:700}
/* The notifications row's two loud states (mockups/notifications-door).
   Green when one tap here can fix it; amber when the fix lives outside the
   app, so the row says so before anybody taps. The amber chip pins its own
   ink rather than borrowing theme tokens: a fixed light ground under a dark
   theme's light ink is the exact pairing that has made text vanish here
   before, so nothing on it may drift with the theme. */
.srow.offed{border:1.5px solid var(--accent);background:var(--pale)}
.srow.offed .v{color:var(--accent-deep);font-weight:600}
.srow.held{border:1.5px solid #E3C255;background:#FAF3DC}
.srow.held .k{color:#232B24}
.srow.held .v{color:#7A5E10;font-weight:600}
.srow.held .go{color:#7A5E10}

/* log out sheet extras (it reuses .link-sheet / .ls-card) */
.lo-ok{background:var(--mine);border:1px solid var(--mine-line);border-radius:11px;padding:11px 12px;
  font-size:12.5px;color:#2E4A32;margin-top:12px;line-height:1.45;font-weight:600}
.lo-warn{background:#FBF0EE;border:1px solid #EBD6D2;border-radius:11px;padding:11px 12px;
  font-size:12.5px;color:var(--flag);margin-top:12px;line-height:1.45;font-weight:600}
.lo-ok[hidden],.lo-warn[hidden]{display:none}
.btn.danger{background:var(--flag);color:#fff;border:0}

/* ---- thread ---- */
.thread{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:9px}
.day{align-self:center;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.m{max-width:84%;display:flex;flex-direction:column;gap:3px}
.m.out{align-self:flex-end;align-items:flex-end}
.m.in{align-self:flex-start}
.b{padding:9px 12px;font-size:14px;line-height:1.45;border-radius:16px;word-wrap:break-word;
  overflow-wrap:anywhere;white-space:pre-wrap}
.m.in .b{background:var(--panel);border:1px solid var(--line);border-bottom-left-radius:5px}
/* Outgoing is the olive bubble of the theme sheet (.bub.me): accent ground,
   light type, per the onboarding contract's I2 frame. */
.m.out .b{background:var(--accent);border:1px solid var(--accent);color:var(--accent-ink);border-bottom-right-radius:5px}
/* touch-action kills the browser's own double-tap-to-zoom on the bubble. A
   photo in a thread had no tap handler at all, so a double tap fell through to
   the page and zoomed it — the app looked broken while doing nothing. */
.b.media{padding:4px;overflow:hidden;touch-action:manipulation;cursor:zoom-in}
.b.media img,.b.media video{display:block;max-width:100%;width:230px;max-height:300px;
  border-radius:12px;object-fit:cover;background:var(--line)}
.b.media video{object-fit:contain}
/* A line the conversation says about itself. Centred, small, no bubble and no
   sender, because it did not come from a person and must not look as though it
   did. Without this rule it rendered as full width body copy, which read like
   the first message rather than like a caption. */
.sysline{align-self:center;text-align:center;font-size:11.5px;line-height:1.45;
  color:var(--mute);padding:8px 18px;max-width:88%}
.meta{font-size:10.5px;color:var(--mute)}
.meta .burn{color:var(--accent)}
.pending{opacity:.55}

/* The thread header name opens contact info now, where renaming is a labelled
   row rather than a hidden tap on a title nothing said was tappable. */
.who:active .who-n{text-decoration:underline}
.who:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}

/* min-width:0 lets the flex row actually shrink to the screen; without it the
   send button was pushed off the right edge on a 390px phone. */
/* The bottom inset keeps the message box clear of the home indicator — right
   with the keyboard down, wrong with it up. iOS goes on reporting the inset
   while the keyboard is covering the very thing it reserves room for, so the
   composer sat on a band of white it did not need. --kb is 0 whenever nothing
   is focused, so the guard only lifts while the keyboard is actually there. */
.composer{flex:none;display:flex;align-items:center;gap:9px;padding:10px 12px;
  width:100%;min-width:0;box-sizing:border-box;
  border-top:1px solid var(--line);background:var(--panel);
  padding-bottom:calc(10px + env(safe-area-inset-bottom))}
.kbup .composer{padding-bottom:10px}
.attach{width:36px;height:36px;border-radius:50%;flex:none;display:grid;place-items:center;
  background:var(--bg);border:1px solid var(--line);color:var(--accent);font-size:19px;cursor:pointer}
/* 16px is not a taste decision, it is the iOS threshold. Safari auto zooms the
   page whenever a text field smaller than 16px receives focus, and it does not
   zoom back out. Once zoomed, the layout viewport is wider than the visual one,
   so the whole app can be dragged sideways: the header goes off the top and the
   send button hangs off the right. That is what "it breaks when I send a
   message" was, because tapping the composer is what triggers it.
   Never let a focusable field in this app go below 16px. */
.field{flex:1;background:var(--bg);border:1px solid var(--line);border-radius:18px;
  padding:10px 13px;font-size:16px;font-family:inherit;color:var(--ink);min-width:0}
.send{width:36px;height:36px;border-radius:50%;background:var(--accent);color:var(--accent-ink);
  border:0;display:grid;place-items:center;font-size:16px;flex:none;cursor:pointer}
.uploading{position:fixed;left:0;right:0;bottom:74px;text-align:center;font-size:12px;
  color:var(--accent);pointer-events:none}
.uploading[hidden]{display:none}

/* ---- adding people: codes, requests, sharing ---- */
.display.sm{font-size:clamp(22px,6.4vw,26px)}
.say.mid{text-align:center}
.lbl{display:block;font-size:12.5px;color:var(--ink2);font-weight:600;margin:15px 0 6px}
/* 16px for the same reason as .field above: below it, focusing zooms iOS.
   That applies to the textarea and the code field too, hence no override. */
.input{width:100%;background:var(--panel);border:1px solid var(--line);border-radius:12px;
  padding:12px 13px;font-size:16px;font-family:inherit;color:var(--ink);margin:0}
.input.on{border-color:var(--accent);border-width:1.5px}
.input.code{font-family:var(--mono);letter-spacing:.08em;font-size:19px;text-align:center;font-weight:700}
.input.area{min-height:74px;resize:none;line-height:1.45}
.input::placeholder,.input.code::placeholder{color:var(--mute);letter-spacing:normal;font-weight:400}

/* The two ways in, on the Add screen. */
.opt{display:block;width:100%;text-align:left;border:1px solid var(--line);background:var(--panel);
  border-radius:14px;padding:13px 14px;margin-top:12px;font-family:inherit;color:var(--ink);cursor:pointer}
.opt .h{display:flex;align-items:center;gap:10px}
.opt .ic{width:32px;height:32px;border-radius:9px;background:var(--mine);flex:none;
  display:grid;place-items:center;color:var(--accent)}
.opt .ic svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
.opt .n{font-weight:700;font-size:15px;flex:1}
.opt .go{color:var(--mute);font-size:16px}
.opt .d{display:block;font-size:12.5px;color:var(--ink2);margin-top:7px;line-height:1.45}

/* Segmented choices: the kind of code, and how long it lives. */
.seg{display:flex;gap:7px;margin-top:6px}
.seg button{flex:1;padding:10px 4px;border-radius:10px;font-size:13px;font-weight:700;
  border:1px solid var(--line);background:var(--panel);color:var(--ink2);font-family:inherit;cursor:pointer}
.seg button.on{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}

.bigcode{font-family:var(--mono);font-size:24px;font-weight:700;letter-spacing:.08em;text-align:center;
  background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 10px;margin-top:6px;
  word-break:break-all}
.linkbox{font-family:var(--mono);font-size:12.5px;line-height:1.5;color:#2F5FA8;background:var(--panel);
  border:1px solid var(--line);border-radius:12px;padding:12px 13px;word-break:break-all}
.notebox{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:11px 13px;
  font-size:14.5px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}
.notebox.empty{color:var(--mute);font-style:italic}

/* The sentence that sits where a number would have been. */
.nothing{border:1px dashed var(--mine-line);border-radius:12px;padding:12px;
  font-size:12.5px;color:#2E4A32;background:var(--mine);line-height:1.5;font-weight:600}
.padwrap{padding:14px 18px}
.srow.flush{margin:6px -20px 0;width:auto}

/* The square, drawn on the invitation's own screen. White ground and a hard
   dark ink, both stated here rather than inherited, because a camera reading
   this across a table is the only thing it has to survive. image-rendering
   keeps the modules square when the canvas is scaled down to fit a phone. */
.qrbox{background:#fff;border:1px solid var(--line);border-radius:16px;padding:14px;
  width:max-content;margin:2px auto 10px}
#code-qr{display:block;image-rendering:pixelated;width:168px;height:168px}
@media (min-width:700px){ #code-qr{width:196px;height:196px} }

/* The link itself, on the screen, next to the one button that copies it. It
   is the thing that actually gets handed over, and until now it existed only
   inside a share sheet. */
.linkrow{display:flex;align-items:center;gap:10px;background:var(--panel);
  border:1px solid var(--line);border-radius:12px;padding:13px 14px;margin-top:10px}
.linkrow .u{flex:1;font-family:var(--mono);font-size:13px;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.linkrow .cp{font-family:inherit;font-size:12.5px;font-weight:800;color:var(--accent);
  background:none;border:0;padding:4px 2px;cursor:pointer;flex:none}

/* The owner's message, quoted. On the share screen it is what they will read;
   on the arrival screen it is the first thing on the page, above the pitch. */
.quote{background:var(--mine);border:1px solid var(--mine-line);border-radius:13px;
  padding:13px;margin-top:14px}
.quote[hidden]{display:none}
.quote .q{font-size:14.5px;color:#22331F;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}
.quote .w{font-size:11px;color:#4A6650;margin-top:7px;font-weight:700;letter-spacing:.04em}
/* Same box as .nothing, used where a sentence is a note rather than the
   sentence standing in for a number. Amber where it is a dead end. */
.note{border:1px dashed var(--mine-line);border-radius:12px;padding:12px;margin-top:13px;
  font-size:12.5px;color:#2E4A32;background:var(--mine);line-height:1.5;font-weight:600}
.note.amber{border-color:#E7DFC8;background:#FDF6E3;color:#7A5F12}
/* Red, per the theme's third rule, only where something is being refused. */
.note.bad{border-color:#E8CFCA;background:#FBEDEA;color:#8A3729}

/* ---- your words: the second way back in ----
   The field is serif and large because these are words a person is reading
   back to themselves, not a string. 20px also keeps it clear of the 16px floor
   below which iOS zooms the whole page on focus. */
.input.words{font-family:var(--serif);font-size:20px;line-height:1.35;letter-spacing:-.01em;
  padding:14px;min-height:64px;margin-top:10px;resize:none}
/* The unit is words. Four dots fill, and there is deliberately no meter: a
   meter measures characters, and characters are the wrong thing to reward. */
.dots{display:flex;gap:7px;margin-top:14px}
.dots i{width:9px;height:9px;border-radius:50%;background:var(--line)}
.dots i.on{background:var(--accent)}
.wordcount{display:flex;justify-content:space-between;font-size:11.5px;color:var(--mute);margin-top:10px}
.wordcount b{color:var(--ink);font-weight:700}
/* The dead primary. Disabled, not hidden: the way forward stays where it will
   be, so nothing jumps when the fourth word lands. */
.btn:disabled{background:#DCD9CC;color:#5E5B4F;cursor:default}
/* The one second, said out loud. */
.working{text-align:center;padding:40px 10px}
.working .ring{width:54px;height:54px;border-radius:50%;border:3px solid var(--mine-line);
  border-top-color:var(--accent);margin:0 auto 18px;animation:wspin 1s linear infinite}
.working .say{margin-top:11px}
@keyframes wspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.working .ring{animation:none}}

/* ---- the three steps (full-flow contract, screens 07 to 13) ----
   A chip in the bar and a rule under it. Both are drawn only while the screen
   is a step in a walk: opened from Settings the same screens are a setting,
   and a setting is not two thirds of anything. */
.step{font-size:11px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  color:var(--accent);flex:none}
.step[hidden]{display:none}
.prog{display:flex;gap:6px;align-items:center;margin-top:2px;flex:none}
.prog[hidden]{display:none}
.prog i{height:4px;border-radius:2px;background:var(--line);flex:1}
.prog i.on{background:var(--accent)}
/* The headline sits under the progress rule rather than against it. */
.steptop{margin-top:14px}
/* The skip. A line, not a third button: it is a way past, not a way forward,
   and drawing it as a button would give it the weight of one. */
.later{display:block;width:100%;background:none;border:0;font-family:inherit;
  text-align:center;font-size:13.5px;color:var(--accent);font-weight:700;
  margin-top:15px;padding:4px;cursor:pointer}
.later[hidden]{display:none}
/* Screen 07's three facts. A dot, not a number: they are not in an order. */
.uses{list-style:none;margin:15px 0 0;padding:0}
.uses li{display:flex;gap:11px;align-items:flex-start;padding:7px 0;font-size:14.5px;
  color:var(--ink2);line-height:1.45}
.uses li .d{width:6px;height:6px;border-radius:50%;background:var(--accent);flex:none;margin-top:8px}
.uses li b{color:var(--ink);font-weight:700}
/* Screen 02's three steps, which ARE in an order, so they are numbered. */
.numsteps{list-style:none;margin:16px 0 0;padding:0}
.numsteps li{display:flex;gap:12px;align-items:flex-start;padding:9px 0;font-size:14.5px;
  color:var(--ink2);line-height:1.45}
.numsteps li .n{width:23px;height:23px;border-radius:50%;background:var(--accent);
  color:var(--accent-ink);flex:none;display:grid;place-items:center;font-size:12px;
  font-weight:800;margin-top:1px}
.numsteps li b{color:var(--ink);font-weight:700}
/* The escape on screen 02, inside a sentence rather than beside it. */
.linkish{color:var(--ink2);font-weight:700;text-decoration:underline;text-underline-offset:3px}
/* The pointer at the share button is pinned to the bottom of the WINDOW, so on
   this one screen the page gets out from under it. The escape is the safety
   net for anybody who cannot install right now, and a safety net behind a
   floating banner is not one. */
#v-getapp .pad{padding-bottom:calc(84px + env(safe-area-inset-bottom))}
/* The brick, in the bar of the installed app's first screen. */
.barlogo{width:30px;height:30px;border-radius:8px;flex:none;display:block;object-fit:cover}

/* ---- the invite journey (mockups/invite-journey, approved 08/30/2026) ----
   The landing, the copied-code guide, and the three doors. Colors come off
   the theme tokens so every theme keeps its own contrast; the one exception
   is the guide card's ink, stated outright because its ground is the deep
   green in every theme. */
.wm{display:flex;align-items:center;gap:9px;font-family:var(--serif);font-size:22px;
  font-weight:600;letter-spacing:-.02em;margin-bottom:14px}
.codebig{font-family:var(--serif);font-size:32px;font-weight:700;letter-spacing:.12em;
  text-align:center;background:var(--panel);border:1.5px dashed var(--accent);border-radius:16px;
  padding:18px;margin-top:16px}
.codesub{text-align:center;font-size:12.5px;color:var(--mute);margin-top:8px}
.quietc{text-align:center;line-height:1.5}
.guidecard{background:var(--deep);border-radius:16px;padding:4px 16px 14px;margin-top:16px}
.guidecard .numsteps li{color:#F4F3EE}
.guidecard .numsteps li b{color:var(--gold-on-deep)}
.guidecard .numsteps li .n{background:rgba(244,243,238,.16);color:#F4F3EE}
.door{display:block;width:100%;text-align:left;background:var(--panel);border:1.5px solid var(--line);
  border-radius:16px;padding:16px;margin-top:12px;font-family:inherit;font-size:inherit;color:var(--ink)}
.door.first{border-color:var(--accent);background:var(--pale)}
.door.tap{cursor:pointer}
.door .k{display:block;font-weight:800;font-size:16px}
.door .v{display:block;font-size:13px;color:var(--mute);margin-top:3px;line-height:1.5}
.pasterow{display:flex;gap:10px;margin-top:10px}
.pasterow .input{flex:1;margin:0;min-width:0}
.pasterow .pb{background:var(--accent);color:var(--accent-ink);border:0;border-radius:13px;
  padding:12px 18px;font-weight:800;font-family:inherit;font-size:14px;cursor:pointer;flex:none}
.pasterow .pb[hidden]{display:none}
#first-status:empty{display:none}

/* Edit, in the header, next to the name it edits. */
.edit{background:none;border:0;color:var(--accent);font-size:13.5px;font-weight:600;
  font-family:inherit;cursor:pointer;padding:4px 2px}
.codeval{font-family:var(--mono);font-size:12.5px;font-weight:700;color:var(--accent);margin-top:3px}
.btn.danger-txt{color:var(--flag)}

/* ---- what we hold ---- */
/* min-height:0 is load bearing. A flex item defaults to min-height:auto, which
   refuses to shrink below its content, so flex:1 plus overflow-y:auto does NOT
   scroll: the list grows past the view and .view's overflow:hidden slices it.
   On a phone that cut "Message history" in half and hid the four rows under
   it, including the recovery backup line. */
.hold{list-style:none;margin:0;padding:0;flex:1;min-height:0;overflow-y:auto;
  -webkit-overflow-scrolling:touch}
/* The footnote under the list must NOT be .pad: that carries flex:1, so the
   paragraph was claiming the same share of the view as the list itself. */
.hold-note{flex:none;padding:14px 20px 18px}
.hold li{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  padding:12px 16px;border-bottom:1px solid var(--line)}
.hold .k{font-size:13.5px;color:var(--ink2)}
.hold .v{font-family:var(--mono);font-size:10.5px;color:var(--accent);font-weight:700;
  white-space:nowrap;text-align:right}
.hold .v.grey{color:var(--mute);font-weight:400}

.toast{position:fixed;left:50%;bottom:86px;transform:translateX(-50%);background:var(--ink);
  color:var(--bg);font-size:13px;padding:10px 16px;border-radius:99px;z-index:50;max-width:88vw;
  text-align:center}
.toast[hidden]{display:none}
.toast.bad{background:var(--flag)}

/* -------------------------------------------------------------- links */
.b .lnk{color:#2F5FA8;text-decoration:underline;text-underline-offset:2px;cursor:pointer;
  word-break:break-word}
/* On the olive outgoing bubble a dark blue link vanishes; lift it. */
.m.out .b .lnk{color:#D8E6FF}
.b .lnk:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}

.link-sheet{position:fixed;inset:0;z-index:60;display:flex;align-items:flex-end}
.link-sheet[hidden]{display:none}
.ls-dim{position:absolute;inset:0;background:rgba(34,30,40,.42)}
.ls-card{position:relative;margin:0 8px 10px;width:100%;background:var(--panel);
  border:1px solid var(--line);border-radius:20px;padding:18px 17px 16px;
  box-shadow:0 -12px 40px rgba(0,0,0,.16);max-height:88svh;overflow-y:auto}
.ls-h{font-weight:800;font-size:16.5px}
.ls-lb{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--mute);
  font-weight:800;margin-top:14px}
.ls-host{font-family:var(--mono);font-size:15px;font-weight:700;margin-top:5px;word-break:break-all}
.ls-path{font-family:var(--mono);font-size:11.5px;color:var(--mute);margin-top:3px;word-break:break-all}
.ls-strip{background:var(--mine);border:1px solid var(--mine-line);border-radius:11px;
  padding:10px 12px;margin-top:12px}
.ls-strip[hidden]{display:none}
.ls-strip .k{font-size:11.5px;font-weight:800;color:#2E4A32}
.ls-strip .v{font-family:var(--mono);font-size:10.5px;color:#5C7A5F;margin-top:4px;
  line-height:1.6;text-decoration:line-through;word-break:break-all}
.ls-note{font-size:12px;color:var(--ink2);line-height:1.5;margin-top:12px}
.ls-card .btn{margin-top:10px}

/* ------------------------------------------------- add to home screen (A) */
.crumb{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--mute);
  font-weight:800;text-align:center}
.sheet{background:var(--panel);border:1px solid var(--line);border-radius:18px;padding:17px}
.sheet-h{font-weight:800;font-size:16px}
.sheet-b{font-size:13.5px;color:var(--ink2);margin-top:6px;line-height:1.5}
.sheet .btn{margin-top:11px}
.steps{margin:12px 0 0;padding-left:20px;font-size:13.5px;color:var(--ink2);line-height:1.6}
.steps[hidden]{display:none}
.steps b{color:var(--ink)}

@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}


/* ---- make-a-code sheets: a stepper and a date picker, replacing two prompt()
   calls. Both live inside .ls-card, so the dim, the placement and the entry
   animation are the ones the link and log out sheets already use. ---- */
.stepper{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-top:14px;
         background:var(--bg);border:1px solid var(--line);border-radius:14px;padding:10px 12px}
.stepper .pm{width:46px;height:46px;border-radius:11px;background:var(--panel);border:1px solid var(--line);
             color:var(--accent);font-size:23px;font-weight:600;flex:none}
.stepper .pm:disabled{color:#6E6A64;opacity:1}
.stepper .n{flex:1;text-align:center}
.stepper .n b{display:block;font-size:32px;font-weight:700;font-family:var(--serif,ui-serif,Georgia,serif);line-height:1.1}
.stepper .n i{display:block;font-style:normal;font-size:11.5px;color:var(--mute);margin-top:1px}
.chips{display:flex;gap:7px;margin-top:11px}
.chips button{flex:1;border:1px solid var(--line);background:var(--bg);border-radius:9px;
              padding:10px 2px;font-size:13.5px;font-weight:600;color:var(--ink)}
.chips button.on{background:var(--mine);border-color:var(--mine-line);color:var(--accent)}
.orow{display:flex;align-items:center;justify-content:space-between;width:100%;padding:14px 2px;
      border-top:1px solid var(--line);margin-top:13px;font-size:14.5px;background:none;color:var(--ink);text-align:left}
.orow .tick{color:var(--accent);font-size:15px;font-weight:800}
.orow .tick[hidden]{display:none}
/* The native control is laid over its own row and made invisible rather than
   hidden, because a display:none date input will not open the system picker. */
.drow{position:relative;display:flex;align-items:center;justify-content:space-between;gap:12px;
      margin-top:11px;padding:14px;background:var(--panel);border:1px solid var(--line);border-radius:12px}
.drow .k{font-size:14.5px}
.drow .cur{color:var(--accent);font-size:13.5px;font-weight:700}
/* 16px for the same reason as .field and .input: an invisible control still
   receives focus, and iOS zooms on ANY focusable field under 16px. This one
   sat at the browser default (13.3px) and was the last field that could zoom. */
.drow input[type=date]{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:0;background:none;
  font-size:16px}


/* ---- the two asks: a home screen slot, and then notifications ----
   One component, because the install contract draws one: a sheet off the
   bottom over a dimmed conversation, a grab handle, a serif line, a sentence
   and two buttons. They ride the .link-sheet machinery the link and log out
   sheets already use, so the dim, the placement and the width cap are shared
   rather than reinvented twice.

   The notifications ask used to be a card wedged above the composer, on the
   argument that typing must never be blocked. The contract draws both asks as
   the same sheet, and two asks that look like two different kinds of thing is
   the worse cost: whichever one arrives second reads as a new mechanism. */
.ls-card .grab{width:38px;height:4px;border-radius:99px;background:#D8D6CE;margin:0 auto 14px}
.ask-h{font-family:var(--serif);font-size:21px;line-height:1.16;letter-spacing:-.02em;
  font-weight:600;margin:0}
.ask-b{font-size:13.5px;color:var(--ink2);line-height:1.5;margin:9px 0 0}
.ask-fine{font-size:11.5px;color:var(--mute);margin-top:11px;text-align:center;line-height:1.45}
.ask-fine[hidden]{display:none}

/* ---- groups. The visual contract is mockups/groups-all-v4: square avatar
   with a member count so a group is never mistaken for a person, centred
   system lines, a name on incoming bubbles with a stable colour per sender. */
.av.grp{border-radius:12px;background:#DDE6E7;color:#3C5A5E;font-size:12px;font-family:-apple-system,system-ui,sans-serif;font-weight:800}
.sysline{text-align:center;font-size:11.5px;color:var(--mute);padding:9px 18px;line-height:1.45}
.m .who{font-size:11px;font-weight:800;margin:0 0 2px 2px}
/* Search. The field is its own band under the header rather than inside the
   scroller, so it stays put while results move. */
.searchwrap{flex:none;padding:10px 14px 4px}
.searchwrap .input{width:100%}
.sres{font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--mute);padding:14px 16px 6px}
.shit mark{background:var(--gold);color:var(--gold-ink);border-radius:3px;padding:0 2px}
:root[data-theme="dark"] .shit mark,
:root[data-theme="forest"] .shit mark,
:root[data-theme="ember"] .shit mark{background:#5A4A12;color:#F6E6A8}

/* The rows inside the who-is-this sheet. Not .srow: these are actions with a
   sentence under them, and the sentence is the point — "a separate
   conversation, nobody in the group sees it" is the whole reason somebody would
   tap it. */
.arow{display:flex;align-items:center;gap:11px;width:100%;padding:13px 12px;margin-top:8px;
  border:1px solid var(--line);border-radius:13px;background:var(--bg);font-family:inherit;
  text-align:left;cursor:pointer;color:var(--ink)}
.arow .ic{width:26px;height:26px;border-radius:50%;background:var(--accent);color:var(--accent-ink);
  flex:none;display:grid;place-items:center;font-size:12px;font-weight:800}
.arow .ac{flex:1;min-width:0}
.arow .k{display:block;font-size:14.5px}
.arow .v{display:block;font-size:11px;color:var(--mute);margin-top:2px}
.arow[disabled]{opacity:.45;cursor:default}
.arow[disabled] .ic{background:var(--line);color:var(--mute)}
.arow:active:not([disabled]){background:var(--pale)}

/* Option C: one line, the first time you open a room, and never again. It is a
   thing the app says once rather than furniture it carries forever. */
.dmhint{margin:8px 12px 0;padding:9px 12px;border:1px dashed var(--line);border-radius:12px;
  font-size:11.5px;color:var(--ink2);background:var(--panel);display:flex;gap:8px;align-items:center}
.dmhint span{flex:1}
.dmhint button{flex:none;background:none;border:0;color:var(--mute);font-size:15px;
  font-family:inherit;cursor:pointer;padding:0 2px;line-height:1}

/* The label is a button now: tap it to name the person. It must not look like
   a control — a room full of buttons would read as a form — so it keeps the
   label's exact type and only answers to being pressed. */
.m .who.nameable{background:none;border:0;padding:0;font-family:inherit;font-size:11px;
  font-weight:800;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent}
.m .who.nameable:active{text-decoration:underline}
.m .who.nameable:focus-visible{outline:2px solid var(--w);outline-offset:2px;border-radius:3px}
/* Eight people, told apart (mockups/bubble-colour-v2-phone, option 3, approved
   08/20/2026). Four muted tones on an 11px label was the old scheme, and a room
   of five guaranteed a collision the app could not admit to. Each person now
   gets a full-strength colour for the circle and the name, and a wash of the
   same colour inside the bubble. No border: the wash IS the bubble.

   Two values per person, and the wash is not the strong colour at low opacity —
   it is mixed toward the paper, so it stays warm rather than going grey. */
.mk0{--w:#A24E1E;--wash:#FBEDE3}
.mk1{--w:#0F6B78;--wash:#E0F2F4}
.mk2{--w:#7A3B8C;--wash:#F4E9F7}
.mk3{--w:#6E6410;--wash:#F5F2DC}
.mk4{--w:#2C6E3F;--wash:#E4F2E7}
.mk5{--w:#A83250;--wash:#FCE8EC}
.mk6{--w:#2B4E9B;--wash:#E7ECFA}
.mk7{--w:#8A6A2B;--wash:#F7EFDF}
/* NO DARK BLOCK, ON PURPOSE.
 *
 * There was one here for about ten minutes and it made the app unreadable. The
 * eight above had dark twins under @media (prefers-color-scheme: dark) — and
 * that media query was the ONLY one in this whole stylesheet. Trap Phone is a
 * light app: the page, the header, the composer and the ink are all fixed
 * light. So on a phone with dark mode switched on at the system level, nothing
 * changed except my bubbles, which went dark while the ink stayed dark on top
 * of them. Every message in every group became invisible.
 *
 * Caught by screenshotting the built app with colorScheme:'dark' rather than by
 * trusting the mockup, which drew its own dark lane and could not know the
 * product has none. If Trap Phone ever gets a real dark theme, the twins live
 * in the history of this file and go back in ALONGSIDE it, never before it.
 */
.m .who{color:var(--w)}
/* The circle. Sits with the bubble, not floating above it. */
/* .m is a COLUMN of three things — name, bubble, then the time and how long it
   has left — so a circle anchored to the bottom of it lands beside the
   TIMESTAMP rather than beside the message. It looked fine in a mockup that had
   no meta line and wrong the moment it was drawn for real.
   
   A three row grid puts it where it belongs: the name spans the top, the circle
   and the bubble share the middle row, and the meta line sits under the bubble.
   The circle aligns to the bottom of the bubble, so a tall message keeps it
   beside the last line rather than floating at the top. */
.m.marked{display:grid;grid-template-columns:26px 1fr;column-gap:8px;row-gap:3px;
  grid-template-areas:"who who" "chip bubble" "gap meta"}
.m.marked .who{grid-area:who}
.m.marked .b{grid-area:bubble}
.m.marked .meta{grid-area:meta}
.m .chip{grid-area:chip;align-self:end;width:26px;height:26px;border-radius:50%;
  display:grid;place-items:center;font-size:11px;font-weight:800;color:#fff;background:var(--w)}
/* The wash, plus the person's own colour as the outline (asked for live on
   08/20/2026 after seeing the wash alone on the site). The wash gives a run of
   messages one block of colour; the border gives it an edge, which is what the
   wash alone was missing against a cream page that is nearly the same value. */
.m .b.washed{background:var(--wash);border-color:var(--w)}
.optrows .orow2{display:flex;align-items:flex-start;gap:11px;width:100%;padding:13px 16px;background:var(--panel);
  border:0;border-bottom:1px solid var(--line);text-align:left;color:var(--ink);font:inherit}
.optrows .orow2 .rad{width:20px;height:20px;border-radius:50%;border:1.6px solid var(--line);flex:none;margin-top:2px}
.optrows .orow2.on .rad{border-color:var(--accent);border-width:6px}
.optrows .orow2 .k2{display:block;font-size:14.5px;font-weight:600}
.optrows .orow2 .v2{display:block;font-size:12.5px;color:var(--ink2);margin-top:3px;line-height:1.45}
.picklist{max-height:44vh;overflow-y:auto;margin-top:10px}
.picklist .prow{display:flex;gap:11px;align-items:center;width:100%;padding:11px 4px;background:none;border:0;
  border-bottom:1px solid var(--line);text-align:left;color:var(--ink);font:inherit}
/* Both pickers build an avatar initial, and until now nothing sized it, so the
   letter ran straight into the name: "BBrian". Same circle as a chats row, one
   size down because a picker row is tighter. */
.picklist .prow .av{width:30px;height:30px;border-radius:50%;flex:none;display:grid;place-items:center;
  font-family:var(--serif);font-size:13px;font-weight:600;color:#fff;background:var(--accent)}
.picklist .prow .pn{flex:1;font-size:14.5px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.picklist .prow .ps{font-size:11.5px;color:var(--mute)}
.picklist .prow .check{width:20px;height:20px;border-radius:50%;border:1.5px solid var(--line);flex:none;
  display:flex;align-items:center;justify-content:center;font-size:11px;color:#fff}
.picklist .prow.on .check{background:var(--accent);border-color:var(--accent)}
/* The group's People screen. Its rows have been built in app.js since the day
   groups shipped and NOTHING in here ever styled them, so every row rendered
   as a bare inline button and the whole screen read as a row of pills saying
   "?Someone newLooks after this groupRemove". Same shape as a chats row, the
   only person-row shape this app has. */
.peoplelist .row{display:flex;gap:12px;align-items:center;width:100%;padding:12px 16px;background:none;border:0;
  border-bottom:1px solid var(--line);text-align:left;color:var(--ink);font:inherit}
.peoplelist .row .av{width:36px;height:36px;border-radius:50%;flex:none;display:grid;place-items:center;
  font-family:var(--serif);font-size:15px;font-weight:600;color:#fff;background:var(--accent)}
.peoplelist .row .c{flex:1;min-width:0}
.peoplelist .row .n{display:block;font-size:14.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Empty for most people, so it must take no room when it says nothing. */
.peoplelist .row .s:not(:empty){display:block;font-size:12.5px;color:var(--mute);margin-top:2px}
.peoplelist .row .act{flex:none;font-size:12.5px;font-weight:700;color:var(--accent);padding:6px 8px;
  border-radius:8px;background:none;border:0;font-family:inherit}
.peoplelist .row .x{flex:none;font-size:12.5px;font-weight:700;color:var(--flag);padding:6px 8px;
  border-radius:8px;background:none;border:0;font-family:inherit}
.note-line{font-size:12.5px;color:var(--ink2);padding:13px 16px;line-height:1.5}
.note-block{border:1px dashed var(--mine-line);border-radius:12px;padding:12px;margin-top:12px;
  font-size:12.5px;color:#2E4A32;background:var(--mine);line-height:1.5;font-weight:600}
.icon.txt{width:auto;font-size:14px;font-weight:700;color:var(--accent);padding:0 4px}
.btn.danger.ghost{background:transparent;border:1px solid var(--line);color:var(--flag)}


/* ---- onboarding Option A ---- */
/* The invited person's note on the arrival screen, drawn as what it actually
   is: the first message of a conversation, in the same clothes as an incoming
   bubble in the thread. */
.arrive-note{flex:none;padding:14px 0 0;display:flex;flex-direction:column}
.arrive-note[hidden]{display:none}
.notebub{background:var(--panel);border:1px solid var(--line);border-radius:16px;
  border-bottom-left-radius:5px;padding:10px 13px;font-size:15px;line-height:1.45;
  max-width:84%;align-self:flex-start;white-space:pre-wrap;overflow-wrap:anywhere}
/* The two cards that can sit under the chat list: the install ask in the
   waiting state, and the name-it-later nudge. */
.homecard{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:15px}
.homecard .h{font-family:var(--serif);font-size:17px;font-weight:600;letter-spacing:-.015em;margin:0}
.homecard .b{font-size:12.5px;color:var(--ink2);line-height:1.55;margin:7px 0 0}
.homecard .btn{margin-top:12px}
/* The Waiting pill on an invite nobody has answered yet: the theme sheet's
   .pill.gold, verbatim colors. */
.list .l1 em.pillwait{font-size:10.5px;letter-spacing:.05em;
  text-transform:uppercase;color:#6B5310;background:#F6EAC4;border:0;
  border-radius:99px;padding:3px 9px;font-weight:800}

/* ---- desktop: the width-capped column ----
   The approved shape from the error-states contract (.appdemo .inner,
   max-width 480px): at desktop widths the app is a centered app column on
   the paper ground, never a phone layout stretched full-bleed. Fixed
   elements center with inset + margin auto, so no transform is introduced
   and nothing inside changes its containing block. */
@media (min-width: 700px){
  body{background:#EDEAE5}
  .view{max-width:480px;margin:0 auto;
    border-left:1px solid var(--line);border-right:1px solid var(--line)}
  .ls-card{max-width:464px;margin-left:auto;margin-right:auto}
  .callout{max-width:456px;margin-left:auto;margin-right:auto}
  .uploading{max-width:480px;margin:0 auto}
}

/* The opening view. Its slow line is invisible for three seconds by CSS
   alone, so a normal fast boot never shows it and a hung one explains itself
   even if no JavaScript ever gets to run. */
#v-opening{justify-content:center}
#v-opening .pad{justify-content:center}
/* On anything wider than a phone the splash is a page, not a pocket: no
   column borders, no 480px cap, one big wordmark on the ground. Ungated on
   .desk because this screen exists BEFORE the app has decided anything —
   it was the last place the product still looked like a phone taped to a
   monitor (Brian, 08/29/2026). */
@media (min-width:700px){
  #v-opening{max-width:none;border-left:0;border-right:0;background:var(--bg)}
  #v-opening .display{font-size:34px}
  body.desk #v-opening{left:0}
}
/* The landing at /r/<slug>, under the splash's law. A friend opened a room
   link in a desktop Chrome window and met a narrow centered column floating
   in the middle of the page (the owner's report, 09/03/2026: a tiny narrow
   screen instead of a desktop app). Ungated on .desk because the visitor
   this page exists for has no trap phone and never earns that class: at
   width it is a page — the icon, the name and the live count on one
   comfortable centered column, no side borders, no 480 cap. */
@media (min-width:700px){
  #v-roomland{max-width:none;border-left:0;border-right:0;background:var(--bg)}
  #v-roomland .pad{max-width:680px;width:100%;margin:0 auto}
  #v-roomland .display{font-size:34px}
  #v-roomland .rlandic{width:80px;height:80px;font-size:39px;border-radius:22px}
  body.desk #v-roomland{left:0}
}
.opening-slow{opacity:0;animation:tp-slow-reveal .4s ease 3s forwards;max-width:34ch;margin:14px auto 0}
@keyframes tp-slow-reveal{to{opacity:1}}


/* --------------------------------------------------------------- the viewer
   Opening a photo. Full bleed on black, because the point of opening it is to
   see the thing rather than the app around it. Sits above everything and
   closes on a tap anywhere, so there is no small target to find. */
.viewer{position:fixed;inset:0;z-index:200;background:#0B0C0B;display:flex;align-items:center;
  justify-content:center;touch-action:manipulation}
.viewer[hidden]{display:none}
.viewer img,.viewer video{max-width:100%;max-height:100%;width:auto;height:auto;display:block;
  object-fit:contain}
.viewer .vclose{position:absolute;top:calc(10px + env(safe-area-inset-top));right:12px;width:40px;height:40px;
  border-radius:50%;border:0;background:rgba(255,255,255,.14);color:#fff;font-size:22px;line-height:1;
  cursor:pointer;font-family:inherit;display:grid;place-items:center}
.viewer .vname{position:absolute;left:0;right:0;bottom:calc(14px + env(safe-area-inset-bottom));
  text-align:center;color:rgba(255,255,255,.72);font-size:12.5px;padding:0 20px}

/* ------------------------------------------------ the home screen shelf (D)
   mockups/tab-banner-d. Option B's shelf at the bottom of the screen, carrying
   two lines. flex:none and a plain sibling of .tabbar rather than anything
   fixed or floating: a fixed element would sit OVER the last row of the list,
   and the list is the thing a person came for.

   Every colour here is a token, so the three dark grounds come free. That is
   not tidiness. A sender-colour block was written as a media query on
   08/20/2026 and made the bubbles unreadable on a phone set to dark, because
   half the screen moved and half did not. A theme moves every token or none. */
.shelf{flex:none}
.shelf[hidden]{display:none}
.shelf-tap{display:flex;width:100%;align-items:center;gap:12px;padding:12px 13px;
  padding-bottom:12px;background:var(--pale);border:0;border-top:1px solid var(--mine-line);
  font-family:inherit;text-align:left;cursor:pointer;color:var(--ink)}
.shelf-col{flex:1;min-width:0}
.shelf-t1{display:block;font-size:13px;font-weight:700;line-height:1.28;color:var(--ink)}
.shelf-t2{display:block;font-size:11.5px;line-height:1.38;margin-top:3px;color:var(--ink2)}
.shelf-cta{flex:none;background:var(--gold);color:var(--gold-ink);border-radius:10px;
  padding:9px 13px;font-size:13px;font-weight:800}
/* The no-words face (mockups/install-shelf-gate, state A). Amber, because it
   asks for one thing before the install rather than offering the install, and
   amber is what this app already means by that — .srow.held is the same
   treatment. Its ink is pinned to literals for .srow.held's exact reason: a
   fixed light ground under a dark theme's light ink is the pairing that has
   made text vanish here before, so nothing on it may drift with the theme. */
.shelf.nowords .shelf-tap{background:#FBF3DC;border-top-color:#EBD9A6}
.shelf.nowords .shelf-t1{color:#232B24}
.shelf.nowords .shelf-t2{color:#6B5410}

/* ---------------------------------------------------- a switch that is real
   mockups/private-room-ping. Drawn at the mockup's own 46×28 and deliberately
   NOT the tappable thing: the whole row carries the tap, the way the theme row
   already does. A 28px control would sit under the 30px floor every-button.mjs
   holds every button to, and shrinking the reach of a switch to keep a drawing
   honest would be the wrong half to protect. */
.tsw{width:46px;height:28px;border-radius:99px;background:#D9D6CE;flex:none;
  position:relative;display:block;transition:background .14s ease}
/* The off track is the mockup's own grey, which reads on a white row; on the
   dark grounds that same grey would be the brightest thing on the screen, so
   it moves to the border token there. */
:root[data-theme] .tsw{background:var(--line)}
.tsw i{position:absolute;top:3px;left:3px;width:22px;height:22px;border-radius:50%;
  background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.22);transition:left .14s ease}
.tsw.on{background:var(--accent)}
.tsw.on i{left:21px}
/* The switch sits against the same right-hand column the chevrons and the ON
   pills do, so three settings rows do not put three controls at three x
   positions. */
.tswrow .tsw{margin-left:auto}

/* An .icon carrying a drawn mark rather than a character (mockups/search-icon,
   option A). Scoped to .bar so the padding of every other .icon in the app is
   left exactly where it is — this is a header change, not a spacing change.

   23px and 1.7 are not chosen numbers: they are .tab svg's numbers, copied.
   The whole argument for option A was that the header and the tab bar should
   be one set, and two icons a point apart would have quietly lost it. */
.bar .icon:has(svg){display:flex;align-items:center;padding:4px 8px}
.bar .icon svg{width:23px;height:23px;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;display:block}

/* --------------------------------------------------- tap targets (option 1)
   mockups/tap-targets, approved 08/26/2026. Thirty controls measured under
   30px on the running app — every back chevron 25×30, the burn chip 32×21,
   the thread-info line 234×18 — against Apple's 44×44 floor.

   The touch area is a pseudo-element with no content, no colour and no size
   in the layout: the control keeps its exact drawn position and size, and the
   region that answers a finger grows around it. If any pixel of the app moves
   because of this block, this block is wrong (qa/every-button re-proves the
   before/after screenshots are identical).

   The one collision in the app is the conversation header: back, the
   thread-info line and the burn chip share a bar with 10 points between them.
   Option 1, as approved: each grows to the full 44 height of the bar and the
   width splits at the MIDPOINT of each gap (±5px), so no two areas can ever
   claim the same pixel. The first and last control also reach outward into
   the bar's own 16px padding, which is where a thumb arriving from the edge
   of the phone actually lands. */
.icon, .cp, .later, .edit, .chip, .who,
#btn-drops-clear, #btn-gconfirm-go { position: relative; }
.icon::after, .cp::after, .later::after, .edit::after, .chip::after,
#btn-drops-clear::after, #btn-gconfirm-go::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: max(100%, 44px); height: max(100%, 44px);
}
.bar .icon::after, .bar .chip::after, .bar .who::after {
  width: auto; height: 44px; transform: translateY(-50%);
  left: -5px; right: -5px;
}
.who { position: relative; }
.who::after {
  content: ''; position: absolute; left: -5px; right: -5px;
  top: 50%; height: 44px; transform: translateY(-50%);
}
.bar .icon:first-child::after { left: -16px; }
.bar .icon:last-child::after,
.bar .chip:last-child::after { right: -16px; }

/* Stragglers the touch-walk in qa/every-button found once it measured what a
   finger gets instead of what the rect says: the two tab-bar tabs answer 42,
   the composer's send arrow 36×36, the two ghost buttons on a code screen 38.
   Same pseudo-element trick, same zero-pixel rule. */
.tab, .send, .btn.ghost { position: relative; }
.tab::after, .send::after, .btn.ghost::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: max(100%, 44px); height: max(100%, 44px);
}
/* The burn chip is 32 drawn; a midpoint split of its two 10pt gaps leaves it
   answering 42. The chip is the smallest and most missed control in the bar,
   so it takes 7 of each gap and its neighbours concede to 3 — still no shared
   pixel anywhere, and everything else in the bar has width to spare. */
.bar .chip::after { left: -7px; right: -7px; }
.bar .who::after { right: -3px; }
.bar .chip + .icon::after { left: -3px; }

/* ------------------------------------------------- the install guide (v2.1)
   mockups/honest-install-button-v2. The replica menu and sheet stay DARK in
   both themes on purpose: they are drawings of iOS's own dark panels, not
   surfaces of this app, and painting them with theme tokens would make the
   drawing stop matching the thing the person is about to see. The card uses
   the app's tokens, so it follows the theme. */
.athguide{position:fixed;inset:0;z-index:90;background:rgba(20,24,21,.78)}
:root[data-theme="dark"] .athguide,:root[data-theme="forest"] .athguide,
:root[data-theme="ember"] .athguide{background:rgba(0,0,0,.82)}
.athguide[hidden]{display:none}
.ath-card{position:absolute;left:18px;right:18px;background:var(--panel);border-radius:14px;
  padding:13px 16px;box-shadow:0 14px 40px rgba(0,0,0,.35)}
.ath-h{font-family:var(--serif);font-size:17px;font-weight:600;letter-spacing:-.02em;margin:0}
.ath-p{font-size:12.5px;color:var(--ink2);line-height:1.5;margin:6px 0 0}
.ath-p b{color:var(--ink)}
.ath-arrow{position:absolute;font-size:42px;line-height:1;font-weight:800;color:#F4D14A;
  text-shadow:0 3px 12px rgba(0,0,0,.55)}
.ath-arrow[hidden],.ath-list[hidden],.ath-cue[hidden]{display:none}
.ath-list{position:absolute;background:#3A3B3D;border-radius:14px;overflow:hidden;
  box-shadow:0 18px 50px rgba(0,0,0,.5)}
.ath-list .r{display:flex;align-items:center;gap:12px;padding:12px 15px;color:#ECECEC;
  font-size:14px;border-bottom:1px solid rgba(255,255,255,.09);
  font-family:-apple-system,system-ui,sans-serif}
.ath-list .r:last-child{border-bottom:0}
.ath-list .r .i{width:20px;text-align:center;opacity:.95}
.ath-list .r.dim{opacity:.42}
.ath-list .r.hot{background:#4A452A;outline:2.5px solid #F4D14A;outline-offset:-2.5px}
.ath-cue{position:absolute;color:#F4D14A;font-size:22px;font-weight:800;letter-spacing:1px;
  text-shadow:0 2px 8px rgba(0,0,0,.6)}

/* --------------------------------------------------- mentions (at-mentions)
   The mention chips inside bubbles and the picker above the composer. Gold is
   reserved for YOU being pointed at — the same gold Send owns — and the green
   wash for everyone else, so a glance sorts "about me" from "about them". */
.at{font-weight:800;border-radius:6px;padding:0 4px}
.at.me{background:var(--gold);color:var(--gold-ink)}
.at.other{background:var(--mine);color:var(--accent)}
.atdot{flex:none;min-width:22px;height:22px;border-radius:11px;background:var(--gold);color:var(--gold-ink);
  font-size:13px;font-weight:900;display:grid;place-items:center;padding:0 6px}
.atpick{flex:none;margin:0 10px 6px;background:var(--panel);border:1px solid var(--line);
  border-radius:14px;overflow:hidden;box-shadow:0 -8px 28px rgba(0,0,0,.14)}
.atpick[hidden]{display:none}
.atrow{display:flex;width:100%;align-items:center;gap:10px;padding:11px 13px;background:none;border:0;
  border-bottom:1px solid var(--line);font-family:inherit;font-size:14.5px;color:var(--ink);
  text-align:left;cursor:pointer}
.atrow:last-child{border-bottom:0}
.atrow .ata{width:26px;height:26px;border-radius:50%;flex:none;display:grid;place-items:center;
  font-size:11px;font-weight:700;color:var(--accent-ink);background:var(--accent);font-family:var(--serif)}
.atrow .atd{margin-left:auto;color:var(--mute);font-size:11.5px}

/* ---------------------------------------------- inline reply (quiet card)
   mockups/reply-quiet-three option 1. ONE surface for the quote — a cream
   card inside the bubble, and the same card above the composer. A bubble that
   carries a reply drops its washed background (set in appendMessage) so the
   card is never tint on tint, which was the thing that read as noise. */
.m .b.q1out{padding:6px}
.b .q1{display:block;width:100%;text-align:left;background:var(--pale);border:0;border-radius:11px;
  padding:8px 11px;margin-bottom:6px;font-family:inherit;cursor:pointer}
.m.out .b .q1{background:rgba(255,255,255,.90)}
:root[data-theme="dark"] .m.out .b .q1,:root[data-theme="forest"] .m.out .b .q1,
:root[data-theme="ember"] .m.out .b .q1{background:rgba(255,255,255,.90)}
.b .q1 .qn{display:block;font-size:11.5px;font-weight:800;color:var(--accent);margin-bottom:2px}
.m.out .b .q1 .qn{color:#4F6350}
.b .q1 .qb{display:block;font-size:13px;line-height:1.35;color:var(--ink2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.m.out .b .q1 .qb{color:#3A423B}
.b .body{display:block;padding:2px 7px 3px}
/* the lift when you jump to a quoted message */
.m.lift .b{animation:replylift 1.4s ease}
@keyframes replylift{0%,100%{box-shadow:0 0 0 0 rgba(79,99,80,0)}
  18%,60%{box-shadow:0 0 0 3px rgba(79,99,80,.45)}}
/* the composer's reply card */
.replybar{flex:none;background:var(--panel);border-top:1px solid var(--line);padding:8px 12px 0}
.replybar[hidden]{display:none}
.replybar .cq1{background:var(--pale);border-radius:10px;padding:7px 32px 7px 11px;position:relative}
.replybar .cq1 .qn{display:block;font-size:11px;font-weight:800;color:var(--accent)}
.replybar .cq1 .qb{display:block;font-size:12.5px;color:var(--ink2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.replybar .cq1 .x{position:absolute;top:50%;right:8px;transform:translateY(-50%);width:22px;height:22px;
  border-radius:50%;border:0;background:var(--mine);color:var(--mute);font-size:12px;font-weight:800;cursor:pointer}
/* the swipe */
.m{touch-action:pan-y}
.m.arming .b{filter:brightness(.97)}

/* ------------------------------------------------ pull to search, made seen
   The gesture used to fire at 70px with nothing on screen: search appeared and
   nothing had said it would. The lip grows with the finger, changes its line
   at the threshold, and folds away on release — so the pull always announces
   itself and never surprises. */
/* The lip lives BEHIND the list and is revealed as the list slides off it —
   so nothing reflows while a finger is down. The scroller carries the theme
   background for exactly that reason: it has to occlude this at rest. */
.pulllip{position:absolute;left:0;right:0;top:0;height:64px;opacity:0;
  display:flex;align-items:center;justify-content:center;gap:8px;
  background:var(--bg);color:var(--mute);font-size:12.5px;font-weight:700;
  pointer-events:none;z-index:0}
.pulllip[hidden]{display:none}
.pulllip .pl-i{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;transition:transform .18s ease;display:block}
.pulllip.armed{color:var(--accent)}
.pulllip.armed .pl-i{transform:scale(1.15)}
/* The pull's stage: the lip is absolutely placed against this. */
.pullhost{position:relative}

/* --------------------------------------------------- your icon (my-icon)
   The mark that replaces a letter circle wherever a contact is drawn. Two
   variants on top of the existing .av / .chip, and only ever ADDED: a contact
   with no icon renders through the exact code path that shipped, so day one
   looks like today byte for byte.
   .icg carries a chosen symbol on its chosen colour (inline style, because the
   colour is data). .ph is a photo clipped by the circle; the img fills it, so
   a broken data URL degrades to the empty tinted circle, never to a stretched
   letter. Both keep .av's border so the darkest colour in the row still has an
   edge on the dark grounds. */
.av.icg{font-family:var(--sans);font-size:19px}
.list .av.icg{font-size:19px}
.picklist .prow .av.icg{font-size:14px}
.peoplelist .row .av.icg{font-size:16px}
.av.ph,.chip.ph,.ata.ph{padding:0;overflow:hidden}
.av.ph img,.chip.ph img,.ata.ph img{width:100%;height:100%;object-fit:cover;border-radius:50%;display:block}
.m .chip.icg{font-size:13px}
.atrow .ata.icg{font-size:12px}

/* The picker screen. One panel, per the mockup: preview, symbol grid, the
   photo row, colour dots, one gold save. */
.iconcard{background:var(--panel);border:1px solid var(--line);border-radius:13px;
  padding:13px 15px;margin:12px 16px 10px}
.icopreview{display:flex;align-items:center;gap:13px;padding:4px 2px 14px}
/* The preview circle carries its own full dress: the bare .av class is only
   ever styled through the lists it lives in (.list .av and friends), so a
   preview leaning on it alone rendered as a square. */
.av.icobig{width:58px;height:58px;border-radius:50%;flex:none;display:grid;place-items:center;
  background:var(--mine);border:1px solid var(--mine-line);font-family:var(--serif);
  font-size:24px;font-weight:600;color:var(--accent)}
.av.icobig.icg{font-size:27px}
.icoptxt{min-width:0}
.icopn{display:block;font-weight:700;font-size:16px}
.icops{display:block;font-size:12px;color:var(--mute);margin-top:2px}
.icolbl{font-size:12px;color:var(--mute);font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;margin-top:14px}
.icogrid{display:grid;grid-template-columns:repeat(6,1fr);gap:8px;margin-top:10px}
.icogrid .g{aspect-ratio:1;border-radius:12px;background:var(--bg);border:1px solid var(--line);
  display:grid;place-items:center;font-size:19px;font-family:inherit;color:var(--ink);
  cursor:pointer;padding:0}
.icogrid .g.on{border:2px solid var(--accent);background:var(--mine)}
.icodots{display:flex;gap:9px;margin-top:12px}
/* A hairline in the theme's own line colour: invisible against the cream
   page, and the one thing that keeps the near-black dot findable on the
   black ground of the dark themes. */
.icodots .dot{width:30px;height:30px;border-radius:50%;flex:none;cursor:pointer;padding:0;
  border:1px solid var(--line)}
.icodots .dot.on{outline:2.5px solid var(--ink);outline-offset:2px}
.icoown{display:flex;align-items:center;gap:11px;border:1.5px dashed var(--mine-line);
  border-radius:12px;padding:10px 12px;margin-top:12px;font-size:13.5px;cursor:pointer}
.icoownic{width:34px;height:34px;border-radius:50%;background:var(--pale);display:grid;
  place-items:center;font-size:16px;flex:none}
.icoowntxt{flex:1;min-width:0;line-height:1.35}
.icoownsub{color:var(--mute);font-size:12px}
.icoowngo{color:var(--accent);font-size:17px;flex:none}
.icoquiet{text-align:center;color:var(--mute);font-size:12.5px;margin:10px 20px 0;line-height:1.5}

/* ------------------------------------------- the desktop two-pane layout
   mockups/web-interface. Above 900px, WITH a trap phone aboard (app.js puts
   .desk on body), the chats list becomes a 340px rail and the open thread
   takes the rest. Below 900px none of these rules exist and the app is
   exactly the phone column that ships today — one codebase, the layout folds
   back. Every rule is behind both the media query AND body.desk so a narrow
   window, a phone, or a pre-identity screen can never meet any of it. */
.pane-empty{display:none}
@media (min-width:900px){
  /* Only the panes shed the centered 480px column; Settings and every other
     full screen keep it, because a settings list stretched across a monitor
     is furniture nobody asked for. */
  /* The rail: the existing Chats view, pinned left at 340. Same rows, same
     header, same cards. The tab bar stays: it is how Settings is reached. */
  body.desk #v-chats{left:0;right:auto;width:340px;max-width:340px;margin:0;
    border-left:0;border-right:1px solid var(--line)}
  /* The thread fills the rest. The message column and the composer cap at
     ~740 and centre, because a bubble stretched across 1400px is unreadable;
     the burn countdown stays glued to every timestamp untouched. */
  body.desk #v-thread{left:340px;max-width:none;margin:0;border-left:0;border-right:0}
  /* Every other full screen sits in the pane too: pinned right of the rail,
     its 480px column centred in what remains. Without this, Settings and its
     children floated alone in the middle of an empty page. */
  body.desk .view{left:340px}
  body.desk #v-thread .thread{align-self:center;width:100%;max-width:744px}
  body.desk #v-thread .composer,body.desk #v-thread .replybar,
  body.desk #v-thread .atpick{align-self:center;width:100%;max-width:744px}
  /* The open conversation, marked on the rail: a pale wash and an accent
     edge, so you always know where you are. Desk only — on the phone the
     list and the thread are never on screen together. */
  body.desk .list li.on{background:var(--pale);box-shadow:inset 3px 0 0 var(--accent)}
  /* Where the keyboard walk is standing. Distinct from .on, which is where
     you already are. */
  body.desk .list li.kb{outline:2px solid var(--accent);outline-offset:-2px}
  /* The details drawer (frame 3): the existing contact / group screens parked
     in a 300px column over the thread's right edge instead of a page. */
  body.desk #v-contact,body.desk #v-ginfo,body.desk #v-gpeople{
    left:auto;right:0;width:300px;max-width:300px;margin:0;z-index:5;
    border-left:1px solid var(--line);border-right:0;
    box-shadow:-14px 0 34px rgba(35,43,36,.10)}
  /* Nothing selected: the pane says what the product is, once, and teaches
     the shortcuts only a desk can honour. */
  body.desk .pane-empty{display:flex;position:fixed;top:0;bottom:0;left:340px;right:0;
    align-items:center;justify-content:center;background:var(--bg)}
  body.desk .pane-empty[hidden]{display:none}
  .pe-card{text-align:center;max-width:420px;padding:20px}
  .pe-wm{font-family:var(--serif);font-size:30px;font-weight:600;letter-spacing:-.02em;color:var(--ink)}
  .pe-dot{display:inline-grid;place-items:center;width:40px;height:40px;border-radius:50%;
    background:var(--accent);color:var(--accent-ink);font-size:19px;vertical-align:-9px;margin-right:11px}
  .pe-one{font-size:14px;color:var(--ink2);margin-top:12px;line-height:1.6}
  .pe-hints{margin:26px auto 0;display:inline-block;text-align:left;background:var(--panel);
    border:1px solid var(--line);border-radius:14px;padding:14px 18px}
  .pe-hint{display:flex;align-items:center;gap:10px;padding:5px 0;font-size:13px;color:var(--ink2)}
  .kbd{display:inline-grid;place-items:center;min-width:24px;height:24px;padding:0 7px;
    border:1px solid var(--line);border-bottom-width:2px;border-radius:6px;background:var(--bg);
    font-size:11.5px;font-weight:800;color:var(--ink)}
  .pe-lb{margin-left:auto;color:var(--mute);padding-left:22px}
}

/* The held confirmation (mockups/cutoff-sheet option 2). The fill crossing
   the button IS the countdown: pressing arms it, the two-second transition
   draws it, releasing early snaps it back to nothing. */
.cuthold{position:relative;overflow:hidden;background:#B4402A;border-color:#B4402A;color:#fff}
.cuthold .fillbar{position:absolute;inset:0;background:rgba(0,0,0,.28);transform:scaleX(0);transform-origin:left}
.cuthold.holding .fillbar{transition:transform 2s linear;transform:scaleX(1)}
.cuthold .lbl{position:relative}

/* ═══════════════════════════════ rooms ═══════════════════════════════
   The open world (mockups/rooms-matrix + rooms-complete-v2 + room-profile +
   chat-rooms-web). Everything rides the theme tokens, so the three dark
   grounds come free; the handful of pinned gold pairs below pin BOTH halves,
   ink and ground together, which is the one safe way to fix a colour here. */
.rchip{border:1px solid var(--mine-line);color:var(--accent);border-radius:99px;
  padding:3px 8px;font-size:10.5px;font-weight:800;background:none;font-family:inherit;flex:none}
button.rchip{cursor:pointer;position:relative}
button.rchip::after{content:'';position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:max(100%,44px);height:max(100%,44px)}
.rchip.pub{border-color:#EBD9A0;color:#8a6d00;background:#FBF4DD}

/* The rail switch: desk only, ever. Phone widths never meet it. */
.railtabs{display:none;gap:8px;padding:14px 14px 10px;flex:none}
.rtab{flex:1;text-align:center;font-weight:800;font-size:12.5px;border-radius:9px;padding:7px 0;
  color:var(--mute);border:1px solid transparent;background:none;font-family:inherit;cursor:pointer}
.rtab.on{background:var(--pale);color:var(--accent);border-color:var(--mine-line)}

/* The directory */
.roomdir{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:10px 14px}
.rm{display:flex;align-items:center;gap:11px;background:var(--panel);border:1px solid var(--line);
  border-radius:13px;padding:11px 13px;margin-bottom:8px;width:100%;font-family:inherit;
  text-align:left;color:var(--ink);cursor:pointer;font-size:14px}
.rm .ric{width:34px;height:34px;border-radius:10px;background:var(--pale);display:grid;
  place-items:center;font-size:17px;flex:none}
.rm .rc2{flex:1;min-width:0}
.rm .rnm{display:block;font-weight:700;font-size:14.5px}
.rm .rds{display:block;font-size:11.5px;color:var(--mute);margin-top:1px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.rm .rds b{color:var(--ink)}
.rm .rct{margin-left:auto;text-align:right;flex:none}
.rm .rseats{display:block;font-size:12px;font-weight:800;color:var(--accent)}
.rm .rseats.full{color:#8a6d00}
:root[data-theme="dark"] .rm .rseats.full,:root[data-theme="forest"] .rm .rseats.full,
:root[data-theme="ember"] .rm .rseats.full{color:#E3C255}
.rm .rlbl{display:block;font-size:10px;color:var(--mute)}
.rm.dashed{border-style:dashed;background:transparent}
.rdh{font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--mute);margin:10px 2px 6px}

/* The join screen's picker */
.rjname{display:flex;align-items:center;gap:8px;margin-top:16px}
.rjname .input{flex:1;min-width:0;font-weight:700;font-size:17px;border-color:var(--accent);border-width:1.5px}
.rdice{flex:none;width:46px;height:46px;border-radius:12px;border:1px solid var(--line);
  background:var(--panel);font-size:19px;cursor:pointer;font-family:inherit}
.pickh{font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--mute);margin:14px 2px 7px}
.rglyphs{display:grid;grid-template-columns:repeat(6,1fr);gap:8px}
.rglyphs .g2{aspect-ratio:1;border:1px solid var(--line);border-radius:13px;display:grid;
  place-items:center;font-size:17px;background:var(--panel);font-family:inherit;cursor:pointer;padding:0}
.rglyphs .g2.on{border:2px solid var(--accent);background:var(--pale)}
.rswatches{display:flex;gap:10px}
.rswatches .sw{width:27px;height:27px;border-radius:50%;flex:none;border:1px solid var(--line);
  cursor:pointer;padding:0;position:relative}
/* The swatch draws at 27 and answers a finger at the row's full height and
   half of each 10px gap — the same midpoint rule the header controls use, so
   no two swatches ever claim the same pixel. */
.rswatches .sw::after{content:'';position:absolute;left:-5px;right:-5px;top:50%;height:44px;
  transform:translateY(-50%)}
.rswatches .sw.on{outline:2.5px solid var(--ink);outline-offset:2.5px}
.previewchip{display:flex;align-items:center;gap:10px;margin-top:14px;background:var(--panel);
  border:1px solid var(--line);border-radius:14px;padding:10px 13px;font-weight:800;font-size:15.5px}
.previewchip .pv{margin-left:auto;font-size:10.5px;font-weight:800;color:var(--accent);
  letter-spacing:.06em;text-transform:uppercase}
.rhint{font-size:12.5px;margin:9px 0 0;color:#8a6d00;line-height:1.5}
.rhint b{cursor:pointer}
.rhint.good{color:#155C33}
:root[data-theme="dark"] .rhint,:root[data-theme="forest"] .rhint,:root[data-theme="ember"] .rhint{color:#E3C255}
:root[data-theme="dark"] .rhint.good,:root[data-theme="forest"] .rhint.good,
:root[data-theme="ember"] .rhint.good{color:#7FD69B}

/* The identity mark: a two-tone circle, coloured by data so the wash and the
   border are set inline by app.js from the six-colour table. */
.rav{border-radius:50%;display:inline-grid;place-items:center;flex:none;
  border:1.5px solid var(--line);background:var(--pale)}
.rav.s{width:19px;height:19px;font-size:10px}
.rav.m{width:32px;height:32px;font-size:15px}
.rav.big{width:66px;height:66px;font-size:31px;border-width:2.5px;margin:0 auto}

/* The rules card */
.rlist{margin-top:18px;display:flex;flex-direction:column;gap:10px}
.rl{display:flex;gap:11px;align-items:flex-start;background:var(--panel);border:1px solid var(--line);
  border-radius:13px;padding:11px 13px;font-size:13.5px;line-height:1.45;color:var(--ink2)}
.rl .ri{flex:none;font-size:16px;margin-top:1px}
.rl b{color:var(--ink)}

/* The room */
.roombody{flex:1;min-height:0;display:flex}
.rstream{flex:1;min-width:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:10px 14px;
  display:flex;flex-direction:column;gap:7px}
.rln{display:flex;gap:7px;align-items:flex-start;font-size:13.5px;line-height:1.45;max-width:95%}
.rln .rav.s{margin-top:1px}
.rln .rtx{min-width:0;overflow-wrap:anywhere;white-space:pre-wrap}
.rln .rn{font-weight:800;margin-right:6px}
.rme{background:var(--mine);border:1px solid var(--mine-line);border-radius:11px;padding:6px 10px;
  align-self:flex-end;font-size:13.5px;line-height:1.45;max-width:85%;overflow-wrap:anywhere;white-space:pre-wrap}
.rsys{text-align:center;font-size:11px;color:var(--mute);align-self:center;max-width:92%;line-height:1.5}
.rsys.kept{color:#8a6d00}
:root[data-theme="dark"] .rsys.kept,:root[data-theme="forest"] .rsys.kept,
:root[data-theme="ember"] .rsys.kept{color:#E3C255}
/* The six name colours, and their lift on the dark grounds. */
.rk0{color:#4F6350}.rk1{color:#8a6d00}.rk2{color:#6B3FA0}.rk3{color:#A0522D}.rk4{color:#245E8C}.rk5{color:#155C33}
:root[data-theme="dark"] .rk0,:root[data-theme="forest"] .rk0,:root[data-theme="ember"] .rk0{color:#8FB894}
:root[data-theme="dark"] .rk1,:root[data-theme="forest"] .rk1,:root[data-theme="ember"] .rk1{color:#E3C255}
:root[data-theme="dark"] .rk2,:root[data-theme="forest"] .rk2,:root[data-theme="ember"] .rk2{color:#C9A0EE}
:root[data-theme="dark"] .rk3,:root[data-theme="forest"] .rk3,:root[data-theme="ember"] .rk3{color:#E0A180}
:root[data-theme="dark"] .rk4,:root[data-theme="forest"] .rk4,:root[data-theme="ember"] .rk4{color:#93B0F0}
:root[data-theme="dark"] .rk5,:root[data-theme="forest"] .rk5,:root[data-theme="ember"] .rk5{color:#7FD69B}

/* The people list: a sheet on the phone, the right rail on a desk. */
.rpslist{max-height:56vh;overflow-y:auto}
.rpslist .rpr,.rroster .rpr{display:flex;align-items:center;gap:10px;padding:8px 4px;font-size:14px;
  width:100%;background:none;border:0;font-family:inherit;color:var(--ink);text-align:left;cursor:pointer}
.rpslist .rpr b{font-weight:800}
.rpslist .rpr .quiet,.rroster .rpr .quiet{margin-left:auto;color:var(--mute);font-size:11.5px;flex:none}
.rroster{display:none}
.rroster .ro{font-size:10.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:var(--mute);padding:2px 14px 8px}
.rroster .rpr{padding:6px 14px;font-size:13px}
.rroster .rpr.you{background:var(--bg)}

/* The card behind a name */
.rcard{text-align:center}
.rc-n{font-weight:800;font-size:18px;margin-top:8px}
.rc-t{font-size:12.5px;color:var(--mute);margin-top:2px}
.rc-s{font-style:italic;font-size:13.5px;color:var(--ink2);margin-top:8px}
.rc-s.ghosty{color:var(--mute);font-style:normal}

/* The share sheet's link */
.rlinkpill{margin-top:14px;border:1.5px dashed var(--mine-line);border-radius:13px;background:var(--bg);
  padding:12px 14px;text-align:center;font-weight:800;font-size:15.5px;color:var(--accent);word-break:break-all}
.rshbtns{display:flex;gap:9px;margin-top:12px}

/* The landing at /r/<slug> */
.rlandic{width:64px;height:64px;border-radius:18px;background:var(--pale);display:grid;
  place-items:center;font-size:31px;margin:12px auto 0}
.rlcount{font-size:13.5px;color:var(--accent);font-weight:800;margin:3px 0 0}
.rpubline{font-size:11.5px;font-weight:800;color:#8a6d00;background:#FBF4DD;border:1px solid #EBD9A0;
  border-radius:99px;padding:4px 13px;display:inline-block;margin:18px auto 0}
.rstin{font-style:italic}

/* The desk: the rooms rail stands where the chats rail does, the room takes
   the pane, and the people list is simply there. Same fold-back rule as the
   two-pane layout above: below 900px none of this exists. */
.rpane-empty{display:none}
@media (min-width:900px){
  body.desk .railtabs{display:flex}
  body.desk #v-rooms{left:0;right:auto;width:340px;max-width:340px;margin:0;
    border-left:0;border-right:1px solid var(--line)}
  body.desk #v-rooms .tabbar{display:flex}
  body.desk #v-room{left:340px;max-width:none;margin:0;border-left:0;border-right:0}
  body.desk #v-room .rstream{padding:16px 22px}
  body.desk .rroster{display:block;width:200px;flex:none;border-left:1px solid var(--line);
    background:var(--panel);padding:12px 0;overflow-y:auto}
  body.desk .rpane-empty{display:flex;position:fixed;top:0;bottom:0;left:340px;right:0;
    align-items:center;justify-content:center;background:var(--bg)}
  body.desk .rpane-empty[hidden]{display:none}
  .rpe-card{max-width:430px;padding:26px 28px;border:1.5px dashed var(--mine-line);
    border-radius:20px;text-align:center}
  .rpe-big{font-family:var(--serif);font-size:23px;font-weight:600;line-height:1.25;color:var(--ink)}
}

/* The visitor's desk. The person the public link exists for — no trap phone,
   a wide browser window — kept the phone column even after joining, because
   .desk is earned by an identity this visitor does not have (the owner's
   report, 09/03/2026). roomdesk is the rooms-scoped answer: applyDesk puts
   it on a wide identity-less window exactly while a room is joined, and it
   unlocks ONLY the rules below — the rooms rail, the room pane, the standing
   people list. The sealed world's desk (the Chats rail, the pane cards, the
   keyboard walk) stays behind .desk, which this never grants, so no door or
   onboarding screen can meet any of it. Below 900px none of this exists. */
@media (min-width:900px){
  body.roomdesk #v-rooms{left:0;right:auto;width:340px;max-width:340px;margin:0;
    border-left:0;border-right:1px solid var(--line)}
  /* The rail's bottom tabs reach Chats and Settings, which do not exist for
     a visitor. The rail is the directory alone. */
  body.roomdesk #v-rooms .tabbar{display:none}
  body.roomdesk #v-room{left:340px;max-width:none;margin:0;border-left:0;border-right:0}
  body.roomdesk #v-room .rstream{padding:16px 22px}
  body.roomdesk .rroster{display:block;width:200px;flex:none;border-left:1px solid var(--line);
    background:var(--panel);padding:12px 0;overflow-y:auto}
}
