/* ════════════════════════════════════════════════════════════════════════ */
/* SAVED LIBRARY (lb-) — the sidebar's three type groups and their rows.     */
/* Sits where the static placeholder session list used to, and reuses the    */
/* sidebar's own row metrics so a saved chat looks like it always belonged.  */
/* ════════════════════════════════════════════════════════════════════════ */

.lb-groups {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  /* No inset of its own. .sidebar-section already applies --sb-inset, the same
     one the nav uses, so a saved row's hover box lands on exactly the same left
     and right edges as the nav items above it. The 4px that used to be here made
     every row 8px narrower than the menu and looked like a mistake, which it was. */
  padding: 0;
  /* .sidebar-section already gives this element a definite height
     (flex: 1, min-height: 0, overflow: hidden), but that constraint only
     reaches a child that agrees to shrink into it. flex: 1 1 auto lets this
     list take the section's leftover space instead of sizing to its own
     content, min-height: 0 is the part a flex child needs to opt into
     shrinking below that content height (without it, a flex item's default
     min-height is auto, i.e. its content size, which is exactly what was
     clipping rows here), and overflow-y: auto turns the now-real overflow
     into a scrollbar instead of silently hidden content. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* The scroll container reaches the PANEL's inner edge, not the section's
     6px inset, so the scrollbar rides the edge instead of floating 7px short
     of it. The inset is handed back as padding, so the rows themselves do not
     move: only the track does. */
  margin-right: calc(-1 * var(--sb-inset));
  padding-right: var(--sb-inset);
}

/* The caret follows the label rather than leading it: a leading caret AND a
   leading icon would push the name in twice, and the caret is the one that can
   move without losing anything. The folder icon stays on the left, where it acts
   as the group's left edge. */
.lb-group-head {
  display: flex;
  align-items: center;
  /* 8px between icon and name, the same as .nav-item. The caret pulls itself
     back to 4px below, so it still sits against the name. */
  gap: var(--spacing-8);
  width: 100%;
  /* 12px left, the same as .nav-item, so the group icon starts on the nav icons'
     left edge and on the "Saved" label's. 6px right, matching the rows below, so
     the count lands on the same edge as their menu dots. */
  padding: var(--spacing-6) var(--spacing-6) var(--spacing-6) var(--spacing-12);
  background: none;
  border: none;
  border-radius: var(--spacing-8);
  font: inherit;
  /* Same type as .nav-item above: the agents are navigation, not metadata. */
  font-size: var(--text-base);
  line-height: 20px;
  color: var(--text-tertiary);
  cursor: pointer;
  text-align: left;
}

/* The row, not just the fold button: hovering the + (its sibling) keeps the
   row lit. */
.lb-group-headrow:hover .lb-group-head { background: var(--bg-secondary); }

/* One icon per type (chat / forecast / presentation), not a generic folder, so a
   collapsed list still says what is in each group. A fixed box, not a shrink-wrap:
   it keeps the three labels starting at the same x even though the glyphs are
   different widths.
   Colour carries the whole "lighter" job here, because stroke weight cannot:
   only the SOLID (900) Font Awesome file is complete, and the Regular 400 file
   this project ships is a ~25KB subset with none of these three names in it, so
   asking for the lighter face silently renders the literal string
   "presentation-screen". Tertiary matches the label beside it, and is the same
   value in both themes (--fg-tertiary and --text-tertiary are one colour). */
.lb-group-icon {
  flex: none;
  /* Same box and size as .nav-icon, and the same duotone family. */
  width: 17px;
  height: 17px;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Secondary, with the label: the three group headers are the list's
     structure, and at tertiary they sank into the rows they head. Secondary
     rather than primary so they still read quieter than a row's own title. */
  color: var(--text-secondary);
}

.lb-group.has-active > .lb-group-headrow .lb-group-label {
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
}

/* Mono Sena mark for the Main Agent group: the neutral lockup used as a mask
   so it takes the icon colour like the Font Awesome glyphs next to it. */
.lb-group-mark {
  background-color: currentColor;
  -webkit-mask: url("logo/logo-neutral.svg") no-repeat center / contain;
          mask: url("logo/logo-neutral.svg") no-repeat center / contain;
}

.lb-group-caret {
  margin-left: calc(var(--spacing-4) - var(--spacing-8));
  font-size: 8px;
  /* Fades up only when the group is worth pointing at, so the header reads as a
     label at rest rather than a control. */
  opacity: 0.55;
  transition: transform var(--duration-fast) ease, opacity var(--duration-fast) ease;
}

.lb-group-headrow:hover .lb-group-caret { opacity: 1; }

.lb-group.is-open > .lb-group-headrow .lb-group-caret { transform: rotate(90deg); }
/* The folder holding the open conversation cannot fold (LibraryList
   toggleGroup), so it offers no arrow and no pointer: nothing to click. */
.lb-group.has-active > .lb-group-headrow .lb-group-caret { display: none; }
.lb-group.has-active > .lb-group-headrow .lb-group-head { cursor: default; }

/* Not flex:1 any more: the label hugs its text so the caret sits against it,
   and the count is pushed to the far edge on its own. */
.lb-group-label {
  flex: 0 1 auto;
  min-width: 0;
  /* Regular like a nav item; bold only while a conversation inside the group
     is open (.lb-group.has-active), the way the active nav item goes bold. */
  font-weight: var(--fw-regular);
  /* Secondary rather than the head's inherited tertiary: weight alone was not
     enough separation from the rows below at this size. */
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--duration-fast) var(--ease-out);
}
/* The name wears its agent's colour under the same rule as its disc
   (DES-170): while the group holds the open conversation or is hovered or
   focused; at rest it stays the secondary text colour. The same 80% mix as
   the agent's name under a chat answer, so the pale hues still read. */
.lb-group:is(.has-active, :hover, :focus-within) > .lb-group-headrow .lb-group-label {
  color: color-mix(in srgb, var(--agent) 80%, var(--text-primary));
}
@media (prefers-reduced-motion: reduce) {
  .lb-group-label { transition: none; }
}

.lb-group-count {
  margin-left: auto;
  padding-left: var(--spacing-4);
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
  /* 10px is off the type scale on purpose, and the scale's own floor
     (--text-xs, 11px) is the group label's size. A count has to sit UNDER its
     label to read as an annotation of it, and there is nothing below xs to reach
     for. Legible because it is only ever one to three tabular digits, never
     copy. */
  font-size: calc(10px + var(--text-bump));
}

/* ── The group's + (DES-19) ─────────────────────────────────────────────
   Starts a new conversation with that agent. It sits over the count, on the
   same right edge, and the two swap on hover or keyboard focus, so the row
   never grows a column. On touch screens (no hover) the + simply shows. */
.lb-group-headrow {
  position: relative;
}
.lb-group-new {
  position: absolute;
  top: 50%;
  right: var(--spacing-4);
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  border-radius: var(--spacing-6);
  background: none;
  color: var(--text-tertiary);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.lb-group-new .fa-solid { font-size: 10px; letter-spacing: normal; text-transform: none; }
.lb-group-count { transition: opacity var(--duration-fast) var(--ease-out); }
.lb-group-headrow:hover .lb-group-new,
.lb-group-headrow:focus-within .lb-group-new { opacity: 1; pointer-events: auto; }
.lb-group-headrow:hover .lb-group-count,
.lb-group-headrow:focus-within .lb-group-count { opacity: 0; }
.lb-group-new:hover { background: var(--bg-primary); color: var(--text-primary); }
.lb-group-new:focus-visible { outline: 1px solid var(--fg-accent); }
@media (hover: none) {
  .lb-group-new { opacity: 1; pointer-events: auto; }
  .lb-group-count { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .lb-group-new, .lb-group-count { transition: none; }
}

.lb-items {
  display: flex;
  flex-direction: column;
  /* No indent HERE any more: the indent moved into .lb-row's own padding, so a
     row's hover box runs the full width like a nav item and only its text is
     inset. Indenting the container instead made the box narrower than the menu,
     which is the thing that looked broken. */
  padding-left: 0;
}

/* ── Opening and closing a folder (DES-170) ──────────────────────────────
   A grid row that transitions between 0fr and 1fr, so the rows' height
   animates without measuring anything, and the rows fade with it: the
   folder's one fast state change, --duration-base on --ease-out, both
   ways, and a transition, so a quick reopen reverses from where it is.
   Height is the one layout property worth animating here (an accordion has
   no transform that grows a container). The rows clip inside the fold;
   their first row's line reaches 4px up toward the disc, so the clip box is
   lifted by 4px of padding cancelled by a -4px margin. */
.lb-fold {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition:
    grid-template-rows var(--duration-base) var(--ease-out),
    opacity var(--duration-base) var(--ease-out);
}
.lb-group.is-open > .lb-fold {
  grid-template-rows: 1fr;
  opacity: 1;
}
.lb-fold > .lb-items {
  min-height: 0;
  overflow: hidden;
  margin-top: calc(-1 * var(--spacing-4));
  padding-top: var(--spacing-4);
}
/* Reduced motion keeps the fade and drops the collapse. */
@media (prefers-reduced-motion: reduce) {
  .lb-fold { transition: opacity var(--duration-base) var(--ease-out); }
}

/* The group's geometry, named once: the disc starts 12px in (.lb-group-head's
   padding) and is 20px wide (AgentIcon size={20} in LibraryList), so its
   centre is 22px in, and a row's text starts after the disc and the head's
   8px gap. --agent is stamped on each .lb-group (LibraryList). */
.lb-group {
  /* The agent's colour (DES-170) only where the eye should go: the group
     holding the open conversation, and the group under the pointer or the
     keyboard. At rest every agent is the same neutral, so eleven hues do not
     turn the list into a Christmas tree. --agent-own is stamped per group
     (LibraryList); the disc, the dots and the line all read --agent. */
  --agent: var(--agent-own);
  --lb-disc-left: var(--spacing-12);
  --lb-disc: 20px;
  --lb-rail-x: calc(var(--lb-disc-left) + var(--lb-disc) / 2);
  --lb-row-indent: calc(var(--lb-disc-left) + var(--lb-disc) + var(--spacing-8));
}

.lb-group:not(.has-active):not(:hover):not(:focus-within) {
  --agent: var(--fg-tertiary);
}
/* The group's disc takes the group's colour (AgentIcon inheritColor): the
   global .agent-disc (screen.css) sets its own --agent default otherwise. */
.lb-group .agent-disc { --agent: inherit; }
/* The colour fades in and out rather than snapping as the pointer crosses
   the list. */
.lb-group .agent-disc,
.lb-items > .lb-row::before {
  transition:
    background-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .lb-group .agent-disc,
  .lb-items > .lb-row::before { transition: none; }
}

/* The agent whose conversation is open reads as one block: the whole group,
   header and rows, on the primary surface (the one .nav-item.active uses),
   with the surface corner. Its own padding stays vertical only, so the disc,
   the rail and the row text keep their x. Inside it the open row needs no
   background of its own (it would vanish on the same surface): the bold
   label and the ringed dot mark it. */
.lb-group.has-active {
  padding: var(--spacing-4) 0;
  background: var(--bg-primary);
  border-radius: var(--spacing-16);
}
/* DES-175: inside the block, the header's and the rows' hover boxes are inset
   4px from its sides (the padding above gives the same 4px at the top and the
   bottom), with 12px corners, the block's 16px less the inset, so the two
   curves run parallel instead of the hover poking out of the block's corner.
   The disc starts 4px further left inside the inset box, so it, the rail and
   the row text keep their x. */
.lb-group.has-active {
  --lb-disc-left: calc(var(--spacing-12) - var(--spacing-4));
}
.lb-group.has-active > .lb-group-headrow,
.lb-group.has-active .lb-items > .lb-row {
  margin: 0 var(--spacing-4);
}
.lb-group.has-active .lb-group-head,
.lb-group.has-active .lb-row {
  border-radius: var(--spacing-12);
}
.lb-group.has-active .lb-group-head {
  padding-left: var(--lb-disc-left);
}
.lb-group.has-active .lb-row.is-active { background: none; }
/* The "New chat" placeholder (DES-19) is not a title yet: regular weight
   and tertiary, until the saved conversation's real row replaces it. */
/* Outranks .lb-row.is-active .lb-row-link below, which would make it bold. */
.lb-row.is-active.is-draft .lb-row-link {
  color: var(--text-tertiary);
  font-weight: var(--fw-regular);
  letter-spacing: normal;
}

/* ── The rail (DES-170) ───────────────────────────────────────────────────
   An open folder hangs from its agent: one dot per row in the agent's colour,
   centred under the disc, and a thin line from just under the disc to the
   last dot. Both are pseudo-elements of the row, so they follow it (rename,
   show all) with no extra markup: each row draws its own stretch of line,
   the first reaches up to the disc, the last stops at its dot. Rows are the
   only divs in .lb-items ("Show all" is a button), so :last-of-type is the
   last row. The dot's colour is pulled a little toward the text so the pale
   agents (Pricing's yellow) still read on the panel.
   The line is 1px and stops 2px short of each dot on both sides, so it reads
   as segments between the dots, not one continuous stroke: each row's
   stretch is a gradient with a transparent break around its own dot.
   --lb-dot-at is where the row's centre (the dot) sits inside that stretch,
   which moves when the stretch is lengthened or cut: 50% for a plain row,
   50% + 2px for the first (reaching 4px up to the disc), 100% for the last
   (ending at its dot). --lb-dot-gap is the dot's radius plus the 2px. */
.lb-items > .lb-row::before,
.lb-items > .lb-row::after {
  content: '';
  position: absolute;
  pointer-events: none;
}
.lb-items > .lb-row {
  --lb-dot-at: 50%;
  --lb-dot-gap: calc(3px + var(--spacing-2));
}
.lb-items > .lb-row::after {
  --lb-line: color-mix(in srgb, var(--agent) 35%, transparent);
  left: calc(var(--lb-rail-x) - 0.5px);
  top: 0;
  bottom: 0;
  width: 1px;
  background: linear-gradient(
    to bottom,
    var(--lb-line) calc(var(--lb-dot-at) - var(--lb-dot-gap)),
    transparent calc(var(--lb-dot-at) - var(--lb-dot-gap)),
    transparent calc(var(--lb-dot-at) + var(--lb-dot-gap)),
    var(--lb-line) calc(var(--lb-dot-at) + var(--lb-dot-gap))
  );
}
.lb-items > .lb-row:first-child::after { top: calc(-1 * var(--spacing-4)); }
.lb-items > .lb-row:first-child { --lb-dot-at: calc(50% + var(--spacing-4) / 2); }
.lb-items > .lb-row:last-of-type::after { bottom: 50%; }
.lb-items > .lb-row:last-of-type { --lb-dot-at: 100%; }
.lb-items > .lb-row::before {
  z-index: 1; /* local: the dot, over its rail line */
  left: calc(var(--lb-rail-x) - 3px);
  top: calc(50% - 3px);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--agent) 85%, var(--text-primary));
}
/* The open chat: a slightly larger dot with a soft ring of the same colour.
   The line's break clears the ring too: 4px radius + 3px ring + the 2px. */
.lb-items > .lb-row.is-active { --lb-dot-gap: calc(4px + 3px + var(--spacing-2)); }
.lb-items > .lb-row.is-active::before {
  left: calc(var(--lb-rail-x) - 4px);
  top: calc(50% - 4px);
  width: 8px;
  height: 8px;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--agent) 30%, transparent);
}

/* The row is a container, NOT the link: a <button> inside an <a> is invalid
   HTML (the anchor content model excludes interactive content) and browsers
   disagree about which one a click belongs to. Link and menu button are
   siblings instead. */
.lb-row {
  /* Positioning context for the overlaid menu button below. */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  /* Indented under the group label: 12px head padding + the 20px agent disc
     + the 8px gap, so a row's text starts exactly where the agent's name
     starts and reads as a child of it (DES-170: this said 17px, the old
     icon, and every title sat 3px short of the name). The hover box still
     runs the full width. */
  /* 5px + 18px + 5px = 28px per row: between the original 31px and the
     25px that read cramped. */
  padding: 5px var(--spacing-6) 5px var(--lb-row-indent);
  border-radius: var(--spacing-8);
  font-size: var(--text-sm);
  line-height: 18px;
}

.lb-row:hover { background: var(--bg-secondary); }
/* Same treatment as .nav-item.active: primary surface, bold, tightened. */
.lb-row.is-active { background: var(--bg-primary); }
.lb-row.is-active .lb-row-link {
  color: var(--text-secondary);
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
}

.lb-row-link {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  color: var(--text-secondary);
  text-decoration: none;
}

.lb-row-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lb-row-glyph {
  flex: none;
  font-size: 11px;
  color: var(--text-tertiary);
}

/* The CPG demo record's tag: quiet accent pill after the title. */
.lb-demo-pill {
  flex: none;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--bg-accent-light);
  color: var(--text-accent);
  font-size: calc(9.5px + var(--text-bump));
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* The menu button is OVERLAID on the row's tail (below), so on hover it lands
   exactly where the trailing tag/glyphs sit. Slide them left past the button's
   footprint (4px inset + 18px + 4px air) so both stay readable. Transform
   only; focus-within gets the shifted state too, since that is when the
   button is also visible. */
.lb-row-glyph,
.lb-demo-pill {
  transition: transform var(--duration-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .lb-row:hover .lb-row-glyph,
  .lb-row:hover .lb-demo-pill {
    transform: translateX(-20px);
  }
}
.lb-row:focus-within .lb-row-glyph,
.lb-row:focus-within .lb-demo-pill {
  transform: translateX(-20px);
}
/* A long title runs under the shifted icon (and, on rows without one, under
   the dots themselves), so while the tail is occupied the LABEL fades out
   across the whole zone the icon travels: fully transparent 10px before the
   label's end, ramping from 40px out. */
@media (hover: hover) and (pointer: fine) {
  .lb-row:hover .lb-row-label {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 48px), transparent calc(100% - 12px));
    mask-image: linear-gradient(to right, #000 calc(100% - 48px), transparent calc(100% - 12px));
  }
}
.lb-row:focus-within .lb-row-label {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 48px), transparent calc(100% - 12px));
  mask-image: linear-gradient(to right, #000 calc(100% - 48px), transparent calc(100% - 12px));
}
@media (prefers-reduced-motion: reduce) {
  .lb-row-glyph,
  .lb-demo-pill {
    /* The shift is functional (it unhides the tag), so it stays; only the
       movement between the two states goes. */
    transition: none;
  }
}

/* A charge is a record and a menu is an action: the menu button stays hidden
   until the row is hovered or something inside it takes focus, so a list of
   twenty rows is not twenty dots. Focus-within keeps it reachable by keyboard. */
/* OVERLAID on the row's tail, not in the flex flow: an in-flow button reserved
   14px + gap on EVERY row for a control that shows on one at a time, and on a
   sidebar of long titles that reservation was pure ellipsis. The title runs the
   full row now; on hover the dots sit on the hover-colored background, so they
   read as a control landing on the row rather than text colliding. */
.lb-row-menu-btn {
  position: absolute;
  right: var(--spacing-4);
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  display: grid;
  place-items: center;
  background: var(--bg-secondary);
  border: none;
  border-radius: var(--spacing-4);
  color: var(--text-tertiary);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  font-size: 11px;
}

.lb-row:hover .lb-row-menu-btn,
.lb-row:focus-within .lb-row-menu-btn { opacity: 1; pointer-events: auto; }
/* --fg-accent, not --stroke-accent: the latter is a translucent white in dark
   mode, so a focus ring drawn with it vanishes on a dark surface. */
.lb-row-menu-btn:focus-visible { opacity: 1; outline: 1px solid var(--fg-accent); }
/* Colour, not background: the row is already on --bg-secondary when this is
   visible, so a background change here would be invisible. */
.lb-row-menu-btn:hover { color: var(--text-primary); }

.lb-rename-input {
  flex: 1;
  min-width: 0;
  padding: var(--spacing-2) var(--spacing-4);
  background: var(--bg-card);
  border: 1px solid var(--fg-accent);
  border-radius: var(--spacing-4);
  font: inherit;
  font-size: var(--text-base);
  color: var(--text-primary);
}

.lb-show-all {
  align-self: flex-start;
  margin: var(--spacing-2) 0 0 0;
  /* Left padding matches .lb-row's so the "Show all" text lines up with the row
     labels above it rather than sitting short of them. */
  padding: var(--spacing-4) var(--spacing-6) var(--spacing-4) var(--lb-row-indent);
  background: none;
  border: none;
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  cursor: pointer;
  text-decoration: underline;
}

/* Spec's Recent (DES-205): the chats sit straight under the heading, with
   no folder, so no disc and no rail; a row's text starts where the heading's
   does (its 12px), and its hover box still runs the full width. */
.lb-flat {
  display: flex;
  flex-direction: column;
  --lb-row-indent: var(--spacing-12);
}

/* ── Chats (Lite and Design) ──────────────────────────────────────────────
   Every conversation in one list, newest first, no folders. A row's
   bullet says whose conversation it is: a 6px dot in its agent's colour
   (--agent-<key>, stamped per dot by LibraryList), or one dot per agent,
   in the order they joined, for a chat with guests (at most five). The
   bullet's box is the old folder's 20px disc column, 12px in, so a title
   still starts 40px in.
   The open single-agent chat's dot GROWS into its agent's disc: the dot
   scales up and fades as the disc scales up from the dot's size and fades
   in, both on transform and opacity, --duration-base on --ease-out, and a
   transition, so a quick switch reverses from where it is. Several dots
   stay dots when their chat is open (no disc is rendered for them). */
.lb-chats {
  --lb-bullet: 20px;
}
/* "Show all" and an empty line start where the titles do. */
.lb-chats .lb-show-all {
  padding-left: calc(var(--spacing-12) + var(--lb-bullet) + var(--spacing-8));
}

.lb-bullet {
  position: relative;
  flex: none;
  width: var(--lb-bullet);
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The link's own 4px gap plus 4px: 8px to the title, as under a disc. */
  margin-right: var(--spacing-4);
}
.lb-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  /* Pulled a little toward the text, so the pale agents (Pricing's
     yellow) still read on the panel: the same mix the folder dots used. */
  background: color-mix(in srgb, var(--agent) 85%, var(--text-primary));
  transition:
    transform var(--duration-base) var(--ease-out),
    opacity var(--duration-base) var(--ease-out);
}
/* Several agents: the dots overlap by 2.5px, five filling the 20px box.
   Each dot after the first is cut where the one before it sits (a circle
   of that dot's 3px radius plus a 1.5px gap, centred 3.5px to the left),
   so the owner reads on top and every dot stays separate. A mask, not a
   ring in the row's colour: the panel is translucent in dark mode, and a
   ring painted with it showed as a halo. */
.lb-bullet.is-multi .lb-dot + .lb-dot {
  margin-left: -2.5px;
  -webkit-mask-image: radial-gradient(circle at -0.5px 50%, transparent 4.25px, #000 4.75px);
  mask-image: radial-gradient(circle at -0.5px 50%, transparent 4.25px, #000 4.75px);
}

/* The disc waits at the dot's size (6 / 20 = 0.3), invisible, centred on it. */
.lb-bullet .agent-disc {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: calc(var(--lb-bullet) / -2) 0 0 calc(var(--lb-bullet) / -2);
  opacity: 0;
  transform: scale(0.3);
  transition:
    transform var(--duration-base) var(--ease-out),
    opacity var(--duration-base) var(--ease-out);
}
.lb-row.is-active .lb-bullet:not(.is-multi) .lb-dot {
  opacity: 0;
  transform: scale(3.33);
}
.lb-row.is-active .lb-bullet .agent-disc {
  opacity: 1;
  transform: scale(1);
}
/* Reduced motion: the dot and the disc cross-fade in place, no growth. */
@media (prefers-reduced-motion: reduce) {
  .lb-dot,
  .lb-bullet .agent-disc {
    transition: opacity var(--duration-base) var(--ease-out);
  }
  .lb-bullet .agent-disc,
  .lb-row.is-active .lb-bullet:not(.is-multi) .lb-dot { transform: none; }
}

/* The Artifacts section (DES-205): its own rule and heading inside the
   scrolling list, so Recent and Artifacts scroll as one. The same rhythm as
   the section above: 12px under the rule, then the heading, then the
   folders. */
.lb-artifacts {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  margin-top: var(--spacing-12);
}

/* An empty folder's one line (DES-203, Spec mode's always-shown folders):
   a row's height and indent, so it sits where the first row would, but not
   a row, so the rail draws no dot for it. */
.lb-row-empty {
  padding: 5px var(--spacing-6) 5px var(--lb-row-indent);
  font-size: var(--text-sm);
  line-height: 18px;
  color: var(--text-tertiary);
}

.lb-empty {
  padding: var(--spacing-12) var(--spacing-16);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  line-height: 1.5;
}

/* Menu items reuse the sidebar account menu's chrome (st-menu). Only the
   disabled Share row and its pill are new. */
.lb-menu-item[disabled] {
  color: var(--text-tertiary);
  cursor: default;
}

.lb-menu-item[disabled]:hover { background: none; }

.lb-soon {
  margin-left: auto;
  padding: 1px var(--spacing-4);
  border: 1px solid var(--stroke-secondary);
  border-radius: var(--spacing-4);
  /* An uppercase letter-spaced pill is the one place --text-xs belongs. */
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* The delete confirm reuses the gate modal's chrome wholesale (.su-overlay
   .st-gate.open > .su-panel > .st-gate-body), so it needs no panel CSS of its
   own. Verified against GateModal.svelte and sidebar.css:632-893. */

/* ── Scrollbar ────────────────────────────────────────────────────────────── */
/* Almost invisible: a thin track with no chrome, and a thumb that only fades in
   once the pointer is over the list. Deliberately not `scrollbar-width: none`
   like the account modal's rail, because this list genuinely overflows and the
   thumb is the only cue that there is more below. Firefox gets `thin` and a
   colour pair; WebKit gets the real treatment. */
.lb-groups {
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
  transition: scrollbar-color var(--duration-base) ease;
}

.lb-groups:hover {
  scrollbar-color: color-mix(in srgb, var(--fg-primary) 22%, transparent) transparent;
}

.lb-groups::-webkit-scrollbar {
  width: 6px;
}

/* No track, no buttons, no corner: the panel's own surface shows through. */
.lb-groups::-webkit-scrollbar-track,
.lb-groups::-webkit-scrollbar-corner {
  background: transparent;
}

.lb-groups::-webkit-scrollbar-thumb {
  background: transparent;
  border-radius: 999px;
  /* Inset via a transparent border so the 6px track reads as a 4px thumb. */
  border: 1px solid transparent;
  background-clip: content-box;
  transition: background var(--duration-base) ease;
}

/* color-mix from the FOREGROUND, not a --stroke-* token: --stroke-secondary
   equals --bg-secondary in dark mode, so a thumb drawn with it would vanish on
   exactly the surface it sits on. */
.lb-groups:hover::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--fg-primary) 20%, transparent);
  background-clip: content-box;
}

.lb-groups::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--fg-primary) 38%, transparent);
  background-clip: content-box;
}

/* Collapsed sidebar: the groups give way to the existing history icon.
   `:not(.hover-expanded)` is not optional. .hover-expanded is how JS holds the
   overlay open (on load, or while a menu is up), and every other rule in
   sidebar.css pairs it with :hover; without it here the panel opened with an
   empty library, because display:none survived the reveal. */
.sidebar-wrap.collapsed:not(:hover):not(.hover-expanded) .lb-groups,
.sidebar-wrap.collapsed.no-hover-expand .lb-groups { display: none; }
