/* Oscar — test console styling.
   One file, no build step, no framework. */

/* ---------- the palette ----------
   Every colour here is pulled off the mark: a black field, one cold white
   highlight, and the spectrum ring. Nothing in this file names a colour that
   is not in that drawing, so the site and the logo cannot drift apart.

   --accent stays a single hue because most of the UI needs one: a border, a
   focus ring, a link. It is the blue at the head of the ring — the colour that
   still reads as Oscar at 12px, which the whole spectrum does not. Surfaces
   big enough to carry the full sweep use --spectrum instead. */
:root {
  --bg: #05070c;
  --bg-soft: #0e1219;
  --line: #1d2431;
  --text: #e9ecf3;
  --muted: #8f9aad;
  --accent: #49b6ff;
  --accent-ink: #040a14;
  --good: #4ade80;
  --bad: #ff6b7d;

  /* The ring, as stops and as the sweep. */
  --ring-1: #4ea8ff;
  --ring-2: #a86bff;
  --ring-3: #ff7ad9;
  --ring-4: #ffa04d;
  --ring-5: #ffe14d;
  --ring-6: #4ade80;
  --spectrum: linear-gradient(
    100deg,
    var(--ring-1),
    var(--ring-2),
    var(--ring-3),
    var(--ring-4),
    var(--ring-5),
    var(--ring-6)
  );

  /* Ink for anything sitting ON the sweep. Dark in BOTH themes, unlike
     --accent-ink, because the sweep runs through yellow in both and white on
     yellow is unreadable whichever way the page is lit. */
  --spectrum-ink: #0a0d14;

  --radius: 14px;
  --shadow: 0 10px 30px rgb(0 0 0 / 55%);
  --glow: 0 0 24px color-mix(in srgb, var(--accent) 40%, transparent);
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f7f8fb;
    --bg-soft: #ffffff;
    --line: #e2e5ea;
    --text: #101420;
    --muted: #5c6472;
    --accent: #0b74d4;
    --accent-ink: #ffffff;
    --shadow: 0 10px 30px rgb(15 23 42 / 8%);
    --glow: 0 0 20px color-mix(in srgb, var(--accent) 22%, transparent);

    /* The ring stops darken here rather than being swapped for other hues:
       the same six colours, taken down far enough to hold their own against
       white. The mark itself is unchanged — it is drawn on its own black. */
    --ring-1: #1f7fe0;
    --ring-2: #7c3fd8;
    --ring-3: #d2429c;
    --ring-4: #d4741a;
    --ring-5: #b8951a;
    --ring-6: #1f9d57;
  }
}

* {
  box-sizing: border-box;
}

/* The `hidden` attribute must always win.
   Browsers implement it as `[hidden] { display: none }` in the UA stylesheet,
   which any class selector outranks. Since this file sets `display` on .card,
   .step and the tab panes, those elements would stay visible even when marked
   hidden — showing both login steps at once, an empty result card, and both
   tabs stacked. One rule fixes all of it. */
[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 ui-sans-serif, -apple-system, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  margin: 0;
  min-height: 100dvh;
}

/* ---------- view switching ----------
   `hidden` is set by app.js, but these rules enforce the same thing from CSS
   so the app view can never flash before JavaScript has run. */

#view-loading,
#view-landing,
#view-login,
#view-app {
  display: none;
}

body[data-state="loading"] #view-loading,
body[data-state="out"] #view-login,
body[data-state="in"] #view-app {
  display: grid;
}

/* Block rather than grid: the landing page is a document that scrolls, and its
   own sections do the layout. */
body[data-state="landing"] #view-landing {
  display: block;
}

/* The app: a normal top-aligned page. */
.shell {
  max-width: 640px;
  margin: 0 auto;
  /* The bottom padding clears the floating tab bar, plus the home indicator on
     an iPhone. Without it the last turn of a conversation sits underneath the
     bar and cannot be scrolled into view. */
  padding: 28px 18px calc(104px + env(safe-area-inset-bottom));
  gap: 18px;
}

/* The login: centred in the viewport, nothing else on the page.
   dvh rather than vh so the mobile keyboard doesn't shove the card off screen. */
.login-shell {
  min-height: 100dvh;
  place-items: center;
  padding: 24px 18px;
}

/* ---------- boot / loading ---------- */

.boot {
  min-height: 100dvh;
  place-items: center;
  align-content: center;
  gap: 14px;
}

.spinner {
  width: 26px;
  height: 26px;
  border: 2px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.boot-text {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
}

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation-duration: 2.4s;
  }
}

/* ---------- header ---------- */

.head {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 10px;
}

/* The logo itself, from public/mark.svg.

   It used to be a rounded square with a notch punched out of it by ::after —
   a placeholder that predated there being a real mark. The drawing carries its
   own black field, so there is no background or radius here: on a light page
   it is still the logo rather than a dark square in the corner. */
.mark {
  width: 40px;
  height: 40px;
  flex: none;
  background: url("./mark.svg") center / contain no-repeat;
  filter: drop-shadow(0 0 12px color-mix(in srgb, var(--ring-1) 35%, transparent));
}

.mark::after {
  content: none;
}

h1 {
  font-size: 20px;
  margin: 0;
  letter-spacing: -0.01em;
}

h2 {
  font-size: 15px;
  margin: 0 0 6px;
}

.sub {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: 13px;
}

.pill {
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--muted);
  white-space: nowrap;
}

.pill[data-state="ok"] {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 40%, transparent);
}

.pill[data-state="bad"] {
  color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 40%, transparent);
}

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

.card {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px;
  display: grid;
  gap: 12px;
}

.field {
  display: grid;
  gap: 6px;
}

.label {
  font-size: 12px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.row {
  display: flex;
  gap: 8px;
}

input,
textarea {
  width: 100%;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  font: inherit;
  resize: vertical;
}

input:focus,
textarea:focus {
  outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
  outline-offset: 1px;
  border-color: transparent;
}

.actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

button {
  font: inherit;
  border-radius: 10px;
  padding: 9px 16px;
  cursor: pointer;
  border: 1px solid var(--line);
  transition: transform 0.06s ease, opacity 0.15s ease;
}

button:active {
  transform: translateY(1px);
}

button:disabled {
  opacity: 0.5;
  cursor: default;
}

/* The one control that gets the whole ring. */
.primary {
  background: var(--spectrum);
  color: var(--spectrum-ink);
  border-color: transparent;
  font-weight: 650;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--ring-2) 28%, transparent);
}

.primary:disabled {
  box-shadow: none;
}

.ghost {
  background: transparent;
  color: var(--text);
}

.ghost.on {
  border-color: var(--accent);
  color: var(--accent);
}

.dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  margin-right: 6px;
  vertical-align: 1px;
  opacity: 0.5;
}

.ghost.on .dot {
  opacity: 1;
  animation: pulse 1.1s ease-in-out infinite;
}

@keyframes pulse {
  50% {
    opacity: 0.25;
    transform: scale(0.75);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ghost.on .dot {
    animation: none;
  }
}

/* ---------- the conversation ---------- */

.thread {
  display: grid;
  gap: 10px;
}

.turn {
  display: grid;
  gap: 4px;
  padding: 11px 14px;
  border-radius: 16px;
  max-width: 86%;
  border: 1px solid var(--line);
  background: var(--bg-soft);
}

/* Yours on the right, Oscar's on the left. The asymmetry is doing the work of a
   label — you never have to read a turn to know who said it. */
.turn.you {
  justify-self: end;
  background: color-mix(in srgb, var(--accent) 16%, var(--bg-soft));
  border-color: color-mix(in srgb, var(--accent) 30%, var(--line));
  border-bottom-right-radius: 5px;
}

.turn.oscar {
  justify-self: start;
  border-bottom-left-radius: 5px;
}

.turn.bad {
  border-color: color-mix(in srgb, var(--bad) 45%, transparent);
}

/* Still being worked on in the background: dimmed rather than removed, because
   the answer that is coming replaces this exact bubble. */
.turn.working {
  opacity: 0.75;
  border-style: dashed;
}

.turn-body {
  font-size: 14.5px;
  white-space: pre-wrap;
  word-break: break-word;
}

.turn.bad .turn-body {
  color: var(--bad);
}

.turn-detail {
  margin: 2px 0 0;
  font-size: 13.5px;
  color: var(--muted);
  white-space: pre-wrap;
}

.turn-meta {
  margin: 2px 0 0;
  font-size: 11px;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.turn-meta:empty {
  display: none;
}

.turn-meta .tag {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 8px;
}

/* Three dots while Oscar is still deciding what to say. */
.typing {
  display: inline-flex;
  gap: 4px;
  padding: 3px 0;
}

.typing i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--muted);
  animation: pulse 1.1s ease-in-out infinite;
}

.typing i:nth-child(2) {
  animation-delay: 0.15s;
}

.typing i:nth-child(3) {
  animation-delay: 0.3s;
}

@media (prefers-reduced-motion: reduce) {
  .typing i {
    animation: none;
  }
}

.detail {
  margin: 0;
  font-size: 14px;
  color: var(--text);
  white-space: pre-wrap;
}

.meta {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}

/* ---------- recipe ---------- */

.steps {
  margin: 0;
  padding-left: 20px;
  font-size: 13px;
  color: var(--muted);
  display: grid;
  gap: 4px;
}

.steps b {
  color: var(--text);
  font-weight: 600;
}

.code {
  margin: 0;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
  font: 12px/1.6 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  color: var(--muted);
  overflow-x: auto;
  white-space: pre;
}

code {
  font: 12px ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 1px 5px;
}

.recipe button {
  justify-self: start;
}

.foot {
  text-align: center;
  font-size: 12px;
  color: var(--muted);
  word-break: break-all;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

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

.gate {
  width: 100%;
  max-width: 380px;
  padding: 24px;
  gap: 16px;
  box-shadow: var(--shadow);
}

/* Login-screen branding — the app header does not exist in this state. */
.brand {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 12px;
  margin-bottom: 2px;
}

.brand h1 {
  font-size: 18px;
}

.wide {
  width: 100%;
  padding: 11px 16px;
}

.step {
  display: grid;
  gap: 12px;
}

.hint {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}

.linky {
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  text-decoration: underline;
}

.small {
  padding: 5px 11px;
  font-size: 13px;
}

/* One-time code: wide tracking so a 6-character code is easy to proof-read. */
.code-input {
  font: 600 22px/1.3 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  letter-spacing: 0.4em;
  text-align: center;
  text-transform: uppercase;
  padding: 12px;
}

.code-input::placeholder {
  letter-spacing: 0.4em;
  font-weight: 400;
  opacity: 0.35;
}

.alert {
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  padding: 10px 12px;
  border-radius: 10px;
  color: var(--bad);
  background: color-mix(in srgb, var(--bad) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--bad) 35%, transparent);
}

.alert.warn {
  color: var(--text);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}

/* The same box in the "that worked" colour. Used by the request-sent panel and
   by anything else that has good news rather than bad. */
.ok-note {
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  padding: 10px 12px;
  border-radius: 10px;
  color: var(--text);
  background: color-mix(in srgb, var(--good, var(--accent)) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--good, var(--accent)) 35%, transparent);
}

.muted {
  opacity: 0.6;
  font-weight: 400;
}

/* A credential shown exactly once. Monospace and selectable, with room to be
   read off a screen and typed into a phone. */
.secret-out {
  margin: 0;
  padding: 12px;
  border-radius: 10px;
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.5;
  word-break: break-all;
  user-select: all;
}

/* ---------- the accounts queue ---------- */

.account-row {
  display: grid;
  gap: 8px;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.account-row:last-child {
  border-bottom: 0;
}

.account-who {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
}

.account-email {
  font-weight: 600;
  word-break: break-all;
}

.account-note {
  font-size: 13px;
  opacity: 0.75;
  line-height: 1.45;
}

.account-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.tag {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  border: 1px solid var(--line);
  opacity: 0.85;
}

.tag.pending {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.tag.approved {
  border-color: color-mix(in srgb, var(--good, var(--accent)) 45%, transparent);
  background: color-mix(in srgb, var(--good, var(--accent)) 12%, transparent);
}

.tag.declined,
.tag.suspended {
  border-color: color-mix(in srgb, var(--bad) 40%, transparent);
  background: color-mix(in srgb, var(--bad) 10%, transparent);
}

/* ---------- panes and the floating tab bar ---------- */

.pane {
  display: grid;
  gap: 18px;
  align-content: start;
}

/* Fixed rather than sticky: it belongs to the app, not to the document, so it
   stays put whether the conversation is two turns long or two hundred. */
.tabbar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(14px + env(safe-area-inset-bottom));
  z-index: 20;

  display: flex;
  gap: 2px;
  padding: 5px;
  border-radius: 18px;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg-soft) 82%, transparent);
  box-shadow: var(--shadow);

  /* Frosted, so a conversation scrolling underneath reads as behind it rather
     than through it. Harmless where it is unsupported — the colour mix above
     already keeps the labels legible. */
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);

  /* Widened, and the tabs below tightened, when Protocols made it five. The
     bar is still one row on a 375px phone — a tab bar that wraps has stopped
     being a tab bar. */
  max-width: min(460px, calc(100vw - 16px));
  width: max-content;
}

.tab {
  position: relative;
  /* `auto` rather than `0`, so a tab is at least as wide as its own label.
     With four tabs a fixed floor was enough for the longest of them; with
     "Protocols" in the row it is not, and a floor that clips the widest label
     is the one arrangement worse than uneven tabs. */
  flex: 1 1 auto;
  border: 0;
  background: transparent;
  color: var(--muted);
  border-radius: 14px;
  padding: 7px 7px 6px;
  /* Eases down on a narrow phone rather than pushing the bar past the edge of
     it. Unchanged at 375px and above, which is what this bar is drawn for. */
  font-size: clamp(10px, 2.9vw, 11px);
  line-height: 1.2;
  display: grid;
  justify-items: center;
  gap: 3px;
  /* Keeps the three short labels from becoming three tiny tap targets. */
  min-width: 52px;
}

.tab svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tab.on {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  font-weight: 600;
}

/* How many jobs are still going. Hidden at zero rather than showing a 0, which
   would read as a state worth checking. */
.badge {
  position: absolute;
  top: 2px;
  right: 8px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

/* ---------- history ---------- */

.history {
  display: grid;
  gap: 10px;
}

.entry {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  display: grid;
  gap: 6px;
}

.entry.bad {
  border-color: color-mix(in srgb, var(--bad) 40%, transparent);
}

.entry-q {
  font-weight: 600;
  font-size: 14px;
}

.entry-a {
  font-size: 14px;
  color: var(--text);
  white-space: pre-wrap;
}

.entry.bad .entry-a {
  color: var(--bad);
}

.entry-meta {
  font-size: 11px;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.tag {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 8px;
}

.empty {
  text-align: center;
  color: var(--muted);
  font-size: 14px;
  padding: 32px 16px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

/* ---------- destructive confirmation ---------- */

.confirm {
  display: grid;
  gap: 10px;
  padding: 12px;
  border: 1px solid color-mix(in srgb, var(--bad) 45%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--bad) 7%, transparent);
}

.confirm-note {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

.danger {
  background: var(--bad);
  color: #fff;
  border-color: transparent;
  font-weight: 600;
}

@media (prefers-color-scheme: light) {
  .danger {
    background: #dc2626;
  }
}

/* ---------- activity log: Oscar thinking out loud ---------- */

.activity-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.activity-head h2 {
  margin: 0;
}

.pill[data-state="idle"] {
  color: var(--muted);
}

.pill[data-state="working"] {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.pill[data-state="working"]::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  margin-right: 6px;
  vertical-align: 1px;
  animation: pulse 1.1s ease-in-out infinite;
}

.pill[data-state="done"] {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 40%, transparent);
}

.log {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  font: 13px/1.5 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

.log:empty {
  display: none;
}

.log li {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: 10px;
  padding: 7px 2px;
  border-bottom: 1px solid var(--line);
}

.log li:last-child {
  border-bottom: 0;
}

.log .tick {
  color: var(--muted);
  font-size: 11px;
  min-width: 2.5em;
}

.log .what {
  color: var(--text);
  word-break: break-word;
}

.log .how {
  color: var(--muted);
  font-size: 11px;
}

.log li.bad .what {
  color: var(--bad);
}

.log li.note .what {
  color: var(--muted);
  font-style: italic;
}

.subtle {
  border: 0;
  padding: 14px 0;
  text-align: left;
}

.small {
  padding: 6px 12px;
  font-size: 13px;
}

/* ------------------------------------------------------------- questions --
   Things Oscar has stopped to ask. Given an accent edge because a suspended
   run is genuinely blocked on you — this is the one card on the page that is
   waiting rather than reporting. */

.card.questions {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  border-left-width: 3px;
}

.card.questions h2 {
  font-size: 0.95rem;
  margin: 0;
  color: var(--accent);
}

.questions-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 14px;
}

.question + .question {
  border-top: 1px solid var(--line);
  padding-top: 14px;
}

.question-text {
  margin: 0 0 4px;
  font-weight: 600;
  line-height: 1.4;
}

.question-row {
  margin-top: 10px;
  flex-wrap: wrap;
  gap: 8px;
}

/* An open question grows to fill the row; option buttons size to their text. */
.question-row input[type='text'] {
  flex: 1 1 12rem;
  min-width: 0;
}

.question[aria-busy='true'] {
  opacity: 0.55;
}

/* ------------------------------------------------------------- composer ----
   Sticky rather than fixed: it stays with the conversation it belongs to, and
   scrolls away with the pane when you switch to another tab. */

.composer {
  position: sticky;
  bottom: calc(88px + env(safe-area-inset-bottom));
  z-index: 10;
  box-shadow: var(--shadow);
}

.composer textarea {
  min-height: 46px;
}

/* Pushes the send controls to the right of a row that also holds New chat. */
.spacer {
  flex: 1;
}

.composer .actions {
  align-items: center;
  justify-content: flex-start;
}

/* --------------------------------------------------------------- tasks -----
   What Oscar decided to do, and where he has got to. This is the part of
   "Oscar's thinking" that survives turning the detailed view off, because it is
   the only part written in words rather than tool names. */

.tasks {
  display: grid;
  gap: 8px;
}

.tasks-head {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.02em;
}

.tasks-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.tasks-list li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 9px;
  align-items: baseline;
  padding: 6px 2px;
  font-size: 14px;
  color: var(--muted);
}

.task-mark {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  align-self: center;
}

/* Done: a filled tick. Current: a pulsing accent dot. Pending: an empty ring.
   Three states you can tell apart at a glance without reading anything. */
.tasks-list li.done {
  color: var(--muted);
}

.tasks-list li.done .task-title {
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--muted) 60%, transparent);
}

.tasks-list li.done .task-mark {
  border-color: color-mix(in srgb, var(--good) 60%, transparent);
  background: color-mix(in srgb, var(--good) 22%, transparent);
  position: relative;
}

.tasks-list li.done .task-mark::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 1px;
  width: 4px;
  height: 8px;
  border: solid var(--good);
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(42deg);
}

.tasks-list li.now {
  color: var(--text);
  font-weight: 600;
}

.tasks-list li.now .task-mark {
  border-color: var(--accent);
  background: var(--accent);
  animation: pulse 1.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .tasks-list li.now .task-mark {
    animation: none;
  }
}

.task-title {
  word-break: break-word;
}

.task-note {
  grid-column: 2;
  font-size: 12px;
  font-weight: 400;
  color: var(--muted);
}

/* ----------------------------------------------------------------- jobs ----
   Work that outgrew a single request. Reuses the history entry card, with a
   status pill and, when there is one, the task list. */

.jobs {
  display: grid;
  gap: 10px;
}

.job-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.job-head .entry-q {
  flex: 1;
}

/* A job still going gets an accent edge, the same signal the questions card
   uses for "this one is not finished with you". */
.entry.live {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  border-left-width: 3px;
}

.entry button {
  justify-self: start;
}

.entry.openable {
  cursor: default;
}

/* Earlier turns inside a history thread, which are context rather than the
   headline — smaller, and not competing with the first question. */
.small-q {
  font-weight: 600;
  font-size: 13px;
  color: var(--muted);
  margin-top: 8px;
}

.entry details summary {
  cursor: pointer;
  padding: 2px 0;
}

/* ------------------------------------------------------------- settings ---- */

.toggle {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 10px;
  align-items: start;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}

.toggle:last-of-type {
  border-bottom: 0;
}

.toggle input {
  width: 20px;
  height: 20px;
  margin: 2px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.toggle b {
  display: block;
  font-size: 14px;
  font-weight: 600;
}

.toggle em {
  display: block;
  margin-top: 2px;
  font-size: 12.5px;
  font-style: normal;
  color: var(--muted);
  line-height: 1.45;
}

/* ---------- what the Settings toggles actually hide ----------
   Done in CSS, keyed off <body>, rather than by re-rendering: flipping a switch
   then has to change nothing that is already on screen, and a conversation you
   are halfway through loses its model names the instant you say so. */

body[data-detailed="off"] #activity-log {
  display: none;
}

body[data-model="off"] .meta-model,
body[data-timing="off"] .meta-timing {
  display: none;
}

/* --------------------------------------------------- server-side settings --
   The note under a setting that is stored on the server rather than in this
   browser. It has states the localStorage toggles never need: saving, because
   there is a round trip, and bad, because the round trip can fail. Everything
   else about it is an ordinary .meta line. */

/* A setting whose answer is one of several, rather than on or off. Laid out
   like .toggle so the Settings pane reads as one list rather than two, but
   stacked instead of side by side: these labels are longer than a checkbox
   caption and wrap badly in a narrow column. */

.choice {
  display: block;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}

.choice:last-of-type {
  border-bottom: 0;
}

.choice b {
  display: block;
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 6px;
}

.choice select {
  width: 100%;
  padding: 8px 10px;
  font: inherit;
  font-size: 13.5px;
  color: var(--text);
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}

.choice select:disabled {
  color: var(--muted);
  cursor: default;
}

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

.choice-note[data-state='saving'] {
  color: var(--muted);
  font-style: italic;
}

.choice-note[data-state='bad'] {
  color: var(--bad);
}


/* ------------------------------------------------------- connected servers --
   The MCP card. Its job is to make a permission decision per tool feel like
   reading a list rather than filling in a form, because a server with fifteen
   tools is fifteen decisions and anything heavier than a dropdown per row would
   make people click "open" on all of them just to be finished. */

.mcp-list {
  display: grid;
  gap: 10px;
}

.mcp-list:empty {
  display: none;
}

.mcp-server {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  display: grid;
  gap: 8px;
  background: var(--bg);
}

/* Switched off at the server level. Dimmed rather than hidden: a server you
   silenced last month should still be findable when you want it back. */
.mcp-server[data-off='1'] {
  opacity: 0.55;
}

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

.mcp-server-head b {
  font-size: 14px;
  font-weight: 600;
}

.mcp-server-head .mcp-url {
  font-size: 12px;
  color: var(--muted);
  word-break: break-all;
  flex: 1 1 140px;
}

.mcp-server-actions {
  display: flex;
  gap: 6px;
}

.mcp-status {
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.45;
}

.mcp-status[data-state='bad'] {
  color: var(--bad);
}

.mcp-tools {
  display: grid;
  gap: 0;
}

.mcp-tool {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid var(--line);
}

.mcp-tool-name {
  flex: 1 1 auto;
  min-width: 0;
}

.mcp-tool-name b {
  display: block;
  font-size: 13px;
  font-weight: 600;
  word-break: break-word;
}

/* The server's own description, and — when it claims one — what it says about
   itself. The claim is shown because it is useful and labelled because it is
   not evidence: see lib/mcp/servers.js. */
.mcp-tool-name em {
  display: block;
  margin-top: 1px;
  font-size: 12px;
  font-style: normal;
  color: var(--muted);
  line-height: 1.4;
}

.mcp-tool select {
  flex: 0 0 auto;
  width: 118px;
  padding: 6px 8px;
  font: inherit;
  font-size: 12.5px;
  color: var(--text);
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}

/* Anything but `off` means the model can reach it, so the row it sits on says
   so at a glance. Reading down a long list for the ones that are live is
   exactly what this card is for. */
.mcp-tool[data-live='1'] .mcp-tool-name b {
  color: var(--accent);
}

.mcp-tool select:disabled {
  color: var(--muted);
  cursor: default;
}

.mcp-add {
  border-top: 1px solid var(--line);
  padding-top: 10px;
  display: grid;
  gap: 10px;
}

.mcp-add summary {
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 600;
  list-style: none;
}

.mcp-add summary::-webkit-details-marker {
  display: none;
}

.mcp-add summary::before {
  content: '+ ';
  color: var(--muted);
}

.mcp-add[open] summary::before {
  content: '− ';
}

/* ---------------------------------------------------------------- schedules

   The scheduled-prompt card reuses .mcp-list and .mcp-add wholesale — the two
   cards are the same shape (a list of things you added, and a form to add
   another), and giving them separate names would mean two sets of rules to keep
   in step for no visible difference. Only the parts with no MCP equivalent are
   below: the day picker, and the line saying when a schedule next runs.
*/

.day-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* Sized so all seven fit on one row at 375px wide. A week that wraps, leaving
   Sunday on a line of its own, reads as a mistake rather than a layout. */
.day-picker .day {
  padding: 8px 4px;
  font-size: 12.5px;
  flex: 1 1 0;
  min-width: 34px;
  white-space: nowrap;
  background: var(--bg);
  color: var(--muted);
  border: 1px solid var(--line);
}

/* Chosen days read as chosen at a glance, because the empty state means
   something quite different from "none of them" — it means every day. */
.day-picker .day[aria-pressed='true'] {
  background: color-mix(in srgb, var(--accent) 20%, var(--bg));
  border-color: var(--accent);
  color: var(--text);
  font-weight: 600;
}

.schedule-when {
  font-size: 12px;
  color: var(--muted);
  flex: 1 1 140px;
}

.schedule-prompt {
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.45;
  /* A prompt can be a paragraph. Two lines is enough to recognise which one
     this is; the whole thing is one tap away in the edit form. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.schedule-next[data-state='bad'] {
  color: var(--bad);
}

/* Paused, and dimmed rather than hidden — same rule as a paused MCP server. */
.mcp-server[data-off='1'] .schedule-next {
  text-decoration: line-through;
}

/* ---------------------------------------------------------------- protocols

   Same shape again — a list of things you added and a form to add another — so
   the protocol cards reuse .mcp-list, .mcp-server and .mcp-add wholesale, and
   the prompt preview reuses the scheduled-prompt clamp. Only the two lines with
   no equivalent anywhere else are below.
*/

/* The sentence that runs it. Quoted, in the accent colour, because it is the
   one thing on the card you have to reproduce exactly — the name above it is
   just what you call it. */
/* Scoped to the head row, because .mcp-url — which this shares its slot with —
   sets a muted colour from a two-class selector and would otherwise win. */
.mcp-server-head .protocol-say {
  font-size: 12px;
  color: var(--accent);
  flex: 1 1 160px;
}

/* Your own note about the prompt. Never sent to the model, and set apart from
   the prompt above it so the difference is visible rather than remembered. */
.protocol-note {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  line-height: 1.45;
}

/* The condensed position log: one .mcp-server card per day, and inside it the
   places you stopped. Reuses the card chrome wholesale — see the note above
   .day-picker for why that is the house style here rather than a fourth set of
   nearly identical rules. Only the inner list is new. */

.location-places {
  display: grid;
  gap: 6px;
}

.location-place {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  font-size: 13px;
}

/* A place Nominatim could not name, or has not been asked about yet. Dimmed
   rather than dropped: you were still there. */
.location-place[data-named='0'] b {
  color: var(--muted);
  font-weight: 500;
}

.location-place span {
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
}
/* ============================================================================
   LANDING PAGE
   ----------------------------------------------------------------------------
   The public face, and the only view that explains what Oscar is. It is the
   widest thing in this file — the console is a 640px phone column, this is a
   full-bleed page — so everything here is namespaced `lp-` or scoped under
   `.landing` and cannot leak into the app.

   The "fluid" part is four blurred blobs drifting behind a veil, plus a wash
   that follows the pointer. Two rules keep it honest:
     - the pointer wash is driven by --mx/--my, which default to the centre, so
       it looks deliberate before app.js has ever moved them;
     - every animation is switched off under prefers-reduced-motion, and the
       reveal transitions fall back to "already visible" rather than blank.
   ========================================================================== */

.landing {
  position: relative;
  min-height: 100dvh;
  /* Its own scroll container is not wanted — the page scrolls — but the
     backdrop is fixed, so this needs a stacking context of its own. */
  isolation: isolate;
  overflow-x: clip;
  padding-bottom: calc(40px + env(safe-area-inset-bottom));
  --mx: 50%;
  --my: 30%;
}

/* ---------- the fluid backdrop ---------- */

.fluid {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(
      60vmax 60vmax at var(--mx) var(--my),
      color-mix(in oklab, var(--accent) 18%, transparent),
      transparent 70%
    ),
    var(--bg);
  /* The wash chases the pointer rather than snapping to it. */
  transition: background-position 0.4s ease;
}

.blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.55;
  will-change: transform;
}

@media (prefers-color-scheme: light) {
  .blob {
    opacity: 0.42;
    filter: blur(80px);
  }
}

.b1 {
  width: 48vmax;
  height: 48vmax;
  left: -12vmax;
  top: -14vmax;
  background: var(--accent);
  animation: drift-a 26s ease-in-out infinite;
}

.b2 {
  width: 40vmax;
  height: 40vmax;
  right: -10vmax;
  top: 4vmax;
  background: var(--ring-2);
  animation: drift-b 32s ease-in-out infinite;
}

.b3 {
  width: 44vmax;
  height: 44vmax;
  left: 18vmax;
  bottom: -18vmax;
  background: var(--ring-6);
  opacity: 0.32;
  animation: drift-c 38s ease-in-out infinite;
}

.b4 {
  width: 30vmax;
  height: 30vmax;
  right: 6vmax;
  bottom: -6vmax;
  background: var(--ring-3);
  opacity: 0.28;
  animation: drift-d 29s ease-in-out infinite;
}

/* Pulls the whole thing back under the text. Without it the blobs are pretty
   and the paragraphs are unreadable, which is the wrong trade. */
.fluid-veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    color-mix(in oklab, var(--bg) 45%, transparent),
    color-mix(in oklab, var(--bg) 78%, transparent)
  );
  backdrop-filter: saturate(1.3);
}

@keyframes drift-a {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  33% {
    transform: translate3d(8vmax, 6vmax, 0) scale(1.12);
  }
  66% {
    transform: translate3d(-4vmax, 10vmax, 0) scale(0.94);
  }
}

@keyframes drift-b {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  40% {
    transform: translate3d(-10vmax, 8vmax, 0) scale(1.18);
  }
  70% {
    transform: translate3d(-2vmax, -6vmax, 0) scale(0.9);
  }
}

@keyframes drift-c {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1.05);
  }
  50% {
    transform: translate3d(12vmax, -10vmax, 0) scale(0.88);
  }
}

@keyframes drift-d {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(0.95);
  }
  50% {
    transform: translate3d(-9vmax, -12vmax, 0) scale(1.2);
  }
}

/* ---------- shared page rhythm ---------- */

.lp-nav,
.lp-hero,
.lp-strip,
.lp-section,
.lp-final,
.lp-foot {
  max-width: 1080px;
  margin: 0 auto;
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
}

/* ---------- nav ---------- */

.lp-nav {
  display: flex;
  align-items: center;
  gap: 16px;
  padding-top: 18px;
  padding-bottom: 18px;
}

.lp-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: inherit;
}

.landing .mark {
  width: 30px;
  height: 30px;
}

.lp-wordmark {
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.lp-nav-links {
  margin-left: auto;
  display: flex;
  gap: 22px;
}

.lp-nav-links a {
  color: var(--muted);
  text-decoration: none;
  font-size: 14px;
  transition: color 0.15s ease;
}

.lp-nav-links a:hover {
  color: var(--text);
}

/* Three links plus two buttons do not fit a phone. The links are the part that
   can go: every section they point at is directly below anyway. */
@media (max-width: 720px) {
  .lp-nav-links {
    display: none;
  }

  .lp-nav .ghost {
    margin-left: auto;
  }
}

/* ---------- hero ---------- */

.lp-hero {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: 48px;
  align-items: center;
  padding-top: clamp(32px, 7vw, 88px);
  padding-bottom: clamp(40px, 8vw, 96px);
}

.lp-eyebrow {
  margin: 0 0 14px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.lp-title {
  font-size: clamp(34px, 6.2vw, 60px);
  line-height: 1.03;
  letter-spacing: -0.035em;
  font-weight: 700;
  margin: 0 0 18px;
}

/* The one gradient in the type. Kept to a single line so it reads as emphasis
   rather than decoration. */
.lp-grad {
  background: linear-gradient(100deg, var(--accent), #a78bfa 55%, #34d399);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.lp-lede {
  margin: 0 0 26px;
  font-size: clamp(15px, 1.6vw, 18px);
  line-height: 1.62;
  color: var(--muted);
  max-width: 46ch;
}

.lp-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.lp-cta-center {
  justify-content: center;
}

.lp-cta button {
  padding: 12px 22px;
  font-size: 15px;
}

.lp-cta .ghost {
  background: color-mix(in oklab, var(--bg-soft) 70%, transparent);
  backdrop-filter: blur(8px);
}

.lp-fineprint {
  margin: 16px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

/* ---------- the lock screen ---------- */

.lp-phone {
  justify-self: center;
  width: min(300px, 100%);
}

.lp-phone-glass {
  border-radius: 30px;
  border: 1px solid color-mix(in oklab, var(--line) 80%, transparent);
  background: color-mix(in oklab, var(--bg-soft) 55%, transparent);
  backdrop-filter: blur(18px) saturate(1.4);
  box-shadow: var(--shadow);
  padding: 26px 14px 22px;
  display: grid;
  gap: 10px;
}

.lp-phone-time {
  margin: 0;
  text-align: center;
  font-size: 46px;
  font-weight: 300;
  letter-spacing: -0.03em;
  line-height: 1;
}

.lp-phone-date {
  margin: 0 0 12px;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}

.lp-notif {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px;
  border-radius: 16px;
  background: color-mix(in oklab, var(--bg-soft) 85%, transparent);
  border: 1px solid color-mix(in oklab, var(--line) 60%, transparent);
  /* Each one slides in a beat after the last, so the stack assembles itself
     the way a real lock screen fills up. */
  opacity: 0;
  transform: translateY(8px);
  animation: notif-in 0.55s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

.lp-notif-1 {
  animation-delay: 0.35s;
}

.lp-notif-2 {
  animation-delay: 0.95s;
}

.lp-notif-3 {
  animation-delay: 1.55s;
}

@keyframes notif-in {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.lp-notif-icon {
  font-size: 17px;
  line-height: 1.3;
  flex: none;
}

.lp-notif-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.lp-notif-title {
  font-size: 12.5px;
  font-weight: 600;
}

.lp-notif-body {
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.4;
}

@media (max-width: 860px) {
  .lp-hero {
    grid-template-columns: 1fr;
    gap: 36px;
  }

  .lp-lede {
    max-width: none;
  }
}

/* ---------- capability strip ---------- */

.lp-strip {
  padding-bottom: clamp(36px, 6vw, 72px);
}

.lp-chips {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}

.lp-chips li {
  font-size: 13px;
  color: var(--muted);
  padding: 6px 13px;
  border-radius: 999px;
  border: 1px solid color-mix(in oklab, var(--line) 75%, transparent);
  background: color-mix(in oklab, var(--bg-soft) 45%, transparent);
  backdrop-filter: blur(6px);
  transition: color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}

.lp-chips li:hover {
  color: var(--text);
  border-color: var(--accent);
  transform: translateY(-2px);
}

/* ---------- sections ---------- */

.lp-section {
  padding-top: clamp(40px, 7vw, 84px);
  padding-bottom: clamp(8px, 2vw, 20px);
  scroll-margin-top: 70px;
}

.lp-h2 {
  font-size: clamp(24px, 3.6vw, 36px);
  line-height: 1.15;
  letter-spacing: -0.025em;
  font-weight: 680;
  margin: 0 0 10px;
}

.lp-sub {
  margin: 0 0 30px;
  color: var(--muted);
  font-size: clamp(14px, 1.5vw, 16.5px);
}

/* ---------- feature cards ---------- */

.lp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
  gap: 14px;
}

.lp-card {
  position: relative;
  padding: 22px 20px;
  border-radius: 18px;
  border: 1px solid color-mix(in oklab, var(--line) 80%, transparent);
  background: color-mix(in oklab, var(--bg-soft) 58%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  transition: transform 0.22s ease, border-color 0.22s ease, background 0.22s ease;
}

.lp-card:hover {
  transform: translateY(-4px);
  border-color: color-mix(in oklab, var(--accent) 55%, var(--line));
  background: color-mix(in oklab, var(--bg-soft) 78%, transparent);
}

.lp-card-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 11px;
  font-size: 18px;
  margin-bottom: 13px;
  background: color-mix(in oklab, var(--accent) 16%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 26%, transparent);
}

.lp-card h3 {
  margin: 0 0 7px;
  font-size: 16.5px;
  letter-spacing: -0.012em;
}

.lp-card p {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
}

/* ---------- steps ---------- */

.lp-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
  counter-reset: none;
}

.lp-step {
  padding: 22px 20px;
  border-radius: 18px;
  border: 1px solid color-mix(in oklab, var(--line) 70%, transparent);
  background: color-mix(in oklab, var(--bg-soft) 40%, transparent);
  backdrop-filter: blur(10px);
}

.lp-step-n {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 14px;
  font-weight: 650;
  margin-bottom: 12px;
  color: var(--accent-ink);
  background: var(--accent);
}

.lp-step h3 {
  margin: 0 0 7px;
  font-size: 16.5px;
}

.lp-step p {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
}

/* ---------- your data ---------- */

.lp-trust {
  padding: clamp(24px, 4vw, 40px);
  border-radius: 22px;
  border: 1px solid color-mix(in oklab, var(--line) 80%, transparent);
  background: color-mix(in oklab, var(--bg-soft) 62%, transparent);
  backdrop-filter: blur(16px) saturate(1.2);
}

.lp-list {
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px 28px;
}

.lp-list li {
  position: relative;
  padding-left: 22px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
}

.lp-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

.lp-list strong {
  color: var(--text);
  font-weight: 620;
}

/* ---------- final call ---------- */

.lp-final {
  text-align: center;
  padding-top: clamp(52px, 9vw, 108px);
  padding-bottom: clamp(36px, 6vw, 68px);
}

.lp-final .lp-sub {
  margin-bottom: 24px;
}

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

.lp-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding-top: 22px;
  border-top: 1px solid color-mix(in oklab, var(--line) 60%, transparent);
  color: var(--muted);
  font-size: 12.5px;
}

.lp-foot p {
  margin: 0;
}

.lp-foot .mark {
  width: 20px;
  height: 20px;
  filter: none;
}

/* ---------- reveal on scroll ----------
   Default is VISIBLE. app.js adds .reveal-armed to the landing view once its
   observer is running, and only then do these start hidden — so a page with no
   JavaScript, or one where the observer never fires, still shows every word. */

.reveal-armed .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

.reveal-armed .reveal.shown {
  opacity: 1;
  transform: none;
}

/* ---------- the way back from the login card ---------- */

.gate-back {
  margin: 14px 0 0;
  text-align: center;
}

/* ---------- reduced motion ----------
   Everything decorative stops. The blobs stay where they are (they are the
   palette, not the effect), the notifications are simply present, and the
   reveal becomes a no-op rather than a fade. */

@media (prefers-reduced-motion: reduce) {
  .blob {
    animation: none;
  }

  .fluid {
    transition: none;
  }

  .lp-notif {
    animation: none;
    opacity: 1;
    transform: none;
  }

  .lp-card:hover,
  .lp-chips li:hover {
    transform: none;
  }

  .reveal-armed .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
