:root {
  --bg: #0d1117;
  --panel: #161b22;
  --border: #30363d;
  --text: #e6edf3;
  --text-dim: #8b949e;
  --accent: #58a6ff;
  --win: #3fb950;
  --lose: #f85149;
  --warn: #d29922;
  --am-dark-green: #2ea043;
  --am-light-green: #7ee787;
  --am-light-blue: #79c0ff;
  --am-red: #f85149;
  --am-empty: #21262d;
  --am-cell: 14px;
  --am-outer-circle: 12px;
  --am-inner-circle: 6px;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
}

.status-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  padding: 10px 24px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  color: var(--text-dim);
}

.status-item strong {
  color: var(--text);
  font-weight: 600;
}

.status-item strong.ok {
  color: var(--win);
}

.status-item strong.bad {
  color: var(--lose);
}

.status-refreshing {
  font-style: italic;
}

.status-ingest {
  margin-left: auto;
}

.status-ingest button {
  padding: 3px 10px;
  background: var(--accent);
  color: #0d1117;
  border: none;
  border-radius: 6px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
}

.status-ingest button:hover:not(:disabled) {
  filter: brightness(1.1);
}

.status-ingest button:disabled {
  opacity: 0.5;
  cursor: default;
}

#ingest-message {
  margin-left: 8px;
}

.confirm-dialog {
  max-width: 320px;
  padding: 16px 20px;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.confirm-dialog::backdrop {
  background: rgba(0, 0, 0, 0.6);
}

.confirm-dialog p {
  margin: 0 0 16px;
  font-size: 14px;
  line-height: 1.5;
}

.confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.confirm-actions button {
  padding: 5px 14px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.confirm-actions .confirm-yes {
  background: var(--accent);
  border-color: var(--accent);
  color: #0d1117;
}

.confirm-actions button:hover {
  filter: brightness(1.15);
}

.layout {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
}

.sidebar {
  width: 240px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border-right: 1px solid var(--border);
  padding: 24px 20px;
}

.sidebar h1 {
  font-size: 18px;
  margin: 0 0 20px;
  color: var(--accent);
}

.analytic-search {
  width: 100%;
  padding: 8px 10px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 14px;
  margin-bottom: 12px;
  flex-shrink: 0;
}

/* flex:1 + min-height:0 makes this scroll within the sidebar's own height
   (set by the .layout row stretching sidebar/content to match) rather than
   growing the whole page -- the standard flexbox nested-scroll pattern. */
.analytic-list {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Overrides flex:1/overflow-y above -- this list (just Dashboard/Ask
   Queerio) sits above the search box and should size to its own content,
   not compete with the searchable list below for the sidebar's remaining
   height. Must come after the base .analytic-list rule above: same
   specificity, so source order is what makes this win. */
.pinned-analytic-list {
  flex: 0 0 auto;
  overflow-y: visible;
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.analytic-list li {
  margin: 0;
}

.analytic-list button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  margin: 0 0 2px;
  background: none;
  border: none;
  border-radius: 6px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  cursor: pointer;
}

/* nl_query's own in-flight indicator (see setNlQueryThinking() in app.js),
   next to the title in the main content area instead of the generic
   status-bar "Refreshing…" every other analytic still uses. */
.thinking-indicator {
  color: var(--text-dim);
  font-size: 14px;
  font-weight: normal;
  font-style: italic;
}

.analytic-list button:hover {
  background: var(--bg);
}

.analytic-list button.active {
  background: var(--accent);
  color: #0d1117;
  font-weight: 600;
}

/* A category folder's header button -- .analytic-list button above already
   gives it padding/border-radius/hover/cursor/font. Same weight/case as a
   leaf analytic's own button (this is a folder in a tree, not a section
   label), just semi-bold to hint it's a folder, with a disclosure
   triangle that rotates via aria-expanded -- which _renderAnalyticTree
   keeps in sync with whether its sublist is actually in the DOM. */
.analytic-category-header {
  display: flex;
  align-items: center;
  gap: 4px;
  font-weight: 600;
}

.analytic-category-header::before {
  content: "▸";
  display: inline-block;
  width: 10px;
  font-size: 10px;
  color: var(--text-dim);
  transition: transform 0.1s ease;
}

.analytic-category-header[aria-expanded="true"]::before {
  transform: rotate(90deg);
}

/* Indents a category's children one level -- nested categories (a "/" in
   analytic.category) compound this naturally, one .analytic-sublist per
   level. */
.analytic-sublist {
  padding-left: 14px;
}

.analytic-list .empty {
  color: var(--text-dim);
  font-size: 13px;
  padding: 8px 10px;
}

/* A flex column filling .layout's height (set by the row it's in, itself
   filling the viewport below the status bar -- see .layout/body above),
   so chart-wrap below can flex to fill whatever's left rather than using
   a fixed vh guess that's wrong whenever the party-manager panel above it
   is shown, collapsed, or the description text wraps to a different
   number of lines. */
.content {
  flex: 1 1 480px;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 24px 32px;
}

.analytic-title-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 4px;
  flex-shrink: 0;
}

.content h2 {
  margin: 0;
}

#analytic-description {
  color: var(--text-dim);
  margin: 0 0 24px;
  flex-shrink: 0;
}

.player-selector {
  flex-shrink: 0;
  align-items: center;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 20px;
  margin: 0 0 20px;
}

/* display is only set here, guarded by :not([hidden]) -- setting it
   directly on .player-selector would be an author-stylesheet rule that
   always beats the browser's default `[hidden] { display: none }` (author
   rules win over user-agent ones regardless of specificity), which is
   exactly what made el.hidden = true stop actually hiding this panel. */
.player-selector:not([hidden]) {
  display: flex;
}

.player-selector label {
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.player-selector select {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  font-family: inherit;
  font-size: 14px;
}

/* Extra flex-wrap on top of .player-selector's row layout: a checkbox
   group can have more entries than a narrow window fits on one line,
   unlike the single dropdown .player-selector was designed around. */
.checkbox-options {
  flex-wrap: wrap;
  row-gap: 8px;
}

.checkbox-option {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  cursor: pointer;
}

/* Reuses .player-selector for the panel look; the input takes up whatever
   width the button doesn't need, rather than the narrow intrinsic width
   a bare <input> defaults to. */
.nl-query-input {
  align-items: center;
}

.nl-query-input input[type="text"] {
  flex: 1;
  min-width: 0;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  font-family: inherit;
  font-size: 14px;
}

.nl-query-input button {
  flex-shrink: 0;
  padding: 6px 14px;
  background: var(--accent);
  color: #0d1117;
  border: none;
  border-radius: 6px;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
}

.nl-query-input button:hover {
  filter: brightness(1.1);
}

.live-stats {
  color: var(--text-dim);
  font-size: 13px;
  margin: -8px 0 20px;
}

.live-stats-bar-track {
  width: 160px;
  height: 6px;
  background: var(--am-empty);
  border-radius: 3px;
  overflow: hidden;
  margin-bottom: 4px;
}

.live-stats-bar-track:not([hidden]) {
  display: block;
}

.live-stats-bar-fill {
  height: 100%;
  border-radius: 3px;
  /* Width and color are both set inline per update (see app.js's
     renderLiveStats) -- width tracks the raw count/cap ratio exactly,
     which a fixed set of CSS breakpoints can't do, and the color needs
     to change together with that same number every tick anyway. */
  transition: width 0.3s ease, background-color 0.3s ease;
}

.checkbox-option input[type="checkbox"] {
  accent-color: var(--accent);
  cursor: pointer;
}

.party-manager {
  flex-shrink: 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px 20px;
  margin: 0 0 20px;
}

.party-manager-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  margin: 0 0 10px;
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}

.party-manager-toggle:hover {
  color: var(--text);
}

/* The toggle button rotates this glyph via a class rather than swapping
   text, so collapsing/expanding is a plain CSS transform, not a DOM
   change. */
#party-manager-toggle-icon {
  display: inline-block;
  transition: transform 0.15s ease;
}

.party-manager.collapsed .party-manager-toggle {
  margin-bottom: 0;
}

.party-manager.collapsed #party-manager-toggle-icon {
  transform: rotate(-90deg);
}

.party-list {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.party-list li {
  display: flex;
  align-items: center;
  gap: 10px;
}

.party-list label {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  cursor: pointer;
}

.party-list .empty {
  color: var(--text-dim);
  font-size: 13px;
}

.party-remove {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  width: 24px;
  height: 24px;
  line-height: 1;
  cursor: pointer;
}

.party-remove:hover {
  color: var(--text);
  border-color: var(--text-dim);
}

.party-add {
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.party-add-players {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-bottom: 10px;
}

.party-add-players label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  cursor: pointer;
}

.party-add-button {
  padding: 6px 14px;
  background: var(--accent);
  color: #0d1117;
  border: none;
  border-radius: 6px;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
}

.party-add-button:hover {
  filter: brightness(1.1);
}

/* flex:1 + min-height:0: fills whatever vertical space is left in .content
   (the same nested-scroll pattern as .analytic-list in the sidebar)
   instead of a fixed vh guess -- so it grows or shrinks correctly as the
   party-manager panel above it is shown, collapsed, or changes size. */
.chart-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 20px;
  max-width: 100%;
  overflow-y: auto;
  overflow-x: hidden;
}

/* Fills chart-wrap by default. When an analytic's chart config carries a
   heightPx (see app.js), JS sets an explicit height here instead -- taller
   than chart-wrap itself, which is how e.g. a hero-per-row chart with many
   more rows than chart-wrap's (now dynamic) height ends up scrolling
   within chart-wrap rather than being squashed to fit. */
.chart-canvas-box {
  position: relative;
  width: 100%;
  height: 100%;
}

/* --- Activity map -------------------------------------------------------
   A sibling of .chart-canvas-box inside the same #chart-wrap (see
   index.html) -- app.js toggles which of the two is hidden rather than
   using Chart.js for this one, since a per-day click popup and a
   variable-width year-by-year grid don't fit its chart-type model.
   Deliberately height: auto (not 100% like .chart-canvas-box): the grid's
   real height comes from its own content, and #chart-wrap's existing
   overflow-y: auto already scrolls it if that's taller than the viewport. */
.activity-map-container {
  height: auto;
}

/* Reuses .player-selector for the panel look and its :not([hidden])
   display guard (see that class's own comment) -- just adding wrapping,
   since a legend item per color can run to 4 lines' worth of width on a
   narrow window, unlike .player-selector's single dropdown. */
.activity-map-legend {
  flex-wrap: wrap;
  gap: 8px 20px;
}

.activity-map-year {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}

.activity-map-year-label {
  width: 40px;
  flex-shrink: 0;
  color: var(--text-dim);
  font-size: 12px;
  text-align: right;
}

.activity-map-grid {
  display: grid;
  grid-template-rows: repeat(7, var(--am-cell));
  grid-auto-flow: column;
  gap: 2px;
}

.activity-map-cell {
  position: relative;
  width: var(--am-cell);
  height: var(--am-cell);
  background: var(--am-empty);
  border-radius: 3px;
}

/* Two concentric circles per cell -- an outer ring for parsed status and
   a smaller inner circle for the real recoverability status, rather than
   blended into one color; see activity_map.py's module comment for why
   the two are tracked separately. Both centered on the cell via the same
   translate(-50%, -50%) trick regardless of their (different) sizes, so
   the inner circle always sits dead center inside the outer one, flush
   against it (no border/gap between them). */
.am-circle {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  cursor: pointer;
}

.am-circle-outer {
  width: var(--am-outer-circle);
  height: var(--am-outer-circle);
}

.am-circle-inner {
  width: var(--am-inner-circle);
  height: var(--am-inner-circle);
  z-index: 1;
}

/* Outer (parsed): dark green if Stratz parsed that day's matches, light
   green if not -- not a good/bad signal on its own (see the analytic's
   description), so both shades stay in the green family rather than
   using red for "not parsed". */
.am-circle-outer-parsed {
  background: var(--am-dark-green);
}

.am-circle-outer-not-parsed {
  background: var(--am-light-green);
}

/* Inner: the real recoverability signal, from best to worst -- dark
   green (networth and tower damage both present), light green (networth
   was missing, already backfilled -- resolved), light blue (networth
   missing, recoverable but not backfilled yet), red (tower damage
   missing -- no backfill source exists for it at all, gone for good). */
.am-circle-inner-dark-green {
  background: var(--am-dark-green);
}

.am-circle-inner-light-green {
  background: var(--am-light-green);
}

.am-circle-inner-light-blue {
  background: var(--am-light-blue);
}

.am-circle-inner-red {
  background: var(--am-red);
}

/* Swatches inside the legend/popup: standalone (not overlapping, no
   absolute positioning), distinguishing "this is a color key" from
   "this is a grid cell". */
.activity-map-legend .am-circle,
.activity-map-popup .am-circle {
  position: static;
  transform: none;
  display: inline-block;
  border: none;
  cursor: default;
  vertical-align: middle;
}

.activity-map-legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim);
}

.activity-map-popup {
  position: fixed;
  z-index: 20;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  max-height: 260px;
  overflow-y: auto;
}

.activity-map-popup-title {
  font-weight: 600;
  margin-bottom: 6px;
}

.activity-map-popup ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.activity-map-popup li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* --- Generic table analytic --------------------------------------------
   Another non-Chart.js analytic (see win_odds.py) -- a plain sibling of
   the canvas and the activity map, same toggle-visibility pattern in
   app.js. height: auto for the same reason as .activity-map-container:
   real height comes from content, #chart-wrap's own overflow-y: auto
   scrolls it if needed. */
.table-analytic-container {
  height: auto;
}

.table-analytic-meta {
  color: var(--text-dim);
  font-size: 13px;
  margin-bottom: 12px;
}

/* nl_query.py only -- see index.html's own comment on this row. */
.nl-query-share-row {
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

/* display set only here, guarded by :not([hidden]) -- an unconditional
   `display` above (like this rule used to have) is an author-stylesheet
   rule that always beats the UA's `[hidden] { display: none }` regardless
   of specificity, which is exactly what made el.hidden = true a no-op --
   same pitfall as .table-analytic-visualization/.table-analytic-answer
   below and .player-selector elsewhere in this file. */
.nl-query-share-row:not([hidden]) {
  display: flex;
}

#nl-query-share-button {
  padding: 6px 14px;
  background: var(--accent);
  color: #0d1117;
  border: none;
  border-radius: 6px;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
}

#nl-query-share-button:hover {
  filter: brightness(1.1);
}

#nl-query-share-button:disabled {
  opacity: 0.6;
  cursor: default;
  filter: none;
}

/* nl_query.py's written answer -- pre-wrap keeps Gemini's own paragraph
   breaks (a plain multi-line string, not markdown) rather than collapsing
   them the way normal text-flow whitespace would. */
.table-analytic-answer {
  white-space: pre-wrap;
  font-size: 15px;
  line-height: 1.6;
  margin-bottom: 16px;
}

/* Same :not([hidden]) pattern as .table-analytic-visualization below --
   see its own comment for why an unconditional `display` would break
   el.hidden = true. */
.table-analytic-answer:not([hidden]) {
  display: block;
}

.table-analytic-visualization {
  width: 100%;
  height: 360px;
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 16px;
  background: var(--panel);
}

/* display set only here, guarded by :not([hidden]) -- see .player-selector's
   own comment on this same pitfall: an unconditional `display` above would
   be an author rule that always beats the UA's `[hidden] { display: none }`
   regardless of specificity, breaking el.hidden = true. */
.table-analytic-visualization:not([hidden]) {
  display: block;
}

.table-analytic-scroll {
  overflow-x: auto;
}

.table-analytic {
  border-collapse: collapse;
  width: 100%;
  font-size: 13px;
}

.table-analytic th,
.table-analytic td {
  padding: 6px 14px 6px 0;
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
}

.table-analytic th {
  color: var(--text-dim);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.03em;
}

/* Every column but the first (the variable name) is numeric -- lining
   figures up with tabular-nums makes a column of numbers actually
   readable as a column instead of a ragged left-aligned list. */
.table-analytic td:not(:first-child) {
  font-variant-numeric: tabular-nums;
}

.table-analytic-notes {
  margin: 16px 0 0;
  padding: 0 0 0 18px;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.6;
}

@media (max-width: 640px) {
  .status-bar {
    padding: 10px 16px;
    gap: 4px 16px;
  }

  .layout {
    flex-direction: column;
  }

  .sidebar {
    width: 100%;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }

  /* Stacked layout gives the sidebar no definite height for flex:1 to fill,
     so cap it explicitly here instead -- otherwise a long analytic list
     would just push the chart far down the page. */
  .analytic-list {
    max-height: 240px;
  }

  .content {
    padding: 20px 16px;
  }

  /* Stacked layout gives .content no definite height either, so flex:1
     has nothing to fill here -- same reasoning as .analytic-list above. */
  .chart-wrap {
    flex: none;
    height: 50vh;
  }
}
