/* Signals page — SIGNALS.md §9, brief §11-21.
 *
 * Layout rules:
 * - The filter bar is laid out in two rows. Row 1: search + four core
 *   filters (Ticker, Strategy, Signal, Direction). Row 2: the remaining
 *   filters (Timeframe, Source, Status), the date pair (From/To) and
 *   Clear. The two-row layout fits cleanly on 1440 px, and the row-2
 *   controls collapse below the rest on small viewports (390 px).
 * - The detail panel is rendered as an inline <td colspan="N"> directly
 *   under its row in the table flow (via DataTable's detailRow option).
 *   No absolute positioning, no scroll past the table.
 *
 * Only rules styles.css does not already paint (panel, table, input, btn).
 * Colour, type, spacing, radius and motion come only from tokens.css custom
 * properties. No literals here.
 *
 * Loaded by views/signals.js, which appends one <link> to <head> on mount.
 * The list controls (page shell, filter bar, sort bar, stats strip) live in
 * signals-controls.css, @imported first below — imported rules apply before
 * this file's own rules, so the cascade order is exactly the order these
 * rules had when they were inline here.
 */

@import "signals-controls.css?v=7bf442d07503";

/* ---- table cells -------------------------------------------------------- */

.signals-ticker {
  color: var(--ink);
  font-weight: var(--fw-semibold);
  font-family: var(--font-mono);
}

.signals-ticker:hover {
  color: var(--accent);
  text-decoration: none;
}

.signals-cell {
  color: var(--ink-2);
}

.signals-dir {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
}

.signals-dir-long {
  color: var(--pos);
  background: var(--up-bg);
}

.signals-dir-short {
  color: var(--neg);
  background: var(--down-bg);
}

/* ---- row activation -------------------------------------------------- */

.signals-table tbody tr[data-table-row] {
  cursor: pointer;
}

.signals-table tbody tr[data-table-row]:hover {
  background: var(--panel-2);
}

.signals-table tbody tr[data-table-row][aria-expanded="true"] {
  background: var(--surface-2);
}

/* The detail <tr> the table emits when detailRow returns a node. The
   cell spans the full column count and the panel inside lays out
   freely. No absolute positioning: the detail sits exactly where the
   table row it belongs to was, so the user sees it on scroll. */
.signals-table .data-table-detail-row > .data-table-detail-cell {
  padding: var(--space-3) var(--space-4);
  background: var(--panel);
  border-top: 1px solid var(--panel-border);
  border-bottom: 1px solid var(--panel-border);
  /* Brief: left-align the detail content. The cell inherits the table's
     right-align from the sticky th, so we override it here. */
  text-align: start;
}

/* ---- pagination alignment ---------------------------------------------- */

.signals-pagination {
  display: flex;
  align-items: center;
}

/* ---- detail panel ------------------------------------------------------- */

/* The table scrolls horizontally inside .table-wrap on a phone, and the
   detail cell is as wide as the whole table. Make the wrap a size container
   and pin the panel to its visible width (sticky at the left edge), so the
   header, timeline and metadata always fit the screen; only the timeline
   itself scrolls sideways, inside its own container. */
.signals-table .table-wrap {
  container-type: inline-size;
}

.signals-detail-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  text-align: start;
  position: sticky;
  left: var(--space-4);
  box-sizing: border-box;
  width: calc(100cqi - 2 * var(--space-4));
  min-width: 0;
  /* Table cells are nowrap; the panel's prose (header, legend, metadata)
     must wrap normally. */
  white-space: normal;
}

.signals-detail-loading {
  padding: var(--space-2) 0;
  text-align: start;
}

.signals-detail-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  text-align: start;
}

.signals-detail-id {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  text-align: start;
}

.signals-detail-ticker {
  font-family: var(--font-mono);
  font-size: var(--fs-figure);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

/* T120: the header ticker is now a button (the chart trigger). Reset ONLY the
   UA button chrome — background, border, padding, margin — and leave the
   typography to .signals-detail-ticker above (IBM Plex Mono figure). Anything
   that says `font-*: inherit` here outranks that rule and the figure reverts
   to the body face. line-height and text-align stay `inherit` for the opposite
   reason: the UA button defaults them to `normal` and `center`, which is not
   how the old span rendered. */
button.signals-detail-ticker {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  margin: 0;
  padding: 0;
  line-height: inherit;
  text-align: inherit;
  cursor: pointer;
}

button.signals-detail-ticker:hover {
  color: var(--accent);
}

button.signals-detail-ticker:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* T120: the figure-sized ticker is a tap target on a phone and its visual box
   is only ~36 × 20. Grow the hit area to 44 × 44 with a transparent ::after
   (the ⓘ pattern in views/holdings.css) so the figure keeps its size and the
   header keeps its height. */
@media (pointer: coarse), (max-width: 767.98px) {
  button.signals-detail-ticker {
    position: relative;
    z-index: 1;
  }

  button.signals-detail-ticker::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: max(100%, var(--hit-min));
    height: max(100%, var(--hit-min));
    pointer-events: auto;
  }
}

.signals-detail-triggered {
  /* Brief §17 wants the year in the trigger line ("Sep 23, 2026 · 2:10 PM ET");
     honouring 12/24-hour format and the zone is lib/timezone.js's job. */
  font-size: var(--fs-small);
  text-align: start;
}

.signals-detail-now {
  font-size: var(--fs-small);
  text-align: start;
}

.signals-detail-section {
  margin: 0;
  font-size: var(--fs-section);
  color: var(--ink);
  font-weight: var(--fw-semibold);
  text-align: start;
}

.signals-detail-metadata {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-align: start;
}

/* Brief §17 metadata: a responsive grid of key/value pairs. Each pair is a
   small two-column grid (key muted, value monospace) so a value always sits
   beside its key; several pairs share a row on wide screens, one per row on
   a phone. Values wrap only when a single token is genuinely too long. */
/* The metadata list and the snapshot / alert lists share one pair grid; the
   class names stay distinct so "the metadata list" always means the metadata
   list. */
.signals-detail-meta-list,
.signals-snapshot-list,
.signals-snapshot-alert-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr));
  gap: var(--space-2) var(--space-6);
  margin: 0;
  padding: 0;
  font-size: var(--fs-small);
  text-align: start;
}

.signals-detail-meta-pair,
.signals-snapshot-pair {
  display: grid;
  grid-template-columns: fit-content(50%) minmax(0, 1fr);
  align-items: baseline;
  column-gap: var(--space-3);
  min-width: 0;
}

.signals-detail-meta-key,
.signals-snapshot-key {
  color: var(--muted);
  font-size: var(--fs-label);
  text-align: start;
  overflow-wrap: anywhere;
}

.signals-detail-meta-val,
.signals-snapshot-val {
  margin: 0;
  min-width: 0;
  color: var(--ink);
  font-family: var(--timeline-mono);
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: start;
}

/* A nested object renders as indented JSON: keep its line breaks and give
   it the full width of the pair's row. */
.signals-detail-meta-val-json,
.signals-snapshot-val-json {
  grid-column: 1 / -1;
  white-space: pre-wrap;
}

/* The timeline host must be allowed to shrink below the timeline's own
   width so the timeline scrolls inside its container on a phone. */
.signals-detail-timeline {
  min-width: 0;
  max-width: 100%;
}

/* ---- snapshot at trigger (SIGNALS.md §5.1) ------------------------------- */

/* The horizons line above the timeline: its label and the checkpoints_source
   badge share one baseline and wrap together on a narrow panel. */
.signals-detail-cp-source {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  text-align: start;
}

/* The 'Snapshot at trigger' block: the field list and the 'Alert values'
   sub-block stacked, with the same pair grid the metadata list uses. */
.signals-detail-snapshot {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-align: start;
}

/* A value the server recorded as `unknown` (§5.1): the em dash is muted and
   its reason rides in the title (hover / tap) — never a fabricated 0. */
.signals-snapshot-unknown {
  color: var(--muted-pending);
}

/* 'Alert values' is a sub-block: a hairline rule and a label-style heading
   separate it from the snapshot fields above. */
.signals-snapshot-alert {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--panel-border);
  text-align: start;
}

.signals-snapshot-alert-title {
  margin: 0;
  font-size: var(--fs-label);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted);
  text-align: start;
}

/* ---- guide: info icon ------------------------------------------------- */

/* 44 px hit area around a 20 px info circle (brief: the button sits right
   after the page title and is an inline SVG on currentColor). */
.signals-guide-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: none;
  background: none;
  color: var(--muted);
  cursor: pointer;
  vertical-align: middle;
}

.signals-guide-btn:hover,
.signals-guide-btn:focus-visible {
  color: var(--ink);
}

.signals-guide-btn:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
  border-radius: var(--radius-sm);
}

.signals-guide-icon {
  width: 20px;
  height: 20px;
  display: block;
}

/* ---- guide: modal content ---------------------------------------------- */

.guide {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  text-align: start;
  font-size: var(--fs-small);
  line-height: var(--lh-relaxed, 1.55);
}

.guide-tabs .tabs {
  margin-bottom: 0;
}

.guide-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.guide-prereq {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  color: var(--ink-2);
}

.guide-steps {
  margin: 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.guide-step {
  padding-left: var(--space-1);
}

.guide-step-title {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-small);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.guide p {
  margin: 0 0 var(--space-2);
  color: var(--ink-2);
}

.guide p:last-child {
  margin-bottom: 0;
}

.guide a {
  color: var(--accent);
}

.guide-tip {
  color: var(--muted);
}

.guide-note {
  color: var(--muted);
  font-size: var(--fs-label);
}

/* The IP-check note reads the server's own tv_ip_check flag; when the server
   sends no flag the note is not rendered at all. */
.guide-ip-note:empty {
  display: none;
}

.guide-inline-code {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  background: var(--surface-2);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-1);
  overflow-wrap: anywhere;
}

p.guide-inline-code {
  display: block;
  padding: var(--space-2);
  overflow-x: auto;
}

.guide-list {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  color: var(--ink-2);
}

.guide-details {
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
}

.guide-details-summary {
  cursor: pointer;
  font-weight: var(--fw-medium);
  color: var(--ink);
}

/* ---- guide: secret field + code block ---------------------------------- */

/* The create-once status line: "Link ending …wxyz · created … · last used …" */
.guide-secret-status {
  margin: var(--space-2) 0 0;
  color: var(--ink-2);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
}

.guide-secret-status:empty {
  display: none;
}

.guide-secret-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0;
}

.guide-secret-note {
  color: var(--warn-text);
  font-size: var(--fs-label);
}

.guide-secret-note:empty {
  display: none;
}

.guide-secret-value {
  flex: 1 1 220px;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-2);
  height: 30px;
}

.guide-secret-value:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

.guide-copy,
.guide-create {
  flex: 0 0 auto;
  min-height: 30px;
}

.guide-secret-value::placeholder {
  color: var(--muted);
}

.guide-code {
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--surface-2);
  overflow: hidden;
}

.guide-code-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-bottom: 1px solid var(--panel-border);
}

.guide-code-label {
  color: var(--muted);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
}

.guide-code-pre {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  overflow-x: auto;
  /* macOS overlay scrollbars hide until the pointer is over the box, so a
     cut-off line looks like the end of the message. Styling ::-webkit-scrollbar
     promotes it to a classic, always-drawn thumb, and the gutter reserves its
     width so the text does not jump. Copy still copies the stored full string. */
  scrollbar-gutter: stable;
  scrollbar-color: var(--muted-2) transparent;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  line-height: var(--lh-snug, 1.45);
  color: var(--ink);
  white-space: pre;
}

.guide-code-pre::-webkit-scrollbar {
  height: 8px;
}

.guide-code-pre::-webkit-scrollbar-thumb {
  background: var(--muted-2);
  border-radius: 4px;
}

.guide-code-pre::-webkit-scrollbar-track {
  background: transparent;
}

/* Belt and braces for engines that use overlay scrollbars regardless: a
   right-edge fade over the clip so "there is more this way" reads at a
   glance. The gradient is token + transparent only (no colour literals). */
.guide-code-body {
  position: relative;
}

.guide-code-body::after {
  content: "";
  position: absolute;
  inset: 0 0 0 auto;
  width: var(--space-5);
  pointer-events: none;
  background: linear-gradient(to left, var(--surface-2), transparent);
}

/* ---- guide: fields reference (a definition list, styled as a table) ----- */

.guide-fields {
  display: grid;
  grid-template-columns: minmax(90px, max-content) minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  margin: 0;
  font-size: var(--fs-label);
}

.guide-field-name {
  font-family: var(--font-mono);
  color: var(--ink);
  overflow-wrap: anywhere;
}

.guide-field-meaning {
  margin: 0;
  color: var(--ink-2);
}

/* On a phone the guide body is already the bottom sheet (modal.css); just
   tighten the fields grid to one column so the meanings stay readable. */
@media (max-width: 479.98px) {
  .guide-fields {
    grid-template-columns: minmax(0, 1fr);
  }
  .guide-field-name {
    margin-top: var(--space-1);
  }
}

/* ---- phones (MOBILE_PLAN.md §3.5) --------------------------------------- */

/* Phone-only chrome. Hidden at every width ≥768 so the desktop layout (and
   the structural / pixel baseline) never sees these nodes at all. */
.signals-actionbar,
.signals-chips,
.signals-filters-btn {
  display: none;
}

/* The FilterSheet re-parents the one filter form into `.sheet-body` inside a
   throwaway `.signals-filter-form` wrapper (phones only — the wrapper node
   never exists at ≥768, so these rules cannot touch the desktop form). The
   fields stack full width per §3.5; the F1 phone block already gives every
   input / select a 44 px min-height and 16 px text. */
.signals-filter-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.signals-filter-form .signals-filter-row {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
}

.signals-filter-form .signals-filter-field,
.signals-filter-form .signals-filter-search-field {
  width: 100%;
  min-width: 0;
  /* The sheet stacks the rows, so the search field's `1 1 220px` would be a
     220 px height. Reset it here too — the sheet is live at every phone
     width, including 480–767 where the 479.98 block does not apply. */
  flex: 0 0 auto;
}

/* Compact list row (the §3.5 sketch). It lives inside DataTable's
   `button.dt-item-main`, so everything here is non-interactive text. */
.signals-m-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  width: 100%;
  text-align: start;
}

.signals-m-line {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}

.signals-m-head {
  justify-content: space-between;
  width: 100%;
}

.signals-m-ticker {
  font-family: var(--font-mono);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-section);
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* T120: the row ticker is the chart trigger on a phone and its visual box is
   only ~38 × 23. Same transparent ::after as the ⓘ — the text keeps its size
   and the row keeps its height. */
@media (pointer: coarse), (max-width: 767.98px) {
  .signals-m-ticker {
    position: relative;
    z-index: 1;
  }

  .signals-m-ticker::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: max(100%, var(--hit-min));
    height: max(100%, var(--hit-min));
    pointer-events: auto;
  }
}

.signals-m-names {
  min-width: 0;
}

/* Signal · strategy ellipsise here; the full strings live in the detail. */
.signals-m-ellipsis {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-2);
}

.signals-m-sub {
  flex-wrap: wrap;
}

.signals-m-sep {
  color: var(--muted);
}

/* Every registry checkpoint, wrapping; Pending / Failed read as text. */
.signals-m-cps {
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
}

.signals-m-cp {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  white-space: nowrap;
}

.signals-m-cp-label {
  color: var(--muted);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
}

@media (max-width: 767.98px) {
  /* Sticky action row under the top bar: [Filters (n)] · Sort · metric ·
     display. sortBar is re-parented into this wrapper below 768 (a sticky
     child cannot leave its parent's box, so the wrapper has to sit high in
     the page). --signals-actionbar-h is a first guess at the row's own
     allowance; it wraps to two lines at the narrow widths, so the deep-link
     scroll measures the real sticky stack and writes --signals-sticky-h
     over the fallback below. */
  .signals {
    --signals-actionbar-h: calc(var(--hit-min) + var(--space-4));
  }

  .signals-actionbar {
    display: block;
    position: sticky;
    top: calc(var(--safe-top) + var(--topbar-h-phone));
    z-index: var(--z-nav);
    margin: 0 0 var(--space-1);
    padding: var(--space-2) 0;
    background: var(--bg);
    border-bottom: 1px solid var(--border-subtle);
  }

  .signals-actionbar .signals-sortbar {
    display: flex;
    flex-wrap: wrap;
    width: 100%;
    align-items: center;
  }

  .signals-actionbar .signals-sortbar-spacer {
    display: none;
  }

  .signals-filters-btn {
    display: inline-flex;
    align-items: center;
    min-height: var(--hit-min);
  }

  .signals-chips {
    display: block;
    min-width: 0;
  }

  /* On a phone the form is in the FilterSheet — filtersEl holds just the
     chips row, so drop the panel chrome around them. The parked rows are
     hidden until the sheet lifts them out (direct children only: once
     re-parented into `.signals-filter-form` they show again). */
  .signals-filters {
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
  }

  .signals-filters > .signals-filter-row {
    display: none;
  }

  /* Stats strip wraps to two lines at the label size (§3.5). */
  .signals-stats {
    font-size: var(--fs-label);
    gap: var(--space-1) var(--space-3);
    padding: var(--space-2);
  }

  /* Metadata: stacked key over value on phones (§3.5). */
  .signals-detail-meta-pair,
  .signals-snapshot-pair {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-1);
  }

  /* `#/signals/{id}` scrolls the row below the sticky stack. The fallback
     sum is the token estimate; `maybeScrollToExpanded` writes the measured
     height into --signals-sticky-h and then corrects the final offset. */
  .signals-table li.dt-item,
  .signals-detail-slot {
    scroll-margin-top: var(
      --signals-sticky-h,
      calc(var(--safe-top) + var(--topbar-h-phone) + var(--signals-actionbar-h))
    );
  }

  /* The display toggle's "%" / "1/0" tabs are narrower than --hit-min;
     pin them so the 44 px probe passes on this page (the component's own
     box is P6's). */
  .signals-metric-seg .tab,
  .signals-display-seg .tab {
    min-width: var(--hit-min);
  }

  /* Long unbroken tokens wrap instead of widening the page (§2.11). */
  .signals-m-row,
  .signals-detail-meta-key,
  .signals-detail-meta-val,
  .signals-snapshot-key,
  .signals-snapshot-val {
    overflow-wrap: anywhere;
  }

  .signals-m-ellipsis {
    overflow-wrap: normal;
  }
}
