/* ============================================================================
   form.css  —  RTL-first Typeform skin for the Raqeemi document generator
   ----------------------------------------------------------------------------
   The skin is Typeform's, not Tally's. What that means, concretely:

     ORDER    one question per screen, vertically centred, everything aligned to
              the inline-start edge: question number, question, description,
              answer, then the CTA with its "or press Enter" hint. Progress is a
              4px rail pinned to the bottom of the viewport; "back" is a pill in
              the bottom corner, out of the thumb's forward path.
     COLOUR   Typeform's default theme — white paper, #3D3D3D question ink, one
              teal that owns the number, the answer, the options, the CTA and the
              rail. Nothing else on the screen is coloured.
     TYPE     one family (IBM Plex Sans Arabic), three sizes: question ~34px,
              answer ~28px, everything else 14-17px. The answer is bigger than
              the description on purpose — Typeform's whole trick is that the
              thing you are doing is the biggest thing on the screen.

   Deviations are marked [D] with a reason. The two that matter:
     * the teal is #1E7B79, not Typeform's #4FB0AE. Typeform's own default teal
       is 2.58:1 on white and fails WCAG AA both as answer text and as the CTA's
       background. #1E7B79 is 5.03:1 and reads as the same colour. #4FB0AE is
       kept as --accent-2 for tints.
     * an underline answer field, not a boxed one, everywhere EXCEPT the
       line-items grid, where 15 rows x 5 borderless columns is a soup.

   Layout rules unchanged from the previous skin, because they are still right:
     * markup is emitted by Hologram from a runtime field list
     * page root is  <html lang="ar" dir="rtl">
     * NO left/right anywhere — logical properties only
     * same Chrome renders the PDF, so color-mix / :has / field-sizing are safe
   ========================================================================== */
   An @import at the top of a stylesheet is render-blocking AND serialised — the browser
   must fetch this file, parse line 17, then start a second round trip to a third party
   before it can paint. On the page that must feel instant, that is the worst possible
   place for it. It was also the one asset a network blip could silently degrade, which
   is exactly why the riyal font was vendored in the first place.
   The PDF renderer needs these same files on disk regardless — headless Chrome runs with
   no network in the container — so self-hosting is the same work, done once. */
@font-face {
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/fonts/plex/IBMPlexSansArabic-Light.woff2') format('woff2'),
       url('/fonts/plex/IBMPlexSansArabic-Light.ttf') format('truetype');
}
@font-face {
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/plex/IBMPlexSansArabic-Regular.woff2') format('woff2'),
       url('/fonts/plex/IBMPlexSansArabic-Regular.ttf') format('truetype');
}
@font-face {
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/plex/IBMPlexSansArabic-Medium.woff2') format('woff2'),
       url('/fonts/plex/IBMPlexSansArabic-Medium.ttf') format('truetype');
}
@font-face {
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/plex/IBMPlexSansArabic-SemiBold.woff2') format('woff2'),
       url('/fonts/plex/IBMPlexSansArabic-SemiBold.ttf') format('truetype');
}
@font-face {
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/plex/IBMPlexSansArabic-Bold.woff2') format('woff2'),
       url('/fonts/plex/IBMPlexSansArabic-Bold.ttf') format('truetype');
}

/* Metric-matched fallback, same trick Tally uses for Inter ("Inter Fallback":
   local("Arial") + ascent/descent/size-adjust). Values below are for
   IBM Plex Sans Arabic against Arial; they kill the webfont swap jump. */
@font-face {
  font-family: 'Plex Arabic Fallback';
  src: local('Arial');
  ascent-override: 104%;
  descent-override: 30%;
  line-gap-override: 0%;
  size-adjust: 96%;
}

/* ============================================================================
   1. TOKENS
   ========================================================================== */
:root {
  /* ---- type ------------------------------------------------------------- */
  --font-ar: 'IBM Plex Sans Arabic', 'Plex Arabic Fallback', system-ui, sans-serif;
  /* Latin-only runs (email, URL, VAT, IBAN). Same family — Plex Arabic ships a
     Latin subset (U+0000-00FF), so metrics match and nothing shifts. */
  --font-mono-num: 'IBM Plex Sans Arabic', ui-monospace, monospace;

  --fs-root:   16px;
  --fs-title:  2.5rem;
  --fs-h2:     1.5rem;
  --fs-lead:   1rem;
  --fs-label:  0.9375rem; /* 15px                                            */
  --fs-help:   0.875rem;  /* 14px — [TF] Typeform never goes below 14 on copy */
  --fs-error:  0.8125rem; /* 13px                                            */
  --fs-btn:    0.9375rem; /* the SMALL buttons (عدّل، حذف، تمام كمّل)          */
  --fs-cta:    1.25rem;   /* [TF] 20px — the one forward button, and only it  */

  /* [TF] Typeform's question is ~34px on a laptop and never smaller than 24. */
  --fs-ask:    clamp(1.5rem, 1.05rem + 2vw, 2.125rem);
  /* [TF] The answer is the second-biggest thing on the screen, always. */
  --fs-answer: clamp(1.25rem, 1rem + 1.15vw, 1.75rem);
  --fs-note:   1.0625rem; /* [TF] the description under the question, 17px    */

  --lh-tight:  1.2;
  --lh-body:   1.75;   /* [D] Arabic needs more leading than Typeform's 1.5:
                              tall ascenders (ك ل أ), deep descenders
                              (ج ح خ ع ي), plus optional harakat.             */
  --lh-ui:     1.5;

  /* ---- spacing ---------------------------------------------------------- */
  --sp-1:  0.25rem;  /*  4 */
  --sp-2:  0.5rem;   /*  8 */
  --sp-3:  0.75rem;  /* 12 */
  --sp-4:  1rem;     /* 16 */
  --sp-5:  1.5rem;   /* 24 */
  --sp-6:  2rem;     /* 32 */
  --sp-7:  2.5rem;   /* 40 */
  --sp-8:  4rem;     /* 64 */

  --block-gap:  var(--sp-5);
  --gutter:     var(--sp-3);

  /* ---- measure ---------------------------------------------------------- */
  --q-w:        42rem;   /* [TF] the one-question column, 672px              */
  --page-w:     44rem;   /* the dense multi-field page                       */
  --page-w-wide: 60rem;  /* ONLY the line-items step                         */
  --field-w-sm: 20rem;
  --edge:       1.5625rem; /* 25px viewport gutter on mobile                 */

  /* ---- color: ink / paper ---------------------------------------------- */
  /* [TF] Typeform "Default" theme: white background, #3D3D3D question.       */
  --paper:      #FFFFFF;
  --ink:        #3D3D3D;  /* [TF] question colour, verbatim                  */
  --ink-mid:    #565656;  /* 7.0:1                                           */
  --ink-soft:   #6B6B6B;  /* 4.83:1 — the description, still AA body text    */
  --ink-ghost:  #9A9A9A;  /* placeholders on the dense page. 2.85:1 non-text */
  --line:       #E4E4E4;
  --wash:       #F7F7F7;
  --wash-row:   #FBFBFB;

  /* ---- color: accent — OVERRIDDEN PER CONTRACTOR AT RUNTIME ------------- */
  /* Hologram writes style="--accent:#xxx;--accent-2:#yyy" on <body> from the
     issuer record. Everything below derives, so one row of DB = whole skin.

     [D] #1E7B79, not Typeform's own #4FB0AE. Measured: #4FB0AE is 2.58:1 on
     white, so BOTH the answer text and the white-on-teal CTA fail WCAG AA at
     Typeform's own default. #1E7B79 is 5.03:1, reads as the same teal, and
     keeps every Typeform surface below legal. #4FB0AE survives as --accent-2,
     where it is only ever a tint. */
  --accent:     #1E7B79;
  --accent-2:   #4FB0AE;  /* [TF] Typeform's literal default answer teal     */
  --accent-ink: #FFFFFF;
  --accent-wash: color-mix(in oklab, var(--accent) 7%, white);
  --accent-line: color-mix(in oklab, var(--accent) 30%, white);
  --link:       #1E7B79;  /* 5.03:1 — AA body                                */

  /* ---- color: state ----------------------------------------------------- */
  --error:      #B20110;  /* 7.23:1 -> AAA                                   */
  --error-wash: #FEF2F2;
  --ok:         #15803D;
  --warn:       #B45309;

  /* ---- controls --------------------------------------------------------- */
  --ctl-h:      2.75rem;  /* 44px — the touch floor, and the height Arabic
                             descenders need at 16px                          */
  --ctl-h-sm:   2.25rem;
  --ctl-pad-i:  0.75rem;
  --radius:     0.375rem; /* [TF] 6px — Typeform's button/option radius      */
  --radius-lg:  0.625rem;

  /* ---- shadow ------------------------------------------------------------
     [TF] Typeform puts a shadow on exactly ONE thing, the forward button:
     rgba(0,0,0,.1) 0 2px 4px. The answer field has no shadow at all — it is a
     single 1px underline. The --sh-* recipe below is retained for the DENSE
     page and the line-items grid, which are not Typeform surfaces and would be
     unreadable as a field of borderless boxes. */
  --sh-cta:   rgba(0,0,0,.1) 0 2px 4px;
  --sh-rest:  rgba(0,0,0,.06)     0 1px 1px 0,
              rgba(61,59,53,.14)  0 0 0 1px;
  --sh-hover: rgba(0,0,0,.06)     0 1px 1px 0,
              rgba(61,59,53,.22)  0 0 0 1px,
              rgba(61,59,53,.08)  0 2px 5px 0;
  --sh-focus: color-mix(in oklab, var(--accent) 22%, transparent) 0 0 0 4px,
              var(--accent) 0 0 0 1px;
  --sh-error: color-mix(in oklab, var(--error) 20%, transparent) 0 0 0 4px,
              var(--error) 0 0 0 1px;
  --sh-pop:   0 1px 2px rgba(0,0,0,.06),
              0 8px 24px -6px rgba(0,0,0,.14);

  --dur:  140ms;          /* [TF] Typeform eases in ~0.15s. Fast enough to
                             read as "instant", slow enough to read as
                             "made".                                          */
  --ease: cubic-bezier(.2,.7,.3,1);
}

@media (prefers-reduced-motion: reduce) {
  :root { --dur: 0ms; }
  *, *::before, *::after { animation-duration: 1ms !important; transition-duration: 1ms !important; scroll-behavior: auto !important; }
}
/* ============================================================================
   2. RESET  (Tally ships normalize + 3 globals; this is the same idea)
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  font-size: var(--fs-root);
  -webkit-text-size-adjust: 100%;
  text-rendering: optimizeLegibility;      /* [T] */
  -webkit-font-smoothing: antialiased;     /* [T] */
  -moz-osx-font-smoothing: grayscale;      /* [T] */
  scroll-behavior: smooth;
  /* Arabic-Indic vs Latin digits: force Latin so the form matches the PDF,
     which prints "1,234.50 ج.م". Intl.NumberFormat('ar-EG') would otherwise
     render ١٬٢٣٤٫٥ (verified in Chrome). */
  font-variant-numeric: lining-nums tabular-nums;
}

body {
  font-family: var(--font-ar);
  font-size: 1rem;
  line-height: var(--lh-body);
  color: var(--ink);
  background: var(--paper);
  min-height: 100dvh;
  overflow-x: hidden;                      /* [T] */
}

button, input, select, textarea {
  font: inherit;
  color: inherit;
  -webkit-appearance: none;
  appearance: none;
}
button { background: none; border: 0; cursor: pointer; }
img, svg { display: block; max-width: 100%; }
[hidden] { display: none !important; }

/* Screen-reader-only. [T] Tally uses exactly this for its live region. */
.f-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ============================================================================
   3. SHELL  —  the "no chrome" layout
   ------------------------------------------------------------------------
   Tally has no card, no border, no header bar, no logo strip. The form floats
   in white space 150px down the page. That vertical air IS the product.
   ========================================================================== */
.f-app {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  background: var(--paper);
}

.f-page {
  inline-size: 100%;
  max-inline-size: var(--page-w);
  margin-inline: auto;
  margin-block-start: 9.375rem;   /* [T] 150px */
  margin-block-end: 3.125rem;     /* [T]  50px */
  flex: 1;
}
/* The items step is the ONLY wide step. Scoped to >=759px on purpose: an
   unscoped `.f-page[data-step]` rule (specificity 0,2,0) would out-rank the
   mobile `.f-page` rule (0,1,0) and silently eat the 25px viewport gutter.
   Caught by rendering this file in headless Chrome at 390px. */
@media (min-width: 759px) {
  .f-page[data-step='items'] { max-inline-size: var(--page-w-wide); }
}

@media (max-width: 758px) {       /* [T] Tally's mobile breakpoint */
  .f-page {
    /* PADDING, not a max-inline-size cap. The cap that used to live here —
         max-inline-size: min(var(--page-w), calc(100vw - 2 * var(--edge)))
       held the 25px gutter only until some later rule set max-inline-size on the
       same element, and four of them do:

         .f-pay     42rem   the paywall
         .f-pricing 58rem   /pricing
         .f-cat     56rem   /services
         .f-prose   44rem   /terms

       All four are `.f-xxx` — specificity 0,1,0, identical to `.f-page` — and all
       four appear LATER in this file, so every one of them wins and the gutter
       silently becomes zero. MEASURED at 390px: main and every child at x=0,
       width = full viewport, cards touching both edges.

       This is the exact failure the comment 20 lines above warns about for
       `.f-page[data-step]`, reintroduced by four rules that were never compared
       against it. Padding cannot be out-ranked by a width, so a fifth one added
       tomorrow cannot bring it back.

       `.f-q` — the one-question step, which never lost its gutter — has always
       used `padding-inline: var(--edge)` for exactly this reason. This is the same
       mechanism, applied to the page that forgot it.

       Mobile only: on desktop these pages are narrower than the viewport and
       `margin-inline: auto` already centres them. */
    padding-inline: var(--edge);
    margin-block-start: 3.125rem; /* [T] 50px */
  }
}

/* ============================================================================
   4. TITLE / SECTION / TEXT BLOCKS
   ========================================================================== */
.f-title {
  font-size: var(--fs-title);
  font-weight: 700;            /* [D] Tally 800. Plex Arabic 800 does not
                                  exist; 700 is its top weight and already
                                  reads heavy in Arabic.                    */
  line-height: var(--lh-tight);
  letter-spacing: 0;           /* never letter-space Arabic — it breaks the
                                  joining of the script                     */
  padding-block-end: var(--sp-7);  /* [T] 2.5rem */
  text-wrap: balance;
}
@media (max-width: 758px) {
  .f-title { font-size: 2rem; padding-block-end: var(--sp-6); }  /* [T] */
}

.f-eyebrow {
  font-size: var(--fs-help);
  font-weight: 600;
  color: var(--accent);
  margin-block-end: var(--sp-2);
}

.f-h2 {
  font-size: var(--fs-h2);
  font-weight: 600;
  line-height: 1.35;
  margin-block: var(--sp-7) var(--sp-4);
}
.f-h2:first-child { margin-block-start: 0; }

.f-text {
  line-height: var(--lh-body);
  color: var(--ink-mid);
  margin-block-end: var(--block-gap);
  max-inline-size: 38rem;      /* ~65 Arabic chars — the readable measure   */
}

.f-divider {
  border: 0;
  border-block-start: 1px solid var(--line);
  margin-block: var(--sp-6);
}

/* Links use --link, NOT --accent-2. Measured: #3B82F6 on white is 3.68:1 and
   FAILS WCAG AA for body text. --link is a fixed accessible blue so a
   contractor cannot pick an accent that makes his own links unreadable. */
/* .f-share__btn is excluded for the same reason .f-btn is: it is a BUTTON that
   happens to be an <a>. `a:not(.f-btn)` is 0,1,1 and out-ranks the 0,1,0 that
   .f-share__btn--wa uses to paint itself white, so the WhatsApp button rendered
   #1E7B79 text on #128C7E — measured, and near-invisible. Every button-shaped
   link needs to be listed here or it silently inherits link styling, including
   the currentColor border below. */
a:not(.f-btn):not(.f-share__btn) {
  color: var(--link);
  text-decoration: none;
  border-block-end: 1px solid currentColor;   /* [T] border, not underline —
                                   an underline collides with Arabic
                                   descenders; a 1px border sits lower.   */
  font-weight: 500;                            /* [T] */
}

/* ============================================================================
   5. FIELD  —  label + control + help + error
   ------------------------------------------------------------------------
   DEVIATION FROM TALLY, ON PURPOSE:
   Tally renders NO <label>. The question text is the placeholder plus an
   aria-label. Tally's own accessibility doc says "don't rely on placeholder
   text alone; always give each field a visible label". We use a real label.
   Placeholder becomes an *example*, not the question.
   ========================================================================== */
.f-field { margin-block-end: var(--block-gap); }

.f-label {
  display: block;
  font-size: var(--fs-label);
  font-weight: 600;
  line-height: var(--lh-ui);
  color: var(--ink);
  margin-block-end: var(--sp-2);
}
/* required marker: inline, textual, never colour-only */
.f-label[data-required]::after {
  content: '*';
  color: var(--error);
  font-weight: 700;
  margin-inline-start: 0.25em;
}
.f-label .f-optional {
  font-weight: 400;
  font-size: var(--fs-help);
  color: var(--ink-soft);
  margin-inline-start: 0.4em;
}

.f-help {
  font-size: var(--fs-help);
  line-height: var(--lh-ui);
  color: var(--ink-soft);
  margin-block-end: var(--sp-2);
}

/* ---- the control ------------------------------------------------------- */
.f-input,
.f-textarea,
.f-select {
  inline-size: 100%;
  max-inline-size: var(--field-w-sm);   /* [T] 320px cap = the Tally silhouette */
  block-size: var(--ctl-h);
  padding-inline: var(--ctl-pad-i);
  padding-block: 0;
  border: 0;                            /* [T] border:0, shadow does the work */
  border-radius: var(--radius);
  outline: none;
  background: var(--paper);
  color: var(--ink);
  caret-color: var(--accent);           /* [T] Tally sets caret-color too     */
  box-shadow: var(--sh-rest);
  transition: box-shadow var(--dur) ease-in-out, background-color var(--dur) ease-in-out;
  text-align: start;                    /* flips with dir, never text-align:right */
}

/* Fields that hold a long string get the full column. */
.f-field[data-w='full'] .f-input,
.f-field[data-w='full'] .f-textarea { max-inline-size: 100%; }
/* Fields that hold a token (VAT %, currency, days) stay tiny — this is what
   makes a 15-field form stop looking like a wall. */
.f-field[data-w='xs'] .f-input { max-inline-size: 7.5rem; }
.f-field[data-w='sm'] .f-input { max-inline-size: 12rem; }

@media (max-width: 758px) {             /* [T] */
  .f-input, .f-textarea, .f-select { max-inline-size: 100% !important; }
}

.f-textarea {
  block-size: auto;
  min-block-size: 6rem;                 /* [T] 96px */
  padding-block: var(--sp-3);
  resize: vertical;                     /* [T] */
  line-height: var(--lh-body);
  field-sizing: content;                /* verified supported in this Chrome */
  max-block-size: 22rem;
}

@media (hover: hover) {                 /* [T] Tally gates hover exactly so   */
  .f-input:not([disabled]):hover,
  .f-textarea:not([disabled]):hover,
  .f-select:not([disabled]):hover { box-shadow: var(--sh-hover); }
}

.f-input:focus, .f-input:focus-within,
.f-textarea:focus,
.f-select:focus { box-shadow: var(--sh-focus); }   /* [T] halo, no outline   */

.f-input::placeholder, .f-textarea::placeholder { color: var(--ink-ghost); }

.f-input[disabled], .f-textarea[disabled] {
  background: var(--wash);
  color: var(--ink-soft);
  box-shadow: inset 0 0 0 1px var(--line);
  cursor: not-allowed;
}

/* ---- Latin-in-RTL: email, URL, VAT number, IBAN -------------------------
   Author these with dir="auto" in the markup so a Latin value sits LTR
   inside the RTL page without the caret ping-ponging. The class only
   guarantees the *empty* state (placeholder) still reads RTL-correct. */
.f-input[data-latin] {
  direction: ltr;
  text-align: start;                     /* = left, because direction:ltr    */
  font-variant-numeric: lining-nums tabular-nums;
}
.f-input[data-latin]:placeholder-shown { direction: rtl; text-align: start; }

/* ---- numeric / money ----------------------------------------------------
   NEVER <input type="number">. Verified in Chrome: type=number silently
   DISCARDS Arabic-Indic digits — typing ٥٠ leaves value "" and
   valueAsNumber NaN. Use type="text" inputmode="decimal" and normalize
   ٠-٩ -> 0-9 on $input server-side. */
.f-input[inputmode='decimal'],
.f-input[inputmode='numeric'],
.f-input[data-money] {
  direction: ltr;
  text-align: end;                       /* numbers hug the number column    */
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: 0.01em;
}
.f-money-wrap { position: relative; display: inline-flex; inline-size: 100%; }
.f-money-wrap .f-input { padding-inline-start: 3.25rem; }
.f-money-wrap .f-cur {                   /* the ج.م / ر.س chip inside the box */
  position: absolute;
  inset-inline-start: 1px;               /* flips to the correct edge in RTL  */
  inset-block: 1px;
  display: grid; place-items: center;
  inline-size: 3rem;
  font-size: var(--fs-help);
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--wash);
  border-start-start-radius: calc(var(--radius) - 1px);
  border-end-start-radius: calc(var(--radius) - 1px);
  pointer-events: none;
}

/* MEASURED BUG, FIXED HERE. The chip is absolute at the WRAPPER's inline-start,
   which under dir="rtl" is the right edge. The input above is forced to
   direction: ltr (Chrome discards Arabic-Indic digits in an RTL numeric field),
   so ITS inline-start is the LEFT edge — the 3.25rem was being reserved on the
   side the chip is not on, and the chip sat on top of the digits. Rendered at
   1200px: chip x=807..855, input x=536..856 with padding-left 52px and
   padding-right 12px, and `text-align: end` pushing "1200.00" straight under
   the chip. Keyed off the DOCUMENT direction, because that is what positions
   the chip; the un-forced case (a plain input in a money wrap) is untouched. */
html[dir='rtl'] .f-money-wrap .f-input[inputmode='decimal'],
html[dir='rtl'] .f-money-wrap .f-input[inputmode='numeric'],
html[dir='rtl'] .f-money-wrap .f-input[data-money] {
  padding-inline-start: var(--ctl-pad-i);
  padding-inline-end: 3.25rem;
}

/* ---- select ------------------------------------------------------------- */
.f-select {
  padding-inline-end: 2.25rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-soft) 50%),
                    linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  background-position: right 1rem center, right calc(1rem + 5px) center;
}
html[dir='rtl'] .f-select {
  background-position: left 1rem center, left calc(1rem + 5px) center;
}

/* ---- choice (radio / checkbox) as a full-width tile — [T] Tally style ---- */
.f-choice {
  display: flex; align-items: center; gap: var(--sp-3);
  min-block-size: var(--ctl-h);
  padding-inline: var(--ctl-pad-i);
  border-radius: var(--radius);
  box-shadow: var(--sh-rest);
  cursor: pointer;
  margin-block-end: var(--sp-2);
  transition: box-shadow var(--dur) ease-in-out, background-color var(--dur) ease-in-out;
}
@media (hover: hover) { .f-choice:hover { box-shadow: var(--sh-hover); } }
.f-choice:has(:focus-visible) { box-shadow: var(--sh-focus); }
.f-choice:has(:checked) { background: var(--accent-wash); box-shadow: var(--accent) 0 0 0 1.5px; }
.f-choice input { inline-size: 1.1rem; block-size: 1.1rem; accent-color: var(--accent); appearance: auto; }
.f-choice .f-key {                       /* the little A / B / C badge [T]   */
  min-inline-size: 1.375rem; block-size: 1.375rem;
  display: grid; place-items: center;
  font-size: 0.75rem; font-weight: 700;
  color: var(--ink-soft); background: var(--wash);
  border-radius: 0.25rem;
  margin-inline-start: auto;
}

/* ---- 2-up column pair — [T] .tally-column-list ------------------------- */
.f-cols { display: flex; gap: var(--gutter); }
.f-cols > * { flex: 1 1 0; min-inline-size: 0; }
.f-cols > * .f-input, .f-cols > * .f-select { max-inline-size: 100%; }
@media (max-width: 576px) {              /* [T] Tally collapses at 576px      */
  .f-cols { flex-direction: column; gap: 0; }
}

/* ============================================================================
   6. VALIDATION
   ------------------------------------------------------------------------
   TIMING (this is the whole trick):
     - never on keystroke while the field is still empty-ish
     - first error appears on SUBMIT of the step  (Tally's model)
     - once a field has errored, it re-validates on $input so the user sees
       it clear the moment they fix it  ->  set data-touched="1" on the field
     - focus + smooth-scroll the first invalid control, exactly like Tally's
       `ed()` routine
   ========================================================================== */
.f-field[data-invalid] .f-input,
.f-field[data-invalid] .f-textarea,
.f-field[data-invalid] .f-select { box-shadow: var(--sh-error); }
.f-field[data-invalid] .f-input:focus,
.f-field[data-invalid] .f-textarea:focus { box-shadow: var(--sh-error); }

.f-error {                                /* [T] .tally-validation-error      */
  display: flex;
  align-items: flex-start;
  gap: 0.375rem;
  padding-block-start: var(--sp-1);       /* [T] 5px */
  font-size: var(--fs-error);             /* [T] 12px */
  line-height: 1.45;                      /* [T] 15px; loosened for Arabic    */
  color: var(--error);
  font-weight: 500;
}
.f-error::before { content: '⚠'; font-size: 0.875em; line-height: 1.4; }

/* success confirmation for async checks (e.g. logo uploaded to R2) */
.f-ok { padding-block-start: var(--sp-1); font-size: var(--fs-error); color: var(--ok); font-weight: 500; }

/* ============================================================================
   7. BUTTONS  —  [T] 36px/15px/8px/#000, ours 44px + accent
   ========================================================================== */
.f-btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.5em;                             /* [T] */
  block-size: var(--ctl-h);
  padding-inline: 1.25rem;                /* [T] 0.9em, widened for Arabic    */
  font-size: var(--fs-btn);               /* [T] 15px */
  font-weight: 600;                       /* [T] */
  line-height: 1;
  border-radius: var(--radius);           /* [T] 8px */
  color: var(--accent-ink);
  background: var(--accent);
  outline: 2px solid transparent;         /* [T] reserve the ring, no shift   */
  outline-offset: -1px;                   /* [T] */
  text-decoration: none;
  transform: translateZ(0);               /* [T] */
  transition: background-color var(--dur) ease-in-out,
              color var(--dur) ease-in-out,
              opacity var(--dur) ease-in-out;
  -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) {
  .f-btn:hover:not([disabled]) { background: color-mix(in oklab, var(--accent) 86%, black); }
}
.f-btn:focus-visible {                    /* [T] 4px translucent outline      */
  outline: 4px solid color-mix(in oklab, var(--accent) 34%, transparent);
}
.f-btn:active:not([disabled]) { transform: translateZ(0) scale(.985); }
.f-btn[disabled], .f-btn[aria-disabled='true'] { opacity: .5; cursor: not-allowed; }

.f-btn--ghost {
  background: transparent; color: var(--ink-mid);
  box-shadow: inset 0 0 0 1px var(--line);
}
@media (hover: hover) { .f-btn--ghost:hover:not([disabled]) { background: var(--wash); color: var(--ink); } }

.f-btn--quiet { background: transparent; color: var(--accent); padding-inline: 0.5rem; }
.f-btn--danger-quiet { background: transparent; color: var(--ink-soft); }
@media (hover: hover) { .f-btn--danger-quiet:hover { color: var(--error); background: var(--error-wash); } }

.f-btn--icon { inline-size: var(--ctl-h-sm); block-size: var(--ctl-h-sm); padding: 0; }

/* optimistic submit: label swaps to a spinner, width is locked so nothing
   jumps, pointer-events die so a double-submit is impossible */
.f-btn[data-busy] { pointer-events: none; color: transparent; }
.f-btn[data-busy]::after {
  content: '';
  position: absolute; inset-block-start: 50%; inset-inline-start: 50%;
  inline-size: 1.1em; block-size: 1.1em;
  margin-block-start: -0.55em; margin-inline-start: -0.55em;
  border: 2px solid color-mix(in oklab, var(--accent-ink) 35%, transparent);
  border-block-start-color: var(--accent-ink);
  border-radius: 50%;
  animation: f-spin 1.618s linear infinite;   /* [T] Tally's exact 1.618s     */
}
@keyframes f-spin { to { transform: rotate(360deg); } }

/* the step footer — [T] Tally: display:flex; justify-content:space-between */
.f-actions {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--sp-3);
  margin-block-start: var(--sp-7);
}
.f-actions .f-hint { font-size: var(--fs-help); color: var(--ink-soft); }
.f-actions kbd {
  font-family: var(--font-ar);
  font-size: 0.6875rem; font-weight: 600;
  padding: 0.1rem 0.4rem;
  border-radius: 0.25rem;
  background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--line);
  direction: ltr; display: inline-block;
}

/* ============================================================================
   8. PROGRESS
   ------------------------------------------------------------------------
   [T] Tally renders a real <progress aria-label="Form progress"
       aria-valuetext="Page 1 of 2"> and hides it off-screen.
   [D] We keep the real <progress> for AT *and* show a 2px bar, because our
       form has real steps a contractor must budget time for.
   ========================================================================== */
.f-progress {
  position: fixed; inset-block-start: 0; inset-inline: 0;
  block-size: 2px; z-index: 20;
  background: var(--line);
}
.f-progress > i {
  display: block; block-size: 100%;
  background: var(--accent);
  transform-origin: right center;          /* RTL: fills from the right       */
  transition: inline-size 320ms var(--ease);
}
html[dir='ltr'] .f-progress > i { transform-origin: left center; }

.f-steps { display: flex; gap: var(--sp-2); margin-block-end: var(--sp-5); font-size: var(--fs-help); }
.f-steps li { display: flex; align-items: center; gap: var(--sp-2); color: var(--ink-soft); }
.f-steps li[aria-current='step'] { color: var(--accent); font-weight: 600; }
.f-steps li + li::before { content: '←'; color: var(--line); }  /* RTL arrow  */

/* ============================================================================
   9. THE "YOU'RE RECOGNISED" PILL
   ------------------------------------------------------------------------
   Shown ONLY when the signed cookie / magic link proved identity. Never on a
   merely-typed email — that would leak taxId + logo + client list.
   ========================================================================== */
.f-known {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  margin-block-end: var(--sp-6);
  border-radius: var(--radius-lg);
  background: var(--accent-wash);
  box-shadow: inset 0 0 0 1px var(--accent-line);
  animation: f-known-in 360ms var(--ease) both;
}
@keyframes f-known-in { from { opacity: 0; transform: translateY(-4px); } }
.f-known img { inline-size: 2rem; block-size: 2rem; border-radius: 0.375rem; object-fit: contain; background: #fff; }
.f-known .f-known-txt { min-inline-size: 0; }
.f-known b { display: block; font-size: var(--fs-label); font-weight: 600; line-height: 1.4; }
.f-known small { display: block; font-size: var(--fs-help); color: var(--ink-mid); }
.f-known .f-btn { margin-inline-start: auto; flex: none; }

/* the collapsed issuer block a returning contractor can open to edit */
.f-disclose > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-label); font-weight: 600; color: var(--accent);
  padding-block: var(--sp-2);
}
.f-disclose > summary::-webkit-details-marker { display: none; }
.f-disclose > summary::after { content: '▾'; transition: transform var(--dur); }
.f-disclose[open] > summary::after { transform: rotate(180deg); }
.f-disclose > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 0.25rem; }
.f-disclose > div { padding-block-start: var(--sp-4); }

/* ============================================================================
   10. LINE ITEMS  —  the part Tally cannot do
   ------------------------------------------------------------------------
   Tally has NO repeating-group primitive (its full public CSS class list has
   no repeater). So this is invented, not copied. Rules:
     * ONE header row of column labels, not a label per cell (15 rows x 4
       labels = 60 labels = unreadable)
     * grid, not <table>, so mobile can restack — but role="table"/"row"/
       "cell" on the wrappers keeps it a table for screen readers
     * the row is the unit: focus ring wraps the ROW, not just the cell
     * Enter in the last cell of the last row appends a row (see §12)
   ========================================================================== */
.f-items { margin-block-end: var(--sp-5); }

.f-items-grid {
  /* --li-cols ARRIVES INLINE, from Raqeemi.Fields.Ask.grid_cols/1, because the track
     COUNT has to equal the cell count and only the field knows that. A hard-coded
     six-track recipe lived here and the quotation catalogue renders five cells per
     row, so auto-placement pushed every body row one column off its header. The
     fallback is generic on purpose: it can be wrong about widths, never about which
     value sits under which label. */
  display: grid;
  grid-template-columns: var(--li-cols, repeat(auto-fit, minmax(6rem, 1fr)));
  gap: 0;
}

.f-items-head {
  display: contents;
}
.f-items-head > span {
  position: sticky; inset-block-start: 0; z-index: 3;
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-help);
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--paper);
  border-block-end: 1px solid var(--line);
  white-space: nowrap;
}
.f-items-head > span[data-num] { text-align: end; }

.f-item-row { display: contents; }
.f-item-row > .f-cell {
  padding: var(--sp-1) var(--sp-1);
  border-block-end: 1px solid var(--line);
  background: var(--paper);
  transition: background-color var(--dur) ease-in-out;
}
.f-item-row:nth-child(even) > .f-cell { background: var(--wash-row); }  /* doc_template --roweven */
/* row-level focus: :has means the whole row lights when any cell is active */
.f-item-row:has(:focus-visible) > .f-cell,
.f-item-row:has(:focus) > .f-cell { background: var(--accent-wash); }

/* cells carry naked inputs — no per-cell shadow, or 15 rows = 60 shadows =
   visual noise. The shadow appears only on focus. */
.f-cell .f-input {
  max-inline-size: 100%;
  block-size: 2.5rem;
  box-shadow: none;
  background: transparent;
  border-radius: 0.375rem;
  padding-inline: var(--sp-2);
}
@media (hover: hover) { .f-cell .f-input:hover { box-shadow: inset 0 0 0 1px var(--line); background: var(--paper); } }
.f-cell .f-input:focus { box-shadow: var(--sh-focus); background: var(--paper); }

/* computed line total — read-only, right-aligned, tabular */
.f-cell-total {
  display: flex; align-items: center; justify-content: flex-end;
  padding-inline: var(--sp-3);
  font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
  direction: ltr;
  color: var(--ink);
}
.f-cell-total[data-zero] { color: var(--ink-ghost); font-weight: 400; }

.f-cell-del { display: flex; align-items: center; justify-content: center; }
.f-cell-del .f-btn { opacity: 0; transition: opacity var(--dur); }
.f-item-row:hover .f-cell-del .f-btn,
.f-item-row:has(:focus-visible) .f-cell-del .f-btn { opacity: 1; }
.f-cell-del .f-btn:focus-visible { opacity: 1; }

.f-add-row {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-block-start: var(--sp-3);
  font-size: var(--fs-label); font-weight: 600;
  color: var(--accent);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius);
}
@media (hover: hover) { .f-add-row:hover { background: var(--accent-wash); } }
.f-add-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.f-add-row[disabled] { color: var(--ink-ghost); }   /* max 15 rows reached    */

/* newly appended row gets one soft flash so the eye follows it */
.f-item-row[data-new] > .f-cell { animation: f-flash 700ms var(--ease); }
@keyframes f-flash { from { background: var(--accent-wash); } }

/* ---- mobile: every row becomes a card, labels come back via attr() ------ */
@media (max-width: 758px) {
  .f-items-grid { display: block; }
  .f-items-head { display: none; }
  .f-item-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-2);
    padding: var(--sp-3);
    /* A HEADER STRIP the card's two absolutely-positioned children own outright: the row
       number at the inline start, the delete button at the inline end. Both used to be
       placed over the first cell, which was survivable only while every cell was half
       width — the moment `data-span` gave the first cell the full row (as its catalogue
       `width: "full"` always asked for), the number landed under that cell's own label
       and the delete button landed on its input. 44px + the 8px inset the two are
       positioned at, so neither can reach the cells below. */
    padding-block-start: calc(var(--sp-2) + var(--ctl-h));
    margin-block-end: var(--sp-3);
    border-radius: var(--radius-lg);
    box-shadow: var(--sh-rest);
    background: var(--paper) !important;
    position: relative;
  }
  .f-item-row > .f-cell { border: 0; padding: 0; background: transparent !important; }
  .f-item-row > .f-cell[data-span] { grid-column: 1 / -1; }
  .f-item-row > .f-cell::before {
    content: attr(data-label);
    display: block;
    font-size: var(--fs-help);
    font-weight: 600;
    color: var(--ink-soft);
    margin-block-end: var(--sp-1);
  }
  .f-cell .f-input { box-shadow: var(--sh-rest); background: var(--paper); block-size: var(--ctl-h); }
  .f-cell-total { justify-content: flex-start; }
  .f-cell-del { position: absolute; inset-block-start: var(--sp-2); inset-inline-end: var(--sp-2); }
  /* Back to the 44px touch floor. --ctl-h-sm (36px) is a POINTER size: on desktop the
     button only appears on row hover, where a mouse is already proven present. On a card
     there is no hover, it is always visible, and it is the one destructive control in the
     grid — so it gets the same target every other tappable thing on the phone gets. */
  .f-cell-del .f-btn { opacity: 1; inline-size: var(--ctl-h); block-size: var(--ctl-h); }
  .f-item-row::after {                    /* row counter, so 15 rows stay legible */
    content: attr(data-index);
    position: absolute; inset-block-start: var(--sp-2); inset-inline-start: var(--sp-3);
    /* Sized to the delete button so the two ends of the header strip sit on one line. */
    display: flex; align-items: center; block-size: var(--ctl-h);
    font-size: var(--fs-help); font-weight: 700; color: var(--ink-ghost);
    font-variant-numeric: lining-nums tabular-nums;
  }
}

/* ============================================================================
   11. STICKY SUMMARY  —  live totals
   ------------------------------------------------------------------------
   Pinned to the bottom of the viewport while the items grid is on screen.
   Recomputed on every $input. This single element is what makes the form
   feel like software instead of paperwork.
   ========================================================================== */
.f-summary {
  position: sticky;
  inset-block-end: 0;
  z-index: 10;
  margin-block-start: var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: color-mix(in oklab, var(--paper) 86%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  backdrop-filter: saturate(180%) blur(12px);
  box-shadow: 0 -1px 0 var(--line), var(--sh-pop);
  display: flex; align-items: center; gap: var(--sp-5);
  flex-wrap: wrap;
}
.f-summary dl { display: flex; align-items: baseline; gap: var(--sp-2); }
.f-summary dt { font-size: var(--fs-help); color: var(--ink-soft); }
.f-summary dd {
  font-size: 1rem; font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
  direction: ltr; unicode-bidi: isolate;   /* keeps "1,234.50 ج.م" from
                                              scrambling next to Arabic       */
}
.f-summary .f-grand dd { font-size: 1.375rem; font-weight: 700; color: var(--accent); }
.f-summary .f-btn { margin-inline-start: auto; }
/* number ticks up: no layout shift because tabular-nums fixes glyph width   */
.f-summary dd[data-changed] { animation: f-tick 400ms var(--ease); }
@keyframes f-tick { 0% { color: var(--accent-2); } 100% { color: inherit; } }

@media (max-width: 576px) {
  .f-summary { gap: var(--sp-3); }
  .f-summary .f-btn { inline-size: 100%; margin-inline-start: 0; order: 9; }
}

/* ============================================================================
   12. FILE / LOGO DROP  (browser -> R2 presigned PUT)
   ========================================================================== */
.f-drop {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4);
  border-radius: var(--radius-lg);
  border: 1.5px dashed var(--line);
  background: var(--wash);
  cursor: pointer;
  transition: border-color var(--dur), background-color var(--dur);
  max-inline-size: var(--field-w-sm);
}
@media (hover: hover) { .f-drop:hover { border-color: var(--accent); background: var(--accent-wash); } }

/* ---- the trainee sheet ---------------------------------------------------
   Above the grid, full width, and visibly an ALTERNATIVE to typing rather than a
   required step: a training centre with four trainees types them, one with two hundred
   does not. */
.f-sheet {
  max-inline-size: 100%;
  margin-block-end: var(--sp-4);
  align-items: flex-start;
}
.f-sheet .f-drop-txt { display: flex; flex-direction: column; gap: var(--sp-1); }
.f-sheet[data-busy] { opacity: 0.6; pointer-events: none; }
.f-sheet[data-busy] .f-drop-thumb { animation: f-sheet-scan 900ms linear infinite; }
@keyframes f-sheet-scan { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .f-sheet[data-busy] .f-drop-thumb { animation: none; }
}
.f-drop:focus-within { border-color: var(--accent); box-shadow: var(--sh-focus); }
.f-drop[data-dragover] { border-color: var(--accent); background: var(--accent-wash); border-style: solid; }
.f-drop input[type='file'] { position: absolute; inline-size: 1px; block-size: 1px; opacity: 0; }
.f-drop-txt { font-size: var(--fs-help); color: var(--ink-mid); }
.f-drop-thumb {
  inline-size: 3rem; block-size: 3rem; flex: none;
  border-radius: 0.5rem; object-fit: contain;
  background: var(--paper); box-shadow: inset 0 0 0 1px var(--line);
}
.f-drop-bar { block-size: 3px; border-radius: 2px; background: var(--line); overflow: hidden; margin-block-start: var(--sp-1); }

/* THE THUMBNAIL. It had no rule at all, so the element every image field renders was a
   zero-size inline span — `image-upload.js` sets `background-image` on it the instant a
   file is chosen and nothing appeared. Hidden until it has one, because an empty grey
   square next to «اسحب الصورة هنا» reads as a broken image. */
.f-drop-preview {
  inline-size: 3rem; block-size: 3rem; flex: none;
  border-radius: 0.5rem;
  background: var(--paper) center / contain no-repeat;
  box-shadow: inset 0 0 0 1px var(--line);
}
.f-drop-preview:not([style*='url']) { display: none; }

/* Mirrors .f-sheet[data-busy]. `image-upload.js` sets this while the bytes are in
   flight, and without a rule the only feedback for a 3 MB logo on mobile data was
   nothing happening. */
.f-drop[data-busy] { opacity: 0.6; pointer-events: none; }
.f-drop[data-busy] .f-drop-thumb { animation: f-sheet-scan 900ms linear infinite; }
@media (prefers-reduced-motion: reduce) {
  .f-drop[data-busy] .f-drop-thumb { animation: none; }
}

/* `.f-drop-bar > i` used to live here and matched NOTHING — every template puts a
   `<span class="f-drop-thumb">` inside the bar, never an `<i>`. Nothing animates the
   bar's width either, so the rule was styling an element that does not exist for a
   feature that does not exist. Deleted rather than fixed: the busy animation above is
   the progress feedback. */

/* ============================================================================
   13. THANK-YOU / DONE STATE
   ========================================================================== */
.f-done { text-align: center; padding-block: var(--sp-8); }
.f-done .f-mark {
  inline-size: 3.5rem; block-size: 3.5rem; margin-inline: auto;
  display: grid; place-items: center;
  border-radius: 50%;
  background: color-mix(in oklab, var(--ok) 12%, white);
  color: var(--ok); font-size: 1.5rem;
  animation: f-pop 420ms var(--ease) both;
}
@keyframes f-pop { from { transform: scale(.6); opacity: 0; } }
.f-done h1 { font-size: 1.75rem; font-weight: 700; margin-block: var(--sp-4) var(--sp-2); }
.f-done p { color: var(--ink-mid); }

/* ============================================================================
   14. HIGH CONTRAST / FORCED COLORS / PRINT
   ------------------------------------------------------------------------
   Everything above hangs the affordance on box-shadow. box-shadow is DROPPED
   in forced-colors mode, so without this block the form loses every border.
   ========================================================================== */
@media (forced-colors: active) {
  .f-input, .f-textarea, .f-select, .f-choice, .f-drop, .f-cell .f-input {
    border: 1px solid CanvasText;
    box-shadow: none;
  }
  .f-input:focus, .f-textarea:focus, .f-cell .f-input:focus { outline: 2px solid Highlight; outline-offset: 1px; }
  .f-btn { border: 1px solid ButtonText; forced-color-adjust: none; }
  .f-field[data-invalid] .f-input { border: 2px solid CanvasText; }
}

@media print { .f-progress, .f-summary, .f-actions, .f-add-row, .f-cell-del { display: none !important; } }

/* ============================================================================
   15. COLOR INPUT
   ------------------------------------------------------------------------
   An unstyled <input type="color"> renders as a full-width black slab inside a
   flex row — observed in headless Chrome on the issuer step. It needs an
   explicit box; the swatch itself keeps the native picker.
   ========================================================================== */
.f-field input[type='color'] {
  inline-size: 3rem;
  block-size: 2.75rem;
  flex: none;
  padding: 0.25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  cursor: pointer;
}
.f-field input[type='color']:focus-visible { box-shadow: var(--sh-focus); outline: none; }
.f-field input[type='color']::-webkit-color-swatch-wrapper { padding: 0; }
.f-field input[type='color']::-webkit-color-swatch { border: none; border-radius: 0.25rem; }

/* ============================================================================
   16. THE SAUDI RIYAL SIGN  —  U+20C1
   ----------------------------------------------------------------------------
   SAMA published the sign in Feb 2025; Unicode 17.0 encodes it at U+20C1 SAUDI
   RIYAL SIGN. No shipping OS font carries it yet, so it arrives as a webfont.

   SOURCE  npm @emran-alhaddad/saudi-riyal-font@1.1.0, SIL OFL 1.1.
           Vendored byte-identical at ../fonts/riyal/ ; see that folder's
           SOURCE.txt for the sha1 and the upstream URL.

   THE FONT IS INLINE, AS A data: URI, ON PURPOSE — THREE MEASURED REASONS:

   (a) NO TOFU, EVER. Measured in headless Chrome 144: a bare U+20C1 with no
       matching font is a 75px box at 100px type, exactly like any unmapped
       codepoint. It falls back to NOTHING. A separate font FILE can fail on
       its own (proxy, blip, cold cache) while this stylesheet succeeds, and
       that window prints "1,200.00 ☐" on a quotation. Inline, the face and
       the rules that use it are the same 1.2 KB of bytes: if one arrives the
       other did. There is no window.
   (b) THE PDF. The document is headless Chrome over HTML written to a temp
       dir, so a RELATIVE url() resolves against the wrong directory and
       Chrome fails SILENTLY and exits 0 — non-negotiable 12, the same trap
       the logo has. MEASURED: the same page rendered with a broken relative
       url() produced a PDF with the riyal face simply absent from `pdffonts`
       and no warning anywhere. A data: URI cannot be resolved wrongly.
   (c) It is 1,268 B (regular) + 1,448 B (bold) of woff2, 3,624 B as base64.
       One fewer request costs more than those bytes on a 4G handset.

   The .ttf second source is for a browser without woff2 only. In practice the
   data: URI always wins, so ../fonts/riyal/*.ttf is never fetched — it is kept
   in `src` so the vendored files stay reachable rather than becoming dead
   weight nobody dares delete.

   unicode-range IS LOAD-BEARING, NOT AN OPTIMISATION.
   The font's cmap holds NINE codepoints: U+0041 U+0042 U+0043 U+0044 U+20C0
   U+20C1 U+E001 U+E002 U+E900. It maps the LETTERS A B C D. MEASURED: with the
   family in a text run and no unicode-range, "ABCD" renders at width 0.000px —
   those four glyphs have zero advance, so every A, B, C and D in the run turns
   INVISIBLE. With the unicode-range below, "ABCD" measured 277.844px, byte-for-
   byte identical to the same run without the family. Never remove it.

   font-display: swap is declared as the brief requires, though with an inline
   src there is no fetch and therefore no swap period to govern.
   ========================================================================== */
@font-face {
  font-family: 'SaudiRiyal';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  unicode-range: U+20C1, U+E900;
  src: url(data:font/woff2;base64,d09GMgABAAAAAAT0AA0AAAAAC2QAAASeAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGhgGYACCZhEICocEhXcLGgABNgIkAzAEIAWHLgeBBRvVCSCOwnEvSojJ5ORhKiWep9/bPPc32mdnUaW+wXeJKlVIJCg8CIfcQWViVHSETJeZqNqErgZPp/28VaAy13pTkXvNQZv4IHCbHD9In+dyevP5Cfy7K+slmC3ybGwuLx2T18L1PKA2pnPbFCvQCdbLLSJq0N0AB31thACv29f9APD1k6ZUJ0tAD4aoQDO8bhmyFC1UI+oCuAO15AO2Q8G5MwSqDefwClCi9Be6wX9nQPsMEKAeA1AQUIMaLCGACzQCljAqQE+OwoCjcu0D6kv8/x/sl6sfkIpP/3/9/WpgdgoEQAeYoIR0QACkAlSghuJ31w3LAXyr/P+vAvSAOTCAzAK5B6CBQzRNhcGgGoYLL+dYrlnFKgu9iOcEj646uXaI2W3sJ9lTI4EetCRQAgulmH6SYMZBziQb1xBipCaQaS6l0vWRWr1q1aFDnTA52r1fr8fUTpXKWvLQ4KnSwb0+PppFHxtVC1at2uXrO9JOSIUWTzV+QWdXLT0K2uhG0jpJEiZs6WjSR9KuG9Zv1u+P2q1UujpcsT1L3dNzqhQszNevVtPHSYnHKlXoGEWtDpi6yX/duUgVJGGvzwIypcpt/Ni4Jly1atcq32n6zc+V4+ld7k6gz53h7vgOHLKN10L3KkUS7kouYUcPhGVony1zOi//ctkTdm/3kAqubk+P0MptoSFlXH1d5ZyIwuAyWX7+z+picypWzAjs1xCbVaGJLgqt5qqbqg/Y6ntEjm76aneF9Yjvano5HXrdrjSkllfHmVOk+o/IY8O89mBNc9Lbwcoe28z7DRmFzSmN4wbrT5X+B/E2SdMQn2S1PFaUu1OkJfIXKxVt+w1pFa2xjdll47yrvsvczl7/4ETotcmj56Zkzxs3b+7w0ZMNx9wX7srQ8rIsghhH+yj839cG7rwS/FVUQnWXgYUdjUP+HL5C8fybvBJ6ZQRWMKOhfsi7ZqHZEz4e3TZxMtEqXR8kGN0n/5/a9jk9IKKiFlmPtlWvYKy8rF34efCtxR9snP0uNWnu7Jj8kj9HHV1INdJDNT2jhozoFXwqYPLE+VjfWDBpsq7FUrJmU3brXPVwppI6gm8PCAjcX9RU3FdX8KtcJcczkIBc+4K9C8ZdxNPL/LtD9KcF19fLgOpBtODVPMNO+5d0/C2p2BEBPefddskgYA8Aa9mMABgKQwChUg9RkbGjprcJDW9v0SJ+QccrQRhDkk6Os7mYoEtPTCIZQk6xo9ALyCFMI3lIDtjnGFLlm9EzZJUcNNIoAAUACL10ULSMoCUYAkaUk2BRfgAnwm+QoZJMytlMKIQkH0pUBpAqdhjUMjIGGlTuklr2MTzoI92UJ23+wONVGmQ40WC8UgM5iCwsRBYopXLQcHEwXzrQIVosogW4IRrFwqmv4ZzGEQ2Em8tRFmff4c5x4kDgmpXdKAOJhsMnRWOAIg8/SVZ+YGXZ5ee18vMkZYbYopod2PHxMh22lbggxy4aiTp8UzunkRxGMUliNsQgyayTKk8k+l05RzpGmT7YjNb5FSWKKTxHRVTCPj0ISoRIUaJDEZowhCWctufXnHPwkJG/e0pZDQA=) format('woff2'),
       url('../fonts/riyal/saudi-riyal-400.ttf') format('truetype');
}
@font-face {
  font-family: 'SaudiRiyal';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  unicode-range: U+20C1, U+E900;
  src: url(data:font/woff2;base64,d09GMk9UVE8AAAWoAAoAAAAADGQAAAVcAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYxyBmAAdAE2AiQDKD9sdGFnEgQGBYctByAbrAtRlCxOieyLA54Mb+RgxYeVCNNn6ibiVNV+Iu7SXN7DMXieH4d/7tNARoBxEVJoS0JXyFsoW1Moa0KaP3h17UL8Z0G+wBqkolCFS0d0cb3/9H9m67tCDa7gd/JWZq0/QJPsJM2HiFWr1tLePW+ILU35CMfCxNgIk9+uDhIpVNFAwkU4QgfkEqEjfCxJFyFEQJA608kHaOYTPEYwgogEzt+7MCg9+j+fFrH2vqj2xzh+xKofcfpHvFlZiXX+sfa3jQP2dSsOW6ke7uaC470xi5nB3UyAMN/6tK8HXiIqOC4pOSU1LTe7+EyoLja6TllRtXBaqpb1etfB2QghKyIRUSIlSng7vBVCM4PF4QnqUy9mzx63qH8GMALA+KHXAAKchaCqZ3+R9X/T3/FDL8bx5L4+sqOeQ/5kKShTh/Ce48012bmYEdGdKW1ZTQRI178DKPS07zm3iwTINywBSLqB/R2BAbxZi7VAsLxAPtouAHzuGJ8w3+AASnjUzhkAeTo6eFgHZ1Mq65uiO5LbR1TD+NN8bJvU620Yk8zmMK/2G0qac1hVc3NNZxEBkE+Jxnt0kTnLrLZ12DwLn9/vhJ/xPeum18lbDgD48nGcyRQmqo+3/eOHXpPN7in/xE6XHH+4VAK4GjJdBK1a/B+EZwCoF717sIcAWAKUKijRP9EyrX0Pr+X2ZhH/XGiehgi/3QS2JhDMPmtjok7o3cbtlrVcJfoX5PgLmFA1CDp+ItViuqSNoImt2dPrzJJOwC3sI5gXX3ib1xLwbVpiHJ+pkEnzRwD8UA5KrhwOyLRRrsfS0x3NFI8BSQ3ll0cgR+ghQDEhrhdXg9MdEpaIHNLxisDya2dFiWy0UNHEvPpWBUegl4TBX6omfTKiAIWSAoH6UKuXeIqi3ARnuJJHuk+albhwLVM/fKhbiF2gKx3ha3kgF77uJK2dXR24Ipz4coGvkPAVoLjYhNSoaKq4Ed0rXiHhmeQZnhGwBgBZiOZ5XkXkVolsdFFsUEJAUgglBieHxYIAphOx+ExNxn+9UbR9llsFeRWGlUc2UFxQfGB8CMWER4fHRFBseGxEQialIjlaeCw+Fh5RxMOAl6f7FrvF/58AtIQDxPb5sX272CC+hBoOF+1N20ZAO3JAhUgS8wIpNyjdI96bCiwEW+EsBcUHxPvEkV+sX6QvCODKAEiiJ9ELNyWFjIkjAJkOAAKEeEEkz58X/q6+Q3wi0/yQPV8bSQDkSgCBa40AwDUBAmSTAJDYjqKINgIUkwSAwMURAOTDAWK60UzXk6nW05/qx3ebflFGQ1JrfPMq+xvuQe4gDa0WJBoLalm1AavaQeSS7BSDNgkEb0YLNyCJ/Jz/vybInkQbBKE96MnSAeG+GEYqwgViut+pmkKl3VHi8RW+Us/8/0+3+P+R4LrUXeCSGeGFAPgqgb1jdlJlX6Q9Abi7s2atyJ5Pbq1GZeBMWH6zvhT299ZbDArMSYbMX5lgsUvtsEBqcAqhZsgb/9yNWU1Nom1LhHCIwpNw0aQ7EEOieLEoNjNdABEPFHkxPcWLeBVLvbtLfRBf8nQs9WOpP0sDJ9ESlDaLNYEllkTw4UIUhlvRhJQlhkCPYhG2xBHAhXgg5Mf0zPCKtn33wRuzQUBVdA4NAQRwEjWgRyPGa5XAIoAAAggggAACACGAAEAIIAAQAsjh83K3Si91KNF/uUlhXUsVkQf1dlBvDnQHmEtGtTejszIqISuPyBhOftilcytw+ut9M94gSbF6NydKLHAzJRInatEz1EUvX3dC/Xz86cw1U5dXVV7BcH91d3ErkDkY6MP2Lppo3ZLES+GNI8oAAAA=) format('woff2'),
       url('../fonts/riyal/saudi-riyal-700.ttf') format('truetype');
}

/* ---- the amount + its unit ----------------------------------------------
   MARKUP (Raqeemi.Riyal.html/3 emits exactly this; a ~HOLO template composes the
   same nodes, because Hologram escapes interpolated strings):

     <span class="f-money">
       <bdi dir="ltr">1,200.00</bdi>
       <span class="f-sar" role="img" aria-label="ريال سعودي"
         ><span class="f-sar__t">ر.س</span></span>
     </span>

   Two rules decided by measurement, both in headless Chrome under dir="rtl":

   ORDER. The unit goes AFTER the amount, which puts it to the LEFT of the
   digits — the order engine.gs already prints and the order a Saudi reader
   expects. Wrapping the digits AND the unit together in one <bdi dir="ltr">
   FLIPS the pair and puts the sign to the right of the number. So the <bdi>
   covers the DIGITS ONLY; the unit is its own element outside it.

   FALLBACK. The letters ر.س are REAL DOM TEXT. This file hides them and draws
   the glyph instead — and this file is also where the font lives. So:
       stylesheet applied -> face present -> glyph
       stylesheet absent  -> no ::before  -> "ر.س"
   There is no third state and no JavaScript font probe. An email client that
   strips CSS shows ر.س; it never shows a box.
   -------------------------------------------------------------------------- */
.f-money {
  white-space: nowrap;                  /* the number never breaks from its unit */
  font-variant-numeric: lining-nums tabular-nums;
  /* THE PAIR IS RTL, THE DIGITS ARE NOT. Saudi form is «٢٫٠٠ ر.س» — the amount on the
     right, the unit to its LEFT, so the unit is read after the number.

     Raqeemi.Amount emits <bdi dir="ltr">digits</bdi> then <Raqeemi.Sar>, and the digits
     keep their own LTR run inside that isolate. What is left for this rule to decide is
     only which SIDE the unit lands on — and that was being decided by whatever ancestor
     happened to set a direction. `.f-summary dd` sets `direction: ltr` (correctly, to
     stop a grouped "1,234.50" scrambling), `.f-money` inherited it, and the pair laid
     out left-to-right: unit to the RIGHT of the number, which in Arabic reads as the
     unit coming first. MEASURED on the totals rail: digits at x=42..66, ر.س at x=69.

     Pinning it here rather than unsetting the ancestor keeps every amount in the product
     — totals, review rows, the done screen — identical no matter what encloses it.

     `unicode-bidi` is not optional and `direction` alone does nothing without it: on an
     INLINE box the bidi algorithm only re-orders when unicode-bidi is something other
     than `normal`. MEASURED: with `direction: rtl` and the default `normal`, the unit
     stayed at x=77 to the right of digits at x=42..73 — the declaration was inert.
     `isolate` over `embed` because it also seals the amount off from the Arabic label
     beside it, which is the same reason `.f-summary dd` sets it. */
  direction: rtl;
  unicode-bidi: isolate;
}

.f-sar {
  display: inline-block;
  line-height: 1;                       /* the face's own metrics never grow a line box */
  font-variant-numeric: normal;         /* it is a sign, not a figure */
}

/* The glyph. 1.08em is measured, not taste: the U+20C1 ink is 0.62em tall on
   the baseline, IBM Plex Sans Arabic figures are 0.72em. 0.62 x 1.08 = 0.67em,
   ~93% of the digit height — a currency sign that matches digits exactly reads
   too loud. Ink descent is 0.01em, so no vertical-align nudge is needed;
   the package README's "-0.1em" advice is for its own 1.0.x baseline. */
.f-sar::before {
  content: '\20C1';
  font-family: 'SaudiRiyal', sans-serif;
  font-size: 1.08em;
  font-weight: inherit;                 /* picks the 700 face inside a bold total */
  font-style: normal;
}

/* Present in the DOM so a CSS-less renderer has something to print; gone the
   instant this file is parsed. role="img" + aria-label on the parent means AT
   reads "ريال سعودي" either way, so hiding it costs nothing. */
.f-sar__t { display: none; }

/* Both units read correctly inside the .f-cur chip of a money input, so
   <span class="f-cur"><span class="f-sar" …>…</span></span> needs no new rule. */

/* ============================================================================
   17. ONE QUESTION AT A TIME  —  the Typeform step shell
   ----------------------------------------------------------------------------
   .f-q REPLACES .f-page for a single-question step. Do not nest one in the
   other: .f-page carries a 150px top margin that fights vertical centring.

     <main class="f-app">
       <form class="f-q" data-enter>
         <div class="f-rail" role="progressbar" …><i style="inline-size:43%"></i></div>
         <p class="f-q__count"><bdi dir="ltr">3</bdi> من <bdi dir="ltr">7</bdi></p>
         <p class="f-eyebrow">بياناتك</p>
         <h1 class="f-q__ask">اسم شركتك؟</h1>
         <p class="f-q__note">يطلع في ترويسة العرض</p>
         <div class="f-q__answer"> … one control … </div>
         <div class="f-q__nav"> … </div>
       </form>
     </main>

   THE TYPEFORM ORDER, top to bottom: number, section, question, description,
   answer, forward. Everything is aligned to the inline-start edge and the block
   is centred in the viewport — never centred text, which is unreadable in
   Arabic at this size.
   ========================================================================== */
.f-q {
  inline-size: 100%;
  max-inline-size: var(--q-w);
  margin-inline: auto;
  padding-inline: var(--edge);
  padding-block: var(--sp-8) var(--sp-7);
  text-align: start;
}

/* Vertical centring ONLY when .f-q is the step itself. 100dvh, not 100vh: on
   iOS the dynamic unit shrinks when the keyboard opens, so the question stays
   above the keyboard instead of being pushed under it. */
.f-app > .f-q {
  min-block-size: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
}

/* ---- 1. the question number — [TF] the accent's first appearance -------- */
/* The counter and the one way out share a line, so the door costs no vertical space on a
   phone — the screen the form is centred on is 100dvh and every row spent is a row the
   question does not get. See Raqeemi.StepNav for why there is exactly one link here. */
.f-q__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-block-end: var(--sp-3);
}

.f-q__count {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  align-self: flex-start;
  font-size: var(--fs-help);
  font-weight: 600;
  line-height: var(--lh-ui);
  color: var(--accent);
  font-variant-numeric: lining-nums tabular-nums;
}

/* Quiet on purpose: --ink-soft, not --accent. The forward button is the only accented
   thing on the screen (§17) and an exit that competes with it is an exit that gets
   taken. It is discoverable, not inviting. */
.f-q__exit {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  font-size: var(--fs-help);
  font-weight: 600;
  color: var(--ink-soft);
  text-decoration: none;
  /* The counter's ← means forward, so out is the other way. */
  /* Quiet does not mean small: it is a top-corner tap, next to the browser's own chrome,
     which is where a mis-tap is most likely. Same 44px floor as every other control. */
  min-block-size: var(--ctl-h);
  padding: var(--sp-1) var(--sp-2);
  margin-inline-end: calc(var(--sp-2) * -1);   /* optical: sits flush with the edge */
  border-radius: var(--radius);
}
.f-q__exit::before { content: '→'; font-size: 0.85em; opacity: 0.75; }
html[dir='ltr'] .f-q__exit::before { content: '←'; }
@media (hover: hover) { .f-q__exit:hover { color: var(--ink); background: var(--wash); } }
.f-q__exit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* [TF] Typeform puts a small arrow after the number. RTL: forward is ←. */
.f-q__count::after { content: '←'; font-size: 0.85em; opacity: 0.75; }
html[dir='ltr'] .f-q__count::after { content: '→'; }

/* ---- 1b. «بياناتك محفوظة» ------------------------------------------------
   The returning contractor's receipt AND his way back in. Quiet on purpose: it is
   a statement of fact he only acts on when something has changed, so it must not
   compete with the question. Sits above the section name, below the number. */
.f-saved {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  align-self: flex-start;
  margin-block-end: var(--sp-4);
  padding: var(--sp-1) var(--sp-3);
  border-radius: 999px;
  background: var(--accent-wash);
  box-shadow: inset 0 0 0 1px var(--accent-line);
  font-size: var(--fs-help);
  color: var(--ink-mid);
}
.f-saved__txt { min-inline-size: 0; overflow-wrap: anywhere; }
.f-saved .f-btn { block-size: auto; padding: 0 0.25rem; font-size: inherit; font-weight: 600; }

/* The currency token when it is NOT the riyal — «ج.م» is a live value in this
   database, and it is printed exactly as the contractor typed it. */
.f-cur-txt { font-size: 0.8em; }

/* ---- 2. the section name ------------------------------------------------
   Typeform has no eyebrow. Ours exists because a 16-question document has real
   sections, but it must never compete with the number for the accent. */
.f-eyebrow {
  font-size: var(--fs-help);
  font-weight: 500;
  color: var(--ink-soft);
  margin-block-end: var(--sp-2);
}

/* ---- 3. THE question ---------------------------------------------------- */
.f-q__ask {
  font-size: var(--fs-ask);
  font-weight: 600;              /* [D] Typeform 700. Plex Arabic at 700 is a
                                    slab at 34px; 600 carries the same weight
                                    of voice without closing the counters.   */
  line-height: 1.4;
  letter-spacing: 0;             /* never letter-space Arabic: it breaks the
                                    joining of the script                     */
  color: var(--ink);
  text-wrap: balance;
  margin-block-end: var(--sp-2);
}

/* ---- 4. the description — ONE short line -------------------------------- */
.f-q__note {
  font-size: var(--fs-note);
  line-height: var(--lh-body);
  color: var(--ink-soft);
  margin-block-end: 0;
  max-inline-size: 34rem;
}

/* ---- 5. the answer ------------------------------------------------------
   THE Typeform control: no box, no shadow, no radius. One hairline underline in
   the accent, text in the accent, the caret in the accent. It is the biggest
   interactive thing on the screen and it looks like writing on paper.

   `:not(.f-cell) >` is load-bearing. The line-items grid renders .f-input inside
   .f-cell, INSIDE .f-q__answer, and 15 rows x 5 borderless columns is a soup —
   the grid keeps its boxes (§10). The child combinator also lifts these rules
   to 0,3,0 so they beat §10's 0,2,0 without !important.                       */
.f-q__answer { margin-block-start: var(--sp-6); }

.f-q__answer :not(.f-cell) > .f-input,
.f-q__answer :not(.f-cell) > .f-select,
.f-q__answer :not(.f-cell) > .f-textarea {
  font-size: var(--fs-answer);
  font-weight: 400;
  line-height: 1.6;
  color: var(--accent);
  caret-color: var(--accent);
  background: transparent;
  border-radius: 0;
  box-shadow: none;
  border-block-end: 1px solid var(--accent-line);
  padding-inline: 0;
  padding-block: var(--sp-2);
  block-size: auto;
  max-inline-size: 100%;
  transition: border-color var(--dur) ease-in-out, box-shadow var(--dur) ease-in-out;
}

.f-q__answer :not(.f-cell) > .f-textarea {
  min-block-size: 4.5rem;
  max-block-size: 40vh;
  line-height: var(--lh-body);
  resize: none;                  /* field-sizing: content already grows it    */
}

/* The <select> keeps its own caret glyph (§5 paints it as two gradients), so
   only the padding needs restating now that the box is gone. */
.f-q__answer :not(.f-cell) > .f-select {
  padding-inline-start: 0;
  padding-inline-end: 1.75rem;
}

/* A field that ASKED to be small stays small — VAT %, currency, days. */
.f-q__answer .f-field[data-w='xs'] :not(.f-cell) > .f-input { max-inline-size: 9rem; }
.f-q__answer .f-field[data-w='sm'] :not(.f-cell) > .f-input { max-inline-size: 15rem; }

.f-q__answer :not(.f-cell) > .f-input::placeholder,
.f-q__answer :not(.f-cell) > .f-textarea::placeholder {
  color: color-mix(in oklab, var(--accent) 42%, white);
}

@media (hover: hover) {
  .f-q__answer :not(.f-cell) > .f-input:not([disabled]):hover,
  .f-q__answer :not(.f-cell) > .f-select:not([disabled]):hover,
  .f-q__answer :not(.f-cell) > .f-textarea:not([disabled]):hover {
    border-block-end-color: var(--accent);
  }
}

/* Focus thickens the underline with a SHADOW, not a border-width change: a 2nd
   pixel of border reflows the control and the caret jumps as you tab in. */
.f-q__answer :not(.f-cell) > .f-input:focus,
.f-q__answer :not(.f-cell) > .f-select:focus,
.f-q__answer :not(.f-cell) > .f-textarea:focus {
  border-block-end-color: var(--accent);
  box-shadow: 0 1px 0 0 var(--accent);
  outline: none;
}

.f-q__answer .f-field[data-invalid] :not(.f-cell) > .f-input,
.f-q__answer .f-field[data-invalid] :not(.f-cell) > .f-textarea,
.f-q__answer .f-field[data-invalid] :not(.f-cell) > .f-select {
  border-block-end-color: var(--error);
  box-shadow: 0 1px 0 0 var(--error);
  color: var(--ink);
}

/* The currency chip is a box affordance and there is no box any more. It stays
   as a quiet unit trailing the number. */
.f-q__answer .f-money-wrap .f-cur {
  background: transparent;
  color: color-mix(in oklab, var(--accent) 70%, white);
  font-size: 0.75em;
  border-radius: 0;
}

.f-q__answer .f-field { margin-block-end: 0; }
.f-q__answer .f-field + .f-field { margin-block-start: var(--sp-5); }

/* The label is redundant when .f-q__ask IS the question. Keep it in the DOM
   for the input's accessible name; hide it visually. Give the field a visible
   label back by adding data-labelled to .f-q__answer. */
.f-q__answer .f-label {
  position: absolute; inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
.f-q__answer[data-labelled] .f-label {
  position: static; inline-size: auto; block-size: auto;
  margin: 0; overflow: visible; clip-path: none; white-space: normal;
  font-size: var(--fs-label); color: var(--ink-mid);
}

.f-q__answer .f-error,
.f-q__answer .f-help { font-size: var(--fs-help); }
.f-q__answer .f-help { margin-block-start: var(--sp-2); }

/* The multiselect's live count, and the issuer step's own wrapper. */
.f-count { display: block; font-size: var(--fs-help); color: var(--ink-soft); margin-block-start: var(--sp-3); }
.f-issuer-q { display: block; }

/* ---- 6. moving on ------------------------------------------------------
   [TF] One large accent button, its shadow the only shadow on the screen, and
   a keyboard hint beside it. "Back" is NOT here — it is a pill in the corner,
   out of the thumb's forward path. */
.f-q__nav {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-block-start: var(--sp-7);
}

.f-q__next {
  block-size: auto;
  padding: 0.65em 1.2em;         /* [TF] */
  font-size: var(--fs-cta);      /* [TF] 20px */
  font-weight: 700;
  line-height: 1.35;             /* Arabic descenders need it; 1 clips ج ي    */
  border-radius: var(--radius);
  box-shadow: var(--sh-cta);     /* [TF] the ONE shadow                       */
}

/* [TF] Typeform's bottom-corner navigation. Fixed, so it never moves between
   questions and the thumb learns one place. Inline-start = the left corner
   under dir="rtl", i.e. behind the reading direction: where "back" belongs. */
.f-q__back {
  position: fixed;
  inset-block-end: var(--sp-5);
  inset-inline-start: var(--sp-5);
  z-index: 12;
  margin: 0;
  block-size: var(--ctl-h);
  padding-inline: var(--sp-4);
  border-radius: 999px;
  color: var(--accent);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--accent-line), var(--sh-cta);
}
.f-q__back::before { content: '↑'; font-weight: 700; }
@media (hover: hover) {
  .f-q__back:hover:not([disabled]) { background: var(--accent-wash); color: var(--accent); }
}

/* Keyboard-first, and honest about it: the hint only exists where a keyboard
   does. (hover: none) is the touch case — a phone gets no Enter hint. */
.f-q__kbd {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-size: var(--fs-help);
  color: var(--ink-soft);
}
.f-q__kbd kbd {
  font: inherit;
  font-weight: 700;
  color: var(--ink-mid);
  direction: ltr;
  background: transparent;
  box-shadow: none;
  padding: 0;
}
.f-q__kbd kbd::after { content: ' ↵'; font-weight: 400; }
@media (hover: none) { .f-q__kbd { display: none; } }

/* ---- 7. the review screen and the thank-you, in the same voice ---------- */
.f-review__row { border-block-end-color: var(--line); }
.f-review__row .f-text { color: var(--ink); }
/* The totals block. MOBILE-FIRST: a stacked ledger, one label/value row per line,
   never a wrapping flex strip — on a 390px handset the old single-row layout put
   «الإجمالي» and its number on separate lines with no relationship between them.

   Every row a Saudi invoice has to state before the contractor approves it:
   net, discount, VAT at its own rate, then the gross. He is approving a number
   that a PDF will print; he has to be able to see how it was reached. */
.f-q .f-summary {
  background: transparent;
  box-shadow: none;
  backdrop-filter: none;
  padding-inline: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-2);
  max-inline-size: 24rem;
}
.f-q .f-summary .f-tot,
.f-q .f-summary .f-grand {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: var(--sp-3);
}
.f-q .f-summary .f-tot dt { font-size: var(--fs-help); color: var(--ink-soft); }
.f-q .f-summary .f-tot dd { font-size: 1rem; font-weight: 500; color: var(--ink); }
.f-q .f-summary .f-tot--minus dd { color: var(--ink-soft); }
.f-q .f-summary .f-grand {
  padding-block-start: var(--sp-2);
  border-block-start: 1px solid var(--line);
}
.f-q .f-summary .f-grand dt { font-size: 1rem; font-weight: 600; color: var(--ink); }
.f-q .f-summary .f-grand dd { font-size: var(--fs-answer); font-weight: 700; color: var(--accent); }

/* The ONE wide step. The line-items grid needs 42rem of columns on its own
   (--li-cols, §10), so inside the 42rem question column its header row was
   clipped at the inline-start edge — measured at 1921px: the first column
   label rendered as "ية". :has keeps the widening on the step that actually
   carries a grid, so every other question keeps the reading measure. */
@media (min-width: 900px) {
  .f-app > .f-q:has(.f-items-grid) { max-inline-size: var(--page-w-wide); }
}

/* ============================================================================
   18. CHIPS  —  short answers that fit on one line
   ----------------------------------------------------------------------------
   Used by amount_or_percent (مبلغ / نسبة) and the colour presets. The option
   CARDS live in §23, next to the rest of the choice family.

   The <input> is clipped, not display:none — display:none removes it from the
   tab order and from the radio group's arrow-key behaviour, which is the whole
   reason to use real radios. The <label> wraps it, so the entire chip is the
   hit target with no JavaScript and no transparent overlay.
   ========================================================================== */
.f-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.f-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  min-block-size: var(--ctl-h);
  padding-inline: var(--sp-4);
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 500;
  line-height: var(--lh-ui);
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 6%, white);
  border: 1px solid var(--accent-line);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur) ease-in-out, border-color var(--dur) ease-in-out;
}
.f-chip > input {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  border: 0;
  appearance: none;
}
@media (hover: hover) { .f-chip:hover { background: color-mix(in oklab, var(--accent) 14%, white); } }
.f-chip:has(:focus-visible) { box-shadow: var(--sh-focus); }
.f-chip:has(:checked) {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 600;
}

/* ============================================================================
   19. THE RAIL  —  progress at the bottom edge, [TF] verbatim
   ----------------------------------------------------------------------------
   Typeform pins a 4px bar to the bottom of the viewport, not the top: the eye
   is at the question, and progress is a thing you glance down at, never a thing
   that sits above what you are reading. It is fixed, so it does not move between
   questions and does not participate in the step's entrance animation.

   Always give it aria-valuetext="3 من 7". A screen reader announcing "43%" is
   an anxiety machine; "3 من 7" is a fact.
   ========================================================================== */
.f-rail {
  position: fixed;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 15;
  block-size: 4px;
  max-inline-size: none;
  margin: 0;
  border-radius: 0;
  background: color-mix(in oklab, var(--accent) 12%, white);
  overflow: hidden;
}
.f-rail > i {
  display: block;
  block-size: 100%;
  min-inline-size: 4px;                 /* step 1 of 16 is still visible */
  background: var(--accent);
  transition: inline-size 240ms var(--ease);
}
   20. MOTION  —  only to explain a transition, never to delay one
   ----------------------------------------------------------------------------
   Everything here is inside prefers-reduced-motion: no-preference. §1 already
   forces --dur to 0ms and clamps every duration under `reduce`; this block is
   the second belt, and the one the brief asks for by name.

   THE ANSWER CONTROL IS DELIBERATELY NOT TRANSFORMED. A transform on an
   ancestor of a focused input creates a containing block and can put the caret
   in the wrong place for the length of the animation — on a step that
   autofocuses its input, that is a visible glitch on every single step. So the
   question and the counter slide 6px; the answer only fades, 160ms, no delay.
   No animation-delay anywhere: a stagger is a delay, and a delay is friction.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  .f-q[data-enter] .f-q__count,
  .f-q[data-enter] .f-q__ask,
  .f-q[data-enter] .f-q__note { animation: f-q-in 200ms var(--ease) both; }

  .f-q[data-enter] .f-q__answer,
  .f-q[data-enter] .f-q__nav { animation: f-q-fade 160ms var(--ease) both; }

  /* Going back reverses the direction so "back" reads as back. */
  .f-q[data-enter='back'] .f-q__count,
  .f-q[data-enter='back'] .f-q__ask,
  .f-q[data-enter='back'] .f-q__note { animation-name: f-q-in-back; }

  @keyframes f-q-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes f-q-in-back {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes f-q-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
}

/* ============================================================================
   21. MOBILE  —  the primary case
   ----------------------------------------------------------------------------
   body already carries overflow-x: hidden (§2). These rules stop anything in
   the new layout from being the thing that needed it.
   ========================================================================== */
@media (max-width: 758px) {
  .f-q { padding-block: var(--sp-6) var(--sp-5); }
  .f-app > .f-q { justify-content: flex-start; }   /* a long question must not
                                                      centre itself off-screen */
  .f-q__nav { flex-wrap: wrap; }

  /* MOBILE-FIRST. A phone has no Enter key, so «أو اضغط Enter» is advice the
     device cannot take — and it was stealing the width the one button needs.
     It was already hidden under `(hover: none)`, which is the honest test for a
     touch device but reports false on a narrow DESKTOP window, so the hint
     survived every narrow-viewport check. Width is the second belt. */
  .f-q__kbd { display: none; }

  /* THE BUTTON BAR.  MEASURED at 390px on the line-items screen: the forward
     button sat at the end of a table taller than the viewport, so it was BELOW
     THE FOLD — the primary action of the screen, invisible until you scrolled
     past fifteen rows — while «رجوع», which is `position: fixed` on desktop,
     floated over it in the corner. Two buttons, one on top of the other, and
     the wrong one was the one you could see.

     Sticky, not fixed: it takes its own space in flow, so it can never cover
     the last row of the table the way a fixed bar does, and it stops at the
     bottom of the viewport while there is still content underneath. Back is
     un-fixed back into the bar, so on a phone there is exactly ONE place where
     navigation lives. */
  .f-q__nav {
    position: sticky;
    inset-block-end: 0;
    z-index: 12;
    flex-wrap: nowrap;
    gap: var(--sp-3);
    margin-block-start: var(--sp-6);
    padding-block: var(--sp-3);
    background: var(--paper);
    box-shadow: 0 -1px 0 0 var(--line), 0 -8px 16px -12px rgba(0, 0, 0, .25);
  }

  /* Forward is the widest thing in the bar and sits in the middle of the thumb's
     arc; back and skip are shrink-to-fit at the edges. */
  .f-q__next { order: 2; flex: 1 1 auto; inline-size: auto; }
  .f-q__skip { order: 3; flex: 0 0 auto; }

  .f-q__back {
    position: static;
    order: 1;
    flex: 0 0 auto;
    inset: auto;
    box-shadow: inset 0 0 0 1px var(--accent-line);
  }

  /* The review row is three columns of very different lengths — a label, an answer
     and «عدّل». At 390px the answer column collapses to one word per line and the
     row grows to four lines tall. Stacked, the label sits above its answer and the
     button trails on the same line as the label, which is the shape a phone can
     actually read. */
  .f-review__row {
    grid-template-columns: 1fr auto;
    gap: var(--sp-1) var(--sp-3);
    padding-block: var(--sp-3);
  }
  /* Explicit placement, because auto-flow puts three children in three cells and
     «عدّل» landed on its own third line. Label and button share row 1; the answer
     spans row 2 under them. */
  .f-review__row > :nth-child(1) { grid-row: 1; grid-column: 1; }
  .f-review__row > :nth-child(2) { grid-row: 2; grid-column: 1 / -1; }
  .f-review__row > :nth-child(3) { grid-row: 1; grid-column: 2; }
  .f-choices, .f-chips, .f-swatches { max-inline-size: 100%; }
  .f-choices .f-choice, .f-chip { min-inline-size: 0; }
  .f-choices .f-choice > span { overflow-wrap: anywhere; }
  .f-rail { max-inline-size: none; }
}

/* ============================================================================
   22. FORCED COLORS  —  the new controls
   ----------------------------------------------------------------------------
   Same reason as §14: every affordance above is a box-shadow, and box-shadow is
   DROPPED in Windows High Contrast. Without this block a radio card is an
   invisible rectangle and the rail is an empty line.
   ========================================================================== */
@media (forced-colors: active) {
  .f-rail { border: 1px solid CanvasText; background: Canvas; }
  .f-rail > i { background: Highlight; forced-color-adjust: none; }

  .f-q__kbd kbd { border: 1px solid CanvasText; box-shadow: none; }
}

@media print {
  .f-q__nav, .f-q__kbd, .f-rail { display: none !important; }
  .f-app > .f-q { min-block-size: 0; display: block; }
}

/* ============================================================================
   19. INTEGRATION FIXES
   ------------------------------------------------------------------------
   Added after the parallel rebuild, where five agents wrote against a shared
   contract without being able to see each other or compile.
   ========================================================================== */

/* .f-ask — the question itself when a field owns the screen.
   This was `.f-label f-q` in 20 components, colliding with `.f-q`, the STEP
   SHELL in §17. Inside `.f-q__answer` the clash was invisible (the label is
   clipped), but the ISSUER STEP renders outside that container, so the first
   screen a new contractor ever sees inherited the shell's 36rem centring and
   vertical padding on all four of its labels. */
.f-ask {
  display: block;
  font-size: var(--fs-h2, 1.5rem);
  font-weight: 600;
  line-height: 1.45;          /* Arabic needs the room; 1.2 collides on diacritics */
  color: var(--ink);
  margin-block-end: var(--sp-4);
  text-wrap: balance;
}
.f-q__answer .f-ask { font-size: inherit; }   /* inside the shell the shell wins */

/* .f-review — the final screen. Had no rule at all. */
.f-review { display: flex; flex-direction: column; gap: var(--sp-1); }
.f-review__row {
  display: grid;
  /* THREE children — label, answer, «عدّل» — so THREE tracks. With two, the
     button wrapped onto its own row and every review line was two lines tall. */
  grid-template-columns: minmax(0, 14rem) minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--sp-3);
  padding-block: var(--sp-2);
  border-block-end: 1px solid var(--line);
}
.f-review__row:last-child { border-block-end: none; }
.f-review__row[data-blank] { color: var(--ink-ghost); }
/* The answer can be Arabic OR Latin, so isolate rather than force a direction —
   a <bdi dir="ltr"> would be wrong for an Arabic company name. */
.f-review__row .f-text { unicode-bidi: isolate; }

/* .f-live — the aria-live region every field renders.

   IT USED TO BE CLIPPED TO 1px. Every field component renders its `.f-error`
   INSIDE this element, so every validation message in the product was announced
   to a screen reader and shown to NOBODY. MEASURED: submit the first question
   blank and the accessibility tree carries «اكتب إيميل صحيح» while the screen is
   unchanged — the button reads as dead. The step also refuses to advance, so the
   user is stuck on a question with no stated reason.

   It is a live region either way; it is simply also visible now. The element is
   empty until there is something to say, so an in-flow block costs no space. */
.f-live {
  display: block;
  min-block-size: 0;
}

/* The totals bar is sticky (§11). Inside a question shell it sits BEFORE the
   nav row in flow, so on a phone with a long items table it can cover the
   primary button. Inside .f-q it stops being sticky. */
.f-q .f-summary { position: static; }
.f-q__nav { position: relative; z-index: 11; }

/* A computed grid cell is output, not input. report.json derives items[].grade from
   score/max, and it was rendering as a live <select> that the next recalculation
   overwrote — the user picked a تقدير and watched it revert. */
.f-cell-computed {
  display: block;
  padding-inline: var(--sp-2);
  padding-block: var(--sp-2);
  color: var(--ink-mid);
  font-variant-numeric: tabular-nums;
}

/* A <fieldset> ships with a 2px groove border and a legend that notches into it. The
   VAT question is the FIRST thing a new contractor sees, and it was rendering as a
   black box with its own heading cut through the top edge. */
fieldset.f-field {
  border: none;
  margin: 0;
  padding: 0;
  min-inline-size: 0;   /* fieldset ignores flex/grid sizing without this */
}
fieldset.f-field > legend { padding: 0; }

/* ============================================================================
   23. OPTION CARDS  —  [TF] the most recognisable thing Typeform draws
   ----------------------------------------------------------------------------
     <span class="f-choices">
       <label class="f-choice"><input type="radio" …/><span>عرض سعر</span></label>
       …
     </span>

   A tinted rectangle in the accent, a letter key at the leading edge, a tick at
   the trailing edge once chosen.

   THE LETTER KEY IS A CSS COUNTER, not markup. `counter-reset` on the group,
   `counter-increment` on each label, `counter(f-key, upper-latin)` in ::before.
   No index threaded through Hologram, no `Enum.with_index`, nothing to keep in
   sync when a catalogue option is added. It is aria-hidden by construction —
   generated content is not in the accessibility tree — which is correct: the
   letter is a visual affordance, not part of the option's name.

   ponytail: the letter is NOT bound to the keyboard yet — arrow keys + Enter are
   the whole keyboard contract today (Raqeemi.Fields.Select's moduledoc). Binding it
   is one bare `$key_down` on the form plus a `:pick_key` action that maps the
   letter to an index, guarded on `@q` being a choice question; do it when a user
   asks for it, not before.

   Latin letters, not أ/ب/ج, because the letter names a PHYSICAL KEY and the
   contractor's keyboard is engraved in Latin.

   The native control is clipped, never display:none — display:none removes it
   from the tab order and from the radio group's arrow-key behaviour, which is
   the whole reason this is a real <input type="radio"> and not a div.
   ========================================================================== */
.f-choices {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  counter-reset: f-key;
}
.f-q__answer .f-choices { max-inline-size: 32rem; }

.f-choices .f-choice {
  position: relative;
  counter-increment: f-key;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-block-size: var(--ctl-h);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--accent) 6%, white);
  color: var(--accent);
  font-weight: 500;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur) ease, border-color var(--dur) ease;
}

.f-choices .f-choice input[type='radio'],
.f-choices .f-choice input[type='checkbox'] {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  border: 0;
  appearance: none;
}

/* the A / B / C key */
.f-choices .f-choice:not(.f-swatch)::before {
  content: counter(f-key, upper-latin);
  flex: none;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  display: grid;
  place-items: center;
  font-size: 0.75rem;
  font-weight: 700;
  direction: ltr;
  color: var(--accent);
  background: var(--paper);
  border: 1px solid var(--accent-line);
  border-radius: 0.25rem;
  transition: background-color var(--dur) ease, color var(--dur) ease;
}

/* the tick, at the trailing edge, only once chosen */
.f-choices .f-choice::after { content: ''; margin-inline-start: auto; }
.f-choices .f-choice:has(input:checked)::after { content: '✓'; font-weight: 700; }

@media (hover: hover) {
  .f-choices .f-choice:hover {
    background: color-mix(in oklab, var(--accent) 14%, white);
    border-color: var(--accent);
  }
}
.f-choices .f-choice:has(input:focus-visible) { box-shadow: var(--sh-focus); }
.f-choices .f-choice:has(input:checked) {
  background: color-mix(in oklab, var(--accent) 20%, white);
  border-color: var(--accent);
}
.f-choices .f-choice:has(input:checked)::before {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.f-choices .f-choice:active { transform: translateZ(0) scale(.995); }

/* ---- the theme picker: ten colour NAMES that must render as colour ------
   Same card, but the swatch replaces the letter — a colour cannot be chosen by
   reading its name, and the name alone is what this field used to show. */
.f-swatches { display: flex; flex-wrap: wrap; gap: var(--sp-2); counter-reset: f-key; }
.f-swatches .f-choice { min-inline-size: 8.5rem; flex: 1 1 8.5rem; }
.f-swatch-dot {
  flex: none;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.15);
}
.f-swatch-name { min-inline-size: 0; }

/* Windows High Contrast strips backgrounds, custom borders AND generated
   colour. Without this the cards are invisible rectangles. */
@media (forced-colors: active) {
  .f-choices .f-choice,
  .f-chip { border: 1px solid CanvasText; background: Canvas; color: CanvasText; }
  .f-choices .f-choice:has(input:checked),
  .f-chip:has(:checked) { border: 3px solid Highlight; }
  .f-choices .f-choice:has(input:focus-visible),
  .f-chip:has(:focus-visible) { outline: 2px solid Highlight; outline-offset: 2px; }
  .f-choices .f-choice::before { border: 1px solid CanvasText; forced-color-adjust: none; }
  .f-q__answer :not(.f-cell) > .f-input,
  .f-q__answer :not(.f-cell) > .f-textarea,
  .f-q__answer :not(.f-cell) > .f-select { border-block-end: 2px solid CanvasText; }
}

/* ============================================================================
   24. PLANS  —  the paywall, and the pricing page
   ----------------------------------------------------------------------------
   The same three cards render in two places: inside the form when the free
   allowance runs out, and on /pricing before he has started. One block of rules
   for both, because a price that looks like a different product in the two
   places it appears is a price nobody trusts.

   MOBILE-FIRST: a single column that becomes three only when there is room for
   three to be readable. `auto-fit` with a `minmax` floor does that without a
   breakpoint to maintain — below 3 x 15rem there is simply one column.
   ========================================================================== */
.f-plans {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-3);
  margin-block: var(--sp-5);
}
@media (min-width: 48rem) {
  .f-plans { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
}

.f-plan {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-4);
  text-align: start;                    /* a <button> centres its text by default */
  font: inherit;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--accent) 5%, white);
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur) ease, border-color var(--dur) ease;
}
/* The pricing page renders the same card as a <div>: it states a price, it does
   not take one. Nothing to press, so nothing that looks pressable. */
.f-plan--card { cursor: default; }

@media (hover: hover) {
  button.f-plan:hover:not([disabled]) {
    background: color-mix(in oklab, var(--accent) 12%, white);
    border-color: var(--accent);
  }
}
button.f-plan:focus-visible { box-shadow: var(--sh-focus); outline: none; }
button.f-plan[disabled] { opacity: .55; cursor: not-allowed; }

.f-plan__name { font-size: var(--fs-label); font-weight: 600; color: var(--ink-mid); }

.f-plan__price {
  display: flex;
  align-items: baseline;
  gap: 0.3em;
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.25;                    /* Arabic descenders; 1 clips ج ي */
  color: var(--accent);
}
.f-plan__per { font-size: 0.875rem; font-weight: 500; color: var(--ink-soft); }
.f-plan__what { font-size: var(--fs-help); font-weight: 600; color: var(--ink-mid); }
.f-plan__note { font-size: var(--fs-help); color: var(--ink-soft); }

/* ---- the paywall screen -------------------------------------------------
   It is NOT an error screen. He did nothing wrong, his document is intact, and
   the copy and the colour both have to say so — red here would read as "your
   quotation was rejected", which is the one thing that did not happen. */
.f-pay { max-inline-size: var(--q-w); }
.f-pay .f-q__ask { margin-block-end: var(--sp-2); }
.f-pay .f-q__note { margin-block-end: var(--sp-4); }
.f-pay__go { margin-block: var(--sp-3) var(--sp-4); }
.f-pay .f-btn--quiet { padding-inline: 0; }

/* The pricing page is a comparison, so the three cards have to be side by side to be
   compared. `.f-page` caps the measure at 44rem, which is right for reading prose and
   one card too narrow for this — at 44rem `auto-fit` yields two columns and the third
   plan wraps underneath, which reads as "and also this one" rather than "pick one". */
/* The payments-paused notice. Written the moment the class was, because non-negotiable
   29 is about exactly this: a class the template emits and the stylesheet has never heard
   of renders as unstyled body text, which on a pricing page reads as a mistake rather
   than as an announcement. */
.f-note {
  margin-block: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--radius);
  font-size: var(--fs-help);
  line-height: 1.9;
}
.f-note--info {
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--ink);
}

/* Written with the class, per non-negotiable 29. */
.f-plan__vat {
  display: block;
  margin-block-start: calc(var(--sp-1) * -1);
  font-size: var(--fs-help);
  color: var(--ink-ghost);
}

.f-pricing { max-inline-size: 58rem; }
.f-pricing .f-plans--page { margin-block: var(--sp-6); }
.f-pricing .f-text { max-inline-size: 42rem; }

/* ============================================================================
   25. THE ROW KNOWS WHERE IT IS  —  validation and progress inside the grid
   ----------------------------------------------------------------------------
   Two holes, both of them silent.

   1. `Raqeemi.Flow.check/3` has ALWAYS validated line-item cells — `check_cells/2`
      keys every message by `[field, rid, column]` and `Raqeemi.Fields.Repeat` has
      always put `data-invalid` on the cell. NOTHING IN THIS STYLESHEET EVER
      MATCHED IT. A quantity of «ثلاثة» was a red flag rendered as an ordinary
      cell, and the message existed only in the accessibility tree. The user
      pressed «التالي», the screen refused to advance, and no box was red.

   2. There was no way to see a row was finished. Fifteen rows of identical
      white boxes and the only progress signal was the running total.
   ========================================================================== */

/* Wash tokens for the two states, mixed from the tokens §1 already defines so a
   theme change cannot leave the grid behind. */
:root {
  --ok-wash:    color-mix(in oklab, var(--ok) 8%, white);
  --ok-line:    color-mix(in oklab, var(--ok) 35%, white);
}

/* ---- finished ----------------------------------------------------------
   "Finished" = every REQUIRED column, per Raqeemi.Fields.Ask.row_done?/2. The
   optional الوصف must not hold a complete row hostage.

   `:not(:has(:focus))` keeps the focus wash (§10) winning while the caret is in
   the row: the two rules have the same specificity, this one is later in the
   file, and without the guard editing a finished row would show it as finished
   rather than as active. Green is for looking back at, not for typing in. */
.f-item-row[data-done]:not(:has(:focus)):not(:has([data-invalid])) > .f-cell {
  background: var(--ok-wash);
}

/* The start edge carries the state as a bar, so the signal survives Windows High
   Contrast (which drops background-color) and does not depend on colour alone. */
.f-item-row[data-done]:not(:has([data-invalid])) > .f-cell:first-child {
  box-shadow: inset 2px 0 0 0 var(--ok-line);
}

/* ---- wrong -------------------------------------------------------------
   Louder than "finished" on purpose: a wash, a border on the input, and the
   message itself. `data-invalid` on the cell rather than on the input, because
   the wrapper is what knows the error path. */
.f-cell[data-invalid] .f-input {
  box-shadow: inset 0 0 0 1px var(--error);
  background: var(--error-wash);
}
.f-cell[data-invalid] .f-input:focus { box-shadow: var(--sh-error); }

/* An invalid cell outranks a finished row — a row can be `data-done` (every
   required cell has SOMETHING in it) and still hold a quantity that is not a
   number. `row_done?/2` only asks "is there anything here", so `:not(:has(
   [data-invalid]))` on the tint above is what makes wrong win over done: a
   green row with a red box in it says finished and not-finished at once, and
   the user believes the colour of the row. */

/* The message. Small, inside the cell, under the input it belongs to, and
   pointed at by aria-describedby so a screen reader reads it after
   aria-invalid rather than never. */
.f-cell-err {
  display: block;
  padding-inline: var(--sp-2);
  font-size: var(--fs-help);
  line-height: 1.35;
}

/* ---- skip, beside forward ---------------------------------------------
   Quiet by construction: no shadow, no fill, smaller than the CTA. It has to be
   reachable and it must never compete with «التالي» — the whole reason it is a
   second button is that the first one stopped renaming itself. */
.f-q__skip {
  color: var(--ink-soft);
  background: transparent;
  box-shadow: none;
  text-decoration: underline;
  text-underline-offset: 3px;
}
@media (hover: hover) {
  .f-q__skip:hover:not([disabled]) { color: var(--ink); background: transparent; }
}

/* On a phone the row is a CARD (§9), and the card carries
   `background: var(--paper) !important` while every cell inside it is
   `transparent !important` — so the desktop rules above paint nothing here.
   The state has to go on the card, and it has to shout as loudly. */
@media (max-width: 758px) {
  .f-item-row[data-done]:not(:has(:focus)):not(:has([data-invalid])) {
    background: var(--ok-wash) !important;
    box-shadow: var(--sh-rest), inset 2px 0 0 0 var(--ok-line);
  }
  .f-item-row:has(.f-cell[data-invalid]) {
    box-shadow: var(--sh-rest), inset 2px 0 0 0 var(--error);
  }
}

/* The finish line. The download is the point of the whole form, so it is the accent
   button and «سوّ مستند ثاني» is the ghost beside it — the reverse of what a "thank
   you" screen usually does, and correct here: he came for the file. */
.f-done__pdf { margin-block: var(--sp-5) var(--sp-2); }
.f-done .f-help + .f-help { margin-block-start: var(--sp-3); }

/* ============================================================================
   26. THE GRID, CORRECTED  —  three things the cell rules got wrong
   ========================================================================== */

/* 1. «الوصف» is a TEXTAREA with rows="2" and §9 forced every .f-cell .f-input to
      block-size: 2.5rem — so the two rows the markup asks for were painted as
      one, and a contractor writing a real line description typed into a slot
      that showed him about six words. `auto` gives `rows` back its meaning;
      `field-sizing: content` grows it as he keeps typing where supported and
      is ignored everywhere else. */
.f-cell textarea.f-input {
  block-size: auto;
  min-block-size: calc(var(--ctl-h) * 1.6);
  field-sizing: content;
  resize: vertical;
  padding-block: var(--sp-2);
  line-height: 1.5;
}

/* 2. The delete button was opacity: 0 until hover — invisible by default, and on
      a TOUCH DEVICE there is no hover at all, so on the phone the only way to
      discover it was to tap blindly. It is now always visible and merely quiet;
      hover and focus promote it. Quiet is the right volume for destructive:
      findable, never inviting. */
.f-cell-del .f-btn {
  opacity: 1;
  color: var(--ink-ghost);
  box-shadow: inset 0 0 0 1px var(--line);
  border-radius: 999px;
}
@media (hover: hover) {
  .f-cell-del .f-btn:hover:not([disabled]) {
    color: var(--error);
    box-shadow: inset 0 0 0 1px var(--error);
    background: var(--error-wash);
  }
}
.f-cell-del .f-btn:focus-visible { color: var(--error); }
.f-cell-del .f-btn[disabled] { opacity: 0.35; }

/* ---- remembered answers, as chips -------------------------------------
   A horizontal scroller, not a wrapping block: forty items would push the
   question itself off the screen, and a row that scrolls is the shape a phone
   already teaches. `scroll-snap` so a flick lands on a chip, not between two. */
.f-memo {
  display: block;
  margin-block: var(--sp-4) var(--sp-2);
}
.f-memo__label {
  display: block;
  font-size: var(--fs-help);
  color: var(--ink-soft);
  margin-block-end: var(--sp-2);
}
.f-memo__row {
  display: flex;
  gap: var(--sp-2);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-block-end: var(--sp-2);
  /* The scrollbar is noise on a row of six chips; the overflow is discoverable
     from the chip clipped at the edge, which is the honest affordance. */
  scrollbar-width: none;
}
.f-memo__row::-webkit-scrollbar { display: none; }

.f-memo__chip {
  flex: 0 0 auto;
  scroll-snap-align: start;
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  max-inline-size: 16rem;
  padding: var(--sp-2) var(--sp-3);
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-size: var(--fs-help);
  color: var(--accent);
  background: var(--accent-wash);
  box-shadow: inset 0 0 0 1px var(--accent-line);
  cursor: pointer;
  transition: background-color var(--dur), box-shadow var(--dur);
}
.f-memo__chip::before { content: '+'; font-weight: 700; opacity: 0.7; }
.f-memo__txt {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.f-memo__price {
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--ink-soft);
  font-size: 0.9em;
}
@media (hover: hover) {
  .f-memo__chip:hover { background: var(--paper); box-shadow: inset 0 0 0 1px var(--accent); }
}
.f-memo__chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Inside a one-question screen the chips sit under the answer, so they must not
   inherit the answer's oversized type. */
.f-q__answer .f-memo__chip, .f-memo__chip { font-size: var(--fs-help); }

/* ---- share, on the finish line ----------------------------------------
   WhatsApp first and in its own colour, because in this market a quotation is
   delivered in a chat and everything else is the exception. */
.f-share {
  display: block;
  margin-block: var(--sp-5);
  padding: var(--sp-4);
  border-radius: var(--radius-lg);
  background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--line);
}
.f-share__label {
  display: block;
  font-size: var(--fs-help);
  color: var(--ink-mid);
  font-weight: 600;
  margin-block-end: var(--sp-3);
}
.f-share__row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.f-share__btn {
  flex: 1 1 auto;
  min-inline-size: 6rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  block-size: var(--ctl-h);
  padding-inline: var(--sp-4);
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-weight: 600;
  color: var(--ink);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
  text-decoration: none;
  cursor: pointer;
}
.f-share__btn--wa { color: #FFFFFF; background: #128C7E; box-shadow: none; }

/* The native share sheet, the primary action when the browser has one. Accent-filled so
   it reads as the recommended path next to WhatsApp's own green — it is the button that
   sends the PDF itself rather than a link.

   `display: none` on [hidden] is written out because .f-share__btn sets
   `display: inline-flex`, and a display value beats the browser's own [hidden] rule.
   Without this line the button ships VISIBLE on every browser that cannot share, which
   is non-negotiable 29 in reverse: markup nothing supports, rendered anyway. */
.f-share__btn--native { color: #FFFFFF; background: var(--accent); box-shadow: none; }
.f-share__btn[hidden] { display: none; }

@media (hover: hover) {
  .f-share__btn:hover { box-shadow: inset 0 0 0 1px var(--accent); color: var(--accent); }
  .f-share__btn--wa:hover { background: #0F7A6D; color: #FFFFFF; box-shadow: none; }
  .f-share__btn--native:hover { filter: brightness(0.92); color: #FFFFFF; box-shadow: none; }
}
.f-share__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.f-share__url {
  inline-size: 100%;
  margin-block-start: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-size: var(--fs-help);
  color: var(--ink-soft);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
  text-overflow: ellipsis;
}

/* ============================================================================
   27. NAVIGATION, THE CATALOGUE, AND PROSE
   ----------------------------------------------------------------------------
   Every page except the form was an island: /pricing had one way out, /me had
   none, and /terms did not exist. A nav in the layout means a page is reachable
   the day it ships rather than the day somebody remembers to link it.
   ========================================================================== */

.f-nav {
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-3) var(--sp-5);
  background: color-mix(in oklab, var(--paper) 88%, transparent);
  backdrop-filter: blur(8px);
  box-shadow: 0 1px 0 0 var(--line);
}
.f-nav__brand {
  font-weight: 700;
  font-size: 1.125rem;
  color: var(--accent);
  text-decoration: none;
}
.f-nav__links {
  display: flex;
  gap: var(--sp-4);
  margin-inline-start: auto;
  font-size: var(--fs-help);
}
.f-nav__links a {
  color: var(--ink-mid);
  text-decoration: none;
  padding-block: var(--sp-2);
  border-block-end: 2px solid transparent;
}
@media (hover: hover) {
  .f-nav__links a:hover { color: var(--accent); border-block-end-color: var(--accent); }
}
.f-nav__links a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The sticky header must not eat the 100dvh the page shells assume. */
.f-nav ~ .f-app { min-block-size: auto; }

.f-foot {
  margin-block-start: var(--sp-8);
  padding: var(--sp-6) var(--sp-5);
  border-block-start: 1px solid var(--line);
  text-align: center;
}
.f-foot__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2) var(--sp-5);
  font-size: var(--fs-help);
}
.f-foot__links a { color: var(--ink-soft); text-decoration: none; }
@media (hover: hover) { .f-foot__links a:hover { color: var(--accent); } }
.f-foot__note {
  margin-block-start: var(--sp-3);
  font-size: var(--fs-help);
  color: var(--ink-ghost);
}
/* The merchant behind the product. Quieter than the tagline above it on purpose —
   it is there to be FOUND, by a customer checking who sent him a quotation and by an
   activation reviewer, not to be read by everyone. */
.f-foot__who {
  margin-block-start: var(--sp-2);
  font-size: var(--fs-help);
  color: var(--ink-ghost);
  line-height: 1.7;
}
.f-foot__who a { color: inherit; text-decoration: none; }
@media (hover: hover) { .f-foot__who a:hover { color: var(--accent); } }

/* ---- the catalogue ------------------------------------------------------
   It was a stack of two-line links with a DEAD email box underneath — an input
   that took a value, dispatched nothing and led nowhere. Cards, because a
   service is a choice between siblings and a choice reads better side by side
   than stacked; `auto-fit` so one card and six both look deliberate. */
.f-cat { max-inline-size: 56rem; }

.f-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: var(--sp-3);
  margin-block: var(--sp-6);
}

.f-card {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border-radius: var(--radius-lg);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
  text-decoration: none;
  color: inherit;
  transition: box-shadow var(--dur), transform var(--dur);
}
@media (hover: hover) {
  .f-card:hover {
    box-shadow: inset 0 0 0 1px var(--accent), var(--sh-cta);
    transform: translateY(-1px);
  }
  .f-card:hover .f-card__go { transform: translateX(-3px); color: var(--accent); }
}
.f-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.f-card__icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: var(--radius);
  font-size: 1.375rem;
  background: var(--accent-wash);
}
.f-card__body { display: flex; flex-direction: column; gap: var(--sp-1); min-inline-size: 0; }
.f-card__name { font-weight: 700; color: var(--ink); }
.f-card__tag { font-size: var(--fs-help); color: var(--ink-mid); line-height: 1.6; }
.f-card__meta { font-size: var(--fs-help); color: var(--ink-ghost); }
.f-card__go {
  margin-inline-start: auto;
  align-self: center;
  color: var(--ink-ghost);
  transition: transform var(--dur), color var(--dur);
}

/* ---- long-form pages (terms) -------------------------------------------- */
.f-prose { max-inline-size: 44rem; }
.f-prose .f-h3 {
  margin-block: var(--sp-6) var(--sp-2);
  font-size: 1.125rem;
  font-weight: 700;
  color: var(--ink);
}
.f-prose .f-text { margin-block-end: var(--sp-3); line-height: 1.9; }
.f-prose a { color: var(--accent); }

@media (max-width: 758px) {
  .f-nav { padding-inline: var(--sp-4); gap: var(--sp-3); }
  .f-nav__links { gap: var(--sp-3); }
  .f-cards { grid-template-columns: 1fr; }
}

/* ============================================================================
   28. THE UNIT ALWAYS TRAILS THE NUMBER
   ----------------------------------------------------------------------------
   `1,200.00 ر.س`, never `ر.س 1,200.00`. Every rendered TOTAL already had this
   right — `Raqeemi.Amount` emits `<bdi>number</bdi> <Sar/>` — but the money
   INPUT put its chip at the wrapper's inline-start, which under dir="rtl" is
   the right edge, i.e. the position an Arabic reader reaches FIRST. So the box
   the contractor types his price into read «ر.س ١٢٠٠» while the total under it
   read «١٢٠٠ ر.س», and the two disagreed on every screen that showed both.

   The chip moves to the document's inline-end (the left edge under RTL) and the
   padding follows it. The input itself stays `direction: ltr` — Chrome discards
   Arabic-Indic digits in an RTL numeric field (non-negotiable 7) — so its OWN
   inline-start is that same left edge, which is why the RTL override below
   reserves the space on `padding-inline-start` and not the other one.
   ========================================================================== */
.f-money-wrap .f-cur {
  inset-inline-start: auto;
  inset-inline-end: 1px;
  border-start-start-radius: 0;
  border-end-start-radius: 0;
  border-start-end-radius: calc(var(--radius) - 1px);
  border-end-end-radius: calc(var(--radius) - 1px);
}

.f-money-wrap .f-input {
  padding-inline-start: var(--ctl-pad-i);
  padding-inline-end: 3.25rem;
}

html[dir='rtl'] .f-money-wrap .f-input[inputmode='decimal'],
html[dir='rtl'] .f-money-wrap .f-input[inputmode='numeric'],
html[dir='rtl'] .f-money-wrap .f-input[data-money] {
  padding-inline-start: 3.25rem;
  padding-inline-end: var(--ctl-pad-i);
}

/* ════════════════════════════════════════════════════════════════════════════
   /owner — the admin dashboard.

   Every selector below matches something RaqeemiWeb.OwnerLive emits, and every
   attribute that page emits is matched here. That is non-negotiable 29 and it is
   the rule this file was written to enforce: `data-invalid` on a grid cell was
   real, correct and invisible for weeks because nothing here selected it.
   ════════════════════════════════════════════════════════════════════════════ */

/* Wider than a form step, narrower than full bleed. A table of documents that
   wraps every row onto three lines is a table nobody scans. */
.f-page--wide { max-inline-size: var(--page-w-wide); }

/* The four things that are configured rather than coded. They exist because all
   four fail SILENTLY — the stub takes no money, Swoosh.Adapters.Local delivers to
   a mailbox nobody can read, local disk loses every logo on the next deploy, and
   no Sentry DSN means a 500 lives in stdout. None of them raises. */
.o-health {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin-block: var(--sp-3) var(--sp-4);
}
.o-hx {
  display: inline-flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line); border-radius: 0.5rem;
  font-size: var(--fs-help);
}
.o-hx__k { font-weight: 700; color: var(--ink); }
.o-hx__v { color: var(--ink-soft); }
.o-hx[data-level='ok']   { border-color: color-mix(in oklab, var(--ok) 35%, white); }
.o-hx[data-level='warn'] { border-color: color-mix(in oklab, var(--warn) 45%, white); }
.o-hx[data-level='bad']  { border-color: var(--error); background: var(--error-wash); }

.o-tabs {
  display: flex; flex-wrap: wrap; gap: var(--sp-1);
  margin-block: var(--sp-3);
  border-block-end: 1px solid var(--line);
  padding-block-end: var(--sp-2);
}
.o-tabs--sub { border-block-end: 0; margin-block-start: 0; }
/* data-on and NOT a second class: the template writes one attribute, this reads
   the same one. A class the template never sets is a tab that never looks
   selected, and it looks like a CSS bug rather than a missing attribute. */
.o-tabs .f-btn[data-on='true'] {
  background: var(--accent-wash);
  color: var(--accent);
  font-weight: 700;
}

.o-row {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-block-end: 1px solid var(--line);
}
.o-row__main { display: flex; flex-direction: column; gap: var(--sp-1); min-inline-size: 0; }
.o-row__t { font-weight: 600; color: var(--ink); }
.o-row__act { display: flex; flex-wrap: wrap; gap: var(--sp-1); flex: none; }

/* The two rows that mean "someone has to do something". A failed render is a
   document the customer has already paid for and already been told is done. */
.o-row[data-state='render_failed'] { background: var(--error-wash); }
.o-row[data-state='suspended']     { opacity: 0.6; }

.o-tag {
  display: inline-block; margin-inline-start: var(--sp-2);
  padding: 0 var(--sp-2); border-radius: 999px;
  font-size: var(--fs-error); font-weight: 700;
  background: var(--accent-wash); color: var(--accent);
}
.o-tag--bad { background: var(--error-wash); color: var(--error); }

@media (max-width: 758px) {
  .o-row { flex-direction: column; }
  .o-row__act { inline-size: 100%; }
}

/* ── the legal pages, and the one destructive control in the product ────────
   Non-negotiable 29: every class below is emitted by lib/raqeemi_web/live/legal_live.ex
   or me_live.ex, and a class with no rule here is a feature that does not exist. */

.f-legal-who {
  padding: var(--sp-3);
  border-radius: var(--radius);
  background: var(--surface-2, rgba(127, 127, 127, 0.08));
  font-size: 0.94rem;
}

.f-list { margin-block: var(--sp-3); padding-inline-start: var(--sp-4); line-height: 1.9; }
.f-list li { margin-block-end: var(--sp-2); }

/* A real <table>, not a stack of divs: a privacy notice's sub-processor list is tabular
   data and a screen reader should be able to say "column: الموقع". */
.f-legal-table {
  inline-size: 100%;
  margin-block: var(--sp-3);
  border-collapse: collapse;
  font-size: 0.9rem;
  text-align: start;
}
.f-legal-table th,
.f-legal-table td {
  padding: var(--sp-2) var(--sp-3);
  border-block-end: 1px solid var(--line, rgba(127, 127, 127, 0.25));
  vertical-align: top;
  text-align: start;
}
.f-legal-table th { font-weight: 600; white-space: nowrap; }

/* Below this the four columns stop being readable side by side on a phone, which is
   where most of this is read. Each row becomes a block. */
@media (max-width: 560px) {
  .f-legal-table, .f-legal-table tbody, .f-legal-table tr, .f-legal-table td { display: block; }
  .f-legal-table thead { display: none; }
  .f-legal-table tr {
    margin-block-end: var(--sp-3);
    padding-block-end: var(--sp-2);
    border-block-end: 1px solid var(--line, rgba(127, 127, 127, 0.25));
  }
  .f-legal-table td { border: 0; padding: 2px var(--sp-2); }
  .f-legal-table td:first-child { font-weight: 600; }
}

/* Fenced off with a real border so «احذف حسابي» is never mistaken for «تسجيل خروج» while
   scrolling. The two sit next to each other and one of them is irreversible. */
.f-danger {
  margin-block-start: var(--sp-5);
  padding: var(--sp-3);
  border: 1px solid var(--danger, #B42318);
  border-radius: var(--radius);
}
.f-danger .f-text { margin-block-start: 0; }
