/* Mobile companion (D-215).
 *
 * Layered on top of the shared katch.css, which supplies every colour, type, radius, and
 * motion token — including the light/dark themes and the reduced-motion and reduced-
 * transparency preferences. Nothing here redefines a token; a phone is a different size,
 * not a different product, and a second palette would drift from the desktop's within a
 * release. What IS different is touch: targets, thumb reach, and the safe areas a notch
 * and a home indicator carve out of the viewport. */

:root {
  /* 44px is the smallest target that reliably survives a gloved thumb in a car. */
  --tap: 44px;
  --bar-h: 56px;
  --tabs-h: 60px;
}

html, body {
  height: 100%;
  overscroll-behavior-y: contain; /* no rubber-band pull-to-refresh eating a scroll */
}

body {
  background: var(--bg0);
  color: var(--ink);
  font-family: var(--font);
  /* 16px base: anything smaller makes iOS Safari zoom on focus, which throws the layout. */
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

/* height, not min-height. With `html, body { height: 100% }` above, a min-height grid grows past
   the viewport and is simply clipped — the body cannot scroll (it is height-constrained) and the
   middle row never becomes a scroll container either, so long pages just lose their bottom.
   Fixing the height pins the shell to the viewport and makes .m-main the one thing that scrolls,
   which is also what keeps the title bar and tab bar still while it does.
   dvh, not vh — vh ignores the collapsing mobile URL bar. */
.m-app {
  display: grid;
  grid-template-rows: auto 1fr auto;
  height: 100dvh;
}

/* ---- Top bar ---- */

.m-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 8px;
  height: calc(var(--bar-h) + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) 12px 0 12px;
  background: var(--panel);
  backdrop-filter: blur(var(--blur));
  border-bottom: 1px solid var(--line);
}

.m-bar h1 {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.m-bar .m-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap);
  min-height: var(--tap);
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink);
  font-size: 25px;
  cursor: pointer;
}

.m-iconbtn {
  position: relative;
  text-decoration: none;
}

.m-iconbtn:hover { background: var(--accent-wash); }
.m-iconbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The back control is an <Icon> like the rest of the bar (D-329) — it sizes itself, so the
   rule that once scaled the ‹ glyph up from comma-size is gone with the glyph. */

.m-bell-badge {
  position: absolute;
  top: 4px;
  right: 2px;
  min-width: 17px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--danger-fill);
  color: var(--on-danger-fill);
  font-size: 10px;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}

.m-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  margin-left: 2px;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: var(--accent-wash);
  color: var(--accent-ink);
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
}

.m-avatar img { width: 100%; height: 100%; object-fit: cover; }
.m-avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- Main scroll area ---- */

.m-main {
  padding: var(--pad);
  padding-bottom: calc(var(--pad) + 8px);
  overflow-y: auto;
  /* Required, not decorative. A grid item's default min-height is `auto`, which means "at least
     as tall as my content" — so a 1fr row refuses to shrink below its content and overflow-y
     never has anything to overflow. This is the line that actually makes the page scroll. */
  min-height: 0;
  /* The horizontal twin of the line above. The width minimum defaults to `auto` too, so a single
     intrinsically wide descendant — an iOS date input's native box, a select's longest option —
     widens this grid item past the viewport and the right edge clips (the body cannot scroll
     sideways; it is overflow-hidden in katch.css). Zeroing it makes any such overflow a local
     problem for the control that caused it, never a shell-level one. */
  min-width: 0;
  -webkit-overflow-scrolling: touch;
}

/* ---- Bottom tab bar: the only navigation, within thumb reach ---- */

/* A segmented choice — the offline page's Photo / Field note / Expense row. Equal columns
   that never wrap: three buttons of three widths stacked two-and-one reads as a layout
   accident, not a control. */
.m-seg {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.m-seg .m-btn {
  padding-left: 4px;
  padding-right: 4px;
  font-size: 13px;
  white-space: nowrap;
}

/* The radio, said plainly (D-221 widening): an amber strip while the device is offline — a
   doorway to /offline, not just a warning — and the same strip in green for a moment when a
   reconnect flush reports what it sent. Sticky under the title bar so scrolling never hides
   the one fact that changes what every button on the page will do. */
.m-offline-bar {
  position: sticky;
  top: calc(var(--bar-h) + env(safe-area-inset-top));
  z-index: 19;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  background: color-mix(in srgb, var(--warn, #b8860b) 18%, var(--panel));
  border-bottom: 1px solid var(--line);
  color: var(--text);
  font-size: 12.5px;
  text-decoration: none;
}

.m-offline-bar.is-back {
  background: color-mix(in srgb, var(--ok, #2e7d32) 16%, var(--panel));
}

.m-offline-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--warn, #b8860b);
  flex: none;
}

.m-offline-bar.is-back .m-offline-dot {
  background: var(--ok, #2e7d32);
}

.m-offline-go {
  margin-left: auto;
  color: var(--accent);
  font-weight: 600;
}

.m-tabs {
  position: sticky;
  bottom: 0;
  z-index: 20;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  height: calc(var(--tabs-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: var(--panel);
  backdrop-filter: blur(var(--blur));
  border-top: 1px solid var(--line);
}

.m-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 11px;
  text-decoration: none;
  cursor: pointer;
}

/* Holds an SVG from the shared icon set (D-328), so it is sized by the Icon's own dimensions;
 * inline-flex keeps the box exactly the icon's height instead of a text line's. */
.m-tab .m-tab-icon { display: inline-flex; }
.m-tab.active { color: var(--accent-ink); }
.m-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

.m-tab-badge {
  position: absolute;
  transform: translate(14px, -10px);
  min-width: 17px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--danger-fill);
  color: var(--on-danger-fill);
  font-size: 10px;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}

/* ---- Conversation screen ----
   The one screen laid out as a chat: the shell renders no tab bar here (MobileShell's
   IsConversationRoute), the thread scrolls by itself, and the composer holds the bottom of the
   screen where the tabs normally sit. Bubbles float on the page background rather than in a
   card — the bubble IS the container, and a panel around a conversation just drew a box around
   a box. */

.m-main.is-chat {
  /* The thread is the scroll container here, not the page — that is what pins the composer. */
  overflow: hidden;
  padding-bottom: 0;
}

.m-main.is-chat > div:last-child { height: 100%; }

.m-chat {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.m-chat-thread {
  flex: 1;
  min-height: 0;
  /* column-reverse pins the scroll to the newest message without JS — the CSP forbids the
     eval a scroll call would want, and the server sends newest-first anyway. */
  display: flex;
  flex-direction: column-reverse;
  gap: 12px;
  overflow-y: auto;
  padding: 4px 0 12px;
}

.m-chat-msg { max-width: 84%; }
.m-chat-msg.mine { align-self: flex-end; }
.m-chat-msg.theirs { align-self: flex-start; }

.m-chat-meta {
  margin: 0 6px 3px;
  color: var(--muted);
  font-size: 12px;
}
.m-chat-msg.mine .m-chat-meta { text-align: right; }

.m-chat-bubble {
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 8px 12px;
  font-size: 14.5px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.m-chat-msg.mine .m-chat-bubble { background: var(--accent-wash); border-bottom-right-radius: 4px; }
.m-chat-msg.theirs .m-chat-bubble { background: var(--panel2); border-bottom-left-radius: 4px; }

.m-chat-quote {
  margin: 0 6px 3px;
  padding: 2px 8px;
  border-left: 2px solid var(--accent);
  color: var(--muted);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.m-chat-replybtn {
  margin: 2px 6px 0;
  padding: 2px 4px;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
  min-height: 0;
  min-width: 0;
}
.m-chat-msg.mine .m-chat-replybtn { display: block; margin-left: auto; }

/* The composer bar: full-bleed like the tab bar it replaces, same glass, same safe-area
   allowance for gesture-navigation phones. */
.m-chat-composer {
  flex: none;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  margin: 0 calc(-1 * var(--pad));
  padding: 8px var(--pad) calc(8px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--panel);
  backdrop-filter: blur(var(--blur));
}

.m-chat-composer textarea {
  flex: 1;
  /* flex: 1 grows but never shrinks past the automatic minimum, and a textarea's minimum is its
     intrinsic cols width (~20ch at 16px) — wider than what is left beside two 44px buttons and
     their gaps on the narrowest screens. The textarea also carries .m-input, whose min-width: 0
     already covers this; stated here too because the flex layout that NEEDS the guard lives
     here, and the pairing should survive either rule being reworked alone. */
  min-width: 0;
  min-height: var(--tap);
  max-height: 120px;
  resize: none;
}

.m-chat-send {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border: 0;
  border-radius: var(--r-md);
  background: var(--accent-fill);
  color: var(--on-accent-fill);
  cursor: pointer;
}
.m-chat-send:disabled { opacity: .45; cursor: default; }
.m-chat-send:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The link-a-record affordance beside the composer, quiet next to the loud send. */
.m-chat-linkbtn {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.m-chat-linkbtn:hover { background: var(--accent-wash); color: var(--accent-ink); }
.m-chat-linkbtn[aria-expanded="true"] { color: var(--accent-ink); border-color: var(--accent); }

/* The record picker that folds open above the composer. */
.m-chat-linkpick {
  flex: none;
  margin-bottom: 8px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--panel);
}

.m-chat-linkkinds {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  margin-bottom: 8px;
}
.m-chat-linkkinds .m-choice { min-height: 36px; font-size: 12.5px; padding: 4px 6px; }

/* A record shared into the chat. A button when it goes somewhere on this client, inert when the
   record's surface is workstation-only — the chip then informs without pretending to navigate. */
.m-chat-link {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  margin-top: 4px;
  padding: 7px 11px;
  border: 1px solid var(--accent);
  border-radius: 10px;
  background: var(--accent-wash);
  color: var(--accent-ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.m-chat-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.m-chat-link svg { flex: none; }
.m-chat-link.is-inert {
  border-color: var(--line);
  background: var(--panel2);
  color: var(--muted);
  cursor: default;
  font-weight: 500;
}
.m-chat-msg.mine .m-chat-link { margin-left: auto; }

/* ---- Cards and rows ---- */

.m-card {
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--panel);
  backdrop-filter: blur(var(--blur));
}

.m-card + .m-card { margin-top: var(--gap); }

.m-section-title {
  margin: 20px 0 8px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.m-section-title:first-child { margin-top: 0; }

/* A section heading that carries an action.
 *
 * The action used to be `float: right` inside the heading. A float leaves normal flow, so the
 * heading kept the height of its text alone and the button hung down over whatever came next —
 * the card below it, usually — which is the overlap that was reported. Flex keeps both in flow,
 * so the row is as tall as its tallest child and nothing can sit on top of anything. */
.m-section-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 20px 0 8px;
}

.m-section-head:first-child { margin-top: 0; }

.m-section-head .m-section-title {
  flex: 1;
  min-width: 0;
  margin: 0;
}

/* The action sizes to its label instead of stretching: .m-btn is full-width by default, which
   is right for a form's primary button and wrong for a header affordance. */
.m-section-head .m-btn {
  flex: none;
  width: auto;
  min-height: 34px;
  padding: 0 12px;
  font-size: 12.5px;
}

.m-row {
  display: block;
  min-height: var(--tap);
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
  color: inherit;
  text-decoration: none;
}

.m-row:last-child { border-bottom: 0; }
/* Interactive rows light up on hover — the shared convention, not a mobile invention. */
.m-row:hover { background: var(--accent-wash); }
.m-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A row that is a BUTTON rather than a link — the notification feed, where tapping does
   something (marks it read) as well as sometimes going somewhere. Without this reset the browser
   supplies its own border, background, centred text and font, and the row stops looking like a
   row at all. */
button.m-row {
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  font: inherit;
  cursor: pointer;
}

button.m-row:last-child { border-bottom: 0; }

.m-row-title { font-size: 15px; font-weight: 600; }
.m-row-meta { margin-top: 3px; color: var(--muted); font-size: 12.5px; }
.m-mono { font-family: var(--mono); font-size: 12px; }

.m-overdue { color: var(--danger); font-weight: 700; }

/* ---- About (More page) ----
 *
 * The about block is a colophon, not a panel: no fill, no border, everything centred, and the
 * heading's usual 20px of air doubled so it reads as the page signing off rather than one more
 * card. Rows keep .m-row for rhythm only - nothing here is interactive, so the shared hover
 * wash is switched off. */
.m-about-title { margin-top: 40px; text-align: center; }
.m-about { text-align: center; }
.m-about-mark { padding-top: 12px; }
.m-about .m-row { border-bottom: 0; }
.m-about .m-row:hover { background: transparent; }
.m-about .m-mono { font-size: 14px; }
.m-about-credit { margin-top: 8px; }
.m-about-credit a { color: var(--accent-ink); text-decoration: none; }

/* ---- Status blocks: loading, empty, denied, error ---- */

.m-state {
  padding: 28px 16px;
  color: var(--muted);
  text-align: center;
}

.m-state-title { margin-bottom: 6px; color: var(--ink); font-weight: 600; }
.m-state.denied .m-state-title { color: var(--amber); }
.m-state.error .m-state-title { color: var(--danger); }

.m-skeleton {
  height: 58px;
  margin-bottom: var(--gap);
  border-radius: var(--r-lg);
  background: linear-gradient(90deg, var(--panel2), var(--panel), var(--panel2));
  background-size: 200% 100%;
  animation: m-shimmer 1.2s linear infinite;
}

@keyframes m-shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .m-skeleton { animation-duration: var(--dur-instant); }
}

/* ---- Corner style (D-218) ----
 *
 * The shared stylesheet's [data-shape] rules name the workstation's own classes — .panel,
 * .tile, .stat and friends — which this client does not use, so the preference applied to
 * nothing here until these were added. The treatments themselves are NOT redefined: the radii
 * below mirror katch.css exactly, because a corner style that means one thing on a workstation
 * and another on a phone is not one preference, it is two.
 *
 * Scoped to the mobile surfaces that read as panels. Inputs and the tab bar are deliberately
 * left alone: a beveled text field is a novelty the first time and a nuisance every time after. */

:root[data-shape="sweep"] .m-card,
:root[data-shape="sweep"] .m-tile,
:root[data-shape="sweep"] .m-quick-btn,
:root[data-shape="sweep"] .m-choice { border-radius: 1.5rem 9px 1.5rem 9px; }

:root[data-shape="cut"] .m-card,
:root[data-shape="cut"] .m-tile,
:root[data-shape="cut"] .m-quick-btn,
:root[data-shape="cut"] .m-choice { corner-shape: bevel; border-radius: 13px; }

:root[data-shape="cut-sweep"] .m-card,
:root[data-shape="cut-sweep"] .m-tile,
:root[data-shape="cut-sweep"] .m-quick-btn,
:root[data-shape="cut-sweep"] .m-choice { corner-shape: bevel; border-radius: 15px 0 15px 0; }

/* "None": the shared stylesheet's blanket rule already squared everything — these are this
   client's identity circles handed their roundness back (this file loads after katch.css, so
   equal-importance ties fall this way), and its panels dropping their hairline like the
   workstation's do. */
:root[data-shape="none"] .m-avatar,
:root[data-shape="none"] .m-person,
:root[data-shape="none"] .m-rec-dot,
:root[data-shape="none"] .m-day-dot { border-radius: 50% !important; }
:root[data-shape="none"] .m-card,
:root[data-shape="none"] .m-tile { border-color: transparent; }

/* ---- Dashboard tiles (D-219) ----
 *
 * Three across on a phone: two is wasteful of a 375px width, four makes the numbers too small
 * to read at arm's length in daylight. auto-fit rather than a fixed count so a larger phone or
 * a tablet gets four without a breakpoint. */

.m-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 8px;
  margin-bottom: var(--gap);
}

.m-tile {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-height: 62px;
  /* The figure sits dead centre of its box, not hung off the top-left corner — a row of tiles
   * reads as a row of dials, and a two-digit number no longer leaves a lopsided void beside it. */
  align-items: center;
  justify-content: center;
  text-align: center;
  /* Tighter than it was. The figure no longer has to be large enough for everybody, because
   * everybody can now size it themselves, so the box only needs to fit the figure it has. */
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--panel);
  backdrop-filter: blur(var(--blur));
  color: inherit;
  text-decoration: none;
}

a.m-tile:hover { background: var(--accent-wash); }
a.m-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.m-tile-value {
  font-family: var(--mono);
  /* 26px left the box looking empty and 38px overfilled it. 30 is the figure that fits the tile
   * at the DEFAULT text size - which is now a floor rather than a compromise, because the device
   * text scale (D-302) means anybody who wants it larger can have it larger. */
  font-size: 30px;
  font-weight: 700;
  line-height: 1.02;
}

.m-tile-label {
  color: var(--muted);
  font-size: 11.5px;
  letter-spacing: .02em;
}

/* Tone colours the numeral and nothing else. The border used to be tinted too, on the reasoning
   that tone should not rest on colour alone - but a tinted border IS colour, and carries no more
   to a reader who cannot separate the hues than the tinted digits do. What actually does that
   work is the label underneath, which names what the number counts. Two coloured edges around
   every figure only made a row of tiles look like a row of warnings. */
.m-tile.tone-danger .m-tile-value { color: var(--danger); }
.m-tile.tone-attention .m-tile-value { color: var(--amber); }

.m-alert {
  display: block;
  border-color: var(--amber);
  background: var(--amber-wash);
  color: inherit;
  text-decoration: none;
}

/* ---- Quick actions ---- */

.m-quick {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}

.m-quick-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 68px;
  /* Padding trimmed to what the tap target needs and no more. The glyph plus its label is the
   * content; everything else was air. */
  padding: 7px 4px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--panel);
  color: inherit;
  font-size: 11.5px;
  text-align: center;
  text-decoration: none;
}

.m-quick-btn:hover { background: var(--accent-wash); }
.m-quick-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A fixed BOX, which now does a smaller job than it once did: these were text glyphs from
 * different Unicode blocks, each drawn at its own optical size, and the box (plus a
 * .is-wide-glyph boost) was what kept the buttons aligned. The glyphs became SVGs from the
 * shared icon set when iOS turned out not to cover them at all (D-328); SVGs share one optical
 * size, so the box's remaining work is centring them and the one text glyph left — the currency
 * sign, which is text so the system font setting keeps scaling it. */
.m-quick-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  color: var(--accent-ink);
  font-size: 31px;
  line-height: 1;
}

/* ---- Recording (D-223) ---- */

.m-recording {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--danger);
  border-radius: var(--r-md);
  background: var(--danger-wash);
  font-size: 14px;
  font-weight: 600;
}

.m-rec-dot {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--danger-fill);
  animation: m-pulse 1.4s ease-in-out infinite;
}

/* The pulse is the only thing distinguishing "recording" from "frozen", so under reduced motion
   it becomes a steady dot rather than disappearing — the elapsed counter carries the rest. */
@keyframes m-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .25; }
}

@media (prefers-reduced-motion: reduce) {
  .m-rec-dot { animation: none; }
}

/* Two across, not four. Four buttons on a 375px screen leave about seventy pixels each, which
   is where the labels overflowed; two gives "Voice note" and "Choose from library" room to sit
   on one line at a readable size. */
.m-capture-types {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}

.m-capture-type {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--panel2);
  color: var(--ink);
  font-family: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  /* The label is short and must never be the thing that widens the control. If a translation
     ever makes it long, it wraps inside the button rather than pushing the grid sideways. */
  min-width: 0;
  overflow-wrap: anywhere;
}

.m-capture-type:hover { background: var(--accent-wash); }
.m-capture-type:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.m-capture-glyph { flex: none; display: inline-flex; }

/* What is already attached, as a row that cannot overflow: the icon and the Remove button are
   fixed, and the middle column is the only thing allowed to shrink. */
.m-attached {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--panel2);
}

.m-attached > div { flex: 1; min-width: 0; }
.m-attached-icon { flex: none; display: inline-flex; }

/* ---- Bottom sheet ----
 *
 * Anchored to the bottom rather than centred: it is where the thumb already is, and it leaves
 * the context above visible so the sheet reads as "one more step" rather than a new screen. */

.m-sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(0, 0, 0, .45);
}

.m-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 41;
  max-height: 80dvh;
  overflow-y: auto;
  padding: var(--pad);
  padding-bottom: calc(var(--pad) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line-strong);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--panel-solid);
}

.m-sheet:focus { outline: none; }
.m-sheet:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

.m-sheet-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: var(--gap);
}

.m-sheet-head h2 { flex: 1; margin: 0; font-size: 17px; font-weight: 600; }

/* Live camera and video-recording preview. 4:3 rather than free-height so the sheet does not
   jump as the stream resolves its dimensions. */
.m-preview {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: #000;
}

/* ---- Media viewer (logs tab) ----
   Full-bleed rather than a bottom sheet. A sheet caps at 80dvh and keeps the case list behind it
   in view, which is right for a form and wrong for a photograph: an exhibit is looked AT, and the
   list around it is a distraction while looking. Black ground because the thing on it is
   evidence, and a tinted panel edge changes how a photograph reads. */
.m-viewer {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background: #000;
}

.m-viewer-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: calc(env(safe-area-inset-top) + 8px) var(--pad) 8px;
  color: #fff;
}

.m-viewer-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15px;
  font-weight: 600;
}

/* The media gets everything left over, and is CONTAINED rather than cropped: cover would quietly
   cut the edges off an exhibit, which is often the part somebody opened it to check. */
.m-viewer-body {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--pad) calc(env(safe-area-inset-bottom) + var(--pad));
}

.m-viewer-body img,
.m-viewer-body video {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  background: #000;
}

.m-viewer-body audio { width: 100%; }

/* White-on-black controls: the viewer's ground is black whatever the theme is. */
.m-viewer .m-iconbtn {
  color: #fff;
  border-color: rgba(255, 255, 255, .35);
  background: rgba(255, 255, 255, .08);
}

/* Audio player. Full width because the native control's scrubber is the thing being used, and
   a narrow one on a phone is unusable with a thumb. */
.m-player {
  display: block;
  width: 100%;
  margin-top: 8px;
}

/* The audio spectrum. Aria-hidden and paired with a live-region elapsed counter, so the
   information it carries is available without looking at it — a canvas cannot be read out. */
.m-spectrum {
  display: block;
  width: 100%;
  height: 110px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--panel-solid);
}

/* A labelled rule between "record" and "choose a file" — two genuinely different routes to the
   same outcome, and stacking them unmarked reads as a list of steps. */
.m-sheet-or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: var(--gap) 0;
  color: var(--muted);
  font-size: 12px;
}

.m-sheet-or::before,
.m-sheet-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

@media (prefers-reduced-motion: no-preference) {
  .m-sheet { animation: m-sheet-in var(--dur-base) var(--ease-out); }
}

@keyframes m-sheet-in {
  from { transform: translateY(12%); opacity: .6; }
  to { transform: none; opacity: 1; }
}

/* A person beside their name, in lists. */
.m-person {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 34px;
  height: 34px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--accent-wash);
  color: var(--accent-ink);
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
}

.m-person.sm { width: 26px; height: 26px; font-size: 10px; }
.m-person img { width: 100%; height: 100%; object-fit: cover; }

/* A row that leads with a face. The name column must be allowed to shrink, or a long title
   pushes the layout wider than the screen and the whole page scrolls sideways. */
.m-person-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.m-person-row > div { min-width: 0; flex: 1; }

/* Lookup results. Bounded height so a long list never pushes the rest of a form off screen —
   on a phone that reads as the form having disappeared. */
.m-lookup {
  max-height: 280px;
  margin-top: 8px;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--panel-solid);
}

.m-lookup .m-row { padding: 10px 12px; }
.m-lookup .m-row:last-child { border-bottom: 0; }

/* ---- Choice grids (appearance settings) ----
 *
 * Deliberately buttons in a wrapping grid rather than a <select>: on a phone a native picker
 * hides every option but one behind a modal, and these are choices you judge by looking at
 * them. Each carries aria-pressed so the selected state is not conveyed by colour alone. */

.m-choice-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}

.m-choice {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--panel2);
  color: var(--ink);
  font-family: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
}

.m-choice:hover { background: var(--accent-wash); }
.m-choice:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Selected carries a border, a wash, AND a weight change — three signals, so the state
   survives a colourblind reader and a high-contrast theme. */
.m-choice.selected {
  border-color: var(--accent);
  background: var(--accent-wash);
  font-weight: 700;
}

.m-swatch {
  flex: none;
  width: 20px;
  height: 20px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
}

.m-toggle {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap);
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}

.m-toggle > span { flex: 1; min-width: 0; }
.m-toggle .m-row-title,
.m-toggle .m-row-meta { display: block; }

/* The switch itself is NOT defined here.
 *
 * katch.css already draws every input[type="checkbox"] as a switch — track, knob, checked state,
 * transitions — and its selector outranks a plain class, so a second definition here does not
 * replace the shared one, it half-overlays it. That is exactly what went wrong first time: this
 * file set a 28px track while the shared rule kept its 20px one, leaving a knob sized for a
 * control that did not exist and a switch that looked broken.
 *
 * So the mobile layer adds only what a phone needs on top: a bigger hit area than the 34x20
 * track, provided by the surrounding label rather than by resizing the control. */
.m-switch {
  /* Keeps the switch from being stretched by the flex row; every visual is the shared rule's. */
  flex: none;
}

/* ---- Sign-in ---- */

.m-signin {
  display: grid;
  align-content: center;
  gap: var(--gap);
  min-height: 100dvh;
  padding: max(var(--pad), env(safe-area-inset-top)) var(--pad) max(var(--pad), env(safe-area-inset-bottom));
}

/* min-width: 0 only does work when a field becomes a grid or flex item (the two-up .m-formrow
   below): the default `auto` minimum lets the control inside — a date input, a select with a
   long option — size the track instead of the track sizing the control. In plain block flow,
   where most fields live, it is inert. */
.m-field { display: block; margin-bottom: var(--gap); min-width: 0; }
.m-field span { display: block; margin-bottom: 5px; color: var(--muted); font-size: 12.5px; font-weight: 600; }

/* Two fields sharing a row — minutes beside miles, start beside end. A class rather than the
 * inline grid each page had grown its own copy of, so the sizing guard exists ONCE: without
 * min-width: 0 on the children, a field's `auto` minimum lets an iOS date input or a select's
 * longest option set the track width, and the pair pushes the card past a 390px screen. The
 * guard lives on the children because a track is only as disciplined as the things inside it. */
.m-formrow {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap);
}

.m-formrow > * { min-width: 0; }

.m-input {
  width: 100%;
  /* width: 100% is not containment: in the min-content sizing pass a percentage acts as `auto`,
     so a date input's native box or a select's longest option still bids its intrinsic width at
     any grid or flex ancestor. max-width pins the rendered box to the parent; min-width lets the
     control be the thing that shrinks; the .m-field / .m-formrow minimums close the loop. */
  min-width: 0;
  max-width: 100%;
  min-height: var(--tap);
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--panel-solid);
  color: var(--ink);
  font-family: inherit;
  font-size: 16px; /* keeps iOS from zooming the viewport on focus */
}

.m-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- Native control chrome (D-329) ----
 *
 * WKWebView paints its own widget chrome on <select>, date and search inputs unless appearance
 * is switched off — and .m-input's background/border already suppress the native dropdown
 * arrow, which left selects as themed boxes with no affordance at all. Opting out hands the
 * whole CLOSED control to the theme. The OPEN picker stays the system sheet, which is right:
 * that surface is the platform's own. */

select.m-input {
  appearance: none;
  -webkit-appearance: none;
  /* Clearance for the chevron: 12px inset + 16px glyph + air, so a long option label shortens
     before it runs under the arrow. */
  padding-right: 40px;
  /* The affordance the native arrow used to be. A data URI cannot read a custom property, so
     this is the one hue in the file that cannot be a token: #7C8AA5 clears the 3:1 non-text
     contrast floor over --panel-solid in the light theme and the dark theme both. One neutral
     rather than per-theme copies, because the theme keying lives in katch.css and duplicating
     it here would drift the first time a theme changes. Stroked, round-capped — the shared
     icon set's language. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='%237C8AA5' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 16px;
  /* A firm-configured option name longer than the phone shortens rather than widening the
     control. WebKit does not reliably paint a select's ellipsis, in which case this clips
     plainly at the padding edge — accepted: a clipped label beats a widened card. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Date and time fields. Un-theming these does three jobs at once on iOS: the value stops being
 * centred in a native grey pill and sits where every other field's text sits, the themed
 * border, background and padding actually paint, and the control stops asserting the intrinsic
 * width that pushed the two-up form rows past the card. Tapping still opens the system picker.
 * (No type="time" exists in the markup today; add it to this selector when one ships.) */
input.m-input[type="date"],
input.m-input[type="datetime-local"] {
  appearance: none;
  -webkit-appearance: none;
}

/* WebKit centres the date value inside an internal wrapper this pseudo-element exposes; without
 * it the un-themed field keeps native centring. An EMPTY date field has no value box to size
 * itself around once the chrome is gone — .m-input's min-height is what stops it collapsing. */
input.m-input::-webkit-date-and-time-value {
  text-align: left;
}

/* Search fields. type="search" is the right keyboard (a Search key instead of Return) but drags
 * native searchfield cosmetics with it — inset corner rounding and fill painted over the themed
 * border. Hand the box back to the theme; only the decoration spacer is reset beyond that. The
 * native clear (x) button is kept wherever the engine still offers it after appearance:none —
 * it is the only clear affordance these fields have — and losing it on engines that tie it to
 * the native appearance is accepted rather than rebuilt. */
input.m-input[type="search"] {
  appearance: none;
  -webkit-appearance: none;
}

input.m-input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/* The text-size slider (Appearance) is the one unclassed control on the surface, and its native
 * box ran a couple of pixels past its row — the thumb's overhang counts toward layout width on
 * some engines. Contained rather than themed: the native slider is fine to look at; it just
 * must not be the widest thing in the card. */
.m-row input[type="range"] {
  max-width: 100%;
  min-width: 0;
  margin: 0;
}

.m-btn {
  width: 100%;
  min-height: var(--tap);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: var(--accent-fill);
  color: var(--on-accent-fill);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

.m-btn[disabled] { opacity: .55; cursor: default; }
.m-btn.secondary { background: transparent; border-color: var(--line-strong); color: var(--ink); }
/* A quiet action that ends something - sign out - names its cost in the danger colour. */
.m-btn.secondary.danger { color: var(--danger); }

.m-error {
  padding: 10px 12px;
  border: 1px solid var(--danger);
  border-radius: var(--r-md);
  background: var(--danger-wash);
  color: var(--danger);
  font-size: 13.5px;
}

/* An explanation, not a fault — "your session ended" is the product being honest, and must
   not wear the error red that says somebody did something wrong. */
.m-notice {
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--accent-wash);
  color: var(--ink);
  font-size: 13.5px;
}

/* A note explaining a device restriction — shown where a desktop would offer the action.
   Visible reassurance that the control is missing on purpose, not broken. */
.m-device-note {
  display: flex;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--amber-wash);
  color: var(--ink);
  font-size: 12.5px;
}


/* ---- Caseload charts (D-302) ----
 *
 * A drawing and a legend, stacked. The drawing carries no text labels of its own on purpose: a
 * status named "Pending external action" fits under no column and beside no slice at a size
 * anybody can read outdoors, so every name lives in the legend where it has a full row. */

/* Edge to edge: the drawing gets the card width. The earlier padding put empty space either
 * side of the figure and made every chart read smaller than its data. */
.m-chart { padding: 10px 4px 4px; }

.m-chart-figure {
  display: block;
  margin-bottom: 4px;
}

.m-chart-figure svg { display: block; width: 100%; }

/* The disclosure for the key. A real button with aria-expanded - the caret alone would be a
 * picture of a control rather than a control. */
.m-chart-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  min-height: 40px;
  border: 0;
  border-top: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font-size: 12.5px;
  cursor: pointer;
}

.m-chart-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.m-chart-caret { transition: transform .15s ease; }
.m-chart-caret.is-open { transform: rotate(180deg); }

/* Off-screen but still read aloud: the collapsed chart hides names a screen reader needs. */
.m-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.m-chart-legend {
  display: grid;
  gap: 2px;
  padding: 4px 8px 8px;
}

.m-chart-key {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 30px;
  padding: 2px 0;
}

.m-chart-swatch {
  flex: none;
  width: 11px;
  height: 11px;
  border-radius: 3px;
}

.m-chart-name {
  flex: 1;
  font-size: 13.5px;
  /* One line: a long firm-configured name wrapping would break the alignment of every count
   * down the right-hand edge, which is the column people actually read. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.m-chart-num {
  flex: none;
  font-size: 14.5px;
  font-weight: 700;
}

/* The row that hands a capped list over to the tab that owns it (D-302). */
.m-row-more .m-row-title { color: var(--accent-ink); }

/* ---- A row of actions (D-302) ----
 *
 * .m-btn is full-width by default, which is right for the single confirming action at the foot
 * of a form. Where two or three belong together — take or choose, save or cancel — they share
 * the row instead, each still wide enough for a thumb. Wraps rather than shrinking below that:
 * three buttons on a 320px screen belong on two lines, not squeezed onto one. */

.m-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.m-actions .m-btn {
  flex: 1 1 130px;
  width: auto;
}

/* ---- Calendar views (D-302) ---- */

.m-viewswitch {
  display: flex;
  gap: 6px;
  margin-bottom: var(--gap);
}

.m-viewbtn {
  flex: 1;
  /* 44px: the smallest target that is reliably hittable with a thumb, and these are icons with
   * no text to widen them. */
  min-height: 44px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--panel);
  color: var(--muted);
  font-size: 17px;
  cursor: pointer;
}

.m-viewbtn.on {
  border-color: var(--accent);
  background: var(--accent-wash);
  color: var(--accent-ink);
}

.m-viewbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.m-monthbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}

.m-month-name { font-weight: 600; }

.m-month { padding: 10px; }

.m-month-head,
.m-month-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}

.m-month-head {
  margin-bottom: 4px;
  color: var(--muted);
  font-size: 11px;
  text-align: center;
}

.m-day {
  position: relative;
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  border: 1px solid transparent;
  border-radius: var(--r-sm, 8px);
  background: transparent;
  color: inherit;
  font-size: 13.5px;
  cursor: pointer;
}

.m-day.is-blank { cursor: default; }
.m-day.is-today { border-color: var(--line-strong); font-weight: 700; }

.m-day.is-selected {
  background: var(--accent-wash);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.m-day:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The mark that a day has anything on it. Sized to stay visible next to a single digit. */
.m-day-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

.m-day.is-selected .m-day-dot { background: var(--accent-ink); }

/* ---- Paging (D-302) ---- */

.m-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: var(--gap) 0;
}

.m-pager-pos {
  color: var(--muted);
  font-size: 13px;
  /* Fixed width so the arrows do not shuffle sideways as the numbers change width. */
  min-width: 84px;
  text-align: center;
}

/* ---- Four tiles across, always (D-302) ----
 *
 * The task pipeline is four figures that belong together and are read against each other, so it
 * must not reflow to 3+1 the way the auto-fit grid above does — a row of three with one orphan
 * reads as two groups. Fixed four columns, tighter inside, and the number is allowed to shrink
 * on a narrow phone rather than the row to break. */

.m-tiles.is-four {
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}

.m-tiles.is-four .m-tile {
  min-height: 58px;
  padding: 7px 3px;
}

.m-tiles.is-four .m-tile-value {
  /* Four across is tighter than three, so it caps lower than the general tile above. */
  font-size: clamp(17px, 5.5vw, 24px);
  /* Never two lines: the figure is the whole point of the tile. */
  white-space: nowrap;
}

.m-tiles.is-four .m-tile-label {
  font-size: 10px;
  line-height: 1.15;
}

/* ---- Text size, on this client (D-302) ----
 *
 * THE BUG THIS FIXES: the shared rules scale `:root[data-text-scale="N"] .content`, and `.content`
 * is the WORKSTATION shell's wrapper. The mobile shell has no such element, so the slider on the
 * Appearance page wrote the preference, the preference reached the DOM as data-text-scale, and
 * absolutely nothing happened. It looked like a dead control because it was one.
 *
 * Scoped to .m-main so the title bar and the tab bar keep their size: those are fixed chrome sized
 * for a thumb, and zooming them eats the screen the content needs. The same steps as the
 * workstation, so a person who has chosen "+2" gets a comparable jump on either client. */

:root[data-text-scale="1"] .m-main { zoom: 1.15; }
:root[data-text-scale="2"] .m-main { zoom: 1.3; }
:root[data-text-scale="3"] .m-main { zoom: 1.45; }
:root[data-text-scale="4"] .m-main { zoom: 1.6; }
:root[data-text-scale="5"] .m-main { zoom: 1.75; }

/* ---- Pager buttons (D-302) ----
 *
 * Their own class rather than .m-iconbtn: that one is title-bar chrome sized to sit in a 44px
 * header, and these sit in open space under a list where they can be the full tap target. */

.m-pagebtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap);
  min-height: var(--tap);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
}

.m-pagebtn[disabled] {
  opacity: .4;
  cursor: default;
}

.m-pagebtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The bell only lights up when it has something to say (D-302). Quiet the rest of the time, so
 * the one coloured thing in the title bar always means something. */
.m-iconbtn.is-alerting { color: var(--accent-ink); }

/* ---- Chart selection (D-302) ----
 *
 * Tapping a bar or a slice picks it out and dims the rest, and a readout underneath names it in
 * full with its count and share. That is what makes a truncated label acceptable: the short form
 * is a glance, the tap is the answer. */

.m-seg {
  cursor: pointer;
  transition: opacity .12s ease;
}

.m-seg-hit { cursor: pointer; }

/* Dimmed rather than greyed: the colour still ties the segment to its key row, it is just
 * pushed back behind the one being asked about. */
.m-seg.is-off { opacity: .35; }

.m-seg.is-on { opacity: 1; }

.m-chart-readout {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 10px;
  border-top: 1px solid var(--line);
  background: var(--accent-wash);
}

.m-chart-readout .m-chart-name {
  flex: 1;
  font-weight: 600;
}

.m-chart-share {
  flex: none;
  color: var(--muted);
  font-size: 13px;
}

/* The drawn flag beside a language name (D-329). The border is load-bearing twice over: white
 * stripes meet the panel in the light theme, and the rounded clip comes from this box rather
 * than from clipPaths inside every flag. */
.m-flag {
  display: inline-flex;
  margin-right: 8px;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  overflow: hidden;
  vertical-align: middle;
}

.m-flag svg { display: block; }

/* A kind icon leading a row title — sits in running text, so it needs the nudge that puts an
 * inline-flex SVG on the text's optical baseline rather than the box's. */
.m-rowicon {
  display: inline-flex;
  vertical-align: -2.5px;
}

/* ---- Which server, on the sign-in screen (D-303) ---- */

.m-server { margin-top: var(--gap); }

/* A row that reads as a value with a way to change it, rather than as a button. The address is
 * the information; the affordance is secondary. */
.m-server-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: var(--tap);
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: transparent;
  color: inherit;
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
}

.m-server-row .m-mono {
  flex: 1;
  /* A long host name shortens rather than wrapping the row into two lines. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.m-server-change { flex: none; color: var(--accent-ink); }

.m-server-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- Pull to refresh ----
 *
 * The strip that opens above the page while a finger drags it down. Height is set inline from the
 * gesture so it tracks the thumb; everything else lives here. It occupies real layout rather than
 * overlaying, because pushing the page down is the thing that makes the gesture legible.
 */
.m-pull {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.m-pull-mark {
  color: var(--muted);
  font-size: 11.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  transition: color .15s ease;
}

/* Armed: let go now and it reloads. Colour alone would be a poor signal, which is why the label
 * changes wording at the same moment - this only reinforces it. */
.m-pull-mark.armed { color: var(--accent-ink); }

@media (prefers-reduced-motion: reduce) {
  .m-pull-mark { transition: none; }
}

/* ---- Month-to-month transition ----
 *
 * The incoming month slides in from the side it came from and the name fades with it, so a swipe
 * reads as moving through a calendar rather than as the grid being swapped underneath. Only the
 * arriving month animates: a true two-way slide needs both months on screen at once, which means
 * rendering a month nobody asked for on every step, and the difference is not worth that.
 *
 * The card clips, or the grid is briefly visible outside its own rounded corners as it travels.
 */
.m-month { overflow: hidden; }

@keyframes m-month-in-fwd {
  from { opacity: 0; transform: translateX(18%); }
  to   { opacity: 1; transform: none; }
}

@keyframes m-month-in-back {
  from { opacity: 0; transform: translateX(-18%); }
  to   { opacity: 1; transform: none; }
}

.m-month-grid.slide-fwd  { animation: m-month-in-fwd  .24s cubic-bezier(.22, .61, .36, 1); }
.m-month-grid.slide-back { animation: m-month-in-back .24s cubic-bezier(.22, .61, .36, 1); }

/* The name travels a shorter distance than the grid it labels - the same move at the same size
 * on a single short word reads as a jolt rather than a slide. */
.m-month-name.slide-fwd,
.m-month-name.slide-back { animation: m-month-name-in .24s ease-out; }

@keyframes m-month-name-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Honour the system setting, as everything else here does: the month still changes, it simply
 * stops moving to do it. */
@media (prefers-reduced-motion: reduce) {
  .m-month-grid.slide-fwd,
  .m-month-grid.slide-back,
  .m-month-name.slide-fwd,
  .m-month-name.slide-back { animation: none; }
}

/* ---- The biometric control ----
 *
 * Plain: no fill, no border, no accent. It is a fingerprint, which is already the most legible
 * symbol on either screen, and dressing it as a primary button put a second loud control next to
 * the one the form is actually built around. The glyph inherits the ink colour, the target stays
 * a full 44px, and the focus ring is untouched - the affordance is the icon, not a box.
 */
.m-biobtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 10px 18px;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

.m-biobtn:hover { background: var(--accent-wash); }
.m-biobtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.m-biobtn:disabled { opacity: .5; cursor: default; }

/* The case tab strip: buttons in a row, no surface behind them. Same grid the card used to
 * supply, without the border, fill and blur that made it look like a panel containing nothing. */
/* A custody entry a correction has replaced: still on the record — the chain is append-only —
   but visibly not the current word. */
.m-superseded { opacity: .55; }

.m-tabstrip {
  display: grid;
  /* Three to a row, wrapping: six sections on a phone-width single row would shrink every
     label past reading. Rows of three keep each button a finger-sized target. */
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  /* Space above as well as below. Dropping the card also dropped the gap its own margin
     used to provide, which left the row sitting hard against the case header - reading as part
     of it rather than as the control for what comes underneath. */
  margin-top: var(--gap);
  margin-bottom: var(--gap);
}

/* The viewer springs back when a drag stops short of dismissing. The transition lives here and
 * the drag suppresses it inline, so the surface tracks the finger exactly while it is held and
 * eases home the moment it is let go. */
.m-viewer { transition: transform .18s ease-out, opacity .18s ease-out; }

@media (prefers-reduced-motion: reduce) {
  .m-viewer { transition: none; }
}
