/* ---------------------------------------------------------------------------
   Queue-it Flow — dashboard styles.

   This stylesheet is loaded into an existing queue-it.com page, alongside
   Bootstrap 4.6 and a 147 KB site stylesheet that are global on every page.
   Two rules follow from that, and both are load-bearing:

   1. EVERY class is prefixed `qf-`. Scoping under a root class would fix
      specificity for properties we set, but not properties we don't: the site's
      `.row` carries `margin-left:-15px`, which would still apply to an
      unprefixed `.row` of ours nested under `.qf`. Six confirmed collisions
      (.row .table .col .tabs .cta .note) forced this; prefixing all of them
      also pre-empts collisions with site CSS written later.

   2. No bare element selectors. `*`, `html`, `body` and `a` rules are scoped
      inside `.qf` so nothing escapes into the host page.

   Design tokens are lifted unchanged from the reference canvas.
   --------------------------------------------------------------------------- */

.qf {
  --qf-ink: #141414;
  --qf-ink-2: #46474a;
  --qf-ink-3: #6b6c6e;
  --qf-ink-4: #8a8b8d;

  --qf-line: #e3e3e4;
  --qf-line-soft: #f0f0f1;
  --qf-line-strong: #d5d5d6;

  --qf-surface: #ffffff;
  --qf-surface-2: #f3f3f4;

  --qf-accent: #0d5f41;
  --qf-accent-dark: #09402c;
  --qf-accent-tint: #e8f9f2;
  --qf-accent-soft: #ceefe1;
  --qf-accent-line: #a0bfa9;

  /* Sale-event / agentic accent — the peach family */
  --qf-warn: #ecbd91;
  --qf-warn-tint: #faebdd;
  --qf-warn-soft: #f7ddc6;
  --qf-warn-ink: #8a5a1c;
  --qf-warn-ink-2: #b07a2e;
  --qf-warn-card: #fdf7f0;

  --qf-shell-max: 1440px;

  /* Was `html, body` in the standalone app. Now the root of our subtree, so the
     host page's own body typography is left alone. */
  font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: var(--qf-ink);
  background: var(--qf-surface);
  -webkit-font-smoothing: antialiased;
  text-align: left;
}

/* Defensive reset for the elements we actually emit. The host stylesheet styles
   buttons, paragraphs and pre blocks; without this our controls inherit its padding,
   borders and margins.
 *
 * Every selector here is wrapped in :where(), which contributes ZERO specificity — so
 * these rules weigh exactly (0,1,0), the same as a single class, and any component class
 * below overrides them.
 *
 * Without :where() this block silently outranks the components, and scoping is what
 * inverts it: a bare `a { color: accent }` is (0,0,1) and loses to any class, but `.qf a`
 * is (0,1,1) and beats every single-class rule below. That difference is invisible until
 * a component tries to restyle an element it also matches — an accent-coloured link
 * inside an accent-filled button, for instance, which renders as nothing at all. */
.qf :where(*, *::before, *::after) { box-sizing: border-box; }

.qf :where(button) {
  margin: 0;
  font-family: inherit;
  line-height: normal;
  border-radius: 0;
  box-shadow: none;
  text-transform: none;
  -webkit-appearance: none;
  appearance: none;
}
.qf :where(p, pre) { margin: 0; }
.qf :where(svg) { display: block; }
.qf :where(a) { color: var(--qf-accent); text-decoration: none; }
.qf :where(a:hover) { color: var(--qf-accent-dark); }

/* Display face for headings and figures. Outfit is not loaded by queue-it.com,
   so embed.html adds it; the fallback keeps the layout intact if it fails. */
.qf-hd {
  font-family: "Outfit", "Open Sans", -apple-system, sans-serif;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.qf-n { font-variant-numeric: tabular-nums; }

.qf-lbl {
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--qf-ink-3);
  font-weight: 700;
  line-height: 14px;
}

.qf-panel {
  border: 1px solid var(--qf-line);
  background: var(--qf-surface);
}

.qf-mono { font-family: Consolas, "Liberation Mono", ui-monospace, monospace; }

.qf-clamp2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.qf-trunc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ------------------------------------------------------------------ shell */

/* The standalone app used `min-height: 100vh` here. Embedded in a page that
   already has a header, nav and footer, that would push the footer a screen
   down; the dashboard now sizes to its content. */
.qf-shell {
  display: flex;
  flex-direction: column;
}

.qf-topbar {
  min-height: 60px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--qf-line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 10px 28px;
  flex-wrap: wrap;
}

.qf-brand { display: flex; align-items: baseline; gap: 9px; }
.qf-brand span { font-size: 21px; }

.qf-tabs { display: flex; gap: 4px; }

.qf-tab {
  font-size: 13px;
  font-weight: 600;
  padding: 7px 13px;
  cursor: pointer;
  color: var(--qf-ink-2);
  background: none;
  border: 0;
}
.qf-tab:hover { background: var(--qf-surface-2); }
.qf-tab.qf-is-active { background: var(--qf-ink); color: #fff; }

.qf-contextbar {
  min-height: 52px;
  flex-shrink: 0;
  background: var(--qf-surface-2);
  border-bottom: 1px solid var(--qf-line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 28px;
  flex-wrap: wrap;
}
.qf-scope { font-size: 12.5px; color: var(--qf-ink-2); }
.qf-privacy { font-size: 11.5px; color: var(--qf-ink-3); }

.qf-view {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 20px 28px 28px;
  max-width: var(--qf-shell-max);
  width: 100%;
  margin: 0 auto;
}

/* ----------------------------------------------------------------- controls */

/* The "Peak events only" toggle lived here. It narrowed every figure to the hours a
   waiting room was holding traffic, and across 12,860 sites that is every hour — so it
   selected the whole window while implying it had narrowed it. Removed with the
   sale-window concept it rested on; --qf-warn stays, since the agentic KPI tile and the
   intermediary pill still use it. */

.qf-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.qf-controls-left { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.qf-rangegroup { display: flex; align-items: center; gap: 8px; }

.qf-seg { display: flex; }
.qf-seg button {
  font-size: 11.5px;
  font-weight: 600;
  padding: 7px 11px;
  cursor: pointer;
  border: 1px solid var(--qf-line);
  background: var(--qf-surface);
  color: var(--qf-ink-2);
}
.qf-seg button + button { border-left: 0; }
.qf-seg button.qf-is-active { border-color: var(--qf-ink); background: var(--qf-ink); color: #fff; }


.qf-chip {
  font-size: 12px;
  font-weight: 600;
  padding: 5px 11px;
  cursor: pointer;
  border: 1px solid var(--qf-line);
  background: var(--qf-surface);
  color: var(--qf-ink-2);
}
.qf-chip.qf-is-active { border-color: var(--qf-accent); background: var(--qf-accent); color: #fff; }

.qf-linkbtn {
  font-size: 12px;
  color: var(--qf-accent);
  font-weight: 600;
  cursor: pointer;
  background: none;
  border: 0;
  padding: 0;
}
.qf-linkbtn:hover { text-decoration: underline; }

/* --------------------------------------------------------------------- KPIs */

.qf-kpis {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 12px;
  flex-shrink: 0;
}

.qf-kpi {
  min-height: 104px;
  padding: 13px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  justify-content: center;
}
.qf-kpi .qf-value { font-size: 26px; line-height: 28px; color: var(--qf-accent); }
.qf-kpi .qf-note { font-size: 11px; color: var(--qf-ink-3); line-height: 15px; }
.qf-kpi.qf-is-agentic { border-color: var(--qf-warn); background: var(--qf-warn-tint); }
.qf-kpi.qf-is-agentic .qf-value { color: var(--qf-ink); }

/* -------------------------------------------------------------------- cards */

.qf-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.qf-card-titles { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.qf-card-title { font-size: 15px; }
.qf-card-sub { font-size: 11.5px; color: var(--qf-ink-3); }

.qf-legend { display: flex; gap: 16px; flex-wrap: wrap; }
.qf-legend div { display: flex; gap: 6px; align-items: center; font-size: 11.5px; color: var(--qf-ink-2); }
.qf-legend .qf-sw { width: 9px; height: 9px; flex-shrink: 0; }

.qf-pad { padding: 16px 18px; display: flex; flex-direction: column; gap: 14px; }

.qf-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

.qf-axis { display: flex; justify-content: space-between; }
.qf-axis span { font-size: 10.5px; color: var(--qf-ink-4); }

.qf-footnote {
  margin-top: auto;
  border-top: 1px solid var(--qf-line-soft);
  padding-top: 11px;
  font-size: 11.5px;
  color: var(--qf-ink-2);
  line-height: 17px;
}

/* --------------------------------------------------------------- bar charts */

/* The chart and the waiting-room gauge share one panel, split by a rule. The panel is
   now the row; .qf-trendmain is the column the chart used to be. */
.qf-trendpanel {
  flex-shrink: 0;
  display: flex;
}

.qf-trendmain {
  flex-grow: 1;
  min-width: 0;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* --------------------------------------------------- waiting-room gauge */

.qf-gauge {
  width: 274px;
  flex-shrink: 0;
  border-left: 1px solid var(--qf-line);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.qf-gauge-head { display: flex; flex-direction: column; gap: 3px; }
.qf-gauge-lbl { color: var(--qf-ink); }
.qf-gauge-active { display: flex; align-items: baseline; gap: 7px; }
.qf-gauge-active .qf-hd { font-size: 26px; line-height: 28px; color: var(--qf-accent); }

.qf-gauge-dial { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.qf-gauge-arcwrap { position: relative; width: 190px; }
.qf-gauge-svg { width: 190px; height: auto; display: block; }
.qf-gauge-track { fill: none; stroke: var(--qf-line-soft); stroke-width: 13; }
.qf-gauge-value { fill: none; stroke: var(--qf-accent); stroke-width: 13; }

/* Sits over the arc's own opening rather than under it, so the dial reads as one object. */
.qf-gauge-pct {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 19px;
  text-align: center;
  font-size: 30px;
  line-height: 30px;
  color: var(--qf-ink);
}
.qf-gauge-scale {
  display: flex;
  justify-content: space-between;
  width: 190px;
  font-size: 10px;
  color: var(--qf-ink-4);
}

.qf-gauge-foot { display: flex; flex-direction: column; gap: 4px; }
.qf-gauge-foot-title { font-size: 12px; font-weight: 700; line-height: 16px; }
.qf-gauge-caption { font-size: 11.5px; color: var(--qf-ink-2); line-height: 16px; }
.qf-gauge-empty { margin: auto 0; }

/* Below the two-column breakpoint the rule would be a hairline across a narrow screen,
   so the gauge stacks under the chart and the rule moves to the top edge. */
@media (max-width: 860px) {
  .qf-trendpanel { flex-direction: column; }
  .qf-gauge {
    width: auto;
    border-left: 0;
    border-top: 1px solid var(--qf-line);
  }
}

/* The trend chart's y-axis. `.qf-chartrow` is shared with the bot detail chart, whose
   plot is a 220px SVG with its own top and bottom padding; these bars are exactly the
   196px of `.qf-trend`, so the column is sized to the bars and not to that SVG. Without
   this the labels drift a third of a bar out of line. */
.qf-trendmain .qf-yaxis {
  height: 196px;
  padding: 0;
  align-self: flex-start;
}

.qf-trend {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 196px;
  flex-shrink: 0;
}
.qf-trend .qf-col {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.qf-trend .qf-pass { flex-grow: 1; background: var(--qf-accent-soft); cursor: help; }
.qf-trend .qf-wait { background: var(--qf-accent); cursor: help; }

/* There is no sale-bucket tint. It shaded the buckets that held a sale event, and across
   12,860 sites essentially every hour holds one — so every bar was tinted and the
   distinction the colour was drawing did not exist. */

.qf-rows { display: flex; flex-direction: column; gap: 11px; }

.qf-row { display: flex; align-items: center; gap: 11px; }
.qf-row .qf-track { flex-grow: 1; height: 10px; background: var(--qf-surface-2); }
.qf-row .qf-fill { height: 10px; background: var(--qf-accent); }
.qf-row .qf-fill.qf-is-warn { background: var(--qf-warn); }
.qf-row .qf-pct {
  font-size: 11.5px;
  color: var(--qf-ink-2);
  width: 42px;
  text-align: right;
  flex-shrink: 0;
}
.qf-row .qf-name { font-size: 12px; width: 158px; flex-shrink: 0; }
.qf-row .qf-name.qf-is-agent { font-weight: 700; }
.qf-row .qf-opname { font-size: 12px; width: 96px; flex-shrink: 0; font-weight: 600; }
.qf-row .qf-delta {
  font-size: 11px;
  font-weight: 700;
  width: 48px;
  text-align: right;
  flex-shrink: 0;
  color: var(--qf-ink-4);
}
.qf-row .qf-delta.qf-is-up { color: var(--qf-accent); }

/* ---------------------------------------------------------------- world map */

.qf-geo { display: flex; flex-shrink: 0; }
.qf-geo .qf-map {
  flex-grow: 1;
  min-width: 0;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.qf-geo .qf-list {
  width: 356px;
  flex-shrink: 0;
  border-left: 1px solid var(--qf-line);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 13px;
}
.qf-geo svg { width: 100%; height: auto; }

.qf-heatkey { display: flex; align-items: center; gap: 8px; }
.qf-heatkey span { font-size: 10.5px; color: var(--qf-ink-4); }
.qf-heatkey .qf-swatches { display: flex; gap: 2px; }
.qf-heatkey .qf-swatches div { width: 22px; height: 10px; }

/* THE COUNT ABOVE THE BAR, NOT BESIDE THE LABEL. Shared by Top countries, Why the bot
   came and Operators, so the three read the same way: name and figures on one line, the
   bar beneath it at full width. `.qf-stat` is the neutral name; `.qf-country` stays as
   an alias so the map's own markup did not have to change. */
.qf-country,
.qf-stat { display: flex; flex-direction: column; gap: 4px; }
.qf-country .qf-top,
.qf-stat .qf-top { display: flex; align-items: baseline; gap: 8px; }
.qf-country .qf-dot,
.qf-stat .qf-dot { width: 8px; height: 8px; flex-shrink: 0; }
.qf-country .qf-cname,
.qf-stat .qf-cname { font-size: 12px; font-weight: 600; flex-grow: 1; }
.qf-country .qf-req,
.qf-stat .qf-req { font-size: 12px; color: var(--qf-ink); }
.qf-country .qf-cpct,
.qf-stat .qf-cpct { font-size: 11px; color: var(--qf-ink-4); width: 42px; text-align: right; }
.qf-country .qf-track,
.qf-stat .qf-track { height: 6px; background: var(--qf-surface-2); margin-left: 16px; }
.qf-country .qf-fill,
.qf-stat .qf-fill { height: 6px; }

/* The two tints the rows can take: the Agent category, and the Unverified operator. */
.qf-stat .qf-fill { background: var(--qf-accent); }
.qf-stat .qf-fill.qf-is-warn { background: var(--qf-warn); }
.qf-stat .qf-cname.qf-is-agent { font-weight: 700; }

/* ------------------------------------------------------------------ AI grid */

.qf-filters {
  flex-shrink: 0;
  padding: 16px 28px 12px;
  border-bottom: 1px solid var(--qf-line);
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: var(--qf-shell-max);
  width: 100%;
  margin: 0 auto;
}
.qf-filter-row { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.qf-filter-row > .qf-lbl { width: 74px; flex-shrink: 0; }
.qf-filter-row .qf-chips { display: flex; gap: 6px; flex-wrap: wrap; }

.qf-resultline {
  flex-shrink: 0;
  padding: 12px 28px 10px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: 12.5px;
  color: var(--qf-ink-2);
  max-width: var(--qf-shell-max);
  width: 100%;
  margin: 0 auto;
}

.qf-grid-wrap {
  flex-grow: 1;
  padding: 0 28px 28px;
  max-width: var(--qf-shell-max);
  width: 100%;
  margin: 0 auto;
}

.qf-botgrid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  align-items: stretch;
}

.qf-botcard {
  padding: 15px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  cursor: pointer;
  text-align: left;
  border: 1px solid var(--qf-line);
  background: var(--qf-surface);
  width: 100%;
  height: 100%;
}
.qf-botcard:hover { border-color: var(--qf-line-strong); }
.qf-botcard .qf-req { font-size: 24px; line-height: 26px; color: var(--qf-accent); }
/* The warning treatment, applied to traffic whose claimed operator could not be confirmed
   (§4.5's `Unverified` literal) rather than to everything with `verified: false`. The
   earlier version keyed off the boolean, which meant a peach card for every bot whose
   operator simply publishes no IP ranges — most of the grid, and nothing to warn about.
   It also tinted the card `--qf-surface-2`, the same colour as the neutral pill sitting on
   it, so the Verified/Unverified pill became invisible. */
.qf-botcard.qf-is-unattributed { border-color: var(--qf-warn); background: var(--qf-warn-card); }
.qf-botcard.qf-is-unattributed .qf-req { color: var(--qf-warn-ink); }
.qf-botcard.qf-is-selected { border-color: var(--qf-ink); background: var(--qf-surface-2); }
.qf-botcard-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.qf-botcard-name { font-size: 16px; line-height: 20px; }
.qf-botcard-req { display: flex; align-items: baseline; gap: 7px; }
.qf-botcard-req-unit { font-size: 11px; color: var(--qf-ink-3); }
.qf-field { display: flex; flex-direction: column; gap: 3px; }
.qf-about { font-size: 11.5px; color: var(--qf-ink-2); line-height: 16px; }

.qf-pill { font-size: 10.5px; font-weight: 600; padding: 3px 8px; flex-shrink: 0; }
.qf-pill.qf-ok { background: var(--qf-accent-tint); color: var(--qf-accent); }
.qf-pill.qf-neutral { background: var(--qf-surface-2); color: var(--qf-ink-2); }

/* The identity pill carries a border as well as a fill, so it stays legible on the peach
   card of an unattributed bot as well as on a white one. */
.qf-pill.qf-identity { border: 1px solid var(--qf-line-strong); background: var(--qf-surface); color: var(--qf-ink-2); }
.qf-pill.qf-intermediary { background: var(--qf-warn-soft); color: var(--qf-warn-ink); }

.qf-tags { margin-top: auto; display: flex; gap: 5px; flex-wrap: wrap; padding-top: 2px; }

.qf-ua {
  font-size: 10.5px;
  color: var(--qf-ink-2);
  background: var(--qf-surface-2);
  padding: 4px 7px;
}

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

.qf-detail {
  grid-column: 1 / -1;
  border: 1px solid var(--qf-ink);
  background: var(--qf-surface);
  display: flex;
  flex-direction: column;
}

.qf-detail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 15px 20px;
  border-bottom: 1px solid var(--qf-line);
  background: var(--qf-surface-2);
  flex-wrap: wrap;
}
.qf-detail-head .qf-who { display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap; }
.qf-detail-head .qf-who .qf-eyebrow { font-size: 11.5px; color: var(--qf-ink-3); }
.qf-detail-head .qf-who .qf-name { font-size: 20px; width: auto; }

.qf-iconbtn {
  width: 26px;
  height: 26px;
  border: 1px solid var(--qf-line-strong);
  background: var(--qf-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}
.qf-iconbtn:hover { background: var(--qf-surface-2); }

.qf-detail-body { padding: 18px 22px 16px; display: flex; flex-direction: column; gap: 12px; }

.qf-chartrow { display: flex; gap: 10px; }
.qf-chartrow .qf-yaxis {
  width: 40px;
  flex-shrink: 0;
  height: 220px;
  padding: 6px 0 22px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-end;
}
.qf-chartrow .qf-yaxis span { font-size: 10px; color: var(--qf-ink-4); }
.qf-chartrow .qf-plot { flex-grow: 1; min-width: 0; }

.qf-stats { display: flex; gap: 30px; border-top: 1px solid var(--qf-line-soft); padding-top: 12px; flex-wrap: wrap; }
.qf-stat { display: flex; flex-direction: column; gap: 2px; }
.qf-stat .qf-v { font-size: 17px; }
.qf-stat .qf-v.qf-is-up { color: var(--qf-accent); }
.qf-stat .qf-v.qf-is-down { color: var(--qf-ink-4); }

.qf-guidance { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.qf-guidance.qf-is-divided { border-top: 1px solid var(--qf-line-soft); padding-top: 14px; }
.qf-guidance .qf-block { display: flex; flex-direction: column; gap: 4px; }
.qf-guidance .qf-block p { font-size: 12px; color: var(--qf-ink-2); line-height: 18px; }

.qf-robots { white-space: pre-wrap; }
.qf-doclink { font-size: 11.5px; font-weight: 600; }
.qf-ualist { display: flex; flex-direction: column; gap: 4px; }
.qf-ualist .qf-ua { word-break: break-all; }


/* ------------------------------------------------------- impersonation view */

.qf-spoof-top { display: grid; grid-template-columns: 1fr 320px; gap: 16px; flex-shrink: 0; }

.qf-callout {
  background: var(--qf-warn-tint);
  border-left: 3px solid var(--qf-warn);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.qf-callout .qf-hl { font-size: 16px; }
.qf-callout p { font-size: 12.5px; color: var(--qf-ink-2); line-height: 19px; max-width: 780px; }

.qf-spoof-rate {
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
}
.qf-spoof-rate .qf-v { font-size: 34px; }
.qf-spoof-rate .qf-note { font-size: 11.5px; color: var(--qf-ink-3); line-height: 16px; }

.qf-tablepanel {
  flex-grow: 1;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.qf-table { display: flex; flex-direction: column; }
.qf-thead {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 30px;
  border-bottom: 1px solid var(--qf-line);
}
.qf-trow {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 48px;
  border-bottom: 1px solid var(--qf-line-soft);
}
.qf-c-claimed { width: 190px; flex-shrink: 0; font-size: 12.5px; font-weight: 600; }
.qf-c-op { width: 118px; flex-shrink: 0; font-size: 12px; color: var(--qf-ink-2); }
.qf-c-bar { flex-grow: 1; display: flex; align-items: center; gap: 10px; min-width: 120px; }
.qf-c-bar .qf-track { flex-grow: 1; height: 10px; background: var(--qf-surface-2); }
.qf-c-bar .qf-fill { height: 10px; background: var(--qf-warn); }
.qf-c-bar .qf-pct { font-size: 11.5px; color: var(--qf-ink-2); width: 38px; flex-shrink: 0; }
.qf-c-failed { width: 92px; flex-shrink: 0; text-align: right; font-size: 12px; font-weight: 600; }


.qf-trow-other { color: var(--qf-ink-2); font-style: italic; }
.qf-trow-other .qf-c-claimed { font-style: italic; }

.qf-empty { padding: 50px 0; text-align: center; font-size: 13px; color: var(--qf-ink-3); }

/* ----------------------------------------------------------------- skeletons */

/* Placeholders shown while a panel's data is in flight.
 *
 * They exist because `null` used to mean both "loading" and "failed", so every panel
 * rendered its *unavailable* text during the first fetch: a visitor's first sight of the
 * dashboard was six tiles reading "—" and three panels saying "unavailable", which then
 * filled in. Correct, and it reads as an outage.
 *
 * Each placeholder occupies the space its real content will, so nothing jumps when the
 * data arrives — the layout shift is the other half of what made loading look broken. */

.qf-skeleton {
  background: var(--qf-surface-2);
  animation: qf-pulse 1.4s ease-in-out infinite;
}

@keyframes qf-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

/* A pulsing block is decorative, and for some readers it is worse than that — vestibular
   disorders and attention conditions both make looping motion actively unpleasant. The
   placeholder still does its job standing still. */
@media (prefers-reduced-motion: reduce) {
  .qf-skeleton { animation: none; }
}

/* Sized to the text they stand in for, so a tile is the same height either way. */
.qf-skeleton-value { height: 28px; width: 82px; }
.qf-skeleton-note { height: 11px; width: 100%; max-width: 150px; }
.qf-skeleton-line { height: 12px; }
.qf-skeleton-bar { height: 10px; flex-grow: 1; }
/* Matches .qf-stat .qf-track -- 6px and indented, so the placeholder is the same height
   and the same shape as the bar it stands in for. */
.qf-skeleton-statbar { height: 6px; margin-left: 16px; }
.qf-skeleton-pct { height: 11px; width: 42px; flex-shrink: 0; }
.qf-skeleton-chart { height: 196px; flex-shrink: 0; }
.qf-skeleton-gauge { height: 150px; flex-shrink: 0; }
.qf-skeleton-map { height: 420px; flex-grow: 1; }
.qf-skeleton-req { height: 26px; width: 96px; }

/* ------------------------------------------------------------------- banner */

/* Shown when the Flow API could not be read. Panels stay empty behind it —
   the dashboard never substitutes numbers that were not measured. */
.qf-banner {
  flex-shrink: 0;
  margin: 16px 28px 0;
  max-width: var(--qf-shell-max);
  padding: 12px 16px;
  background: var(--qf-warn-tint);
  border-left: 3px solid var(--qf-warn);
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12.5px;
  color: var(--qf-ink-2);
  line-height: 18px;
}
.qf-banner .qf-hd { font-size: 13px; }
.qf-banner .qf-mono { background: var(--qf-warn-soft); padding: 1px 5px; font-size: 11.5px; }

/* The banner's retry. Aligned left with the text rather than floated, so it reads as part
   of the sentence it follows rather than as a dismissal. */
.qf-retry {
  align-self: flex-start;
  margin-top: 4px;
  padding: 5px 11px;
  cursor: pointer;
  border: 1px solid var(--qf-warn-ink);
  background: var(--qf-surface);
  color: var(--qf-warn-ink);
  font-size: 12px;
  font-weight: 600;
}
.qf-retry:hover { background: var(--qf-warn-soft); }

/* -------------------------------------------------------------- responsive */

@media (max-width: 1240px) {
  .qf-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .qf-botgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .qf-geo { flex-direction: column; }
  .qf-geo .qf-list { width: auto; border-left: 0; border-top: 1px solid var(--qf-line); }
  .qf-spoof-top { grid-template-columns: 1fr; }
  }

@media (max-width: 860px) {
  .qf-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* `minmax(0, 1fr)`, never a bare `1fr`.
   *
   * `1fr` is shorthand for `minmax(auto, 1fr)`, and that `auto` minimum means a track
   * cannot shrink below its content's min-content width. The bot card contains the
   * user-agent string under `.qf-trunc`, which is `white-space: nowrap` — its min-content
   * width is the whole unbroken string. So a single-column grid blew out to 1123px at a
   * 390px viewport and the page scrolled sideways by 749px, while the three- and
   * two-column rules above were fine because they already carried the explicit zero.
   *
   * The ellipsis in `.qf-trunc` only ever appeared to work: it needs a bounded container,
   * and the grid was resizing to fit the text rather than clipping it. */
  .qf-two-col { grid-template-columns: minmax(0, 1fr); }
  .qf-botgrid { grid-template-columns: minmax(0, 1fr); }
  .qf-guidance { grid-template-columns: minmax(0, 1fr); }

  .qf-view,
  .qf-filters,
  .qf-resultline,
  .qf-grid-wrap { padding-left: 16px; padding-right: 16px; }
  .qf-topbar, .qf-contextbar { padding-left: 16px; padding-right: 16px; }
  .qf-banner { margin-left: 16px; margin-right: 16px; }

  /* Six columns of fixed-width table do not fit a phone, and the alternatives are worse:
     dropping more columns loses data the tab exists to show, and stacking each row into a
     card loses the comparison down the failure-rate column. So the table scrolls inside
     its own box — the row keeps its widths, the reader swipes, and crucially the PAGE
     does not move. `width: auto` on two of the columns was an earlier attempt to make it
     fit, which shrank the labels without ever getting under the viewport. */
  .qf-table { overflow-x: auto; }
  .qf-thead, .qf-trow { min-width: 640px; }
}

/* Brand wordmark — the "Flow" half carries the accent. */
.qf-brand-accent { color: var(--qf-accent); }
