/* MorseHub web app. Every color comes from tokens.css (DESIGN.md). No new colors here. */
@import url("tokens.css");

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
html { -webkit-tap-highlight-color: transparent; }
body {
  margin: 0; background: var(--canvas); color: var(--ink);
  font: 400 var(--t-body)/var(--lh) var(--font);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
[hidden] { display: none !important; }
h1, h2, h3, p { margin: 0; }
button, input, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip { position: absolute; left: var(--s2); top: -100px; background: var(--card); color: var(--accent); padding: var(--s2) var(--s3); border-radius: var(--r-pill); border: 1px solid var(--line); z-index: 10; }
.skip:focus { top: var(--s2); }
/* Thin scrollbars in the palette, everywhere something scrolls. */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--r-pill); }
::-webkit-scrollbar-track { background: transparent; }
.mono { font-family: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace; }
.icon { display: block; flex: none; }

.wordmark { font-size: var(--t-mark); font-weight: 700; letter-spacing: -0.01em; display: inline-flex; align-items: center; gap: var(--s2); }
/* The mark (UIcons "comment dots", DESIGN.md "Logo"): Signal blue beside the ink wordmark. */
.mark { display: block; flex: none; }
.wordmark .mark { width: 22px; height: 22px; color: var(--accent); }
/* Typing dots: first load and first sync only, never for sends or status. */
.mark.typing .dot { animation: mh-typing 1.2s ease-in-out infinite; }
.mark.typing .dot:nth-of-type(2) { animation-delay: .15s; }
.mark.typing .dot:nth-of-type(3) { animation-delay: .3s; }
@keyframes mh-typing { 0%, 60%, 100% { transform: translateY(0); opacity: .4; } 30% { transform: translateY(-2.5px); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .mark.typing .dot { animation: none; } }
.loading-mark { display: grid; justify-items: center; gap: var(--s3); color: var(--ink-2); }
.loading-mark .mark { color: var(--accent); }

/* ---------- buttons and inputs ---------- */
.btn {
  min-height: var(--tap); padding: 0 var(--s5); border-radius: var(--r-pill);
  font-size: var(--t-small); font-weight: 600; border: 1px solid transparent; background: none;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2); white-space: nowrap;
  transition: background var(--fast) var(--ease), opacity var(--fast) var(--ease);
}
.btn.fill { background: var(--accent-fill); color: var(--on-accent); }
.btn.line { border-color: var(--accent); color: var(--accent); }
.btn.plain { color: var(--ink); border-color: var(--line); }
.btn.wide { width: 100%; }
.btn:disabled { opacity: .5; cursor: default; }
.btn.small { min-height: 32px; padding: 0 var(--s3); font-size: var(--t-meta); }
/* Small buttons keep a 44 px hit area around them. */
.btn.small { position: relative; }
.btn.small::after { content: ""; position: absolute; inset: -6px -2px; }

.icon-btn {
  width: var(--tap); height: var(--tap); border-radius: var(--r-pill); border: 0; background: none;
  display: inline-flex; align-items: center; justify-content: center; color: var(--ink); flex: none;
  transition: background var(--fast) var(--ease);
}

.input {
  width: 100%; min-height: var(--tap); padding: var(--s2) var(--s4);
  border: 1px solid var(--line); border-radius: var(--r-card); background: var(--card);
}
.input { font-size: var(--t-body); }
.input::placeholder { color: var(--ink-2); }
.input { caret-color: var(--accent); }
.input:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: transparent; }

.label { display: block; font-size: var(--t-meta); font-weight: 600; color: var(--ink-2); margin: 0 0 var(--s1); }
.help { color: var(--ink-2); }
.note, .row-help { color: var(--ink-2); font-size: var(--t-small); }
.error { color: var(--ink); font-size: var(--t-small); font-weight: 600; display: flex; gap: var(--s2); align-items: flex-start; }
.error .icon { margin-top: 1px; }

/* ---------- full screens (pairing, revoked) ---------- */
.screen { min-height: 100%; display: grid; place-items: center; padding: var(--s6) var(--s4); }
.screen-card {
  width: 100%; max-width: 440px; background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s6); display: grid; gap: var(--s4);
}
.screen-card h1 { font-size: var(--t-body); font-weight: 600; }
.screen-card .input { font-size: var(--t-body); letter-spacing: .04em; text-transform: uppercase; }
/* Paused screen and Settings, Subscription: the link to Stripe shows here in the desktop apps only. */
#paused-out:empty, #sub-out:empty { display: none; }
#paused-title:focus { outline: none; }

/* ---------- app shell ---------- */
#app { height: 100vh; height: 100dvh; display: flex; flex-direction: column; }
.bar {
  height: var(--bar); flex: none; display: flex; align-items: center; gap: var(--s3);
  padding: 0 var(--s2) 0 var(--s4); width: 100%; max-width: var(--max); margin: 0 auto;
}
.bar .wordmark { margin-right: auto; }
.pill {
  display: inline-flex; align-items: center; gap: var(--s2); min-height: 32px; padding: 0 var(--s3);
  border: 1px solid var(--line); border-radius: var(--r-pill); font-size: var(--t-meta); font-weight: 600;
  color: var(--ink-2); background: var(--card); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 60vw;
}
.pill .dot { width: 8px; height: 8px; border-radius: var(--r-pill); border: 1.5px solid var(--ink); flex: none; }
.pill.on .dot { background: var(--ink); }
.pill.on { color: var(--ink); }
/* Phone status colors (Chris 2026-10-02, the one exception to "no red, one accent"): green when the
   phone is connected, red when it is offline. The words always say it too. Nowhere else. */
.pill.ok { color: var(--ok); background: var(--ok-wash); border-color: var(--ok); }
.pill.ok .dot { background: var(--ok); border-color: var(--ok); }
.pill.bad { color: var(--bad); background: var(--bad-wash); border-color: var(--bad); }
.pill.bad .dot { border-color: var(--bad); }
/* The pill is a button that opens Settings at Phone (spec 17). Same look; the 44 px hit area sits around it. */
button.pill { font-family: inherit; cursor: pointer; position: relative; }
button.pill::after { content: ""; position: absolute; inset: -6px -2px; }
button.pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) { button.pill:not(.ok):not(.bad):hover { border-color: var(--ink-3); } }

.panes {
  flex: 1; min-height: 0; width: 100%; max-width: var(--max); margin: 0 auto;
  display: grid; grid-template-columns: var(--list) 1fr; gap: var(--s3); padding: 0 var(--s3) var(--s3);
}
.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--r-card); }
.pane { min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.pane-head {
  min-height: 56px; display: flex; align-items: center; gap: var(--s2);
  padding: var(--s1) var(--s2) var(--s1) var(--s4); border-bottom: 1px solid var(--line); flex: none;
}
.pane-title { font-size: var(--t-body); font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.convo-head { padding-left: var(--s2); }
.convo-head .back { display: none; }
.convo-h { flex: 1; min-width: 0; font: inherit; }
/* Name and number together are one button that copies the number (44 px+ tall). */
.convo-title {
  display: grid; width: 100%; min-height: var(--tap); padding: var(--s1) var(--s2); margin-left: calc(var(--s1) * -1);
  border: 0; background: none; text-align: left; border-radius: var(--r-row); justify-items: start; align-content: center;
  transition: background var(--fast) var(--ease);
}
.convo-title:disabled { cursor: default; }
.convo-title .pane-title { max-width: 100%; }
.convo-head > .avatar { margin-left: var(--s2); }
.sub { font-size: var(--t-meta); font-weight: 400; color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* ---------- banners ---------- */
.banner {
  margin: var(--s3); padding: var(--s3) var(--s4); border: 1px solid var(--line); border-radius: var(--r-card);
  display: grid; gap: var(--s2); font-size: var(--t-small);
}
.banner strong { font-weight: 600; font-size: var(--t-small); }
.banner p { color: var(--ink-2); }
.banner .actions { justify-content: flex-start; }
/* One line of text and its button side by side, so the banner stays short. */
.banner.row-banner { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.banner.row-banner .btn { flex: none; }
.convo-note { padding: var(--s2) var(--s4); font-size: var(--t-small); color: var(--ink-2); border-top: 1px solid var(--line); }

/* ---------- list header: "Mark all read" is a plain text button (it was a one-item menu) ---------- */
.list-head { gap: 0; }
.text-btn {
  min-height: var(--tap); padding: 0 var(--s3); border: 0; border-radius: var(--r-pill); background: none;
  font-size: var(--t-small); font-weight: 600; color: var(--accent); white-space: nowrap; flex: none;
}
.text-btn[aria-disabled="true"] { color: var(--ink-3); cursor: default; }

/* ---------- search: a filled field with the magnifier, like iMessage ---------- */
.search-row { padding: 0 var(--s3) var(--s2); flex: none; }
/* The label is the 44 px target; the filled field inside it is 36 px. */
.search-field { display: flex; align-items: center; min-height: var(--tap); cursor: text; position: relative; }
.search-icon { position: absolute; left: var(--s3); color: var(--ink-2); pointer-events: none; }
.search {
  width: 100%; height: 36px; border: 0; border-radius: var(--r-row); background: var(--wash);
  padding: 0 var(--s3) 0 34px; font-size: 16px; caret-color: var(--accent);
}
.search::placeholder { color: var(--ink-2); }
.search:focus { outline: 2px solid var(--accent); outline-offset: 0; }
.search::-webkit-search-cancel-button { cursor: pointer; }

/* ---------- thread list (iMessage rows, DESIGN.md "Thread list") ---------- */
.threads {
  list-style: none; margin: 0; padding: 0 var(--s2) var(--s2); overflow-y: auto; flex: 1;
}
.threads > li { position: relative; }
/* Inset hairline under each row: from the text column to the right padding. Not next to the
   selected or hovered row, whose filled shape needs no edge. */
.threads > li:not(.empty)::after {
  content: ""; position: absolute; left: calc(var(--s3) + 48px + var(--s3)); right: var(--s3); bottom: 0;
  height: 1px; background: var(--line); pointer-events: none;
}
.threads > li:last-child::after,
.threads > li.sel::after, .threads > li:has(+ li.sel)::after { display: none; }
.thread {
  width: 100%; display: grid; grid-template-columns: 48px 1fr; gap: 0 var(--s3); align-items: center;
  padding: var(--s3); background: none; border: 0; text-align: left; min-height: 72px;
  border-radius: var(--r-row); position: relative; color: var(--ink);
  transition: background var(--fast) var(--ease);
}
.thread:focus-visible { outline-offset: -2px; }
/* The open thread: a filled accent pill, all text white (white on #2c6bed is 4.7:1 in both themes). */
.thread[aria-current="true"], .thread[aria-current="true"]:hover { background: var(--accent-fill); color: var(--on-accent); }
.thread[aria-current="true"] .time, .thread[aria-current="true"] .preview { color: var(--on-accent); }
.thread[aria-current="true"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Unread: a dot in the left gutter, next to the avatar. */
.udot { position: absolute; left: 1px; top: 50%; width: 9px; height: 9px; margin-top: -4.5px; border-radius: var(--r-pill); }
.thread.unread .udot { background: var(--accent); }
.thread[aria-current="true"].unread .udot { background: var(--on-accent); }
.avatar {
  width: 48px; height: 48px; border-radius: var(--r-pill); background: var(--wash); color: var(--ink-2);
  display: grid; place-items: center; font-weight: 600; font-size: var(--t-body); letter-spacing: .02em; flex: none;
}
.avatar.hue-1 { background: var(--av1); color: var(--av1-ink); }
.avatar.hue-2 { background: var(--av2); color: var(--av2-ink); }
.avatar.hue-3 { background: var(--av3); color: var(--av3-ink); }
.avatar.hue-4 { background: var(--av4); color: var(--av4-ink); }
.avatar.hue-5 { background: var(--av5); color: var(--av5-ink); }
.avatar.hue-6 { background: var(--av6); color: var(--av6-ink); }
.avatar.small { width: 32px; height: 32px; font-size: var(--t-meta); }
.avatar.small .icon { width: 18px; height: 18px; }
.thread .row { min-width: 0; display: grid; gap: 2px; align-self: center; }
.thread .top { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; }
.thread .name { flex: 1; min-width: 0; font-size: var(--t-body); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thread .time { flex: none; font-size: var(--t-small); font-weight: 400; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.thread .preview {
  font-size: var(--t-small); line-height: 1.35; color: var(--ink-2); min-width: 0;
  display: flex; align-items: flex-start; gap: var(--s1);
}
.thread .preview .icon { margin-top: 2px; }
.thread .ptext strong { font-weight: 700; color: var(--ink); }
.thread[aria-current="true"] .ptext strong { color: var(--on-accent); }
/* An unsent draft leads its row's preview (drafts.js); never on the open row, whose draft is in the composer. */
.thread .ptext .draft { color: var(--accent); font-weight: 600; }
.thread .ptext {
  overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow-wrap: anywhere; min-height: calc(2 * 1.35em);
}
.thread.unread .name { font-weight: 700; }
.thread.unread .preview { color: var(--ink); }
.thread[aria-current="true"].unread .preview { color: var(--on-accent); }
.empty { padding: var(--s6) var(--s4); color: var(--ink-2); text-align: center; font-size: var(--t-small); }

/* ---------- conversation ---------- */
.messages {
  flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: var(--s2) var(--s4) var(--s4); display: flex; flex-direction: column;
}
/* Short threads sit at the bottom, next to the composer, like every messenger. */
.messages::before { content: ""; flex: 1 0 auto; }
.messages:focus { outline: none; }
/* Holds the message list and the jump-to-newest button that floats over its bottom edge. */
.msgs-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; position: relative; }
.to-bottom {
  position: absolute; right: var(--s4); bottom: var(--s3); width: var(--tap); height: var(--tap);
  border-radius: var(--r-pill); border: 1px solid var(--line); background: var(--card); color: var(--ink);
  display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
}
.to-bottom[hidden] { display: none; }
/* Time separator above a run more than an hour after the one before: "Today 10:25 AM". */
.stamp { align-self: center; text-align: center; font-size: var(--t-meta); color: var(--ink-3); margin: var(--s5) 0 var(--s2); font-variant-numeric: tabular-nums; }
.stamp strong { font-weight: 600; }
.msg { display: flex; flex-direction: column; max-width: min(75%, 520px); position: relative; }
/* Runs from one side sit 2 px apart; a new run starts 12 px down. */
.msg.first { margin-top: var(--s3); }
.msg.cont { margin-top: 2px; }
/* Room for the reaction badge that sits on the bubble's top corner. */
.msg.reacted { margin-top: var(--s4); }
.stamp + .msg { margin-top: 0; }
.msg.in { align-self: flex-start; align-items: flex-start; }
.msg.out { align-self: flex-end; align-items: flex-end; }
.msg.new { animation: slide var(--fast) var(--ease); }
@keyframes slide { from { transform: translateY(6px); opacity: 0; } to { transform: none; opacity: 1; } }
.bw { position: relative; max-width: 100%; display: flex; flex-direction: column; align-items: inherit; }
.bubble {
  padding: var(--s2) var(--s3); border-radius: var(--r-bubble); white-space: pre-wrap; overflow-wrap: anywhere;
  font-size: var(--t-body); line-height: var(--lh);
}
.msg.in .bubble { background: var(--wash); }
.msg.out .bubble { background: var(--accent-fill); color: var(--on-accent); }
.bubble a { color: inherit; text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; }
.msg.in .bubble a { color: var(--accent); }
/* Only the last bubble of a run has the tail corner. */
.msg.in.tail .bubble, .msg.in.tail .otp, .msg.in.tail .code-btn { border-bottom-left-radius: var(--r-tail); }
.msg.out.tail .bubble { border-bottom-right-radius: var(--r-tail); }
/* Not yet with the phone: outlined, full-contrast ink text (no opacity). Cancelled: hairline, secondary ink. */
.msg.pending .bubble { background: var(--card); color: var(--ink); border: 1px solid var(--accent); padding: calc(var(--s2) - 1px) calc(var(--s3) - 1px); }
.msg.muted .bubble { background: var(--card); color: var(--ink-2); border: 1px solid var(--line); padding: calc(var(--s2) - 1px) calc(var(--s3) - 1px); }
.otp {
  background: var(--wash); border-radius: var(--r-bubble);
  padding: var(--s3) var(--s4); display: grid; gap: var(--s2); justify-items: start;
}
.otp-code { font-size: 28px; font-weight: 600; line-height: 1.2; letter-spacing: .08em; font-variant-numeric: tabular-nums; user-select: all; }
.otp-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.otp-actions { justify-content: flex-start; }
.link {
  background: none; border: 0; padding: 0; min-height: 32px; color: var(--accent); font-size: var(--t-small); font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px; position: relative;
}
.link::after { content: ""; position: absolute; inset: -6px -4px; }
.code-btn {
  display: inline-flex; align-items: center; gap: var(--s2); min-height: var(--tap); padding: 0 var(--s4);
  border-radius: var(--r-bubble); border: 1px dashed var(--ink-3);
  background: var(--wash); font-weight: 600;
}
.meta { font-size: var(--t-meta); font-weight: 400; color: var(--ink-3); margin: 2px var(--s1) 0; display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.msg.in .meta { justify-content: flex-start; }
.meta .problem { color: var(--ink); display: inline-flex; align-items: center; gap: var(--s1); }
.meta .status { color: var(--ink-3); }
.meta .actions { gap: var(--s1); }
.meta.quiet { margin: 0; }
.call { align-self: center; display: flex; align-items: center; gap: var(--s2); font-size: var(--t-small); color: var(--ink-2); padding: var(--s2) var(--s3); border: 1px solid var(--line); border-radius: var(--r-pill); margin: var(--s3) 0 var(--s1); }
.call .icon { width: 16px; height: 16px; }
/* Tapbacks: emoji on the bubble's top corner (away from the tail), like iMessage. */
.reacts {
  position: absolute; top: -14px; display: flex; gap: 2px; padding: 1px 6px; z-index: 1;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-pill);
  font-size: 13px; line-height: 20px; white-space: nowrap; pointer-events: none;
}
.msg.in .reacts { right: -10px; }
.msg.out .reacts { left: -10px; }
/* A reaction to a text we cannot find: one quiet line, not a bubble. */
.msg.note { max-width: 85%; }
.tapnote { font-size: var(--t-small); color: var(--ink-2); padding: 2px var(--s1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
/* Per-message time and Copy: beside the bubble on hover, under it after a tap. */
.aside { display: none; align-items: center; gap: var(--s1); font-size: var(--t-meta); color: var(--ink-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
.msg.show .bw > .aside { display: flex; margin: 2px var(--s1) 0; }
.mini {
  position: relative; min-height: 28px; padding: 0 var(--s3); border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--card); color: var(--ink); font-size: var(--t-meta); font-weight: 600;
}
.mini::after { content: ""; position: absolute; inset: -8px -4px; }
.hidden-note { align-self: center; font-size: var(--t-small); color: var(--ink-2); padding: var(--s2) 0; }

.composer { display: flex; padding: var(--s2) var(--s3) calc(var(--s2) + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--line); flex: none; }
/* Filled at rest, a 2 px accent ring on focus. 44 px tall exactly: 22 px line + 11 px padding. */
.compose-box {
  flex: 1; min-width: 0; display: flex; align-items: flex-end; background: var(--wash); border-radius: 22px;
}
.compose-box:focus-within { outline: 2px solid var(--accent); outline-offset: 0; }
.compose {
  flex: 1; min-width: 0; height: var(--tap); max-height: 160px; resize: none; border: 0; background: none;
  font-size: 16px; line-height: 22px; padding: 11px var(--s1) 11px var(--s4); caret-color: var(--accent);
}
.compose::placeholder { color: var(--ink-2); }
.compose:focus, .compose:focus-visible { outline: none; }
.compose:disabled { cursor: not-allowed; }
/* 32 px circle inside the field, 44 px hit area. Always there; grey and inert when there is nothing to send. */
.send-btn {
  position: relative; width: 32px; height: 32px; margin: 6px; border-radius: var(--r-pill); border: 0; flex: none;
  background: var(--accent-fill); color: var(--on-accent); display: grid; place-items: center;
  transition: background var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.send-btn::after { content: ""; position: absolute; inset: -6px; }
.send-btn:disabled { background: var(--ink-3); color: var(--card); cursor: default; }
.send-btn .icon { width: 18px; height: 18px; }

.to-row { padding: var(--s3) var(--s4); border-bottom: 1px solid var(--line); display: grid; gap: var(--s1); flex: none; }
.to-results { list-style: none; margin: var(--s2) 0 0; padding: 0; max-height: 50vh; overflow-y: auto; }
.to-results button {
  width: 100%; min-height: var(--tap); display: flex; align-items: center; gap: var(--s3); text-align: left;
  padding: var(--s2) var(--s2); border: 0; background: none; border-radius: var(--r-card);
}
.to-results .sub { font-weight: 400; }
.to-results .row-title { font-weight: 500; }

/* ---------- dialogs ---------- */
.dialog {
  width: min(480px, calc(100vw - 32px)); max-height: calc(100vh - 48px); padding: 0; border: 1px solid var(--line);
  border-radius: var(--r-card); background: var(--card); color: var(--ink); box-shadow: var(--shadow-dialog);
}
.dialog.small { width: min(400px, calc(100vw - 32px)); }
.dialog::backdrop { background: color-mix(in srgb, var(--canvas) 75%, transparent); }
.dialog-head { display: flex; align-items: center; padding: var(--s2) var(--s2) var(--s2) var(--s5); border-bottom: 1px solid var(--line); }
.dialog-head h2 { flex: 1; font-size: var(--t-body); font-weight: 600; }
.dialog-body { padding: var(--s5); display: grid; gap: var(--s4); }
.dialog-body h2 { font-size: var(--t-body); font-weight: 600; }
.set { display: grid; gap: var(--s3); }
.set + .set { border-top: 1px solid var(--line); padding-top: var(--s4); }
.set h3 { font-size: var(--t-meta); font-weight: 600; color: var(--ink-2); text-transform: uppercase; letter-spacing: .04em; }
.devices { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.device { display: flex; align-items: center; gap: var(--s3); min-height: var(--tap); }
.device > div { flex: 1; min-width: 0; }
.device .row-title { display: block; font-weight: 600; }
.row-toggle { display: flex; align-items: center; gap: var(--s4); justify-content: space-between; min-height: var(--tap); }
.row-toggle > .btn { flex: none; }
.row-toggle .switch[hidden] { display: none; }
/* Desktop app downloads: square tiles with the OS mark (Settings and the homepage). */
.os-row { display: grid; gap: var(--s3); }
.os-tiles { display: flex; gap: var(--s3); flex-wrap: wrap; }
.os-tile {
  width: 128px; aspect-ratio: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s2); border: 1px solid var(--line); border-radius: var(--r-card); background: var(--wash); color: var(--ink);
  text-decoration: none; transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.os-tile .icon { color: var(--accent); }
.os-tile .os-name { font-weight: 600; font-size: var(--t-small); }
.os-tile .os-sub { font-size: var(--t-meta); color: var(--ink-2); }
.os-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) { .os-tile:hover { border-color: var(--accent); } }
.row-title { display: block; font-weight: 600; }
.row-help { display: block; }
.actions { display: flex; gap: var(--s2); flex-wrap: wrap; justify-content: flex-end; }

.switch {
  appearance: none; -webkit-appearance: none; width: 48px; height: 28px; border-radius: var(--r-pill); flex: none;
  border: 1px solid var(--line); background: var(--wash); position: relative; cursor: pointer; margin: 8px 0;
  transition: background var(--fast) var(--ease);
}
.switch::before {
  content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: var(--r-pill);
  background: var(--card); border: 1px solid var(--line); transition: transform var(--fast) var(--ease);
}
.switch:checked { background: var(--accent-fill); border-color: var(--accent-fill); }
.switch:checked::before { transform: translateX(20px); border-color: var(--accent-fill); }

.seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-pill); padding: 3px; gap: 2px; justify-self: start; }
.seg label { position: relative; }
.seg input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.seg span { display: inline-flex; align-items: center; min-height: 38px; padding: 0 var(--s4); border-radius: var(--r-pill); font-size: var(--t-small); font-weight: 600; color: var(--ink-2); }
.seg input:checked + span { background: var(--wash); color: var(--ink); }
.seg input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 1px; }

.code-box {
  font-size: 28px; font-weight: 600; letter-spacing: .04em; line-height: 1.5; text-align: center; overflow-wrap: normal;
  padding: var(--s4); border: 1px solid var(--line); border-radius: var(--r-card); background: var(--canvas); user-select: all;
}
.code-box .grp { white-space: nowrap; }
/* Add a computer: the QR on a white card (scans in dark mode too), the code with a copy button beside it. */
.qr-wrap { display: flex; justify-content: center; }
.qr-wrap .qr { border-radius: var(--r-row); border: 1px solid var(--line); max-width: 100%; height: auto; }
.code-row { display: flex; align-items: center; justify-content: center; gap: var(--s2); }
.code-row .code-box { margin: 0; min-width: 0; }
.code-row .code-copy { flex: none; }
@media (max-width: 479.98px) { .code-box { font-size: 22px; } }
.countdown { text-align: center; font-size: var(--t-small); color: var(--ink-2); font-weight: 600; }
.warn { display: flex; gap: var(--s3); align-items: flex-start; font-size: var(--t-small); color: var(--ink); }
.warn .icon { width: 20px; height: 20px; margin-top: 1px; }

/* API keys (spec 13): name, access choice, the key once, the command to run. */
.api-form { display: grid; gap: var(--s4); }
.choices { border: 0; margin: 0; padding: 0; min-width: 0; display: grid; gap: var(--s1); }
.choices legend { padding: 0; }
.choice { display: flex; gap: var(--s3); align-items: flex-start; min-height: var(--tap); padding: var(--s2) 0; cursor: pointer; }
.choice input { flex: none; width: 20px; height: 20px; margin: 2px 0 0; accent-color: var(--accent-fill); cursor: pointer; }
.choice > span { min-width: 0; }
.cmd {
  font-size: var(--t-small); line-height: 1.5; padding: var(--s3); border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--canvas); overflow-wrap: anywhere; user-select: all;
}
.wrap-any { overflow-wrap: anywhere; }
.actions.center { justify-content: center; }

.toast {
  position: fixed; left: 50%; bottom: var(--s6); transform: translateX(-50%); max-width: calc(100vw - 32px);
  background: var(--ink); color: var(--card); padding: var(--s3) var(--s5); border-radius: var(--r-pill);
  font-size: var(--t-small); font-weight: 600; z-index: 20;
}

/* ---------- hover (only where there is a pointer that hovers) and pressed ---------- */
@media (hover: hover) {
  .btn.fill:hover { opacity: .9; }
  .btn.line:hover, .btn.plain:hover { background: var(--wash); }
  .icon-btn:hover { background: var(--wash); }
  .text-btn:not([aria-disabled="true"]):hover { background: var(--wash); }
  .thread:hover { background: color-mix(in srgb, var(--wash) 60%, var(--card)); }
  .threads > li:hover::after, .threads > li:has(+ li:hover)::after { display: none; }
  .to-results button:hover { background: var(--wash); }
  .convo-title:not(:disabled):hover { background: var(--wash); }
  .mini:hover { background: var(--wash); }
  /* Time and actions beside the bubble while the pointer is on it. The strip runs the full height of
     the message (padding bridges the gap), so sliding out sideways from anywhere on it keeps them up. */
  .msg:not(.show) .bw:hover > .aside { display: flex; position: absolute; top: 0; bottom: 0; align-items: flex-start; padding-top: 3px; }
  .msg.in:not(.show) .bw:hover > .aside { left: 100%; padding-left: var(--s2); }
  .msg.out:not(.show) .bw:hover > .aside { right: 100%; padding-right: var(--s2); flex-direction: row-reverse; }
}
.btn:active, .icon-btn:active, .text-btn:not([aria-disabled="true"]):active, .to-results button:active, .convo-title:not(:disabled):active, .mini:active {
  background: color-mix(in srgb, var(--ink) 10%, var(--wash));
}
.btn.fill:active, .send-btn:not(:disabled):active { opacity: 1; transform: scale(.96); }
.thread:active { background: color-mix(in srgb, var(--ink) 6%, var(--wash)); }
.thread[aria-current="true"]:active { background: var(--accent-fill); }

/* ---------- one pane under 720 px ---------- */
@media (max-width: 719.98px) {
  .panes { grid-template-columns: 1fr; padding: 0; gap: 0; }
  .card { border-radius: 0; border-left: 0; border-right: 0; border-bottom: 0; }
  .bar { padding-left: var(--s4); }
  body:not(.in-convo) #convo-pane { display: none; }
  body.in-convo #list-pane { display: none; }
  .convo-head .back { display: inline-flex; }
  .convo-head > .avatar { margin-left: 0; }
  .msg { max-width: 85%; }
  .pill { max-width: 52vw; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- pictures and group texts (spec 4a) ---------- */
.composer { flex-direction: column; gap: var(--s2); }
.compose-row { display: flex; align-items: flex-end; gap: var(--s1); }
.attach-btn { flex: none; color: var(--ink-2); }
.attach-btn:disabled { color: var(--ink-3); cursor: not-allowed; }
.attach-preview { display: flex; align-items: center; gap: var(--s2); }
.attach-preview img { width: 64px; height: 64px; object-fit: cover; border-radius: var(--r-card); border: 1px solid var(--line); }
.attach-preview .row-help { flex: 1; min-width: 0; }
.who { font-size: var(--t-meta); color: var(--ink-2); margin: 0 var(--s3) 2px; }
.pics { display: grid; gap: 2px; margin-bottom: 2px; }
.pic {
  display: block; padding: 0; border: 0; background: var(--wash); border-radius: var(--r-bubble); overflow: hidden;
  max-width: min(280px, 70vw); cursor: zoom-in;
}
.pic img { display: block; max-width: 100%; max-height: 320px; height: auto; }
.pic-state {
  display: inline-flex; align-items: center; gap: var(--s2); min-height: var(--tap); padding: var(--s2) var(--s3);
  border-radius: var(--r-bubble); border: 1px dashed var(--ink-3); background: var(--wash); color: var(--ink);
  font-size: var(--t-small); text-align: left; max-width: min(280px, 70vw);
}
button.pic-state { cursor: pointer; font-weight: 600; }
.pic-state .icon { width: 18px; height: 18px; flex: none; }
.picview { width: min(96vw, 1100px); }
.picview img { display: block; max-width: 100%; max-height: 80vh; margin: 0 auto; }
.media { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.pic-video { display: block; max-width: min(280px, 70vw); max-height: 320px; border-radius: var(--r-bubble); background: var(--wash); }
.media audio { max-width: 200px; }
.banner.update.warn { border-color: var(--ink); }
/* Phone health (spec 17): a texts-permission problem gets the ink border and cannot be hidden. */
.banner.health.warn { border-color: var(--ink); }
/* The generic .warn line style (a flex row) must not turn a warn banner into columns. */
.banner.health.warn, .banner.update.warn { display: grid; }

/* ---------- Settings, Phone (spec 17) ---------- */
.phone-health { display: grid; gap: var(--s3); }
.ph-row { display: flex; align-items: center; gap: var(--s3); min-height: var(--tap); }
.ph-row > div { flex: 1; min-width: 0; }
.ph-row .row-title { font-size: var(--t-body); }
.ph-row > .btn, .ph-row > .text-link { flex: none; }
.ph-row .row-help, .ph-again .row-help { color: var(--ink-2); }
.checks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s1); }
.checks li {
  display: grid; grid-template-columns: 18px 1fr auto; column-gap: var(--s3); row-gap: var(--s1);
  align-items: center; min-height: var(--tap); padding: var(--s1) 0; border-radius: var(--r-row);
}
.checks .icon { width: 18px; height: 18px; color: var(--ink); }
.checks li.ok .icon { color: var(--ink-3); }
.check-name { font-size: var(--t-body); font-weight: 500; }
.check-state { font-size: var(--t-small); color: var(--ink-2); }
.checks li.bad .check-state { color: var(--ink); font-weight: 600; }
.check-fix { grid-column: 2 / -1; font-size: var(--t-small); color: var(--ink-2); }
.checks li:focus, .ph-row:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
#phone-title:focus { outline: none; }
#phone-title:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.checks-more summary { min-height: var(--tap); display: inline-flex; align-items: center; cursor: pointer; color: var(--accent); font-size: var(--t-small); font-weight: 600; }
.checks-more summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-row); }
.ph-again { display: grid; gap: var(--s2); justify-items: start; }

/* ---------- delete from the web (spec 14) ---------- */
.icon-mini { padding: 0 var(--s1); display: inline-flex; align-items: center; justify-content: center; min-width: 26px; }
.icon-mini .icon { width: 16px; height: 16px; }
/* Menu sheet: a list of actions, 12 px rows (DESIGN.md menus). A bottom sheet at phone width. */
.menu .dialog-body { padding: var(--s3); gap: var(--s1); }
.menu-title { font-size: var(--t-small); font-weight: 600; color: var(--ink-2); padding: var(--s1) var(--s3) var(--s2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-items { display: grid; gap: 2px; }
.menu-item {
  display: flex; align-items: center; gap: var(--s3); width: 100%; min-height: var(--tap); padding: 0 var(--s3);
  border: 0; border-radius: var(--r-row); background: none; color: var(--ink); text-align: left; font-size: var(--t-body);
}
.menu-item .icon { color: var(--ink-2); }
.menu-item.warn { font-weight: 600; }
.menu-item.warn .icon { color: var(--ink); }
.menu-item.cancel { justify-content: center; color: var(--ink-2); margin-top: var(--s1); border-top: 1px solid var(--line); border-radius: 0 0 var(--r-row) var(--r-row); }
.menu-item:active { background: color-mix(in srgb, var(--ink) 10%, var(--wash)); }
@media (hover: hover) { .menu-item:hover { background: var(--wash); } }
@media (max-width: 719.98px) {
  .dialog.menu { width: 100%; max-width: 100%; margin: auto 0 0; border-radius: var(--r-card) var(--r-card) 0 0; padding-bottom: env(safe-area-inset-bottom, 0px); }
}
/* Picking messages: a round check on the side away from the screen edge; picked = accent ring. */
.messages.selecting .msg, .messages.selecting .call { cursor: pointer; }
.messages.selecting .msg * { pointer-events: none; }
.messages.selecting .aside { display: none !important; }
.pick {
  position: absolute; top: 50%; width: 22px; height: 22px; margin-top: -11px; border-radius: var(--r-pill);
  border: 1.5px solid var(--ink-3); background: var(--card); display: grid; place-items: center; color: var(--on-accent);
}
.msg.in > .pick { right: -32px; }
.msg.out > .pick, .call > .pick { left: -32px; }
.call { position: relative; }
.picked > .pick { background: var(--accent-fill); border-color: var(--accent-fill); }
.msg.picked .bw { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-bubble); }
.call.picked { border-color: var(--accent); }
.unpickable { opacity: .55; }
.pickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--r-bubble); }
.select-bar {
  display: flex; align-items: center; gap: var(--s2); flex: none; flex-wrap: wrap;
  padding: var(--s2) var(--s3) calc(var(--s2) + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--line);
}
.select-count { flex: 1; min-width: 0; font-size: var(--t-small); font-weight: 600; text-align: center; }
/* Long press on a touch screen opens the menu: no text-selection callout on the bubbles there. */
@media (hover: none) {
  .msg .bubble, .msg .pic img, .thread { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
}
.select-count { white-space: nowrap; }
/* Narrow: the count and Cancel on top, the two deletes side by side under them. */
@media (max-width: 559.98px) {
  .select-count { order: -2; flex: 1 1 calc(100% - 96px); text-align: left; padding: 0 var(--s1); }
  .select-bar #select-cancel { order: -1; }
  .select-bar #select-delete, .select-bar #select-perm { flex: 1 1 auto; }
}

/* ---------- emoji panel (composer smiley button) ---------- */
/* Placed by emoji-picker.js just above the composer (the text stays visible). Wide: a 360 px panel
   over the smiley button. Narrow: full width with square sides, like a sheet. No dimming. */
.emoji-panel {
  position: fixed; margin: 0; width: min(360px, calc(100vw - 16px)); overflow: hidden;
  height: min(440px, calc(100vh - 16px)); max-height: calc(100vh - 16px); /* older WebKitGTK: no dvh */
  height: min(440px, calc(100dvh - 16px)); max-height: calc(100dvh - 16px);
}
.emoji-panel[open] { display: flex; flex-direction: column; }
.emoji-panel::backdrop { background: transparent; }
.emo-search { padding: var(--s2) var(--s3) 0; flex: none; }
.emo-tabs { display: flex; gap: 2px; padding: var(--s1) var(--s2); border-bottom: 1px solid var(--line); flex: none; overflow-x: auto; scrollbar-width: none; }
.emo-tabs::-webkit-scrollbar { display: none; }
.emo-tab {
  flex: 1 0 36px; min-width: 36px; height: var(--tap); border: 0; border-radius: var(--r-row); background: none;
  font-size: 20px; line-height: 1; display: grid; place-items: center; position: relative; opacity: .6;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", var(--font);
}
.emo-tab[aria-selected="true"] { opacity: 1; background: var(--wash); }
.emo-tab[aria-selected="true"]::after { content: ""; position: absolute; left: 25%; right: 25%; bottom: 3px; height: 2px; border-radius: var(--r-pill); background: var(--accent); }
.emo-scroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0 var(--s2) var(--s2); }
.emo-head {
  position: sticky; top: 0; z-index: 1; background: var(--card);
  font-size: var(--t-meta); font-weight: 600; color: var(--ink-2); padding: var(--s2) var(--s2) var(--s1);
}
.emo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--tap), 1fr)); }
.emo {
  height: var(--tap); min-width: 0; border: 0; border-radius: var(--r-row); background: none; padding: 0;
  font-size: 26px; line-height: 1; display: grid; place-items: center;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", var(--font);
  /* Off-screen emoji skip layout and paint, so a 400-emoji tab opens at once. */
  content-visibility: auto; contain-intrinsic-size: auto var(--tap) auto var(--tap);
}
.emo:active, .emo-tab:active { background: color-mix(in srgb, var(--ink) 10%, var(--wash)); }
.emo:focus-visible, .emo-tab:focus-visible { outline-offset: -2px; }
.emo-none { padding: var(--s5) var(--s3); color: var(--ink-2); font-size: var(--t-small); text-align: center; }
@media (hover: hover) { .emo:hover, .emo-tab:hover { background: var(--wash); opacity: 1; } }
@media (max-width: 719.98px) {
  .dialog.emoji-panel {
    width: 100%; max-width: 100%; height: min(50vh, 400px); height: min(50dvh, 400px);
    border-left: 0; border-right: 0; border-radius: var(--r-card) var(--r-card) 0 0;
  }
}

/* ---------- calls from the computer (spec 18) ---------- */
/* Call in the conversation header: a text button with the phone icon, 44 px tall, like Mark all read. */
.call-btn { display: inline-flex; align-items: center; gap: var(--s1); }
.call-btn .icon { width: 18px; height: 18px; }
/* The one call bar above both panes. Off, it stays on the page out of sight, so its live region is heard. */
.callbar { width: 100%; max-width: var(--max); margin: 0 auto; padding: 0 var(--s3) var(--s3); flex: none; }
.callbar:not(.on) { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.callbar-in {
  display: flex; align-items: center; gap: var(--s3); min-height: var(--bar); padding: var(--s2) var(--s2) var(--s2) var(--s4);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-card);
}
.callbar.ringing .callbar-in { border-color: var(--accent); }
.callbar-ico { color: var(--accent); flex: none; }
.callbar-text { flex: 1; min-width: 0; font-size: var(--t-body); font-weight: 600; overflow-wrap: anywhere; }
/* 44 px buttons, 12 px apart (HIG: bordered controls). Waiting for the phone: dimmed, still focusable. */
.callbar-acts { display: flex; gap: var(--s3); flex: none; }
.btn[aria-disabled="true"] { opacity: .5; cursor: default; }
@media (max-width: 719.98px) {
  .callbar { padding: 0; }
  .callbar-in { border-radius: 0; border-width: 1px 0 0; padding: var(--s2) var(--s4); }
}
/* Narrow: the words on top, the buttons side by side under them. */
@media (max-width: 479.98px) {
  .callbar-in { flex-wrap: wrap; }
  .callbar-text { flex-basis: calc(100% - 20px - var(--s3)); }
  .callbar-acts { width: 100%; }
  .callbar-acts .btn { flex: 1; }
}
/* A call made from a computer: the same call line, its state under it like a delivery line. */
.dial { align-self: center; display: flex; flex-direction: column; align-items: center; margin: var(--s3) 0 var(--s1); position: relative; }
.dial .call { margin: 0; }
.dial .meta { justify-content: center; margin-top: var(--s1); }
.messages.selecting .dial { cursor: pointer; }
.messages.selecting .dial .actions { display: none; }
.dial > .pick { left: -32px; top: 8px; margin-top: 0; }
.dial.picked .call { border-color: var(--accent); }
/* Settings, Phone: an ADB command as the fix wraps anywhere. */
.check-fix code { font-size: var(--t-meta); overflow-wrap: anywhere; }
/* The consent box before a free trial or a checkout (terms, privacy, 18+; 2026-10-12): a .choice row, 44 px tall,
   read left to right even inside the centered landing sections. */
.terms-field { text-align: left; max-width: 34em; }
.terms-field .choice { font-size: 15px; line-height: 1.45; color: var(--ink); }
.terms-field .choice input { margin-top: 1px; }
