/* Chat app, centred overlays: search, settings modal, library grid. */
/* Search, centered overlay (ChatGPT Ctrl+K style): sits near the top of the
   screen, not perfectly vertically centered like Settings, since it needs
   room for a results list to grow underneath the input. */
/* Centered over the CHAT area, not the full viewport, inset:0 centers
   relative to the whole window, which visually reads as shifted toward the
   sidebar since the sidebar eats space on the left the eye doesn't count. */
.search-modal { position: fixed; top: 0; bottom: 0; left: calc(var(--shell-pad) + var(--sidebar-w) + var(--shell-gap)); right: 0; z-index: 141; display: flex; justify-content: center; align-items: flex-start; padding-top: 14vh; opacity: 0; pointer-events: none; transition: opacity 0.16s, left 0.24s var(--ease); }
body.sidebar-collapsed .search-modal { left: calc(var(--shell-pad) + var(--rail-w) + var(--shell-gap)); }
/* Stay pointer-events:none even while open, this element's flex box spans
   the WHOLE content area (not just the visible search box), and it sits
   above the backdrop (z-index 141 vs 40). Without this, clicking the empty
   space around the search box hit dead space with no close handler instead
   of passing through to the backdrop underneath. Only .search-box itself
   (below) re-enables pointer-events. */
.search-modal.open { opacity: 1; }
/* `visibility: hidden` while closed is load-bearing, not tidiness.
   .search-box sets pointer-events: auto (it has to, its wrapper is
   pointer-events: none so clicks around it fall through to the backdrop). But
   opacity: 0 does NOT remove an element from hit-testing, so a CLOSED, invisible
   search box still swallowed taps landing on it. On desktop that went unnoticed
   because the overlay starts to the right of the sidebar; once the mobile band moved
   it to left: 0 it sat directly on top of the sidebar and ate every tap aimed at
   Library and New chat. visibility also takes it out of the accessibility tree, so a
   screen reader no longer walks a hidden search field.
   The 0.16s delay matches the opacity fade, so it disappears only once faded out. */
.search-modal { visibility: hidden; transition: opacity 0.16s, left 0.24s var(--ease), visibility 0s 0.16s; }
.search-modal.open { visibility: visible; transition: opacity 0.16s, left 0.24s var(--ease), visibility 0s; }
.search-box { pointer-events: auto; width: min(600px, 92vw); max-height: 60dvh; background: var(--panel); border-radius: var(--r-xl); box-shadow: var(--elev-2); display: flex; flex-direction: column; overflow: hidden; transform: translateY(10px) scale(0.98); transition: transform 0.16s var(--ease); }
.search-modal.open .search-box { transform: translateY(0) scale(1); }
.search-input-row { display: flex; align-items: center; gap: 10px; padding: 16px 18px; flex: none; }
/* The row now holds a .search-field (see index.html) rather than a bare icon + input, so
   its old rules are gone: the icon is positioned INSIDE the field by the component, and
   a `background: transparent` input would have punched a hole in the field's fill.
   Only the layout of the field within the row belongs here. */
.search-input-row .search-field { flex: 1; margin: 0; }
.search-results { overflow-y: auto; padding: 8px; }
.search-results:empty::after { content: "Type to search your conversations."; display: block; padding: 24px 12px; text-align: center; color: var(--muted); font-size: 13px; }


/* ---- .search-field: ONE search bar component, used twice ---------------------
   Consumers: the Library toolbar (built in js/chat/library.js) and the Ctrl+K search
   overlay (index.html). It used to be called `.sidebar-search` and lived in
   chat-sidebar.css, which was doubly misleading -- there is no search bar in the
   sidebar, and the overlay had its own separate lookalike rules that could drift from
   the Library's. One component here, in the file that owns both consumers; each consumer
   only says how it sits in ITS layout (flex/basis), never how it looks.
   ------------------------------------------------------------------------- */
/* Geometry copied from the companies-list search bar in the yc_tool_frontend reference
   (assets/css/landing.css `.cp-search`, Figma 574:7119) so the two products' search bars
   are the same object: white pill, 1px hairline, 100px radius, 32px left inset, and a
   52px black circular button on the right whose hover is brand orange.

   Two deliberate overrides of rules set earlier in this project, both chosen explicitly:
     - HEIGHT is 72px (10px pad + 52px button + 10px pad), not the composer's 52px. The
       button is what sets it, exactly as in the reference.
     - It carries a 1px BORDER, making it the third stroke in the chatbot alongside
       .settings-instructions and .about-link. Without the border the reference's white
       pill has no edge against a white panel (the Ctrl+K overlay is white).

   The container holds the fill and the border; the input inside is transparent and
   unstyled, so the pill is one surface rather than a box inside a box. */
.search-field {
  margin: 0; display: flex; align-items: center; gap: 12px;
  background: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 6px 6px 6px 32px;
  box-sizing: border-box;
  min-height: var(--field-h);
}
.search-field[hidden] { display: none; }
.search-field input {
  flex: 1; min-width: 0; border: none; background: transparent; outline: none;
  font-family: inherit; font-size: clamp(15px, 1.1vw, 17px); color: var(--text);
}
.search-field input::placeholder { color: var(--placeholder); }
/* No clear button. `type="search"` gives the browser one for free, and the reference
   product styles it as a grey X -- removed here because backspace already does the job
   and the X sits in the same corner as the circular search button, so the two competed
   for the same spot. `-webkit-appearance: none` is what actually suppresses it; `display:
   none` alone is ignored by some builds.
   This is scoped to .search-field, so the composer is untouched -- it is a <textarea> and
   never had a clear control to begin with. */
.search-field input::-webkit-search-cancel-button,
.search-field input::-webkit-search-decoration {
  -webkit-appearance: none; appearance: none; display: none;
}
/* The circular button. A real <button> rather than a decorative span: it focuses the
   field, so it is a genuine (if small) affordance and a keyboard user gets a tab stop
   that does something, instead of a control-shaped element that ignores clicks.
   These search bars filter as you type, so there is nothing to submit -- which is why it
   focuses rather than pretending to run a query. */
.search-field-btn {
  /* Size DERIVED from the field's height rather than hardcoded, so it re-derives if
     --field-h moves (the phone band retunes --pad-y; Settings changes the font size).
     The subtraction is the field's own box: 6px padding top and bottom + 1px border top
     and bottom = 14px.

     Explicit width AND height, not `align-self: stretch` + `aspect-ratio: 1`. That was
     tried and produced a 34x38 oval: as a flex item the button's WIDTH still came from its
     content (the 22px icon), and the stretched height was not a definite enough basis for
     aspect-ratio to square it against. Two equal lengths cannot be ambiguous. */
  --btn-size: calc(var(--field-h) - 14px);
  flex: none; width: var(--btn-size); height: var(--btn-size); border: none;
  border-radius: var(--r-pill);
  background: var(--btn-solid-bg); color: var(--btn-solid-fg);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
}
/* NO hover state, deliberately -- and no `transition` either, since there is nothing
   left to ease between. This button does not run a query (both search bars filter as
   you type); all it does is focus the field beside it. It used to go brand orange on
   hover, which made the most eye-catching colour in the app sit on the one control that
   changes nothing -- and it read as "click here to search" on a bar that had already
   searched. The magnifier is now a static black disc in every state.
   One rule, so BOTH consumers -- the Ctrl+K chat search and the Library toolbar -- stay
   identical; there is nowhere for them to drift apart. */
/* Icon capped so it can never widen the button past --btn-size. */
.search-field-btn svg { width: 18px; height: 18px; flex: none; }

/* Settings, centered modal.

   Background alternation for this panel. The whole stack was INVERTED: the panel used
   to be F2F2F2 with white things on it, which made it the same grey as the chatbot
   behind it, so its edge was invisible. The panel is white now and the alternation
   continues from there:
     .settings-box (the panel itself)         FFFFFF  <- white, so it reads as a
                                                         distinct surface against the
                                                         #F2F2F2 chatbot behind it
     a control sitting on it                   F2F2F2  (.segmented track always;
                                                          .settings-btn only on
                                                          hover/press -- it is
                                                          transparent at rest)
     something sitting on ONE of those         FFFFFF  (.segmented button.active)

   Two elements deliberately break the alternation and stay WHITE on the white panel:
   .settings-instructions and .about-link. Having no fill contrast to rely on, they are
   the only two elements in the entire chatbot permitted a 1px border -- see the
   "STROKE EXCEPTION" notes on each. About itself is not a card at all any more. */
/* The padding is what stops the card overlapping the composer. The modal is a
   full-viewport grid with the card centred in it, so at its old 84dvh height the card's
   bottom edge landed ~30px INSIDE the composer pill -- the two were simply drawn on top
   of each other. Reserving --composer-zone as bottom padding shrinks the grid area, and
   because the card is centred in that area it also moves up by half the reservation, so
   it clears the input instead of merely ending above it.
   Padding rather than a smaller max-height with a magic number: the card then adapts on
   its own to any viewport and to --shell-pad changing per breakpoint, and there is only
   one place that knows how tall the composer is. box-sizing: border-box so `inset: 0`
   plus padding still resolves to the viewport. */
.settings-modal {
  position: fixed; inset: 0; z-index: 141; display: grid; place-items: center;
  padding: var(--sp-4) var(--sp-4) calc(var(--composer-zone) + var(--sp-4));
  box-sizing: border-box;
  opacity: 0; pointer-events: none; transition: opacity 0.16s;
}
.settings-modal.open { opacity: 1; pointer-events: auto; }
/* border-radius: --r-sm (16px), NOT the --r-xl (40px) every other large surface uses.
   This panel is the one whose BODY SCROLLS (.settings-box .drawer-body below), and the box
   clips it (`overflow: hidden`). At a 40px radius the corner arc cut straight across the
   bottom of the scrollbar track, so the scrollbar appeared to break off before reaching the
   panel edge. 16px keeps the arc clear of the track. The alternative -- dropping the clip --
   would let the body's square corners poke out past the rounded panel, which is worse.
   See --r-sm in chat-base.css for why the radius scale grew a fourth value for this.

   max-height 100% (of the PADDED grid area) rather than 84dvh (of the whole viewport):
   the reservation above is only honoured if the card measures itself against the space
   left over, not against the screen. 660px stays as the comfortable upper bound on a
   tall display, so the card never stretches into a letterbox. */
.settings-box { width: min(560px, 92vw); max-height: min(100%, 660px); background: var(--surface-1); border-radius: var(--r-sm); box-shadow: var(--elev-2); display: flex; flex-direction: column; overflow: hidden; transform: translateY(10px) scale(0.98); transition: transform 0.16s var(--ease); }
.settings-modal.open .settings-box { transform: translateY(0) scale(1); }
.settings-box .drawer-body { overflow-y: auto; padding: 6px 22px 20px; }
/* No divider line between rows. Each row is a label plus its controls, and the vertical
   padding here is what separates one row from the next -- a hairline would be a stroke,
   which only .settings-instructions and .about-link are allowed. */
.settings-row { padding: 18px 2px; }
/* Section headings -- Appearance, Font size, Instructions, Data, About.
   font-weight: 500, not 400. These are sub-headings that label a group of controls, and
   at body weight they read as another line of prose rather than as the start of a
   section. 500 is what the brand doc asks for at this level (Satoshi Medium for
   sub-headings) and is also the app's ceiling, so this adds emphasis without adding a
   third weight to the design. */
.settings-row label { display: block; font-size: 12.5px; font-weight: 500; color: var(--text); margin-bottom: 12px; }
.settings-btn-row { display: flex; flex-direction: column; gap: 8px; }
/* Icon + label on one baseline. `align-items: center` does the vertical alignment and
   the flexed <span> takes the remaining width, which is what keeps both buttons'
   labels starting on the same x regardless of icon width. The icon is `flex: none` so
   a long label can never squeeze it out of shape. */
/* Transparent at REST. The grey only appears on hover and on press -- these are two
   actions (export, delete-everything), not a permanent pair of filled blocks, and a
   resting fill made them shout at the same volume as the destructive one.
   There is no separate "in progress" state to style: both handlers are synchronous
   (export builds a blob and clicks a link; clear wipes state and closes the modal), so
   :active -- the press itself -- IS the action, and it takes the same grey as hover. */
/* `margin-inline: -13px` cancels this button's own horizontal padding, so its ICON starts
   on the same left edge as everything else in the panel -- the group labels, the
   Instructions box, the About block, all of which sit at the .settings-row 2px inset. With
   the padding uncancelled the two Data buttons were indented 13px further in than every
   other row, which is what made them look out of line with the content below.
   The padding stays (it is what gives the hover fill its shape); only the box is shifted
   back, so the fill extends slightly past the text on both sides and stays inside the
   panel's own 22px body padding. */
.settings-btn { background: transparent; color: var(--text); font: inherit; font-size: 13px; font-weight: 500; padding: 10px 13px; margin-inline: -13px; border-radius: var(--r); cursor: pointer; text-align: left; transition: background 0.14s; display: flex; align-items: center; gap: 10px; }
.settings-btn svg { width: 16px; height: 16px; flex: none; }
.settings-btn span { flex: 1; min-width: 0; }
.settings-btn:hover, .settings-btn:active { background: var(--surface-2); }
/* The destructive one is the only place colour carries meaning, so the icon inherits
   it via currentColor rather than being coloured separately. */
.settings-btn.danger { color: #C05227; }
.settings-btn.danger:hover, .settings-btn.danger:active { background: var(--surface-2); }
.settings-about-link { margin-top: 4px; }
/* STROKE EXCEPTION 1 of 2 (the other is .about-link). The Instructions field keeps a
   WHITE fill, which on the now-white Settings panel leaves it with no fill contrast to
   sit on -- so this is the one place a 1px hairline is allowed to do the separating
   instead. 1px is the minimum that renders, and --border is the palette neutral
   (#E0E0E0) reserved for hairlines. Nothing else in the chatbot
   may carry a border; see the reset in chat-base.css. */
.settings-instructions { width: 100%; box-sizing: border-box; resize: vertical; min-height: 88px; font: inherit; font-size: 13px; line-height: 1.55; color: var(--text); background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r); padding: 11px 13px; outline: none; }
/* No focus glow: `* { box-shadow: none !important }` in chat-base.css would drop it
   anyway, so declaring one was a no-op pretending to be a focus style. */
.settings-instructions:focus { border-color: var(--ring); }
.settings-instructions::placeholder { color: var(--placeholder); }
.segmented { display: flex; gap: 4px; background: var(--surface-2); border-radius: var(--r); padding: 4px; }
.segmented button { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; border: none; background: transparent; color: var(--muted); font: inherit; font-size: 11.5px; font-weight: 500; padding: 10px 0; border-radius: var(--r); cursor: pointer; transition: background 0.15s, color 0.15s, box-shadow 0.15s; }
.segmented button svg { width: 18px; height: 18px; }
.segmented button:hover { color: var(--text); }
.segmented button.active { background: var(--surface-1); color: var(--text); }
/* text-wrap: pretty, not balance. This is multi-line body copy, and `pretty` is the
   value made for it: it keeps normal line-filling but refuses to leave a single word
   alone on the last line. `balance` would even out ALL the lines, which on a paragraph
   this long makes the measure look ragged rather than tidy. Both are progressive --
   a browser without support just wraps normally. */
.settings-hint { margin: 11px 2px 0; font-size: 12px; color: var(--muted); line-height: 1.55; text-wrap: pretty; }
/* NO CARD. About is plain content on the Settings panel, so its brand lockup, blurb
   and link all start on the same left edge as the row's own "About" label (which sits
   at the .settings-row 2px inset). The card previously added 18px of its own padding,
   which indented every line of About relative to that label. No background, no radius,
   no padding -- the element stays only as the grouping box for its children. */
.about-card { padding: 0; }
.about-brand { display: flex; align-items: center; gap: 12px; margin-bottom: 13px; }
/* The brand star (see .av-star in chat-base.css), matching the sidebar lockup and the
   welcome hero. Was a gradient circle with a "Y" in it; the badge container went with
   the letter, since the star is the mark on its own everywhere else in the app.
   Width only, so .av-star's aspect-ratio keeps it in proportion. */
.about-logo { width: 34px; color: var(--accent); }
.about-name { font-family: var(--font-display); font-weight: 500; font-size: 15.5px; letter-spacing: -0.01em; }
.about-by { font-size: 12.5px; color: var(--muted); margin-top: 1px; }
.about-desc { font-size: 13px; line-height: 1.6; color: var(--muted); margin: 0 0 15px; }
/* STROKE EXCEPTION 2 of 2. Same reasoning as .settings-instructions above: white fill
   on a white panel, so a 1px --border hairline is what makes it a button. */
.about-link { display: flex; align-items: center; gap: 10px; padding: 11px 13px; border-radius: var(--r); background: var(--surface-1); border: 1px solid var(--border); color: var(--text); font-size: 13px; font-weight: 500; text-decoration: none; transition: background .15s; }
.about-link:hover { background: var(--hover); }
.about-link svg { width: 16px; height: 16px; flex: none; color: var(--muted); transition: color .15s; }
.about-link:hover svg { color: var(--text); }
.about-link span { flex: 1; }
.about-link .about-link-arrow { width: 15px; height: 15px; }

/* Library. */
.library-empty { margin: auto; color: var(--muted); font-size: 13.5px; text-align: center; padding: 40px 20px; }
/* flex: none on both header strips below.
   .drawer-body is a column flex container whose grid child takes flex: 1, so once the
   grid is taller than the panel these strips become shrink candidates. That is
   normally harmless because a flex item's automatic minimum size floors it at its
   content height, but that floor only applies while `overflow` is `visible`, and the
   filter row sets `overflow-x: auto` on narrow screens to scroll its pills. The floor
   therefore disappears and the row collapses to zero height, dropping the pills out of
   view. Pinning both strips is the fix, and it is correct regardless: neither should
   ever give up space to the grid. */
.library-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex: none; }
.library-toolbar .search-field { flex: 1; margin: 0; }
/* Height comes from --field-h, the same token the search field beside it uses, so the
   two controls in this toolbar line up exactly and cannot drift apart. Width HUGS the
   label (~135px) rather than being fixed: an earlier 185px was ~50px wider than the text
   needs, which read as a button with a hole in it.

   It was briefly 68px tall to match a Figma reference pill. That reference carries 24px
   Bold text; this label is 13px, so the same box around a third of the type just looked
   oversized -- and 68 next to a 52px field left the button overhanging 8px above and
   below it. The 32px side padding from that spec is kept, which is what still gives it
   the reference's generous proportions horizontally.

   `box-sizing: border-box` so the height is the outer box. `flex: none` stops the flex
   row from squeezing it when the search field claims the remaining space. Pill radius,
   matching the field. */
.library-sort {
  /* Two states. Newest first (the default) is ORANGE with a white label; clicking flips to
     Oldest first, which is brand BLACK with a white label. `.active` is set by library.js
     when the sort is descending, i.e. Newest first. */
  background: var(--btn-solid-bg); color: var(--btn-solid-fg);
  /* 15px, up from 13: at 13 the label read as small print inside a 52px pill sitting
     beside a 15px search field. */
  font: inherit; font-size: 15px; font-weight: 500;
  height: var(--field-h); box-sizing: border-box; flex: none;
  padding: 0 32px; gap: 16px;
  border-radius: var(--r-pill); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  white-space: nowrap; transition: background 0.15s, color 0.15s;
}
/* Newest first = descending = `.active` (library.js). Orange, white label. */
.library-sort.active { background: var(--accent); color: #fff; }
/* ---- the two hovers SWAP the two states ------------------------------------------
   Hovering either state shows the colour of the OTHER one, so the hover is a preview of
   what the click is about to produce:

     Newest first  orange + white   ->  hover: black + white   (Oldest first's colours)
     Oldest first  black  + white   ->  hover: orange + white  (Newest first's colours)

   That is why neither rule invents a third colour. A lighter/darker orange read as an
   accidental extra state and a grey read as "disabled"; borrowing the other state's fill
   says "click to get that" with the two colours the button already owns.
   --btn-solid-* rather than literals, so the black half shares the one "solid brand-black
   button" declaration with the New chat pill and the search field's circle. */
.library-sort.active:hover { background: var(--btn-solid-bg); color: var(--btn-solid-fg); }
.library-sort:not(.active):hover { background: var(--accent); color: #fff; }
/* Oldest first inherits the black/white pair from the rule above, so both states are
   declared in exactly one place each. */
.library-filter-row { display: flex; align-items: center; gap: 6px; margin-bottom: 16px; flex-wrap: wrap; flex: none; }
/* ---- category filters, matching the alphaventure.com/blog.php "Filter by:" row ------
   Three states, taken from the reference screenshots:
     rest      grey pill  (--surface-2 #F2F2F2), brand-black label
     hover     one step darker grey (#E0E0E0 via --border), same label
     selected  ORANGE fill (#F06731) with a white label
   All three values are approved AlphaVenture palette entries (#F2F2F2 and #E0E0E0 from
   the neutrals, #F06731 the primary), and orange is doing real work here rather than
   decoration: it marks which single category is active.

   Deliberately NOT --hover / --hover-2. Those are translucent grey overlays, so the
   resting and hover states were both `rgba(133,133,133,...)` over the same ground and
   `.active` was indistinguishable from `:hover` -- you could not see which filter was on.
   Solid fills are what make selected unambiguous.

   `:not(.active)` on the hover rule matters: without it, hovering the selected pill would
   drop it from orange back to grey, i.e. hovering the thing you had chosen would make it
   look unchosen. Selected stays orange and darkens one step instead. */
/* RESTING is now surface-1 WHITE, not surface-2 grey. The Library panel stopped being a
   white card and became the grey page (see .drawer#libraryPanel in chat-drawer.css), and
   a #F2F2F2 pill on a #F2F2F2 page is invisible -- the pills simply disappeared, leaving
   four floating labels. White is the correct level for a control sitting directly on the
   page, and it keeps the three states clearly ordered: white -> #E0E0E0 on hover ->
   orange when selected, each a visible step from the last. */
.lib-pill {
  font: inherit; font-size: 14px; font-weight: 500;
  padding: 12px 24px; border-radius: var(--r-pill);
  background: var(--surface-1); color: var(--text);
  cursor: pointer; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 8px;
  transition: background 0.15s, color 0.15s;
}
.lib-pill:not(.active):hover { background: var(--border); color: var(--text); }
.lib-pill.active { background: var(--accent); color: #fff; }
.lib-pill.active:hover { background: var(--accent-press); }
/* The count rides along in the label's colour, so it inverts with the pill instead of
   needing its own rule per state. */
/* Full-strength colour, no dimming. `opacity: 0.65` made "All 9" read as a greyed-out
   number beside a black label; the count is data, not a footnote. It inherits the pill's
   own colour, so it stays black on a grey pill and white on the selected orange one. */
.lib-pill-count { font-size: 12px; }
.library-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--lib-card-min), 1fr)); gap: 16px; }
.library-grid-wrap { flex: 1; }
/* surface-1 WHITE, moved down a level with the panel around it (see .lib-pill above and
   .drawer#libraryPanel in chat-drawer.css): the Library is the grey page now, so a
   surface-2 card on it would be grey-on-grey with nothing to separate it -- and this app
   has neither borders nor shadows to fall back on, so the fill IS the card.
   It also fixes a latent collision underneath: .library-card-thumb below is
   --bubble-bot (#F2F2F2), which was the SAME value as the card that contained it. Card
   white / thumb grey is the alternation the thumb always wanted. */
.library-card { display: flex; flex-direction: column; border-radius: var(--r); overflow: hidden; background: var(--surface-1); cursor: pointer; transition: transform 0.12s; }
/* --hover is a translucent grey overlay, so on the new white card it darkens toward
   #F2F2F2 rather than being a flat repaint -- still a visible change, and it no longer
   lands on the same colour as the ground behind the card (which is what made the old
   grey card appear to dissolve into the panel on hover). */
.library-card:hover { background: var(--hover); transform: translateY(-2px); }
.library-card-thumb { height: 124px; position: relative; overflow: hidden; background: var(--bubble-bot); display: grid; place-items: center; }
.library-card-thumb .chart-box { position: absolute; inset: 6px; }
.library-card-thumb table.artifact-table { font-size: 11px; width: 100%; }
.library-card-thumb table.artifact-table th, .library-card-thumb table.artifact-table td { padding: 4px 7px; }
.library-card-thumb-fade { position: absolute; z-index: 1; inset: 0; pointer-events: none; background: linear-gradient(to bottom, transparent 60%, var(--bubble-bot)); }
/* The placeholder shown while a report peek loads (and if it fails). Grey ground with
   a muted glyph, matching the chart and table thumbnails -- it was an orange gradient,
   which made reports the one artifact kind that looked different in the Library grid.
   The report itself still opens WHITE in the drawer; this is only the thumbnail. */
.library-card-cover { position: absolute; inset: 0; display: grid; place-items: center; background: var(--bubble-bot); color: var(--muted); }
.library-card-cover svg { width: 34px; height: 34px; }
/* The report peek (js/chat/library.js: attachReportPeek). The iframe is laid out at a
   fixed document width and scaled down by an inline transform, so `transform-origin` has
   to be the top-left corner -- at the default `center` the shrunken page pulls away from
   both edges and floats in the middle of the card.
   TRANSPARENT background, not --bubble-bot: the grey comes from .library-card-thumb behind
   it and from the report's own page fill being made transparent inside the iframe (see
   reportSrcdoc's preview mode). Painting grey here as well would be a second source of
   truth for the same colour. `pointer-events: none` sends the click to the card, which is
   what opens the report. */
.library-card-iframe {
  position: absolute; top: 0; left: 0; border: none; pointer-events: none;
  transform-origin: top left; background: transparent;
}
/* z-index is load-bearing, not decoration. Every layer in .library-card-thumb is
   `position: absolute` with no stacking context of its own, so paint order was DOM order
   -- and the report branch appends its preview iframe + fade ASYNCHRONOUSLY, after
   getReportContent() resolves, i.e. AFTER this button is already in the DOM. Result: on
   chart and table cards the expand button painted on top (their previews are appended
   synchronously, before it), but on report cards the iframe and the fade covered it. The
   button was still clickable the whole time (both layers are pointer-events: none), it
   just could not be seen -- which read as "reports have no expand button".
   A z-index here fixes it for any append order, including layers added later, which
   re-ordering the appends would not. */
.library-card-expand { position: absolute; z-index: 3; top: 8px; right: 8px; width: 30px; height: 30px; border-radius: var(--r); border: none; background: rgba(10,10,10,0.62); color: #fff; cursor: pointer; display: grid; place-items: center; opacity: 0; transition: opacity 0.15s, background 0.15s; }
.library-card:hover .library-card-expand { opacity: 1; }
.library-card-expand:hover { background: rgba(10,10,10,0.88) !important; }
.library-card-expand svg { width: 13px; height: 13px; }
.library-card-expand::after { content: "Open artefact"; position: absolute; right: 36px; top: 50%; transform: translateY(-50%); background: rgba(10,10,10,0.82); color: #fff; font-size: 11.5px; font-weight: 500; padding: 4px 9px; border-radius: var(--r); white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity 0.15s; }
.library-card-expand:hover::after { opacity: 1; }
/* `align-items: flex-end`, not flex-start. The foot is a two-line text block (title, then
   "From <chat>") beside a square download button, and top-aligning the button hung it off
   the title with a growing gap under it -- it read as floating in the corner rather than
   belonging to the row. Bottom-aligned, its lower edge finishes flush with the last line
   of text, which is the edge the eye actually reads the block against.
   Identical for all three artifact kinds: one .library-card-foot rule serves the chart,
   table and report cards, so they cannot drift apart. */
.library-card-foot { padding: 11px 13px; display: flex; align-items: flex-end; gap: 8px; }
.library-card-foot .artifact-title, .library-card-foot .artifact-sub { flex: 1; min-width: 0; }
.library-card-foot .artifact-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.library-card-foot .artifact-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 32px, up from 28. The text block beside it is ~33px tall (a 14px title, 2px gap, 12px
   subtitle), so at 28 the button was visibly shorter than the thing it sat against and the
   bottom alignment still looked a few pixels off. At 32 the square spans essentially the
   full two lines: its bottom edge lands on the subtitle's and its top edge on the title's,
   which is what makes the alignment read as deliberate rather than approximate.
   The icon goes 13px -> 15px with it, keeping the glyph the same proportion of the box. */
.library-card-dl { background: transparent; color: var(--muted); cursor: pointer; flex: none; width: 32px; height: 32px; border-radius: var(--r); display: grid; place-items: center; transition: color 0.14s; }
.library-card-dl svg { width: 15px; height: 15px; }
/* No hover FILL -- the glyph darkening is the signal, matching .icon-btn and the kebab. */
.library-card-dl:hover { background: transparent; color: var(--text); }

