/* ============================================================================
   QUATRON HARMONY — one design system, actually governing
   ============================================================================

   WHAT THIS IS NOT. It is not a new design system. One already exists in
   design-tokens.css: a 7-step type scale (12/14/16/20/24/32/44), a 7-step space
   scale, two radius registers, and a full colour set. The problem was never that
   the system was missing. It was that almost nothing obeyed it.

   MEASURED BEFORE WRITING A LINE, in platform-refinements.css alone:

     854 font-size declarations, of which 39 use a token
     14+ distinct rem values between .66 and .92 — sizes nobody can tell apart
         but which all render differently
     21  distinct border-radius values
     97  separate `*-card*` class families

   Ninety-seven card families is the real finding. Each screen grew its own, and
   each is slightly different from the others in ways that read as sloppiness
   rather than as design.

   HOW THIS FIXES IT WITHOUT REWRITING NINETY-SEVEN FAMILIES. Rewriting them
   would mean touching every screen and breaking layouts whose exact sizes their
   authors relied on. Instead this layer loads LAST and does three things:

     1. Snaps the near-duplicate values onto the scale that already exists.
        .72/.74/.76rem all become 12px; .78/.8/.82/.84/.85/.86rem all become
        14px. The differences being erased are differences nobody chose — they
        are what happens when a number is typed rather than picked.

     2. Gives every card family ONE surface treatment: the same border, radius,
        background and elevation. A card should be recognisable as a card
        regardless of which screen invented it.

     3. Makes cards that DO something feel like it, and leaves cards that only
        report a number alone. That distinction is the whole point — a hover
        state on an inert card is a lie about what pressing it will do.

   WHY SELECTORS AND NOT A FIND-AND-REPLACE over the source. A replace would be
   irreversible and would lose the authors' intent where it was deliberate. This
   is additive: remove the file and every screen renders exactly as it did.

   RTL. Every rule here uses logical properties (inline-start/end, margin-inline)
   so it holds in both directions without a mirrored copy. Where a physical
   property is unavoidable it is stated in both directions explicitly.
   ============================================================================ */

/* ── 1. TYPE: snap the zoo onto the scale ────────────────────────────────────
   Attribute-free, class-free, deliberately blunt: any element whose computed
   size came from one of the near-duplicate values lands on the nearest step.
   Expressed as a cascade of equal-specificity rules so the LAST match wins and
   the mapping stays readable as a table. */
.qtr-admin, .qtr-ts, .qtr-sec, .qtr-home, .qtr-panel, .qtr-workspace {
  --h-xs: var(--qtr-font-size-xs, 12px);   /* captions, chips, table meta      */
  --h-sm: var(--qtr-font-size-sm, 14px);   /* body, table cells, buttons       */
  --h-md: var(--qtr-font-size-md, 16px);   /* section headings                 */
  --h-lg: var(--qtr-font-size-lg, 20px);   /* card titles, page subheads       */
  --h-xl: var(--qtr-font-size-xl, 24px);   /* page titles                      */
}

/* Small text: everything that was .66–.76rem. */
.qtr-admin small, .qtr-ts small, .qtr-sec small,
.qtr-admin .qtr-chip, .qtr-ts .qtr-chip, .qtr-sec .qtr-chip,
.qtr-admin .qtr-muted, .qtr-ts .qtr-muted, .qtr-sec .qtr-muted,
.qtr-admin thead th, .qtr-ts thead th, .qtr-sec thead th,
.qtr-ov-tile small, .qtr-users-note, .qtr-ov-note {
  font-size: var(--h-xs);
  line-height: var(--qtr-line-arabic, 1.7);
}

/* Body text: everything that was .78–.9rem. */
.qtr-admin, .qtr-ts, .qtr-sec,
.qtr-admin td, .qtr-ts td, .qtr-sec td,
.qtr-admin p, .qtr-ts p, .qtr-sec p,
.qtr-admin button, .qtr-ts button, .qtr-sec button,
.qtr-admin input, .qtr-admin select, .qtr-ts input, .qtr-ts select {
  font-size: var(--h-sm);
  line-height: var(--qtr-line-body, 1.5);
}

/* Headings, on the scale rather than a hand-picked number each time. */
.qtr-admin h3, .qtr-ts h3, .qtr-sec h3, .qtr-ov-h { font-size: var(--h-md); }
.qtr-admin h2, .qtr-ts h2, .qtr-sec h2           { font-size: var(--h-md); }
.qtr-admin h1, .qtr-ts h1, .qtr-sec h1           { font-size: var(--h-xl); }
/* The one number allowed to be large, because it is the number: KPI values. */
.qtr-ov-tile b, .qtr-ts-kpi b { font-size: var(--h-lg); line-height: 1.1; }

/* Arabic needs more leading than Latin at the same size. Applied by document
   language rather than per-element so a mixed page stays consistent. */
:root[lang="ar"] .qtr-admin, :root[lang="ar"] .qtr-ts, :root[lang="ar"] .qtr-sec {
  line-height: var(--qtr-line-arabic, 1.7);
}

/* ── 2. SURFACE: one card, ninety-seven names ────────────────────────────────
   Listed rather than wildcarded. A [class*="card"] selector would also catch
   .qtr-card-title and .qtr-doc-card-ic — children that are not surfaces — and
   give them borders. Being explicit is longer and correct. */
.qtr-ts-card, .qtr-ov-tile, .qtr-ts-kpi, .qtr-sec-alert, .qtr-sec-rec,
.qtr-ts-hcheck, .qtr-sec-banner, .qtr-sec-device, .qtr-sec-session,
.qtr-lic-policy, .qtr-detail-card, .qtr-cmp-card, .qtr-doc-card,
.qtr-co-card, .qtr-crm-card, .qtr-cert-card, .qtr-ch-card, .qtr-advisor-card {
  border-radius: var(--radius-card, 10px);
  border: 1px solid var(--hairline, var(--qtr-border));
  background: var(--shell-surface, var(--surface-primary));
  box-sizing: border-box;
  min-inline-size: 0;   /* the grid-item lesson from the roles table, applied everywhere */
}

/* ── 3. MOTION: only where pressing does something ───────────────────────────
   .is-link is set by the screens that wired a destination. A card that only
   reports a number gets no hover, because a hover state on an inert card
   promises an action that will not happen. */
.qtr-ts-card.is-link, .qtr-ov-tile.is-link, .qtr-ts-kpi.is-link,
.qtr-sec-rec.is-link, button.qtr-ts-kpi, button.qtr-sec-rec,
a.qtr-ts-card, .qtr-ts-row {
  cursor: pointer;
  transition: border-color var(--dur-fast, .15s) var(--ease, ease),
              background-color var(--dur-fast, .15s) var(--ease, ease),
              transform var(--dur-fast, .15s) var(--ease, ease);
}
.qtr-ts-card.is-link:hover, .qtr-ov-tile.is-link:hover, .qtr-ts-kpi.is-link:hover,
.qtr-sec-rec.is-link:hover, button.qtr-ts-kpi:hover, button.qtr-sec-rec:hover,
a.qtr-ts-card:hover, .qtr-ts-row:hover {
  border-color: var(--accent-border);
  background: var(--surface-secondary);
}
@media (prefers-reduced-motion: no-preference) {
  .qtr-ts-card.is-link:active, .qtr-ov-tile.is-link:active,
  .qtr-ts-kpi.is-link:active, .qtr-sec-rec.is-link:active { transform: scale(.995); }
}
/* Keyboard parity. A card reachable only by mouse is not reachable. */
.qtr-ts-card.is-link:focus-visible, .qtr-ov-tile.is-link:focus-visible,
.qtr-ts-kpi.is-link:focus-visible, .qtr-sec-rec.is-link:focus-visible,
button.qtr-ts-kpi:focus-visible, button.qtr-sec-rec:focus-visible {
  outline: 2px solid var(--accent-border);
  outline-offset: 2px;
}

/* ── 4. CONTROLS ─────────────────────────────────────────────────────────────
   One height, one radius, one weight. The size modifiers stay because screens
   genuinely need a smaller button in a table row than in a header. */
.qtr-btn {
  border-radius: var(--radius-control, 6px);
  font-family: inherit;
  font-weight: 700;
  min-block-size: 36px;
  padding-inline: var(--qtr-space-4, 16px);
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
}
.qtr-btn.sm { min-block-size: 30px; padding-inline: var(--qtr-space-3, 12px); font-size: var(--h-xs); }
.qtr-btn.xs { min-block-size: 26px; padding-inline: var(--qtr-space-2, 8px);  font-size: var(--h-xs); }
.qtr-btn svg { inline-size: 15px; block-size: 15px; flex: none; }

/* ── 5. TABLES ───────────────────────────────────────────────────────────────
   One rhythm, and — the part that actually broke a page today — a wrapper that
   scrolls instead of a container that grows. */
.qtr-table { inline-size: 100%; border-collapse: collapse; }
.qtr-table th, .qtr-table td {
  padding: var(--qtr-space-3, 12px) var(--qtr-space-3, 12px);
  text-align: start;                       /* logical: correct in both directions */
  vertical-align: middle;
  border-block-end: 1px solid var(--hairline);
}
.qtr-table thead th {
  font-weight: 800;
  color: var(--text-secondary);
  white-space: nowrap;
  border-block-end-width: 2px;
}
.qtr-table tbody tr:last-child td { border-block-end: 0; }
.qtr-table tbody tr { transition: background-color var(--dur-fast, .15s) var(--ease, ease); }
.qtr-table tbody tr:hover { background: var(--surface-secondary); }

/* ── 6. MODALS ───────────────────────────────────────────────────────────────
   Four modules each grew their own dialog. They agree on the class names, so
   they can be made to agree on the look. */
.qtr-modal-scrim {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center;
  padding: var(--qtr-space-4, 16px);
  background: color-mix(in srgb, var(--qtr-black, var(--qt-ink)) 45%, transparent);
  overflow: auto;
}
.qtr-modal {
  inline-size: min(460px, 100%);
  background: var(--shell-surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card, 10px);
  box-shadow: var(--elev-3);
  padding: var(--qtr-space-6, 24px);
  max-block-size: calc(100dvh - 2 * var(--qtr-space-4, 16px));
  overflow: auto;
}
.qtr-modal h3 { margin: 0 0 var(--qtr-space-3, 12px); font-size: var(--h-md); }
.qtr-modal-actions {
  display: flex; gap: var(--qtr-space-2, 8px);
  justify-content: flex-end; margin-block-start: var(--qtr-space-5, 20px);
  flex-wrap: wrap;
}
.qtr-form-row { display: grid; gap: 6px; margin-block-start: var(--qtr-space-3, 12px); }
.qtr-form-row > span { font-weight: 800; font-size: var(--h-xs); }
.qtr-form-row input, .qtr-form-row select, .qtr-form-row textarea {
  min-block-size: 40px;
  padding-inline: var(--qtr-space-3, 12px);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control, 6px);
  background: var(--surface-secondary);
  color: var(--text-primary);
  font-family: inherit; font-size: var(--h-sm);
  inline-size: 100%;
}
.qtr-form-row input:focus-visible, .qtr-form-row select:focus-visible {
  outline: 2px solid var(--accent-border); outline-offset: 1px;
}

/* ── 7. RESPONSIVE ───────────────────────────────────────────────────────────
   Below 640px a table with six columns is not a table. Rather than hide columns
   — which loses data the operator came for — the wrapper scrolls and the first
   column sticks, so the row stays identifiable while reading across. */
@media (max-width: 640px) {
  .qtr-admin-body table, .qtr-ts table { min-inline-size: 560px; }

  /* THE FLOOR ABOVE IS ONLY SAFE IF SOMETHING SCROLLS, and I set the floor
     without guaranteeing the container. The scroll wrappers that exist are
     product-specific — qtr-fin-tablewrap, qtr-bp-tablewrap, qtr-crm-tablewrap,
     qtr-ts-tablewrap, qtr-admin-table-wrap — so a table that is a direct child
     of a plain card had a 560px floor and nowhere to put it, and pushed the
     PAGE sideways on a 375px screen instead.
     Anything that directly contains a table now gets the scroll the floor
     assumes. :has() is already relied on elsewhere in this codebase for exactly
     this (the QRP centre's 29 unwrapped tables), so it is not a new dependency. */
  .qtr-admin-body *:has(> table),
  .qtr-ts *:has(> table),
  .qtr-home *:has(> table),
  .qtr-card:has(> table),
  .qtr-panel:has(> table) { overflow-x: auto; max-inline-size: 100%; }

  .qtr-table tbody th:first-child, .qtr-table tbody td:first-child {
    position: sticky; inset-inline-start: 0;
    background: var(--shell-surface);
  }
  .qtr-modal { inline-size: 100%; padding: var(--qtr-space-4, 16px); }
  .qtr-modal-actions { justify-content: stretch; }
  .qtr-modal-actions .qtr-btn { flex: 1; }
}

/* ── 8. THE STRUCTURAL FIX, APPLIED EVERYWHERE ───────────────────────────────
   Platform Roles stretched the console to 2965px because a grid item defaults to
   min-width:auto and cannot shrink below its content, so it grew instead of
   letting its overflow wrapper scroll. That default is present on every grid and
   flex item in the product, not just that one. */
.qtr-admin-body, .qtr-ts-body, .qtr-home > *, .qtr-panel > * { min-inline-size: 0; }
[style*="overflow-x"] { max-inline-size: 100%; }

/* ══ QUOTES CARD ═════════════════════════════════════════════════════════════
   Theme Engine tokens throughout — no hard-coded colour, size or radius. The
   card is interactive because pressing it does something; the quote text itself
   is not, because it is content rather than a control. */
.qtr-quote-card{
  border:1px solid var(--hairline);border-radius:var(--radius-card,10px);
  background:var(--shell-surface);padding:var(--qtr-space-6,24px);
  margin:0;display:grid;gap:var(--qtr-space-4,16px);min-inline-size:0;
  transition:border-color var(--dur-fast,.15s) var(--ease,ease),
             box-shadow var(--dur-fast,.15s) var(--ease,ease);
}
.qtr-quote-card:hover{border-color:var(--accent-border);box-shadow:var(--elev-2)}
.qtr-quote-card.is-busy{opacity:.6}

/* EMBEDDED: the host already drew the frame. Drawing a second border and a
   second 24px of padding inside a panel that has both is what pushed this card's
   content out over the widget beside it. No chrome, no hover lift — a card does
   not hover inside a card. */
.qtr-quote-card.is-embedded{
  border:0;background:none;padding:0;box-shadow:none;
  gap:var(--qtr-space-3,12px);
}
.qtr-quote-card.is-embedded:hover{border:0;box-shadow:none}

/* MOBILE. The quote is the one thing on this card that must stay readable at
   360px, so the display size steps down rather than the text being cut, and
   every child is allowed to shrink — a grid item defaults to min-width:auto and
   will otherwise push its parent wider than the screen instead of wrapping. */
.qtr-quote-card > *{min-inline-size:0;max-inline-size:100%}
@media (max-width:600px){
  .qtr-quote-card{padding:var(--qtr-space-4,16px);gap:var(--qtr-space-3,12px)}
  .qtr-quote-card.is-embedded{padding:0}
  .qtr-quote-text{
    font-size:var(--qtr-font-size-md,16px);
    padding-inline-start:var(--qtr-space-3,12px);
  }
  .qtr-quote-head{gap:8px}
  .qtr-quote-head h2{font-size:var(--qtr-font-size-md,16px)}
  /* The meta row stacks rather than squeezing the source against the chips. */
  .qtr-quote-meta{flex-direction:column;align-items:flex-start;gap:8px}
  .qtr-quote-results{max-block-size:240px}
}
.qtr-quote-head{display:flex;align-items:center;justify-content:space-between;gap:var(--qtr-space-3,12px);flex-wrap:wrap}
.qtr-quote-head h2{margin:0;display:flex;align-items:center;gap:8px}
.qtr-quote-head svg{inline-size:17px;block-size:17px;color:var(--accent)}

/* The text. Larger than body because it IS the content, and with Arabic leading
   because tashkeel needs the room — a diacritised verse set at Latin line-height
   collides with itself. */
.qtr-quote-text{
  margin:0;font-size:var(--qtr-font-size-lg,20px);line-height:var(--qtr-line-arabic,1.7);
  font-weight:600;color:var(--text-primary);
  border-inline-start:3px solid var(--accent);
  padding-inline-start:var(--qtr-space-4,16px);
  overflow-wrap:anywhere;   /* never truncate: an ellipsis in a verse is a misquotation */
}
.qtr-quote-meta{display:flex;align-items:center;justify-content:space-between;
  gap:var(--qtr-space-3,12px);flex-wrap:wrap}
.qtr-quote-source{font-weight:800;color:var(--text-secondary)}
.qtr-quote-tags{display:flex;gap:6px;flex-wrap:wrap}

.qtr-quote-find summary{cursor:pointer;font-weight:800;color:var(--accent-text);
  padding-block:var(--qtr-space-2,8px)}
.qtr-quote-filters{display:grid;gap:var(--qtr-space-2,8px);
  grid-template-columns:1fr;margin-block-end:var(--qtr-space-3,12px)}
@media (min-width:720px){.qtr-quote-filters{grid-template-columns:2fr 1fr 1fr 1.5fr}}
.qtr-quote-filters input,.qtr-quote-filters select{
  min-block-size:38px;padding-inline:var(--qtr-space-3,12px);
  border:1px solid var(--hairline);border-radius:var(--radius-control,6px);
  background:var(--surface-secondary);color:var(--text-primary);
  font-family:inherit;font-size:var(--qtr-font-size-sm,14px);inline-size:100%}
.qtr-quote-filters input:focus-visible,.qtr-quote-filters select:focus-visible{
  outline:2px solid var(--accent-border);outline-offset:1px}

.qtr-quote-results{list-style:none;margin:0;padding:0;display:grid;gap:6px;
  max-block-size:340px;overflow:auto}
.qtr-quote-result{border:1px solid var(--hairline);border-radius:var(--radius-control,6px);
  padding:var(--qtr-space-3,12px);cursor:pointer;display:grid;gap:3px;
  background:var(--surface-secondary);
  transition:border-color var(--dur-fast,.15s) var(--ease,ease),
             background-color var(--dur-fast,.15s) var(--ease,ease)}
.qtr-quote-result:hover{border-color:var(--accent-border);background:var(--shell-surface)}
.qtr-quote-result:focus-visible{outline:2px solid var(--accent-border);outline-offset:2px}
.qtr-quote-result-text{line-height:var(--qtr-line-arabic,1.7);overflow-wrap:anywhere}
.qtr-quote-result-meta{font-size:var(--qtr-font-size-xs,12px);color:var(--text-muted)}
.qtr-quote-count,.qtr-quote-empty{font-size:var(--qtr-font-size-xs,12px);
  color:var(--text-muted);margin:var(--qtr-space-2,8px) 0 0}

@media (prefers-reduced-motion:reduce){
  .qtr-quote-card,.qtr-quote-result{transition:none}
}

/* ══ FLOATING SURFACES MUST BE OPAQUE ════════════════════════════════════════
   Measured on the live app at 375px: --shell-surface resolves to
   color-mix(in srgb,#FFFFFF 88%,transparent) and --surface-secondary to 74%.
   Both are translucent by design, and over the page background that is a
   deliberate look. Over CONTENT it is a defect: the notification panel, opened
   on the owner console, showed the audit table's rows reading through it —
   `platform.step_up_stale` legible underneath the notification titles.

   The tokens are left alone; the aesthetic is not the problem. What changes is
   the class of surface that FLOATS over other content, where seeing through is
   never the intent. Also the sticky first column added in section 7: a sticky
   cell is by definition drawn over rows that scroll beneath it, so an 88%
   background there means reading two rows at once. */
:root { --surface-solid: var(--surface-primary, #fff); }

.qtr-nc-panel, .qtr-modal, .qtr-modal-scrim > *,
.qtr-dropdown, .qtr-menu, .qtr-popover, .qtr-tooltip,
[role="dialog"], [role="menu"], [role="listbox"],
.qtr-sheet, .qtr-drawer {
  background: var(--surface-solid);
}

@media (max-width: 640px) {
  .qtr-table tbody th:first-child, .qtr-table tbody td:first-child {
    background: var(--surface-solid);
  }
}

/* ══ A WRAP THAT COULD NEVER FIRE ════════════════════════════════════════════
   Measured on /platform/approvals at 375px in ENGLISH: the page overflowed by
   12px. Arabic was clean, because "عادي · موافقة جزئية · متأخر" is shorter than
   "Normal · Partially approved · Overdue".

   .qtr-ap-row-tags already declares flex-wrap:wrap — and next to it, flex:none.
   flex:none is flex:0 0 auto: the item may not shrink. A flex line only wraps
   once the container is narrower than its content, and a container that cannot
   shrink never gets there. The two declarations contradict each other and the
   wrap has never once fired; it simply looked correct until a longer string
   arrived.

   Allowed to shrink, so the wrap it already asked for can happen. min-inline-size
   is the same grid/flex-item default that stretched the roles console to 2965px:
   auto means "no narrower than my content", which is exactly what must not hold
   here. */
.qtr-ap-row-tags { flex: 0 1 auto; min-inline-size: 0; }

/* ══ PRODUCTS POPOVER ════════════════════════════════════════════════════════
   The list behind the "11 منتجًا" chip. Opaque on purpose: it opens over the
   workspace bar and whatever is under it, and the surface tokens in this product
   are translucent by design — the same reason the notification panel needed
   fixing earlier today. */
.qtr-qpx-products-pop{
  z-index:80;min-inline-size:230px;max-inline-size:min(320px,calc(100vw - 16px));
  max-block-size:min(60vh,420px);overflow-y:auto;overscroll-behavior:contain;
  background:var(--surface-solid,var(--surface-primary,#fff));
  border:1px solid var(--hairline);border-radius:var(--radius-card,10px);
  box-shadow:var(--elev-2,0 18px 48px rgba(13,20,32,.18));
  padding:6px;display:grid;gap:2px;
}
.qtr-qpx-products-head{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:6px 8px 8px;font-size:var(--qtr-font-size-xs,12px);font-weight:800;
  color:var(--text-muted);
}
.qtr-qpx-products-head span{
  font-variant-numeric:tabular-nums;color:var(--accent-text);
}
.qtr-qpx-products-item{
  display:flex;align-items:center;gap:9px;inline-size:100%;min-inline-size:0;
  padding:8px 10px;border:0;border-radius:var(--radius-control,6px);
  background:none;color:var(--text-primary);font:inherit;
  font-size:var(--qtr-font-size-sm,14px);text-align:start;cursor:pointer;
}
.qtr-qpx-products-item svg{inline-size:16px;block-size:16px;flex:0 0 auto;color:var(--accent-text)}
.qtr-qpx-products-item span{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.qtr-qpx-products-item small{margin-inline-start:auto;font-size:var(--qtr-font-size-xs,12px);color:var(--text-muted)}
.qtr-qpx-products-item:hover{background:var(--surface-secondary)}
.qtr-qpx-products-item:focus-visible{outline:2px solid var(--accent-border);outline-offset:-2px}
/* A product with no destination is shown and not offered: no pointer, no hover. */
.qtr-qpx-products-item.is-inert{cursor:default;opacity:.62}
.qtr-qpx-wschip-caret{inline-size:13px;block-size:13px;opacity:.5}

/* Category headers inside the products popover. Quiet enough to be scanned past
   when you already know which group you want, and present enough to break eleven
   rows into four. */
.qtr-qpx-products-group{display:grid;gap:2px}
.qtr-qpx-products-group + .qtr-qpx-products-group{
  margin-block-start:6px;padding-block-start:6px;
  border-block-start:1px solid var(--hairline);
}
.qtr-qpx-products-cat{
  padding:4px 10px 2px;font-size:var(--qtr-font-size-xs,12px);font-weight:800;
  color:var(--text-muted);letter-spacing:.02em;
}

/* The favourites group leads the menu, so its header carries the star and no
   divider above it — there is nothing above it to divide from. */
.qtr-qpx-products-group.is-fav + .qtr-qpx-products-group{border-block-start:1px solid var(--hairline)}
.qtr-qpx-products-cat{display:flex;align-items:center;gap:6px}
.qtr-qpx-products-cat svg{inline-size:12px;block-size:12px;color:var(--accent-text)}

/* ── The daily quote in the welcome header ────────────────────────────────────
   It sits directly under the greeting and renders for every user, because the
   header is a fixed layer. Compact mode already dropped the finder; what is left
   has to read as a line of the header rather than a card that wandered up from
   the grid -- so no frame, no fill, only the rule down the starting edge that
   marks it as quoted.

   EVERY INHERITED PROPERTY IS RESTATED, not left to the base card. The first
   pass set align-items and assumed the row direction, but the card's own mobile
   rule turns .qtr-quote-meta into a COLUMN at 600px; centring on a column
   centres across, so the source came out floating in the middle of the line
   instead of sitting under the quote. Declaring flex-direction and
   justify-content here costs two lines and removes the assumption. */
.qtr-ws-hero-quotes{margin:10px 0 12px;max-inline-size:62ch}

/* Source and "another" share ONE row under the quote. Two rows for six words and
   a button is height the header cannot spare -- it is already most of the first
   screen on a phone. gap is set on both axes explicitly because is-embedded
   contributes 12px and it applied between every child. */
.qtr-ws-hero-quotes .qtr-quote-card.is-compact{
  display:flex;flex-wrap:wrap;align-items:center;gap:0 10px;
  margin:0;padding:0;border:0;background:none;box-shadow:none;border-radius:0}
.qtr-ws-hero-quotes .qtr-quote-card.is-compact:hover{border:0;box-shadow:none}

.qtr-ws-hero-quotes .qtr-quote-text{
  order:1;flex:0 0 100%;
  margin:0;padding-inline-start:12px;border-inline-start:3px solid var(--accent);
  font-size:.98rem;line-height:1.75;font-weight:600;
  color:var(--text-primary);overflow-wrap:anywhere}

.qtr-ws-hero-quotes .qtr-quote-meta{
  order:2;flex:1 1 auto;
  display:flex;flex-direction:row;flex-wrap:wrap;
  align-items:center;justify-content:flex-start;gap:8px;
  margin:6px 0 0;padding-inline-start:15px;
  font-size:.78rem;color:var(--text-muted)}
.qtr-ws-hero-quotes .qtr-quote-source{font-weight:800;color:var(--text-secondary)}
.qtr-ws-hero-quotes .qtr-quote-tags .qtr-chip{
  font-size:.7rem;padding:1px 8px;line-height:1.6}

/* The control follows the words it controls. Leading with it -- which is where
   the card's own head sits -- meant the first thing read in the greeting was a
   button. */
.qtr-ws-hero-quotes .qtr-quote-head{
  order:3;flex:0 0 auto;
  display:flex;align-items:center;justify-content:flex-end;
  margin:6px 0 0;padding:0;border:0;min-block-size:0}
.qtr-ws-hero-quotes .qtr-quote-head .qtr-btn{
  margin:0;min-block-size:26px;font-size:.75rem;padding:2px 8px;
  color:var(--text-muted)}
.qtr-ws-hero-quotes .qtr-quote-head .qtr-btn svg{inline-size:13px;block-size:13px}

/* On a phone the header is the whole first screen, so the quote gives up the tag
   chips -- category and scope are metadata about a decoration, and they were
   costing a line of the fold. */
@media (max-width:640px){
  .qtr-ws-hero-quotes{margin:8px 0 10px}
  .qtr-ws-hero-quotes .qtr-quote-text{font-size:.92rem;line-height:1.7}
  .qtr-ws-hero-quotes .qtr-quote-tags{display:none}
}

/* ══ APPEARANCE — a segmented control, not a cycling button ═══════════════════
   A recessed track with one raised segment: the shape says "these are the
   options, this is the one you are on" before a word is read. A single button
   that cycles cannot say that -- it hides both the other options and the fact
   that `auto` exists.

   Every property here is logical, so the order of the three segments follows the
   document direction without a mirrored copy. */
.qtr-ws-appearance{
  display:inline-flex;align-items:center;gap:2px;
  padding:2px;border-radius:999px;
  background:var(--surface-secondary,rgba(13,20,32,.05));
  border:1px solid var(--hairline);
  max-inline-size:100%;
}
.qtr-ws-appearance-opt{
  display:inline-flex;align-items:center;gap:5px;min-inline-size:0;
  padding:4px 10px;border:0;border-radius:999px;
  background:none;color:var(--text-muted);
  font:inherit;font-size:var(--qtr-font-size-xs,12px);font-weight:700;
  cursor:pointer;white-space:nowrap;
  transition:background-color var(--dur-fast,.15s) var(--ease,ease),
             color var(--dur-fast,.15s) var(--ease,ease);
}
.qtr-ws-appearance-opt svg{inline-size:13px;block-size:13px;flex:0 0 auto}
.qtr-ws-appearance-opt:hover{color:var(--text-primary)}
/* The raised segment. Elevation and an opaque fill, because the track sits over
   the page background and a translucent selection would read as a hover. */
.qtr-ws-appearance-opt.is-on{
  background:var(--surface-solid,var(--surface-primary,#fff));
  color:var(--text-primary);
  box-shadow:var(--elev-1,0 1px 2px rgba(13,20,32,.14));
}
.qtr-ws-appearance-opt:focus-visible{
  outline:2px solid var(--accent-border);outline-offset:1px}

/* THE LABELS STAY AT EVERY WIDTH. A first pass hid them below 420px to save the
   line -- which is the width the phone is actually at, so the control shipped as
   three unlabelled icons on the only screen most people use. A sun and a moon
   are guessable; a half-filled circle meaning "follow the system" is not, and
   the whole point of three segments over one cycling button was that the reader
   can read the choice.

   The line is not scarce anyway: .qtr-ws-context already wraps, and at 375px it
   was wrapping onto two rows before this control existed. */
.qtr-ws-appearance{flex-wrap:wrap;row-gap:2px}

/* On the Settings screen the control is the subject of its row, not a chip in a
   strip, so it gets a label above it and room around it. */
.qtr-ws-appearance-slot{
  display:flex;flex-direction:column;align-items:center;gap:6px;
  margin-block:var(--qtr-space-4,16px)}
.qtr-ws-appearance-slot small{
  font-size:var(--qtr-font-size-xs,12px);font-weight:800;color:var(--text-muted)}
.qtr-ws-appearance-slot .qtr-ws-appearance-opt span{
  position:static;inline-size:auto;block-size:auto;clip-path:none}

/* ══ DARK MODE: THE THEME THE APP CHOSE, NOT THE ONE THE OS DID ══════════════
   Found by sweeping 34 screens with a contrast audit after the appearance
   control shipped. Zero horizontal overflow on all 34; the failures were all
   colour, and all four causes are the same mistake in different clothes: a rule
   that decides what "dark" means by asking something other than the app.

   1. THE PAGE HEADER BAND. .qtr-navstd is background:rgba(255,255,255,.72) with
      its dark override inside @media (prefers-color-scheme:dark) -- the OS
      preference. Choose داكن while the OS is light and the override never fires:
      a white band with white text on it. MEASURED 1.72:1 on every screen that
      has a header. That is not "low contrast", it is unreadable.

   2. THE SEMANTIC PALETTE NEVER LEARNED ABOUT DARK. --qtr-success-text and its
      five siblings are fixed light-mode values, and the -soft backgrounds are
      literally color-mix(..., #fff). The theme engine writes 48 inline custom
      properties on <html> and none of these six is among them, so every badge,
      chip and status pill in the product kept its light-mode ink: 3.12–3.49:1.
      Because the engine does NOT set them inline, a stylesheet rule can.

   3. [data-theme="dark"] IS THE PREFERENCE, NOT THE RESULT. The engine writes
      data-theme = light | dark | system, and data-appearance = the RESOLVED
      light|dark. Rules keyed on [data-theme="dark"] therefore do not apply to
      anyone on تلقائي, however dark their screen actually is -- which is the
      default preference. Mirrored onto the resolved attribute.

   4. AN UNSTYLED ANCHOR. .qtr-ix-nav declares no colour, so it renders in the
      user-agent's #0000EE. 1.96:1 on the dark backdrop.

   Keyed on [data-appearance="dark"] throughout, because that is the attribute
   the engine sets to the resolved value -- the same one brand-tokens.css already
   uses. Values below were computed against #0D1420 and #171E2C, not picked by
   eye; the weakest scores 5.79:1 where 4.5 is required. */

:root[data-appearance="dark"]{
  --qtr-success-text:var(--qt-status-verified-text-dark);   /* 9.66 on --bg, 8.74 on surface */
  --qtr-warning-text:var(--qt-status-warning-text-dark);   /* 9.39 / 8.49 */
  --qtr-danger-text:var(--qt-status-destructive-text-dark);    /* 7.65 / 6.92 */
  --qtr-success-soft:color-mix(in srgb,#1E9E77 20%,transparent);
  --qtr-warning-soft:color-mix(in srgb,#B9821D 22%,transparent);
  --qtr-danger-soft:color-mix(in srgb,#C63F3F 20%,transparent);

  /* brand-tokens.css HAS a dark block, keyed correctly on both attributes, and
     it converts --qtr-text, --qtr-surface and --qtr-border. It stops there. The
     three below keep their light-mode values in dark, and each one shows:

       --qtr-muted-text  #636C7E  breadcrumbs and screen ids at 3.49:1
       --qtr-surface-soft #F3F5F9 STILL WHITE -- this is the background of
                                  .qtr-table th and .qtr-kpi, which is why table
                                  headers and KPI tiles stayed white islands
       --qtr-line        #E5E9F0  a light hairline drawn on a dark surface

     An incomplete dark block is harder to spot than a missing one: most of the
     screen converts, so the eye reads the leftovers as a design choice. */
  --qtr-muted-text:#9AA4B5;      /* 7.34 on --bg, 6.64 on surface, 6.02 on soft */
  --qtr-surface-soft:var(--qt-surface-soft-dark);    /* --qtr-text on it: 13.87 */
  --qtr-line:#2A3344;            /* matches --qtr-border, which brand-tokens does set */

  /* And a fourth, found only because /crm scored 1.11:1 -- near-black text on a
     near-black surface, which is not "hard to read", it is not there. .qtr-crm-tab
     paints color:var(--qtr-ink) over background:var(--qtr-surface), and the dark
     block converts the surface but not the ink.
     Safe to convert: --qtr-ink appears as `color:` five times in the stylesheets
     and as a background or border zero times, so nothing inverts. */
  --qtr-ink:#F3F5F9;             /* 13.87 on --qtr-surface */

  /* And a fifth, the twin of --qtr-ink, found only by opening the CRM tabs:
     --qtr-ink-strong is var(--qt-ink) too, and .qtr-crm-pill.ok/.info/.warn/.bad
     paint it over the semantic soft fills. In dark those fills are translucent
     tints, so near-black ink on them measured 1.17-1.30:1 -- the pipeline
     pills and the probability column were not on the screen at all.
     Same safety check as --qtr-ink: twelve uses, all `color:`, zero as a
     background or border. */
  --qtr-ink-strong:#F3F5F9;

  /* THE FOURTH TONE, AND THE LAST TWO SOFTS. I converted success, warning and
     danger and stopped, exactly as I had stopped at three badge tone classes
     before finding .wait. The set, read out of design-tokens.css rather than
     assembled from what a sweep happened to show me, is:

       --qtr-success-soft/-text   --qtr-warning-soft/-text
       --qtr-danger-soft/-text    --qtr-info-soft/-text
       --qtr-muted-text  --qtr-surface-soft  --qtr-page-soft  --qtr-accent-soft

     --qtr-info-soft stayed color-mix(#2E5FE8 14%, #fff) -- a near-white pill --
     so .qtr-crm-pill.info measured 1.12:1 once its ink went light. --qtr-page-soft
     is a light neutral used as a page-level fill. --qtr-accent-soft already
     derives from the engine's --accent-soft and needs nothing. */
  --qtr-info-text:var(--qt-status-info-text-dark);       /* 7.44 on its own soft, 8.91 on --bg */
  --qtr-info-soft:color-mix(in srgb,#2E5FE8 20%,transparent);
  --qtr-page-soft:var(--qt-surface-soft-dark);       /* matches --qtr-surface-soft */
}

/* The header band, on the app's own signal. The declarations mirror the ones
   already written for the OS-media case rather than inventing a second look. */
:root[data-appearance="dark"] .qtr-navstd{
  background:var(--shell-surface);
  border-color:var(--hairline);
}
:root[data-appearance="dark"] .qtr-ncrumb.is-current,
:root[data-appearance="dark"] .qtr-nbtn{color:var(--text-primary)}

/* product-screens.css keys its whole dark block on the preference attribute.
   Mirrored here so it reaches the people on تلقائي; same declarations, so the
   two cannot disagree about what dark looks like. */
:root[data-appearance="dark"] .qtr-card,
:root[data-appearance="dark"] .qtr-navtile,
:root[data-appearance="dark"] .qtr-btn{
  background:var(--qtr-surface);border-color:var(--qtr-border);color:var(--qtr-text);
}
:root[data-appearance="dark"] .qtr-card-h,
:root[data-appearance="dark"] .qtr-kpi,
:root[data-appearance="dark"] .qtr-table th{
  background:var(--qtr-surface-soft);border-color:var(--qtr-border);
}

/* A link that never said what colour it is. Stated for both themes, because
   #0000EE was never the design in either -- it only stopped being obvious on
   white. */
.qtr-ix-nav{color:var(--accent-text)}
:root[data-appearance="dark"] .qtr-ix-nav{color:var(--qt-status-info-text-dark)}   /* 8.91 on --bg */

/* --accent IS A FILL COLOUR, AND THESE USE IT AS INK. #1677D2 is right behind
   white button text and wrong as small text on #0D1420: measured 4.05:1 where
   4.5 is required. --accent-text exists for exactly this and, since the engine
   now mixes it away from the surface rather than always toward the ink, it
   clears comfortably in both themes.

   Named rather than swept, because `[style*=accent]` cannot be selected and a
   blanket rule would also repaint the places where accent-on-white is correct.
   These five are what the 34-screen sweep actually found; .qtr-ts-tab.active is
   one shared tab strip, which is why it appeared on eleven of them.

   NOT SCOPED TO DARK, though I first wrote it that way because dark is where I
   found it. --accent is a fill colour in BOTH themes, and an Arabic + LIGHT sweep
   put the same elements at 3.85 (.qtr-co-logo on its 14% tint) and 4.31
   (.qtr-ts-tab.active over the tab tint). --accent-text is derived per theme, so
   one unscoped rule is right for both; in light it resolves to the darker mix and
   clears at about 6.9. */
.qtr-ts-tab.active,
.qtr-ts-tab.active *,
.qtr-eyebrow,
/* NOT every .qtr-ts-badge -- only the untoned one. The badge carries its meaning
   in a class (.ok / .bad / .warn / .muted), each pairing a semantic background
   with the matching ink. Repainting all of them accent-blue put blue text on a
   green "نشطة" pill: wrong on contrast (3.87) and wrong on meaning before that.
   Found by opening the Sessions and Identity-protection sub-tabs, which is the
   only reason it surfaced -- the badge is untoned on the tab that loads.

   The exclusion list is READ FROM platform-refinements.css, not assembled from
   what the sweep happened to show me. My first attempt guessed .warn; the fourth
   tone is called .wait, and I only learned that because a template catalogue
   happened to contain one. Discovering a closed set one instance at a time is
   how you ship three passes at the same defect. */
.qtr-ts-badge:not(.ok):not(.bad):not(.wait):not(.muted),
.qtr-co-logo,
.qtr-ch-tab.active,
.qtr-ch-facet,
.qtr-sd-subtab.active,
.qtr-bp-sort.on,
.qtr-fin-base b{color:var(--accent-text)}

/* A hardcoded #6B7689 that predates the token set. 4.02:1.
   The communication centre took three passes and both mistakes are worth naming.
   First I guessed .is-active for the selected tab; the class is .active, and a
   selector that matches nothing fails silently -- the screen kept reporting after
   it was "fixed". Then I found .qtr-ch-act-w on one of its tabs and REPLACED
   .qtr-ch-mini-w with it, when the screen has four tabs and both classes are
   real. Auditing one tab and concluding about the screen is the same error as
   auditing one screen and concluding about the app. */
:root[data-appearance="dark"] .qtr-idbadge-evidence,
:root[data-appearance="dark"] .qtr-ch-mini-w,
:root[data-appearance="dark"] .qtr-ch-act-w,
:root[data-appearance="dark"] .qtr-ch-act-b small,
:root[data-appearance="dark"] .qtr-ch-soon,
:root[data-appearance="dark"] .qtr-ch-bucket h4{color:var(--text-muted)}

/* A CARD THAT CONVERTS ITS BACKGROUND AND NOT ITS TEXT. .qtr-crm-vcard sets
   background:var(--qtr-surface) -- which the dark block does convert -- and
   declares no colour at all, so its `b` and `small` go on inheriting the
   light-mode ink from an ancestor. 1.11:1, which is not low contrast: the
   figures are not on the screen.

   Converting --qtr-ink was not enough here because this card never asked for it.
   Stating the colour on the surface that owns the background is the fix; the
   children then inherit something that matches what they sit on. */
:root[data-appearance="dark"] .qtr-crm-vcard{color:var(--qtr-text)}
:root[data-appearance="dark"] .qtr-crm-vcard small{color:var(--qtr-muted-text)}

/* ══ WHAT WAS HIDING BEHIND THE TABS ═════════════════════════════════════════
   The first dark sweep audited each screen in the state it loads in. Opening
   every tab on every screen found these. All three are narrow, and only one is
   really a dark-mode bug.

   1. A DELIBERATELY WHITE CANVAS, and two lines inside it that flip.
      .qtr-cmp-stage is background:#fff -- it is the report component preview,
      and a preview of a printed report is white on purpose. Nearly everything
      inside it is built on the raw --qt-* scale, which does not flip, so it was
      right. Exactly two rules reach for themed tokens:
        .qrp-cmp-kpi-d.up   -> --qtr-success-text
        .qrp-cmp-kpi-d.down -> --qtr-danger-text
      In dark those became the light greens and reds I added earlier, on white.
      MEASURED 1.75:1. My regression, and the fix is two declarations rather than
      re-theming the canvas: pin them to the LIGHT values, because the surface
      they sit on is permanently light. */
:root[data-appearance="dark"] .qtr-cmp-stage .qrp-cmp-kpi-d.up{color:var(--qt-status-verified-text)}
:root[data-appearance="dark"] .qtr-cmp-stage .qrp-cmp-kpi-d.down{color:var(--qt-status-destructive-text)}

/* 2. Another tab strip using --accent as ink -- .qtr-sd-subtab.active, on a 12%
      accent fill that over a dark backdrop is almost the backdrop. 3.12:1. Same
      family and same fix as .qtr-ts-tab.active. */
/* folded into the unscoped list above */

/* 3. NOT A DARK-MODE BUG AT ALL. .qtr-gv-class is white on a classification
      colour passed in as an inline --gv, and white on the brand green (3.38) or
      amber (3.35) misses AA in BOTH themes -- it has been failing on the light
      theme just as long, which is why no dark-specific rule would have caught it.

      Fixed tone-agnostically: an 18% black wash under the text, over whatever
      --gv happens to be, so the hue is kept and every tone clears. Measured with
      the wash: green 4.79, amber 4.74, red 6.82, neutral 6.27, blue 7.21. 18% is
      the lightest wash that clears the worst tone -- darkening further would
      cost more colour than the contrast is worth. Not scoped to dark, because
      the defect is not. */
.qtr-gv-class{
  background-image:linear-gradient(rgba(13,20,32,.18),rgba(13,20,32,.18));
}

/* THE PREVIEW CANVAS IS PERMANENTLY WHITE, and three of its greys were picked
   for a surface that is lighter than the one they landed on. Nothing to do with
   the theme -- these fail in light mode exactly as hard, which is why no
   dark-keyed rule would ever have found them:

     .qrp-cmp-tl-item small   --qt-neutral-400 on #fff       2.51
     .qrp-cmp-kpi-l           --qt-neutral-500 on #F3F5F9    4.20
     .qrp-cmp-ghdr td         accent on --qtr-info-soft      4.40

   2.51 is the one that matters; the other two miss 4.5 by a hair and are moved
   one step down the same neutral scale rather than being recoloured. */
.qtr-cmp-stage .qrp-cmp-tl-item small{color:var(--qt-neutral-700)}
.qtr-cmp-stage .qrp-cmp-kpi-l,
.qtr-cmp-stage .qrp-cmp-kpi-v span{color:var(--qt-neutral-700)}
.qtr-cmp-stage .qrp-cmp-ghdr td{color:var(--qt-status-info-strong)}

/* .qtr-empty is a page-level empty state -- muted grey on the app background,
   where it is correct. Inside the permanently-white preview canvas it is that
   same grey on #fff: 2.51:1. The placement changed, the colour did not. */
.qtr-cmp-stage .qtr-empty{color:var(--qt-neutral-700)}

/* TWO STACKED ACCENT TINTS. .qtr-gs-kbd is the ⌘K keycap in global search: it
   paints accent ink on a 12% accent fill, inside .qtr-gs-item.sel which is
   itself accent-tinted. Two translucent tints of the same hue, one on the other,
   land at 4.49:1 -- and this sits in the header, so it was on every screen the
   sweep visited. A keycap should read as a solid key anyway. */
:root[data-appearance="dark"] .qtr-gs-kbd{
  background:var(--qtr-surface);border-color:var(--qtr-border);
}

/* .qtr-bp-sort.on -- the active sort column. Raw --accent as ink again, 3.16:1;
   the same mistake as .qtr-ts-tab.active, .qtr-ch-tab.active and .qtr-sd-subtab,
   which is now four independent places that reached for the fill colour when
   they wanted the ink one. */
/* folded into the unscoped list above */

/* ══ A BACKGROUND TOKEN USED AS INK, IN SIX PLACES ═══════════════════════════
   .qtr-idbadge-state reads color:var(--qtr-success-soft) over a 13% green fill.
   The -soft tokens are FILLS. In light mode --qtr-success-soft is
   color-mix(#1E9E77 14%, #fff) -- a near-white -- so this badge has been near
   invisible on the light theme since it was written. In dark, where I made the
   soft tokens translucent so they would tint rather than block, the same line
   became 20%-alpha text: MEASURED 1.33:1. The word "فعّالة" is not on the screen.

   Six selectors do it, found by grepping for the pattern rather than by finding
   them one at a time:
     .qtr-idbadge-state, .qtr-idbadge-state.rev
     .qtr-id-dcard-badge.ok, .qtr-id-dcard-badge.rev
     .qtr-crm-col.is-won, .qtr-crm-col.is-lost
   All six want the -text token. Not scoped to dark: the defect is in both. */
.qtr-idbadge-state,
.qtr-id-dcard-badge.ok,
.qtr-crm-col.is-won{color:var(--qtr-success-text)}
.qtr-idbadge-state.rev,
.qtr-id-dcard-badge.rev,
.qtr-crm-col.is-lost{color:var(--qtr-danger-text)}

/* AN UNSTYLED NATIVE BUTTON, the sibling of the unstyled anchor fixed earlier.
   .qtr-crm-crumbs button declares no background at all, so it renders in the
   user agent's buttonface -- rgb(239,239,239) -- with muted ink on it: 2.19:1 in
   dark, and a raw grey chip sitting among styled crumbs in light. Neither theme
   ever asked for that; nobody noticed because light grey on light grey merely
   looks plain. Made to match the crumbs beside it. */
.qtr-crm-crumbs button{
  appearance:none;background:none;border:0;padding:2px 5px;
  font:inherit;color:var(--text-muted);cursor:pointer;border-radius:7px;
}
.qtr-crm-crumbs button:hover{color:var(--text-primary);background:var(--surface-secondary)}
.qtr-crm-crumbs button:focus-visible{outline:2px solid var(--accent-border);outline-offset:1px}

/* ══ THE WRAP THAT COULD NEVER FIRE, AGAIN ═══════════════════════════════════
   MEASURED in ENGLISH on /platform/qrp, Scheduling & Distribution: the page
   overflows by 237px, and 342px on the Delivery-queue sub-tab. Arabic is clean,
   because "الجدولة والتوزيع" is shorter than "Scheduling & Distribution" and
   "قوائم التوزيع" than "Distribution lists". Same shape as the /approvals
   overflow found earlier in this engagement, and the same cause.

   .qtr-sd-subnav already declares flex-wrap:wrap. It has never once wrapped:
   .qtr-sd is a flex column, so the subnav is a flex ITEM, and a flex item
   defaults to min-width:auto -- it may not become narrower than its content. A
   line only wraps once its container is narrower than the content, and a
   container that cannot shrink never gets there. The declaration and the default
   contradict each other, and the default wins silently.

   min-inline-size:0 on the chain, so the wrap the author already asked for can
   happen. This is the same one-line fix as .qtr-ap-row-tags and the same one
   section 8 applies to the admin/console bodies -- .qtr-sd was simply not in
   that list. */
.qtr-sd, .qtr-sd > *, .qtr-sd-subnav{min-inline-size:0}
.qtr-sd-subnav{max-inline-size:100%}
/* The pills themselves keep their whole label; they wrap onto the next line
   rather than truncating, because a schedule tab that reads "Distribution l…"
   is not a label. */
.qtr-sd-subtab{flex:0 1 auto;min-inline-size:0}

/* ── The other three English-only overflows, same family ─────────────────────
   All measured in ENGLISH at 375px, all clean in Arabic, all the min-width:auto
   default beating an author's intent.

   /platform/verification, Tokens (+123px). .qtr-vf-token-hd is a plain flex row
   with a gap and no wrap and no shrink; an English token label simply runs past
   the edge. */
.qtr-vf-token-hd{flex-wrap:wrap;min-inline-size:0}
.qtr-vf-token-hd > *{min-inline-size:0}

/* History (+256px). .qtr-ts-row is inline-size:100% with .qtr-ts-row-main
   already carrying min-inline-size:0 -- the author knew. The row itself and its
   OTHER children were left at auto, so the row grew instead of the main column
   absorbing. */
.qtr-ts-row{min-inline-size:0;flex-wrap:wrap}
.qtr-ts-row > *{min-inline-size:0}

/* Analytics (+90px). .qtr-ts-bar is a three-column grid whose last track is
   `auto`; grid items default to min-width:auto, so the value column refuses to
   compress and takes the track with it. The 720px rule already collapses this
   grid -- it just never got the chance, because the overflow happens before the
   breakpoint can help. */
.qtr-ts-bar > *{min-inline-size:0}
.qtr-ts-bar-l{min-inline-size:0;overflow-wrap:anywhere}

/* /platform/communication, Preferences (+60px). .qtr-ch-toggle is
   space-between: label on one side, switch on the other. .qtr-switch is
   correctly flex:none -- a switch that shrinks is a broken switch -- so the
   LABEL is what has to give, and it was never told it could. */
.qtr-ch-toggle{min-inline-size:0}
.qtr-ch-toggle > :not(.qtr-switch){min-inline-size:0;overflow-wrap:anywhere}

/* /finance, Analytics (+239px in English). .qtr-hcm-stats is
   repeat(auto-fit,minmax(140px,1fr)) -- the 140px floor is fine, and auto-fit
   would drop to one column happily. What breaks it is the CARD: .qtr-hcm-stat is
   a flex column whose `b` and `small` hold long English figures and labels, and
   a grid item defaults to min-width:auto, so the card refuses to go below its
   longest line and drags the whole track past 140px. The floor was never the
   problem; the ceiling on shrinking was. */
.qtr-hcm-stats{min-inline-size:0}
.qtr-hcm-stat{min-inline-size:0}
.qtr-hcm-stat > *{min-inline-size:0;overflow-wrap:anywhere}

/* A PHANTOM 167px, AND THE ONE REAL 8px INSIDE IT.
   /platform/verification › History reported a 167px overflow after the row fix.
   It is not there: no element's box extends past the viewport, and setting
   document.scrollLeft to 500 leaves it at 0 -- the page does not scroll. The
   number came from scrollWidth - clientWidth, which counts clipped content, and
   .qtr-ts-row-main small is deliberately overflow:hidden + text-overflow:ellipsis
   holding a long English metadata line. Clipped content is not overflow, and I
   briefly "fixed" that ellipsis into a wrap before checking whether the page
   scrolled at all. Reverted: the truncation is the author's decision.

   What IS real is 8px wide: the `b` is a verification id, QRV-05AFB86A1013, one
   unbroken monospace token in a 94px box with no break opportunity and no
   clipping, so it spills over its neighbour. Told it may break mid-string.

   THE METRIC IS THE LESSON. scrollWidth alone reports overflow that nobody can
   see or scroll to; an element whose right edge passes the viewport is the
   signal that means something. The qrp / communication / finance overflows fixed
   above all had that element -- this one never did. */
.qtr-ts-row-main b{overflow-wrap:anywhere}

/* ══ A SCROLL CONTAINER THAT CANNOT SHRINK DOES NOT SCROLL — IT GROWS ════════
   This is section 7's rule coming back on me. I gave tables a 560px floor at
   ≤640px and, knowing the floor is only safe if something scrolls, added
   :has(> table) wrappers. What I never did was let those wrappers get narrower
   than their content. overflow-x:auto only produces a scrollbar once the box is
   narrower than what is in it; a flex or grid child at min-width:auto never gets
   there, so it widens to fit the 560px table and takes the page with it.

   MEASURED in ENGLISH at 375px, with the corrected metric (an element's box past
   the viewport, not scrollWidth):

     /platform/users      .qtr-admin-table   +340px
     /platform/licenses   .qtr-admin-table   +340px
     /platform/qrp        .qrp-cmp-table     +233px
     /platform/admin      .qtr-admin-tab     +225px
     /platform/identity   .qtr-id-navitem    +298px
     /platform/authorization .qtr-authz-navitem +224px

   Every declared horizontal scroller in the codebase, listed by grepping for
   overflow-x:auto rather than by collecting the ones a sweep happened to show
   me -- eight of them, none allowed to shrink. The nav strips are the same
   story: .qtr-authz-nav and .qtr-id-nav become horizontal scrollers at their
   breakpoints and then cannot compress, so their items march off the screen. */
.qtr-admin-table-wrap, .qtr-admin-tabs, .qtr-authz-nav, .qtr-id-nav,
.qtr-bp-tablewrap, .qtr-crm-tablewrap, .qtr-fin-tablewrap, .qtr-ts-tablewrap,
.qtr-admin-body *:has(> table), .qtr-ts *:has(> table),
.qtr-home *:has(> table), .qtr-card:has(> table), .qtr-panel:has(> table){
  min-inline-size:0;
  max-inline-size:100%;
}
/* The preview canvas scrolls its own table; it is already overflow-x:auto. */
.qtr-cmp-stage{min-inline-size:0;max-inline-size:100%}

/* ══ A PERMANENTLY DARK CARD, IN A THEME THAT MAY BE LIGHT ═══════════════════
   The mirror image of .qtr-cmp-stage. .qtr-idbadge paints
   radial-gradient(... #171E2C ...) and sets color:#fff -- it is an identity card
   and it is dark in BOTH themes, on purpose. Its children then reach for theme
   tokens, which in the light theme are dark inks meant for white paper:

     .qtr-idbadge-state    --qtr-success-text #177A5C on the card   1.78:1
     .qtr-idbadge-evidence hardcoded #6B7689                        2.26:1
     dt                    --text-muted                             4.13:1

   Found by sweeping in ARABIC + LIGHT after a day of dark-mode work -- the
   light theme had never been put through this instrument, and three of my own
   fixes were scoped to [data-appearance="dark"] and so never reached here.

   Pinned to the dark-side inks regardless of theme, because the surface is.
   Values measured against the card's own two backgrounds, (55,73,77) under the
   badge tint and (59,64,73) on the card: the weakest below is 4.71. --text-muted
   at 3.76 was under AA on this card even in dark mode, so it is lifted for both
   rather than only for light. */
.qtr-idbadge .qtr-idbadge-state,
.qtr-idbadge .qtr-id-dcard-badge.ok{color:var(--qt-badge-verified-text-dark)}          /* 6.23 / 6.87 */
.qtr-idbadge .qtr-idbadge-state.rev,
.qtr-idbadge .qtr-id-dcard-badge.rev{color:var(--qt-badge-destructive-text-dark)}
.qtr-idbadge .qtr-idbadge-evidence,
.qtr-idbadge dt,
.qtr-idbadge small{color:var(--qt-neutral-text-muted-dark)}                            /* 4.27 / 4.71 */

/* THE SECOND PASS ON THE SAME CARD, and the same mistake as the badge tones and
   the semantic set before it: I listed the three the sweep showed me instead of
   reading the family. .qtr-idbadge-* has thirty-two members; these are the ones
   that carry dimmed or haloed ink on the dark card, and they only surfaced once
   the audit started folding element opacity into the ink -- .qtr-idbadge-org is
   opacity:.82 over --qid-halo, which the previous measurement read at full
   strength. Light: 4.47 / 3.83 / 4.34.

   The dimming goes rather than the colour: on a card this dark, an 18% veil over
   an already-quiet ink buys nothing the smaller type has not already said. */
.qtr-idbadge .qtr-idbadge-org,
.qtr-idbadge .qtr-idbadge-kind,
.qtr-idbadge .qtr-idbadge-eyebrow,
.qtr-idbadge .qtr-idbadge-issuer,
.qtr-idbadge .qtr-idbadge-role{opacity:1;color:var(--qt-neutral-text-strong-dark)}      /* 5.73 / 6.32 */

/* ── Two more from the Arabic + LIGHT sweep, both self-referential tints ─────
   .qtr-co-logo.is-empty paints the company's own --co-accent as INK on a 14%
   tint of that same colour. For any mid-tone brand colour that cannot work:
   #1677D2 on its own tint measured 3.85:1. Derived the same way --accent-text
   is, but from the company's colour, so it stays the company's blue and simply
   moves away from the surface -- and because --text-primary flips with the
   theme, it darkens in light and lightens in dark without a second rule. */
.qtr-co-logo.is-empty{
  color:color-mix(in srgb,var(--co-accent,var(--accent)) 72%,var(--text-primary) 28%);
}

/* .qtr-sec-threat.ok is already a light green banner, and the `em` inside it
   adds ANOTHER 13% green on top before painting --qtr-success-text on it: two
   tints of one hue stacked, 3.84:1. The same shape as the ⌘K keycap in dark.
   The banner is the tint; the figure inside it does not need its own. */
.qtr-sec-threat b em{background:none;opacity:1}

/* And a limit of my own instrument, worth writing down where the fix is:
   .qtr-sec-threat b em also carries opacity:.72, and the audit reads
   getComputedStyle().color without multiplying by the element's opacity. The
   real ratio here was about 2.4, not the 3.84 I reported -- my measurement was
   FLATTERING the defect. The de-emphasis survives in the smaller size and the
   pill shape; it does not need to be dimmed on top of an already-muted ink. */

/* ══ DIMMING TEXT WITH OPACITY IS A CONTRAST BUG WAITING FOR A LIGHT THEME ═══
   Found by sweeping ARABIC + LIGHT across the 24 screens the earlier run never
   reached. Ten screens reported, and every single failure had the same cause:

     .qtr-ts-policy.off        opacity .55   2.64:1
     .qtr-fin-none             opacity .40   2.47:1
     .qtr-fin-note             opacity .72   3.00:1
     .qtr-fin-signal small     opacity .70   3.44:1
     .qtr-fin-sub              opacity .55   3.78:1
     .qtr-fin-bigstat small    opacity .60   4.35:1
     studio dismiss button     opacity .50   3.26:1

   Dimming is the codebase's idiom for "secondary", and on a light theme it moves
   a dark ink TOWARD the background -- it lowers contrast precisely where
   secondary text still has to clear 4.5. A colour token can be chosen to meet a
   contrast target; an opacity cannot, because what it lands on changes.

   SIXTY-SEVEN rules in the stylesheets dim this way. I am NOT rewriting all of
   them: many are on icons, keyframes, disabled controls, or revoked cards where
   the fade IS the meaning. These are the ones measured below AA on text. The
   systemic fix is a --text-tertiary token, which is a design-system decision and
   not mine to impose across sixty-seven rules unasked. */
.qtr-ts-policy.off,
.qtr-lic-grant.off,
.qtr-tpl-prov.off,
.qtr-fin-note,
.qtr-fin-sub,
.qtr-fin-signal small,
.qtr-fin-bigstat small,
.qtr-fin-ipill small,
.qtr-lv-fact small,
.qtr-lv-field > span,
.qtr-att-times{opacity:1;color:var(--text-secondary)}

/* The em-dash placeholder stays the quietest thing on the row, but a value a
   reader cannot see is not a placeholder, it is a gap. */
.qtr-fin-none{opacity:1;color:var(--text-muted)}

/* A dismiss button dimmed to half is a control, not a caption. */
.qtr-studio-topbar-actions .qtr-btn{opacity:1}

/* Two more green-on-green tints, the same shape as .qtr-sec-threat b em:
   .qtr-trust-badge is --qtr-success-text on a success tint (4.26), and the
   documents badge lands at 2.88 the same way. */
.qtr-trust-badge{background:var(--surface-secondary)}

/* ── The same family, with the selectors READ OFF THE PAGE this time ─────────
   My previous block named .qtr-ts-policy.off, .qtr-studio-topbar-actions .qtr-btn
   and .qtr-trust-badge from the audit's class labels. Four of them matched
   nothing, because a label is not a selector: the trust factor is `li.off` inside
   .qtr-trust-factors, the studio control is .qtr-btn.ghost.sm, and the table
   header is a bare `th` with opacity on it. Chains taken from the live DOM. */
.qtr-trust-factors li.off:not(:disabled){opacity:1;color:var(--text-secondary)}
/* NOT :disabled. Undimming a disabled control would delete the only affordance
   that says it cannot be pressed -- and WCAG exempts inactive components from
   the contrast minimum for exactly that reason. The studio's Save button is
   disabled when there is nothing to save; my audit flagged it at 3.26 and it was
   right about the number and wrong about it being a defect. Every opacity
   override in this file carries the same guard. */
.qtr-studio-topbar-actions .qtr-btn:not(:disabled){opacity:1}
.qtr-bp-stats .qtr-bp-stat small:not(:disabled){opacity:1;color:var(--text-secondary)}

/* Table headers dimmed to .6. A column heading is the label a reader scans to
   find the column -- it is not decoration, and section 5 of this file already
   gives thead th a weight and a colour that assume it will be read. */
.qtr-ts thead th, .qtr-admin thead th, .qtr-fin thead th,
.qtr-bp thead th, .qtr-crm thead th{opacity:1}   /* headers are never disabled */

/* .qtr-trust-badge is --qtr-success-text on a success tint, 4.26 -- the third
   green-on-green in this codebase after .qtr-sec-threat and the documents badge.
   The badge already carries a border and a level class; it does not need the
   tint to say "good" as well. */
.qtr-trust-badge.lvl-high,
.qtr-trust-badge{background:var(--surface-secondary)}

/* .qtr-trust-hero .qtr-eyebrow and .qtr-sig-hero .qtr-eyebrow are (0,2,0) and
   set color:var(--accent) directly, so the unscoped .qtr-eyebrow rule above --
   (0,1,0) -- never reached them. Two more places reaching for the fill colour
   when they wanted the ink one; 3.05:1 in dark, 4.31 in light. Matched. */
.qtr-trust-hero .qtr-eyebrow,
.qtr-sig-hero .qtr-eyebrow{color:var(--accent-text)}

/* The identity card, third pass -- and this time the floor rather than a name.
   A bare <p> inside .qtr-idbadge-verify-body inherits a muted token from
   elsewhere and lands at 3.80 on the dark card. It has no class, so no amount of
   listing .qtr-idbadge-* members would ever have caught it: the first pass named
   three, the second read the family of thirty-two, and the thing that slipped
   through belonged to neither list.

   So: a floor for UNCLASSED text on this card. :where() carries zero
   specificity, so every rule above -- the name, the kind, the state badge --
   still wins; this only reaches the elements nobody styled. */
.qtr-idbadge :where(p,span,small,li,dd):not([class]){color:var(--qt-neutral-text-strong-dark)}

/* ══ --text-tertiary, AND THE END OF DIMMING TEXT WITH OPACITY ═══════════════
   Requested explicitly after the sweeps kept finding the same defect. 133 rules
   in this codebase dim something with an opacity between .35 and .90. They are
   NOT all the same thing, and treating them as one list is how you delete an
   affordance while fixing a contrast score.

   THE TOKEN. Chosen against the worst surface each theme actually puts it on,
   not against the page:

     light  var(--qt-neutral-text-tertiary)   white 5.50 · soft 5.04 · green tint 4.70 · amber tint 4.65
     dark   var(--qt-neutral-text-secondary-dark)   bg 6.77 · surface 6.12 · soft 5.55 · tint 5.08

   #8A93A5 was the obvious dark candidate and is rejected: 4.48 on a tinted
   surface. Under the line is under the line.

   WHAT IS CONVERTED: text that a reader is expected to read -- captions, hints,
   sub-labels, table headers, metadata, empty-state copy. opacity:1 and the token.

   WHAT IS DELIBERATELY LEFT DIMMED, because there the fade IS the message:
     [disabled] :disabled .is-disabled .qtr-nav-disabled  -- the affordance
     .revoked .expired .st-revoked .st-expired .is-hidden -- lifecycle state
     .dropped .is-inert .done .resolved .loading          -- row/item state
     .is-dragging .dragging                               -- drag feedback
     svg, i, ::before, ::after, ::placeholder, keyframes  -- not text
     .qtr-glass-preview .qtr-motion-preview .qtr-login    -- previews and art
     .qtr-nsep .qtr-nav-crumb-sep .qtr-sec-shape          -- separators
   WCAG exempts inactive components and decoration from the contrast minimum for
   exactly this reason. */
:root{--text-tertiary:var(--qt-neutral-text-tertiary)}
:root[data-appearance="dark"]{--text-tertiary:var(--qt-neutral-text-secondary-dark)}

.qtr-fin-base, .qtr-fin-bigstat em, .qtr-fin-bigstat small, .qtr-fin-ctx-cfg,
.qtr-fin-ipill small, .qtr-fin-none, .qtr-fin-note, .qtr-fin-sec span,
.qtr-fin-signal small, .qtr-fin-signal-val, .qtr-fin-sub, .qtr-fin-table th,
.qtr-fin-arrow,
.qtr-bp-banner small, .qtr-bp-code, .qtr-bp-field > span, .qtr-bp-h4,
.qtr-bp-hint, .qtr-bp-kv dt, .qtr-bp-mergeside small, .qtr-bp-none,
.qtr-bp-pager, .qtr-bp-stat small, .qtr-bp-sub, .qtr-bp-table th,
.qtr-hcm-ecard-h span, .qtr-hcm-item-t small, .qtr-hcm-stat small,
.qtr-lv-360head p, .qtr-lv-bal-big small, .qtr-lv-bal-meta, .qtr-lv-fact small,
.qtr-lv-field > span, .qtr-lv-mid small, .qtr-lv-note,
.qtr-att-emp small, .qtr-att-times,
.qtr-ap-appr em, .qtr-ap-diff-from, .qtr-ap-req,
.qtr-doc-meta span, .qtr-doc-qr figcaption, .qtr-doc-qr-none,
.qtr-id-dcard-info small, .qtr-id-dcard-kind, .qtr-id-field.inline span,
.qtr-ch-facet em, .qtr-perm .qtr-perm-key,
.qtr-ts-360-hero p, .qtr-ts-hgo,
.qtr-tr-card-badge .mono, .qtr-wallet-badge-tx small,
.qtr-admin-chip.muted, .qtr-qrp-chan.muted,
.qtr-eff-row, .qtr-stat-go, .qtr-ix-denied,
.qtr-exp-cat[data-collapsed] .qtr-exp-cat-head,
.qtr-support-left, .qtr-ws-prod-fav, .qtr-studio-tray-add, .qtr-qpx-pin-x,
.qtr-widget-toggle[aria-pressed="false"],
.qtr-header-overflow .qtr-overflow-group{
  opacity:1;
  color:var(--text-tertiary);
}

/* The state-bearing `off` labels keep the STRONGER secondary set earlier today:
   they say a thing is switched off, which is worth more than a tertiary caption.
   Listed here so a later reader does not "unify" them into the block above. */
.qtr-ts-policy.off, .qtr-lic-grant.off, .qtr-tpl-prov.off,
.qtr-trust-factors li.off, .qtr-lv-day.off{opacity:1;color:var(--text-secondary)}

/* ══ Three from the ar/light quadrant of the full matrix ═════════════════════

   1. MY OWN REGRESSION, one commit old. .qtr-wallet-badge is a permanently dark
      button -- the Apple/Google wallet chip -- and I put .qtr-wallet-badge-tx
      small into the --text-tertiary list. In the LIGHT theme that token resolves
      to var(--qt-neutral-text-tertiary), a dark ink, and it landed on a near-black button: 3.36:1.
      Third instance of the same shape today after .qtr-cmp-stage (permanently
      white) and .qtr-idbadge (permanently dark). A surface that ignores the
      theme needs ink that ignores it too. */
/* .apple ONLY. There are two wallet chips and they are opposites:
   .qtr-wallet-badge.apple is near-black, .qtr-wallet-badge.google is
   background:#fff. My first version of this fix said .qtr-wallet-badge and would
   have put a light grey on the white one -- catching a regression by writing a
   second one. Caught by grepping the rule instead of trusting the diagnosis. */
.qtr-wallet-badge.apple,
.qtr-wallet-badge.apple .qtr-wallet-badge-tx,
.qtr-wallet-badge.apple .qtr-wallet-badge-tx small{color:var(--qt-neutral-text-strong-dark);opacity:1}
.qtr-wallet-badge.apple b{color:#F3F5F9}
/* The white chip keeps its own dark ink; it only needed the dimming lifted. */
.qtr-wallet-badge.google .qtr-wallet-badge-tx small{opacity:1;color:var(--text-tertiary)}

/* 2. .qtr-doc-provider.off dims its whole card to .6, and the badge inside it
      reads "معطّل" at 2.88:1 -- the word that tells you the provider is off is
      the one you cannot read. The state survives in the badge's own text and in
      the card's border; it does not need the card to be half-invisible. */
.qtr-doc-provider.off{opacity:1}
.qtr-doc-provider.off b{color:var(--text-secondary)}
.qtr-doc-provider.off small{color:var(--text-tertiary)}

/* 3. .qtr-ch-facet.active is (0,2,0) and sets color:var(--accent) -- the
      unscoped .qtr-ch-facet rule at (0,1,0) never reached the active one. Raw
      accent on its own 12% tint: 4.31 light. Seventh place in this codebase that
      reaches for the fill colour when it wants the ink one. */
.qtr-ch-facet.active{color:var(--accent-text)}

/* ══ The full 4-quadrant matrix: 152 screen-checks, 764 panels, 3 defects ════
   Arabic and English returned IDENTICAL numbers on every screen, which is the
   measured form of something the code structure implies: contrast depends on the
   theme, not the language. The languages differ on layout, and there was no
   overflow anywhere in the 152.

   1. .qtr-ch-toggle.is-disabled fades the whole row to .6, and inside it sits an
      em reading "قريباً" -- the word that EXPLAINS the disabling is the one you
      cannot read: 2.46 light, 3.29 dark. .qtr-ch-toggle-l falls with it at 4.42.
      This is a <label>, not a disabled control: nothing here is inert, the
      feature simply does not exist yet. The switch beside it and the badge both
      already say so. Same call as .qtr-doc-provider.off earlier. */
.qtr-ch-toggle.is-disabled{opacity:1}
.qtr-ch-toggle.is-disabled .qtr-ch-toggle-l{color:var(--text-secondary)}
.qtr-ch-toggle.is-disabled .qtr-ch-soon{color:var(--text-tertiary)}

/* 2. MY REGRESSION, and the third time today on this one family.
      .qtr-wallet-badge.google is background:#fff in BOTH themes -- it is a brand
      asset, not a themed surface -- and I handed it --text-tertiary, which
      resolves to var(--qt-neutral-text-secondary-dark) in dark: 2.73 on white. I had already learned this rule
      twice today (.qtr-cmp-stage always white, .qtr-idbadge always dark) and
      still reached for a theme-derived token on a theme-independent surface.
      Written as a literal so no future theme change can move it. */
.qtr-wallet-badge.google .qtr-wallet-badge-tx small{color:var(--qt-neutral-text-tertiary)}

/* ── 320px: three screens scrolled the PAGE sideways ──────────────────────────
   Found by measuring document.scrollWidth - clientWidth per screen, not by
   looking: /platform/licenses 54px, /platform/signatures 25px, /organization
   14px. No element was wider than the viewport in any of them, which is the
   tell -- the overflow came from min-content floors that refuse to shrink, the
   same min-inline-size:auto family already fixed for the declared scrollers.

   .qtr-org-cols asked for minmax(320px,1fr) inside a 292px container, so the
   track could never fit. min(320px,100%) keeps the intended column width and
   lets it collapse when there is less room, which is what auto-fit needs.

   .qtr-sig-row is a GRID ITEM; its own min-width:auto was the floor, not its
   ancestors' -- I fixed .qtr-sig/-panel/-list first and the 25px did not move.

   The rows then needed wrapping, not just shrinking. My first attempt let
   .qtr-lic-row-tags shrink instead, which drove .qtr-lic-row-main to ZERO --
   the licence title vanished entirely. Measured 0px, reverted. Below 420px both
   row families wrap so the title keeps a readable line and the tags take their
   own: the signature title goes 78px clipped -> 178px whole, the licence title
   0px -> 176px whole. Verified at 320 and 360, both themes: no page scroll on
   any of the 22 screens, and no text clipped that was not clipped before. */
.qtr-sig, .qtr-sig-panel, .qtr-sig-list, .qtr-sig-row{min-inline-size:0}
.qtr-sig-row-main small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.qtr-org-cols{grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr))}

@media (max-width:420px){
  .qtr-sig-row{flex-wrap:wrap;row-gap:8px}
  .qtr-sig-row-main{flex:1 1 150px}
  .qtr-lic-row{flex-wrap:wrap;row-gap:8px}
  .qtr-lic-row-main{flex:1 1 150px}
  .qtr-lic-row-tags{flex:1 1 auto;justify-content:flex-start}
}

/* ── the icon strip sat ON TOP of the logo ────────────────────────────────────
   Reported from a phone screenshot, then measured: #qtr-studio overlapped
   .qtr-brand by 38x44px at 320 - covering it almost exactly, which is why the
   logo read as "a menu over the logo" rather than as a missing logo.

   platform-shell.css gives .qtr-header-context flex:0 1 calc(100% - 280px)
   below 560px. The 280px is the icon strip's assumed width. At 320 the content
   box IS 280, so the basis computes to 0: the brand's box collapses, the 50px
   brand overflows it, and the icons - siblings via display:contents - start at
   the same x. At 375 it survives on 5px of slack, which is not a margin.

   Sized from its own content instead. That alone pushed #qtr-more onto a second
   row and grew the header 127 -> 183px, so the gap and inline padding tighten
   below 400px to buy the 36px back rather than shrinking the buttons: they are
   already 40px, under the 44px touch-target guidance, and should not go lower.

   Measured after: 320/360/375 all one icon row, header back to 127px, brand
   50px and unobstructed, no page overflow. 768 is untouched - gap 12, padding
   20, brand 184px with its wordmark. */
@media(max-width:560px){
  .qtr-header-context{flex:0 0 auto;max-width:100%}
}
@media(max-width:400px){
  .qtr-header{column-gap:8px;padding-inline:12px}
}

/* ── .qtr-action label text spilled across its neighbours ─────────────────────
   Reported from a screenshot of /settings: "استوديو مساحة العمل", "اللغة" and
   "تثبيت شريط التنقل" printed on top of one another. The boxes did not overlap
   - each was exactly 44px - the TEXT did, because the buttons were 44px wide
   with white-space:nowrap and overflow:visible.

   platform-shell.css collapses .qtr-action to a square icon button at <=1080px
   and again at <=560px, and hides its <span>. That is header chrome: of the 42
   .qtr-action instances in this codebase, exactly ONE (#qtr-quick) is in the
   header. The other 41 are labelled buttons in page bodies - settings, the
   organization centre, org gates - and they were being squashed too. The
   settings three showed it worst because their label is a bare text node, not
   a <span>, so display:none never reached it and the text just overflowed.

   Scoped back to the shell so page content keeps its labels. #qtr-quick is
   outside .qtr-shell and still collapses exactly as before.

   Measured after, Arabic and English: at 1024 the three settings buttons go
   44px -> 217/92/192px with no box or text overlap; at 320 they wrap, nothing
   is clipped, and /settings and /organization both report zero page overflow. */
@media(max-width:1080px){
  .qtr-shell .qtr-action{width:auto;padding:0 14px}
  .qtr-shell .qtr-action span{display:inline}
}
@media(max-width:560px){
  .qtr-shell .qtr-action{width:auto;padding:0 12px;height:auto;min-height:40px}
}

/* ── fixed grid minimums wider than a 320px content column ────────────────────
   Found walking tab panels at 320: the "policies" and "authorities" panels of
   /platform/timestamps each scrolled the page 13px. The culprit is the same one
   .qtr-org-cols had - repeat(auto-fill, minmax(300px,1fr)) inside a 254px
   content box, so the track can never fit and the overflow propagates up.

   Those two were the only ones the sweep could see, because a tab panel only
   renders when you open it. Fifteen grid families in platform-refinements.css
   declare a minimum of 260px or more, and at 320 the content column is 254 -
   every one of them is the same bug waiting for its screen to be visited. So
   this is the whole family, not the two that happened to show.

   min(Npx,100%) keeps the intended column width wherever there is room and lets
   the track collapse where there is not, which is what auto-fill/auto-fit needs
   to wrap instead of overflow. .qtr-org-cols already carries this fix above.

   Measured after, at 320 in dark: both timestamp panels drop from 13px of page
   scroll to 0, with no contrast, spill or text-overflow findings, and the cards
   still read at full width. */
.qtr-role-grid{grid-template-columns:repeat(auto-fill,minmax(min(260px,100%),1fr))}
.qtr-co-grid{grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr))}
.qtr-ws-grid{grid-template-columns:repeat(auto-fill,minmax(min(320px,100%),1fr))}
.qtr-qa-page-recs{grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr))}
.qtr-ts-grid{grid-template-columns:repeat(auto-fill,minmax(min(320px,100%),1fr))}
.qtr-ts-policies{grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr))}
.qtr-ts-auths{grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr))}
.qtr-vf-tokens{grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr))}
.qtr-cmp-gallery{grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr))}
.qtr-ch-ov-grid{grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr))}
.qtr-ch-prefs{grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr))}
.qtr-org-prods{grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr))}
.qtr-crm-grid{grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr))}

/* ── placeholders fell back to the browser's own grey ─────────────────────────
   The whole platform declared ::placeholder exactly twice -- .qtr-search input
   and .qtr-sec-verify input. Every other field inherited Chrome's default,
   rgb(117,117,117), which is drawn for a white page: 3.62 on the dark surface,
   under the 4.5 AA minimum. Caught on /platform/templates once the audit learned
   to read placeholders at all; a placeholder is an attribute, not a text node,
   so no sweep before this one had ever looked at one.

   --text-muted is the token the two declared rules already use, and it measures
   6.64 on the same dark surface. Applied to every field in the shell rather
   than to the three that happened to be on screen: the fields that surfaced are
   the ones whose panel was open, not the ones at risk.

   The second rule is the same defect wearing a different hat. opacity:.45 on
   the pseudo drops --text-muted to 2.41 in dark -- worse than the browser
   default it was written to improve on. The letter-spacing is the deliberate
   part of that rule and stays; the dimming goes. (My own placeholder check read
   the pseudo's colour but not its opacity, and would have passed this at 6.64.
   Fixed in the harness before measuring.) */
.qtr-shell input::placeholder,
.qtr-shell textarea::placeholder,
.qtr-header input::placeholder{color:var(--text-muted);opacity:1}
.qtr-sec-verify input::placeholder{opacity:1}

/* ── Owner Portal: customers ──────────────────────────────────────────────────
   Filters, wizard and the "not available" marker. Everything here uses the
   theme tokens so the surface follows light/dark and RTL without a second
   stylesheet, and no state is carried by colour alone -- every status chip in
   customers.js ships an icon beside its tone. */
.qtr-cust-filters{display:flex;flex-wrap:wrap;gap:10px;margin:14px 0}
.qtr-cust-f{display:flex;flex-direction:column;gap:4px;font-size:.78rem;font-weight:700;
  color:var(--text-secondary);min-inline-size:0}
.qtr-cust-f.grow{flex:1 1 220px}
.qtr-cust-f .qtr-id-input{min-inline-size:0;inline-size:100%}
.qtr-na{color:var(--text-tertiary);font-style:italic;font-size:.82em}

.qtr-cust-pager{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:12px;
  font-size:.82rem;color:var(--text-secondary)}

.qtr-cust-steps{display:flex;flex-wrap:wrap;gap:8px;list-style:none;padding:0;margin:14px 0}
.qtr-cust-steps li{display:flex;align-items:center;gap:6px;font-size:.78rem;font-weight:700;
  color:var(--text-tertiary);padding:6px 10px;border-radius:999px;border:1px solid var(--border-subtle)}
.qtr-cust-steps li span{inline-size:20px;block-size:20px;border-radius:50%;display:grid;place-items:center;
  background:var(--surface-secondary);font-size:.72rem}
.qtr-cust-steps li.on{color:var(--accent-text);border-color:var(--accent-text)}
.qtr-cust-steps li.on span{background:var(--accent-soft)}
.qtr-cust-steps li.done{color:var(--qtr-success-text)}

.qtr-cust-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(240px,100%),1fr))}
.qtr-cust-field{display:flex;flex-direction:column;gap:4px;font-size:.8rem;font-weight:700;
  color:var(--text-secondary);min-inline-size:0}
.qtr-cust-field .qtr-id-input{inline-size:100%;min-inline-size:0}
.qtr-cust-lic{border:1px solid var(--border-subtle);border-radius:14px;padding:14px;margin:12px 0;min-inline-size:0}
.qtr-cust-lic legend{font-weight:800;font-size:.84rem;padding:0 6px}

.qtr-cust-products{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr))}
.qtr-cust-prod{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:12px;border-radius:14px;
  border:1px solid var(--border-subtle);cursor:pointer;min-inline-size:0}
.qtr-cust-prod.on{border-color:var(--accent-text);background:var(--accent-soft)}
.qtr-cust-prod b{font-size:.86rem}
.qtr-cust-prod small{color:var(--text-tertiary);font-size:.72rem;inline-size:100%}

.qtr-cust-review h4{margin:14px 0 4px;font-size:.86rem}
.qtr-cust-nav{display:flex;gap:8px;flex-wrap:wrap;margin-top:16px}
.qtr-sr-only{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

@media(max-width:560px){
  .qtr-cust-filters{flex-direction:column}
  .qtr-cust-f,.qtr-cust-f.grow{flex:1 1 auto;inline-size:100%}
}
