/* ---------------------------------------------------------------------------
   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;
  /* Secondary text is black too: the greys it used were hard to read at small sizes, so
     hierarchy now comes from size and weight alone. */
  --qf-ink-2: var(--qf-ink);
  --qf-ink-3: var(--qf-ink);
  --qf-ink-4: var(--qf-ink);

  --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);
  /* The dashboard is drawn light only, so the browser's own defaults inside it must be
     too -- queue-it.com allows dark, which would otherwise reach our buttons. */
  color-scheme: light;
  -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; }

/* `color: inherit` because the browser's own button colour follows the visitor's colour
   scheme, and queue-it.com declares `color-scheme: light dark`: in dark mode an unstyled
   label -- a bot card's name -- turned white on our white card. */
.qf :where(button) {
  margin: 0;
  color: inherit;
  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;
  flex-wrap: wrap;
}

.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); }
/* A pulsing dot while any panel is still waiting on its call. */
.qf-scope.qf-is-loading::before {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 8px;
  border-radius: 50%;
  background: var(--qf-accent);
  vertical-align: 1px;
  animation: qf-pulse 1.4s ease-in-out infinite;
}
.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 0 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 */

/* One bordered panel of tiles, split by rules: a row, or the Overview's two-by-two grid.
   Each tile names its own figure, so nothing heads the panel. */
.qf-kpi-row { display: flex; flex-shrink: 0; }
.qf-kpi-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); flex-shrink: 0; }
.qf-kpi-grid .qf-kpi:nth-child(even) { border-left: 1px solid var(--qf-line); }
.qf-kpi-grid .qf-kpi:nth-child(n + 3) { border-top: 1px solid var(--qf-line); }

.qf-kpi {
  flex: 1 1 0;
  min-width: 0;
  min-height: 104px;
  padding: 13px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  justify-content: center;
}
.qf-kpi-row .qf-kpi + .qf-kpi { border-left: 1px solid var(--qf-line); }
.qf-kpi .qf-lbl { line-height: 14px; }
.qf-kpi .qf-value { font-size: 26px; line-height: 28px; color: var(--qf-ink); }
.qf-kpi .qf-note { font-size: 11px; color: var(--qf-ink-3); line-height: 15px; }
.qf-kpi.qf-is-agentic { background: var(--qf-warn-tint); }
.qf-sharebar { height: 10px; margin-top: 4px; background: var(--qf-accent-soft); }
.qf-sharebar .qf-wait { display: block; height: 100%; background: var(--qf-accent); }

/* The Waiting rooms tab: one section per region, its label above its figures. */
.qf-region { display: flex; flex-direction: column; gap: 10px; }
.qf-region + .qf-region { margin-top: 18px; }
.qf-region-title { font-size: 18px; line-height: 22px; margin: 0; }
.qf-region-fold { border-top: 1px solid var(--qf-line-soft); padding: 12px 0; }
.qf-region + .qf-region-fold { margin-top: 0; }
.qf-region-fold[open] { gap: 10px; }
/* THE WHOLE ROW OPENS THE CHART: the title and the figures are the summary. */
.qf-region-summary {
  display: flex; flex-direction: column; gap: 10px; cursor: pointer; list-style: none;
}
.qf-region-summary::-webkit-details-marker { display: none; }
.qf-region-summary > .qf-region-title::before {
  content: '▸'; display: inline-block; width: 16px; color: var(--qf-ink-2);
}
.qf-region-fold[open] > .qf-region-summary > .qf-region-title::before { content: '▾'; }
.qf-region-summary:hover .qf-kpi-row { border-color: var(--qf-ink-3); }

/* -------------------------------------------------------------------- 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
   the row; .qf-trendmain is the chart's column. */
.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 {
  /* 300, not 274. At 274 the head's content box is 238px and "17,128" plus "active in
     this window" on one line wants 237 -- so any wider figure pushed the note onto a
     second and third line, and a wider figure still pushed the number itself down. */
  width: 300px;
  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); }
/* THE FIGURE GETS ITS OWN LINE. It shared one with the caption, baseline-aligned, which
   reads well only while both fit: 17,128 rooms already filled the row exactly, and the
   caption broke over three lines the moment the figure grew a digit. Stacked, the figure
   is never the thing that moves. */
.qf-gauge-active { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; }
.qf-gauge-active .qf-hd { font-size: 26px; line-height: 28px; color: var(--qf-ink); }
.qf-gauge-active .qf-note { font-size: 11px; line-height: 15px; }

.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;
}
/* CAPPED, BECAUSE A YEAR CAN BE ONE BAR.
   `1y` is drawn in calendar months and clamped at the measurement floor, so until the
   data has a second month the chart holds a single column -- and an unbounded flex-grow
   turned that into a 1,100px-wide block of colour that read as a filled panel rather than
   as a bar. The cap is wide enough that 7 columns still fill the row. */
.qf-trend .qf-col {
  flex-grow: 1;
  max-width: 240px;
  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-ink); }

/* ---------------------------------------------------------------- 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-filter-lbl { display: inline-flex; align-items: center; gap: 5px; }

/* The "i" beside a filter label, and the definition it opens. */

.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 0 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-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; color: var(--qf-ink); }
.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 any card. */
.qf-pill.qf-identity { border: 1px solid var(--qf-line-strong); background: var(--qf-surface); color: var(--qf-ink-2); }

.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-ink); }
.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-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; }

.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 {
  /* The line colour, one step darker than the card's own surface, so a placeholder reads
     as "loading" and not as an empty card. */
  background: var(--qf-line);
  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, .qf-scope.qf-is-loading::before { 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-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); }
  }

@media (max-width: 860px) {
  .qf-kpi-row { flex-wrap: wrap; }
  .qf-kpi { flex-basis: 50%; }
  /* The third tile wraps to its own line: a rule above it, none on its left. */
  .qf-kpi-row .qf-kpi.qf-kpi-share { flex-basis: 100%; border-left: 0; border-top: 1px solid var(--qf-line); }

  /* `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); }

  /* Text rows keep a 16px inset on small screens, so their text never touches the edge;
     the cards and panels below them run edge to edge. */
  .qf-filters, .qf-resultline, .qf-contextbar { padding-left: 16px; padding-right: 16px; }
  .qf-banner { margin-left: 16px; margin-right: 16px; }
}
