/* ────────────────────────────────────────────────────────────────────────
   Banne — the agent app shell. Agent-centric: the agent owns the screen; the
   conversation is a secondary transcript (a quiet subtitle strip) that shows
   what is being said right now. The page never scrolls; the transcript does.
   Tokens only (design/tokens.css); no raw color, radius, or spacing.
   ──────────────────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }

/* `display` in a class beats the `hidden` attribute, so it must win. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  overflow: hidden; /* the shell is exactly one viewport */
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-brand);
  -webkit-font-smoothing: antialiased;
}

.app {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  max-width: 760px;
  margin: 0 auto;
  /* iOS SAFE AREAS. The viewport meta declares `viewport-fit=cover`, which lets the page
     use the whole screen — and is only safe if the notch and the home indicator are padded
     for. Declaring the meta without these insets is exactly how the primary button ends up
     under the home indicator, where taps reach the system gesture instead.
     env() falls back to 0 everywhere else, so nothing changes off-device. */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  padding: calc(var(--s-16) + var(--safe-t)) calc(var(--s-24) + var(--safe-r))
           calc(var(--s-16) + var(--safe-b)) calc(var(--s-24) + var(--safe-l));
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: var(--s-12);
}

/* ── icons ─────────────────────────────────────────────────────────────── */
.icon {
  width: 1.15em;
  height: 1.15em;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ── header ────────────────────────────────────────────────────────────── */
.app__header {
  display: flex;
  align-items: center;
  gap: var(--s-16);
  padding-block: var(--s-8);
  border-bottom: 1px solid var(--rule);
}
.brand-lockup { flex: 0 0 auto; }
.agent-id { flex: 1 1 auto; min-width: 0; }
.agent-id__name {
  margin: 0;
  font-size: var(--fs-label-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.agent-id__state {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--s-8);
  font-size: var(--fs-label-md);
  color: var(--ink-muted);
}
.agent-id__state::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--ink-soft);
}
.app[data-session="live"] .agent-id__state::before { background: var(--accent); }
.app[data-session="connecting"] .agent-id__state::before { background: var(--warning); }
.app[data-session="error"] .agent-id__state::before { background: var(--danger); }

.app__header-actions { display: flex; align-items: center; gap: var(--s-4); }
.app__toggle { font: inherit; white-space: nowrap; }
.app__toggle[aria-pressed="true"] .app__toggle-off,
.app__toggle[aria-pressed="false"] .app__toggle-on { display: none; }

.btn-icon { padding: var(--s-8); min-width: 44px; min-height: 44px; }
.btn-icon .icon { width: var(--s-24); height: var(--s-24); }
#theme .icon--sun { display: none; }
#theme[aria-pressed="true"] .icon--moon { display: none; }
#theme[aria-pressed="true"] .icon--sun { display: block; }

/* ── workspace: the agent owns the space, the transcript is secondary ──── */
.app__main {
  min-height: 0; /* lets the grid item shrink, so the page never scrolls */
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto; /* agent grows, transcript caps */
  gap: var(--s-8);
}

/* ── the agent — the principal actor ──────────────────────────────────── */
.presence {
  /* The CAP on the presence box — not its size. The box is sized from its grid
     ROW (see .singularity); a viewport-based size does not shrink when the row
     does, so on short screens it overflowed and collided with the transcript. */
  --shape-size: min(57vmin, 416px);
  min-height: 0;
  display: grid;
  /* shape | name | status — the shape takes the leftover space and can shrink,
     which `place-content: center` prevented. */
  grid-template-rows: minmax(0, 1fr) auto auto;
  justify-items: center;
  gap: var(--s-8);
}
.presence__name {
  margin: 0;
  font-size: var(--fs-headline-lg);
  font-weight: 600;
  letter-spacing: -0.02em;
}
.presence__state {
  margin: 0;
  min-height: 1.25em;
  font-size: var(--fs-body-sm);
  color: var(--ink-muted);
}

/* ── the transcript — quiet, capped, the only scroller ────────────────── */
.panel {
  position: relative; /* anchors the floating jump button */
  min-height: 0;
  display: grid;
  grid-template-rows: minmax(0, 1fr); /* the jump button no longer takes a row */
}

.conversation {
  min-height: 0;
  /* FIXED height, not max-height. `.panel` sits in an `auto` row of .app__main,
     so a content-driven transcript grows the panel and steals height from
     Banne's 1fr row — while --shape-size is a fixed vmin size that does not
     shrink with it. The shape then overflowed 82px up into the header and down
     into the conversation. Reserving the space keeps both sizes constant. */
  height: min(30vh, 240px);
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  /* extra bottom room so the floating "Nuevos mensajes" pill never sits on top
     of the last line when the transcript is scrolled to the end */
  padding: var(--s-4);
  padding-bottom: calc(var(--s-4) + 40px);
  border-radius: var(--r-md);
}
.conversation:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.conversation__log {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
}

.conversation__empty {
  margin: auto;
  align-self: center;
  max-width: 46ch;
  padding: var(--s-4);
  text-align: center;
  color: var(--ink-soft);
  font-size: var(--fs-body-sm);
  line-height: 1.55;
}
.conversation__empty strong { color: var(--ink-muted); }

.conversation__jump {
  /* Floats over the transcript instead of sitting in the grid, so showing it
     cannot change the panel's height — and therefore cannot resize Banne. */
  position: absolute;
  left: 50%;
  bottom: var(--s-8);
  transform: translateX(-50%);
  z-index: 2;
  appearance: none;
  border: 1px solid var(--rule);
  background: var(--surface);
  color: var(--primary);
  font: inherit;
  font-size: var(--fs-label-sm);
  font-weight: 600;
  padding: var(--s-4) var(--s-12);
  border-radius: var(--r-md);
  box-shadow: var(--glow);
  cursor: pointer;
}
.conversation__jump:hover { border-color: var(--primary); }

/* ── messages: plain lines, the last one is the focus ─────────────────── */
.msg {
  display: grid;
  gap: var(--s-2);
  max-width: 62ch;
  opacity: 0.4;
  transition: opacity var(--t-base) var(--ease);
}
.msg:last-child { opacity: 1; }

.msg--agent { align-self: flex-start; }
.msg--user { align-self: flex-end; text-align: right; }
.msg--system { align-self: center; text-align: center; }

.msg__who {
  font-size: var(--fs-label-sm);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.msg__text {
  margin: 0;
  font-size: var(--fs-body-md);
  line-height: 1.5;
}
.msg--agent .msg__text { color: var(--ink); }
.msg--user .msg__text { color: var(--ink-muted); }
.msg--system .msg__text { color: var(--ink-soft); font-size: var(--fs-body-sm); }
.msg[data-final="false"] .msg__text { opacity: 0.7; }

.msg__artifact {
  justify-self: start;
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--primary);
  font: inherit;
  font-size: var(--fs-label-md);
  font-weight: 600;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.msg--user .msg__artifact { justify-self: end; }

/* ── footer: controls + full session metadata ──────────────────────────── */
.app__footer {
  display: flex;
  align-items: center;
  gap: var(--s-12) var(--s-24);
  flex-wrap: wrap;
  padding-block: var(--s-12);
  border-top: 1px solid var(--rule);
}
.app__controls { display: flex; gap: var(--s-8); flex-wrap: wrap; }
/* Short/long pairs, swapped on narrow screens. Both forms live in the DOM, so the copy is
   in one place each and nothing is injected by CSS. */
.btn__label--short, .greet--short { display: none; }
.btn-primary .icon--stop { display: none; }
.app[data-session="live"] .btn-primary .icon--mic { display: none; }
.app[data-session="live"] .btn-primary .icon--stop { display: block; }

.session-meta {
  display: flex;
  gap: var(--s-24);
  margin: 0 0 0 auto;
}
.session-meta__item { display: grid; gap: var(--s-2); }
.session-meta dt {
  font-size: var(--fs-label-sm);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.session-meta dd {
  margin: 0;
  font-size: var(--fs-body-md);
  font-weight: 600;
}

.disclaimer { flex-basis: 100%; margin: 0; }

/* ── artifact — a sheet over the shell, not a layout row ───────────────── */
.artifact {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  padding: var(--s-24);
}
.artifact::before {
  content: "";
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--ink) 38%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.artifact__panel {
  position: relative;
  width: min(100%, 760px);
  max-height: 88dvh;
  overflow: auto;
  display: grid;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--glow);
}
.artifact__head {
  display: grid;
  gap: var(--s-4);
  padding: var(--s-16) var(--s-24) var(--s-8);
}
.artifact__kind {
  justify-self: start;
  font-size: var(--fs-label-sm);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--primary);
  background: var(--primary-tint);
  padding: var(--s-2) var(--s-8);
  border-radius: var(--r-sm);
}
.artifact__title { margin: 0; font-size: var(--fs-headline-md); }
.artifact__meta { margin: 0; font-size: var(--fs-label-md); color: var(--ink-muted); }
.artifact__preview {
  width: 100%;
  height: min(52vh, 460px);
  border: 0;
  border-block: 1px solid var(--rule);
  background: var(--canvas);
}
.artifact__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-8);
  padding: var(--s-12) var(--s-24) var(--s-16);
}

/* ── error ─────────────────────────────────────────────────────────────── */
.app__error {
  position: fixed;
  inset-inline: var(--s-24);
  inset-block-end: var(--s-24);
  z-index: 20;
  margin: 0;
  padding: var(--s-12) var(--s-16);
  border-radius: var(--r-md);
  border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--danger) 8%, var(--surface));
  color: var(--ink);
  font-size: var(--fs-body-sm);
}

/* ── mobile ────────────────────────────────────────────────────────────────
   MOBILE IS A REQUIREMENT, NOT A SHRINK-TO-FIT, AND THIS IS THE PORTRAIT LAYOUT.

   Scoped to `orientation: portrait` deliberately. A phone in LANDSCAPE is 844x390: the
   width test alone passes nothing there, but the vh-based sizing would collapse (34vh of
   390px is 132px), so landscape keeps the wide layout instead of a squeezed tall one.

   Measured on device viewports before this block was written:

     iPhone 12  (390x664)   presence  62 x  50 px
     Pixel 5    (393x727)   presence 121 x  97 px
     iPhone SE  (320x568)   presence   0 x   0 px   <- Banne was GONE entirely

   On the SE the chrome ate 554 of 568px (header 173 + transcript 148 + footer 213), which
   collapsed the 1fr row. Three causes: the header repeated the agent name and state that
   .presence already shows AND wrapped to three rows to do it; .presence had a CAP but no
   FLOOR; and the footer wrapped its two buttons onto separate full-width lines.
   ------------------------------------------------------------------------- */
@media (max-width: 560px) and (orientation: portrait) {
  .app {
    padding: calc(var(--s-12) + var(--safe-t)) calc(var(--s-12) + var(--safe-r))
             calc(var(--s-12) + var(--safe-b)) calc(var(--s-12) + var(--safe-l));
    gap: var(--s-8);
  }

  /* One row. The name and state are NOT lost — `.presence` shows them, larger, right
     below, and still carries role="status" aria-live. */
  .app__header { flex-wrap: nowrap; padding-block: var(--s-4); }
  .app__header .agent-id { display: none; }
  .app__header-actions { margin-left: auto; }

  /* THE FLOOR, not just the cap. `--shape-size` sizes the shape; `min-height` on the ROW
     is what stops the surrounding chrome starving it to zero. The cap alone still
     collapsed, which is exactly what the measurements showed. */
  .presence {
    min-height: min(34vh, 260px);
    --shape-size: min(34vh, 260px);
    gap: var(--s-4);
  }
  .presence__name { font-size: var(--fs-headline-md); }

  /* 132px was not enough: the greeting is 116px on the SE and was clipped by 73px, so the
     first sentence a new user reads was cut off mid-sentence. */
  .conversation { height: min(23vh, 160px); }

  /* The destructive action carries the long label, so it shortens here. The button keeps
     its icon and an explicit aria-label, so nothing is lost to a screen reader. */
  .btn__label--long { display: none; }
  .btn__label--short { display: inline; }
  #clear { min-width: 44px; }

  /* At this width the capability list costs more lines than it earns; who she is and what
     to do is the part that has to survive. */
  .greet--long { display: none; }
  .greet--short { display: inline; }

  /* THE FOOTER IS TWO FULL-WIDTH ROWS: controls, then status.
     Side by side, the buttons and the stats competed for one narrow line and the buttons
     wrapped onto two rows of their own, which cost 213px on the SE — more than Banne's
     entire row. Stacking them deliberately is both shorter and clearer. */
  .app__footer {
    flex-direction: column;
    /* `nowrap` is load-bearing, not tidiness. The base rule sets `flex-wrap: wrap`, and in
       COLUMN direction wrap flows into extra COLUMNS — so with wrap left on, the disclaimer
       was pushed out to the right of the buttons and clipped at the edge instead of becoming
       a third row. */
    flex-wrap: nowrap;
    align-items: stretch;
    gap: var(--s-8);
    padding-block: var(--s-8);
  }
  /* Every child spans the row; nothing hugs its content. */
  .app__footer > * { width: 100%; }
  .app__controls { width: 100%; gap: var(--s-8); }
  /* fill the row: each button takes an equal share instead of hugging its label */
  .app__controls .btn { flex: 1 1 0; justify-content: center; }
  .session-meta {
    width: 100%;
    margin-left: 0;
    gap: var(--s-8);
    justify-content: space-between;
  }

  .disclaimer { font-size: var(--fs-label-sm); line-height: 1.4; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 1ms !important; animation-duration: 1ms !important; }
}
