/* app.css — tethr.io licensed product, customer-facing
   Rebuilt for the flat, icon-led web direction: no glassmorphism, no
   backdrop-filter, no background glow. Surfaces are solid and sharp;
   hierarchy comes from borders, dividers, and the lime accent rather
   than from blur and depth.

   Every color is a variable defined twice below — once per theme — so
   light mode is a genuine first-class mode, not a filter or an
   afterthought layered over the dark palette. */

/* Tethr UI colour system — both themes are approved specs, not one
   derived from the other. Token names match the design docs exactly
   (--tethr-*) with our existing shorthand aliased on top, so component
   rules keep working while the source of truth stays the spec.

   Ratio in both: ~85% neutrals / ~10% black-grey / 5% lime.
   Lime is a SIGNAL — active nav, progress, status dots, selected
   icons, small emphasis. Never a surface. */

:root,
[data-theme='dark'] {
  --tethr-bg: #101214;
  --tethr-sidebar: #090B0D;
  --tethr-card: #191C1F;
  --tethr-card-secondary: #202428;
  --tethr-control: #23272B;
  --tethr-active-nav: #2A2E32;
  --tethr-border: #30353A;
  --tethr-text-primary: #F3F4F1;
  --tethr-text-secondary: #A4A9AE;
  --tethr-text-muted: #747A80;
  --tethr-lime: #B9F634;
  --tethr-lime-dark: #93C72A;
  --tethr-lime-soft: #263116;
  --tethr-lime-pale: #263116;
  --tethr-success: #9FD13C;
  --tethr-warning: #D6AA4A;
  --tethr-danger: #D66A5B;
  --tethr-black: #050607;

  /* Primary buttons: lime on dark reads as the signal it is. */
  --cta-bg: var(--tethr-lime);
  --cta-fg: #101214;
  --shadow: none;
  --shadow-card: none;
}

[data-theme='light'] {
  --tethr-bg: #F4F1EC;
  --tethr-sidebar: #F1EEE8;
  --tethr-card: #FBF9F5;
  --tethr-card-secondary: #F6F3EE;
  --tethr-control: #ECE9E3;
  --tethr-active-nav: #EAE7E1;
  --tethr-border: #E1DDD5;
  --tethr-text-primary: #17181A;
  --tethr-text-secondary: #6E716E;
  --tethr-text-muted: #9A9C98;
  --tethr-lime: #B6D62D;
  --tethr-lime-dark: #8FAA15;
  --tethr-lime-pale: #E8F1B9;
  --tethr-lime-soft: #F0F5D8;
  --tethr-success: #7D9D23;
  --tethr-warning: #C49A2C;
  --tethr-danger: #C86655;
  --tethr-black: #17181A;

  /* Light-mode CTAs are BLACK per the spec, not lime — a pale lime fill
     on a warm off-white page has almost no separation from the surface
     behind it, which is the exact problem measured earlier (1.4:1). */
  --cta-bg: var(--tethr-black);
  --cta-fg: #FBF9F5;
  --shadow: 0 1px 2px rgba(23, 24, 26, 0.04);
  --shadow-card: 0 1px 3px rgba(23, 24, 26, 0.05);
}

/* Shorthand aliases — every component rule written so far uses these.
   Mapping them here means the spec tokens stay authoritative while
   nothing downstream has to be rewritten. */
:root, [data-theme='dark'], [data-theme='light'] {
  --bg: var(--tethr-bg);
  --surface: var(--tethr-card);
  --surface-raised: var(--tethr-card-secondary);
  --control: var(--tethr-control);
  --border: var(--tethr-border);
  --border-strong: var(--tethr-border);
  --divider: var(--tethr-border);
  --text: var(--tethr-text-primary);
  --text-muted: var(--tethr-text-secondary);
  --text-faint: var(--tethr-text-muted);
  --accent: var(--tethr-lime);
  --accent-text: var(--tethr-lime-dark);
  --accent-soft: var(--tethr-lime-soft);
  --accent-border: var(--tethr-lime-dark);
  --icon-bg: var(--tethr-lime-soft);
  --danger: var(--tethr-danger);
  --success: var(--tethr-success);
  --warning: var(--tethr-warning);
  --user-bubble: var(--tethr-lime-soft);
  --user-bubble-border: var(--tethr-lime-dark);
  --accent-glow: transparent;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* A global utility, added because several elements toggle `hidden`
   without having their own scoped rule — the "Saved" confirmation was
   permanently visible for exactly that reason. Views keep their own
   .view.hidden rule since they need display:flex when shown. */
.hidden { display: none !important; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  height: 100vh;
  overflow: hidden;
  transition: background 0.2s ease, color 0.2s ease;
}

.app-shell {
  display: grid;
  grid-template-columns: 248px 1fr;
  height: 100vh;
}

/* ---------- Shell ---------- */
/* The sidebar sits collapsed to a narrow rail and expands on hover, so
   the workspace gets the width by default. The logo stays visible in
   both states — it's the anchor that makes the rail read as a
   deliberately collapsed sidebar rather than a missing one. */
:root {
  --rail-w: 74px;
  --sidebar-w: 250px;
}

body { padding: 0; }

.app-shell {
  display: grid;
  grid-template-columns: var(--rail-w) 1fr;
  gap: 0;
  height: 100vh;
  padding: 14px;
  /* Content shifts rather than being overlaid, per the brief. The grid
     column is what animates; nothing inside needs to know. */
  transition: grid-template-columns 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.app-shell.sidebar-open { grid-template-columns: var(--sidebar-w) 1fr; }

.sidebar {
  position: relative;
  background: var(--tethr-sidebar);
  border: 1px solid var(--border);
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  padding: 20px 12px 16px;
  overflow: hidden;
  box-shadow: var(--shadow-card);
}

.brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 8px 24px;
  min-height: 44px;
}
.brand-mark {
  position: relative;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-weight: 500;
  font-size: 1.3rem;
  letter-spacing: 0.1em;
  color: var(--text);
  white-space: nowrap;
  flex-shrink: 0;
}
/* The standalone dot from the brand lockup. */
.brand-mark::after {
  content: '';
  position: absolute;
  top: 2px; right: -9px;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--tethr-lime);
}

/* The rail gives the brand row only 34px of content width, and the mark
   plus its offset dot needs more than that — so the dot landed on the
   sidebar's own border and the rounded corner clipped it. Three small
   reclamations rather than one big one: the three dots are already
   invisible here but were still reserving ~25px of layout, the row's
   own padding can afford to give some back, and the mark itself can sit
   a touch smaller when it has no label beside it. */
.app-shell:not(.sidebar-open) .brand-dots { display: none; }
.app-shell:not(.sidebar-open) .brand { padding: 0 2px 24px; }
.app-shell:not(.sidebar-open) .brand-mark { font-size: 1.2rem; }

/* Three dots exist only while the sidebar is open, and pulse ONCE on
   each open — a loading tick, not a loop. Driven by the same class
   that opens the sidebar, so the animation restarts naturally on every
   open without any JS timers. */
.brand-dots {
  display: flex;
  gap: 5px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.sidebar-open .brand-dots { opacity: 1; }
.brand-dots span {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--tethr-lime);
}
.sidebar-open .brand-dots span { animation: dotRipple 0.9s ease-out 1; }
.sidebar-open .brand-dots span:nth-child(2) { animation-delay: 0.09s; }
.sidebar-open .brand-dots span:nth-child(3) { animation-delay: 0.18s; }
@keyframes dotRipple {
  0%   { transform: scale(0.4); opacity: 0.25; }
  45%  { transform: scale(1.35); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

.nav { display: flex; flex-direction: column; gap: 3px; flex: 1; }
.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  background: none;
  border: none;
  color: var(--text-secondary, var(--text-muted));
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  padding: 11px 12px;
  border-radius: 11px;
  cursor: pointer;
  text-align: left;
  /* Full width on the rail. The 34px of slack below exists so the 1.2x
     magnify has somewhere to grow inside the OPEN sidebar — but the rail
     only offers 50px of content width, so the same subtraction left a
     16px box wrapping an 18px icon, which is why the active highlight
     rendered as a sliver with the icon spilling out of it. */
  width: 100%;
  white-space: nowrap;
  transform-origin: left center;
  transition: background 0.15s ease, color 0.15s ease, transform 0.18s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.sidebar-open .nav-item { width: calc(100% - 34px); }
/* On the rail the row filled all 48px of content width while the icon
   sat 12px in from the left - so the highlight ran 18px past the icon
   on the right and 12px on the left. Lopsided, and wide enough that it
   read as a bar spanning the rail rather than a button around an icon.
   Squared and centred, so it wraps the icon evenly on all four sides. */
.app-shell:not(.sidebar-open) .nav-item {
  width: 42px;
  height: 42px;
  padding: 0;
  /* Both of these matter, and the first attempt set only the middle
     line. .nav-label is hidden on the rail with opacity, which paints
     nothing but still occupies its full text width as a flex item - so
     the row's contents came to 18px of icon plus a 14px gap plus ~60px
     of invisible label, inside a 42px box. Under flex-start that
     overflow all ran off the right and was clipped, which is why it
     never showed. Centring split it evenly and pushed the icon out of
     the left-hand side of its own highlight. */
  gap: 0;
  justify-content: center;
  margin: 0 auto;
  /* The base rule anchors the hover scale to the left edge, which is
     right when the row starts at the left edge and grows into its own
     slack. This one is centred, so growing from the left would slide it
     off-centre on hover. */
  transform-origin: center;
}
/* Width, not display: none. The label has to stay in the layout with a
   real computed style, or the fade-in on open has nothing to animate
   from and the text simply appears while the panel is still sliding. */
.app-shell:not(.sidebar-open) .nav-label {
  width: 0;
  overflow: hidden;
}
/* Gentler on the rail: a 50px row at 1.2x reaches 60px and all but
   touches the sidebar's own border. */
.nav-item:hover:not(:disabled) {
  background: var(--tethr-active-nav);
  color: var(--text);
  transform: scale(1.12);
}
.sidebar-open .nav-item:hover:not(:disabled) { transform: scale(1.2); }
.nav-item.active { background: var(--tethr-active-nav); color: var(--tethr-lime-dark); font-weight: 600; }
.nav-item.active::before {
  content: '';
  position: absolute;
  /* -8px, not -12px: the nav sits 12px in from the sidebar edge, so a
     -12px offset put this marker exactly on the 1px border and the
     20px corner radius clipped it. */
  left: -8px; top: 7px; bottom: 7px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--tethr-lime);
}
.nav-item:disabled { opacity: 0.4; cursor: not-allowed; }
.nav-item svg { width: 18px; height: 18px; flex-shrink: 0; }
/* Labels fade with the rail so the icons don't sit beside clipped text
   mid-transition. */
.nav-label { opacity: 0; transition: opacity 0.16s ease; }
.sidebar-open .nav-label { opacity: 1; transition-delay: 0.08s; }

.nav-divider {
  height: 1px;
  background: var(--border);
  margin: 12px 6px;
}

/* Pin: hover is the default, but a hover-only sidebar is unreachable by
   keyboard and on touch. Pinning locks it open. */
.sidebar-pin {
  width: 28px; height: 28px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: none;
  color: var(--text-faint);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.sidebar-pin:hover { color: var(--text); border-color: var(--border-strong); }
.sidebar-pin.pinned { color: var(--tethr-lime-dark); border-color: var(--tethr-lime-dark); }
.sidebar-pin svg { width: 14px; height: 14px; }

/* Calendar card — hidden on the rail, since a collapsed 74px column
   can't show it meaningfully. */
.sidebar-calendar {
  background: linear-gradient(160deg, var(--tethr-lime), var(--tethr-lime-dark));
  border-radius: 16px;
  padding: 16px;
  margin-top: 12px;
  opacity: 0;
  transition: opacity 0.18s ease;
  pointer-events: none;
}
.sidebar-open .sidebar-calendar { opacity: 1; pointer-events: auto; transition-delay: 0.08s; }
.sidebar-calendar-title { font-weight: 700; font-size: 0.9rem; color: #17181A; }
.sidebar-calendar-item { font-size: 0.78rem; color: rgba(23, 24, 26, 0.72); margin-top: 4px; }
.sidebar-calendar-btn {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%;
  margin-top: 34px;
  background: var(--tethr-black);
  color: #F3F4F1;
  border: none;
  border-radius: 11px;
  padding: 11px 14px;
  font-family: inherit; font-size: 0.8rem; font-weight: 600;
  cursor: pointer;
}
.sidebar-calendar-btn svg { width: 14px; height: 14px; }

/* Five items in 210px that together wanted ~224. The overflow landed on
   the last one — the lime dot — pushing it under the sidebar's rounded
   corner where overflow:hidden cut it in half. Tighter gaps and padding
   buy back the difference; min-width:0 on the text below is the safety
   net that makes an overflow impossible rather than merely unlikely. */
.sidebar-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 4px 0;
  border-top: 1px solid var(--border);
  margin-top: 12px;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.sidebar-open .sidebar-footer { opacity: 1; transition-delay: 0.08s; }
.sidebar-footer .brand-mark.small { font-size: 0.72rem; letter-spacing: 0.08em; }
.sidebar-footer .brand-mark.small::after { display: none; }
.sidebar-footer-text {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.56rem;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  line-height: 1.7;
  flex: 1;
  white-space: nowrap;
  /* A flex item defaults to min-width:auto, which for nowrap text means
     "never shrink below the full string" — so this refused to give way
     and shoved the dot beside it out of the sidebar instead. With this,
     the text is what yields under pressure, not the layout. */
  min-width: 0;
  overflow: hidden;
}
.footer-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--tethr-lime); flex-shrink: 0; }

.theme-toggle {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-muted);
  width: 28px; height: 28px;
  border-radius: 8px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.theme-toggle:hover { border-color: var(--border-strong); color: var(--text); }
.theme-toggle svg { width: 14px; height: 14px; }

/* Motion here is decorative — the magnify, the ripple and the slide all
   convey nothing the layout doesn't already show, so they're removed
   outright rather than slowed for anyone who asked for reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .app-shell, .nav-item, .nav-label, .brand-dots,
  .sidebar-calendar, .sidebar-footer, .sidebar-pin {
    transition: none !important;
  }
  .sidebar-open .brand-dots span { animation: none !important; }
  .nav-item:hover:not(:disabled) { transform: none; }
}

/* ---------- Top bar ---------- */
.topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 4px 0 22px;
}
.topbar-spacer { flex: 1; }
.topbar-search {
  position: relative;
  width: 300px;
}
.topbar-search svg {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  width: 15px; height: 15px; color: var(--text-faint); pointer-events: none;
}
.topbar-search input {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 10px 14px 10px 36px;
  font-family: inherit;
  font-size: 0.85rem;
}
.topbar-search input:focus { outline: none; border-color: var(--accent-border); }
.topbar-search input::placeholder { color: var(--text-faint); }

.topbar-account { position: relative; flex-shrink: 0; }

.topbar-avatar {
  position: relative;
  cursor: pointer;
  padding: 0;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.7rem; font-weight: 600;
  color: var(--text-muted);
  flex-shrink: 0;
}
.topbar-avatar::after {
  content: '';
  position: absolute; right: -1px; bottom: -1px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
}
.topbar-avatar:hover { border-color: var(--text-muted); }
.topbar-avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The account menu. Right-aligned under the avatar, because the avatar
   sits at the right-hand end of the bar and a menu that opens leftwards
   off the edge of the window is a menu nobody can read. */
.account-menu {
  position: absolute;
  top: calc(100% + 8px); right: 0;
  min-width: 208px;
  z-index: 60;
  padding: 4px;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
}
.account-menu-who {
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}
/* Both lines wrap rather than truncate: an address you cannot read in
   full is no use for telling one account from another, which is the
   entire reason this panel exists. */
.account-menu-name {
  font-size: 0.78rem; font-weight: 600; color: var(--text);
  overflow-wrap: anywhere;
}
.account-menu-mail {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.68rem; color: var(--text-muted);
  overflow-wrap: anywhere;
}
.account-menu-item {
  display: block; width: 100%;
  padding: 8px 10px;
  text-align: left;
  background: none; border: 0; border-radius: 7px;
  font: inherit; font-size: 0.8rem;
  color: var(--text);
  cursor: pointer;
}
.account-menu-item:hover { background: var(--surface-hover, rgba(127, 127, 127, 0.12)); }
.account-menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.account-menu-item[disabled] { opacity: 0.6; cursor: default; }

/* ---------- Main / views ---------- */
.main { overflow: hidden; display: flex; flex-direction: column; padding-left: 14px; }

@keyframes viewEnter {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
.view {
  flex: 1;
  overflow-y: auto;
  padding: 6px 4px 30px;
  display: flex;
  flex-direction: column;
  animation: viewEnter 0.32s cubic-bezier(0.16, 1, 0.3, 1);
}
.view.hidden { display: none; }

.view-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 26px;
}
.view-header h1, .chat-header-title h1 {
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.025em;
}
.view-subtitle { color: var(--text-muted); font-size: 0.9rem; margin-top: 6px; }
.view-header-actions { display: flex; align-items: center; gap: 10px; }
.view-header-actions .topbar-search { width: 220px; }

.filter-dropdown-wrap { position: relative; }
.filter-dropdown {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 20;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px; width: 200px; box-shadow: var(--shadow);
}
.filter-dropdown.hidden { display: none; }
.filter-dropdown-label { font-size: 0.75rem; color: var(--text-faint); margin-bottom: 6px; }
.filter-dropdown select {
  width: 100%; background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 8px 10px; font-family: inherit; font-size: 0.85rem; cursor: pointer;
}

.pagination-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 4px 4px; color: var(--text-muted); font-size: 0.82rem;
}
.pagination-buttons { display: flex; align-items: center; gap: 6px; }
.page-btn {
  min-width: 30px; height: 30px; padding: 0 8px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--text-muted);
  cursor: pointer; font-family: inherit; font-size: 0.82rem;
}
.page-btn:hover:not(:disabled) { border-color: var(--border-strong); color: var(--text); }
.page-btn:disabled { opacity: 0.4; cursor: default; }
.page-btn.current { background: var(--accent-soft); border-color: var(--accent-border); color: var(--accent-text); }

/* Section label with a rule running out to a terminal dot. Lost when
   the views block was rewritten for the reference layout — the markup
   kept emitting it, so the label rendered as bare text with no line.
   Restored here rather than left to the generic heading styles. */
.section-rule {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
}
.section-rule-label {
  font-size: 1.05rem;
  font-weight: 600;
  white-space: nowrap;
}
.section-rule-line {
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--border-strong), var(--accent));
}
.section-rule-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}

/* ---------- Panels ---------- */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 20px 22px;
}
.panel-title { font-size: 1rem; font-weight: 600; margin-bottom: 16px; }

/* ---------- Stat cards ---------- */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 18px 19px;
}
.stat-card-top {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
}
.stat-label { color: var(--text-muted); font-size: 0.83rem; }
.stat-icon {
  width: 30px; height: 30px;
  border-radius: 9px;
  background: var(--icon-bg);
  border: 1px solid var(--accent-border);
  color: var(--accent-text);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.stat-icon svg { width: 15px; height: 15px; }
.stat-value { font-size: 1.85rem; font-weight: 700; letter-spacing: -0.03em; margin: 10px 0 8px; }
.stat-delta { font-size: 0.76rem; font-weight: 600; color: var(--accent-text); }
.stat-delta.neutral { color: var(--text-faint); font-weight: 500; }

/* ---------- Tabs ---------- */
.tabs {
  display: flex; gap: 24px;
  border-bottom: 1px solid var(--divider);
  margin-bottom: 18px;
}
.tab {
  background: none; border: none;
  color: var(--text-muted);
  font-family: inherit; font-size: 0.86rem; font-weight: 500;
  padding: 0 0 11px;
  cursor: pointer;
  position: relative;
  transition: color 0.15s ease;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent-text); font-weight: 600; }
.tab.active::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; background: var(--accent); border-radius: 2px;
}

/* ---------- Filter bar ---------- */
.filter-bar { display: flex; gap: 10px; margin-bottom: 18px; }
.filter-bar .topbar-search { flex: 1; width: auto; }
.filter-btn {
  /* inline-flex, not the default inline: this button gained an icon,
     and without a flex context the SVG became its own line box with the
     label wrapping underneath and spilling out past the button's edge. */
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-muted);
  border-radius: 10px;
  padding: 10px 16px;
  font-family: inherit; font-size: 0.83rem;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s ease, color 0.15s ease;
}
/* Unsized inline SVG defaults to 300x150 in most engines — the single
   biggest reason an icon button blows out its own layout. */
.filter-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
.filter-btn:hover { border-color: var(--border-strong); color: var(--text); }

/* ---------- Data table ---------- */
.table-head {
  display: grid;
  gap: 18px;
  padding: 0 4px 12px;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.64rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  border-bottom: 1px solid var(--divider);
}
.data-row {
  display: grid;
  gap: 18px;
  align-items: center;
  padding: 16px 4px;
  border-bottom: 1px solid var(--divider);
  cursor: pointer;
  transition: background 0.15s ease;
}
.data-row:last-child { border-bottom: none; }
.data-row:hover { background: var(--surface-raised); }
.row-title { font-weight: 600; font-size: 0.9rem; }
/* Two lines, then an ellipsis. A row in a list is a glance, not a read:
   the whole point of the table is comparing projects down a column, and
   one project whose summary runs to eight lines pushes every other row
   off the screen. The full text is one click away on the project. */
.row-sub {
  color: var(--text-muted); font-size: 0.79rem; margin-top: 3px;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow: hidden;
}

.pill {
  justify-self: start;
  font-size: 0.7rem; font-weight: 600;
  padding: 5px 12px; border-radius: 999px;
  white-space: nowrap;
}
.pill.active, .pill.in_progress { color: var(--accent-text); background: var(--accent-soft); }
.pill.review, .pill.in_review { color: #7DD3FC; background: rgba(125, 211, 252, 0.13); }
.pill.hold { color: #FDBA74; background: rgba(253, 186, 116, 0.13); }
.pill.done { color: var(--text-muted); background: var(--surface-raised); }
/* A shell with no plan yet — informational, not urgent, so it stays
   neutral rather than reaching for lime or the warning amber. */
.pill.planning { color: var(--text-faint); background: var(--surface-raised); border: 1px dashed var(--border); }
/* Measuring: the work is done and the answer is not in yet.

   Deliberately not Completed's grey and not In Progress's lime. Grey
   would say finished, which is the lie this state exists to stop; lime
   would say somebody is working, and nobody is — the owner is waiting
   for weeks to pass. Its own quiet colour, closer to done than to
   active, with the outline that says it is still open. */
.pill.measuring {
  color: #9FD13C;
  background: rgba(159, 209, 60, 0.10);
  border: 1px solid rgba(159, 209, 60, 0.28);
}
/* Set aside on purpose, so it reads as filed away, not as a normal
   status among the active ones. */
.pill.archived { color: var(--text-faint); background: var(--surface-raised); }

/* Privacy badge in the project overview aside. Same restraint: this is
   a fact about the project, not a live signal, so it stays quiet. */
.privacy-badge { font-size: 0.78rem; padding: 2px 8px; border-radius: 999px; background: var(--surface-raised); color: var(--text-muted); }
.privacy-badge.public { color: var(--accent-text); background: var(--accent-soft); }

/* ---------- Project access (private projects only) ---------- */
.access-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.access-chip { display: flex; align-items: center; gap: 9px; }
.access-chip-name { font-size: 0.83rem; flex: 1; }
.access-chip-owner {
  font-size: 0.68rem; color: var(--text-faint); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 7px; margin-left: 4px;
}
.access-chip-remove {
  background: none; border: none; color: var(--text-faint); cursor: pointer;
  width: 20px; height: 20px; font-size: 0.95rem; line-height: 1; border-radius: 6px;
  opacity: 0; transition: opacity 0.15s ease, color 0.15s ease;
}
.access-chip:hover .access-chip-remove { opacity: 1; }
.access-chip-remove:hover { color: var(--danger); }
.access-add-row { display: flex; gap: 8px; }
.access-add-row select {
  flex: 1; background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 7px 9px; font-family: inherit; font-size: 0.8rem; cursor: pointer;
}
@media (prefers-reduced-motion: reduce) { .access-chip-remove { transition: none; } }

.bar-cell { display: flex; align-items: center; gap: 11px; }
.bar-track { flex: 1; height: 5px; border-radius: 999px; background: var(--surface-raised); overflow: hidden; min-width: 60px; }
.bar-track span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.bar-pct { font-size: 0.76rem; color: var(--text-muted); width: 34px; text-align: right; flex-shrink: 0; }
.row-meta { color: var(--text-faint); font-size: 0.76rem; white-space: nowrap; }
.row-menu-wrap { position: relative; }
.row-menu {
  background: none; border: none; color: var(--text-faint);
  font-size: 1rem; letter-spacing: 1px; cursor: pointer; padding: 4px 6px;
  border-radius: 6px;
}
.row-menu:hover { color: var(--text); background: var(--surface-raised); }
.row-menu-dropdown {
  position: absolute; top: calc(100% + 4px); right: 0; z-index: 15;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: var(--shadow); min-width: 150px; overflow: hidden;
}
.row-menu-dropdown.hidden { display: none; }
.row-menu-item {
  display: block; width: 100%; text-align: left; padding: 9px 12px;
  background: none; border: none; color: var(--text); font-family: inherit;
  font-size: 0.82rem; cursor: pointer;
}
.row-menu-item:hover { background: var(--surface-raised); }
/* Sits directly under "Archive project", which is reversible from the
   same menu. Colour is the only thing separating them at a glance. */
.row-menu-danger { color: var(--danger); }
.row-menu-danger[data-armed="1"] { background: var(--danger); color: #fff; font-weight: 600; }
.row-menu-danger:disabled { opacity: 0.6; cursor: default; }

.empty-note { color: var(--text-faint); font-size: 0.86rem; padding: 26px 4px; }

/* ---------- Workforce cards ---------- */
.team-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 16px;
}
.team-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.team-card:hover { border-color: var(--accent-border); transform: translateY(-2px); }
.team-card-top { display: flex; align-items: center; gap: 13px; }
.avatar {
  width: 42px; height: 42px;
  border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-weight: 600;
  font-size: 0.92rem;
  color: #0A0A0A;
  flex-shrink: 0;
}
.team-card-name { font-weight: 600; font-size: 1rem; }
.team-card-role { color: var(--text-muted); font-size: 0.82rem; margin-top: 2px; }
.team-card-expertise { font-size: 0.85rem; color: var(--text-muted); line-height: 1.6; }

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--accent-text);
  align-self: flex-start;
}
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }

.usage-strip {
  margin-top: auto;
  padding-top: 28px;
  color: var(--text-faint);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.72rem;
}

/* ---------- Onboarding banner ---------- */
.onboarding-banner { margin-bottom: 22px; }
.onboarding-banner.hidden { display: none; }
.onboarding-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  border-radius: 14px;
  padding: 20px 22px;
  display: flex;
  align-items: center;
  gap: 16px;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: border-color 0.15s ease;
}
.onboarding-card:hover { border-color: var(--accent-border); border-left-color: var(--accent); }
.onboarding-card-text { flex: 1; }
.onboarding-card-title { font-weight: 600; font-size: 0.97rem; }
.onboarding-card-sub { color: var(--text-muted); font-size: 0.85rem; margin-top: 3px; }
.onboarding-card-cta {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.78rem;
  color: var(--accent-text);
  white-space: nowrap;
}

/* ---------- Chat ---------- */
.chat-header { display: flex; align-items: center; gap: 16px; margin-bottom: 22px; }
.chat-header-title { flex: 1; }

.btn-outline {
  background: none;
  border: 1px solid var(--accent-border);
  color: var(--accent-text);
  padding: 10px 17px;
  border-radius: 10px;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: background 0.15s ease, transform 0.1s ease;
}
.btn-outline:hover { background: var(--accent-soft); }
.btn-outline:active { transform: scale(0.97); }
.btn-outline.hidden { display: none; }
.btn-outline svg { width: 14px; height: 14px; }

/* Filled accent button — used where an action is the primary thing on
   screen (Finish & Save). The outlined variant above is for secondary
   accent actions like "Update with Ada", matching the reference. */
.btn-accent {
  background: var(--cta-bg);
  color: var(--cta-fg);
  border: none;
  padding: 10px 18px;
  border-radius: 10px;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.1s ease, filter 0.15s ease;
}
.btn-accent:hover { filter: brightness(1.07); }
.btn-accent:active { transform: scale(0.97); }
.btn-accent.hidden { display: none; }

.btn-quiet, .back-btn {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-muted);
  padding: 9px 15px;
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.83rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}
.btn-quiet:hover, .back-btn:hover { color: var(--text); border-color: var(--border-strong); }
.btn-quiet:active, .back-btn:active { transform: scale(0.97); }

/* The send and suggest buttons now hold inline SVGs rather than text
   arrows, so they need explicit sizing and centering — without this the
   icon inherits font-size sizing rules that don't apply to SVG. */
.send-btn, .suggest-btn {
  display: flex;
  align-items: center;
  justify-content: center;
}
.send-btn svg, .suggest-btn svg { width: 18px; height: 18px; }

.chat-body {
  flex: 1;
  display: grid;
  grid-template-columns: 228px 1fr;
  gap: 18px;
  overflow: hidden;
  min-height: 0;
}
/* Hiding the roster with display:none alone would drop it out of grid
   flow and shift chat-main into its old column instead of expanding —
   a real quirk confirmed by seeing it render wrong. Collapsing to one
   column AND hiding the element together is what actually works. */
.chat-body.no-roster { grid-template-columns: 1fr; }
.chat-body.no-roster .chat-roster { display: none; }

.chat-roster {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px;
  overflow-y: auto;
}
.roster-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 9px;
  border-radius: 9px;
  cursor: pointer;
  transition: background 0.15s ease;
}
.roster-item:hover { background: var(--surface-raised); }
.roster-item.active { background: var(--surface-raised); }
.roster-avatar {
  width: 31px; height: 31px;
  border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-weight: 600;
  font-size: 0.72rem;
  color: #0A0A0A;
  flex-shrink: 0;
}
.roster-name { font-size: 0.87rem; font-weight: 500; }
.roster-role { font-size: 0.7rem; color: var(--text-muted); }

.chat-main {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  min-height: 0;
}
.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.chat-empty-hint {
  color: var(--text-faint);
  font-size: 0.86rem;
  margin: auto;
  text-align: center;
  max-width: 360px;
}
.chat-empty-hint p { margin-bottom: 15px; }
.update-chips-row { max-width: 420px; margin-top: 6px; }
.starter-chips { display: flex; flex-direction: column; gap: 9px; }
.starter-chip {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 11px 15px;
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.84rem;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.starter-chip:hover { border-color: var(--accent-border); background: var(--accent-soft); }

@keyframes msgEnter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}
/* renderMessages() rebuilds the entire list each time, so animating
   every .msg would replay the entrance across the whole history on
   every new message. :last-child is always the newest after a rebuild,
   so only it animates — no JS bookkeeping needed. */
.msg:last-child { animation: msgEnter 0.28s cubic-bezier(0.16, 1, 0.3, 1); }
/* A message that was watched being written does not get an entrance.

   .msg:last-child fades and slides every new message in, which is right
   for one that appears complete and wrong for one the owner has been
   reading for thirty seconds: at the moment the saved reply replaces the
   streamed bubble it would flash back in on top of itself. */
.msg.msg-settled { animation: none; }


.msg { max-width: 74%; padding: 13px 16px; border-radius: 12px; font-size: 0.89rem; line-height: 1.6; }
/* A message carrying a block is not speech, it is a form, and 74% of a
   wide panel squeezes a five-column grid into half the room it needs.
   The cap stays for prose, where a short line length is the whole reason
   it is there — a paragraph running the full width of a desktop is
   harder to read, not easier.
   max-width only, and deliberately no width: raising the ceiling lets a
   grid take the room it needs, where setting the width MADE it take the
   whole panel — three columns stretched across a desktop with a sentence
   above them running the full width. It should fit its contents, not
   fill the space it is allowed.
   :has() rather than a class from the renderer so it covers every place
   a message is drawn at once. Where it is unsupported the message simply
   stays at 74%, which is exactly today's behaviour. */
/* Every reply the same width, whether it carries a block or a sentence.
   Sizing each bubble to its own contents left a ragged right edge down
   the page — a grid spanning the panel, the reply under it stopping a
   third of the way across — and the eye reads that as things being
   unrelated rather than as one conversation.
   Safe to do now only because the writing inside is capped at 78ch
   below: a bubble that spans is not a paragraph that spans. */
.msg-assistant { width: 100%; max-width: 88ch; }
/* A reply carrying a grid may take more room than one carrying only
   sentences.
   88ch is a LINE LENGTH, and it is the right cap for writing. It is the
   wrong cap for a six-column table: "Regular | Usual size | Last visit |
   Rush week offered | Messaged? | Confirmed?" arrived with a horizontal
   scrollbar and its first column squeezed to nothing, so the one column
   that names each row was the one you could not read.
   The prose inside stays at 78ch either way — the rule below is
   unchanged and still applies — so this widens the table without
   letting a paragraph span the desk. */
.msg-assistant:has(.input-table) { max-width: min(124ch, 100%); }
/* The bubble grows for the block; the writing inside it must not.
   A seven-column grid legitimately needs the width, and letting the
   sentences above and below it run the same distance is how a reply ends
   up spanning a desktop — the exact thing the 74% cap exists to prevent,
   reintroduced by the rule that lets the grid escape it.
   Measured in ch, so it is a line length rather than a fraction of
   whatever the panel happens to be. */
.msg-assistant p,
.msg-assistant li,
.msg-assistant .msg-quote,
.msg-assistant .msg-h { max-width: 78ch; text-wrap: pretty; }
.msg-user {
  align-self: flex-end;
  background: var(--user-bubble);
  border: 1px solid var(--user-bubble-border);
  /* What someone typed, laid out the way they typed it. Applied here and
     NOT to the assistant bubble: that one now holds real markup, whose
     newlines sit between tags and would render as stray blank space. */
  white-space: pre-wrap;
}
.msg-assistant {
  align-self: flex-start;
  background: var(--surface-raised);
  border: 1px solid var(--border);
}
.msg-assistant-name {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.65rem;
  color: var(--text-faint);
  margin-bottom: 5px;
  letter-spacing: 0.06em;
}

/* ---------- Formatted replies ---------- */
/* A specialist writing out a cost breakdown produces tables, steps and
   emphasis. All of it used to be flattened into one paragraph on the way
   to the screen; these are the styles that let the structure survive.
   Deliberately quiet — this is a chat bubble, not a document. */
.msg p { margin: 0 0 9px; }
.msg p:last-child { margin-bottom: 0; }
.msg ul, .msg ol { margin: 0 0 9px; padding-left: 20px; }
.msg ul:last-child, .msg ol:last-child { margin-bottom: 0; }
.msg li { margin-bottom: 4px; }
.msg li:last-child { margin-bottom: 0; }
.msg-h {
  font-weight: 600;
  font-size: 0.9rem;
  margin: 12px 0 6px;
}
.msg-h:first-child { margin-top: 0; }
.msg code {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.84em;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 1px 5px;
}
.msg strong { font-weight: 600; color: var(--text); }

/* A table is the one thing here that can genuinely exceed the bubble —
   four costed columns on a narrow screen. It scrolls inside its own
   wrapper so the message never widens the thread to fit it. */
.msg-table-wrap { overflow-x: auto; margin: 0 0 10px; }
.msg-table-wrap:last-child { margin-bottom: 0; }
.msg-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.82rem;
}
.msg-table th, .msg-table td {
  border: 1px solid var(--border);
  padding: 6px 10px;
  text-align: left;
  white-space: nowrap;
}
.msg-table th {
  background: var(--bg);
  font-weight: 600;
  color: var(--text);
}

/* Code the model wrote, shown as code rather than as a paragraph that
   happens to contain backticks. */
.msg-pre {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  overflow-x: auto;
  margin: 0 0 10px;
}
.msg-pre code {
  background: none; border: none; padding: 0;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.8rem;
  white-space: pre;
}

/* ---------- Interactive figures ---------- */
/* A specialist asking for several numbers gets boxes to type into
   instead of a list of questions. Lime on the send button only: this is
   a live thing waiting on you, and the one action that completes it. */
/* Quoted text — a note to pin, a message to send on. Set apart because
   its whole purpose is being lifted out and used somewhere else. */
.msg-quote {
  margin: 10px 0; padding: 9px 13px;
  border-left: 2px solid var(--accent-border);
  background: var(--bg); border-radius: 0 8px 8px 0;
  color: var(--text); font-size: 0.87rem; line-height: 1.62;
}
.msg-quote strong { color: var(--text); }

.input-table {
  border: 1px solid var(--accent-border);
  border-radius: 10px;
  background: var(--bg);
  padding: 12px;
  margin: 0 0 10px;
}
.input-table-title { font-weight: 600; font-size: 0.85rem; margin-bottom: 10px; }
/* A row that was in the previous table and is not in this one. Stated,
   not corrected — the specialist may have dropped it on purpose, and a
   grid that silently puts rows back is one nobody can trust either way. */
/* Louder than a dropped row, because it is a person turning up to a
   closed door rather than a line missing from an order. */
.input-table-clash {
  margin: 0 0 10px; padding: 8px 11px;
  border: 1px solid var(--danger); border-radius: 7px;
  background: var(--surface); color: var(--text); font-size: 0.8rem; font-weight: 500;
}
.input-table-dropped {
  margin: 0 0 10px; padding: 7px 10px;
  border-left: 2px solid var(--clay, var(--danger)); border-radius: 0 6px 6px 0;
  background: var(--surface); color: var(--text-muted); font-size: 0.78rem;
}
/* A block that failed to parse. The payload stays visible — it is the
   only record of what was meant — but it is framed as a failure rather
   than presented as though the specialist meant to send you code. */
.block-broken {
  border: 1px solid var(--border); border-left: 2px solid var(--danger);
  border-radius: 9px; padding: 11px 13px; margin: 10px 0;
}
.block-broken-note { margin: 0 0 8px; font-size: 0.84rem; color: var(--text-muted); }
.block-broken .msg-pre { margin: 0; max-height: 120px; overflow: auto; }

.input-table-wrap { overflow-x: auto; }
/* The table fills its box, and the FIELDS take up the slack rather than
   the gaps between them.
   Both halves matter. A table stretched while its fields stayed a fixed
   width put the spare room between the columns — a hand's width of
   nothing, which is what looked wrong. Fields that grow with the table
   put the same room inside the boxes, where it is somewhere to type. */
.input-table table { border-collapse: collapse; width: 100%; }
.input-table th {
  text-align: left; font-size: 0.72rem; font-weight: 500;
  color: var(--text-faint); padding: 0 6px 6px 0;
  /* Headings wrap. Held on one line, "Typical grooms/week" and "Supplier
     lead time" each set a floor under their own column, and seven of
     them pushed a grid wider than the message — so the computed answer,
     the reason the grid exists, sat off the right-hand edge behind a
     scrollbar. Two short lines of heading cost a few pixels of height;
     a hidden answer costs the whole point of the table. */
  white-space: normal; line-height: 1.3;
}
.input-table td { padding: 0 6px 6px 0; }
.input-table input {
  /* A ceiling as well as a floor, on the same rule so the two cannot be
     separated: a grid that genuinely needs the full width should not
     spend it on one enormous box per answer. */
  width: 100%; min-width: 92px;
  /* A column holding "63" does not need the room a column holding
     "Large / double-coat" needs. Sizing every field the same made the
     grid a third wider than its contents and left the writing beside it
     stranded at half the width. */
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text); border-radius: 7px; padding: 7px 9px;
  font-family: inherit; font-size: 0.82rem;
}
.input-table input[type="number"] { min-width: 76px; }
/* Date and time fields carry the browser's own picker, which needs room
   the field cannot be told about — a width that fits "01/09/2026" hides
   the calendar icon on the end of it. */
.input-table input[type="date"] { min-width: 150px; }
.input-table input[type="time"] { min-width: 118px; }
/* Text fields size themselves from the size attribute the renderer sets
   from the value's own length. width must be auto for that to apply at
   all — a fixed width silently wins over size and was what clipped the
   labels. The bounds stop one long name pushing the grid off the page. */
.input-table input[type="text"] { min-width: 120px; }
.input-table input:focus { outline: none; border-color: var(--accent-border); }
/* A computed cell can be tapped to show its arithmetic. Marked as
   tappable, quietly — a dotted rule under a number reads as "there is
   more here" without competing with the number itself. */
.input-table-out { cursor: pointer; border-bottom: 1px dotted var(--accent-border); }
.input-table-out:hover { border-bottom-style: solid; }

.input-table-working {
  margin-top: 10px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--bg);
}
.working-sum {
  font-variant-numeric: tabular-nums; font-size: 0.9rem;
  color: var(--text); letter-spacing: 0.01em;
}
.working-sum b { color: var(--accent-text); }
.working-legend {
  margin-top: 7px; font-size: 0.76rem; line-height: 1.65; color: var(--text-muted);
}

/* An answered grid, collapsed to its answer.
   Everything is hidden but the bar; nothing is removed, so expanding it
   shows exactly what was filled in and what it worked out. */
/* Collapsed, it is a row rather than a table, so it spans instead of
   hugging — a one-line summary shrunk to its own text reads as a stray
   pill, and the chevron loses the right-hand edge it opens from. */
.input-table.sent:not(.open) { width: 100%; }
.input-table.sent { padding: 0; border-color: var(--divider); background: transparent; }
.input-table.sent .input-table-title,
.input-table.sent .input-table-wrap,
.input-table.sent .input-table-foot { display: none; }
/* :not(.open) rather than a display:block counterpart, so the panel's
   own hidden attribute still decides when the grid is expanded — forcing
   it visible would show an empty box under a grid nobody has tapped. */
.input-table.sent:not(.open) .input-table-working { display: none; }
.input-table.sent.open .input-table-title { display: block; }
.input-table.sent.open .input-table-wrap { display: block; }
.input-table.sent.open .input-table-foot { display: flex; }
.input-table.sent.open { padding: 14px 16px; }

.input-table-sentbar {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 14px; border: none; border-radius: 10px;
  background: none; color: var(--text-muted);
  font: inherit; font-size: 0.8rem; text-align: left; cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease;
}
.input-table-sentbar:hover { background: var(--surface); color: var(--text); }
.sentbar-tick { color: var(--accent-text); font-size: 0.85rem; }
.sentbar-title { font-weight: 600; color: var(--text); }
.sentbar-sum {
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sentbar-chev { margin-left: auto; transition: transform 0.16s ease; }
.input-table.sent.open .sentbar-chev { transform: rotate(180deg); }

/* The remove-row column. Narrow, and visible at rest rather than on
   hover — a control nobody can see is a control nobody knows they have,
   which is how the grid ended up add-only in the first place. Faint
   until the row is under the pointer, then plain; red only on the button
   itself, since the row is not what is being deleted, the row is where
   the button lives. */
.input-table-x { width: 34px; padding-left: 2px !important; }
.input-table-del {
  width: 22px; height: 22px;
  display: grid; place-items: center;
  background: none; border: 1px solid transparent; border-radius: 6px;
  color: var(--text-faint); font: inherit; font-size: 1rem; line-height: 1;
  cursor: pointer; opacity: 0.45;
  transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.input-table tbody tr:hover .input-table-del { opacity: 1; }
.input-table-del:hover { color: var(--danger); border-color: var(--border); }
.input-table-del:focus-visible { opacity: 1; outline: none; border-color: var(--accent-border); }

.input-table-foot { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
.input-table-add {
  background: none; border: 1px dashed var(--border-strong);
  color: var(--text-muted); border-radius: 7px; padding: 6px 11px;
  font-family: inherit; font-size: 0.76rem; cursor: pointer;
}
.input-table-add:hover:not([disabled]) { color: var(--text); border-color: var(--accent-border); }
.input-table-send {
  margin-left: auto;
  background: var(--cta-bg); color: var(--cta-fg);
  border: none; border-radius: 7px; padding: 7px 14px;
  font-family: inherit; font-size: 0.78rem; font-weight: 500; cursor: pointer;
}
.input-table-send[disabled], .input-table-add[disabled] { opacity: 0.5; cursor: default; }
/* The third option in the row, and the quietest.

   Send these is the CTA, Add row is a dashed outline, and this had no
   rule at all — so the browser drew its default button: a white slab in
   system font between two designed controls. It looked like a bug on
   the page rather than a way out of a grid you cannot fill in yet,
   which is exactly the person least likely to press an odd-looking
   button.

   Text only: keeping a grid for later is a smaller act than sending it,
   and should not compete with either neighbour. */
/* A column that answers rather than measures.

   Yes and No are read at a glance down a column, which is the point —
   the owner should be able to see WHICH row is the problem without
   holding a floor in her head and comparing five prices to it. So No
   is allowed to look like something, and Yes is allowed to be quiet. */
.input-table-out.is-answer { font-weight: 600; letter-spacing: 0.01em; }
.input-table-out.is-answer[data-value="0"] { color: var(--danger); }
.input-table-out.is-answer[data-value="1"] { color: var(--accent); }

.input-table-keep {
  background: none;
  border: none;
  color: var(--text-muted);
  border-radius: 7px;
  padding: 6px 10px;
  font-family: inherit;
  font-size: 0.76rem;
  cursor: pointer;
  transition: color 0.15s ease;
}
.input-table-keep:hover:not([disabled]) { color: var(--text); }
.input-table-keep[disabled] { opacity: 0.5; cursor: default; }
/* Asked by the page, under the grid it belongs to.

   This was a window.prompt: the browser own grey box, headed with the
   host name, landing over the top of everything. It asked for the date
   as (YYYY-MM-DD) in words, which was the third arrangement of a date
   the owner had met in ten minutes. A real date input has a picker and
   no format to get wrong. */
.keep-until-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 9px;
  margin-top: 10px;
  padding-top: 11px;
  border-top: 1px solid var(--border);
}
.keep-until-label { color: var(--text-muted); font-size: 0.78rem; }
.keep-until-date {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text);
  border-radius: 7px;
  padding: 6px 9px;
  font-family: inherit;
  font-size: 0.78rem;
}
.keep-until-date:focus { outline: none; border-color: var(--accent-border); }
.keep-until-cancel {
  background: none; border: none; color: var(--text-faint);
  font-family: inherit; font-size: 0.76rem; cursor: pointer; padding: 6px 4px;
}
.keep-until-cancel:hover { color: var(--text-muted); }
/* Sits under the row on its own line: it is the answer to "and then
   what happens to it", which is the question that stops somebody
   pressing an unfamiliar button. */
.keep-until-why {
  flex-basis: 100%;
  margin: 2px 0 0;
  color: var(--text-faint);
  font-size: 0.73rem;
}
.input-table-keep.open { color: var(--text); }

.input-table-hint { color: var(--text-faint); font-size: 0.73rem; }
/* Answered: kept on screen as the record of what was asked, but visibly
   finished with. Removing it would leave the conversation referring to
   figures nobody can see any more. */
.input-table.sent { border-color: var(--border); opacity: 0.72; }
.msg-error { align-self: center; color: var(--danger); font-size: 0.8rem; font-family: 'IBM Plex Mono', ui-monospace, monospace; }

/* Appears only once a reply has genuinely taken a while — quiet, and
   not styled as an error, since nothing has actually failed yet. */
.msg-slow-note {
  color: var(--text-faint);
  font-size: 0.78rem;
  text-align: center;
  padding: 6px 0 2px;
}

.msg-typing {
  align-self: flex-start;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  display: flex;
  gap: 5px;
  padding: 15px 17px;
  border-radius: 12px;
  animation: msgEnter 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.msg-typing span {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--text-muted);
  animation: typingBounce 1.1s ease-in-out infinite;
}
.msg-typing span:nth-child(2) { animation-delay: 0.15s; }
.msg-typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes typingBounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
  30% { transform: translateY(-4px); opacity: 1; }
}

.chat-input-row {
  display: flex;
  gap: 10px;
  padding: 16px;
  border-top: 1px solid var(--divider);
  /* Both buttons sit level with the bottom of the field. Without this
     the row stretches its children, and a 34px paperclip in a taller
     row lands against the top edge - level with nothing, next to a send
     button that is centred. flex-end rather than center so they stay
     put as the textarea grows to two and three lines. */
  align-items: flex-end;
}
#chat-input {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 11px 15px;
  font-family: inherit;
  font-size: 0.89rem;
  resize: none;
  min-height: 44px;
  max-height: 160px;
}
#chat-input:focus { outline: none; border-color: var(--accent-border); }
#chat-input::placeholder { color: var(--text-faint); }

.send-btn {
  background: var(--cta-bg);
  color: var(--cta-fg);
  border: none;
  width: 44px; height: 44px;
  border-radius: 10px;
  font-size: 1.05rem;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.1s ease, filter 0.15s ease;
}
.send-btn:hover { filter: brightness(1.07); }
.send-btn:active { transform: scale(0.94); }
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; }

.suggest-btn {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  color: var(--text);
  width: 44px; height: 44px;
  border-radius: 10px;
  font-size: 1.05rem;
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 0.15s ease, transform 0.1s ease;
}
.suggest-btn:hover { border-color: var(--accent-border); }
.suggest-btn:active { transform: scale(0.94); }
.suggest-btn.hidden { display: none; }
.suggest-btn:disabled { opacity: 0.5; cursor: wait; }

/* ---------- Knowledge ---------- */
.knowledge-header-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  margin-bottom: 4px;
}

.profile-grid {
  /* Multi-column, not CSS Grid: Grid stretches every card in a row to
     match its tallest row-mate, which left short sections like "Goals"
     padded with dead space next to long ones. Columns let each card
     size to its own content. */
  columns: 330px;
  column-gap: 16px;
}
.profile-section-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 20px 22px;
  line-height: 1.65;
  box-shadow: var(--shadow);
  /* Column layouts space vertically by margin — column-gap only
     controls horizontal spacing. break-inside stops a card splitting
     across two columns; inline-block is the most reliably supported
     pairing for it. */
  display: inline-block;
  width: 100%;
  margin-bottom: 16px;
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
}
.profile-card-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 15px;
  border-bottom: 1px solid var(--divider);
  margin-bottom: 15px;
}
/* Selector matches the class name app.js actually emits (.card-icon).
   An earlier pass named these .profile-card-icon / .profile-card-title
   and the icons silently vanished — the box lost its styling and the
   SVG, having no intrinsic size, collapsed to zero. Caught by looking
   at a render, not by reading the code. */
.card-icon {
  width: 38px; height: 38px;
  border-radius: 10px;
  background: var(--icon-bg);
  border: 1px solid var(--accent-border);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--accent-text);
}
.card-icon svg { width: 19px; height: 19px; }
.profile-card-head h2 { font-size: 1rem; font-weight: 600; }

.profile-section-card p { color: var(--text-muted); font-size: 0.9rem; margin-bottom: 8px; }
.profile-section-card p:last-child { margin-bottom: 0; }
.profile-section-card ul { margin: 4px 0 0 18px; }
.profile-section-card li { color: var(--text-muted); font-size: 0.9rem; margin-bottom: 9px; line-height: 1.6; }
.profile-section-card li:last-child { margin-bottom: 0; }
.profile-section-card strong { color: var(--text); font-weight: 600; }

.profile-updated-note {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  color: var(--text-faint);
  margin-top: 8px;
  padding-top: 14px;
  border-top: 1px solid var(--divider);
  display: inline-block;
}

.profile-empty-state {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 44px;
  text-align: center;
  max-width: 500px;
  box-shadow: var(--shadow);
}
.profile-empty-state p { color: var(--text-muted); font-size: 0.92rem; margin: 12px 0 22px; line-height: 1.6; }
.profile-empty-cta {
  display: inline-block;
  background: var(--cta-bg);
  color: var(--cta-fg);
  padding: 11px 22px;
  border-radius: 10px;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.86rem;
  cursor: pointer;
  border: none;
  transition: transform 0.1s ease, filter 0.15s ease;
}
.profile-empty-cta:hover { filter: brightness(1.07); }
.profile-empty-cta:active { transform: scale(0.97); }

/* ---------- First-run welcome ---------- */
/* The brackets separate and the message appears between them, so the
   mark performs its own meaning rather than just animating: [ ] is an
   open, unfilled space, and here it literally opens to hold something.
   Everything is driven by a single .is-open class on the stage, which
   keeps the whole sequence in CSS instead of chained JS timers. */
.welcome-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.5s ease;
}
.welcome-overlay.hidden { display: none; }
/* Dismissal fades the whole overlay out; .hidden is applied afterwards
   so the element is genuinely gone rather than sitting invisible on top
   of the app swallowing clicks. */
.welcome-overlay.dismissing { opacity: 0; pointer-events: none; }

.welcome-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
}

.welcome-mark {
  background: none;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding: 0;
  color: var(--text);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
}
.welcome-bracket {
  font-size: 4.5rem;
  font-weight: 500;
  line-height: 1;
  /* Text colour, not the accent — matching the sidebar's own `[ ]`,
     which was already off-white, and the brand identity itself. The
     lime is the signal colour (active nav, status, progress, CTAs);
     spending it on the identity mark makes identity read as state and
     weakens both. On this screen the accent is left to do exactly one
     job: mark the thing you actually click. */
  color: var(--text);
  display: block;
  transition: transform 0.75s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Idle: the brackets sit close together and breathe. The pulse lives on
   the brackets rather than the whole button so it reads as the mark
   itself being alive. */
.welcome-bracket.left { transform: translateX(6px); animation: markPulse 2.4s ease-in-out infinite; }
.welcome-bracket.right { transform: translateX(-6px); animation: markPulse 2.4s ease-in-out infinite; }

@keyframes markPulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

/* Gated on :not(.is-open) rather than left open-ended. Ungated, this
   selector carries 4 classes against the opened state's 3, so it won
   on specificity — and because the cursor stays on the mark after a
   click, the brackets snapped shut again while hovering. A real bug
   every user would hit, not a testing artifact. */
.welcome-stage:not(.is-open) .welcome-mark:hover .welcome-bracket.left { transform: translateX(0); }
.welcome-stage:not(.is-open) .welcome-mark:hover .welcome-bracket.right { transform: translateX(0); }

/* Opened: brackets push apart and stop pulsing, and the message that
   was collapsed to zero width expands between them. They also scale
   vertically — at their idle size they're shorter than the revealed
   content, so without this they sit beside the message rather than
   enclosing it, which loses the whole point of the gesture. */
.is-open .welcome-bracket { animation: none; opacity: 1; }
.is-open .welcome-bracket.left { transform: translateX(-14px) scaleY(1.75); }
.is-open .welcome-bracket.right { transform: translateX(14px) scaleY(1.75); }

.welcome-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  max-width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width 0.75s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.4s ease 0.35s,
              padding 0.75s cubic-bezier(0.22, 1, 0.36, 1);
}
.is-open .welcome-inner {
  /* 600, not 560. The longest line — the one carrying the question
     count and the time — measures about 466px, which left 26px of room:
     a two-digit count or a longer name for her and it clips, silently,
     at both ends, because the panel centres what it cannot fit. */
  max-width: 600px;
  opacity: 1;
  padding: 0 34px;
}

.welcome-title {
  font-family: 'Inter', -apple-system, sans-serif;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text);
}
.welcome-sub {
  font-family: 'Inter', -apple-system, sans-serif;
  font-size: 0.95rem;
  color: var(--text-muted);
}
/* The manner, under the facts. Quieter than the line above, because
   it is the sentence that stops eight questions reading as eight
   fields — it should register rather than announce itself.

   It was removed once, on the grounds that the headline already said
   what the answers were for. What that missed is why three lines
   exist at all: the panel sets its text on ONE line and clips the
   overflow, which is how the reveal animates from zero width. Folded
   into the line above, the sentence lost both its ends. */
.welcome-why {
  font-family: 'Inter', -apple-system, sans-serif;
  font-size: 0.85rem;
  color: var(--text-faint);
  margin-top: -2px;
}
/* Deliberately weightier than the in-app buttons. This is the one
   action on an otherwise empty screen, so it can carry more presence
   than a standard control without competing with anything. The shine
   is slow and low-contrast on purpose — enough to catch the eye as
   something live, not enough to read as a flashing advert. */
.welcome-note {
  font-family: 'Inter', -apple-system, sans-serif;
  font-size: 0.78rem;
  color: var(--text-faint);
  opacity: 0;
  transition: opacity 0.4s ease 0.25s;
}
.is-open .welcome-note { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .welcome-note { transition: none; }
}

/* Type, not a component.

   The only filled shape on a screen of line and type read as a control
   borrowed from the app and pasted onto a title sequence — and it sat
   INSIDE the real button, implying a small target where the whole
   bracketed panel is the hit area. As a label it says the same thing
   without claiming to be a second place to press.

   Lime because it is still the one thing being offered; no fill, no
   radius, no shadow, no sweep, because none of those exist anywhere
   else on this screen. */
.welcome-cta {
  margin-top: 16px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--accent);
  font-family: 'Inter', -apple-system, sans-serif;
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: -0.01em;
  transition: opacity 0.18s ease;
}
.welcome-cta svg {
  width: 16px;
  height: 16px;
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}

/* The arrow moves and the label does not: enough to answer a hover on
   an element that no longer looks pressable, without the whole line
   shifting under the cursor. */
.welcome-mark:hover .welcome-cta svg { transform: translateX(5px); }
.welcome-mark:active .welcome-cta { opacity: 0.7; }

.welcome-hint {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  opacity: 0;
  /* Delayed rather than immediate: pulsing alone doesn't reliably read
     as "click me", but showing the words instantly undercuts the moment.
     Appearing after a beat gives the mark room to be noticed first. */
  animation: hintIn 0.6s ease 1.6s forwards;
}
@keyframes hintIn {
  to { opacity: 1; }
}
/* Taken out of the flow, not merely faded.
   Zeroing its height leaves the row, and the stage is a column with a
   26px gap either side of it — so an invisible hint still held the
   note fifty-odd pixels further from the button than it belongs.
   Absolute keeps the fade and removes the gaps it was sitting in. */
.is-open .welcome-hint {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  animation: none;
  transition: opacity 0.3s ease;
}

/* A pulsing, expanding entrance is exactly the kind of motion that
   causes problems for people with vestibular sensitivity. Rather than
   removing the screen for them, this jumps straight to the opened
   state: same content, same meaning, no movement. */
@media (prefers-reduced-motion: reduce) {
  .welcome-bracket,
  .welcome-inner,
  .welcome-hint,
  .welcome-overlay,
  .welcome-cta,
  .welcome-cta svg {
    animation: none !important;
    transition: none !important;
  }
  /* The opened state, arrived at without the travel. These numbers
     are a second copy of the ones above and have to match them: when
     the panel was widened to fit its longest line, this was missed,
     and the sentence stayed clipped for exactly the people least able
     to work out why. */
  .welcome-bracket.left { transform: translateX(-14px) scaleY(1.75); }
  .welcome-bracket.right { transform: translateX(14px) scaleY(1.75); }
  .welcome-inner { max-width: 600px; opacity: 1; padding: 0 34px; }
  .welcome-hint { opacity: 1; }
}

/* ---------- Team recommendation ---------- */
.team-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 24px;
  overflow-y: auto;
}
.team-overlay.hidden { display: none; }

.team-overlay-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 30px 32px;
  width: 100%;
  max-width: 620px;
  max-height: 100%;
  overflow-y: auto;
}
.team-overlay-title {
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: 10px;
}
.team-overlay-lead {
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.65;
  margin-bottom: 20px;
}
.team-overlay-lead strong { color: var(--text); }
.team-overlay-loading { color: var(--text-muted); font-size: 0.9rem; padding: 20px 0; }
.team-overlay-error { color: var(--danger); font-size: 0.85rem; margin-bottom: 14px; display: block; }

.team-pick-list { display: flex; flex-direction: column; gap: 10px; }
.team-pick {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  width: 100%;
  text-align: left;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 15px;
  cursor: pointer;
  font-family: inherit;
  color: var(--text);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.team-pick:hover { border-color: var(--border-strong); }
/* Selection is carried by border, tint AND an explicit check mark
   rather than colour alone — colour-only state is invisible to some
   users and ambiguous against the persona avatars, which are already
   coloured. */
.team-pick.picked { border-color: var(--accent); background: var(--accent-soft); }

.team-pick-avatar {
  width: 34px; height: 34px;
  border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-weight: 600;
  font-size: 0.8rem;
  color: #0A0A0A;
  flex-shrink: 0;
}
.team-pick-text { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.team-pick-name { font-size: 0.92rem; font-weight: 600; }
.team-pick-role { color: var(--text-muted); font-weight: 400; font-size: 0.82rem; }
.team-pick-reason { color: var(--text-muted); font-size: 0.84rem; line-height: 1.55; }
.team-pick-check {
  color: var(--accent-text);
  font-size: 1rem;
  width: 18px;
  flex-shrink: 0;
  text-align: right;
}

.team-overlay-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 16px;
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--divider);
}
.team-count {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--text-faint);
  margin-right: auto;
}
.team-count.over { color: var(--danger); }
.btn-accent:disabled { opacity: 0.4; cursor: not-allowed; }

/* ---------- Onboarding progress ---------- */
/* Bounded, visible progress matters more here than it looks: an
   open-ended interview with no end in sight is what makes people
   abandon onboarding. The count is server-driven, so it's honest. */
.onboarding-progress {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
}
.onboarding-progress.hidden { display: none; }
.progress-bar {
  flex: 1;
  height: 4px;
  background: var(--surface-raised);
  border-radius: 999px;
  overflow: hidden;
  max-width: 320px;
}
.progress-bar span {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
  transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
.progress-label {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  color: var(--text-faint);
  white-space: nowrap;
}

/* ---------- Quick answers ---------- */
.quick-answer-row { align-self: flex-start; max-width: 460px; }
.quick-answer-hint {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 9px;
}
/* Selection shown by border and tint together rather than colour alone,
   consistent with the team picker. */
.quick-answer.picked {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.quick-answer-send { margin-top: 10px; align-self: flex-start; }
.quick-answer-send:disabled { opacity: 0.45; cursor: not-allowed; }

/* ---------- Studio ---------- */
.studio-prompt {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  border-radius: 14px;
  padding: 20px 22px;
  box-shadow: var(--shadow);
}
.studio-prompt-label { display: block; font-weight: 600; font-size: 0.95rem; margin-bottom: 10px; }
#studio-goal {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 12px 14px;
  font-family: inherit;
  font-size: 0.9rem;
  resize: none;
}
#studio-goal:focus { outline: none; border-color: var(--accent-border); }
#studio-goal::placeholder { color: var(--text-faint); }
.studio-prompt-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 12px;
}
.studio-prompt-hint { color: var(--text-faint); font-size: 0.8rem; }

.studio-projects {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 14px;
}
.studio-project-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 13px;
  padding: 16px 17px;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.studio-project-card:hover { border-color: var(--accent-border); transform: translateY(-2px); }
.studio-project-name { font-weight: 600; font-size: 0.92rem; margin-bottom: 4px; }
.studio-project-meta { color: var(--text-faint); font-size: 0.75rem; font-family: 'IBM Plex Mono', ui-monospace, monospace; margin-bottom: 12px; }
.mini-progress {
  display: flex; align-items: center; gap: 10px;
}
.mini-progress-bar {
  flex: 1; height: 4px; border-radius: 999px;
  background: var(--surface-raised); overflow: hidden;
}
.mini-progress-bar span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.mini-progress-label { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 0.72rem; color: var(--text-muted); }

.studio-workforce { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
.studio-worker {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  display: flex; align-items: center; gap: 12px;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: border-color 0.15s ease;
}
.studio-worker:hover { border-color: var(--accent-border); }
.studio-worker-name { font-weight: 600; font-size: 0.88rem; }
.studio-worker-role { color: var(--text-muted); font-size: 0.75rem; }
.studio-worker-status {
  margin-left: auto;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.66rem; letter-spacing: 0.05em;
  color: var(--accent-text);
}
.studio-empty { color: var(--text-faint); font-size: 0.85rem; padding: 8px 2px; }

/* ---------- Projects list ---------- */
.project-row {
  display: grid;
  grid-template-columns: 1fr 120px 220px 130px;
  align-items: center;
  gap: 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 13px;
  padding: 16px 18px;
  margin-bottom: 10px;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: border-color 0.15s ease;
}
.project-row:hover { border-color: var(--accent-border); }
.project-row-name { font-weight: 600; font-size: 0.93rem; }
.project-row-goal { color: var(--text-muted); font-size: 0.8rem; margin-top: 3px;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.status-pill {
  justify-self: start;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.68rem; letter-spacing: 0.05em;
  padding: 5px 10px; border-radius: 999px;
}
.status-pill.in-progress { color: var(--accent-text); background: var(--accent-soft); border: 1px solid var(--accent-border); }
.status-pill.done { color: var(--text-muted); background: var(--surface-raised); border: 1px solid var(--border); }
.project-row-lead { display: flex; align-items: center; gap: 9px; color: var(--text-muted); font-size: 0.8rem; }
.lead-avatar {
  width: 26px; height: 26px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-weight: 600; font-size: 0.65rem; color: #0A0A0A; flex-shrink: 0;
  /* A persona with no icon still falls back to a letter, which is why
     the mono font and this size stay. */
  line-height: 1;
}

/* The icon inherits the square's ink, so the tile stays the one coloured
   thing in it. Emoji were tried first and looked cheap for exactly that
   reason: they bring their own palette to a surface that already has
   one, and two colour schemes inside 26 pixels is a sticker rather than
   a symbol.

   Sized against the square rather than in pixels, so the same rule works
   on the 34px avatar in the sidebar and the 22px one in the chat strip
   without a second declaration for each. */
.persona-icon {
  width: 58%; height: 58%;
  display: block;
}
@media (max-width: 1100px) { .project-row { grid-template-columns: 1fr 200px; } .project-row .status-pill, .project-row-lead { display: none; } }

/* ---------- New project brief ---------- */
.brief-grid { display: grid; grid-template-columns: 1fr 300px; gap: 18px; align-items: start; }
@media (max-width: 1000px) { .brief-grid { grid-template-columns: 1fr; } }
.brief-form {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 22px 24px;
  box-shadow: var(--shadow);
}
.brief-label { display: block; font-weight: 600; font-size: 0.88rem; margin: 16px 0 8px; }
.brief-label:first-child { margin-top: 0; }
.brief-optional { color: var(--text-faint); font-weight: 400; font-size: 0.75rem; }
/* .design-form shares every field/label/foot rule below with
   .brief-form but never the card box (background/border/shadow) — it
   lives inside a .project-card, which already provides that, and
   nesting both would double-box it. */
.brief-form input[type="text"], .brief-form input[type="date"], .brief-form select, .brief-form textarea,
.design-form input[type="text"], .design-form input[type="date"], .design-form select, .design-form textarea,
.drawer-body input[type="text"], .drawer-body input[type="date"], .drawer-body select, .drawer-body textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 11px 13px;
  font-family: inherit;
  font-size: 0.88rem;
  resize: vertical;
}
.brief-form select, .drawer-body select { appearance: auto; cursor: pointer; }
.brief-form input:focus, .brief-form select:focus, .brief-form textarea:focus,
.design-form input:focus, .design-form textarea:focus,
.drawer-body input:focus, .drawer-body select:focus, .drawer-body textarea:focus { outline: none; border-color: var(--accent-border); }
.brief-form ::placeholder, .drawer-body ::placeholder { color: var(--text-faint); }
/* Two dates side by side rather than stacked — they are one pairing
   (a start and an end), not two independent fields, and the layout
   should say so. */
.brief-date-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.brief-date-row .brief-label { margin-top: 16px; }
.brief-char-count { text-align: right; font-size: 0.72rem; color: var(--text-faint); margin-top: 4px; }
.brief-foot { display: flex; align-items: center; justify-content: flex-end; gap: 14px; margin-top: 20px; }
.brief-error { color: var(--danger); font-size: 0.82rem; margin-right: auto; }

/* ---------- New Project drawer ---------- */
/* A right-anchored slide-over, not a routed page: whatever view is open
   underneath stays visible and dimmed, because creating a project is a
   quick aside, not a trip away from where you were. */
.drawer-overlay {
  position: fixed;
  inset: 0;
  z-index: 95;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: flex-end;
}
.drawer-overlay.hidden { display: none; }
.drawer-panel {
  width: 420px;
  max-width: 92vw;
  height: 100%;
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  /* Slides in from the edge it's anchored to, rather than fading — the
     motion itself says "this came from off-screen and will go back
     there," which a fade does not. */
  animation: drawerIn 0.25s ease;
}
@keyframes drawerIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
@media (prefers-reduced-motion: reduce) { .drawer-panel { animation: none; } }

.drawer-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 22px 24px 18px; border-bottom: 1px solid var(--border);
}
.drawer-head h2 { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.01em; }
.drawer-subtitle { color: var(--text-muted); font-size: 0.82rem; margin-top: 4px; }
.drawer-close {
  background: none; border: none; color: var(--text-faint); cursor: pointer;
  width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
  border-radius: 8px; flex-shrink: 0;
}
.drawer-close:hover { color: var(--text); background: var(--surface-raised); }
.drawer-close svg { width: 18px; height: 18px; }

.drawer-body { flex: 1; overflow-y: auto; padding: 20px 24px; }
.drawer-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 16px 24px; border-top: 1px solid var(--border);
}

.brief-aside {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  border-radius: 14px;
  padding: 20px 22px;
  box-shadow: var(--shadow);
}
.brief-aside h3 { font-size: 0.95rem; margin-bottom: 12px; }
.brief-aside ol { margin-left: 18px; }
.brief-aside li { color: var(--text-muted); font-size: 0.85rem; line-height: 1.6; margin-bottom: 10px; }

/* ---------- Project detail ---------- */
.project-grid { display: grid; grid-template-columns: 1fr 300px; gap: 18px; align-items: start; }
@media (max-width: 1000px) { .project-grid { grid-template-columns: 1fr; } }
.project-main { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.project-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 20px 22px;
  box-shadow: var(--shadow);
}
.project-card h3 { font-size: 0.98rem; margin-bottom: 14px; }
.task-row {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid var(--divider);
}
.task-row:last-child { border-bottom: none; }
.task-check {
  width: 20px; height: 20px; border-radius: 6px;
  border: 1px solid var(--border-strong);
  background: var(--bg);
  cursor: pointer; flex-shrink: 0; margin-top: 1px;
  display: flex; align-items: center; justify-content: center;
  color: transparent; font-size: 0.75rem;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.task-check.done { background: var(--cta-bg); border-color: var(--cta-bg); color: var(--cta-fg); }
.task-title { font-size: 0.9rem; font-weight: 500; }
.task-row.done .task-title { color: var(--text-faint); text-decoration: line-through; }
.task-desc { color: var(--text-muted); font-size: 0.8rem; margin-top: 3px; line-height: 1.55; }
/* A fixed width, so the avatars line up down the page.
   It had none, so the block was as wide as whoever was in it and
   "Chee Keong / Admin & Compliance" sat a centimetre left of
   "Priya / Finance" on the row above — eight steps across three
   specialists giving three different left edges.

   126px, not 168px. The first attempt was sized for the full role and
   left a visible gap after the short ones, because the widest thing in
   the badge was never the name: "Admin & Compliance" is eighteen
   characters against "Chee Keong"'s ten. The role now has a short form
   here (Admin, People, Support, Online), the longest label is
   "Marketing" at nine, and the column fits it with very little slack. */
.task-owner {
  margin-left: auto; flex-shrink: 0;
  display: flex; align-items: center; gap: 7px;
  width: 126px;
  color: var(--text-muted); font-size: 0.75rem;
  /* It is a button now — clicking it shows the job title — but it must
     not look like one. Eight steps would read as eight buttons. */
  background: none; border: 0; padding: 0;
  font-family: inherit; text-align: left;
  cursor: pointer;
}
.task-owner:hover .task-owner-name { color: var(--text); }
.task-owner:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 8px; }

/* Folded until asked for. The icon and the colour already say who; the
   job title on all eight rows is text you have read by the second one. */
.task-owner .task-owner-role { display: none; }
.task-owner.showing-role .task-owner-role { display: block; }
/* min-width:0 is what actually lets the text shrink — a flex child
   defaults to min-content, so without it the longest role would push the
   column wider than the width above and the alignment would go again. */
.task-owner-who { min-width: 0; }
.task-owner-name, .task-owner-role {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Narrow enough and the role is the first thing worth losing: the name
   and the colour still say who, and 168px of empty column does not. */
@media (max-width: 1000px) {
  .task-owner { width: auto; }
  .task-owner-role { display: none; }
}
/* "In review" is a status, which is exactly what lime is reserved for —
   a signal, never a surface and never identity. The control stays quiet
   until the row is hovered or the flag is actually set, so a plan of
   twelve tasks doesn't read as twelve buttons. */
.task-review {
  flex-shrink: 0; align-self: flex-start;
  margin-top: 1px; padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-faint);
  font: inherit; font-size: 0.7rem; letter-spacing: 0.01em;
  cursor: pointer; opacity: 0;
  transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.task-row:hover .task-review,
.task-review:focus-visible { opacity: 1; }
.task-review:hover { color: var(--text-muted); border-color: var(--border); }
/* Once set it is always visible: a flag only one person can raise is
   worthless if the next person has to hover to discover it. */
.task-review.on {
  opacity: 1;
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent-text);
}
.task-review[disabled] { cursor: default; opacity: 0.5; }
.task-row.in-review { box-shadow: inset 2px 0 0 var(--accent); }
/* A finished task is not awaiting anyone, so the control goes away
   rather than sitting there inviting a contradictory state. */
.task-row.done .task-review { display: none; }
@media (prefers-reduced-motion: reduce) {
  .task-review { transition: none; }
}

/* The escalation offer. Reads as something the team said, not as a
   system notice — it sits in the thread where the reply that prompted it
   sits. Lime edges it because it is a live state waiting on a decision,
   but it stays a surface the eye can skip: an invitation nobody has to
   accept should not shout. */
.msg-offer {
  margin: 8px 0 4px;
  padding: 12px 14px;
  border: 1px solid var(--accent-border);
  border-left-width: 2px;
  border-radius: 10px;
  background: var(--accent-soft);
  display: flex; flex-direction: column; gap: 10px;
  align-items: flex-start;
}
.msg-offer-text {
  color: var(--text-muted);
  font-size: 0.8rem; line-height: 1.55;
}
.msg-offer-btn {
  padding: 7px 13px;
  border-radius: 999px;
  border: 1px solid var(--accent-border);
  background: transparent;
  color: var(--accent-text);
  font: inherit; font-size: 0.78rem; font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.msg-offer-btn:hover:not([disabled]) { background: var(--cta-bg); color: var(--cta-fg); border-color: var(--cta-bg); }
.msg-offer-btn[disabled] { opacity: 0.6; cursor: default; }

/* A considered reply is the SAME specialist, so it is not styled as a
   different kind of message — just a quiet mark that this one was worked
   through rather than answered off the cuff. Making it look like another
   voice would undo the thing the persona layer is for. */
.msg-deep-tag {
  margin-left: 7px; padding: 1px 7px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-text);
  font-size: 0.62rem; font-weight: 500;
  letter-spacing: 0.04em; text-transform: uppercase;
  vertical-align: 1px;
}
.msg-deep { box-shadow: inset 2px 0 0 var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .msg-offer-btn { transition: none; }
}

/* Documents. The unread state carries a price, and that price is the
   whole design: the owner sees what a thing costs before they buy it,
   which is why attaching files needed no budget of its own. */
.files-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.chat-attach {
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; flex-shrink: 0;
  border-radius: 9px;
  color: var(--text-faint);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.chat-attach:hover { color: var(--text); background: var(--surface-raised); }
.chat-attach svg { width: 17px; height: 17px; }
.chat-attach.busy { opacity: 0.5; pointer-events: none; }

/* Files about the business, under the profile they feed. */
.company-files { margin-top: 34px; }
.company-files .file-list { display: flex; flex-direction: column; gap: 8px; margin: 14px 0; }
.company-files .file-add { max-width: 260px; margin-top: 4px; }

.file-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-raised);
}
.file-main { min-width: 0; flex: 1; }
.file-name {
  font-size: 0.8rem; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.file-meta { color: var(--text-faint); font-size: 0.7rem; margin-top: 2px; }
.file-state { font-size: 0.7rem; flex-shrink: 0; }
.file-state.ready { color: var(--accent-text); }
.file-state.failed { color: var(--tethr-danger); cursor: help; }
/* Lime because this is the live one — the state waiting on a decision.
   A file already read is settled and goes quiet. */
.file-read-btn {
  flex-shrink: 0;
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--accent-border);
  background: transparent; color: var(--accent-text);
  font: inherit; font-size: 0.7rem; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.file-read-btn:hover:not([disabled]) { background: var(--cta-bg); color: var(--cta-fg); border-color: var(--cta-bg); }
.file-read-btn[disabled] { opacity: 0.6; cursor: default; }
.file-del {
  flex-shrink: 0; width: 20px; height: 20px;
  border: none; background: transparent;
  color: var(--text-faint); font-size: 1rem; line-height: 1;
  cursor: pointer; opacity: 0;
  transition: opacity 0.15s ease, color 0.15s ease;
}
.file-row:hover .file-del, .file-del:focus-visible { opacity: 1; }
.file-del:hover { color: var(--tethr-danger); }
.file-add {
  display: block; text-align: center;
  padding: 9px; border-radius: 8px;
  border: 1px dashed var(--border-strong);
  color: var(--text-muted); font-size: 0.78rem;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.file-add:hover { border-color: var(--accent-border); color: var(--accent-text); }
.file-note { margin-top: 8px; color: var(--text-muted); font-size: 0.72rem; min-height: 1em; }

@media (prefers-reduced-motion: reduce) {
  .file-read-btn, .file-del, .file-add { transition: none; }
}

/* Sign-in. The licence key used to sit in client-side JavaScript and
   was posted with every request; the server now derives it from the
   session and ignores whatever the browser sends, so there has to be a
   way to actually sign in. Same palette discipline as everywhere else —
   lime only on the one thing you are here to press. */
.signin-wrap {
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; background: var(--bg);
}
.signin-card {
  width: 100%; max-width: 340px;
  display: flex; flex-direction: column;
  padding: 30px 28px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.signin-mark {
  font-size: 1.1rem; font-weight: 600;
  letter-spacing: 0.1em; color: var(--text);
  margin-bottom: 18px;
}
.signin-card h1 { font-size: 1.15rem; font-weight: 600; margin: 0; }
.signin-sub { color: var(--text-muted); font-size: 0.82rem; margin: 5px 0 22px; }
.signin-card label {
  font-size: 0.72rem; color: var(--text-muted);
  margin-bottom: 5px;
}
.signin-card input {
  padding: 9px 11px; margin-bottom: 15px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--bg); color: var(--text);
  font: inherit; font-size: 0.85rem;
}
.signin-card input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.signin-card button {
  margin-top: 4px; padding: 10px;
  border: none; border-radius: 8px;
  background: var(--cta-bg); color: var(--cta-fg);
  font: inherit; font-size: 0.85rem; font-weight: 500;
  cursor: pointer;
}
.signin-card button[disabled] { opacity: 0.6; cursor: default; }
.signin-error {
  min-height: 1.2em; margin-top: 12px;
  color: var(--tethr-danger); font-size: 0.76rem; line-height: 1.5;
}

/* A failure that would otherwise be invisible. Sits above everything
   because the thing it reports is that what is underneath cannot be
   trusted to be current. */
.app-error {
  position: fixed; top: 0; left: 0; right: 0; z-index: 9999;
  padding: 10px 16px;
  background: var(--tethr-danger); color: #fff;
  font-size: 0.8rem; text-align: center;
}

.project-aside { display: flex; flex-direction: column; gap: 14px; }
.overview-item { margin-bottom: 14px; }
.overview-item:last-child { margin-bottom: 0; }
.overview-label {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.65rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint); margin-bottom: 5px;
}
/* Keeps the line breaks the owner typed.

   Success criteria go into a free-text box and are read back one per
   line — that is how the closing report knows there are five things to
   judge rather than one. Shown without this, five separate sentences
   collapse into a single run-on paragraph, so the page disagrees with
   both what she typed and what the ending is built on. */
.overview-value { color: var(--text-muted); font-size: 0.86rem; line-height: 1.6; white-space: pre-line; }

.project-chat-thread { display: flex; flex-direction: column; gap: 12px; max-height: 420px; overflow-y: auto; margin-bottom: 14px; }
/* An empty thread collapsed to one line of grey text, so the card that
   holds the actual work ended halfway up the page and read as a footnote
   under the step description. A floor here gives the conversation the
   room it deserves before anything is in it; the ceiling rises with the
   window so a long thread is not squeezed into 420px on a large screen.
   max() rather than a bare vh so a short window keeps the old height. */
.project-chat-thread { min-height: 340px; max-height: max(420px, 52vh); }
.project-chat-thread.is-empty { justify-content: center; }
.chat-starter { width: 100%; max-width: 440px; margin: 0 auto; }
.chat-starter-lead {
  color: var(--text-muted);
  font-size: 0.86rem;
  line-height: 1.55;
  text-align: center;
  margin: 0 0 16px;
}
.responder-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.responder-chip {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-muted);
  border-radius: 999px;
  padding: 6px 13px 6px 7px;
  font-family: inherit; font-size: 0.8rem;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.responder-chip .lead-avatar { width: 22px; height: 22px; font-size: 0.58rem; border-radius: 999px; }
.responder-chip.active { border-color: var(--accent-border); color: var(--text); background: var(--accent-soft); }
/* The name identifies, the profession is what actually tells you who to
   ask. Set apart rather than run together so a row of them scans as
   roles first — eight names alone is a cast list nobody can keep
   straight. */
.responder-name { font-weight: 500; }
.responder-role {
  color: var(--text-faint);
  font-size: 0.72rem;
  padding-left: 7px;
  margin-left: 1px;
  border-left: 1px solid var(--border);
}
.responder-chip.active .responder-role { color: var(--accent-text); border-left-color: var(--accent-border); }
/* How much has actually been said to each specialist. The number is the
   fastest way to find the conversation you were in the middle of. */
.responder-count {
  margin-left: 2px;
  min-width: 17px; padding: 0 5px;
  border-radius: 999px;
  background: var(--surface-raised);
  color: var(--text-faint);
  font-size: 0.66rem; line-height: 17px; text-align: center;
}
.responder-chip.active .responder-count { background: var(--accent-border); color: var(--cta-fg); }
/* Set apart from the specialists: it is a view of all of them, not
   another one of them. */
.responder-all { padding-left: 13px; margin-left: 4px; border-style: dashed; }
.all-note { margin: 0; padding: 10px 2px 2px; font-size: 0.78rem; }

/* Stacked in the task list, where the column is narrow and two short
   lines read better than one long one. */
.task-owner-who { display: flex; flex-direction: column; line-height: 1.25; }
.task-owner-name { color: var(--text-muted); }
.task-owner-role { color: var(--text-faint); font-size: 0.68rem; }

/* ---------- Team chat ---------- */
/* People first, specialist as a guest. Styled deliberately closer to a
   normal conversation than the project chat is: no responder row to
   pick from, because the default here is talking to each other. */
.team-sub { color: var(--text-faint); font-size: 0.78rem; margin: -6px 0 12px; }
/* A teammate's message. Left-aligned against your own on the right, so a
   room with three people reads as a conversation and not a log. */
.msg-teammate {
  align-self: flex-start;
  background: var(--surface-raised);
  border: 1px solid var(--border);
}
/* Joins and leaves are recorded in the room itself — a specialist should
   never just appear mid-conversation between people. Centred and quiet:
   it is a stage direction, not something anyone said. */
.team-note {
  align-self: center;
  color: var(--text-faint);
  font-size: 0.72rem;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  padding: 4px 0;
  text-align: center;
}
.team-guest-tag {
  margin-left: 7px; padding: 1px 6px;
  border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-text);
  font-size: 0.6rem; letter-spacing: 0.04em; text-transform: uppercase;
}
.team-agent-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.team-agent-row select {
  flex: 1; min-width: 0;
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 7px 10px; font-family: inherit; font-size: 0.8rem; cursor: pointer;
}
.team-agent-in {
  flex: 1; display: flex; align-items: center; gap: 7px;
  font-size: 0.8rem; color: var(--accent-text);
}
.team-error { color: var(--danger); font-size: 0.78rem; min-height: 1em; margin-top: 6px; }
.project-chat-input { display: flex; gap: 10px; }
.project-chat-input textarea {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 11px 14px;
  font-family: inherit; font-size: 0.88rem;
  /* Draggable as well as auto-growing. The script only ever sets a
     height up to its cap and keeps anything larger the person set by
     hand, so the two do not fight over the same property. */
  resize: vertical; min-height: 44px; max-height: 45vh;
  line-height: 1.5;
}
.project-chat-input textarea:focus { outline: none; border-color: var(--accent-border); }

/* ---------- Analytics ---------- */
.analytics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; align-items: start; }
.stat-big { font-size: 1.7rem; font-weight: 700; letter-spacing: -0.02em; }
.stat-sub { color: var(--text-muted); font-size: 0.82rem; margin-top: 3px; }
.spend-bar { height: 8px; border-radius: 999px; background: var(--surface-raised); overflow: hidden; margin-top: 14px; }
.spend-bar span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.persona-stat { display: flex; align-items: center; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--divider); }
.persona-stat:last-child { border-bottom: none; }
.persona-stat-name { font-size: 0.86rem; font-weight: 500; width: 92px; flex-shrink: 0; }
.persona-stat-bar { flex: 1; height: 6px; border-radius: 999px; background: var(--surface-raised); overflow: hidden; }
.persona-stat-bar span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.persona-stat-count { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 0.72rem; color: var(--text-muted); width: 70px; text-align: right; flex-shrink: 0; }
.activity-row { display: flex; align-items: baseline; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--divider); }
.activity-row:last-child { border-bottom: none; }
.activity-who { font-weight: 600; font-size: 0.83rem; flex-shrink: 0; }
.activity-what { color: var(--text-muted); font-size: 0.82rem; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.activity-when { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 0.68rem; color: var(--text-faint); flex-shrink: 0; }

/* ---------- Settings ---------- */
.settings-stack { display: flex; flex-direction: column; gap: 16px; max-width: 640px; }
.settings-row { display: flex; align-items: center; gap: 14px; padding: 13px 0; border-bottom: 1px solid var(--divider); }
.settings-row:last-child { border-bottom: none; }
.settings-row-text { flex: 1; }
.settings-row-title { font-weight: 600; font-size: 0.9rem; }
.settings-row-sub { color: var(--text-muted); font-size: 0.8rem; margin-top: 2px; }
.settings-input {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 10px 13px;
  font-family: inherit; font-size: 0.88rem;
  width: 240px;
}
.settings-input:focus { outline: none; border-color: var(--accent-border); }
.coming-soon-tag {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.62rem; letter-spacing: 0.06em;
  color: var(--text-faint);
  border: 1px solid var(--border);
  border-radius: 999px; padding: 4px 10px;
}
.settings-saved { color: var(--accent-text); font-size: 0.8rem; }

/* ---------- Studio hero ---------- */
.studio-hero { margin-bottom: 26px; }
.studio-hero h2 {
  font-size: 2.5rem;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.15;
}
.studio-hero h2 .dim { color: var(--text-muted); }
.studio-hero p { color: var(--text-muted); font-size: 0.92rem; margin-top: 12px; }

.two-col { display: grid; grid-template-columns: 1.45fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 1150px) { .two-col { grid-template-columns: 1fr; } }

.activity-item {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface-raised);
  border: 1px solid transparent;
  border-radius: 11px;
  padding: 13px 15px;
  margin-bottom: 9px;
}
/* No pointer, no hover lift. This row had every visual signal of being
   clickable and no listener behind it, because there is nothing to click
   through TO — usage_log records a persona, a preview and a time, never
   which conversation it came from. */
.activity-item-text { flex: 1; min-width: 0; }
.activity-item-title { font-weight: 600; font-size: 0.87rem; }
.activity-item-meta { color: var(--text-faint); font-size: 0.74rem; margin-top: 3px; }
.activity-chevron { color: var(--text-faint); flex-shrink: 0; }
.activity-chevron svg { width: 15px; height: 15px; display: block; }

.quick-action {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%;
  background: var(--surface-raised);
  border: 1px solid transparent;
  border-radius: 11px;
  padding: 15px 16px;
  margin-bottom: 9px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.87rem;
  font-weight: 600;
  color: var(--text);
  text-align: left;
  transition: border-color 0.15s ease;
}
.quick-action:hover { border-color: var(--accent-border); }
.quick-action svg { width: 15px; height: 15px; color: var(--text-faint); flex-shrink: 0; }

/* ---------- Knowledge cards ---------- */
/* The dot-in-a-box marker replaces the per-section outline icons: it
   reads as a consistent card marker rather than seven different
   pictograms competing for attention. */
.card-dot {
  width: 26px; height: 26px;
  border-radius: 8px;
  background: var(--icon-bg);
  border: 1px solid var(--accent-border);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.card-dot span { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }

/* ---------- Integrations ---------- */
.integration-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 14px;
}
.integration-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 17px 18px;
  display: flex; flex-direction: column; gap: 22px;
}
.integration-top { display: flex; align-items: center; gap: 12px; }
.integration-logo {
  width: 34px; height: 34px;
  border-radius: 10px;
  background: var(--icon-bg);
  border: 1px solid var(--accent-border);
  color: var(--accent-text);
  display: flex; align-items: center; justify-content: center;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-weight: 600; font-size: 0.8rem;
  flex-shrink: 0;
}
.integration-name { font-weight: 600; font-size: 0.9rem; }
.integration-cat { color: var(--text-muted); font-size: 0.76rem; margin-top: 2px; }
.integration-foot { display: flex; align-items: center; justify-content: space-between; }
.integration-status {
  display: flex; align-items: center; gap: 7px;
  font-size: 0.76rem; font-weight: 600;
  color: var(--text-faint);
}
.integration-status .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-faint); }

.notice-bar {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  border-radius: 12px;
  padding: 14px 17px;
  margin-bottom: 18px;
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* ---------- Charts ---------- */
.chart-svg { width: 100%; height: 210px; display: block; overflow: visible; }
.chart-axis { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 9px; fill: var(--text-faint); }
.chart-grid { stroke: var(--divider); stroke-width: 1; }
.chart-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chart-dot { fill: var(--accent); }
.chart-area { fill: var(--accent); opacity: 0.07; }

.donut-wrap { display: flex; flex-direction: column; align-items: center; gap: 18px; }
.donut-center-value { font-size: 1.55rem; font-weight: 700; letter-spacing: -0.02em; fill: var(--text); }
.donut-center-label { font-size: 0.62rem; fill: var(--text-faint); font-family: 'IBM Plex Mono', ui-monospace, monospace; }
.legend { width: 100%; }
.legend-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-size: 0.8rem; }
.legend-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.legend-name { color: var(--text-muted); flex: 1; }
.legend-val { font-weight: 600; font-size: 0.78rem; }

.eff-row { display: flex; align-items: center; gap: 16px; padding: 9px 0; }
.eff-name { font-size: 0.83rem; width: 150px; flex-shrink: 0; }
.eff-track { flex: 1; height: 7px; border-radius: 999px; background: var(--surface-raised); overflow: hidden; }
.eff-track span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.eff-val { font-size: 0.77rem; color: var(--text-muted); width: 40px; text-align: right; flex-shrink: 0; }

/* ---------- Settings ---------- */
.settings-layout { display: grid; grid-template-columns: 220px 1fr; gap: 16px; align-items: start; }
@media (max-width: 1000px) { .settings-layout { grid-template-columns: 1fr; } }
.settings-nav { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 12px; }
.settings-nav-item {
  display: block; width: 100%; text-align: left;
  background: none; border: none;
  color: var(--text-muted);
  font-family: inherit; font-size: 0.85rem;
  padding: 10px 13px; border-radius: 9px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.settings-nav-item:hover:not(:disabled) { background: var(--surface-raised); color: var(--text); }
.settings-nav-item.active { background: var(--surface-raised); color: var(--accent-text); font-weight: 600; }
.settings-nav-item:disabled { opacity: 0.4; cursor: not-allowed; }

.settings-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 24px 26px; }
.settings-panel h3 { font-size: 1.05rem; font-weight: 600; }
.settings-panel-sub { color: var(--text-muted); font-size: 0.84rem; margin: 5px 0 24px; }
.settings-cols { display: grid; grid-template-columns: 1.5fr 1fr; gap: 30px; align-items: start; }
@media (max-width: 900px) { .settings-cols { grid-template-columns: 1fr; } }
.field { margin-bottom: 17px; }
.field label { display: block; color: var(--text-muted); font-size: 0.78rem; margin-bottom: 7px; }
.field input, .field select {
  width: 100%;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 11px 14px;
  font-family: inherit;
  font-size: 0.87rem;
}
.field input:focus, .field select:focus { outline: none; border-color: var(--accent-border); }
.field input:disabled { color: var(--text-faint); cursor: not-allowed; }

.side-label {
  font-size: 0.83rem; font-weight: 600; margin-bottom: 11px;
}
.side-label:not(:first-child) { margin-top: 24px; }
.choice {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 11px 14px;
  margin-bottom: 8px;
  font-family: inherit; font-size: 0.85rem;
  cursor: pointer;
  transition: border-color 0.15s ease;
}
.choice:hover { border-color: var(--border-strong); }
.choice.active { border-color: var(--accent); }
.choice-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); opacity: 0; }
.choice.active .choice-dot { opacity: 1; }

.danger-box {
  border: 1px solid rgba(248, 113, 113, 0.35);
  border-radius: 12px;
  padding: 15px 16px;
  display: flex; align-items: center; gap: 14px;
}
.danger-text { flex: 1; }
.danger-title { font-size: 0.85rem; font-weight: 600; }
.danger-sub { color: var(--text-muted); font-size: 0.76rem; margin-top: 3px; }
.btn-danger {
  background: none;
  border: 1px solid rgba(248, 113, 113, 0.5);
  color: var(--danger);
  border-radius: 9px;
  padding: 9px 18px;
  font-family: inherit; font-size: 0.82rem; font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}
.btn-danger:hover { background: rgba(248, 113, 113, 0.1); }
.settings-save-row { display: flex; justify-content: flex-end; align-items: center; gap: 14px; margin-top: 26px; }

/* ---------- Task → specialist handoff ---------- */
.task-body {
  min-width: 0;
  flex: 1;
  cursor: pointer;
  border-radius: 8px;
  padding: 4px 8px;
  margin: -4px -8px;
  transition: background 0.15s ease;
}
.task-body:hover, .task-body:focus-visible { background: var(--surface-raised); outline: none; }
.task-body:focus-visible { box-shadow: 0 0 0 1px var(--accent-border); }

/* The prompt stays hidden until hover so a seven-task plan doesn't read
   as seven competing calls to action, but the row is still keyboard
   reachable and the cursor change signals it's clickable. */
.task-ask-hint {
  display: inline-block;
  margin-top: 5px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--accent-text);
  opacity: 0;
  transition: opacity 0.15s ease;
}
.task-body:hover .task-ask-hint,
.task-body:focus-visible .task-ask-hint { opacity: 1; }
.task-row.done .task-body:hover { background: transparent; }
.task-row.done .task-ask-hint { display: none; }
.task-owner-off { color: var(--text-faint); text-decoration: line-through; }

/* ---------- Task detail ---------- */
/* One step of a plan, on its own page. Same two-column shape as the
   project it came from — familiar on arrival rather than a screen that
   has to be relearned — but the wide column holds one step's worth of
   work instead of the whole plan. */
/* The overview card's heading and its one action, on a line. */
.overview-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
}
.overview-head h3 { margin-bottom: 0; }
.details-edit { margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--divider); }
.details-edit input, .details-edit textarea {
  width: 100%;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text); border-radius: 9px;
  padding: 9px 11px; font: inherit; font-size: 0.86rem;
  margin-bottom: 12px; resize: vertical;
}
.details-edit input:focus, .details-edit textarea:focus {
  outline: none; border-color: var(--accent-border);
}

.task-page { display: grid; grid-template-columns: 1fr 300px; gap: 18px; align-items: start; }
@media (max-width: 1000px) { .task-page { grid-template-columns: 1fr; } }

/* The step page is a brief with a conversation under it, and the
   conversation is where the work actually happens — so it takes the
   room that is left rather than stopping at a fixed height with dead
   space beneath it.
   .main and .view are already a height-constrained flex column, so this
   only completes the chain down to the thread. min-height: 0 at every
   level because a flex or grid child defaults to min-height: auto and
   refuses to shrink below its own content, which is what turns "fill
   the space" into "overflow the page".
   align-items moves from start to stretch so the main column receives
   the row's full height, and the row is named explicitly: grid rows are
   auto-sized by default, so a taller container would otherwise leave
   the extra height unused and nothing would appear to happen. */
/* The step page does not scroll; the conversation inside it does.
   .view is overflow-y: auto for every other page, and a scrollable flex
   parent leaves its children free to settle at content height rather
   than being made to fill — which is why #task-body sat at 703 inside a
   900px view with the gap below it. Taking the scroll away removes the
   ambiguity: task-body must fit exactly, and #task-thread absorbs the
   overflow, which is what a chat page should do anyway. */
#view-task { overflow: hidden; }
#task-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* A very long brief can no longer push the conversation off the bottom,
   because nothing below it can scroll any more. It scrolls inside
   itself instead and gives up at most 40% of the column. */
.task-brief { flex: 0 1 auto; min-height: 0; max-height: 40%; overflow-y: auto; }
/* Folded keeps the row that tells you where the step stands — status,
   due date, owner — and the row that lets you act on it. What goes is
   the paragraph, which is the tall part and the part you have already
   read. */
.task-brief.folded .task-brief-desc,
.task-brief.folded .studio-empty,
.task-brief.folded .task-brief-note { display: none; }
/* Folded, the two surviving rows sit on ONE line rather than stacked:
   status and owner on the left, the actions on the right. Stacked they
   cost 110px for two rows of chrome; side by side they cost 62. */
.task-brief.folded {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 12px 18px;
}
/* Status and owner read as one group on the left; the actions as one
   group on the right. Letting the status row stretch instead put a
   third of the page between "Open" and the person who owns the step,
   which reads as two unrelated things rather than one line about where
   this step stands. The owner's margin-left: auto is what was doing the
   pushing, and it is right when that row owns the full width — only
   wrong here, where it does not. */
.task-brief.folded .task-brief-top { margin-bottom: 0; flex: 0 1 auto; gap: 12px; }
.task-brief.folded .task-brief-who { margin-left: 0; }
.task-brief.folded .task-brief-actions {
  border-top: none; padding-top: 0; margin-top: 0; margin-left: auto;
}

/* ---- Vertical chrome on the step page ----
   The conversation is the page, and everything above it was sized for a
   page you read rather than one you work in. Each of these is small; the
   point is that there were eight of them between the top of the window
   and the first message, and together they were costing about 110px of
   thread. Scoped to #view-task so no other page changes. */
#view-task { padding-bottom: 12px; }
#view-task .chat-header { margin-bottom: 12px; }
#view-task .chat-header-title h1 { font-size: 1.5rem; }
.task-chat-card { padding: 16px 20px; }
.task-chat-card h3 { margin-bottom: 10px; }
.task-chat-card #task-thread { margin-bottom: 10px; }
#task-body > .task-page {
  flex: 1; min-height: 0;
  align-items: stretch;
  grid-template-rows: minmax(0, 1fr);
}
/* Single column: the side column stacks underneath at its own height
   and the main column takes what is left. */
@media (max-width: 1000px) {
  #task-body > .task-page { grid-template-rows: minmax(0, 1fr) auto; }
}
.task-chat-card { flex: 1; display: flex; flex-direction: column; min-height: 0; }
/* Overrides the shared .project-chat-thread ceiling, which is right for
   the project chat — a card among several on a scrolling page — and
   wrong here, where the thread IS the page. The floor stays so a short
   window still gives the conversation somewhere to live. */
.task-chat-card #task-thread { flex: 1; min-height: 220px; max-height: none; }
/* align-self and min-height belong on the same rule as the display, so
   the cascade cannot separate them: an earlier block setting min-height
   and a later one re-declaring the layout is exactly how a stretch
   quietly stops applying. */
.task-page-main { display: flex; flex-direction: column; gap: 12px; min-width: 0; min-height: 0; align-self: stretch; }
/* Scrolls inside itself, because the page around it no longer scrolls.
   With eight steps in the list the last two were simply cut off at the
   viewport edge and unreachable — the page scroll had been carrying
   them, and taking it away for the conversation took them with it.
   min-height: 0 so the column can be shorter than its own content;
   without it a grid item refuses to shrink and overflows instead. */
.task-page-side { display: flex; flex-direction: column; gap: 18px; min-width: 0; min-height: 0; overflow-y: auto; }

.task-brief-top {
  display: flex; align-items: center; gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.task-brief-who { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.task-brief-desc { color: var(--text-muted); font-size: 0.9rem; line-height: 1.65; }
.task-brief-note {
  margin-top: 12px; padding: 10px 12px;
  border-radius: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.8rem; line-height: 1.55;
}

/* Said in words, not inferred from a coloured edge. A row in a list has
   no room for a label; a page does, and "Waiting on you" is the state
   most worth being unambiguous about. */
.task-status {
  padding: 4px 11px; border-radius: 999px;
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.02em;
  border: 1px solid var(--border);
  color: var(--text-muted);
}
.task-status.done { background: var(--cta-bg); border-color: var(--cta-bg); color: var(--cta-fg); }
.task-status.review { background: var(--accent-soft); border-color: var(--accent-border); color: var(--accent-text); }

.task-brief-actions {
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-top: 18px; padding-top: 16px;
  border-top: 1px solid var(--divider);
}
.task-brief-actions .btn-quiet.on {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent-text);
}

/* Previous / next, so a plan can be walked end to end without going
   back up to the list between every step. Kept as a spread row with
   empty spans holding the ends, so "Next" stays right-aligned on the
   first step where there is no "Previous" beside it. */
/* The previous/next row was removed from the step page. It cost the
   conversation a row and a gap for something the numbered jump list in
   the side column already does — and does better, since it shows where
   the step sits rather than only what is adjacent to it. Both used the
   same data-step handler, so nothing needed rewiring. */

/* The rest of the plan, as a jump list. This is what keeps the page from
   being a dead end: you can see where the step sits without leaving it,
   and move without a round trip through the project. */
.step-jump-list { display: flex; flex-direction: column; gap: 2px; }
.task-side-goal {
  color: var(--text-muted); font-size: 0.82rem; line-height: 1.6;
  padding-bottom: 14px; margin-bottom: 12px;
  border-bottom: 1px solid var(--divider);
}
.step-jump {
  display: flex; align-items: flex-start; gap: 10px;
  width: 100%; text-align: left;
  padding: 8px 10px; border-radius: 8px;
  background: transparent; border: 1px solid transparent;
  color: var(--text-muted); font: inherit; font-size: 0.82rem; line-height: 1.45;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.step-jump:hover { background: var(--bg); color: var(--text); }
.step-jump-n {
  flex-shrink: 0;
  width: 18px; height: 18px; border-radius: 5px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg); border: 1px solid var(--border);
  font-size: 0.68rem; font-weight: 600;
  margin-top: 1px;
}
.step-jump-title { min-width: 0; overflow-wrap: anywhere; }
.step-jump.current {
  background: var(--bg); color: var(--text); font-weight: 500;
  border-color: var(--border);
}
.step-jump.current .step-jump-n { background: var(--cta-bg); border-color: var(--cta-bg); color: var(--cta-fg); }
.step-jump.done .step-jump-title { text-decoration: line-through; color: var(--text-faint); }
/* Lime only ever marks a state, never identity — same rule as the plan's
   own In Review row. */
.step-jump.in-review .step-jump-n { background: var(--accent-soft); border-color: var(--accent-border); color: var(--accent-text); }
@media (prefers-reduced-motion: reduce) {
  .step-jump { transition: none; }
}

/* ---------- Handing a step to someone else ---------- */
/* The assignee is now a control, so it has to look like one — but
   quietly. It is the identity line of the step first and a button
   second, and styling it as a button would put a call to action where a
   label belongs. */
.task-brief-who {
  display: flex; align-items: center; gap: 8px;
  margin-left: auto;
  padding: 5px 8px; border-radius: 9px;
  background: transparent; border: 1px solid transparent;
  color: inherit; font: inherit; text-align: left;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.task-brief-who:hover { background: var(--bg); border-color: var(--border); }
.task-who-caret { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-faint); }
.task-brief-who[aria-expanded="true"] { background: var(--bg); border-color: var(--border); }
.task-brief-who[aria-expanded="true"] .task-who-caret { transform: rotate(180deg); }

.assign-panel {
  margin-top: 14px; padding: 16px;
  border-radius: 12px;
  background: var(--bg);
  border: 1px solid var(--border);
}
.assign-lead, .assign-confirm { font-size: 0.88rem; margin-bottom: 12px; }
.assign-list { display: flex; flex-direction: column; gap: 4px; }
.assign-choice {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  padding: 9px 10px; border-radius: 9px;
  background: transparent; border: 1px solid transparent;
  color: inherit; font: inherit; cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.assign-choice:hover { background: var(--surface); border-color: var(--border); }

/* Said before the click that spends it, not after. A handover is a real
   AI call and the daily limit means a mis-click cannot be taken back, so
   the consequences are listed rather than summarised. */
.assign-consequences { list-style: none; margin: 0 0 14px; padding: 0; }
.assign-consequences li {
  position: relative; padding-left: 16px; margin-bottom: 7px;
  color: var(--text-muted); font-size: 0.82rem; line-height: 1.55;
}
.assign-consequences li::before {
  content: ''; position: absolute; left: 3px; top: 8px;
  width: 4px; height: 4px; border-radius: 50%; background: var(--text-faint);
}
.assign-why, .assign-locked {
  color: var(--text-muted); font-size: 0.79rem; line-height: 1.55;
  margin-top: 12px;
}
.assign-locked { margin-top: 0; color: var(--text); font-size: 0.86rem; }
.assign-error { color: var(--danger); font-size: 0.8rem; margin-bottom: 10px; }
.assign-actions { display: flex; gap: 10px; justify-content: flex-end; }

/* A handover is not one side of a conversation — it is the outgoing
   specialist telling the incoming one where things stand, written before
   this thread had anything in it. Full width and edged rather than
   bubbled, so it never reads as the new specialist's opening remark. */
.msg-handover {
  margin: 4px 0 14px;
  padding: 14px 16px;
  border-radius: 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  box-shadow: inset 2px 0 0 var(--border-strong);
  color: var(--text-muted); font-size: 0.85rem; line-height: 1.6;
}
.msg-handover-head {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 8px;
}

/* Previous assignees' threads. Collapsed by default — this is history,
   and history that opens itself competes with the conversation you are
   actually having. */
.task-history-block { border-bottom: 1px solid var(--divider); }
.task-history-block:last-of-type { border-bottom: none; }
.task-history-head {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  padding: 11px 2px;
  background: transparent; border: none;
  color: inherit; font: inherit; cursor: pointer;
}
.task-history-who { font-size: 0.85rem; }
.task-history-count { margin-left: auto; color: var(--text-faint); font-size: 0.75rem; }
.task-history-head[aria-expanded="true"] .task-who-caret { transform: rotate(180deg); }
.task-history-thread { max-height: 420px; margin-bottom: 12px; }
.task-history-note { margin-top: 12px; font-size: 0.76rem; }
@media (prefers-reduced-motion: reduce) {
  .task-brief-who, .assign-choice { transition: none; }
}

/* ---------- Choices ---------- */
/* Three answers hidden inside one sentence have to be read twice before
   they can be answered at all, and then answered by typing. As chips
   they are read once and answered with a tap. The message box below
   stays live — these are the likely answers, not the only allowed ones. */
.msg-choices { margin: 12px 0 2px; }
.msg-choices-q {
  font-size: 0.84rem; font-weight: 600;
  margin-bottom: 9px;
}
.msg-choices-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* What happened, after the fact — "Sent, with your note", or why a
   send was refused. It used to carry a sentence explaining that a tap
   would take your typing along with it, which is a thing you have to
   read and believe BEFORE you dare type. The send button makes the same
   promise by being labelled "Send with note", so the prose went. */
.msg-choices-hint {
  margin: 8px 0 0;
  font-size: 0.78rem;
  color: var(--text-faint, #767C82);
  line-height: 1.5;
}
.msg-choices-hint:empty { display: none; }
.msg-choices-hint.has-note { color: var(--accent, #B9F634); opacity: 0.85; }

/* Selection, shown by border and tint together rather than colour alone,
   for the same reason the quick answers are: a tap is now reversible and
   has to look like a state rather than an event. Deliberately the same
   treatment as .quick-answer.picked — one gesture, one appearance,
   wherever a set of options appears. */
.msg-choice.picked {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-text);
}
.msg-choices-send { margin-top: 10px; display: inline-block; }
.msg-choices-send:disabled { opacity: 0.45; cursor: not-allowed; }
.msg-choices-send[hidden] { display: none; }

.msg-choice {
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  font: inherit; font-size: 0.82rem; line-height: 1.3;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.msg-choice:hover:not(:disabled) {
  border-color: var(--accent-border);
  background: var(--accent-soft);
  color: var(--accent-text);
}
/* Answered. The row stays put with the taken option marked, the same
   rule an answered grid follows — removing it would leave the reply
   above referring to options nobody can see any more. */
.msg-choices.sent .msg-choice { cursor: default; opacity: 0.45; }
.msg-choices.sent .msg-choice.taken {
  opacity: 1;
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent-text);
  font-weight: 600;
}
.msg-choice:disabled { pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .msg-choice { transition: none; }
}

/* ---------- Scrollbars ---------- */
/* The chat thread was drawing the platform's own scrollbar — a light
   grey track and a white thumb sitting inside a dark panel, which is the
   one element on screen that had not been told what theme it is in.
   Themed from the same tokens as everything else, so it follows a theme
   switch rather than needing its own. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-corner { background: transparent; }
/* The transparent border plus padding-box clip is what makes the thumb
   read as a slim pill with breathing room, rather than a bar wedged
   against the panel edge. */
::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--text-faint);
  background-clip: padding-box;
}

/* ---------- Derived cells ---------- */
/* The answer, not a field. A disabled input here would look like
   something you were meant to fill in and were not allowed to; this
   reads as the grid's own output — which is the point, since a grid that
   collects four numbers and leaves the multiplying to you has moved the
   work rather than done it. */
.input-table-out {
  display: block;
  padding: 8px 10px;
  min-width: 70px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: var(--accent-soft);
  color: var(--accent-text);
  font-size: 0.83rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
  min-height: 35px;
}
.input-table-out:empty::before {
  content: '—';
  color: var(--text-faint);
  font-weight: 400;
}
.input-table tfoot td { padding: 8px 6px 0 0; border-top: 1px solid var(--divider); }
.input-table-total {
  font-size: 0.85rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
  padding-left: 10px !important;
}
.input-table-total:empty::before { content: '—'; color: var(--text-faint); font-weight: 400; }
.input-table-total-label {
  color: var(--text-muted); font-size: 0.78rem; font-weight: 600;
  padding-left: 10px !important;
}
.input-table.sent .input-table-out { opacity: 0.8; }

/* ---------- Editing the plan ---------- */
/* A mode, not a permanent set of controls. Eight rows each carrying a
   drag handle, a text field, an insert and a delete is the daunting
   thing this whole direction exists to remove — so the plan stays a
   clean list until you say you want to change it. */
.plan-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.plan-head h3 { margin-bottom: 0; }
.plan-edit-toggle {
  margin-left: auto;
  padding: 5px 12px; border-radius: 999px;
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); font: inherit; font-size: 0.75rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.plan-edit-toggle:hover { color: var(--text); border-color: var(--border-strong); }
.plan-edit-toggle.on {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent-text);
}

.task-row.editing { gap: 8px; align-items: center; }
.task-drag {
  flex-shrink: 0; cursor: grab;
  color: var(--text-faint); font-size: 0.8rem; letter-spacing: -3px;
  padding: 0 4px; user-select: none;
}
.task-drag:active { cursor: grabbing; }
.task-row.dragging { opacity: 0.4; }
.task-edit-title {
  width: 100%;
  padding: 7px 9px; border-radius: 8px;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text); font: inherit; font-size: 0.9rem; font-weight: 500;
}
.task-edit-title:focus { outline: none; border-color: var(--accent-border); }
/* A step already ticked off keeps its strike-through in the list, but the
   field itself must stay readable — struck-through text in a box you are
   trying to type into is unusable. */
.task-row.editing.done .task-edit-title { text-decoration: none; color: var(--text); }
.task-tool {
  flex-shrink: 0;
  width: 26px; height: 26px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); font: inherit; font-size: 0.9rem; line-height: 1;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.task-tool:hover { color: var(--text); border-color: var(--border-strong); }
.task-tool-del:hover { color: var(--danger); border-color: var(--danger); }

/* The row asks before it goes, and says what goes with it. */
.task-row.confirming {
  align-items: center; gap: 10px;
  background: var(--bg);
  border-radius: 10px;
  padding: 12px;
  border-bottom: none;
}
/* Only what the existing .btn-danger does not already cover. It is a
   quiet outlined button defined further up, which is the right
   treatment here too — this just makes it sit in a row and stop being
   clickable while the request is out. */
.task-row.confirming .btn-danger { padding: 7px 14px; font-size: 0.8rem; flex-shrink: 0; }
.btn-danger:disabled { opacity: 0.6; cursor: default; }

.plan-add {
  display: flex; gap: 8px; align-items: center;
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--divider);
}
.plan-add input, .plan-add select {
  padding: 8px 10px; border-radius: 8px;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text); font: inherit; font-size: 0.85rem;
}
.plan-add input { flex: 1; min-width: 0; }
.plan-add select { flex-shrink: 0; max-width: 210px; }
.plan-add input:focus, .plan-add select:focus { outline: none; border-color: var(--accent-border); }
.plan-note { margin-top: 10px; font-size: 0.75rem; }
@media (prefers-reduced-motion: reduce) {
  .plan-edit-toggle, .task-tool { transition: none; }
}

/* ---------- A proposed rewrite ---------- */
/* Offered, never taken. A plan that changes itself because of something
   said in a chat is the wrong shape — the same reason escalation is an
   invitation rather than an action. */
.msg-rewrite {
  margin: 12px 0 4px;
  padding: 14px 16px;
  border-radius: 12px;
  background: var(--bg);
  border: 1px solid var(--accent-border);
}
.msg-rewrite-head {
  font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: 10px;
}
/* Nothing to accept, so nothing to click. Muted rather than removed:
   the specialist did answer, and silently dropping her reply would look
   like the message failed to arrive. */
.msg-rewrite.is-noop { opacity: 0.7; border-style: dashed; }

.msg-rewrite-was {
  color: var(--text-faint); font-size: 0.82rem;
  text-decoration: line-through;
  margin-bottom: 4px;
}
.msg-rewrite-new { font-size: 0.92rem; font-weight: 600; line-height: 1.45; }
.msg-rewrite-desc { color: var(--text-muted); font-size: 0.82rem; line-height: 1.55; margin-top: 5px; }
.msg-rewrite-actions {
  display: flex; gap: 10px; justify-content: flex-end;
  margin-top: 14px;
}
.msg-rewrite.sent { border-color: var(--border); opacity: 0.72; }

/* What you decided, not something you said. Centred and quiet so it
   reads as a mark in the transcript rather than as a turn — though it
   IS your turn as far as the specialist is concerned, which is what
   stops her offering the same rewrite twice. */
.msg-note {
  align-self: center;
  max-width: 90%;
  text-align: center;
  color: var(--text-faint);
  font-size: 0.76rem; line-height: 1.5;
  padding: 4px 0;
}

/* The reset confirmation opens up to say what it actually takes. */
.danger-box.confirming { align-items: flex-start; }
.danger-box.confirming .danger-sub { max-width: 46ch; }
.danger-err { color: var(--danger); margin-top: 6px; }

/* ---------- A step with real detail in it ---------- */
/* The plan is a list. A step whose detail runs to a paragraph must not
   turn the row into one — two lines here, the whole thing on the step's
   own page, which is where you went to read it. */
.task-desc {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* The confirmation borrows .task-desc for its warning, which is short by
   construction and must never be cut off mid-sentence. */
.task-row.confirming .task-desc { display: block; overflow: visible; }

/* On the page, the detail is prose rather than one escaped blob —
   asking a specialist to "make it detailed" and then rendering her
   bullets as literal dashes would make the request look unsupported. */
.task-brief-desc > *:first-child { margin-top: 0; }
.task-brief-desc > *:last-child { margin-bottom: 0; }
.task-brief-desc p { margin: 0 0 10px; }
.task-brief-desc ul, .task-brief-desc ol { margin: 0 0 10px; padding-left: 20px; }
.task-brief-desc li { margin-bottom: 5px; }
.task-brief-desc .msg-h { font-weight: 600; font-size: 0.88rem; margin: 12px 0 6px; }

.task-edit { display: flex; flex-direction: column; }
.task-edit input, .task-edit textarea {
  width: 100%;
  padding: 9px 11px; border-radius: 9px;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text); font: inherit; font-size: 0.88rem; line-height: 1.6;
  margin-bottom: 4px;
}
.task-edit textarea { resize: vertical; min-height: 120px; }
.task-edit input:focus, .task-edit textarea:focus { outline: none; border-color: var(--accent-border); }
.task-edit-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 10px; }
.task-edit-open { margin-left: auto; }

/* The usage line in the top bar. This element did not exist until an
   audit found loadUsageStrip() returning early on every call since the
   app was written — the one number a licence on a monthly cap actually
   wants was being computed and discarded. Styled as quiet text rather
   than as a badge: it is context, and it should not compete with the
   screen it sits above. */
#usage-strip {
  color: var(--text-faint);
  font-size: 0.76rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- The calendar ---------- */
/* Two kinds of thing on one grid, and they must not look alike: a
   project occupies a range and a step falls on a day. Bars behind, dots
   on top — flattening both into "an event" is what makes most calendars
   unreadable. */
.calendar-controls { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; }
.calendar-controls .btn-quiet { padding: 6px 12px; font-size: 0.78rem; }

.sidebar-range { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: var(--bg); border: 1px solid var(--border); }
.sidebar-range-btn {
  padding: 4px 10px; border-radius: 999px;
  background: transparent; border: none;
  color: var(--text-faint); font: inherit; font-size: 0.7rem; font-weight: 500;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.sidebar-range-btn:hover { color: var(--text); }
.sidebar-range-btn.active { background: var(--surface); color: var(--text); }

.cal-month { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; overflow: hidden; }
.cal-weekdays, .cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cal-weekdays > div {
  padding: 10px 8px; text-align: left;
  color: var(--text-faint); font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  border-bottom: 1px solid var(--divider);
}
.cal-cell {
  min-height: 104px;
  padding: 6px;
  border-right: 1px solid var(--divider);
  border-bottom: 1px solid var(--divider);
  display: flex; flex-direction: column; gap: 3px;
  cursor: pointer;
  transition: background 0.15s ease;
}
.cal-cell:nth-child(7n) { border-right: none; }
.cal-cell:nth-last-child(-n+7) { border-bottom: none; }
.cal-cell:hover { background: var(--bg); }
/* Days from the neighbouring months are fetched and drawn, because
   something due on one is real — just dimmed, so the month you asked for
   is still the month you see. */
.cal-cell.outside { opacity: 0.4; }
.cal-daynum { font-size: 0.74rem; color: var(--text-muted); font-weight: 600; }
.cal-cell.today .cal-daynum {
  align-self: flex-start;
  background: var(--accent); color: var(--accent-text);
  border-radius: 999px; padding: 1px 7px;
}

.cal-project {
  display: flex; align-items: center; gap: 6px; justify-content: space-between;
  width: 100%; text-align: left;
  padding: 3px 7px; border-radius: 5px;
  background: var(--accent-soft); border: 1px solid var(--accent-border);
  color: var(--accent-text); font: inherit; font-size: 0.68rem; font-weight: 600;
  cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-project.end { background: transparent; }
/* A project that neither starts nor ends today gets a bar with no words
   in it. Repeating its name on fourteen consecutive cells is noise. */
.cal-project.through { height: 4px; padding: 0; border-radius: 999px; background: var(--accent-border); border: none; }
.cal-project-prog { flex-shrink: 0; opacity: 0.75; font-weight: 500; }

.cal-task {
  display: flex; align-items: center; gap: 6px;
  width: 100%; text-align: left;
  padding: 3px 6px; border-radius: 5px;
  background: transparent; border: 1px solid transparent;
  color: var(--text-muted); font: inherit; font-size: 0.7rem;
  cursor: pointer;
  overflow: hidden;
  transition: background 0.15s ease, color 0.15s ease;
}
.cal-task:hover { background: var(--bg); color: var(--text); border-color: var(--border); }
.cal-task-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.cal-task.done { text-decoration: line-through; opacity: 0.5; }
.cal-task.late { color: var(--danger); font-weight: 600; }
.cal-task.late .cal-dot { background: var(--danger) !important; }
.cal-task.in-review .cal-dot { box-shadow: 0 0 0 2px var(--accent-soft); }

/* Day and week are the same list at different lengths — a week is seven
   days, not a different idea, and building it as one avoids two layouts
   that drift apart. */
.cal-days { display: flex; flex-direction: column; gap: 12px; }
.cal-day {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; padding: 14px 16px;
  display: flex; flex-direction: column; gap: 6px;
}
.cal-day.today { border-color: var(--accent-border); }
.cal-day-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; }
.cal-day-name { font-size: 0.88rem; font-weight: 600; }
.cal-day-rel { color: var(--text-faint); font-size: 0.74rem; }
.cal-day-empty { color: var(--text-faint); font-size: 0.8rem; }
.cal-days .cal-task, .cal-days .cal-project { font-size: 0.82rem; padding: 7px 10px; }

/* ---------- The sidebar's next few days ---------- */
.sidebar-calendar-head { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.sidebar-calendar-more { color: var(--text-faint); font-size: 0.68rem; padding: 4px 0 0; }
/* The chip is drawn at rest, not only on hover. An item that is
   invisible until the pointer finds it does not read as a thing you can
   click, and this is the only route from the sidebar into a project —
   so it was hiding its own affordance.
   Ink-based translucency rather than var(--surface): the widget is a
   lime card with near-black text in BOTH themes, so a theme token here
   would be a light chip under dark text in light mode. These values are
   the same ink the widget already uses for its own muted text. */
button.sidebar-calendar-item {
  display: flex; flex-direction: column; gap: 2px;
  width: 100%; text-align: left;
  padding: 7px 8px; border-radius: 8px;
  background: rgba(23, 24, 26, 0.10);
  border: 1px solid rgba(23, 24, 26, 0.12);
  color: inherit; font: inherit; cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
button.sidebar-calendar-item + button.sidebar-calendar-item { margin-top: 6px; }
button.sidebar-calendar-item:hover {
  background: rgba(23, 24, 26, 0.18);
  border-color: rgba(23, 24, 26, 0.24);
}
.side-cal-title { font-size: 0.76rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-cal-when { color: var(--text-faint); font-size: 0.68rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
button.sidebar-calendar-item.late .side-cal-when { color: var(--danger); }

/* A due date on the step itself. "in 3 days" rather than a date — the
   date matters on a calendar, the distance matters everywhere else. */
.task-due {
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.72rem; font-weight: 600;
}
.task-due.late { border-color: var(--danger); color: var(--danger); }
.brief-dates { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 560px) { .brief-dates { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) {
  .cal-cell, .cal-task, .sidebar-range-btn, button.sidebar-calendar-item { transition: none; }
}

/* ---------- The command palette ---------- */
/* The top bar used to hold a wide "Search anything..." input bound to
   nothing. A narrow trigger that names its own shortcut promises exactly
   what it does, and stops the widest element on the page being the
   emptiest one. */
button.topbar-search {
  display: flex; align-items: center; gap: 8px;
  margin-left: auto;
  padding: 7px 12px;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-faint);
  font: inherit; font-size: 0.8rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
button.topbar-search:hover { color: var(--text); border-color: var(--border-strong); }
/* The shared .topbar-search svg rule above positions the icon
   absolutely at left:13px, which is right for the input variant - the
   icon overlays the field's own padding. This variant is a flex button
   with a real label, so the same rule dropped the magnifier on top of
   the word "Search". Put it back in flow and let the gap space it. */
button.topbar-search svg {
  position: static;
  transform: none;
  width: 15px; height: 15px; flex-shrink: 0;
}
.topbar-search-label { line-height: 1; }
kbd {
  padding: 2px 6px; border-radius: 5px;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-faint);
  font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 0.66rem; line-height: 1.4;
}

.palette-overlay {
  position: fixed; inset: 0; z-index: 400;
  background: rgba(0, 0, 0, 0.55);
  /* Near the top rather than centred: the list grows downward, and a
     box that grows from the middle of the screen moves under the cursor
     as you type. */
  display: flex; justify-content: center; align-items: flex-start;
  padding: 12vh 20px 20px;
}
.palette-overlay.hidden { display: none; }
.palette {
  width: 100%; max-width: 560px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
  overflow: hidden;
  display: flex; flex-direction: column;
  max-height: 70vh;
}
.palette-input-row {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--divider);
}
.palette-input-row svg { width: 17px; height: 17px; color: var(--text-faint); flex-shrink: 0; }
#palette-input {
  flex: 1; min-width: 0;
  background: transparent; border: none; outline: none;
  color: var(--text); font: inherit; font-size: 0.95rem;
}
#palette-input::placeholder { color: var(--text-faint); }

.palette-results { overflow-y: auto; padding: 6px; }
.palette-group {
  padding: 10px 10px 5px;
  color: var(--text-faint);
  font-size: 0.66rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
}
.palette-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  padding: 8px 10px; border-radius: 8px;
  background: transparent; border: none;
  color: var(--text); font: inherit; font-size: 0.86rem;
  cursor: pointer;
}
/* Highlighted by the keyboard, not by hover — the arrow keys are the
   primary way through this, and a mouse passing over must move the same
   selection rather than fight a second one. */
.palette-item.active { background: var(--bg); }
.palette-item.dim { opacity: 0.55; }
.palette-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.palette-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-sub {
  margin-left: auto; padding-left: 12px;
  color: var(--text-faint); font-size: 0.74rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 45%;
}
.palette-empty { padding: 22px 14px; color: var(--text-faint); font-size: 0.84rem; text-align: center; }
.palette-foot {
  display: flex; gap: 16px;
  padding: 9px 14px;
  border-top: 1px solid var(--divider);
  color: var(--text-faint); font-size: 0.7rem;
}
.palette-foot span { display: flex; align-items: center; gap: 5px; }
@media (prefers-reduced-motion: reduce) {
  button.topbar-search { transition: none; }
}

/* ---------- Integrations ---------- */
/* This page used to be twelve cards for services that connected to
   nothing, above a banner saying so. Two now, both real. */
.integration-lead { margin-bottom: 22px; max-width: 62ch; }
.integration-lead h2 { font-size: 1.05rem; margin-bottom: 6px; }
.integration-lead p { color: var(--text-muted); font-size: 0.88rem; line-height: 1.65; }
.integration-scope { margin-top: 26px; }

.integration-link-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 20px 22px;
  margin-bottom: 22px;
}
.feed-intro { color: var(--text-muted); font-size: 0.88rem; margin-bottom: 14px; }
/* Shut, this card is one row: what it is, that it is on, and the two
   things you actually came to do. Everything else - the URL itself, the
   warning about it, replacing and revoking - is behind Show link. */
/* A deliverable, shown in full. A collapsed offer letter is an offer
   letter nobody checks before sending it. */
/* Measuring — the part of a project that outlives its steps.

   A project can have every step done and still not know whether it
   worked: the criteria are about the eight weeks AFTER the work. This is
   where the owner comes back to, so it sits under the plan rather than
   in a conversation, and it is quiet: it will be looked at once a week
   for two months. */
/* Not a warning. Measuring properly takes as long as it takes, and
   the point of saying this is that the target date arriving does not
   make the project look late when it is merely still watching. */
.tracker-overrun {
  margin: 0 0 12px;
  padding: 9px 12px;
  border-left: 2px solid var(--accent-border);
  background: var(--accent-soft);
  color: var(--text-muted);
  font-size: 0.8rem;
  line-height: 1.55;
  border-radius: 0 8px 8px 0;
}

.tracker-panel {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.tracker-panel h4 { margin: 0 0 4px; font-size: 0.92rem; font-weight: 600; }
.tracker-panel .studio-empty { margin: 0 0 14px; max-width: 60ch; }
.tracker { margin-bottom: 18px; }
.tracker-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 6px;
}
.tracker-title { font-size: 0.88rem; font-weight: 600; }
.tracker-progress { font-size: 0.78rem; color: var(--text-faint, #767C82); }
/* A date that has passed with rows still empty is the one state worth a
   colour: the period is over and the answer is not in. */
.tracker-progress.late { color: var(--warning, #D6AA4A); }
.input-table.kept .input-table-keep { opacity: 0.7; }
.input-table-kept-note {
  font-size: 0.78rem;
  color: var(--text-faint, #767C82);
  align-self: center;
}

/* The closing report, at the foot of the plan.

   Placed under the steps rather than in the chat because it is about all
   of them, and because it is a document to keep rather than a message to
   read. Separated by a rule so it does not read as another step. */
.closing-report {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.closing-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.closing-head h4 {
  margin: 0 0 4px;
  font-size: 0.92rem;
  font-weight: 600;
}
.closing-head .studio-empty { margin: 0; max-width: 52ch; }
.closing-again {
  margin-top: 10px;
}

.msg-doc {
  border: 1px solid var(--accent-border);
  border-radius: 12px;
  margin: 10px 0;
  overflow: hidden;
}
.msg-doc-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 14px;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--border);
}
.msg-doc-title { font-size: 0.85rem; font-weight: 600; }
.msg-doc-dl { flex-shrink: 0; font-size: 0.78rem; padding: 6px 12px; }
.msg-doc-dl[disabled] { opacity: 0.7; cursor: default; }
.msg-doc-body { padding: 14px 16px; font-size: 0.85rem; }
.msg-doc-body > *:first-child { margin-top: 0; }

/* A graphic the specialist drew, shown at whatever size fits the chat
   and downloaded at full resolution. The stage is neutral rather than
   themed: the card supplies its own background, and a dark panel behind
   a cream card is the only way to see where one ends. */
.msg-graphic {
  border: 1px solid var(--accent-border);
  border-radius: 12px;
  margin: 10px 0;
  overflow: hidden;
}
.msg-graphic-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 14px;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--border);
}
.msg-graphic-title { font-size: 0.85rem; font-weight: 600; }
.msg-graphic-dl { flex-shrink: 0; font-size: 0.78rem; padding: 6px 12px; }
.msg-graphic-stage {
  display: flex; justify-content: center;
  padding: 16px; background: var(--tethr-black);
}
/* Capped by HEIGHT as well as width, or a Story fills the whole
   conversation and pushes the message that explains it off screen. */
.msg-graphic-canvas {
  max-width: 100%; max-height: 420px;
  width: auto; height: auto;
  border-radius: 6px;
  box-shadow: 0 2px 14px rgba(0, 0, 0, 0.45);
}
.msg-graphic-foot {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px;
  padding: 10px 14px;
  background: var(--surface-raised);
  border-top: 1px solid var(--border);
}
.msg-graphic-opts { display: flex; align-items: center; gap: 6px; }
.msg-graphic-size {
  font: inherit; font-size: 0.74rem;
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent; color: var(--text-secondary);
  cursor: pointer;
}
.msg-graphic-size:hover { border-color: var(--accent-border); color: var(--text-primary); }
.msg-graphic-size.on {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent-text);
  font-weight: 600;
}
.msg-graphic-swatch {
  width: 24px; height: 24px; padding: 0;
  border-radius: 999px; border: 1px solid;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.msg-graphic-swatch i { display: block; width: 8px; height: 8px; border-radius: 999px; }
.msg-graphic-swatch.on { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The line a restart drew. A divider rather than a bubble: it is not
   something anyone said, it is where the specialist starts reading. */
.msg-restart {
  display: flex; align-items: center; gap: 12px;
  margin: 6px 0; color: var(--text-faint);
  font-size: 0.72rem; line-height: 1.5;
}
.msg-restart::before, .msg-restart::after {
  content: ''; flex: 1; height: 1px; background: var(--border);
}
.msg-restart span { flex-shrink: 1; max-width: 46ch; text-align: center; }

/* A reply still being written. The caret sits inline after the last
   character so the eye has somewhere to rest, and stops the moment the
   message is replaced by the saved one. */
.msg-assistant.is-streaming { position: relative; }
.msg-caret {
  display: inline-block;
  width: 0.5em; height: 1em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: var(--accent);
  opacity: 0.85;
  animation: msg-caret-blink 1s steps(1, end) infinite;
}
@keyframes msg-caret-blink { 0%, 50% { opacity: 0.85 } 50.01%, 100% { opacity: 0 } }
@media (prefers-reduced-motion: reduce) {
  .msg-caret { animation: none; }
}

/* What is being built, while it is being built.

   A fenced block is held back until it is closed, which is right — half
   a grid is a syntax error, not a table — but it leaves the reply
   looking stalled: the prose stops mid-thought and nothing moves for the
   ten or fifteen seconds the block takes to arrive. This takes the
   caret's place and says what is coming.

   Deliberately quiet: it is a status line, not a message. The dots are
   the ones the waiting bubble uses, so the wait reads as one thing
   continuing rather than two different animations. */
.msg-activity {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  color: var(--text-muted);
  font-size: 0.82rem;
}
.msg-activity-dots { display: inline-flex; gap: 4px; flex: none; }
/* A wave, not a bounce.

   These borrowed the waiting bubble's typingBounce, which holds still
   from 60% to 100% of its cycle and then jumps — read as three dots
   ticking rather than one movement passing along them. This is a single
   ease-in-out swell with no dead stretch, and the offsets are a third of
   the cycle apart, so the crest travels left to right and arrives back
   at the first dot exactly as it starts again. */
.msg-activity-dots i {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0.3;
  animation: activityWave 1.5s cubic-bezier(0.45, 0, 0.55, 1) infinite;
  will-change: opacity, transform;
}
.msg-activity-dots i:nth-child(2) { animation-delay: 0.5s; }
.msg-activity-dots i:nth-child(3) { animation-delay: 1s; }
@keyframes activityWave {
  0%, 100% { opacity: 0.3; transform: scale(0.8); }
  50% { opacity: 1; transform: scale(1.15); }
}
/* A title can be long, and a status line that wraps to three lines
   pushes the reply around while it is still being read. */
.msg-activity-what {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  .msg-activity-dots i { animation: none; opacity: 0.6; transform: none; }
}

/* A drawing the specialist wrote itself. Same furniture as the card
   above, but the stage has no palette control: the colours are inside
   the drawing. */
.msg-svg {
  border: 1px solid var(--accent-border);
  border-radius: 12px;
  margin: 10px 0;
  overflow: hidden;
}
.msg-svg-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 14px;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--border);
}
.msg-svg-title { font-size: 0.85rem; font-weight: 600; }
.msg-svg-actions { display: flex; align-items: center; gap: 8px; }
.msg-svg-png { flex-shrink: 0; font-size: 0.78rem; padding: 6px 12px; }
.msg-svg-file {
  font: inherit; font-size: 0.74rem;
  padding: 5px 10px; border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent; color: var(--text-secondary);
  cursor: pointer;
}
.msg-svg-file:hover { border-color: var(--accent-border); color: var(--text-primary); }
.msg-svg-stage {
  display: flex; justify-content: center;
  padding: 16px; background: var(--tethr-black);
}
/* The drawing sizes itself to the box. Capped by height too, or a tall
   infographic fills the conversation and pushes off the message that
   explains it. */
.msg-svg-wants {
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px 14px;
  background: var(--surface-raised);
  border-top: 1px solid var(--border);
}
.msg-svg-want {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.msg-svg-want-text { font-size: 0.78rem; color: var(--text-secondary); }
.msg-svg-make {
  font: inherit; font-size: 0.74rem; font-weight: 600;
  padding: 5px 12px; border-radius: 999px;
  border: 1px solid var(--accent-border);
  background: var(--accent-soft); color: var(--accent-text);
  cursor: pointer; flex-shrink: 0;
}
.msg-svg-make[disabled] { opacity: 0.65; cursor: default; }

.msg-svg-stage svg {
  max-width: 100%; max-height: 520px;
  height: auto; width: auto;
  border-radius: 6px;
  box-shadow: 0 2px 14px rgba(0, 0, 0, 0.45);
}

.file-letterhead {
  flex-shrink: 0; font-size: 0.7rem;
  padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--border); background: transparent;
  color: var(--text-faint); font-family: inherit; cursor: pointer;
}
.file-letterhead:hover { border-color: var(--accent-border); color: var(--accent-text); }
.file-letterhead.is-set { border-color: var(--accent-border); color: var(--accent-text); }

.audit-day { margin-top: 16px; }
.audit-day-label {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-faint); margin-bottom: 6px;
}
.audit-row {
  display: grid; grid-template-columns: 56px 130px 1fr; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--border);
  font-size: 0.82rem; align-items: baseline;
}
.audit-row:last-child { border-bottom: none; }
.audit-time { color: var(--text-faint); font-size: 0.75rem; font-variant-numeric: tabular-nums; }
.audit-actor { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.audit-what { color: var(--text-muted); }
@media (max-width: 640px) {
  .audit-row { grid-template-columns: 1fr; gap: 2px; }
}

.settings-locked {
  display: flex; flex-direction: column; gap: 5px;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  font-size: 0.85rem;
}
.settings-locked-why { color: var(--text-muted); font-size: 0.76rem; line-height: 1.45; }

.feed-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.feed-head-text { display: flex; flex-direction: column; gap: 3px; }
.feed-state { font-size: 0.78rem; color: var(--text-muted); }
.feed-head-actions { display: flex; align-items: center; gap: 8px; }
.feed-detail {
  margin-top: 16px; padding-top: 16px;
  border-top: 1px solid var(--border);
}
.feed-url-row { display: flex; gap: 8px; align-items: center; }
#feed-url {
  flex: 1; min-width: 0;
  padding: 9px 12px; border-radius: 9px;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text-muted);
  font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 0.76rem;
}
#feed-url:focus { outline: none; border-color: var(--accent-border); color: var(--text); }
/* A URL that grants access without a login deserves saying so plainly,
   in the place it is handed over rather than in documentation nobody
   opens. */
.feed-warning {
  margin-top: 12px;
  color: var(--text-muted); font-size: 0.79rem; line-height: 1.6;
  max-width: 74ch;
}
.feed-note { margin-top: 10px; color: var(--text-faint); font-size: 0.75rem; line-height: 1.55; max-width: 74ch; }
.feed-actions { display: flex; gap: 10px; margin-top: 16px; }

.integration-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }
.integration-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 18px 20px;
}
.integration-top { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.integration-logo {
  width: 34px; height: 34px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 0.92rem;
  flex-shrink: 0;
}
.integration-name { font-size: 0.92rem; font-weight: 600; }
.integration-cat { color: var(--text-faint); font-size: 0.74rem; margin-top: 1px; }
.integration-steps { margin: 0 0 12px; padding-left: 18px; }
.integration-steps li { color: var(--text-muted); font-size: 0.82rem; line-height: 1.6; margin-bottom: 6px; }
.integration-note { color: var(--text-faint); font-size: 0.75rem; line-height: 1.55; }

/* ---------- Members ---------- */
/* The invite and remove routes have both worked from the start and
   nothing ever called either, so a product sold with up to three seats
   had no way to fill the second one. */
.member-list { display: flex; flex-direction: column; gap: 2px; margin-bottom: 26px; }
.member-row {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 12px; border-radius: 10px;
  border: 1px solid transparent;
}
.member-row:hover { background: var(--bg); }
.member-row.confirming { background: var(--bg); border-color: var(--border); align-items: flex-start; }
.member-avatar {
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-raised); border: 1px solid var(--border);
  font-size: 0.8rem; font-weight: 600; flex-shrink: 0;
}
.member-main { flex: 1; min-width: 0; }
.member-name { font-size: 0.88rem; font-weight: 500; }
.member-sub { color: var(--text-faint); font-size: 0.76rem; margin-top: 2px; line-height: 1.55; }
.member-you {
  margin-left: 6px; padding: 1px 7px; border-radius: 999px;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text-faint); font-size: 0.64rem; font-weight: 600;
}
.member-role {
  padding: 3px 10px; border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.68rem; font-weight: 600;
  text-transform: capitalize; flex-shrink: 0;
}
.member-role.owner { background: var(--accent-soft); border-color: var(--accent-border); color: var(--accent-text); }
/* Invited and not yet joined is a real state and worth its own label —
   otherwise a seat looks filled by someone who has never signed in. */
.member-pending { color: var(--text-faint); font-size: 0.7rem; flex-shrink: 0; }
.member-remove {
  width: 26px; height: 26px; border-radius: 7px;
  background: transparent; border: 1px solid transparent;
  color: var(--text-faint); font: inherit; font-size: 1rem; line-height: 1;
  cursor: pointer; flex-shrink: 0;
  opacity: 0;
  transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.member-row:hover .member-remove, .member-remove:focus-visible { opacity: 1; }
.member-remove:hover { color: var(--danger); border-color: var(--danger); }

.invite-box { border-top: 1px solid var(--divider); padding-top: 22px; }
.invite-box h4 { font-size: 0.92rem; margin-bottom: 12px; }
.invite-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.invite-row input, .invite-row select {
  padding: 9px 11px; border-radius: 9px;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text); font: inherit; font-size: 0.85rem;
}
.invite-row input { flex: 1; min-width: 170px; }
.invite-row input:focus, .invite-row select:focus { outline: none; border-color: var(--accent-border); }
.invite-role-note { color: var(--text-faint); font-size: 0.76rem; line-height: 1.55; margin-top: 10px; max-width: 70ch; }
.invite-result {
  margin-top: 16px; padding: 14px 16px;
  background: var(--bg); border: 1px solid var(--accent-border); border-radius: 11px;
}
.invite-result p { color: var(--text-muted); font-size: 0.82rem; line-height: 1.6; margin-bottom: 10px; max-width: 70ch; }
#invite-link {
  flex: 1; min-width: 0;
  padding: 9px 12px; border-radius: 9px;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-muted);
  font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 0.74rem;
}

/* A sticker sheet, previewed as one sticker.
   The preview is a real aspect ratio rather than a fixed box: a 70x40mm
   label and a 50x25mm one are different shapes and she is choosing
   between them by eye. The PDF behind the button is the true size. */
.msg-label { margin: 12px 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.msg-label-preview {
  margin: 12px auto; width: min(320px, 90%); padding: 4.5%;
  border-radius: 3px; box-sizing: border-box;
  display: flex; flex-direction: column; gap: 2px;
  box-shadow: 0 1px 3px rgba(0,0,0,.14);
}
.msg-label-brand { font-family: Georgia, 'Times New Roman', serif; font-style: italic; font-weight: 700; font-size: 12px; }
.msg-label-item { font-weight: 700; font-size: 14px; margin-top: 1px; }
.msg-label-line { font-size: 9px; line-height: 1.25; opacity: .9; }
.msg-label-footer { font-size: 9px; font-weight: 700; margin-top: auto; }
.msg-label-note { margin: 0 12px 12px; font-size: 12px; color: var(--muted); }
.msg-label-preview.pal-cream { background: #FBF4E8; color: #3A2A20; }
.msg-label-preview.pal-cream .msg-label-brand,
.msg-label-preview.pal-cream .msg-label-footer { color: #B5563A; }
.msg-label-preview.pal-sage { background: #F2F4EC; color: #22271F; }
.msg-label-preview.pal-sage .msg-label-brand,
.msg-label-preview.pal-sage .msg-label-footer { color: #55703A; }
.msg-label-preview.pal-blush { background: #FBF0F0; color: #3A2328; }
.msg-label-preview.pal-blush .msg-label-brand,
.msg-label-preview.pal-blush .msg-label-footer { color: #B04A55; }
.msg-label-preview.pal-slate { background: #F1F3F6; color: #1F262E; }
.msg-label-preview.pal-slate .msg-label-brand,
.msg-label-preview.pal-slate .msg-label-footer { color: #3C5A78; }
.msg-label-preview.pal-ink { background: #FFFFFF; color: #1A1A1A; }

/* What the product could not do when this thread began.
   Sits under the step's own buttons, beside "Start again", because the
   fix is that button and a notice that does not point at its remedy is
   just bad news. */
.task-stale-notice {
  margin: 8px 0 0; padding: 8px 10px;
  border-left: 3px solid var(--accent); border-radius: 0 6px 6px 0;
  background: var(--soft); color: var(--muted);
  font-size: 13px; line-height: 1.45;
}

/* A specialist already on the team, shown in the picker when filling a
   free slot. Picked, and not unpickable: the server refuses a save that
   drops one, so the card must not look like it could be tapped off. */
.team-pick.held { cursor: default; opacity: 0.72; }
.team-pick.held:hover { background: inherit; }
.team-pick.held .team-pick-reason { font-style: italic; }
