/* Weaver - modelled on Gemini's actual layout.
   View switching is driven by body[data-view], never the hidden attribute:
   class rules set display:flex, which outranks [hidden]{display:none}, and
   that mismatch is what left the chat composer stranded on the dashboard. */

:root {
  --bg: #131314;
  --surface-1: #1b1c1d;
  --surface-2: #1e1f20;
  --surface-3: #282a2c;
  --surface-4: #333537;

  --ink: #e3e3e3;
  --ink-secondary: #c4c7c5;
  --ink-muted: #9aa0a6;
  --ink-faint: #7b8084;

  --line: rgba(255, 255, 255, 0.09);
  --line-strong: rgba(255, 255, 255, 0.16);

  --blue: #8ab4f8;
  --red: #f28b82;
  --yellow: #fdd663;
  --green: #81c995;

  --blue-solid: #4285f4;
  --red-solid: #ea4335;
  --yellow-solid: #fbbc05;
  --green-solid: #34a853;

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-pill: 999px;
  /* Chat bubbles are rounded rectangles, never pills. A pill radius is half
     the height, so the moment a message wraps to three or four lines the
     bubble stops being a rounded rectangle and becomes an ellipse. The
     composer keeps its pill; a message does not. */
  --r-bubble: 20px;

  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s8: 32px; --s12: 48px;

  --ease: cubic-bezier(0.23, 1, 0.32, 1);

  /* ---- motion ----
     One scale, so timing is a decision made once rather than per rule.
     Under ~100ms reads as a jump, over ~500ms as sluggish, and almost
     everything in a UI belongs in the 150-300ms band.

     Entering uses ease-out (fast start, gentle landing, the shape of
     something arriving); leaving uses ease-in, which gets out of the way.
     Only transform and opacity are animated - both are composited, so they
     cost nothing, while animating height or top forces layout on every
     frame. */
  --t-fast: 120ms;   /* state flips: hover, colour, small nudges */
  --t-med: 200ms;    /* the default: entrances, panels, tiles */
  --t-slow: 320ms;   /* view changes, where a beat aids orientation */
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --lift: 0 8px 24px rgba(0, 0, 0, 0.32);
  --sidebar-w: 264px;
  --rail-w: 300px;
  --composer-max: 720px;
}

/* ---- access gate ----
   Covers the app entirely on the hosted copy until a session exists. The
   proxy holds GCP credentials, so an ungated URL would let anyone drive the
   agents and spend the project's Vertex AI quota. */
.gate {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: var(--bg);
}
.gate-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--s3);
  width: 100%; max-width: 380px;
  padding: var(--s8);
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
}
.gate-title {
  margin: 0;
  font-size: 22px; font-weight: 500; letter-spacing: -0.01em;
}
.gate-blurb {
  margin: 0;
  font-size: 13px; line-height: 1.6;
  color: var(--ink-muted);
}
.gate-input {
  width: 100%;
  margin-top: var(--s2);
  padding: var(--s3) var(--s4);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  color: var(--ink);
  font: inherit; font-size: 14px;
}
.gate-input::placeholder { color: var(--ink-faint); }
.gate-input:focus { outline: none; border-color: var(--blue-solid); }
.gate-btn { width: 100%; text-align: center; }
.gate-error { font-size: 12px; color: var(--red); }
.gate-error[hidden] { display: none; }

/* Motion is an enhancement, never load-bearing. Anyone who has asked their
   system for less of it gets the same interface with the movement removed:
   nothing here conveys meaning that the static state does not. Kept to a
   single rule at the top rather than a guard on every animation, so it
   cannot be forgotten later. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

* { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Google Sans Text", "Roboto", -apple-system, "Segoe UI", sans-serif;
  font-size: 14px;
  -webkit-font-smoothing: antialiased;
  display: flex;
  height: 100vh;
  overflow: hidden;
}

button { font-family: inherit; }

/* ---- view switching: exactly one view is ever mounted ---- */
.home, .chat, .browse, .activity-view, .rail { display: none; }
body[data-view="home"] .home { display: flex; }
body[data-view="chat"] .chat { display: flex; }
body[data-view="chat"] .rail { display: flex; }
body[data-view="browse"] .browse { display: flex; }
body[data-view="activity"] .activity-view { display: flex; }

/* A view arrives by rising slightly into place. Short, and transform and
   opacity only, so it composites rather than reflowing the whole page. */
.home, .chat, .browse, .activity-view { animation: view-in var(--t-slow) var(--ease-out); }
@keyframes view-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ---- sheet visibility ---- */
.sheet, .sheet-backdrop { display: none; }
body.sheet-open .sheet,
body.sheet-open .sheet-backdrop { display: flex; }

/* Small round affordances (send, close, refresh); everything else is a pill. */
.icon-btn {
  background: none;
  border: none;
  color: var(--ink-muted);
  cursor: pointer;
  width: 40px; height: 40px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  transition: background 120ms var(--ease), color 120ms var(--ease);
}
.icon-btn:hover { background: var(--surface-3); color: var(--ink); }
.icon-btn:active { transform: scale(0.92); }

/* ================= sidebar ================= */

.sidebar {
  flex: 0 0 var(--sidebar-w);
  width: var(--sidebar-w);
  background: var(--surface-1);
  display: flex; flex-direction: column;
  padding: var(--s4) var(--s3);
  gap: var(--s4);
}

.side-head { display: flex; align-items: center; gap: var(--s2); padding: var(--s2) var(--s2) 0; }
.brand { font-size: 19px; font-weight: 500; letter-spacing: -0.01em; }

.side-primary {
  display: flex; align-items: center; gap: var(--s2);
  background: var(--surface-3);
  color: var(--ink);
  border: none;
  border-radius: var(--r-pill);
  padding: var(--s3) var(--s5);
  font-size: 14px;
  cursor: pointer;
  transition: background 140ms var(--ease);
}
.side-primary:hover { background: var(--surface-4); }
.side-primary:active { transform: scale(0.985); }

.side-section { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.side-label { font-size: 12px; color: var(--ink-muted); padding: var(--s2) var(--s4); }
.side-list { overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.side-empty { font-size: 13px; color: var(--ink-faint); padding: var(--s2) var(--s4); }

.side-item {
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  color: var(--ink-secondary);
  font-size: 13px;
  padding: var(--s3) var(--s4);
  border-radius: var(--r-pill);
  cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: background 120ms var(--ease);
}
.side-item:hover { background: var(--surface-3); color: var(--ink); }
.side-item.active { background: var(--surface-3); color: var(--ink); }

/* ---- sidebar nav ---- */
.side-nav { display: flex; flex-direction: column; gap: 2px; }

.nav-item {
  display: flex; align-items: center; gap: var(--s3);
  width: 100%;
  padding: var(--s2) var(--s4);
  background: none; border: none;
  border-radius: var(--r-pill);
  color: var(--ink-secondary);
  font-size: 13px; text-align: left;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out);
}
.nav-item:hover { background: var(--surface-3); color: var(--ink); }
.nav-item.active { background: var(--surface-3); color: var(--ink); }
.nav-item svg { flex: 0 0 auto; }
.nav-count {
  margin-left: auto;
  font-size: 11px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* ---- browse + activity views ---- */
.browse, .activity-view {
  flex: 1 1 auto; min-height: 0;
  flex-direction: column;
  overflow-y: auto;
}
.browse-inner {
  width: 100%; max-width: 1100px;
  margin: 0 auto;
  padding: var(--s8) var(--s6) var(--s12);
}
.view-note {
  font-size: 12px; color: var(--ink-faint);
  margin: 0 0 var(--s5);
}

.browse-filter {
  width: 100%;
  margin-bottom: var(--s5);
  padding: var(--s3) var(--s4);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  color: var(--ink);
  font: inherit; font-size: 13px;
  transition: border-color var(--t-fast) var(--ease-out);
}
.browse-filter::placeholder { color: var(--ink-faint); }
.browse-filter:focus { outline: none; border-color: var(--blue-solid); }

.see-all {
  display: block;
  margin: var(--s5) auto 0;
  padding: var(--s2) var(--s5);
  background: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  color: var(--ink-secondary);
  font: inherit; font-size: 13px;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out);
}
.see-all:hover { background: var(--surface-3); color: var(--ink); border-color: var(--ink-faint); }
.see-all[hidden] { display: none; }

/* ---- activity log ---- */
.activity-list { display: flex; flex-direction: column; gap: var(--s2); }

.activity-row {
  display: flex; align-items: center; gap: var(--s4);
  width: 100%;
  padding: var(--s3) var(--s4);
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink);
  font: inherit; text-align: left;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out);
}
.activity-row:hover { background: var(--surface-3); border-color: var(--line-strong); }

.activity-dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; }
.activity-dot.success { background: var(--green-solid); }
.activity-dot.failed { background: var(--red-solid); }

.activity-name {
  flex: 1 1 auto; min-width: 0;
  font-size: 13px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.activity-meta {
  flex: 0 0 auto;
  font-size: 11px; color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* ---- edit ("jiggle") mode ----
   Borrowed from the phone gesture everyone already knows: enter edit mode
   and the things you can remove start shifting, with a remove badge on each.
   The movement is the affordance - it says "these are loose now" without a
   word of instruction. Kept to under a degree, because the point is unease,
   not a dance. */
@keyframes jiggle {
  0%, 100% { transform: rotate(-0.55deg); }
  50%      { transform: rotate(0.55deg); }
}
body.editing .tile-grid .tile {
  animation: jiggle 220ms ease-in-out infinite;
}
/* Desynchronise the phases, or the grid rocks as one slab. */
body.editing .tile-grid .tile:nth-child(2n)  { animation-delay: -70ms; }
body.editing .tile-grid .tile:nth-child(3n)  { animation-delay: -140ms; }
body.editing .tile-grid .tile { cursor: default; }

.tile-remove {
  position: absolute;
  top: -8px; left: -8px;
  width: 24px; height: 24px;
  display: none;
  align-items: center; justify-content: center;
  background: var(--red-solid);
  color: #fff;
  border: 2px solid var(--bg);
  border-radius: 50%;
  cursor: pointer;
  font-size: 15px; line-height: 1;
  padding: 0;
  z-index: 2;
  animation: pop-in var(--t-med) var(--ease-out);
}
body.editing .tile-remove { display: flex; }
@keyframes pop-in {
  from { opacity: 0; transform: scale(0.6); }
  to   { opacity: 1; transform: none; }
}
.tile-remove:hover { background: #f6685e; }

/* A pencil, not three dots. A meatball menu promises a menu; this opens a
   mode, and the pencil says which. It sits next to refresh as a matching
   icon button, and swaps for a tick while the mode is on - the tick is blue
   because it is the way out of a state, not another way in. */
.edit-done { display: none; color: var(--blue); }
.edit-done:hover { color: var(--blue); }
body.editing .edit-done { display: flex; }
body.editing #edit-btn,
body.editing #browse-edit { display: none; }

/* The gear and the remove badge are two routes to the same place, so only
   one shows at a time: considered by default, quick while editing. */
body.editing .tile-gear { display: none; }
body.editing .edit-done { display: inline-block; }
body.editing #edit-btn { display: none; }

/* ---- toast ----
   Bottom centre, above the mobile tab bar, out of the way of both the
   sidebar and the run panel. */
.toast {
  position: fixed;
  left: 50%; bottom: 24px;
  transform: translateX(-50%);
  z-index: 120;
  max-width: min(90vw, 420px);
  padding: var(--s3) var(--s5);
  background: var(--surface-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  box-shadow: var(--lift);
  color: var(--ink);
  font-size: 13px;
  animation: toast-in var(--t-med) var(--ease-out);
}
@keyframes toast-in {
  from { opacity: 0; transform: translate(-50%, 12px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.toast-leaving {
  opacity: 0;
  transform: translate(-50%, 8px);
  transition: opacity var(--t-med) var(--ease-in),
              transform var(--t-med) var(--ease-in);
}
@media (max-width: 860px) { .toast { bottom: 76px; } }

/* ---- automation settings popup ---- */
.modal-backdrop {
  position: fixed; inset: 0;
  z-index: 90;
  background: rgba(0, 0, 0, 0.55);
  display: flex; align-items: center; justify-content: center;
  padding: var(--s5);
  animation: fade-in var(--t-med) var(--ease-out);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.modal {
  width: 100%; max-width: 420px;
  padding: var(--s6);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--lift);
  animation: modal-in var(--t-med) var(--ease-out);
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(12px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
.modal h3 { margin: 0 0 var(--s2); font-size: 16px; font-weight: 500; }
.modal-sub {
  margin: 0 0 var(--s5);
  font-size: 12px; line-height: 1.5; color: var(--ink-muted);
}
.modal-label {
  display: block;
  font-size: 12px; color: var(--ink-secondary);
  margin-bottom: var(--s2);
}
.modal-input {
  width: 100%;
  padding: var(--s3) var(--s4);
  background: var(--surface-1);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  color: var(--ink);
  font: inherit; font-size: 14px;
}
.modal-input:focus { outline: none; border-color: var(--blue-solid); }
.modal-actions {
  display: flex; align-items: center; gap: var(--s2);
  margin-top: var(--s6);
}
.modal-actions .spacer { flex: 1 1 auto; }
.modal-danger {
  background: none;
  border: 1px solid rgba(234, 67, 53, 0.5);
  color: var(--red);
}
.modal-danger:hover { background: rgba(234, 67, 53, 0.12); border-color: var(--red-solid); color: var(--red); }
.modal-error { margin-top: var(--s3); font-size: 12px; color: var(--red); }
.modal-error[hidden] { display: none; }

/* ---- where a run actually landed ----
   DEMO ONLY: a judge reading a run should be able to go and look at the
   message rather than take "posted to Slack" on faith. A real product would
   deep-link to the message, not hand out a join link to a demo server. */
.run-open {
  display: inline-flex; align-items: center; gap: var(--s2);
  margin-top: var(--s3);
  padding: var(--s2) var(--s4);
  background: var(--surface-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  color: var(--ink);
  font-size: 12px; text-decoration: none;
  transition: background var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.run-open:hover { background: var(--surface-4); transform: translateY(-1px); }

/* ---- join links ----
   Sits above the task notification, below the conversation list, so it reads
   as part of the sidebar's furniture rather than as a call to action. Brand
   colours live in the marks alone; the labels stay in the app's own ink, or
   two logos would fight the four-colour composer ring for attention. */
/* No margin-top:auto needed - .side-section above already grows to fill. */
.side-join { padding: var(--s3) var(--s4) var(--s2); }
.side-join .side-label { margin-bottom: var(--s2); }

.join-link {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s3);
  border-radius: var(--r-pill);
  color: var(--ink-secondary);
  font-size: 13px;
  text-decoration: none;
  transition: background 120ms var(--ease), color 120ms var(--ease);
}
.join-link:hover { background: var(--surface-3); color: var(--ink); }
.join-link svg { flex: 0 0 auto; display: block; }

/* ---- background task notification ----
   Only appears while something is actually running. A run lives on the
   server and continues whether or not this is open, so this is peripheral
   awareness rather than a dialog: expanded it names what is running,
   collapsed it is just the spinning ring, which is the smallest honest way
   to say "still working". The collapsed choice is remembered per browser. */
.side-foot {
  display: block;
  padding: var(--s3) var(--s4);
  border-top: 1px solid var(--line);
}
.side-foot[hidden] { display: none; }

.task-note {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: var(--s3);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 120ms var(--ease), border-color 120ms var(--ease);
}
.task-note:hover { background: var(--surface-3); border-color: var(--line-strong); }
.task-note:focus-visible { outline: 2px solid var(--blue-solid); outline-offset: 2px; }

/* Collapsed: the ring alone. No card edge to compete with the circle. */
.side-foot.collapsed .task-note {
  width: 40px; height: 40px;
  padding: 0;
  gap: 0;
  border-radius: 50%;
  justify-content: center;
}
.side-foot.collapsed .task-text,
.side-foot.collapsed .task-chevron { display: none; }

/* The four Google colours swept once around, masked to an annulus so it
   reads as a loading ring and not a filled pie chart. Plain radial-gradient
   mask, deliberately not mask-composite, which rendered as a broken
   diagonal in this same stylesheet once already. */
.task-ring {
  flex: 0 0 auto;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    var(--blue-solid), var(--red-solid),
    var(--yellow-solid), var(--green-solid), var(--blue-solid));
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  animation: task-spin 1.1s linear infinite;
}
@keyframes task-spin { to { transform: rotate(360deg); } }

.task-text { display: block; flex: 1 1 auto; min-width: 0; }
.task-title {
  display: block;
  font-size: 12px; font-weight: 500; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.task-sub { display: block; font-size: 11px; color: var(--ink-faint); margin-top: 2px; }
.task-chevron { flex: 0 0 auto; color: var(--ink-faint); font-size: 10px; }

@media (prefers-reduced-motion: reduce) {
  .task-ring { animation-duration: 4s; }
}

/* ================= main ================= */

.main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }

/* ---- home ---- */

.home { flex: 1 1 auto; overflow-y: auto; flex-direction: column; padding: 0 var(--s8) var(--s12); }

.home-inner {
  width: 100%;
  max-width: var(--composer-max);
  margin: 0 auto;
  padding-top: 16vh;
  text-align: center;
}

.greeting {
  font-size: 44px;
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: 0 0 var(--s8);
  background: linear-gradient(92deg, var(--blue-solid), var(--red-solid) 42%, var(--yellow-solid) 68%, var(--green-solid));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-wrap: balance;
}

.grid-section { margin-top: var(--s12); text-align: left; }
/* space-between spread three children evenly, which parked the edit button
   in the middle of the row with nothing to do with it. The heading takes the
   slack instead, so the controls sit together as one group on the right. */
.grid-head { display: flex; align-items: center; gap: var(--s1); margin-bottom: var(--s4); }
.grid-head h2 {
  font-size: 13px; font-weight: 500; color: var(--ink-muted);
  margin: 0 auto 0 0;
}

.tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: var(--s3); }
.tiles-empty { grid-column: 1 / -1; color: var(--ink-faint); font-size: 13px; padding: var(--s8) 0; text-align: center; }

/* ---- composer: a true pill ---- */

.composer {
  display: flex;
  /* Centre, not flex-end: the textarea's line box is taller than the 40px
     button, so bottom-aligning left the button visibly low on a single-line
     composer. Centring reads correctly at one line and stays sensible as the
     textarea grows. */
  align-items: center;
  gap: var(--s2);
  width: 100%;
  border: 2px solid transparent;
  /* A pill while it holds one line, a rounded rectangle once it wraps.
     A 999px radius is half the height, so at one line it draws clean
     semicircular ends - and the moment the box grows past two lines those
     same ends bow outward into a lozenge. Switching at the wrap keeps the
     softness at rest and the straight sides when there is text to read.
     The class is set in autoResize, which already knows the height. */
  border-radius: var(--r-pill);
  padding: var(--s2) var(--s2) var(--s2) var(--s5);
  text-align: left;
  /* The four-colour ring is always on. It was focus-only, which meant the
     one piece of brand colour on the page was invisible until you clicked -
     so the first thing a visitor saw was a grey box, and the signature
     element only existed for people already committed to typing. Now it
     identifies the composer on arrival, and focus deepens it rather than
     introducing it. */
  background:
    linear-gradient(var(--surface-3), var(--surface-3)) padding-box,
    conic-gradient(from 0deg,
      var(--blue-solid), var(--red-solid),
      var(--yellow-solid), var(--green-solid), var(--blue-solid)) border-box;
  opacity: 1;
  transition: box-shadow var(--t-med) var(--ease-out),
              transform var(--t-med) var(--ease-out),
              border-radius var(--t-med) var(--ease-out);
}

.composer.multiline { border-radius: 26px; }

/* Focus is now a change in intensity, not in kind: the same ring, lifted. */
.composer:focus-within {
  box-shadow: 0 0 0 4px rgba(66, 133, 244, 0.10), 0 8px 24px rgba(0, 0, 0, 0.30);
}

.composer-input {
  flex: 1 1 auto;
  border: none;
  background: transparent;
  resize: none;
  font-family: inherit;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  padding: var(--s3) 0;
  max-height: 200px;
}
.composer-input::placeholder { color: var(--ink-faint); }
.composer-input:focus { outline: none; }

.send-btn {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--blue-solid);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 140ms var(--ease), transform 140ms var(--ease), opacity 140ms var(--ease);
}
.send-btn:hover:not(:disabled) { background: #5a95f5; }
.send-btn:active:not(:disabled) { transform: scale(0.94); }
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* ---- chat ---- */

.chat { flex: 1 1 auto; flex-direction: column; min-height: 0; }
.chat-scroll { flex: 1 1 auto; overflow-y: auto; padding: var(--s8) var(--s6) var(--s4); }

.message-list {
  max-width: var(--composer-max);
  margin: 0 auto;
  display: flex; flex-direction: column;
  gap: var(--s6);
}

/* Gemini shape: the user's turn is a pill bubble on the right; the
   assistant's is plain text on the page, no avatar, no container. */
.message { display: flex; flex-direction: column; }
.message.from-user { align-items: flex-end; }
.message.from-weaver { align-items: flex-start; }

.message.from-user .bubble {
  background: var(--surface-3);
  border-radius: var(--r-bubble);
  padding: var(--s3) var(--s5);
  max-width: 76%;
  font-size: 15px;
  line-height: 1.5;
  white-space: pre-wrap;
}

.message.from-weaver .bubble {
  font-size: 15px;
  line-height: 1.7;
  color: var(--ink);
  max-width: 100%;
  text-wrap: pretty;
}

.message.error .bubble { color: var(--red); }

.bubble p { margin: 0 0 var(--s3); }
.bubble p:last-child { margin-bottom: 0; }
.bubble strong { font-weight: 500; color: #fff; }
.bubble em { font-style: italic; }
.bubble s { color: var(--ink-muted); }
.bubble ul, .bubble ol { margin: 0 0 var(--s3); padding-left: var(--s6); }
.bubble ul:last-child, .bubble ol:last-child { margin-bottom: 0; }
.bubble li { margin-bottom: var(--s1); }
.bubble h3, .bubble h4, .bubble h5, .bubble h6 { font-size: 15px; font-weight: 500; margin: var(--s4) 0 var(--s2); color: #fff; }
.bubble h3:first-child, .bubble h4:first-child { margin-top: 0; }
.bubble code { font-family: "Roboto Mono", ui-monospace, monospace; font-size: 13px; background: var(--surface-3); padding: 2px 6px; border-radius: var(--r-sm); }
/* A bullet whose first line is a headline and whose following lines are
   context: stack them so the title leads and the detail reads as a subline. */
.bubble li { margin-bottom: var(--s3); }
.li-head { display: block; font-weight: 500; color: #fff; line-height: 1.45; }
.li-body { display: block; color: var(--ink-muted); line-height: 1.6; margin-top: 3px; }
.bubble ul, .bubble ol { padding-left: var(--s5); }
.bubble li::marker { color: var(--ink-faint); }

.bubble a { color: var(--blue); text-decoration: none; }
.bubble a:hover { text-decoration: underline; }

.chat-foot {
  flex: 0 0 auto;
  width: 100%;
  max-width: var(--composer-max);
  margin: 0 auto;
  padding: var(--s3) var(--s6) var(--s5);
}
.foot-hint { text-align: center; font-size: 11px; color: var(--ink-faint); margin-top: var(--s2); }

/* thinking - four-colour cascade */
.thinking-dots { display: flex; align-items: center; gap: 5px; height: 24px; }
.thinking-dots i { width: 7px; height: 7px; border-radius: 50%; display: block; animation: bounce 1.3s infinite var(--ease); }
.thinking-dots i:nth-child(1) { background: var(--blue-solid); animation-delay: 0s; }
.thinking-dots i:nth-child(2) { background: var(--red-solid); animation-delay: 0.15s; }
.thinking-dots i:nth-child(3) { background: var(--yellow-solid); animation-delay: 0.3s; }
.thinking-dots i:nth-child(4) { background: var(--green-solid); animation-delay: 0.45s; }
@keyframes bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
  30% { transform: translateY(-6px); opacity: 1; }
}

/* ================= right rail ================= */

.rail {
  flex: 0 0 var(--rail-w);
  width: var(--rail-w);
  background: var(--surface-1);
  border-left: 1px solid var(--line);
  flex-direction: column;
  padding: var(--s4) var(--s3);
}
.rail-head { display: flex; align-items: center; justify-content: space-between; padding: var(--s2) var(--s3) var(--s3); }
.rail-head h2 { font-size: 13px; font-weight: 500; color: var(--ink-muted); margin: 0; }
.rail-list { flex: 1 1 auto; overflow-y: auto; display: flex; flex-direction: column; gap: var(--s2); }

/* ================= tiles ================= */

.tile {
  position: relative;   /* the remove badge and the gear anchor to this */
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s4);
  cursor: pointer;
  display: flex; flex-direction: column; gap: var(--s2);
  color: var(--ink);
  transition: background var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out),
              box-shadow var(--t-fast) var(--ease-out);
}
/* A small lift on hover: enough to read as raised, not enough to shove the
   grid around. */
.tile:hover:not(:disabled) {
  background: var(--surface-3);
  border-color: var(--line-strong);
  transform: translateY(-2px);
  box-shadow: var(--lift);
}
.tile:active:not(:disabled) { transform: translateY(0) scale(0.99); }
.tile:disabled { cursor: not-allowed; opacity: 0.75; }

/* Tiles arrive staggered rather than all at once, which makes a grid of
   thirty read as a list being laid down instead of a slab appearing. Capped
   at eight, or the last tiles would visibly lag. */
.tile-grid .tile { animation: tile-in var(--t-med) var(--ease-out) backwards; }
@keyframes tile-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.tile-grid .tile:nth-child(1) { animation-delay: 0ms; }
.tile-grid .tile:nth-child(2) { animation-delay: 25ms; }
.tile-grid .tile:nth-child(3) { animation-delay: 50ms; }
.tile-grid .tile:nth-child(4) { animation-delay: 75ms; }
.tile-grid .tile:nth-child(5) { animation-delay: 100ms; }
.tile-grid .tile:nth-child(6) { animation-delay: 125ms; }
.tile-grid .tile:nth-child(n+7) { animation-delay: 150ms; }
/* Editing overrides the entrance, or the two animations fight. */
body.editing .tile-grid .tile { animation: jiggle 220ms ease-in-out infinite; }

/* Deleting collapses the tile rather than blinking it out, so the grid
   closing up reads as a consequence of what you just did. */
.tile-leaving {
  pointer-events: none;
  opacity: 0;
  transform: scale(0.92);
  transition: opacity var(--t-med) var(--ease-in),
              transform var(--t-med) var(--ease-in);
}

.tile-gear {
  position: absolute;
  top: var(--s3); right: var(--s3);
  width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none;
  border-radius: 50%;
  color: var(--ink-faint);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease-out),
              background var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out);
}
.tile:hover .tile-gear, .tile-gear:focus-visible { opacity: 1; }
.tile-gear:hover { background: var(--surface-4); color: var(--ink); }
/* Touch has no hover, so on a phone it is simply always visible. */
@media (hover: none) { .tile-gear { opacity: 1; } }

.tile-top { display: flex; align-items: center; gap: var(--s2); }
.tile-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-faint); flex: 0 0 auto; }
.tile-dot.success { background: var(--green); }
.tile-dot.failed { background: var(--red); }
.tile-dot.blocked { background: var(--yellow); }
.tile-name { font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile-goal { font-size: 12px; line-height: 1.5; color: var(--ink-muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.tile-foot {
  margin-top: auto;
  display: flex; align-items: center; gap: var(--s2);
  padding-top: var(--s2);
  flex-wrap: wrap;
}
.tile-steps { display: flex; gap: var(--s1); flex-wrap: wrap; }
.step-chip { font-size: 10px; font-weight: 500; color: var(--ink-muted); background: var(--surface-4); border-radius: var(--r-pill); padding: 2px 9px; }
.tile-run { font-size: 12px; font-weight: 500; color: var(--blue); flex: 0 0 auto; }
.tile.is-running .tile-run { color: var(--ink-muted); }
.tile-warn { font-size: 11px; color: var(--yellow); line-height: 1.4; }
.tile-note { font-size: 11px; color: var(--ink-muted); line-height: 1.4; font-style: italic; }

.tile-sched {
  font-size: 11px;
  color: var(--blue);
  font-family: "Roboto Mono", ui-monospace, monospace;
}
.tile-next { font-size: 11px; color: var(--ink-faint); }
.tile-sched-btn {
  font-size: 12px;
  color: var(--ink-muted);
  cursor: pointer;
  margin-left: auto;
  margin-right: var(--s3);
  white-space: nowrap;
}
.tile-sched-btn:hover { color: var(--ink); text-decoration: underline; }

.rail .tile { padding: var(--s3); gap: var(--s1); }
.rail .tile-goal { font-size: 11px; }

/* ================= run sheet ================= */

.sheet-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 10; animation: fade 160ms var(--ease); }

.sheet {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: min(460px, 100vw);
  background: var(--surface-2);
  border-left: 1px solid var(--line);
  z-index: 11;
  flex-direction: column;
  animation: slide 220ms var(--ease);
}
@keyframes fade { from { opacity: 0; } }
@keyframes slide { from { transform: translateX(16px); opacity: 0; } }

.sheet-head { display: flex; align-items: flex-start; gap: var(--s3); padding: var(--s5) var(--s6); border-bottom: 1px solid var(--line); }
.sheet-headings { flex: 1 1 auto; min-width: 0; }
.sheet-title { font-size: 16px; font-weight: 500; }
.sheet-sub { font-size: 12px; color: var(--ink-muted); margin-top: 3px; line-height: 1.5; }

.sheet-body { flex: 1 1 auto; overflow-y: auto; padding: var(--s6); display: flex; flex-direction: column; gap: var(--s4); }

.run-step { border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--s4); background: var(--surface-1); }
.run-step-head { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s2); }
.run-step-id { font-size: 13px; font-weight: 500; }
.run-step-status { font-size: 11px; font-weight: 500; margin-left: auto; }
.run-step-status.success { color: var(--green); }
.run-step-status.failed { color: var(--red); }
.run-step-body { font-size: 13px; line-height: 1.6; color: var(--ink-secondary); white-space: pre-wrap; }
.run-step-error {
  margin-top: var(--s2);
  font-family: "Roboto Mono", ui-monospace, monospace;
  font-size: 11px; line-height: 1.5;
  color: var(--red);
  background: rgba(242,139,130,0.08);
  border-radius: var(--r-sm);
  padding: var(--s2);
  word-break: break-word;
  max-height: 200px; overflow-y: auto;
}
/* ---- schedule editor ----
   Cron is precise and unreadable, so the presets carry the common cases and
   the custom field keeps the expressive ones reachable. Both live in the run
   panel rather than a browser prompt(), which dropped a half-filled form on
   dismissal and blocked the page while open. */
.sched-options { display: flex; flex-direction: column; gap: var(--s2); }

.sched-option {
  display: flex; flex-direction: column; gap: 2px;
  width: 100%;
  padding: var(--s3) var(--s4);
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 120ms var(--ease), border-color 120ms var(--ease);
}
.sched-option:hover { background: var(--surface-3); border-color: var(--line-strong); }
.sched-option.chosen { border-color: var(--blue-solid); background: rgba(66, 133, 244, 0.10); }
.sched-option-label { font-size: 13px; font-weight: 500; }
.sched-option-cron {
  font-family: "Roboto Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--ink-faint);
}

.sched-custom {
  width: 100%;
  margin-top: var(--s3);
  padding: var(--s3);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  color: var(--ink);
  font: inherit; font-size: 13px;
}
.sched-custom::placeholder { color: var(--ink-faint); }
.sched-custom:focus { outline: none; border-color: var(--blue-solid); }

.sched-error {
  margin-top: var(--s3);
  font-size: 12px;
  color: var(--red);
}
.sched-error[hidden] { display: none; }

/* ---- feedback ----
   Asked once a run has finished, because "did it work" and "was it what you
   wanted" are different questions and only the person can answer the second.
   Set apart by a rule above it so it reads as a question from Weaver rather
   than one more result card. */
.feedback {
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}
.feedback-q { font-size: 13px; color: var(--ink-secondary); margin-bottom: var(--s3); }
.feedback-row { display: flex; gap: var(--s2); flex-wrap: wrap; }

.feedback-btn {
  font: inherit; font-size: 13px;
  padding: var(--s2) var(--s4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-secondary);
  cursor: pointer;
  transition: background 120ms var(--ease), color 120ms var(--ease),
              border-color 120ms var(--ease);
}
.feedback-btn:hover {
  background: var(--surface-3);
  color: var(--ink);
  border-color: var(--ink-faint);
}
.feedback-btn:focus-visible { outline: 2px solid var(--blue-solid); outline-offset: 2px; }

/* The note is the part that actually changes what gets built next time, so
   it is a real field in the panel, not a browser prompt() sitting on top of
   the page. */
.feedback-note {
  width: 100%;
  margin-top: var(--s3);
  padding: var(--s3);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  color: var(--ink);
  font: inherit; font-size: 13px; line-height: 1.5;
  resize: vertical; min-height: 68px;
}
.feedback-note::placeholder { color: var(--ink-faint); }
.feedback-note:focus { outline: none; border-color: var(--blue-solid); }

.feedback-actions { display: flex; gap: var(--s2); margin-top: var(--s3); }
.feedback-btn.primary {
  background: var(--blue-solid);
  border-color: var(--blue-solid);
  color: #fff;
}
.feedback-btn.primary:hover { background: #5a95f5; border-color: #5a95f5; color: #fff; }
.feedback-done { font-size: 13px; color: var(--green); }

.loader { display: flex; align-items: center; gap: var(--s3); }
.loader-label { color: var(--ink-muted); font-size: 13px; }

/* The trailing ellipsis cycles . / .. / ... rather than sitting static, so
   a long-running step still reads as alive. */
.loader-label::after {
  content: "";
  animation: ellipsis 1.4s steps(1, end) infinite;
}
@keyframes ellipsis {
  0%   { content: "."; }
  25%  { content: ".."; }
  50%  { content: "..."; }
  75%  { content: ".."; }
}

/* ================= responsive ================= */

@media (max-width: 1180px) { body[data-view="chat"] .rail { display: none; } }

/* ---- phones ----
   The sidebar was hidden below 860px and nothing replaced it, so a phone had
   no navigation, no way to start an automation, and no way to reach any view
   but the one it loaded on. A bottom bar is where a thumb already is. */
.tabbar { display: none; }

@media (max-width: 860px) {
  .sidebar { display: none; }

  .tabbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 70;
    display: flex;
    background: var(--surface-1);
    border-top: 1px solid var(--line);
    /* Clears the home indicator on a notched phone. */
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .tab {
    flex: 1 1 0;
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    padding: var(--s3) var(--s1);
    background: none; border: none;
    color: var(--ink-faint);
    font-size: 10px;
    cursor: pointer;
    transition: color var(--t-fast) var(--ease-out);
  }
  .tab.active { color: var(--blue); }
  .tab:active { color: var(--ink); }

  /* Every scrolling surface has to clear the bar, or its last row sits
     under it and cannot be reached. */
  body { padding-bottom: 62px; }
  .home, .browse, .activity-view { padding-bottom: var(--s8); }
  .chat-foot { padding-bottom: var(--s4); }

  .home { padding: 0 var(--s4) var(--s8); }
  .home-inner { padding-top: 6vh; }
  .greeting { font-size: 27px; line-height: 1.2; }
  .chat-scroll, .chat-foot { padding-left: var(--s4); padding-right: var(--s4); }
  .tile-grid { grid-template-columns: 1fr; }
  .browse-inner { padding: var(--s6) var(--s4) var(--s8); }

  /* A message bubble at 76% of a narrow screen wastes the width it has. */
  .message.from-user .bubble { max-width: 88%; }

  /* Comfortable touch targets, and inputs at 16px so iOS stops zooming the
     page every time the keyboard opens. */
  .composer-input, .browse-filter, .modal-input, .gate-input { font-size: 16px; }
  .send-btn { width: 44px; height: 44px; }
  .icon-btn { width: 40px; height: 40px; }
  .tile-gear { width: 34px; height: 34px; }
  .modal { padding: var(--s5); }
}

@media (max-width: 400px) {
  .greeting { font-size: 23px; }
  .tab { font-size: 9px; }
}

@media (prefers-reduced-motion: reduce) {
  .thinking-dots i { animation: none; opacity: 0.7; }
  /* The jiggle is the affordance for edit mode, so it cannot simply be
     removed - a dashed outline says the same thing without moving. */
  body.editing .tile-grid .tile {
    animation: none !important;
    outline: 2px dashed var(--line-strong);
    outline-offset: 2px;
  }
}

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--surface-4); border-radius: var(--r-pill); }
::-webkit-scrollbar-track { background: transparent; }
