/* ============================================================
   SHELL: Investigation, node-faithful, THEME-AWARE (dark+light).
   ============================================================ */
:root {
  /* Ambient module background, THIS BLOCK IS THE ?bg=img FALLBACK PATH ONLY.
     The default is now the pure-CSS trace in bg-trace.css, for all three modules
     in both themes. Everything below applies only when data-bgmode="img".
     Source: AI-generated ambient PNGs in ~/Documents/XPIN/ZEO SPARK/, re-exported as
     JPEG (sips q92, ≤1600w, ~110 to 205KB each) into ./assets/. Matches the Figma kit 1:1.
     --bgfall = the image's edge colour, shown if the JPEG fails to load.
     NOTE: ./assets/ was emptied 2026-07-26, so --bg is none and ?bg=img paints flat
     --bgfall (pointing at the missing JPEGs only produced 404s). Re-export from the
     source PNGs and restore url("../assets/bg-investigation-{dark,light}.jpg").
     HISTORY: an early pure-CSS trace was REVERTED 2026-07-20: accurate on paper but
     visually flat, because it ring-averaged ONE circular gradient and darkened it,
     crushing the glow and both corner bleeds into a single blob. The current trace
     is a multi-layer greedy residual fit and does not have that failure. Grain is
     separate + still CSS, see --grain / .screen::before. */
  --bg: none;
  --bgfall: #14161d;
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.6' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  /* top-bar chips + icon buttons, values taken from Figma "Header Actions" (5740:23708 light / 5740:23602 dark) */
  --hchip-bg: #27272a;
  --hchip-stroke: rgba(255, 255, 255, 0.05);
  /* composer controls hover, Figma Icon Button 4091:1399 State=Hover = Zinc/70. Stroke never changes. */
  --cc-hover: #3f3f46;
  --panel: #18181b;
  --panel-stroke: #27272a;
  --card: #212121;
  --card-stroke: rgba(110, 110, 110, 0.2);
  --pill: #27272a;
  --pill-hover: #323238;
  --ink: #fafafa;
  --ink-2: #ededed;
  --sub: #ababab;
  --ricon: #d4d4d4;
  --ticon: #8b92a0;
  --sel-bg: #fff;
  --sel-fg: #18181b;
  --hair: #111;
  --hcard: #262626;
  --hcard-stroke: #111;
  --meta: #71717a;
  --line: rgba(255, 255, 255, 0.06);
  --line-strong: rgba(255, 255, 255, 0.06);
  --glass: rgba(255, 255, 255, 0.05);
  --glass-2: rgba(255, 255, 255, 0.08);
  /* SUNKEN: a step DOWN from the background, the opposite of --glass. For a band
     that the page sits in rather than one laid on top of it. Opaque, not an alpha
     scrim: things scroll underneath these and have to be hidden, not tinted. */
  --sunken: #101012;
  --spin-track: rgba(255, 255, 255, 0.22);
  --brand-bg: #fff;
  --brand-fg: #000;
  --avatar-bg: #d4d4d4;
  --avatar-fg: #000000;
  --send-bg: #27272a;
  --send-fg: #ffffff;
  --send-active-bg: #fafafa;
  --send-active-fg: #0a0a0a; /* send lights up only with something to send */
  --acard: #1b1b1b;
  --acard-stroke: #303030;
  --box: #212121;
  --box-stroke: rgba(
    110,
    110,
    110,
    0.2
  ); /* original: Figma Gray/50 #212121 box on #1b1b1b card, Gray/40 #6e6e6e@20% inside hairline */
  --acard-lift: #232323; /* main container bottom lifts just ~1 point (#1b1b1b→#232323), blending up into the dark top */
  /* answer container + inner box hairlines: OPAQUE = the RESOLVED colour of Figma's Gray/40 #6e6e6e@20% inside-stroke over the card. Opaque (not alpha) so it reads uniformly/crisply on every edge over the warm ambient backdrop, matching how Figma renders the inside stroke. --box-stroke #333 = #6e6e6e@20% resolved over #212121, so the inner cards stand out like Figma instead of a washed-out alpha hairline. */
  --dock-tint: rgba(9, 9, 11, 0.18);
  --dock-glow: rgba(3, 3, 6, 0.9);
  --dock-glow-mid: rgba(3, 3, 6, 0.34);
}
/* ---- BUILD DASHBOARD, DARK: the surfaces blend with the cyan page ------------
   Every other module paints its container in the same neutral zinc, which works
   there because the ambient page behind it is neutral too. Dashboard's page is
   cyan, so the same zinc container sat on it as a grey rectangle laid over a
   blue photo, two unrelated materials meeting at a hard edge.

   Fixed by leaning every surface a few points toward the page's own hue rather
   than by recolouring anything: the container, the blocks, the pills, the
   hairlines and the chips all keep their tonal RELATIONSHIP (container darkest,
   block one step up, pill one step above that), they just sit on the same axis
   as the backdrop now. Nothing is saturated enough to read as "blue UI", which
   is the point, it should look like one material, not a blue theme.

   Values chosen against the rendered page, not picked in the abstract: the
   container lands at rgb(23,27,31) over a backdrop of rgb(23,26,30). */
html[data-theme="dark"][data-module="dashboard"] {
  --panel: #171a1e;
  --panel-stroke: #242a2f;
  --box: #1c2126;
  --box-stroke: rgba(140, 175, 190, 0.16);
  --card: #1c2126;
  --card-stroke: rgba(140, 175, 190, 0.16);
  --hcard: #1c2126;
  --hcard-stroke: #242a2f; /* the Dashboard History cards, same step as a block */
  --acard: #181c21;
  --acard-lift: #1c2126;
  --acard-stroke: #2a3138;
  --hair: #1f252a;
  --pill: #232a2f;
  --pill-hover: #2b333a;
  --cc-hover: #333c43; /* composer control hover, one step above the pill */
  --hchip-bg: #232a2f;
  --hchip-stroke: rgba(190, 225, 240, 0.06);
  --send-bg: #232a2f;
  --line: rgba(190, 225, 240, 0.07);
  --line-strong: rgba(190, 225, 240, 0.09);
  --sunken: #0f1216;
}
[data-theme="light"] {
  --bg: none;
  --bgfall: #f7f1ee;
  --hchip-bg: #ffffff;
  --hchip-stroke: #e7e5e4; /* white fill + visible hairline, per Figma */
  --cc-hover: #e4e4e7; /* mirrored step of the dark Zinc/80 → Zinc/70 hover */
  --panel: #ffffff;
  --panel-stroke: #e7e5e4;
  /* card hairline = the SAME stroke as the top-bar tags (--hchip-stroke #e7e5e4).
     Was #d6d3d1, which read noticeably darker than the header chips on the composer
     and the "start with one of these" cards. Referenced, not copied, so they can't drift. */
  --card: #ffffff;
  --card-stroke: var(--hchip-stroke);
  /* rail/pill surfaces + icon come from the DS Zinc ramp (Figma Rail Item v2 4110:1406):
     default Zinc/10 + Zinc/60 · hover Zinc/20 · selected Zinc/90 + Zinc/0 */
  --pill: #f4f4f5;
  --pill-hover: #e4e4e7;
  --ink: #1c1917;
  --ink-2: #44403c;
  --sub: #78716c;
  --ricon: #52525b;
  --ticon: #78716c;
  --sel-bg: #18181b;
  --sel-fg: #fafafa;
  --hair: #e7e5e4;
  --hcard: #f5f5f4;
  --hcard-stroke: #e7e5e4;
  --meta: #a8a29e;
  --line: rgba(0, 0, 0, 0.1);
  --line-strong: rgba(0, 0, 0, 0.16);
  --glass: rgba(0, 0, 0, 0.04);
  --glass-2: rgba(0, 0, 0, 0.06);
  --sunken: #efedeb;
  --spin-track: rgba(0, 0, 0, 0.18);
  --brand-bg: #18181b;
  --brand-fg: #fff;
  --avatar-bg: #18181b;
  --avatar-fg: #fff;
  --send-bg: #f5f5f4;
  --send-fg: #57534e;
  --send-active-bg: #18181b;
  --send-active-fg: #fff;
  --acard: #ffffff;
  --acard-stroke: #e7e5e4;
  --box: #f5f5f4;
  --box-stroke: #e7e5e4;
  --acard-lift: #ffffff;
  --dock-tint: rgba(244, 244, 247, 0.5);
  --dock-glow: rgba(244, 244, 247, 0.9);
  --dock-glow-mid: rgba(244, 244, 247, 0.45);
}
/* module-specific ambient backgrounds (Chat with Data = purple) */
[data-module="chat"] {
  --bgfall: #12131b;
  --bg: none /* bg-chat-dark.jpg missing from ./assets (404) */;
}
[data-theme="light"][data-module="chat"] {
  --bgfall: #f4f0f5;
  --bg: none /* bg-chat-light.jpg missing from ./assets (404) */;
}
/* Dashboard = cyan */
[data-module="dashboard"] {
  --bgfall: #151921;
  --bg: none /* bg-dashboard-dark.jpg missing from ./assets (404) */;
}
[data-theme="light"][data-module="dashboard"] {
  --bgfall: #f0f4f7;
  --bg: none /* bg-dashboard-light.jpg missing from ./assets (404) */;
}

.screen {
  position: relative;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background-color: var(--bgfall);
  background-image: var(--bg);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* film grain, inline SVG noise, kept behind content (same stacking level as .bgfx-ghost).
   overlay blend = symmetric grain (lightens + darkens, no brightness wash); opacity calibrated
   to the source images' measured grain amplitude (dark SD≈1.0, light SD≈0.5). */
.screen::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--grain) repeat;
  background-size: 220px 220px;
  mix-blend-mode: overlay;
  opacity: 0.1;
}
[data-theme="light"] .screen::before {
  opacity: 0.05;
}
/* ---- module-switch transition: ambient bg crossfade + content fade/rise ---- */
.bgfx-ghost {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 1;
  transition: opacity 0.65s var(--ease);
  pointer-events: none;
}
.mod-enter .topbar {
  animation: modFade 0.5s var(--ease) both;
}
.mod-enter .home,
.mod-enter .thread,
.mod-enter .search-page,
.mod-enter .ds-page,
.mod-enter .page,
.mod-enter .workbox,
.mod-enter .dash-home {
  animation: modRise 0.5s var(--ease) both;
}
@keyframes modFade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes modRise {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ---------- LEFT NAV ---------- */
.nav {
  position: absolute;
  top: 24px;
  left: 24px;
  bottom: 24px;
  width: 72px;
  z-index: 30;
  background: var(--panel);
  border: 1px solid var(--panel-stroke);
  border-radius: 28px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 20px 16px;
  transition:
    width 0.22s var(--ease),
    padding 0.22s var(--ease);
  overflow: visible;
}
.nav.expanded {
  width: 280px;
  padding: 16px;
  justify-content: flex-start;
}
/* ---- MONOCHROME SPARK LOGO (Figma 8448:1461) ------------------------------
   The rail is 72px wide, so what fits here is the MARK, not the lockup: the same
   S the expanded header and the auth pages carry, on its own. The white tile is
   gone with the letter it framed - the mark carries the brand now.

   28px, and that is the S itself: the file's box was cropped to its own ink, so a
   height here is the height of the mark rather than of the padding around it. 28
   is where it has always sat next to the 20px rail icons.
   TO REVERT: restore js/app.js.bak-mono + css/molecules.css.bak-mono. */
.brandmark {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: none;
  color: var(--ink);
  display: grid;
  place-items: center;
  flex: none;
}
/* THE LETTER S, not the swoosh (Ameer, 4 Sep): "on the collapse menu it should be
   letter S, not that old logo mark". It is the wordmark's OWN capital S, exported
   from the same Poppins Medium outline, so the rail and the expanded lockup are
   the same letter at two sizes rather than two different marks.
   26, not 28: the swoosh was 42x55 and this is 29x42, a squarer box, so the same
   pixel height read noticeably larger in the rail. */
.brandmark .bm-mark img {
  width: auto;
  height: 26px;
  display: block;
}
.brandmark .bm-mark .bm-mark--light {
  display: none;
}
[data-theme="light"] .brandmark .bm-mark .bm-mark--dark {
  display: none;
}
[data-theme="light"] .brandmark .bm-mark .bm-mark--light {
  display: block;
}
/* Z logo doubles as the expand toggle: hover reveals the expand icon */
.nav-toggle {
  cursor: pointer;
  border: 0;
  padding: 0;
}
.brandmark .bm-mark,
.brandmark .bm-exp {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
}
.brandmark .bm-exp {
  display: none;
}
.brandmark .bm-exp svg {
  width: 22px;
  height: 22px;
}
.nav:not(.expanded) .brandmark:hover .bm-mark {
  display: none;
}
.nav:not(.expanded) .brandmark:hover .bm-exp {
  display: grid;
}
.rail-divider {
  width: 24px;
  height: 1px;
  background: var(--hair);
  margin: 8px auto;
}
.rail-item {
  position: relative;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: var(--pill);
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
  border: 0;
  flex: none;
  transition: 0.15s;
}
.rail-item svg {
  width: 20px;
  height: 20px;
}
.rail-item:hover {
  background: var(--pill-hover);
}
.rail-item.sel {
  background: var(--sel-bg);
  color: var(--sel-fg);
}
.rail-avatar {
  position: relative;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: var(--avatar-bg);
  color: var(--avatar-fg);
  display: grid;
  place-items: center;
  font-weight: 600;
  font-size: 14px;
  flex: none;
}
/* tooltip that slides in from the right when a collapsed rail icon is hovered */
.tip {
  position: absolute;
  left: calc(100% + 16px);
  top: 50%;
  transform: translate(-8px, -50%);
  background: var(--menu-bg);
  border: 1px solid var(--panel-stroke);
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  padding: 8px 12px;
  border-radius: 10px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition:
    opacity 0.14s var(--ease),
    transform 0.14s var(--ease);
  z-index: 60;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}
.tip::before {
  content: "";
  position: absolute;
  left: -4px;
  top: 50%;
  transform: translateY(-50%) rotate(45deg);
  width: 8px;
  height: 8px;
  background: var(--menu-bg);
  border-left: 1px solid var(--panel-stroke);
  border-bottom: 1px solid var(--panel-stroke);
}
.nav:not(.expanded) .rail-item:hover .tip,
.nav:not(.expanded) .rail-avatar:hover .tip {
  opacity: 1;
  transform: translate(0, -50%);
}
/* hover flyout: collapsed rail items that have sub-items show their list on the right */
.flyout {
  position: absolute;
  left: 100%;
  top: -10px;
  padding-left: 14px;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-6px);
  transition:
    opacity 0.14s var(--ease),
    transform 0.14s var(--ease);
  z-index: 60;
}
/* same rhythm as the expanded list, per the Figma Nav Flyout component (6056:3724):
   rows 32 · gap 4 · header sits 8 above the first row · labels inset 10 to match */
.flyout-menu {
  background: var(--menu-bg);
  border: 1px solid var(--panel-stroke);
  border-radius: 14px;
  padding: 6px;
  min-width: 236px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  box-shadow: 0 16px 44px rgba(0, 0, 0, 0.5);
}
.flyout-hd {
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--sub);
  padding: 8px 10px 4px;
}
/* a flyout row = session label + a hover ⋯ (same anatomy as .nx-subrow in the expanded nav) */
.flyout-item {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 32px;
  font-size: 13px;
  color: var(--ink-2);
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
}
.flyout-item > a {
  flex: 1;
  min-width: 0;
  display: block;
  line-height: 32px;
  padding: 0 10px;
  color: inherit;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.flyout-item:hover {
  background: var(--glass);
  color: var(--ink);
}
.flyout-item.flyout-sel {
  background: var(--glass-2);
  color: var(--ink);
  font-weight: 500;
}
.flyout-item > .pop {
  position: absolute;
  inset-inline-end: 4px;
  top: 50%;
  transform: translateY(-50%);
  line-height: 0;
}
.flyout-item:hover > a,
.flyout-item:has(> .pop.is-open) > a {
  padding-inline-end: 32px;
}
.flyout-item:hover .nx-more,
.flyout-item .pop.is-open .nx-more {
  opacity: 1;
}
.flyout-item:not(:hover) .pop:not(.is-open) .nx-more {
  pointer-events: none;
}
.nav:not(.expanded) .rail-item.has-flyout:hover .flyout,
.nav:not(.expanded) .rail-item.has-flyout:has(.pop.is-open) .flyout {
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}
.nav-top {
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-items: center;
}
.nav-bottom {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
}
.nav .exp {
  display: none;
  flex-direction: column;
  justify-content: space-between;
  flex: 1;
  min-height: 0;
}
.nav.expanded .col {
  display: none;
}
.nav.expanded .exp {
  display: flex;
}
.nx {
  display: flex;
  flex-direction: column;
  gap: 20px;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
/* left inset 8 keeps the brand at 24 (matches Figma); right padding 0 so the collapse
   button sits flush at the content edge, vertically aligned with the search icon below (Figma 4058:1810). */
.nx-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  padding-inline-start: 8px;
  height: 40px;
}
/* the lockup = go home, one image where the tile and the word used to be.
   28px, and that is the VISIBLE height: the file's box was cropped to its own ink.
   Matching the old header's optical size put it at 25, Ameer asked for it bigger
   (1 Sep) which took it to 31, then back down 10% on the same day - the lockup was
   reading heavy against the nav items under it at 31. The 40px header row keeps its
   height either way. 126px wide at this size, against ~208 of usable header width,
   so nothing crowds the collapse button. Two files, one shown: the treatment mirrors with the theme, and the
   theme toggles inside the app. */
.nx-head .navlogo {
  flex: 1;
  min-width: 0;
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: flex-start;
}
/* 30, not 28: same reason as .authlogo img in auth.css, the strapline is inside the box now */
.nx-head .navlogo img {
  height: 30px;
  width: auto;
  display: block;
}
.nx-head .navlogo .navlogo--light {
  display: none;
}
[data-theme="light"] .nx-head .navlogo .navlogo--dark {
  display: none;
}
[data-theme="light"] .nx-head .navlogo .navlogo--light {
  display: block;
}
.nx-head .nav-home {
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
}
.nx-head .toggle {
  color: var(--ricon);
  cursor: pointer;
  background: none;
  border: 0;
  display: grid;
  place-items: center;
}
.nx-head .toggle svg {
  width: 20px;
  height: 20px;
}
.nx-quick {
  display: flex;
  gap: 8px;
}
.nx-new {
  flex: 1;
  height: 44px;
  border-radius: 12px;
  background: var(--pill);
  border: 1px solid var(--line);
  color: var(--ink);
  font-weight: 500;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
}
.nx-new svg {
  width: 16px;
  height: 16px;
}
.nx-search {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--pill);
  border: 1px solid var(--line);
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.nx-search svg {
  width: 22px;
  height: 22px;
}
.nx-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow-y: auto;
  flex: 1;
}
.nx-item {
  height: 48px;
  border-radius: 8px;
  padding: 6px;
  padding-inline-end: 12px;
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--ink-2);
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
}
.nx-item .ico {
  width: 36px;
  height: 36px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  flex: none;
}
.nx-item .ico svg {
  width: 20px;
  height: 20px;
}
.nx-item .lbl {
  flex: 1;
}
.nx-item .chev {
  width: 16px;
  height: 16px;
  color: var(--sub);
}
.nx-item:hover {
  background: var(--glass);
}
.nx-item.sel {
  background: var(--glass-2);
  color: var(--ink);
}
[data-theme="dark"] .nx-item.sel .ico {
  background: var(--sel-bg);
  color: var(--sel-fg);
}
/* rhythm mirrors the Figma component (Sub Link - Expanded 4055:1426): row 32, gap 6, indent 44 */
.nx-sub {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 4px 0;
  padding-inline-start: 44px;
}
/* collapsible nav group (accordion), smooth grid-rows collapse, no full re-render needed */
.nx-group {
  display: flex;
  flex-direction: column;
}
.nx-item[data-navgroup] {
  cursor: pointer;
}
.nx-subwrap {
  display: grid;
  grid-template-rows: 1fr;
  transition:
    grid-template-rows 0.28s var(--ease),
    opacity 0.22s var(--ease);
  opacity: 1;
}
.nx-subwrap > .nx-sub {
  overflow: hidden;
  min-height: 0;
}
.nx-group.collapsed .nx-subwrap {
  grid-template-rows: 0fr;
  opacity: 0;
}
.nx-item .chev {
  transition: transform 0.22s var(--ease);
}
.nx-group.collapsed > .nx-item .chev {
  transform: rotate(-90deg);
}
/* history row = label + hover-revealed ⋯ (ChatGPT/Claude pattern). The ROW owns the
   hover surface so the label and the ⋯ read as one target. */
.nx-subrow {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 32px;
  border-radius: 8px;
}
.nx-subrow:hover {
  background: var(--glass);
}
.nx-subrow:hover > a {
  color: var(--ink);
}
.nx-subrow.sub-sel {
  background: var(--glass-2);
}
.nx-subrow.sub-sel > a {
  color: var(--ink);
  font-weight: 500;
}
/* block, not flex, flex containers ignore text-overflow:ellipsis. line-height does the centring. */
.nx-sub a {
  flex: 1;
  min-width: 0;
  display: block;
  color: var(--sub);
  font-size: 13px;
  line-height: 32px;
  cursor: pointer;
  padding: 0 10px;
  border-radius: 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: padding-inline-end 0.12s var(--ease);
}
/* the ⋯ FLOATS over the row, at rest it reserves no width, so labels keep their full
   length; only on hover does the label shorten to clear it (ChatGPT/Claude behaviour) */
.nx-subrow > .pop {
  position: absolute;
  inset-inline-end: 4px;
  top: 50%;
  transform: translateY(-50%);
  line-height: 0;
}
.nx-subrow:hover > a,
.nx-subrow:has(> .pop.is-open) > a {
  padding-inline-end: 32px;
}
.nx-rename {
  flex: 1;
  min-width: 0;
  width: 100%;
  background: var(--glass);
  border: 1px solid var(--ink);
  border-radius: 6px;
  padding: 5px 8px;
  margin: 1px 0;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 16px;
  outline: none;
}
.nx-more {
  width: 24px;
  height: 24px;
  flex: none;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--sub);
  display: grid;
  place-items: center;
  cursor: pointer;
  opacity: 0;
  transition:
    opacity 0.12s var(--ease),
    background 0.12s var(--ease),
    color 0.12s var(--ease);
}
.nx-more svg {
  width: 16px;
  height: 16px;
}
/* revealed on row hover, and kept visible while its own menu is open */
.nx-subrow:hover .nx-more,
.nx-subrow .pop.is-open .nx-more,
.nx-more:focus-visible {
  opacity: 1;
}
.nx-subrow:not(:hover) .pop:not(.is-open) .nx-more {
  pointer-events: none;
} /* invisible ⋯ must not eat label clicks */
.nx-more:hover {
  background: none;
  color: var(--ink);
}
.nx-sep {
  height: 1px;
  flex-shrink: 0;
  background: var(--panel-stroke);
  margin: 4px 0;
}
.nx-foot {
  height: 56px;
  border-radius: 8px;
  padding: 8px;
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  margin-top: 8px;
}
.nx-foot:hover {
  background: var(--glass);
}
.nx-foot .av {
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: var(--avatar-bg);
  color: var(--avatar-fg);
  display: grid;
  place-items: center;
  font-weight: 600;
  font-size: 12px;
  flex: none;
}
.nx-foot .nm {
  flex: 1;
}
.nx-foot .nm b {
  display: block;
  font-weight: 500;
  font-size: 14px;
  color: var(--ink);
}
.nx-foot .nm span {
  font-size: 12px;
  color: var(--sub);
}
.nx-foot .chev {
  width: 16px;
  height: 16px;
  color: var(--ink-2);
}

/* ---------- STAGE / TOP BAR ---------- */
.stage {
  position: absolute;
  top: 0;
  left: 120px;
  right: 24px;
  bottom: 0;
  display: flex;
  flex-direction: column;
  transition:
    left 0.22s var(--ease),
    right 0.22s var(--ease);
}
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 94px;
  padding: 0 4px 0 8px;
  flex: none;
}
/* Data Sources only (topbarHTML_wrap), same row, same 40px circular controls,
   just less empty space above/below them since this page has no card of its
   own for the header to visually anchor against. */
.topbar--compact {
  height: 64px;
}
.breadcrumb {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  min-width: 0;
}
.breadcrumb .bc-mut {
  color: var(--sub);
  font-weight: 500;
} /* Figma 5740:23597, Inter Medium */
.breadcrumb .bc-sep {
  color: var(--sub);
  opacity: 0.5;
}
.breadcrumb .bc-cur {
  color: var(--ink);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
} /* Figma, Inter SemiBold */
/* pinned to the browser's top-right, always, independent of nav/history expand-collapse */
/* in-flow, right side of the topbar flex row, NOT position:fixed. Fixed pinned it to the viewport,
   so in any offset/scaled/preview context (e.g. Tablet ≤1024 frame) the buttons drifted off the
   breadcrumb's vertical centre. In flow, the topbar's align-items:center centres left + right
   clusters together on every screen. */
.hactions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}
.hchip {
  height: 40px;
  border-radius: 999px;
  background: var(--hchip-bg);
  border: 1px solid var(--hchip-stroke);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  padding-inline-start: 14px;
  color: var(--ink-2);
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
}
.hchip svg {
  width: 18px;
  height: 18px;
  color: var(--ricon);
}
.hchip .chev {
  width: 14px;
  height: 14px;
}
/* live-connection mark, the circle-dot ICON, as in Figma's Chip → Leading slot. Declared
   AFTER `.hchip svg` so it wins the colour, and via --success (theme-aware) rather than the
   hardcoded #34d399 this used to carry as a filled span. */
.hchip svg.dot {
  color: var(--success);
  flex: none;
}
.hbtn {
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: var(--hchip-bg);
  border: 1px solid var(--hchip-stroke);
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
  flex: none;
}
.hbtn svg {
  width: 18px;
  height: 18px;
}
[data-theme="dark"] .hchip,
[data-theme="dark"] .hbtn {
  border: 1px solid rgba(255, 255, 255, 0.05);
}
/* hover, same treatment + tokens as the composer controls (.cbtn/.cchip → --cc-hover / Zinc/70) */
.hchip,
.hbtn {
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease);
}
.hchip:hover,
.hbtn:hover {
  background: var(--cc-hover);
  color: var(--ink);
}

/* ---------- HOME ---------- */
.home {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 32px;
  padding: 0 41px 40px;
  overflow-y: auto;
}
.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 58px;
}
.toggle {
  display: flex;
  align-items: center;
  gap: 12px;
}
.toggle .pill {
  height: 46px;
  border-radius: 14px;
  background: var(--accent);
  color: #fff;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 20px;
  padding-inline-start: 16px;
  font-weight: 600;
  font-size: 14px;
  border: 0;
  cursor: pointer;
}
.toggle .pill svg {
  width: 18px;
  height: 18px;
}
.toggle .tg {
  width: 48px;
  height: 48px;
  border-radius: 14px;
  background: var(--glass);
  border: 1px solid var(--line-strong);
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.toggle .tg svg {
  width: 20px;
  height: 20px;
}
[data-theme="dark"] .toggle .tg {
  border-color: rgba(255, 255, 255, 0.08);
}
/* light: the unselected buttons are solid white on an 8% black hairline (Figma 5740:26964) */
[data-theme="light"] .toggle .tg {
  background: var(--card);
  border-color: rgba(0, 0, 0, 0.08);
}
/* hover: a very light glassy white lifts in the background + the icon brightens toward ink.
   Both come from DS tokens (--glass-2, --ink); no colour wash. */
.toggle .tg {
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease),
    color 0.15s var(--ease);
}
.toggle .tg:hover {
  background: var(--glass-2);
  color: var(--ink);
}
.hero-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}
.hero-text h1 {
  font-weight: 600;
  font-size: 36px;
  line-height: 40px;
  letter-spacing: 0;
  color: var(--ink);
}
.hero-text p {
  font-weight: 500;
  font-size: 14px;
  line-height: 20px;
  color: var(--sub);
  max-width: 520px;
}
.stack820 {
  width: 820px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  gap: 62px;
}
.composer {
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: 28px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  box-shadow: none;
}
.composer textarea {
  background: none;
  border: 0;
  outline: none;
  resize: none;
  width: 100%;
  min-height: 24px;
  max-height: 150px;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 24px;
}
.composer textarea::placeholder {
  color: var(--sub);
}
/* Reply-context row (Figma Reply composer 4870:1391): inline "↩ Replying to <answer> ×" + hairline */
.composer--reply {
  gap: 14px;
}
.reply-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  color: var(--sub);
  font-size: 16px;
  line-height: 24px;
}
.reply-row .reply-ic {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--ricon);
}
.reply-row .reply-ic svg {
  width: 16px;
  height: 16px;
}
.reply-row .reply-txt {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.reply-row .reply-txt b {
  color: var(--ink);
  font-weight: 600;
}
.reply-row .reply-x {
  flex: none;
  background: none;
  border: 0;
  padding: 2px;
  cursor: pointer;
  color: var(--ricon);
  display: grid;
  place-items: center;
}
.reply-row .reply-x:hover {
  color: var(--ink);
}
.reply-row .reply-x svg {
  width: 16px;
  height: 16px;
}
.cc {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.cc-l,
.cc-r {
  display: flex;
  align-items: center;
  gap: 8px;
}
.cbtn {
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: var(--pill);
  border: 1px solid var(--line-strong);
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
  flex: none;
}
.cbtn svg {
  width: 20px;
  height: 20px;
}
.cchip {
  height: 40px;
  border-radius: 999px;
  background: var(--pill);
  border: 1px solid var(--line-strong);
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  padding-inline-start: 14px;
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
}
.cchip svg {
  width: 18px;
  height: 18px;
  color: var(--ricon);
}
.cchip .chev {
  width: 14px;
  height: 14px;
}
.send {
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: var(--send-bg);
  color: var(--send-fg);
  display: grid;
  place-items: center;
  cursor: pointer;
  border: 0;
  flex: none;
}
.send svg {
  width: 20px;
  height: 20px;
}
.suggests {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: center;
}
.suggests .lbl {
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 1px;
  color: var(--sub);
}
.sgrid {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
/* DS interactive-surface state, Figma "Suggestion Card" 5925:22628
   default Zinc/90 + Zinc/80 stroke · hover Zinc/80 + Zinc/60 stroke */
/* Suggestion Card, matches the SCREEN (5740:23727): card surface #212121 + #6e6e6e@20%,
   same chrome as the widget cards. Hover = Zinc/80 on Zinc/60 (DS state 5925:22628). */
.scard {
  height: 66px;
  border-radius: 14px;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 0 17px;
  cursor: pointer;
  text-align: start;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.scard:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.scard .ibox {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: var(--pill);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--icon);
}
.scard .ibox svg {
  width: 18px;
  height: 18px;
}
.scard span {
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink);
}

/* ---------- ANSWER THREAD (Answer Types, Calm container) ---------- */
/* the scroll viewport carries the SAME 24px top radius as the answer card, so content
   scrolling up under the topbar clips on the rounded corner instead of a sharp square cut */
.thread {
  flex: 1;
  overflow-y: auto;
  padding: 0 0 192px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  border-radius: 24px 24px 0 0;
}
/* a lone answer fills the available height (no dead void) BUT is allowed to grow past it so
   tall answers scroll and reveal all their content, never clipped to the viewport. */
.thread > .ans:only-child {
  min-height: 100%;
}
/* jump-to-turn: leave a small gap above the target, and flash it briefly on arrival */
.ans,
.conv-turn {
  scroll-margin-top: 18px;
}
.turn-flash {
  animation: turnFlash 1.5s var(--ease);
}
@keyframes turnFlash {
  0% {
    box-shadow: 0 0 0 0 var(--accent);
  }
  22% {
    box-shadow:
      0 0 0 2px var(--accent),
      0 12px 40px -12px var(--accent);
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
}

/* ---- Composer Dock (Figma "Glass"): frosted backdrop-blur + soft ambient dark glow.
       Answers scroll UNDER the composer; the band is a soft frost, NOT a hard patch. ---- */
.dock {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 216px;
  pointer-events: none;
  z-index: 10;
}
/* frosted glass: 14px backdrop blur + faint tint, masked so the top edge dissolves (no clean-cut edge) */
.dock::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  background: var(--dock-tint);
  -webkit-mask-image: linear-gradient(to top, #000 40%, transparent);
  mask-image: linear-gradient(to top, #000 40%, transparent);
}
/* ambient glow: soft blurred ellipse behind the composer (Figma Ambient Glow, layer-blur 80) */
.dock::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: 960px;
  max-width: 100%;
  height: 210px;
  pointer-events: none;
  z-index: 2;
  background: radial-gradient(
    60% 82% at 50% 92%,
    var(--dock-glow),
    var(--dock-glow-mid) 46%,
    transparent 72%
  );
}
.dock .composer {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  width: 820px;
  max-width: calc(100% - 48px);
  margin: 0;
  pointer-events: auto;
  z-index: 3;
}
.ans {
  position: relative;
  flex: none;
  width: 100%;
  max-width: none;
  margin: 0 auto;
  background: linear-gradient(180deg, var(--acard) 45%, var(--acard-lift) 100%);
  border-radius: 24px;
  padding: 40px 42px 42px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 12px;
  animation: rise 0.3s var(--ease) both;
}
/* container hairline drawn on a TOP layer (above the ambient glow), opaque + uniform on every edge -
   mirrors Figma's inside stroke painted over fill+glow, so top corners stay as crisp as the bottom. */
.ans::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--acard-stroke);
  border-radius: 24px;
  pointer-events: none;
  z-index: 2;
}
/* opacity-only entrance: animating transform here would make .ans a containing block for
   position:fixed, which offsets the floated footer/⋯ menu. Fade only keeps menus viewport-anchored. */
@keyframes rise {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
.ans > * {
  position: relative;
  z-index: 1;
}
/* Top Glow (ambient), traced from Figma answer card node 4789:5256 (ellipse #F97316@.15).
   FIXED to the top of the answer viewport: it lives on the non-scrolling .stage (below the
   94px topbar), NOT on each answer box, so the module-colour ambient stays put while the
   thread scrolls under it, instead of scrolling off per-box (which looked patchy). It's a
   light front layer that fades out fast, so headline/text below stay crisp. var(--glow) is
   module-scoped, so Chat (purple) inherits it automatically.
   ONLY where an answer card actually exists (:has(.ans)), the home/search views keep their
   own ambient, so this overlay never adds a second glow band (which cut a hard edge on the
   home page). Scoped to .ans rather than .thread because a thread can now hold nothing but
   the stopped note: with no card under it the band has nothing to sit on and its own
   rectangle becomes visible against the empty stage, the same hard edge, moved. */
.stage:has(.ans)::before {
  content: "";
  position: absolute;
  top: 94px;
  left: 0;
  right: 0;
  height: 320px;
  z-index: 5;
  pointer-events: none;
  background: radial-gradient(
    ellipse 72% 88% at 50% -4%,
    var(--glow) 0%,
    var(--glow) 16%,
    transparent 78%
  );
}
/* Answer Question Header, Figma 4821:1398: top-aligned row, question Inter Bold, timestamp Inter Regular #a1a1aa @80% */
.aqh {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding-bottom: 15px;
  border-bottom: 1px solid var(--glass);
}
.aqh > svg {
  width: 16px;
  height: 16px;
  color: var(--ricon);
  flex: none;
}
.aqh .q {
  flex: 1;
  font-size: 14px;
  color: var(--ink);
  font-weight: 700;
}
.aqh .tm {
  font-size: 12px;
  color: var(--text-muted);
  opacity: 0.8;
}
/* ---- edit the question in place ----------------------------------------------------
   The pencil is PERSISTENT (not hover-only) and pinned to the card's top-right corner, so
   the affordance is constant, you never have to hunt for it. Neutral utility icon, never
   the module accent, and the same glass hover the other icon controls use. */
.aqh-edit {
  flex: none;
  width: 26px;
  height: 26px;
  margin: -4px -4px 0 0;
  border: 0;
  background: none;
  border-radius: 7px;
  display: grid;
  place-items: center;
  color: var(--ricon);
  cursor: pointer;
  transition:
    background 0.12s var(--ease),
    color 0.12s var(--ease);
}
.aqh-edit svg {
  width: 15px;
  height: 15px;
}
.aqh-edit:hover {
  background: var(--glass);
  color: var(--ink);
}
/* while generating, the same corner slot holds Pause, same neutral treatment, so the
   control reads as one affordance that changes job, not as a second button appearing */
.aqh-pause {
  color: var(--ink);
}
.aqh-pause:hover {
  background: var(--glass-2);
}
/* paused status strip, a state, not an action, so neutral glass and never the accent */
.ans__paused {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 15px;
  padding: 12px 14px;
  background: var(--glass);
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
}
.ans__paused > svg {
  width: 16px;
  height: 16px;
  color: var(--ricon);
  flex: none;
  margin-top: 2px;
}
/* edit mode: the question BECOMES the field, same row, same rhythm, so the card doesn't jump */
.aqh--edit {
  align-items: center;
  gap: 11px;
}
.aqh-in {
  flex: 1;
  min-width: 0;
  resize: none;
  overflow: hidden;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  line-height: 20px;
  color: var(--ink);
  background: var(--glass);
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  padding: 7px 10px;
  outline: none;
}
.aqh-in:focus {
  border-color: var(--line-strong);
  background: var(--glass-2);
}
.aqh-b {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  cursor: pointer;
  background: var(--send-bg);
  border: 1px solid var(--line-strong);
  color: var(--send-fg);
  transition:
    background 0.12s var(--ease),
    color 0.12s var(--ease);
}
.aqh-b svg {
  width: 15px;
  height: 15px;
}
.aqh-b:hover {
  background: var(--pill-hover);
  color: var(--ink);
}
/* update = the composer's active send, so "commit" reads the same everywhere */
.aqh-b--go {
  background: var(--send-active-bg);
  color: var(--send-active-fg);
  border-color: transparent;
}
.aqh-b--go:hover {
  background: var(--send-active-bg);
  color: var(--send-active-fg);
  opacity: 0.92;
}
.ans__prenote {
  font-size: 15px;
  line-height: 22px;
  color: var(--ink-2);
}
/* cached-recall banner (answer type 4789:5392), orange left accent, neutral lightbulb, no widgets below */
.ans__recall {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  margin-top: 15px;
  padding: 14px 20px;
  background: var(--glass);
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--accent);
  border-radius: 12px;
}
.ans__recall > svg {
  width: 20px;
  height: 20px;
  color: var(--ricon);
  flex: none;
  margin-top: 1px;
}
.ans__recall .rt {
  font-size: 14px;
  line-height: 22px;
  color: var(--sub);
}
.ans__recall .rt b {
  color: var(--ink);
  font-weight: 600;
}
.ans--recall .ans__h {
  margin-top: 18px;
}
.ans__h {
  font-weight: 600;
  font-size: 34px;
  line-height: 40px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.ans__s {
  font-size: 14px;
  color: var(--sub);
}
.ans__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
  position: relative;
  z-index: 5;
} /* lift above .ans__body so the chip tooltips paint over the widget cards */
.mchip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 12px;
  border-radius: 29px;
  background: var(--panel);
  border: 1px solid var(--line);
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
} /* Figma 4130:1566, Inter Semi Bold 12/16 */
.mchip svg {
  width: 14px;
  height: 14px;
  color: var(--ricon);
}
.mchip.ok svg {
  color: #34d399;
}
/* clickable "Sourced from" chip → opens the source-tables menu */
.ans__meta .pop {
  display: inline-flex;
}
.mchip--btn {
  cursor: pointer;
}
.mchip--btn:hover {
  border-color: var(--panel-stroke);
  background: var(--pill);
}
.mchip .mchip-chev {
  width: 13px;
  height: 13px;
  color: var(--ricon);
  margin-inline-start: -2px;
}
/* multi-line explanatory tooltip for the trust chips, overrides the single-line [data-tip] */
.mchip[data-tip]::after {
  bottom: auto;
  top: calc(100% + 10px);
  left: 0;
  right: auto;
  white-space: normal;
  width: 280px;
  max-width: min(280px, 62vw);
  padding: 11px 13px;
  line-height: 1.55;
  font-weight: 400;
  color: var(--sub);
  text-align: left;
  border-radius: 12px;
  transform: translateY(4px);
}
.mchip[data-tipside="r"][data-tip]::after {
  left: auto;
  right: 0;
}
.mchip[data-tip]:hover::after {
  transform: translateY(0);
}
/* source-tables dropdown */
.tables-menu {
  min-width: 0;
  padding: 6px;
} /* width is set to the chip's width in floatMenu */
.tables-hd {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--sub);
}
.tables-hd svg {
  width: 14px;
  height: 14px;
  color: var(--ricon);
}
.tables-hd .th-db {
  color: var(--ink);
}
.tables-hd .th-n {
  margin-inline-start: auto;
  font-weight: 500;
  color: var(--meta);
}
.table-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  background: none;
  border-radius: 9px;
  cursor: pointer;
  text-align: left;
}
.table-item:hover {
  background: var(--glass);
}
.table-item .ti-ic svg {
  width: 15px;
  height: 15px;
  color: var(--ricon);
}
.table-item .ti-main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.table-item .ti-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
}
.table-item .ti-meta {
  font-size: 11px;
  color: var(--meta);
}
.table-item .ti-link {
  width: 15px;
  height: 15px;
  color: var(--ricon);
  flex: none;
}
.table-item:hover .ti-link {
  color: var(--ink);
}
.ans__body {
  margin-top: 14px;
}
.ans__body:has(.inv-widgets) {
  margin-top: 0;
} /* widget-grid answers: .ans flex gap:12 = Figma parent gap-12, no extra */
.ans__insight {
  display: flex;
  gap: 14px;
  align-items: baseline;
  margin-top: 22px;
  padding-top: 22px;
  border-top: 1px solid var(--glass);
}
.ans__insight .l {
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 1px;
  color: var(--ink);
  flex: none;
  text-transform: uppercase;
} /* INSIGHT label = Figma Gray/0 #fafafa (NOT orange). Figma 4789:5385. */
.ans__insight .x {
  font-size: 14px;
  color: var(--ink);
  line-height: 20px;
}
/* ---- ASK NEXT: suggested follow-ups at the foot of an answer ----------------
   Figma: "Answer / Ask Next Row" 7453:81060 inside variant 7454:6834.
   Section rule reuses the SAME divider as .ans__insight above (--glass, 22/22), so the two
   rules in one card are identical. Rows are SQUARE and butt together at gap 0, so each row's
   own top rule forms one continuous list with a single shared left edge, no staircase.
   The first row carries no rule, so the list opens cleanly under the eyebrow.
   Arrow: --sub (Gray/30) at rest → --ink (Gray/0) on hover. NO brand colour in this section;
   the answer above keeps the emphasis. Hover surface is the documented neutral state
   treatment (--glass), not the module accent. */
.ans__asknext {
  margin-top: 22px;
  padding-top: 22px;
  border-top: 1px solid var(--glass);
}
.anx__l {
  font-weight: 600;
  font-size: 12px;
  color: var(--sub);
  text-transform: uppercase;
}
.anx__rows {
  display: flex;
  flex-direction: column;
  margin-top: 12px;
}
.anx {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 16px;
  background: none;
  border: 0;
  border-top: 1px solid var(--glass);
  border-radius: 0;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink);
  text-align: start;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.anx:first-child {
  border-top: 0;
}
.anx__q {
  flex: 1;
  min-width: 0;
}
.anx svg.anx__a {
  width: 16px;
  height: 16px;
  flex: none;
  color: var(--sub);
  transition: color 0.15s var(--ease);
}
.anx:hover {
  background: var(--glass);
}
.anx:hover svg.anx__a {
  color: var(--ink);
}
.anx:focus-visible {
  outline: none;
  background: var(--glass);
}
.anx:focus-visible svg.anx__a {
  color: var(--ink);
}

.ans__act {
  display: flex;
  gap: 10px;
  margin-top: 14px;
}
.abtn {
  height: 40px;
  border-radius: 8px;
  padding: 0 16px;
  font-weight: 500;
  font-size: 14px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  border: 1px solid transparent;
  transition:
    background 0.15s ease,
    border-color 0.15s ease,
    filter 0.15s ease,
    opacity 0.15s ease;
}
/* Primary (brand fill), hover/active deepen the button's OWN orange (DS Button Primary /50→/60).
   This is the button's fill, not a neutral control getting a brand wash. */
.abtn--pri {
  background: var(--accent);
  color: var(--on-accent);
}
.abtn--pri:hover {
  background: var(--accent-hover);
}
.abtn--pri:active {
  background: var(--accent-hover);
  filter: brightness(0.94);
}
/* Outline (secondary/neutral), subtle neutral state wash only (DS UI-state: --glass / --glass-2),
   never an orange tint; text + border stay neutral. */
.abtn--out {
  background: transparent;
  border-color: var(--ink-2);
  color: var(--ink);
}
.abtn--out:hover {
  background: var(--glass);
}
.abtn--out:active {
  background: var(--glass-2);
}
/* text-only button (DS text + icon), used for "View full analysis ⌄" in Short Answer */
.abtn--text {
  background: transparent;
  border-color: transparent;
  color: var(--sub);
  padding: 0 10px;
  gap: 6px;
}
.abtn--text:hover {
  background: var(--glass);
  color: var(--ink);
}
.abtn--text svg {
  width: 16px;
  height: 16px;
  transition: transform 0.15s var(--ease);
}
.abtn--text.is-open svg {
  transform: rotate(180deg);
} /* chevron flips up when full analysis is open */
/* Short Answer sizes to its content, do NOT stretch to fill the thread like a full answer does */
.thread > .ans--short:only-child {
  min-height: 0;
}
/* Disabled, both variants */
.abtn:disabled,
.abtn.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
  filter: none;
}
.afoot {
  margin-top: 14px;
} /* no divider above the footer, Figma answer footer has no top stroke */
.afbar {
  display: flex;
  gap: 4px;
}
.afb {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  color: var(--ricon);
  cursor: pointer;
  background: none;
  border: 0;
  transition:
    color 0.12s var(--ease),
    background 0.12s var(--ease);
}
.afb:hover {
  background: var(--glass);
  color: var(--ink);
}
.afb svg {
  width: 20px;
  height: 20px;
}
.afb[hidden] {
  display: none;
} /* collapsed like/dislike (author rule beats .afb display:grid) */
.afb.is-on {
  color: var(--ink);
} /* picked feedback = filled white icon (ChatGPT-style) */
.afb.is-on svg {
  fill: currentColor;
}
.afb.is-copied {
  color: var(--ink);
} /* copy → tick, clearly visible */
.afb.spinning svg {
  animation: aspin 0.65s var(--ease);
}
@keyframes aspin {
  to {
    transform: rotate(360deg);
  }
}

/* ---------- THINKING / LOADER (follow-up generation) ----------
   Light, ChatGPT-style: three soft grey dots + status text + a live elapsed timer.
   Shown as a pending answer while the next answer-type box is being generated. */
.ans--pending {
  opacity: 1;
}
.thinking {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 18px 0 4px;
}
.thinking__dots {
  display: inline-flex;
  gap: 6px;
  flex: none;
}
.thinking__dots i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ricon);
  opacity: 0.35;
  animation: think 1.15s infinite ease-in-out;
}
.thinking__dots i:nth-child(2) {
  animation-delay: 0.14s;
}
.thinking__dots i:nth-child(3) {
  animation-delay: 0.28s;
}
@keyframes think {
  0%,
  75%,
  100% {
    opacity: 0.3;
    transform: translateY(0);
  }
  38% {
    opacity: 0.85;
    transform: translateY(-3px);
  }
}
.thinking__txt {
  font-size: 14px;
  color: var(--sub);
}
.thinking__eta {
  font-size: 12px;
  color: var(--meta);
  margin-inline-start: auto;
  font-variant-numeric: tabular-nums;
}
/* subtle shimmer skeleton under the status line, so the space the answer will fill is hinted */
.thinking-skel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 6px 0 2px;
}
.thinking-skel span {
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(
    90deg,
    var(--glass) 25%,
    var(--glass-2) 37%,
    var(--glass) 63%
  );
  background-size: 400% 100%;
  animation: shimmer 1.4s infinite linear;
}
.thinking-skel span:nth-child(1) {
  width: 62%;
}
.thinking-skel span:nth-child(2) {
  width: 88%;
}
.thinking-skel span:nth-child(3) {
  width: 44%;
}
@keyframes shimmer {
  0% {
    background-position: 100% 0;
  }
  100% {
    background-position: 0 0;
  }
}

/* ---- THE ANSWER'S OWN WIDGETS, WHILE THEY LOAD (buildAnswerSkel, js/app.js) --------
   Arrive one at a time under the running loader, each the shape and size of the widget
   it stands in for, and are replaced in place when the data lands.

   No new frame is invented here: a skeleton card is the SAME box every real widget card
   is (--box on --box-stroke, radius 16, 22/24), because the point of the stand-in is that
   the real thing lands in exactly the space it was holding. .wcard / .kpi-card / .cwd-stat
   keep their own frame and only their contents are replaced, so this .skw frame is needed
   for the library widgets alone, which draw their card in React.

   Every bar is .skel-line above: the app's one shimmer, one speed, everywhere. */
/* THE STAND-IN NOW HOLDS THE WHOLE ANSWER, not just its widgets (Ameer, 3 Sep),
   so its gap is the CARD's gap: .ans is a 12px flex column, and the pieces inside
   here resolve into that card's own children. At 16 the run was spaced one way and
   the committed answer another, and the difference showed as a settle at the swap. */
.ansk {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ansk-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
/* ---- the placeholders for the pieces that are not widgets -------------------
   EACH ONE WEARS ITS COUNTERPART'S OWN CLASS (.ans__h.ansk-ph, .ans__s.ansk-ph,
   and so on), so it inherits that piece's margins, border and box metrics from
   the rules further up this file and the shimmer occupies the exact space the
   real thing will. That is the whole trick: resolving a piece is then a crossfade
   inside a box that is already the right size, not a piece of layout landing.

   .ansk-ph only has to undo what a TEXT rule would do to a box of bars: a flex
   column with the bars' own rhythm, and no text metrics leaking through. */
.ansk-ph {
  display: flex;
  flex-direction: column;
  gap: 8px;
  justify-content: center;
  font-size: 0;
  line-height: 0;
  color: transparent;
}
/* the headline stands two bars tall because the real one usually wraps to two */
.ans__h.ansk-ph {
  gap: 10px;
  min-height: 80px;
}
.ans__s.ansk-ph {
  min-height: 20px;
}
/* insight and Ask Next keep their own rule above the block (margin-top, padding-top
   and the hairline), so the run already shows the separator the answer will have */
.ans__insight.ansk-ph {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: stretch;
}
.ans__asknext.ansk-ph {
  gap: 10px;
}
/* the action row is a ROW of button-shaped bars, not a column */
/* justify-content is reset because the base .ansk-ph centres its bars, which is
   right for a stack of lines in a text block and wrong for a row of buttons: the
   two stand-ins sat in the middle of the card and then jumped to the leading edge
   when they resolved. */
.ans__act.ansk-ph {
  flex-direction: row;
  gap: 10px;
  align-items: center;
  justify-content: flex-start;
}
/* one trust chip's placeholder. The row it sits in is the real .ans__meta, so the
   chips arrive into their final container and nothing re-flows when they resolve. */
.ansk-chip {
  display: inline-flex;
  flex: none;
}
.skw {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
  background: var(--box);
  border: 1px solid var(--box-stroke);
  border-radius: 16px;
  padding: 22px 24px;
}
.skw-hd {
  display: flex;
  align-items: center;
  gap: 12px;
}
.skw-col {
  display: flex;
  flex-direction: column;
  gap: 14px;
  flex: 1;
  min-width: 0;
}
.skw-r {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}
.skw-r--sp {
  justify-content: space-between;
}
.skw-split {
  display: flex;
  align-items: center;
  gap: 26px;
} /* = .cr__grid, the widget it stands for */
.skw-total {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: none;
}
.skw-cols {
  display: flex;
  gap: 14px;
  min-width: 0;
}
.skw-stat {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
  min-width: 0;
}
/* the two cards that carry their own rhythm keep it: the skeleton inherits the card's gap
   rather than bringing its own, or a KPI stand-in would be taller than the KPI */
.kpi-card > .skel-line,
.cwd-stat > .skel-line {
  flex: none;
}

/* ---------- HISTORY PANEL ---------- */
.history {
  position: absolute;
  top: 99px;
  right: 24px;
  bottom: 20px;
  width: 320px;
  z-index: 20;
  background: var(--panel);
  border: 1px solid var(--panel-stroke);
  border-radius: 20px;
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  transition: width 0.22s var(--ease);
  overflow: hidden;
}
.h-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* History List = 32px gap between the active card and every following row (Figma 4035:29495) */
.h-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  margin-inline-end: -14px;
  padding-inline-end: 10px;
  display: flex;
  flex-direction: column;
  gap: 32px;
}
.h-scroll--docs {
  gap: 0;
} /* the Documents tab is its own list, .doc-item carries its own rhythm */
/* collapsed = 44px tail pinned to the top-right (clock + expand arrow), Figma variant 4205:1391 */
.history.collapsed {
  right: 0;
  bottom: auto;
  width: 44px;
  height: auto;
  padding: 16px 13px;
  background: var(--panel);
  border-color: var(--panel-stroke);
  border-radius: 16px 0 0 16px;
  border-right: 0;
  box-shadow: -4px 0 8px rgba(0, 0, 0, 0.35);
  justify-content: center;
  align-items: center;
  gap: 12px;
}
.history.collapsed .flag-btn {
  width: 18px;
  height: 18px;
  border-radius: 6px;
  display: grid;
  place-items: center;
  color: var(--ricon);
  background: none;
  border: 0;
  cursor: pointer;
  transition: 0.15s;
}
.history.collapsed .flag-btn:hover {
  color: var(--ink);
}
.history.collapsed .flag-btn svg {
  width: 18px;
  height: 18px;
}
[data-theme="light"] .history.collapsed {
  box-shadow: -4px 0 8px rgba(0, 0, 0, 0.1);
}
.hh {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  flex: none;
}
.hh .t {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 18px;
  line-height: 28px;
  color: var(--ink);
}
.hh .t svg {
  width: 14px;
  height: 14px;
  color: var(--ricon);
}
/* chevron points RIGHT: the direction the panel collapses towards */
.hh .x {
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  display: grid;
  place-items: center;
}
.hh .x svg {
  width: 12px;
  height: 12px;
}
.hh .x:hover {
  color: var(--ink);
}
/* Active Investigation card, Neutral/80 surface on a Gray/80 hairline (Figma 4035:1417) */
.hcard {
  border-radius: 12px;
  background: var(--hcard);
  border: 1px solid var(--hcard-stroke);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  cursor: pointer;
  transition:
    border-color 0.18s var(--ease),
    background 0.18s var(--ease);
}
.hcard:hover {
  border-color: var(--border-hover);
} /* DS interactive-surface state, 5925:22628 */
/* TOC selection, the history row whose turn you're currently viewing */
.hcard.is-current {
  box-shadow: none;
}
.hitem.is-current {
  background: var(--glass);
}
.hitem.is-current .tw .t {
  color: var(--ink);
}
.hitem.is-current::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 2px;
  bottom: 2px;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
}
/* header row = the message-square that marks this as a thread ⟷ the ⋯ menu */
.hcard-hd {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}
.hcard-ic {
  color: var(--ricon);
  display: grid;
  place-items: center;
  flex: none;
}
.hcard-ic svg {
  width: 14px;
  height: 14px;
}
.hcard .t {
  font-weight: 400;
  font-size: 14px;
  color: var(--ink-2);
  line-height: 20px;
}
.hcard .m {
  font-weight: 500;
  font-size: 12px;
  line-height: 16px;
  color: var(--ink-2);
}
.hcard .more {
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 4px;
}
.hcard .more svg {
  width: 16px;
  height: 16px;
}
.hcard .more:hover {
  color: var(--ink);
}
/* History Item, icon + (query / timestamp) + ⋯ ; flush with the card, hover bleeds into the gutter */
.hitem {
  position: relative;
  border-radius: 8px;
  padding: 6px 8px;
  margin-inline-start: -8px;
  width: calc(100% + 8px);
  display: flex;
  gap: 12px;
  align-items: flex-start;
  cursor: pointer;
  flex: none;
}
.hitem-ic {
  color: var(--ricon);
  display: grid;
  place-items: center;
  flex: none;
  margin-top: 2px;
}
.hitem-ic svg {
  width: 14px;
  height: 14px;
}
.hitem:hover {
  background: var(--glass);
}
.hitem .tw {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.hitem .tw .t {
  font-weight: 400;
  font-size: 14px;
  color: var(--ink-2);
  line-height: 20px;
}
.hitem .tw .m {
  font-weight: 500;
  font-size: 12px;
  line-height: 16px;
  color: var(--ricon);
}
.hitem .more {
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 4px;
}
.hitem .more svg {
  width: 16px;
  height: 16px;
}
.hitem .more:hover {
  color: var(--ink);
}
.h-expand {
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  display: grid;
  place-items: center;
}
.h-expand svg {
  width: 16px;
  height: 16px;
}

/* A toast has to outrank every scrim in the app, not just the page under them.
   At z-index:200 it tied with .dialog-scrim (200) and lost outright to
   .drawer-scrim (250), so a confirmation fired from inside a drawer, "Copy
   list" in the Semantic Intelligence unmapped-columns drawer is the one that
   caught this, painted UNDERNEATH the scrim and the user got no feedback that
   the action had worked. 300 clears both scrims and still sits below
   .conv-ghost (9999), which is a pointer-following drag proxy and must stay on
   top of everything including this. */
.toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  opacity: 0;
  background: var(--panel);
  border: 1px solid var(--panel-stroke);
  border-radius: 12px;
  padding: 10px 16px;
  font-size: 13px;
  color: var(--ink);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
  z-index: 300;
  pointer-events: none;
  transition: 0.2s;
  display: flex;
  gap: 8px;
  align-items: center;
}
.toast svg {
  width: 16px;
  height: 16px;
  color: var(--accent);
}
.toast.show {
  opacity: 1;
  transform: translateX(-50%);
}

/* ---------- SEARCH RESULTS PAGE (dummy) ---------- */
.search-page {
  flex: 1;
  overflow-y: auto;
  padding: 8px 24px 48px;
}
.search-inner {
  max-width: 860px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
/* "not designed yet" notice, red/danger box so devs never treat this page as final */
.notice--todo {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 16px 18px;
  border-radius: 14px;
  background: var(--danger-soft);
  border: 1px solid var(--danger);
}
.notice__ic {
  color: var(--danger);
  flex: none;
  display: grid;
  place-items: center;
  margin-top: 1px;
}
.notice__ic svg {
  width: 18px;
  height: 18px;
}
.notice__body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.notice__t {
  font-weight: 600;
  font-size: 14px;
  color: var(--danger);
}
.notice__m {
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
}
.search-hd h1 {
  font-weight: 600;
  font-size: 28px;
  line-height: 34px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.search-hd p {
  font-size: 14px;
  color: var(--sub);
  margin-top: 6px;
}
.search-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 52px;
  padding: 0 18px;
  border-radius: 14px;
  background: var(--card);
  border: 1px solid var(--card-stroke);
}
.search-bar svg {
  width: 20px;
  height: 20px;
  color: var(--sub);
  flex: none;
}
.search-bar input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 15px;
}
.search-bar input::placeholder {
  color: var(--sub);
}
.search-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.inv-card {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 16px 18px;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  cursor: pointer;
  text-align: start;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.inv-card:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
} /* DS state, 5925:22628 */
.inv-ic {
  width: 36px;
  height: 36px;
  border-radius: 999px;
  background: var(--pill);
  display: grid;
  place-items: center;
  color: var(--ricon);
  flex: none;
}
.inv-ic svg {
  width: 18px;
  height: 18px;
}
.inv-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.inv-t {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.inv-m {
  font-size: 13px;
  color: var(--sub);
}
.inv-chev {
  width: 18px;
  height: 18px;
  color: var(--sub);
  flex: none;
}

/* ---- list/search page v2, toolbar (Select · Filter · New), flat rows, select mode ---- */
.search-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
/* one page-title recipe, shared by every full-page list view (search + Data Sources) */
.search-top h1,
.ds-head h1 {
  font-weight: 700;
  font-size: 30px;
  line-height: 38px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.search-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
.sbtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 16px;
  border-radius: 12px;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.sbtn:hover {
  background: var(--surface-hover);
  border-color: var(--border-hover);
}
.sbtn svg {
  width: 16px;
  height: 16px;
  flex: none;
}
.sbtn--filter b {
  font-weight: 600;
  margin-left: -2px;
}
.sbtn--new {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.sbtn--new:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}
.sbtn--danger {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}
.sbtn--danger:hover {
  background: var(--danger);
  filter: brightness(0.94);
}
.sbtn--icon {
  width: 40px;
  padding: 0;
  justify-content: center;
  color: var(--ricon);
}
.sel-count {
  font-size: 14px;
  color: var(--sub);
  margin-right: 2px;
}
/* filter dropdown = same width as its "Filter by" button (matches the trigger, never wider) */
.search-actions .pop:has(> .sbtn--filter) {
  position: relative;
}
.search-actions .pop:has(> .sbtn--filter) > .menu {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 6px);
  min-width: 0;
  width: auto;
}

.search-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin-top: 2px;
}
/* fixed row height so default ↔ hover never jumps; the ⋯ is absolute (nav pattern), not in flow */
.srow {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  height: 56px;
  padding: 0 14px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.srow:first-child {
  border-top: 1px solid var(--line);
}
/* rounded inset hover/selected highlight (no sharp edges), same --glass/--glass-2 as the nav rows.
   NO isolation: it would trap the ⋯ menu in the row's stacking context so clicks fall through to the
   rows behind. Highlight = z-0, content = z-1, open menu = z-60. */
.srow::before {
  content: "";
  position: absolute;
  inset: 4px 6px;
  border-radius: var(--r-lg);
  background: transparent;
  transition: background 0.12s var(--ease);
  z-index: 0;
  pointer-events: none;
}
.srow:hover::before {
  background: var(--glass);
}
.srow.is-on::before {
  background: var(--glass-2);
}
/* the two rules touching an active row step aside, see the note in the Access
   block below. Without this the inset patch is cut by a full-width line at the
   top and the bottom, leaving a tail at each of its four corners. */
.srow:hover,
.srow.is-on,
.srow:has(+ .srow:hover),
.srow:has(+ .srow.is-on) {
  border-bottom-color: transparent;
}
.srow:first-child:hover,
.srow:first-child.is-on {
  border-top-color: transparent;
}
.srow > .srow__ck,
.srow > .srow__ic,
.srow > .srow__t,
.srow > .srow__rename,
.srow > .srow__time {
  position: relative;
  z-index: 1;
}
.srow__ck {
  width: 22px;
  height: 22px;
  border-radius: 7px;
  border: 1.5px solid var(--card-stroke);
  background: transparent;
  display: grid;
  place-items: center;
  color: #fff;
  flex: none;
  cursor: pointer;
  padding: 0;
}
.srow__ck.checked {
  background: var(--accent);
  border-color: var(--accent);
}
.srow__ck svg {
  width: 14px;
  height: 14px;
}
.srow__ic {
  color: var(--ricon);
  flex: none;
  display: grid;
  place-items: center;
}
.srow__ic svg {
  width: 18px;
  height: 18px;
}
.srow__t {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.srow__rename {
  flex: 1;
  min-width: 0;
}
.srow__time {
  font-size: 13px;
  color: var(--sub);
  flex: none;
  margin-left: auto;
  transition: opacity 0.12s var(--ease);
}
/* ⋯ reuses the nav .nx-more (absolute → no reflow → no height jump) */
.srow > .pop {
  position: absolute;
  inset-inline-end: 8px;
  top: 50%;
  transform: translateY(-50%);
  line-height: 0;
  z-index: 2;
}
.srow > .pop.is-open {
  z-index: 60;
}
.srow:hover .nx-more,
.srow > .pop.is-open .nx-more,
.nx-more:focus-visible {
  opacity: 1;
}
.srow:not(:hover) > .pop:not(.is-open) .nx-more {
  pointer-events: none;
}
.srow:hover .srow__time,
.srow:has(> .pop.is-open) .srow__time {
  opacity: 0;
}

/* ---- BUILD ROW -------------------------------------------------------------
   All builds is the same list page as All investigations and All chats, and it
   deliberately keeps that page: one heading, Select / Filter by / New, one
   search field, one column of rows. The row is the only thing that changes.

   It changes because it has more to say. An investigation row answers "when did
   I last open this". A build row has to answer "did this one go anywhere", and
   the answer is a count of boards that came out of it, next to a count of turns
   it took to get there. That is a second line, and a second line is a taller
   row, so the icon comes off the text baseline and into a tile, which is what
   holds the two lines together instead of leaving the glyph floating beside a
   block of text.

   Everything else is inherited: the ::before hover wash, the --glass-2 selected
   state, the checkbox, the hairline between rows, the ⋯ that replaces the
   timestamp on hover. Nothing here restates any of it. */
.srow--build {
  height: auto;
  min-height: 76px;
  padding-block: 14px;
  align-items: center;
  gap: 14px;
}

/* the glyph, in the tile the taller row needs */
.srow--build > .srow__ic {
  width: 38px;
  height: 38px;
  border-radius: var(--r-md, 11px);
  background: var(--glass);
  border: 1px solid var(--card-stroke);
  color: var(--accent);
  align-self: center;
}
.srow--build > .srow__ic svg {
  width: 19px;
  height: 19px;
}

.srow--build .srow__body {
  position: relative;
  z-index: 1;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.srow--build .srow__t {
  font-size: 15px;
  font-weight: 600;
  line-height: 20px;
}
.srow--build .srow__rename {
  width: 100%;
}

/* the second line */
.srow__meta {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  line-height: 16px;
  color: var(--sub);
  min-width: 0;
}
.srow__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--sub);
  opacity: 0.55;
  flex: none;
}
.srow__dot.is-on {
  background: var(--accent);
  opacity: 1;
}
/* THE COUNT IS THE ANSWER, so it carries the module accent when there is one to
   carry. A build that saved nothing is not an error and is not marked red; it
   simply stays in the muted voice the rest of the line is already in. */
.srow__saved.is-on {
  color: var(--accent);
  font-weight: 500;
}
.srow__sep {
  opacity: 0.5;
  flex: none;
}
.srow__turns {
  white-space: nowrap;
}
.srow__saved {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* the checkbox sits with the title rather than in the middle of a two-line row */
.srow--build > .srow__ck {
  align-self: center;
}

/* a build row is taller, so its wash has to be too */
.srow--build::before {
  inset: 4px 6px;
}

@media (max-width: 640px) {
  /* the turn count is the first thing to go: it is the least of the three
     numbers on the row, and the timestamp already says when */
  .srow--build .srow__sep,
  .srow--build .srow__turns {
    display: none;
  }
}
.bc-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.bc-link:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ============================================================
   DATA SOURCES: listing page (wireframe ds-01-empty 7930:19400)
   The page shell mirrors the Investigation THREAD exactly (not the search/
   list pages): zero inset padding, content spans the full stage width, and
   the one visible container is the card itself, precisely how .thread
   holds .ans. That is what makes the two screens feel like the same
   product: the card's left/right edges land exactly where every other
   screen's cards land, at whatever gap the nav's current width leaves,
   never a second, independent margin layered on top of it.
   ============================================================ */
/* the scroll viewport carries the SAME 24px top radius as the shell it holds, so content
   scrolling up under the topbar clips on the rounded corner instead of a sharp square cut -
   identical to .thread above, and it works here for the same reason: .ds-page has zero
   horizontal padding, so the card's edges ARE the viewport's edges and the curve lands
   exactly on the card's own corner. Covers both Data Sources views (listing + detail). */
.ds-page {
  flex: 1;
  overflow-y: auto;
  padding: 0 0 48px;
  border-radius: 24px 24px 0 0;
}

/* ---- shell, the structured container Ameer asked for, explored as an
   alternative to the content running edge-to-edge on the page. IS the same
   recipe as the Investigation thread's answer card (.ans): identical
   acard→acard-lift gradient, identical 24px radius, identical 40/42/42
   padding, identical inset hairline drawn on its own layer so the gradient
   can't dull it, and the same module-tinted ambient top-glow (below), down
   to the value, not just the idea. It is a new class rather than a literal
   reuse of .ans only because .ans also carries answer-only plumbing (drag
   handles) this page never uses. One gap value drives ALL section spacing
   here, so "consistent throughout" isn't something to eyeball per block. */
/* overflow:CLIP, not hidden. Both do the same job here (keep the content inside
   the 24px radius) but `hidden` makes this a scroll container, and a scroll
   container between a sticky child and the page's real scroller (.ds-page) is
   what stops the child from ever sticking. `clip` clips without scrolling. */
.ds-shell {
  position: relative;
  width: 100%;
  background: linear-gradient(180deg, var(--acard) 45%, var(--acard-lift) 100%);
  border-radius: 24px;
  overflow: clip;
  padding: 40px 42px 42px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-40);
  animation: rise 0.3s var(--ease) both;
}
.ds-shell::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--acard-stroke);
  border-radius: 24px;
  pointer-events: none;
  z-index: 2;
}
.ds-shell > * {
  position: relative;
  z-index: 1;
}
/* same ambient top-glow .ans gets, module-tinted via var(--glow), which
   resolves through [data-module]. Data Sources is a hard rule: this is
   ALWAYS Investigation's orange, on every entry, regardless of which module
   the user came from, render() overrides the <html data-module> attribute
   for as long as a Data Sources view is open (see the note above its call to
   setAttribute), so --glow here can never resolve to Chat's purple or
   Dashboard's cyan. top:64px matches .topbar--compact's height, since this
   page's header is shorter than every other page's.
   No mask here, exactly like .ans: the glow starts AT the top of the card and
   is covered by the card's own opaque background, so its flat top edge never
   shows. This is what makes the gradient read as beginning at the top on every
   page in the app. */
.stage:has(.ds-shell)::before {
  content: "";
  position: absolute;
  top: 64px;
  left: 0;
  right: 0;
  height: 320px;
  z-index: 5;
  pointer-events: none;
  background: radial-gradient(
    ellipse 72% 88% at 50% -4%,
    var(--glow) 0%,
    var(--glow) 16%,
    transparent 78%
  );
}
/* ...with ONE exception, the bare landing page. .ds-shell--bare strips the card
   surface, so there is nothing opaque under the glow and its flat top edge sits
   directly on the page's ambient background as a visible seam. Only there does
   it need a soft alpha ramp over its first 40px.

   This mask used to live on the rule above, which matches BOTH shells, so the
   carded detail page lost the top 40px of its own glow and opened on a dark
   band where every other page in the app opens on colour. That was the bug;
   the fix is that the exception is now scoped to the case its own comment
   always described. */
.stage:has(.ds-shell--bare)::before {
  mask-image: linear-gradient(to bottom, transparent 0, black 40px, black 100%);
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    black 40px,
    black 100%
  );
}

.ds-head p {
  font-size: 14px;
  line-height: 20px;
  color: var(--sub);
  margin-top: var(--sp-6);
}

/* ---- empty state, generous vertical padding on purpose: this is the
   page's one hero moment (nothing connected yet), so it gets room to
   breathe rather than sitting flush around its own button. ---- */
.ds-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--sp-48) var(--sp-32);
  border-radius: var(--r-lg);
  background: var(--card);
  border: 1px solid var(--card-stroke);
}
/* the plate is a --glass lift ON the card, so it stays one step above whatever
   surface it sits on in either theme, the same recipe as .canvas-empty__ic,
   just neutral instead of accent-tinted (that icon isn't a call to action). */
.ds-empty__ic {
  width: 52px;
  height: 52px;
  border-radius: var(--r-full);
  background: var(--glass);
  display: grid;
  place-items: center;
  color: var(--ricon);
  flex: none;
}
.ds-empty__ic svg {
  width: 22px;
  height: 22px;
}
.ds-empty__t {
  margin-top: var(--sp-16);
  font-weight: 600;
  font-size: 17px;
  line-height: 24px;
  color: var(--ink);
}
.ds-empty__s {
  margin-top: var(--sp-6);
  font-size: 14px;
  line-height: 20px;
  color: var(--sub);
  max-width: 420px;
}
.ds-empty .btn {
  margin-top: var(--sp-24);
}
/* the Projects card's own empty state reuses .ds-empty verbatim, same icon
   plate/title/sub/button recipe the page's own top-level empty state already
   uses, just at a size that fits inside a card instead of owning the whole
   page; the 48/32 padding read as too much air nested one level in. */
.ds-empty--sm {
  padding: 32px 24px;
}

/* ---- available connectors ---- */
.ds-sect {
  display: flex;
  flex-direction: column;
  gap: var(--sp-16);
}
.ds-sect__hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-16);
}
.ds-sect__lbl {
  font-weight: 600;
  font-size: 11px;
  line-height: 16px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sub);
}
.ds-sect__n {
  font-size: 13px;
  line-height: 16px;
  color: var(--sub);
}
.ds-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-16);
}
/* connector tile = the Suggestion Card (.scard) anatomy, two lines tall */
.ds-card {
  height: 96px;
  padding: var(--sp-16) var(--sp-16) var(--sp-12);
  border-radius: var(--r-lg);
  background: var(--card);
  border: 1px solid var(--card-stroke);
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  align-items: stretch;
  text-align: start;
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.ds-card:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
} /* DS state, 5925:22628 */
.ds-card__hd {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-12);
  min-width: 0;
}
.ds-card__mono {
  width: 34px;
  height: 34px;
  border-radius: var(--r-md);
  background: var(--pill);
  color: var(--ink);
  font-weight: 600;
  font-size: 12px;
  line-height: 1;
  display: grid;
  place-items: center;
  flex: none;
}
/* every connector brand mark, ONE line weight. See the comment above logoIc()
   in js/connector-logos.js for why this is a global stroke override rather
   than 13 individually redrawn icons. round joins/caps to match the app's own
   lucide icon convention (icons.js: "stroke 2, round caps, no fill"), thinner
   here because these paths are far denser than a simple glyph and a matching
   2px weight blobbed out every curve. */
.conn-logo {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.ds-card__id {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-6);
  min-width: 0;
}
.ds-card__nm {
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.ds-card__desc {
  font-size: 12px;
  line-height: 16px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* footnote belongs to the catalogue it sits under, so it rides that section's
   own 16px rhythm instead of the shell's 40px section gap, a sibling of
   .ds-grid inside the same .ds-sect, not a fourth top-level block. */
.ds-foot {
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
}

/* the grid steps down before the tiles get too narrow to hold "SQL Server" + its tag */
@media (max-width: 1180px) {
  .ds-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (max-width: 860px) {
  .ds-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 560px) {
  .ds-grid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 640px) {
  .ds-shell {
    padding: var(--sp-28) var(--sp-20) var(--sp-32);
  }
}

/* ---- the empty-state landing page's own shell, same .ds-shell element as
   the connected-list page, same flex/gap layout, same >* {z-index:1} child
   scaffolding, same ambient top-glow (it hooks :has(.ds-shell), and the
   class stays), with the CARD's own surface stripped: no gradient fill, no
   radius, no inset hairline, no clip. This is the page's content sitting
   directly on the ambient background rather than a boxed panel. Padding
   drops from the shell's own 40/42/42 to the SAME recipe .page/.search-page
   already use for content with no box around it (8/24/48), reused, not
   invented, and also most of how the content sits higher: 8px of top
   padding instead of 40px. ---- */
.ds-shell--bare {
  background: none;
  border-radius: 0;
  overflow: visible;
  padding: 8px 24px 48px;
}
.ds-shell--bare::after {
  content: none;
}
@media (max-width: 640px) {
  .ds-shell--bare {
    padding: 8px 24px 48px;
  }
} /* no narrower step, matches .page's own lack of one */

/* ---- connected source row (Figma ds-06-list 7931:25780), a DIV (not
   .doc-item's button), because it now holds a real ⋯ menu and a button can't
   contain a button. Same neutral --card/--card-stroke surface as every OTHER
   card on this page (Connection, Recent activity, the status bar), this
   used to borrow --hcard/--hcard-stroke (the history-panel row recipe,
   #111 solid on dark), which was the one visibly heavier, darker-bordered
   box on the page and read as inconsistent with everything around it.
   THREE rows on the left, not four: identity (name + status), connector +
   domain hint, then results (the stats line), "Learned just now" and "Used
   by N projects" moved out of the body entirely and onto the right, next to
   the Sync action that actually refreshes them, instead of stacking as a
   fourth line of body text (Ameer's own note that the left side read
   crowded). ---- */
.ds-conn-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* position:relative (for the shimmer pseudo-element below), deliberately NOT
   overflow:hidden, a background gradient already clips to its own element's
   box on its own; the row itself needs to stay overflow:visible so the Sync
   button's tooltip (an absolutely-positioned ::after escaping upward past
   the row's own top edge) doesn't get clipped by its container. */
.ds-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  width: 100%;
  padding: 18px 20px;
  border-radius: var(--r-lg);
  cursor: pointer;
  position: relative;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.ds-row:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.ds-row .ds-card__mono {
  margin-top: 1px;
}
.ds-row__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.ds-row__hd {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.ds-row__nm {
  font-weight: 600;
  font-size: 15px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds-row__hd .tag {
  flex: none;
}
.ds-row__meta {
  font-size: 13px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* the results row sits a little further from the identity lines above it
   than those lines sit from each other, the extra margin is what actually
   reads as two groups instead of three interchangeable rows of grey text */
.ds-row__stats {
  font-size: 13px;
  color: var(--sub);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 3px;
}
.ds-row__stats b {
  color: var(--ink);
  font-weight: 600;
}
.ds-row__dot {
  color: var(--text-faint);
}

/* ---- right side: freshness + usage stacked above the actions that touch
   them, not loose text in the body ---- */
.ds-row__side {
  flex: none;
  display: flex;
  align-items: center;
  gap: 14px;
  align-self: center;
}
.ds-row__side-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: end;
}
.ds-row__learned {
  color: var(--text-faint);
  font-size: 12px;
  white-space: nowrap;
}
.ds-row__used {
  font-size: 13px;
  color: var(--sub);
  white-space: nowrap;
}

/* ---- Sync, a clearly-visible NEUTRAL button (.btn--subtle, not the page's
   orange primary, that's "Connect a database"'s job, and two competing
   orange actions on one screen is a hierarchy problem, not emphasis) with
   its own tooltip explaining what it does. The icon itself spins while
   syncing, rather than a separate spinner glyph replacing it. ---- */
.ds-sync-btn {
  flex: none;
}
.ds-sync-btn svg {
  transition: transform 0.2s var(--ease);
}
/* right-anchored + wrapped, not the default centred single-line tooltip -
   this button sits near the row's own right edge, close enough to the
   viewport edge that a centred nowrap sentence could run off it */
.ds-sync-btn[data-tip]::after {
  left: auto;
  right: 0;
  transform: translate(0, 4px);
  white-space: normal;
  width: 220px;
  text-align: start;
  font-weight: 400;
}
.ds-sync-btn[data-tip]:hover::after {
  transform: translate(0, 0);
}
[dir="rtl"] .ds-sync-btn[data-tip]::after {
  right: auto;
  left: 0;
}
.is-syncing .ds-sync-btn svg {
  animation: dsSyncSpin 0.8s linear infinite;
}
@keyframes dsSyncSpin {
  to {
    transform: rotate(360deg);
  }
}

/* ---- syncing state, a skeleton-style shimmer sweep across the WHOLE card,
   no progress bar (Ameer's own spec). The real content dims rather than
   hides (nothing reflows, nothing jumps when it settles) and a soft
   diagonal highlight band sweeps left to right on loop via a background-
   position animation, the exact technique .thinking-skel already uses for
   the answer-loading skeleton, just as a full-card overlay instead of
   skeleton bars, since there's already real content shaped to dim rather
   than needing fake placeholder blocks.
   Shared by .ds-row (the listing row) AND .ds-status (the detail page's
   status bar) via the plain .is-syncing class, same Sync experience in
   both places, one set of rules driving it, not two near-duplicates. Both
   hosts already carry position:relative for this to anchor against. */
.is-syncing {
  pointer-events: none;
}
.is-syncing::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(
    100deg,
    transparent 35%,
    var(--glass-2) 48%,
    var(--glass) 50%,
    var(--glass-2) 52%,
    transparent 65%
  );
  background-size: 250% 100%;
  animation: dsSyncSweep 1.4s linear infinite;
}
@keyframes dsSyncSweep {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -100% 0;
  }
}
[dir="rtl"] .is-syncing::after {
  animation-name: dsSyncSweepRTL;
}
@keyframes dsSyncSweepRTL {
  0% {
    background-position: -100% 0;
  }
  100% {
    background-position: 200% 0;
  }
}

.ds-row.is-syncing .ds-row__nm,
.ds-row.is-syncing .ds-row__hd .tag,
.ds-row.is-syncing .ds-row__meta,
.ds-row.is-syncing .ds-row__stats,
.ds-row.is-syncing .ds-row__side-meta,
.ds-row.is-syncing .ds-card__mono {
  opacity: 0.45;
  transition: opacity 0.2s var(--ease);
}
.ds-status.is-syncing .ds-status__ic,
.ds-status.is-syncing .ds-status__main,
.ds-status.is-syncing .ds-status__synced {
  opacity: 0.45;
  transition: opacity 0.2s var(--ease);
}

/* ---- post-sync metric deltas, "briefly glow, then settle," the same
   language a live scoreboard uses for a number that just changed. ONE CSS
   animation drives both the colour and the arrow's fade so nothing needs a
   second JS timeout to clean up: it starts lit and ends back at plain ink/
   transparent-arrow on its own. .is-up is --success (a real increase earns
   the one place this list uses colour beyond the status dot); .is-down
   mirrors it in --danger at the same, equally subtle, weight. */
.ds-stat-delta {
  animation: dsStatFlashUp 2.2s var(--ease) forwards;
}
.ds-stat-delta.is-down {
  animation-name: dsStatFlashDown;
}
@keyframes dsStatFlashUp {
  0%,
  55% {
    color: var(--success);
  }
  100% {
    color: var(--ink);
  }
}
@keyframes dsStatFlashDown {
  0%,
  55% {
    color: var(--danger);
  }
  100% {
    color: var(--ink);
  }
}
.ds-stat-arrow {
  display: inline-flex;
  margin-inline-start: 2px;
  vertical-align: -1px;
  animation: dsArrowFade 2.2s var(--ease) forwards;
}
.ds-stat-delta.is-up .ds-stat-arrow {
  color: var(--success);
}
.ds-stat-delta.is-down .ds-stat-arrow {
  color: var(--danger);
}
.ds-stat-arrow svg {
  width: 10px;
  height: 10px;
  stroke-width: 3;
}
@keyframes dsArrowFade {
  0% {
    opacity: 0;
    transform: translateY(3px) scale(0.8);
  }
  15% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  55% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

/* ============================================================
   DATA SOURCE DETAIL (Figma ds-07-detail 7931:25950, Overview redesigned
   per 7933:26171). Same .ds-shell as the listing page, so navigating between
   them never feels like a different surface. Tabs reuse .panel-tabs (Chat
   with Data's History/Documents tabs) verbatim; entity/domain chips are the
   plain .tag atom. .ds-kv, .ds-proj and .ds-status are the genuinely new
   pieces here, kept to the same type scale, radii and neutral surfaces as
   everything around them; .ds-activity leans entirely on the existing .card
   and .tl (timeline) components instead of adding a fourth.
   ============================================================ */
.ds-detail {
  gap: 24px;
}
.ds-detail__hd {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.ds-detail__id {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  min-width: 0;
}
.ds-card__mono--lg {
  width: 44px;
  height: 44px;
  border-radius: var(--r-lg);
  font-size: 14px;
  flex: none;
}
.ds-detail__idmain {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ds-detail__name {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.ds-detail__name h1 {
  font-weight: 700;
  font-size: 24px;
  line-height: 30px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.ds-detail__sub {
  font-size: 13px;
  color: var(--sub);
}
.ds-detail__actions {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
}

.ds-detail__tabs {
  margin: 0;
}

.ds-detail__cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
/* The shared .card atom carries a full-strength --border (dark: solid #27272a)
  , right for a page that has nothing else to compare it to, wrong here: every
   OTHER box on this page (.ds-card, .ds-pick, .ds-row, .ds-stat) is drawn with
   the translucent --card-stroke, and .card's harder line was the one visibly
   heavier stroke on the screen, a page-local override, not a change to the
   shared atom, since other .card call sites elsewhere in the kit still want
   the atom's own default. */
.ds-detail__cols .card,
.ds-projects {
  padding: 20px 22px;
  gap: 14px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.ds-link {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 13px;
  color: var(--accent);
  cursor: pointer;
  flex: none;
}
.ds-link:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ---- Overview status bar (Figma 7933:26171), what the last learn run
   found, plus Sync, as ONE card rather than two separate boxes: they are
   both facets of a single fact ("where this source stands right now"), not
   unrelated content the way the two-column cards below it are. The
   icon-plate + title/sub pattern is .ds-done's (the drawer's Preview banner)
   at a smaller scale, so a source's "settled" state reads as the same kind
   of thing wherever it appears. */
/* .card sets flex-direction:column at equal specificity, state explicitly,
   don't rely on load order to win it */
.ds-status {
  display: flex;
  flex-direction: row;
  align-items: center;
  padding: 18px 22px;
  gap: 0;
  border-radius: var(--r-lg);
  background: var(--card);
  border-color: var(--card-stroke);
  position: relative;
}
.ds-status__seg {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}
.ds-status__div {
  width: 1px;
  align-self: stretch;
  background: var(--border);
  margin: 0 22px;
  flex: none;
}
.ds-status__ic {
  width: 32px;
  height: 32px;
  border-radius: var(--r-full);
  flex: none;
  display: grid;
  place-items: center;
}
.ds-status__ic svg {
  width: 15px;
  height: 15px;
  stroke-width: 2.25;
}
.ds-status__ic--neutral {
  background: var(--surface-raised);
  color: var(--ricon);
}
.ds-status__main {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.ds-status__t {
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink);
}
.ds-status__s {
  font-size: 12px;
  line-height: 17px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* the learned segment's fragment reuses .ds-row__stats verbatim (bold counts,
   the row's own middot separators), a 12px override here would fight it */
.ds-status__main .ds-row__stats {
  font-size: 13px;
}
/* ---- Sync, the SAME experience the listing row has (button, tooltip,
   shimmer, delta animation), just on the right of this card instead of the
   right of a row. "Last synced" sits beside the button the way "Learned just
   now"/"Used by N projects" sit beside the row's own Sync button. ---- */
.ds-status__sync {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
}
.ds-status__synced {
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
}
/* still learning: no health, no numbers exist yet, one quiet line, the
   .ll-dot the row already uses for "happening now" rather than a health icon
   that would be answering a question with no answer yet */
.ds-status--learning {
  gap: 12px;
}
.ds-status--learning .ll-dot {
  width: 7px;
  height: 7px;
}

/* ---- Recent activity (replaces "What Spark learned", a log now belongs on
   a page like this more than a snapshot does). Built entirely on .card + the
   existing .tl timeline component (already used by the fraud dashboard's
   Recent alerts), the only new rule is the footnote under it. */
.ds-activity__foot {
  font-size: 12px;
  line-height: 17px;
  color: var(--text-faint);
}

.ds-kv {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ds-kv__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.ds-kv__k {
  font-size: 13px;
  color: var(--sub);
  flex: none;
}
.ds-kv__v {
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  text-align: end;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds-kv__v--success {
  color: var(--success);
}

.ds-detail__hr {
  height: 1px;
  background: var(--border);
  margin: 2px 0;
}
.ds-entities__lbl {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sub);
}
.ds-entities {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.ds-projects__hd {
  margin-bottom: 2px;
}
.ds-projects__sub {
  font-size: 13px;
  color: var(--sub);
}
.ds-projects__grid {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}
.ds-proj {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
  text-align: start;
  padding: 14px 16px;
  border-radius: var(--r-md);
  background: var(--card);
  border: 1px solid var(--card-stroke);
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.ds-proj:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.ds-proj__ic {
  color: var(--ricon);
}
.ds-proj__nm {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}
.ds-proj__meta {
  font-size: 12px;
  color: var(--sub);
}
.ds-proj--add {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  color: var(--sub);
  border-style: dashed;
  background: transparent;
}
.ds-proj--add:hover {
  color: var(--ink);
}

.ds-tab-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 56px 0;
  color: var(--sub);
}
.ds-tab-empty svg {
  width: 22px;
  height: 22px;
  color: var(--ricon);
}
.ds-tab-empty p {
  font-size: 13px;
}

@media (max-width: 860px) {
  .ds-detail__cols {
    grid-template-columns: 1fr;
  }
  .ds-detail__hd {
    flex-direction: column;
  }
  .ds-status {
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
  }
  .ds-status__div {
    display: none;
  }
  .ds-status__sync {
    justify-content: space-between;
  }
}

/* ============================================================
   CONNECT A DATABASE: right-side drawer
   (Figma ds-02-add-connector 7930:19577 → ds-03-add-connect 7930:20123)
   Shares the confirm-dialog's scrim convention (fixed, dimmed, blurred) but
   docks the panel to the inline-end edge instead of centering it, and slides
   it in from that edge. Selection is neutral --sel-bg/--sel-fg (the same pair
   the nav rail's active row already uses), NOT the reference's orange.
   No click-outside-to-close: a multi-field form is exactly the case where an
   accidental backdrop click shouldn't cost the user everything they typed,
   so only the explicit Close/Cancel dismiss it (see the app.js click
   handler). .drawer-scrim/.drawer are the STABLE outer shell, created once
   per open and never rebuilt while the drawer stays open; every in-session
   interaction patches .drawer__inner instead (syncDsDrawer() in app.js),
   which is what stops the entrance animation from replaying on every click. */
.drawer-scrim {
  position: fixed;
  inset: 0;
  z-index: 250;
  display: flex;
  justify-content: flex-end;
  background: rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  animation: scrimIn 0.16s var(--ease) both;
}
[dir="rtl"] .drawer-scrim {
  justify-content: flex-start;
}
/* AMBIENT TOP-GLOW: the same gesture the Data Sources container itself carries
   (see .stage:has(.ds-shell)::before), brought onto the panel that sits over it so
   the drawer reads as part of the same surface family instead of a flat grey slab
   dropped on top of one. Module-tinted via --glow, so it is whatever accent you
   came from.

   Deliberately WEAKER than the page's. The page glow paints over a photographic
   ambient background that already carries warmth, holds full --glow for its first
   16% and fades across 320px of a ~1400px-wide container. Here it sits on a flat
   opaque --menu-bg at roughly half that width, where the same values read as a
   visible orange wash across the header. So: the ellipse is centred at -12%, which
   keeps its peak off-screen and shows only the falloff; there is no solid plateau;
   and it is fully gone by 340px, well above the footer, which matters, because
   .drawer__foot::before fades the scrolling list into flat var(--menu-bg) and
   would band against any tint still present down there.

   The ellipse's centre is 80px ABOVE the panel, so only its falloff is ever on
   screen, no hot spot at the top edge, just a wash already on its way out. The
   520px radius with the stop at 80% puts full transparency at y≈336px, which is
   also why it can never band against the footer fade.

   Measured with a headless screenshot + RGB read, not guessed. See the note above
   --drawer-glow in tokens.css: reusing the page's --glow here measured +19 RGB
   points at the top edge. */
.drawer {
  width: min(760px, calc(100vw - 48px));
  height: 100%;
  background:
    radial-gradient(
      ellipse 90% 520px at 50% -80px,
      var(--drawer-glow) 0%,
      transparent 80%
    ),
    var(--menu-bg);
  border-inline-start: 1px solid var(--border);
  box-shadow: var(--shadow-2xl);
  display: flex;
  flex-direction: column;
  min-height: 0;
  animation: drawerIn 0.22s var(--ease) both;
}
@keyframes drawerIn {
  from {
    transform: translateX(100%);
  }
  to {
    transform: translateX(0);
  }
}
[dir="rtl"] .drawer {
  animation-name: drawerInRtl;
}
@keyframes drawerInRtl {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}
/* the part syncDsDrawer() actually replaces on every in-session interaction -
   a quick, subtle fade (not a full 0→1) so frequent micro-updates (picking a
   connector, changing a filter) read as responsive, not laggy */
.drawer__inner {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
  animation: dsContentIn 0.15s var(--ease) both;
}
@keyframes dsContentIn {
  from {
    opacity: 0.5;
  }
  to {
    opacity: 1;
  }
}

/* ============================================================
   DRAWER TYPE + SPACE SYSTEM: the one place all four steps get their
   hierarchy from. Nothing inside the drawer sets a size, weight or
   colour that isn't in this table.

   WHY IT WAS REBUILT. The drawer had eleven text roles sharing three
   sizes: 13px alone was the stepper, the step heading, the supporting
   sentence, the field label, the loader's step text AND the footnote,
   and five near-identical greys (--sub #ababab, --text-muted #a1a1aa,
   --text-secondary #e4e4e7, --ricon #d4d4d4, --text-faint #71717a) were
   used interchangeably. With no size difference and no colour
   difference between roles, nothing could read as more important than
   anything else, which is exactly what "cluttered, no hierarchy"
   looks like. The fix is not more decoration, it is FEWER values,
   FURTHER apart.

   ── TYPE ─────────────────────────────────────────────────────────
   role           size/lh  wt   track    colour        job
   drawer title   20/28    600  -.015em  --ink         the drawer's identity, constant across steps
   step heading   15/22    600  -.005em  --ink         what THIS step is (was a 13px uppercase eyebrow)
   lead           13/20    400 ,        --sub         ONE supporting sentence per step
   row / value    14/20    500 ,        --ink         connector names, field values, loader head
   field label    12/16    500  .01em    --sub         sits UNDER its value in weight, never over it
   micro label    11/16    600  .08em    --text-faint  uppercase group labels: furniture, not content
   note / help    12/18    400 ,        --text-faint  the quietest readable tier
   stat value     26/32    600  -.02em   --ink         the one display size, Preview only

   Seven sizes, each with exactly ONE job, every neighbouring pair at
   least 1.25x apart. The old 24/700 title drops to 20/600: at 24/700 it
   shouted over its own content, and the 24 → 13 cliff left nothing in
   between, so a real step heading had nowhere to sit. 20/600 → 15/600 →
   13/400 is a ladder you can read down.

   ── COLOUR (three tiers, nothing in between) ─────────────────────
   --ink        #fafafa  primary: headings, names, values, running step
   --sub        #ababab  secondary: supporting sentences, descriptions, labels
   --text-faint #71717a  tertiary: micro labels, notes, footnote, elapsed timer
   The accent appears in exactly ONE place per step, the primary button.
   --success appears in exactly one place in the whole flow, the check
   mark on Preview. Everything else is greyscale, deliberately.

   ── SPACE (even scale, one rhythm) ───────────────────────────────
   28  between top-level blocks in the body (the body's inner gaps used
       to run 6/8/12/14/16/18/20/22/24 with no system behind them)
   24  between connector groups
   20  between form fields
   12  micro label → the thing it labels · search → filter chips
    6  heading → its lead
   Head 24/32/18 · body 28/32/32 · foot 18/32, one 32px inline edge
   through all three zones, so every heading, label, row, input and note
   in the drawer starts on the same vertical line.
   ============================================================ */
.drawer__head {
  flex: none;
  padding: 24px 32px 18px;
  border-bottom: 1px solid var(--border);
}
.drawer__hrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.drawer__htitle {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
/* title + optional source line. Stays a two-row stack rather than a beside-the-
   title lockup so the 20px title keeps its own baseline and the sub can truncate
   independently of it. */
.drawer__hstack {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.drawer__htitle h2 {
  font-weight: 600;
  font-size: 20px;
  line-height: 28px;
  letter-spacing: -0.015em;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* WHICH source Spark just learned. Sits in the drawer's secondary tier, the same
   13/20/--sub every supporting line in this panel uses, so it identifies without
   competing with the title above it. It replaces the connector tile that used to
   sit on this step: a name says more than a vendor mark, and says it in words. */
.drawer__hsub {
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* matched to the 28px cap-height box of the 20px title beside it */
.ds-card__mono--sm {
  width: 28px;
  height: 28px;
  font-size: 11px;
  border-radius: 8px;
  flex: none;
}
.drawer__body {
  flex: 1;
  overflow-y: auto;
  padding: 28px 32px 32px;
  display: flex;
  flex-direction: column;
  gap: 28px;
}
/* .drawer__body is THE scroll container (overflow-y:auto), its own children
   must never shrink to "fit". Without this, a flex column's default
   flex-shrink:1 compresses every child (search box down to ~19px, its
   border-radius then reading as a full pill on a near-zero-height bar,
   which is what actually happened here) once total content exceeds the
   visible height, instead of just letting the body scroll like it's
   supposed to. Every fixed-size block stays its real size; only the
   scrollable collections give. */
.drawer__body > * {
  flex-shrink: 0;
}
.drawer__intro {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* THE step heading. It was `.drawer__t`, a 13px uppercase letterspaced label,
   which put a second shouting element directly under the title: two competing
   headers and no body voice between them. A sentence-case 15/600 heading reads
   as the answer to "what am I doing here", and hands the uppercase treatment
   back to the group labels, where recessive furniture belongs. */
.drawer__h {
  font-weight: 600;
  font-size: 15px;
  line-height: 22px;
  letter-spacing: -0.005em;
  color: var(--ink);
}
/* measure-capped: a supporting sentence running the full 696px of a 760px
   drawer is one long line the eye has to track back across. 60ch keeps it to
   the two comfortable lines it was written as. */
.drawer__s,
.drawer__lead {
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
  max-width: 62ch;
}
/* uppercase group labels, recessive by SCALE and tracking, never by fading a
   body-sized string. Drawer-local so it can't drift from (or drag along) the
   Data Sources page's own .ds-sect__lbl. */
.drawer__label {
  display: block;
  font-weight: 600;
  font-size: 11px;
  line-height: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
/* No border-top. The footer is the same surface as the rest of the panel, not a
   separate zone bolted onto it, a hairline here was the one hard edge inside an
   otherwise seamless drawer, and it read as heavier than the rest of the Spark
   surface language, where a footer earns a border only when it's genuinely a
   different material (the dialog shell's own footer). The scroll fade below is
   what still tells the picker apart from "the list is done", it does that job
   without a line. */
.drawer__foot {
  flex: none;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 32px;
}
/* The picker is 13 rows in a fixed-height panel, so it always scrolls, and it
   was ending in a hard horizontal cut through the middle of a card, which reads
   as broken rather than as "there is more". A 36px fade of the drawer's own
   surface, anchored to the top of the footer, lets the list dissolve instead of
   snapping off. Attached to the footer, not the body, so it can't scroll away
   from the edge it belongs to, and invisible on the steps that don't overflow. */
.drawer__foot::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 100%;
  height: 36px;
  background: linear-gradient(to top, var(--menu-bg), transparent);
  pointer-events: none;
}
.drawer__footnote {
  font-size: 12px;
  line-height: 18px;
  color: var(--text-faint);
}

/* progress strip, Connect / Learn / Preview, all three now real steps the
   flow actually walks through. Three states, no colour between them: ahead =
   an empty ring on the muted label; here = a filled --ink donut on the ink
   label; passed = a filled --ricon disc with a check. Neutral throughout -
   the reference's blue/green would be the only hue on the drawer, and "where
   am I in this flow" is not a semantic state. The dot is ONE size in every
   state so the strip can never jitter as the flow advances. */
/* Wayfinding, not content, so it sits in the note tier (12/500/--text-faint)
   and only the step you are ON is allowed to reach --ink. At 13/500 on --sub it
   was reading at the same weight as the supporting sentence below the divider,
   which is how a progress strip ends up competing with the screen it labels. */
.wiz-steps {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
}
.wiz-step {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  color: var(--text-faint);
  white-space: nowrap;
}
.wiz-step .wiz-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--border-strong);
  background: transparent;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--menu-bg);
  transition:
    background 0.2s var(--ease),
    border-color 0.2s var(--ease);
}
.wiz-step .wiz-dot svg {
  width: 8px;
  height: 8px;
  stroke-width: 3.5;
}
.wiz-step.is-done {
  color: var(--sub);
}
.wiz-step.is-active {
  color: var(--ink);
  font-weight: 600;
}
.wiz-step.is-active .wiz-dot {
  background: var(--ink);
  border-color: var(--ink);
}
/* the hole that turns the active dot into "you are here" rather than "done" -
   --menu-bg, not --page, because it is punched out of the DRAWER's surface */
.wiz-step.is-active .wiz-dot::after {
  content: "";
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--menu-bg);
}
.wiz-step.is-done .wiz-dot {
  background: var(--ricon);
  border-color: var(--ricon);
}
.wiz-line {
  flex: 1;
  height: 1px;
  background: var(--border);
  min-width: 24px;
  transition: background 0.2s var(--ease);
}
.wiz-line.is-done {
  background: var(--border-strong);
}

/* ============================================================
   DATA SOURCE EMPTY STATE (Figma 7933:26367, IA reference only, never its
   colours, connector tiles stay the app's own neutral .ds-card family, not
   the reference's per-brand fills). dsLandingV2HTML() in app.js.
   ============================================================ */
/* the hero copy reuses .hero-text verbatim (Investigation/Chat/Dashboard's
   own home-screen opener), .ds-hero only tightens the shell's 40px section
   gap above/below it, since a hero that owns the WHOLE top of the page reads
   right with less air than one competing with a title above it */
.ds-hero {
  margin: 8px 0 4px;
}

/* ---- "how it works", reuses .wiz-step/.wiz-line (just above) at hero
   scale. Nothing is "active" or "done" here (nothing has started), so every
   step stays one neutral weight and the dot shows its own number instead of
   staying empty, placed AFTER the base .wiz-step .wiz-dot rule on purpose:
   that rule sets color:var(--menu-bg) for its own punched-out-hole trick on
   .is-active, which doubles as "invisible" for any plain text a normal dot
   never otherwise contains, an override earlier in the cascade loses to it
   at equal specificity regardless of source order elsewhere in the file. */
.wiz-steps--intro {
  justify-content: center;
  margin-top: 0;
}
.wiz-steps--intro .wiz-step {
  font-size: 13px;
  color: var(--sub);
}
/* Dot + line contrast, retuned for where this strip actually sits now: the open
   ambient background (Option 2 dropped the flat --acard/--menu-bg card these
   colours were originally picked against). --border-strong on a transparent
   fill was a thin, unfilled ring the same darkness as the busiest part of the
   photo behind it, close to unreadable. Two changes, not one: the ring steps
   up to --border-hover (one clear notch brighter, still a neutral gray, not
   the accent, "where am I" stays wayfinding, not a coloured status), AND the
   dot gets a fill it never had before, --glass, the same "subtle raised
   plate on an ambient/photo surface" token the empty-state icon plate and
   every other floating mark on this kind of background already use. A ring
   alone reads as a stray outline at a glance; a filled dot reads as a shape. */
.wiz-steps--intro .wiz-dot {
  width: 20px;
  height: 20px;
  font-size: 11px;
  font-weight: 600;
  color: var(--sub);
  background: var(--glass);
  border-color: var(--border-hover);
}
/* the connecting line gets the SAME step up, and only within this scope, the
   drawer's own Connect/Learn/Preview stepper (plain .wiz-steps, no --intro)
   keeps the base .wiz-line colour untouched, since it sits on the drawer's
   flat --menu-bg where the original value was never a contrast problem */
.wiz-steps--intro .wiz-line {
  min-width: 32px;
  flex: none;
  width: 40px;
  background: var(--border-hover);
}

/* ---- primary CTA, the one-click path for someone who already knows what
   they want, between "here's how it works" and "here are your options".
   Deliberately roomier than the shell's own 40px section rhythm on both
   sides (Ameer's own note that the page felt compressed around it), the
   button EARNS this much air because it is the page's one primary action,
   the way .ds-empty's button got the same generous treatment for the same
   reason. Scrolling is fine; the page choosing to feel cramped is not. */
.ds-cta-block {
  display: flex;
  justify-content: center;
  margin: 20px 0 25px;
}
/* ---- "or" divider, line + centred label + line, sitting closer to the CTA
   above it than the picker below (it belongs to the choice, "this, or
   browse", more than to the grid). Each half is a gradient, transparent at
   the outer edge, fading UP to a soft peak near the label, so the line
   reads as blending into the page rather than sitting on top of it. The peak
   is --border-hover: --border (the softer step, tried first against the old
   flat --acard card) measures DARKER than Option 2's warm ambient
   background, not lighter, so it read as no line at all rather than a
   subtle one. --border-hover is the same one-notch-brighter neutral gray
   the stepper's dot/line fix uses just above (.wiz-steps--intro), picked
   for the same reason: visible against the photo background without
   reading as an accent. */
.ds-or {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  margin-bottom: 25px;
}
.ds-or::before,
.ds-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--border-hover));
}
.ds-or::after {
  background: linear-gradient(to left, transparent, var(--border-hover));
}
/* the fade direction is physical (to right/to left), so a flipped flex row
   under RTL needs it swapped explicitly, .ds-or::before now sits at the
   RTL "start" (visually the right), so ITS fade has to point left instead */
[dir="rtl"] .ds-or::before {
  background: linear-gradient(to left, transparent, var(--border-hover));
}
[dir="rtl"] .ds-or::after {
  background: linear-gradient(to right, transparent, var(--border-hover));
}
.ds-or em {
  font-style: normal;
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  color: var(--text-muted);
}

.ds-sect--center {
  align-items: center;
  text-align: center;
}
.ds-sect--center .ds-chip-filter--center {
  justify-content: center;
}
.ds-sect--center .ds-grid {
  width: 100%;
}
.ds-foot--center {
  text-align: center;
}

/* ---- square connector tiles (Option 2 only, Figma 7933:26367's LAYOUT idea,
   never its per-brand colour fills). A bold icon-only grid reads as an entry
   point the way a short list of description-heavy rows doesn't; the icon and
   name identify the connector by default, and only the description reveals
   on hover, enough on its own to say "here are your options, pick one"
   without the extra sentence per tile until you're actually considering one.
   A genuinely new molecule, nothing already in the kit shows a hover-reveal
   tile, built from existing tokens only (--card/--card-stroke/
   --surface-raised/--border-hover/--pill), and the reveal itself reuses the
   nav rail's own smooth-collapse trick (.nx-subwrap: grid-template-rows
   0fr→1fr + opacity) rather than inventing a second one. The tile's OUTER
   box (aspect-ratio:1) never changes on hover, only what's painted inside
   it does, so a hovered tile can never shift its grid siblings, the same
   zero-reflow discipline the drawer's connector picker already holds itself
   to. */
/* flex, not grid: a CSS Grid track (even auto-fit) still stretches to fill
   its row via 1fr once it HAS content, so a filtered result short of a full
   row (e.g. 4 NoSQL tiles in a row that fits 7) rendered hugging the left
   edge with the remainder of the row just empty space. Flex-wrap wraps the
   same way, but justify-content:center re-centers whatever's left on each
   row, including a short last one, for free, and a fixed (non-growing)
   flex-basis keeps every tile the same size regardless of how many share
   the row, so centering never comes at the cost of stretched-looking tiles. */
.ds-sqgrid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
  width: 100%;
}
.ds-sqcard {
  position: relative;
  flex: 0 0 160px;
  aspect-ratio: 1/1;
  border-radius: var(--r-lg);
  background: var(--card);
  border: 1px solid var(--card-stroke);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 14px 12px;
  overflow: hidden;
  cursor: pointer;
  text-align: center;
  transition:
    background 0.18s var(--ease),
    border-color 0.18s var(--ease),
    transform 0.18s var(--ease),
    box-shadow 0.18s var(--ease);
}
/* lift + shadow is the "this is clickable" tell a colour change alone isn't -
   the same language .ds-proj/.chip already use for their own hover, just with
   a shadow added since this tile sits on its own rather than in a packed row */
.ds-sqcard:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
  transform: translateY(-3px);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.22);
}
.ds-sqcard:active {
  transform: translateY(-1px);
}
.ds-sqcard__ic {
  width: 40px;
  height: 40px;
  border-radius: var(--r-md);
  background: var(--pill);
  color: var(--ink);
  display: grid;
  place-items: center;
  flex: none;
  margin-bottom: 10px;
  transition:
    transform 0.22s var(--ease),
    margin 0.22s var(--ease);
}
/* the icon steps back a touch (smaller, tighter margin) to make room for the
   description growing in below the name, small enough that the default
   icon+name pairing doesn't visibly shift when it happens */
.ds-sqcard:hover .ds-sqcard__ic {
  transform: scale(0.88);
  margin-bottom: 6px;
}
.ds-sqcard__nm {
  font-weight: 600;
  font-size: 13px;
  line-height: 18px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.ds-sqcard__meta {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  width: 100%;
  transition:
    grid-template-rows 0.26s var(--ease),
    opacity 0.2s var(--ease);
}
.ds-sqcard__meta > .ds-sqcard__metain {
  overflow: hidden;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 4px;
}
.ds-sqcard:hover .ds-sqcard__meta,
.ds-sqcard:focus-visible .ds-sqcard__meta {
  grid-template-rows: 1fr;
  opacity: 1;
}
.ds-sqcard__desc {
  font-size: 11px;
  line-height: 15px;
  color: var(--sub);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media (max-width: 640px) {
  .wiz-steps--intro {
    flex-wrap: wrap;
    row-gap: 10px;
  }
  .ds-sqcard {
    flex-basis: 110px;
  }
}

/* ---- form fields, built on .dlg-input (the Rename dialog's field), NOT
   the canvas .input atom: glass fill, neutral ink focus, no accent anywhere.
   Taller than the 40px canvas scale for a dense, field-heavy form, matches
   the auth pages' --lg input height, an already-existing Spark scale. ---- */
.ds-input,
.ds-search {
  height: 44px;
  padding: 0 14px;
  border-radius: var(--r-md);
  background: var(--glass);
  border: 1px solid var(--border);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
  transition:
    border-color 0.15s var(--ease),
    background 0.15s var(--ease);
}
.ds-input:focus,
.ds-input:focus-within,
.ds-search:focus-within {
  outline: none;
  border-color: var(--ink);
  background: var(--glass-2);
}
.ds-input::placeholder {
  color: var(--text-faint);
}

/* ---- search box, the SAME .ds-input field this drawer already uses for
   every other field (Display name, Host, …): identical height, radius,
   glass fill, and neutral focus, just with a leading icon slot. One input
   style for the whole drawer, search included, instead of a third one-off
   recipe that matches nothing else in it. ---- */
.ds-search {
  display: flex;
  align-items: center;
  gap: 10px;
}
.ds-search__ic {
  color: var(--icon-muted);
  flex: none;
  display: grid;
  place-items: center;
}
.ds-search__ic svg {
  width: 16px;
  height: 16px;
}
.ds-search input {
  flex: 1;
  min-width: 0;
  height: 100%;
  background: none;
  border: 0;
  outline: 0;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
}
.ds-search input::placeholder {
  color: var(--text-faint);
}

/* ---- step 1: choose a connector ----
   Search and the category chips are ONE control: both narrow the same list.
   They used to be two top-level body blocks 24px apart, which read as two
   unrelated sections and pushed the list a whole block further down. Grouped
   at 12px they are visibly one filter bar, and the list starts sooner. */
.ds-filters {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.chip-filter {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
/* neutral selected chip, the same --sel-bg/--sel-fg pair as the nav rail's
   active row, not an accent fill */
.chip--sel {
  background: var(--sel-bg);
  border-color: var(--sel-bg);
  color: var(--sel-fg);
}
.chip--sel:hover {
  background: var(--sel-bg);
}
.ds-pickgroups {
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.ds-pickgrp {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* minmax(0,1fr), NOT 1fr, a bare 1fr track won't shrink below its content's
   min-content width, so a long name+description forced the grid wider than
   its container and produced a horizontal scrollbar. minmax(0,1fr) lets the
   ellipsis truncation actually take effect instead of fighting the grid. */
.ds-pickgrid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
}
.ds-pick {
  height: 60px;
  min-width: 0;
  padding: 0 14px;
  border-radius: var(--r-md);
  background: var(--card);
  border: 1px solid var(--card-stroke);
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  text-align: start;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.ds-pick:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
/* selected = neutral ink ring + a small filled sel-bg/sel-fg check badge -
   the DS's own "this one" language (rail-item.sel, cbtn.is-on), not orange */
.ds-pick.is-sel {
  border-color: var(--ink);
  background: var(--surface-raised);
}
/* A row NAME is body text, not a heading: at 600 it sat at the same weight as
   the step heading two blocks above and thirteen of them shouted in unison.
   500 keeps it primary within its row and secondary to the screen. */
.ds-pick__main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.ds-pick__nm {
  font-weight: 500;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds-pick__desc {
  font-size: 12px;
  line-height: 16px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* ALWAYS in the layout (app.js never conditionally inserts/removes it) -
   only its opacity toggles. That keeps every row's width identical whether
   it's selected or not, so picking one can never shift the others. */
.ds-pick__check {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--sel-bg);
  color: var(--sel-fg);
  display: grid;
  place-items: center;
  flex: none;
  opacity: 0;
}
.ds-pick__check svg {
  width: 12px;
  height: 12px;
}
.ds-pick.is-sel .ds-pick__check {
  opacity: 1;
}
.ds-pick-empty {
  padding: 12px 0 4px;
  text-align: center;
  font-size: 13px;
  color: var(--sub);
}

/* ---- Link a project drawer, the list is .ds-pick verbatim (same row the
   connector picker uses: name/desc + a check badge that only toggles
   opacity, never inserted/removed, so a row's own width can't shift when it
   gets picked). Multi-select needs no CSS of its own, .is-sel just applies
   to more than one row at once, nothing about the rule assumes exactly one. */
.ds-link-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* the "small loading state" the confirm step shows before committing, the
   same .mini-spin the composer's own doc-chip upload uses, just sized up for
   a drawer's worth of empty space instead of a 14px chip slot */
.ds-link-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 64px 0;
  color: var(--sub);
  font-size: 13px;
}
.ds-link-loading .mini-spin {
  width: 22px;
  height: 22px;
  border-width: 2px;
}

/* ---- step 2: connection details ----
   .ds-fieldrow pairs Name+Port, Username+Password, and Database+Domain hint,
   one per line (Ameer's own layout, restored after a brief single-column
   pass). It used to be named .ds-row, the SAME class the connected-source
   list row now uses for its own card (padding, dark hcard fill,
   hcard-stroke border, hover state). Same selector, two unrelated
   components: a field pair was silently inheriting the connected row's full
   boxed-card treatment, which is where the unwanted dark-bordered
   containers around paired fields came from. Renamed so a field pair is
   what it always should have been, a plain flex row with no surface of its
   own, since each `.ds-input` inside it already carries its own fill and
   border. */
.ds-form {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.ds-fieldrow {
  display: flex;
  gap: 16px;
}
.ds-fieldrow > .ds-field {
  flex: 1;
  min-width: 0;
}
/* Port is the one narrow field in its pair, a numeric value never needs
   Name's full width, and giving it flex:1 anyway just left the input's own
   right two-thirds empty */
.ds-field--port {
  flex: 0 0 140px;
}
.ds-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* A label must never out-shout the value it labels. It was 13/500 on
   --text-secondary (#e4e4e7) against a 14/400 --ink value: near-identical
   size, brighter grey, so six labels read louder than the six answers and the
   form looked like a wall of headings. 12/500 on --sub puts the label
   underneath its value in the hierarchy, which is where a label belongs. */
.ds-field label {
  display: flex;
  align-items: center;
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--sub);
}
/* the one field-level exception to "all fields are required" (Domain hint) -
   quieter than the label itself, a footnote on it rather than competing with it */
.ds-field__opt {
  margin-inline-start: 6px;
  font-size: 11px;
  font-weight: 400;
  color: var(--text-faint);
}
/* Domain hint's (i) icon, the field's explanation moved here from a permanent
   .ds-field__help line, so one optional field doesn't cost the form a whole
   extra row just to explain itself. Reuses the app's existing [data-tip]
   tooltip (css above, "composer icon controls") for the show/hide mechanics;
   only the position/width/wrap are overridden here, the same way .mchip's
   own multi-line tooltip already overrides that base rule for a longer
   sentence. Positioned BELOW the icon, not above, this can be the first
   real field in the form with no headroom before the drawer header. Shows on
   :hover only in css/reference; also on plain :focus (not :focus-visible,
   which Chrome skips for a mouse click) so clicking it and dismissing it by
   clicking elsewhere works with zero extra JS. */
.ds-field__info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin-inline-start: 6px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--text-faint);
  cursor: pointer;
  transition: color 0.12s var(--ease);
}
.ds-field__info svg {
  width: 13px;
  height: 13px;
}
.ds-field__info:hover,
.ds-field__info:focus {
  color: var(--sub);
}
.ds-field__info:focus-visible {
  outline: 2px solid var(--sel-bg);
  outline-offset: 2px;
}
.ds-field__info[data-tip]::after {
  bottom: auto;
  top: calc(100% + 8px);
  left: 0;
  right: auto;
  transform: translateY(4px);
  white-space: normal;
  width: 210px;
  text-align: start;
  font-weight: 400;
  line-height: 1.4;
}
.ds-field__info[data-tip]:focus::after {
  opacity: 1;
  transform: translateY(0);
}
[dir="rtl"] .ds-field__info[data-tip]::after {
  left: auto;
  right: 0;
}
/* The per-field required marker is GONE: see dsFieldHTML() in app.js. A mark
   that appears on every field distinguishes none of them; the step's lead says
   "All fields are required" once instead. --danger stays reserved for
   .has-error below, which is where "required" actually needs to be loud. */
.ds-field__help {
  font-size: 12px;
  line-height: 18px;
  color: var(--text-faint);
  margin-top: -2px;
}
/* ---- ERROR STATE: ONE recipe, shared with the auth pages ------------------
   Identical to .field.has-error in css/auth.css (which app.html doesn't load,
   so the rule is restated here rather than shared through a file): --danger
   stroke on the input, and a 12px message with a circle-alert icon underneath
   saying what is missing, in words. The stroke alone was the bug, a field
   went red and never said why, so the same form told the user something was
   wrong in a completely different way from the way Sign in / Create account
   tell them. Six red boxes and no sentence is six questions.
   The error out-ranks :focus (the base .ds-input:focus rule would otherwise
   paint --ink back over --danger the moment the user clicks into the field
   they were sent to), and it out-ranks the field's own help line, which steps
   aside so one input never carries two 12px lines saying overlapping things -
   the same rule auth.css applies to the password hint. */
.ds-field.has-error .ds-input,
.ds-field.has-error .ds-input:focus,
.ds-field.has-error .ds-input:focus-within {
  border-color: var(--danger);
}
.ds-field.has-error .ds-field__help {
  display: none;
}
.ds-field__err {
  display: none;
  align-items: flex-start;
  gap: 6px;
  margin-top: -2px;
  font-size: 12px;
  line-height: 16px;
  font-weight: 400;
  color: var(--danger);
}
.ds-field.has-error .ds-field__err {
  display: flex;
  animation: dsErrIn 0.18s var(--ease) both;
}
.ds-field__err svg {
  width: 14px;
  height: 14px;
  flex: none;
  margin-top: 1px;
}
@keyframes dsErrIn {
  from {
    opacity: 0;
    transform: translateY(-2px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.ds-input--pw {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ds-input--pw input {
  flex: 1;
  min-width: 0;
  height: 100%;
  background: none;
  border: 0;
  outline: 0;
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 14px;
}
.ds-input--pw input::placeholder {
  color: var(--text-faint);
}
/* same eye-toggle recipe as the auth pages (css/auth.css), which app.html
   doesn't load, kept local so this drawer doesn't pull in the whole auth sheet */
.input-eye {
  background: none;
  border: 0;
  padding: 0;
  display: flex;
  color: var(--icon-muted);
  cursor: pointer;
  flex: none;
}
.input-eye svg {
  width: 18px;
  height: 18px;
}
.input-eye:hover {
  color: var(--text-primary);
}
/* The closing note on a step. 16px icon in a 16px box + 10px gap puts its text
   on a 26px indent, the same indent .ll-step's 18px mark + 12px gap gives the
   loader rows on Learn, so the two steps' quiet trailing lines start on the
   same optical column. The icon rides the note's own tier rather than
   --icon-muted, so the whole line is one value instead of two. */
.ds-secnote {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 12px;
  line-height: 18px;
  color: var(--text-faint);
}
.ds-secnote svg {
  width: 16px;
  height: 16px;
  flex: none;
  margin-top: 1px;
  color: currentColor;
  opacity: 0.85;
}

/* ---- step 2 of 3: LEARN (Figma ds-04-learning 7931:25335) --------------------
   There is almost nothing here, and that is the point: the whole loading
   experience is the Investigation thread's Live Loader (.ll), mounted straight
   into the drawer body with no restyling at all, and the bar under it is the
   document-upload progress bar (.up-bar--row) reused untouched, the app's one
   neutral "how far along" surface. All this block adds is the pairing of the
   two, plus a duration line under the bar. ---- */
/* THE LOADER IS THE SCREEN. Learn used to open with an uppercase eyebrow, a
   supporting sentence, the loader (with its own header), a bar, a duration
   note AND a security note: six blocks, two of which said the same thing about
   rows never being copied. Now it is three, one lead sentence, the loader,
   the bar and its note, so the step list is unmistakably the thing you are
   watching. Nothing else on the screen is allowed to reach --ink except the
   source name and the step that is currently running.

   The loader's own header row becomes the step heading: the SOURCE NAME at
   14/500/--ink (it is the subject of the whole screen, and it was sitting at
   13/500 on --sub, quieter than the eyebrow above it, an inverted hierarchy),
   with the elapsed counter demoted to the note tier beside it. */
.drawer__body .ll {
  margin-top: 0;
  gap: 20px;
} /* .ll ships margin-top:16 for the answer card's question row */
.drawer__body .ll-hd {
  align-items: baseline;
}
.drawer__body .ll-hd-t {
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  color: var(--ink);
}
.drawer__body .ll-time {
  font-size: 12px;
  line-height: 18px;
  color: var(--text-faint);
}
.drawer__body .ll-steps {
  gap: 16px;
}
/* Pinned to the FOOT of the scroll area, not stacked under the last step. The
   bar reports the whole run, not the row above it, and while the run streams
   the step list grows, so a bar that follows it slides down the panel seven
   times before it settles. Anchored at the bottom it never moves, the void a
   part-finished list leaves sits between the detail and the summary where it
   reads as breathing room, and the composition has a top and a bottom. */
.ds-prog {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: auto;
  padding-top: 12px;
}
.ds-prog .up-bar--row {
  margin-top: 0;
} /* the flex gap owns this spacing, not the bar */
.ds-prog__note {
  font-size: 12px;
  line-height: 18px;
  color: var(--text-faint);
}

/* ---- step 3 of 3: PREVIEW (Figma ds-05-review 7931:25176) --------------------
   The reference paints this screen green. Spark keeps --success SEMANTIC: the
   run genuinely succeeded, so the check mark earns the colour and nothing else
   does. The banner is the ordinary --card surface every other box on this page
   sits on, its title is --ink like any other title, and the counters below are
   literal .kpi-card atoms from the Investigation pages, no new component, no
   new colour, no tinted success surface. ---- */
/* THE BANNER IS THE SCREEN'S ANSWER, so it is the only block on Preview that
   gets a surface, and it opens the body with no heading above it, a step
   heading reading "Review what Spark learned" under a title reading "Review
   what Spark learned" is the kind of duplication that makes a screen feel
   busy without adding a word. */
.ds-done {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px 20px;
  border-radius: var(--r-lg);
  background: var(--card);
  border: 1px solid var(--card-stroke);
}
/* the ONE place --success lands in the entire flow. Sized to the 36px optical
   weight of the two text lines beside it, and kept a circle (not the drawer's
   8px squircle) because it is a state mark, not a logo tile.
   align-items:center on .ds-done (not flex-start), flex-start pinned the
   icon to the TOP of the row, which reads as "too high" against a two-line
   text block whose visual centre sits lower than its first line. */
.ds-done__ic {
  width: 36px;
  height: 36px;
  border-radius: var(--r-full);
  flex: none;
  display: grid;
  place-items: center;
  background: var(--success-soft);
  color: var(--success);
}
.ds-done__ic svg {
  width: 17px;
  height: 17px;
  stroke-width: 2.5;
}
.ds-done__main {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.ds-done__t {
  font-weight: 600;
  font-size: 15px;
  line-height: 22px;
  letter-spacing: -0.005em;
  color: var(--ink);
}
.ds-done__s {
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
}
/* the source name is emphasised by WEIGHT ONLY, not by a second colour: at
   --ink-2 it came back almost as bright as "Learning complete" directly above
   it, so the banner had two things claiming to be its headline */
.ds-done__s b {
  font-weight: 500;
  color: inherit;
}
/* .kpi-row is the 4-up canvas grid; six counters in a 760px drawer want 3-up,
   so the LAYOUT is local and the CARD is the existing atom */
/* a micro label sits 12px off the thing it labels, everywhere in the drawer -
   .ds-sect's own 16px is the Data Sources PAGE's rhythm, which is a step
   coarser than a 760px panel wants */
.drawer__body .ds-sect {
  gap: 12px;
}
.ds-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
/* A .kpi-card at drawer scale. The value carries the whole block, so it goes UP
   (22 → 26) and the weight comes DOWN (700 → 600): six 700-weight numbers in a
   grid read as six alarms, and this screen is good news delivered calmly. The
   label drops to the micro tier so the number is unambiguously the content and
   the word is unambiguously its caption. --r-lg to match the banner above -
   panels and cards are 14px in this drawer, inputs and rows are 10px. */
.ds-stat {
  padding: 16px 18px;
  gap: 4px;
  border-radius: var(--r-lg);
}
.ds-stat .kpi-v {
  font-size: 26px;
  line-height: 32px;
  font-weight: 600;
  letter-spacing: -0.02em;
}
/* One notch ABOVE the section label (11/500/.06em/--sub vs 11/600/.08em/faint).
   Both were identical, which flattened the grouping: the header of a set has to
   be quieter than the captions inside it, or the set has no header. */
.ds-stat .kpi-k {
  font-size: 11px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--sub);
}

/* a source still being learned, on the page behind, the Live Loader's own
   running mark, held to the tag's neutral ink rather than the accent */
.tag--learning .ll-dot {
  width: 7px;
  height: 7px;
  background: currentColor;
  flex: none;
}

/* THE Online/Offline pill (js/app.js: dsOnlineTagHTML), light-gray .tag--neutral
   fill, a small SOLID dot carrying the only colour, plain-ink label. Replaces the
   old .tag--success/--danger treatment (a green/red-tinted pill + circle-dot, an
   outline ring icon) that read as a generic status badge rather than something
   from this kit, the same complaint, and the same fix direction, as
   .tag--learning's dot just above. Static, unlike .ll-dot: online/offline is a
   settled state, not a process, so nothing here pulses. */
.tag--status {
  gap: 6px;
}
.status-dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  flex: none;
  display: block;
}
.status-dot--online {
  background: var(--success);
}
.status-dot--offline {
  background: var(--danger);
}
/* an invite that has not been accepted yet: pending, not broken */
.status-dot--invited {
  background: var(--warning);
}

/* ---- RUN IN BACKGROUND: the floating pill (js/app.js: dsBgIndicatorHTML) ----
   Built on the SAME recipe as .stopped-note .pill (glass surface, panel-
   stroke, rounded-full, 13px --sub text), the app's one existing "floating
   status note" pattern, just pinned to the viewport instead of centred
   inline, and clickable. Bottom-inset-inline-end so it mirrors correctly in
   Arabic without a rule of its own. z-index 220: above the toast (200, so
   the two can coexist without fighting) and below any dialog/drawer scrim
   (250), moot in practice, since the pill and the drawer are mutually
   exclusive by construction (dsBgIndicatorHTML bails whenever state.dsDrawer
   is set), but z-index should still say what's actually true. */
.ds-bg-pill {
  position: fixed;
  bottom: 24px;
  inset-inline-end: 24px;
  z-index: 220;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 44px;
  padding: 0 8px 0 16px;
  background: var(--panel);
  border: 1px solid var(--panel-stroke);
  border-radius: 999px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
  cursor: pointer;
  font-family: var(--font-sans);
  animation: dsBgIn 0.22s var(--ease) both;
  transition:
    background 0.15s var(--ease),
    transform 0.15s var(--ease);
}
[dir="rtl"] .ds-bg-pill {
  padding: 0 16px 0 8px;
}
.ds-bg-pill:hover {
  background: var(--surface-raised);
  transform: translateY(-1px);
}
@keyframes dsBgIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.ds-bg-pill .ll-dot {
  flex: none;
}
.ds-bg-pill__t {
  font-size: 13px;
  line-height: 18px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 240px;
}
/* the arrow lives in its own quiet chip, not loose in the pill, the same
   "icon gets a plate" language every other trailing glyph in the kit uses
   (.ds-done__ic, .mchip-chev's hover state), so it doesn't read as a stray
   mark floating at the end of a sentence */
.ds-bg-pill__ic {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: var(--surface-raised);
  color: var(--ricon);
  display: grid;
  place-items: center;
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease);
}
.ds-bg-pill__ic svg {
  width: 13px;
  height: 13px;
  stroke-width: 2.25;
}
.ds-bg-pill:hover .ds-bg-pill__ic {
  background: var(--surface-hover);
  color: var(--ink);
}

@media (max-width: 720px) {
  .ds-pickgrid {
    grid-template-columns: 1fr;
  }
  .ds-fieldrow {
    flex-direction: column;
  }
  .ds-field--port {
    flex: 1;
  }
  .ds-stats {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ---------- INVESTIGATION SUB-PAGES + WIDGETS ---------- */
.page {
  flex: 1;
  overflow-y: auto;
  padding: 8px 24px 48px;
}
.page-inner {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.page-hd h1 {
  font-weight: 600;
  font-size: 28px;
  line-height: 34px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.page-hd p {
  font-size: 14px;
  color: var(--sub);
  margin-top: 6px;
}
.kpi-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
.page-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
@media (max-width: 1000px) {
  .kpi-row {
    grid-template-columns: repeat(2, 1fr);
  }
  .page-grid {
    grid-template-columns: 1fr;
  }
}
.inv-widgets {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 4px;
} /* Figma 5740:23607 Panels pt-4 → 12 (gap) + 4 = 16 from meta chips to cards */
.kpi-card {
  background: var(--box);
  border: 1px solid var(--box-stroke);
  border-radius: 16px;
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.kpi-card .kpi-k {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sub);
}
.kpi-card .kpi-v {
  font-weight: 700;
  font-size: 26px;
  color: var(--ink);
  line-height: 1.15;
}
.kpi-card .kpi-d {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 500;
  color: var(--sub);
}
.kpi-card .kpi-d.up {
  color: var(--success);
}
.kpi-card .kpi-d.down {
  color: var(--danger);
}
.wcard {
  background: var(--box);
  border: 1px solid var(--box-stroke);
  border-radius: 16px;
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.wcard--wide {
  grid-column: 1 / -1;
}
.wcard__hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.wcard__hd > span:first-child {
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink);
}
.wcard__ic {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ricon);
}
.wcard__ic > svg {
  width: 18px;
  height: 18px;
}
.spark svg {
  width: 100%;
  height: 30px;
  display: block;
}
.linechart svg {
  width: 100%;
  height: 130px;
  display: block;
}
.vbars {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  height: 170px;
}
.vbar {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  height: 100%;
  justify-content: flex-end;
}
.vbar .bar {
  width: 100%;
  max-width: 40px;
  border-radius: 6px 6px 0 0;
  background: var(--accent);
}
.vbar .vl {
  font-size: 12px;
  color: var(--sub);
}
.hbars {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.hbar-row {
  display: flex;
  align-items: center;
  gap: 14px;
}
.hbar-lbl {
  width: 104px;
  flex: none;
  font-size: 14px;
  color: var(--ink-2);
}
.hbar-track {
  flex: 1;
  height: 10px;
  border-radius: 6px;
  background: var(--surface-raised);
  overflow: hidden;
}
.hbar-fill {
  display: block;
  height: 100%;
  border-radius: 6px;
  background: var(--accent);
}
.hbar-vl {
  width: 52px;
  text-align: end;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.donut-wrap {
  display: flex;
  align-items: center;
  gap: 26px;
}
.donut {
  width: 132px;
  height: 132px;
  border-radius: 50%;
  flex: none;
  position: relative;
}
.donut::after {
  content: "";
  position: absolute;
  inset: 20px;
  border-radius: 50%;
  background: var(--box);
}
.donut-legend {
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
}
.donut-leg {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  white-space: nowrap;
}
.donut-leg .d {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
}
.donut-leg .nm {
  color: var(--ink-2);
}
.donut-leg .vl {
  margin-inline-start: auto;
  color: var(--ink);
  font-weight: 500;
}
.hist {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 150px;
}
.hist span {
  flex: 1;
  background: var(--accent);
  border-radius: 4px 4px 0 0;
  min-height: 3px;
}
.tl {
  display: flex;
  flex-direction: column;
}
.tl-row {
  display: flex;
  gap: 14px;
  padding: 8px 0;
}
.tl-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  margin-top: 5px;
  flex: none;
}
.tl-t {
  font-size: 14px;
  color: var(--ink-2);
  line-height: 19px;
}
.tl-m {
  font-size: 12px;
  color: var(--sub);
  margin-top: 3px;
}
.p-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
.p-table th {
  text-align: start;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sub);
  padding: 0 12px 12px;
  border-bottom: 1px solid var(--acard-stroke);
}
.p-table td {
  padding: 13px 12px;
  border-bottom: 1px solid var(--border-subtle);
  color: var(--ink-2);
}
.p-table tr:last-child td {
  border-bottom: 0;
}
.p-table td:first-child {
  color: var(--ink);
  font-weight: 500;
}
.p-table .mono {
  font-family: var(--font-mono);
}
.plist {
  display: flex;
  flex-direction: column;
}
.plist-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.plist-row:last-child {
  border-bottom: 0;
}
.plist-n {
  width: 18px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--sub);
  flex: none;
}
.plist-nm {
  flex: 1;
  font-size: 14px;
  color: var(--ink);
  font-weight: 500;
}
.plist-vl {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
}

/* ============================================================
   CHAT WITH DATA (Brand Secondary / purple), upload flow,
   tabbed History|Documents panel, stat cards + coverage table.
   ============================================================ */
/* the docked composer's send reads as the module accent */
[data-module="chat"] .send {
  background: var(--accent);
  color: #fff;
}
[data-module="chat"] .send:hover {
  background: var(--accent-hover);
}
.toggle .pill {
  box-shadow: none;
}

/* ---- upload composer (empty = drop-zone + locked controls) ---- */
.up-area {
  width: 100%;
  border-radius: 16px;
  padding: 26px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  background: rgba(255, 255, 255, 0.02);
  border: 1.5px dashed rgba(255, 255, 255, 0.16);
}
[data-theme="light"] .up-area {
  background: #faf7ff;
  border-color: var(--accent-border);
}
.up-ic {
  width: 46px;
  height: 46px;
  border-radius: 23px;
  display: grid;
  place-items: center;
  background: var(--accent-soft);
  color: var(--accent);
}
.up-ic svg {
  width: 22px;
  height: 22px;
}
.up-drop {
  font-size: 14px;
  color: var(--ink-2);
}
.up-btn {
  height: 40px;
  border-radius: 8px;
  padding: 0 16px;
  background: var(--accent);
  color: #fff;
  font-weight: 500;
  font-size: 14px;
  border: 0;
  cursor: pointer;
  transition: 0.15s;
}
.up-btn:hover {
  background: var(--accent-hover);
}
.up-fmt {
  font-size: 12px;
  color: var(--text-faint);
}
.up-unlock {
  font-size: 12px;
  color: var(--text-faint);
}
.cc--off {
  opacity: 0.4;
  pointer-events: none;
}
.cc--off .send {
  background: var(--surface-raised);
  color: var(--icon-muted);
}
/* filled = uploaded document chips + live composer */
.up-files {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  width: 100%;
}
.doc-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 280px;
  height: 34px;
  padding: 0 8px;
  padding-inline-start: 12px;
  border-radius: 10px;
  background: var(--pill);
  border: 1px solid var(--line-strong);
  color: var(--ink);
}
/* file-type icon is NEUTRAL, never the module accent, matches the × and + in this same
   chip (--ricon) and the doc badge in the Documents panel. Brand is for emphasis, not utility icons. */
.doc-chip-ic {
  color: var(--ricon);
  display: grid;
  place-items: center;
}
.doc-chip-ic svg {
  width: 14px;
  height: 14px;
}
/* ---- the chip's own loader ------------------------------------------------
   A file added from the composer has no panel row to watch, so the chip carries the whole
   status. While bytes are moving or the index is building, the file glyph becomes a small
   spinning ring in its place, same 14px slot, so the chip does not resize or jump.
   Neutral, like every other progress surface here: this is a system report, not a brand
   moment. Red stays reserved for failure. */
.doc-chip-ic--busy {
  width: 14px;
  height: 14px;
}
.mini-spin {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  display: block;
  border: 1.5px solid var(--spin-track);
  border-top-color: var(--ink);
  animation: miniSpin 0.7s linear infinite;
}
@keyframes miniSpin {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .mini-spin {
    animation-duration: 2.4s;
  }
}
.doc-chip .nm {
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.doc-chip-x {
  width: 20px;
  height: 20px;
  border-radius: 6px;
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
}
.doc-chip-x:hover {
  background: var(--glass);
  color: var(--ink);
}
.doc-chip-x svg {
  width: 12px;
  height: 12px;
}
.up-add {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  background: var(--pill);
  border: 1px solid var(--line-strong);
  color: var(--ricon);
  cursor: pointer;
}
.up-add:hover {
  color: var(--ink);
}
.up-add svg {
  width: 16px;
  height: 16px;
}

/* ---- upload progress ------------------------------------------------------
   The bar is deliberately NEUTRAL: white on dark, near-black on light. Progress
   is a system report, not a brand moment; painting it accent-purple would make a
   transfer read as loudly as the Upload CTA next to it. Red is reserved for the
   one state that actually needs attention. Same rule as the utility icons. */
.doc-chip {
  position: relative;
  overflow: hidden;
}
.up-bar {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 3px;
  background: var(--glass);
  pointer-events: none;
}
.up-bar__fill {
  display: block;
  height: 100%;
  background: var(--ink);
  opacity: 0.55;
  transition: width 0.12s linear;
}
.up-bar--row {
  position: static;
  display: block;
  margin-top: 8px;
  border-radius: 2px;
  overflow: hidden;
}
/* Indexing has no measurable percentage, so the bar becomes a travelling band instead of a
   fill, an honest indeterminate, rather than a bar parked at 100% pretending to still be
   moving. Uploading keeps the real percentage; only Indexing sweeps. The width comes from
   here, not from an inline style, so the band is the same in the chip and in the row. */
.is-indexing .up-bar__fill {
  width: 38%;
  opacity: 0.5;
  transition: none;
  animation: upSweep 1.3s var(--ease) infinite;
}
@keyframes upSweep {
  0% {
    transform: translateX(-115%);
  }
  100% {
    transform: translateX(380%);
  }
}

.up-pct {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--meta);
  flex: none;
  font-variant-numeric: tabular-nums;
}
.up-stat {
  font-size: 11px;
  color: var(--meta);
  flex: none;
  white-space: nowrap;
}
/* ---- INDEXING SHIMMER ------------------------------------------------------
   Indexing is the only state in the set that is a PROCESS rather than a result, so it is
   the only label that moves: a slow highlight crosses the word, and the status dot breathes
   with it. Loud enough to say "this is still running" at a glance, quiet enough to sit in a
   list of three rows without pulling the eye off the answer. The highlight is --ink, so it
   inherits the theme instead of introducing a colour, and the base stays the status colour
   (amber for the row, --meta for the chip), the shimmer never restates the status. */
/* The gradient TILES (background-repeat) rather than sliding across once. A no-repeat
   sweep paints nothing once it has travelled past the box, and with background-clip:text
   that reads as the label blinking out of existence, the one thing a status label must
   never do. Both ends of the ramp are currentColor, so the tiles join invisibly and the
   word is fully painted at every frame of the loop. */
.doc-status.is-shimmer .ds-t,
.up-stat.is-shimmer {
  background-image: linear-gradient(
    100deg,
    currentColor 0%,
    currentColor 40%,
    var(--ink) 50%,
    currentColor 60%,
    currentColor 100%
  );
  background-size: 220% 100%;
  background-repeat: repeat;
  -webkit-background-clip: text;
  background-clip: text;
  /* only the GLYPH FILL goes transparent. Setting `color` transparent as well would take
     currentColor, which the ramp above is built from, down with it, and the gradient
     would be transparent-to-white instead of status-colour-to-white. */
  -webkit-text-fill-color: transparent;
  animation: statShimmer 2.2s linear infinite;
}
@keyframes statShimmer {
  from {
    background-position: 220% 0;
  }
  to {
    background-position: 0% 0;
  }
}
.doc-status.is-shimmer .dot {
  animation: statDot 1.6s var(--ease) infinite;
}
@keyframes statDot {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.35;
    transform: scale(0.78);
  }
}
/* motion is the signal, not the message, with motion off the label is still a plain,
   fully-legible status word and the bar is still visibly present */
@media (prefers-reduced-motion: reduce) {
  .doc-status.is-shimmer .ds-t,
  .up-stat.is-shimmer {
    background-image: none;
    -webkit-text-fill-color: currentColor;
    animation: none;
  }
  .doc-status.is-shimmer .dot,
  .is-indexing .up-bar__fill {
    animation: none;
  }
  .is-indexing .up-bar__fill {
    width: 100%;
    opacity: 0.35;
  }
}
.up-err {
  font-size: 11px;
  color: var(--danger);
  flex: none;
  white-space: nowrap;
}
.doc-chip.is-uploading,
.doc-chip.is-indexing {
  max-width: 320px;
}
.doc-chip.is-uploading .nm,
.doc-chip.is-indexing .nm {
  color: var(--ink-2);
}
/* failed = red edge + red glyph, but the file NAME stays legible, you need to read which
   one broke before you can decide to retry it */
.doc-chip.is-failed {
  max-width: 340px;
  border-color: var(--danger);
  background: var(--danger-soft);
}
.doc-chip.is-failed .doc-chip-ic {
  color: var(--danger);
}
.doc-chip-retry svg {
  width: 12px;
  height: 12px;
}
.doc-chip-retry:hover {
  color: var(--ink);
}
.doc-item.is-failed {
  border-color: var(--danger);
  background: var(--danger-soft);
}
.doc-item.is-failed .doc-meta {
  color: var(--danger);
}
.doc-status.err {
  color: var(--danger);
}

/* ---- DUPLICATE: pointing at the copy that is already here ------------------
   A duplicate is rejected like any other bad file (red Failed row, with its reason), but the
   useful half of the message is WHERE the existing copy is. So the row or chip that already
   holds that file pulses once in the module accent, the same "you landed here" language the
   history panel uses when it jumps to a turn. Accent, not red: the thing being pointed at is
   not the thing that went wrong. */
.dupe-flash {
  animation: dupeFlash 1.6s var(--ease);
}
@keyframes dupeFlash {
  0% {
    box-shadow: 0 0 0 0 transparent;
  }
  18% {
    box-shadow:
      0 0 0 2px var(--accent),
      0 10px 30px -14px var(--accent);
  }
  60% {
    box-shadow:
      0 0 0 2px var(--accent),
      0 10px 30px -14px var(--accent);
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
}
@media (prefers-reduced-motion: reduce) {
  .dupe-flash {
    animation: none;
    box-shadow: 0 0 0 2px var(--accent);
  }
}

/* the drop-zone the copy has always promised, now real */
.composer.is-dragover {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.composer.is-dragover .up-area {
  border-color: var(--accent);
  border-style: dashed;
  background: var(--accent-soft);
}
.composer.is-dragover * {
  pointer-events: none;
} /* children must not steal the drop */

/* ---- UPLOAD DOCUMENT MODAL -------------------------------------------------
   Not a new component: the confirmation-dialog shell (.dialog-scrim / .dialog /
   .dialog__ic / .dialog__t / .dialog__m / .dialog__actions) with the home screen's
   drop-zone atoms (.up-area and friends) placed inside it. The modal is a little wider
   than the 400px confirm dialog because a drop target has to look droppable, 400px
   makes the zone read as a button with a border round it. */
.dialog--upload {
  width: min(480px, calc(100vw - 48px));
}
.up-area--dlg {
  margin-top: 16px;
  padding: 22px 20px;
  gap: 8px;
}
.up-area--dlg .up-ic {
  width: 40px;
  height: 40px;
  border-radius: 20px;
}
.up-area--dlg .up-ic svg {
  width: 20px;
  height: 20px;
}
.up-area--dlg .up-drop {
  font-size: 13px;
  text-align: center;
}
.up-area--dlg .up-btn {
  height: 36px;
  font-size: 13px;
}
/* same drag feedback as the composer's zone, one behaviour, drawn on whichever zone
   the file is actually over */
.up-area--dlg.is-dragover {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.up-area--dlg.is-dragover * {
  pointer-events: none;
}
/* the files staged in THIS modal pass, running the same lifecycle rows as the panel behind */
.dlg-files {
  margin-top: 14px;
  max-height: 188px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.doc-item--staged {
  margin-bottom: 8px;
  padding: 10px;
}
.doc-item--staged:last-child {
  margin-bottom: 0;
}
.doc-item--staged .doc-badge {
  width: 34px;
  height: 34px;
  border-radius: 8px;
}

/* ---- tabbed right panel (History | Documents) ---- */
.panel-tabs {
  display: flex;
  gap: 22px;
  border-bottom: 1px solid var(--panel-stroke);
  margin-bottom: 16px;
  flex: none;
}
.panel-tab {
  position: relative;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0 2px 12px;
  font-weight: 500;
  font-size: 15px;
  color: var(--sub);
  display: flex;
  align-items: center;
  gap: 8px;
}
.panel-tab:hover {
  color: var(--ink-2);
}
.panel-tab.sel {
  color: var(--ink);
  font-weight: 600;
}
.panel-tab.sel::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
}
.tab-badge {
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--surface-raised);
  color: var(--sub);
  font-size: 12px;
  font-weight: 600;
  display: grid;
  place-items: center;
}
.panel-tab.sel .tab-badge {
  color: var(--ink);
}
/* Panel foot button, glass fill + glass hairline, 12/18 padding (Figma 4035:29557).
   History lists scroll infinitely and have NO foot; only the Documents tab uses this
   now, for "Upload document". */
.hviewall {
  padding: 12px 18px;
  line-height: 20px;
  border-radius: 12px;
  background: var(--glass);
  border: 1px solid var(--glass);
  color: var(--ink);
  font-weight: 500;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
  flex: none;
  margin-top: 20px;
}
.hviewall:hover {
  background: var(--glass-2);
}
.hviewall svg {
  width: 14px;
  height: 14px;
}

/* ---- document list item ---- */
.doc-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border-radius: 12px;
  background: var(--hcard);
  border: 1px solid var(--hcard-stroke);
  margin-bottom: 10px;
}
.doc-badge {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--sub);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.02em;
}
.doc-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.doc-nm {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.doc-meta {
  font-size: 12px;
  color: var(--meta);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.doc-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 500;
  flex: none;
  white-space: nowrap;
}
.doc-status .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.doc-status.ok {
  color: var(--accent);
}
.doc-status.ing {
  color: var(--warning);
}
.doc-item .more {
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  flex: none;
  display: grid;
  place-items: center;
}
.doc-item .more svg {
  width: 16px;
  height: 16px;
}
.doc-item .more:hover {
  color: var(--ink);
}
.doc-empty {
  font-size: 13px;
  color: var(--meta);
  padding: 12px 4px;
}

/* ---- chat history item (leading icon + N documents + status) ---- */
/* document count under a chat history row, no verification tick (Figma Chat History Item 5080:2883) */
.cwd-hmeta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 500;
  font-size: 12px;
  line-height: 16px;
  color: var(--ricon);
}

/* ---- chat answer: stat trio + requirements coverage table ---- */
.cwd-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.cwd-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
.cwd-stat {
  background: var(--box);
  border: 1px solid var(--box-stroke);
  border-radius: 16px;
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cwd-stat__hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.cwd-stat__hd > span:first-child {
  font-size: 13px;
  font-weight: 500;
  color: var(--sub);
}
.cwd-stat__ic {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ricon);
}
.cwd-stat__ic > svg {
  width: 18px;
  height: 18px;
}
.cwd-stat__v {
  font-weight: 700;
  font-size: 28px;
  line-height: 1.1;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.cwd-stat__s {
  font-size: 13px;
  color: var(--sub);
}
.req-card .wcard__hd > span:first-child {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sub);
}
.req-rows {
  display: flex;
  flex-direction: column;
}
.req-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.req-row:last-child {
  border-bottom: 0;
}
.req-nm {
  flex: 1;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.req-vl {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
}
.req-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 5px 9px;
  border-radius: 6px;
  background: var(--surface-raised);
  color: var(--sub);
  white-space: nowrap;
}
.req-tag.req {
  background: var(--accent);
  color: #fff;
}
@media (max-width: 1000px) {
  .cwd-stats {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
   BUILD DASHBOARD (Cyan module), 3-column workbox
   ============================================================ */
.workbox {
  flex: 1;
  min-height: 0;
  margin-bottom: 20px;
  display: flex;
  background: var(--panel);
  border: 1px solid var(--panel-stroke);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 18px 50px -18px rgba(0, 0, 0, 0.5);
}
/* The soft light at the top of the container, the same gesture the answer card
   and the Data Source shell already use (.ans / .ds-shell): a wide, very low
   opacity wash of the module accent bleeding in from just above the top edge,
   over a body that lifts a point or two towards the bottom.

   Painted as two BACKGROUND LAYERS rather than a ::before overlay on purpose.
   .workbox is a flex container, and a pseudo-element in one is a flex item; the
   absolute positioning that takes it back out would also make .workbox a
   containing block for every absolutely positioned descendant inside the canvas
   (the block resize handles, the drag chrome), which is a layout change nobody
   asked for in exchange for an effect that is purely paint. */
html[data-theme="dark"][data-module="dashboard"] .workbox {
  background:
    /* WIDE and SHORT. The first attempt reused the answer card's 72%-wide ellipse,
       which on a container this wide put the whole wash in the middle and left the
       Conversation header at the far left completely untouched, so the effect read
       as absent exactly where it was being looked for. 105% carries it edge to
       edge; 46% keeps it a band at the TOP rather than a tint over everything. */
    radial-gradient(
      ellipse 105% 46% at 50% -8%,
      var(--glow) 0%,
      var(--glow) 14%,
      transparent 78%
    ),
    linear-gradient(180deg, #171a1e 45%, #1b2126 100%);
}

/* ---- INSIDE the container, every surface is RELATIVE to it -------------------
   The container now has light across its top and none at its bottom, so anything
   painted an ABSOLUTE dark colour reads as a hole punched in the lit part and as
   nothing at all in the dark part. That is one bug with five faces: the container's
   own stroke went black along the top while blending perfectly at the bottom left
   corner, the view toggle and the avatar sat as dark blobs in the lit header, the
   rule under "Conversation" vanished, and the empty slot's dashes did the same.

   All five are fixed the same way, by switching those surfaces from an opaque
   colour to WHITE AT A LOW ALPHA (the design system's own #fff@5% fill / #fff@8%
   stroke treatment). An alpha surface lifts whatever happens to be behind it by a
   fixed amount, so it holds the same relationship to the container at the top, at
   the bottom and anywhere the glow lands in between. */
html[data-theme="dark"][data-module="dashboard"] .workbox {
  border-color: rgba(255, 255, 255, 0.1);
}
/* the rule under the Conversation header, and the one above the composer */
html[data-theme="dark"][data-module="dashboard"] .conv-div {
  background: rgba(255, 255, 255, 0.09);
}
/* the width rule's own hairlines: the horizontal line under the ticks, and each
   tick's own vertical divider. Same reason as .conv-div right above - they were
   still --border, the plain neutral grey every OTHER stroke in this module moved
   off of, so against the teal ambient backdrop they read as a mismatched, slightly
   muddy line while every divider beside them reads clean (Ameer, 22 Aug). Same
   value as .conv-div: one language for "a hairline" across this whole panel. */
/* QUIETER STILL: .09 kept reading as a visible grey stripe across the ambient
   gradient rather than picking it up (Ameer, 22 Aug, second pass: "still a gray
   colour, not nice, should blend"). .06 matches --border-subtle, the same value
   .crule-bg's own edge and the empty-canvas dashed slot already settle for as
   "barely there". */
html[data-theme="dark"][data-module="dashboard"] .crule__line {
  background: rgba(255, 255, 255, 0.06);
}
/* THE LIT RUN TOO. Only the hairline and the ticks were moved off the neutral grey
   ramp last time; the run from 0 to the page edge kept --border-strong, an OPAQUE zinc,
   and that is the stripe still reading grey across the teal ambient (Ameer, 24 Aug).
   Same white-alpha language as everything else on this strip, and the same .14 the
   ticks inside the page already use - the two lines that meet at every mark now agree. */
html[data-theme="dark"][data-module="dashboard"] .crule__lit {
  background: rgba(255, 255, 255, 0.14);
}
html[data-theme="dark"][data-module="dashboard"] .crule__t {
  border-inline-start-color: rgba(255, 255, 255, 0.06);
}
html[data-theme="dark"][data-module="dashboard"] .crule__t.is-on {
  border-inline-start-color: rgba(255, 255, 255, 0.14);
}
/* List / Dashboard view switcher */
html[data-theme="dark"][data-module="dashboard"] .vtoggle {
  background: var(--glass);
  border-color: rgba(255, 255, 255, 0.08);
}
html[data-theme="dark"][data-module="dashboard"] .vt.sel {
  background: rgba(255, 255, 255, 0.13);
}
/* LIGHT: the dark side is a white glass over the ambient, so the light side has to
   be a BLACK glass over it - not the opaque zinc ramp it was using. --pill #f4f4f5
   on a near-white panel with --pill-hover #e4e4e7 selected put two greys within 16
   points of each other and of the surface behind them, so the whole control read as
   one muddy blob (Ameer, 20 Aug).
   Segmented controls in light UI work the other way round: a faint tinted TRACK with
   the selected segment RAISED in white. The track tints whatever it sits on, so it
   blends; the white pill lifts, so the selection is unmistakable. */
html[data-theme="light"] .vtoggle {
  background: rgba(0, 0, 0, 0.045);
  border-color: rgba(0, 0, 0, 0.07);
}
html[data-theme="light"] .vt.sel {
  background: #fff;
  color: var(--ink);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.1),
    0 0 0 1px rgba(0, 0, 0, 0.05);
}
html[data-theme="light"] .vt:not(.sel):hover {
  background: rgba(0, 0, 0, 0.04);
  color: var(--ink);
}

/* the "AM" avatar on the user's own message */
/* the empty add-slot on the canvas: silent at rest now (see .cblock--empty in
   the section below), so this only ever needs to colour the HOVER state, not
   the permanent dashed border it used to be borrowed for. */
html[data-theme="dark"][data-module="dashboard"] .cblock--empty:hover {
  border-color: rgba(255, 255, 255, 0.24);
}
/* the vertical rule between the conversation and the canvas, and its grip. Same
   fix again: an opaque dark line disappears into the lit top of the container, so
   the divider faded out exactly where the two panels are furthest apart and most
   need separating. */
html[data-theme="dark"][data-module="dashboard"] .splitter::before {
  background: rgba(255, 255, 255, 0.09);
}
html[data-theme="dark"][data-module="dashboard"] .grip {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.1);
}

/* ---- Conversation panel ---- */
.conv-panel {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.conv-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  padding: 0 12px;
  padding-inline-start: 20px;
}
.conv-head__r {
  display: flex;
  align-items: center;
  gap: 6px;
}
/* Sized and styled like the .ts view buttons it sits beside, so the header reads as
   ONE row of controls. It briefly carried the history header's `x` class, which is
   scoped to .hh - so it matched nothing and the browser painted the default control
   background, which is the grey "burger" look. There is no global button reset in
   this codebase: a bare <button> always needs its own rule. */
.conv-collapse {
  flex: none;
  width: 30px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 7px;
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  transition: 0.12s;
}
.conv-collapse svg {
  width: 16px;
  height: 16px;
}
.conv-collapse:hover {
  background: var(--glass);
  color: var(--ink);
}

/* ---- CONVERSATION COLLAPSED --------------------------------------------------
   A tail, deliberately the SAME shape and behaviour as .history.collapsed on the
   other side of the workbox: 44px, the panel's own icon plus a chevron pointing the
   way it will open. One collapse language across all three panels, mirrored, so the
   gesture reads the same whichever edge you are on. */
/* ONE control, sitting on the header's own line. Two icons stacked in a column read
   as a two-row toolbar in a 44px gutter - a lot of furniture for "open this again".
   The button lines up with the .conv-head band it replaces, so collapsing does not
   move the control up or down the screen. */
.conv-panel--collapsed {
  flex: 0 0 44px;
  width: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: 14px;
  border-inline-end: 1px solid var(--panel-stroke);
}
.conv-expand {
  width: 30px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 7px;
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  transition: 0.12s;
}
.conv-expand svg {
  width: 18px;
  height: 18px;
}
.conv-expand:hover {
  background: var(--glass);
  color: var(--ink);
}
/* both glyphs point at the panel they act on, so they mirror in Arabic */
[dir="rtl"] .conv-expand svg,
[dir="rtl"] .conv-collapse svg {
  transform: scaleX(-1);
}
.conv-head__t {
  font-size: 18px;
  font-weight: 600;
  color: var(--ink);
}
/* Runs the last 8px INTO the splitter so it meets the vertical seam instead of
   stopping at the panel's edge. Measured: the panel ends at the splitter's outer
   edge, the seam is drawn at the splitter's centre (left:50% of 16px), so the two
   lines were 8px apart and every divider read as broken where it met the grip.
   Logical margin, so RTL mirrors without a second rule. */
.conv-div {
  height: 1px;
  background: var(--panel-stroke);
  flex: none;
  margin-inline-end: -9px;
}
.vtoggle {
  display: flex;
  background: var(--pill);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
}
.vt {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 12px;
  border: 0;
  background: none;
  color: var(--sub);
  font-size: 13px;
  font-weight: 500;
  border-radius: 7px;
  cursor: pointer;
  transition: 0.12s;
}
.vt svg {
  width: 15px;
  height: 15px;
}
.vt.sel {
  background: var(--pill-hover);
  color: var(--ink);
}
/* Icon-only variant. The label moved into the hover tooltip (.ts-tip, borrowed from the
   tile view switcher) so the pair costs ~66px instead of ~190px, which is what makes it
   sit comfortably in the 370px default panel. position:relative anchors that tooltip. */
.vt--ico {
  position: relative;
  width: 32px;
  padding: 0;
  justify-content: center;
  gap: 0;
}
.vt--ico svg {
  width: 16px;
  height: 16px;
}
.vt--ico:hover {
  color: var(--ink);
}
/* Tooltip hangs BELOW, not above. This toggle sits in the first 56px of .workbox,
   which is overflow:hidden, so the default upward .ts-tip was sliced in half by the
   panel's top edge. */
.vt--ico .ts-tip {
  bottom: auto;
  top: calc(100% + 8px);
  transform: translateX(-50%) translateY(-4px);
}
.vt--ico .ts-tip::after {
  top: auto;
  bottom: 100%;
  transform: translateX(-50%) rotate(45deg) translateY(3px);
  border: 0;
  border-left: 1px solid var(--panel-stroke);
  border-top: 1px solid var(--panel-stroke);
}
.vt--ico:hover .ts-tip {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
/* ---- ONE 20px GAP ON ALL FOUR SIDES, WHICH IS 12 IN THE CSS ON THE TRAILING
   EDGE. READ THIS BEFORE "FIXING" THE 12. ----
   The visible boundary on the trailing side is NOT this column's box edge. It is
   the splitter's hairline, and the splitter is a 16px column with its line drawn
   at left:50%, so the seam sits 8px BEYOND where this padding stops. Setting the
   trailing padding to 20 to "make it symmetrical" therefore renders 28, and it
   reads instantly as a fatter gap on that side. It has been got wrong in both
   directions now, so the numbers are: leading 20, trailing 12, and 12 + 8 = 20
   to the line you can actually see. Measure to the SEAM, never to the box.

   Logical properties, because in Arabic the panel and its splitter swap sides and
   the 8px allowance has to travel with the splitter.

   The head, the cards and the composer all share these two edges, so the composer
   lines up exactly with the cards above it. Vertically the same 20: the body's
   bottom padding is 0 and the dock's own 20 supplies the gap above the composer,
   so the two do not stack into 22. */
.conv-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 20px 12px 0;
  padding-inline-start: 20px;
}
.conv-dock {
  flex: none;
  padding: 20px 12px;
  padding-inline-start: 20px;
}
.conv-dock .composer {
  border-radius: 18px;
  padding: 14px 16px;
  gap: 14px;
  box-shadow: none;
}
.conv-dock .composer textarea {
  font-size: 15px;
}
/* compact composer: shrink controls so they hold one line in the ~468px panel */
.conv-dock .cc-l,
.conv-dock .cc-r {
  gap: 6px;
}
.conv-dock .cbtn {
  width: 36px;
  height: 36px;
}
.conv-dock .cbtn svg {
  width: 18px;
  height: 18px;
}
.conv-dock .cchip {
  height: 36px;
  padding: 0 10px;
  padding-inline-start: 12px;
  font-size: 13px;
  white-space: nowrap;
  flex: none;
}
.conv-dock .cchip svg {
  width: 16px;
  height: 16px;
}
.conv-dock .send {
  width: 36px;
  height: 36px;
}
.conv-dock .send svg {
  width: 18px;
  height: 18px;
}

/* ---- THE QUESTION IS A BUBBLE ------------------------------------------------
   It used to be bare right-aligned text, which put it in the same visual class as
   every label and caption in the panel, so scrolling a built dashboard the eye
   slid straight past the one line that says what was ASKED. A conversation panel
   has exactly two kinds of thing in it, a question and what came back, and only
   one of them was drawn as a thing.

   So the question gets a surface: the same white-at-low-alpha treatment the rest
   of the panel uses, which keeps it blending with the container's gradient, plus
   the asymmetric corner that reads as "spoken by the person on this side".
   Deliberately NOT the accent: the accent means pinned, live and selected here,
   and a question is none of those.

   The text goes back to reading from the start edge. Right-aligned text inside a
   bubble is a ragged left edge for no reason; the bubble itself is what puts the
   line on the user's side. */
/* the trailing avatar was removed (see convUserMsgHTML); the bubble is the only child now */
.umsg {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 22px;
}
.umsg__wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  max-width: 82%;
}
.umsg__b {
  color: var(--ink);
  font-size: 15px;
  line-height: 21px;
  text-align: start;
  padding: 10px 14px;
  border-radius: 16px;
  /* tokens, not raw white alphas: the hardcoded rgba(255,255,255,…) pair was
     invisible on the light theme's white panel, so the bubble vanished and the
     question read as loose text. --glass / --line-strong already carry the
     black-alpha equivalents for light. */
  background: var(--glass);
  border: 1px solid var(--line-strong);
}
/* the corner nearest the avatar, logical so it mirrors itself in Arabic */
.umsg__b {
  border-end-end-radius: 6px;
}
.umsg__t {
  font-size: 12px;
  color: var(--meta);
  padding-inline-end: 2px;
}
/* ---- THE HOVER ROW (Claude.ai's own pattern) --------------------------------
   At rest: just the bubble. On hover: the timestamp plus Copy and Edit appear
   together underneath it, on the same trailing edge the bubble itself hugs, so
   the row reads as one more line of the same message rather than a toolbar
   bolted beside it. Kept in flow (not absolutely positioned) so the row before
   it never has to reserve dead space for a corner nobody is pointing at yet. */
.umsg__acts {
  display: flex;
  align-items: center;
  gap: 2px;
  padding-inline-end: 2px;
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.umsg:hover .umsg__acts,
.umsg:focus-within .umsg__acts {
  opacity: 1;
}
.umsg__acts .umsg__t {
  padding-inline-start: 6px;
} /* time is now LAST: space it off the icons before it */
.umsg__ac {
  flex: none;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--ricon);
  cursor: pointer;
  transition:
    background 0.12s var(--ease),
    color 0.12s var(--ease);
}
.umsg__ac svg {
  width: 13px;
  height: 13px;
}
.umsg__ac:hover {
  background: var(--glass);
  color: var(--ink);
}
@media (prefers-reduced-motion: reduce) {
  .umsg__acts {
    transition: none;
  }
}
/* ---- EDIT MODE: the bubble becomes the field ---------------------------------
   Same width contract as the bubble it replaces (max-width:82% on the wrap), same
   rounded shape, so editing does not resize the message or shift the update/cancel
   controls to a different line than the text they act on. */
.umsg--edit .umsg__wrap {
  width: 100%;
}
.umsg__in {
  width: 100%;
  min-width: 0;
  resize: none;
  overflow: hidden;
  box-sizing: border-box;
  font-family: inherit;
  font-size: 15px;
  line-height: 21px;
  color: var(--ink);
  text-align: start;
  background: var(--glass-2);
  border: 1px solid var(--accent);
  border-radius: 16px;
  padding: 10px 14px;
  outline: none;
}
.umsg__editacts {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ---- the answer set's header ----
   A box, one per set, holding what came back and what you can do with it. Same
   surface language as everything else in this panel: a low-alpha fill over the
   container behind it, so it reads as part of the conversation rather than as a
   toolbar dropped on top of it. */
.set-hd {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 8px 0 14px;
  padding: 7px 7px 7px 14px;
  border-radius: 12px;
  background: var(--glass);
  border: 1px solid var(--line);
}
.set-hd__n {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}
/* The two icon buttons belong with the ACTION, not with the count. Sitting beside
   "5 answers" they read as a label decoration; pushed to the end they read as the
   quieter siblings of Pin all, which is what they are. The auto margin moves
   here, off .pinall, so the whole action cluster travels as one. */
.set-hd__acts {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-inline-start: auto;
}
.set-hd .pinall {
  margin-inline-start: 0;
}
.set-hd .pop {
  display: flex;
}
.set-hd__ic {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid var(--line);
  color: var(--ricon);
  cursor: pointer;
  transition:
    background 0.12s var(--ease),
    color 0.12s var(--ease);
}
.set-hd__ic:hover {
  background: var(--glass-2);
  color: var(--ink);
}
.set-hd__ic svg {
  width: 15px;
  height: 15px;
}
.set-hd__ic:disabled {
  cursor: default;
  opacity: 0.7;
}
/* THE REGEN CYCLE showing on the button itself: the icon spins for as long as
   state.dashRegenTurn names this set, so the same corner that started the run
   also says it is still running. */
.set-hd__ic.is-spinning svg {
  animation: regenSpin 0.9s linear infinite;
}
@keyframes regenSpin {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .set-hd__ic.is-spinning svg {
    animation: none;
  }
}
.pinall {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 14px;
  border-radius: 9px;
  background: var(--accent);
  color: #fff;
  border: 0;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: 0.12s;
}
.pinall svg {
  width: 14px;
  height: 14px;
}
.pinall:hover {
  background: var(--accent-hover);
}

.ans-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* the whole set, regenerating: the SAME card shell every answer tile already
   sits in (.ansitem/.anstile's own background+border+radius), holding the .ll
   loader a brand-new answer runs - without the shell this floated as bare text
   with no left inset, edge to edge, reading as broken rather than as a card
   that is loading (Ameer, 22 Aug). */
.regen-row {
  background: var(--hcard);
  border: 1px solid var(--hcard-stroke);
  border-radius: 14px;
  padding: 16px 16px 18px;
}
.regen-row .ll {
  margin-top: 0;
}
/* a SINGLE tile, regenerating: keep its own card shell and footprint (same
   background/border/radius/min-height as the real tile) so the layout does not
   jump, just swap its insides for the loader */
.anstile.is-regen,
.ansitem.is-regen {
  display: flex;
  align-items: center;
  min-height: 120px;
  padding: 16px;
}
.regen-row--tile {
  width: 100%;
}
.regen-row--tile .ll {
  margin-top: 0;
}
/* each conversation turn (question + its answers); a hairline separates refinements */
.conv-turn {
  border-radius: 12px;
}
.conv-turn + .conv-turn {
  margin-top: 22px;
  padding-top: 22px;
  border-top: 1px solid var(--hair);
}
/* Icon only, and sized to the .ts view buttons it shares the footer with, so the
   two read as one row of controls rather than a labelled button beside some icons.
   Pinned keeps the accent FILL - this is the one on/off state on the card and it
   has to be readable at a glance across a scrolling panel - but the label lives in
   the tooltip now. */
.pin-btn {
  position: relative;
  width: 30px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 7px;
  background: var(--glass);
  border: 1px solid var(--line);
  color: var(--accent);
  cursor: pointer;
  transition: 0.12s;
}
.pin-btn svg {
  width: 15px;
  height: 15px;
}
.pin-btn:hover {
  background: var(--glass-2);
}
.pin-btn.pinned {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
}
.pin-btn.pinned:hover {
  background: var(--accent-hover);
}
.pin-btn:hover .ts-tip {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.icob {
  width: 28px;
  height: 28px;
  border-radius: 7px;
  background: none;
  border: 0;
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.icob svg {
  width: 16px;
  height: 16px;
}
.icob:hover {
  background: var(--glass);
  color: var(--ink);
}

/* List view answer item */
.ansitem {
  background: var(--hcard);
  border: 1px solid var(--hcard-stroke);
  border-radius: 14px;
  padding: 16px 16px 14px;
  transition: border-color 0.12s;
}
.ansitem:hover,
.anstile:hover {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-border);
}
.ansitem__hd {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.ansitem__badge {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--pill);
  color: var(--sub);
  display: grid;
  place-items: center;
  flex: none;
}
.ansitem__badge svg {
  width: 16px;
  height: 16px;
}
.ansitem__t {
  flex: 1;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  line-height: 19px;
  padding-top: 4px;
}
/* ONE rule for the answer card's ⋯ in both views. The grid tile's used to be the
   generic widget ⋮ and had no styling of its own once it became a real button, so
   the browser painted it with the default control background - the grey box. */
.ansitem__more,
.anstile__more {
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  flex: none;
  display: grid;
  place-items: center;
  padding: 4px;
  border-radius: 7px;
}
.ansitem__more svg,
.anstile__more svg {
  width: 16px;
  height: 16px;
}
.ansitem__more:hover,
.anstile__more:hover {
  background: var(--glass);
  color: var(--ink);
}
.ansitem__d {
  font-size: 13px;
  color: var(--sub);
  line-height: 19px;
  margin: 8px 0 14px;
}
.ansitem__ft {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Dashboard view answer tile */
.anstile {
  background: var(--hcard);
  border: 1px solid var(--hcard-stroke);
  border-radius: 14px;
  padding: 16px;
}
.anstile__hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}
.anstile__t {
  font-size: 13px;
  color: var(--sub);
  font-weight: 600;
}
.anstile__div {
  height: 1px;
  background: var(--hcard-stroke);
  margin: 14px 0 12px;
}
.anstile__ft {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.tswitch {
  display: flex;
  gap: 4px;
}
/* the same control on a canvas block, in its own footer under a hairline, so the
   block reads as widget-then-controls exactly like the conversation card does */
.cblock__div {
  height: 1px;
  background: var(--box-stroke);
  flex: none;
  margin-top: auto;
}
.cblock__ft {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex: none;
}
/* the drag knob and the move pill sit at the block's top edge, so a switcher in the
   footer never collides with them; the resize handles are on the border itself. */
.cblock__ft .ts {
  position: relative;
}

/* ---- BAR CHART (magnitude) --------------------------------------------------
   Horizontal, because every categorical axis here has long names. One row per
   category: name, track, value. Single series, so no legend and no colour-coding
   by rank - the accent hue carries the whole series, and where the data already
   owns a status colour (the risk bands) the bar keeps it, so identity survives a
   view switch. Value labels are ink tokens, never the series colour. */
.tbars {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}
.tbar {
  display: grid;
  grid-template-columns: minmax(72px, 34%) 1fr auto;
  align-items: center;
  gap: 12px;
}
.tbar__l {
  font-size: 12.5px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tbar__t {
  position: relative;
  height: 10px;
  border-radius: 999px;
  background: var(--glass);
  overflow: hidden;
}
.tbar__f {
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  bottom: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.3s var(--ease);
}
.tbar__v {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.ts {
  position: relative;
  width: 30px;
  height: 28px;
  border-radius: 7px;
  background: none;
  border: 0;
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.ts svg {
  width: 15px;
  height: 15px;
}
.ts:hover {
  background: var(--glass);
  color: var(--ink);
}
.ts.sel {
  background: var(--glass-2);
  color: var(--ink);
}
/* switcher tooltip (what kind of widget) */
.ts-tip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--menu-bg);
  border: 1px solid var(--panel-stroke);
  color: var(--ink);
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  padding: 6px 9px;
  border-radius: 8px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition:
    opacity 0.14s var(--ease),
    transform 0.14s var(--ease);
  z-index: 40;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.4);
}
.ts-tip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%) rotate(45deg) translateY(-3px);
  width: 7px;
  height: 7px;
  background: var(--menu-bg);
  border-right: 1px solid var(--panel-stroke);
  border-bottom: 1px solid var(--panel-stroke);
}
.ts:hover .ts-tip {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
/* tile widgets scoped small; donut hole matches tile bg */
.anstile__wd {
  min-height: 132px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.anstile__wd .donut::after {
  background: var(--hcard);
}
.anstile__wd--pie .donut-wrap {
  gap: 20px;
}
.anstile__wd--pie .donut {
  width: 100px;
  height: 100px;
}
.anstile__wd--pie .donut::after {
  inset: 16px;
}
.anstile__wd--pie .donut-leg {
  font-size: 13px;
}
.anstile__wd--table {
  min-height: 0;
}
.anstile__wd--table .p-table {
  font-size: 13px;
}
.anstile__wd--table .p-table th {
  padding: 0 8px 8px;
}
.anstile__wd--table .p-table td {
  padding: 9px 8px;
}
.anstile__wd--table .rec {
  gap: 12px;
  display: flex;
  flex-direction: column;
}
.tchart {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.tchart svg {
  width: 100%;
  height: 118px;
  display: block;
}
.tchart__x {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--meta);
}
.dstat__v {
  font-size: 30px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.1;
}
.dstat__s {
  font-size: 13px;
  color: var(--sub);
  margin-top: 7px;
}
.darea__x {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--meta);
}
.drank {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.drank__row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 2px;
}
.drank__n {
  width: 14px;
  flex: none;
  text-align: center;
  color: var(--sub);
  font-size: 13px;
}
.drank__nm {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.drank__nm b {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.drank__nm span {
  font-size: 12px;
  color: var(--sub);
}
.drank__sc {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
}
.rtag {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 3px 7px;
  border-radius: 6px;
  flex: none;
}
.rtag--crit {
  background: var(--danger-soft);
  color: var(--danger);
}
.rtag--high {
  background: var(--warning-soft);
  color: var(--warning);
}
.dcrisk {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.dcrisk__top {
  display: flex;
  gap: 24px;
  align-items: center;
}
.dcrisk__total {
  display: flex;
  flex-direction: column;
  flex: none;
}
.dcrisk__total b {
  font-size: 30px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1;
}
.dcrisk__total span {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--sub);
  margin-top: 5px;
}
.dcrisk__legend {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.dcrisk__row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
}
.dcrisk__row .d {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
}
/* A legend row is a label and its figure, and it means nothing split over two
   lines: "Low" above "Risk", or a percentage divorced from its count. The row
   never wraps, and the widget's minW in WIDGET_SIZE is set so it never has to. */
.dcrisk__row .nm {
  color: var(--ink-2);
  white-space: nowrap;
}
.dcrisk__row .vl {
  margin-inline-start: auto;
  color: var(--ink);
  font-weight: 500;
  white-space: nowrap;
}
.dcrisk__insight {
  font-size: 13px;
  color: var(--sub);
  line-height: 19px;
}

/* ---- Splitter ---- */
.splitter {
  flex: 0 0 16px;
  position: relative;
  cursor: col-resize;
  display: grid;
  place-items: center;
  touch-action: none;
}
.splitter::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: var(--panel-stroke);
}
.grip {
  position: relative;
  z-index: 1;
  width: 16px;
  height: 54px;
  border-radius: 8px;
  background: var(--pill-hover);
  border: 1px solid var(--line-strong);
  display: grid;
  place-items: center;
  color: var(--sub);
  transition: 0.12s;
}
/* An OPAQUE underlay, so the seam stops at the pill instead of running through it.
   z-index:1 alone was not enough: the grip's own fill is a translucent glass in the
   dashboard palette, so the line behind it simply showed through and the divider
   looked like it was drawn on top of the handle. This paints the workbox's own
   surface underneath that glass - no visual change to the pill, it is the colour it
   was already compositing against - and z-index:-1 keeps it inside the grip's
   stacking context, which is already above .splitter::before. */
.grip::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--panel);
  z-index: -1;
}
.grip svg {
  width: 14px;
  height: 14px;
}
.splitter:hover .grip {
  color: var(--ink);
  border-color: var(--border-strong);
}

/* ---- Canvas ---- */
.canvas {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  position: relative;
}
/* padding-top moves OFF the container and onto the grid, so .canvas-head can own a
   fixed 56px band that lines up with .conv-head across the seam (see below) */
.canvas-content {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  --cpad: 24px;
  padding: 0 var(--cpad) var(--cpad);
}
.canvas-zoom > .canvas-grid {
  padding-top: 20px;
}
/* Same 56px band as .conv-head, so the dashboard name, Play, Export and undo/redo
   share a centre line with "Conversation" and the list/grid toggle. They used to
   sit 12px lower: the head was content-height (36px) inside a 22px top padding,
   which centred it at 135 against the conversation's 123. Measured, not guessed. */
/* its own horizontal inset now that it no longer sits inside .canvas-content's padding */
.canvas-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  flex: none;
  margin-bottom: 0;
  padding: 0 24px;
}
/* ...and the rule under it reaches back to the seam. The canvas starts 8px to the
   RIGHT of the splitter's hairline (the splitter is a 16px column with its line
   down the middle), so a divider that merely filled the canvas stopped 8px short
   and read as a broken line rather than one that runs across both panels. */
.canvas > .conv-div {
  margin-inline-start: -8px;
}
/* ...but only the START side. The -9px END margin on the base rule exists to reach
   the SPLITTER, which lives to the conversation panel's right; on the canvas that
   same edge is the workbox's own outer edge, so the divider was poking 8px out past
   the rounded corner into the page. Reset it here, where there is nothing to bridge. */
.canvas > .conv-div {
  margin-inline-end: 0;
}
/* ...and there is no seam to reach back to when the conversation is COLLAPSED: the
   splitter is not rendered at all in that state, so the canvas begins hard against
   the 44px rail's hairline. The -8px then dragged the rule 8px INTO the rail, laying
   a horizontal line across its vertical border and over the workbox's corner inset.
   Nothing to bridge, so no bridge. */
.workbox:has(.conv-panel--collapsed) .canvas > .conv-div {
  margin-inline-start: 0;
}
/* min-height so the row keeps its height while the dashboard is still unnamed.
   Without it the whole canvas head shrank during the build and grew back at
   settle, which is exactly the movement the in-place settle exists to avoid. */
/* The board's name is the twin of "Conversation" across the divider from it: same
   size, same weight, same ink. It used to be a 12px uppercase eyebrow, which read
   as a section label rather than as the title of the thing you are looking at, and
   left the two halves of the workbox titled in two different voices. */
.canvas-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  flex: 0 1 auto;
  min-height: 30px;
  font-size: 18px;
  color: var(--ink);
  font-weight: 600;
}
.canvas-eyebrow .verify {
  display: flex;
  color: var(--success);
}
.canvas-eyebrow .verify svg {
  width: 16px;
  height: 16px;
}

/* ---- the dashboard's name, click to rename ----
   Rendered as a real <button> in both places, so it is keyboard reachable and
   announces itself; it just does not LOOK like a control until you go near it.
   The name is auto-generated from the prompt, so it has to be obviously
   editable without shouting. */
.dash-name {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  background: none;
  border: 1px solid transparent;
  border-radius: 8px;
  /* a ch cap, NOT a percentage: `100%` here resolves against a parent that is itself
     sized by this button, and the browser collapsed it to a stub - "Fraud Ops Board"
     rendered as "FRAUD OPS B…" on a canvas with 900px to spare. */
  padding: 4px 8px;
  margin: -4px -8px;
  max-width: 44ch;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: start;
  cursor: text;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease),
    color 0.15s var(--ease);
}
.dash-name:hover {
  background: var(--glass);
  border-color: var(--line-strong);
  color: var(--ink);
}
.dash-name:focus-visible {
  outline: 2px solid var(--accent-border);
  outline-offset: 1px;
}
/* the field inherits the type it replaces, so the name does not jump size or
   weight the moment it becomes editable */
.dash-rename {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--ink);
  background: var(--glass);
  border: 1px solid var(--border-hover);
  border-radius: 8px;
  padding: 4px 8px;
  margin: -4px -8px;
  max-width: 44ch;
  width: 26ch;
  outline: none;
}
.dash-rename:focus {
  border-color: var(--accent-border);
}
.dash-rename--bc {
  width: 32ch;
}
.canvas-tools {
  display: flex;
  gap: 8px;
}
.ctool {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: var(--glass);
  border: 1px solid var(--line);
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: 0.12s;
}
.ctool svg {
  width: 17px;
  height: 17px;
}
.ctool:hover {
  background: var(--glass-2);
  color: var(--ink);
}
.ctool--pri {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
}
.ctool--pri:hover {
  background: var(--accent-hover);
  color: #fff;
}
.ctool:disabled {
  opacity: 0.35;
  cursor: default;
  pointer-events: none;
}
/* spacer that separates the [add · present · export] group from [undo · redo] (~33px) */
.ctool-sep {
  width: 1px;
  height: 22px;
  background: var(--line-strong);
  margin: 0 16px;
  flex: none;
  align-self: center;
}
/* ---- CANVAS ZOOM ------------------------------------------------------------
   Same control and the same numbers as the schema graph's zoom, so "make this
   bigger" behaves identically in both places. The percentage IS the reset button -
   clicking it goes back to fit, which is where a value that is already showing you
   the current state naturally belongs. */
.czoom {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: none;
}
.ctool--sm {
  width: 30px;
  height: 30px;
  border-radius: 8px;
}
.ctool--sm svg {
  width: 15px;
  height: 15px;
}
.czoom__v {
  min-width: 46px;
  height: 30px;
  padding: 0 6px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--sub);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: 0.12s;
}
.czoom__v:hover {
  background: var(--glass);
  color: var(--ink);
}
.czoom__v.is-off {
  color: var(--ink);
} /* not at 100%: the value is worth reading */

/* The grid lays out at the INVERSE width and is then scaled, so the cards genuinely
   re-flow at the new size - more per row when you zoom out - instead of the board
   being blown up as a flat picture. */
/* The zoom layer scales the BOARD. No compensating width: that is what used to make
   the cards re-lay-out instead of simply getting smaller. position:relative because
   a card being dragged is positioned inside this layer's coordinates. */
.canvas-content {
  --dz: 1;
  overflow: auto;
  position: relative;
}
.canvas-content {
  --rule-h: 46px;
}
/* OUT OF FLOW and always the full canvas width, so the cards inside lay out against
   the same width at every zoom level. Only the paint scales. */
/* An absolutely positioned child is laid out against the PADDING box, so width:100%
   was the canvas plus its own 24px insets: the board came out 48px too wide, sat
   24px too far left, and every card's column width was measured off that. It has to
   be told the padding explicitly. */
/* THE PAGE IS A FIXED WIDTH, not the panel's. --dboard is set per render and
   patched live while the grip is dragged; the fallback keeps every other surface
   that reuses this layer (the saved-dashboard view, the presentation) on the old
   fill-the-panel behaviour. Everything else follows from this one value, because
   boardWidth() is measured off .canvas-grid inside it. */
/* --cx (0 by default: the old, always-left behaviour) is the extra push past
   --cpad that centres the board when the panel has room to spare - see
   applyDashCenter() in app.js, which is the only thing that ever sets it. */
.canvas-zoom {
  position: absolute;
  top: var(--rule-h);
  inset-inline-start: calc(var(--cpad) + var(--cx, 0px));
  width: var(--dboard, calc(100% - var(--cpad) * 2));
  transform: scale(var(--dz, 1));
  transform-origin: top left;
}
/* NO TRANSITION BY DEFAULT. See setDashZoom(): the ease is armed for the zoom
   buttons and nothing else, because every other path that touches this element is a
   re-render, and a re-render animating from scale(1) to the current scale is the
   jump. The fallback in scale(var(--dz,1)) is belt and braces for the same bug: if
   --dz ever went missing again the board would sit still rather than leap. */
.canvas-content.is-zooming .canvas-zoom {
  transition: transform 0.16s var(--ease);
}
[dir="rtl"] .canvas-zoom {
  transform-origin: top right;
}
.canvas-sizer {
  pointer-events: none;
}
/* the empty state wants the dashed slot to fill the panel, which needs the board
   back in flow with a height to fill */
/* EMPTY IS NOT THE PAGE. With something pinned, this layer IS the page: --dboard wide
   and scaled, because every card's column is measured off it. With nothing pinned there
   is no page to measure - only a message - so the layer drops both and becomes the
   COLUMN: width:auto is the visible pane, and the message centres in that at any panel
   width and any zoom. The rule above still marks 1200; the page width is a promise about
   what you are about to build, not about where this sentence sits (Ameer, 24 Aug).

   sticky + start:0 because .canvas-content also scrolls HORIZONTALLY - the rule always
   runs two ticks past the page, so at a narrow panel there is scroll to the right even
   with nothing on the board. Without this the message would slide out of view while the
   ticks travelled; pinned to the scrollport it stays centred in the column it describes. */
.canvas-content.is-empty .canvas-zoom {
  position: sticky;
  inset-inline-start: 0;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  height: auto;
  width: auto;
  transform: none;
}
.canvas-content.is-empty .canvas-sizer {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .canvas-content.is-zooming .canvas-zoom {
    transition: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .canvas-content > .canvas-grid {
    transition: none;
  }
}

/* TWELVE FLUID COLUMNS ON A FIXED ROW UNIT. It was a wrapping flex row, and a
   flex line gives every card in it the height of the tallest, so raising one
   card raised its neighbours and one tall card beside two short ones was not
   expressible. A card now states how many columns and rows it occupies and owns
   that area alone. The three numbers here are mirrored in js/widgets.js
   (GRID_COLS / GRID_GAP / GRID_ROW); the resize drag divides by them to turn
   pointer distance into columns and rows, so they have to agree. */
/* row-gap wider than column-gap on purpose (Ameer, 22 Aug: "any vertical gaps can
   be little bit increased"): more room between stacked rows, same density across
   a row. Must match GRID_GAP / GRID_ROW_GAP in js/widgets.js. */
.canvas-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: 10px;
  row-gap: 18px;
  column-gap: 14px;
  align-content: flex-start;
}
/* AT OR ABOVE THE BASE PAGE the track stops being a share of the page and becomes a
   fixed size, so a widget keeps the width it was built at and the page gains more
   columns instead of stretching the ones it has. auto-fill, not repeat(12): the
   extra columns have to exist for anything to be arranged into them. Below the
   base the rule above stands and the board is fluid, which is what makes an 800px
   page a tablet layout rather than a clipped desktop one. */
.canvas-grid.is-fixedcol {
  grid-template-columns: repeat(auto-fill, var(--dcol, 87.167px));
}
/* An empty canvas fills its column instead of floating a short dashed box at the top
   over a large void. Only when empty: the normal grid must stay top-aligned so blocks
   do not stretch to the viewport height. */
.canvas-content.is-empty {
  display: flex;
  flex-direction: column;
}
/* display:flex, not the twelve-column grid. .canvas-grid became a real CSS grid, and
   the dashed slot is a plain child of it, so it was being laid out as ONE grid item:
   a 1/12-wide strip with the copy broken a word per line and the button spilling out
   of it. When empty the container is a flex column again, which is what .canvas-empty's
   flex:1 1 100% (width) and align-content:stretch (height) were always written for. */
.canvas-grid--empty {
  display: flex;
  flex: 1;
  min-height: 0;
  align-content: stretch;
}
/* ============================================================
   SECTIONS: the board's own headings
   ============================================================ */
/* FULL WIDTH IS THE WHOLE RULE. 1 / -1 makes the band take every column, which
   forces its own row break, so two bands can never end up side by side and the
   widgets after one are always the ones under it. The layout enforces the model
   instead of a handler having to. */
/* column flex, bottom-anchored: the divider and the text sit at the FOOT of the
   row-span the band was given, so the empty room it bought (see below) appears
   ABOVE the divider, reading as "this much board ends here, then a new section
   starts" rather than as a hole under a heading. */
.csec {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-width: 0;
}
/* A HEADING GETS AIR ABOVE IT, a subheading gets less, and EITHER gets a further
   row if it carries a description line (.csec--has-sub). The row span is what
   buys that air (grid-auto-rows is 10px, the row gap is 18, so N rows =
   28N-18px). */
/* Spans sized to CONTENT, not to "some air": each is (top margin + the 1px line +
   bottom margin + heading, and the subheading's line if it has one) rounded up to
   the nearest row. Getting this wrong is what put ~80px above the divider and
   ~15px below it (Ameer, 22 Aug: "lot of gap between the last cards and the
   divider and less gap between the section heading and the divider") - the span
   was picked for "looks like enough room" and flex-end dumped every unused pixel
   of it above the line, because that is the one place in the box nothing else
   was anchoring. The fix is not a bigger bottom margin fighting a huge span; it
   is a span with almost nothing left over to dump anywhere. */
.csec--l1 {
  grid-row: span 3;
}
/* Re-measured after the subheading became a TWO-line field (Ameer, 24 Aug -
   see .csec__sub's line-clamp above): a full two lines now measures ~105px of
   content here, which the old span 4 (94px) was already 11px short of - the
   next row would have started drawing over the second line rather than below
   it. Span 5 (122px) clears it with a little to spare. */
.csec--l1.csec--has-sub {
  grid-row: span 5;
}
.csec--l2 {
  grid-row: span 3;
  padding-inline-start: 20px;
}
/* same shortfall, worse: l2's old span 3 (66px) against a measured ~91px of
   two-line content would have overlapped by 25px, not just clipped a few
   pixels off the bottom. */
.csec--l2.csec--has-sub {
  grid-row: span 5;
}
/* THE FIRST SECTION HAS NOTHING ABOVE IT AND NO DIVIDER OF ITS OWN (the line is
   display:none - see .csec:first-child .csec__line), so it no longer needs the
   same box height a section WITH a rule to bottom-anchor against does. Span 3
   left over ~36px of dead air above the heading once the line stopped
   rendering, because flex-end still bottom-anchored a much shorter stack of
   content inside a box sized for a taller one (Ameer, 24 Aug: "why so much
   space... between the first section and the top header"). Sized tight to the
   heading's own content (measured: 30px, 51px with a subheading) and
   top-anchored, not bottom - flex-end existed to read as "the last section's
   content stops here, right above the line"; the first section has no line and
   nothing before it for that reading to serve. */
.csec:first-child {
  justify-content: flex-start;
  grid-row: span 2;
}
/* re-measured for the same reason as .csec--l1.csec--has-sub above: a full
   two-line subheading here runs ~69px, past span 3's 66px. */
.csec:first-child.csec--has-sub {
  grid-row: span 4;
}
/* THE DIVIDER IS ABOVE THE HEADING, not under it (Ameer, 22 Aug: on the preview
   "I can see one line under that, the line can be above the heading"). It reads
   the way a rule above a chapter title does: this is where the last section's
   content stops. Full width, including the level-2 indent, which is why it sits
   outside .csec__row rather than being that row's own border.

   THE TWO MARGINS ARE DELIBERATE, NOT LEFTOVER SPACE: a touch more room above
   (separating from what came before) than below (leading into the name it is
   right next to). Set explicitly so the split holds regardless of how much slack
   the row-span happens to have. */
.csec__line {
  display: block;
  height: 1px;
  background: var(--border);
  margin-top: 16px;
  margin-bottom: 20px;
}
.csec--l2 .csec__line {
  background: transparent;
  margin-top: 10px;
  margin-bottom: 12px;
}
/* THE VERY FIRST SECTION HAS NOTHING ABOVE IT TO BE DIVIDED FROM. margin-top:0
   alone still left the 1px line itself sitting at the top of the board with
   nothing on either side of it to separate - a stray rule floating over empty
   space (Ameer, 24 Aug: "the very first section should not have the top
   border"). display:none removes the mark, not just its margin. */
.csec:first-child .csec__line {
  display: none;
}
.csec__row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding-inline-end: 2px;
}
.csec__chev {
  flex: none;
  width: 22px;
  height: 22px;
  margin-inline-start: -4px;
  margin-top: 1px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--icon-muted);
  cursor: pointer;
  transition:
    transform 0.14s var(--ease),
    color 0.12s var(--ease),
    background 0.12s var(--ease);
}
.csec__chev svg {
  width: 15px;
  height: 15px;
}
.csec__chev:hover {
  background: var(--glass);
  color: var(--ink);
}
.csec__chev.is-closed {
  transform: rotate(-90deg);
}
[dir="rtl"] .csec__chev.is-closed {
  transform: rotate(90deg);
}
/* THE DRAG HANDLE, before the name. Invisible at rest - like the ⋮, it only
   claims room once the section is actually being looked at (Ameer, 24 Aug: "I
   should see a grip bar before the section name... that grip bar help me to
   drag that entire section... vertically move this section"). Not a button:
   onCanvasPointerDown reads it as a plain pointer-drag target, the same family
   as a card's own [data-resize] handles, so it never competes with a click. */
.csec__grip {
  flex: none;
  width: 18px;
  height: 22px;
  margin-top: 1px;
  display: grid;
  place-items: center;
  color: var(--icon-muted);
  cursor: grab;
  opacity: 0;
  transition:
    opacity 0.12s var(--ease),
    color 0.12s var(--ease);
}
.csec__grip svg {
  width: 14px;
  height: 14px;
}
.csec:hover .csec__grip {
  opacity: 1;
}
.csec__grip:hover {
  color: var(--ink);
}
/* the name + its optional subheading, stacked, so the subheading is a second line
   under the name rather than a second thing competing with it for the row */
.csec__head {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  flex: 1 1 auto;
}
/* THE NAME AND THE ⋮ SIT TOGETHER, hugging the word they act on rather than
   spanning the row's full width to opposite corners (Ameer, 24 Aug: "the three
   dot is appearing on the rightmost corner... should appear along with the text
   only"). csec__t goes back to shrink-to-fit (flex:0 1 auto, not the stretched
   full-width button it was as a lone flex child of a column container), so ⋮
   lands right after the last letter instead of at the edge of the board. */
.csec__namerow {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
}
/* A SECTION HEADING READS AS A HEADING NOW: 20px/700, tight tracking, the same
   register .drawer__h's family uses for a step title, not the 15px/600 label it
   used to share with a card's own name (Ameer, 22 Aug: "it's a section heading,
   man", "the font size is not correct"). The name is a BUTTON so the whole word
   opens the rename dialog on the first click; it looks like text because it is
   text, sized like the heading it is. */
/* Only the RIGHT side keeps the negative margin (hugs the ⋮ close, see above);
   the left stays flush with .csec__row's own gap instead of pulling 4px back
   into it - with a grip now sitting right before it, that overhang read as the
   grip and the word touching (Ameer, 24 Aug: "the gap between the grip bar and
   the testing is very near"). */
/* NO ARTIFICIAL CAP AND NO TRUNCATION (Ameer, 24 Aug, reversing the half-board
   cap this rule used to hold: "let it spread across the canvas... truncation
   should not be there, very important"). The heading stays on ONE line and
   uses however much of the row's real width it needs - white-space:nowrap
   with no max-width, so it only wraps if it truly cannot fit even the full
   row (min-width:0 on every ancestor already allows that shrink). Nothing
   is cut off with an ellipsis any more; the input's own SEC_NAME_MAX is the
   only limit left, and that is a character count, not a display truncation.
   syncSecHeights() (see above canvasSectionHTML) grows the section's row-span
   if a heading or subheading ever needs more room than the CSS default. */
.csec__t {
  flex: 0 1 auto;
  min-width: 0;
  padding: 2px 4px;
  margin-block: -2px;
  margin-inline: 0 -4px;
  border: 0;
  border-radius: 6px;
  background: none;
  cursor: pointer;
  text-align: start;
  font-size: 20px;
  font-weight: 700;
  line-height: 26px;
  letter-spacing: -0.015em;
  color: var(--ink);
  transition: background 0.12s var(--ease);
}
.csec--l2 .csec__t {
  font-size: 14px;
  font-weight: 600;
  line-height: 20px;
  color: var(--sub);
}
.csec__t:hover {
  background: var(--glass);
}
/* the description line under the name, wrapping to however many lines it
   needs - no line-clamp, no ellipsis, no width cap. */
.csec__sub {
  padding-inline: 4px;
  font-size: 13px;
  line-height: 18px;
  color: var(--sub);
}
.csec--l2 .csec__sub {
  font-size: 12px;
  color: var(--text-faint);
}
.csec__more {
  flex: none;
  margin-top: 2px;
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.csec:hover .csec__more,
.csec .pop.is-open .csec__more {
  opacity: 1;
}
.csec__more:focus-visible {
  opacity: 1;
}
/* FOLDED. The band keeps its chevron and its name and nothing else changes size,
   so unfolding puts the board back exactly where it was. */
.cblock.is-hidden,
.csec.is-hidden {
  display: none;
}
.csec--present .csec__row {
  padding-inline-end: 0;
}
.csec--present .csec__namerow {
  gap: 0;
}
/* WHAT THE GRIP IS CARRYING, dimmed in place while its chip follows the cursor -
   see .conv-ghost, reused for the same "here's what you're holding" chip. The
   members are not lifted out of flow (a dozen cards is not one placeholder-sized
   hole), they just visibly step back for the moment they are spoken for. */
.secgroup-lift {
  opacity: 0.35;
}
/* the seam a dragged section GROUP would land in: unlike .drop-slot, its shape is
   unknown (the group can be one heading or a heading and eleven widgets), so this
   is a bar, not a card-shaped preview. grid-column:1/-1 the same as any band. */
.secmove-line {
  grid-column: 1/-1;
  grid-row: span 1;
  align-self: center;
  height: 3px;
  border-radius: 2px;
  background: var(--accent);
  pointer-events: none;
}

/* ---- WHERE A SECTION CAN GO -------------------------------------------------
   One hit target per seam between rows, laid over the grid gap by syncSecRail.
   Absolutely positioned, so the rail is NOT a grid item and takes no row of its
   own; pointer-events:none on the rail and auto on the strips, so the only thing
   that can intercept a click on a card is a strip sitting in the gap beside it.

   THE HIT TARGET IS TALLER THAN THE SEAM IT MARKS. At the old 14px row gap this
   sat in an area barely bigger than the "+ Section" pill itself, which is small
   even to hover accurately, let alone notice was there. It now reaches 8px into
   the cards on either side of the gap (still just a hover/click target, nothing
   moves), and the row gap itself grew to 18px alongside it, so the visible seam
   has more room to read as "space you could put something into" even before a
   pointer is anywhere near it (Ameer, 22 Aug: widen it "so that user thinks I can
   actually hover over there and do it"). */
.csec-rail {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
}
/* invisible at rest, on purpose: the general row gap grew instead (see
   .canvas-grid's row-gap) so the seam reads as "room" before anyone has found
   the control living in it. This is what appears once they do. */
.csec-ins {
  position: absolute;
  inset-inline: 0;
  height: 30px;
  transform: translateY(-50%);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  pointer-events: auto;
  display: flex;
  align-items: center;
  opacity: 0;
  transition:
    opacity 0.14s var(--ease),
    transform 0.16s var(--ease);
}
.csec-ins:hover,
.csec-ins:focus-visible {
  opacity: 1;
}
/* THE SEAM ITSELF OPENS, not just the control on it. A fixed 18px gap read as
   too tight to believe you could drop something in, even once the hit target
   (above) made it clickable (Ameer, 24 Aug: "that area should expand vertically
   ... user should feel like they can actually put something there"). On hover,
   syncSecRail's pointerenter handler pushes every row from this seam down by
   --gap-push and this button re-centers in the taller gap that leaves behind,
   a preview shift (transform, not real layout), so nothing about the saved
   arrangement changes just from hovering it. */
.csec-ins.sec-gap-open {
  transform: translateY(calc(-50% + var(--gap-push, 0px) / 2));
}
/* every LATER seam's rows moved down by the full push too (see .sec-gap-push),
   so its own line has to follow by the same full amount or it drifts up into
   the row that just moved out from under it. */
.csec-ins.sec-gap-follow {
  transform: translateY(calc(-50% + var(--gap-push, 0px)));
}
.csec-ins__l {
  flex: 1;
  height: 1px;
  background: var(--accent);
}
/* BIGGER, BOLDER on hover: it was easy to miss even once found (Ameer, 22 Aug:
   "that plus section should be clearly visible"). Same accent fill, a full size
   step up (18px control to 24px) and a shadow that lifts it off the line it
   sits on rather than reading as flat text on a strip. */
.csec-ins__b {
  position: absolute;
  inset-inline-start: 0;
  display: flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  padding: 0 10px 0 8px;
  border-radius: 7px;
  background: var(--accent);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  line-height: 24px;
  white-space: nowrap;
  box-shadow: 0 4px 14px -4px rgba(0, 0, 0, 0.4);
}
.csec-ins__b svg {
  width: 14px;
  height: 14px;
}
/* the rows a hovered seam pushes down, per .sec-gap-open above. .cblock already
   transitions (below); .csec has nothing to merge into, so it gets its own rule. */
.csec {
  transition: transform 0.16s var(--ease);
}
.sec-gap-push {
  transform: translateY(var(--gap-push, 0px));
}
@media (prefers-reduced-motion: reduce) {
  .csec__chev {
    transition: none;
  }
  .csec-ins,
  .cblock,
  .csec {
    transition: none;
  }
}

.cblock {
  position: relative;
  box-sizing: border-box;
  max-width: 100%;
  height: 100%;
  min-width: 0;
  background: var(--box);
  border: 1px solid var(--box-stroke);
  border-radius: 16px;
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  transition:
    border-color 0.12s,
    box-shadow 0.12s,
    transform 0.16s var(--ease);
}
.cblock:hover {
  border-color: var(--accent-border);
}
.cblock.sel {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
/* ONLY a newly added card animates in. Existing cards keep their DOM node through a
   reconcile (see patchCanvas), so they never replay this. */
@keyframes blockIn {
  from {
    opacity: 0;
    transform: scale(0.97);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.cblock.block-in {
  animation: blockIn 0.28s var(--ease) both;
}
@media (prefers-reduced-motion: reduce) {
  .cblock.block-in {
    animation: none;
  }
}
.cblock__hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
/* Widget titles are sentence case. All-caps is a shouting label; these are names,
   and at 11px with letter-spacing a name like "Customers under watch" was harder to
   read than the number underneath it. The size goes up two points to match. */
.cblock__t {
  font-size: 13px;
  color: var(--sub);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cblock__ic {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ricon);
  flex: none;
}
.cblock__ic > svg {
  width: 18px;
  height: 18px;
}
/* A GRID AREA IS A FIXED HEIGHT, where the old flex row let min-height grow to
   whatever the content needed. So content that outgrows the area a person chose
   scrolls inside it rather than being quietly cut off at the border. */
.cblock__bd {
  flex: 1;
  min-height: 0;
  overflow: auto;
}
.cblock--kpi {
  min-width: 180px;
}
.dkpi {
  margin-top: 6px;
}
.dkpi__v {
  font-size: 30px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.1;
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.dkpi__delta {
  font-size: 13px;
  font-weight: 600;
}
.dkpi__delta.down {
  color: var(--danger);
}
.dkpi__delta.up {
  color: var(--success);
}
.dkpi__s {
  font-size: 12px;
  color: var(--sub);
  margin-top: 6px;
}
/* the same number card, offered in the conversation before it is pinned, so
   what is dragged onto the board is what was already being looked at */
.dkpi--tile {
  margin-top: 0;
  padding: 4px 0;
}
.dkpi--tile .dkpi__v {
  font-size: 26px;
}

/* ---- NOTE ------------------------------------------------------------------
   A widget made of a sentence. It exists because not every answer is a number
   or a shape, and a board with no way to say anything in words ends up with the
   explanation living in the title of the chart next to it. Body copy, not label
   copy: it is meant to be read, so it gets reading measure and reading colour
   rather than the muted 12px the widget captions use. */
.dtext {
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
  margin: 0;
  max-width: 62ch;
}
.anstile--text .anstile__wd,
.anstile--kpi .anstile__wd {
  min-height: 0;
  padding: 2px 0;
}
.cblock--area .linechart svg {
  height: 100%;
  min-height: 150px;
}
.cblock--area .cblock__bd {
  display: flex;
  flex-direction: column;
}
.cblock--area .darea {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.cblock--area .linechart {
  flex: 1;
  display: flex;
}
.cblock--custrisk .dcrisk {
  margin-top: 4px;
}

/* empty add slot: SILENT AT REST. It was a permanent dashed box with an icon and
   a full sentence sitting on the board at all times - a fixed piece of
   furniture, not free space (Ameer, 22 Aug: "wherever user hovers on a free
   space, it just gives you a small indicator... anywhere free space I have, I
   should be able to drag it"). The dashed border and the plus both live only on
   :hover now; at rest this is just an unmarked area of the board, the same
   language the section-insert seams already use. */
.cblock--empty {
  border: 1.5px dashed transparent;
  background: transparent;
  align-items: center;
  justify-content: center;
  box-shadow: none;
  cursor: pointer;
  transition:
    border-color 0.15s var(--ease),
    background 0.15s var(--ease);
}
.cblock--empty:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.addslot__ic {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--glass);
  display: grid;
  place-items: center;
  color: var(--sub);
  opacity: 0;
  transition: opacity 0.15s var(--ease);
}
.cblock--empty:hover .addslot__ic {
  opacity: 1;
}

/* selection handles.
   THE GRIP KNOB IS DELETED, not hidden. It used to sit at the card's top-left on
   hover, and before it a "Drag to move" pill sat across the top edge. Both are
   gone: the card is draggable from anywhere on it, so a separate handle was a
   second, unlabelled way to do the one thing the whole card already does, and it
   landed in the same corner as the selection ring and the corner resize handle.
   The rules are removed rather than overridden so there is nothing left to
   re-enable by accident. Do not add one back. */
.rhandle {
  position: absolute;
  width: 9px;
  height: 9px;
  background: #fff;
  border: 1.5px solid var(--accent);
  border-radius: 2px;
  display: none;
  z-index: 7;
}
/* the handles appear on HOVER (no click needed) as well as when selected */
.cblock:hover > .rhandle,
.cblock.sel > .rhandle,
.cblock.resizing > .rhandle {
  display: block;
}
.cblock:hover,
.cblock.resizing {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-border);
}
/* while resizing/moving a card, the OTHERS must not react to the pointer (no hover-select, no jump/collision) */
body.block-resizing .cblock:not(.resizing),
body.grabbing .cblock:not(.dragging) {
  pointer-events: none;
}
.rh-nw {
  top: -5px;
  left: -5px;
  cursor: nwse-resize;
}
.rh-ne {
  top: -5px;
  right: -5px;
  cursor: nesw-resize;
}
.rh-sw {
  bottom: -5px;
  left: -5px;
  cursor: nesw-resize;
}
.rh-se {
  bottom: -5px;
  right: -5px;
  cursor: nwse-resize;
}
.rh-n {
  top: -5px;
  left: 50%;
  transform: translateX(-50%);
  cursor: ns-resize;
}
.rh-s {
  bottom: -5px;
  left: 50%;
  transform: translateX(-50%);
  cursor: ns-resize;
}
.rh-e {
  right: -5px;
  top: 50%;
  transform: translateY(-50%);
  cursor: ew-resize;
}
.rh-w {
  left: -5px;
  top: 50%;
  transform: translateY(-50%);
  cursor: ew-resize;
}
.cblock.dragging {
  opacity: 0.9;
  box-shadow: 0 18px 44px -10px rgba(0, 0, 0, 0.55);
  z-index: 20;
}
.cblock.drop-target {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* ---- History rail (inside workbox) ---- */
.hrail {
  flex: 0 0 48px;
  border-inline-start: 1px solid var(--panel-stroke);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 12px 0;
  transition: flex-basis 0.2s var(--ease);
}
.hrail.open {
  flex: 0 0 300px;
  align-items: stretch;
  padding: 0;
}
.rail-flag {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: none;
  border: 0;
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.rail-flag svg {
  width: 18px;
  height: 18px;
}
.rail-flag:hover {
  background: var(--glass);
  color: var(--ink);
}
.rail-open {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
  padding: 20px;
}
.rail-hh {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.rail-hh .t {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 16px;
  color: var(--ink);
}
.rail-hh .t svg {
  width: 14px;
  height: 14px;
  color: var(--ricon);
}
.rail-hh .x {
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  display: grid;
  place-items: center;
}
.rail-hh .x svg {
  width: 14px;
  height: 14px;
}
.rail-scroll {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 32px;
} /* same History List rhythm as the external panel */

/* drag cursors */
body.col-resizing,
body.col-resizing * {
  cursor: col-resize !important;
  user-select: none;
}
body.block-resizing {
  user-select: none;
}
body.grabbing,
body.grabbing * {
  cursor: grabbing !important;
  user-select: none;
}

/* ---- pin-to-canvas: draggable conversation cards + drop states ---- */
.ansitem,
.anstile {
  cursor: grab;
}
.ansitem:active,
.anstile:active {
  cursor: grabbing;
}
.ansitem button,
.ansitem .pop,
.anstile button,
.anstile .pop {
  cursor: pointer;
}
.ans-dragging {
  opacity: 0.5;
}
.conv-ghost {
  position: fixed;
  z-index: 9999;
  transform: translate(14px, 10px);
  pointer-events: none;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--accent);
  color: #fff;
  padding: 9px 13px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 14px 34px -8px rgba(0, 0, 0, 0.55);
  white-space: nowrap;
  max-width: 300px;
}
.conv-ghost svg {
  width: 15px;
  height: 15px;
  flex: none;
}
.conv-ghost span {
  overflow: hidden;
  text-overflow: ellipsis;
}
.canvas-grid.droppable {
  outline: 2px dashed var(--accent-border);
  outline-offset: 8px;
  border-radius: 12px;
}
/* empty canvas state (e.g. Fraud Monitoring Board opened but nothing pinned yet) */
/* ---- the build, narrated in the conversation panel ----
   Not an .ans card: no fill, no border, no card padding. The panel IS the surface, so the
   loader sits directly on it exactly the way a settled answer list does, and there is no
   box-inside-a-box at 370px. Keeps .ll's own internal styling untouched. */
.conv-pending {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.conv-pending .ll {
  margin: 0;
}
.conv-pending .ans__h {
  font-size: 17px;
  line-height: 23px;
  font-weight: 600;
  color: var(--ink);
}
.conv-pending .ans__s {
  font-size: 14px;
  line-height: 20px;
  color: var(--sub);
}
.conv-pending .stopped-note {
  padding: 0;
}

/* waiting variant: no call to action, because there is nothing to act on yet */
.canvas-empty--waiting .canvas-empty__ic {
  color: var(--sub);
  background: var(--glass);
}
.canvas-empty--waiting .canvas-empty__t {
  color: var(--ink-2);
}

/* NO DASHED SLOT. The box was drawing the PAGE (1200px) while the message it holds
   is about the column you are looking at, so on any panel narrower than the page it
   ran off the right edge and the copy inside it sat off-centre against the space it
   was meant to fill (Ameer, 24 Aug). Nothing is being outlined here - the canvas is
   the whole column - so the outline goes and the message simply centres in the room
   it has. The drop target is still the whole canvas; it never needed a border to be. */
.canvas-empty {
  flex: 1 1 100%;
  min-height: 340px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 14px;
  padding: 40px;
}
.canvas-empty__ic {
  width: 64px;
  height: 64px;
  border-radius: 18px;
  background: var(--glass);
  display: grid;
  place-items: center;
  color: var(--accent);
}
.canvas-empty__ic svg {
  width: 30px;
  height: 30px;
}
.canvas-empty__t {
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
}
.canvas-empty__s {
  font-size: 13px;
  color: var(--sub);
  max-width: 400px;
  line-height: 19px;
}
.canvas-empty__s b {
  color: var(--ink-2);
  font-weight: 600;
}
.canvas-empty__btn {
  margin-inline-start: 0;
  margin-top: 6px;
  height: 40px;
  border-radius: 10px;
  padding: 0 18px;
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  font-size: 14px;
  border: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: 0.15s;
}
.canvas-empty__btn:hover {
  background: var(--accent-hover);
}
.canvas-empty__btn svg {
  width: 16px;
  height: 16px;
}
/* drop indicator, thin LINE between two cards (Google-style); dashed "Place here" BOX over empty space / the end */
/* the MOVE placeholder: the dragged card's own footprint, so the grid geometry is
   identical whether the card is sitting in it or lifted out of it. Quiet, not a
   "Place here" invitation - it marks where the card WILL land, and it is the only
   thing that moves while you drag. */
.drop-slot--ghost {
  box-sizing: border-box;
  height: 100%;
  border-radius: 16px;
  border: 1.5px dashed var(--accent-border);
  background: var(--accent-soft);
  animation: none;
}
/* the slot is a grid item like any other: its area is set inline, from the span
   of the widget about to land in it */
.drop-slot--box {
  height: 100%;
  max-width: 100%;
  box-sizing: border-box;
  border-radius: 16px;
  border: 2px dashed var(--accent);
  background: var(--accent-soft);
  display: flex;
  flex-direction: column;
  padding: 16px 18px;
  gap: 12px;
  overflow: hidden;
  animation: slotBox 0.18s var(--ease);
}
/* the fallback shape (no card known yet, e.g. dragging in from the canvas itself
   with no answer behind it) keeps the old centred invitation */
.drop-slot--box:not(:has(.drop-slot__hd)) {
  display: grid;
  place-items: center;
  padding: 0;
}
.drop-slot__in {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
}
.drop-slot__in svg {
  width: 16px;
  height: 16px;
}
/* THE PREVIEW. Same header rhythm as the real card it is standing in for
   (icon + title, .cblock__hd's own sizes), so the box reads as "this card, not yet
   placed" rather than as an empty slot with a caption. */
.drop-slot__hd {
  flex: none;
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}
.drop-slot__ic {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--accent);
}
.drop-slot__ic svg {
  width: 15px;
  height: 15px;
}
.drop-slot__t {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
}
/* the body is a plain wash, not fabricated content: it says "the rest of the card
   goes here", not "here is what the chart looks like" */
.drop-slot__ph {
  flex: 1;
  min-height: 0;
  border-radius: 10px;
  background: linear-gradient(180deg, var(--accent-soft), transparent);
}
@keyframes slotIn {
  from {
    opacity: 0;
    transform: scaleX(0.3);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes slotBox {
  from {
    opacity: 0;
    transform: scale(0.94);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* while dragging a card, hide the passive add-slot / empty placeholder so only the drop indicator shows */
/* visibility, not display: the add-slot has to keep its box during a drag or its
   row-mate re-flows and changes height - the same class of bug as the hole a lifted
   card leaves. .canvas-empty still goes entirely; it is the whole column and the
   drop preview replaces it. */
body.grabbing .cblock--empty {
  visibility: hidden;
}
body.grabbing .canvas-empty {
  display: none;
}

/* Grabbable ON HOVER, which is when the bounding box, the handles and the move pill
   appear - the cursor has to agree with what the card is showing. Not scoped to .sel
   any more: needing a click first made the affordance a promise the card would not
   keep. The children that are NOT drag handles put their own cursor back. */
.cblock {
  cursor: grab;
}
.cblock--empty,
.cblock--present {
  cursor: default;
}
.cblock button,
.cblock a,
.cblock [data-dd] {
  cursor: pointer;
}
/* .rhandle is deliberately NOT reset here: each .rh-* carries its own directional
   resize cursor, and a blanket rule on .cblock would outrank all eight. */
body.grabbing .cblock,
body.grabbing .cblock * {
  cursor: grabbing;
}

/* ============================================================
   COMPOSER: controls, dropdowns, tooltips, recording
   Mirrors Figma Prompt Composer 4093:1515 (Variation=1 / =2).
   Shared by Investigation · Chat with Data · Build Dashboard.
   ============================================================ */

/* Send: dark by default (Icon Button State=Default), lit only when there is
   something to send. The white/accent fill is the ACTIVE state, not the resting one. */
.send {
  background: var(--send-bg);
  border: 1px solid var(--line-strong);
  color: var(--send-fg);
  transition:
    background 0.14s var(--ease),
    color 0.14s var(--ease),
    transform 0.14s var(--ease);
}
.send:hover {
  background: var(--pill-hover);
  color: var(--ink);
}
.send.is-active {
  background: var(--send-active-bg);
  color: var(--send-active-fg);
  border-color: transparent;
}
.send.is-active:hover {
  transform: translateY(-1px);
  opacity: 0.92;
}
[data-module="chat"] .send {
  background: var(--send-bg);
  color: var(--send-fg);
}
[data-module="chat"] .send:hover {
  background: var(--pill-hover);
  color: var(--ink);
}
[data-module="chat"] .send.is-active,
[data-module="dashboard"] .send.is-active {
  background: var(--accent);
  color: #fff;
}
[data-module="chat"] .send.is-active:hover,
[data-module="dashboard"] .send.is-active:hover {
  background: var(--accent-hover);
}
.cc--off .send,
.cc--off .send.is-active {
  background: var(--surface-raised);
  color: var(--icon-muted);
  border-color: var(--line-strong);
}
/* Send stays DISABLED until the user types. DS disabled treatment = opacity .5
   (Answer / Action Button 4211:1943 · State=Disabled). Applies in both themes and all modules. */
.send:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.send:disabled:hover {
  background: var(--send-bg);
  color: var(--send-fg);
  transform: none;
}
[data-module="chat"] .send:disabled:hover,
[data-module="dashboard"] .send:disabled:hover {
  background: var(--send-bg);
  color: var(--send-fg);
}

/* brain = reasoning toggle. Selected = white fill, dark icon, no brand colour. */
.cbtn.is-on {
  background: var(--sel-bg);
  border-color: transparent;
  color: var(--sel-fg);
}
.cbtn.is-on:hover {
  background: var(--sel-bg);
  color: var(--sel-fg);
}
/* Composer controls (attach · Full Analysis · settings · brain · EN · mic) lift on hover:
   background steps Zinc/80 → Zinc/70 per Figma Icon Button State=Hover. Stroke stays put.
   The send button is excluded, it is disabled until there is something to send. */
.cbtn:hover,
.cchip:hover {
  background: var(--cc-hover);
  color: var(--ink);
}
.cc--off .cbtn:hover,
.cc--off .cchip:hover {
  background: var(--pill);
  color: var(--ricon);
} /* locked composer: no lift */

/* keyboard focus = the same subtle treatment, never the browser's blue ring */
.cchip:focus-visible,
.cbtn:focus-visible,
.send:focus-visible,
.hchip:focus-visible,
.hbtn:focus-visible,
.mi:focus-visible,
.sm-hd:focus-visible,
.sm-row:focus-visible,
.sm-eng:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 1px var(--glass-2);
}

/* hover tooltips on the composer's icon controls (above, since the composer is docked) */
[data-tip] {
  position: relative;
}
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translate(-50%, 4px);
  background: var(--menu-bg);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  z-index: 70;
  transition:
    opacity 0.14s var(--ease),
    transform 0.14s var(--ease);
}
[data-tip]:hover::after {
  opacity: 1;
  transform: translate(-50%, 0);
}
.pop.is-open [data-tip]::after {
  opacity: 0;
} /* the open menu replaces the tooltip */

/* composer menus open UPWARD: the composer is docked to the bottom */
.composer .pop > .menu,
.conv-dock .pop > .menu {
  top: auto;
  bottom: calc(100% + 8px);
  transform-origin: bottom;
}
.composer .pop > .menu {
  transform: translateY(4px) scale(0.98);
}
.composer .pop.is-open > .menu {
  transform: none;
}

/* rich menu rows: icon + label + description (Figma Menu / Action Item 4356:1991) */
.menu--rich {
  min-width: 288px;
  max-width: 320px;
  padding: 8px;
}
.mi {
  display: flex;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border-radius: var(--r-sm);
  background: none;
  border: 0;
  text-align: start;
  cursor: pointer;
  transition: background 0.12s var(--ease);
}
.mi:hover {
  background: var(--glass);
}
.mi--sel {
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--glass-2);
}
.mi--sel:hover {
  background: var(--glass);
}
.mi-ic {
  width: 16px;
  flex: none;
  display: grid;
  place-items: center;
  padding-top: 1px;
}
.mi-ic svg {
  width: 16px;
  height: 16px;
  color: var(--icon-muted);
}
.mi--sel .mi-ic svg {
  color: var(--icon);
}
.mi-c {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.mi-t {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  line-height: 1.2;
}
.mi--sel .mi-t {
  color: var(--text-primary);
}
.mi-d {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-faint);
  line-height: 1.35;
  white-space: normal;
}
.menu-div {
  height: 1px;
  background: var(--border);
  margin: 8px 2px;
}

/* settings dropdown, Agents group + Deep Research and its engines */
.settings-menu {
  min-width: 300px;
}
.sm-hd,
.sm-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 9px 10px;
  border-radius: var(--r-sm);
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--text-primary);
  font-size: 13px;
  font-weight: 600;
  transition: background 0.12s var(--ease);
}
.sm-hd:hover,
.sm-row:hover {
  background: var(--glass);
}
.sm-hd-l {
  display: flex;
  align-items: center;
  gap: 9px;
}
.sm-hd-l svg {
  width: 18px;
  height: 18px;
  color: var(--icon-muted);
}
.sm-hd .chev {
  width: 16px;
  height: 16px;
  color: var(--icon-muted);
  transition: transform 0.16s var(--ease);
}
.sm-hd.is-closed .chev {
  transform: rotate(-90deg);
}
.sm-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px 0;
  padding-inline-start: 6px;
}
/* on/selected = the SAME subtle treatment as hover, fill --glass, stroke --glass-2. No brand colour. */
.sm-row.is-on {
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--glass-2);
  color: var(--text-primary);
}
.sm-row.is-on:hover {
  background: var(--glass);
}
.sm-row.is-on .sm-hd-l svg {
  color: var(--icon);
}
.sm-tog {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--glass);
  color: var(--text-faint);
}
.sm-row.is-on .sm-tog {
  background: var(--glass-2);
  color: var(--text-secondary);
}
.sm-engines {
  display: flex;
  gap: 6px;
  padding: 8px 4px 2px;
  padding-inline-start: 6px;
}
.sm-eng {
  flex: 1;
  padding: 8px 4px;
  border-radius: 9px;
  background: none;
  border: 1px solid transparent;
  color: var(--text-faint);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.12s var(--ease);
}
.sm-eng:hover {
  background: var(--glass);
  color: var(--text-primary);
}
.sm-eng.is-sel {
  background: var(--glass);
  border-color: var(--glass-2);
  color: var(--text-primary);
}

/* recording state, Figma Prompt Composer Variation=2 (4093:1429).
   The composer keeps its resting shape/fill; only the control row collapses to
   wave + close + send, and the live transcript takes the textarea's place. */
.rec-transcript {
  min-height: 24px;
  max-height: 96px;
  overflow-y: auto;
  color: var(--ink);
  font-size: 16px;
  line-height: 24px;
  font-style: italic;
}
.rec-transcript:empty::before {
  content: "Listening…";
  color: var(--sub);
  font-style: italic;
}
.cc--rec {
  gap: 16px;
}
.wave {
  flex: 1;
  min-width: 0;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  overflow: hidden;
}
.wave span {
  width: 2px;
  flex: none;
  border-radius: 1px;
  background: var(--ricon);
  animation: waveP 1.1s ease-in-out infinite alternate;
  transform-origin: center;
}
/* the transcript has landed, the wave settles so the text is the focus */
.rec-transcript.is-done ~ .cc--rec .wave span {
  animation: none;
  opacity: 0.55;
}
@keyframes waveP {
  from {
    transform: scaleY(0.35);
  }
  to {
    transform: scaleY(1);
  }
}
@media (prefers-reduced-motion: reduce) {
  .wave span {
    animation: none;
  }
}

/* reasoning block, shown on every answer while the brain toggle is on */
.ans__reason-hd {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
}
.ans__reason-hd svg {
  width: 14px;
  height: 14px;
}
.ans__reason-n {
  margin-inline-start: auto;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-faint);
  text-transform: none;
}
.ans__reason-l {
  margin: 9px 0 0;
  padding-inline-start: 18px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.ans__reason-l li {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-secondary);
}
.ans__reason-l li::marker {
  color: var(--accent);
  font-weight: 600;
}

/* zoe-widgets mount points, the library card supplies its own chrome, so the
   placeholder only handles layout inside the answer body. */
.zw-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
.zw {
  display: flex;
  min-width: 0;
}
.zw > div {
  flex: 1;
  min-width: 0;
}
@media (max-width: 1100px) {
  .zw-row {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
   TABLET RESPONSIVE BAND: ≤1024px  (tested down to an 800px floor)
   The left nav and right history collapse into scrim drawers and the
   stage goes full-bleed. Only one drawer opens at a time (JS-enforced).
   The Build Dashboard module is intentionally excluded, it keeps its
   desktop multi-column layout for now (own responsive pass later).
   ============================================================ */
.tb-left {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  flex: 1;
}
.nav-burger,
.hist-burger {
  display: none;
}
.scrim {
  display: none;
}

@media (max-width: 1024px) {
  /* burgers appear (never on Build Dashboard) */
  html:not([data-module="dashboard"]) .nav-burger,
  html:not([data-module="dashboard"]) .hist-burger {
    display: grid;
  }

  /* TABLET-ONLY breadcrumb variation, give it the SAME 40px control height as the burger + header
     buttons and centre the single line inside it, so the text sits on the exact same centreline as
     every round control (fixes the "text reads high" optical offset in the tablet header). */
  html:not([data-module="dashboard"]) .breadcrumb {
    height: 40px;
  }
  html:not([data-module="dashboard"]) .breadcrumb .bc-mut,
  html:not([data-module="dashboard"]) .breadcrumb .bc-sep,
  html:not([data-module="dashboard"]) .breadcrumb .bc-cur {
    display: flex;
    align-items: center;
    height: 100%;
    line-height: 1;
  }

  /* stage reclaims full width, inline column offsets are cleared by render() */
  html:not([data-module="dashboard"]) .stage {
    left: 16px;
    right: 16px;
  }

  /* dimmed scrim sits behind whichever drawer is open */
  html:not([data-module="dashboard"]) .scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 40;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s var(--ease);
  }
  html:not([data-module="dashboard"]) .screen.nav-open .scrim,
  html:not([data-module="dashboard"]) .screen.hist-open .scrim {
    opacity: 1;
    pointer-events: auto;
  }

  /* LEFT NAV → off-canvas drawer, slides in from the left */
  html:not([data-module="dashboard"]) .nav {
    transform: translateX(calc(-100% - 40px));
    transition: transform 0.28s var(--ease);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
  }
  html:not([data-module="dashboard"]) .nav.expanded {
    width: 300px;
    max-width: calc(100vw - 56px);
    padding: 16px;
  }
  html:not([data-module="dashboard"]) .screen.nav-open .nav {
    transform: none;
    z-index: 50;
  }
  /* An OPEN drawer covers the top bar, and the menu burger with it - measured: the
     drawer's own .nx-head sits over the burger, so tapping the menu icon a second
     time to close it is physically impossible. The drawer therefore has to carry a
     close control that reads as one and is worth aiming at: a real 40px target with
     an X, not the 20px rail-collapse sliver at the panel's far edge. The scrim still
     closes it too. (Ameer, 20 Aug - "I am not able to collapse it".) */
  .screen.nav-open .nav .nav-collapse {
    width: 40px;
    height: 40px;
    border-radius: 12px;
    flex: none;
    background: var(--glass);
    border: 1px solid var(--line);
    color: var(--ricon);
    display: grid;
    place-items: center;
    margin-inline-end: -4px;
  }
  .screen.nav-open .nav .nav-collapse:hover {
    background: var(--glass-2);
    color: var(--ink);
  }
  .screen.nav-open .nav .nav-collapse svg {
    width: 18px;
    height: 18px;
  }

  /* RIGHT HISTORY → off-canvas drawer, slides in from the right */
  html:not([data-module="dashboard"]) .history {
    top: 16px;
    bottom: 16px;
    right: 16px;
    width: 340px;
    max-width: calc(100vw - 48px);
    transform: translateX(calc(100% + 48px));
    transition: transform 0.28s var(--ease);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
  }
  html:not([data-module="dashboard"]) .screen.hist-open .history {
    transform: none;
    z-index: 50;
  }
}

/* Stop, the send arrow becomes a square-in-a-circle while a brand-new answer generates.
   Same 40px circle, so the control changes job in place instead of moving. */
.send--stop {
  background: var(--send-active-bg);
  color: var(--send-active-fg);
  border-color: transparent;
  cursor: pointer;
}
.send--stop:hover {
  opacity: 0.92;
  transform: translateY(-1px);
}
.send--stop svg {
  width: 15px;
  height: 15px;
}
/* the field is inert while its answer generates; Stop hands it straight back */
.composer textarea[disabled] {
  opacity: 0.55;
  cursor: default;
}

/* A toast reporting a STATE rather than a success keeps a neutral icon, the accent is
   reserved for "it worked", and a stop is neither good nor bad news. */
.toast.is-plain svg {
  color: var(--ricon);
}

/* Stopped, one centred note where the card would have been. No question, no partial
   answer, no actions: the run did not happen, so nothing is left to act on. */
.stopped-note {
  display: flex;
  justify-content: center;
  padding: 34px 0 10px;
}
.stopped-note .pill {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  background: var(--panel);
  border: 1px solid var(--panel-stroke);
  border-radius: 999px;
  padding: 10px 18px;
  font-size: 13px;
  line-height: 18px;
  color: var(--sub);
}
.stopped-note .pill svg {
  width: 15px;
  height: 15px;
  color: var(--ricon);
  flex: none;
}

/* trailing chevron on a menu item that opens a submenu (Add to project) */
.menu-item .mi-trail {
  margin-inline-start: auto;
  color: var(--icon-muted);
  flex: none;
}

/* ---- LIVE LOADER -------------------------------------------------------------------
   The trace of how an answer was reached, shown while it is being reached. Grey and
   off-white carry the whole component; the ONE dot marking the running step is the only
   colour. No icon on the header, no progress bar, no percentage, nothing blocking. */
.ll {
  display: flex;
  flex-direction: column;
  gap: 18px;
  margin-top: 16px;
}
.ll-hd {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ll-hd-t {
  flex: 1;
  font-size: 14px;
  font-weight: 500;
  color: var(--sub);
}
/* the timer states fact, not prediction, mono so the digits do not shift width */
.ll-time {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.ll-steps {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ll-step {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.ll-mark {
  flex: none;
  width: 18px;
  height: 18px;
  display: grid;
  place-items: center;
  margin-top: 1px;
}
.ll-mark svg {
  width: 18px;
  height: 18px;
  color: var(--text-muted);
}
.ll-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.ll-t {
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
}
.ll-sub {
  font-size: 12px;
  line-height: 18px;
  color: var(--text-muted);
}
/* identifiers stay Latin and LTR even inside Arabic prose */
.ll-t code {
  font-family: var(--font-mono);
  font-size: 12.5px;
  direction: ltr;
  unicode-bidi: isolate;
}
/* the running step is the only bright row, and the only colour in the loader */
.ll-step--running .ll-t {
  color: var(--ink);
}
.ll-dot {
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: var(--accent);
  display: block;
  animation: ll-pulse 1.5s var(--ease) infinite;
}
@keyframes ll-pulse {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.55;
    transform: scale(0.82);
  }
}
/* the step that just finished swaps its dot for a tick in place, fade it in so the change
   reads as the step settling rather than a hard cut. Scoped to the live loader: the answered
   trace (.ll-trace) draws all its ticks at once and must not stagger them. */
.ll .ll-step--done .ll-mark svg,
.ll .ll-step--web .ll-mark svg {
  animation: ll-mark-in 0.26s var(--ease) both;
}
@keyframes ll-mark-in {
  from {
    opacity: 0;
    transform: scale(0.72);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* a settled row drops to the muted tone over the same beat, so the brightness moves down
   the list instead of blinking off */
.ll .ll-step--done .ll-t {
  transition: color 0.26s var(--ease);
}
.ll-step--error .ll-t {
  color: var(--ink);
}
.ll-step--error .ll-mark svg {
  color: var(--danger);
}
/* the loader fades as the finished answer takes its place (set from fadeLoaderThen) */
.ll {
  will-change: opacity;
}
@media (prefers-reduced-motion: reduce) {
  .ll-dot {
    animation: none;
  }
  .ll .ll-step--done .ll-mark svg,
  .ll .ll-step--web .ll-mark svg {
    animation: none;
  }
}

/* answered, the trace stays open above the headline. The brain toggle is the switch;
   there is no collapse and nothing to click. */
.ll-trace {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 15px;
  margin-bottom: 10px;
}

/* ============================================================
   DATA SOURCE → SCHEMA
   Three sections behind one sub-tab strip: Raw Schema, Entity Relationships,
   Semantic Intelligence. Figma wireframes 5680:16547 · 5682:16553 · 5683:16565.

   Almost all of this is layout over parts that already exist, .card, .p-table,
   .tag, .chip, .input, .btn, .panel-tabs. Three things here are genuinely new
   and reusable: .seg (segmented control), .kbadge (the PK/FK key marker) and
   the .er-* graph canvas.

   COLOUR NOTE. The wireframes tag ACTIVITY/FACT in a blue that does not exist
   anywhere in the Zoé palette, and REFERENCE/DIMENSION in emerald. Adding a
   blue primitive would put a colour on screen that the Figma library cannot
   account for, and emerald is reserved for semantic "good" (see the confidence
   badge below, which is a legitimate use of it). So the two classification
   tags are drawn as a NEUTRAL pair, filled vs outlined, which separates them
   just as hard without inventing a hue. Emerald and amber appear on this page
   exactly twice, both times semantically: confidence, and rule severity.
   ============================================================ */

/* ---- shared bits ---- */
.ta-end {
  text-align: end;
}
/* .p-table th sets text-align:start and is MORE specific than a bare .ta-end,
   so every right-aligned header was silently left-aligned while its own values
   sat right, the column read as misaligned because it was. */
.p-table th.ta-end,
.p-table th.sch-w3 {
  text-align: end;
}
.input--sm {
  height: 36px;
  font-size: 13px;
}
.input--sm svg {
  width: 16px;
  height: 16px;
}

/* sub-level tab strip, the same .panel-tabs component one size down, so the
   detail page's own Overview/Schema tabs stay the louder of the two */
.panel-tabs--sub {
  gap: 26px;
  margin-bottom: 20px;
}
.panel-tabs--sub .panel-tab {
  font-size: 14px;
  padding: 0 2px 10px;
  gap: 7px;
}
.panel-tabs--sub .panel-tab svg {
  width: 16px;
  height: 16px;
  color: var(--ricon);
}
.panel-tabs--sub .panel-tab.sel svg {
  color: var(--ink);
}

/* segmented control. Selected = the DS's own "this one" treatment (--sel-bg on
   --sel-fg), the same pair .rail-item.sel and .cbtn.is-on already use, never
   the brand colour. */
.seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  flex: none;
  background: var(--pill);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
}
.seg__btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 13px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--sub);
  cursor: pointer;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 13px;
  white-space: nowrap;
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease);
}
.seg__btn svg {
  width: 15px;
  height: 15px;
  flex: none;
}
.seg__btn:hover {
  background: var(--glass);
  color: var(--ink);
}
.seg__btn.sel {
  background: var(--sel-bg);
  color: var(--sel-fg);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.seg__btn.sel:hover {
  background: var(--sel-bg);
  color: var(--sel-fg);
}
.seg--xs .seg__btn {
  height: 26px;
  padding: 0 10px;
  font-size: 12px;
}

/* classification tags, see the colour note above */
.tag--cls {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10px;
  font-weight: 700;
  height: 20px;
  padding: 0 7px;
}
.tag--cls-activity {
  background: var(--glass-2);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.tag--cls-reference {
  background: transparent;
  color: var(--sub);
  box-shadow: inset 0 0 0 1px var(--border-strong);
}

/* PK / FK key marker */
.kbadge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 19px;
  min-width: 26px;
  padding: 0 6px;
  border-radius: var(--r-xs);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.04em;
  flex: none;
}
/* PK and FK are told apart by the colour of the LABEL: the chip behind both is
   the same neutral glass. Deliberately not a tinted background: these repeat on
   every row of every table, and 300 tinted chips would read as a page covered in
   status. One coloured word each is enough. */
.kbadge--pk {
  background: var(--glass-2);
  color: var(--key-pk);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.kbadge--fk {
  background: var(--glass-2);
  color: var(--key-fk);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

/* ---- the chrome that stays put -------------------------------------------
   Scrolling a 43-table list, or a graph as tall as the window, used to carry
   away the controls for the thing being scrolled: which section you are in,
   which mode the graph is in, the search over the list you are reading. They
   are pinned now, in the order they are read, the section tabs first, then the
   view's own bar under them, then the panel controls under that.

   `--sch-tabs-h` / `--sch-chrome-h` are measured after each render (see
   stickSchemaChrome in js/app.js) rather than hard-coded, so a bar that wraps
   on a narrow window still parks the next one below it instead of under it. */
.ds-schema {
  display: flex;
  flex-direction: column;
  --sch-tabs-h: 44px;
  --sch-chrome-h: 88px;
}
/* The cushion above the tabs is padding on the bar, cancelled by an equal
   negative margin: at rest the strip sits exactly where it always did, and the
   moment it pins, that padding is the air between it and the top edge, so it
   reads as a bar the page slides under, not as a strip stuck to the ceiling.

   The 20px that used to sit BELOW the strip as margin is padding now, for the
   same reason from the other side: a margin is outside the box, so the frosted
   fill stopped at the rule and rows surfaced in the gap underneath it before
   passing behind. Inside the box, the fill covers the whole band. The rule
   itself moves to ::after so it stays where it always drew, level with the
   selected tab's own indicator, instead of dropping to the new box edge. */
.ds-schema__tabs {
  position: sticky;
  top: 0;
  z-index: 14;
  padding-top: var(--sp-16);
  margin-top: calc(var(--sp-16) * -1);
  padding-bottom: var(--sp-20);
  margin-bottom: 0;
  border-bottom: 0;
  background: color-mix(in srgb, var(--acard) 86%, transparent);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}
.ds-schema__tabs::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: var(--sp-20);
  height: 1px;
  background: var(--panel-stroke);
}
/* the bar belongs to the view under the tabs, so only the views that HAVE one
   pin it, Raw Schema keeps its tabs and lets everything else scroll */
.sch-er .sch-bar {
  position: sticky;
  top: var(--sch-tabs-h);
  z-index: 13;
  padding: 10px 0;
  background: color-mix(in srgb, var(--acard) 86%, transparent);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

/* ============ 1 · RAW SCHEMA ============ */
.sch-raw {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* search + Sync schema share one row (wireframe 8087:2460) */
.sch-searchrow {
  display: flex;
  align-items: stretch;
  gap: 12px;
}
.sch-search {
  flex: 1;
  min-width: 0;
  height: 48px;
  border-radius: var(--r-lg);
  background: var(--card);
  border-color: var(--card-stroke);
  padding: 0 16px;
  gap: 12px;
}

/* Sync schema. Two lines: the action, and when the structure was last read -
   which is the whole reason to press it. Neutral like every other control on
   this page; the brand colour is not a button treatment here. */
.sch-sync {
  display: flex;
  align-items: center;
  gap: 11px;
  flex: none;
  height: 48px;
  padding: 0 18px;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: var(--r-lg);
  cursor: pointer;
  text-align: start;
  font-family: var(--font-sans);
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.sch-sync:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.sch-sync:disabled {
  cursor: default;
}
.sch-sync:disabled:hover {
  background: var(--card);
  border-color: var(--card-stroke);
}
.sch-sync__ic {
  color: var(--ricon);
  display: grid;
  place-items: center;
  flex: none;
}
.sch-sync.is-syncing .sch-sync__ic svg {
  animation: dsSyncSpin 0.8s linear infinite;
}
.sch-sync__main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.sch-sync__t {
  font-weight: 600;
  font-size: 13px;
  line-height: 18px;
  color: var(--ink);
}
.sch-sync__s {
  font-size: 11px;
  line-height: 15px;
  color: var(--text-faint);
}
.sch-search svg {
  color: var(--ricon);
  flex: none;
}
.sch-search input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
}
.sch-search input::placeholder {
  color: var(--text-faint);
}
.sch-search__x {
  background: none;
  border: 0;
  padding: 2px;
  cursor: pointer;
  color: var(--ricon);
  display: grid;
  place-items: center;
  border-radius: var(--r-xs);
}
.sch-search__x:hover {
  color: var(--ink);
}

/* one row: the finding leads, the count sits where it always has. */
.sch-legend {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 0 2px;
}
.sch-legend__n {
  font-size: 13px;
  color: var(--sub);
  white-space: nowrap;
  margin-inline-start: auto;
}
.sch-legend__n b {
  color: var(--ink);
  font-weight: 600;
}
/* the page's one gap sentence. Amber, never the brand orange, orange on this
   page means "press me", and this is a finding, not an action. Drawn only when
   the count is above zero, so the quiet state has no line at all. */
.sch-gaps {
  color: var(--danger);
  font-size: 12.5px;
}

.sch-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.sch-tbl {
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: var(--r-lg);
  scroll-margin-top: calc(var(--sch-tabs-h) + 12px);
  transition: border-color 0.18s var(--ease);
}
.sch-tbl:hover {
  border-color: var(--border-hover);
} /* DS interactive-surface state, 5925:22628 */
.sch-tbl.is-flash {
  border-color: var(--accent-border);
  box-shadow: 0 0 0 1px var(--accent-border);
}
/* the column the gap marker took you to, flashed the same way its table is */
.sch-cols tr.is-flash > td {
  background: var(--warning-soft);
}
.sch-cols tr > td {
  transition: background 0.3s var(--ease);
}
/* 18/16 was balancing a two-line block (name over description). The
   description moved onto hover, so the row is one line and the padding is
   even again. */
.sch-tbl__hd {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 16px 20px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: start;
  font-family: var(--font-sans);
}
.sch-tbl__hd:disabled {
  cursor: default;
}
.sch-tbl__ic {
  width: 32px;
  height: 32px;
  border-radius: var(--r-md);
  background: var(--pill);
  color: var(--ricon);
  display: grid;
  place-items: center;
  flex: none;
}
.sch-tbl__id {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}
.sch-tbl__top {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.sch-tbl__nm {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 15px;
  color: var(--ink);
  letter-spacing: -0.01em;
}
/* the gap marker leads the row's meta, left of the column count, it is the
   one thing here that is worth stopping for, so it is read first */
/* ---- table completeness marker, rebuilt ----------------------------------
   The icon and the label are centred against each other by an explicit grid
   row, not by inherited flex alignment. The previous version inherited
   align-items:baseline from `.sch-tbl__meta>span` (a more specific selector
   than .sch-gap itself, so it silently won), and an SVG's baseline is its
   BOTTOM edge, which is exactly why the triangle sat low against the text.
   The `>span:not(.sch-gap)` below keeps that rule off this element for good.

   Three tones on one number: under 5% is a neutral fact, 5-10% is worth a
   look, 10%+ means answers drawn from this table will be wrong. Only the two
   upper bands carry the warning glyph; a neutral chip does not need one. */
.sch-gap {
  display: inline-grid;
  grid-auto-flow: column;
  align-items: center;
  justify-items: center;
  gap: 7px;
  height: 26px;
  padding: 0 11px;
  border-radius: var(--r-full);
  border: 0;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease);
}
.sch-gap svg {
  display: block;
  width: 13px;
  height: 13px;
  flex: none;
}
.sch-gap__t {
  display: block;
  line-height: 1;
}
.sch-gap--low {
  background: var(--glass-2);
  color: var(--sub);
}
.sch-gap--low:hover {
  background: var(--surface-hover);
  color: var(--ink);
}
.sch-gap--mid {
  background: var(--warning-soft);
  color: var(--warning);
}
.sch-gap--high {
  background: var(--danger-soft);
  color: var(--danger);
}
.sch-gap--mid:hover,
.sch-gap--high:hover {
  filter: brightness(1.14);
}
.sch-tbl__meta {
  display: flex;
  align-items: center;
  gap: 18px;
  flex: none;
  font-size: 12px;
  color: var(--sub);
  white-space: nowrap;
}
.sch-tbl__meta > span:not(.sch-gap) {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  justify-content: flex-end;
}
.sch-tbl__meta > span.sch-cnt--cols {
  min-width: 88px;
}
.sch-tbl__meta > span.sch-cnt--rows {
  min-width: 98px;
}
.sch-gap {
  margin-inline-end: 2px;
}
.sch-tbl__meta b {
  color: var(--ink);
  font-weight: 600;
  font-size: 13px;
}
.sch-tbl__chev {
  width: 20px;
  flex: none;
  color: var(--ricon);
  display: grid;
  place-items: center;
  transition: transform 0.2s var(--ease);
}
.sch-tbl.is-open .sch-tbl__chev {
  transform: rotate(180deg);
}
.sch-tbl__body {
  padding: 20px 20px 8px;
}
.sch-tbl__note {
  font-size: 12px;
  color: var(--text-faint);
  padding: 0 12px 8px;
}

/* the column table is .p-table, the app's data table, with mono cells */
.sch-cols {
  direction: ltr;
}
/* ---- table header (Raw Schema + the four Semantic tables) ----
   Three problems, one fix. The header sat almost on top of the table name above
   it; its rule was the same hairline as every row rule, so nothing said "this is
   where the data starts"; and the labels were --sub, the same tone as half the
   text on the page, so they read as another row rather than as the key to one.

   Scoped to the Schema tables on purpose: .p-table is also the answer-card
   widget table, and that one is fine as it is. */
.sch-cols th,
.sch-tblcard th {
  padding: 0 12px 15px;
  color: var(--ink-2); /* was --sub: the label now leads the column */
  font-size: 10.5px;
  letter-spacing: 0.09em; /* small uppercase needs the air to stay legible */
  border-bottom: 1px solid var(--border-strong); /* the one rule heavier than the row hairlines */
}
.sch-cols td {
  padding: 13px 12px;
  vertical-align: top;
}
/* NB: never `display:flex` on a <td>. It stops being a table-cell, drops out of
   the row's height calculation, and its border-bottom then paints at its own
   content height instead of the row's, which reads as broken, staggered rules
   across the table. Stack with block children, or flex an inner wrapper. */
.sch-cols .sch-col {
  white-space: nowrap;
}
.sch-col__n {
  display: block;
  color: var(--ink);
  font-weight: 500;
  font-size: 13px;
}
/* "used in N answers", quiet by design: it is evidence, not a label. Absent
   entirely for a column nothing has touched yet. */
.sch-used {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  line-height: 15px;
  color: var(--text-faint);
  font-family: var(--font-sans);
}
.sch-cols .sch-type {
  color: var(--sub);
  font-size: 12.5px;
  white-space: nowrap;
}
.sch-cols {
  table-layout: fixed;
}
.sch-w0 {
  width: 27%;
}
.sch-w1 {
  width: 15%;
}
.sch-w2 {
  width: 37%;
}
.sch-w3 {
  width: 21%;
}

/* ---- COMPLETENESS (replaces CONSTRAINTS) ----
   Blank unless the column can be empty, so the column reads as a short list of
   things worth knowing rather than a wall of "not null".
   Left-aligned like every other column on this table: a value that starts on a
   different vertical line from its own label is the eye's problem, not the
   layout's convenience. */
.sch-comp {
  text-align: start;
}
.sch-comp__wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  outline: none;
}
.sch-comp__bar {
  width: 58px;
  height: 5px;
  border-radius: 3px;
  background: var(--glass-2);
  overflow: hidden;
  flex: none;
}
.sch-comp__bar i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--text-faint);
}
.sch-comp__bar.is-high i {
  background: var(--warning);
}
.sch-comp__chip {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 9px;
  border-radius: var(--r-full);
  background: var(--glass-2);
  color: var(--sub);
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
  flex: none;
}
.sch-comp__chip.is-high {
  background: var(--warning-soft);
  color: var(--warning);
}

/* hover card. The DS menu surface (--menu-bg on --panel-stroke), opening above
   and to the leading side so it never leaves the table card. */
.sch-tip {
  position: absolute;
  bottom: calc(100% + 10px);
  inset-inline-start: 0;
  z-index: 40;
  width: 302px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 13px 15px;
  text-align: start;
  white-space: normal;
  background: var(--menu-bg);
  border: 1px solid var(--panel-stroke);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-xl);
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  pointer-events: none;
  transition:
    opacity 0.15s var(--ease),
    transform 0.15s var(--ease),
    visibility 0.15s;
}
.sch-comp__wrap:hover .sch-tip,
.sch-comp__wrap:focus-visible .sch-tip {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.sch-tip__lbl {
  font-weight: 600;
  font-size: 9.5px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sch-tip__rule {
  font-size: 13px;
  color: var(--ink);
}
.sch-tip__p {
  font-size: 12px;
  line-height: 18px;
  color: var(--sub);
}

/* ---- hover card (.hint) ---------------------------------------------------
   One explanation component, used by every mark on this page that carries
   meaning the layout no longer spells out: PK, FK, the ACTIVITY/REFERENCE
   class tag, and a table's own description. The mark stays; the sentence
   arrives only when someone asks for it by pointing at it. Same surface as the
   completeness card above (--menu-bg on --panel-stroke) so the page has one
   hover language, not two. */
/* the hovered mark lifts its whole subtree, or the card is painted over by the
   NEXT card in the list, later siblings win at equal stacking level, and a
   z-index on the card alone cannot beat that from inside */
.hint {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
}
.hint:hover,
.hint:focus-within {
  z-index: 70;
}
.hint__card {
  position: absolute;
  top: calc(100% + 8px);
  inset-inline-start: 0;
  z-index: 60;
  width: 250px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 11px 13px;
  text-align: start;
  white-space: normal;
  background: var(--menu-bg);
  border: 1px solid var(--panel-stroke);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-xl);
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  pointer-events: none;
  transition:
    opacity 0.15s var(--ease),
    transform 0.15s var(--ease),
    visibility 0.15s;
}
.hint:hover .hint__card,
.hint:focus-visible .hint__card {
  opacity: 1;
  visibility: visible;
  transform: none;
}
/* a badge in the trailing column has no room to open that way */
.hint--end .hint__card {
  inset-inline-start: auto;
  inset-inline-end: 0;
}
.hint__card--wide {
  width: 292px;
}
.hint__t {
  font-weight: 600;
  font-size: 12px;
  color: var(--ink);
}
.hint__p {
  font-size: 12px;
  line-height: 17px;
  color: var(--sub);
}
/* the columns behind the number, each with its own share */
.hint__list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 2px 0 4px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: var(--glass);
}
.hint__li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: 11.5px;
  color: var(--sub);
}
.hint__li b {
  font-weight: 500;
  color: var(--ink);
  font-size: 11.5px;
}
.hint__li--more {
  color: var(--text-faint);
}
.hint__hint {
  font-size: 11px;
  line-height: 16px;
  color: var(--text-faint);
}
/* the marker is itself the hover trigger now, so it keeps its own pill look */
.hint.sch-gap {
  cursor: pointer;
}

/* the one line that explains why most cells are blank */
.sch-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 10px 12px 4px;
  font-size: 12px;
  line-height: 17px;
  color: var(--text-faint);
}
.sch-note svg {
  flex: none;
  margin-top: 1px;
  color: var(--ricon);
}
.sch-key {
  white-space: nowrap;
}
.sch-key__in {
  display: flex;
  align-items: center;
  gap: 9px;
}
.sch-fk {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--sub);
  border-bottom: 1px dashed var(--line-strong);
}
.sch-fk:hover {
  color: var(--ink);
  border-bottom-color: var(--border-hover);
}
.sch-dash {
  color: var(--text-faint);
}
.sch-cols mark,
.er-rail__nm mark {
  background: var(--accent-soft);
  color: var(--ink);
  border-radius: 3px;
  padding: 0 2px;
}
.sch-tbl__nm mark {
  background: var(--accent-soft);
  color: var(--ink);
  border-radius: 3px;
  padding: 0 2px;
}

.sch-more {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 48px;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: var(--r-lg);
  color: var(--ink-2);
  cursor: pointer;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 13px;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.sch-more:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
  color: var(--ink);
}
.sch-more svg {
  color: var(--ricon);
}
.sch-more svg.is-up {
  transform: rotate(180deg);
}
.sch-empty {
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: var(--r-lg);
}

/* ============ 2 · ENTITY RELATIONSHIPS ============
   Built to the ER reference (Figma 8087:7022). Everything below is that
   frame's geometry expressed in our own tokens: the same 1px hairline every
   card on this page uses, the same --r-sm/--r-md radii, the same mono for
   identifiers, and two hues that already mean something here, Emerald for a
   REFERENCE table (a thing that exists) and Cyan for an ACTIVITY one (a thing
   that happens). The reference paints the second one blue; the kit has no blue
   ramp, and Cyan is already the cool step this page reads as "a key into
   another table" (see --key-fk in tokens.css), so it is the one hue that can
   carry it without inventing a colour. */
.sch-er {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* fact on the left, instruction beside it, controls hard right, one bar, one
   reading order, identical on all four ER states */
.sch-bar {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}
.sch-bar__note {
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
  min-width: 0;
}
.sch-bar__note b {
  color: var(--ink);
  font-weight: 600;
}
.sch-bar__note code {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink);
  background: var(--glass);
  border-radius: var(--r-xs);
  padding: 2px 6px;
}
.sch-bar__hint {
  font-size: 13px;
  line-height: 20px;
  color: var(--text-faint);
  min-width: 0;
}
/* the controls are pinned to the trailing edge of the bar, not parked after the
   sentence: the note and hint grow and shrink with the state, and controls that
   move with them would never be twice in the same place. The whole cluster
   (mode switch + full screen) takes the margin, not just the .seg inside it. */
.sch-bar__ctl {
  margin-inline-start: auto;
}

/* the rail's width is a variable so collapsing it is one value, in one place,
   for both the in-page grid and the full-screen one */
.er-wrap {
  display: grid;
  grid-template-columns: var(--er-rail-w, 272px) minmax(0, 1fr);
  gap: 0;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: var(--r-lg);
  overflow: clip;
  height: 732px;
  transition: grid-template-columns 0.22s var(--ease);
}
.er-wrap:has(.er-panel) {
  grid-template-columns: var(--er-rail-w, 272px) minmax(0, 1fr) var(
      --er-panel-w,
      294px
    );
}
.er-wrap:has(.er-rail.is-collapsed) {
  --er-rail-w: 52px;
}
.er-wrap:has(.er-panel.is-collapsed) {
  --er-panel-w: 52px;
}

/* ---- left rail ---- */
.er-rail {
  border-inline-end: 1px solid var(--border-subtle);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* width:auto is load-bearing: .input sets width:100%, and 100% of the rail's
   content box PLUS these side margins is wider than the rail, so the field
   pushed 15px out past the divider. auto lets the block flex fill what is
   actually left after the margins. */
/* search + the collapse control share one row, so the control sits on the
   rail's own trailing edge rather than floating over the list below it */
/* the search and the fold control stay reachable while the list scrolls under
   them AND while the page scrolls past the whole block */
.er-rail__top {
  position: sticky;
  top: var(--sch-chrome-h, 0px);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 16px 10px;
  background: var(--card);
}
.er-rail__search {
  width: auto;
  flex: 1;
  min-width: 0;
  margin: 0;
  background: var(--glass);
  border-color: transparent;
  border-radius: var(--r-md);
}
.er-rail__tog {
  width: 30px;
  height: 30px;
  flex: none;
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--ricon);
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease);
}
.er-rail__tog:hover {
  background: var(--glass);
  color: var(--ink);
}
.er-rail__tog svg {
  width: 16px;
  height: 16px;
}
/* collapsed: the tail keeps the control and the one fact worth keeping, how
   many entities are waiting behind it */
.er-rail.is-collapsed {
  align-items: center;
  padding: 14px 0;
  gap: 10px;
  overflow: hidden;
}
.er-rail.is-collapsed .er-rail__tail {
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.er-rail__search svg {
  color: var(--ricon);
  flex: none;
}
.er-rail__search input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 13px;
}
.er-rail__search input::placeholder {
  color: var(--text-faint);
}
.er-rail__body {
  flex: 1;
  overflow-y: auto;
  padding: 0 10px 14px;
}
/* Rows are spaced, not stacked. Lit rows carry a fill, and with no gap a run of
   consecutive ones fused into a single slab, you could not tell three lit rows
   from one tall one. The gap lives on the group so the rhythm is identical in
   the resting state, which is what keeps the list from twitching when a
   selection turns the fills on. */
.er-rail__grp {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 16px;
}
.er-rail__lbl {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px 4px;
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.er-rail__row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 12px;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  cursor: pointer;
  text-align: start;
  transition:
    background 0.14s var(--ease),
    opacity 0.14s var(--ease),
    box-shadow 0.14s var(--ease);
}
.er-rail__row:hover {
  background: var(--glass);
}
/* Three levels, and they have to be told apart at a glance:
   DIM  = outside the neighbourhood, faded right back
   NEAR = supporting cast, a quiet fill and nothing else
   SEL  = the one you clicked. It gets the fill, a ring, a full-height marker
          bar and the only bright, weighted label in the list. Previously it
          differed from NEAR by 3% of fill opacity and a 2px stub, which is not
          a difference you can see. */
.er-rail__row.is-near {
  background: var(--glass);
}
.er-rail__row.is-near .er-rail__nm {
  color: var(--ink-2);
}
.er-rail__row.sel {
  background: var(--glass-2);
  box-shadow: inset 0 0 0 1px var(--border-hover);
}
.er-rail__row.sel::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 3px;
  bottom: 3px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--ink);
}
.er-rail__row.sel .er-rail__nm {
  color: var(--ink);
  font-weight: 500;
}
.er-rail__row.sel .er-rail__n {
  color: var(--ink-2);
}
.er-rail__row.sel .er-dot {
  box-shadow: 0 0 0 3px var(--glass);
}
.er-rail__row.is-dim {
  opacity: 0.34;
}
.er-rail__nm {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.er-rail__n {
  font-size: 11px;
  color: var(--text-faint);
  flex: none;
  font-variant-numeric: tabular-nums;
}
.er-rail__none {
  padding: 14px 12px;
  font-size: 12px;
  color: var(--text-faint);
}

/* the two classes, one dot ---------------------------------------------------
   Used by the rail, every node, the legend, the side panel and the second-hop
   chips, so the colour is learned once and then holds everywhere. */
.er-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  display: block;
}
.er-dot--reference {
  background: var(--success);
}
.er-dot--activity {
  background: var(--cyan-40);
}
[data-theme="light"] .er-dot--activity {
  background: var(--cyan-60);
}

/* ---- canvas ---- */
/* min-height:0 + height:100% keep the graph inside .er-wrap so .er-canvas
   scrolls tall diagrams instead of growing past overflow:clip */
.er-main {
  position: relative;
  min-width: 0;
  min-height: 0;
  height: 100%;
  display: flex;
  overflow: hidden;
}
.er-canvas {
  flex: 1;
  min-width: 0;
  min-height: 0;
  height: 100%;
  overflow: auto;
  padding: 28px;
  direction: ltr;
  display: flex;
  align-items: safe center;
  justify-content: safe center;
}
.er-fit {
  position: relative;
  flex: none;
}
.er-stage {
  position: relative;
  transform-origin: 0 0;
  transition: transform 0.2s var(--ease);
}
.er-edges {
  position: absolute;
  inset: 0;
  overflow: visible;
  pointer-events: none;
}
/* the port layer rides above the nodes; the edges themselves stay behind them */
.er-edges--top {
  z-index: 4;
}
.er-edges path {
  fill: none;
  stroke: var(--border-strong);
  stroke-width: 1.25;
  transition: opacity 0.18s var(--ease);
}
/* a selection dims the map rather than hiding it, the shape of the whole
   database stays readable behind the answer */
.er-canvas.has-sel .er-edges path {
  opacity: 0.22;
}
.er-edges path.is-live {
  stroke-width: 1.6;
  opacity: 1;
}
.er-edges path.is-live.is-reference {
  stroke: var(--success);
}
.er-edges path.is-live.is-activity {
  stroke: var(--cyan-40);
}
[data-theme="light"] .er-edges path.is-live.is-activity {
  stroke: var(--cyan-60);
}
/* the ring where a live line meets a node: hollow, so the line reads through it */
.er-port {
  fill: var(--card);
  stroke-width: 1.6;
}
.er-port.is-reference {
  stroke: var(--success);
}
.er-port.is-activity {
  stroke: var(--cyan-40);
}
[data-theme="light"] .er-port.is-activity {
  stroke: var(--cyan-60);
}

/* subject-area label, sits over its own column, not over the whole canvas */
.er-col__t {
  position: absolute;
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding-inline-start: 2px;
}

/* ---- overview node ---- */
.er-node {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  cursor: pointer;
  text-align: start;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease),
    opacity 0.18s var(--ease);
}
.er-node:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.er-node__nm {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* selected · in the selection · out of it. The selected node keeps the neutral
   surface treatment the app uses for "on" everywhere else (#fff at 5%), so the
   emphasis never competes with the two meaning-carrying hues. */
.er-node.is-sel {
  background: var(--glass-2);
}
.er-node.is-sel .er-node__nm {
  color: var(--ink);
  font-weight: 500;
}
.er-node.is-lit {
  background: var(--surface-raised);
}
/* a node in the selection takes its own class colour on the stroke, so the
   fan's two hues are legible on the nodes as well as on the lines */
.er-node.is-sel.is-reference,
.er-node.is-lit.is-reference {
  border-color: color-mix(in srgb, var(--success) 55%, transparent);
}
.er-node.is-sel.is-activity,
.er-node.is-lit.is-activity {
  border-color: color-mix(in srgb, var(--cyan-40) 55%, transparent);
}
[data-theme="light"] .er-node.is-sel.is-activity,
[data-theme="light"] .er-node.is-lit.is-activity {
  border-color: color-mix(in srgb, var(--cyan-60) 55%, transparent);
}
.er-node.is-sel {
  border-width: 1.5px;
}
.er-node.is-off {
  opacity: 0.32;
}
.er-node.is-off:hover {
  opacity: 0.7;
}

/* ---- canvas floor: legend, or the selection's own bar ---- */
.er-legend,
.er-selbar {
  position: absolute;
  bottom: 20px;
  inset-inline-start: 28px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 10px;
}
.er-legend {
  gap: 26px;
}
.er-legend span {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  color: var(--text-faint);
}
.er-selbar__chip {
  display: flex;
  align-items: center;
  gap: 9px;
  height: 30px;
  padding: 0 16px;
  border-radius: var(--r-full);
  background: var(--glass-2);
  color: var(--ink);
  font-size: 12.5px;
  font-weight: 600;
}
.er-selbar__clear {
  height: 30px;
  padding: 0 18px;
  border-radius: var(--r-full);
  background: none;
  border: 1px solid var(--line-strong);
  color: var(--ink-2);
  font-family: var(--font-sans);
  font-size: 12.5px;
  cursor: pointer;
  transition:
    border-color 0.15s var(--ease),
    color 0.15s var(--ease);
}
.er-selbar__clear:hover {
  border-color: var(--border-hover);
  color: var(--ink);
}

/* ---- focus: the opened table ---- */
.er-card {
  position: absolute;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  border: 1.5px solid var(--border-hover);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}
.er-card__hd {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  flex: none;
  border-bottom: 1px solid var(--border-subtle);
}
.er-card__id {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.er-card__nm {
  font-family: var(--font-mono);
  font-size: 14px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.er-card__sub {
  font-size: 11px;
  line-height: 14px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.er-card__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 14px;
  flex: none;
}
.er-card__cn {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the identifying row is lifted; a joining row is tinted with the class of the
   table it points at, so the row and the line leaving it are the same colour */
.er-card__row.is-pk {
  background: var(--glass);
}
.er-card__row.is-pk .er-card__cn {
  color: var(--ink);
}
.er-card__row.is-fk.is-reference {
  background: var(--success-soft);
}
.er-card__row.is-fk.is-activity {
  background: rgba(34, 211, 238, 0.11);
}
[data-theme="light"] .er-card__row.is-fk.is-activity {
  background: rgba(8, 145, 178, 0.1);
}
.er-card__row.is-fk .er-card__cn {
  color: var(--ink);
}
/* the PK/FK badge is the Raw Schema tab's badge, unchanged, the same mark
   cannot mean one thing on one tab and another on the next */
.er-card .key {
  flex: none;
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 3px 6px;
  border-radius: var(--r-xs);
  background: var(--glass-2);
}
.er-card .key--pk {
  color: var(--key-pk);
}
.er-card .key--fk {
  color: var(--key-fk);
}

/* ---- focus: a neighbour ---- */
.er-pill {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 11px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  cursor: pointer;
  text-align: start;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
/* a neighbour carries its own class on the stroke, so which side of the card a
   table sits on and what kind of table it is are readable at the same glance */
.er-pill.is-reference,
.er-card.is-reference {
  border-color: color-mix(in srgb, var(--success) 55%, transparent);
}
.er-pill.is-activity,
.er-card.is-activity {
  border-color: color-mix(in srgb, var(--cyan-40) 55%, transparent);
}
[data-theme="light"] .er-pill.is-activity,
[data-theme="light"] .er-card.is-activity {
  border-color: color-mix(in srgb, var(--cyan-60) 55%, transparent);
}
.er-pill:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.er-pill__nm {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the parents stack flush against the rows they join, one hairline between
   any two of them, the reference's contiguous stack, without its overlap */
.er-pill--l {
  margin-top: 0;
}

/* ---- focus: the second hop ---- */
.er-hop2 {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 9px;
  max-width: 120px;
  border-radius: var(--r-full);
  background: var(--surface);
  border: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 10.5px;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.er-hop2--reference {
  color: var(--success);
  border-color: color-mix(in srgb, var(--success) 42%, transparent);
}
.er-hop2--activity {
  color: var(--cyan-40);
  border-color: color-mix(in srgb, var(--cyan-40) 42%, transparent);
}
[data-theme="light"] .er-hop2--activity {
  color: var(--cyan-60);
  border-color: color-mix(in srgb, var(--cyan-60) 42%, transparent);
}
/* a left-hand chip is anchored by its RIGHT edge, so a column of them lines up
   against the pill they belong to however long each name is */
.er-hop2--l {
  transform: translateX(-100%);
}
.er-hop2:hover {
  background: var(--surface-raised);
}
.er-hop2n {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

/* ---- floating canvas controls ---- */
/* a zero-width, zero-height sticky rail: it takes no space in the flex row and
   parks the control over the canvas, following the page down */
.er-float {
  position: sticky;
  top: calc(var(--sch-chrome-h, 0px) + 16px);
  z-index: 5;
  width: 0;
  height: 0;
  flex: none;
  align-self: flex-start;
  overflow: visible;
}
.er-hops {
  position: absolute;
  top: 16px;
  inset-inline-start: 16px;
  z-index: 5;
  width: max-content;
}
.er-zoom {
  position: absolute;
  bottom: 16px;
  inset-inline-end: 16px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  background: var(--menu-bg);
  border: 1px solid var(--panel-stroke);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
}
.er-zoom__b {
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--r-xs);
  background: none;
  color: var(--ricon);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.er-zoom__b:hover {
  background: var(--glass);
  color: var(--ink);
}
.er-zoom__b:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.er-zoom__b:disabled:hover {
  background: none;
  color: var(--ricon);
}
.er-zoom__v {
  min-width: 44px;
  text-align: center;
  font-size: 12px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* ---- focus side panel ---- */
/* clip, not hidden, same reason as .ds-shell above: `hidden` would make this a
   scroll container and the sticky head inside it would never reach the page */
.er-panel {
  border-inline-start: 1px solid var(--border-subtle);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow: clip;
  min-height: 0;
}
.er-panel__hd {
  position: sticky;
  top: var(--sch-chrome-h, 0px);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 6px;
  background: var(--card);
}
/* the collapse control takes the panel's own trailing edge, mirroring the
   rail's, the two sides of the canvas fold the same way */
.er-panel__tog {
  width: 30px;
  height: 30px;
  flex: none;
  margin-inline-start: auto;
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--ricon);
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease);
}
.er-panel__tog:hover {
  background: var(--glass);
  color: var(--ink);
}
.er-panel__tog svg {
  width: 16px;
  height: 16px;
}
.er-panel.is-collapsed {
  align-items: center;
  padding: 14px 0;
  gap: 10px;
  overflow: hidden;
}
.er-panel.is-collapsed .er-panel__tog {
  margin-inline-start: 0;
}
.er-panel.is-collapsed .er-panel__tail {
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.er-panel__nm {
  font-family: var(--font-mono);
  font-size: 15px;
  color: var(--ink);
}
.er-panel__desc {
  font-size: 12.5px;
  line-height: 18px;
  color: var(--sub);
}
.er-panel__stats {
  display: flex;
  gap: 26px;
  padding: 6px 0 10px;
  border-bottom: 1px solid var(--border-subtle);
}
.er-panel__stats div {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.er-panel__stats b {
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.er-panel__stats span {
  font-size: 11px;
  color: var(--text-faint);
}
.er-panel__lbl {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 2px;
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.er-panel__rels {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  margin-inline: -10px;
}
.er-rel {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  cursor: pointer;
  text-align: start;
  border-bottom: 1px solid var(--border-subtle);
}
.er-rel:last-child {
  border-bottom: 0;
}
.er-rel:hover {
  background: var(--glass);
}
.er-rel .er-dot {
  margin-top: 5px;
}
.er-rel__b {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.er-rel__t {
  font-size: 12.5px;
  line-height: 17px;
  color: var(--ink-2);
}
.er-rel__t b {
  color: var(--ink);
  font-weight: 600;
}
.er-rel__j {
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 15px;
  color: var(--success);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.er-panel__open {
  width: 100%;
  justify-content: center;
  margin-top: 6px;
  flex: none;
}

/* ---- the opened table card, as Find path draws it ------------------------
   Find path stacks the same idea as Focus's centre card, but for every table on
   the route at once, so it keeps its own flatter version: no lifted rows, an
   accent tint on the columns that do the joining, and a step number. These are
   the only rules the .er-node--tbl markup needs; the graph's own node above is
   a single collapsed chip and shares nothing with it but the dot. */
.er-node--tbl {
  position: absolute;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: 0;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.er-node--path {
  cursor: default;
}
.er-node__hd {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 12px;
  height: 46px;
  flex: none;
  border-bottom: 1px solid var(--border-subtle);
}
.er-node__id {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.er-node__sub {
  font-size: 10.5px;
  line-height: 14px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.er-node__cols {
  display: flex;
  flex-direction: column;
}
.er-node__col {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 26px;
  padding: 0 12px;
  border-bottom: 1px solid var(--border-subtle);
}
.er-node__col:last-child {
  border-bottom: 0;
}
.er-node__cn {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.er-node__ct {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-faint);
  flex: none;
}
.er-k {
  width: 12px;
  height: 12px;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--text-faint);
}
.er-k--pk {
  color: var(--key-pk);
}
.er-k--fk {
  color: var(--key-fk);
}
.er-node__step {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 600;
  flex: none;
}
.er-node--path .er-node__col.is-join {
  background: var(--accent-soft);
}
.er-node--path .er-node__col.is-join .er-node__cn {
  color: var(--ink);
}
.er-edges--path path {
  stroke: var(--accent);
  stroke-width: 1.8;
}

/* ---- Find path ---- */
.er-path {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.er-path__bar {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 14px 18px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.er-path__lbl {
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.er-sel {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 46px;
  padding: 0 12px;
  min-width: 186px;
  background: var(--glass);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color 0.15s var(--ease);
}
.er-sel:hover {
  border-color: var(--border-hover);
}
.er-sel select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: 13px;
}
.er-sel__v {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
  pointer-events: none;
}
.er-sel__t {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.er-sel__s {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.er-sel svg {
  width: 15px;
  height: 15px;
  color: var(--ricon);
  flex: none;
  pointer-events: none;
}
.er-path__arw {
  color: var(--ricon);
  display: grid;
  place-items: center;
  flex: none;
}
.er-path__go {
  flex: none;
}
.er-path__n {
  margin-inline-start: auto;
  font-size: 12px;
  color: var(--sub);
  white-space: nowrap;
}
.er-path__n b {
  color: var(--ink);
  font-weight: 600;
}
/* the plain-English reading of the route, the app's existing callout shape:
   an accent rule on the leading edge, no fill, no icon */
.er-path__say {
  padding: 2px 0 2px 16px;
  border-inline-start: 2px solid var(--accent);
  font-size: 14px;
  line-height: 24px;
  color: var(--sub);
}
.er-path__say b {
  color: var(--ink);
  font-weight: 600;
}
.er-path__stage {
  padding: 0;
  overflow: hidden;
  background: var(--card);
  border-color: var(--card-stroke);
}
.er-path__stage.is-alt {
  opacity: 0.85;
}
.er-canvas--path {
  padding: 24px 28px 20px;
  justify-content: flex-start;
}
.er-canvas--path .er-stage {
  flex: none;
}
.er-join {
  position: absolute;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 7px 12px;
  background: var(--glass);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  white-space: nowrap;
}
.er-join__e {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-2);
}
.er-join__c {
  font-size: 10px;
  color: var(--text-faint);
}
.er-path__alt {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px;
  background: var(--glass);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  font-size: 12.5px;
  color: var(--sub);
}
.er-path__alt svg {
  color: var(--ricon);
  flex: none;
}
.er-path__alt span {
  flex: 1;
  min-width: 0;
}
.er-path__alt b {
  color: var(--ink);
  font-weight: 600;
}
.er-path__alt .ds-link {
  flex: none;
}

/* ============ 3 · SEMANTIC INTELLIGENCE ============ */
.sch-sem {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.sch-sem__note {
  margin-bottom: -2px;
}

.sch-stats {
  flex-direction: row;
  padding: 0;
  gap: 0;
  background: var(--card);
  border-color: var(--card-stroke);
  overflow: hidden;
}
.sch-stat {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 18px 20px;
  border-inline-end: 1px solid var(--border-subtle);
  min-width: 0;
}
.sch-stat:last-child {
  border-inline-end: 0;
}
.sch-stat b {
  font-size: 22px;
  font-weight: 700;
  line-height: 28px;
  letter-spacing: -0.01em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.sch-stat span {
  font-size: 12px;
  line-height: 16px;
  color: var(--sub);
}

.sch-cov {
  flex-direction: row;
  align-items: center;
  gap: 20px;
  padding: 16px 20px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sch-cov__l,
.sch-cov__r {
  font-size: 12.5px;
  color: var(--sub);
  white-space: nowrap;
  flex: none;
}
.sch-cov__l b,
.sch-cov__r b {
  color: var(--ink);
  font-weight: 600;
}
.sch-cov__bar {
  flex: 1;
  min-width: 60px;
  height: 6px;
  border-radius: 3px;
  background: var(--glass-2);
  overflow: hidden;
}
.sch-cov__bar i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
}

.sch-sect {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sch-sect__hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 6px 2px 0;
}

.sch-domain {
  gap: 10px;
  padding: 20px 22px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sch-domain__hd {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.sch-domain__k {
  font-family: var(--font-mono);
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
}
.sch-domain__p {
  font-size: 13px;
  line-height: 21px;
  color: var(--sub);
  max-width: 820px;
}
.sch-domain__p code {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-2);
  background: var(--glass);
  border-radius: var(--r-xs);
  padding: 2px 5px;
}

.sch-tblcard {
  padding: 14px 20px 10px;
  background: var(--card);
  border-color: var(--card-stroke);
  overflow-x: auto;
}
.sch-tblcard .p-table {
  min-width: 640px;
}
.sch-tblcard td {
  vertical-align: middle;
}
.sch-tblcard .sch-col {
  color: var(--ink);
  font-weight: 500;
  font-size: 13px;
  white-space: nowrap;
}
.sch-tblcard .sch-expr {
  color: var(--ink-2);
  font-size: 12px;
}
.sch-tblcard .sch-src {
  color: var(--sub);
  font-size: 12px;
  white-space: nowrap;
}
.sch-tblcard .sch-type {
  color: var(--text-faint);
  font-size: 11.5px;
  white-space: nowrap;
}
.sch-tblcard .sch-grain {
  color: var(--sub);
  font-size: 13px;
}
.sch-tblcard .sch-conf {
  color: var(--text-faint);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.sch-alias {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  padding-top: 9px !important;
  padding-bottom: 9px !important;
}
.sch-chip {
  height: 26px;
  padding: 0 10px;
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--sub);
  background: var(--glass);
  border: 1px solid transparent;
}
.sch-chip--more {
  cursor: pointer;
  border-style: dashed;
  border-color: var(--line-strong);
  color: var(--ink-2);
}
.sch-chip--more:hover {
  background: var(--glass-2);
  color: var(--ink);
}

.sch-unmapped {
  gap: 14px;
  padding: 18px 20px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sch-unmapped__hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.sch-unmapped__hd p {
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
}
.sch-unmapped__hd b {
  color: var(--ink);
  font-weight: 600;
}
.sch-unmapped__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* ---- tablet band (≤1024px): the rail and the side panel stop being columns.
   Same rule the rest of the app follows, see RESPONSIVE.md. ---- */
@media (max-width: 1024px) {
  /* stacked, the three panes each need their own height, the desktop rule
     fixes .er-wrap at 640px so the rail can scroll inside a row, and carrying
     that into a single column would squeeze all three into one screen-height */
  .er-wrap,
  .er-wrap:has(.er-panel) {
    grid-template-columns: minmax(0, 1fr);
    height: auto;
    min-height: 0;
  }
  .er-rail {
    border-inline-end: 0;
    border-bottom: 1px solid var(--border-subtle);
    height: 260px;
  }
  .er-panel {
    border-inline-start: 0;
    border-top: 1px solid var(--border-subtle);
    overflow: visible;
  }
  .er-panel__rels {
    overflow: visible;
  }
  .er-canvas {
    height: 420px;
  }
  .sch-stats {
    flex-wrap: wrap;
  }
  .sch-stat {
    flex: 1 1 33%;
    border-bottom: 1px solid var(--border-subtle);
  }
  .sch-cov {
    flex-wrap: wrap;
  }
  .sch-cov__bar {
    flex: 1 1 100%;
    order: 3;
  }
}
@media (prefers-reduced-motion: reduce) {
  .er-stage,
  .sch-tbl__chev {
    transition: none;
  }
}

/* ============================================================
   ENTITY RELATIONSHIPS: FULL SCREEN
   Find path is gone; the control that replaced it opens the same two modes
   into the whole window, which is what a 24-table map actually needs.
   Mounted on <body> (see renderErFull) so no transformed ancestor can clip it.
   ============================================================ */
.sch-bar__ctl {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}
.er-expand {
  width: 38px;
  height: 38px;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--pill);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  color: var(--ricon);
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.er-expand:hover {
  background: var(--pill-hover);
  color: var(--ink);
  border-color: var(--border-hover);
}
.er-expand svg {
  width: 16px;
  height: 16px;
}

body.er-full-open {
  overflow: hidden;
}
.er-full {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  flex-direction: column;
  background: var(--page);
  animation: erFullIn 0.2s var(--ease) both;
}
@keyframes erFullIn {
  from {
    opacity: 0;
    transform: scale(0.995);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.er-full__bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface);
}
.er-full__id {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  font-size: 14px;
  color: var(--sub);
}
.er-full__id svg {
  color: var(--ricon);
  flex: none;
}
.er-full__id b {
  color: var(--ink);
  font-weight: 600;
}
.er-full__n {
  color: var(--text-faint);
  font-size: 12.5px;
  white-space: nowrap;
}

/* the graph fills what is left, and the rail scrolls inside it rather than
   stretching the window */
.er-wrap--full {
  flex: 1;
  min-height: 0;
  height: auto;
  border: 0;
  border-radius: 0;
  background: none;
}
.er-wrap--full .er-canvas {
  height: auto;
}
@media (max-width: 1024px) {
  .er-full__n {
    display: none;
  }
  .er-wrap--full,
  .er-wrap--full:has(.er-panel) {
    grid-template-columns: minmax(0, 1fr);
  }
}
@media (prefers-reduced-motion: reduce) {
  .er-full {
    animation: none;
  }
}

/* ---- full-screen Analytics subpage -----------------------------------------
   The five subpages are long lists, so they get the window rather than a slot
   inside the tab. Deliberately the SAME frame as .er-full above, fixed layer,
   own bar, one close control, because from the user's side "open this list
   big" and "open the graph big" are the same gesture, and two different
   full-screen treatments in one page would read as two different products.
   Reuses erFullIn, .er-expand for the close, and .ds-shell--bare for the body,
   so the only new things here are the bar's back control and the measure.
   ---------------------------------------------------------------------------- */
body.an-full-open {
  overflow: hidden;
}
/* The layer covers the viewport, so it also replaces the surface the content
   was sitting on. --page is the app's ground, several points darker than the
   card, so painting it here read as "the page went black" rather than "the
   list opened". It takes the MAIN CONTAINER's own colour instead (--acard, the
   .ds-shell card), which is the surface these rows sit on everywhere else.
   Flat rather than the shell's 45%-to-100% gradient: that ramp is calibrated
   for a card of known height and stretches to nothing over a full scrolling
   viewport. Ameer's call. */
.an-full {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  flex-direction: column;
  background: var(--acard);
  animation: erFullIn 0.2s var(--ease) both;
}
.an-full__bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-20);
  padding: var(--sp-12) var(--sp-20);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--box);
}
.an-full__bar {
  position: relative;
}
.an-full__id {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  min-width: 0;
  font-size: 14px;
  color: var(--sub);
}
.an-full__id svg {
  color: var(--ricon);
  flex: none;
}
.an-full__id b {
  color: var(--ink);
  font-weight: 600;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.an-full__n {
  color: var(--text-faint);
  font-size: 12.5px;
  white-space: nowrap;
}
/* a hairline between "leave" and "where you are", the bar reads as two things */
.an-full__div {
  flex: none;
  width: 1px;
  height: 18px;
  background: var(--border-subtle);
}
.an-full__back {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  font: inherit;
  font-size: 13.5px;
  color: var(--sub);
  background: none;
  border: 0;
  cursor: pointer;
  padding: var(--sp-4) var(--sp-6);
  border-radius: var(--r-sm);
  transition:
    color 0.15s var(--ease),
    background 0.15s var(--ease);
}
.an-full__back:hover {
  color: var(--ink);
  background: var(--pill-hover);
}
.an-full__back svg {
  flex: none;
}
/* the list scrolls INSIDE the layer, so the bar stays put and the page behind
   never scrolls with it */
.an-full__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* a measure, not the full window width, a 2000px-wide table row is unreadable.
   animation:none because the layer already animates in; the shell's own rise
   on top of that reads as two separate movements. */
.an-full__shell {
  max-width: 1180px;
  margin: 0 auto;
  animation: none;
  padding: var(--sp-28) var(--sp-24) var(--sp-64);
}
@media (max-width: 1024px) {
  .an-full__n {
    display: none;
  }
  .an-full__back span {
    display: none;
  }
  .an-full__shell {
    padding: var(--sp-20) var(--sp-16) var(--sp-48);
  }
}
@media (prefers-reduced-motion: reduce) {
  .an-full {
    animation: none;
  }
}

/* ---- full-screen presentation of a built dashboard --------------------------
   Same frame as .er-full / .an-full above, on purpose: fixed layer, one bar, one
   close. Reuses .an-full__bar / __id / __n / __body and .er-expand, so the only
   new things here are the stage's measure and the read-only block treatment.
   ---------------------------------------------------------------------------- */
body.dash-present-open {
  overflow: hidden;
}
.dash-present {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  animation: erFullIn 0.2s var(--ease) both;
}
.dpr-ctl {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}
/* THE "you are looking at a presentation" badge, centred on the bar (Figma's own
   convention for exactly this). Absolutely positioned against .an-full__bar
   rather than a third flex child: the two side clusters can be different widths
   (a long dashboard name, a narrow viewport hiding button labels) and the badge
   has to stay dead-centre regardless, which only true centring on the bar itself
   guarantees. Quiet, not a second toolbar item: a soft chip, not the accent. */
.dpr-badge {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 12px;
  border-radius: var(--r-full);
  background: var(--glass-2);
  color: var(--sub);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  pointer-events: none;
}
.dpr-badge svg {
  width: 14px;
  height: 14px;
  color: var(--icon-muted);
}
@media (max-width: 900px) {
  .dpr-badge span {
    display: none;
  }
}
.dpr-tab {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 38px;
  padding: 0 14px;
  background: var(--pill);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  color: var(--sub);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.dpr-tab:hover {
  background: var(--pill-hover);
  color: var(--ink);
  border-color: var(--border-hover);
}
.dpr-tab svg {
  width: 15px;
  height: 15px;
  flex: none;
}
/* a measure, not the whole window: blocks authored at ~340-480px stretched edge
   to edge on a wide monitor read as a spreadsheet, not a dashboard */
.dash-present__stage {
  max-width: 1440px;
  margin: 0 auto;
  padding: 28px 24px 64px;
}
/* Presenting honours the PAGE WIDTH the board was arranged at, capped to the stage
   so a 2400px page still fits on screen rather than running off it. Setting a width
   is a statement about the layout, and a presentation that ignored it would show an
   arrangement nobody made. */
.dash-present__grid {
  align-content: flex-start;
  width: min(100%, var(--dboard, 100%));
  margin-inline: auto;
}
/* read-only: nothing to grab, and no hover lift promising an interaction the
   layer cannot commit anywhere */
.cblock--present {
  cursor: default;
}
.cblock--present:hover {
  border-color: var(--box-stroke);
}
@media (max-width: 1024px) {
  .dpr-tab span {
    display: none;
  }
  .dpr-tab {
    width: 38px;
    padding: 0;
    justify-content: center;
  }
  .dash-present__stage {
    padding: 20px 16px 48px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .dash-present {
    animation: none;
  }
}

/* ============================================================
   SEMANTIC INTELLIGENCE  (Figma section 8135:13811)
   Two views over one body of knowledge. The business view reads as five
   numbered chapters, a label and one sentence in a left rail, the evidence on
   the right, and each chapter ends in a drawer rather than a truncated list.

   The only new layout primitive is .sem-ch, that two-column chapter. Everything
   else is the page's existing furniture: .card, .p-table, .chip, .tag, .seg,
   .input and the app's own .drawer shell.
   ============================================================ */
.sch-sem {
  display: flex;
  flex-direction: column;
}
/* the read-only note and the view switcher share one row: the note on the
   leading edge, secondary in weight and tone, the switcher trailing */
.sem-switch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}
.sem-body {
  display: flex;
  flex-direction: column;
  gap: 34px;
}

/* ---- hero ---- */
.sem-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 28px;
  align-items: stretch;
  padding: 30px 32px;
  border-radius: var(--r-xl);
  background: var(--card);
  border: 1px solid var(--card-stroke);
}
.sem-hero__main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* Neutral, not green. Green in this product means "exists / is available"
   (see the class dots and the coverage meter); a page title is neither, so
   colouring it green was borrowing a meaning it does not have. */
.sem-hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  align-self: flex-start;
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sem-hero__eyebrow svg {
  width: 13px;
  height: 13px;
  color: var(--ricon);
}
.sem-hero__domain {
  margin-top: 14px;
  font-weight: 700;
  font-size: 38px;
  line-height: 44px;
  letter-spacing: -0.025em;
  color: var(--ink);
}
.sem-hero__desc {
  margin-top: 10px;
  font-size: 14px;
  line-height: 22px;
  color: var(--sub);
  max-width: 46ch;
}
.sem-hero__meta {
  display: flex;
  gap: 32px;
  flex-wrap: wrap;
  margin-top: auto;
  padding-top: 26px;
}
.sem-meta {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.sem-meta__ic {
  color: var(--ricon);
  display: grid;
  place-items: center;
  height: 18px;
  flex: none;
}
.sem-meta div {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.sem-meta__t {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-2);
}
.sem-meta__s {
  font-size: 11.5px;
  color: var(--text-faint);
}
/* the only caution on the hero: the picture is older than the database */
.sem-meta--warn .sem-meta__ic,
.sem-meta--warn .sem-meta__t {
  color: var(--warning);
}

/* coverage: dots, not a bar. 30 of 312 on a bar reads as "empty", which is true
   and useless; dots you can count. */
.sem-cov {
  display: flex;
  flex-direction: column;
  padding: 18px 20px;
  border-radius: var(--r-lg);
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.sem-cov__lbl {
  font-weight: 600;
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sem-cov__n {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-top: 8px;
}
.sem-cov__n b {
  font-weight: 700;
  font-size: 34px;
  line-height: 38px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.sem-cov__n span {
  font-size: 15px;
  color: var(--text-faint);
}
.sem-cov__sub {
  margin-top: 3px;
  font-size: 12px;
  color: var(--sub);
}
.sem-cov__dots {
  display: flex;
  gap: 3px;
  margin-top: 14px;
  flex-wrap: wrap;
}
.sem-cov__dots i,
.sem-split__dots i {
  width: 6px;
  height: 6px;
  border-radius: 2px;
  background: var(--glass-2);
  display: block;
}
/* 30 of 312 is a BAD number, not an achievement, colouring the lit dots green
   read as "all good" when the opposite is true. The meter takes the danger tone
   so the shortfall is the thing you see. */
.sem-cov__dots i.is-on,
.sem-split__dots i.is-on {
  background: var(--danger);
}
.sem-cov__rest {
  margin-top: 14px;
  font-size: 11.5px;
  line-height: 17px;
  color: var(--text-faint);
}
.sem-cov__go {
  margin-top: 12px;
  align-self: flex-start;
}
.sem-readonly {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: 11.5px;
  color: var(--text-faint);
}
.sem-readonly svg {
  color: var(--ricon);
  flex: none;
}

/* ---- the numbered chapter ---- */
.sem-ch {
  display: grid;
  grid-template-columns: 216px minmax(0, 1fr);
  gap: 34px;
  align-items: start;
}
/* a hairline between chapters, sitting in the middle of the space rather than
   hugging either side: the .sem-body gap supplies the room above it, this
   padding the room below. Applies to both views, since both are built from
   .sem-ch. */
.sem-ch + .sem-ch {
  padding-top: 32px;
  border-top: 1px solid var(--border);
}
.sem-ch__rail {
  position: sticky;
  top: var(--sch-chrome-h, 0px);
  display: flex;
  flex-direction: column;
}
.sem-ch__t {
  font-weight: 700;
  font-size: 21px;
  line-height: 28px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.sem-ch__lead {
  margin-top: 12px;
  font-size: 12.5px;
  line-height: 19px;
  color: var(--sub);
}
.sem-ch__body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

/* the closing line of a chapter */
.sem-all {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--text-faint);
}
.sem-all--split .sem-all__note {
  margin-inline-start: auto;
  text-align: end;
}
/* A "view all" is navigation, not a call to action, so it does not take the
   brand colour. Ink + an arrow reads as a link without spending orange on it. */
.sem-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 12.5px;
  color: var(--ink-2);
}
.sem-link:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.sem-link svg {
  color: var(--ricon);
}
.sem-link:hover svg {
  color: var(--ink-2);
}
.sem-link svg {
  width: 14px;
  height: 14px;
}
.sem-foot {
  font-size: 11.5px;
  line-height: 17px;
  color: var(--text-faint);
}
.sem-close {
  padding-top: 22px;
  border-top: 1px solid var(--border-subtle);
  font-size: 11.5px;
  color: var(--text-faint);
}

/* 01, the translation table */
.sem-tr {
  padding: 0;
  overflow: hidden;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-tr__hd,
.sem-tr__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 24px;
  padding: 13px 22px;
  align-items: center;
}
.sem-tr__hd {
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
  border-bottom: 1px solid var(--border-strong);
}
.sem-tr__hd2 {
  color: var(--ink-2);
}
.sem-tr__row {
  border-bottom: 1px solid var(--border-subtle);
}
.sem-tr__row:last-child {
  border-bottom: 0;
}
.sem-tr__db {
  font-size: 12.5px;
  color: var(--sub);
}
.sem-tr__sp {
  font-weight: 600;
  font-size: 14px;
  color: var(--ink);
}
.sem-tr__note {
  padding: 0 22px 12px;
  margin-top: -6px;
  font-size: 11.5px;
  color: var(--text-faint);
}

/* 02, the two classes, then the example questions */
.sem-two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.sem-grp {
  gap: 14px;
  padding: 18px 20px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-grp__hd {
  display: flex;
  align-items: center;
  gap: 9px;
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
}
.sem-grp--happen .sem-grp__hd span {
  color: var(--ink-2);
}
.sem-grp__hd b {
  margin-inline-start: auto;
  font-weight: 500;
  font-size: 12px;
  color: var(--text-faint);
}
.sem-grp__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.sem-thing {
  display: inline-flex;
  align-items: center;
  height: 30px;
  padding: 0 13px;
  border-radius: var(--r-sm);
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--line-strong);
  font-size: 12.5px;
  color: var(--ink-2);
}
.sem-asks {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 8px;
}
.sem-asks__lbl {
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sem-asks__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.sem-ask {
  padding: 16px 18px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-ask__q {
  font-size: 13px;
  line-height: 20px;
  color: var(--ink-2);
}
.sem-ask__q::before {
  content: "“";
  color: var(--text-faint);
  margin-inline-end: 1px;
}
.sem-ask__q::after {
  content: "”";
  color: var(--text-faint);
}

/* 03, term cards */
.sem-tgrid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.sem-term {
  gap: 6px;
  padding: 16px 18px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-term__t {
  font-weight: 600;
  font-size: 13.5px;
  color: var(--ink);
}
.sem-term__m {
  font-size: 12.5px;
  line-height: 19px;
  color: var(--sub);
  flex: 1;
}
.sem-term__f {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 8px;
  padding-top: 11px;
  border-top: 1px solid var(--border-subtle);
}
.sem-term__from {
  font-size: 11.5px;
  color: var(--text-faint);
}
.sem-term__from b {
  color: var(--sub);
  font-weight: 500;
}

/* 04, the number leads, because the number is the decision */
.sem-rgrid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}
.sem-rule {
  gap: 8px;
  padding: 18px 20px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-rule__v {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 21px;
  line-height: 26px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.sem-rule__t {
  font-size: 12.5px;
  line-height: 19px;
  color: var(--sub);
  flex: 1;
}
.sem-rule__w {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 8px;
  padding-top: 11px;
  border-top: 1px solid var(--border-subtle);
  font-size: 11px;
  line-height: 16px;
}
.sem-rule__w i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
/* every rule states where it came from in the same quiet tone; "unconfirmed" is
   information, not an alarm */
.sem-rule__w--learned,
.sem-rule__w--person,
.sem-rule__w--unconfirmed {
  color: var(--text-faint);
}

/* 05, what they say, what it means */
.sem-ph {
  padding: 6px 22px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-ph__row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 13px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.sem-ph__row:last-child {
  border-bottom: 0;
}
.sem-ph__says {
  font-size: 13px;
  color: var(--sub);
  flex: none;
  max-width: 44%;
}
.sem-ph__line {
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}
.sem-ph__hears {
  font-weight: 600;
  font-size: 13.5px;
  color: var(--ink);
  flex: none;
}

/* ---- technical ---- */
.sem-hd {
  padding-bottom: 20px;
  border-bottom: 1px solid var(--border-strong);
}
.sem-hd__id h2 {
  font-weight: 700;
  font-size: 22px;
  line-height: 30px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.sem-hd__id p {
  margin-top: 6px;
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
}
.sem-unm {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 6px 14px;
  align-items: start;
  padding: 18px 20px;
  background: var(--card);
  border-color: var(--card-stroke);
}
/* the glyph is a label for the row, not the warning itself, the red meter and
   the count carry that. Ink, so it flips with the theme rather than being a
   second amber signal next to the first. */
.sem-unm__ic {
  color: var(--ink);
  display: grid;
  place-items: center;
  height: 22px;
}
.sem-unm__main {
  min-width: 0;
}
.sem-unm__t {
  font-weight: 600;
  font-size: 14px;
  line-height: 22px;
  color: var(--ink);
}
.sem-unm__s {
  margin-top: 2px;
  font-size: 12.5px;
  line-height: 19px;
  color: var(--sub);
}
.sem-unm .btn {
  grid-column: 3;
  grid-row: 1;
  white-space: nowrap;
}
.sem-unm .sem-meter,
.sem-unm__scale {
  grid-column: 1/-1;
}
.sem-meter {
  height: 6px;
  border-radius: 3px;
  background: var(--glass-2);
  overflow: hidden;
  margin-top: 12px;
}
.sem-meter i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--danger);
}
.sem-unm__scale {
  display: flex;
  justify-content: space-between;
  margin-top: 7px;
  font-size: 11.5px;
  color: var(--text-faint);
}
.sem-unm__mapped {
  color: var(--ink-2);
  font-weight: 600;
}
.sem-dom {
  gap: 12px;
  padding: 0;
  overflow: hidden;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-dom__hd {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 20px 22px 0;
}
.sem-dom__k {
  font-size: 17px;
  font-weight: 500;
  color: var(--ink);
}
.sem-dom__p {
  padding: 0 22px;
  font-size: 13px;
  line-height: 20px;
  color: var(--sub);
  max-width: 76ch;
}
.sem-dom__f {
  padding: 13px 22px;
  border-top: 1px solid var(--border-subtle);
  background: var(--glass);
}
.sem-dom__f code {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--sub);
}
/* confidence reads as a little bar plus the number, so a low one is visible
   before it is read */
.sem-sure {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  justify-content: flex-end;
}
.sem-sure__bar {
  width: 56px;
  height: 4px;
  border-radius: 2px;
  background: var(--glass-2);
  overflow: hidden;
  flex: none;
}
.sem-sure__bar i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--success);
}
.sem-sure__bar.is-low i {
  background: var(--warning);
}
.sem-sure__v {
  font-size: 12.5px;
  color: var(--success);
  font-variant-numeric: tabular-nums;
}
.sem-sure__v.is-low {
  color: var(--warning);
}
.sem-origin {
  font-size: 12px;
  color: var(--text-faint);
}
.sem-origin--unconfirmed {
  color: var(--warning);
}

/* ============ drawers ============ */
.sem-drawer {
  width: min(660px, calc(100vw - 48px));
}
.sem-drawer--unmapped,
.sem-drawer--terms {
  width: min(720px, calc(100vw - 48px));
}
.sem-drawer .drawer__htitle {
  gap: 12px;
  flex-wrap: wrap;
}
.sem-dbadge {
  background: var(--warning-soft);
  color: var(--warning);
}
.sem-drawer--terms .sem-dbadge,
.sem-drawer--things .sem-dbadge {
  background: var(--success-soft);
  color: var(--success);
}
.sem-drawer--phrases .sem-dbadge {
  background: var(--glass-2);
  color: var(--sub);
}
.sem-dsec {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.sem-split {
  flex-direction: row;
  align-items: center;
  gap: 26px;
  padding: 16px 20px;
  flex-wrap: wrap;
  background: var(--glass);
  border-color: var(--line-strong);
}
.sem-split__n {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  font-size: 12px;
  color: var(--sub);
}
.sem-split__n b {
  font-weight: 700;
  font-size: 22px;
  letter-spacing: -0.01em;
}
.sem-split__n b {
  color: var(--ink);
}
.sem-split__n--gap b {
  color: var(--danger);
}
.sem-split__dots {
  display: flex;
  gap: 3px;
  margin-inline-start: auto;
  flex-wrap: wrap;
  max-width: 180px;
}
.sem-blocks {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sem-block {
  flex-direction: row;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-block__main {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  flex: 1;
}
.sem-block__t {
  font-weight: 600;
  font-size: 13.5px;
  color: var(--ink);
}
.sem-block__s {
  font-size: 12px;
  line-height: 17px;
  color: var(--sub);
}
.sem-block__from {
  font-size: 11.5px;
  color: var(--text-faint);
  flex: none;
}
.sem-note {
  flex-direction: row;
  align-items: flex-start;
  gap: 12px;
  padding: 15px 16px;
  background: var(--glass);
  border-color: var(--line-strong);
}
.sem-note__ic {
  color: var(--sub);
  display: grid;
  place-items: center;
  height: 18px;
  flex: none;
}
.sem-note__main {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.sem-note__t {
  font-weight: 600;
  font-size: 12.5px;
  color: var(--ink);
}
.sem-note__s {
  font-size: 11.5px;
  line-height: 17px;
  color: var(--sub);
}
.sem-filters {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
/* flex:1 is meant to fill the ROW it shares with the table filter. Left as a
   direct child of .drawer__body, a column flex container, that same rule
   grows it vertically instead, which is how this field once rendered as a
   300px-tall box. flex-grow only ever applies inside .sem-filters now. */
.sem-search {
  flex: none;
  width: 100%;
  height: 40px;
  background: var(--glass);
  border-color: transparent;
  border-radius: var(--r-md);
}
.sem-filters .sem-search {
  flex: 1;
  min-width: 200px;
  width: auto;
}
.sem-search svg {
  color: var(--ricon);
  flex: none;
}
.sem-search input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 13px;
}
.sem-search input::placeholder {
  color: var(--text-faint);
}
.sem-tablesel {
  min-width: 176px;
  flex: none;
  height: 40px;
}
.sem-tablesel .er-sel__t {
  font-family: var(--font-mono);
  font-size: 12.5px;
}
.sem-cols {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sem-cols .p-table th {
  padding-bottom: 11px;
  border-bottom-color: var(--border-strong);
  color: var(--ink-2);
  letter-spacing: 0.09em;
  font-size: 10.5px;
}
.sem-cols td {
  padding: 12px 0;
  vertical-align: middle;
}
.sem-ucol {
  color: var(--ink);
  font-size: 12.5px;
  white-space: nowrap;
}
.sem-utype {
  color: var(--text-faint);
  font-size: 11.5px;
}
.sem-unlock {
  color: var(--sub);
  font-size: 12px;
}
.sem-dterm {
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
  white-space: nowrap;
}
.sem-means {
  font-size: 12.5px;
  color: var(--sub);
}
.sem-dfrom {
  white-space: nowrap;
  color: var(--text-faint);
  font-size: 11.5px;
}
.sem-dsays {
  font-size: 12.5px;
  color: var(--sub);
}
.sem-dhears {
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
}
.sem-showing {
  font-size: 11.5px;
  color: var(--text-faint);
}
.sem-drules {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sem-drule {
  flex-direction: row;
  align-items: center;
  gap: 22px;
  padding: 16px 20px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.sem-drule__v {
  width: 132px;
  flex: none;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.sem-drule__main {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}
.sem-drule__t {
  font-size: 13px;
  color: var(--ink-2);
}
.sem-drule .sem-rule__w {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
.sem-tgrp {
  display: flex;
  flex-direction: column;
}
.sem-tgrp__hd {
  display: flex;
  align-items: center;
  gap: 9px;
  padding-bottom: 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border-subtle);
  font-weight: 600;
  font-size: 13px;
}
.sem-tgrp__t--activity {
  color: var(--ink-2);
}
.sem-tgrp__t--reference {
  color: var(--success);
}
.sem-tgrp__hd b {
  margin-inline-start: auto;
  font-weight: 500;
  font-size: 12px;
  color: var(--text-faint);
}
.sem-trow {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.sem-trow:last-child {
  border-bottom: 0;
}
.sem-trow__n {
  width: 132px;
  flex: none;
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
}
.sem-trow__g {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  color: var(--sub);
}
.sem-trow__r {
  font-size: 11.5px;
  color: var(--text-faint);
  flex: none;
}
.sem-unnamed {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sem-unnamed__t {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 12.5px;
  color: var(--warning);
}
.sem-unnamed__s {
  padding-inline-start: 22px;
  font-size: 11.5px;
  color: var(--text-faint);
}

@media (max-width: 1180px) {
  .sem-tgrid,
  .sem-asks__grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .sem-rgrid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 1024px) {
  .sem-hero {
    grid-template-columns: minmax(0, 1fr);
  }
  .sem-ch {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }
  .sem-ch__rail {
    position: static;
  }
  .sem-two,
  .sem-tgrid,
  .sem-asks__grid,
  .sem-rgrid {
    grid-template-columns: minmax(0, 1fr);
  }
  .sem-unm {
    grid-template-columns: auto minmax(0, 1fr);
  }
  .sem-unm .btn {
    grid-column: 1/-1;
    grid-row: auto;
    justify-self: start;
  }
}

/* drawer footer: Copy list on the leading edge, Close pushed to the trailing one */
.sem-foot-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.sem-foot-row__end {
  margin-inline-start: auto;
}

/* ---- column hint ----------------------------------------------------------
   Explains what a column means without spending a row on it. Opens DOWNWARD and
   anchored to the trailing edge: .sch-tblcard sets overflow-x:auto, which makes
   overflow-y compute to auto as well, so a card opening upward out of the
   header would be clipped. Below, over its own rows, it always has room. */
.sem-hint {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: help;
  outline: none;
}
.sem-hint > svg {
  width: 12px;
  height: 12px;
  color: var(--ricon);
  flex: none;
}
.sem-hint:hover > svg,
.sem-hint:focus-visible > svg {
  color: var(--ink-2);
}
.sem-hint__tip {
  position: absolute;
  top: calc(100% + 9px);
  inset-inline-end: 0;
  z-index: 40;
  width: 270px;
  padding: 12px 14px;
  text-align: start;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: 12px;
  line-height: 18px;
  color: var(--sub);
  white-space: normal;
  background: var(--menu-bg);
  border: 1px solid var(--panel-stroke);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-xl);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  pointer-events: none;
  transition:
    opacity 0.15s var(--ease),
    transform 0.15s var(--ease),
    visibility 0.15s;
}
.sem-hint:hover .sem-hint__tip,
.sem-hint:focus-visible .sem-hint__tip {
  opacity: 1;
  visibility: visible;
  transform: none;
}
.sem-hint__tip b {
  display: block;
  margin-bottom: 4px;
  color: var(--ink);
  font-weight: 600;
}

/* ============================================================
   DATA SOURCE → ANALYTICS  (Figma section 8263:45465 -
   overview 8263:45240, subpage 8263:45121)

   Layout only. Every surface is .card on the page's own --card/--card-stroke
   pair, every colour is a token, and the type sizes are the ones the Schema
   and Access tabs beside it already use, so the four tabs read as one page
   with four subjects rather than four pages.

   Colour carries meaning here and nothing else:
     amber  (--warning) a question that never got an answer
     orange (--accent)  an answer that came back wrong
     blue   (--info)    a question that had to be asked again, and how much of
                        the work each table carries, process, not judgement
     green  (--success) answered, and the quiet row of things nobody needs to
                        look at
   Neutral is the default; a figure only takes a colour when acting on it is
   the point.
   ============================================================ */
.ds-an {
  display: flex;
  flex-direction: column;
  gap: 30px;
}

/* ---- the stale-read banner. Amber only: it renders exclusively when the
   read is old enough to matter (anIsStale()), so there is no "all is well"
   variant to style. There was a green one until freshness moved to the quiet
   header control; these rules went with it. ---- */
.an-read {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 16px;
  position: relative;
  overflow: hidden;
  padding: 20px 24px;
  background: var(--card);
  border-color: var(--card-stroke);
}
.an-read::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 3px;
  background: var(--warning);
}
.an-read__ic {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--warning);
}
.an-read__main {
  flex: 1;
  min-width: 0;
}
.an-read__t {
  font-weight: 700;
  font-size: 15px;
  line-height: 22px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.an-read__s {
  margin-top: 4px;
  font-size: 12.5px;
  line-height: 18px;
  color: var(--sub);
}
.an-read__btn {
  flex: none;
}
.an-read.is-syncing .an-read__btn {
  opacity: 0.6;
}

/* ---- page head + the window picker -------------------------------------- */
.an-hd {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}
/* the freshness control and the window picker travel together on the right.
   The control is the Schema tab's own 48px button, the picker is 36px, so the
   two are centred against each other rather than top-aligned, and they stay on
   one row when the head stacks at narrow widths. */
.an-hd__acts {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: none;
  flex-wrap: wrap;
}
.an-hd__main {
  flex: 1;
  min-width: 0;
}
.an-hd__main h2 {
  font-weight: 700;
  font-size: 24px;
  line-height: 31px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.an-hd__main p {
  margin-top: 7px;
  font-size: 13px;
  line-height: 19px;
  color: var(--sub);
}
.an-hd .an-copy {
  flex: none;
  margin-top: 4px;
}
.an-hd__acts .pop {
  flex: none;
}
/* 48px to match .sch-sync beside it. The two sit as a pair in .an-hd__acts and
   a 36px pill against a 48px button read as a mismatch rather than a hierarchy.
   Ameer's call: level the picker up rather than flatten the sync control down,
   so the freshness reading keeps its two lines. */
.an-range {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 48px;
  padding: 0 16px 0 20px;
  cursor: pointer;
  border: 1px solid var(--card-stroke);
  border-radius: var(--r-md);
  background: var(--card);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
}
.an-range:hover {
  border-color: var(--border-hover);
  background: var(--surface-raised);
}
.an-range svg {
  color: var(--ricon);
  flex: none;
}
.an-range__menu {
  min-width: 190px;
}
.an-range__ck {
  width: 15px;
  display: inline-grid;
  place-items: center;
  flex: none;
  color: var(--accent);
}
.an-range__menu .menu-item.is-sel {
  color: var(--ink);
}

/* ---- the headline ------------------------------------------------------- */
.an-head {
  display: grid;
  grid-template-columns: 250px 1fr 220px;
  gap: 0;
  padding: 0;
  background: var(--card);
  border-color: var(--card-stroke);
}
.an-head__cell {
  padding: 26px 32px;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.an-head__total {
  border-inline-end: 1px solid var(--border-subtle);
}
.an-head__n {
  font-weight: 700;
  font-size: 40px;
  line-height: 48px;
  letter-spacing: -0.03em;
  color: var(--ink);
}
.an-head__n--sm {
  font-size: 26px;
  line-height: 32px;
  letter-spacing: -0.02em;
}
.an-head__lbl {
  margin-top: 6px;
  font-size: 13px;
  color: var(--sub);
}
.an-head__foot {
  margin-top: 6px;
  font-size: 11.5px;
  line-height: 17px;
  color: var(--text-faint);
}
.an-head__side {
  text-align: end;
  align-items: flex-end;
  border-inline-start: 1px solid var(--border-subtle);
  justify-content: center;
}
.an-head__mix {
  justify-content: center;
}
.an-head__mixhd {
  display: flex;
  align-items: baseline;
  gap: 16px;
}
.an-head__ok {
  font-weight: 600;
  font-size: 13.5px;
  color: var(--success);
}
.an-head__pct {
  margin-inline-start: auto;
  font-size: 12.5px;
  color: var(--text-faint);
}
/* the one stacked bar on the page. Segments add to the total by construction,
   so the percentage beside it is a reading of the bar, not a second claim. */
.an-mix {
  display: flex;
  margin-top: 12px;
  height: 18px;
  border-radius: 9px;
  overflow: hidden;
  background: var(--surface-raised);
}
.an-mix__seg {
  display: block;
  height: 100%;
  transition: width 0.5s var(--ease);
}
.an-mix__seg--ok {
  background: var(--success);
}
.an-mix__seg--warn {
  background: var(--warning);
}
.an-mix__seg--danger {
  background: var(--danger);
}
.an-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  margin-top: 14px;
}
.an-leg {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--sub);
}
.an-leg svg {
  color: var(--ricon);
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.an-leg:hover {
  color: var(--ink);
}
.an-leg:hover svg {
  opacity: 1;
}
.an-leg__d {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
}
.an-leg__d--warn {
  background: var(--warning);
}
.an-leg__d--danger {
  background: var(--danger);
}
.an-leg__d--info {
  background: var(--info);
}

/* ---- section label rhythm ---------------------------------------------- */
.an-sec {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.an-lbl {
  display: flex;
  align-items: center;
  gap: 24px;
  min-height: 28px;
}
.an-lbl__t {
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
  flex: none;
}
.an-lbl__note {
  font-size: 12.5px;
  color: var(--sub);
  min-width: 0;
}
.an-lbl__end {
  margin-inline-start: auto;
  flex: none;
}
.an-lbl__n {
  font-size: 12px;
  color: var(--text-faint);
}
/* Schema and Access still open with a title + one line; Analytics does not,
   so this pair stays for them */
.an-sec__hd h3 {
  font-weight: 700;
  font-size: 17px;
  line-height: 24px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.an-sec__hd p {
  margin-top: 5px;
  font-size: 12.5px;
  line-height: 19px;
  color: var(--sub);
}
.an-card {
  padding: 20px 22px;
  background: var(--card);
  border-color: var(--card-stroke);
  gap: 0;
}
.an-empty {
  padding: 20px 0;
  font-size: 12.5px;
  color: var(--text-faint);
}
.ds-an .an-empty {
  padding: 44px 24px;
  font-size: inherit;
  color: inherit;
}

/* ---- WHAT TO LOOK AT ---------------------------------------------------- */
.an-look {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
.an-look__c {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  text-align: start;
  cursor: pointer;
  padding: 24px 28px;
  background: var(--card);
  border-color: var(--card-stroke);
  gap: 0;
  transition:
    background 0.14s var(--ease),
    border-color 0.14s var(--ease);
}
.an-look__c::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 3px;
}
.an-look__c--warn::before {
  background: var(--warning);
}
.an-look__c--danger::before {
  background: var(--danger);
}
.an-look__c--info::before {
  background: var(--info);
}
.an-look__c:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.an-look__hd {
  display: flex;
  align-items: center;
}
.an-look__c--warn .an-look__ic {
  color: var(--warning);
}
.an-look__c--danger .an-look__ic {
  color: var(--danger);
}
.an-look__c--info .an-look__ic {
  color: var(--info);
}
.an-look__ic {
  display: grid;
  place-items: center;
}
.an-look__go {
  margin-inline-start: auto;
  color: var(--ricon);
  display: grid;
  place-items: center;
}
.an-look__c:hover .an-look__go {
  color: var(--ink);
}
.an-look__n {
  margin-top: 16px;
  font-weight: 700;
  font-size: 32px;
  line-height: 38px;
  letter-spacing: -0.03em;
  color: var(--ink);
}
.an-look__t {
  margin-top: 8px;
  font-weight: 600;
  font-size: 15px;
  line-height: 22px;
  color: var(--ink);
}
.an-look__why {
  margin-top: 8px;
  flex: 1;
  font-size: 12.5px;
  line-height: 19px;
  color: var(--sub);
}
.an-look__foot {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border-subtle);
  font-size: 11.5px;
  line-height: 17px;
}
.an-look__c--warn .an-look__foot {
  color: var(--warning);
}
.an-look__c--danger .an-look__foot {
  color: var(--danger);
}
.an-look__c--info .an-look__foot {
  color: var(--info);
}

/* ---- WHAT IS FINE: the quiet row, deliberately inert ------------------- */
.an-fine {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  padding: 0;
  background: var(--card);
  border-color: var(--card-stroke);
}
.an-fine__c {
  padding: 20px 24px;
  border-inline-end: 1px solid var(--border-subtle);
  min-width: 0;
}
.an-fine__c:last-child {
  border-inline-end: 0;
}
.an-fine__t {
  display: flex;
  align-items: center;
  gap: 9px;
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
}
.an-fine__t svg {
  color: var(--success);
  flex: none;
}
.an-fine__v {
  display: block;
  margin-top: 9px;
  font-size: 13px;
  color: var(--sub);
}

/* ---- a labelled meter row: name, bar, value, trailing note --------------
   Used twice, how empty a column is, and how much of the work a table
   carries, so it is one primitive rather than two look-alikes. */
.an-meters {
  padding: 14px 0 0;
  background: var(--card);
  border-color: var(--card-stroke);
  gap: 0;
}
.an-meter {
  display: grid;
  grid-template-columns: 240px minmax(140px, 420px) 130px 1fr;
  align-items: center;
  gap: 20px;
  width: 100%;
  padding: 8px 30px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: start;
  font-family: var(--font-sans);
  transition: background 0.12s var(--ease);
}
.an-meter:hover {
  background: var(--glass);
}
.an-meter__t {
  font-size: 13px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.an-meter__bar {
  display: block;
  height: 11px;
  border-radius: 6px;
  background: var(--surface-raised);
  overflow: hidden;
}
.an-meter__fill {
  display: block;
  height: 100%;
  border-radius: 6px;
  transition: width 0.5s var(--ease);
}
.an-meter__fill--warn {
  background: var(--warning);
}
.an-meter__fill--danger {
  background: var(--danger);
}
.an-meter__fill--idle {
  background: var(--text-faint);
  opacity: 0.5;
}
.an-meter__fill--info {
  background: var(--info);
}
.an-meter__v {
  font-weight: 600;
  font-size: 12.5px;
  color: var(--sub);
  white-space: nowrap;
}
.an-meter__end {
  margin-inline-start: auto;
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
}
.an-meters__foot {
  margin-top: 8px;
  padding: 14px 30px 18px;
  border-top: 1px solid var(--border-subtle);
  font-size: 12px;
  line-height: 18px;
  color: var(--sub);
}

/* ---- the summary pair. Two boxes, left and right: what is wrong with the
   data, and where the answers come from. Each is the high-level reading only;
   the list itself lives one click away, because both lists grow without
   limit and neither belongs at full length on a page you scan. ---- */
.an-two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}
.an-box {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  text-align: start;
  cursor: pointer;
  padding: 22px 26px;
  background: var(--card);
  border-color: var(--card-stroke);
  gap: 0;
  transition:
    background 0.14s var(--ease),
    border-color 0.14s var(--ease);
}
.an-box::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 3px;
}
.an-box--warn::before {
  background: var(--warning);
}
.an-box--info::before {
  background: var(--info);
}
.an-box:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.an-box__hd {
  display: flex;
  align-items: center;
}
.an-box__lbl {
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.an-box:hover .an-look__go {
  color: var(--ink);
}
.an-box__n {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin-top: 14px;
  font-size: 13px;
  color: var(--sub);
}
.an-box__n b {
  font-weight: 700;
  font-size: 32px;
  line-height: 38px;
  letter-spacing: -0.03em;
}
.an-box__lead {
  margin-top: 8px;
  font-size: 12.5px;
  line-height: 19px;
  color: var(--sub);
}
/* three rows of evidence, deliberately small: they are a sample, not the list */
.an-mini {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border-subtle);
}
.an-mini__r {
  display: grid;
  grid-template-columns: minmax(0, 190px) minmax(60px, 1fr) auto;
  align-items: center;
  gap: 12px;
}
.an-mini__t {
  font-size: 11.5px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.an-mini__bar {
  height: 6px;
  border-radius: 3px;
}
.an-mini__bar i {
  border-radius: 3px;
}
.an-mini__v {
  font-size: 11px;
  color: var(--text-faint);
  white-space: nowrap;
}
.an-box__link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 18px;
  font-weight: 600;
  font-size: 12.5px;
  color: var(--accent);
}
.an-box__link svg {
  color: var(--accent);
}
.an-box:hover .an-box__link {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* the full lists, on their own subpages */
.an-meters--list {
  padding-top: 10px;
}
.an-meters--list .an-meter {
  grid-template-columns: 280px minmax(140px, 1fr) 150px 170px;
}
/* the tables list has no trailing note, so it must not reserve a column for one */
.an-meters--list.an-meters--3 .an-meter {
  grid-template-columns: 280px minmax(140px, 1fr) 150px;
}
.an-meter.is-dim .an-meter__t {
  color: var(--text-faint);
}
.an-meter.is-dim .an-meter__v {
  color: var(--text-faint);
}

/* ---- a row of counted columns: WHO IS USING IT, THE SHAPE OF THIS DB ---- */
.an-cols {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  padding: 0;
  background: var(--card);
  border-color: var(--card-stroke);
}
.an-cols--shape {
  grid-template-columns: repeat(5, 1fr);
}
.an-cols__c {
  padding: 24px 28px;
  border-inline-end: 1px solid var(--border-subtle);
  min-width: 0;
}
.an-cols__c:last-of-type {
  border-inline-end: 0;
}
.an-cols__n {
  display: block;
  font-weight: 700;
  font-size: 24px;
  line-height: 31px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.an-cols--shape .an-cols__n {
  font-size: 26px;
  line-height: 34px;
}
.an-cols__t {
  display: block;
  margin-top: 7px;
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
}
.an-cols--shape .an-cols__t {
  font-weight: 400;
  font-size: 12.5px;
  color: var(--sub);
}
.an-cols__f {
  display: block;
  margin-top: 8px;
  font-size: 11.5px;
  line-height: 17px;
  color: var(--text-faint);
}
.an-cols__foot {
  grid-column: 1/-1;
  padding: 14px 28px 18px;
  border-top: 1px solid var(--border-subtle);
  font-size: 12px;
  line-height: 18px;
  color: var(--sub);
}

/* ---- tone: the only four colours a figure on this page can take --------- */
.an-tone--warn {
  color: var(--warning) !important;
}
.an-tone--danger {
  color: var(--danger) !important;
}
.an-tone--info {
  color: var(--info) !important;
}
.an-tone--idle {
  color: var(--ink) !important;
}
.an-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  display: inline-block;
}
.an-dot--warn {
  background: var(--warning);
}
.an-dot--danger {
  background: var(--danger);
}
.an-dot--info {
  background: var(--info);
}
.an-dot--idle {
  background: var(--text-faint);
}

/* ---- shared chrome the Access tab also draws with ----------------------- */
.an-link {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--sub);
}
.an-link:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.an-link svg.is-up {
  transform: rotate(-90deg);
}
.an-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding-bottom: 18px;
}
.an-search {
  width: 236px;
  flex: none;
  background: var(--glass);
  border-color: transparent;
  border-radius: var(--r-md);
}
.an-search svg {
  color: var(--ricon);
  flex: none;
}
.an-search input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 12.5px;
}
.an-search input::placeholder {
  color: var(--text-faint);
}
.an-chips {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.an-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 13px;
  border-radius: var(--r-sm);
  cursor: pointer;
  border: 1px solid transparent;
  background: var(--glass);
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--sub);
}
.an-chip b {
  font-weight: 600;
  color: var(--text-faint);
  margin-inline-start: 2px;
}
.an-chip:hover {
  background: var(--glass-2);
  color: var(--ink);
}
.an-chip.sel,
.an-chip.is-sel {
  background: var(--glass-2);
  color: var(--ink);
  border-color: var(--border-hover);
}
.an-chip.sel b {
  color: var(--ink-2);
}
.an-chip svg {
  color: var(--ricon);
  flex: none;
}

/* ============================================================
   THE SUBPAGES  (Figma 8263:45121)
   ============================================================ */
.ds-an--sub {
  gap: 26px;
}
.an-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--sub);
}
.an-back svg {
  color: var(--ricon);
}
.an-back:hover {
  color: var(--ink);
}
.an-back:hover svg {
  color: var(--ink);
}
.an-hd--sub {
  align-items: flex-start;
}

/* the causes, which are also the filter */
.an-causes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
.an-cause {
  display: flex;
  flex-direction: column;
  text-align: start;
  cursor: pointer;
  padding: 22px 24px;
  background: var(--card);
  border-color: var(--card-stroke);
  gap: 0;
  transition:
    background 0.14s var(--ease),
    border-color 0.14s var(--ease);
}
.an-cause:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.an-cause.is-sel {
  background: var(--glass-2);
  border-color: var(--border-hover);
}
.an-cause.is-empty {
  opacity: 0.45;
  cursor: default;
}
.an-cause.is-empty:hover {
  background: var(--card);
  border-color: var(--card-stroke);
}
.an-cause__hd {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.an-cause__hd b {
  font-weight: 700;
  font-size: 28px;
  line-height: 34px;
  letter-spacing: -0.02em;
}
.an-cause__unit {
  font-size: 13px;
  color: var(--text-faint);
}
.an-cause__t {
  margin-top: 12px;
  font-weight: 600;
  font-size: 13.5px;
  line-height: 20px;
  color: var(--ink);
}
.an-cause__why {
  margin-top: 6px;
  flex: 1;
  font-size: 12px;
  line-height: 18px;
  color: var(--sub);
}
.an-cause__bar {
  margin-top: 18px;
  height: 5px;
  border-radius: 3px;
}
.an-cause__bar i {
  border-radius: 3px;
}

/* the search sits in the section label, where the reference puts it */
.an-qbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.an-qsearch {
  width: 240px;
  height: 32px;
  flex: none;
  background: var(--glass);
  border-color: transparent;
  border-radius: var(--r-sm);
}
.an-qsearch svg {
  color: var(--ricon);
  flex: none;
}
.an-qsearch input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 12.5px;
}
.an-qsearch input::placeholder {
  color: var(--text-faint);
}
.an-qsearch__x {
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--ricon);
  flex: none;
}
.an-qsearch__x:hover {
  color: var(--ink);
}

/* one question, and the one thing that would fix it */
.an-qs {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.an-q {
  padding: 18px 24px;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: var(--r-lg);
}
.an-q__hd {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}
.an-q__ic {
  display: grid;
  place-items: center;
  color: var(--ricon);
  flex: none;
  margin-top: 2px;
}
.an-q__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.an-q__t {
  font-weight: 600;
  font-size: 15px;
  line-height: 22px;
  color: var(--ink);
}
.an-q__meta {
  margin-top: 5px;
  font-size: 11.5px;
  color: var(--text-faint);
}
.an-q__fix {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 12.5px;
  color: var(--accent);
}
.an-q__fix svg {
  color: var(--accent);
}
.an-q__fix:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}
.an-q__evi {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--border-subtle);
}
.an-q__evi .an-dot {
  align-self: center;
}
.an-q__target {
  font-size: 12.5px;
  white-space: nowrap;
}
.an-q__why {
  font-size: 12.5px;
  line-height: 19px;
  color: var(--sub);
  min-width: 0;
}
.an-qs__foot {
  display: flex;
  align-items: center;
  gap: 20px;
  font-size: 12.5px;
  color: var(--text-faint);
}
/* the who-can-change footnote. The page's quietest text, sitting under the
   list it qualifies. No rule, no icon, no surface. */
.an-whocan {
  margin-top: 8px;
  font-size: 12px;
  line-height: 18px;
  color: var(--text-faint);
}
.an-qs__foot .an-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 600;
  color: var(--accent);
}
.an-qs__foot .an-link svg {
  color: var(--accent);
}
.an-qs__foot .an-link:hover {
  color: var(--accent-hover);
}

/* ---- responsive. Same two bands the rest of the app uses. --------------- */
@media (max-width: 1240px) {
  .an-head {
    grid-template-columns: 220px 1fr 190px;
  }
  .an-head__cell {
    padding: 22px 24px;
  }
  .an-meter {
    grid-template-columns: 200px minmax(120px, 1fr) 120px auto;
    gap: 14px;
    padding: 8px 24px;
  }
  .an-meters__foot {
    padding-inline: 24px;
  }
  .an-fine__c,
  .an-cols__c {
    padding: 20px;
  }
  .an-cols__foot {
    padding-inline: 20px;
  }
}
@media (max-width: 1080px) {
  .an-head {
    grid-template-columns: 1fr 1fr;
  }
  .an-head__mix {
    grid-column: 1/-1;
    order: 3;
    border-top: 1px solid var(--border-subtle);
  }
  .an-head__side {
    text-align: start;
    align-items: flex-start;
    border-inline-start: 0;
    border-inline-end: 1px solid var(--border-subtle);
  }
  .an-head__pct {
    margin-inline-start: auto;
  }
  .an-look,
  .an-causes {
    grid-template-columns: 1fr 1fr;
  }
  .an-meters--list .an-meter {
    grid-template-columns: 220px minmax(120px, 1fr) 130px 150px;
  }
  .an-fine {
    grid-template-columns: repeat(3, 1fr);
  }
  .an-fine__c:nth-child(3n) {
    border-inline-end: 0;
  }
  .an-fine__c:nth-child(-n + 3) {
    border-bottom: 1px solid var(--border-subtle);
  }
  .an-cols {
    grid-template-columns: 1fr 1fr;
  }
  .an-cols--shape {
    grid-template-columns: repeat(3, 1fr);
  }
  .an-cols__c:nth-child(2n) {
    border-inline-end: 0;
  }
  .an-cols--shape .an-cols__c:nth-child(2n) {
    border-inline-end: 1px solid var(--border-subtle);
  }
  .an-cols--shape .an-cols__c:nth-child(3n) {
    border-inline-end: 0;
  }
  .an-cols__c {
    border-bottom: 1px solid var(--border-subtle);
  }
}
@media (max-width: 820px) {
  .an-hd {
    flex-direction: column;
    gap: 14px;
  }
  .an-hd__acts,
  .an-hd .an-copy {
    margin-top: 0;
    align-self: flex-start;
  }
  .an-head {
    grid-template-columns: 1fr;
  }
  .an-head__total,
  .an-head__side {
    border-inline-end: 0;
    border-bottom: 1px solid var(--border-subtle);
  }
  .an-head__mix {
    order: 0;
    border-top: 0;
  }
  .an-look,
  .an-causes,
  .an-two {
    grid-template-columns: 1fr;
  }
  .an-meters--list .an-meter {
    grid-template-columns: 1fr auto;
  }
  .an-fine,
  .an-cols,
  .an-cols--shape {
    grid-template-columns: 1fr;
  }
  .an-fine__c,
  .an-cols__c {
    border-inline-end: 0;
    border-bottom: 1px solid var(--border-subtle);
  }
  .an-fine__c:last-child,
  .an-cols__c:last-of-type {
    border-bottom: 0;
  }
  .an-meter {
    grid-template-columns: 1fr auto;
    gap: 6px 12px;
    padding: 12px 20px;
  }
  .an-meter__bar {
    grid-column: 1/-1;
    order: 3;
  }
  .an-meter__end {
    grid-column: 1/-1;
    order: 4;
    margin-inline-start: 0;
  }
  .an-lbl {
    flex-wrap: wrap;
    gap: 10px 16px;
  }
  .an-lbl__end {
    margin-inline-start: 0;
  }
  .an-qsearch {
    width: 100%;
  }
  .an-q__hd {
    flex-wrap: wrap;
  }
  .an-q__fix {
    width: 100%;
    padding-inline-start: 30px;
  }
}

/* ============================================================================
   ACCESS  (Data source detail → Access), Figma wireframes 8254:43748

   Layout only. Every surface, chip, table, field and button below is a part
   the app already owns: .card + .p-table for the list, .an-chip for the three
   counters, .seg for the two levels, .drawer for the three action pages,
   .dialog for Remove, .ck / .sw for the two controls (atoms.css), .tag for
   every badge. Nothing here introduces a colour, a radius or a type size that
   isn't already a token.
   ============================================================================ */
.acc {
  display: flex;
  flex-direction: column;
  gap: var(--sp-20);
}
/* The section head is .an-sec__hd, the same 17/700 + 12.5 pair every other tab
   on this page uses. The actions beside it are .search-actions, the list page's
   own toolbar, so the rest state (one accent .sbtn--new) and the select state
   (.sel-count + .sbtn + .sbtn--danger + .sbtn--icon) are the SAME controls the
   All-investigations list already uses for exactly this job. Nothing here is a
   second answer to a question the app had already answered. */
.acc__hd {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-24);
  flex-wrap: wrap;
}
.acc__hd .an-sec__hd {
  min-width: 0;
  flex: 1;
}
.acc__hd .an-sec__hd p {
  max-width: 62ch;
}
.acc__acts {
  flex: none;
}
.acc__empty {
  margin-top: var(--sp-4);
}
/* the search + counters bar is .an-bar, sitting inside the card above the table,
   exactly as the Analytics tab's Freshness table does it */
/* .an-search is already 236px wide; Access does not get its own number */
.acc__card {
  padding: 20px 22px;
}
.acc-nores {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
}
.acc-tbl {
  table-layout: fixed;
}
.acc-tbl th {
  padding-top: var(--sp-8);
}
.acc-tbl__ck {
  width: 46px;
}
.acc-tbl__mo {
  width: 44px;
}
.acc-tbl__a {
  width: 34%;
}
.acc-tbl__st {
  width: 118px;
}
.acc-tbl td {
  vertical-align: middle;
}
/* the flex layouts live on a span INSIDE the cell, never on the <td> itself,
   display:flex on a table-cell drops it out of the table layout model, which
   takes the fixed column widths and the row hairline with it */
/* End-to-end row highlight with rounded ends, so it reads as a finished shape
   rather than a band with two cut edges. It is painted on the CELLS, not on a
   pseudo-element of the <tr>: generated content on a table-row box becomes an
   anonymous cell and shifts every real cell one column across. Same --glass on
   hover / --glass-2 when selected as .srow, which is the app's list-row recipe;
   only the mechanics differ, because a table row is not a flex row. */
.acc-tbl {
  border-collapse: separate;
  border-spacing: 0;
}
.acc-row {
  cursor: pointer;
}
.acc-row > td {
  background: transparent;
  transition: background 0.12s var(--ease);
}
.acc-row:hover > td {
  background: var(--glass);
}
.acc-row.is-sel > td {
  background: var(--glass-2);
}
.acc-row > td:first-child {
  border-start-start-radius: var(--r-lg);
  border-end-start-radius: var(--r-lg);
}
.acc-row > td:last-child {
  border-start-end-radius: var(--r-lg);
  border-end-end-radius: var(--r-lg);
}
/* The divider must stay a straight line. It cannot be the cells' own
   border-bottom any more: those cells are rounded so the hover patch can be
   rounded, and a border follows the radius, which curled both ends of every
   hairline up. It is drawn on the ROW instead, which has no radius, so the two
   things are independent: straight rule, rounded patch. */
.acc-tbl tbody td {
  border-bottom: 0;
}
.acc-row {
  box-shadow: 0 1px 0 0 var(--border-subtle);
}
.acc-tbl tbody tr:last-child {
  box-shadow: none;
}
/* ---- ONE RULE FOR EVERY DIVIDED LIST WITH A ROUNDED HOVER PATCH ----------
   A straight full-width rule and a rounded patch cannot share an edge: the
   rule carries on past the corner curve and leaves a short tail at each end.
   So the two rules that TOUCH an active row step aside while it is active,
   its own and the one belonging to the row above it. The patch then reads as
   one closed shape and the rest of the list keeps its rules.
   Applied to all three places this shape occurs: the Access table (here), the
   Access drawer's area rows (.acc-ar), and .srow, the All-investigations /
   All-chats / All-dashboards list. */
.acc-row:hover,
.acc-row.is-sel {
  box-shadow: none;
}
.acc-row:has(+ .acc-row:hover),
.acc-row:has(+ .acc-row.is-sel) {
  box-shadow: none;
}
.acc-tbl thead:has(+ tbody > .acc-row:first-child:hover) th,
.acc-tbl thead:has(+ tbody > .acc-row:first-child.is-sel) th {
  border-bottom-color: transparent;
}
.acc-row > td .pop.is-open {
  z-index: 60;
}
.acc-row.is-owner {
  cursor: default;
}
.acc-row.is-owner:hover > td {
  background: transparent;
}
.acc-ck-pad {
  display: block;
  width: 22px;
  height: 22px;
}
.acc-tbl__p {
  overflow: hidden;
}
.acc-p {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  min-width: 0;
}
.acc-p__main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.acc-p__n {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  font-weight: 500;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink);
  min-width: 0;
  white-space: nowrap;
}
.acc-p__nm {
  overflow: hidden;
  text-overflow: ellipsis;
}
.acc-p__e {
  font-size: 12px;
  line-height: 16px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acc-tbl td.acc-tbl__p {
  color: var(--ink);
}
/* never wrap: the cell already shows at most two areas and hands the rest to
   the +N, so a wrapped chip means a squeezed column, not more information */
.acc-chips {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  flex-wrap: nowrap;
  min-width: 0;
}
.acc-chip,
.acc-all {
  white-space: nowrap;
}
.acc-chip {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  display: inline-block;
  line-height: 22px;
}
.acc-all {
  gap: 5px;
}
.acc-all svg {
  color: var(--sub);
}
.acc-more {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-faint);
  cursor: default;
}
/* the areas this chip stands in for, one per line. The base [data-tip] bubble is
   a single nowrap line, which turned three area names into a comma run; the
   attribute carries newlines and this un-nowraps them. Everything else about the
   bubble, surface, stroke, radius, shadow, timing, is the shared tooltip. */
.acc-more[data-tip]::after {
  white-space: pre;
  text-align: start;
  line-height: 20px;
  padding: 8px 12px;
}
/* somebody with Only some and nothing ticked: a real state the list has to be
   able to draw, and the one place on this page that earns a warning tone */
.acc-none {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  color: var(--warning);
  cursor: default;
}
.acc-none svg {
  width: 13px;
  height: 13px;
  flex: none;
}
.acc-tbl__mo {
  text-align: end;
}
.acc-tbl td.acc-tbl__mo {
  padding-inline-end: var(--sp-4);
}
/* the ⋯ only appears when the row is being used: hover, keyboard focus, or a
   menu already open on it, so five identical dots don't sit in the margin
   competing with the content */
.acc-tbl__mo .pop {
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.acc-row:hover .acc-tbl__mo .pop,
.acc-tbl__mo .pop.is-open,
.acc-tbl__mo .pop:focus-within {
  opacity: 1;
}

/* ---- the three drawers -------------------------------------------------- */
.acc-drawer {
  width: min(520px, calc(100vw - 48px));
}
.acc-body {
  gap: var(--sp-24);
}
.acc-sec {
  display: flex;
  flex-direction: column;
  gap: var(--sp-12);
}
.acc-sec__hd {
  display: flex;
  align-items: center;
}
.acc-sec__hd .ds-field__info {
  margin-inline-start: var(--sp-6);
}
.acc-seg {
  align-self: flex-start;
  width: 100%;
}
.acc-seg .seg__btn {
  flex: 1;
  justify-content: center;
}
.acc-dh {
  gap: var(--sp-12);
  flex: 1;
  min-width: 0;
}
.acc-dh__acts {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex: none;
}
/* who a bulk change is about, under the drawer title: the faces, the names, and
   a chevron into the full list. The trigger is a hover row (.menu-item's own
   padded + rounded + --glass recipe); the list itself is .menu with .table-item
   rows, the same dropdown the "Sourced from" chip opens. */
.acc-who {
  margin-top: var(--sp-8);
  align-self: flex-start;
}
.acc-who__btn {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  width: 100%;
  padding: var(--sp-8) var(--sp-12);
  margin-inline-start: calc(var(--sp-12) * -1);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  cursor: pointer;
  font-family: var(--font-sans);
  text-align: start;
  transition: background 0.12s var(--ease);
}
.acc-who__btn:hover {
  background: var(--glass);
}
.acc-who__btn .chev {
  width: 14px;
  height: 14px;
  color: var(--ricon);
  flex: none;
  transition: transform 0.16s var(--ease);
}
.acc-who.is-open .acc-who__btn .chev {
  transform: rotate(180deg);
}
.acc-who__menu {
  min-width: 300px;
  max-width: 360px;
}
.acc-who__row {
  gap: 10px;
  cursor: default;
}
.acc-who__row:hover {
  background: var(--glass);
}
.acc-who__row .ti-main {
  gap: 1px;
}
.acc-who__x {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: var(--r-full);
  background: none;
  color: var(--icon-muted);
  cursor: pointer;
  transition:
    background 0.12s var(--ease),
    color 0.12s var(--ease);
}
.acc-who__x:hover {
  background: var(--glass-2);
  color: var(--ink);
}
.acc-who__x svg {
  width: 13px;
  height: 13px;
}
/* faces first, in the order the sentence names them, so the leading avatar is
   the one on top and no set of initials is clipped by the face after it */
.acc-stack {
  display: flex;
  align-items: center;
  padding-inline-end: var(--sp-6);
}
.acc-stack .avatar {
  position: relative;
  margin-inline-end: -5px;
  box-shadow: 0 0 0 2px var(--menu-bg);
}
.acc-stack .avatar:nth-child(1) {
  z-index: 4;
}
.acc-stack .avatar:nth-child(2) {
  z-index: 3;
}
.acc-stack .avatar:nth-child(3) {
  z-index: 2;
}
.acc-stack .avatar:nth-child(4) {
  z-index: 1;
}
.acc-stack__n {
  background: var(--glass);
  color: var(--sub);
  font-size: 10px;
}
.acc-who__t {
  font-size: 13px;
  line-height: 20px;
  color: var(--ink-2);
  min-width: 0;
}
/* the email token field, the drawer's own .ds-input, grown to hold chips */
.acc-emails {
  height: auto;
  min-height: 52px;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-6);
  padding: 9px 10px;
  cursor: text;
}
.acc-tok {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-6);
  height: 26px;
  padding: 0 6px 0 10px;
  border-radius: var(--r-full);
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--glass-2);
  font-size: 12.5px;
  color: var(--ink);
  max-width: 100%;
}
.acc-tok.is-bad {
  color: var(--danger);
  box-shadow: inset 0 0 0 1px var(--danger);
}
.acc-tok__x {
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--icon-muted);
  cursor: pointer;
  flex: none;
}
.acc-tok__x:hover {
  background: var(--glass-2);
  color: var(--ink);
}
.acc-tok__x svg {
  width: 11px;
  height: 11px;
}
.acc-emails input {
  flex: 1;
  min-width: 140px;
  height: 26px;
  background: none;
  border: 0;
  outline: none;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
}
.acc-emails input::placeholder {
  color: var(--text-faint);
}
.acc-emailfield .ds-input:focus-within {
  border-color: var(--ink);
}
/* ---- the area list ---- */
.acc-areas {
  gap: var(--sp-8);
}
.acc-areas__hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-12);
  min-height: 18px;
}
.acc-areas__note {
  font-size: 12px;
  color: var(--text-faint);
}
.acc-areas__n {
  font-size: 12px;
  font-weight: 600;
  color: var(--sub);
  white-space: nowrap;
}
/* The row is padded and rounded so its hover fill has room on all four sides,
   the same shape .menu-item / .mi / .table-item use for a hoverable row. The
   list is pulled out by that padding so the labels still sit on the drawer's
   own left edge, and the hairline is drawn inset by a pseudo-element rather
   than as a border on a rounded box. */
.acc-arlist {
  display: flex;
  flex-direction: column;
  margin-inline: calc(var(--sp-12) * -1);
}
.acc-ar {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-16);
  padding: var(--sp-12);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background 0.12s var(--ease);
}
.acc-ar::after {
  content: "";
  position: absolute;
  inset-inline: var(--sp-12);
  bottom: 0;
  height: 1px;
  background: var(--border-subtle);
}
.acc-ar:last-child::after {
  display: none;
}
.acc-ar:hover {
  background: var(--glass);
}
.acc-ar:hover::after,
.acc-ar:has(+ .acc-ar:hover)::after {
  opacity: 0;
}
.acc-ar__main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.acc-ar__t {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  font-weight: 500;
  font-size: 14px;
  line-height: 20px;
  color: var(--sub);
  transition: color 0.12s var(--ease);
}
/* an area that is ON reads as ink; one that is off steps back to --sub, so the
   list can be scanned for what is on without reading every row */
.acc-ar.is-on .acc-ar__t {
  color: var(--ink);
}
.acc-ar__sens {
  flex: none;
}
/* one line, always: the rows are scanned, not read, and a description that
   wraps makes every row a different height */
.acc-ar__s {
  font-size: 12px;
  line-height: 17px;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.acc-areas__more {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: var(--sp-4);
  font-size: 13px;
}
.acc-areas__more svg {
  width: 14px;
  height: 14px;
}
.acc-areas__more .is-up {
  transform: rotate(180deg);
}
.acc-note {
  margin-top: var(--sp-4);
}
.acc-areas__err {
  gap: 0;
  margin-top: var(--sp-4);
}
.acc-foot {
  gap: var(--sp-16);
}
.acc-foot .drawer__footnote {
  flex: 1;
  min-width: 0;
}
.acc-foot__acts {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  flex: none;
}
/* the Remove dialog's "let them know" row */
.acc-notify {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  width: 100%;
  padding: 11px 14px;
  border-radius: var(--r-md);
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--glass-2);
  font-size: 13px;
  color: var(--ink-2);
  cursor: pointer;
  text-align: start;
}
.acc-notify:hover {
  background: var(--glass-2);
}

/* ---- the toast, with a second line and one action -----------------------
   Same component as every other toast in the app (.toast in the SEARCH block
   above); these rules only add the two parts the Access flow needs. */
.toast__main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.toast__t {
  font-size: 13px;
  line-height: 18px;
  color: var(--ink);
}
.toast__s {
  font-size: 11.5px;
  line-height: 16px;
  color: var(--sub);
}
.toast--action {
  pointer-events: auto;
  padding-inline-end: 8px;
  gap: var(--sp-20);
}
.toast__act {
  background: none;
  border: 0;
  padding: 6px 10px;
  border-radius: var(--r-sm);
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.12s var(--ease);
}
.toast__act:hover {
  background: var(--glass);
}

/* ---- ONE checked look across the app ------------------------------------
   The list page's select-mode tick was the only control that went brand-orange
   when it turned on. It is the same "this row is picked" reading as .ck, so it
   now wears the same neutral selected treatment instead of being a second
   answer to the same question. */
.srow__ck.checked,
.srow__ck.mixed {
  background: var(--sel-bg);
  border-color: var(--sel-bg);
  color: var(--sel-fg);
}
/* mixed: some of the rows below are ticked, not all. Same control, third state. */
.srow__ck.mixed svg {
  width: 14px;
  height: 14px;
}

/* ---- tablet / narrow ----------------------------------------------------
   Can-ask-about is the column that goes: it is the widest and the most
   repetitive, and every value in it is restated in full in the drawer the row
   opens. Person and Status stay, because those are what the list is FOR. */
@media (max-width: 1024px) {
  .acc__hd {
    flex-direction: column;
    align-items: stretch;
  }
  .acc__hd .btn {
    align-self: flex-start;
  }
  .acc-tbl__a {
    display: none;
  }
  .acc-tbl__st {
    width: 110px;
  }
}

/* ============================================================================
   AMBIENT-BLENDED SURFACES  (option, dark theme only)
   ----------------------------------------------------------------------------
   Ameer, 19 Aug: on the Build Dashboard landing the composer and the
   "start with one of these" cards read as grey rectangles pasted on top of the
   deep-sea backdrop. Measured on the rendered page they were rgb(33,33,33) - a
   flat NEUTRAL grey - sitting on a COOL rgb(23,29,35) ambient: 10 points
   brighter and a DIFFERENT HUE, so the eye read them as foreign objects.

   Round 1 made them a thin white veil. Too far: it removed the surface
   altogether and read as glass. Ameer: "I was fine with the earlier thing, it
   was just slightly off - do something in between."

   So this is the middle. Each surface is still real and mostly opaque, but:
     - one step DOWN from its DS token, so it sits into the page instead of
       floating over it, and
     - ~78% opaque, so a fifth of the ambient bleeds through and the surface
       takes the module hue for free - teal on Build Dashboard, warm on
       Investigation, purple on Document Intelligence.
   Net on the landing composer: rgb(33,33,33) neutral becomes ~rgb(28,29,30)
   cool. The hue clash is gone, the surface is still unmistakably a surface.

   Round 3, same day: Ameer asked for the TOP BAR to match - the neonDB chip,
   the EN chip and the light/dark toggle were still opaque #27272a pills on the
   same ambient, which is the identical mismatch one level up. They now run off
   the same recipe, on every screen (the top bar always sits directly on the
   ambient, never over a card), so the whole chrome reads as one system.

   Every value lives in the custom properties below. Tune there, nowhere else.
   The landing pair is scoped to .home so the thread dock, the answer cards and
   every other screen are untouched. Dark only - the light theme is
   white-on-near-white and has no hue clash to fix.

   TO REVERT: delete this whole block. Nothing above depends on it.
   ========================================================================== */
html:not([data-theme="light"]) {
  /* landing composer + suggestion cards: DS --card #212121 one step darker, 78% opaque */
  --amb-surface: rgba(29, 29, 29, 0.78);
  /* hover = DS --pill #27272a at the same opacity, so the lift matches the rail */
  --amb-surface-hover: rgba(39, 39, 42, 0.82);
  /* top-bar chips + icon buttons: DS --hchip-bg #27272a one step darker, 78% opaque */
  --amb-chip: rgba(35, 35, 38, 0.78);
  /* hover = DS --cc-hover #3f3f46, the step the composer controls already use */
  --amb-chip-hover: rgba(63, 63, 70, 0.82);
  /* hairline resolves to ~rgb(48,49,49), the same weight the old opaque
     --card-stroke resolved to, so every box outline stays exactly as readable.
     It has to carry more of the shape now that the fills are recessive. */
  --amb-stroke: rgba(255, 255, 255, 0.09);
  --amb-stroke-hover: rgba(255, 255, 255, 0.14);
  /* controls keep the same +6 step over the surface they had at #27272a on #212121 */
  --amb-control: rgba(255, 255, 255, 0.055);
  --amb-control-hover: rgba(255, 255, 255, 0.12);
}

/* ---- landing: composer + "start with one of these" cards ---- */
html:not([data-theme="light"]) .home .composer,
html:not([data-theme="light"]) .home .scard {
  background: var(--amb-surface);
  border-color: var(--amb-stroke);
}
html:not([data-theme="light"]) .home .scard:hover {
  background: var(--amb-surface-hover);
  border-color: var(--amb-stroke-hover);
}
/* The controls INSIDE the composer have to follow, or they become the new thing
   that stands out: an opaque #27272a pill on a tinted card is the same mismatch
   one level down. .is-on / .is-active are deliberately left alone, a selected
   control is SUPPOSED to be solid. */
html:not([data-theme="light"]) .home .composer .cbtn:not(.is-on),
html:not([data-theme="light"]) .home .composer .cchip,
html:not([data-theme="light"]) .home .composer .send:not(.is-active),
html:not([data-theme="light"]) .home .scard .ibox {
  background: var(--amb-control);
  border-color: var(--amb-stroke);
}
html:not([data-theme="light"]) .home .composer .cbtn:not(.is-on):hover,
html:not([data-theme="light"]) .home .composer .cchip:hover,
html:not([data-theme="light"]) .home .composer .send:not(.is-active):hover {
  background: var(--amb-control-hover);
}

/* ---- top bar: neonDB chip, EN chip, light/dark toggle ----
   Type selector `html` included on purpose: it lifts these past the existing
   `[data-theme="dark"] .hchip,[data-theme="dark"] .hbtn` border rule above,
   which is otherwise the same specificity and would keep the old .05 hairline. */
html:not([data-theme="light"]) .hchip,
html:not([data-theme="light"]) .hbtn {
  background: var(--amb-chip);
  border: 1px solid var(--amb-stroke);
}
html:not([data-theme="light"]) .hchip:hover,
html:not([data-theme="light"]) .hbtn:hover {
  background: var(--amb-chip-hover);
  border-color: var(--amb-stroke-hover);
}

/* ============================================================
   DASHBOARDS: the library (see dashboardsHTML in js/app.js)

   Nothing here introduces a colour, a radius or a type size that was not already
   in the kit. The card is the canvas block's surface (--box on --panel, its
   --box-stroke hairline, the 16px radius), the tabs are .panel-tabs, the switch
   is .vtoggle, the pills are .tag--*, and every value below resolves to a token.
   These rules exist to LAY OUT those parts, not to restyle them.
   ============================================================ */
.dlib-page {
  flex: 1;
  overflow-y: auto;
  padding: 8px 24px 48px;
}
.dlib-inner {
  max-width: 1360px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* ---- THE SAME MATERIAL AS THE BUILDER ----------------------------------------
   The first cut painted this page's cards straight onto the ambient backdrop in an
   absolute dark colour, and the backdrop is a cyan wash that is bright at the top
   and near-black at the bottom, so every card, every hairline and every control
   read as a flat cut-out laid over a photograph. The builder does not have that
   problem because its content sits INSIDE a container that supplies its own base
   (.workbox), and everything within it is then a small alpha step off that base.

   So the shelf gets the same container, with the same recipe copied from .workbox
   verbatim: the accent wash across the top, a body that lifts a couple of points
   towards the bottom, an alpha stroke, the 20px radius. The cards inside are then
   exactly what a canvas block is: --box on a known surface. */
html[data-theme="dark"][data-module="dashboard"] .dlib-shell {
  background:
    radial-gradient(
      ellipse 105% 46% at 50% -8%,
      var(--glow) 0%,
      var(--glow) 14%,
      transparent 78%
    ),
    linear-gradient(180deg, #171a1e 45%, #1b2126 100%);
  border-color: rgba(255, 255, 255, 0.1);
}
.dlib-shell {
  background: var(--panel);
  border: 1px solid var(--panel-stroke);
  border-radius: 20px;
  padding: 20px 20px 24px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* Chrome that sits OUTSIDE the container, on the ambient backdrop itself, cannot
   have a base of its own, so it uses the other half of the same idea: white at a
   low alpha, which lifts whatever is behind it by a fixed amount and therefore
   holds the same relationship at every point of the wash. */
html[data-theme="dark"][data-module="dashboard"] .dlib-head__r .search-bar,
html[data-theme="dark"][data-module="dashboard"] .dlib-head__r .sbtn {
  background: var(--glass);
  border-color: rgba(255, 255, 255, 0.09);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-head__r .sbtn:hover,
html[data-theme="dark"][data-module="dashboard"]
  .dlib-head__r
  .search-bar:focus-within {
  background: var(--glass-2);
  border-color: rgba(255, 255, 255, 0.14);
}
/* the tab rule, the count bubbles and the sort control, all inside the container */
html[data-theme="dark"][data-module="dashboard"] .dlib-tabs {
  border-bottom-color: rgba(255, 255, 255, 0.09);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-bar .tab-badge {
  background: rgba(255, 255, 255, 0.08);
  color: var(--sub);
}
html[data-theme="dark"][data-module="dashboard"]
  .dlib-bar
  .panel-tab.sel
  .tab-badge {
  background: rgba(255, 255, 255, 0.14);
  color: var(--ink);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-bar__r .sbtn {
  background: var(--glass);
  border-color: rgba(255, 255, 255, 0.09);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-bar__r .sbtn:hover {
  background: var(--glass-2);
  border-color: rgba(255, 255, 255, 0.14);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-bar__r .vtoggle {
  background: var(--glass);
  border-color: rgba(255, 255, 255, 0.08);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-bar__r .vt.sel {
  background: rgba(255, 255, 255, 0.13);
}
/* the card's own internals: the divider above the footer, and the two nested surfaces */
html[data-theme="dark"][data-module="dashboard"] .dlib-card {
  border-color: rgba(255, 255, 255, 0.1);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-card__top {
  background: rgba(255, 255, 255, 0.03);
  border-bottom-color: rgba(255, 255, 255, 0.08);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-mini {
  background: rgba(255, 255, 255, 0.045);
  border-color: rgba(255, 255, 255, 0.07);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-row {
  border-color: rgba(255, 255, 255, 0.1);
}
html[data-theme="dark"][data-module="dashboard"] .dlib-row__ic {
  background: rgba(255, 255, 255, 0.07);
}
/* and the hover state to go with it. The two rules above are more specific than
   .dlib-card:hover, so in this module the hover stroke never landed at all.
   The fill is --pill, not the generic --surface-raised: this block's whole point
   is that Dashboard-dark is ONE material on one axis (block #1c2126, pill one
   step above it), and the neutral zinc the shared rule reaches for is off that
   axis, so a hover would have shifted hue as well as level. The stroke is one
   step up from the .10 resting one, the same relationship --border-hover gives
   the light theme. */
html[data-theme="dark"][data-module="dashboard"] .dlib-card:hover,
html[data-theme="dark"][data-module="dashboard"] .dlib-row:hover {
  background: var(--pill);
  border-color: rgba(255, 255, 255, 0.18);
}

.dlib-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  padding-top: 8px;
}
.dlib-head__l h1 {
  font-size: 30px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.dlib-head__l p {
  margin-top: 6px;
  font-size: 14px;
  color: var(--sub);
}
.dlib-head__r {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
/* the same .search-bar atom, sized to sit in a toolbar rather than own a page */
.search-bar--inline {
  height: 44px;
  width: 320px;
  max-width: 100%;
  padding: 0 14px;
  gap: 10px;
  border-radius: 12px;
}
.search-bar--inline input {
  font-size: 14px;
}

.dlib-bar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}
.dlib-shell .dlib-bar {
  margin-bottom: 2px;
}
.dlib-tabs {
  margin-bottom: 0;
  flex: 1;
  min-width: 280px;
}
.dlib-bar__r {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 6px;
}
.dlib-sortlbl {
  font-size: 13px;
  color: var(--sub);
  white-space: nowrap;
}

/* ---- grid ---- */
/* TWO per row: the card carries the widget chips now, and they need the width to
   read as labels rather than wrap into confetti. auto-fill keeps it sane on a
   narrow window by dropping to one. */
.dlib-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
  gap: 18px;
  align-items: stretch;
}
/* THE CARD SURFACE IS --card, NOT --box (Ameer, 3 Sep: "the cards are grey,
   can you make it white"). In light theme --box is #f5f5f4 and --card is
   #ffffff, so a listing of grey cards sat inside a white shell on a pale page,
   which is the one place in the product where a card is darker than the panel
   holding it. Every other interactive card on the page - .scard, .hcard,
   .inv-card - is already built on --card, so this is the family's own token,
   not a new value. In DARK the two resolve to the same colour (#212121 base,
   #1c2126 in the dashboard module) and so do their strokes, so nothing about
   the dark theme moves. */
.dlib-card {
  position: relative;
  display: flex;
  flex-direction: column;
  text-align: start;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: 18px;
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
/* HOVER IS THE DS INTERACTIVE-SURFACE STATE, not the module accent (Ameer,
   3 Sep: "while hovering a blue border is coming, that blue border, no need,
   you can have a different style"). --accent-border in this module is Cyan/50
   at 45%, which is the blue he means. The house state for an interactive card
   is a raised fill plus the neutral hover stroke, Figma Suggestion Card
   5925:22628, and it is what .scard, .hcard, .inv-card and .sbtn all already
   use: brand colour means "the one action", never "the cursor is here"
   (see the colour rule). Applied to the row below as well, because grid and
   list are two views of one listing and a hover cannot differ between them. */
.dlib-card:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.dlib-card__body {
  display: flex;
  flex-direction: column;
  padding: 20px 20px 16px;
  flex: 1;
}
/* The name on the left, the two controls on the right. The row is the height of
   the title's FIRST line and the controls centre on it, so they sit level with the
   name whether it runs to one line or two, instead of floating above it. */
.dlib-card__hd {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 22px;
}
.dlib-card__hd > .dlib-card__t {
  flex: 1;
  min-width: 0;
}
.dlib-card__t {
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.28;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.dlib-card__src {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  margin-top: 9px;
}
/* the same live mark the top bar's database chip uses, at card scale */
.dlib-card__src .dot {
  color: var(--success);
  flex: none;
}
.dlib-db {
  font-size: 12.5px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* RESERVES TWO LINES ALWAYS, even when the text only needs one. It clamped at
   two already, but nothing reserved the space for a second one, so a short
   description left every element under it sitting higher than on a card whose
   description actually wrapped. That is not visible card-by-card, but CSS grid
   stretches every card in a ROW to match its tallest neighbour, and this card's
   own footer is pinned to its bottom (margin-top:auto) - so the short-description
   cards absorbed that stretch as extra, invisible space between the chips and
   the footer, while a genuinely two-line card (which was already at full height)
   had none to absorb, and read as "no gap" sitting next to its own stretched
   neighbours (Ameer, 22 Aug: "the first card has two line subtext, that is why
   there is no gap... keep the real estate for two lines for all the cards").
   min-height, not height: a two-line description still grows this box exactly
   as far as it needs to, this only stops a one-line one from being shorter. */
.dlib-card__d {
  font-size: 13.5px;
  line-height: 19px;
  color: var(--sub);
  margin-top: 10px;
  min-height: 38px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---- what the board is built from ----
   The chips ARE the differentiator: several dashboards read the same source and
   carry similar names, so "which one has the regional table" is the question the
   card has to answer. Neutral, not accent - they are labels, not actions. */
/* NOWRAP. A chip may shrink and clip its own label rather than push the row onto a
   second line, and the overflow chip never shrinks, so the count stays readable. */
.dlib-made {
  display: flex;
  flex-wrap: nowrap;
  gap: 7px;
  margin-top: 14px;
  align-items: flex-start;
  min-width: 0;
}
.dlib-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 11px;
  border-radius: 8px;
  background: var(--glass);
  border: 1px solid var(--line);
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-2);
  white-space: nowrap;
  min-width: 0;
  flex: 0 1 auto;
  overflow: hidden;
}
.dlib-chip__t {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dlib-chip svg {
  width: 14px;
  height: 14px;
  flex: none;
  color: var(--ricon);
}
.dlib-chip--more {
  color: var(--meta);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.dlib-card__ft {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: auto;
  padding-top: 13px;
  font-size: 12px;
  color: var(--meta);
}

/* ⋯ lives in the header row now that there is no thumbnail to sit on, and now
   that the pin icon is gone from every card (Ameer, 22 Aug: "I don't want that
   pin icon on all the cards"). Reveals on hover, same as before. */
.dlib-card__ctl {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: none;
  margin-inline-end: -6px;
}
.dlib-card__ctl .w-more {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  color: var(--ricon);
  cursor: pointer;
  opacity: 0;
  transition:
    opacity 0.14s var(--ease),
    background 0.12s var(--ease),
    color 0.12s var(--ease);
}
.dlib-card__ctl .w-more:hover {
  background: var(--glass);
  color: var(--ink);
}
.dlib-card:hover .dlib-card__ctl .w-more,
.dlib-card__ctl .w-more:focus-visible {
  opacity: 1;
}

/* ---- list view: the same shelf at row density ---- */
.dlib-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* the list view of the same listing: same surface token, same hover, see .dlib-card */
.dlib-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  background: var(--card);
  border: 1px solid var(--card-stroke);
  border-radius: 12px;
  cursor: pointer;
  transition:
    background 0.15s var(--ease),
    border-color 0.15s var(--ease);
}
.dlib-row:hover {
  background: var(--surface-raised);
  border-color: var(--border-hover);
}
.dlib-row__ic {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: var(--pill);
  color: var(--ricon);
  flex: none;
}
.dlib-row__main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.dlib-row__t {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dlib-row__d {
  font-size: 12.5px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dlib-row__db,
.dlib-row__m {
  font-size: 12.5px;
  color: var(--meta);
  flex: none;
  white-space: nowrap;
}
.dlib-row .dlib-card__more {
  position: static;
}
.dlib-row .dlib-card__more .w-more {
  position: static;
  background: none;
  border: 0;
  width: 30px;
  height: 30px;
}
@media (max-width: 1100px) {
  .dlib-row__conf,
  .dlib-row__d {
    display: none;
  }
}
@media (max-width: 820px) {
  .dlib-head__r {
    width: 100%;
  }
  .search-bar--inline {
    flex: 1;
    width: auto;
  }
  .dlib-row__db {
    display: none;
  }
}
/* "no matches here, but N over there" - a text button, the .bc-link treatment */
.dlib-elsewhere {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 2px;
}
.dlib-jump {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
}
.dlib-jump:hover {
  text-decoration: underline;
}

/* ---- BUILD DASHBOARD: focus mode ---------------------------------------------
   The builder expanded to the whole viewport. Deliberately NOT a second copy of
   the builder on a fixed layer (the pattern .er-full and the presentation view
   use): those layers render read-only content, and a second copy of an EDITOR
   would be a second set of drag, resize, pin and undo paths to keep in step with
   the first. This is the same DOM, with the shell taken away around it, so there
   is exactly one builder and focus mode cannot drift from it.

   The stage insets are set in render() (navSide/panelSide); everything here is
   the chrome that has to get out of the way. */
body.dash-full .nav {
  display: none;
}
body.dash-full .topbar {
  display: none;
}
body.dash-full .stage {
  top: 16px;
  bottom: 0;
}
body.dash-full .history {
  top: 16px;
  bottom: 16px;
  right: 16px;
}
[dir="rtl"] body.dash-full .history {
  right: auto;
  left: 16px;
}
/* The COLLAPSED history is a tail pinned to the top edge, not a full-height rail:
   .history.collapsed sets bottom:auto for exactly that, and the rule above was
   putting bottom:16px back, so in full screen the tail stretched the whole height
   and justify-content:center floated its two icons into the middle. Same treatment
   as the standard builder now - flush to the edge, hanging from the top. */
body.dash-full .history.collapsed {
  top: 16px;
  bottom: auto;
  right: 0;
}
[dir="rtl"] body.dash-full .history.collapsed {
  right: auto;
  left: 0;
}
body.dash-full .workbox {
  margin-bottom: 16px;
}
/* the control is a toggle now, so it says which state it is in */
.ctool--on {
  background: var(--glass-2);
  color: var(--ink);
  border-color: var(--accent-border);
}

/* ---- WIDGET LOADING SKELETON (W.skeleton, js/widgets.js) ----------------------
   The bars reuse the app's existing shimmer exactly (.thinking-skel above): the
   same three-stop gradient over --glass/--glass-2 at 400% width, sliding on the
   same @keyframes shimmer, so a widget building on the canvas and a thinking
   answer card move at one speed instead of two. */
.skel {
  display: flex;
  flex-direction: column;
  gap: 0;
  flex: 1;
  min-height: 0;
  justify-content: center;
}
.skel--split {
  flex-direction: row;
  align-items: center;
  gap: 22px;
}
.skel-col {
  display: flex;
  flex-direction: column;
  gap: 14px;
  flex: 1;
  min-width: 0;
}
.skel-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.skel-row + .skel-row {
  margin-top: 14px;
}
.skel-row--axis {
  justify-content: space-between;
  margin-top: 14px;
}
.skel-line,
.skel-dial {
  background: linear-gradient(
    90deg,
    var(--glass) 25%,
    var(--glass-2) 37%,
    var(--glass) 63%
  );
  background-size: 400% 100%;
  animation: shimmer 1.4s infinite linear;
  border-radius: 6px;
  flex: none;
  display: block;
}
.skel-line {
  height: 12px;
}
.skel-row > .skel-line:last-child {
  margin-inline-start: auto;
}
.skel-dial {
  width: 110px;
  height: 110px;
  border-radius: 50%;
  flex: none;
  -webkit-mask: radial-gradient(circle, transparent 52%, #000 53%);
  mask: radial-gradient(circle, transparent 52%, #000 53%);
}
@media (prefers-reduced-motion: reduce) {
  .skel-line,
  .skel-dial {
    animation: none;
  }
}
/* a block that is still building is inert: nothing to grab, nothing to open */
.cblock.is-building {
  cursor: default;
  pointer-events: none;
}

/* ============================================================
   A SAVED DASHBOARD, read-only (dashboardViewHTML)
   Same material as everywhere else in this module: the container carries the
   accent wash and the blocks are one alpha step off it, so a saved board and the
   builder that made it are recognisably the same object.
   ============================================================ */
.dv-page {
  flex: 1;
  overflow-y: auto;
  padding: 8px 24px 48px;
}
.dv-inner {
  max-width: 1360px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.dv-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding-top: 4px;
}
/* the name and nothing else: no strapline, no "created from", no metadata the
   reader did not ask for */
.dv-title {
  font-size: 30px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
  min-width: 0;
}
/* ONE ROW, not two: the filter controls and the three actions are a single
   cluster on the right of the title, not a second line under it (Ameer, 22
   Aug, third pass: "why you are making two different lines... everything can
   be together"). */
.dv-head__right {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.dv-head__div {
  flex: none;
  width: 1px;
  height: 20px;
  background: var(--border-subtle);
}
.dv-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
/* Only two filters ever show here: Date range and Compare (Branch / Coverage
   plan / Provider are removed from this row entirely, not just quieted - and
   there is no "Filters" label in front of them, the controls say what they are
   on their own). The date dropdown is the plain .sbtn--filter button used
   nowhere-special-about-it - the same trigger the Dashboards library's sort
   control and the Share drawer's role picker already use, so this page is not
   introducing a control shape of its own. */
.dv-filters {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.dv-filters__cmp {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  padding: 0 4px;
}
.dv-filters__cmplbl {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--sub);
}
/* who can see it, answered before it is asked */
.dv-board {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-content: flex-start;
  background: var(--panel);
  border: 1px solid var(--panel-stroke);
  border-radius: 20px;
  padding: 20px;
}
/* THIS BOARD IS FLEX, not the grid the live builder and Present use, so a
   section band's grid-column:1/-1 does nothing here - it needs its own way to
   claim the whole row. flex-basis:100% on a wrapped flex item is that: the next
   card starts a fresh line after it, the same effect the grid gave it for free
   elsewhere (Ameer, 22 Aug, the saved-dashboard reader was still missing its
   own sections entirely, this is what makes them look right once they show). */
.dv-board .csec {
  flex: 1 1 100%;
  min-height: 0;
}
.dv-board .csec--l1 {
  padding-top: 16px;
}
.dv-board .csec--l2 {
  padding-top: 8px;
}
.dv-board .csec:first-child {
  padding-top: 0;
}
html[data-theme="dark"][data-module="dashboard"] .dv-board {
  background:
    radial-gradient(
      ellipse 105% 46% at 50% -8%,
      var(--glow) 0%,
      var(--glow) 14%,
      transparent 78%
    ),
    linear-gradient(180deg, #171a1e 45%, #1b2126 100%);
  border-color: rgba(255, 255, 255, 0.1);
}

/* ---- Share + Connect: one drawer, two tabs ----
   Reuses .drawer wholesale (scrim, panel, head, scrolling body) and .panel-tabs for
   the strip, so the only new rules here are the layout of what sits inside. */
.share-drawer {
  width: min(480px, calc(100vw - 48px));
}
.shr-tabs {
  margin: 16px 0 -18px;
  border-bottom: 0;
}
/* on step 2 of Save this dashboard the same strip sits in the BODY, not the head,
   so it takes the body's own rhythm instead of the head's negative offset */
.drawer__body > .shr-tabs {
  margin: 0 0 -14px;
}
.drawer__body .shr-sect {
  display: flex;
  flex-direction: column;
}
.shr-h {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 4px;
}
.shr-note {
  font-size: 12.5px;
  line-height: 18px;
  color: var(--sub);
  margin-bottom: 12px;
}
/* one row of things you can do WITH the board, each saying what it actually sends */
.shr-acts {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.shr-act {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: start;
  cursor: pointer;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--glass);
  border: 1px solid var(--line);
  transition:
    background 0.12s var(--ease),
    border-color 0.12s var(--ease);
}
.shr-act:hover {
  background: var(--glass-2);
  border-color: var(--border-hover);
}
.shr-act__ic {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  display: grid;
  place-items: center;
  background: var(--pill);
  color: var(--ricon);
  flex: none;
}
.shr-act__main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.shr-act__t {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}
.shr-act__d {
  font-size: 12.5px;
  color: var(--sub);
}
.shr-act__go {
  color: var(--ricon);
  flex: none;
}
/* ---- A DRAWER IS NOT THE CANVAS: the controls take its surface, not the board's ----
   .sbtn's default fill is --card (#1c2126) over a --card-stroke of
   rgba(140,175,190,.16), and .shr-act__ic's plate is --pill (#232a2f). Both of
   those are CANVAS surfaces, mixed cool on purpose so they sit on the dashboard's
   blue ambient. A drawer's surface is --menu-bg (#1c1c20), a flat neutral, so in
   here the same tokens render as bluish chips on a grey panel, which is exactly
   what Ameer saw on Copy link, Can view and the Export icon plate (21 Aug 2026).

   They take the drawer's own glass pair instead, the same one .shr-link, .shr-act
   and every field in here already use, so the whole panel is one surface family.
   Invite keeps the accent: it is the primary action, not a surface. */
.drawer .sbtn {
  background: var(--glass-2);
  border-color: var(--line);
}
.drawer .sbtn:hover {
  background: var(--glass-2);
  border-color: var(--border-hover);
}
/* Invite is the primary action, so it keeps the accent. It needs restating at
   this specificity or the .drawer .sbtn rule above would repaint it as glass. */
.drawer .sbtn--new {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.drawer .sbtn--new:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}
.drawer .shr-act__ic {
  background: var(--glass-2);
  color: var(--ricon);
}
/* the settled "Connected" line: grey text, small tick, no pill and no status dot.
   See the note in connectTabHTML for why it is not a --success tag. */
.cnx-on {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--sub);
  white-space: nowrap;
}
.cnx-on svg {
  width: 14px;
  height: 14px;
  color: var(--icon-muted);
}
.dialog--wide {
  max-width: 560px;
  width: calc(100vw - 48px);
  text-align: start;
}
/* a dialog button can carry an icon; keep it on the same line as its label */
.dialog__actions .dlg-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  white-space: nowrap;
}
.dialog__actions .dlg-btn svg {
  width: 16px;
  height: 16px;
  flex: none;
}
.dlg-t--left,
.dlg-m--left {
  text-align: start;
}
.shr-row {
  display: flex;
  gap: 8px;
  align-items: center;
}
.shr-row .dlg-input {
  flex: 1;
  margin: 0;
}
.shr-people {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 10px;
}
.shr-p {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
}
.shr-p__main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.shr-p__n {
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 8px;
}
.shr-p__e {
  font-size: 12.5px;
  color: var(--meta);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.shr-p__r {
  font-size: 13px;
  color: var(--sub);
  white-space: nowrap;
}
.shr-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--glass);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.shr-link__ic {
  display: flex;
  color: var(--ricon);
  flex: none;
}
.shr-link__u {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cnx-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.cnx-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
}
.cnx-row__ic {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  display: grid;
  place-items: center;
  background: var(--pill);
  color: var(--ricon);
  flex: none;
}
.cnx-row__main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.cnx-row__t {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}
.cnx-row__d {
  font-size: 12.5px;
  color: var(--sub);
}
@media (max-width: 640px) {
  .shr-row {
    flex-wrap: wrap;
  }
  .shr-row .dlg-input {
    flex: 1 1 100%;
  }
}

/* ---- LIGHT THEME: the same wash, on white -------------------------------------
   Investigation and Document Intelligence already carry a light accent wash at the
   top of their containers in BOTH themes (the answer card's --glow). Build
   Dashboard only had it in dark, so in light its three containers were flat white
   panels sitting on a pale blue page: the one module whose containers did not
   belong to the page behind them.

   Same recipe as the dark rule above, same ellipse, same --glow, over a white body
   that lifts a hair towards the bottom. --glow is declared per MODULE, not per
   theme, so this is Dashboard's own cyan in both. */
html[data-theme="light"][data-module="dashboard"] .workbox,
html[data-theme="light"][data-module="dashboard"] .dlib-shell,
html[data-theme="light"][data-module="dashboard"] .dv-board {
  background:
    radial-gradient(
      ellipse 105% 46% at 50% -8%,
      var(--glow) 0%,
      var(--glow) 14%,
      transparent 78%
    ),
    linear-gradient(180deg, #ffffff 45%, #fbfeff 100%);
}
/* The history rail is a main container too, and it stands at the same height as
   the workbox beside it. Left flat it was the one box in the viewport whose top
   did not catch the light, which reads as a different material rather than as a
   different panel. */
html[data-theme="dark"][data-module="dashboard"] .history {
  background:
    radial-gradient(
      ellipse 130% 40% at 50% -6%,
      var(--glow) 0%,
      var(--glow) 14%,
      transparent 78%
    ),
    linear-gradient(180deg, #171a1e 45%, #1b2126 100%);
  border-color: rgba(255, 255, 255, 0.1);
}
html[data-theme="light"][data-module="dashboard"] .history {
  background:
    radial-gradient(
      ellipse 130% 40% at 50% -6%,
      var(--glow) 0%,
      var(--glow) 14%,
      transparent 78%
    ),
    linear-gradient(180deg, #ffffff 45%, #fbfeff 100%);
}

/* ============================================================
   SAVE THIS DASHBOARD (the drawer)
   ============================================================
   Everything here is layout for content the app already had components for: the
   fields are .ds-field / .ds-input (the connection form's), the switch is .sw
   (the Access drawer's), the step strip is .wiz-steps (the data source wizard's),
   and step 2 is the Share drawer's own body. What is new is the filter list, and
   the only reason it is new is that its switch LEADS the row: this list is
   scanned down the on/off column first and read second, which is the opposite of
   .acc-ar, where the switch trails because the area's name is the thing you look
   for. Same atom, different job, so the same atom in a different position.
   ============================================================ */
.save-drawer {
  width: min(560px, calc(100vw - 48px));
}
/* TWO steps, not three, so the connector cannot be flex:1: one line stretched
   across the whole header reads as a rule under the title rather than as the
   thread between two dots. Capped, and the pair sits at the leading edge. */
.save-drawer .wiz-steps {
  justify-content: flex-start;
}
.save-drawer .wiz-line {
  flex: 0 0 40px;
  min-width: 0;
}
/* the description field. A textarea on the .ds-input recipe rather than a second
   input recipe: same fill, same stroke, same focus, just able to hold two lines. */
.ds-input--ta {
  height: auto;
  min-height: 72px;
  padding: 11px 14px;
  line-height: 20px;
  resize: vertical;
}

.svf {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.svf__hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
/* the section label wears the same 11px uppercase as .drawer__label, with the
   filter glyph in front of it, so it reads as a heading for the list and not as a
   sixth field label */
.svf__lbl {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 11px;
  line-height: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sub);
}
.svf__lbl svg {
  width: 15px;
  height: 15px;
  color: var(--icon-muted);
}
.svf__n {
  font-size: 12px;
  font-weight: 600;
  color: var(--sub);
  white-space: nowrap;
}
.svf__list {
  display: flex;
  flex-direction: column;
  margin-inline: -12px;
}
/* One row, one control. The whole row is the hit area (the switch, the icon and
   the name all toggle it), which is why it carries the hover fill: a row that
   responds has to look like one. */
.svf-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background 0.12s var(--ease);
}
.svf-row::after {
  content: "";
  position: absolute;
  inset-inline: 12px;
  bottom: 0;
  height: 1px;
  background: var(--border-subtle);
}
.svf-row:last-child::after {
  display: none;
}
.svf-row:hover {
  background: var(--glass);
}
.svf-row:hover::after,
.svf-row:has(+ .svf-row:hover)::after {
  opacity: 0;
}
.svf-ic {
  display: grid;
  place-items: center;
  color: var(--icon-muted);
  flex: none;
  transition: color 0.12s var(--ease);
}
.svf-ic svg {
  width: 16px;
  height: 16px;
}
/* a filter that is ON reads as ink, one that is off steps back to --sub, so the
   list can be scanned for what is on without reading every row. Same rule as
   .acc-ar.is-on, deliberately: two lists of switches in one product should not
   answer "which of these are live" in two different ways. */
.svf-t {
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  color: var(--sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color 0.12s var(--ease);
}
.svf-row.is-on .svf-t {
  color: var(--ink);
}
.svf-row.is-on .svf-ic {
  color: var(--ricon);
}
/* the (i). Sized and toned to be findable and never louder than the name it sits
   beside; it is the only thing in the row that is NOT the row's own toggle, so it
   stops the click rather than passing it on. */
.svf-i {
  width: 20px;
  height: 20px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--text-faint);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition:
    color 0.12s var(--ease),
    background 0.12s var(--ease);
}
.svf-i svg {
  width: 14px;
  height: 14px;
}
.svf-i:hover {
  color: var(--ink);
  background: var(--glass-2);
}
.svf-i:focus-visible {
  outline: 2px solid var(--border-hover);
  outline-offset: 1px;
}
.svf-row .pop {
  display: flex;
  flex: none;
}
.svf-meta {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  color: var(--text-faint);
  white-space: nowrap;
}
/* the value count's dot. Not a status light: it is the same quiet mark the
   connector rows use to separate a count from the word next to it. */
.svf-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--icon-muted);
  flex: none;
}
.svf-x {
  width: 24px;
  height: 24px;
  flex: none;
  color: var(--text-faint);
}
.svf-x:hover {
  color: var(--ink);
}
.svf-x svg {
  width: 15px;
  height: 15px;
}
.svf__note {
  font-size: 12px;
  line-height: 18px;
  color: var(--text-faint);
}
.svf__add {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.svf__add svg {
  width: 15px;
  height: 15px;
}
.svf__acts {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* ---- the info popover: two questions, in that order ----
   What it does is the promise; How it works is the part that stops the promise
   being read as more than it is (which column, and what happens to a widget that
   does not have it). Both are short enough to read standing up, which is the only
   length that earns a popover. */
.svf-pop {
  width: 290px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  white-space: normal;
}
.svf-pop__t {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 2px;
}
.svf-pop__k {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-top: 6px;
}
.svf-pop__p {
  font-size: 12.5px;
  line-height: 18px;
  color: var(--sub);
  margin: 0;
}

/* ---- Add another field ----
   Opens under the list it adds to, never as a second layer over the first: the
   point of the control is that you can still see what you have while you choose. */
.svf-pick {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px;
  border-radius: var(--r-md);
  background: var(--glass);
  border: 1px solid var(--line);
}
.svf-pick__hd {
  display: flex;
  align-items: center;
  gap: 8px;
}
.svf-pick__hd .ds-search {
  flex: 1;
  min-width: 0;
  height: 38px;
}
.svf-pick__list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-height: 260px;
  overflow-y: auto;
  margin-inline: -4px;
  padding-inline: 4px;
}
.svf-pick__grp {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.svf-pick__grp .drawer__label {
  margin-bottom: 4px;
}
.svf-pick__row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: start;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: none;
  border: 0;
  cursor: pointer;
  transition: background 0.12s var(--ease);
}
.svf-pick__row:hover {
  background: var(--glass-2);
}
.svf-pick__main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.svf-pick__t {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* the column it actually reads, in the mono face the app reserves for identifiers.
   It is here because "Status" means nothing until you know which table's status. */
.svf-pick__c {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.svf-pick__n {
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
  flex: none;
}
.svf-pick__add {
  display: grid;
  place-items: center;
  color: var(--icon-muted);
  flex: none;
}
.svf-pick__add svg {
  width: 15px;
  height: 15px;
}
.svf-pick__row:hover .svf-pick__add {
  color: var(--ink);
}
.svf-pick__empty {
  font-size: 12.5px;
  color: var(--text-faint);
  padding: 6px 10px;
}

/* step 2's opening line. The same .sem-note callout the Access and Semantic pages
   use for "this is settled", so the one moment in the app that says "that worked"
   says it in the app's existing voice. */
.svf-done {
  margin: 0;
}

/* ============================================================
   THE PAGE RULE (dashboard width)
   ============================================================
   A rule, not a number field, because the question is "how wide, relative to what
   is already on the board", and a rule answers it in place: the lit run IS the
   page, the marks are what the widths mean, and everything past the grip is
   workspace. Ameer's wireframe, 8327:19790.

   GREY, NOT ACCENT (his instruction). The accent in this module means "live" or
   "primary action"; a page edge is neither, it is chrome you set once and stop
   looking at. So the lit run is plain ink and the grip is the app's own glass
   pair, and the only thing that brightens is the part you are touching.

   Positioned in SCREEN pixels, outside the zoom layer, so the labels stay
   readable at 50%. Every offset in here is therefore board-px times the scale.
   ============================================================ */
/* NO margin-inline-start. The rule is IN FLOW, so .canvas-content's own 24px
   padding already insets it; the board beside it is absolutely positioned, whose
   offsets are measured from the padding BOX, so it needs its 24 stated. Giving the
   rule the same 24 again put its zero mark 24px right of the board's left edge,
   which is the one alignment in here that has to be exact. */
/* A SOLID, SUNKEN STRIP WITH A SHARP BOTTOM EDGE, RUNNING EDGE TO EDGE.
   Two corrections, both Ameer, 21 Aug.

   COLOUR. It was --panel plus a step of --glass, which is LIGHTER than the canvas
   behind it, so the rule read as a grey slab laid over the board. It is a gutter the
   page sits in, so it goes the other way: --sunken, one step DOWN from the
   background. Opaque, not an alpha scrim - cards scroll under this strip and have to
   pass BEHIND it, not show through it.

   EXTENT. The rule is in flow inside .canvas-content, whose 24px --cpad is what puts
   the zero mark on the board's own left edge. That inset is correct for the MARKS and
   wrong for the BAND, which was stopping 24px short of the canvas and reading as a
   gap. So the band is a pseudo-element that bleeds --cpad past the rule on both
   sides: empty strip before the zero, but no gap in the strip. It has to be a
   pseudo-element rather than negative margins on .crule, because every mark is
   absolutely positioned against .crule's own padding box and widening .crule would
   drag all of them 24px with it. z-index:-1 keeps it inside .crule's own stacking
   context (sticky + z-index:6), so it paints behind the marks and still over the
   board, the same trick .grip::before uses. */
/* THE VISIBLE STRIP. Positioned against .canvas (position:relative, above), NOT
   inside the scrolling .canvas-content: see the long comment at its call site in
   app.js for why a plain flow child living inside the scroller could not do this
   job. 57px = .canvas-head's 56px plus the 1px .conv-div under it - the rule
   sits directly below that seam at every scroll and every drag position. LIGHT,
   not black - one step of glass over the panel, the same quiet lift .svf-pick
   and the empty-state dashed slot already use for "this is a shelf, not a hole"
   (Ameer, 21 Aug: "it's too black"). */
.crule-bg {
  position: absolute;
  top: 57px;
  inset-inline: 0;
  height: var(--rule-h);
  pointer-events: none;
  z-index: 5;
  background: var(--glass);
}
/* THE MEASURED STRIP. Same box it always was - explicit width (the full
   scrollable rule, usually wider than the viewport), marks positioned against
   it - just with no background of its own any more, so .crule-bg shows through
   underneath it undisturbed. */
/* every mark inside is positioned against THIS box, not the board - so
   centring the board (see --cx on .canvas-zoom) has to shift the ruler by the
   same amount too, or its zero mark stops sitting on the board's actual left
   edge the moment there's room to centre into. */
.crule {
  position: sticky;
  top: 0;
  height: var(--rule-h);
  flex: none;
  z-index: 6;
  user-select: none;
  margin-inline-start: var(--cx, 0px);
}
.crule__line {
  position: absolute;
  inset-inline-start: 0;
  right: 0;
  top: 27px;
  height: 1px;
  background: var(--border);
}
[dir="rtl"] .crule__line {
  right: auto;
  left: 0;
}
/* the lit run: the page. It was --ink-2, near-white, and on a dark strip that
   read as the one thing on the whole canvas demanding attention (Ameer, 21 Aug:
   "it is highlighting like anything, taking the entire attention"). --border-
   strong is the same step up from the hairline that a selected tab or an active
   filter chip uses elsewhere in this app for "this one, quietly" - present, not
   shouting. */
.crule__lit {
  position: absolute;
  inset-inline-start: 0;
  top: 27px;
  height: 1.5px;
  border-radius: 1px;
  background: var(--border-strong);
  transition: width 0.04s linear;
}
/* a mark is its own label: the number sits under the tick it belongs to, so there
   is no legend to read and nothing to align by eye */
.crule__t {
  position: absolute;
  top: 12px;
  font-size: 9.5px;
  line-height: 12px;
  letter-spacing: 0.02em;
  color: var(--text-faint);
  padding-inline-start: 4px;
  white-space: nowrap;
  border-inline-start: 1px solid var(--border);
  height: 12px;
  transition: color 0.12s var(--ease);
}
.crule__t.is-on {
  color: var(--sub);
  border-inline-start-color: var(--border-strong);
}
.crule__ws {
  position: absolute;
  top: 32px;
  font-size: 10.5px;
  line-height: 14px;
  color: var(--text-faint);
  white-space: nowrap;
  pointer-events: none;
}
/* THE GRIP. Sits ON the line, centred on the page's edge, so what you grab and
   what moves are the same 1px. */
/* ABOVE EVERYTHING ELSE ON THE RULE, explicitly: the tick marks sit right beside
   it and, without this, painted over it at the exact positions Ameer pointed out
   (22 Aug) - the one thing on this strip you actually grab cannot be the one
   thing something else can cover. */
.crule__grip {
  position: absolute;
  top: 17px;
  transform: translateX(-50%);
  width: 14px;
  height: 22px;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 5px;
  cursor: col-resize;
  z-index: 3;
  background: var(--glass-2);
  border: 1px solid var(--border-strong);
  color: var(--icon-muted);
  transition:
    background 0.12s var(--ease),
    border-color 0.12s var(--ease),
    color 0.12s var(--ease);
}
[dir="rtl"] .crule__grip {
  transform: translateX(50%);
}
.crule__grip svg {
  width: 12px;
  height: 12px;
}
/* a 14px target is a 14px target; the grab area is bigger than the mark, the same
   trick .splitter uses to be findable without being visible */
.crule__grip::before {
  content: "";
  position: absolute;
  inset: -8px -10px;
}
/* the tip hangs BELOW the grip: the rule is the first thing in the scroller, so an
   upward tooltip would be sliced off by the panel's own top edge. Two lines, so it
   un-nowraps the same way .acc-more's list does. */
.crule__grip[data-tip]::after {
  bottom: auto;
  top: calc(100% + 8px);
  left: 50%;
  white-space: pre;
  text-align: center;
  font-weight: 500;
  line-height: 1.5;
  transform: translate(-50%, 4px);
}
.crule__grip[data-tip]:hover::after {
  transform: translate(-50%, 0);
}
.crule.is-dragging .crule__grip[data-tip]::after {
  opacity: 0;
}
.crule__grip:hover {
  background: var(--pill-hover);
  border-color: var(--ink-2);
  color: var(--ink);
}
.crule__grip:focus-visible {
  outline: 2px solid var(--border-hover);
  outline-offset: 2px;
}
/* the readout is only there while it is changing: a number that never moves is a
   label, and this rule already carries its labels */
.crule__read {
  position: absolute;
  top: 14px;
  margin-inline-start: 12px;
  padding: 2px 7px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 600;
  line-height: 16px;
  color: var(--ink);
  white-space: nowrap;
  background: var(--menu-bg);
  border: 1px solid var(--border-strong);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s var(--ease);
}
.crule.is-dragging .crule__read {
  opacity: 1;
}
.crule.is-dragging .crule__grip {
  background: var(--pill-hover);
  border-color: var(--ink);
  color: var(--ink);
}
.crule.is-dragging .crule__ws {
  opacity: 0;
}
/* THE EDGE, drawn down the whole board while you are moving it, so the width is
   set against the cards rather than against a number in a pill. */
.canvas-content::after {
  content: "";
  position: absolute;
  top: var(--rule-h);
  bottom: 0;
  width: 1px;
  inset-inline-start: calc(
    var(--cpad) + var(--cx, 0px) + var(--dboard, 0px) * var(--dz)
  );
  background: var(--border-strong);
  opacity: 0;
  transition: opacity 0.12s var(--ease);
  pointer-events: none;
}
body.col-resizing .canvas-content:has(.crule.is-dragging)::after {
  opacity: 1;
}
