:root {
  color: #14213d;
  background: #f6f3ed;
  font-family: ui-rounded, "SF Pro Rounded", "Segoe UI", sans-serif;
  font-synthesis: none;
}

* {
  box-sizing: border-box;
}

body {
  min-width: 320px;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  background: #f6f3ed;
}

button {
  color: inherit;
  font: inherit;
}

.app-shell {
  width: min(100%, 42rem);
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0 auto;
  padding: max(1.5rem, env(safe-area-inset-top)) 1.25rem
    max(1.5rem, env(safe-area-inset-bottom));
}

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.eyebrow {
  margin: 0 0 0.2rem;
  color: #b53d2d;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

h1,
h2,
p {
  overflow-wrap: anywhere;
}

h1 {
  margin: 0;
  color: #071b3f;
  font-size: clamp(2rem, 8vw, 3rem);
  line-height: 1;
}

.icon-button {
  display: grid;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 1px solid #c9c3b9;
  border-radius: 50%;
  background: #ffffff;
  place-items: center;
}

.icon-button span {
  font-size: 1.5rem;
  line-height: 1;
}

/* The chat list (CB-022): one row per chat the phone holds, each a button
   because tapping the row is what opens that chat. The nickname takes the
   whole first line -- it is the sender's own text and can be any length in
   any script -- and the pair, the tier label, the time, and the unread count
   wrap beneath it rather than being pushed off a narrow phone. */
.chat-list {
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
}

.chat-list li + li {
  margin-top: 0.5rem;
}

.chat-entry {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.75rem;
  align-items: baseline;
  width: 100%;
  min-height: 3.5rem;
  padding: 0.75rem 1rem;
  border: 1px solid #c9c3b9;
  border-radius: 1rem;
  background: #ffffff;
  text-align: start;
}

.chat-entry-name {
  flex: 1 0 100%;
  color: #071b3f;
  font-weight: 800;
  overflow-wrap: anywhere;
}

.chat-entry-pair {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  color: #5d6370;
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

/* A mark rather than a word between the two languages, so the pair needs no
   chrome string and reads the same in a right-to-left interface. */
.chat-entry-language + .chat-entry-language::before {
  margin-inline-end: 0.35rem;
  content: "\00b7";
}

.chat-entry-time {
  margin-inline-start: auto;
  color: #5d6370;
  font-size: 0.85rem;
}

.chat-entry-unread {
  min-width: 1.5rem;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  color: #ffffff;
  background: #b53d2d;
  font-size: 0.8rem;
  font-weight: 800;
  text-align: center;
}

.empty-state {
  display: grid;
  min-height: 65vh;
  padding: 3rem 1rem;
  text-align: center;
  place-content: center;
  justify-items: center;
}

.empty-state h2 {
  margin: 1.5rem 0 0.5rem;
  color: #071b3f;
  font-size: 1.35rem;
}

.empty-state p {
  max-width: 24rem;
  margin: 0;
  color: #5d6370;
  line-height: 1.5;
}

/* The empty state gives way to the list the moment the phone holds a chat.
   Its `display: grid` would otherwise beat the user agent's
   `[hidden] { display: none }` and leave both on screen at once -- the trap
   the presence rows and the screens below document. */
.empty-state[hidden] {
  display: none;
}

.empty-mark {
  position: relative;
  width: 6.5rem;
  height: 5rem;
}

.empty-mark span {
  position: absolute;
  width: 4.5rem;
  height: 3.5rem;
  border-radius: 1.25rem;
}

.empty-mark span:first-child {
  top: 0;
  left: 0;
  background: #ff715b;
}

.empty-mark span:last-child {
  right: 0;
  bottom: 0;
  border: 0.35rem solid #071b3f;
  background: #d7f4e3;
}

.install-sheet,
.invite-sheet,
.privacy-sheet,
.shorthand-sheet,
.transfer-sheet,
.settings-sheet {
  width: min(calc(100% - 2rem), 32rem);
  max-height: calc(100dvh - max(1rem, env(safe-area-inset-top)));
  margin: auto auto 0;
  padding: 0.75rem 1.5rem calc(1.5rem + env(safe-area-inset-bottom));
  border: 0;
  border-radius: 1.5rem 1.5rem 0 0;
  color: #14213d;
  background: #ffffff;
  overflow-y: auto;
}

.install-sheet::backdrop,
.invite-sheet::backdrop,
.privacy-sheet::backdrop,
.shorthand-sheet::backdrop,
.transfer-sheet::backdrop,
.settings-sheet::backdrop {
  background: rgb(7 27 63 / 55%);
}

.install-sheet h2,
.invite-sheet h2,
.privacy-sheet h2,
.shorthand-sheet h2,
.transfer-sheet h2,
.settings-sheet h2 {
  margin: 1rem 0;
  color: #071b3f;
}

.install-sheet ol {
  padding-left: 1.5rem;
  line-height: 1.5;
}

.install-sheet li + li {
  margin-top: 0.75rem;
}

.settings-sheet > .text-button {
  display: block;
  width: 100%;
  padding: 0.75rem 0;
  text-align: start;
}

/* The invite sheet (CB-044) and the transfer sheet (CB-028). The link is shown
   in full, because the person reading it is about to hand it to someone, or to
   another phone of their own, and a truncated secret is not a link; it wraps
   anywhere rather than overflowing a phone's width. */
.invite-link,
.transfer-link {
  margin: 0.75rem 0;
  padding: 0.75rem;
  border-radius: 0.65rem;
  background: #f2efe9;
  color: #14213d;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
  user-select: all;
}

.invite-scan {
  margin: 1.25rem 0 0.5rem;
  color: #343b4b;
  line-height: 1.5;
}

/* Drawn on this phone, so it is sized in the sheet rather than by whatever a
   server returned. A white plate under the symbol keeps the contrast a
   camera needs whatever the sheet sits on. */
.invite-qr {
  width: min(100%, 16rem);
  margin: 0 auto;
  padding: 0;
  border-radius: 0.75rem;
  background: #ffffff;
}

.invite-qr svg {
  display: block;
  width: 100%;
  height: auto;
}

.link-warning {
  margin: 0.75rem 0 0;
  color: #343b4b;
  line-height: 1.5;
}

.shorthand-description,
.transfer-description {
  color: #343b4b;
  line-height: 1.5;
}

.shorthand-sheet label {
  display: block;
  margin-top: 0.75rem;
  font-weight: 700;
}

.address-sheet form {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.address-sheet input,
.address-sheet select {
  min-height: 2.75rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid #8f9299;
  border-radius: 0.65rem;
  color: inherit;
  background: #ffffff;
  font: inherit;
}

.address-sheet .primary-button,
.clear-sheet .primary-button {
  width: 100%;
  margin-top: 1rem;
}

.address-sheet .text-button,
.clear-sheet .text-button {
  width: 100%;
}

.shorthand-sheet input,
.shorthand-sheet textarea {
  width: 100%;
  margin-top: 0.35rem;
  padding: 0.7rem;
  border: 1px solid #8f9299;
  border-radius: 0.65rem;
  color: inherit;
  background: #ffffff;
  font: inherit;
}

.shorthand-list {
  margin: 1rem 0;
  padding: 0;
  list-style: none;
}

.shorthand-list li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid #dedad2;
}

/* The row's own text is the sender's shorthand, so it wraps rather than
   pushing the remove control off a narrow phone. */
.shorthand-entry {
  flex: 1;
  padding: 0.5rem 0;
  border: 0;
  background: transparent;
  text-align: start;
  overflow-wrap: anywhere;
}

.shorthand-mine {
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  color: #071b3f;
  background: #d7f4e3;
  font-size: 0.8rem;
  font-weight: 700;
}

.shorthand-remove {
  width: 2.75rem;
  min-width: 2.75rem;
  height: 2.75rem;
  border: 1px solid #c9c3b9;
  border-radius: 50%;
  background: #ffffff;
  font-size: 1.4rem;
}

.privacy-disclosure {
  color: #343b4b;
  line-height: 1.5;
}

.privacy-disclosure p + p {
  margin-top: 1rem;
}

.sheet-handle {
  width: 2.5rem;
  height: 0.3rem;
  margin: 0 auto;
  border-radius: 999px;
  background: #c9c3b9;
}

.primary-button {
  width: 100%;
  min-height: 3rem;
  margin-top: 0.75rem;
  border: 0;
  border-radius: 999px;
  color: #ffffff;
  background: #071b3f;
  font-weight: 800;
}

button:focus-visible {
  outline: 0.2rem solid #ff715b;
  outline-offset: 0.2rem;
}

@media (display-mode: standalone) {
  #show-install-instructions {
    display: none;
  }
}

/* Confirm-script choice (CB-013): the secondary script sits beneath the
   primary one, set apart by colour only — shrinking it would drop it under
   the profile's minimum type size. */
.card-text-secondary {
  margin-top: 0.35rem;
  color: #343b4b;
}

.card-candidates {
  margin: 0;
  padding-inline-start: 1.5rem;
}

/* Presence indicator (CB-071): the chat header carries one row per state
   and client/js/presence.js shows exactly one of them. The dot is decoration
   — colour is never the only signal, because each state is its own sentence
   in the chat's language. All three clear 4.5:1 against the page. */
.presence {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  margin: 0.2rem 0 0;
  font-size: 0.85rem;
  font-weight: 600;
}

/* An author `display` beats the user agent's `[hidden] { display: none }`,
   so the hidden rows have to be told again -- without this every state shows
   at once and `online` is on screen next to `unknown`. */
.presence[hidden] {
  display: none;
}

.presence::before {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
  content: "";
  flex: none;
}

.presence[data-presence="online"] {
  color: #1f6f4a;
}

.presence[data-presence="unknown"] {
  color: #8a5a12;
}

.presence[data-presence="offline"] {
  color: #5b6472;
}

/* Provisional-tier label (CB-017): the same badge marks the language a
   picker has selected, the open chat's header, and the trust-mode toggle it
   holds off. Colour is never the only signal — the badge is its own text. */
.tier-badge {
  display: inline-block;
  margin: 0.35rem 0 0;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  color: #071b3f;
  background: #ffe0b3;
  font-size: 0.8rem;
  font-weight: 700;
}

/* Profile-aware message text (CB-014). JavaScript supplies the profile's
   minimum as a custom property and explicit lang/dir attributes on every
   bubble line, card line/candidate, and the composer. The system UI stack
   leaves glyph selection and shaping to each platform's installed script
   fonts; visual font coverage remains part of the CB-084 device gate.
   Isolation contains each mixed-direction message's bidi context. */
[data-profile-text] {
  min-width: 0;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: max(1rem, var(--profile-min-type-size, 18px));
  line-height: 1.5;
  overflow-wrap: anywhere;
  text-align: start;
  unicode-bidi: isolate;
  white-space: pre-wrap;
  word-break: normal;
}

/* Message bubbles (CB-070). The log is the chat screen's own scroll, so the
   composer under it stays put while the conversation moves; client/js/chat-log.js
   drives scrollTop against exactly this box, and CB-072's backfill is what
   scrolling to the top of it asks for. */
.message-log {
  flex: 1;
  min-height: 0;
  margin: 0;
  padding: 0.5rem 1.25rem;
  overflow-y: auto;
  list-style: none;
}

/* One bubble, sized to its own text so the two sides of a conversation are
   told apart by position as well as by colour. */
.message {
  display: flex;
  flex-direction: column;
  align-items: start;
  width: fit-content;
  max-width: 85%;
  margin-top: 0.5rem;
  padding: 0.6rem 0.85rem;
  border: 1px solid #c9c3b9;
  border-radius: 1rem;
  background: #ffffff;
}

.message[data-own="yes"] {
  margin-inline-start: auto;
  border-color: #b6c4e4;
  background: #e7edfa;
}

.message p {
  margin: 0.35rem 0 0;
}

.message p:first-child {
  margin-top: 0;
}

/* Section 9 asks for the lead version large and the one under it legible. The
   lead line is set above the profile's minimum; the line beneath keeps that
   minimum and is set apart by colour, because shrinking it would drop it
   under the floor CB-014 puts on every message line -- the same rule the
   confirm card's secondary script follows. */
.message [data-bubble-line="primary"] {
  color: #071b3f;
  font-size: max(1.25rem, var(--profile-min-type-size, 18px));
}

.message [data-bubble-line="secondary"],
.message [data-bubble-line="raw"] {
  color: #343b4b;
}

/* The tap that reaches the raw original lines up with the text above it.
   Scoped to a bubble so it wins over the plain .text-button below. */
.message .message-raw-toggle {
  padding: 0;
}

/* A frame that did not decrypt or authenticate (CB-042). The row is drawn
   rather than passed over, so the bubble has to read as a message that could
   not be shown and not as a message that says nothing: a dashed edge marks it
   apart from the bubbles around it without asking anyone to tell two colours
   apart, and the line inside it keeps the type-size floor CB-014 puts under
   every other line on a bubble. The side it is drawn on is left alone, so an
   unreadable frame still sits where its sender role puts it.

   Not italic, which is the obvious way to set a line like this apart and the
   wrong one here: the word is shown in all thirteen languages, and most of
   the nine scripts they declare have no italic, so a browser would slant the
   glyphs itself and make the one line a person most needs to read the hardest
   one to. The border carries the distinction instead. */
.message[data-opened="no"] {
  border-style: dashed;
}

.message .message-placeholder {
  color: #343b4b;
  font-size: max(1rem, var(--profile-min-type-size, 18px));
}

/* The auto tag (CB-062): a word rather than a mark, so a bubble sent without
   its owner tapping confirm says so to a reader as well as to a looker. */
.message .auto-tag {
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  color: #071b3f;
  background: #ffe0b3;
  font-size: 0.8rem;
  font-weight: 700;
}

/* Screens (CB-020 and the flows that follow it). One screen is on at a time;
   the rest carry [hidden], which an author `display` would otherwise beat --
   the same trap the presence rows above document. */
.screen {
  display: flex;
  flex-direction: column;
  width: min(100%, 42rem);
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0 auto;
}

.screen[hidden] {
  display: none;
}

/* The header stays put while the questions scroll under it, so a sender
   deep in a long form can still leave without scrolling back up. */
.screen-header {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  padding: max(0.75rem, env(safe-area-inset-top)) 1.25rem 0.75rem;
  background: #f6f3ed;
}

.screen-header h2 {
  margin: 0;
  color: #071b3f;
  font-size: 1.35rem;
}

.text-button {
  min-height: 2.75rem;
  padding: 0 0.25rem;
  border: 0;
  background: transparent;
  color: #b53d2d;
  font-weight: 700;
}

/* The new-chat and join forms on a small phone with the keyboard open. The
   keyboard leaves a viewport a few rows tall, so the form itself scrolls
   rather than the page, every field clears the sticky header when focus
   scrolls it into view, and the action stays on the last row of the form
   instead of below the fold. Inputs are held at 16px because a smaller one
   makes iOS zoom in on focus, which strands the rest of the form off screen. */
.screen form {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 0 1.25rem max(1rem, env(safe-area-inset-bottom));
  gap: 0.35rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.screen form label,
.screen form legend {
  margin-top: 0.75rem;
  font-weight: 700;
}

.screen form input,
.screen form select {
  min-height: 2.75rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid #8f9299;
  border-radius: 0.65rem;
  color: inherit;
  background: #ffffff;
  font-family: inherit;
  font-size: max(1rem, 16px);
  scroll-margin-block: 4rem 1rem;
}

.screen form fieldset {
  margin: 0.75rem 0 0;
  padding: 0.5rem 0.75rem 0.75rem;
  border: 1px solid #c9c3b9;
  border-radius: 0.65rem;
}

.screen form fieldset label {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  min-height: 2.75rem;
  margin: 0;
  font-weight: 400;
}

/* The address questions both profiles declare, drawn from the served
   catalog. Each row is a prompt in the language that asks it and the answer
   beneath, so a mixed pair reads as two sets of questions rather than one
   run-on list. */
.address-question {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.address-question label {
  margin-top: 0.75rem;
  overflow-wrap: anywhere;
}

/* The paste entry point on the join screen, grouped so the screen can take
   the whole row away for a joiner who arrived holding the link (CB-021). */
.join-link {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

/* The action stays reachable with the keyboard up. */
.screen form .primary-button {
  position: sticky;
  bottom: 0;
  margin-top: auto;
  box-shadow: 0 -0.75rem 1rem #f6f3ed;
}

.notice {
  margin: 0.75rem 0 0;
  padding: 0.6rem 0.75rem;
  border-radius: 0.65rem;
  color: #5c1f16;
  background: #ffd9d2;
  line-height: 1.4;
}

/* Confirm-before-send composer and card (CB-060). The generic .screen form
   rules above serve the setup screens; the chat composer is one fixed row
   beneath the scrolling log, with enough room for a message in any script. */
.screen form.composer {
  display: grid;
  flex: none;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.5rem;
  align-items: end;
  padding: 0.75rem 1.25rem max(0.75rem, env(safe-area-inset-bottom));
  border-top: 1px solid #dedad2;
  background: #f6f3ed;
  overflow: visible;
}

.screen form.composer textarea {
  width: 100%;
  min-height: 2.75rem;
  max-height: 9rem;
  padding: 0.65rem 0.75rem;
  border: 1px solid #8f9299;
  border-radius: 1rem;
  color: inherit;
  background: #ffffff;
  font-family: inherit;
  resize: vertical;
}

.screen form.composer .primary-button {
  position: static;
  margin: 0;
  box-shadow: none;
}

.screen form.composer[aria-busy="true"] textarea {
  border-color: #8a5a12;
}

.confirm-card {
  width: min(calc(100% - 2rem), 32rem);
  max-height: calc(100dvh - 2rem);
  margin: auto;
  padding: 1.25rem;
  border: 0;
  border-radius: 1.25rem;
  color: #14213d;
  background: #ffffff;
  overflow-y: auto;
}

.confirm-card::backdrop {
  background: rgb(7 27 63 / 55%);
}

.confirm-card h2 {
  margin: 0 0 1rem;
  color: #071b3f;
}

.confirm-card [hidden] {
  display: none;
}

.card-banner {
  padding: 0.6rem 0.75rem;
  border-radius: 0.65rem;
  color: #5c1f16;
  background: #ffd9d2;
}

/* Beneath the paused banner and quieter than it: the wait is a detail of the
   sentence above, not a second thing to read. */
.card-wait {
  margin: 0.35rem 0 0;
  padding: 0 0.75rem;
  color: #5c1f16;
}

.card-text {
  margin: 0.75rem 0 0;
  color: #071b3f;
  font-size: max(1.25rem, var(--profile-min-type-size, 18px));
}

.card-text-secondary {
  color: #343b4b;
  font-size: max(1rem, var(--profile-min-type-size, 18px));
}

.card-candidates {
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}

.card-candidates button {
  width: 100%;
  min-height: 2.75rem;
  margin-top: 0.5rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid #c9c3b9;
  border-radius: 0.65rem;
  background: #ffffff;
}

.card-candidates button[aria-pressed="true"] {
  border-color: #071b3f;
  background: #d7f4e3;
  box-shadow: 0 0 0 0.12rem #071b3f;
}

.card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.25rem;
}
