:root {
  --bg: #f4f6f9;
  --surface: #ffffff;
  --text: #12161b;
  --muted: #5c6674;
  --border: #dde2e9;
  --accent: #2563eb;
  /* The accent has two jobs with opposite contrast needs: it is TEXT on a
     surface (the outline button, focus rings) and it is the BACKGROUND under
     white text (the filled button). One value cannot serve both in dark mode,
     where a blue light enough to read as text is too light behind white. In
     light mode the same value clears 4.5:1 either way, so they match here. */
  --accent-solid: #2563eb;
  --accent-text: #ffffff;
  --good: #15803d;
  --good-bg: #dcfce7;
  --bad: #b91c1c;
  --bad-bg: #fee2e2;
  --warn: #92400e;
  --warn-bg: #fef3c7;
  --bar-track: #e5e8ec;
  --bar-good: #22c55e;
  --bar-warn: #f59e0b;
  --bar-bad: #ef4444;
  --bar-stale: #a7aeb8;
  --claude: #b8501f;
  --codex: #0b7a5f;
  --shadow: 0 1px 2px rgba(16, 20, 28, 0.06), 0 6px 16px rgba(16, 20, 28, 0.05);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0d1014;
    --surface: #171b21;
    --text: #e9ecef;
    --muted: #98a2ae;
    --border: #272d35;
    /* 4.70:1 as text on --surface. */
    --accent: #3b82f6;
    /* 4.72:1 under white, where #3b82f6 manages only 3.68:1. It matters at
       this size: the filled button's label is 18.36px at the phone root, just
       under the 18.66px bold cutoff for large text, so it needs 4.5:1. */
    --accent-solid: #2b6fe0;
    --accent-text: #ffffff;
    --good: #4ade80;
    --good-bg: #14331f;
    --bad: #f87171;
    --bad-bg: #3b1414;
    --warn: #fbbf24;
    --warn-bg: #3a2c0e;
    --bar-track: #2c333c;
    --bar-stale: #59636f;
    --claude: #e0895f;
    --codex: #2dd4a7;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 20px rgba(0, 0, 0, 0.32);
  }
}

* { box-sizing: border-box; }

/* Safety net: any author display rule would otherwise beat the UA stylesheet
   and leave a "hidden" screen on the page. This bit us once already. */
[hidden] { display: none !important; }

html {
  /* Base size the rest of the sheet scales from. Phones get a larger root so
     0.9rem detail text lands around 16px instead of 14px, which is the
     difference between "readable at arm's length" and squinting. */
  font-size: 17px;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* Every phone in portrait, not just narrow ones: a Pixel is ~412-448px wide
   and was falling through to the smaller desktop base.
   18px, not the 19px this was: at 19px a three-account dashboard ran past the
   bottom of the screen, so the way to see everything was to scroll rather than
   to look. Body text still lands at ~17px, comfortably above the 16px floor. */
@media (max-width: 540px) {
  html { font-size: 18px; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* Android: no grey flash box on every tap, and no browser pull-to-refresh
     stealing a swipe that was meant for the page. */
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior-y: contain;
}

#app {
  /* Fill the phone edge to edge; only hold a column back on big screens. */
  width: 100%;
  max-width: 620px;
  margin: 0 auto;
  padding: 11px 14px calc(16px + env(safe-area-inset-bottom, 0px));
  padding-left: max(14px, env(safe-area-inset-left, 0px));
  padding-right: max(14px, env(safe-area-inset-right, 0px));
}

@media (min-width: 700px) {
  #app { padding: 18px 20px 28px; }
}

.brand {
  font-size: 1.24rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  margin: 0;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- login ---------- */

/* An author `display` rule beats the UA stylesheet's [hidden] { display:none },
   so this MUST be scoped to the visible state - otherwise the login form keeps
   rendering above the dashboard after signing in. */
#login-screen[hidden] { display: none; }

#login-screen:not([hidden]) {
  /* Centre the form in the viewport instead of leaving it as a small island
     stranded near the top of an otherwise empty page. dvh, not vh: on Android
     the collapsing URL bar changes vh mid-scroll and the form jumps with it. */
  min-height: calc(100dvh - 46px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  padding-bottom: 8vh;
}

#login-screen .brand {
  font-size: 2.4rem;
  margin-bottom: 4px;
}

#login-screen .brand-sub {
  color: var(--muted);
  margin: 0 0 8px;
  font-size: 1.02rem;
}

#login-form {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  text-align: left;
}

#login-form label {
  font-size: 1.02rem;
  color: var(--muted);
}

#login-form input {
  min-height: 58px;
  padding: 0 16px;
  font-size: 1.2rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text);
}

#login-form input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.error { color: var(--bad); margin: 4px 0 0; }

/* ---------- notices (offline screen, login help) ---------- */

.notice {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px;
  margin: 0;
  text-align: left;
}

.notice-title { margin: 0; font-weight: 700; }

.notice-body {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

#offline-screen { padding-top: 12vh; text-align: center; }
#offline-screen .notice { margin-top: 20px; }
#offline-screen .big-btn { margin-top: 14px; }

.shake { animation: shake 0.4s; }

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(8px); }
  60% { transform: translateX(-6px); }
  80% { transform: translateX(6px); }
}

/* ---------- dashboard ---------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}

.ghost-btn {
  /* 48px is Android's minimum comfortable target; 44 was borderline. */
  min-height: 48px;
  min-width: 48px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  font-size: 0.94rem;
  cursor: pointer;
}

.ghost-btn:active { background: var(--bar-track); }

/* ---------- agents row ---------- */

/* What is actually running on the cloud, at the top where it gets read first:
   before taking the desktop you want to know which agent is alive. */
.agents-row {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-bottom: 8px;
}

/* Read-only status, not a control, so it is sized for reading rather than for
   a thumb - the 44px floor applies to the buttons, not to these. */
.agent-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 0 11px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  font-size: 0.88rem;
  font-weight: 600;
  box-shadow: var(--shadow);
}

.agent-pill.off { color: var(--muted); font-weight: 500; }
.agent-pill .agent-since { color: var(--muted); font-weight: 500; }

/* ---------- system strip ---------- */

.system-strip {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 10px 13px 11px;
  margin-bottom: 8px;
  box-shadow: var(--shadow);
}

.strip-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.system-strip .mem-line {
  margin: 0;
  font-weight: 700;
  font-size: 1.02rem;
  font-variant-numeric: tabular-nums;
}

.system-strip .cpu-line {
  margin: 5px 0 0;
  color: var(--muted);
  font-size: 0.88rem;
  font-variant-numeric: tabular-nums;
}

.badge {
  display: inline-block;
  flex: none;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.86rem;
  font-weight: 700;
  white-space: nowrap;
}
.badge.good { color: var(--good); background: var(--good-bg); }
.badge.bad { color: var(--bad); background: var(--bad-bg); }
.badge.unknown { color: var(--muted); background: var(--bar-track); }

.warning {
  background: var(--warn-bg);
  color: var(--warn);
  border-radius: 14px;
  padding: 10px 14px;
  margin-bottom: 10px;
  font-weight: 600;
  font-size: 0.95rem;
}

/* ---------- account cards ---------- */

.accounts { display: flex; flex-direction: column; gap: 8px; }

.card {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 10px 13px 12px;
  box-shadow: var(--shadow);
}

/* A hairline in the app's own colour, so Claude and Codex are told apart
   before a single word is read. */
.card.prov-claude::before,
.card.prov-codex::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
}
.card.prov-claude::before { background: var(--claude); }
.card.prov-codex::before { background: var(--codex); }
.card.prov-claude, .card.prov-codex { padding-left: 18px; }

.card-head {
  display: flex;
  align-items: baseline;
  gap: 6px 8px;
  flex-wrap: wrap;
}

.card-head .label { font-weight: 700; font-size: 1.06rem; word-break: break-word; }

.chip {
  font-size: 0.76rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 2px 9px;
  white-space: nowrap;
}
.chip.active { color: var(--good); background: var(--good-bg); }
.chip.idle { color: var(--muted); background: var(--bar-track); }
.chip.plan {
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--border);
  font-weight: 600;
}

/* The app is the identity that matters: two accounts can share one address. */
.card-head .label.prov-claude { color: var(--claude); }
.card-head .label.prov-codex { color: var(--codex); }

/* Rides on the head line beside the app name; wraps to its own line only on a
   card narrow enough that it has to, and stays left-aligned when it does -
   pushing it right with margin-left:auto stranded it as a right-aligned orphan
   on exactly the cards where it did not fit. */
.account-email {
  color: var(--muted);
  font-size: 0.84rem;
  word-break: break-all;
}

/* One usage window: the number, the window it belongs to, and when it resets,
   all on one line with the bar under it. Three stacked lines per window is
   what made these cards tall enough to push the buttons off a phone screen. */
.usage { margin-top: 6px; }

.usage-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 10px;
  margin-bottom: 4px;
}

.bar-label {
  margin: 0;
  font-weight: 700;
  font-size: 1.06rem;   /* the number you actually came to read */
  font-variant-numeric: tabular-nums;
  /* The wrap above is what stops the reset time squeezing this into a broken
     phrase. At 360px "53% left · weekly limit" was crushed until "limit" fell
     to a line of its own; flex-wrap sizes both items at their full text first,
     so the reset time drops to its own line instead - it is the half that can
     afford to move. */
}

.bar-label .bar-scope {
  font-weight: 500;
  font-size: 0.84rem;
  color: var(--muted);
}

.resets {
  flex: none;
  margin-left: auto;   /* stays right-aligned on the line it wraps onto */
  color: var(--muted);
  font-size: 0.82rem;
  white-space: nowrap;
}

.bar-wrap {
  height: 8px;
  border-radius: 999px;
  background: var(--bar-track);
  overflow: hidden;
}

.bar {
  height: 100%;
  border-radius: 999px;
  background: var(--bar-good);
  transition: width 0.4s ease;
}
.bar.warn { background: var(--bar-warn); }
.bar.bad { background: var(--bar-bad); }
.bar.stale { background: var(--bar-stale); }

/* Reasoned, not measured: the reading is old, but nothing else can have spent
   this account, so what is left can only have gone up. Striped so it never
   passes for a live figure at a glance. */
.bar.inferred {
  background: repeating-linear-gradient(
    135deg,
    var(--bar-good) 0 6px,
    var(--bar-stale) 6px 12px
  );
}

.muted { color: var(--muted); }
.small { font-size: 0.88rem; }
.card .stale-note { font-size: 0.84rem; font-style: italic; margin: 6px 0 0; }

/* An account whose numbers are not from right now. */
.card.not-current { border-style: dashed; }

.card .empty-title { margin: 0; font-weight: 700; }
.card .empty-body { color: var(--muted); font-size: 0.92rem; margin: 5px 0 0; }

/* ---------- the way in to the machine, at the top of the page ---------- */

/* This used to be a dock fixed to the bottom of the viewport. That put the
   control within thumb reach but cost more than it bought: it covered the last
   account card, and with the phone on its side it took a third of a 393px-tall
   screen. At the top it is read first, reached without scrolling, and costs
   nothing but its own height. */
.connect-bar { margin-bottom: 12px; }

.connect-buttons {
  display: flex;
  gap: 10px;
}

/* The mode that fits the device in front of the owner leads the row, and
   takes the larger share of it. */
.connect-buttons .mode-first { order: -1; flex: 1.7; }

.mode-hint {
  margin: 7px 0 0;
  text-align: center;
  font-size: 0.82rem;
  text-wrap: balance;
}

.big-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 54px;
  padding: 0 16px;
  border: none;
  border-radius: 14px;
  background: var(--accent-solid);
  color: var(--accent-text);
  font-size: 1.02rem;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
}

.big-btn.secondary {
  background: var(--surface);
  color: var(--accent);
  border: 2px solid var(--accent);
}

/* Android has no hover; a press state is the only feedback a tap ever gets. */
.big-btn:active { transform: scale(0.98); filter: brightness(0.94); }

@media (prefers-reduced-motion: reduce) {
  .bar, .big-btn { transition: none; }
  .big-btn:active { transform: none; }
  .shake { animation: none; }
}

/* ---------- manage accounts ---------- */

/* A <details> rather than a screen of its own: adding an account is a
   once-each errand, and the dashboard is what the phone came here to read.
   Native disclosure also means the open/closed state costs no JavaScript and
   survives the list being re-rendered on every poll. */
.manage {
  margin-top: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow);
}

.manage > summary {
  /* 48px of target, and the padding is on the summary so the whole strip is
     tappable rather than just the words. */
  min-height: 48px;
  display: flex;
  align-items: center;
  padding: 0 14px;
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
  border-radius: 14px;
}

.manage > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.manage[open] > summary { border-bottom: 1px solid var(--border); border-radius: 14px 14px 0 0; }

.add-account {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
}

/* App and address side by side: the select needs only its own width, and on a
   phone the address is what deserves the rest of the line. */
.field-row { display: flex; gap: 10px; align-items: flex-end; }
.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field.grow { flex: 1 1 auto; }
.field > span { font-size: 0.88rem; color: var(--muted); }

/* Wide enough for the longest app name plus the dropdown arrow. Sized on the
   field rather than the select, so the box grows with its column instead of
   spilling over the address field beside it. */
.field-app { flex: 0 0 8em; }

.add-account input,
.add-account select {
  min-height: 48px;
  padding: 0 12px;
  font-size: 1rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text);
  /* Without this a long address makes the flex row wider than the phone. */
  min-width: 0;
  width: 100%;
}

.add-account input:focus,
.add-account select:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.add-account .big-btn { margin-top: 2px; }

.record-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border);
}

.record-list > .muted { margin: 0; padding: 14px; }

.record-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
}

.record-row + .record-row { border-top: 1px solid var(--border); }

.record-text { min-width: 0; flex: 1 1 auto; }

.record-name {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  margin: 0;
}

.record-name .label { font-weight: 700; font-size: 0.94rem; }
.record-name .label.prov-claude { color: var(--claude); }
.record-name .label.prov-codex { color: var(--codex); }
.record-email { word-break: break-all; font-size: 0.94rem; }
.record-text > .small { margin: 3px 0 0; }
.record-remove { flex: 0 0 auto; }

/* The note, editable where the account is listed. Borderless until you touch
   it, so a list of accounts still reads as a list rather than a form. */
.record-note {
  margin-top: 4px;
  width: 100%;
  min-width: 0;
  min-height: 36px;
  padding: 0 8px;
  font-size: 0.9rem;
  font-style: italic;
  color: var(--text);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
}

.record-note::placeholder { font-style: normal; color: var(--muted); }
.record-note:hover { border-color: var(--border); }

.record-note:focus {
  font-style: normal;
  background: var(--bg);
  border-color: var(--border);
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* The owner's own note about an account, on its card. */
.card .card-note { margin: 6px 0 0; font-style: italic; }

/* One line above the cards: how many accounts are reporting, how many still
   need signing into on the machine. */
.accounts-summary { margin: 0 0 8px; }

/* The way onto the machine from the card that needs it. Full width and clearly
   a button, but secondary to the two mode buttons at the top of the page - it
   is one account's errand, not the main way in. */
.card .card-signin {
  margin-top: 10px;
  min-height: 48px;
  font-size: 1rem;
}

/* "This is one of my accounts", on a card no record claims yet. */
.link-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.link-select {
  flex: 1 1 12ch;
  min-width: 0;
  min-height: 44px;
  padding: 0 10px;
  font-size: 0.94rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
}

.link-select:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.link-row .ghost-btn { min-height: 44px; flex: 0 0 auto; }

/* ---------- footer ---------- */

.page-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0 12px;
  margin-top: 10px;
}

#updated-at { margin: 0; }

/* Footer link out to the password manager. */
.footer-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 0.9rem;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.footer-link:hover,
.footer-link:focus-visible { color: var(--text); }

/* "Claude is open" indicators. */
.dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
}
.dot.on { background: var(--good); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 22%, transparent); }
.dot.off { background: var(--muted); opacity: 0.55; }

/* ---------- connect page ---------- */

.connect-page h1 { margin: 8px 0 16px; }
.connect-page ol { padding-left: 22px; }
.connect-page li { margin-bottom: 12px; }
.connect-page .big-btn { margin-top: 20px; }
.connect-page code, .connect-page strong.addr {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px 8px;
  font-size: 1rem;
  word-break: break-all;
}
