/* Design tokens (issue #8). Every color elsewhere in this file
   references one of these -- change the palette here, nowhere else. */
:root {
  --ink: #1E1730;
  --paper: #F7F5FB;
  --accent: #7C5CFC;
  --muted: #8B84A0;
  --scenery: #E8A5C4;
  /* A brighter surface than --paper, for elements that should lift off
     the page background (the report's dashboard cards) -- pure white
     reads as "elevated" against --paper's faint lilac tint without
     introducing an unrelated hue. */
  --surface: #FFFFFF;

  /* Supplementary status colors for the activity view's outcome badges.
     Not part of the required 5-token palette above, but kept as
     variables for the same reason -- centrally editable, not scattered
     hex literals. */
  --success: #2E9E52;
  --warning: #B5590A;
  --error: #C23B33;

  /* Event-kind colors for the Activity chart (#activity redesign) --
     deliberately distinct from success/warning/error above, which are
     reserved for outcome (did the call succeed), not kind (what the
     call was for). "ask" reuses --accent, the app's existing signature
     interactive color (already used for the chat spinner/token bar),
     rather than adding a 5th one-off token for it. */
  --kind-ingestion: #2B6CB5;
  --kind-enrichment: #1B8C8C;
  --kind-reindex: #9C8016;

  --font-sans: "Inter", system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;

  --radius: 10px;
}

* { box-sizing: border-box; }

body {
  font-family: var(--font-sans);
  margin: 2rem;
  background: var(--paper);
  color: var(--ink);
}

nav { margin-bottom: 1.5rem; display: flex; align-items: center; justify-content: space-between; }
nav a { text-decoration: none; color: var(--muted); font-weight: 600; }
nav a:hover { text-decoration: underline; color: var(--ink); }
nav a.active { color: var(--accent); text-decoration: underline; text-underline-offset: 0.25em; }

.nav-links a { margin-right: 1rem; }

/* Activity + Documentation grouped together at the right edge of the
   nav, rather than Activity living with the other page links on the
   left -- Activity is more of a "look under the hood" page than a
   primary navigation destination, so it reads more naturally paired
   with Documentation. */
.nav-secondary { display: flex; align-items: center; gap: 1.25rem; }
/* Log out is a POST form (see app/auth.py), styled to read like the nav links. */
.nav-logout { margin: 0; }
.nav-logout button {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.nav-logout button:hover { text-decoration: underline; color: var(--ink); }
.nav-user { font-size: 0.8rem; color: var(--muted); max-width: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Synthetic-data disclaimer strip (app/templates/_synthetic_disclaimer.html),
   shown on every page including the login screen. */
.synthetic-notice {
  margin: 0 0 1rem;
  padding: 0.45rem 0.9rem;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 8%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--paper));
  color: var(--ink);
  font-size: 0.8rem;
}
.synthetic-notice a { color: var(--accent); font-weight: 600; }

/* Login page (app/templates/login.html) -- standalone, no nav. */
.login-card { max-width: 24rem; margin: 4rem auto; text-align: center; }
.login-error { color: var(--error); font-weight: 600; }
.login-button {
  display: inline-block;
  margin-top: 1rem;
  padding: 0.6rem 1.4rem;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  text-decoration: none;
}
.login-button:hover { filter: brightness(1.08); }

/* Documentation dropdown (right side of the nav) -- native <details>,
   no JS. summary gets the same link-ish treatment as the nav <a>s so it
   reads as part of the same nav, not a stray control. */
.nav-docs { position: relative; }
.nav-docs summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--muted);
  list-style: none;
}
.nav-docs summary::-webkit-details-marker { display: none; }
.nav-docs summary:hover { color: var(--ink); }
.nav-docs[open] summary { color: var(--ink); }
.nav-docs summary::after { content: " \25BE"; font-size: 0.8em; }

.nav-docs-menu {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  min-width: 220px;
  background: var(--paper);
  border: 1px solid color-mix(in srgb, var(--ink) 12%, var(--paper));
  border-radius: var(--radius);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--ink) 14%, transparent);
  padding: 0.4rem;
  z-index: 10;
}
.nav-docs-menu a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  border-radius: 6px;
  font-weight: 500;
  font-size: 0.9rem;
  color: var(--ink);
}
.nav-docs-menu a:hover { background: color-mix(in srgb, var(--accent) 10%, var(--paper)); text-decoration: none; }
.nav-docs-external { color: var(--muted); font-size: 0.85em; }

/* Analyst picker (#45) -- a session-scoped view-switcher, deliberately
   plain (native select + button) so it never reads as a real login
   control. See app/routes/analyst.py. */
.analyst-widget {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.analyst-widget label { font-weight: 600; }
.analyst-widget select {
  font-family: var(--font-sans);
  font-size: 0.85rem;
  padding: 0.15rem 0.4rem;
  border: 1px solid color-mix(in srgb, var(--ink) 15%, var(--paper));
  border-radius: 6px;
  background: var(--paper);
  color: var(--ink);
}
.analyst-widget button {
  font-family: var(--font-sans);
  font-size: 0.8rem;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: transparent;
  color: var(--accent);
  cursor: pointer;
}
.analyst-widget button:hover { background: color-mix(in srgb, var(--accent) 10%, var(--paper)); }
.analyst-widget-loading { font-style: italic; }

h1, h2, h3 { letter-spacing: -0.01em; }

code {
  font-family: var(--font-mono);
  background: color-mix(in srgb, var(--ink) 6%, var(--paper));
  padding: 0.05rem 0.35rem;
  border-radius: 4px;
}

.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
th, td {
  text-align: left;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, var(--paper));
}
th {
  background: color-mix(in srgb, var(--ink) 5%, var(--paper));
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
}

.filters { display: flex; gap: 1.5rem; align-items: flex-end; margin-bottom: 1rem; flex-wrap: wrap; }
.filters label { display: flex; flex-direction: column; font-size: 0.85rem; color: var(--muted); gap: 0.3rem; }
.filters select {
  font-family: var(--font-sans);
  padding: 0.35rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--ink) 20%, var(--paper));
  border-radius: 6px;
  background: var(--paper);
  color: var(--ink);
}

dl.meta { display: grid; grid-template-columns: max-content 1fr; gap: 0.35rem 1rem; margin: 1rem 0; }
dl.meta dt { color: var(--muted); font-size: 0.85rem; }
dl.meta dd { margin: 0; overflow-wrap: anywhere; }
dl.meta dd.mono { font-size: 0.9rem; }

/* The entity panel is only ~300px wide -- a side-by-side label|value
   grid leaves too little room for values (long mono timestamps were
   overflowing the column outright, not just wrapping tightly), so
   stack dt above dd there instead of the two-column layout used
   elsewhere. */
.pane-entities dl.meta { grid-template-columns: 1fr; gap: 0.1rem; }
.pane-entities dl.meta dt { margin-top: 0.6rem; }
.pane-entities dl.meta dt:first-child { margin-top: 0; }

ul.entities { list-style: none; padding: 0; }
ul.entities li {
  padding: 0.4rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, var(--paper));
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: 0.3rem;
  gap: 0.5rem;
}

.badge {
  display: inline-block;
  padding: 0.1rem 0.6rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 8%, var(--paper));
  color: var(--muted);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 600;
}
.badge.scenery-badge {
  background: color-mix(in srgb, var(--scenery) 30%, var(--paper));
  color: color-mix(in srgb, var(--scenery) 65%, var(--ink));
}
.badge.outcome-success { background: color-mix(in srgb, var(--success) 16%, var(--paper)); color: var(--success); }
.badge.outcome-refusal { background: color-mix(in srgb, var(--warning) 16%, var(--paper)); color: var(--warning); }
.badge.outcome-truncated { background: color-mix(in srgb, var(--warning) 16%, var(--paper)); color: var(--warning); }
.badge.outcome-error { background: color-mix(in srgb, var(--error) 16%, var(--paper)); color: var(--error); }

/* Event kind -- same color-mix badge convention as outcome above, plus a
   solid-fill variant for the chart's bar segments/legend swatches. */
.badge.kind-ingestion { background: color-mix(in srgb, var(--kind-ingestion) 16%, var(--paper)); color: var(--kind-ingestion); }
.badge.kind-ask { background: color-mix(in srgb, var(--accent) 16%, var(--paper)); color: var(--accent); }
.badge.kind-enrichment { background: color-mix(in srgb, var(--kind-enrichment) 16%, var(--paper)); color: var(--kind-enrichment); }
.badge.kind-reindex { background: color-mix(in srgb, var(--kind-reindex) 16%, var(--paper)); color: var(--kind-reindex); }
.badge.kind-mixed, .badge.kind-other { background: color-mix(in srgb, var(--muted) 16%, var(--paper)); color: var(--muted); }

.chart-swatch.kind-ingestion, .chart-bar-segment.kind-ingestion { background: var(--kind-ingestion); }
.chart-swatch.kind-ask, .chart-bar-segment.kind-ask { background: var(--accent); }
.chart-swatch.kind-enrichment, .chart-bar-segment.kind-enrichment { background: var(--kind-enrichment); }
.chart-swatch.kind-reindex, .chart-bar-segment.kind-reindex { background: var(--kind-reindex); }
.chart-swatch.kind-mixed, .chart-bar-segment.kind-mixed,
.chart-swatch.kind-other, .chart-bar-segment.kind-other { background: var(--muted); }

/* Per-model breakdown cards (Activity view). Bar widths are set inline per-row
   in app/routes/activity.py's _aggregate_by_model, the same server-computed-
   percentage convention used for --h avatar hues on the chats view. */
.model-breakdown { display: flex; flex-direction: column; gap: 0.75rem; margin: 1.5rem 0; }

.model-card {
  border: 1px solid color-mix(in srgb, var(--ink) 12%, var(--paper));
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  background: color-mix(in srgb, var(--ink) 3%, var(--paper));
}
.model-card-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.model-name { font-family: var(--font-mono); font-weight: 700; }
.model-card-stats { font-size: 0.8rem; color: var(--muted); }

/* Stacked outcome bar -- one segment per non-zero outcome. Same success/
   warning/error mapping as .badge.outcome-* above. */
.outcome-bar {
  display: flex;
  height: 8px;
  border-radius: 999px;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink) 8%, var(--paper));
  margin-bottom: 0.35rem;
}
.outcome-bar-segment { height: 100%; }
.outcome-bar-segment.outcome-success { background: var(--success); }
.outcome-bar-segment.outcome-refusal { background: var(--warning); }
.outcome-bar-segment.outcome-truncated { background: var(--warning); }
.outcome-bar-segment.outcome-error { background: var(--error); }

/* Token-usage bar -- single fill, relative to the busiest model in the
   current filter set (that model's bar is always full-width). */
.token-bar {
  height: 6px;
  border-radius: 999px;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink) 8%, var(--paper));
}
.token-bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--accent); }

.cost-unknown { color: var(--muted); font-style: italic; }

.time-range-notice { margin: 0 0 1rem; font-size: 0.85rem; color: var(--muted); }

/* Activity chart (#activity redesign) -- first vertical/column bar chart
   in this codebase. Same "server computes the percentage, template
   applies it as an inline style" convention as .outcome-bar/.token-bar
   above, just extended to the height axis: each bucket's own height is
   relative to the tallest bucket in view (.chart-bar), and each
   segment's height is relative to that bucket's own total (.chart-bar-
   segment, stacking to 100% of .chart-bar's own height). */
.activity-chart { margin: 1.5rem 0; }
.chart-legend { display: flex; flex-wrap: wrap; gap: 0.9rem; margin-bottom: 0.75rem; font-size: 0.8rem; color: var(--muted); }
.chart-legend-item { display: flex; align-items: center; gap: 0.35rem; }
.chart-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: hsl(var(--h, 250) 65% 50%);
}
.chart-bars {
  display: flex;
  align-items: flex-end;
  gap: 0.4rem;
  height: 180px;
  padding: 0 0.25rem;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 15%, var(--paper));
}
.chart-bar-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
  min-width: 0;
  height: 100%;
  text-decoration: none;
  color: inherit;
}
.chart-bar-track { flex: 1; display: flex; align-items: flex-end; width: 100%; }
.chart-bar {
  width: 100%;
  display: flex;
  flex-direction: column-reverse;
  border-radius: 3px 3px 0 0;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink) 6%, var(--paper));
  min-height: 2px;
}
.chart-bar-col:hover .chart-bar { background: color-mix(in srgb, var(--ink) 10%, var(--paper)); }
/* Default fill uses --h (set inline per-segment for model-coloring mode,
   same hsl(var(--h, 250) ...) convention as .chart-swatch/.avatar above)
   -- the .chart-bar-segment.kind-* rules further up override this for
   kind-coloring mode, since two-class selectors win regardless of
   source order in the stylesheet. */
.chart-bar-segment { width: 100%; background: hsl(var(--h, 250) 65% 50%); }
.chart-bar-label { margin-top: 0.35rem; font-size: 0.68rem; color: var(--muted); }

.summary { display: flex; flex-wrap: wrap; gap: 2rem; margin: 1rem 0 1.5rem; }
.stat { display: flex; flex-direction: column; }
.stat-value { font-size: 1.4rem; font-weight: 700; font-family: var(--font-mono); }
.stat-label { font-size: 0.8rem; color: var(--muted); }
.stat.outcomes { flex-basis: 100%; }
.stat.outcomes .mono { font-size: 0.9rem; }

.notice {
  padding: 0.75rem 1rem;
  background: color-mix(in srgb, var(--warning) 12%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--paper));
  border-radius: var(--radius);
  color: color-mix(in srgb, var(--warning) 80%, var(--ink));
}
.muted { color: var(--muted); }

/* Two-pane chat list + detail layout */
.two-pane {
  display: flex;
  border: 1px solid color-mix(in srgb, var(--ink) 15%, var(--paper));
  border-radius: 12px;
  overflow: hidden;
  height: 75vh;
  margin-top: 1rem;
  box-shadow: 0 1px 3px color-mix(in srgb, var(--ink) 12%, transparent);
}
.pane-list {
  width: 320px;
  flex-shrink: 0;
  overflow-y: auto;
  background: var(--ink);
}
.pane-detail {
  flex: 1;
  display: flex;
  overflow: hidden;
  background: var(--paper);
  color: var(--ink);
}
.pane-detail .empty-state { margin: 2rem auto; text-align: center; color: var(--muted); }

/* Chat detail = messages column + collapsible entity panel (issue #6).
   .pane-messages is flex:1, so when .pane-entities shrinks to its
   collapsed rail width, the messages column reflows to fill the freed
   space automatically -- no JS needed for that part, only for the
   class toggle itself (see chats.html's <script>). */
.pane-messages { flex: 1; min-width: 0; overflow-y: auto; padding: 1.5rem 2rem; }

.pane-entities {
  width: 300px;
  flex-shrink: 0;
  overflow-y: auto;
  border-left: 1px solid color-mix(in srgb, var(--ink) 12%, var(--paper));
  background: color-mix(in srgb, var(--ink) 3%, var(--paper));
}
.two-pane.entity-panel-collapsed .pane-entities { width: 44px; }
.two-pane.entity-panel-collapsed .entity-panel-body,
.two-pane.entity-panel-collapsed .panel-title {
  display: none;
}
.two-pane.entity-panel-collapsed .entity-panel-header { justify-content: center; padding: 0.9rem 0; }

.entity-panel-header {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.9rem 1rem;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, var(--paper));
  background: inherit;
  font-weight: 600;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.panel-toggle {
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--muted);
  width: 26px;
  height: 26px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
  flex-shrink: 0;
}
.panel-toggle:hover { background: color-mix(in srgb, var(--ink) 8%, var(--paper)); color: var(--ink); }
.panel-toggle span { display: inline-block; transition: transform 0.15s ease; }
.two-pane.entity-panel-collapsed .panel-toggle span { transform: rotate(180deg); }

.entity-panel-body { padding: 1.1rem 1.25rem 1.5rem; }
.entity-summary-text { margin-top: 0; }
.entity-caption { font-size: 0.75rem; color: var(--muted); }

/* Narrow viewports: deliberately no separate media query here. The
   .entity-panel-collapsed rules above already shrink the panel at any
   width once that class is present, and chats.html's init script
   applies it by default on a first-ever visit under 900px (only when
   there's no saved preference yet) -- so the narrow-screen default is
   collapsed without a three-column squeeze, while still leaving the
   toggle free to expand it at any width if the user wants that. A
   parallel CSS-only breakpoint here would fight that JS-driven state
   instead of deferring to it. */

/* Single search box (issue #9), replacing the old category/triage_status/
   extraction_status dropdowns for the chat list. Lives inside the dark
   .pane-list per the issue's brief, so it's styled against --ink
   rather than the .filters/select treatment used elsewhere (still
   used as-is by the activity view, untouched by this issue). */
.search-box {
  position: sticky;
  top: 0;
  padding: 0.75rem;
  background: var(--ink);
  border-bottom: 1px solid color-mix(in srgb, var(--paper) 12%, transparent);
  z-index: 1;
}
.search-input {
  width: 100%;
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  border: 1px solid color-mix(in srgb, var(--paper) 22%, transparent);
  background: color-mix(in srgb, var(--paper) 10%, transparent);
  color: var(--paper);
  font-family: var(--font-sans);
  font-size: 0.85rem;
}
.search-input::placeholder { color: color-mix(in srgb, var(--paper) 45%, transparent); }
.search-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

ul.chat-list { list-style: none; margin: 0; padding: 0; }
a.chat-row {
  display: flex;
  gap: 0.65rem;
  align-items: flex-start;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid color-mix(in srgb, var(--paper) 10%, transparent);
  text-decoration: none;
  color: color-mix(in srgb, var(--paper) 85%, transparent);
  cursor: pointer;
}
a.chat-row:hover { background: color-mix(in srgb, var(--paper) 6%, transparent); }
a.chat-row.selected {
  background: color-mix(in srgb, var(--accent) 35%, var(--ink));
  border-left: 3px solid var(--accent);
  padding-left: calc(1rem - 3px);
}
.chat-row-avatars { display: flex; flex-shrink: 0; }
.chat-row-avatars .avatar:not(:first-child) { margin-left: -0.6rem; }
.chat-row-body { min-width: 0; flex: 1; }
.chat-row-title { font-weight: 600; font-size: 0.88rem; overflow-wrap: break-word; color: var(--paper); }
.chat-row-meta { margin-top: 0.35rem; display: flex; flex-wrap: wrap; gap: 0.3rem; }
.chat-row-meta .badge { background: color-mix(in srgb, var(--paper) 12%, transparent); color: color-mix(in srgb, var(--paper) 80%, transparent); }
.chat-row-entities {
  margin-top: 0.3rem;
  font-size: 0.78rem;
  color: color-mix(in srgb, var(--paper) 55%, transparent);
}

/* Avatars: --h (hue) is set inline per element/group from avatar_hue().
   "self" always uses --accent instead, since it's a fixed anchor
   concept per chat rather than a per-identity color. */
.avatar {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.68rem;
  font-weight: 700;
  color: #fff;
  background: hsl(var(--h, 250) 65% 50%);
  border: 2px solid var(--ink);
}
.avatar.self { background: var(--accent); }
.avatar.sm { width: 20px; height: 20px; font-size: 0.58rem; border: none; }
.pane-detail .avatar { border-color: var(--paper); }

/* iMessage-style bubbles, colored per sender via the same --h token */
.messages { display: flex; flex-direction: column; gap: 0.9rem; margin: 1.25rem 0 1.75rem; }
.bubble-group { display: flex; gap: 0.5rem; max-width: 100%; }
.bubble-group.self { flex-direction: row-reverse; }
.bubble-stack { display: flex; flex-direction: column; max-width: calc(100% - 2.5rem); }
.bubble-group.self .bubble-stack { align-items: flex-end; }
.bubble-group.other .bubble-stack { align-items: flex-start; }
.bubble-sender { font-size: 0.75rem; color: var(--muted); margin-bottom: 0.2rem; margin-left: 0.6rem; }
.bubble {
  max-width: 100%;
  padding: 0.5rem 0.85rem;
  border-radius: 1.1rem;
  margin-bottom: 0.2rem;
  font-size: 0.9rem;
  line-height: 1.35;
  word-wrap: break-word;
}
.bubble.self { background: var(--accent); color: #fff; border-bottom-right-radius: 0.3rem; }
.bubble.other {
  background: hsl(var(--h, 250) 70% 92%);
  color: color-mix(in srgb, var(--ink) 92%, black);
  border-bottom-left-radius: 0.3rem;
}

/* Message attachments (issue #37) -- images render inline in the bubble;
   Unsplash-sourced ones get a clickable attribution line beneath, per
   https://help.unsplash.com/en/articles/2511315-guideline-attribution.
   Generated document mockups (source: "generated") have no attribution
   key at all, so this block simply doesn't render for them. */
.attachment { margin-top: 0.4rem; }
.attachment-image { display: block; max-width: 260px; border-radius: 0.6rem; cursor: pointer; }
.attachment-image:hover { opacity: 0.9; }
.attachment-image:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.attachment-attribution {
  font-size: 0.7rem;
  opacity: 0.75;
  margin-top: 0.2rem;
}
.attachment-attribution a { color: inherit; text-decoration: underline; }

/* Image viewer dialog (native <dialog>, see chats.html) -- larger copy of
   a clicked attachment on the left, a details pane (caption for now, room
   to grow) on the right. */
.image-viewer-dialog {
  padding: 0;
  border: none;
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  max-width: 900px;
  width: 90vw;
}
.image-viewer-dialog::backdrop {
  background: color-mix(in srgb, var(--ink) 60%, transparent);
}
.image-viewer-body { display: flex; max-height: 80vh; }
.image-viewer-body img {
  max-width: 60%;
  max-height: 80vh;
  object-fit: contain;
  background: color-mix(in srgb, var(--ink) 4%, var(--paper));
}
.image-viewer-details { flex: 1; padding: 1.5rem; overflow-y: auto; }
.image-viewer-details h3 { margin-top: 0; }
.image-viewer-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  border: none;
  background: none;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 0.3rem 0.5rem;
}
.image-viewer-close:hover { color: var(--ink); }
@media (max-width: 700px) {
  .image-viewer-body { flex-direction: column; max-height: 90vh; overflow-y: auto; }
  .image-viewer-body img { max-width: 100%; max-height: 50vh; }
}

/* Entity highlights inside bubble text (issue #5). Both treatments mix
   with currentColor rather than a fixed hex, so they stay readable on
   both the solid --accent "self" bubble (white text) and the pale
   per-sender "other" bubble (dark text) without introducing a new
   color outside the existing token set. Person vs location are told
   apart by shape (underline+fill vs outline pill), not hue, since
   --scenery is reserved for the relevant:false signal below and there
   is no third brand hue to spend on this.
   entity-scenery overrides both to the same dashed, --scenery-tinted
   treatment -- reusing the app's one existing "not relevant" signal
   (already used in the entities list below) rather than inventing a
   second, unrelated way to say the same thing. */
.entity-highlight { border-radius: 4px; padding: 0 0.2em; font-weight: 600; }
.entity-person {
  background: color-mix(in srgb, currentColor 16%, transparent);
  border-bottom: 2px solid color-mix(in srgb, currentColor 55%, transparent);
}
.entity-location {
  border-radius: 999px;
  padding: 0 0.5em;
  border: 1.5px solid color-mix(in srgb, currentColor 50%, transparent);
}
.entity-highlight.entity-scenery {
  font-weight: 500;
  background: color-mix(in srgb, var(--scenery) 25%, transparent);
  border-color: color-mix(in srgb, var(--scenery) 65%, transparent);
  border-style: dashed;
}
/* Locations panel only (chat_detail_fragment.html) -- a destination that
   resolved to a geocoded point links to /map?focus=<id>. Neutralizes the
   browser's default link color/underline so it keeps reading as the same
   pill shape as an unlinked entity-location, with just a cursor + subtle
   hover lift as the "this is clickable" signal. */
a.entity-location-linked {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
a.entity-location-linked:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}
/* Dates/Times (issue #6's entity panel only -- there's no inline bubble
   precedent for this one from issue #5, since datetimes aren't
   highlighted inline). Reuses the mono treatment already established
   for timestamps elsewhere rather than the person/location shapes,
   which wouldn't make sense for a date. Still gets the same
   entity-scenery dashed override for relevant:false. */
.entity-date {
  font-family: var(--font-mono);
  font-weight: 600;
  background: color-mix(in srgb, var(--ink) 8%, var(--paper));
  border: 1px solid transparent;
}
.bubble-time { font-size: 0.7rem; color: var(--muted); margin: 0.15rem 0.6rem 0; font-family: var(--font-mono); }

/* Focus states -- explicit rather than relying on (and accidentally
   losing) the browser default when re-skinning interactive elements. */
a:focus-visible, select:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* .chat-row sits flush against .pane-list's edges, which clip an
   outside outline via .two-pane's overflow:hidden -- draw this one
   inset instead so it stays fully visible. */
a.chat-row:focus-visible {
  outline-offset: -2px;
}

@media (prefers-reduced-motion: no-preference) {
  a.chat-row, .badge { transition: background-color 0.15s ease, color 0.15s ease; }
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Chat UI (issue #22) -- one bubble for the question, then a separate
   labeled card per path that answered it, echoing the reference
   screenshot's layout (stacked, individually-rounded response cards
   under a labeled sender row) without borrowing its literal colors --
   everything here reads off this file's existing tokens, and the card
   shell reuses .model-card's border/radius/background treatment rather
   than inventing a second "card" language. */
.chat-ask-form { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.5rem; }
.chat-mode-toggle { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.chat-mode-toggle label {
  display: flex; align-items: center; gap: 0.4rem;
  font-size: 0.88rem; color: var(--muted); cursor: pointer;
}
.chat-mode-toggle input:checked + label,
.chat-mode-toggle label:has(input:checked) { color: var(--ink); font-weight: 600; }
.chat-input-row { display: flex; gap: 0.6rem; }
.chat-input-row input[type="text"] {
  flex: 1;
  padding: 0.6rem 0.9rem;
  border: 1px solid color-mix(in srgb, var(--ink) 16%, var(--paper));
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 0.92rem;
}
.chat-input-row input[type="text"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.chat-input-row button {
  padding: 0.6rem 1.25rem;
  border: none;
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
}
.chat-input-row button:hover { background: color-mix(in srgb, var(--accent) 88%, black); }
.chat-input-row button:disabled { opacity: 0.6; cursor: default; }

/* htmx toggles .htmx-request (and this element's opacity, via htmx's own
   injected default CSS) while the /chat/ask request is in flight -- this
   only supplies layout/color, not the show/hide itself. */
.chat-ask-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  font-size: 0.85rem;
}

.chat-thread { display: flex; flex-direction: column; gap: 1.5rem; }
.chat-turn { display: flex; flex-direction: column; gap: 0.75rem; }

.chat-card { border-radius: 1.1rem; padding: 0.8rem 1.1rem; }
.chat-card-question {
  align-self: flex-end;
  max-width: 75%;
  background: var(--accent);
  color: #fff;
  border-bottom-right-radius: 0.3rem;
}
.chat-card-patha, .chat-card-pathb {
  align-self: flex-start;
  max-width: 85%;
  border: 1px solid color-mix(in srgb, var(--ink) 12%, var(--paper));
  background: color-mix(in srgb, var(--ink) 3%, var(--paper));
  border-bottom-left-radius: 0.3rem;
}
.chat-card-header { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.4rem; }
.chat-sender-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.3rem; height: 1.3rem; border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 18%, var(--paper));
  color: var(--accent); font-size: 0.75rem;
}
.chat-sender-label { font-weight: 600; font-size: 0.85rem; }
.chat-card-meta { font-size: 0.75rem; color: var(--muted); margin-left: auto; }
.chat-card-body { white-space: pre-wrap; line-height: 1.5; font-size: 0.92rem; }
/* The question bubble and error/pending text are still plain strings
   (pre-wrap is right for those -- literal newlines should show), but a
   rendered-markdown answer already gets its spacing from real <p>/<ul>
   elements -- pre-wrap on top of that would double up on blank lines. */
.chat-card-body .rendered-markdown { white-space: normal; }
.chat-card-searches { margin-top: 0.5rem; font-size: 0.78rem; color: var(--muted); }
.chat-card-searches-empty { font-style: italic; }
.search-repeat-badge {
  display: inline-block;
  padding: 0 0.4em;
  border-radius: 1em;
  background: color-mix(in srgb, var(--warning) 16%, transparent);
  color: var(--warning);
  font-weight: 600;
}

.chat-card-pending .chat-card-body { color: var(--muted); font-style: italic; }
.chat-pending-body { display: flex; align-items: center; gap: 0.5rem; }
.chat-spinner {
  width: 0.8rem; height: 0.8rem; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-top-color: var(--accent);
  animation: chat-spin 0.7s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .chat-spinner { animation: none; }
}
@keyframes chat-spin { to { transform: rotate(360deg); } }

.chat-card-error { border-color: color-mix(in srgb, var(--error) 40%, var(--paper)); }

/* Modeled on .refresh-transparency/.query-list (analyst_report.html's
   own live-search transparency list) -- same collapsed-strip pattern,
   scoped down to fit inside a chat card rather than a full page section. */
.chat-card-references {
  margin-top: 0.6rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 6%, var(--paper));
  font-size: 0.8rem;
}
.chat-card-references summary { cursor: pointer; font-weight: 600; color: var(--muted); }
.chat-card-references + .chat-card-references { margin-top: 0.4rem; }
.citation-list { margin: 0.5rem 0 0; padding-left: 1.1rem; }
.citation-list li { margin-bottom: 0.4rem; }
.citation-link { font-weight: 600; }
.citation-meta { color: var(--muted); margin-left: 0.4em; }
.citation-summary { color: var(--muted); font-size: 0.85em; margin-top: 0.1rem; }

.map-container {
  height: 70vh;
  border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--ink) 12%, var(--paper));
}
.map-empty-state { margin: 2rem auto; text-align: center; color: var(--muted); }
.map-popup { font-family: var(--font-sans); }
.map-popup .map-popup-location { font-weight: 700; margin-bottom: 0.2rem; }
.map-popup .map-popup-meta { font-size: 0.8rem; color: var(--muted); margin-bottom: 0.4rem; }
.map-popup .map-popup-flag { font-size: 0.8rem; color: var(--error); margin-bottom: 0.4rem; }
.map-popup .map-popup-people { font-size: 0.85rem; margin-bottom: 0.3rem; }
.map-popup .map-popup-link { display: inline-block; margin-top: 0.5rem; font-size: 0.85rem; color: var(--accent); font-weight: 600; text-decoration: none; }
.map-popup .map-popup-link:hover { text-decoration: underline; }
.chat-card-error .chat-card-body { color: var(--error); }

/* Analyst report (#48) */
.report-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}
.report-meta { color: var(--muted); font-size: 0.9rem; display: flex; align-items: center; gap: 0.4rem; }
.refresh-transparency {
  margin-bottom: 1.5rem;
  padding: 0.6rem 1rem;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 6%, var(--paper));
  font-size: 0.85rem;
}
.refresh-transparency summary { cursor: pointer; font-weight: 600; }
.query-list { margin: 0.5rem 0 0; padding-left: 1.2rem; color: var(--muted); }

/* Trip cards are grouped by month (analyst_report.py's month_label);
   each group is a heading + its own grid, so the grid never mixes
   trips from different months without a visual break. */
.trip-month-group { margin-bottom: 1.75rem; }
.trip-month-heading {
  font-size: 0.95rem;
  font-weight: 700;
  margin: 0 0 0.75rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, var(--paper));
}
.trip-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 1rem;
  margin-bottom: 0.5rem;
}

/* Each card is a plain link to its own single-page detail view (#48
   follow-up), NOT an inline <details> expand -- a <details> inside a
   CSS grid row stretches every OTHER card in that row to match its
   opened height (grid items stretch to fill their row by default),
   which read as "the whole row opened" for one click. A real page
   navigation sidesteps that and gives a genuine, closeable/back-
   button-able view. */
.trip-card {
  display: block;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--paper));
  border-radius: var(--radius);
  box-shadow: 0 1px 3px color-mix(in srgb, var(--ink) 6%, transparent);
  padding: 1rem 1.25rem;
  color: inherit;
  text-decoration: none;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.trip-card:hover, .trip-card:focus-visible {
  box-shadow: 0 4px 14px color-mix(in srgb, var(--ink) 12%, transparent);
  transform: translateY(-1px);
  text-decoration: none;
}
.trip-card-header { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.trip-card-header h2 { margin: 0; font-size: 1.05rem; }
.trip-dates { color: var(--muted); font-size: 0.82rem; white-space: nowrap; }
.trip-people { color: var(--muted); font-size: 0.85rem; margin: 0.25rem 0 0.7rem; }

/* Trip detail page (app/templates/analyst_report_trip.html) */
.trip-detail-back {
  display: inline-block;
  margin-bottom: 1.25rem;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
}
.trip-detail-back:hover { color: var(--accent); text-decoration: underline; }
.trip-detail-header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.trip-detail-header h1 { margin: 0; font-size: 1.6rem; }
.trip-card-detail-page {
  margin-top: 1.25rem;
  padding: 1.25rem;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--paper));
  border-radius: var(--radius);
  box-shadow: 0 1px 3px color-mix(in srgb, var(--ink) 6%, transparent);
  max-width: 640px;
}

.trip-badge-row { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.badge-weather {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 5%, var(--paper));
  color: var(--ink);
  font-size: 0.78rem;
}
.badge-alert {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--warning) 16%, var(--paper));
  color: var(--warning);
  font-size: 0.78rem;
  font-weight: 600;
}

.trip-map {
  height: 200px;
  border-radius: 8px;
  margin-bottom: 0.85rem;
  /* Filled while Leaflet lazily loads its tiles (on first open, see
     app/static/analyst_report.js) so the space doesn't read as a
     broken/empty box before the map paints. */
  background: color-mix(in srgb, var(--ink) 6%, var(--paper));
}

.trip-weather-days { font-size: 0.85rem; margin-bottom: 0.6rem; display: flex; flex-wrap: wrap; gap: 0.6rem; }
.weather-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  color: var(--muted);
  min-width: 3.4rem;
}
.weather-day .weather-day-icon { font-size: 1.2rem; }
.weather-day .weather-day-date { font-size: 0.72rem; }
.weather-day .weather-day-temp { color: var(--ink); font-size: 0.78rem; }
.trip-weather-note { font-size: 0.85rem; color: var(--muted); margin-bottom: 0.6rem; }
.attribution { color: var(--muted); font-size: 0.72rem; display: block; margin-top: 0.3rem; }

.trip-events, .trip-alerts {
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
  margin-top: 0.5rem;
  font-size: 0.88rem;
}
.trip-events { background: color-mix(in srgb, var(--ink) 4%, var(--paper)); }
/* Alerts get a distinct, warning-toned treatment -- never blended visually
   with the neutral events block above, per #48's explicit requirement. */
.trip-alerts {
  background: color-mix(in srgb, var(--warning) 10%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--warning) 25%, var(--paper));
}
.trip-alerts strong { color: var(--warning); }

/* One event/alert's rendered markdown (markdown_render.py -- real
   headers/bold/lists instead of literal "**"/"##" characters). Each
   summary gets a hairline divider from the next one within the same
   block, since a single window can match more than one indexed cluster. */
.rendered-markdown { margin: 0.5rem 0; line-height: 1.5; }
.rendered-markdown:not(:first-child) {
  padding-top: 0.6rem;
  margin-top: 0.6rem;
  border-top: 1px solid color-mix(in srgb, var(--ink) 8%, var(--paper));
}
.rendered-markdown h1, .rendered-markdown h2, .rendered-markdown h3,
.rendered-markdown h4, .rendered-markdown h5, .rendered-markdown h6 {
  font-size: 0.92rem;
  font-weight: 700;
  margin: 0.7rem 0 0.3rem;
}
.rendered-markdown h1:first-child, .rendered-markdown h2:first-child,
.rendered-markdown h3:first-child { margin-top: 0; }
.trip-alerts .rendered-markdown h1, .trip-alerts .rendered-markdown h2,
.trip-alerts .rendered-markdown h3 { color: var(--warning); }
.rendered-markdown p { margin: 0.4rem 0; }
.rendered-markdown ul, .rendered-markdown ol { margin: 0.3rem 0; padding-left: 1.3rem; }
.rendered-markdown li { margin: 0.2rem 0; }
.rendered-markdown a { color: var(--accent); }
.rendered-markdown code {
  font-family: var(--font-mono);
  background: color-mix(in srgb, var(--ink) 8%, var(--paper));
  padding: 0.05rem 0.3rem;
  border-radius: 4px;
  font-size: 0.85em;
}

/* Past trips (#48 redesign) -- same card component, tucked behind its
   own top-level disclosure so it never competes with upcoming trips for
   attention, but every card inside is fully intact, not a stripped-down
   summary-only view. */
.past-section {
  margin-bottom: 1.5rem;
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--paper));
}
.past-section > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--muted);
  list-style: none;
}
.past-section > summary::-webkit-details-marker { display: none; }
.past-section > summary::after { content: " \25BE"; font-size: 0.8em; }
.past-section[open] > summary { margin-bottom: 1rem; color: var(--ink); }
.past-section .trip-card { opacity: 0.85; }

.trip-list-undated, .unplaceable-section { margin-bottom: 1.5rem; color: var(--muted); font-size: 0.88rem; }
.trip-list-undated h3, .unplaceable-section h3 { color: var(--ink); font-size: 0.95rem; margin-bottom: 0.3rem; }
