/* =================================================================
   The stylesheet. The COLOURS AND THE TYPE ARE `assets/tokens.css`,
   loaded before this file, each with its job in a `JOB:` line; this
   file only consumes them (SESSION DA-1). No gradient, no urgency, no
   badge, no score, no colour-coded rate (DA-4). The accent has one job,
   the total paid, and appears once below (DA-5). Logical properties
   only, so the Arabic tree flips without a rule of its own; numbers are
   isolated LTR. SESSION GB (11 Oct 2026): the Banknote direction folded
   in — ink by default, grey for dates and counts only, one type scale,
   no capitals, the band, the brass for the reader's choice.
   ================================================================= */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--size-body);
  line-height: var(--leading-latin);
  text-rendering: optimizeLegibility;
  /* DA-1/DA-3: tabular figures on the whole page; `.num`/`.amount`
     repeat it so a face without them fails the check. */
  font-variant-numeric: tabular-nums;
}

/* A link is ink with an underline; the accent is the total's alone. */
a { color: var(--ink); text-underline-offset: 2px; }
a:hover { text-decoration-thickness: 2px; }

h1, h2, h3 { line-height: 1.25; font-weight: var(--weight-bold);
             margin: 0 0 .4em; }
/* SESSION GB: a page's own title in display type (Banknote). */
h1 { font-size: var(--size-display); font-weight: var(--weight-display);
     line-height: 1.08; letter-spacing: -0.025em;
     margin: 0 0 var(--space-3); max-width: 32ch; }
h2 { font-size: var(--size-page-title); font-weight: var(--weight-display);
     margin: var(--space-5) 0 var(--space-3); }
h3 { font-size: var(--size-sub); margin-top: 1.5rem; }
/* The thread: a short bar in the total's hue that opens a section. */
.wrap--narrow > h2::before, .fd-block > h2::before, .cash-line > h2::before {
  content: ""; display: block; width: 2.5rem; height: 3px;
  background: var(--thread); margin-bottom: var(--space-3); }

p { margin: 0 0 .9em; max-width: var(--measure); }
ul, ol { max-width: var(--measure); padding-inline-start: 1.3em; }
li { margin-bottom: .35em; }

.wrap { max-width: 1120px; margin-inline: auto; padding-inline: 20px; }
/* Prose pages keep the gutter; --measure holds the line length. */
.wrap--narrow ol, .wrap--narrow ul { max-width: var(--measure); }
.wrap--narrow p, .wrap--narrow li { line-height: 1.7; }
[dir="rtl"] .wrap--narrow p, [dir="rtl"] .wrap--narrow li { line-height: 1.9; }

/* ---------- header ------------------------------------------- */

/* SESSION GB: the band — the header and the date line on `--band`; the
   tokens are redefined inside it (tokens.css), so the rules here name no
   colour of their own. */
.site-header, .freshness { background: var(--band); color: var(--ink); }
.site-header .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) 22px;
  padding-block: 20px 14px;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-weight: 640;
  font-size: 1.55rem;
  /* SESSION ER: the name's token; the wordmark is drawn in currentColor. */
  color: var(--brand);
  text-decoration: none;
}
/* SESSION FH (audit 165): the drawn name's seat per script, from
   assets/wordmark.json `baseline_offset_em` (session_er/make_wordmark.py
   checks these two lines); the CSP refuses an inline style. */
.wordmark--en { vertical-align: -0.308em; }
.wordmark--ar { vertical-align: -0.480em; }
.brand-logo:empty { display: none; }
.brand-logo img { display: block; height: 26px; width: auto; }

/* The unset-brand state is meant to look unset, not designed. */
.brand-name.is-placeholder {
  font-family: var(--mono);
  font-size: .86rem;
  font-weight: 500;
  color: var(--muted);
  border: 1px dashed var(--rule);
  border-radius: var(--radius);
  padding: 2px 7px;
}

.site-nav {
  margin-inline-start: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) 22px;
  font-size: var(--size-small);
}
/* SESSION FV: 24 px tall at least (audit 176); 44 under 600 px. */
.site-nav a { color: var(--ink); text-decoration: none;
              display: inline-flex; align-items: center; min-height: 24px; }
.site-nav a:hover { text-decoration: underline; }
.site-nav a[aria-current="page"] { font-weight: var(--weight-mid);
  text-decoration: underline; text-decoration-color: var(--choice);
  text-decoration-thickness: 2px; text-underline-offset: 7px; }

/* ---------- freshness strip ---------------------------------- */

.freshness { font-size: var(--size-small); }
.freshness .wrap { padding-block: 0 6px; }
.freshness strong { font-weight: var(--weight-mid); }

/* ---------- generic blocks ----------------------------------- */

main { padding-block: var(--space-5) var(--space-6); }

/* SESSION GB: ink by default; the ratified lines are said plainly, in
   ink, at reading size. Grey is for dates and counts. */
.lede { font-size: var(--size-sub); line-height: 1.6; max-width: var(--measure); }
/* SESSION UI2, 2026-10-22 (audit 310; LR2-8, audit 307; ruling UI2-R2): a
   grace page's one sentence is its h1 (`render_grace_page`), drawn exactly
   as the paragraph it was -- `p`, `.lede` and `.wrap--narrow p` above. */
.wrap--narrow h1.lede { font-size: var(--size-sub); font-weight: inherit;
                        line-height: 1.7; letter-spacing: normal;
                        margin: 0 0 .9em; max-width: var(--measure); }
[dir="rtl"] .wrap--narrow h1.lede { line-height: 1.9; }

/* One sentence; at the full width so its closing date does not break at a
   hyphen (it is bare text in English). */
.coverage {
  font-size: var(--size-body);
  margin-bottom: 1.2em;
  max-width: none;
}

.note {
  margin: 0 0 1em;
  font-size: var(--size-small);
  max-width: var(--measure);
}
.note p:last-child { margin-bottom: 0; }
/* The two ruled scope lines (not-a-quote, not-the-same) read in the
   body size and the mid weight; a quiet note is the small size. */
main .note--quote, main .coverage + .note, main .note.fd-not-same {
  font-weight: var(--weight-mid); font-size: var(--size-body);
  max-width: 54ch; }
/* Session AV: the two-shop line and the no-plan sentence scope the table
   below, so they sit tight under the heading block. */
.note--scope {
  margin-top: -0.4em;
  margin-bottom: 1.1em;
}
.note--scope strong { color: var(--ink); }

/* The per-category shop-count split on the index, under the category heading.
   Same register as .coverage, which is the site's other counted-fact line. */
.cat-split {
  font-size: var(--size-small);
  margin: -0.5em 0 1em;
  max-width: var(--measure);
}

.note--review {
  border-inline-start: 3px solid var(--notice);
  background: var(--surface);
  padding: 12px 14px;
}
.note--fixture {
  border: 2px solid var(--notice);
  border-inline-start-width: 6px;
  background: var(--surface);
  padding: 12px 14px;
  max-width: none;
  color: var(--ink);
  font-weight: 500;
}

/* SESSION DA-1 — the four semantic tones, each on its one surface. */
.note--dark { border-inline-start: 3px solid var(--dark-day);
              padding-inline-start: 14px; color: var(--dark-day); }
.note--not-published { color: var(--not-published); }
.listing-note--stock, .is-out-of-stock { color: var(--out-of-stock); }
.is-stale { color: var(--stale); }

.honesty-line {
  font-size: var(--size-small);
  max-width: var(--measure);
  margin: 0 0 var(--space-1);
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- cash-price line ---------------------------------- */

/* SESSION GB (Banknote): the cash block is the product page's hero — no
   box, a rule under it, the prices large and light. */
.cash-line {
  margin: 0 0 var(--space-4);
  padding: var(--space-4) 0 var(--space-3);
  border-bottom: 1px solid var(--rule);
  max-width: none;
}
.cash-line h2 { font-size: var(--size-body); font-weight: var(--weight-mid);
                margin: 0 0 var(--space-3); }
.cash-list { list-style: none; margin: 0; padding: 0; display: flex;
             flex-wrap: wrap; gap: 6px 26px; max-width: none; }
.cash-list li { margin: 0; }
.cash-date { color: var(--muted); font-size: var(--size-small); }
/* SESSION BQ — the "View at <shop>" link beside the price it belongs to. */
.cash-buy { font-size: var(--size-small); white-space: nowrap; }
/* SESSION FD2, RULING_pm5_changes P3: one line per shop; the price is the
   link; a stale side takes the stale token; the read date never breaks. */
.cash-list--lines { display: block; }
.cash-list--lines li + li { margin-top: var(--space-3); }
.cash-list--lines .cash-date { white-space: nowrap; }
.cash-read .buy-link .amount { font-weight: var(--weight-mid); }
.cash-list--lines .cash-read .amount { font-size: var(--size-page-title);
  font-weight: var(--weight-light); letter-spacing: -0.01em; }
.cash-list--third .cash-read .amount { font-size: var(--size-section); }
.cash-read .buy-link { text-decoration-thickness: 1px; text-underline-offset: 6px; }
.cash-read.is-stale .amount, .cash-read.is-stale .cash-date,
.cash-read.is-stale .buy-link { color: var(--stale); }
/* Scoped to a block that has the lines (`~`), so a page not yet moved to
   them keeps its own spacing. */
.cash-list--lines ~ .note,
.cash-list--lines ~ .cash-about > .note { border-inline-start: 0; padding: 0;
                   margin: .7em 0 0; font-size: var(--size-small);
                   max-width: var(--measure); }
.cash-list--lines ~ .date-note-sm { margin: .7em 0 0; }
.cash-about { margin: .3em 0 0; }
.cash-about > summary { min-height: 44px; display: flex; align-items: center;
                        cursor: pointer; font-size: var(--size-small); }
.result-scope, .tie-line { font-size: var(--size-small);
                           margin: 0 0 var(--space-1); }

/* ---------- the two links a row carries ---------------------- */
/* SESSION BQ: .buy-link is the shop's page (a link with the external
   mark); .source-link is the evidence inside the details panel. */
.buy-link { text-decoration: underline; text-underline-offset: 2px; }
.buy-link .ext { font-size: .78em; margin-inline-start: 2px;
                 text-decoration: none; display: inline-block; }
table.data tr.plan > td .cash-buy { display: block; margin-top: 2px; }
/* The action cell: where to buy above the details toggle, never wider.
   SESSION UI1 (2026-10-21, audit 288; LR1-3): the link was 20.8 px tall in
   English (23.4 in Arabic), under the 24-px minimum (UI_OVERVIEW §2). Its
   box now takes 2 px of empty space above it (the margin-top of the
   .cash-buy line it opens, which collapses with this -2 px) and 2 px of
   the 4 px below it: 24.8 / 27.4 px, and every row, every line and every
   glyph is where it was (session_ui1/README.md: the whole 4 px as padding
   moved rows, because a cell's last margin is not all counted). The
   product name's line above reaches 1.4 px into that space at 1280 in
   English, and a line's text is hit before a block's padding; `position:
   relative` (no offset: nothing moves) lets the link win in its own box. */
table.data tr.plan > td .buy-link { display: block; font-size: var(--size-fine);
                                    padding-block: 2px; margin-top: -2px;
                                    margin-bottom: 2px; white-space: nowrap;
                                    position: relative; }

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

.controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: flex-start;
  margin-bottom: var(--space-4);
}
.control-group { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* SESSION GB: capitals off everywhere; a label is ink in the mid weight. */
.control-label {
  font-size: var(--size-small); font-weight: var(--weight-mid);
  margin-inline-end: 4px;
}
.chip {
  font: inherit;
  font-size: var(--size-small);
  line-height: 1.2;
  padding: 8px 14px;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink);
  border-radius: var(--radius);
  cursor: pointer;
}
.chip:hover { border-color: var(--muted); }
/* The reader's own choice is brass (Banknote). */
.chip[aria-pressed="true"] {
  background: var(--choice);
  border-color: var(--choice);
  color: var(--paper);
  font-weight: var(--weight-mid);
}
.chip:focus-visible, .rowtoggle:focus-visible, a:focus-visible {
  outline: 2px solid var(--choice);
  outline-offset: 2px;
}

/* The count line titles the table under it (Banknote). */
.result-count { font-size: var(--size-sub); font-weight: var(--weight-display);
                margin: var(--space-5) 0 var(--space-2); }

/* ---------- tables ------------------------------------------- */

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* SESSION FD2 (2026-10-07, task 5; P7 "at 1280 px neither the table's box
   nor the document scrolls sideways"). A table wider than its box scrolls
   inside it -- but every `.sr-only` in a cell is `position: absolute`, and
   with no positioned ancestor it is laid out against the page, not the box,
   so the one in the last column pushed the DOCUMENT sideways once the
   table was ~100 px wider than its box (measured: a 1,333 px table in a
   1,080 px box made the page 1,374 px wide at 1280; the All state of
   `appliance-ariston-lfc3o33wltx`, 1,212 px, was just inside). Positioned,
   the box is their containing block and clips them. */
.table-scroll { position: relative; }

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--size-body);
}
table.data caption {
  text-align: start;
  font-size: var(--size-small);
  padding-bottom: 8px;
}
table.data th, table.data td {
  text-align: start;
  padding: 16px 12px;
  border-bottom: 1px solid var(--rule-soft);
  vertical-align: top;
}
table.data th:first-child, table.data td:first-child { padding-inline-start: 0; }
table.data th:last-child, table.data td:last-child { padding-inline-end: 0; }
table.data thead th {
  border-bottom: 2px solid var(--ink);
  font-size: var(--size-small);
  font-weight: var(--weight-mid);
  vertical-align: bottom;
}
table.data th.num, table.data td.num { text-align: end; }
table.data tbody tr.plan:hover td { background: var(--surface); }

/* The date column's head carries the volatility sentence. */
table.data th.date-col { min-width: 12rem; }
table.data thead th .flag {
  font-weight: var(--weight-body);
  font-size: var(--size-fine);
  line-height: 1.35;
  max-width: 30ch;
  margin-top: 4px;
}

/* Shop and lender are short labels: no wrap above a phone. */
table.data tr.plan > td:first-child,
table.data tr.plan > td:nth-child(2) { white-space: nowrap; }
table.data tr.plan > td:nth-child(2) .tag { display: block; width: fit-content; }

/* THE ISOLATION. `build_site.money()` emits `.amount`; `.num` is its
   column. Both isolated, both tabular (DA-2 on the rendered trees, DA-3). */
.num, .amount, .iso, bdi {
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
}
.amount { white-space: nowrap; }
/* SESSION EX2: a date in a <bdi> never breaks at its hyphen; scoped to
   the date-bearing containers (the site name is a <bdi> too). */
.promise bdi, [data-provenance-stamp] bdi, .card-change bdi,
.pairing-dates bdi { white-space: nowrap; }
/* SESSION UI2, 2026-10-22 (audit 310; LR2-3, audit 307). The Arabic
   template wraps the LIST of dates in one `.iso` span, an LTR run that
   cannot wrap: at 360 px the methodology page was 399 px wide, and the
   list's «و» (U+0648) inside the LTR run reordered the dates. The span
   takes the line's own direction and wraps between the dates; each date
   keeps its own LTR, unbreakable <bdi> (the rule above). No word changes. */
[dir="rtl"] .pairing-dates .iso { direction: rtl; white-space: normal; }
/* SESSION GV2 (audit 219 should-dos 4-6): ShopName, a shop's name in a
   sentence (`build_site.shop_html`). Isolated, never broken inside itself,
   drawn in its own script's direction (`dir` = first strong character);
   replaces GV's `.note--scope > strong > bdi`. */
.shop { unicode-bidi: isolate; white-space: nowrap; }
.shop[dir="rtl"] { direction: rtl; }
/* The one place the accent is spent. */
.total-amount { font-weight: var(--weight-bold); color: var(--accent); }

/* SESSION DA-3 — "+EGP x vs cash" (front-door ruling §2), on its own
   line under the total in both layouts, body weight. */
.vs-cash {
  display: block;
  font-size: var(--size-fine);
  font-weight: var(--weight-body);
  color: var(--muted);
  white-space: nowrap;
  margin-top: 2px;
  /* SESSION DA-3: in the LTR numeric cell the leading sign took the cell's
     direction and landed at the far end of the Arabic line; `plaintext`
     bases the line on its own first strong character. Found only in a
     rendered crop (`session_da/da3/sign_bidi_before.png`). */
  unicode-bidi: plaintext;
}

.date-cell { white-space: nowrap; color: var(--muted); font-size: var(--size-small); }

.flag {
  display: block;
  font-size: var(--size-fine);
  font-weight: var(--weight-body);
  margin-top: 2px;
  white-space: normal;
}
/* A flag in a numeric column stays on one line; scoped to `.num` (Session
   AV: unscoped it widened card-programmes' column to 705 px). */
table.data tr.plan > td.num .flag { white-space: nowrap; }

/* SESSION BK — the spec-matched module's shop cell: the shop's own title
   and the derived note, secondary to the shop name. */
.listing-title {
  display: block;
  font-size: var(--size-fine);
  font-weight: 400;
  margin-top: 3px;
  white-space: normal;
}
.listing-note {
  display: block;
  font-size: var(--size-fine);
  font-weight: 400;
  margin-top: 2px;
  white-space: normal;
}
.tag {
  display: inline-block;
  font-size: var(--size-fine);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1px 6px;
  margin-top: 3px;
  white-space: nowrap;
}

.rowtoggle {
  font: inherit;
  font-size: var(--size-small);
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  color: var(--ink);
  padding: 3px 9px;
  cursor: pointer;
  white-space: nowrap;
}
.rowtoggle:hover { border-color: var(--muted); }

tr.plan-detail > td {
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
  padding-block: 12px;
}
.detail-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 34px;
  max-width: none;
}
.detail-item { max-width: 42ch; }
.detail-key {
  display: block;
  font-size: var(--size-fine);
  font-weight: var(--weight-mid);
}
.detail-val { font-size: var(--size-small); }
.detail-basis { font-size: var(--size-fine); margin-top: 2px; }

/* Rows hidden by the filters (JS sets .is-hidden). Qualified with
   `table.data` so the stacked layout's display rule cannot outrank them. */
table.data tr.is-hidden { display: none; }
table.data tr.plan-detail { display: none; }
table.data tr.plan-detail.is-open { display: table-row; }
/* A filtered-out row's detail stays shut whatever else is set on it. */
table.data tr.plan-detail.is-open.is-hidden { display: none; }

/* Shown only where the table header is not — see the media query. */
.date-note-sm { display: none; font-size: var(--size-small);
                margin: 0 0 var(--space-1); max-width: var(--measure); }
/* SESSION FD, P6: where the date column has left the table, its header's
   sentence has no header to sit on at any width. */
.date-note-sm.date-note-all { display: block; }

/* SESSION FD, P4. The words that make a stacked row a sentence (once
   string 2 is approved, "EGP x a month") are the column heads' job on the
   table, so they show under 600 px only. */
.unit { display: none; }
/* P4: the row itself opens Details (assets/app.js); the button stays the
   real control, with a 44-px target. */
table.data tbody tr.plan[data-detail] { cursor: pointer; }
.rowtoggle { min-height: 44px; min-width: 44px; }

.empty-state {
  padding: 18px 0;
  font-size: var(--size-small);
}
.empty-state[hidden] { display: none; }

/* ---------- product index ------------------------------------ */

.cat-block { margin-bottom: 2.4rem; }
.product-list { list-style: none; margin: 0; padding: 0; max-width: none;
                border-top: 2px solid var(--ink); }
.product-list li { margin: 0; border-bottom: 1px solid var(--rule-soft); }
.product-list a {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 18px;
  align-items: baseline;
  padding: 18px 0;
  text-decoration: none;
  color: inherit;
}
.product-list a:hover { background: var(--surface); }
.product-name { font-weight: var(--weight-bold); }
.product-meta { font-size: var(--size-small); }
.product-meta .sep { padding-inline: 6px; opacity: .55; }

.catnav { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: var(--size-small);
          padding: 10px 0; border-block: 1px solid var(--rule-soft);
          margin-bottom: 1.6rem; }
/* SESSION DN: the category bar is a row of its own in the header, one
   hairline above it. SESSION GB: chips outlined on the band; the current
   category is the reader's choice, filled brass. */
.site-header .catnav { flex-basis: 100%; margin-bottom: 0; border-block: 0;
                       border-top: 1px solid var(--rule-soft);
                       padding: 12px 0 0; }
.site-header .catnav a { text-decoration: none; }
.site-header .catnav .catchip { background: transparent; color: var(--ink); }
.site-header .catnav .catchip[aria-current] { background: var(--choice);
  border-color: var(--choice); color: var(--band); font-weight: var(--weight-mid); }
.catchip .chip-count { opacity: .78; }

/* ---------- SESSION FJ: the header and the chip bar (F1, F2) ----
   F1: at 360 px one row -- the name, a search control, a menu button --
   every control 44 px. F2: the category bar is one row of chips that
   scrolls sideways. At 600 px and above the links sit beside the name and
   the chips wrap. The menu is a checkbox and a label (no script needed). */
.site-header .catnav .catchip { display: inline-flex; align-items: center;
                                gap: 6px; white-space: nowrap; }
.site-header .catnav { gap: var(--space-2); }
.head-search, .menu-toggle { display: none; }
.menu-close { display: none; }
@media (min-width: 600px) {
  .site-header .catnav { gap: 6px; }
  .site-header .catnav .catchip { padding: 5px 8px; gap: 5px; }
}
/* F3 (string 5): one line that opens the full sentence; a 44-px control. */
.freshness-fold > summary { display: block; padding-block: 11px;
                            min-height: 44px; cursor: pointer; color: var(--muted);
                            list-style: none; text-decoration: underline;
                            text-decoration-style: dotted;
                            text-underline-offset: 3px; }
.freshness-fold > summary::-webkit-details-marker { display: none; }
.freshness-fold > summary:focus-visible { outline: 2px solid var(--choice);
                                          outline-offset: 2px; }
.freshness-fold[open] > summary { text-decoration: none; color: var(--ink); }
.freshness-fold > p { margin: 0 0 6px; }
.freshness:has(.freshness-fold) .wrap { padding-block: 0; }
.menuswitch {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  overflow: hidden;
  clip-path: inset(50%);
}
/* SESSION UI2, 2026-10-22 (audit 310; LR2-7, audit 307): from 600 px the
   menu button is not drawn (`.menu-toggle` above) and the links stand in
   the header, so its switch leaves the Tab order the way the button left
   the page; under 600 px it stays focusable and its focus ring is drawn on
   the button. */
@media (min-width: 600px) {
  .menuswitch { display: none; }
}
@media (max-width: 599px) {
  .site-header .wrap { align-items: center; gap: 0 2px;
                       padding-block: var(--space-1) var(--space-2); }
  /* One row: the name, the search control, the menu button (which opens
     the links as a column under the row). SESSION GB: the search control
     is drawn as a filled field's end, a 44-px square on the band. */
  .brand { min-height: 44px; flex: 0 0 auto; order: 0; font-size: 1.3rem; }
  .head-search { display: inline-flex; align-items: center;
                 justify-content: center; width: 44px; height: 44px;
                 color: var(--ink); background: var(--surface);
                 border-radius: var(--radius);
                 order: 2; margin-inline-start: auto; }
  .menuswitch, .menu-toggle { order: 3; }
  .menu-toggle { display: inline-flex; align-items: center; min-height: 44px;
                 padding-inline: 10px; color: var(--ink); font-size: var(--size-small);
                 text-decoration: underline; text-underline-offset: 2px;
                 cursor: pointer; }
  .menuswitch:checked ~ .menu-toggle .menu-open { display: none; }
  .menuswitch:checked ~ .menu-toggle .menu-close { display: inline; }
  .menuswitch:focus-visible ~ .menu-toggle { outline: 2px solid var(--choice);
                                             outline-offset: 2px; }
  .site-nav { order: 1; flex: 1 1 0; min-width: 0; margin-inline-start: 6px;
              flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
              gap: 0 2px; }
  .site-nav::-webkit-scrollbar { display: none; }
  .site-nav a { display: inline-flex; align-items: center;
                justify-content: center; min-height: 44px; min-width: 44px;
                padding-inline: 8px; white-space: nowrap; flex: 0 0 auto; }
  .site-nav .lang-switch { display: inline-flex; flex: 0 0 auto;
                           margin-inline-start: 0; }
  .menuswitch:not(:checked) ~ .site-nav { display: none; }
  .menuswitch:checked ~ .site-nav { order: 4; flex: 0 0 100%;
                                    margin-inline-start: 0;
                                    flex-direction: column;
                                    overflow: visible;
                                    border-top: 1px solid var(--rule); }
  .menuswitch:checked ~ .site-nav a { border-bottom: 1px solid
                                      var(--rule-soft); }
  .site-header .catnav { order: 5; flex-wrap: nowrap; overflow-x: auto;
                         scrollbar-width: none; padding: var(--space-1) 0 0;
                         border-top: 0; }
  .site-header .catnav::-webkit-scrollbar { display: none; }
  .site-header .catnav .catchip { flex: 0 0 auto; min-height: 44px; }
  /* SESSION FP: a fade at each end that hides chips (app.js sets `fp-s`,
     `fp-e`); an alpha-only mask, no colour painted (DA-4, ruling 7 Oct). */
  .site-header .catnav { --fp-s: 0px; --fp-e: 0px; --fp-to: left; }
  [dir="rtl"] .site-header .catnav { --fp-to: right; }
  .site-header .catnav.fp-s { --fp-s: 2rem; }
  .site-header .catnav.fp-e { --fp-e: 2rem; }
  .site-header .catnav:is(.fp-s, .fp-e):not(:focus-within) {
    -webkit-mask-image: linear-gradient(to var(--fp-to), transparent,
      #000 var(--fp-e), #000 calc(100% - var(--fp-s)), transparent);
    mask-image: linear-gradient(to var(--fp-to), transparent,
      #000 var(--fp-e), #000 calc(100% - var(--fp-s)), transparent); }
  main { padding-top: 20px; }
}

/* ---------- SESSION GN: the theme toggle ----------------------
   One 44-px icon button at the end of the header's row: the sun while
   the page is light, the moon while it is dark (tokens.css, the
   `data-theme` block). Drawn in the band's ink; brass ring on focus, as
   every control. Without script it would do nothing, so it is not shown;
   it is not printed. */
.theme-toggle { display: inline-flex; align-items: center;
                justify-content: center; flex: 0 0 auto; width: 44px;
                height: 44px; padding: 0; margin: 0; border: 0;
                border-radius: var(--radius); background: transparent;
                color: var(--ink); cursor: pointer; }
.theme-toggle:hover { background: var(--surface); }
.theme-toggle:focus-visible { outline: 2px solid var(--choice);
                              outline-offset: 2px; }
.theme-toggle svg { display: block; }
.theme-toggle .theme-moon,
[data-theme="dark"] .theme-toggle .theme-sun { display: none; }
[data-theme="dark"] .theme-toggle .theme-moon { display: block; }
@media (scripting: none) { .theme-toggle { display: none; } }
@media print { .theme-toggle { display: none; } }
@media (max-width: 599px) { .theme-toggle { order: 3; } }
/* From 600 px the toggle is held at the end of the name's row, centred on
   the name, out of the flow: below ~920 px the links wrap under the name,
   where a toggle after them would take a row of its own (+48 px). From
   850 px (the links share the name's row from 910 px in Arabic, 930 in
   English; on a row of their own 850-910 they have the room) they keep
   the toggle's width (44 + the 22-px gap) free at their end. */
@media (min-width: 600px) {
  .site-header .wrap:has(> .theme-toggle) { position: relative; }
  .theme-toggle { position: absolute; top: 18px; inset-inline-end: 20px; }
  [dir="rtl"] .theme-toggle { top: 20px; }
}
@media (min-width: 850px) {
  .site-header .wrap:has(> .theme-toggle) > .site-nav { margin-inline-end: 66px; }
}

/* ---------- footer ------------------------------------------- */

.site-footer {
  border-top: 2px solid var(--ink);
  margin-top: var(--space-6);
  padding-block: var(--space-5) var(--space-6);
  font-size: var(--size-small);
}
.site-footer p { max-width: var(--measure); }
.build-stamp { font-family: var(--mono); font-size: var(--size-fine); color: var(--muted); }

.backlink { font-size: var(--size-small); margin-bottom: var(--space-3); display: inline-block; }

/* ---------- small screens: stack the table ------------------- */

/* SESSION DA-3: §2.5's one breakpoint — the stacked row under 600 px, the
   table above (it scrolls inside `.table-scroll` where it must). SESSION
   GB: under 600 px the display title gives way (F6 is gated). */
@media (max-width: 599px) {
  .wrap { padding-inline: var(--space-3); }
  h1 { font-size: var(--size-page-title); line-height: 1.2; max-width: none;
       margin-bottom: var(--space-1); }
  h2 { font-size: var(--size-section); }

  table.data thead { display: none; }
  /* a table-caption inside a display:block table shrink-wraps to a column */
  table.data caption { display: block; }
  .date-note-sm { display: block; }
  table.data, table.data tbody, table.data tr, table.data td { display: block; width: 100%; }
  table.data tr.plan {
    border-bottom: 1px solid var(--rule-soft);
    margin-bottom: 10px;
    padding: 4px 0;
  }
  table.data tr.plan td, table.data tr.plan-detail td {
    border-bottom: 0;
    padding: 4px 12px;
    display: flex;
    flex-wrap: wrap;
    gap: 2px 12px;
    justify-content: space-between;
    align-items: baseline;
    text-align: start;
  }
  table.data tr.plan > td:first-child,
  table.data tr.plan > td:nth-child(2) { white-space: normal; }
  table.data tr.plan > td .flag { white-space: normal; flex: 1 0 100%; }
  table.data tr.plan > td .listing-title,
  table.data tr.plan > td .listing-note {
    white-space: normal;
    flex: 1 0 100%;
    text-align: start;
  }
  table.data td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: var(--size-fine);
    font-weight: var(--weight-mid);
    padding-top: 2px;
  }
  /* SESSION BQ: the action cell's controls carry their own words. */
  table.data td.row-actions::before { content: none; }
  table.data td.num { text-align: end; }
  table.data tr.plan td > .flag, table.data tr.plan td > .tag { text-align: end; }
  table.data tr.plan-detail.is-open { display: block; }
  tr.plan-detail > td { border-radius: var(--radius); margin-bottom: 10px; }
  .detail-grid { flex-direction: column; gap: 10px; }
  .detail-item { max-width: none; }

  /* SESSION FD — RULING_pm5_changes P4: a plan is three short lines.

       CIB · 6 months · noon Egypt                    [Details]
       Monthly 2,172.50                      Total 13,817.10
       Upfront 782.10                   +EGP 782.10 vs cash

     A wrapping flex line (the order numbers are the layout; DOM order is
     the table's); the Total cell is `display: contents`; the two
     pseudo-elements are the line breaks. SESSION GB (Banknote): a line of
     text, not a box, and "extra entry conditions" on its own last line;
     P7's six whole rows a screen hold a clean row to <= 123 px. */
  table.data tr.plan[data-months] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 0;
    row-gap: 2px;
    border: 0;
    border-bottom: 1px solid var(--rule-soft);
    margin: 0;
    padding: 8px 0;
    line-height: 1.5;
  }
  [dir="rtl"] table.data tr.plan[data-months] { line-height: 1.55; }
  table.data tr.plan[data-months]::before,
  table.data tr.plan[data-months]::after {
    content: ""; flex: 0 0 100%; height: 0;
  }
  table.data tr.plan[data-months]::before { order: 5; }
  table.data tr.plan[data-months]::after { order: 8; }
  table.data tr.plan[data-months] > td {
    width: auto;
    padding: 0;
    justify-content: flex-start;
    gap: 2px 8px;
  }
  /* The lender cell's contents are items of the row, so its flag can
     take the last line of its own. */
  table.data tr.plan[data-months] > td[data-cell="lender"] {
    display: contents; order: 1; font-weight: var(--weight-mid);
  }
  table.data tr.plan[data-months] > td[data-cell="lender"] > .flag {
    order: 11; flex: 0 0 100%; margin-top: var(--space-1);
  }
  table.data tr.plan[data-months] > td[data-cell="months"] {
    order: 2; flex: 0 0 auto; padding-inline: 6px 0; white-space: pre;
    gap: 0; font-size: var(--size-small);
  }
  /* On the stacked row a numeric cell is a phrase of the row's sentence
     and takes the row's direction; the figure stays isolated by `.amount`. */
  table.data tr.plan[data-months] > td[data-cell="months"],
  table.data tr.plan[data-months] > td[data-cell="monthly"],
  table.data tr.plan[data-months] > td[data-cell="upfront"] {
    direction: inherit; text-align: start;
  }
  table.data tr.plan[data-months] > td[data-cell="shop"] {
    order: 3; flex: 0 1 auto; padding-inline: 6px 0; font-size: var(--size-small);
  }
  table.data tr.plan[data-months] > td.row-actions {
    order: 4; flex: 0 0 auto; margin-inline-start: auto;
  }
  table.data tr.plan[data-months] > td[data-cell="monthly"] {
    order: 6; flex: 1 1 auto;
  }
  table.data tr.plan[data-months] > td[data-cell="monthly"],
  table.data tr.plan[data-months] > td[data-cell="upfront"] {
    column-gap: .3em;
  }
  table.data tr.plan[data-months] > td[data-cell="total"] {
    display: contents;
  }
  table.data tr.plan[data-months] > td[data-cell="total"]::before {
    order: 7; margin-inline-start: auto; padding-inline-end: 8px;
    align-self: baseline;
  }
  table.data tr.plan[data-months] > td[data-cell="total"] .total-amount {
    order: 7;
  }
  table.data tr.plan[data-months] > td[data-cell="upfront"] {
    order: 9; flex: 1 1 auto;
  }
  table.data tr.plan[data-months] > td[data-cell="total"] .vs-cash {
    order: 10; margin: 0; text-align: end;
  }
  table.data tr.plan[data-months] > td[data-cell="total"] .flag {
    order: 11; flex: 0 0 100%; text-align: start;
  }
  table.data tr.plan[data-months] > td[data-cell="date"] {
    order: 12; flex: 0 0 100%;
    font-size: var(--size-fine); color: var(--muted);
  }
  /* String 3 pending: the column leaves the stacked row (the fallback). */
  table.data tr.plan[data-months] > td[data-cell="extra"] { display: none; }
  /* The separators of line 1 are drawing, not words. */
  table.data tr.plan[data-months] > td[data-cell="months"]::before,
  table.data tr.plan[data-months] > td[data-cell="shop"]::before {
    content: "·" !important;
    margin-inline-end: 6px;
  }
  table.data tr.plan[data-months] > td[data-cell="months"]::after {
    content: attr(data-unit);
  }
  table.data tr.plan[data-months] > td .unit { display: inline; }
  /* String 2, once approved: the unit words replace the cell's label. */
  table.data tr.plan[data-months] > td[data-cell="monthly"]:has(> .unit)::before,
  table.data tr.plan[data-months] > td[data-cell="upfront"]:has(> .unit)::before {
    content: none;
  }
  /* On the plan row a label is part of the row's sentence: body weight. */
  table.data tr.plan[data-months] > td[data-label]::before {
    font-weight: var(--weight-body);
    font-size: var(--size-fine);
    padding-top: 0;
  }
  table.data tr.plan[data-months] > td[data-cell="lender"]::before {
    content: none;
  }
  table.data tr.plan[data-months] .rowtoggle { border: 0; padding-inline: 2px;
    text-decoration: underline; text-underline-offset: 3px; }

  /* SESSION DG, PM4-1 (audit 97): the row rule above is (0,3,2) and sets
     `display`, so `tr.is-hidden` at (0,2,2) hid nothing under 600 px. This
     is (0,4,2) and after it; keep it separate — DG-1 asserts that a
     `.is-hidden` selector here outranks every `display` rule on `tr.plan`. */
  table.data tr.plan[data-months].is-hidden { display: none; }
}

/* ---------- language switch ---------------------------------- */

.lang-switch { margin-inline-start: 8px; }
.lang-switch a { text-decoration: underline; }

/* ---------- Arabic / RTL: layout, not just direction -------------
   Session AY. Letter-spacing breaks joined Arabic, which needs more
   leading; Latin and numeric runs stay LTR inside an RTL line; a value
   opening on a neutral symbol is not flipped (session_ay/screenshots/). */
[dir="rtl"] body { line-height: var(--leading-arabic); }
[dir="rtl"] h1 { letter-spacing: 0; line-height: 1.3; }
[dir="rtl"] .brand-name.is-placeholder,
[dir="rtl"] .build-stamp,
[dir="rtl"] input[data-calc-input] { direction: ltr; unicode-bidi: isolate; }
[dir="rtl"] .build-stamp { text-align: start; unicode-bidi: plaintext; }
[dir="rtl"] .detail-val, [dir="rtl"] .cash-date { unicode-bidi: plaintext; }
[dir="rtl"] .product-name { unicode-bidi: plaintext; }
/* A sentence in a numeric column is longer in Arabic; let it wrap rather
   than widen the column past a phone's screen. */
[dir="rtl"] table.data tr.plan > td.num .flag { white-space: normal; }
/* An ISO date or a price range inside Arabic text: isolated, LTR and
   unbreakable (it broke at a hyphen, session_ay/screenshots/). */
.iso { direction: ltr; unicode-bidi: isolate; white-space: nowrap; }

/* SESSION BQ: the external mark ↗ is mirrored in RTL; a link ending in a
   Latin shop name is isolated so the mark stays at its end. */
[dir="rtl"] .buy-link .ext { transform: scaleX(-1); }
[dir="rtl"] .buy-link { unicode-bidi: isolate; }

/* ---------- SESSION DA-2 — the front door --------------------- */
/* The search box, the suggestions, the result card, the two filter chips
   and paging (OUTCOME §2.1-2.2). DA-4 and DA-5 hold the bans.

   SESSION GB: the box as PriceRunner draws it — a filled field with a
   drawn magnifier, a real submit button at its end, a placeholder in ink
   (>= 7 : 1), the widest control on the page. The visible label stays,
   in sentence case: it is the field's accessible name. */
.searchbox { margin: var(--space-3) 0 var(--space-5); max-width: none; }
.searchbox .control-label { display: block; font-size: var(--size-body);
                            margin-bottom: var(--space-2); }
.searchbox-field { position: relative; }
.searchbox-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 64px;
  padding-inline: 16px 6px;
  background: var(--surface);
  border: 2px solid var(--ink);
  border-radius: var(--radius);
}
.searchbox-row:focus-within { outline: 2px solid var(--choice);
                              outline-offset: 2px; }
.searchbox-icon { flex: 0 0 auto; color: var(--ink); }
.searchbox input[type="search"] {
  font: inherit;
  font-size: var(--size-sub);
  flex: 1 1 auto;
  min-width: 0;
  min-height: 56px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--ink);
}
.searchbox input[type="search"]:focus-visible { outline: 0; }
.searchbox input::placeholder { color: var(--ink); opacity: .8; }
.searchbox-go {
  font: inherit;
  font-weight: var(--weight-mid);
  min-height: 48px;
  min-width: 44px;
  padding-inline: 20px;
  border: 0;
  border-radius: var(--radius);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
}
.searchbox-go:focus-visible { outline: 2px solid var(--choice);
                              outline-offset: 2px; }

/* PRODUCT NAMES ONLY (§2.2): a hairline and the paper, no shadow. */
.suggest {
  /* IN NORMAL FLOW: floating, it covered the filter chips and cost a press
     (`session_da/drive_front_door.py`); the page moves when it opens. */
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  max-width: none;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.suggest-item { margin: 0; border-bottom: 1px solid var(--rule-soft); }
.suggest-item:last-child { border-bottom: 0; }
.suggest-item a {
  display: block;
  padding: 8px 11px;
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.suggest-item a:hover,
.suggest-item[aria-selected="true"] a { background: var(--surface); }

/* The result card: a compact list row, three text lines. */
.result-list {
  list-style: none;
  margin: 0 0 1.2rem;
  padding: 0;
  max-width: none;
  border-top: 1px solid var(--rule);
}
.result {
  margin: 0;
  padding: 10px 2px;
  border-bottom: 1px solid var(--rule-soft);
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.result-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Without this a flex child refuses to shrink and the name cannot be
     truncated to one line. */
  min-width: 0;
  flex: 1 1 auto;
}
.result-name {
  font-weight: var(--weight-mid);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.result-price, .result-depth { font-size: var(--size-small); }
.result-price .sep, .result-depth .sep,
.result-count .sep { padding-inline: 6px; opacity: .55; }
/* §2.1's dark day: the depth line in the dark day's tone. */
.result-depth--dark { color: var(--dark-day); }

/* The two filter chips are one scrolling row each (wrapped at 360 they
   stood 362 px tall); nothing hidden, the row scrolls, the page does not. */
.controls[data-search-filters] { display: block; }
.controls[data-search-filters] .control-group {
  flex-wrap: nowrap;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 4px;
}
.controls[data-search-filters] .control-group + .control-group {
  margin-top: 8px;
}
.controls[data-search-filters] .control-label,
.controls[data-search-filters] .chip { flex: 0 0 auto; }

.paging {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 1.4rem;
}
.paging-label { font-size: var(--size-small); color: var(--muted); }
/* SESSION EA2 (DW finding 13): `search.js` hides by `hidden` alone, which
   the display rules above beat; same specificity and later, so it wins. */
.paging[hidden],
.controls[hidden] { display: none; }
.chip[disabled] { opacity: .45; cursor: default; }
/* SESSION GH: the pager at the foot of a paged list. Every control is a
   44-px target; the current page is the reader's place, filled brass. */
.pager { display: flex; flex-wrap: wrap; align-items: center;
         gap: var(--space-2); margin: var(--space-5) 0; }
.pager-pages { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.pager a { display: inline-flex; align-items: center; justify-content: center;
           min-height: 44px; min-width: 44px; padding: 0 var(--space-3);
           border: 1px solid var(--rule); border-radius: var(--radius);
           color: var(--ink); text-decoration: none; }
.pager a:hover { border-color: var(--muted); }
.pager a[aria-current] { background: var(--choice); border-color: var(--choice);
                         color: var(--paper); font-weight: var(--weight-mid); }
/* Under 600 px the numbers take the first line and Previous / Next share
   the second, at its two ends (three lines otherwise on a middle page). */
@media (max-width: 599px) {
  .pager-pages { order: -1; flex-basis: 100%; }
  .pager-step[rel="next"] { margin-inline-start: auto; }
}
.chip-count { font-size: var(--size-fine); opacity: .75; }

/* SESSION FS (2026-10-08): the grid and its switch are gone (ruling Q4
   (D)); the list is the one view. The row keeps the order control,
   which the script unhides: until then the row is not drawn. */
.controls:has(> [data-sort][hidden]:only-child) { display: none; }

/* RTL. The layout mirrors through logical properties; these two are the
   things a text check cannot see. A result name is often a Latin model
   string inside an Arabic page, and a lender list is a run of Latin names
   separated by an Arabic comma. */
[dir="rtl"] .result-name,
[dir="rtl"] .result-lenders { unicode-bidi: plaintext; }

/* ---------- the catalogue: rail, drawer, image slot ----------------
   SESSION DT — the operator's option 1: a filter rail on the leading edge
   above 600 px, one "Filters" button opening a drawer below it, the
   ranking unchanged. `[hidden]` is honoured inside the catalogue whatever
   a class says: the script shows and hides by that attribute alone. */
.catalogue [hidden] { display: none; }

/* The page's glyph symbols, out of sight (not display:none, which some
   engines drop). */
.glyph-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.catalogue.has-rail {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  column-gap: 32px;
  align-items: start;
}
/* The rail's place is kept from the first paint where script will run
   (a layout shift of 0.32 otherwise); without script, the list alone. */
@media (scripting: enabled) {
  .catalogue > .rail[hidden] { display: block; }
}
@media (scripting: enabled) and (min-width: 600px) {
  .catalogue {
    display: grid;
    grid-template-columns: 236px minmax(0, 1fr);
    column-gap: 32px;
    align-items: start;
  }
}
/* SESSION GB: a rail that reads as one — a titled column opened by the
   thread, group titles in ink, counts in grey, a ticked box in brass.
   Restyle only: the price stays the ruled fixed bands, no new script. */
.rail { font-size: var(--size-small); }
.rail-title { font-size: var(--size-section); font-weight: var(--weight-display);
              margin: 0 0 var(--space-2); }
.rail-title::before { content: ""; display: block; width: 2.5rem; height: 3px;
                      background: var(--thread); margin-bottom: var(--space-2); }
.rail-toggle, .rail-close { display: none; }
.facet {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 0;
  padding: 12px 0 10px;
  min-width: 0;
}
.facet legend {
  padding: 0;
  margin-bottom: var(--space-1);
  font-weight: var(--weight-bold);
  font-size: var(--size-body);
}
.facet-value {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 3px 0;
  line-height: 1.35;
  cursor: pointer;
}
.facet-value input { accent-color: var(--choice); margin: 0; flex: 0 0 auto;
                     width: 16px; height: 16px; }
.facet-value:has(input:checked) { font-weight: var(--weight-mid); }
.facet-label { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.facet-count { color: var(--muted); font-size: var(--size-fine);
               font-variant-numeric: tabular-nums; }
/* A value that would leave nothing, given the other filters set. It stays
   choosable — the empty state then says so — and reads as quieter. */
.facet-value.is-empty { color: var(--muted); }
/* SESSION FJ, F5: the drawer under 600 px — 44-px rows; groups other than
   Shop and the category's own facet show a title button (app.js) with + or
   -; string 7's foot link a 44-px row. At 600 px and above all are open. */
.facet-toggle { font: inherit; color: inherit; background: none; border: 0;
                padding: 0; margin: 0; cursor: pointer; text-align: start; }
.rail-results { display: inline-flex; align-items: center; min-height: 44px;
                margin-top: 6px; font-weight: var(--weight-mid); }
@media (max-width: 599px) {
  .rail .facet-value { min-height: 44px; align-items: center;
                       padding-block: 0; }
  .rail .rail-toggle, .rail .rail-close { min-height: 44px; }
  .controls .chip { min-height: 44px; }
  .facet-toggle { display: flex; align-items: center;
                  justify-content: space-between; width: 100%;
                  min-height: 44px; }
  .facet-toggle[aria-expanded="false"]::after { content: "+"; }
  .facet-toggle[aria-expanded="true"]::after { content: "\2212"; }
  .facet.is-collapsed > :not(legend) { display: none; }
}
.rail-note { font-size: var(--size-fine); margin: 10px 0 0; }

.applied { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; }
.chip--applied, .chip--clear { color: var(--ink); }
.chip--clear { border-color: var(--ink); }
.chip-x { margin-inline-start: 6px; }
.showing { font-size: var(--size-small); color: var(--muted);
           margin: 0 0 8px; }

@media (min-width: 600px) {
  /* The rail stays beside the list as it scrolls, and scrolls on its own
     when its facets are longer than the screen (Phones: 24 lenders). */
  .catalogue .rail {
    position: sticky;
    top: 12px;
    max-height: calc(100vh - 24px);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-inline-end: 8px;
  }
}

/* §2.5's one breakpoint. The rail becomes one button; the drawer opens IN
   THE FLOW above the list (DP's D5, the inline drawer): no layer over the
   rows, so nothing covers a control and nothing has to be dismissed before
   a row can be pressed. */
@media (max-width: 599px) {
  .catalogue.has-rail { grid-template-columns: minmax(0, 1fr); }
  .rail-toggle { display: inline-flex; align-items: center; margin-bottom: 10px;
                 border-color: var(--ink); font-weight: var(--weight-mid); }
  .rail-title { display: none; }
  .rail:not(.is-open) .rail-body { display: none; }
  .rail.is-open .rail-body {
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 2px 12px 12px;
    margin-bottom: 12px;
  }
  .rail.is-open .rail-close { display: inline-block; margin-top: 10px; }
}

/* SESSION GB: the card around the photo. One column under 600 px with the
   photo on the leading edge; two across from 600 px and three from
   1,100 px, the photo on top. The slot is ONE token, `--photo-slot`
   (UI §9: 48 px; 140 is the operator's to rule), so both sizes render from
   the same build. The card's content is unchanged (UI §4, DK-1, FA-1–8). */
.catalogue .product-list a, .fd-block .product-list a {
  display: grid;
  grid-template-columns: var(--photo-slot) minmax(0, 1fr);
  column-gap: var(--space-3);
  row-gap: 2px;
  align-items: start;
}
.catalogue .product-list .thumb,
.fd-block .product-list .thumb { grid-row: 1 / span 2; }
@media (min-width: 600px) {
  .catalogue .cat-block .product-list, .fd-block .product-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-4);
  }
  .catalogue .product-list a, .fd-block .product-list a {
    grid-template-columns: minmax(0, 1fr);
  }
  .catalogue .product-list .thumb,
  .fd-block .product-list .thumb { grid-row: auto;
                                   margin-bottom: var(--space-2); }
}
@media (min-width: 1100px) {
  .catalogue .cat-block .product-list, .fd-block .product-list {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
/* P6 (SESSION FS): a list with no slot starts its text at the edge. */
.catalogue .product-name:first-child,
.catalogue .product-name:first-child + .product-meta { grid-column: 1 / -1; }
/* The name and the meta lines sit at the grid's logical start (a Latin
   name on an Arabic page is `plaintext`, so its box is sized to it). */
.catalogue .product-list .product-name,
.catalogue .product-list .product-meta,
.fd-block .product-list .product-name,
.fd-block .product-list .product-meta { justify-self: start;
                                         max-width: 100%; }
.thumb {
  display: grid;
  place-items: center;
  width: var(--photo-slot);
  aspect-ratio: 1 / 1;
  background: var(--photo-ground);
  border-radius: var(--radius);
  overflow: hidden;
  color: var(--glyph-ink);
}
/* The <picture> fills the slot; as a grid item sized to its content it
   stopped at the 96-px file's own width in any slot over 96 (GB2). */
.thumb picture { display: block; width: 100%; height: 100%; }
.thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
/* P6 (SESSION FS): the glyph at the photos' box, 84 % of the slot. */
.thumb .glyph {
  width: 84%;
  height: 84%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}


/* SESSION FA — the category card: line 1 the name, then the code; one
   block per line under it: the shops and months, the lenders, the dated
   cash line, stock only where a side is not in stock. SESSION GB: the name
   in the sub size, the cash figure large and light, so the total stays the
   only bold coloured figure anywhere. */
.catalogue .product-list .product-name,
.fd-block .product-list .product-name { font-size: var(--size-sub);
                                        line-height: 1.35; }
.product-code { font-weight: var(--weight-body); font-size: var(--size-small);
                margin-inline-start: 6px; }
.catalogue .product-list .product-meta,
.fd-block .product-list .product-meta { font-size: var(--size-small); }
.product-meta .card-line { display: block; }
.card-plans, .card-stock { font-size: var(--size-small); }
.card-cash { margin-top: 2px; }
.card-cash .amount { font-size: var(--size-section);
                     font-weight: var(--weight-light); }
.card-cash bdi, .card-stock bdi, .result-price bdi { white-space: nowrap; }
/* SESSION UI2, 2026-10-22 (audit 310; LR2-6, audit 307): the product
   page's out-of-stock line (`gi2_card_lines`) broke its date at a hyphen,
   and the search count line's date (`search.js` factLine, ruling UI2-R1)
   is now a <bdi> of its own; both keep the date on one line. So do the
   other containers whose dates broke in UI2's census of both trees: the
   header's cash line, the notes (every <bdi> in a note is a date or a
   shop name, `.shop` already unbroken) and the banner's date; and the
   plan table's two-date cell (`date_cell`, ruling UI2-R3). */
.pd-stock bdi, .pd-cash-at bdi, .result-count bdi, .note bdi,
.freshness-fold strong, .date-cell bdi { white-space: nowrap; }
.card-cash.is-stale, .card-where.is-stale,
.result-price.is-stale { color: var(--stale); }
.catalogue .product-list .product-meta,
.fd-block .product-list .product-meta { line-height: 1.55; }
/* SESSION FJ, F4: the front door's blocks, drawn by the same card rules. */
.fd-block { margin-bottom: var(--space-6); }
.fd-block h2 { margin: 0 0 var(--space-1); }
/* The first block follows the not-the-same line directly; the search
   results list is empty until a query and keeps no gap of its own then. */
.fd-not-same + .fd-block h2 { margin-top: 0; }
.result-list:empty { margin: 0; }
.fd-count { font-size: var(--size-small); color: var(--muted);
            margin: 0 0 var(--space-3); }
.fd-more { margin: .4em 0 0; }
.fd-more a { font-weight: var(--weight-mid); }
/* the block's link and string 9's link, each alone on its line: 44 px */
.fd-more a, .fd-quote-check a { display: inline-flex; align-items: center;
                                min-height: 44px; }
/* The order control: two radio chips in one group. */
.sort-control { border: 0; margin: 0; padding: 0; min-inline-size: 0; }
.sort-control[hidden] { display: none; }
.sort-control legend { float: inline-start; padding: 0; }
.chip--radio input { margin-block: 0; margin-inline: 0 4px;
                     accent-color: var(--choice); }
.showing [hidden] { display: none; }

@media print {
  .controls, .site-nav, .rowtoggle { display: none; }
  table.data tr.plan-detail { display: table-row; }
}

/* SESSION FD2, P1 — one length on load, without a jump. Until app.js
   clears `data-loading`, a browser that will run script holds back the
   rows the load state hides and shows its sentences. THE HOLD FAILS OPEN
   (audit 160): the hide is the three-second animation `fd-hold`, never the
   cascade, so a script that never arrives leaves the no-script page
   (`session_fd2/evidence/hold_engines.txt`; battery FD2-3). */
@keyframes fd-hold {
  from, to { display: none; position: absolute; visibility: hidden;
             width: 0; height: 0; overflow: hidden; }
}
.load-js { display: none; }
@media (scripting: enabled) {
  .load-js[data-loading] { display: inline; animation: fd-hold 0s 3s forwards; }
  .load-nojs[data-loading],
  [data-load-hide],
  table.data[data-loading][data-default-months="standard"]
    tr.plan:not([data-default-tenor="1"]),
  table.data[data-loading][data-default-months="12"]
    tr.plan:not([data-months="12"]),
  table.data[data-loading] tr.plan[data-default-lender="0"],
  table.data[data-loading] th[data-cell="extra"],
  table.data[data-loading][data-default-months="standard"]
    tr.plan[data-default-tenor="1"][data-default-lender="1"] > [data-cell="extra"],
  table.data[data-loading][data-default-months="12"]
    tr.plan[data-months="12"][data-default-lender="1"] > [data-cell="extra"]
    { animation: fd-hold 3s; }
}
/* Only the extra cells of rows the load state SHOWS are held (a held
   row hides its own). SESSION FD2 (audit 160, ruling 2): string 3's
   fallback, matching nothing since 7 Oct. */
table.data[data-extra-off] th[data-cell="extra"],
table.data[data-extra-off] td[data-cell="extra"] { display: none; }
@media (scripting: none) {
  [data-nojs-hide] { display: none; }
}

/* SESSION GD: the landing. One tile per category, the slot over the bar's
   name and count: two across, four from 600 px, seven from 1,100. */
.lp-tiles { list-style: none; margin: var(--space-4) 0; padding: 0;
  max-width: none; display: grid; gap: var(--space-3);
  grid-template-columns: repeat(2, minmax(0, 1fr)); }
.lp-tiles li { margin: 0; }
.lp-tiles a { display: flex; flex-direction: column; align-items: center;
  gap: var(--space-1); height: 100%; padding: var(--space-2);
  text-align: center; text-decoration: none; color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius); }
.lp-tiles a:hover { background: var(--surface); }
.lp-tiles .thumb { width: min(var(--photo-slot), 100%); }
.lp-tile-name { font-weight: var(--weight-mid); line-height: 1.3; }
.lp-tile-count { font-size: var(--size-small); color: var(--muted); }
/* A row is a front-door block whose cards sit side by side; under 1,100 px
   it scrolls sideways and snaps, the next card cut at the edge. No script. */
.fd-block[data-lp-row] h2 { margin-bottom: var(--space-3); }
.fd-block[data-lp-row] .product-list { display: grid; grid-auto-flow: column;
  grid-auto-columns: 42%; grid-template-columns: none;
  column-gap: var(--space-3); overflow-x: auto;
  scroll-snap-type: x mandatory; overscroll-behavior-x: contain; }
.fd-block[data-lp-row] .product-list li { scroll-snap-align: start; }
.fd-block[data-lp-row] .product-list a { grid-template-columns: minmax(0, 1fr); }
.fd-block[data-lp-row] .product-list .thumb { grid-row: auto;
  margin-bottom: var(--space-2); }
.fd-block[data-lp-row] ~ .note--quote { font-size: var(--size-small); }
.lp-date { font-weight: var(--weight-body); color: var(--muted); }
.lp-notes { margin-top: var(--space-4); padding-top: var(--space-3);
  border-top: 1px solid var(--rule-soft); }
@media (min-width: 600px) {
  .lp-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 1100px) {
  .lp-tiles { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .fd-block[data-lp-row] .product-list { grid-auto-flow: row;
    overflow: visible; grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ---------- SESSION GB: under 600 px (DA-15's one breakpoint) ------
   Last in the file so it outranks the rules above at equal weight. F6 is
   gated on the front door: the search input's top <= 300 px and the first
   card's <= 480 px at 360 (check_geometry). */
@media (max-width: 599px) {
  [dir="rtl"] h1 { line-height: 1.35; }
  .searchbox { margin: var(--space-2) 0 var(--space-3); }
  .searchbox .control-label { font-size: var(--size-small);
                              margin-bottom: var(--space-1); }
  .searchbox-row { min-height: 56px; padding-inline: 12px 4px; }
  .searchbox input[type="search"] { min-height: 48px;
                                    font-size: var(--size-body); }
  .searchbox-go { padding-inline: 14px; }
  main .note.fd-not-same, main .note--quote, main .coverage + .note {
    font-size: var(--size-small); line-height: 1.45; }
  [dir="rtl"] main .note.fd-not-same, [dir="rtl"] main .note--quote,
  [dir="rtl"] main .coverage + .note { line-height: 1.5; }
  main .note.fd-not-same { margin-bottom: 12px; }
  .fd-block > h2::before { content: none; }
  .fd-block .product-list { border-top-width: 1px; }
  [dir="rtl"] .fd-block > h2 { line-height: 1.35; }
  [dir="rtl"] .fd-count { line-height: 1.5; }
  .fd-count { margin-bottom: var(--space-2); }
  .product-list a { padding: 12px 0; }
}

/* ---------- SESSION GI2: the product page (stage 2, audit 204) ------
   GI's draft on the real pages, to the reference study's targets
   (session_x/REFERENCE_PRODUCT_HEADER_2026-10-14.md): [photo | name, cash
   line, count] -- 96 px beside the name under 600 px; from 600 px a
   240-px panel with the 192 file at 1x, centred, never upscaled. Then the
   page's own controls in the rail (a drawer under 600 px; from 1,100 px
   beside the table, from its top), the count line and the rows; the cash
   block and the notes in one fold after them. */
.pd-head { display: grid; grid-template-columns: 96px minmax(0, 1fr);
  column-gap: 12px; align-items: start; margin: 0 0 var(--space-3); }
.pd-photo { width: 96px; }
.pd-id > * { margin: 0 0 2px; max-width: none; font-size: var(--size-fine); }
.pd-id h1 { font-size: var(--size-sub); font-weight: var(--weight-bold);
  line-height: 1.3; letter-spacing: 0; margin: 0 0 var(--space-1); }
.pd-id .note--scope { margin-top: 0; }
/* The card's cash line: the amount alone on its line, shop and date on
   the line under it (the builder wraps the words after the amount). */
.pd-id > .pd-cash { margin: var(--space-2) 0 var(--space-1); }
.pd-cash .amount { font-size: var(--size-section);
  font-weight: var(--weight-bold); line-height: 1.3; }
.pd-cash-at { display: block; }
.pd-cash.is-stale { color: var(--stale); }
main:has(.pd-head) .backlink { margin-bottom: var(--space-2); }
.pd-layout { display: grid; grid-template-columns: minmax(0, 1fr); }
.pd-layout > .rail { min-width: 0; }
@media (scripting: enabled) { .pd-layout > .rail[hidden] { display: block; } }
.pd-rail .controls { flex-direction: column; align-items: stretch; gap: 0;
  margin: 0; }
.pd-rail .control-group { border-top: 1px solid var(--rule);
  padding: 12px 0 10px; gap: 6px; }
.pd-rail .control-label { flex: 0 0 100%; font-size: var(--size-body);
  font-weight: var(--weight-bold); }
.pd-rail .controls > .note { margin: 0 0 10px; }
.pd-rail .chip { min-height: 44px; }
.pd-count .result-count { font-size: var(--size-small);
  font-weight: var(--weight-mid); margin: 0 0 var(--space-1); }
.pd-count > p { max-width: none; }
.pd-source { margin: var(--space-5) 0 var(--space-3);
  border-top: 1px solid var(--rule); }
.pd-source > summary { min-height: 44px; display: flex; align-items: center;
  font-weight: var(--weight-mid); cursor: pointer; }
.pd-source .cash-line { padding-top: var(--space-2); }
main:has(.pd-head) { padding-top: var(--space-4); }
@media (max-width: 599px) {
  main:has(.pd-head) { padding-top: var(--space-2); }
  /* The category chip strip leaves the band on a product page under
     600 px (asked of the orchestrator, Q1): the photo starts 48 px higher. */
  body:has(.pd-head) .site-header .catnav { display: none; }
  .pd-head { margin-bottom: 12px; }
  .pd-layout > .rail .rail-toggle { margin-bottom: var(--space-2); }
}
@media (min-width: 600px) {
  .pd-head { grid-template-columns: 240px minmax(0, 1fr);
    column-gap: var(--space-4); }
  .pd-photo { width: 240px; }
  .pd-photo picture { width: 192px; height: 192px; }
  .pd-id > * { font-size: var(--size-small); }
  .pd-id h1 { font-size: 2rem; line-height: 1.2; }
  .pd-cash .amount { font-size: var(--size-page-title); }
}
@media (min-width: 1100px) {
  .pd-layout { grid-template-columns: 220px minmax(0, 1fr); column-gap: 24px;
    grid-template-areas: ". count" "rail plans"; align-items: start; }
  .pd-layout:not(:has(> .rail)) { grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "count" "plans"; }
  .pd-layout > .rail { grid-area: rail; position: sticky; top: 12px; }
  .pd-count { grid-area: count; }
  .pd-plans { grid-area: plans; min-width: 0; }
  /* Beside the rail the table has 836 px at 1280 (1,080 without it); 111
     of 888 plan tables were wider. The date head's 12rem kept room for the
     flag that left it on 14 Oct (GL), and two cells did not wrap. */
  .pd-plans table.data th, .pd-plans table.data td { padding-inline: 6px; }
  .pd-plans table.data th.date-col { min-width: 0; }
  .pd-plans .date-cell,
  .pd-plans table.data tr.plan > td.num .flag { white-space: normal; }
}
