/*
 * The differential expression page.
 *
 * Its own file rather than another few hundred lines of `style.css`, which is past two hundred
 * kilobytes and holds every view in the application. Everything here is scoped under `.deg-view`
 * or a `deg-` prefix; the page still takes `.stat-grid`, `.data-table`, `.filter-bar`,
 * `.toggle-btn`, `.badge`, `.pill` and `.resize-handle` from the shared sheet, because those are
 * what make it look like the rest of the app.
 *
 * The layout is one column: the chart takes whatever height the table is not using, and the
 * table's height is a variable `resize.js` writes as its top edge is dragged. The old page had
 * that the other way round — a chart fixed at 340px over a table that took the remainder — which
 * on a 1000px screen left three visible rows of a four-hundred-gene signature and no way to
 * change it.
 */

/* ── the height budget ──
 *
 * This column does not scroll; the gene table inside it does. So every pixel the head spends is
 * a pixel the table cannot have, and the head was spending 306 of 747: the chart had already
 * been squeezed to its minimum and the table still hung 57px below the bottom of the panel, with
 * its last rows and its horizontal scrollbar off-screen and no drag able to recover them.
 *
 * The cards, the caption and the gaps below are therefore tighter here than the shared sheet
 * draws them elsewhere. Scoped to this page rather than changed at the source: the other views
 * that use `.stat-grid` are short pages with room to spare, and they should keep the roomier
 * proportions.
 */

.deg-stats {
  margin-bottom: 12px;
}

.deg-stats .stat-card { padding: 10px 12px; }
.deg-stats .stat-card:hover { transform: none; }   /* a 2px lift on a card this flat only jitters */
.deg-stats .stat-value { font-size: 22px; }

/* ── chart ── */

.deg-chart {
  position: relative;
  flex: 1 1 auto;
  /* The floor the table's drag stops at, read back by `resize.js` to bound the handle. Below
     this the two axes have fewer than four rows of cells and the chart stops being readable, so
     a reader wanting only rows is better served by a chart that holds its ground than by one
     squeezed to a stripe. */
  min-height: 132px;
  margin-bottom: 8px;
}

/* Pinned rather than sized at `height: 100%`: the percentage has no definite ancestor height to
   resolve against inside a `.view-fill` column, and a canvas sized from its own attributes would
   ignore the flex box it sits in. `deg-view.js` observes this box and redraws at its real size. */
.deg-chart canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: var(--radius);
  background: var(--ink-950);
  display: block;
}

/* The card under the pointer. It replaces a native `title`, which arrives after a delay the
   reader did not ask for, cannot hold a mechanism sentence and cannot say what a click will do. */
.deg-hover {
  position: absolute;
  z-index: 20;
  max-width: 320px;
  padding: 9px 11px;
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  background: var(--ink-900);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  pointer-events: none;   /* never between the pointer and the dot it describes */
  font-size: 11px;
  line-height: 1.45;
}
.deg-hover[hidden] { display: none; }

.deg-hover-title {
  font-weight: 600;
  font-size: 12px;
  color: var(--text-0);
  margin-bottom: 2px;
}

.deg-hover-line { color: var(--ink-400); }

.deg-hover-mech {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--line);
  color: var(--ink-300);
}

.deg-hover-hint {
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-500);
}

/* ── caption and legend ── */

/* One row, not two blocks. The shared `.chart-caption` is capped at 78ch, which turned this into
   three lines with the legend stacked under it — 67px of the column to say what fits on one. */
.deg-caption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 18px;
  max-width: none;
  margin-bottom: 10px;
}

.deg-legend {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px 16px;
}

.deg-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.deg-swatch {
  flex: none;
  width: 11px;
  height: 11px;
  border-radius: 50%;
}
.deg-swatch-dot { background: rgba(169, 180, 196, 0.85); }
.deg-swatch-block {
  border-radius: 3px;
  border: 1px solid rgba(169, 180, 196, 0.5);
  background: rgba(169, 180, 196, 0.22);
}
.deg-swatch-ring {
  border: 1.5px solid #f59e0b;
  background: transparent;
}

/* ── signature notes ── */

.deg-notes {
  flex: none;
  margin-bottom: 10px;
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  background: var(--glass-bg);
}

.deg-notes > summary {
  padding: 9px 12px;
  cursor: pointer;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-300);
  list-style-position: inside;
  user-select: none;
}
.deg-notes > summary:hover { color: var(--text-0); }
.deg-notes[open] > summary { border-bottom: 1px solid var(--line); }

.deg-notes > p {
  margin: 0;
  padding: 10px 12px 12px;
  max-height: 200px;
  overflow-y: auto;
  font-size: 11px;
  line-height: 1.6;
  color: var(--ink-400);
}

/* ── filter bar ── */

.deg-filters {
  flex: none;
  margin-bottom: 10px;
}

.deg-count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-500);
  white-space: nowrap;
}

/* Which chart cell the table is pinned to, and how to let go of it. A filter set by clicking a
   picture is the one filter with no control showing its state, so it gets its own. */
.deg-bucket {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px 3px 9px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--accent-dim);
  font-size: 11px;
  color: var(--text-0);
}
.deg-bucket[hidden] { display: none; }

.deg-bucket-x {
  border: none;
  background: transparent;
  color: var(--ink-300);
  font-size: 15px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}
.deg-bucket-x:hover { color: var(--text-0); }

.deg-clear[hidden] { display: none; }

/* ── the table and its drag handle ── */

/*
 * `flex: 0 1 auto` rather than `none`: the height is what the reader dragged, but it is a request,
 * not a guarantee. A window short enough that the head plus this height exceeds the column has to
 * take the difference out of *something*, and with `none` it came out of the bottom of the screen —
 * the panel simply hung past the container, taking its last rows with it. Shrinking is the honest
 * failure: fewer rows, all of them reachable. `resize.js` keeps the handle inside this bound too,
 * so the drag stops where the layout stops instead of accepting a number nothing can honour.
 */
.deg-table-panel {
  position: relative;        /* the handle is absolutely placed on this edge */
  flex: 0 1 auto;
  display: flex;
  flex-direction: column;
  /*
   * No floor here, deliberately, even though the drag has one of 120px in `resize.js`. The two
   * are different questions: 120px is the smallest table worth dragging *to*, while a window too
   * short to hold the head, the chart's floor and 120px of table has to put the difference
   * somewhere, and a floor here would put it past the bottom of the screen. The drag stays
   * bounded; the layout yields.
   */
  min-height: 0;
  height: var(--deg-table-height, 320px);
  padding-top: 6px;          /* room for the 5px handle, so it is not over the sort headers */
}

/* Only the wrapper scrolls. The handle must stay out of a scrolling box or it scrolls away with
   the rows, and `replay-stage.js` looks for exactly one scroller on this page. */
.deg-table-panel > .gene-table-wrapper {
  flex: 1;
  min-height: 0;
}

.deg-table th[data-sort] { white-space: nowrap; }

/* The caret is drawn for the sorted column only. Every header was already `cursor: pointer` over
   a table that never sorted; this is the other half of making that true. */
.deg-table th.sorted { color: var(--text-0); }
.deg-table th.sorted::after {
  content: ' ▾';
  font-size: 9px;
}
.deg-table th.sorted-asc::after { content: ' ▴'; }

.deg-row { cursor: pointer; }
.deg-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.deg-row.expanded > td { background: var(--accent-faint); }

.deg-symbol { font-weight: 600; }
.deg-num { font-family: var(--font-mono); font-size: 11px; }
.deg-star { color: var(--caution); }
.deg-domains { font-size: 11px; color: var(--ink-400); }

.deg-empty-row td {
  padding: 22px 12px;
  text-align: center;
  color: var(--ink-500);
}

/* The two provenances, told apart at a glance. The distinction decides whether a row is the
   run's own prediction or an entry composed from a regulon, and the bench scores them apart. */
.deg-prov {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 10px;
  border: 1px solid transparent;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.03em;
}
.deg-prov-simulated {
  border-color: var(--accent);
  color: var(--bright);
}
.deg-prov-inferred {
  border-color: var(--line-strong);
  color: var(--ink-500);
}

/* ── the expanded row ── */

.deg-detail-row > td {
  padding: 0;
  background: var(--ink-950);
  border-bottom: 1px solid var(--edge);
}
.deg-detail-row:hover > td { background: var(--ink-950); }   /* not a row; do not light up */

.deg-detail {
  padding: 12px 14px 14px;
  border-left: 2px solid var(--accent);
}

.deg-detail-mech {
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-1);
  max-width: 96ch;
}

.deg-detail-absent { color: var(--ink-500); font-style: italic; }

.deg-detail-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 10px 20px;
  margin: 12px 0 0;
}

.deg-detail-meta > div { min-width: 0; }

.deg-detail-meta dt {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-500);
  margin-bottom: 3px;
}

.deg-detail-meta dd {
  margin: 0;
  font-size: 11px;
  line-height: 1.55;
  color: var(--ink-400);
}

/* ── stat cards ── */

.stat-split {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
}
.stat-split-sep { color: var(--ink-600); font-weight: 400; }

/* ── short windows ──
 *
 * On a laptop window the head plus the chart's floor plus a usable table came to more than the
 * column had, and the surplus went out of the bottom of the panel. Nothing here is hidden — a
 * page whose job is to be checked against a bench result cannot quietly drop the caption saying
 * the axes are not a volcano plot, or the provenance split — it is the same page drawn tighter.
 */
@media (max-height: 900px) {
  .deg-view > .section-eyebrow {
    margin-bottom: 6px;
    padding-bottom: 4px;
  }

  /* The same five figures as one line instead of a grid of cards: at this height the cards were
     wrapping to two rows and spending 78px to say what reads fine in 24. */
  .deg-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 18px;
    margin-bottom: 8px;
  }
  .deg-stats .stat-card {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 0;
    border: none;
    background: none;
    text-align: left;
  }
  .deg-stats .stat-card:hover { border: none; }
  .deg-stats .stat-value { font-size: 15px; }
  .deg-stats .stat-label { margin-top: 0; }

  /* Enough for three rows of cells once `charts.js` tightens its own margins on a short plot. */
  .deg-chart { min-height: 112px; }

  .deg-caption,
  .deg-notes,
  .deg-filters { margin-bottom: 6px; }
}
