/* Tartini AI — components. All design values come from tokens.css. */

* { box-sizing: border-box; }

/*
 * `hidden` HAS TO WIN, and on its own it does not.
 *
 * The attribute is not magic: it works through ONE user agent rule, `[hidden] { display: none }`,
 * whose specificity is (0,1,0) — exactly what a bare class selector scores. That is a TIE, and an
 * author stylesheet beats the user agent's on a tie. So every `.card { display: flex }` in this
 * repository silently cancelled `hidden` on the elements wearing it, and nothing reported an error:
 * the attribute was set, the property was there, the element was on screen.
 *
 * It cost four visible defects — an account menu that was open before it was clicked and could not
 * be closed, a usage meter that drew an empty box until `/api/account` answered (and forever when
 * billing is not configured), a Query Workspace that left its editor showing underneath the message
 * meant to replace it, and a "Published view" link offered on a dashboard that has never been
 * published.
 *
 * This is deliberately `!important` and deliberately global. `hidden` means the element is not
 * relevant, so there is no design in which one must be both hidden and displayed; a per-class
 * `[hidden]` guard beside every `display` would be the same fact written down N times, and the
 * defects above are all what happens the one time somebody forgets.
 *
 * That is not a hypothetical: this trap had already been found and patched LOCALLY five separate
 * times — `.qw [hidden]`, `.modal-back[hidden]`, `.range-menu[hidden]`, `.dbe-actions [hidden]` and
 * `.ins-field[hidden]`, each written by somebody who had just watched a control they had hidden
 * stay on screen. Those five are deleted; this is the one place the rule lives now.
 */
[hidden] { display: none !important; }

html, body { height: 100%; }
body {
  margin: 0;
  background: var(--app-bg);
  color: var(--text);
  font: var(--type-body-size)/var(--type-body-line) var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--foreground); text-decoration: none; }
a:hover { text-decoration: underline; }
/* A link with no class of its own inside running text is prose, so it is underlined at rest: purple is
   no longer what marks a link (tokens.css). `:where()` keeps it at zero specificity. */
:where(p, li, dd, td, blockquote) a:where(:not([class])) { text-decoration: underline; text-decoration-color: color-mix(in oklab, currentColor 35%, transparent); text-underline-offset: 3px; }

/*
 * ONE FOCUS RING, for everything a keyboard can reach. `:where()` keeps its specificity at zero, so a
 * component that draws its own focus state still wins; this is the floor, never the ceiling.
 */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus-ring-color);
  outline-offset: 2px;
}

/* ---- App shell: the main rail + main ------------------------------------
   `shell.js` draws the rail (brand · main nav · Recent conversations · project switcher) into
   #app-sidebar and the top bar (the account menu) into main; `shell.css` styles their contents.
   `body.detail-mode` — every page that carries Tartini AI — shows the main shell COLLAPSED to its
   icon rail, with the Tartini AI panel beside it and the work surface on the right (shell.js forces
   the rail collapsed on entry there; shell.css hides the shell's own top bar). */
.app-shell { display: flex; min-height: 100vh; }
#app-sidebar { flex: 0 0 var(--sidebar-w); }

/* ---- Moving between pages -------------------------------------------------
   CROSS-DOCUMENT VIEW TRANSITIONS: both documents of a same-origin navigation opt in here, and the main
   rail (below) and the Tartini AI panel (conversation.css) each carry a stable `view-transition-name`,
   so they stay in place while the work surface changes instead of the whole page blanking and
   redrawing. A browser without the feature navigates exactly as before. Nothing is animated for a
   person who asked for reduced motion. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

.sidebar {
  width: var(--sidebar-w);
  background: var(--tartini-sidebar-bg);
  border-right: 1px solid var(--border);
  /* shadcn's Sidebar: 8px around each group, so an item's text starts 16px from the edge. */
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  position: sticky;
  top: 0;
  height: 100vh;
  /* The same rail on every page (see "Moving between pages" above). */
  view-transition-name: tartini-rail;
  /* A `view-transition-name` makes the rail a stacking context, and so is the Tartini AI panel beside
     it (conversation.css), which comes later in the page and so painted over the rail's own menus: the
     project switcher opened from the icon rail disappeared behind the panel. The rail sits above the
     panel and the page's sticky bars (70) and below the assistant overlay (89/90) and every modal (100+). */
  z-index: 85;
}

/* ---- A page that is ONE card: an invitation, the GitHub return ------------------------------
   The shadcn canvas (2026-10-09): the sidebar's gray ground, the wordmark, and one centred Card —
   its title 20/28, its words muted, its buttons full width under them. Every state of the page is the
   same card with other words, so a page that loads, refuses or succeeds never changes shape. */
.t-standalone { margin: 0; min-height: 100vh; background: var(--sidebar); color: var(--foreground); }
.t-standalone__main { display: flex; flex-direction: column; align-items: center; gap: 32px; min-height: 100vh; padding: 56px 24px; box-sizing: border-box; }
.t-standalone__logo { display: block; width: auto; height: 26px; }
.t-standalone__card { width: 380px; max-width: 100%; display: flex; flex-direction: column; gap: 24px; padding: 24px; box-sizing: border-box; font-size: 14px; line-height: 20px; }
.t-standalone__card.tartini-card:hover, .t-standalone__card.tartini-card:focus-within { border-color: var(--tartini-border); box-shadow: var(--shadow-card); }
.t-standalone__head { display: grid; justify-items: center; gap: 8px; text-align: center; }
.t-standalone__icon { display: inline-flex; width: 24px; height: 24px; }
.t-standalone__icon svg { width: 24px; height: 24px; }
.t-standalone__icon.is-ok { color: var(--tartini-green); }
.t-standalone__icon.is-wait { color: var(--muted-foreground); }
.t-standalone__icon.is-warn { color: var(--tartini-orange); }
.t-standalone__title { margin: 0; font-size: 20px; line-height: 28px; font-weight: 600; letter-spacing: -0.01em; }
.t-standalone__desc { margin: 0; color: var(--muted-foreground); }
.t-standalone__desc b, .t-standalone__desc strong { color: var(--foreground); font-weight: 600; }
.t-standalone__desc code, .t-standalone__desc .mono { padding: 1px 5px; border-radius: 4px; background: var(--accent); color: var(--foreground); font-family: var(--font-mono); font-size: 13px; }
.t-standalone__actions { display: grid; gap: 8px; }
.t-standalone__actions .t-btn { width: 100%; }
.t-standalone__hint { margin: 0; font-size: 12px; line-height: 18px; color: var(--muted-foreground); text-align: center; }
.t-standalone__card .load-state { justify-content: center; padding: 8px 0; }

/* ---- The cookie choice ----------------------------------------------------
   Lives HERE and not in `landing.css` because this sheet is the one every page already loads, so
   `consent.js` is a single script tag anywhere it is needed rather than a stylesheet decision too.
   Both buttons are the same size and weight (`t-btn--sm`, consent.js): refusing has to be exactly as
   easy as agreeing — Reject is a bordered button beside Accept, never a bare text link. */
.ck-banner {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 9999;
  margin: 0 auto; max-width: 880px;
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 14px 16px 14px 20px; border-radius: var(--radius-lg);
  background: var(--background); border: 1px solid var(--border);
  box-shadow: var(--shadow-pop);
}
.ck-copy { margin: 0; flex: 1; min-width: 260px; font-size: 13.5px; line-height: 1.5; color: var(--text-2); }
.ck-copy a { color: var(--foreground); font-weight: 500; text-decoration: underline; text-underline-offset: 3px; }
.ck-actions { display: flex; gap: 8px; flex: none; }
@media (max-width: 560px) {
  .ck-banner { gap: 12px; }
  .ck-actions { width: 100%; }
  .ck-btn { flex: 1; }
}

/* ---- THE BRAND: the wordmark, whole, at every width ----------------------
   The logo sets the product's name itself, so the rail no longer types "Tartini AI" beside it. The
   collapsed rail shows the SAME wordmark, smaller — there is no monogram to swap in, because one
   letter is not the logo and a narrow rail is not a reason to invent a second mark. */
.brand { display: flex; align-items: center; gap: 11px; min-height: 48px; padding: 8px; box-sizing: border-box; }
.brand:hover { text-decoration: none; }
.brand-word { display: block; width: 129px; height: auto; }
.brand-text { display: flex; flex-direction: column; gap: 4px; }
.brand-sub { font-size: 10.5px; font-weight: 600; letter-spacing: 0.1em; color: var(--muted); text-transform: uppercase; }

.nav { display: flex; flex-direction: column; gap: 4px; }
/* SidebarMenuButton: "flex h-8 w-full items-center gap-2 rounded-md p-2 text-sm hover:bg-sidebar-accent
   data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium" (shadcn's Sidebar). */
.nav-item {
  position: relative; display: flex; align-items: center; gap: 8px;
  min-height: 32px; padding: 6px 8px; border-radius: var(--radius-control);
  color: var(--foreground); font-weight: 400; font-size: 14px; line-height: 20px;
  cursor: pointer; transition: background 0.12s ease, color 0.12s ease;
}
.nav-item .nav-ico { color: var(--muted-foreground); }
.nav-item:hover { background: var(--accent); color: var(--foreground); text-decoration: none; }
.nav-item:hover .nav-ico { color: var(--foreground); }
/*
 * THE SELECTED ITEM: shadcn's active SidebarMenuButton — the sidebar accent ground and medium weight;
 * the rail's purple is the logo and the current project's mark (the 2026-10-09 restyle removed the 2×16px
 * purple edge). `aria-current="page"` is the state; `.active` is kept for anything written against it.
 */
.nav-item[aria-current="page"], .nav-item.active {
  background: var(--accent); color: var(--foreground); font-weight: 500;
}
.nav-item[aria-current="page"] .nav-ico, .nav-item.active .nav-ico { color: var(--foreground); }
/* A project destination with no project open: shown, explained, and not a link. */
.nav-item[aria-disabled="true"] { color: var(--tartini-text-muted); cursor: not-allowed; }
.nav-item[aria-disabled="true"]:hover { background: transparent; color: var(--tartini-text-muted); }
.nav-ico { display: inline-flex; width: 16px; height: 16px; flex: none; }
.nav-ico svg { width: 16px; height: 16px; }
.nav-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Collapsible sidebar --------------------------------------------------
   A slim icon rail (toggle in the header) frees width for the page. Width is driven by the
   --sidebar-w token so #app-sidebar and .sidebar collapse together; shell.js persists the state in
   localStorage. */
.side-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.side-collapse {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex: none; padding: 0;
  border: 0; background: transparent; color: var(--muted-foreground);
  border-radius: var(--radius-control); cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.side-collapse:hover { background: var(--accent); color: var(--foreground); }
.side-collapse svg { width: 16px; height: 16px; transition: transform 0.16s ease; }
/* NB: do NOT transition flex-basis/width here — they resolve from the --sidebar-w
   custom property, and a transition on a var-driven value makes Chrome "swallow"
   the change (the rail stays wide). The collapse is instant by design. */

/* The collapsed rail — desktop only (the ≤820px top-bar layout owns small screens). */
@media (min-width: 821px) {
  body.sidebar-collapsed { --sidebar-w: 66px; }
  /*
   * A TARTINI AI PAGE starts with the rail collapsed — before shell.js has drawn it, so the empty rail's
   * placeholder is already the icon rail's width and nothing shifts when it arrives. `rail-expanded` is
   * the person opening it on this page (shell.js); the next such page starts collapsed again.
   */
  body.detail-mode:not(.rail-expanded) { --sidebar-w: 66px; }
  /* shadcn's icon rail: 32px squares, 8px apart; the project's mark, a separator, the nav. */
  body.sidebar-collapsed .sidebar { padding: 16px 8px 8px; align-items: center; gap: 8px; }
  body.sidebar-collapsed .side-head { flex-direction: column; gap: 14px; }
  body.sidebar-collapsed .brand { justify-content: center; padding: 0; }
  /* The whole lockup, fitted to the rail — not a different mark. */
  body.sidebar-collapsed .brand-word { width: 46px; }
  body.sidebar-collapsed .brand-sub { display: none; }
  body.sidebar-collapsed .nav { width: 100%; align-items: center; gap: 8px; }
  body.sidebar-collapsed .nav-item { justify-content: center; width: 32px; height: 32px; min-height: 32px; padding: 0; gap: 0; }
  body.sidebar-collapsed .nav-label { display: none; }
  body.sidebar-collapsed .side-collapse svg { transform: rotate(180deg); }
}

.main { flex: 1 1 auto; min-width: 0; }
.container { max-width: 1180px; margin: 0 auto; padding: 30px 40px 90px; }

/* ---- Hero (Set up tracking) --------------------------------------------- */
.hero {
  border-radius: 20px;
  padding: 30px 34px 32px;
  background: var(--sidebar);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  margin-bottom: 34px;
}
.hero h1 { font-size: 32px; font-weight: 700; letter-spacing: -0.6px; margin: 16px 0 8px; color: var(--text); }
.hero-desc { color: var(--text-2); font-size: 14.5px; max-width: 640px; margin: 0 0 22px; line-height: 1.55; }

/* ---- Buttons ------------------------------------------------------------- */
button, .btn {
  font: inherit; cursor: pointer; border: none; border-radius: var(--radius-sm);
  transition: filter 0.12s ease, background 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease;
}
/* Safe default so no button is ever unstyled; explicit classes below win. */
button { background: var(--surface); color: var(--text-2); font-weight: 500; padding: 8px 16px; }
button:hover { filter: brightness(0.98); }
button:disabled { opacity: 0.55; cursor: not-allowed; }

/*
 * A BUTTON THAT OWNS ITS BOX CENTRES WHAT IS IN IT — ONE PLACE, NOT TWELVE.
 *
 * `padding: 9px 16px` above is the right default for a button shaped by its LABEL. A button that
 * declares its own width/height inherits it anyway, and with `box-sizing: border-box` a 34px square
 * then has a content box TWO PIXELS WIDE. Its icon is laid out in that two-pixel box and overflows
 * to one side, which is how the 34×34 refresh button drew its icon eight pixels right of centre and
 * the 34px pager drew its digit three pixels low.
 *
 * Half of these already remembered `padding: 0` and half did not, which is the tell: it is not a
 * property each component should have to know about. So every button whose size is its own decision
 * resets it here and centres on both axes, and the next one added cannot forget.
 *
 * Components below may still refine this — `.kebab` wants its 4px hit area, `.pg` wants room for a
 * three-digit page — and they win on order, because this rule exists to beat the bare `button`
 * default, not to overrule a component that made a real decision.
 */
.pg, .kebab, .refresh-btn, .side-collapse,
.ask-send-btn, .sc-send, .wb-send, .wb-thumb-x, .wev-remove {
  padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
/* checklist "Start" (bare .small) reads as the primary action */
button.small:not(.ghost) { background: var(--primary); color: var(--primary-foreground); border-color: transparent; }
button.small:not(.ghost):hover { background: var(--primary-hover); }
/*
 * THE OLDER BUTTON CLASSES, RESTYLED IN PLACE. `.btn-primary`, `.btn-pub`, `.btn-add` look exactly like
 * `.t-btn--primary`; `.ghost`, `.btn-ghost`, `.btn-outline` exactly like `.t-btn--outline` — same
 * tokens, same 36px (32px `.sm`) box, no glow. The markup keeps its class names (JS and tests read
 * them); new markup uses `.t-btn`.
 */
.btn-primary {
  background: var(--primary); color: var(--primary-foreground); font-weight: 500; font-size: 14px; line-height: 20px;
  /* `justify-content` matters the moment a caller gives this button a fixed width — without it a
     lone glyph sits against the left padding instead of in the middle. */
  padding: 8px 16px; border-radius: var(--radius-control); display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  box-shadow: none;
}
.btn-primary:hover { background: var(--primary-hover); text-decoration: none; }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.ghost, button.ghost {
  background: var(--background); border: 1px solid var(--border); color: var(--foreground);
  font-weight: 500; padding: 7px 16px; border-radius: var(--radius-control); box-shadow: var(--shadow-xs);
}
.ghost:hover { background: var(--accent); }
button.small, .ghost.small { padding: 6px 13px; font-size: 12.5px; border-radius: 8px; }
/* Approve & land is a code-review action that adds to the codebase -> orange */
button.approve { background: var(--primary); color: var(--primary-foreground); font-weight: 500; padding: 8px 16px; border-radius: var(--radius-control); box-shadow: none; }
button.approve:hover { background: var(--primary-hover); }
button.reject { background: var(--background); border: 1px solid var(--border); color: var(--foreground); font-weight: 500; padding: 7px 16px; border-radius: var(--radius-control); }
button.reject:hover { background: var(--accent); }

.field { display: block; font-size: 13px; color: var(--muted); margin-bottom: 7px; font-weight: 500; }
input[type="text"] {
  width: 100%; background: var(--white); border: 1px solid var(--border);
  border-radius: var(--radius-control); padding: 8px 12px; font: inherit; font-size: 14px; color: var(--text);
}
input[type="text"]:focus { outline: none; border-color: var(--ring); box-shadow: var(--focus-ring); }

/* ---- Segmented control --------------------------------------------------- */

.seg { display: inline-flex; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 3px; gap: 2px; }
.seg span { padding: 5px 13px; border-radius: 7px; font-size: 12.5px; font-weight: 600; color: var(--muted); }
.seg span.on { background: var(--white); color: var(--text); box-shadow: var(--shadow-sm); }

/* ---- Cards --------------------------------------------------------------- */
.card {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 20px 22px; margin-bottom: 18px; box-shadow: var(--shadow-sm);
}
.card h2 { margin: 0 0 14px; font-size: 16px; font-weight: 600; }

/* ---- Tartini primitives ---------------------------------------------------
 *
 * The shared building blocks of the redesign (spec §26–§34, §47), in one place, every value a token.
 *
 * Portions adapted from shadcn-ui/ui, apps/v4/registry/new-york-v4/ui/{button,badge,card,tabs,tooltip,
 * skeleton,scroll-area,switch,toggle-group,toggle,progress,empty,kbd,alert}.tsx (commit db2db46). MIT License, Copyright (c) 2023 shadcn. Full text:
 * app/public/THIRD_PARTY_NOTICES.txt. TRANSLATED: each rule quotes the Tailwind class list it
 * translates, and every utility is written out with Tailwind v4's value (text-sm = 14px/20px,
 * h-9 = 36px, rounded-md = 8px and rounded-xl = 14px in shadcn's theme, shadow-xs =
 * 0 1px 2px 0 rgb(0 0 0 / 0.05), animate-pulse = 2s cubic-bezier(0.4, 0, 0.6, 1)). Changes, and only
 * these: colours are tokens.css (primary → --tartini-purple-600, background/card/popover →
 * --tartini-surface, accent/muted → --tartini-surface-muted, muted-foreground →
 * --tartini-text-secondary, border/input → --tartini-border, destructive → --tartini-red, the focus
 * ring → --focus-ring); the primary hover is --tartini-purple-700 (spec §26: hover is darker, where
 * shadcn lightens to primary/90); a card also darkens its border on hover; a status badge is shadcn's
 * outline badge whose dot or icon carries the status colour (the 2026-10-09 restyle; it was a filled
 * pill), and the `--lg`/`--block`/`--xs` sizes are Tartini's own (spec §33); the progress bar is
 * neutral (DESIGN.md: the Credits meter is neutral) where shadcn's is primary. Every button colour reads the semantic tokens (--primary, --accent, --border), so the
 * landing page can retune them in one place.
 *
 *   .tartini-card        the one card: 1px border, shadcn's shadow-sm, 14px radius;
 *                        [data-slot="card"] + its parts for shadcn's full Card layout
 *   .t-btn               buttons: --primary (purple-600, hover 700) · --outline = --secondary (shadcn
 *                        outline) · --soft (shadcn secondary, neutral) · --ghost · --danger (shadcn
 *                        destructive), sizes --xs / --sm / --lg; .t-icon-btn for icon-only (needs
 *                        aria-label)
 *   .t-badge             status pills, always TEXT + a coloured dot or icon (drawn when absent), never colour alone:
 *                        --success Connected/Ready · --warning Needs review/Warning · --error Error/Failed
 *                        · --neutral Not connected/Disabled · --unknown Unknown · --purple Tartini AI
 *   .t-tabs / .t-tab     shadcn's Tabs, the gray segmented list; active = aria-selected or .on
 *   .t-tooltip           shadcn's tooltip, shown on hover AND focus of its .t-tooltip-trigger
 *   .t-skeleton          shadcn's skeleton                  .t-scroll-area  shadcn's scrollbar
 *   .t-input             shadcn's input          .t-alert  shadcn's alert (+ --destructive, __title, __desc)
 *   .t-switch            shadcn's switch, drawn on a native checkbox
 *   .t-toggle-group      shadcn's outline toggle group (aria-pressed or .on is the chosen item)
 *   .t-progress          shadcn's progress, neutral          .t-kbd  shadcn's kbd
 *   .t-empty             shadcn's empty state (__media, __title, __desc)
 *   .t-sr-only           text for assistive technology only
 */

/* Card: "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm". Change: a
   hover that also darkens the border; the layout lives on [data-slot="card"] so every existing card
   keeps its own. */
.tartini-card {
  border-radius: var(--radius-xl);
  border: 1px solid var(--tartini-border);
  background: var(--tartini-surface);
  color: var(--tartini-text);
  box-shadow: var(--shadow-card);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.tartini-card:hover, .tartini-card:focus-within {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-card-hover);
}
.tartini-card.is-static:hover, .tartini-card.is-static:focus-within { border-color: var(--tartini-border); box-shadow: var(--shadow-card); }
.tartini-card[data-slot="card"] { display: flex; flex-direction: column; gap: 24px; padding: 24px 0; }
/* CardHeader: "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6
   has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6" */
[data-slot="card-header"] {
  container: card-header / inline-size;
  display: grid; grid-auto-rows: min-content; grid-template-rows: auto auto; align-items: start; gap: 8px; padding: 0 24px;
}
[data-slot="card-header"]:has([data-slot="card-action"]) { grid-template-columns: 1fr auto; }
[data-slot="card-header"].border-b { padding-bottom: 24px; }
/* CardTitle: "leading-none font-semibold"; CardDescription: "text-sm text-muted-foreground" */
[data-slot="card-title"] { line-height: 1; font-weight: 600; }
[data-slot="card-description"] { font-size: 14px; line-height: 20px; color: var(--tartini-text-secondary); }
/* CardAction: "col-start-2 row-span-2 row-start-1 self-start justify-self-end" */
[data-slot="card-action"] { grid-column-start: 2; grid-row: 1 / span 2; align-self: start; justify-self: end; }
/* CardContent: "px-6"; CardFooter: "flex items-center px-6 [.border-t]:pt-6" */
[data-slot="card-content"] { padding: 0 24px; }
[data-slot="card-footer"] { display: flex; align-items: center; padding: 0 24px; }
[data-slot="card-footer"].border-t { padding-top: 24px; }

/* Button: "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium
   whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px]
   focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50
   [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", size default
   "h-9 px-4 py-2 has-[>svg]:px-3". A `.t-btn` with no variant keeps a neutral surface (Tartini). */
.t-btn {
  display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center; gap: 8px;
  border: 0; border-radius: var(--radius-sm);
  font-family: var(--font-sans); font-size: 14px; line-height: 20px; font-weight: 500;
  white-space: nowrap; text-decoration: none;
  transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;
  outline: none;
  height: 36px; padding: 8px 16px;
  background: var(--tartini-surface); color: var(--tartini-text);
}
.t-btn:has(> svg) { padding-left: 12px; padding-right: 12px; }
.t-btn:hover { text-decoration: none; filter: none; }
.t-btn svg { pointer-events: none; flex-shrink: 0; width: 16px; height: 16px; }
.t-btn:focus-visible, .t-icon-btn:focus-visible { border-color: var(--ring); box-shadow: var(--focus-ring); }
/* disabled: opacity .5. Changes: `cursor: not-allowed` instead of `pointer-events: none`, so a disabled
   button's title can still say why (a reviewer demo's switched-off Publish). */
.t-btn:disabled, .t-btn[aria-disabled="true"], .t-btn[aria-busy="true"] { opacity: 0.5; cursor: not-allowed; }
/* size sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5"; size lg: "h-10 rounded-md px-6
   has-[>svg]:px-4". `--block` is Tartini's. */
/* size xs (28px) is Tartini's: a row action inside a table, a filter chip. */
.t-btn--xs { height: 28px; gap: 4px; padding: 0 10px; font-size: 12px; line-height: 16px; }
.t-btn--xs:has(> svg) { padding-left: 8px; padding-right: 8px; }
.t-btn--xs svg { width: 14px; height: 14px; }
.t-btn--sm { height: 32px; gap: 6px; padding: 0 12px; }
.t-btn--sm:has(> svg) { padding-left: 10px; padding-right: 10px; }
.t-btn--lg { height: 40px; padding: 0 24px; }
.t-btn--lg:has(> svg) { padding-left: 16px; padding-right: 16px; }
.t-btn--block { width: 100%; }
/* variant default: "bg-primary text-primary-foreground hover:bg-primary/90" */
.t-btn--primary { background: var(--primary); color: var(--primary-foreground); }
.t-btn--primary:hover { background: var(--primary-hover); color: var(--primary-foreground); }
/* variant outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground".
   `--outline` is shadcn's name; `--secondary` is the older one for the same button, so both share
   ONE rule. */
.t-btn--outline, .t-btn--secondary { border: 1px solid var(--border); background: var(--background); color: var(--foreground); box-shadow: var(--shadow-xs); }
.t-btn--outline:hover, .t-btn--secondary:hover { background: var(--accent); color: var(--foreground); }
/* A pressed choice (an amount, a filter): purple edge, the faintest purple ground. */
.t-btn--outline[aria-pressed="true"], .t-btn--secondary[aria-pressed="true"] { border-color: var(--primary); background: var(--brand-tint); box-shadow: inset 0 0 0 1px var(--primary); }
/* variant secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80" — neutral. */
.t-btn--soft { background: var(--secondary); color: var(--secondary-foreground); }
.t-btn--soft:hover { background: color-mix(in oklab, var(--secondary), var(--foreground) 5%); }
/* variant ghost: "hover:bg-accent hover:text-accent-foreground" */
.t-btn--ghost { background: transparent; color: var(--foreground); }
.t-btn--ghost:hover { background: var(--accent); color: var(--foreground); }
/* variant destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20" */
.t-btn--danger { background: var(--tartini-red); color: #fff; }
.t-btn--danger:hover { background: color-mix(in oklab, var(--tartini-red) 90%, transparent); color: #fff; }
.t-btn--danger:focus-visible { box-shadow: 0 0 0 3px color-mix(in oklab, var(--tartini-red) 20%, transparent); }

/* The ONE in-page dialog (page-dialog.js): never a browser-native alert, confirm or prompt. */
.t-dialog { width: min(460px, calc(100vw - 32px)); max-width: none; border: 1px solid var(--tartini-border); border-radius: var(--radius-lg); padding: 20px; background: var(--tartini-surface); color: var(--tartini-text-body); box-shadow: var(--shadow-pop); }
.t-dialog::backdrop { background: rgb(0 0 0 / 0.32); }
.t-dialog__title { margin: 0 0 8px; font-size: 15px; font-weight: 600; color: var(--tartini-text); }
.t-dialog__body p { margin: 0 0 8px; font-size: 13.5px; line-height: 1.5; overflow-wrap: anywhere; }
.t-dialog__field { display: grid; gap: 6px; margin-top: 8px; }
.t-dialog__label { font-size: 12.5px; color: var(--tartini-text-secondary); }
.t-dialog__input { width: 100%; box-sizing: border-box; padding: 8px 10px; border: 1px solid var(--tartini-border); border-radius: 8px; font: inherit; font-size: 13.5px; color: var(--tartini-text); background: var(--tartini-surface); }
.t-dialog__input:focus { outline: 2px solid color-mix(in oklab, var(--tartini-text) 20%, transparent); outline-offset: 1px; }
.t-dialog__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }

/* An icon-only button: Button variant ghost + size icon-sm ("size-8"). */
.t-icon-btn {
  display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--tartini-text);
  transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;
  outline: none;
}
.t-icon-btn:hover { background: var(--accent); color: var(--foreground); filter: none; }
.t-icon-btn svg { pointer-events: none; flex-shrink: 0; width: 16px; height: 16px; }

/* Badge: "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full
   border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap
   transition-[color,box-shadow] [&>svg]:pointer-events-none [&>svg]:size-3". The variants are the
   spec §33 status colours, always with words. */
.t-badge {
  display: inline-flex; width: fit-content; flex-shrink: 0; align-items: center; justify-content: center; gap: 4px;
  overflow: hidden; border-radius: var(--radius-pill); border: 1px solid transparent;
  padding: 2px 8px; font-family: var(--font-sans); font-size: 12px; line-height: 16px; font-weight: 500; white-space: nowrap;
  transition-property: color, box-shadow; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;
}
.t-badge > svg { pointer-events: none; width: 12px; height: 12px; }
.t-badge--lg { padding: 4px 12px; font-size: 13px; line-height: 20px; }
.t-dot { width: 7px; height: 7px; flex: none; border-radius: 50%; background: var(--t-badge-tone, currentColor); }
/* A status badge is shadcn's OUTLINE badge; its status is the colour of its dot or icon (shadcn's
   dashboard-01 pattern), never a filled pill. A status badge built with neither gets the dot here, so
   no caller loses the colour. */
.t-badge--success, .t-badge--warning, .t-badge--error, .t-badge--neutral { background: var(--background); color: var(--foreground); border-color: var(--border); }
.t-badge--success { --t-badge-tone: var(--tartini-green); }
.t-badge--warning { --t-badge-tone: var(--tartini-orange); }
.t-badge--error { --t-badge-tone: var(--tartini-red); color: var(--tartini-red-text); }
.t-badge--neutral { --t-badge-tone: var(--tartini-text-muted); color: var(--muted-foreground); }
.t-badge:is(.t-badge--success, .t-badge--warning, .t-badge--error, .t-badge--neutral) svg { color: var(--t-badge-tone); }
/* Drawn only when the badge carries no mark of its own — a dot or an icon anywhere inside it (an icon in
   a wrapper of its own, as Connections draws one, counts). */
.t-badge:is(.t-badge--success, .t-badge--warning, .t-badge--error, .t-badge--neutral):not(:has(.t-dot, svg))::before {
  content: ""; width: 6px; height: 6px; flex: none; border-radius: 50%; background: var(--t-badge-tone);
}
/* UNKNOWN is its own state, not a paler "not connected": dashed, and its dot is hollow. */
.t-badge--unknown { background: var(--tartini-unknown-bg); color: var(--tartini-unknown-text); border: 1px dashed var(--tartini-unknown-border); }
.t-badge--unknown .t-dot { background: transparent; box-shadow: inset 0 0 0 1.5px currentColor; }
.t-badge--purple { background: var(--tartini-purple-50); color: var(--tartini-purple-700); }
/* The two neutral badges a list or a header uses for a KIND rather than a status. */
.t-badge--outline { background: var(--background); color: var(--foreground); border-color: var(--border); }
.t-badge--secondary { background: var(--secondary); color: var(--secondary-foreground); }

/* Input: "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs
   transition-[color,box-shadow] outline-none placeholder:text-muted-foreground md:text-sm
   focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50". Change: a white ground.
   page-dialog.js has always put this class on its prompt input. */
.t-input {
  width: 100%; min-width: 0; height: 36px; padding: 4px 12px; box-sizing: border-box;
  border: 1px solid var(--input); border-radius: var(--radius-control); background: var(--background);
  font-family: var(--font-sans); font-size: 14px; line-height: 20px; color: var(--foreground); outline: none;
  box-shadow: var(--shadow-xs);
  transition-property: color, box-shadow, border-color; transition-duration: 150ms;
}
.t-input::placeholder { color: var(--muted-foreground); }
.t-input:focus-visible { border-color: var(--ring); box-shadow: var(--focus-ring); }
.t-input:disabled { opacity: 0.5; cursor: not-allowed; }

/* Alert: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3
   text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4
   [&>svg]:translate-y-0.5 [&>svg]:text-current", variant default "bg-card text-card-foreground",
   destructive "bg-card text-destructive". */
.t-alert {
  position: relative; display: grid; grid-template-columns: 0 1fr; align-items: start; row-gap: 2px;
  padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius-panel);
  background: var(--card); color: var(--foreground); font-size: 14px; line-height: 20px;
}
.t-alert:has(> svg) { grid-template-columns: 16px 1fr; column-gap: 12px; }
.t-alert > svg { width: 16px; height: 16px; transform: translateY(2px); color: currentColor; }
.t-alert > :not(svg) { grid-column: 2; min-width: 0; }
.t-alert--destructive { color: var(--destructive); }
/* AlertTitle: "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight"; AlertDescription:
   "col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed". */
.t-alert__title { grid-column: 2; min-height: 16px; font-weight: 500; letter-spacing: -0.01em; }
.t-alert__desc { grid-column: 2; display: grid; justify-items: start; gap: 4px; font-size: 14px; color: var(--muted-foreground); }
.t-alert__desc p { margin: 0; line-height: 1.625; }

/* TabsList: "inline-flex h-9 w-fit items-center justify-center rounded-lg bg-muted p-[3px]
   text-muted-foreground" — shadcn's default, the gray segmented list (the 2026-10-09 restyle; it was the
   line variant with a purple underline). */
.t-tabs {
  display: inline-flex; width: fit-content; align-items: center; justify-content: center;
  height: 36px; padding: 3px; gap: 0; box-sizing: border-box;
  border-radius: var(--radius-panel); background: var(--accent); color: var(--muted-foreground);
}
/* TabsTrigger: "inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md
   border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground
   transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px]
   focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50
   data-[state=active]:bg-background data-[state=active]:shadow-sm"; active is `aria-selected="true"` (or
   the older `.on`). Changes (the canvas): 12px side padding, and an inactive trigger at 60% foreground. */
.t-tab {
  position: relative; display: inline-flex; height: calc(100% - 1px); flex: 1; align-items: center; justify-content: center;
  gap: 6px; border-radius: var(--radius-sm); border: 1px solid transparent; padding: 4px 12px; margin: 0;
  font-family: var(--font-sans); font-size: 14px; line-height: 20px; font-weight: 500; white-space: nowrap;
  color: color-mix(in oklab, var(--foreground) 60%, transparent); background: transparent; cursor: pointer;
  transition-property: color, box-shadow, background-color; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;
}
.t-tab:hover { color: var(--foreground); filter: none; }
.t-tab:focus-visible { border-color: var(--ring); box-shadow: var(--focus-ring); }
.t-tab:disabled { pointer-events: none; opacity: 0.5; }
.t-tab svg { pointer-events: none; flex-shrink: 0; width: 16px; height: 16px; }
.t-tab[aria-selected="true"], .t-tab.on { color: var(--foreground); background: var(--background); box-shadow: var(--shadow-card); }

/* TooltipContent: "z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md
   bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95"; its Arrow
   "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground". TooltipProvider's
   delayDuration is 0, so it shows at once — on hover AND on keyboard focus of its trigger (spec §47).
   Placed above its trigger (Radix's default side). */
.t-tooltip-trigger { position: relative; }
.t-tooltip {
  position: absolute; z-index: 50; left: 50%; bottom: calc(100% + 8px);
  width: max-content; max-width: 240px; border-radius: var(--radius-sm);
  background: var(--tartini-text); padding: 6px 12px;
  font-family: var(--font-sans); font-size: 12px; line-height: 16px; font-weight: 400; text-wrap: balance; color: #fff;
  pointer-events: none; visibility: hidden; transform: translateX(-50%);
}
.t-tooltip::after {
  content: ""; position: absolute; left: 50%; top: 100%; width: 10px; height: 10px;
  border-radius: 2px; background: var(--tartini-text);
  transform: translate(-50%, calc(-50% - 2px)) rotate(45deg);
}
.t-tooltip-trigger:hover > .t-tooltip, .t-tooltip-trigger:focus-within > .t-tooltip {
  visibility: visible; animation: t-tooltip-in 150ms ease;
}
/* tw-animate-css `enter` with fade-in-0 and zoom-in-95. */
@keyframes t-tooltip-in { from { opacity: 0; transform: translateX(-50%) scale3d(0.95, 0.95, 0.95); } }

/* Skeleton: "animate-pulse rounded-md bg-accent" (animate-pulse: `pulse 2s cubic-bezier(0.4, 0, 0.6, 1)
   infinite`, `50% { opacity: .5 }`). */
.t-skeleton { animation: t-skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; border-radius: var(--radius-sm); background: var(--tartini-surface-muted); }
@keyframes t-skeleton-pulse { 50% { opacity: 0.5; } }

/* Switch: Root "peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent
   shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px]
   focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary
   data-[state=unchecked]:bg-input"; Thumb "pointer-events-none block size-4 rounded-full bg-background
   ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)]
   data-[state=unchecked]:translate-x-0". Change: drawn on a native `<input type="checkbox" class="t-switch">`
   (its ::before is the thumb), so the control keeps the browser's own keyboard and form behaviour. On is
   purple: it is a selected state. */
input.t-switch {
  -webkit-appearance: none; appearance: none; position: relative; display: inline-flex; flex-shrink: 0; align-items: center;
  width: 32px; height: 18.4px; margin: 0; box-sizing: border-box; border: 1px solid transparent; border-radius: var(--radius-pill);
  background: var(--input); box-shadow: var(--shadow-xs); cursor: pointer; outline: none;
  transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;
}
input.t-switch::before {
  content: ""; position: absolute; left: 0; top: 50%; width: 16px; height: 16px; border-radius: 50%;
  background: var(--background); transform: translate(0, -50%); pointer-events: none;
  transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
input.t-switch:checked { background: var(--primary); }
input.t-switch:checked::before { transform: translate(calc(100% - 2px), -50%); }
input.t-switch:focus-visible { border-color: var(--ring); box-shadow: var(--focus-ring); }
input.t-switch:disabled { cursor: not-allowed; opacity: 0.5; }

/* ToggleGroup, variant outline: root "flex w-fit items-center rounded-md shadow-xs"; item "min-w-0 flex-1
   shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 border-l-0
   first:border-l" over Toggle outline "border border-input bg-transparent hover:bg-accent h-9 px-2 min-w-9
   data-[state=on]:bg-accent data-[state=on]:text-accent-foreground". Chosen = aria-pressed or `.on`. */
.t-toggle-group { display: flex; width: fit-content; align-items: center; border-radius: var(--radius-control); box-shadow: var(--shadow-xs); }
.t-toggle-group > * {
  position: relative; display: inline-flex; min-width: 36px; flex: 1 0 auto; align-items: center; justify-content: center; gap: 8px;
  height: 36px; padding: 0 12px; margin: 0; box-sizing: border-box;
  border: 1px solid var(--input); border-left-width: 0; border-radius: 0; background: transparent; box-shadow: none;
  font-family: var(--font-sans); font-size: 14px; line-height: 20px; font-weight: 500; color: var(--foreground); cursor: pointer;
  transition-property: color, background-color, box-shadow; transition-duration: 150ms;
}
.t-toggle-group > :first-child { border-left-width: 1px; border-radius: var(--radius-control) 0 0 var(--radius-control); }
.t-toggle-group > :last-child { border-radius: 0 var(--radius-control) var(--radius-control) 0; }
.t-toggle-group > :only-child { border-radius: var(--radius-control); }
.t-toggle-group > :hover { background: var(--accent); }
.t-toggle-group > :focus-visible { z-index: 10; outline: none; border-color: var(--ring); box-shadow: var(--focus-ring); }
.t-toggle-group > [aria-pressed="true"], .t-toggle-group > .on { background: var(--accent); color: var(--accent-foreground); }

/* Progress: Root "relative h-2 w-full overflow-hidden rounded-full bg-primary/20"; Indicator "h-full w-full
   flex-1 bg-primary transition-all". Changes: 6px and NEUTRAL (the border colour and the foreground), the
   look the Credits meters already had (DESIGN.md: the account's Credits meter is neutral). The bar is the
   `<i>` child; its width is the value. */
.t-progress { position: relative; display: block; width: 100%; height: 6px; overflow: hidden; border-radius: var(--radius-pill); background: var(--border); }
.t-progress > i { display: block; height: 100%; border-radius: inherit; background: var(--foreground); transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1); }

/* Empty: "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6
   text-center text-balance md:p-12"; EmptyMedia (icon) "flex size-10 shrink-0 items-center justify-center
   rounded-lg bg-muted text-foreground [&_svg]:size-6"; EmptyTitle "text-lg font-medium tracking-tight";
   EmptyDescription "text-sm/relaxed text-muted-foreground". Changes (the canvas): 8px gaps, 40px 24px. */
.t-empty {
  display: flex; min-width: 0; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  padding: 40px 24px; border: 1px dashed var(--border); border-radius: var(--radius-panel); text-align: center; text-wrap: balance;
}
.t-empty__media { display: flex; width: 40px; height: 40px; flex-shrink: 0; align-items: center; justify-content: center; border-radius: var(--radius-panel); background: var(--accent); color: var(--foreground); }
.t-empty__media svg { width: 24px; height: 24px; }
.t-empty__title { margin: 0; font-size: 18px; line-height: 28px; font-weight: 500; letter-spacing: -0.01em; color: var(--foreground); }
.t-empty__desc { margin: 0; font-size: 14px; line-height: 1.625; color: var(--muted-foreground); }

/* Kbd: "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm
   bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none [&_svg]:size-3". */
.t-kbd {
  display: inline-flex; width: fit-content; min-width: 20px; height: 20px; align-items: center; justify-content: center; gap: 4px;
  padding: 0 4px; box-sizing: border-box; border-radius: 4px; background: var(--accent); color: var(--muted-foreground);
  font-family: var(--font-sans); font-size: 12px; line-height: 1; font-weight: 500; pointer-events: none; user-select: none;
}
.t-kbd svg { width: 12px; height: 12px; }

/* ScrollArea's scrollbar, on the native one: ScrollBar "flex touch-none p-px transition-colors
   select-none" + vertical "h-full w-2.5 border-l border-l-transparent", its thumb "relative flex-1
   rounded-full bg-border". */
.t-scroll-area { scrollbar-width: thin; scrollbar-color: var(--tartini-border) transparent; }
.t-scroll-area::-webkit-scrollbar { width: 10px; height: 10px; }
.t-scroll-area::-webkit-scrollbar-track { background: transparent; }
.t-scroll-area::-webkit-scrollbar-thumb {
  border-radius: var(--radius-pill); background-color: var(--tartini-border);
  border: 1px solid transparent; border-left-width: 2px; background-clip: padding-box;
}

@media (prefers-reduced-motion: reduce) {
  .t-btn, .t-icon-btn, .t-badge, .t-tab, .t-switch, .t-switch::before, .t-toggle-group > *, .t-progress > i { transition: none; }
  .t-skeleton, .t-tooltip-trigger:hover > .t-tooltip, .t-tooltip-trigger:focus-within > .t-tooltip { animation: none; }
}

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

/* ---- Runs table ---------------------------------------------------------- */
.runs { width: 100%; border-collapse: collapse; }
.runs th {
  text-align: left; padding: 12px 20px; font-size: 11.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted);
  border-bottom: 1px solid var(--border); background: #fcfcfd;
}
.runs td { padding: 15px 20px; border-bottom: 1px solid var(--border-soft); font-size: 14px; vertical-align: middle; }
.runs tr:last-child td { border-bottom: none; }
.runs tr.clickable { cursor: pointer; transition: background 0.1s ease; }
.runs tbody tr.clickable:hover { background: var(--accent); }
.runs td.target { font-family: var(--mono); font-size: 13px; color: var(--text-2); max-width: 380px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.runs td.when { color: var(--muted); font-size: 13px; white-space: nowrap; }

/* Destructive secondary button (run detail: Remove tracking) */
button.danger { background: var(--background); color: var(--red-text); border: 1px solid var(--red-border); font-weight: 500; padding: 7px 16px; border-radius: var(--radius-control); box-shadow: var(--shadow-xs); }
button.danger:hover { background: var(--red-soft); }
.empty { color: var(--muted); font-size: 14px; padding: 20px; text-align: center; }

/* ---- Status badges (dot + label) ---------------------------------------- */
.status-badge {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px; line-height: 16px; font-weight: 500; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--background); color: var(--foreground); white-space: nowrap;
}
.status-badge .sdot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.status-badge.pending, .status-badge.running { color: var(--foreground); }
.status-badge.pending .sdot, .status-badge.running .sdot { background: var(--foreground-2); }
.status-badge.awaiting_review { color: var(--foreground); }
.status-badge.awaiting_review .sdot { background: var(--amber); }
.status-badge.approved, .status-badge.published { color: var(--foreground); }
.status-badge.approved .sdot, .status-badge.published .sdot { background: var(--blue); }
.status-badge.changes_requested { color: var(--foreground); }
.status-badge.changes_requested .sdot { background: var(--amber); }
/* Landed: the code really is in the base branch. Same green as verified — it is a good terminal
   state — but its own class, because "landed" and "verified" are different facts. */
/* "Update from <base>" in the workbench toolbar, beside the other draft-level actions. It is sized
   like its neighbours (Discard, Publish diff) rather than like a panel chip, because it sits among
   them; the note beside it carries the fact ("Behind main by 3 commits") the button acts on. */
/* The action on top, the fact it acts on underneath it in small type — the button is what the user
   clicks, and "Behind main by 4 commits" is the reason it is there. */
.wb-base { display: inline-flex; flex-direction: column; align-items: center; gap: 3px; }
.wb-base-note { font-size: 11px; font-weight: 600; color: var(--muted); white-space: nowrap; line-height: 1.2; }
.wb-base-note.ok { color: var(--green-text); }
.wb-base-note.warn { color: var(--amber-text); }
.wb-base-note.err { color: var(--red-text); }
.wb-base-btn { background: var(--background); color: var(--foreground); font-weight: 500; font-size: 14px; line-height: 20px; padding: 7px 16px;
  border: 1px solid var(--border); border-radius: var(--radius-control); cursor: pointer; white-space: nowrap; box-shadow: var(--shadow-xs); }
.wb-base-btn:hover:not(:disabled) { background: var(--accent); }
.wb-base-btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* Behind the base, or a delivery that failed: the action is the one that needs attention. */
.wb-base-btn.warn { border-color: var(--amber); }
.wb-base-btn.warn:hover:not(:disabled) { background: var(--accent); }

.status-badge.landed { color: var(--foreground); }
.status-badge.landed .sdot { background: var(--green); }
.status-badge.verified { color: var(--foreground); }
.status-badge.verified .sdot { background: var(--green); }
.status-badge.failed { color: var(--red-text); }
.status-badge.failed .sdot { background: var(--red); }
.status-badge.rejected { color: var(--muted-foreground); }
.status-badge.rejected .sdot { background: var(--placeholder); }
.status-badge.no_changes { color: var(--muted-foreground); }
.status-badge.no_changes .sdot { display: none; }
.status-badge.inactive { color: var(--muted-foreground); }
.status-badge.inactive .sdot { background: var(--placeholder); }
/* GitHub PR open (approved locally, awaiting merge on GitHub) — a pending state. */
.status-badge.pr_open { color: var(--foreground); }
.status-badge.pr_open .sdot { background: var(--amber); }

/* ---- Mode chips ---------------------------------------------------------- */
/* The run's mode: an outline badge (the 2026-10-09 canvas); a removal keeps its destructive words. */
.mode-chip { display: inline-flex; align-items: center; font-size: 12px; line-height: 16px; font-weight: 500; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); color: var(--foreground); background: var(--background); }
.mode-chip.install { color: var(--foreground); background: var(--background); border-color: var(--border); }
.mode-chip.update { color: var(--foreground); background: var(--background); border-color: var(--border); }
.mode-chip.remove { color: var(--red-text); background: var(--background); border-color: var(--border); }

/* ---- Alerts / boxes ------------------------------------------------------ */
/* shadcn's Alert: a neutral card; an error is the destructive variant (red text, no red fill), and a
   success or warning keeps its colour in a small dot before the text. */
.box, .alert { border-radius: var(--radius-panel); padding: 12px 16px; font-size: 14px; margin-bottom: 14px; line-height: 1.5; }
.box.error, .alert.error { background: var(--card); border: 1px solid var(--border); color: var(--destructive); }
.box.info { background: var(--card); border: 1px solid var(--border); color: var(--foreground-2); }
.box.success { background: var(--card); border: 1px solid var(--border); color: var(--foreground); }
.box.warn { background: var(--card); border: 1px solid var(--border); color: var(--foreground); }
.box.success::before, .box.warn::before { content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: 50%; vertical-align: 1px; background: var(--tartini-green); }
.box.warn::before { background: var(--tartini-orange); }
.box code, .hint code { background: var(--accent); padding: 2px 6px; border-radius: 5px; font-family: var(--mono); font-size: 12.5px; }

.hint { color: var(--muted); font-size: 12.5px; margin-top: 8px; }
.mono { font-family: var(--mono); }

/* ---- Run detail: header, meta ------------------------------------------- */
.run-title { display: flex; align-items: center; gap: 11px; }
.run-title h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; margin: 0; }
.meta { display: flex; flex-wrap: wrap; gap: 8px 22px; color: var(--muted); font-size: 12.5px; margin: 8px 0 2px; }
.meta .mono { color: var(--text-2); }
.actions { display: flex; gap: 10px; margin-top: 18px; }
.back { font-size: 13px; color: var(--muted); font-weight: 500; }

/* ---- Loading ------------------------------------------------------------
 *
 * ONE loading state for the whole product. There were four font sizes (12.5 / 13 / 14 / 17px),
 * three layouts and eighteen places with no spinner at all, so "the page is working" looked like a
 * different event depending on which page you were on — and a bare line of text with no motion
 * reads as a finished, empty result rather than as work in progress.
 *
 * Every one of them is this now: a spinner, then a sentence naming what is being fetched. `.sm` is
 * the same thing inside a panel too small for the full padding — a second size, not a second design.
 *
 * It is `.load-state` and not `.loading` because that word was already taken twice, by
 * `.dhealth.loading` and `.wap-badge.loading`. A bare class name here would have reached both and
 * laid out somebody else's component as a centred flex row.
 */
.load-state {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 40px 20px; font-size: 14.5px; font-weight: 500;
  color: var(--muted); line-height: 1.5; text-align: center;
}
.load-state .spinner { margin-right: 0; flex: none; }
.load-state.sm { padding: 16px 14px; font-size: 13px; gap: 8px; }

/* ---- Spinner ------------------------------------------------------------- */
.spinner {
  display: inline-block; width: 15px; height: 15px; vertical-align: -2px;
  border: 2px solid var(--border); border-top-color: var(--border-strong);
  border-radius: 50%; animation: spin 0.8s linear infinite; margin-right: 8px;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- Revalidating (the client-side SWR cache) ---------------------------
 *
 * What the reader is looking at came out of this tab's own cache and is on screen instantly; a
 * quiet check is running behind it. This must never look like a spinner blocking the page — the
 * page is usable, so the chip is small, in the muted colour, and takes no layout of its own.
 */
.swr-chip {
  display: inline-flex; align-items: center; gap: 6px; vertical-align: middle;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.01em; color: var(--muted);
  padding: 2px 8px; border-radius: 999px; background: var(--accent);
  border: 1px solid var(--border-soft); white-space: nowrap;
}
.swr-chip.failed { color: var(--orange, #b45309); }
.swr-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--muted-foreground);
  animation: swr-pulse 1.1s ease-in-out infinite;
}
@keyframes swr-pulse { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .swr-dot { animation: none; opacity: 0.7; }
}

/* ---- Post-land guided checklist ----------------------------------------- */
.checklist { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--white); }
.step { display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 14px; padding: 14px 18px; border-bottom: 1px solid var(--border-soft); }
.step:last-child { border-bottom: none; }

.dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; vertical-align: 0; margin-right: 4px; }
.dot.green { background: var(--green); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.16); }
.dot.red { background: var(--red); box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.14); }
.dot.gray { background: var(--placeholder); }

.counter { font-weight: 700; color: var(--foreground); font-size: 13.5px; }

/* ---- Diff container ------------------------------------------------------ */
#diff { overflow-x: auto; border-radius: 10px; border: 1px solid var(--border); }

/* small screens: collapse the sidebar */
@media (max-width: 820px) {
  .app-shell { flex-direction: column; }
  #app-sidebar { flex-basis: auto; }
  .sidebar { width: 100%; height: auto; position: static; flex-direction: row; align-items: center; flex-wrap: wrap; gap: 12px; }
  .nav { flex-direction: row; flex-wrap: wrap; }
  .container { padding: 22px 18px 60px; }
}

/* ========================================================================
   The create-project conversation (projects.html)
   ======================================================================== */
.ask-card {
  position: relative; max-width: 760px; margin: 0 auto; text-align: left;
  background: var(--white); border: 1px solid var(--border); border-radius: 16px;
  box-shadow: var(--shadow-lg); padding: 18px 20px 14px;
}
/* Live ask-card: a real input + send + source picker wired to /api/ask. */
.ask-card.live { padding: 12px 14px 12px 22px; border-radius: 22px; max-width: 920px; border-color: var(--border); box-shadow: var(--shadow-pop); }
/* ONE ROW: the spark, the question, the send. The design's box is a single line and the source
   picker is a second row that appears only when it has something to choose between. */
.ask-main { display: flex; align-items: center; gap: 12px; }
.ask-spark { color: var(--foreground); display: inline-flex; flex: none; }
.ask-field { flex: 1; min-width: 0; width: 100%; border: 0; outline: 0; background: transparent; font: inherit; font-size: 15.5px; color: var(--text); padding: 10px 0; resize: none; font-family: var(--sans); }
.ask-field::placeholder { color: var(--placeholder); }
.ask-btn { border: 0; cursor: pointer; }
.ask-send-btn { width: 48px; height: 48px; box-shadow: none; border-radius: 50%; background: var(--primary); color: #fff; display: inline-flex; align-items: center; justify-content: center; transition: background 0.12s ease, opacity 0.12s ease; }
.ask-send-btn:hover { background: var(--primary-hover); }
.ask-send-btn:disabled { opacity: 0.5; cursor: default; }
.ask-send-btn svg { width: 22px; height: 22px; }
.ask-answer { position: relative; max-width: 920px; margin: 14px auto 0; text-align: left; background: var(--white); border: 1px solid var(--border); border-radius: 18px; box-shadow: var(--shadow-md); padding: 16px 18px; display: none; }
.ask-answer.show { display: block; }

/*
 * ── THE ASK BOX AS A CONVERSATION ───────────────────────────────────────────────────────────
 *
 * Before a conversation exists this is exactly what it always was: a composer under a greeting, on a
 * page with starting points and cards. `is-chatting` is added the moment there is one, and then the
 * conversation IS the page — the greeting and the pills are for somebody who has not started, and a
 * transcript growing underneath them scrolls itself out of view.
 *
 * The composer is pinned under the transcript with flex `order` rather than by moving nodes: a chat
 * that re-parents its own input loses what is half-typed in it, and the answer card is a live region
 * a screen reader is already following.
 */
.ws-ask { position: relative; }
.ws-ask.is-chatting {
  display: flex; flex-direction: column;
  height: calc(100vh - 150px); min-height: 460px;
  max-width: 920px; margin: 0 auto; text-align: left;
}
.ws-ask.is-chatting .aa-log { order: 1; flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 2px 2px; }
.ws-ask.is-chatting .ask-answer { order: 2; flex: 0 0 auto; margin: 10px 0 0; }
/*
 * THE COMPOSER IS FULL WIDTH, and the `auto` side margins have to go for it.
 *
 * `.ask-card` centres itself with `margin: … auto`, which is right on a page and wrong in a column
 * flex: an auto cross-axis margin both centres the item AND opts it out of `stretch`, so the box
 * collapsed to its content and sat 339px wide in the middle of a 920px conversation.
 */
.ws-ask.is-chatting #askForm {
  order: 3; flex: 0 0 auto;
  width: 100%; max-width: none; align-self: stretch;
  margin: 12px 0 0;
}

/* The transcript. Only ever visible while chatting — an empty log above a greeting is a blank gap. */
.aa-log { display: none; }
.ws-ask.is-chatting .aa-log { display: block; }
.aa-turn { margin: 0 0 14px; }
.aa-turn .aa-who {
  font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 4px;
}
.aa-turn.is-user .aa-who { color: var(--text-2); }
.aa-turn.is-assistant .aa-who { color: var(--foreground); }
.aa-turn .aa-text { font-size: 14px; line-height: 1.6; color: var(--text-2); }
.aa-turn.is-user .aa-text {
  background: var(--accent); border: 1px solid var(--border);
  border-radius: 14px; padding: 10px 13px; color: var(--text);
}

/* The conversation's own header: what it is about, and the way out of it. */
.ws-chat-head { display: none; align-items: center; gap: 10px; margin: 0 0 10px; }
.ws-ask.is-chatting .ws-chat-head { display: flex; }
.wch-title {
  font-size: 13.5px; font-weight: 700; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 70%;
}

/*
 * A SYSTEM LINE IS NOT THE ASSISTANT SPEAKING. It is what the product DID — an invite code redeemed,
 * a project joined — and it is drawn as a plain note rather than in the assistant's voice, because an
 * outcome styled identically to a generated reply starts being read as one.
 */
.aa-turn.is-system .aa-who { color: var(--muted); }
.aa-turn.is-system .aa-text {
  background: var(--accent); border: 1px solid var(--border);
  border-radius: 12px; padding: 9px 12px; color: var(--text-2); font-size: 13.5px;
}

/*
 * ── THE CREATE-PROJECT CONVERSATION, ON THE PROJECTS PAGE ───────────────────────────────────
 *
 * It wears the AI Workspace chat's own classes, so the two surfaces cannot drift apart visually
 * while sharing one conversation store and one durable job underneath. What is different is only
 * what this page is: the conversation IS the page while it is open, and the list of projects it
 * replaces is hidden rather than left underneath it.
 */
.cp-chat { max-width: 920px; margin: 0 auto; }
.cp-chat .ws-ask.is-chatting { height: calc(100vh - 168px); min-height: 440px; }
#projPage.chatting { display: none; }
/* The action card the model does not write: a proposal to confirm, or an outcome to open. */
.cp-card {
  margin-top: 14px; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--accent);
}

@media (max-width: 760px) {
  .ws-ask.is-chatting { height: calc(100vh - 118px); min-height: 380px; }
  .cp-chat .ws-ask.is-chatting { height: calc(100vh - 132px); min-height: 380px; }
}
.ask-answer .aa-head { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; color: var(--foreground); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 8px; }
.ask-answer .aa-body { color: var(--text-2); font-size: 14px; line-height: 1.6; }
/* The Stop button sits with the thinking dots, on the same baseline. `.ai-stop` is the product's ONE
   Stop button — the Tables, Dashboard, Tracking and Workspace assistants all wear it. */
.ask-answer .aa-body .ai-stop { margin-left: 10px; vertical-align: middle; }
.ask-answer .aa-stopnote { margin: 8px 0 0; color: var(--muted); font-size: 12.5px; }
/* The answer's INNARDS are `.wb-md` — paragraphs, headings, rules, lists, quotes, code, tables and
   links all come from the shared assistant-Markdown block below, so this card cannot drift into a
   second definition of what a heading or a link looks like. Only what is genuinely card-specific
   stays here. */
.ask-answer .aa-body .wb-md strong { color: var(--text); }
.ask-answer .aa-body .wb-md a { font-weight: 600; text-underline-offset: 2px; }
.ask-answer .aa-body .wb-md a:hover { color: var(--foreground); }
.ask-answer.err { border-color: var(--red-border); }
.ask-answer.err .aa-head { color: var(--red-text); }
.thinking-dots span { display: inline-block; width: 5px; height: 5px; margin-right: 3px; border-radius: 50%; background: var(--muted-foreground); opacity: 0.4; animation: tdot 1s infinite; }
.thinking-dots span:nth-child(2) { animation-delay: 0.15s; }
.thinking-dots span:nth-child(3) { animation-delay: 0.3s; }
@keyframes tdot { 0%, 60%, 100% { opacity: 0.3; } 30% { opacity: 1; } }

.home-empty { color: var(--muted); font-size: 13px; padding: 6px 2px; line-height: 1.5; }

/* ========================================================================
   Dashboard page
   ======================================================================== */
.select {
  appearance: none; background: var(--white); border: 1px solid var(--border); border-radius: 10px;
  padding: 9px 34px 9px 14px; font: inherit; font-size: 14px; font-weight: 500; color: var(--text); cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.4'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
}

.panel { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px 22px; box-shadow: var(--shadow-sm); }

.dash-empty { text-align: center; padding: 60px 20px; color: var(--muted); }
.dash-empty b { color: var(--text); display: block; font-size: 16px; margin-bottom: 6px; }

/* ========================================================================
   Code review (run.html): the diff and the full-screen preview
   ======================================================================== */
/* "Unified": the pressed toggle (shadcn's Toggle, on). */
.cr-unified { display: inline-flex; align-items: center; height: 32px; box-sizing: border-box; font-size: 14px; font-weight: 500; color: var(--foreground); background: var(--accent); border: 1px solid var(--border); border-radius: var(--radius-control); padding: 0 12px; }

/* Expand-diff button + full-screen diff modal */
/* Expand: shadcn's ghost sm button. */
.diff-expand { display: inline-flex; align-items: center; gap: 6px; height: 32px; box-sizing: border-box; font-size: 14px; font-weight: 500; color: var(--foreground); background: transparent; border: 0; border-radius: var(--radius-control); padding: 0 10px; cursor: pointer; white-space: nowrap; }
.diff-expand:hover { background: var(--accent); color: var(--foreground); }
.wb-phead .diff-expand { margin-left: auto; margin-right: 8px; }

/* Full-screen website preview: the pane expands in place (no reparent, so the
   iframe keeps its state); the browser chrome + stage stretch to fill. */
.wb-preview { position: relative; }   /* positioning context for the Event details bar */
.wb-preview.fullscreen { position: fixed; inset: 0; z-index: 1000; border-radius: 0; }
/* When the bar is open the preview content is inset so the site is never hidden behind it. */
.wb-preview.has-details .wb-pbody, .wb-preview.has-details .wb-preview-controls, .wb-preview.has-details .wb-phead { padding-right: 348px; }
.wb-preview.has-details.details-collapsed .wb-pbody, .wb-preview.has-details.details-collapsed .wb-preview-controls, .wb-preview.has-details.details-collapsed .wb-phead { padding-right: 52px; }
.wb-preview.fullscreen .wb-pbody { display: flex; flex-direction: column; min-height: 0; }
/* The browser area SCROLLS and the iframe is sized to the page CONTENT (same as embedded
   mode — NOT stretched to 100%). So a short page no longer fills the screen with white; the
   neutral background shows below it, reading as "the page ends here", not a broken iframe. */
.wb-preview.fullscreen .wb-browser { flex: 1; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; background: var(--accent); }
/* Every child keeps its natural height (no flex-shrink) so the "When it happens" legend +
   url bar don't get collapsed to make room for the content-height stage — the browser
   scrolls the total instead. (Regression fix: the legend vanished in fullscreen.) */
.wb-preview.fullscreen .wb-browser > * { flex: none; }
.diff-modal { position: fixed; inset: 0; z-index: 1000; background: rgba(15, 23, 42, 0.45); display: grid; place-items: center; padding: 22px; }
.diff-modal-panel { width: 100%; height: 100%; max-width: 1500px; background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow-lg); display: flex; flex-direction: column; overflow: hidden; }
.diff-modal-head { display: flex; align-items: center; gap: 8px; padding: 12px 18px; border-bottom: 1px solid var(--border-soft); flex: none; font-size: 15px; }
.diff-modal-head .dmh-sub { color: var(--muted); font-weight: 500; font-size: 13px; }
.diff-modal-head .dmh-counts { font-size: 12.5px; font-weight: 600; margin-right: 8px; }
.diff-modal-head .dmh-counts .added { color: var(--green-text); }
.diff-modal-head .dmh-counts .removed { color: var(--red-text); }
.diff-modal-x { border: none; background: none; cursor: pointer; font-size: 15px; color: var(--muted); padding: 4px 9px; border-radius: 8px; }
.diff-modal-x:hover { background: var(--surface); color: var(--text); }
.diff-modal-body { flex: 1; overflow: auto; padding: 14px 18px; }

/* ========================================================================
   Instrumentation review workbench (workbench.html) — 3-pane
   ======================================================================== */
.wb { height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
.wb-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 24px; border-bottom: 1px solid var(--border); background: var(--white); flex: none; }
.wb-tb-left { display: flex; flex-direction: column; gap: 2px; }
.wb-event-title { font-size: 14px; font-weight: 500; color: var(--text); display: flex; align-items: center; gap: 8px; }
.wb-event-title .mono { color: var(--foreground); font-size: 13px; }
/* The run's id: an outlined mono field, 32px (the canvas). */
.wb-runid { display: inline-flex; align-items: center; height: 32px; box-sizing: border-box; font-size: 13px; font-weight: 400; color: var(--foreground); background: var(--background); border: 1px solid var(--border); padding: 0 10px; border-radius: var(--radius-control); }
/* The run's Pull Request, once one exists: its number and the title it was opened with. The title
   is allowed to shrink and ellipsize so a long one can never push the run id or the actions off the
   toolbar — the NUMBER is the identity and never truncates. */
.wb-prchip { display: inline-flex; align-items: center; gap: 6px; max-width: 42ch; min-width: 0;
  font-size: 12px; font-weight: 600; text-decoration: none; color: var(--foreground);
  background: var(--accent); border: 1px solid var(--border);
  padding: 2px 9px; border-radius: 6px; transition: background 0.12s ease, border-color 0.12s ease; }
.wb-prchip:hover { background: var(--accent); border-color: var(--border-strong); }
.wb-prnum { flex: none; font-variant-numeric: tabular-nums; }
.wb-prtitle { font-weight: 500; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Event workspace panel (the run page's card in the project sidebar's context slot) */
/* The EVENT WORKSPACE — the confirmed Events of this run, each with its own implementation
   state. There is no recommendation card here: an Event exists because the user confirmed it.
   The sidebar's context slot (`.pa-context`) is the one scroller and draws the divider under it,
   so the card neither scrolls on its own nor draws a second rule. */
.wb-events { flex: none; padding: 12px 16px; }
.wb-events.collapsed { max-height: none; overflow: visible; }
.wev-head { width: 100%; background: var(--background); border: 1px solid var(--border); border-radius: var(--radius-control); cursor: pointer; font: inherit; font-size: 14px; font-weight: 500; color: var(--text); margin-bottom: 10px; display: flex; align-items: center; gap: 8px; text-align: left; padding: 10px 12px; }
.wev-badge { background: var(--background); color: var(--foreground); border: 1px solid var(--border); border-radius: 999px; font-size: 12px; font-weight: 500; padding: 0 7px; min-width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; }
.wev-chevron { margin-left: auto; color: var(--muted); flex: none; width: 30px; height: 30px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; transition: transform 0.15s ease, background 0.12s ease, color 0.12s ease; }
.wev-chevron svg { width: 20px; height: 20px; }
.wev-toggle:hover .wev-chevron { background: var(--surface); color: var(--text-2); }
.wb-events.collapsed .wev-chevron { transform: rotate(-90deg); }
.wb-events.collapsed .wev-bodywrap { display: none; }
.wb-events.collapsed .wev-head { margin-bottom: 0; }
.wev-hint { font-size: 11px; font-weight: 400; color: var(--muted); line-height: 1.4; margin-bottom: 8px; }
.wev-card { display: flex; align-items: flex-start; gap: 10px; background: var(--background); border: 1px solid var(--border); border-radius: var(--radius-control); padding: 10px 12px; margin-bottom: 8px; }
.wev-card.failed { border-color: var(--red-border); }
.wev-card.pending { opacity: 0.75; }
.wev-body { flex: 1; min-width: 0; }
.wev-name { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--text); }
.wev-desc { font-size: 12px; color: var(--text-2); line-height: 1.4; margin-top: 2px; }
.wev-why { font-size: 11.5px; color: var(--muted); line-height: 1.4; margin-top: 3px; }
.wev-error { font-size: 11.5px; color: var(--red-text); line-height: 1.4; margin-top: 4px; word-break: break-word; }
/*
 * An event whose LAST attempt did not finish but which still has a working implementation in the
 * diff. It is not an error — nothing was lost — so it must not be red: red beside a complete,
 * correct implementation is what made a working event read as a broken one.
 */
.wev-kept { font-size: 11.5px; color: var(--muted-foreground); line-height: 1.4; margin-top: 4px; word-break: break-word; }
.wev-btns { display: flex; gap: 6px; flex: none; align-items: center; }
.wev-remove { flex: none; height: 28px; padding: 0 10px; border-radius: var(--radius-control); background: var(--background); color: var(--foreground); border: 1px solid var(--border); box-shadow: var(--shadow-xs); font-size: 12px; font-weight: 500; line-height: 1; cursor: pointer; }
.wev-remove:hover { background: var(--accent); color: var(--red-text); }
.wev-remove:disabled { opacity: 0.6; cursor: default; }
.wev-state { font-size: 10px; font-weight: 700; border-radius: 5px; padding: 1px 5px; margin-left: 6px; vertical-align: middle; border: 1px solid transparent; }
.wev-state.ready { color: var(--foreground); background: var(--background); border-color: var(--border); }
.wev-state.pending { color: var(--muted); background: var(--background); border-color: var(--border); }
.wev-state.failed { color: var(--red-text); background: var(--background); border-color: var(--red-border); }
.wev-trig { font-size: 10px; font-weight: 500; color: var(--muted-foreground); background: var(--background); border: 1px solid var(--border); border-radius: 5px; padding: 1px 4px; margin-left: 5px; vertical-align: middle; }

/* Pending Reviews below the project workbench (full width, aligned with the
   workbench's 24px gutter; sits under the 100vh workbench so the page scrolls). */
/* The Pull Requests: one shadcn Card, its rows inside it (the canvas). */
.wb-pending { margin: 8px 24px 24px; padding: 20px 8px 12px; border: 1px solid var(--border); border-radius: var(--radius-xl); background: var(--background); box-shadow: var(--shadow-xs); }
.wb-pending .wbp-head { padding: 0 12px; }
.wb-pending .wbp-empty { padding: 4px 12px; }
.wb-pending .wbp-more { margin: 4px 4px 0; }
.wbp-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.wbp-h-title { font-size: 16px; font-weight: 600; letter-spacing: 0; color: var(--text); }
.wbp-count { background: var(--secondary); color: var(--secondary-foreground); border: 1px solid transparent; font-size: 12px; font-weight: 500; border-radius: 999px; min-width: 22px; height: 22px; padding: 0 7px; display: inline-flex; align-items: center; justify-content: center; }
.wbp-count.zero { background: var(--white); color: var(--placeholder); border-color: var(--border); }
.wbp-empty { color: var(--muted); font-size: 13px; padding: 4px 2px; }
.wbp-list { display: flex; flex-direction: column; gap: 0; }
.wbp-item { display: flex; align-items: center; gap: 12px; background: transparent; border: 0; border-radius: var(--radius-control); padding: 10px 12px; box-shadow: none; text-decoration: none; color: inherit; }
.wbp-item:hover { background: var(--accent); text-decoration: none; }
.wbp-ico { width: 32px; height: 32px; box-sizing: border-box; flex: none; border-radius: 6px; border: 1px solid var(--border); background: var(--accent); color: var(--foreground); display: inline-flex; align-items: center; justify-content: center; }
.wbp-ico svg { width: 16px; height: 16px; }
.wbp-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.wbp-title { font-size: 14px; font-weight: 500; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wbp-sub { font-size: 14px; color: var(--muted); }
.wbp-sub.mono { font-family: var(--font-sans); }
.wbp-badge { background: var(--background); color: var(--foreground); border: 1px solid var(--border); font-size: 12px; font-weight: 500; padding: 2px 8px; border-radius: 999px; white-space: nowrap; }
.wbp-time { font-size: 14px; color: var(--muted); white-space: nowrap; min-width: 56px; text-align: right; }
.wbp-chev { color: var(--placeholder); flex: none; }
.wbp-chev svg { width: 16px; height: 16px; }
/* Landed Reviews — same layout as Pending Reviews, green "landed" accent. */
#wbPending { margin-bottom: 20px; }
.wbp-count.landed { background: var(--secondary); color: var(--secondary-foreground); border-color: transparent; }
.wbp-count.landed.zero { background: var(--white); color: var(--placeholder); border-color: var(--border); }
.wbp-ico.landed { background: var(--accent); color: var(--tartini-green); }
.wbp-badge.landed { background: var(--background); color: var(--foreground); border-color: var(--border); }
/* GitHub PR opened but not merged → orange "PR open", plus a direct merge link */
.wbp-ico.pr { background: var(--accent); color: var(--foreground); }
.wbp-badge.pr { background: var(--background); color: var(--foreground); border-color: var(--border); }
.wbp-pr { font-size: 14px; font-weight: 500; color: var(--foreground); cursor: pointer; white-space: nowrap; padding: 2px 8px; border-radius: var(--radius-control); }
.wbp-pr:hover { background: var(--accent); }
/* The toolbar's controls: shadcn's sm buttons 8px apart (the 2026-10-09 canvas). */
.wb-tb-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wb-tb-right .wb-tb-set { width: 32px; height: 32px; padding: 0; justify-content: center; border: 0; box-shadow: none; background: transparent; }
.wb-tb-right .wb-tb-set:hover { background: var(--accent); }
.wb-pill.purple { background: var(--accent); border-color: transparent; }
/* "✓ Draft ready" / "○ No changes yet": shadcn's outline badge; the glyph in the text is the status. */
.wb-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; line-height: 16px; font-weight: 500; padding: 2px 8px; border-radius: 999px; white-space: nowrap; color: var(--foreground); background: var(--background); border: 1px solid var(--border); }
.wb-pill.green, .wb-pill.orange, .wb-pill.purple { color: var(--foreground); background: var(--background); border-color: var(--border); }
.btn-pub { background: var(--primary); color: var(--primary-foreground); font-weight: 500; line-height: 20px; padding: 8px 16px; border-radius: var(--radius-control); display: inline-flex; align-items: center; gap: 8px; box-shadow: none; border: 0; cursor: pointer; }
.btn-pub:hover { background: var(--primary-hover); text-decoration: none; }
/* Inactive Approve & Land (before the diff is published) — visibly disabled. */
/* shadcn's disabled button: the same purple at half opacity. */
/* A land action that waits for something is shadcn's outline, at half strength (the canvas). */
.btn-pub.off, .btn-pub:disabled { opacity: 0.5; box-shadow: none; cursor: not-allowed; }
.btn-pub.off { background: var(--background); color: var(--foreground); border: 1px solid var(--border); }
.btn-pub.off:hover, .btn-pub:disabled:hover { background: var(--primary); }
.btn-pub.off:hover { background: var(--background); }
/* Published is a finished fact, not a second action colour: shadcn's outline button. */
.btn-pub.published { background: var(--background); color: var(--foreground); border: 1px solid var(--border); box-shadow: var(--shadow-xs); }
.btn-pub.published:hover { background: var(--accent); }
/* In the run page's toolbar: shadcn's sm size (the 2026-10-09 canvas). */
.wb-tb-right .btn-pub { height: 32px; padding: 0 12px; box-sizing: border-box; }

.wb-body { flex: 1; min-height: 0; display: flex; gap: 18px; padding: 18px 24px; overflow: hidden; }
.wb-right { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 18px; min-height: 0; }
/* Website Preview and Diff Review are shadcn Cards (the canvas). */
.wb-panel { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-xl); box-shadow: var(--shadow-card); display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
/* Preview takes ~2/3 of the right column height, diff ~1/3, so the live site is
   easy to inspect. (2:1 within the flex column, minus the 18px gap.) */
.wb-preview { flex: 2; }
.wb-diff { flex: 1; min-height: 220px; }
.wb-phead { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 20px 20px 12px; border-bottom: 0; flex: none; }
.wb-phead .ttl { font-size: 16px; font-weight: 600; }
.wb-phead .ttl .sub { color: var(--muted); font-weight: 400; font-size: 14px; margin-left: 4px; }
.wb-pbody { flex: 1; overflow: auto; min-height: 0; }

/* Transcript messages — the `.wb-*` family the project sidebar's transcript wears. */
.wb-msg { display: flex; gap: 10px; align-items: flex-start; }
.wb-avatar { width: 28px; height: 28px; border-radius: 8px; background: var(--brand-mark-tint); display: inline-flex; align-items: center; justify-content: center; flex: none; color: var(--brand-mark); font-size: 15px; }
.wb-bubble { background: var(--accent); border-radius: 12px; padding: 11px 14px; font-size: 13.5px; color: var(--text); line-height: 1.5; }
.wb-msg.user { flex-direction: row-reverse; }
.wb-msg.user .wb-avatar { background: var(--surface); color: var(--muted); font-size: 11px; font-weight: 700; }
.wb-msg.user .wb-bubble { background: var(--surface); }
.wb-msg.plain .wb-bubble { background: transparent; padding: 0; }
.wb-msg.plain h4 { margin: 0 0 4px; font-size: 14px; font-weight: 700; color: var(--text); }
.wb-msg.plain ul { margin: 4px 0 0; padding-left: 0; list-style: none; }
.wb-msg.plain li { padding: 2px 0; }
.wb-msg.plain li::before { content: "▪"; color: var(--foreground); margin-right: 8px; }
/* System status — the INTERFACE reporting a fact (model unreachable, request failed), never
   the assistant speaking. Deliberately unlike a chat bubble so the two are never confused. */
.wb-sysmsg { display: flex; gap: 10px; align-items: flex-start; border: 1px solid var(--border); border-left: 3px solid var(--orange, #f59e0b); border-radius: 10px; padding: 10px 12px; background: var(--surface); }
/* A fact the product recorded (a Pull Request opened): neutral, not a warning. */
.wb-sysmsg[data-kind="fact"] { border-left-color: var(--border-strong, #94a3b8); }
.wb-sysdot { width: 20px; height: 20px; border-radius: 50%; background: var(--orange-soft, #fef3c7); color: var(--orange, #b45309); font-size: 12px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; flex: none; }
/* A response the USER stopped. It is not an outage and does not wear the warning colour: the amber
   dot beside "the assistant could not answer" and beside "you ended this" would say the same thing
   about two opposite events. */
.wb-sysdot.stopped { background: var(--surface); color: var(--muted); font-weight: 400; }
.wb-sysbody { font-size: 13px; color: var(--text); line-height: 1.5; }
.wb-sysdetail { color: var(--muted); font-size: 12.5px; margin-top: 2px; }

/* A CONFIRMATION — durable server state with an identity, asking the one question only the person
   can answer. It carries the real branch, the real Pull Request and the real head, because "Are you
   sure?" is not a question anybody can answer and "Merge #214 at 4f1c2ab into main?" is. Its shape
   is the system status's, not a chat bubble's: the interface is asking, never the assistant. */
.wb-confirm { display: flex; gap: 10px; align-items: flex-start; border: 1px solid var(--border); border-left: 3px solid var(--muted-foreground); border-radius: 10px; padding: 10px 12px; background: var(--surface); }
.wb-confirm-dot { width: 20px; height: 20px; border-radius: 50%; background: var(--accent); color: var(--foreground); font-size: 12px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; flex: none; }
.wb-confirm-body { font-size: 13px; color: var(--text); line-height: 1.5; flex: 1; min-width: 0; }
.wb-confirm-what { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
.wb-confirm-refs { color: var(--muted); font-size: 12px; margin-top: 4px; font-family: var(--mono); overflow-wrap: anywhere; }
.wb-confirm-actions { display: flex; gap: 8px; margin-top: 10px; }
.wb-confirm-actions button { font-size: 12.5px; padding: 5px 12px; border-radius: 8px; cursor: pointer; border: 1px solid var(--border); background: var(--surface); color: var(--text); }
.wb-confirm-actions button.go { border-color: var(--border-strong); background: var(--primary); color: #fff; font-weight: 600; }
.wb-confirm-actions button[disabled] { opacity: .55; cursor: default; }
.wb-confirm-done { color: var(--muted); font-size: 12.5px; margin-top: 2px; }

.wb-input-row { display: flex; gap: 8px; background: var(--white); border: 1px solid var(--border); border-radius: 12px; padding: 6px 6px 6px 14px; align-items: center; }
.wb-input-row:focus-within { border-color: var(--ring); box-shadow: var(--focus-ring); }
.wb-input-row input { flex: 1; border: none; outline: none; font: inherit; font-size: 13.5px; background: transparent; color: var(--text); }
.wb-send { width: 40px; height: 40px; border-radius: 11px; background: var(--primary); color: #fff; display: inline-flex; align-items: center; justify-content: center; flex: none; padding: 0; cursor: pointer; border: 0; }
.wb-send:hover { background: var(--primary-hover); }
.wb-send svg { width: 22px; height: 22px; }
.wb-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.wb-chip { background: var(--white); border: 1px solid var(--border); color: var(--text-2); border-radius: 8px; padding: 7px 12px; font-size: 12.5px; font-weight: 500; }
.wb-chip:hover { background: var(--surface); }
.wb-disclaimer { text-align: center; color: var(--placeholder); font-size: 11.5px; margin-top: 10px; }

/* Preview pane */
.wb-browser { margin: 14px; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.wb-bbar { display: flex; align-items: center; gap: 12px; padding: 9px 14px; background: var(--surface); border-bottom: 1px solid var(--border); }
.wb-dots { display: flex; gap: 6px; }
.wb-dot { width: 10px; height: 10px; border-radius: 50%; }
.wb-url { flex: 1; background: var(--white); border: 1px solid var(--border); border-radius: 7px; padding: 5px 12px; font-family: var(--mono); font-size: 12.5px; color: var(--text-2); }
/* The page the frame is on — follows every navigation inside the Preview (run.html followPreviewAddress). */
.wb-url-path { opacity: .6; overflow-wrap: anywhere; }
.wb-ident { padding: 8px 14px; font-size: 12.5px; color: var(--muted); border-bottom: 1px solid var(--border); background: var(--surface); }
.wb-ident b { color: var(--text-2); font-family: var(--mono); }
.wb-ident-help { margin-left: 8px; color: var(--foreground); cursor: help; font-size: 11.5px; }
/* Bookmarklet escape hatch (login-gated pages) */
.wb-bm { margin: 14px; border: 1px dashed var(--border); border-radius: 12px; background: var(--surface); font-size: 12.5px; }
.wb-bm > summary { cursor: pointer; padding: 10px 14px; font-weight: 600; color: var(--text-2); list-style: none; }
.wb-bm > summary::-webkit-details-marker { display: none; }
.wb-bm[open] > summary { border-bottom: 1px solid var(--border); }
.wb-bm-body { padding: 6px 16px 14px; color: var(--muted); }
.wb-bm-body ol { margin: 8px 0; padding-left: 20px; }
.wb-bm-body li { margin: 6px 0; }
.wb-bm-pill { display: inline-block; padding: 3px 10px; margin: 0 2px; border-radius: 999px; background: #1e1b4b; color: #fff !important; font-weight: 700; text-decoration: none; cursor: grab; user-select: none; }
.wb-bm-pill:active { cursor: grabbing; }
.wb-bm-note { font-size: 11.5px; opacity: .85; margin-top: 8px; }
/* "Preview a signed-in page": a helpful capability, not a warning — same surface/dashed frame as
   before, with a real title + subtitle so the section scans in one glance. */
.wb-bm > summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.wb-bm-title { font-weight: 700; color: var(--text); }
.wb-bm-title::after { content: "›"; margin-left: 6px; color: var(--muted); font-weight: 400; display: inline-block; transition: transform .15s ease; }
.wb-bm[open] .wb-bm-title::after { transform: rotate(90deg); }
.wb-bm-sub { font-weight: 400; color: var(--muted); font-size: 12px; }
/* Numbered steps line up on one baseline even when a step wraps around the pill. */
.wb-bm-steps { margin: 10px 0; padding-left: 0; list-style: none; counter-reset: bmstep; }
.wb-bm-steps li { position: relative; margin: 8px 0; padding-left: 26px; line-height: 1.9; counter-increment: bmstep; }
.wb-bm-steps li::before {
  content: counter(bmstep); position: absolute; left: 0; top: 2px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--accent); color: var(--foreground);
  font-size: 11px; font-weight: 700; line-height: 18px; text-align: center;
}
.wb-bm-pill:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.wb-bm-more { text-decoration: underline dotted; cursor: help; }
.wb-bm-more:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
@media (max-width: 560px) { .wb-bm-sub { flex-basis: 100%; } }

/* Clickable named-event rows in the "Currently tracked" summary (click to locate). */
.wt-ev.clickable { cursor: pointer; border-radius: 8px; margin-left: -6px; margin-right: -6px; padding-left: 6px; padding-right: 6px; }
.wt-ev.clickable:hover { background: var(--surface); }
.wt-ev.offpage { opacity: 0.65; }
.wt-tag { display: inline-block; font-size: 9.5px; font-weight: 700; margin-left: 6px; padding: 1px 6px; border-radius: 6px; vertical-align: middle; }
.wt-tag.here { color: var(--foreground); background: var(--accent); }
.wt-tag.off { color: var(--muted); background: var(--surface); }

/* Diff pane */
.wb-dl { display: grid; grid-template-columns: 46px 22px 1fr; align-items: stretch; }
.wb-dl .ln { text-align: right; padding: 2px 10px; color: var(--tartini-text-muted); user-select: none; }
.wb-dl .sg { text-align: center; padding: 2px 0; }
.wb-dl .tx { white-space: pre; padding: 2px 16px 2px 4px; overflow-x: visible; color: var(--text); }
.wb-dl.add { background: var(--tartini-green-bg); }
.wb-dl.add .sg, .wb-dl.add .tx { color: var(--green-text); }
.wb-dl.add .ln { color: var(--green-text); }
.wb-dl.del { background: var(--tartini-red-bg); }
.wb-dl.del .sg, .wb-dl.del .tx { color: var(--red-text); }
.wb-dl.del .ln { color: var(--red-text); }
.wb-diff-foot { display: flex; align-items: center; gap: 12px; padding: 12px 20px 20px; border-top: 0; flex: none; font-size: 14px; font-variant-numeric: tabular-nums; }
.wb-diff-foot .added { color: var(--green-text); font-weight: 500; }
.wb-diff-foot .removed { color: var(--red-text); font-weight: 500; }
.wb-diff-foot .spc { flex: 1; }

/* toast */
.wb-toast { position: fixed; bottom: 26px; left: 50%; transform: translateX(-50%) translateY(16px); background: #111827; color: #fff; padding: 12px 20px; border-radius: 11px; font-size: 13.5px; font-weight: 500; box-shadow: var(--shadow-lg); opacity: 0; pointer-events: none; transition: opacity .25s ease, transform .25s ease; z-index: 60; }
.wb-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* OPENING TARTINI AI NEVER REARRANGES A WORK PAGE (the owner, 2026-09-29): in the work host these rules
   measure the page's frame, so the run workbench, a table's partitions and schema, and a plan's body
   keep their arrangement down to a phone-narrow page and scale in between. */
@media (max-width: 640px) {
  .wb { height: auto; overflow: visible; }
  .wb-toolbar { flex-wrap: wrap; }
  .wb-body { flex-direction: column; overflow: visible; padding: 16px; }
  .wb-right { min-height: 0; }
  .wb-preview, .wb-diff { flex: none; }
}

/* review workbench: live preview iframe stage + marker overlay */
.wb-preview-stage { position: relative; }
/* Marker layers fade while the previewed page scrolls (stale positions), then
   reappear once redrawn against the new layout. */
#autoLayer, #markerLayer { transition: opacity 0.15s ease; }
.wb-preview-stage iframe { width: 100%; border: 0; display: block; background: #fff; }
#main.wb .container { display: none; }
/* Discard: shadcn's outline sm button; the destructive colour is its words on hover. */
.wb-discard { display: inline-flex; align-items: center; height: 32px; box-sizing: border-box; background: var(--background); color: var(--foreground); font-weight: 500; font-size: 14px; line-height: 20px; padding: 0 12px; border: 1px solid var(--border); border-radius: var(--radius-control); box-shadow: var(--shadow-xs); cursor: pointer; }
.wb-discard:hover { background: var(--accent); color: var(--red-text); }
.wb-discard:disabled { opacity: 0.5; background: var(--background); color: var(--foreground); border-color: var(--border); cursor: not-allowed; }
.wb-discard:disabled:hover { background: var(--background); color: var(--foreground); }

/* ===== Preview: autocapture + named-event overlay layers ===== */
.wb-preview-controls { display: flex; align-items: center; gap: 12px; padding: 0 20px 12px; border-bottom: 0; flex-wrap: wrap; font-size: 12px; flex: none; }
.wb-preview-controls .spacer { flex: 1; }
/* One blue button PER EVENT — clicking any locates it in the preview. Wraps to as many
   rows as needed (20 events → 20 buttons), grouped by Autocapture / Custom. */
.wb-event-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
/* The chip rows' labels and the event chips: shadcn outline badges, mono; a custom event on the gray; a
   new one dashed (the 2026-10-09 canvas — they were blue). The boxes drawn on the customer's site are
   not these, and are unchanged. */
.wb-evg-label { font-size: 12px; font-weight: 500; letter-spacing: .02em; text-transform: none; color: var(--muted); margin: 0 4px 0 8px; }
.wb-event-chips .wb-evg-label:first-child { margin-left: 0; }
.wb-evg-note { font-size: 12px; color: var(--muted); font-family: var(--font-sans); }
.wb-ev-chip { display: inline-flex; align-items: center; background: var(--background); border: 1px solid var(--border); color: var(--foreground); border-radius: 999px; padding: 2px 8px; font-weight: 400; font-family: var(--mono); font-size: 12px; line-height: 16px; cursor: pointer; transition: background .12s, border-color .12s, transform .06s; }
/* A custom event is drawn like every other event: the same outlined chip (2026-10-10, Janet — the
   restyle had made custom ones gray). `data-group` stays on the chip; nothing styles it. */
.wb-ev-chip:hover { background: var(--accent); box-shadow: none; }
.wb-ev-chip:active { transform: translateY(1px); }
/* THE PREVIEW'S ONE DISTINCTION: solid = already in the code the live site is built from; dashed =
   new in this Draft, not sent by the live site until the Pull Request is merged. Everything is blue. */
.wb-ev-chip.new { border-style: dashed; border-color: var(--border-strong); background: var(--background); }
/* Box drawn on a code event's real trigger element (found by text) */
.wb-code-box { position: absolute; box-sizing: border-box; border: 2px solid var(--blue, #2563eb); background: rgba(37,99,235,.10); border-radius: 5px; pointer-events: none; z-index: 3; }
.wb-code-tag { position: absolute; left: 0; top: -17px; background: var(--blue, #2563eb); color: #fff; font: 700 11px/1.5 var(--mono); padding: 0 6px; border-radius: 5px; white-space: nowrap; }
.wb-code-box.em-pulse { animation: emPulse 1.5s ease-out; }
/* An event's box: solid when the live site already sends it, dashed when it is new in this Draft.
   How it was found (locator hints or a class analysis) is not drawn. The bookmarklet restates these
   values; overlay-visual-parity.test.js reads both. */
.wb-code-box.logged { border-style: solid; }
.wb-code-box.new { border-style: dashed; background: rgba(37,99,235,.05); }

/* "Currently tracked" summary card, shown at the top of the AI Assistant (the project sidebar's
   context slot, which is the one scroller — the card does not scroll on its own). */
.wb-tracked { flex: none; padding: 12px 16px 2px; }
.wb-tracked.collapsed { max-height: none; overflow: visible; }
.wb-tracked.collapsed .wt-bodywrap { display: none; }
.wb-tracked.collapsed .wt-card .wt-head { margin-bottom: 0; }
.wt-toggle { width: 100%; background: none; border: 0; cursor: pointer; font: inherit; text-align: left; padding: 0; }
.wt-chevron { margin-left: auto; color: var(--muted); flex: none; width: 30px; height: 30px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; transition: transform 0.15s ease, background 0.12s ease, color 0.12s ease; }
.wt-chevron svg { width: 20px; height: 20px; }
.wt-toggle:hover .wt-chevron { background: var(--surface); color: var(--text-2); }
.wb-tracked.collapsed .wt-chevron { transform: rotate(-90deg); }
.wt-card { background: transparent; border: 0; border-radius: 0; padding: 0; }
/* Section header pill — SHARED look with the "Events in this draft" header (.wev-head)
   so the two summary rows read as one consistent design (both bordered). */
.wt-head { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 500; color: var(--foreground); margin-bottom: 10px; border: 1px solid var(--border); border-radius: var(--radius-control); padding: 10px 12px; background: var(--background); }
.wt-check { width: 18px; height: 18px; flex: none; border-radius: 50%; background: transparent; color: var(--green); display: inline-flex; align-items: center; justify-content: center; font-size: 11px; }
.wt-auto { font-size: 11.5px; color: var(--foreground); background: var(--accent); border: 1px solid var(--border); border-radius: 8px; padding: 5px 9px; margin-bottom: 8px; font-family: var(--mono); }
.wt-autohead { font-size: 12px; color: var(--muted); font-weight: 600; margin-bottom: 5px; }
.wt-autosub { font-weight: 400; }
.wt-autochips { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; }
.wt-autochip { font-family: var(--mono); font-size: 11px; line-height: 1.5; padding: 1px 7px; border-radius: 6px; background: var(--accent); color: var(--foreground); border: 1px solid var(--border); white-space: nowrap; }
.wt-autochip[data-enh] { opacity: 0.62; }
/* Chips are clickable → locate the event in the preview */
button.wt-autochip { cursor: pointer; transition: background .12s, box-shadow .12s, opacity .12s; }
button.wt-autochip:hover { background: var(--accent); opacity: 1; box-shadow: 0 0 0 3px rgb(0 0 0 / 0.05); }
button.wt-autochip:active { transform: translateY(0.5px); }

/* --- GA4 Enhanced-Measurement overlay markers — blue, like every Preview marker --- */
:root { --em: var(--blue, #2563eb); --em-soft: rgba(37,99,235,.10); }   /* one Preview colour: blue */
.wb-em { position: absolute; pointer-events: none; z-index: 3; font-family: var(--mono); }
/* Every EM marker is a readable dashed pill: glyph + event name (never a bare glyph) */
.wb-em-pill { display: inline-flex; align-items: center; gap: 3px; padding: 0 6px; height: 16px; border: 1.5px dashed var(--em); border-radius: 5px; background: #fff; color: var(--em); font-size: 10.5px; font-weight: 700; white-space: nowrap; box-shadow: 0 1px 3px rgba(0,0,0,.14); }
.wb-em-glyph { font-family: -apple-system, "Segoe UI", sans-serif; font-size: 11px; }
.wb-em-underline { height: 0; border-bottom: 2px dashed var(--em); }
.wb-em-underline .wb-em-pill { position: absolute; left: 0; top: -17px; }
.em-pulse { animation: emPulse 1.5s ease-out; }
@keyframes emPulse { 0% { box-shadow: 0 0 0 0 rgba(13,148,136,.55); } 70% { box-shadow: 0 0 0 10px rgba(13,148,136,0); } 100% { box-shadow: 0 0 0 0 rgba(13,148,136,0); } }
/* Page-level highlight — a full-stage glow when locating a page-view event
   (view_item/view_cart/…), which fires on the whole page load, not a button. */
.wb-page-pulse { position: absolute; inset: 0; pointer-events: none; border-radius: 8px; opacity: 0; z-index: 5; }
.wb-page-pulse.em-pulse { animation: wbPagePulse 1.6s ease; }
@keyframes wbPagePulse { 0% { opacity: 0; box-shadow: inset 0 0 0 3px var(--primary); } 25% { opacity: 1; } 100% { opacity: 0; box-shadow: inset 0 0 0 3px var(--primary); } }
.wb-em-scrollhair { position: absolute; left: 0; right: 0; top: 0; height: 0; border-top: 2px dashed var(--em); opacity: 0; pointer-events: none; z-index: 4; }
.wb-em-scrollhair.em-pulse { animation: emHair 1.5s ease-out; }
@keyframes emHair { 0% { opacity: 0; } 15% { opacity: 1; } 100% { opacity: 0; } }
.wt-ev { display: flex; align-items: flex-start; gap: 10px; padding: 5px 0; }
.wt-num { width: 19px; height: 19px; flex: none; border-radius: 50%; color: var(--primary-foreground); font-size: 11px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; margin-top: 1px; }
.wt-num.click { background: var(--tartini-text-body); }
.wt-num.view { background: var(--foreground); }
.wt-num.code { width: auto; height: 19px; border-radius: 6px; padding: 0 6px; font-size: 9.5px; background: var(--background); color: var(--foreground); border: 1px solid var(--border); font-family: var(--mono); }
/* An event this project's code defines whose NAME Google also collects automatically — one event,
   two collection paths, and worth distinguishing from a purely custom one. */
.wt-num.both { width: auto; height: 19px; border-radius: 6px; padding: 0 6px; font-size: 9.5px; background: var(--accent); color: var(--foreground); border: 1px solid var(--border); font-family: var(--mono); }
.wt-body { min-width: 0; flex: 1; }
/* Remove "−" button on an added event row */
/* "Remove" button — same pill shape/size as the Event workspace Remove button (outlined
   secondary variant), so the two actions read as a matched pair. */
.wt-remove { flex: none; align-self: center; height: 28px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--white); color: var(--text-2); font-size: 13px; font-weight: 600; line-height: 1; white-space: nowrap; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: background .12s, color .12s, border-color .12s; }
.wt-remove:hover { background: var(--accent); color: var(--red-text); border-color: var(--red-border); }
.wt-name { display: block; font-family: var(--mono); font-size: 12.5px; font-weight: 700; color: var(--text); }
.wt-sub { display: block; font-size: 11px; color: var(--muted); font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wt-empty { font-size: 12px; color: var(--muted); line-height: 1.4; }
.wb-code-events { padding: 8px 20px; border-bottom: 0; background: transparent; font-size: 12px; flex: none; }

.wb-auto-box { position: absolute; border: 1.5px solid var(--blue); border-radius: 4px; background: rgba(37, 99, 235, 0.05); box-sizing: border-box; }
.wb-auto-box.new { border-style: dashed; }
.wb-auto-tag { position: absolute; top: -9px; left: 4px; background: var(--blue); color: #fff; font-size: 9px; font-weight: 700; line-height: 1; padding: 2px 5px; border-radius: 4px; font-family: var(--mono); white-space: nowrap; letter-spacing: 0.02em; }
/* THE PREVIEW'S TWO STRIPS — part of the layout, never of the overlay. The page-level indicators sit
   in a strip ABOVE the page and the element events with no box here in a strip BELOW it, so neither
   covers the site (the GA4 chips used to be pinned over the frame's top-left: on the site's logo and
   navigation). A strip with nothing in it takes no room. */
.wb-pv-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 6px 12px; background: var(--surface); }
.wb-pv-strip:empty { display: none; }
.wb-pv-page { border-bottom: 1px solid var(--border); }
.wb-pv-elements { border-top: 1px solid var(--border); }
.wb-page-chip { display: inline-flex; align-items: center; gap: 5px; background: rgba(37, 99, 235, 0.94); color: #fff; font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 999px; box-shadow: none; font-family: var(--mono); }
/* A page-level event new in this Draft: the same chip, outlined and dashed instead of filled. */
.wb-page-chip.new { background: #fff; color: var(--blue); border: 1.5px dashed var(--blue); padding: 2.5px 8.5px; }
/* ELEMENT-LEVEL runtime states. An element event with no box on this page is NOT a page-level
   event, so it must not borrow the page chip's shape, colour or place: these are square-cornered
   outlined pills in the strip BELOW the preview, while page chips are filled rounded chips in the
   strip above it. Each carries its own named state (analyzing / not verified here / another page), so
   the user is never told "AI could not box this" about work that is still running. Clicking one is
   how the user jumps to the event's own page. */
.wb-el-status { pointer-events: auto; display: inline-flex; align-items: center; gap: 5px; max-width: 100%; background: var(--surface, #fff); color: var(--text-2, #334); border: 1px solid var(--border, #d8dae0); border-left: 3px solid var(--muted, #6b7280); border-radius: 4px; padding: 3px 9px; font: 600 11px/1.4 var(--mono); box-shadow: none; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wb-el-status:hover { border-color: var(--blue, #2563eb); }
.wb-el-status.working { border-left-color: var(--blue, #2563eb); }
.wb-el-status.bad { border-left-color: #d97706; }
.wb-el-status.muted { border-left-color: var(--muted, #6b7280); opacity: .9; }
/* The preview's binding-source tag: the revision the boxes come from, beside the page they are
   drawn on — two sources, never presented as one. */
.wb-bindsrc { font: 600 10.5px/1.4 var(--mono); color: var(--muted, #6b7280); background: var(--accent); border-radius: 999px; padding: 2px 8px; margin-left: 8px; white-space: nowrap; }
.wb-bridge-err { margin: 0 14px 14px; padding: 10px 14px; border-radius: 10px; background: var(--amber-soft); border: 1px solid var(--amber); color: #92600b; font-size: 12.5px; line-height: 1.5; }

/* ===== Chat image composer + attachments ===== */
.wb-attach { background: transparent; color: var(--muted); padding: 7px; border-radius: 8px; display: inline-flex; align-items: center; flex: none; cursor: pointer; border: 0; }
.wb-attach:hover { background: var(--surface); color: var(--foreground); }
.wb-attach svg { width: 20px; height: 20px; }
.wb-chat-notice { color: var(--red-text); background: var(--red-soft); border: 1px solid var(--red-border); border-radius: 8px; padding: 6px 10px; font-size: 12px; margin-bottom: 8px; }
.wb-msg-imgs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.wb-msg-img { max-width: 160px; max-height: 160px; border-radius: 8px; border: 1px solid var(--border); cursor: zoom-in; display: block; }
.wb-bubble-text { }
.wb-lightbox { position: fixed; inset: 0; background: rgba(17,24,39,0.82); display: none; align-items: center; justify-content: center; z-index: 200; cursor: zoom-out; padding: 30px; }
.wb-lightbox.show { display: flex; }
.wb-lightbox img { max-width: 92vw; max-height: 92vh; border-radius: 10px; box-shadow: var(--shadow-lg); }

/* ─────────────────────────────────────────────────────────────────────────────────────────
   Assistant Markdown (.wb-md) — scoped to a chat message body, never global.
   Everything inside is produced by app/public/markdown.js (markdown-it + DOMPurify). The chat
   pane is a fixed 400px column, so the two things that can burst it — a fenced code block and a
   wide table — scroll INSIDE the bubble instead of widening the panel. That needs min-width:0
   on the flex item; without it a flex child refuses to shrink below its content.
   ───────────────────────────────────────────────────────────────────────────────────────── */
.wb-bubble { min-width: 0; }
.wb-bubble-text, .wb-md { min-width: 0; max-width: 100%; }
.wb-md > *:first-child { margin-top: 0; }
.wb-md > *:last-child { margin-bottom: 0; }
.wb-md p { margin: 0 0 8px; }
.wb-md h1, .wb-md h2, .wb-md h3, .wb-md h4, .wb-md h5, .wb-md h6 {
  margin: 12px 0 6px; font-weight: 700; line-height: 1.35; color: var(--text);
}
.wb-md h1 { font-size: 16px; }
.wb-md h2 { font-size: 15px; }
.wb-md h3 { font-size: 14px; }
.wb-md h4, .wb-md h5, .wb-md h6 { font-size: 13.5px; }
.wb-md strong { font-weight: 700; }
.wb-md em { font-style: italic; }
.wb-md s, .wb-md del { text-decoration: line-through; opacity: .75; }
.wb-md ul, .wb-md ol { margin: 6px 0; padding-left: 20px; }
.wb-md ul { list-style: disc; }
.wb-md ol { list-style: decimal; }
.wb-md li { margin: 3px 0; }
.wb-md li > ul, .wb-md li > ol { margin: 3px 0; }
.wb-md li > ul { list-style: circle; }
.wb-md blockquote {
  margin: 8px 0; padding: 2px 0 2px 10px;
  border-left: 3px solid var(--border); color: var(--muted);
}
.wb-md hr { border: 0; border-top: 1px solid var(--border); margin: 12px 0; }
.wb-md a { color: var(--foreground); text-decoration: underline; overflow-wrap: anywhere; }
.wb-md code {
  font-family: var(--mono); font-size: 12.5px;
  background: var(--surface); padding: 1px 5px; border-radius: 5px;
}
.wb-md pre {
  margin: 8px 0; padding: 10px 12px; max-width: 100%;
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px;
  overflow-x: auto;
}
.wb-md pre code {
  display: block; background: none; padding: 0; border-radius: 0;
  white-space: pre; line-height: 1.55;
}
.wb-md table {
  display: block; width: max-content; max-width: 100%; overflow-x: auto;
  margin: 8px 0; border-collapse: collapse; font-size: 12.5px;
}
.wb-md th, .wb-md td { border: 1px solid var(--border); padding: 5px 8px; text-align: left; vertical-align: top; }
.wb-md th { background: var(--surface); font-weight: 700; }
/* The plain-text fallback: the message is still readable when the renderer is unavailable. */
.wb-md-plain { white-space: normal; overflow-wrap: anywhere; }
.wb-think-meta { opacity: .65; font-variant-numeric: tabular-nums; }

/* ===== Preview analysis cover — every tracked event is described + located BEFORE the
   Website Preview is shown. Honest live checklist over the preview, never a bare spinner. */
.wb-analysis-cover { position: absolute; inset: 0; z-index: 40; display: flex; align-items: flex-start; justify-content: center; padding: 34px 18px; background: rgba(248,250,252,.94); backdrop-filter: blur(2px); overflow: auto; }
.wb-analysis-cover .wac-card { width: 100%; max-width: 620px; background: var(--card, #fff); border: 1px solid var(--border, #e2e8f0); border-radius: 14px; box-shadow: var(--shadow-lg, 0 10px 30px rgba(0,0,0,.12)); padding: 18px 20px; }
.wb-analysis-cover .wac-head { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 15px; color: var(--text); }
.wb-analysis-cover .wac-el { margin-left: auto; font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--muted); }
.wb-analysis-cover .wac-sub { margin: 7px 0 12px; font-size: 12.5px; line-height: 1.55; color: var(--muted); }
.wb-analysis-cover .wac-rows { display: flex; flex-direction: column; gap: 3px; }
.wb-analysis-cover .wac-row { display: flex; align-items: baseline; gap: 9px; font-size: 12.5px; padding: 4px 8px; border-radius: 7px; }
.wb-analysis-cover .wac-row.running { background: var(--accent); }
.wb-analysis-cover .wac-row.done { color: var(--text-2, #475569); }
.wb-analysis-cover .wac-row.queued { color: var(--placeholder, #94a3b8); }
.wb-analysis-cover .wac-row.unresolved, .wb-analysis-cover .wac-row.failed { color: var(--muted); }
.wb-analysis-cover .wac-g { flex: none; width: 14px; text-align: center; font-weight: 700; }
.wb-analysis-cover .wac-row.done .wac-g { color: var(--green, #16a34a); }
.wb-analysis-cover .wac-l { font-family: var(--mono); font-size: 12px; overflow: hidden; text-overflow: ellipsis; }
.wb-analysis-cover .wac-n { margin-left: auto; flex: none; font-size: 11.5px; color: var(--muted); }
.wb-analysis-cover .wac-skip { margin-top: 14px; width: 100%; border: 1px solid var(--border); background: transparent; color: var(--muted); font-size: 12px; padding: 7px 10px; border-radius: 8px; cursor: pointer; }
.wb-analysis-cover .wac-skip:hover { background: var(--surface); color: var(--text); }
@media (prefers-color-scheme: dark) {
  .wb-analysis-cover { background: rgba(15,23,42,.94); }
  .wb-analysis-cover .wac-card { background: #1e293b; border-color: #334155; }
}
/* An analyzed GA4 auto event's button — its location is already known, so the click is instant.
   `data-unres` = the AI read the code and this site has nothing that fires it (honest, muted). */
.wb-ev-chip[data-unres="1"] { opacity: .5; }

/* ===== Custom unified diff renderer (workbench) ===== */
/* A file in the Diff Review: the bordered box, its head on the sidebar's gray; added lines green, removed
   ones red; the badge, the hunk line and the gutter neutral (the canvas). */
.wb-dfile { border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; margin: 0 20px 12px; }
.wb-dfile-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 40px; box-sizing: border-box; padding: 0 12px; background: var(--sidebar); border-bottom: 1px solid var(--border); }
.wb-dfile-head .fn { font-family: var(--mono); font-size: 13px; font-weight: 500; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-dfile-head .fbadge { flex: none; font-size: 12px; line-height: 16px; font-weight: 500; text-transform: capitalize; letter-spacing: 0; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); background: var(--background); color: var(--foreground); }
.wb-dfile-head .fbadge.added { color: var(--foreground); background: var(--background); }
.wb-dfile-head .fbadge.deleted { color: var(--foreground); background: var(--background); }
.wb-dfile-head .fbadge.changed { color: var(--foreground); background: var(--background); }
.wb-dfile-body { overflow-x: auto; font-family: var(--mono); font-size: 12.5px; line-height: 1.65; padding: 4px 0; }
.wb-dl { display: flex; align-items: stretch; min-width: 100%; width: max-content; }
.wb-dl > span { display: inline-block; }
.wb-dl .oldn, .wb-dl .newn { flex: none; width: 42px; text-align: right; padding: 0 8px; color: var(--tartini-text-muted); user-select: none; }
.wb-dl .newn { border-right: 0; }
.wb-dl .sg { flex: none; width: 16px; text-align: center; user-select: none; color: var(--muted); }
.wb-dl .tx { flex: none; white-space: pre; padding: 0 16px 0 8px; color: var(--text-2); }
.wb-dl.add { background: var(--tartini-green-bg); }
.wb-dl.add .sg, .wb-dl.add .tx { color: var(--green-text); }
.wb-dl.del { background: var(--tartini-red-bg); }
.wb-dl.del .sg, .wb-dl.del .tx { color: var(--red-text); }
.wb-dl.hunk { background: var(--sidebar); }
.wb-dl.hunk .tx { color: var(--muted); }
.wb-rawdiff { white-space: pre; overflow-x: auto; padding: 14px; font-family: var(--mono); font-size: 12.5px; margin: 0; }

/* ========================================================================
   Tracking Events — Projects list
   ======================================================================== */
.proj-head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 22px; }
.proj-head .ph-text { flex: 1; min-width: 0; }
.proj-head h1 { font-size: 27px; font-weight: 700; letter-spacing: -0.5px; margin: 0 0 6px; }
.proj-head p { color: var(--muted); font-size: 14px; margin: 0; max-width: 620px; line-height: 1.5; }
.btn-add { display: inline-flex; align-items: center; justify-content: center; gap: 8px; background: var(--primary); color: var(--primary-foreground); border: 0; border-radius: var(--radius-control); padding: 8px 16px; font-size: 14px; line-height: 20px; font-weight: 500; cursor: pointer; box-shadow: none; white-space: nowrap; }
.btn-add:hover { background: var(--primary-hover); text-decoration: none; }
.btn-add svg { width: 16px; height: 16px; }

/* Add-project inline form (revealed by the button) */
.add-proj { display: none; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-md); padding: 14px 16px; margin-bottom: 18px; }
.add-proj.show { display: block; }
.add-proj input { flex: 1; border: 0; outline: 0; font: inherit; font-size: 14px; font-family: var(--mono); background: transparent; color: var(--text); }
/* Conversational Add Project (free-text setup assistant) */
.setup-chat { display: none; }
.setup-chat.show { display: block; }
.sc-head { font-size: 13px; font-weight: 700; color: var(--foreground); margin-bottom: 12px; display: flex; align-items: center; gap: 7px; }
.sc-spark { color: var(--foreground); }
.sc-log { display: flex; flex-direction: column; gap: 8px; max-height: 340px; overflow-y: auto; padding: 4px 2px; }

/*
 * ── WHEN THE CONVERSATION IS THE PAGE ───────────────────────────────────────────────────────
 *
 * On a project with nothing connected there is nothing else to show: Tracking has no run to open and
 * Tables has no warehouse to read. Both make the setup conversation their whole `not_connected`
 * state — and a transcript that explains a GitHub App installation, its two permissions and an
 * optional Google Analytics service account does not fit the 340px window a card-in-a-page gets. The
 * last third of it sat below a scrollbar nobody knew was there, which is where the optional step was.
 *
 * So the card stops being a card in a page and becomes the page: it fills the space under the header
 * and the transcript grows to meet the composer instead of scrolling inside a box.
 *
 * ONE rule, used by both surfaces. Two copies is how they come to disagree about what "unconnected"
 * looks like — which is exactly what this redesign spent its time removing.
 */
.setup-chat.is-page.show { display: flex; }
.setup-chat.is-page { flex-direction: column; height: calc(100vh - 176px); min-height: 460px; margin-bottom: 0; }
.setup-chat.is-page .sc-log { flex: 1 1 auto; max-height: none; min-height: 0; }
/* The composer must not be squeezed by a long transcript; it is the only thing the person can act on. */
.setup-chat.is-page .sc-composer { flex: 0 0 auto; }
@media (max-width: 760px) {
  .setup-chat.is-page { height: calc(100vh - 132px); min-height: 380px; }
}
.sc-msg { max-width: 82%; font-size: 13.5px; line-height: 1.5; padding: 9px 13px; border-radius: 13px; }
.sc-msg.agent { align-self: flex-start; background: var(--surface); color: var(--text); border-bottom-left-radius: 4px; }
.sc-msg.user { align-self: flex-end; background: var(--accent); color: var(--foreground); border-bottom-right-radius: 4px; }
.sc-msg.pending { background: var(--surface); }
.sc-msg-imgs { display: flex; flex-wrap: wrap; gap: 6px; }
.sc-msg-imgs img { max-width: 200px; max-height: 200px; border-radius: 9px; display: block; }
.sc-msg-imgs + .sc-msg-txt { display: block; margin-top: 6px; }
/* ─────────────────────────────────────────────────────────────────────────────────────────
   The setup conversations render through the SAME `.wb-md` block every assistant reply uses.
   Only what the tinted bubbles genuinely break is restated here, and nothing else: the shared
   code chip and fenced block are painted in --surface, which is exactly the agent bubble's own
   background (so the chip disappears into it) and fights the purple of the user's. Sizes,
   spacing, lists, headings and tables all stay the shared ones.
   ───────────────────────────────────────────────────────────────────────────────────────── */
.sc-msg .wb-md code { background: var(--white); border: 1px solid var(--border-soft); }
.sc-msg .wb-md pre { background: var(--white); }
.sc-msg .wb-md pre code { background: none; border: 0; }
/* The user's own bubble is solid purple with white text, so everything the shared block colours
   from a light-background token needs the light-on-dark counterpart. */
.sc-msg.user .wb-md h1, .sc-msg.user .wb-md h2, .sc-msg.user .wb-md h3,
.sc-msg.user .wb-md h4, .sc-msg.user .wb-md h5, .sc-msg.user .wb-md h6 { color: #fff; }
.sc-msg.user .wb-md a { color: #fff; }
.sc-msg.user .wb-md code { background: rgba(255,255,255,0.18); border-color: rgba(255,255,255,0.32); color: #fff; }
.sc-msg.user .wb-md pre { background: rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.28); }
.sc-msg.user .wb-md blockquote { border-left-color: rgba(255,255,255,0.5); color: rgba(255,255,255,0.85); }
.sc-msg.user .wb-md hr { border-top-color: rgba(255,255,255,0.35); }
.sc-msg.user .wb-md th, .sc-msg.user .wb-md td { border-color: rgba(255,255,255,0.32); }
.sc-msg.user .wb-md th { background: rgba(255,255,255,0.14); }
.sc-dots span { display: inline-block; width: 6px; height: 6px; margin: 0 1px; border-radius: 50%; background: var(--placeholder); animation: scbounce 1.2s infinite ease-in-out; }
.sc-dots span:nth-child(2) { animation-delay: 0.15s; } .sc-dots span:nth-child(3) { animation-delay: 0.3s; }
@keyframes scbounce { 0%, 80%, 100% { opacity: 0.3; } 40% { opacity: 1; } }
.sc-thumbs { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0 0; }
.sc-thumbs:empty { margin: 0; }
.sc-thumb { position: relative; }
.sc-thumb img { width: 44px; height: 44px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border); }
.sc-thumb button { position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; border-radius: 50%; border: none; background: var(--text); color: #fff; font-size: 10px; cursor: pointer; line-height: 1; }
.sc-composer { display: flex; align-items: center; gap: 8px; margin-top: 12px; background: var(--white); border: 1px solid var(--border); border-radius: 12px; padding: 6px 8px 6px 10px; }
.sc-composer input[type=text] { flex: 1; border: 0; outline: 0; font: inherit; font-size: 14px; background: transparent; color: var(--text); }
.sc-attach { border: none; background: none; cursor: pointer; font-size: 15px; padding: 4px; border-radius: 8px; }
.sc-attach:hover { background: var(--surface); }
.sc-send { border: none; background: var(--primary); color: #fff; width: 32px; height: 32px; border-radius: 9px; cursor: pointer; display: grid; place-items: center; flex: none; padding: 0; }
.sc-send svg { width: 16px; height: 16px; display: block; }
.sc-send:hover { background: var(--primary-hover); }

.proj-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.proj-search { flex: 1; display: flex; align-items: center; gap: 9px; background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-control); padding: 10px 14px; box-shadow: var(--shadow-xs); }
.proj-search svg { width: 17px; height: 17px; color: var(--placeholder); flex: none; }
.proj-search input { flex: 1; border: 0; outline: 0; font: inherit; font-size: 14px; background: transparent; color: var(--text); }
/* The toolbar controls are one row of equals: the filters and the clear action match the search
   field's height, radius and elevation instead of sitting inside it like small chips. */
.proj-toolbar .dbi-select, .proj-toolbar .btn-ghost {
  align-self: stretch; min-width: 160px; padding: 10px 16px; border: 1px solid var(--border); border-radius: var(--radius-control);
  background: var(--white); box-shadow: var(--shadow-xs); cursor: pointer;
  /* Typography stays exactly what .dbi-select / .btn-ghost use on the Tables toolbar — only the
     geometry is adjusted here, so the two project lists read as one control set. */
  font-size: 13.5px; font-weight: 500; color: var(--foreground);
}
.proj-toolbar .btn-ghost { min-width: 0; display: inline-flex; align-items: center; gap: 7px; }
.proj-toolbar .dbi-select:hover, .proj-toolbar .btn-ghost:hover { border-color: var(--border-strong); color: var(--foreground); }
/* Refresh is an ACTION, not a mode: one button, one behaviour. The dropdown that used to be here
   listed two modes only to say they did not exist. */
#refreshBtn { display: inline-flex; align-items: center; gap: 7px; }
#refreshBtn svg { width: 15px; height: 15px; }

/* WHICH FILTERS THIS WIDGET IS CARRYING — under the chart, in the card's own quiet voice. It answers
   "is this everything, or a slice?", which is the question a number on a shared dashboard always
   raises, and it names only the filters that really narrowed THIS read. */
.wcard-filters { display: flex; align-items: center; gap: 7px; margin-top: 10px; padding-top: 9px;
  border-top: 1px solid var(--border-soft); font-size: 11.5px; color: var(--muted); }
.wcard-filters svg { width: 13px; height: 13px; flex: none; color: var(--foreground); }

/* The table a widget reads, named on the card when one is bound. */
.wcard-src { margin-left: auto; font-size: 11.5px; color: var(--muted); }
.wcard-src .mono { color: var(--text-2); }

.type-badge.logging { background: var(--accent); color: var(--foreground); }

.proj-table { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-sm); overflow: hidden; }
.proj-thead, .proj-row { display: grid; grid-template-columns: minmax(240px, 2.4fr) 0.9fr 1.1fr 1.1fr 1fr 60px; align-items: center; gap: 14px; padding: 14px 20px; }
.proj-thead { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); border-bottom: 1px solid var(--border); background: var(--app-bg); }
.proj-row { border-bottom: 1px solid var(--border-soft); cursor: pointer; transition: background 0.12s ease; }
.proj-row:last-child { border-bottom: 0; }
.proj-row:hover { background: var(--app-bg); text-decoration: none; }
.proj-name-cell { display: flex; align-items: center; gap: 12px; min-width: 0; overflow: hidden; }
.proj-nm { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.proj-nm .pn { display: block; max-width: 100%; font-size: 14.5px; font-weight: 700; color: var(--text); line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.proj-nm .pd { display: block; max-width: 100%; font-family: var(--mono); font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.proj-events, .proj-updated { min-width: 0; }
.proj-events .pe-num { font-size: 16px; font-weight: 700; color: var(--text); }
.proj-events .pe-sub { font-size: 11.5px; color: var(--muted); }
.pstatus { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; padding: 4px 11px; border-radius: 999px; }
.pstatus .pdot { width: 7px; height: 7px; border-radius: 50%; }
.pstatus.up { background: var(--green-soft); color: var(--green-text); }
.pstatus.up .pdot { background: var(--green); }
.pstatus.review { background: var(--amber-soft); color: var(--amber-text); }
.pstatus.review .pdot { background: var(--amber); }
.pstatus.sync { background: var(--red-soft); color: var(--red-text); }
.pstatus.sync .pdot { background: var(--red); }
.pstatus.inactive { background: var(--app-bg); color: var(--muted); border: 1px solid var(--border); }
.pstatus.inactive .pdot { background: var(--placeholder); }
.proj-row.inactive { opacity: 0.62; }
.proj-row.inactive:hover { opacity: 1; }
.proj-row.inactive .pd { text-decoration: line-through; text-decoration-color: var(--border); }
.proj-updated { font-size: 13px; color: var(--muted); }
.ppending { display: inline-flex; align-items: center; justify-content: center; font-size: 12.5px; font-weight: 700; padding: 4px 13px; border-radius: 999px; min-width: 42px; }
.ppending.has { background: var(--orange-soft); color: var(--orange); border: 1px solid var(--orange-border); }
.ppending.zero { background: var(--white); color: var(--placeholder); border: 1px solid var(--border); }
.proj-chevron { color: var(--placeholder); display: inline-flex; justify-content: flex-end; }
.proj-chevron svg { width: 17px; height: 17px; }
.proj-actions { display: inline-flex; align-items: center; justify-content: flex-end; gap: 6px; }
.proj-del { border: none; background: none; color: var(--placeholder); cursor: pointer; padding: 4px; border-radius: 7px; display: grid; place-items: center; opacity: 0; transition: opacity 0.12s ease, color 0.12s ease, background 0.12s ease; }
.proj-del svg { width: 16px; height: 16px; display: block; }
.proj-set { border: none; background: none; color: var(--placeholder); cursor: pointer; padding: 4px; border-radius: 7px; display: grid; place-items: center; opacity: 0; transition: opacity 0.12s ease, color 0.12s ease, background 0.12s ease; }
.proj-set svg { width: 16px; height: 16px; display: block; }
.proj-row:hover .proj-set { opacity: 1; }
.proj-set:hover { color: var(--foreground); background: var(--surface); }
.proj-row:hover .proj-del { opacity: 1; }
.proj-del:hover { color: var(--red-text); background: var(--red-soft); }
.proj-empty { padding: 44px 20px; text-align: center; color: var(--muted); }
.proj-empty b { display: block; color: var(--text-2); font-size: 15px; margin-bottom: 4px; }

/* ===================================================================
   Dashboards feature (index + detail). All real-data. Violet theme.
   =================================================================== */
/* shared button/pill/chip helpers */
.btn-ghost { display: inline-flex; align-items: center; justify-content: center; gap: 6px; background: var(--background); border: 1px solid var(--border); color: var(--foreground); font-weight: 500; font-size: 14px; line-height: 20px; padding: 7px 14px; border-radius: var(--radius-control); cursor: pointer; text-decoration: none; transition: background .12s, border-color .12s;  box-shadow: var(--shadow-xs);}
.btn-ghost:hover { background: var(--accent); text-decoration: none; }
.btn-ghost.sm, .btn-primary.sm, .btn-outline.sm { padding: 5px 12px; font-size: 13px; border-radius: var(--radius-control); }
.btn-ghost:disabled { opacity: .5; cursor: not-allowed; }
/* A snapshot dashboard's "Refresh: off" (onboarding M8): inert, but hoverable so its tooltip shows. */
/* "Refresh: off" is the disabled button at half strength (`.t-btn[aria-disabled]`), and stays so on hover. */
.refresh-off[aria-disabled="true"] { cursor: not-allowed; }
.t-btn--outline.refresh-off[aria-disabled="true"]:hover { background: var(--background); }
.t-btn--ghost.refresh-off[aria-disabled="true"]:hover { background: transparent; }
.btn-outline { display: inline-flex; align-items: center; gap: 6px; background: var(--background); border: 1px solid var(--border); color: var(--foreground); font-weight: 500; font-size: 14px; line-height: 20px; padding: 7px 14px; border-radius: var(--radius-control); cursor: pointer; text-decoration: none; transition: background .12s;  box-shadow: var(--shadow-xs);}
.btn-outline:hover { background: var(--accent); text-decoration: none; }
.mono-chip { display: inline-flex; align-items: center; gap: 4px; font-family: var(--mono); font-size: 12px; background: var(--surface); border: 1px solid var(--border-soft); border-radius: 7px; padding: 2px 8px; color: var(--muted); text-decoration: none; white-space: nowrap; max-width: 100%; }
.mono-chip svg { width: 13px; height: 13px; }

/* toolbar */
.dbi-toolbar { display: flex; gap: 12px; margin-bottom: 16px; }
.dbi-search { flex: 1; display: flex; align-items: center; gap: 8px; background: var(--card); border: 1px solid var(--border); border-radius: 11px; padding: 0 14px; }
.dbi-search svg { width: 17px; height: 17px; color: var(--placeholder); flex: none; }
.dbi-search input { flex: 1; border: none; outline: none; background: none; padding: 11px 0; font-size: 14px; color: var(--text); }
.dbi-select { background: var(--card); border: 1px solid var(--border); border-radius: 11px; padding: 0 14px; font-size: 13.5px; font-weight: 700; color: var(--text-2); cursor: pointer; }
/* A filter label is a control, not body copy: it reads at the same weight as the Clear action
   beside it, on both the Tables and the Tracking Events toolbars. */
.dbi-toolbar .btn-ghost, .proj-toolbar .btn-ghost { font-weight: 700; }

/* dashboard card / list */
/* A dashboard's Live / Draft pills and its unpublished-edits hint: on the Dashboards pages AND in the
   Tartini AI panel's context card, which in the work host is the host's document — so they are shared. */
.pill-live { display: inline-flex; align-items: center; gap: 4px; background: var(--background); color: var(--foreground); border: 1px solid var(--border); font-weight: 500; font-size: 12px; line-height: 16px; padding: 2px 8px; border-radius: 999px; }
/* Live and Draft: outline badges whose 12px icon carries the state — a green check, a muted pencil. */
.pill-live svg, .pill-draft svg { width: 12px; height: 12px; flex: none; }
.pill-live svg { color: var(--tartini-green); }
.pill-draft svg { color: var(--muted-foreground); }
.pill-draft { display: inline-flex; align-items: center; gap: 4px; background: var(--background); color: var(--foreground); border: 1px solid var(--border); font-weight: 500; font-size: 12px; line-height: 16px; padding: 2px 8px; border-radius: 999px; }
.unpub-hint { font-size: 12px; font-weight: 500; color: var(--amber-text); }
.auto-tag { font-size: 12px; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--foreground); background: var(--accent); padding: 2px 8px; border-radius: 999px; }
.own-name { font-size: 13px; color: var(--text-2); }
.kebab { background: none; border: none; color: var(--placeholder); cursor: pointer; padding: 4px; border-radius: 7px; display: grid; place-items: center; }
.kebab:hover { background: var(--surface); color: var(--text-2); }
.kebab svg { width: 18px; height: 18px; }
/* A row's "…" menu (Dashboards) as shadcn's DropdownMenu, the Files menu's look (the 2026-10-09 canvas):
   EVERY item — the Edit link and the buttons alike — is one 14px row. Only buttons had a rule, so the
   Edit link sat flush left, unpadded, above three padded buttons. */
.kebab-menu { position: absolute; top: 100%; right: 0; margin-top: 4px; background: var(--popover); border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: var(--shadow-pop); z-index: 20; min-width: 128px; padding: 4px; }
.kebab-menu a, .kebab-menu button { display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box; text-align: left; background: none; border: 0; padding: 6px 8px; border-radius: 6px; font: inherit; font-size: 14px; line-height: 20px; font-weight: 400; color: var(--foreground); text-decoration: none; cursor: pointer; }
.kebab-menu a:hover, .kebab-menu button:hover, .kebab-menu a:focus-visible, .kebab-menu button:focus-visible { background: var(--accent); color: var(--foreground); text-decoration: none; outline: none; }
.kebab-menu button.danger { color: var(--red-text); }

.dbi-empty { padding: 34px 20px; text-align: center; color: var(--muted); font-size: 14px; }
.dbi-empty.big { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 54px 20px; }
.dbi-empty.big b { font-size: 17px; color: var(--text); }
.dbi-empty.big span { max-width: 380px; }
.dbi-empty-ic { width: 52px; height: 52px; border-radius: 14px; background: var(--accent); color: var(--foreground); display: grid; place-items: center; }
.dbi-empty-ic svg { width: 26px; height: 26px; }
.foot-count { font-size: 13px; color: var(--muted); }
.pager { display: flex; gap: 5px; }
/* Padding, not width: a three-digit page number must not be cramped, and `min-width` keeps a
   one-digit one square. Centring itself comes from the shared rule above. */
.pg { min-width: 34px; height: 34px; padding: 0 10px; border: 1px solid var(--border); background: var(--card); border-radius: 9px; font-size: 13px; font-weight: 600; color: var(--text-2); cursor: pointer; }
.pg:hover:not(:disabled) { background: var(--app-bg); }
.pg.on { background: var(--brand-tint); border-color: var(--primary); color: var(--foreground); box-shadow: inset 0 0 0 1px var(--primary); }
.pg:disabled { opacity: .4; cursor: default; }

/* modal */
/* GitHub App install / "connect to land" guided modal */
.gh-modal-back { position: fixed; inset: 0; background: rgba(17,24,39,.5); display: flex; align-items: center; justify-content: center; z-index: 120; padding: 24px; }
.gh-modal { position: relative; background: var(--card); border-radius: 16px; box-shadow: var(--shadow-lg); width: 100%; max-width: 620px; padding: 26px 28px; max-height: 90vh; overflow: auto; }
.gh-modal h2 { font-size: 19px; font-weight: 700; margin-bottom: 8px; }
.gh-modal-sub { color: var(--muted); font-size: 13.5px; line-height: 1.55; margin-bottom: 18px; }
.gh-modal-x { position: absolute; top: 16px; right: 18px; background: none; border: 0; font-size: 16px; color: var(--placeholder); cursor: pointer; }
.gh-modal .mono { font-family: var(--mono); font-size: .92em; }
.gh-btn { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; box-sizing: border-box; border-radius: var(--radius-control); border: 1px solid var(--border); background: var(--background); color: var(--foreground); font-size: 13px; font-weight: 500; cursor: pointer; text-decoration: none; box-shadow: var(--shadow-xs); }
.gh-btn:hover { background: var(--accent); }
.gh-btn.primary { background: var(--primary); color: #fff; border-color: transparent; }
.gh-btn.primary:hover { background: var(--primary-hover); }
.gh-btn:disabled { opacity: .6; cursor: default; }
/*
 * A DOCUMENT PAGE — the Project Admin runbook, rendered from its Markdown source. Wide content (a
 * permission table, a long console URL) scrolls inside its own box; the page itself never does.
 */
/* shadcn's Typography (the 2026-10-09 canvas): 15/26 body in the body colour, h1 30/36, h2 20/28, h3
   16/24, all 600; a quote is shadcn's Alert; links in the foreground colour, underlined. */
.doc { font-size: 15px; line-height: 26px; color: var(--text-2); margin-top: 18px; }
.doc h1 { font-size: 30px; line-height: 36px; font-weight: 600; letter-spacing: -0.025em; color: var(--text); margin: 24px 0 16px; }
.doc h2 { font-size: 20px; line-height: 28px; font-weight: 600; letter-spacing: -0.015em; color: var(--text); margin: 36px 0 12px; }
.doc h3 { font-size: 16px; line-height: 24px; font-weight: 600; color: var(--text); margin: 24px 0 8px; }
.doc p { margin: 0 0 16px; }
.doc strong { color: var(--text); font-weight: 600; }
.doc ul { margin: 0 0 16px; padding-left: 24px; }
.doc li { margin-bottom: 8px; }
.doc blockquote {
  margin: 0 0 20px; padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius-md);
  font-size: 14px; line-height: 22px; color: var(--text-2); background: var(--background);
}
.doc blockquote > :last-child { margin-bottom: 0; }
.doc li.doc-task { list-style: none; margin-left: -20px; font-family: var(--mono); font-size: 12.5px; }
/*
 * A console URL is one unbreakable word. Left alone it does not wrap, so it runs straight out of its
 * cell and paints over the column beside it — which is what a runbook full of
 * `console.cloud.google.com/...` links does to the table it lives in. It breaks anywhere instead.
 */
.doc code { font-family: var(--mono); font-size: 13px; background: var(--accent); border: 0; border-radius: 4px; padding: 1px 4px; color: var(--text); overflow-wrap: anywhere; }
.doc pre { background: var(--sidebar); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px 16px; overflow-x: auto; margin: 0 0 16px; }
.doc pre code { background: none; border: 0; padding: 0; font-size: 13px; line-height: 1.6; }
.doc hr { border: 0; border-top: 1px solid var(--border); margin: 24px 0; }
.doc a { color: var(--foreground); font-weight: 500; text-decoration: underline; text-underline-offset: 4px; }
/* shadcn's Table in a bordered box: the header row on the gray, hairlines between rows. */
.doc-table-wrap { overflow-x: auto; margin: 0 0 20px; border: 1px solid var(--border); border-radius: var(--radius-md); }
/* A wide setup table scrolls inside its own box rather than squeezing its columns to nothing. */
.doc-table { border-collapse: collapse; width: max-content; min-width: 100%; font-size: 14px; line-height: 20px; }
/* A cell is capped and its content wraps INSIDE that cap; nothing is allowed to spill over a
   neighbour. The table as a whole is still free to be wider than the page and scroll. */
.doc-table td, .doc-table th { max-width: 230px; overflow-wrap: anywhere; }
/* The last column is the explanation — in the permission table it is a numbered checklist, and it
   is the column a reader actually came for. It gets the room instead of being squeezed last. */
.doc-table td:last-child, .doc-table th:last-child { max-width: 420px; min-width: 300px; }
.doc-table th, .doc-table td { border: 0; border-bottom: 1px solid var(--border); padding: 10px 16px; text-align: left; vertical-align: top; }
.doc-table tbody tr:last-child td { border-bottom: 0; }
.doc-table th { background: var(--accent); font-weight: 500; color: var(--muted); height: 40px; padding-top: 0; padding-bottom: 0; vertical-align: middle; }

.set-side { display: grid; gap: 8px; justify-items: start; }
/*
 * ONE THING, AND THE BUTTON THAT ACTS ON IT, ON ONE LINE.
 *
 * A member and a pending invitation are rows, not stacks, and they used to be written as `.set-side`
 * carrying an inline `flex-direction: row`. `.set-side` is a GRID, so that declaration — and the
 * `flex-wrap` beside it — did nothing whatsoever: every row laid itself out one item per line. With
 * two invitations outstanding the card drew a line of text, a Revoke, another line of text and
 * another Revoke, which reads as one control duplicated rather than one button per invitation.
 *
 * So the row gets a class that really is a row. The button stays beside the thing it revokes, and
 * the hint's card-level bottom margin is dropped so the line centres on the control.
 *
 * ── AND THE NAME IS ITS OWN ──────────────────────────────────────────────────────────────────
 *
 * This was `.set-row` for one deploy, which is already taken: `.set-row` is the PROJECT LIST row
 * forty lines above — a padded, bordered, hoverable card, one per project. Reusing the name gave
 * these lines that card's box and gave the project list this rule's `gap` and `flex-wrap`, which is
 * the same class of defect as the one being fixed: a component wearing another component's clothes.
 * A row that is a LINE inside a card gets a name no card answers to.
 */
.set-inline-row { display: flex; flex-direction: row; align-items: center; gap: 10px; flex-wrap: wrap; }
.set-inline-row + .set-inline-row { margin-top: 8px; }
/*
 * THE BIGQUERY SETUP CHECKS, on the first level of the connection card. Every permission,
 * prerequisite and capability check is drawn here in the runbook's own order, grouped by the thing
 * an administrator actually grants — never summarized into generic capability lines with the real
 * checks hidden behind an expander.
 */
.bq-check-group { display: grid; gap: 3px; justify-items: start; width: 100%; }
.bq-check-group + .bq-check-group { margin-top: 9px; }
.bq-check-title { font-size: 11.5px; font-weight: 700; letter-spacing: .02em; text-transform: uppercase; color: var(--muted); }
.set-side .bq-check-group .hint { margin: 0; }
.bq-check-note { font-size: 11.5px; color: var(--muted); padding-left: 15px; }
.bq-check-blocker { display: grid; gap: 4px; justify-items: start; margin-top: 11px; padding: 9px 11px; border: 1px solid var(--red-border); background: var(--red-soft); border-radius: 10px; }
.set-side .bq-check-blocker .hint { margin: 0; }
.btn-ghost.danger { color: var(--red-text); border-color: var(--red-border); }
.btn-ghost.danger:hover { background: var(--red-soft); }

/* ---- WHAT WAS HERE ---------------------------------------------------------
 * The project tab strip (`Connections · Token & Cost`) and the whole Token & Cost
 * visual language — `.tc-*`: the range head, the two-column grid, the metric
 * tiles, the bar chart with its in-chart callout, the per-model table, the effort
 * pills and the meter.
 *
 * Project Settings is Connections ONLY now. Spend is an account-level fact and it
 * lives in Account Settings, where the balance, the purchases and the total are —
 * `account.css`'s `.acc-usage*` block is its stylesheet, and `account.css` is the
 * right file for it because shell.js injects that one on every page.
 *
 * Nothing in `app/public/` referenced a `.tc-*` or a `.set-tab` when this was
 * removed; `account.css` keeps its own `.set-tabs`/`.set-tab` pair (active class
 * `.is-active`) and is untouched by this deletion.
 */
/* Connections page — return-from-install banner */
.conn-banner { margin: 14px 0 4px; padding: 11px 14px; border-radius: 10px; font-size: 13.5px; line-height: 1.5; }
.conn-banner.ok { background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46; }
.conn-banner.warn { background: #fff7ed; border: 1px solid #fed7aa; color: #9a3412; }
.modal-back { position: fixed; inset: 0; background: rgba(17,24,39,.42); display: flex; align-items: center; justify-content: center; z-index: 100; padding: 20px; }
.modal { background: var(--card); border-radius: 16px; box-shadow: var(--shadow-lg); width: 100%; max-width: 460px; padding: 22px; max-height: 90vh; overflow: auto; }
.modal-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.modal-head h2 { font-size: 18px; font-weight: 700; }
.modal-x { background: none; border: none; font-size: 16px; color: var(--placeholder); cursor: pointer; }
.fld { display: block; margin-bottom: 14px; }
.fld-l { display: block; font-size: 12.5px; font-weight: 600; color: var(--text-2); margin-bottom: 6px; }
.fld-l em { color: var(--red); font-style: normal; }
.fld-opt { color: var(--placeholder); font-weight: 400; }
.fld input, .fld select, .fld textarea { width: 100%; border: 1px solid var(--border); border-radius: 9px; padding: 9px 11px; font-size: 14px; font-family: inherit; color: var(--text); background: var(--white); }
.fld.chk { display: flex; align-items: center; gap: 8px; font-size: 13.5px; color: var(--text-2); }
.fld.chk input { width: auto; }
.modal-err { color: var(--red-text); font-size: 13px; margin-bottom: 10px; display: none; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 6px; }
/* ADD A WIDGET. Each tile shows the thing that type MAKES — bars, a line over axes, a dial, a table,
   a funnel, a ring, a bubble with a spark — in the ink on a gray tile, two-tone so the shape reads at
   a glance (neutral since the 2026-10-09 canvas; purple is for primary and selected only). `--w-ic-*` are what the pictograms paint with; the tile owns them, so the same markup
   works on a resting tile and a hovered one. */
.add-tile { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 16px 12px; border: 1px solid var(--border); border-radius: 14px; background: var(--white); cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text-2); transition: border-color .12s ease, background .12s ease, color .12s ease, transform .12s ease; }
.add-tile:hover { border-color: var(--border-strong); background: var(--accent); color: var(--foreground); transform: translateY(-1px); }
.add-tile:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.at-ic { width: 46px; height: 46px; display: grid; place-items: center; border-radius: 12px; background: var(--accent);
  color: var(--foreground); --w-ic-soft: var(--border-strong); --w-ic-bg: var(--white); }
.add-tile:hover .at-ic { background: var(--white); --w-ic-bg: var(--white); }
.at-ic svg { width: 26px; height: 26px; }

/* ---- detail page ---- */
.spacer { flex: 1; }
.db-controls { display: flex; align-items: center; gap: 8px; position: relative; }
.range-pick { position: relative; }
.range-menu { position: absolute; top: 100%; right: 0; margin-top: 5px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-md); z-index: 30; padding: 6px; min-width: 200px; }
.range-menu > button { display: block; width: 100%; text-align: left; background: none; border: none; padding: 8px 10px; border-radius: 8px; font-size: 13px; color: var(--text-2); cursor: pointer; }
.range-menu > button:hover { background: var(--surface); }
.range-custom { border-top: 1px solid var(--border-soft); margin-top: 6px; padding-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.range-custom label { font-size: 12px; color: var(--muted); display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.range-custom input { border: 1px solid var(--border); border-radius: 7px; padding: 4px 6px; font-size: 12px; }
/* The dashboard's OWN default window. It sits under the range controls because that is where the
 * range is already being chosen — and it says what the dashboard opens on today, so pressing the
 * button is a change somebody can see the effect of. */
.range-default { border-top: 1px solid var(--border-soft); margin-top: 6px; padding-top: 8px; display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
.range-default.view { display: block; font-size: 11.5px; color: var(--muted); }
.rd-says { font-size: 11.5px; color: var(--muted); }
.edit-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--orange); margin-left: 4px; }
.db-desc { color: var(--muted); font-size: 14px; margin-top: 3px; }

/* canvas + widgets */
.db-canvas-col { min-width: 0; }
.wgrid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 14px; }
/* An empty canvas says so across the whole grid, not in its first column. */
.wgrid > .dbi-empty.big { grid-column: 1 / -1; }
/* A widget is shadcn's Card: one border, the 14px radius, the card shadow (the 2026-10-09 canvas). */
.wcard { grid-column: span 12; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-xl); box-shadow: var(--shadow-card); padding: 16px; position: relative; min-width: 0; }
.wcard.editable { cursor: grab; }
/* The header is a row of separate facts — a title, its description, where the number came from, when
 * it was read. `space-between` alone put the title and its description flush against each other, so
 * they read as one run-on sentence; the gap is what keeps them separate things. */
/*
 * TWO ROWS, ALWAYS THE SAME TWO. The title (with whatever the card has to say about its source, its
 * skipped filters and when it was last read) is the first row; the DESCRIPTION is the second one, on
 * its own line, at every width. It used to share a wrapping flex row with the title, so a short
 * description sat on the title's baseline and read as part of the name, and the same widget looked
 * like two different things depending on how wide the card was.
 */
.wcard-head { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
.wcard-head-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.wcard-title { font-weight: 700; font-size: 14.5px; }
.wcard-desc { font-size: 12px; color: var(--muted); line-height: 1.45; }
/* The editing controls stop short of the ⋯ button's corner: the two rows overlapping would put a
   Remove button underneath a menu trigger. */
.w-controls { position: absolute; top: 8px; right: 40px; display: none; align-items: center; gap: 4px; background: var(--card); border: 1px solid var(--border); border-radius: 9px; padding: 3px; box-shadow: var(--shadow-sm); z-index: 5; }
/* ── the widget's own ⋯ menu ────────────────────────────────────────────────────────────────
   The button is always present (a keyboard and a touch device have no right-click), quiet until the
   card is hovered or it is itself focused — and always visible while its menu is open, because a
   trigger that disappears under its own popup is a control nobody can find again. */
.wcard-more { position: absolute; top: 8px; right: 8px; z-index: 6; display: grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 1px solid transparent; border-radius: 8px; background: none; color: var(--placeholder); cursor: pointer; opacity: 0; transition: opacity .12s ease; }
.wcard:hover .wcard-more, .wcard-more:focus-visible, .wcard-more[aria-expanded="true"] { opacity: 1; }
.wcard-more:hover, .wcard-more[aria-expanded="true"] { background: var(--surface); border-color: var(--border); color: var(--text-2); }
.wcard-more svg { width: 18px; height: 18px; }
.wmenu { position: absolute; top: 40px; right: 8px; z-index: 20; min-width: 216px; padding: 6px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-lg, 0 12px 30px rgba(15, 23, 42, .16)); display: flex; flex-direction: column; gap: 2px; }
.wmenu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border: none; border-radius: 8px; background: none; color: var(--text); font-size: 13.5px; font-weight: 600; text-align: left; cursor: pointer; }
.wmenu-item:hover:not(:disabled) { background: var(--accent); color: var(--foreground); }
/*
 * The same leak, one control down: an item of the ⋯ menu is a ROW, and the page-level
 * `button.danger` turned Delete into an outlined box inside a list of plain rows.
 */
.wmenu .wmenu-item.danger { color: var(--red-text); background: none; border: none; padding: 8px 10px; border-radius: 8px; }
.wmenu-item.danger:hover:not(:disabled) { background: var(--red-soft, rgba(239, 68, 68, .1)); color: var(--red-text); }
/* A refused action stays VISIBLE and says why in its tooltip. Hiding it would leave somebody
   looking for a menu item that is not there. */
.wmenu-item.off { color: var(--placeholder); cursor: not-allowed; }
.wmenu-ic { display: grid; place-items: center; width: 18px; height: 18px; flex: none; }
.wmenu-ic svg { width: 17px; height: 17px; }
.wmenu-sep { height: 1px; margin: 4px 2px; background: var(--border-soft); }
/* What the last action could not do — the SERVER's own sentence, on the menu it was asked from. */
.wmenu-err { margin: 4px 2px 0; padding: 7px 8px; border-radius: 7px; background: var(--red-soft, rgba(239, 68, 68, .1)); color: var(--red-text); font-size: 12px; line-height: 1.45; }
.wcard.editable:hover .w-controls { display: flex; }
.w-drag { cursor: grab; color: var(--placeholder); display: grid; place-items: center; padding: 2px; }
.w-drag svg { width: 16px; height: 16px; }
.w-sizes { display: flex; gap: 1px; }
.wsz { border: none; background: none; color: var(--placeholder); cursor: pointer; font-size: 11px; padding: 3px 4px; border-radius: 5px; }
.wsz.on { background: var(--accent); color: var(--foreground); }
/*
 * THE THREE WIDGET CONTROLS ARE ONE SIZE, AND THE DANGEROUS ONE IS NO BIGGER.
 *
 * `button.danger` (near the top of this file) is the app's PAGE-LEVEL destructive button: white,
 * outlined, `padding: 11px 18px`, radius 12. Being element-qualified it outranks a plain `.w-btn`,
 * so Remove silently inherited all of it and stood a head taller than Edit and Duplicate beside it —
 * three buttons doing the same kind of thing in three-quarters of an inch of chrome, one of them a
 * different shape. Colour is the right way to say "this one deletes"; size is not, and a bigger
 * target for the irreversible action is the wrong way round anyway.
 *
 * So the component states its own box, scoped to its container, which beats the global on
 * specificity without `!important` and without touching the page-level rule other screens rely on.
 */
.w-controls .w-btn { border: none; background: var(--surface); color: var(--text-2); font-size: 11.5px; font-weight: 600;
  padding: 4px 8px; border-radius: 6px; cursor: pointer; line-height: 1.45; }
.w-controls .w-btn:hover { background: var(--border); }
.w-controls .w-btn.danger { color: var(--red-text); background: var(--surface); border: none; }
.w-controls .w-btn.danger:hover { background: var(--red-soft); }
.w-empty { color: var(--muted); font-size: 13px; padding: 16px; text-align: center; }

/* KPI */
.kpi-top { display: flex; align-items: center; gap: 8px; }
.kpi-lbl { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.kpi-val { font-size: 30px; font-weight: 700; letter-spacing: -.02em; margin-top: 8px; line-height: 1; }
.kpi-unit { font-size: 14px; color: var(--muted); font-weight: 600; }
.kpi-foot { margin-top: 8px; font-size: 12px; }
.kpi-delta { font-weight: 700; }
.kpi-delta.up { color: var(--green-text); } .kpi-delta.down { color: var(--red-text); }
.kpi-vs { color: var(--muted); }

/* charts */
/* ── THE STANDARD WIDGET HEIGHT ──────────────────────────────────────────────────────────────
 *
 * `--standard-widget-h` is the height of the WHOLE WIDGET — the card as a reader sees it — for every
 * standard chart (line, bar, donut, funnel) AND for a table. It is what a 6-column line chart widget
 * has always been, and it does not change when the card is 12 columns wide, when a bar chart has no
 * legend, when a header wraps onto a second line, or when a table has four rows or four hundred.
 * Everything inside adapts within it: Apache ECharts draws into `.chart-hold`, the box the header
 * leaves (`dashboard-echarts.js`, `dashboard-widgets.css`).
 *
 * A TABLE IS IN THIS SET because a row of widgets should be a row: a table whose height followed its
 * own row count made every board it sat on a different shape from one read to the next, and one long
 * table pushed everything below it off the screen. Its rows scroll inside the box instead. It is
 * listed beside the charts rather than sharing their class, because `is-chart` selects the things
 * that are DRAWINGS elsewhere in this stylesheet and a table is not one. */
:root { --standard-widget-h: 414px; }
.wcard.is-chart, .wcard.is-table { height: var(--standard-widget-h); display: flex; flex-direction: column; }
/* The body takes what the header leaves, and the drawing (or the rows) takes what is left of that.
   Both are `min-height: 0` so a flex child may really shrink instead of overflowing its parent. */
.wcard.is-chart .wcard-body, .wcard.is-table .wcard-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* The drawing FILLS its box on every page that draws one — a dashboard's canvas and a conversation
   card's thumbnail alike (`dashboard-echarts.js` puts a `.echart` element inside and ECharts measures
   it). This lived in dashboard-widgets.css, which only the dashboard page loads, so a card's chart
   was drawn into a 0px box everywhere else (2026-10-02). */
.chart-hold { position: relative; width: 100%; flex: 1; min-height: 0; }
.chart-hold > .echart { position: absolute; inset: 0; }
.chart-svg { width: 100%; height: 100%; display: block; }

.add-widget { margin-top: 16px; width: 100%; border: 1.5px dashed var(--border); background: var(--accent); color: var(--foreground); font-weight: 700; font-size: 13.5px; padding: 12px; border-radius: 12px; cursor: pointer; }
.add-widget:hover { background: var(--accent); }

/* The footer names every table the widgets really read, so it wraps rather than truncating: a
 * dashboard whose widgets read six tables has six links, not "…". */
.db-updated { display: flex; align-items: center; gap: 8px; }
.refresh-btn { border: none; background: none; color: var(--placeholder); cursor: pointer; display: grid; place-items: center; padding: 3px; border-radius: 6px; }
.refresh-btn:hover { background: var(--surface); color: var(--foreground); }
.refresh-btn svg { width: 16px; height: 16px; }

/* Off-screen export composition rasterized to PNG by "⤓ Download". */
.db-export { box-sizing: border-box; padding: 28px 28px 20px; background: var(--app-bg); font-family: var(--sans); }
.db-export-head { margin-bottom: 18px; }
.db-export-title { font-size: 24px; font-weight: 700; letter-spacing: -0.4px; color: var(--text); }
.db-export-sub { margin-top: 5px; font-size: 13px; color: var(--muted); }
/*
 * ONE CARD ON ITS OWN. It carries no dashboard heading or footer — the picture is of that widget,
 * and the card already names itself — so it needs only enough padding not to sit on the edge, and
 * it must not force the card back to full-grid width.
 */
.db-export-one { padding: 12px; }
.db-export-foot { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--border-soft); font-size: 11.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--placeholder); }
.dl-err-toast { position: fixed; right: 20px; bottom: 20px; z-index: 9999; max-width: 460px; display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: start; background: var(--red-soft); border: 1px solid var(--red-border); border-radius: var(--radius-sm); padding: 12px 14px; box-shadow: var(--shadow-md); }
.dl-err-toast b { grid-column: 1; color: var(--red-text); font-size: 13.5px; }
.dl-err-toast span { grid-column: 1; color: var(--text-2); font-size: 12px; font-family: var(--mono); word-break: break-word; line-height: 1.5; }
.dl-err-toast button { grid-column: 2; grid-row: 1 / span 2; background: none; border: none; color: var(--red-text); cursor: pointer; padding: 0 2px; font-size: 13px; }

.ai-msg { font-size: 13px; line-height: 1.5; padding: 9px 12px; border-radius: 12px; max-width: 92%; }
.ai-msg.agent { background: var(--accent); color: var(--foreground); align-self: flex-start; }
/* System status in the dashboard editor: the interface reporting a fact, not the assistant. */
.ai-msg.system { background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--orange, #f59e0b); color: var(--text); align-self: stretch; max-width: 100%; }
.ai-msg.user { background: var(--surface); color: var(--text); align-self: flex-end; }
.ai-msg.pending { color: var(--muted); }
/*
 * A CONFIRMATION CARD. It is deliberately not styled as the assistant speaking: it is the interface
 * asking, with the real numbers on it, and two buttons. Nothing about it is a sentence the user
 * answers in the composer — consent is attached to the action id these buttons carry.
 */
/*
 * THE EDGE IS TARTINI AI'S, AND RED MEANS SOMETHING WENT WRONG.
 *
 * Every one of these cards used to be red, so the colour said nothing: a query waiting to be read, a
 * table being rebuilt and a run that actually failed all looked equally alarming, and by the third
 * one nobody reads the edge at all. A gray edge is the product asking; red is reserved for the one
 * state where something really broke, which is `.failed` — set from the card's own resolved status,
 * never from how consequential the action feels.
 *
 * Cancelled and expired keep the gray edge on purpose: nothing went wrong in either, and the sentence
 * inside already says which it was.
 */
.ai-msg.confirm { background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--muted-foreground); color: var(--text); align-self: stretch; max-width: 100%; padding: 12px 14px; }
.ai-msg.confirm.failed { border-left-color: var(--red); }
.ai-confirm-h { font-weight: 700; font-size: 13.5px; margin-bottom: 6px; }
.ai-confirm-what { font-size: 12.5px; color: var(--muted); margin-bottom: 8px; }
/*
 * WHY A CARD IS BEING ASKED AGAIN. Only a `pending` row handed back by the recovery pass has this,
 * and it is the first thing to read: the person pressed Confirm, watched nothing happen, and is
 * being shown the same question. It is not an error — nothing broke and nothing changed — so it is
 * the ordinary text colour on a quiet ground rather than the red an actual failure gets.
 */
.ai-confirm-again { font-size: 12.5px; color: var(--text); background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px; padding: 7px 9px; margin-bottom: 8px; }
.ai-confirm-refs { font-family: var(--mono); font-size: 11.5px; color: var(--text); background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px; padding: 7px 9px; margin-bottom: 9px; max-height: 148px; overflow-y: auto; }
.ai-confirm-refs div { padding: 1px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* How the thing being confirmed will be kept up to date. It sits above the statement because it is
   the fact the statement itself cannot tell you. */
.ai-confirm-refresh { font-size: 12.5px; color: var(--text); margin-bottom: 8px; }
/*
 * THE PHYSICAL CONTRACT OF A MANAGED SCHEDULED TABLE. It is a definition list rather than prose
 * because every row is a decision somebody has to be able to check at a glance — what a row is,
 * which calendar `ds` runs on, which days each run rewrites — and prose buries exactly that.
 */
.ai-confirm-contract { border: 1px solid var(--border-soft); border-radius: 8px; padding: 8px 10px; margin-bottom: 9px; background: var(--surface); }
.ai-confirm-crow { display: flex; gap: 10px; align-items: baseline; font-size: 12px; line-height: 1.5; padding: 2px 0; }
.ai-confirm-crow span { color: var(--muted); flex: 0 0 148px; }
.ai-confirm-crow b { color: var(--text); font-weight: 600; min-width: 0; word-break: break-word; }
/* WHAT WILL BE DIFFERENT: the schedule a deletion takes with it, the runs a backfill replays. */
.ai-confirm-changes { margin: 0 0 9px; padding-left: 18px; font-size: 12.5px; color: var(--text); }
.ai-confirm-changes li { margin-bottom: 2px; }
.ai-confirm-actions { display: flex; gap: 8px; align-items: center; }
.ai-confirm-note { font-size: 11.5px; color: var(--placeholder); margin-left: auto; }
/*
 * WHAT A TURN PRODUCED, under the sentence that produced it. These are rendered from server state
 * (real filename, real size, real URL), never from a path the model typed into its reply — so the
 * controls are here whatever words it chose, and nobody has to glue a host onto a path.
 */
.ai-files { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.ai-file { border: 1px solid var(--border-soft, var(--border)); border-radius: 16px; background: var(--card); overflow: hidden; }
.ai-file-row { display: flex; align-items: center; gap: 14px; padding: 14px 16px; }
.ai-file-main { min-width: 0; flex: 1; }
.ai-file-name { font-size: 14px; font-weight: 700; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ai-file-meta { font-size: 12px; color: var(--placeholder); margin-top: 2px; }
.ai-file-acts { display: flex; gap: 10px; flex: none; align-items: center; }
/*
 * ONE primary action per file, and it is the one people came for. Download is filled; Preview, Open
 * and Copy link are outlined, because three equal buttons turn an offer into a decision.
 */
.ai-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 36px; padding: 0 16px; box-sizing: border-box; border-radius: var(--radius-control); font-size: 14px; font-weight: 500; line-height: 20px; cursor: pointer; text-decoration: none; white-space: nowrap; border: 1px solid transparent; transition: background .15s, box-shadow .15s; }
.ai-btn svg { width: 17px; height: 17px; flex: none; }
.ai-btn.sm { height: 32px; padding: 0 12px; font-size: 13px; border-radius: var(--radius-control); }
.ai-btn.primary { color: var(--primary-foreground); background: var(--primary); box-shadow: none; }
.ai-btn.primary:hover { background: var(--primary-hover); box-shadow: none; }
.ai-btn.ghost { color: var(--foreground); background: var(--background); border-color: var(--border); box-shadow: var(--shadow-xs); }
.ai-btn.ghost:hover { background: var(--accent); }
.ai-btn.ghost.done { color: var(--foreground); border-color: var(--border); background: var(--accent); }
/*
 * A chart is SHOWN. It is an `<img>` pointing at the artifact route, which serves a sanitized SVG as
 * `image/svg+xml` — inert by specification once an image element loads it, which is why this needs
 * no new tag in the chat's own markup allowlist.
 */
.ai-file-img { background: #fff; border-bottom: 1px solid var(--border-soft); padding: 12px; }
.ai-file-img img { display: block; width: 100%; height: auto; max-height: 340px; object-fit: contain; }
.ai-preview { border: 1px solid var(--border-soft, var(--border)); border-radius: 16px; overflow: hidden; margin-top: 10px; background: var(--card); }
.ai-preview-h { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 14px; font-size: 13px; font-weight: 700; border-bottom: 1px solid var(--border-soft); }
.ai-preview-frame { display: block; width: 100%; height: 420px; border: 0; background: #fff; }
.ai-note { font-size: 11px; color: var(--placeholder); text-align: center; padding: 0 12px 12px; }

/* The Stop control beside a working answer. */
.ai-stop { border: 1px solid var(--border); border-radius: 999px; background: var(--card); color: var(--muted);
  font-size: 12px; padding: 2px 10px; cursor: pointer; }
.ai-stop:hover { color: var(--text); border-color: var(--border-strong, var(--border)); }

/* ===================================================================
   Tables feature (index + detail). Reuses the Dashboards design system.
   =================================================================== */
.mono { font-family: var(--mono); }

/* index table list */
.thead { display: grid; grid-template-columns: minmax(240px, 2.2fr) 1.2fr 1fr 0.7fr 0.7fr 0.95fr 130px; align-items: center; gap: 14px; }
.thead { padding: 13px 22px; border-bottom: 1px solid var(--border-soft); font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--placeholder); }
.tname { font-weight: 700; font-size: 14.5px; }
.tdesc { font-size: 12.5px; color: var(--muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ava { display: grid; place-items: center; border-radius: 50%; color: #fff; font-weight: 700; flex: none; }
.ava.sm { width: 30px; height: 30px; font-size: 12px; }
.type-badge { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; line-height: 16px; font-weight: 500; padding: 2px 8px; border-radius: 999px; border: 1px solid transparent; }
.type-badge svg { width: 14px; height: 14px; }
.type-badge.metric { background: var(--secondary); color: var(--secondary-foreground); }
.type-badge.raw { background: var(--secondary); color: var(--muted-foreground); }
/*
 * The two kinds of warehouse row. `source` is somebody else's table that Tartini AI can read, so it is
 * quiet; `managed` is Tartini AI's own object, so it carries the product's colour — the badge is the
 * fastest way to tell which rows have a lifecycle and which are read-only.
 */
.type-badge.source { background: var(--secondary); color: var(--muted-foreground); }
.type-badge.managed { background: var(--accent); color: var(--foreground); }
/*
 * DERIVED is shadcn's secondary badge (it was purple once). It is the badge for a table a query PRODUCES, and it replaces `.managed`, which
 * named Tartini AI's bookkeeping rather than the table. Who created it is a separate fact and gets a
 * quiet sub-label rather than a second colour — two colours for two axes on one row reads as one
 * four-way scale, which is not what either axis means.
 */
.type-badge.derived { background: var(--accent); color: var(--foreground); }
.origin-toastia { color: var(--foreground); font-weight: 600; }
.tby { font-size: 12px; color: var(--muted); margin-top: 2px; }

/* schema tab */
.schema-cols { display: grid; grid-template-columns: 280px 1fr; gap: 18px; align-items: start; }
/* The table page's side card ("In BigQuery"): shadcn's Card (the 2026-10-09 canvas; it was a gray box). */
.ai-sum { background: var(--background); border: 1px solid var(--border); border-radius: var(--radius-xl); box-shadow: var(--shadow-xs); padding: 20px; }
/* `.ai-sum-head` survives for the SOURCE rail's own heading (`.src-sec`, with its Open-in-BigQuery
   link). The generated-prose body it used to sit above is gone. */
.ai-sum-head { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 16px; color: var(--foreground); margin-bottom: 12px; }
.ai-sum-head svg { width: 16px; height: 16px; }
.ai-sum-built { font-size: 12.5px; font-weight: 700; color: var(--foreground); margin: 16px 0 8px; }
/*
 * A source table's rail: the warehouse's own facts where a managed table has its AI summary. Plain
 * label/value pairs, because there is nothing generated here to style — Tartini AI has nothing grounded
 * to say about somebody else's table beyond what BigQuery told it.
 */
/* The workspace-wide description action, above the list it is about. */
.tw-genbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; padding: 11px 14px; border: 1px solid var(--border); border-radius: 12px; background: var(--accent); }
.tw-genbar span { font-size: 12px; color: var(--muted); flex: 1; min-width: 220px; }
.tw-genbar.warn { border-color: var(--amber-border, #fde68a); background: var(--amber-soft, #fffbeb); }
.tw-genbar .spinner { flex: 0 0 auto; min-width: 0; }
/* A table's description in the list, with a generated one tinted the way it is everywhere else. */
.src-desc { font-family: inherit !important; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A description Tartini AI wrote says so with Tartini AI's ✦ (its tag mark), not with a warning colour
   (the 2026-10-09 canvas draws every description muted). */
.src-desc.generated { color: var(--muted); }
.src-desc.generated::before { content: "✦\00a0"; color: var(--brand-mark); }

.src-facts { display: flex; flex-direction: column; gap: 7px; }
.src-fact { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12.5px; }
.src-fact span { color: var(--muted); }
.src-fact b { font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 60%; }

/*
 * A source table's schema, with WHERE EACH DESCRIPTION CAME FROM visible at a glance. A sentence the
 * warehouse team wrote and a sentence a model wrote are worth different amounts of trust, so a
 * generated one carries its own tint as well as its badge — the difference survives even if nobody
 * reads the label.
 */
/* The rail's own heading, with the link to the object it is all about pushed to the far edge. */
.ai-sum-head.src-sec { justify-content: space-between; }
.ai-sum-head.src-sec > span { display: flex; align-items: center; gap: 7px; }
.src-edit { font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.src-col-row { display: grid; grid-template-columns: minmax(180px, 1.3fr) 120px 110px minmax(220px, 2fr) 160px; gap: 14px; align-items: start; padding: 12px 16px; }
.src-col-row { border-bottom: 1px solid var(--border); font-size: 13px; }
.src-col-row:last-child { border-bottom: 0; }
/* A nested field is indented under its parent, with a rule that makes the parentage visible. */
.src-col-row.nested .col-name { padding-left: calc(var(--depth, 1) * 22px); border-left: 2px solid var(--border); margin-left: 10px; }
/* A local table's columns have no BigQuery mode, so that slot is dropped rather than left as an
   always-blank column. Everything else about the grid is identical on both kinds of table. */
.src-col-row.no-mode { grid-template-columns: minmax(180px, 1.3fr) 130px minmax(220px, 2fr) 190px; }
.src-col-row.edited { background: var(--accent); }
.src-col-row.deleted { opacity: .5; }
.src-col-row.deleted .col-name .mono { text-decoration: line-through; }
/* The trailing cell carries the origin badge and, on a table Tartini AI owns, its edit menu. */
.src-col-row .col-origin { display: flex; align-items: center; justify-content: space-between; gap: 8px; position: relative; }
.col-mode-v { font-size: 12px; color: var(--muted); font-weight: 600; }
.desc-body { font-size: 13px; line-height: 1.5; }
.desc-body.generated { background: var(--amber-soft, #fffbeb); border: 1px solid var(--amber-border, #fde68a); border-radius: 10px; padding: 8px 10px; }
.desc-tag { display: flex; align-items: center; gap: 5px; margin-top: 6px; font-size: 12px; line-height: 16px; font-weight: 500; color: var(--muted-foreground); }
.desc-tag svg { width: 11px; height: 11px; color: var(--brand-mark); }
.desc-none { font-size: 12.5px; color: var(--placeholder); }
/* A generic template is a placeholder, and reads as one — it is not somebody's description. */
.desc-body.template { color: var(--placeholder); font-style: italic; }
.desc-origin { display: inline-block; font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 999px; white-space: nowrap; }
.desc-origin.from-bq { background: var(--accent); color: var(--foreground); }
.desc-origin.from-sdk, .desc-origin.from-defined { background: var(--border-soft); color: var(--muted); }
.desc-origin.from-toastia { background: transparent; color: var(--amber-text, #b45309); padding-left: 0; }
/* The legend lost its heading, so it carries the spacing the heading used to provide. */
.src-legend { display: flex; flex-direction: column; gap: 12px; margin-top: 18px; }
.src-legend > div { display: grid; grid-template-columns: 18px 1fr; gap: 4px 8px; font-size: 12px; }
.src-legend b { grid-column: 2; font-size: 12.5px; }
.src-legend span:last-child { grid-column: 2; color: var(--muted); line-height: 1.45; }
.lg-ic { grid-row: span 2; font-size: 13px; }
.lg-ic.bq { color: var(--foreground); }
.lg-ic.sdk { color: var(--muted); }
.lg-ic.ai { color: var(--amber-text, #b45309); }
.src-legend-count { margin-top: 10px; font-size: 11.5px; color: var(--muted); }
.src-genmsg { margin-top: 8px; font-size: 11.5px; color: var(--muted); }
.schema-main { min-width: 0; }
.col-name { display: flex; align-items: center; gap: 10px; font-weight: 600; }
.col-ic { width: 26px; height: 26px; border-radius: 7px; background: var(--accent); color: var(--foreground); display: grid; place-items: center; flex: none; }
.col-ic svg { width: 15px; height: 15px; }
.col-dep { font-size: 10px; font-weight: 700; text-transform: uppercase; color: var(--amber-text); background: var(--amber-soft); padding: 1px 6px; border-radius: 5px; }
.col-desc { font-size: 13px; color: var(--muted); }
.ai-note { margin-top: 16px; background: var(--accent); border: 1px solid var(--border); border-radius: 12px; padding: 13px 16px; font-size: 13px; color: var(--text-2); display: flex; align-items: center; gap: 8px; }
.ai-note svg { width: 16px; height: 16px; color: var(--brand-mark); flex: none; }

/* preview tab */
.preview-card { overflow: hidden; }
.pv-head { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border-soft); }
.pv-title { display: flex; align-items: center; gap: 12px; font-weight: 700; font-size: 15px; }
.dbi-select.sm { padding: 5px 10px; font-size: 12.5px; }
.pv-actions { display: flex; gap: 10px; align-items: center; }
.pv-grid { overflow-x: auto; }
.pv-grid table { width: 100%; border-collapse: collapse; font-size: 13px; }
.pv-grid th { text-align: left; padding: 0; border-bottom: 1px solid var(--border); background: var(--white); position: sticky; top: 0; }
.th-btn { display: flex; align-items: center; gap: 6px; background: none; border: none; padding: 12px 16px; font-size: 12px; font-weight: 700; color: var(--muted); cursor: pointer; white-space: nowrap; }
.th-btn .th-ic { width: 15px; height: 15px; opacity: .7; display: inline-grid; }
.th-btn svg { width: 13px; height: 13px; opacity: .5; }
.th-btn:hover { color: var(--foreground); }
.pv-grid td { padding: 12px 16px; border-bottom: 1px solid var(--border-soft); color: var(--text); white-space: nowrap; }
.pv-grid td.null { color: var(--placeholder); }
.pv-grid td.pv-empty { text-align: center; padding: 40px 20px; }
.pv-grid td.pv-empty b { display: block; color: var(--text-2); font-size: 15px; margin-bottom: 6px; }
.pv-grid td.pv-empty span { color: var(--muted); font-size: 13px; max-width: 460px; display: inline-block; line-height: 1.55; }
.pv-grid td.pct-val { color: var(--green-text); font-weight: 700; }
.pv-grid tr:hover td { background: #fcfcfe; }
.pv-foot { display: flex; justify-content: space-between; align-items: center; padding: 14px 20px; border-top: 1px solid var(--border-soft); font-size: 13px; color: var(--muted); }
.pg-dots { padding: 0 6px; color: var(--placeholder); }

/* partition tab — three regions, three facts.
   Job runs = what the schedule DID. Partition status = what the warehouse HOLDS. The chart = how
   much. They reuse the preview card, grid, pager and select wholesale; everything below is only
   what those three need and the preview does not have. */
.pt-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px; align-items: stretch; }
.pt-cols > * { min-width: 0; }
.pt-cols .pv-foot > span { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pt-cols > .preview-card { display: flex; flex-direction: column; }
.pt-cols > .preview-card > .pt-chart-body { flex: 1; display: flex; align-items: center; }
.pt-cols > .preview-card > .pt-chart-body > * { width: 100%; }
.pt-cols > .preview-card > .pt-grid { flex: 1; }
.pt-sub { font-size: 12.5px; color: var(--muted); margin-top: 4px; line-height: 1.5; }

/* --- 1. Job runs: a compact monitoring strip, one small box per ds ---------------------------
   Compact is the whole point: about a month has to read as one glance-able row, so the boxes are
   small, the gaps are small, and the row does NOT stretch to fill the card. A day is one cell no
   matter how many times it ran — a retry and a backfill are not extra squares, they are just a
   newer latest run. */
.jr-card { margin-bottom: 18px; }
.jr-head { align-items: flex-start; }
.jr-actions { gap: 14px; }
.jr-actions .btn-ghost { white-space: nowrap; }
.jr-actions .btn-ghost svg { width: 14px; height: 14px; flex: none; }
.jr-strip { display: flex; flex-wrap: wrap; gap: 2px 3px; padding: 14px 20px 4px; }
.jr-cell { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 36px; }
.jr-date { font-family: var(--mono); font-size: 9.5px; color: var(--muted); letter-spacing: -0.2px; }
/* A finished day is a solid colour — at this size a ring reads as "empty", which is the opposite of
   what a green box means. */
.jr-box { width: 18px; height: 18px; border-radius: 5px; border: 1px solid transparent; }
.jr-box.ok { background: var(--green); border-color: var(--green); }
.jr-box.bad { background: var(--red); border-color: var(--red); }
/* Waiting is a turning outline, not a colour: nothing has happened yet, so there is nothing to fill. */
.jr-box.wait { border-radius: 50%; border: 2px solid var(--border); border-top-color: var(--border-strong); background: none; animation: jrspin 0.9s linear infinite; }
.jr-box.none { background: var(--surface); border-color: var(--border); border-style: dashed; }
@keyframes jrspin { to { transform: rotate(360deg); } }
.jr-legend { display: inline-flex; gap: 12px; font-size: 12px; font-weight: 600; color: var(--muted); }
.jr-lg { display: inline-flex; align-items: center; gap: 5px; }
.jr-lg::before { content: ''; width: 8px; height: 8px; border-radius: 50%; }
.jr-lg.ok::before { background: var(--green); }
.jr-lg.bad::before { background: var(--red); }
.jr-lg.wait::before { background: var(--muted-foreground); }
.jr-foot { padding: 10px 20px 14px; font-size: 12.5px; color: var(--muted); display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.jr-status.ok { color: var(--green-text); }
.jr-status.bad { color: var(--red-text); }
.jr-status.wait { color: var(--foreground); }
.jr-status.none { color: var(--muted); }
.jr-warn { color: var(--amber-text); background: var(--amber-soft); border-radius: 6px; padding: 1px 8px; }
.jr-next { color: var(--text-2); }
.jr-next b { color: var(--text); }
.jr-next.paused { color: var(--amber-text); }
.jr-local { color: var(--muted); }

/* --- 2. Partition status: the partition, and nothing about the job ---------------------------
   No status column and no red rows. A partition holding 0 records is a normal successful result,
   so the only row that reads differently is one where no partition exists at all — and that is
   stated in words (`Not available`), quietly. */
/* The ingestion caveat: informative, not a warning — a recent 0 is normal, not a problem. */
.pt-note { display: flex; gap: 8px; align-items: flex-start; padding: 11px 20px; background: var(--surface); border-bottom: 1px solid var(--border-soft); font-size: 12.5px; line-height: 1.55; color: var(--muted); }
.pt-note svg { width: 15px; height: 15px; flex: none; margin-top: 1px; color: var(--placeholder); }

.pt-grid th { padding: 12px 16px; font-size: 12px; font-weight: 700; color: var(--muted); white-space: nowrap; }
.pt-grid th.pt-num, .pt-grid td.pt-num { text-align: right; }
.pt-grid td.pt-pid { color: var(--muted); }
.pt-grid tr.pt-absent td { background: var(--surface); }
.pt-grid tr.pt-absent:hover td { background: var(--app-bg); }

/* --- 3. Number of records: volume only, one series, no status colouring ----------------------- */
.pt-chart-body { padding: 14px 18px 4px; }
.pt-chart-wrap { position: relative; }
.pt-chart { width: 100%; height: auto; display: block; }
.pt-cursor { pointer-events: none; }
.pt-tip { position: absolute; top: 10px; pointer-events: none; background: var(--card); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow-sm); padding: 9px 12px; font-size: 12.5px; white-space: nowrap; z-index: 2; }
.pt-tip-ds { font-weight: 700; color: var(--text); margin-bottom: 5px; }
.pt-tip-row { display: flex; align-items: center; gap: 7px; color: var(--muted); }
.pt-tip-row b { margin-left: auto; padding-left: 14px; color: var(--text); }
.pt-tip-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--chart-purple); flex: none; }
.pt-legend { display: flex; flex-wrap: wrap; gap: 14px; font-size: 12.5px; font-weight: 600; }
.pt-lg { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }
.pt-lg::before { content: ''; width: 14px; height: 3px; border-radius: 2px; background: var(--chart-purple); }
.pt-reserved { margin-top: 0; }
.pt-cols > .tw-note { grid-column: 1 / -1; }
@media (max-width: 640px) { .pt-cols { grid-template-columns: 1fr; } }

/* --- 4. The scheduled query: what the runs above RUN --------------------------------------
   Facts first, statement second, and the statement is read-only text — this page shows what
   Google holds and has no way to change it. It scrolls inside its own box rather than pushing the
   partitions off the screen, and it wraps rather than clipping: a line nobody can finish reading
   is not the query. */
.sq-card { margin-top: 18px; }
.sq-head { align-items: flex-start; }
.sq-facts { display: flex; flex-wrap: wrap; gap: 8px 28px; padding: 14px 20px; border-bottom: 1px solid var(--border-soft); }
.sq-fact { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; }
.sq-fact span { color: var(--muted); }
.sq-fact b { color: var(--text); font-size: 12.5px; }
.sq-local { color: var(--muted); font-weight: 500; }
.sq-sql { margin: 0; padding: 16px 20px; max-height: 360px; overflow: auto; font-size: 12.5px; line-height: 1.65; color: var(--text); background: var(--surface); white-space: pre-wrap; word-break: break-word; }

@media (max-width: 600px) {
  .schema-cols { grid-template-columns: 1fr; }
  .thead { grid-template-columns: 1.6fr 1fr 1fr 90px; }
  .thead span:nth-child(4), .thead span:nth-child(5), .thead span:nth-child(6) { display: none; }
}

/* "Open ↗" link in the preview browser bar (deployed-site preview) */
.wb-bbar .wb-open { margin-left: auto; font-size: 12px; font-weight: 600; color: var(--foreground); text-decoration: none; padding: 2px 8px; border: 1px solid var(--border); border-radius: 6px; }
.wb-bbar .wb-open:hover { background: var(--accent); }

/* Event trigger chip — WHEN the event fires (unified event+trigger model). A click and
   an exposure must never look the same; a viewable impression states its real rule. */
.wev-trig { display: inline-block; margin-left: 6px; font-size: 10px; font-weight: 500; letter-spacing: .02em; padding: 2px 7px; border-radius: 999px; color: var(--muted); background: var(--accent); vertical-align: middle; }
.wev-trig.viewable_impression { color: var(--foreground); background: var(--accent); }
/* The trigger kinds are words on the same gray tag — no colour of their own (2026-10-09). */
.wev-trig.click, .wev-trig.business_state { color: var(--foreground); background: var(--accent); }

/* ===== Event details bar — the single explanation surface for a selected event ==========
   Docked to the right of the Website Preview, collapsible, shown only when an event is
   selected. Every value in it is real (AI analysis / parsed code / live measurement). */
.wb-evd { position: absolute; top: 0; right: 0; bottom: 0; width: 340px; z-index: 60;
  background: var(--card, #fff); border-left: 1px solid var(--border, #e2e8f0);
  box-shadow: -8px 0 24px rgba(15,23,42,.06); display: flex; flex-direction: column; overflow: hidden; }
.wb-evd.collapsed { width: 44px; }
.wb-evd.collapsed .evd-body, .wb-evd.collapsed .evd-title, .wb-evd.collapsed .evd-x { display: none; }
.wb-evd .evd-head { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border); flex: none; }
.wb-evd .evd-title { font-weight: 700; font-size: 14px; color: var(--text); }
.wb-evd .evd-collapse, .wb-evd .evd-x { border: 0; background: transparent; cursor: pointer; color: var(--muted); font-size: 17px; line-height: 1; padding: 2px 4px; }
.wb-evd .evd-x { margin-left: auto; font-size: 20px; }
.wb-evd .evd-body { overflow: auto; padding: 0 0 18px; }
.wb-evd .evd-id { padding: 14px; border-bottom: 1px solid var(--border-soft, #eef2f7); }
.wb-evd .evd-name { font-family: var(--mono); font-size: 15px; font-weight: 700; color: var(--text); word-break: break-all; }
.wb-evd .evd-status { display: inline-block; margin-top: 8px; font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 999px; background: var(--surface); color: var(--muted); }
.wb-evd .evd-status.on_page { color: var(--green, #16a34a); background: rgba(22,163,74,.12); }
.wb-evd .evd-status.locate { color: #0369a1; background: rgba(3,105,161,.10); }
.wb-evd .evd-status.page_level { color: var(--foreground); background: var(--accent); }
.wb-evd .evd-status.not_in_site { color: var(--muted); background: var(--surface); }
.wb-evd .evd-status.locating { color: #b45309; background: rgba(217,119,6,.12); }
.wb-evd .evd-sec { padding: 13px 14px; border-bottom: 1px solid var(--border-soft, #eef2f7); }
.wb-evd .evd-sec-h { display: flex; align-items: center; gap: 7px; font-weight: 700; font-size: 12.5px; color: var(--text); margin-bottom: 7px; }
.wb-evd .evd-sec-h svg { width: 15px; height: 15px; flex: none; color: var(--muted); }
.wb-evd .evd-p { font-size: 12.5px; line-height: 1.55; color: var(--text-2, #475569); margin: 0; }
.wb-evd .evd-p.muted, .wb-evd .muted { color: var(--placeholder, #94a3b8); }
.wb-evd .evd-row { display: flex; gap: 10px; font-size: 12.5px; padding: 3px 0; }
.wb-evd .evd-k { color: var(--muted); min-width: 92px; flex: none; }
.wb-evd .evd-v { color: var(--text); word-break: break-word; }
.wb-evd .evd-v code { font-family: var(--mono); font-size: 12px; }
.wb-evd .evd-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.wb-evd .evd-chip { font-family: var(--mono); font-size: 11.5px; padding: 3px 8px; border-radius: 6px; background: var(--surface); color: var(--text-2); }
.wb-evd .evd-why { margin: 0; padding-left: 16px; font-size: 12.5px; line-height: 1.6; color: var(--text-2); }
.wb-evd .evd-why li { margin: 2px 0; }
.wb-evd .evd-src { margin-top: 7px; font-size: 11px; font-style: italic; color: var(--placeholder); }
.wb-evd .evd-conf { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.wb-evd .evd-conf-bar { flex: 1; height: 5px; border-radius: 3px; background: var(--surface); overflow: hidden; }
.wb-evd .evd-conf-bar span { display: block; height: 100%; background: var(--green, #16a34a); }
.wb-evd .evd-conf-n { font-weight: 700; font-size: 12px; color: var(--text); }
.wb-evd .evd-conf-l { font-size: 11px; color: var(--muted); }
.wb-evd .evd-more > summary { cursor: pointer; font-size: 11.5px; color: var(--muted); margin-top: 6px; }
@media (prefers-color-scheme: dark) { .wb-evd { background: #1e293b; border-color: #334155; } }

/* The preview frame changes height once per navigation (a single re-measure at the reference
   viewport). Animate that one change so it reads as motion, not a flash. */
#siteFrame { transition: height .18s ease-out; }
@media (prefers-reduced-motion: reduce) { #siteFrame { transition: none; } }

/* Honest notice when the PREVIEWED page cycles between routes and we stop following it. */
.wb-navloop { background: var(--amber-soft, #fffbeb); border-bottom: 1px solid var(--border); padding: 10px 14px; font-size: 12.5px; }
.wb-navloop .wnl-t { font-weight: 700; color: var(--amber-text, #b45309); margin-bottom: 4px; }
.wb-navloop .wnl-b { color: var(--text-2); line-height: 1.55; }
.wb-navloop code { font-family: var(--mono); font-size: 11.5px; }
.wb-navloop .wnl-x { margin-top: 8px; border: 1px solid var(--border); background: var(--card, #fff); color: var(--text); font-size: 11.5px; padding: 4px 10px; border-radius: 7px; cursor: pointer; }

/* ---- Website Preview · Google Analytics configuration status ---------------
   The connection is a project-level capability, but its EFFECT is visible here:
   which GA4 automatic events Preview can speak about with Google's own evidence.
   So the status and the re-sync live in this header, not only in Settings. */
.wb-phead-right { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.wb-phead-right .diff-expand { margin-left: 0; }
/*
 * ── THE SYNC CHIP ────────────────────────────────────────────────────────────────────────────
 *
 * One connection's state, WHEN Tartini AI last confirmed it, and the button that confirms it again.
 * Two surfaces wear it: GA4 in the Website Preview header, BigQuery in the Tables project header.
 * They are different connections with different evidence, but a person reads them the same way, so
 * there is ONE set of rules here rather than a second copy that drifts. The `wb-ga-*` names are the
 * original GA4 ones and stay because run.html is written against them.
 */
.wb-ga-status, .sync-chip { display: inline-flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.wb-ga-status:empty, .sync-chip:empty { display: none; }
.wb-ga-dot, .sync-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex: none; }
.wb-ga-dot.ok, .sync-dot.ok { background: var(--tartini-green); }
.wb-ga-dot.warn, .sync-dot.warn { background: var(--tartini-orange); }
/* Sync GA4 (and the Tables' sync): shadcn's outline sm button. */
.wb-ga-btn, .sync-btn { display: inline-flex; align-items: center; height: 32px; box-sizing: border-box; font-size: 14px; font-weight: 500; padding: 0 12px; border-radius: var(--radius-control); border: 1px solid var(--border); background: var(--background); color: var(--text); box-shadow: var(--shadow-xs); cursor: pointer; white-space: nowrap; }
a.wb-ga-btn { display: inline-block; text-decoration: none; }
.wb-ga-btn:hover:not(:disabled), .sync-btn:hover:not(:disabled) { background: var(--accent); }
.wb-ga-btn:disabled, .sync-btn:disabled { opacity: 0.5; cursor: default; }
.wb-ga-note:empty { display: none; }
.wb-ga-note .conn-banner { margin: 10px 18px 4px; display: flex; align-items: flex-start; gap: 10px; }
.wb-ga-note .conn-banner .x { margin-left: auto; cursor: pointer; opacity: 0.6; font-size: 15px; line-height: 1; padding: 0 2px; }
.wb-ga-note .conn-banner .x:hover { opacity: 1; }

/* Add Project · the Connect GitHub action the server attached to a setup turn. */
.sc-gh-note { font-size: 12.5px; color: var(--muted); }
.sc-gh-out { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
.sc-gh-out input { flex: 1; min-width: 200px; font-family: var(--mono); font-size: 12px; border: 1px solid var(--border); border-radius: 8px; padding: 7px 9px; }
.sc-gh-out button { font-size: 12.5px; font-weight: 600; padding: 6px 11px; border-radius: 8px; border: 1px solid var(--border); background: #fff; cursor: pointer; }
.sc-gh-hint { flex-basis: 100%; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
.sc-gh-steps { font-size: 13px; line-height: 1.6; margin-top: 10px; }
.sc-gh-steps ol { margin: 6px 0 8px; padding-left: 20px; }
.sc-gh-steps li { margin: 3px 0; }
.sc-gh-steps code { font-family: var(--mono); font-size: 12px; background: var(--accent); padding: 1px 5px; border-radius: 5px; }
.sc-gh-out a { font-size: 12.5px; font-weight: 600; }

/*
 * ── THE WAY OUT OF A SETUP THAT WILL NOT FINISH ───────────────────────────────────────────────
 *
 * Deliberately quiet: it sits under the action that finishes setup, in small type, and is never
 * competing with it. It is always present because the moment somebody needs it is the moment the
 * primary button has just refused.
 */
.sc-recovery {
  margin: 10px 2px 0; font-size: 12.5px; color: var(--muted); line-height: 1.7;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px;
}
.sc-recovery b { color: var(--text-2); font-weight: 600; }
.sc-linkbtn {
  background: none; border: 0; padding: 0; font: inherit; color: var(--foreground);
  font-weight: 600; cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
.sc-linkbtn:hover { color: var(--foreground); }
.sc-linkbtn:disabled { color: var(--muted); cursor: default; text-decoration: none; }

/*
 * A REFUSAL, WHERE THE BUTTON IS. It used to be pushed into the transcript, which renders ABOVE the
 * action cards in a panel that does not auto-scroll — so the one thing a person needed to read was
 * the one thing they never saw.
 */
.sc-actionerr {
  margin: 10px 0 2px; padding: 10px 12px; border-radius: 10px;
  background: var(--amber-soft); border: 1px solid var(--amber-border);
  color: var(--amber-text); font-size: 13px; line-height: 1.6;
}

/* Event details · what Google's own Data Stream configuration says about an automatic event.
   Two facts side by side: the enablement, and how well it is known. */
.wb-evd .evd-cfg { display: inline-flex; align-items: stretch; margin-top: 8px; margin-left: 6px; border-radius: 999px; overflow: hidden; font-size: 11px; font-weight: 700; }
.wb-evd .evd-cfg-state { padding: 3px 9px; }
.wb-evd .evd-cfg-ev { padding: 3px 9px; font-weight: 600; background: rgba(0,0,0,.06); }
.wb-evd .evd-cfg.cfg-on .evd-cfg-state { color: var(--green, #16a34a); background: rgba(22,163,74,.12); }
.wb-evd .evd-cfg.cfg-on .evd-cfg-ev { color: var(--green, #16a34a); background: rgba(22,163,74,.20); }
.wb-evd .evd-cfg.cfg-off .evd-cfg-state { color: var(--red-text, #b91c1c); background: rgba(185,28,28,.12); }
.wb-evd .evd-cfg.cfg-off .evd-cfg-ev { color: var(--red-text, #b91c1c); background: rgba(185,28,28,.20); }
.wb-evd .evd-cfg.cfg-unsure .evd-cfg-state { color: #b45309; background: rgba(217,119,6,.12); }
.wb-evd .evd-cfg.cfg-unsure .evd-cfg-ev { color: #b45309; background: rgba(217,119,6,.20); }
.wb-evd .evd-cfg-why { margin-top: 6px; font-size: 11.5px; line-height: 1.5; color: var(--muted); }
.wb-evd .evd-cfg-act { font-weight: 700; white-space: nowrap; }

/* ===================================================================
   Tables — Project List (level 1) + Project Workspace (level 2).
   Level 3 (table detail) keeps the existing .t-detail styles above.
   =================================================================== */
/*
 * The BigQuery setup conversation. Two visually distinct regions on purpose: the transcript above is
 * what the assistant SAID, and the block below it is what the SERVER offers — signing in and
 * confirming are always a button the server decided to show, never a sentence.
 */
/*
 * BigQuery setup is the SAME inline card Add Project uses — `.add-proj.setup-chat`, shown by
 * `.show` — so it inherits that shell wholesale and only needs its own placement here.
 */
#bqSetup { margin: 4px 0 18px; }
#bqSetup .sc-log { min-height: 180px; }
/* An action turn is an agent bubble that happens to contain controls, so it is wider than prose. */
.sc-msg.sc-action { max-width: 100%; align-self: stretch; display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.setup-choice { display: flex; justify-content: space-between; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 9px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--background); font-size: 13px; font-weight: 600; cursor: pointer; }
.setup-choice:hover { border-color: var(--border-strong); }
.setup-choice span { font-family: var(--mono); font-size: 11.5px; font-weight: 400; color: var(--muted); }
/*
 * The setup card's checklist is the SAME `statusReport` the Settings card renders, so it reuses the
 * same `.bq-check-*` rules rather than a second set that could drift. Only the container differs —
 * `.set-side` there, an agent action bubble here — so the one margin reset those rules scope to a
 * container is repeated for this one, and nothing else is.
 */
.setup-review { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.sc-action .bq-check-group .hint { margin: 0; }

/* Workspace header */
/* A note on the Tables page: shadcn's Alert — the background and the border, no tinted ground and no
   coloured bar on its left (the 2026-10-09 canvas). */
.tw-note { background: var(--background); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px 16px; font-size: 14px; color: var(--text-2); margin-bottom: 16px; line-height: 20px; }
/* A note about something that is BROKEN rather than in progress: the same shape, in the refusal colour. */
.tw-note-warn { border-color: var(--red-border); background: var(--background); color: var(--text); }
.tw-main { min-width: 0; }

/*
 * THE PROJECT WORKSPACE IS SHARED. The `tw-` prefix reads as "tables workspace" for historical
 * reasons; it is really THE project workspace layout — a header, a main list and, for Tables, the
 * saved query rail — and both Tables and Dashboards render into it. One implementation
 * is the point: the two screens are the same shape, and two copies would drift the first time either
 * one grew a breakpoint.
 */

/*
 * WHAT A TOOL REALLY DID, on its own line in the transcript. It is deliberately not styled as the
 * assistant: a receipt is the server reporting a mutation, and it survives a turn whose model never
 * wrote a sentence — which is exactly when it matters most.
 */
/* What an action did, in one line: a bordered neutral row (the shadcn canvas, 2026-10-09 — it was a
   green pill); a failure keeps the destructive text colour. */
.ai-receipt { align-self: flex-start; max-width: 92%; padding: 12px 16px; border-radius: var(--radius-control); border: 1px solid var(--border); background: var(--background); color: var(--muted-foreground); font-size: 14px; line-height: 1.5; }
.ai-receipt b { font-weight: 500; color: var(--foreground); }
.ai-receipt a { color: var(--foreground); font-weight: 500; text-decoration: underline; text-underline-offset: 4px; }
.ai-receipt.failed { color: var(--red-text); }

.tqr-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 16px 16px 12px; border-bottom: 1px solid var(--border); }
.tqr-head b { font-size: 16px; font-weight: 600; }
.tqr-count { font-size: 12px; font-weight: 500; color: var(--foreground); background: var(--accent); border-radius: 999px; padding: 2px 8px; }
.tqr-ic { flex: none; width: 32px; height: 32px; box-sizing: border-box; border-radius: 6px; border: 1px solid var(--border); background: var(--accent); color: var(--foreground); display: grid; place-items: center; }
.tqr-ic svg { width: 15px; height: 15px; }
.tqr-main { flex: 1; min-width: 0; display: grid; gap: 2px; }
.tqr-name { font-size: 13px; font-weight: 500; font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tqr-sub { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tqr-runs { flex: none; display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-weight: 700; color: var(--text-2); background: var(--surface); border-radius: 999px; padding: 3px 8px; }
.tqr-runs svg { width: 12px; height: 12px; }
.tqr-empty { padding: 14px 16px; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
.tqr-all { display: inline-flex; align-items: center; gap: 6px; padding: 12px 16px; font-size: 12.5px; font-weight: 700; color: var(--foreground); text-decoration: none; }
.tqr-all svg { width: 14px; height: 14px; }
.tqr-all:hover { text-decoration: underline; }

/* THE BACKFILLS GOOGLE IS STILL RUNNING, at the top of the Tables list column. Each job wears the
   confirmation card's shape (`.ai-msg.confirm`) — a gray edge while it runs, red only once a run failed. */
.tw-backfills { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.tw-backfills-h { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.tw-backfills-h b { font-weight: 700; }
.tw-backfills .tw-note { margin-bottom: 0; }

/* One saved query, as a card in the assistant transcript. */
.ai-query { display: flex; align-items: center; gap: 10px; background: var(--white); border: 1px solid var(--border); border-radius: 12px; padding: 11px 13px; margin: 8px 0; }
.ai-query.removed { background: var(--surface); border-style: dashed; }
.aiq-ic { flex: none; width: 30px; height: 30px; border-radius: 9px; background: var(--accent); color: var(--foreground); display: grid; place-items: center; }
.aiq-ic svg { width: 16px; height: 16px; }
.ai-query.removed .aiq-ic { background: var(--border-soft); color: var(--muted); }
.aiq-body { flex: 1; min-width: 0; }
.aiq-kind { font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.aiq-name { font-size: 13px; font-weight: 700; font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aiq-sub { font-size: 11.5px; color: var(--muted); }
.aiq-open { flex: none; display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 700; color: var(--foreground); text-decoration: none; }
.aiq-open svg { width: 14px; height: 14px; }
.aiq-open:hover { text-decoration: underline; }

/* ── Tartini AI project sidebar ──────────────────────────────────────────────────────────────
   public/project-assistant.js (docs/overall/unified-project-assistant.md §5). ONE assistant per
   project page, between the nav rail and the page: `.app-shell` = #app-sidebar | aside.pa | main.
   The messages, cards and composer wear the Tracking workbench's `.wb-*` family; only the SHELL of
   the sidebar is `.pa-*`. Every value is a tokens.css token. The collapsed preference is the
   sidebar's own (`toastiaAssistantCollapsed`), independent of the nav rail's.
   ───────────────────────────────────────────────────────────────────────────────────────────── */
.pa {
  flex: 0 0 400px; width: 400px; min-width: 0;
  align-self: flex-start; position: sticky; top: 0; height: 100vh;
  display: flex; flex-direction: column;
  background: var(--card); border-right: 1px solid var(--border);
}
.pa-panel { position: relative; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; background: var(--card); }

/* Collapsed: a narrow strip with the spark. The panel is HIDDEN, never removed — its transcript,
   its composer and a running turn all carry on behind the strip. */
.pa-strip { display: none; }
.pa.collapsed { flex-basis: 52px; width: 52px; }
.pa.collapsed .pa-panel { display: none; }
.pa.collapsed .pa-strip { display: flex; flex-direction: column; align-items: center; padding: 16px 0; }
.pa-restore {
  width: 36px; height: 36px; padding: 0; border-radius: var(--radius-sm); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: var(--accent); color: var(--foreground);
  font-size: 17px; line-height: 1; transition: background 0.12s ease, box-shadow 0.12s ease;
}
.pa-restore:hover { background: var(--accent); box-shadow: var(--shadow-sm); }
.pa-spark { color: var(--brand-mark); }

/* The header: title · + New · Expand · the icon-only Collapse. */
.pa-head {
  display: flex; align-items: center; gap: 6px; flex: none;
  padding: 12px 12px 12px 16px; border-bottom: 1px solid var(--border-soft);
  font-weight: 700; font-size: 15px;
}
.pa-title { margin-right: auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pa-hbtn {
  font: inherit; font-size: 11.5px; font-weight: 600; color: var(--text-2);
  background: var(--white); border: 1px solid var(--border); border-radius: 7px;
  padding: 3px 10px; cursor: pointer; white-space: nowrap;
  transition: border-color 0.12s ease, color 0.12s ease;
}
.pa-hbtn:hover { border-color: var(--border-strong); color: var(--foreground); }
.pa-hbtn[disabled] { opacity: .55; cursor: default; }
.pa-collapse {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex: none; padding: 0; margin-left: 2px;
  border: 1px solid var(--border); background: var(--white); color: var(--muted);
  border-radius: 8px; cursor: pointer; transition: background 0.12s ease, color 0.12s ease;
}
.pa-collapse:hover { background: var(--surface); color: var(--text); }
.pa-collapse svg { width: 16px; height: 16px; }
.pa-hbtn:focus-visible, .pa-collapse:focus-visible, .pa-restore:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }

/* The page's own factual cards (Currently tracked, the current table, the current dashboard). */
.pa-context { flex: none; max-height: 38vh; overflow-y: auto; }
/* The conversation line: its plan(s), and "No background" on an older conversation. A hint. */
.pa-convline { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding: 6px 14px; font-size: 12.5px; color: var(--muted); border-bottom: 1px solid var(--border); }
.pa-convline a { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.pa-convline-tag { padding: 1px 7px; border-radius: 999px; border: 1px solid var(--border); }
/* The setup, always in sight (design partner feedback F2): "Setup · Step k of n · the step · Continue". */
.pa-setupbar { flex: none; display: flex; align-items: center; gap: 4px; padding: 6px 14px; font-size: 12.5px; color: var(--muted); border-bottom: 1px solid var(--border); background: var(--surface, transparent); }
.pa-setupbar__label { font-weight: 600; color: var(--text); }
.pa-setupbar__where { white-space: nowrap; }
.pa-setupbar__title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.pa-setupbar__go { flex: none; margin-left: 4px; }
.pa-setupbar__dot { white-space: pre; }
/* A sketch of GA4's Add users panel beside the GA4 Viewer step (design partner feedback F4). */
.t-ga4-sketch { margin: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; font-size: 12px; color: var(--text); background: var(--tartini-surface); }
.t-ga4-sketch__bar { padding: 6px 10px; border-bottom: 1px solid var(--border); color: var(--muted); background: var(--surface); }
.t-ga4-sketch__panel { display: grid; gap: 6px; padding: 10px; }
.t-ga4-sketch__head { display: flex; justify-content: space-between; align-items: center; font-weight: 600; }
.t-ga4-sketch__add { padding: 2px 10px; border-radius: 4px; background: var(--text); color: var(--tartini-surface); font-weight: 500; }
.t-ga4-sketch__label { color: var(--muted); }
.t-ga4-sketch__keep { margin-left: 6px; font-weight: 600; color: var(--amber-text); }
.t-ga4-sketch__field { padding: 5px 8px; border: 1px solid var(--border); border-radius: 4px; font-family: var(--font-mono); font-size: 11.5px; overflow-wrap: anywhere; }
.t-ga4-sketch__roles { display: flex; flex-wrap: wrap; gap: 4px 12px; margin: 0; padding: 0; list-style: none; color: var(--muted); }
.t-ga4-sketch__roles li.is-on { color: var(--text); font-weight: 600; }
.t-ga4-sketch figcaption { padding: 6px 10px; border-top: 1px solid var(--border); color: var(--muted); }

.pa-context:empty { display: none; }
.pa-context:not(:empty) { border-bottom: 1px solid var(--border-soft); }
/* THE CONTEXT CARD — the one shape a page's facts take in that slot (`assistant.contextEl()`): the
   dashboard count and the current dashboard with its version state on the Dashboard pages, the table,
   query and connection facts on the Tables pages. `.pa-ctx` holds one or more heading lines
   (`.pa-ctx-head`: an icon and a title that ellipsises, so a long name never widens the panel), each
   with an optional line under it (`.pa-ctx-sub`). Statements, never controls. Tracking's two cards
   keep their own collapsible `.wb-tracked` / `.wb-events` shape. */
.pa-ctx { padding: 12px 16px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.pa-ctx-head { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: 14px; font-weight: 500; color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-control); padding: 10px 12px; background: var(--background); }
.pa-ctx-head svg { width: 16px; height: 16px; flex: none; color: var(--foreground); }
.pa-ctx-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pa-ctx-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; padding: 0 2px; font-size: 12px; color: var(--muted); }

/* The transcript is the one part that scrolls. */
.pa-log {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
  padding: 18px 16px; display: flex; flex-direction: column; gap: 15px;
}
.pa-log .wb-bubble { max-width: calc(100% - 38px); }
.pa-pending .wb-bubble { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }
.pa-pending .wb-think-stop { margin-left: 8px; }
.pa-log .wb-confirm-actions { align-items: center; flex-wrap: wrap; }
.pa-log .wb-confirm-actions button.go.danger { border-color: var(--red); background: var(--red); }
.pa-log .wb-confirm.resolved.failed { border-left-color: var(--red); }
.pa-log .wb-confirm .ai-confirm-contract,
.pa-log .wb-confirm .ai-confirm-refs,
.pa-log .wb-confirm .ai-confirm-changes,
.pa-log .wb-confirm .ai-confirm-refresh,
.pa-log .wb-confirm .ai-confirm-again { margin-top: 8px; margin-bottom: 0; }
.pa-log .ai-confirm-crow span { flex-basis: 118px; }
.pa-log .ai-files { margin-top: 0; }
.pa-log .ai-file-row { flex-wrap: wrap; gap: 10px; padding: 12px 14px; }
.pa-log .ai-file-acts { flex-wrap: wrap; gap: 6px; }
.pa-log .ai-btn { padding: 7px 11px; font-size: 12.5px; border-radius: 10px; }
.pa-log .ai-query { margin: 0; }

/* The composer: Tracking's input row, with a textarea that grows with what is typed. */
.pa-foot { flex: none; border-top: 1px solid var(--border-soft); padding: 12px 14px; }
.pa-form { align-items: flex-end; }
.pa-form textarea {
  flex: 1; min-width: 0; min-height: 22px; max-height: 160px; resize: none;
  border: none; outline: none; background: transparent; color: var(--text);
  font: inherit; font-size: 13.5px; line-height: 1.45; padding: 9px 0; margin: 0;
}
.pa-form textarea::placeholder { color: var(--placeholder); }
.pa-form textarea[disabled], .pa-form button[disabled] { opacity: .55; cursor: default; }
.pa-form .wb-send { color: var(--white); }
.pa-form .wb-attach { align-self: center; }
.pa-readonly {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
  font-size: 12.5px; color: var(--muted);
}
.pa-chips { margin-top: 10px; }
.pa-chips .wb-chip { cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 500; }

/* Expand: the SAME element becomes a full-window overlay. The sidebar keeps its footprint so the
   page underneath does not reflow; its panel is lifted over a backdrop, centred to a reading measure.
   The overlay layer is 90 (backdrop 89): above page content, which reaches 60 (the Event details bar,
   toasts), and below every modal (100 and up) and the image lightbox (200). */
body.pa-expanded { overflow: hidden; }
body.pa-expanded::before {
  content: ""; position: fixed; inset: 0; z-index: 89;
  background: color-mix(in srgb, var(--text) 42%, transparent);
}
.pa.expanded { z-index: 90; }
.pa.expanded .pa-panel {
  position: fixed; inset: 24px; z-index: 90;
  border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-lg); overflow: hidden;
}
.pa.expanded .pa-log,
.pa.expanded .pa-foot,
.pa.expanded .pa-context { padding-left: max(18px, calc((100% - 860px) / 2)); padding-right: max(18px, calc((100% - 860px) / 2)); }
.pa.expanded .pa-log .wb-bubble { max-width: min(760px, calc(100% - 38px)); }

@media (max-width: 1100px) {
  .pa:not(.collapsed) { flex-basis: 340px; width: 340px; }
}

/* Small screens (where .app-shell becomes a column): the sidebar is a round launcher, and opening it
   is a full-screen overlay. That is not the desktop preference and is not persisted as one. A window
   that narrows WHILE expanded shows the same full-screen overlay (its ⤡ Exit and Esc still close it),
   so the scroll lock `body.pa-expanded` holds is never a lock over nothing. */
@media (max-width: 820px) {
  /* `:not(.collapsed)` too, so the 1100px width above cannot reach the launcher. */
  .pa, .pa.collapsed, .pa:not(.collapsed) {
    position: fixed; right: 16px; bottom: 16px; top: auto; left: auto; z-index: 90;
    width: 52px; height: 52px; flex: none; align-self: auto;
    border: 0; border-radius: 50%; background: transparent; box-shadow: var(--shadow-lg);
  }
  .pa .pa-panel { display: none; }
  .pa .pa-strip { display: flex; width: 100%; height: 100%; padding: 0; }
  .pa .pa-restore { width: 52px; height: 52px; border-radius: 50%; font-size: 20px; background: var(--primary); border-color: var(--border-strong); color: var(--white); }
  .pa .pa-restore .pa-spark { color: var(--white); }
  .pa.mobile-open, .pa.expanded { inset: 0; width: auto; height: auto; border-radius: 0; background: var(--card); box-shadow: none; }
  .pa.mobile-open .pa-strip, .pa.expanded .pa-strip { display: none; }
  .pa.mobile-open .pa-panel, .pa.expanded .pa-panel { display: flex; }
  .pa.mobile-open #paExpand { display: none; }
  body.pa-mobile-open { overflow: hidden; }
  .pa.expanded .pa-panel { position: relative; inset: auto; border: 0; border-radius: 0; box-shadow: none; }
  body.pa-expanded::before { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .pa-restore, .pa-hbtn, .pa-collapse { transition: none; }
}

/* ── Tartini AI at the centre of the AI Workspace ────────────────────────────────────────────
   The SAME controller as the project sidebar (public/project-assistant.js, `mode: 'center'`), drawn
   into the page rather than beside the nav rail: a card at the hero's reading width, never an aside,
   never collapsible. Before a conversation exists it is a composer under the greeting; once there is
   a transcript (`.is-chatting`, set by the page) it becomes the page, exactly as the question box it
   replaced did. Every value is a tokens.css token. */
.ws-tartini { position: relative; max-width: 920px; margin: 0 auto; text-align: left; }
.pa-center .pa-panel {
  position: relative; display: flex; flex-direction: column; min-height: 0;
  background: var(--white); border: 1px solid var(--border); border-radius: 22px;
  box-shadow: var(--shadow-md); overflow: hidden;
}
.pa-center .pa-head { gap: 10px; padding: 10px 14px 10px 18px; }
.pa-center .pa-log:empty { display: none; }
.pa-center:not(.is-chatting) .pa-foot { border-top: 0; }
.pa-center.is-chatting { display: flex; flex-direction: column; height: calc(100vh - 150px); min-height: 460px; }
.pa-center.is-chatting .pa-panel { flex: 1 1 auto; }
@media (max-width: 760px) {
  .pa-center.is-chatting { height: calc(100vh - 118px); min-height: 380px; }
}

/* The Tracking draft selector: which draft this conversation works on, and the choice of one. */
.pa-draft { padding: 12px; border-bottom: 1px solid var(--border); font-size: 12px; }
.pa-draft-head { border-bottom: 0; padding: 0; min-width: 0; }
.pa-draft-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.pa-draft-l { color: var(--muted); flex: none; font-weight: 500; }
/* The selector: shadcn's Select trigger — 32px, outlined with shadow-xs on the background (2026-10-09). */
.pa-draft-btn { flex: 1 1 auto; min-width: 0; height: 32px; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 12px; border: 1px solid var(--border); border-radius: var(--radius-control); background: var(--background); box-shadow: var(--shadow-xs); color: var(--text); cursor: pointer; font: inherit; font-size: 14px; }
.pa-draft-btn:disabled { opacity: .6; cursor: default; }
.pa-draft-none { color: var(--muted); }
.pa-draft-state { color: var(--muted); font-size: 11.5px; margin-left: 4px; }
.pa-draft-open { flex: none; font-size: 12px; }
.pa-draft-menu { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; padding: 4px; border: 1px solid var(--border); border-radius: var(--radius-control); background: var(--background); box-shadow: var(--shadow-md); max-height: 240px; overflow-y: auto; }
.pa-draft-opt { text-align: left; padding: 6px 8px; border: 0; border-radius: 6px; background: transparent; color: var(--text); cursor: pointer; font: inherit; }
.pa-draft-opt:hover:not(:disabled) { background: var(--accent); }
.pa-draft-new { font-weight: 600; }
.pa-draft-hint, .pa-draft-empty { color: var(--muted); padding: 4px 8px; }
.pa-draft-error { margin-top: 6px; color: var(--red-text); }

/* ── Demo dashboards: sample numbers drawn from a Measurement Plan, labelled wherever they appear ── */
/* DEMO: shadcn's secondary (gray) badge — the word says it, on every card, the dashboard and the list. */
.w-demo, .chip-demo { display: inline-flex; align-items: center; flex: none; font-size: 12px; font-weight: 500; letter-spacing: 0; line-height: 16px; color: var(--secondary-foreground); background: var(--secondary); border: 1px solid transparent; border-radius: var(--radius-pill); padding: 2px 8px; }
.demo-banner { margin: 0 0 12px; padding: 12px 16px; border: 1px dashed var(--border-strong); background: var(--background); color: var(--foreground); border-radius: var(--radius-panel); font-size: 14px; line-height: 20px; }
.demo-banner a { color: inherit; font-weight: 600; text-decoration: underline; }

/* ── Measurement Plans: a project's plans, and one plan's document, versions and demo dashboards ── */
.mp-toggle { font: inherit; font-size: 13px; font-weight: 600; color: var(--text-2); background: var(--white); border: 1px solid var(--border); border-radius: 9px; padding: 6px 12px; cursor: pointer; }
.mp-toggle[aria-pressed="true"] { color: var(--foreground); border-color: var(--border-strong); background: var(--accent); }
.mp-dash-ic { display: inline-flex; flex: none; color: var(--foreground); }
.mp-dash-ic svg, .mp-actions svg, #mpEmpty .btn-primary svg { width: 15px; height: 15px; }
.mp-dash-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mp-ver { font-weight: 700; }
.mp-muted { color: var(--muted); }
.mp-notice, .mp-error { margin: 0 0 12px; padding: 9px 12px; border-radius: var(--radius-sm); font-size: 13px; }
.mp-notice { background: var(--app-bg); border: 1px solid var(--border-soft); color: var(--text-2); }
.mp-error { background: var(--red-soft); border: 1px solid var(--red-border); color: var(--red-text); }
.mp-head { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin: 6px 0 14px; }
.mp-title { font-size: 24px; font-weight: 700; letter-spacing: -.02em; overflow-wrap: anywhere; }
.mp-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; font-size: 13px; color: var(--text-2); }
.mp-dot { color: var(--placeholder); }
.mp-sources { margin-top: 4px; font-size: 12.5px; overflow-wrap: anywhere; }
.mp-sources a { color: var(--foreground); }
.mp-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.mp-confirm { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 0 12px; padding: 10px 14px; border: 1px solid var(--red-border); background: var(--red-soft); border-radius: var(--radius-sm); font-size: 13px; color: var(--text); }
.mp-confirm-actions { display: flex; gap: 8px; margin-left: auto; }
.mp-body { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 18px; align-items: start; }
.mp-doc { min-width: 0; background: var(--card); border: 1px solid var(--border-soft); border-radius: var(--radius); box-shadow: var(--shadow-sm); padding: 22px 26px; font-size: 14px; line-height: 1.6; }
.mp-doc.old { border-style: dashed; }
.mp-doc .wb-md h1 { font-size: 20px; }
.mp-doc .wb-md h2 { font-size: 16.5px; margin-top: 18px; }
.mp-banner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin: 0 0 16px; padding: 9px 12px; border-radius: var(--radius-sm); background: var(--amber-soft); border: 1px solid var(--amber-border); color: var(--amber-text); font-size: 13px; }
.mp-side { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.mp-side-empty { font-size: 12.5px; color: var(--muted); }
.mp-versions, .mp-dashes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.mp-version { width: 100%; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; font: inherit; text-align: left; color: var(--text); background: none; border: 1px solid transparent; border-radius: 9px; cursor: pointer; }
.mp-version:hover { background: var(--app-bg); }
.mp-version.on { border-color: var(--border-strong); background: var(--accent); }
.mp-version-n { font-size: 13px; font-weight: 700; }
.mp-current { font-size: 11px; font-weight: 700; color: var(--green-text); }
.mp-version-what { font-size: 12.5px; color: var(--text-2); overflow-wrap: anywhere; }
.mp-version-when { font-size: 11.5px; }
.mp-dashes a { display: flex; align-items: center; gap: 8px; padding: 7px 8px; border-radius: 8px; color: var(--text); text-decoration: none; font-size: 13px; }
.mp-dashes a:hover { background: var(--app-bg); }
@media (max-width: 640px) { .mp-body { grid-template-columns: minmax(0, 1fr); } }

/* The change a result card opened, marked for a moment in the page that shows it (checkup step 32,
   `result-open.js`). The page's own outline colour; the animation itself is the element's. */
.pa-flash { outline: 2px solid var(--ring); outline-offset: 4px; border-radius: var(--radius-sm); transition: outline-color 0.6s ease; }
