@charset "UTF-8";
/* =========================================================================
   Etebar Taban Brokerage - form digitisation design system
   کارگزاری اعتبار تابان

   One file, plain CSS, no build step. RTL first.

   Direction: the paper carried onto the screen. Pale mint plates the customer
   writes into, thin teal rulings between rows, navy for every heading and for
   the section spine. Depth is surface tint plus hairlines only, never shadows,
   because the printed form has none. The single exception is the fullscreen
   contract overlay, which is genuinely above the page.

   Reading order of this file:
     01 fonts
     02 tokens
     03 base and reset
     04 utilities
     05 app shell (header, rail, chips, content column)
     06 cards and section bars
     07 field rows and inputs
     08 character-cell inputs
     09 choices (radio, checkbox)
     10 signature pad
     11 file upload
     12 validation states
     13 buttons
     14 contract viewer
     15 dark mode (screen only)
     16 print and .sheet facsimile
     17 class contract (comment block)

   Every class carries a comment naming the markup it expects.
   ========================================================================= */


/* =========================================================================
   01. FONTS
   Self-hosted Vazirmatn. Drop the three woff2 files in /fonts/.
   ========================================================================= */

@font-face {
  font-family: "Vazirmatn";
  src: url("/fonts/Vazirmatn-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Vazirmatn";
  src: url("/fonts/Vazirmatn-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Vazirmatn";
  src: url("/fonts/Vazirmatn-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}


/* =========================================================================
   02. TOKENS
   Brand values are used verbatim where they carry the brand. Two derived
   values exist because the brand pair fails AA as text:
     --grey  #7B7979 is 4.33:1 on white, so it rules hairlines and icons only
             and --ink-2 #5F5D5D (6.54:1) carries secondary TEXT.
     --teal  #37AB9C is 2.81:1 on white, so it rules borders, fills and focus
             only and --teal-ink #1F7A6E (5.16:1) carries teal TEXT.
   ========================================================================= */

:root {
  color-scheme: light;

  /* --- brand, sampled from the printed form, verbatim --- */
  --navy:       #24265D;  /* headings, labels, spine, tab strip */
  --teal:       #37AB9C;  /* rules, focus, active, accents */
  --grey:       #7B7979;  /* hairlines, icon strokes, disabled marks */
  --mint:       #E6F4F3;  /* the pale plate the customer writes in */
  --band:       #B8E1DC;  /* the darker banded section header row */
  --paper:      #FFFFFF;

  /* --- derived, AA-safe text colours --- */
  --ink:        var(--navy);  /* primary text            13.9:1 on paper */
  --ink-2:      #5F5D5D;      /* secondary text           6.5:1 on paper */
  --ink-3:      #6E6C6C;      /* metadata, hints          5.0:1 on paper */
  --ink-muted:  #9A9898;      /* disabled only, never load-bearing text */
  --teal-ink:   #1F7A6E;      /* teal-coloured text       5.2:1 on paper */
  --seal:       #C0392B;      /* the red stamp: errors    5.4:1 on paper */
  --seal-ink:   #A5301F;      /* error text on mint       6.9:1 on paper */
  --seal-wash:  #FBEDEB;      /* error field plate */

  /* --- semantic roles. These are the ONLY colours a component references for
     anything that must flip in dark mode. Brand tokens above never flip. --- */
  --heading:     var(--navy);   /* h1-h4, field labels, legends */
  --on-band:     var(--navy);   /* text sitting on --band */
  --plate:       var(--navy);   /* the navy plate: active rail item, primary button */
  --plate-hover: #1B1D4B;
  --on-plate:    #FFFFFF;       /* text on --plate */
  --spine:       var(--navy);   /* the rail spine and other structural navy */
  --fill-focus:  #FFFFFF;       /* an input plate while focused */
  --rule-strong: var(--navy);   /* the underrule of a focused input */

  /* --- surfaces --- */
  --surface:      var(--paper);   /* card */
  --surface-sunk: #F6F8F8;        /* page canvas, one step under the card */
  --surface-rail: #FBFCFC;        /* rail and chip strip */
  --fill:         var(--mint);    /* every input plate */
  --fill-strong:  #D6ECEA;        /* input plate, hover and active */

  /* --- hairlines. Borders should disappear until you look for them. --- */
  --rule:        #37AB9C;               /* the teal ruling, load bearing */
  --rule-soft:   rgba(55,171,156,.42);  /* cell separators, row dividers */
  --rule-quiet:  rgba(36,38,93,.12);    /* card edges, neutral structure */
  --rule-grey:   rgba(123,121,121,.28); /* the grey pencil rule */

  /* --- focus --- */
  --focus:       var(--teal);
  --focus-width: 2px;
  --focus-offset: 2px;

  /* --- type --- */
  --font-fa: Vazirmatn, "IRANSans", "Segoe UI", Tahoma, sans-serif;
  --font-mono: ui-monospace, "SFMono-Regular", "Menlo", "Consolas",
               "Liberation Mono", monospace;

  /* 15px base, minor third (1.2). Rounded to whole px. */
  --fs-micro: 11px;   /* tab strip, sheet number */
  --fs-meta:  12px;   /* helper text, chips */
  --fs-body:  15px;   /* labels and prose */
  --fs-lead:  17px;   /* card titles */
  --fs-h3:    18px;
  --fs-h2:    22px;
  --fs-h1:    26px;
  --lh-tight: 1.3;
  --lh-body:  1.75;   /* Farsi needs more leading than Latin */

  /* --- spacing, 4px base --- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;

  /* --- radii, small for controls, larger for containers --- */
  --r-1: 3px;  --r-2: 6px;  --r-3: 10px;  --r-pill: 999px;

  /* --- layout --- */
  --col-max:   760px;   /* the content column, matches a comfortable A4 measure */
  --rail-w:    232px;
  --appbar-h:  56px;
  --tap:       44px;    /* minimum tap target */

  /* --- motion --- */
  --ease-out: cubic-bezier(.23, 1, .32, 1);
  --dur-1: 120ms;
  --dur-2: 180ms;

  /* --- character cells, overridable per field --- */
  --cells:    10;    /* set inline: style="--cells:24" */
  --cell-w:   26px;
  --cell-h:   44px;
}

/* Force a theme from the app shell. Both directions are covered so a toggle
   wins over the OS setting either way. Markup: <html data-theme="dark">. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark;  }


/* =========================================================================
   03. BASE
   ========================================================================= */

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

html {
  direction: rtl;
  -webkit-text-size-adjust: 100%;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

body {
  margin: 0;
  font-family: var(--font-fa);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
  background: var(--surface-sunk);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  -webkit-tap-highlight-color: rgba(55,171,156,.18);
}

h1, h2, h3, h4 {
  margin: 0;
  color: var(--heading);
  font-weight: 700;
  line-height: var(--lh-tight);
  text-wrap: balance;
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-lead); }

p { margin: 0 0 var(--sp-3); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: var(--teal-ink); text-underline-offset: 3px; }
a:hover { color: var(--heading); }

img, svg, canvas { max-width: 100%; }
img { display: block; height: auto; }

/* Headings must clear the sticky header when linked to. */
[id] { scroll-margin-top: calc(var(--appbar-h) + var(--sp-4)); }

/* One focus treatment for the whole system. Never removed, only replaced. */
:focus-visible {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
  border-radius: var(--r-1);
}
:focus:not(:focus-visible) { outline: none; }

/* Controls inherit the Farsi stack. Native select needs an explicit pair of
   colours or Windows renders it with system chrome. */
button, input, select, textarea {
  font: inherit;
  color: inherit;
  touch-action: manipulation;
}
select { background-color: var(--fill); color: var(--ink); }

::placeholder { color: var(--ink-muted); opacity: 1; }

::selection { background: var(--band); color: var(--on-band); }


/* =========================================================================
   04. UTILITIES
   ========================================================================= */

/* .sr-only - visually hidden, still announced. On any element. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* .skip-link - first child of <body>, an <a href="#main">. */
.skip-link {
  position: absolute;
  inset-block-start: -100%;
  inset-inline-start: var(--sp-4);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-4);
  background: var(--plate);
  color: var(--on-plate);
  border-radius: 0 0 var(--r-2) var(--r-2);
  text-decoration: none;
}
.skip-link:focus { inset-block-start: 0; }

/* .nums - any element holding figures that must line up in a column.
   Note: CSS cannot turn 1234 into ۱۲۳۴. Convert the digits when you render,
   or ship the Farsi-digit build of Vazirmatn. This only makes them tabular
   so a changing number never shifts the layout. */
.nums {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}

/* .ltr-run - wraps a Latin or numeric run inside Farsi copy, e.g. an IBAN,
   an email, a ticker. Pair with the LRI/PDI isolates you already emit;
   this makes the isolation hold even where the codepoints are stripped.
   Markup: <span class="ltr-run">IR62 0170 ...</span> */
.ltr-run {
  direction: ltr;
  unicode-bidi: isolate;
  display: inline-block;
}

/* Escape hatch for any subtree explicitly marked LTR in the markup. */
[dir="ltr"] { direction: ltr; unicode-bidi: isolate; text-align: left; }

/* .no-print / .print-only - see section 16. */
.print-only { display: none; }

/* .stack - vertical rhythm helper. Markup: any container of block children. */
.stack > * + * { margin-block-start: var(--sp-4); }
.stack-tight > * + * { margin-block-start: var(--sp-2); }

/* .muted / .hint - supporting copy. */
.muted { color: var(--ink-2); }
.hint  { color: var(--ink-3); font-size: var(--fs-meta); line-height: 1.6; }

/* Motion is decoration here. Strip movement, keep colour and opacity. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}


/* =========================================================================
   05. APP SHELL
   Markup:
     <body>
       <a class="skip-link" href="#main">…</a>
       <header class="appbar no-print"> … </header>
       <nav class="chips no-print"> … </nav>
       <div class="app">
         <nav class="rail no-print"> … </nav>
         <main class="col" id="main"> … </main>
       </div>
     </body>
   ========================================================================= */

/* .appbar - sticky compact header. Holds the logo, the progress meter and
   the save chip. Hairline base, no shadow, so it reads as a ruled edge. */
.appbar {
  position: sticky;
  inset-block-start: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--appbar-h);
  padding: var(--sp-2) var(--sp-4);
  padding-inline-start: max(var(--sp-4), env(safe-area-inset-right));
  padding-inline-end:   max(var(--sp-4), env(safe-area-inset-left));
  background: var(--surface);
  border-block-end: 1px solid var(--rule-quiet);
}

/* .appbar__logo - <img src="/logo.png" alt="کارگزاری اعتبار تابان">.
   The asset is 741x243, so height drives it and width follows. The ratio is
   restated here and the fit pinned: height + width:auto only holds the shape
   while the markup carries no width/height attributes and nothing stretches
   the flex line, and a wordmark that has been squashed a few percent looks
   wrong without looking obviously broken, which is the kind of thing that
   ships. Measured at 360 and 390 the rendered ratio is 3.049 against a
   natural 3.049, so this is a guard, not a repair. */
.appbar__logo {
  height: 26px;
  width: auto;
  aspect-ratio: 741 / 243;
  object-fit: contain;
  flex: none;
  align-self: center;
}

/* .appbar__spacer - empty <span>, pushes the trailing cluster over. */
.appbar__spacer { flex: 1 1 auto; min-width: var(--sp-2); }

/* .progress - the completion meter.
   Markup: <div class="progress" role="progressbar" aria-valuenow="4"
            aria-valuemin="0" aria-valuemax="9"
            aria-label="پیشرفت تکمیل فرم" style="--progress:44%">
             <span class="progress__bar"><span class="progress__fill"></span></span>
             <span class="progress__text nums">۴ از ۹</span>
           </div>
   The text is required. The bar alone is a colour-only cue. */
/* The progress block is the only thing in the appbar allowed to shrink, so
   the header fits a 360px phone. Everything else is flex: none. The text
   truncates rather than overflowing over the save chip; truncation is
   visual only, so a screen reader still reads the whole sentence, and the
   role="progressbar" values carry it independently. */
.progress {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 1 auto;
  min-width: 0;
}
.progress__bar {
  position: relative;
  display: block;
  flex: none;
  width: clamp(44px, 14vw, 140px);
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--rule-grey);
  overflow: hidden;
}
.progress__fill {
  display: block;
  height: 100%;
  width: var(--progress, 0%);
  border-radius: inherit;
  background: var(--teal);
  transition: width var(--dur-2) var(--ease-out);
}
/* .progress.is-complete - every section done. Only then does it read navy. */
.progress.is-complete .progress__fill { background: var(--plate); }
.progress__text {
  font-size: var(--fs-meta);
  color: var(--ink-2);
  white-space: nowrap;
  min-width: 0;                 /* lets the flex algorithm shrink it */
  overflow: hidden;
  text-overflow: ellipsis;
}

/* .savechip - the save-state pill. One of five explicit states; they are not
   interchangeable and none of them may be shown as another. Put the real
   sentence in .savechip__text, the tint is only a second cue.
   Markup: <p class="savechip is-saved" aria-live="polite">
             <span class="savechip__dot" aria-hidden="true"></span>
             <span class="savechip__text">ذخیره شد</span>
           </p>
   States: .is-idle (نشده ذخیره) .is-saving (…در حال ذخیره) .is-saved (ذخیره شد)
           .is-offline (آفلاین، در دستگاه ذخیره شد) .is-error (ذخیره نشد) */
.savechip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex: none;                   /* the save state never truncates */
  margin: 0;
  padding: 5px var(--sp-3);
  border-radius: var(--r-pill);
  font-size: var(--fs-meta);
  line-height: 1.4;
  white-space: nowrap;
  border: 1px solid var(--rule-quiet);
  background: var(--surface-sunk);
  color: var(--ink-2);
}
/* .savechip__text - the sentence itself. Required: the tint is only a second cue. */
.savechip__text { min-width: 0; }

.savechip__dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  flex: none;
  background: currentColor;
}
.savechip.is-idle    { color: var(--ink-3); }
.savechip.is-idle    .savechip__dot { background: none; box-shadow: inset 0 0 0 1.5px currentColor; }
.savechip.is-saving  { color: var(--teal-ink); border-color: var(--rule-soft); }
.savechip.is-saving  .savechip__dot { animation: pulse 1.2s var(--ease-out) infinite; }
.savechip.is-saved   { color: var(--teal-ink); background: var(--mint); border-color: var(--rule-soft); }
.savechip.is-offline { color: var(--ink-2); background: var(--surface-sunk); }
.savechip.is-offline .savechip__dot { border-radius: var(--r-1); }
.savechip.is-error   { color: var(--seal-ink); background: var(--seal-wash); border-color: rgba(192,57,43,.3); }
.savechip.is-error   .savechip__dot { clip-path: polygon(50% 0, 100% 100%, 0 100%); border-radius: 0; }

@keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .3 } }

/* .app - the shell grid. Single column on phones. From 900px a fixed rail
   sits on the RIGHT, because the first grid track in an RTL container is the
   right one, which is where the paper's tab strip lives in the reader's mind. */
.app {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 900px) {
  .app {
    grid-template-columns: var(--rail-w) minmax(0, var(--col-max));
    justify-content: center;
    column-gap: var(--sp-8);
    padding-inline: var(--sp-6);
  }
}

/* .col - the content column. Cards live here. */
.col {
  width: 100%;
  max-width: var(--col-max);
  margin-inline: auto;
  padding: var(--sp-4);
  padding-block-end: calc(var(--sp-10) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
@media (min-width: 900px) {
  .col { padding-inline: 0; padding-block-start: var(--sp-6); }
}

/* .rail - desktop section navigator, the screen twin of the printed spine.
   Markup: <nav class="rail" aria-label="بخش‌های فرم">
             <ol class="rail__list">
               <li><a class="rail__item is-active" href="#s2" aria-current="step">
                     <span class="rail__num nums">۲</span>
                     <span class="rail__label">مشخصات مشتری</span></a></li>
             </ol>
           </nav>
   Hidden below 900px, where .chips takes over. */
.rail { display: none; }
@media (min-width: 900px) {
  .rail {
    display: block;
    position: sticky;
    inset-block-start: calc(var(--appbar-h) + var(--sp-5));
    align-self: start;
    max-height: calc(100vh - var(--appbar-h) - var(--sp-10));
    overflow-y: auto;
    padding-block: var(--sp-6);
    /* the spine: a navy rule on the edge facing the content column */
    border-inline-end: 2px solid var(--spine);
  }
}
.rail__list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.rail__item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-3);
  color: var(--ink-2);
  text-decoration: none;
  border-inline-end: 2px solid transparent;
  transition: background-color var(--dur-1) var(--ease-out),
              color var(--dur-1) var(--ease-out);
}
.rail__item:hover { background: var(--mint); color: var(--heading); }
.rail__num {
  flex: none;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  border-radius: 50%;
  font-size: var(--fs-micro);
  font-weight: 500;
  color: var(--ink-3);
  box-shadow: inset 0 0 0 1px var(--rule-grey);
}
.rail__label {
  font-size: var(--fs-body);
  line-height: 1.4;
  min-width: 0;
  overflow-wrap: anywhere;
}
/* Current section. Navy plate bleeding into the spine, echoing the filled
   top tab on the printed sheet. */
.rail__item.is-active {
  background: var(--plate);
  color: var(--on-plate);
  border-inline-end-color: var(--plate);
  font-weight: 500;
}
.rail__item.is-active .rail__num {
  color: var(--on-plate);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.45);
}
/* Completed section. Teal tick, set in the number slot. */
.rail__item.is-done .rail__num {
  color: var(--teal-ink);
  box-shadow: inset 0 0 0 1px var(--rule-soft);
  background: var(--mint);
}
.rail__item.is-done .rail__num::after { content: "✓"; }
.rail__item.is-done .rail__num > * { display: none; }
/* A section with an unresolved error. Never shown as merely incomplete. */
.rail__item.is-error .rail__num {
  color: var(--seal-ink);
  background: var(--seal-wash);
  box-shadow: inset 0 0 0 1px rgba(192,57,43,.35);
}

/* .chips - mobile section navigator, a horizontal scroller.
   Markup: <nav class="chips" aria-label="بخش‌های فرم">
             <a class="chip is-active" href="#s2" aria-current="step">مشخصات مشتری</a>
           </nav>
   Hidden from 900px, where .rail takes over. */
.chips {
  position: sticky;
  inset-block-start: var(--appbar-h);
  z-index: 30;
  display: flex;
  gap: var(--sp-2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  padding: var(--sp-2) var(--sp-4);
  background: var(--surface-rail);
  border-block-end: 1px solid var(--rule-quiet);
}
.chips::-webkit-scrollbar { display: none; }
@media (min-width: 900px) { .chips { display: none; } }

.chip {
  flex: none;
  scroll-snap-align: start;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap);        /* the rail is the primary nav on a phone */
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  border: 1px solid var(--rule-quiet);
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--fs-meta);
  text-decoration: none;
  white-space: nowrap;
  position: relative;
}
.chip:hover { border-color: var(--rule-soft); color: var(--heading); }
.chip.is-active {
  background: var(--plate);
  border-color: var(--plate);
  color: var(--on-plate);
  font-weight: 500;
}
.chip.is-done { background: var(--mint); border-color: var(--rule-soft); color: var(--teal-ink); }
.chip.is-error { background: var(--seal-wash); border-color: rgba(192,57,43,.35); color: var(--seal-ink); }


/* =========================================================================
   06. CARDS AND SECTION BARS
   Markup:
     <section class="card" id="s2" aria-labelledby="s2-t">
       <header class="card__head">
         <h2 class="card__title" id="s2-t">مشخصات مشتری</h2>
         <p class="card__note hint">…</p>
       </header>
       <div class="section-bar">کد(های) معاملاتی مشتری</div>   <!-- direct child -->
       <div class="card__body"> … </div>
     </section>
   ========================================================================= */

/* .card - one form section. Borders-only depth, matching the printed sheet. */
.card {
  background: var(--surface);
  border: 1px solid var(--rule-quiet);
  border-radius: var(--r-3);
  overflow: hidden;             /* keeps .section-bar flush to the corners */
}

.card__head {
  padding: var(--sp-4) var(--sp-4) var(--sp-3);
  border-block-end: 2px solid var(--rule);
}
.card__title { font-size: var(--fs-lead); }
.card__note { margin-block-start: var(--sp-1); }

.card__body { padding: var(--sp-4); }

/* .section-bar - the banded header row from the paper. Must be a DIRECT
   child of .card, outside .card__body, so it reaches both edges without a
   negative margin. Centred navy bold on the darker mint band. */
.section-bar {
  padding: var(--sp-2) var(--sp-4);
  background: var(--band);
  color: var(--on-band);
  font-weight: 700;
  font-size: var(--fs-body);
  text-align: center;
  border-block: 1px solid var(--rule-soft);
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
/* .section-bar--start - a left-set band, used where the paper sets the label
   against the ruling rather than centring it. */
.section-bar--start { text-align: start; }


/* =========================================================================
   07. FIELD ROWS AND INPUTS
   The visual signature of the paper: a right-aligned navy label, a pale mint
   plate to write in, and a thin teal rule under it. Rows are separated by a
   teal hairline, never by a gap.

   Markup:
     <div class="rows">
       <div class="field">
         <label class="field__label" for="fname">نام<span class="req" aria-hidden="true">*</span></label>
         <div class="field__control">
           <input class="input" id="fname" name="firstName" autocomplete="given-name">
         </div>
         <p class="field__msg" id="fname-msg">…</p>   <!-- optional -->
       </div>
     </div>
   ========================================================================= */

/* .rows - a run of .field rows, ruled like the paper. Use on <fieldset>
   (reset its default chrome) or a plain <div>. */
.rows {
  margin: 0;
  padding: 0;
  border: 0;
  border-block-start: 1px solid var(--rule-soft);
}
.rows > .field,
.rows > .row { border-block-end: 1px solid var(--rule-soft); }

/* .row - two or three fields sharing one ruled line, as the paper packs
   "جنسیت / محل صدور / تاریخ تولد". Stacks below 640px.
   Markup: <div class="row row--3"> <div class="field">…</div> ×3 </div> */
.row { display: grid; grid-template-columns: minmax(0, 1fr); }
.row > .field + .field { border-block-start: 1px solid var(--rule-soft); }
@media (min-width: 640px) {
  .row--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .row--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .row--2 > .field + .field,
  .row--3 > .field + .field {
    border-block-start: 0;
    /* inline-START, because under RTL the second field sits to the LEFT of
       the first and the shared edge is its right one. inline-end would rule
       the outer edge of the card instead. */
    border-inline-start: 1px solid var(--rule-soft);
  }
}

/* .field - one label plus control. Label stacks above on phones; from 640px
   it sits inline on the right, as it does on the paper. */
.field {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-1) var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  align-items: center;
}
@media (min-width: 640px) {
  .field {
    grid-template-columns: minmax(6rem, 11rem) minmax(0, 1fr);
    gap: var(--sp-2) var(--sp-4);
  }
  .field__label { text-align: start; }
  .field__msg   { grid-column: 2; }
}

/* .field__label - <label for="…">. Navy, medium, right-aligned in RTL. */
.field__label {
  color: var(--heading);
  font-weight: 500;
  font-size: var(--fs-body);
  line-height: 1.4;
  align-self: center;
}
/* .req - the required marker inside a label. Always aria-hidden; the input
   carries `required` and the label text says الزامی for screen readers. */
.req { color: var(--seal); margin-inline-start: 2px; }

/* .field__control - wraps the input so a field can hold a suffix, a unit
   or a second control without breaking the grid. */
.field__control {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}
/* A choice group fills the control, the same way an input does. Without
   this it is only as wide as its options, and anything after it (the
   .is-done tick) lands between the options and reads as a third choice. */
.field__control > .choices { flex: 1 1 auto; }

/* .field__unit - the parenthetical unit the paper prints beside the box,
   e.g. (به ریال). Markup: <span class="field__unit">به ریال</span> */
.field__unit {
  flex: none;
  font-size: var(--fs-meta);
  color: var(--ink-3);
  white-space: nowrap;
}

/* .field__msg - helper or error text. Link it with aria-describedby. */
.field__msg {
  margin: 0;
  font-size: var(--fs-meta);
  line-height: 1.6;
  color: var(--ink-3);
}

/* .input, .select, .textarea - the mint plate. Inset, not raised: the fill is
   darker than the card so it reads as somewhere to write. A 1px teal wash on
   three sides and a real 1.5px teal rule underneath, as printed. */
.input, .select, .textarea {
  width: 100%;
  min-width: 0;
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-3);
  font-size: 16px;              /* never below 16px, or iOS zooms on focus */
  line-height: 1.5;
  color: var(--ink);
  background: var(--fill);
  border: 1px solid rgba(55,171,156,.25);
  border-block-end: 1.5px solid var(--rule);
  border-radius: var(--r-2) var(--r-2) var(--r-1) var(--r-1);
  appearance: none;
  transition: background-color var(--dur-1) var(--ease-out),
              border-color var(--dur-1) var(--ease-out);
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.textarea { min-height: 96px; resize: vertical; line-height: var(--lh-body); }

.input:hover, .select:hover, .textarea:hover { background: var(--fill-strong); }
.input:focus-visible, .select:focus-visible, .textarea:focus-visible {
  background: var(--fill-focus);
  border-block-end-color: var(--rule-strong);
}
.input:disabled, .select:disabled, .textarea:disabled {
  background: var(--surface-sunk);
  border-color: var(--rule-grey);
  color: var(--ink-muted);
  cursor: not-allowed;
}
.input:read-only:not(:disabled) { background: var(--surface-sunk); }

/* .select - native <select>, with a chevron drawn in the padding box so the
   arrow sits on the inline-end side under RTL. */
.select {
  padding-inline-end: var(--sp-8);
  background-image:
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%2324265D' stroke-width='1.6' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: left var(--sp-3) center;
  background-size: 12px 8px;
}
[dir="ltr"] .select, .select[dir="ltr"] {
  background-position: right var(--sp-3) center;
}

/* .input--short / .input--num - narrow plates for dates, codes and amounts,
   matching the paper's short boxes. */
.input--short { max-width: 12rem; }
.input--num {
  max-width: 16rem;
  font-variant-numeric: tabular-nums lining-nums;
}

/* .input.is-latin - an input holding a Latin or numeric run (email, IBAN,
   ticker). Isolated so it cannot reorder the Farsi around it. */
.input.is-latin {
  direction: ltr;
  unicode-bidi: isolate;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 15px;
  letter-spacing: .02em;
}

/* .field__grid - the paper's small tables, e.g. the trading-code grid or the
   signatories table. Markup: a <table class="field__grid"> inside .card__body. */
.field__grid {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-meta);
}
.field__grid th,
.field__grid td {
  border: 1px solid var(--rule-soft);
  padding: var(--sp-2);
  text-align: center;
  vertical-align: middle;
}
.field__grid th {
  background: var(--mint);
  color: var(--on-band);
  font-weight: 500;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.field__grid td { height: var(--tap); }
/* Tables must scroll rather than break the column at 360px. */
.table-scroll { overflow-x: auto; overscroll-behavior-x: contain; }


/* =========================================================================
   08. CHARACTER-CELL INPUTS
   The paper's segmented boxes: one glyph per cell. A single <input> renders
   as separated cells using a repeating gradient plus letter-spacing, so the
   value stays a real form value with real validation and real paste support.

   Markup:
     <div class="cells" style="--cells:10">
       <input class="cells__input nums" id="nid" name="nationalId"
              inputmode="numeric" maxlength="10" autocomplete="off"
              spellcheck="false" enterkeyhint="next">
     </div>

   Counts used by this form: national id 10, postal code 10, IBAN 24.

   TWO HARD CONSTRAINTS, both measured in Chrome against real Vazirmatn,
   not assumed. Break either one and the field mangles itself silently.

   1. A cells field holds ASCII digits, never Persian ones. Blink and WebKit
      apply NO letter-spacing at all to Arabic-Indic digits: ten ۰-۹ glyphs
      measure 105.39px at letter-spacing 0 and 105.39px at letter-spacing
      10px, where the Latin run grows by exactly 100px. The whole cell grid
      is built on letter-spacing, so with ۰-۹ every digit collapses into the
      first two cells. Keep inputmode="numeric" and ASCII in the input, and
      show Persian digits in read-only text (.nums) instead.

   2. Do NOT put .nums on a .cells__input. `tabular-nums` changes
      Vazirmatn's Latin digit advance to 9.305px while the `ch` unit still
      reports 8.992px, and the digits then walk out of their cells by about
      a third of a cell across a ten-cell field.

   Two variants:
     .cells            ASCII digits. Vazirmatn renders 0-9 at one fixed
                       advance equal to 1ch, so --cell-adv stays 1ch.
     .cells--latin     the IBAN, which mixes Latin letters with digits. Uses
                       the monospace stack, where every glyph is 1ch.

   Geometry: one glyph is --cell-adv wide, letter-spacing pads it out to a
   full cell, and half the leftover centres the first glyph. The gradient
   draws only the internal separators, so there is no rule after the last
   cell. A 2px tolerance past the last cell absorbs sub-pixel accumulation
   so a full field never scrolls itself sideways; it is transparent, so it
   is invisible.

   If you swap the typeface and the digits drift, set --cell-adv on .cells
   to the measured advance of one digit at 16px, e.g. --cell-adv: 9.4px.

   Degradation: outside the @supports block this is a plain mint box with a
   normal caret, which is a usable field, just not a segmented one.
   ========================================================================= */

/* .cells - the scroller. At 360px a 24-cell IBAN cannot fit, so the FIELD
   scrolls horizontally instead of the page breaking. Focus is rung on this
   wrapper, not the input, so the ring is never clipped by the overflow. */
.cells {
  --cell-adv: 1ch;                              /* one glyph's advance */
  --cell-pad: calc((var(--cell-w) - var(--cell-adv)) / 2);
  /* The scroller itself is LTR, matching its content. Left as RTL, a field
     too wide to fit (the 24-cell IBAN at 360px) opens scrolled to the END of
     the value, so the customer sees its last digits and not its first. */
  direction: ltr;
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--rule-soft) transparent;
  border-radius: var(--r-2);
}
.cells::-webkit-scrollbar { height: 4px; }
.cells::-webkit-scrollbar-thumb {
  background: var(--rule-soft);
  border-radius: var(--r-pill);
}
.cells:focus-within {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
}

/* .cells__input - the field itself. Always LTR: Persian and Latin digits are
   both written left to right, and the caret must agree with that. */
.cells__input {
  direction: ltr;
  unicode-bidi: isolate;
  text-align: left;
  display: block;
  box-sizing: content-box;
  width: calc(var(--cells) * var(--cell-w) + 2px);   /* +2px tolerance */
  height: var(--cell-h);
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  font-size: 16px;
  font-variant-numeric: normal;   /* see the note above: tnum breaks this */
  color: var(--ink);
  background: var(--fill);
  border-block-end: 1.5px solid var(--rule);
  appearance: none;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.cells__input:focus { outline: none; }   /* the ring lives on .cells */

/* .cells--latin - IBAN and any run mixing Latin letters with digits. Every
   glyph in a monospace face is exactly 1ch, so the default advance holds. */
.cells--latin .cells__input {
  font-family: var(--font-mono);
  text-transform: uppercase;
}

@supports (background-image: repeating-linear-gradient(to right, red 0 1px, transparent 1px 2px)) {
  .cells__input {
    padding-inline-start: var(--cell-pad);
    letter-spacing: calc(var(--cell-w) - var(--cell-adv));
    background-color: transparent;
    border-block-end: 0;
    background-origin: border-box;
    background-repeat: no-repeat;
    background-image:
      /* 1. internal cell separators, (cells - 1) of them */
      repeating-linear-gradient(to right,
        transparent 0 calc(var(--cell-w) - 1px),
        var(--rule-soft) calc(var(--cell-w) - 1px) var(--cell-w)),
      /* 2. the teal rule under the plate */
      linear-gradient(var(--rule), var(--rule)),
      /* 3. the mint plate itself */
      linear-gradient(var(--fill), var(--fill));
    background-size:
      calc((var(--cells) - 1) * var(--cell-w)) 100%,
      calc(var(--cells) * var(--cell-w)) 1.5px,
      calc(var(--cells) * var(--cell-w)) 100%;
    background-position: 0 0, 0 100%, 0 0;
  }
  .cells__input:hover {
    background-image:
      repeating-linear-gradient(to right,
        transparent 0 calc(var(--cell-w) - 1px),
        var(--rule-soft) calc(var(--cell-w) - 1px) var(--cell-w)),
      linear-gradient(var(--rule), var(--rule)),
      linear-gradient(var(--fill-strong), var(--fill-strong));
  }
}

/* Tighter cells on small phones so a 10-cell field clears 360px with the
   card padding still intact: 10 x 22px = 220px. */
@media (max-width: 420px) {
  .cells { --cell-w: 22px; }
}

/* .cells__count - optional live counter beside the field.
   Markup: <p class="cells__count nums hint" aria-live="polite">۷ از ۱۰</p> */
.cells__count { margin-block-start: var(--sp-1); }


/* =========================================================================
   09. CHOICES (RADIO AND CHECKBOX)
   The paper's small circles. The <label> wraps the control, so the label and
   the control are one hit target with no dead zone between them.

   Markup:
     <fieldset class="choices">
       <legend class="choices__legend">جنسیت</legend>
       <label class="choice">
         <input class="choice__input" type="radio" name="gender" value="m">
         <span class="choice__mark" aria-hidden="true"></span>
         <span class="choice__text">مرد</span>
       </label>
     </fieldset>
   ========================================================================= */

.choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-5);
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}
/* .choices--stack - one option per line, for long option text. */
.choices--stack { flex-direction: column; gap: 0; }
/* .choices__legend - the group question. Styled like a field label. */
.choices__legend {
  padding: 0;
  margin-block-end: var(--sp-1);
  color: var(--heading);
  font-weight: 500;
  font-size: var(--fs-body);
  float: right;            /* legend needs this to sit right in RTL Safari */
  width: 100%;
}

.choice {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap);       /* the whole label is the tap target */
  padding-block: var(--sp-1);
  cursor: pointer;
  color: var(--ink);
  line-height: 1.4;
}
.choice__input {
  /* Kept in the layout and focusable, visually replaced by .choice__mark. */
  position: absolute;
  opacity: 0;
  width: 20px; height: 20px;
  margin: 0;
  cursor: pointer;
}
.choice__mark {
  flex: none;
  width: 20px; height: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;                          /* radio: the paper's circle */
  background: var(--surface);
  box-shadow: inset 0 0 0 1.5px var(--grey);
  transition: box-shadow var(--dur-1) var(--ease-out),
              background-color var(--dur-1) var(--ease-out);
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.choice__input[type="checkbox"] ~ .choice__mark { border-radius: var(--r-1); }

.choice:hover .choice__mark { box-shadow: inset 0 0 0 1.5px var(--teal); }

.choice__input:checked ~ .choice__mark {
  background: var(--teal);
  box-shadow: inset 0 0 0 1.5px var(--teal);
}
.choice__input[type="radio"]:checked ~ .choice__mark::after {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--paper);
}
.choice__input[type="checkbox"]:checked ~ .choice__mark::after {
  content: "";
  width: 10px; height: 6px;
  border: 2px solid var(--paper);
  border-block-start: 0;
  border-inline-end: 0;
  transform: rotate(-45deg) translate(1px, -1px);
}
/* Focus lands on the hidden input, so ring the mark it controls. */
.choice__input:focus-visible ~ .choice__mark {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
}
.choice__input:disabled ~ .choice__mark {
  background: var(--surface-sunk);
  box-shadow: inset 0 0 0 1.5px var(--rule-grey);
}
.choice__input:disabled ~ .choice__text { color: var(--ink-muted); }
.choice:has(.choice__input:disabled) { cursor: not-allowed; }

/* .choice--boxed - the paper's banded option rows (the trading-volume bands).
   Adds a mint plate around the whole option so the row reads as a cell. */
.choice--boxed {
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-2);
  background: var(--surface);
}
.choice--boxed:has(.choice__input:checked) {
  background: var(--mint);
  border-color: var(--rule);
}


/* =========================================================================
   10. SIGNATURE PAD
   Markup:
     <div class="sigpad" data-empty="true">
       <canvas class="sigpad__canvas" width="960" height="360"
               aria-label="محل امضا"></canvas>
       <p class="sigpad__hint">با انگشت یا ماوس امضا کنید</p>
       <button type="button" class="sigpad__clear btn btn--ghost">پاک کردن</button>
     </div>
   Set data-empty="false" from JS on the first stroke to hide the hint.
   ========================================================================= */

.sigpad {
  position: relative;
  background: var(--fill);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-3);
  padding: var(--sp-3);
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
/* The baseline the signature sits on, as ruled on the paper. */
.sigpad::after {
  content: "";
  position: absolute;
  inset-inline: var(--sp-6);
  inset-block-end: 28%;
  border-block-end: 1px dashed var(--rule);
  pointer-events: none;
}
.sigpad__canvas {
  display: block;
  width: 100%;
  height: 180px;
  touch-action: none;            /* drawing must not scroll the page */
  cursor: crosshair;
  border-radius: var(--r-2);
  background: transparent;
}
.sigpad__hint {
  position: absolute;
  inset-block-start: 50%;
  inset-inline: 0;
  transform: translateY(-50%);
  margin: 0;
  text-align: center;
  color: var(--ink-3);
  font-size: var(--fs-meta);
  pointer-events: none;
}
.sigpad[data-empty="false"] .sigpad__hint { display: none; }
/* .sigpad__clear - the "پاک کردن" affordance, corner-set on the inline-end. */
.sigpad__clear {
  position: absolute;
  inset-block-start: var(--sp-2);
  inset-inline-end: var(--sp-2);
  z-index: 1;
}


/* =========================================================================
   11. FILE UPLOAD
   Markup:
     <div class="upload">
       <label class="upload__drop" for="scan">
         <input class="sr-only" id="scan" type="file" accept="image/*,.pdf" multiple>
         <span class="upload__title">تصویر کارت ملی را بارگذاری کنید</span>
         <span class="upload__hint hint">JPG یا PDF، حداکثر ۵ مگابایت</span>
       </label>
       <ul class="upload__grid">
         <li class="upload__thumb">
           <img src="…" alt="تصویر کارت ملی، رو">
           <button type="button" class="upload__remove" aria-label="حذف تصویر">×</button>
         </li>
       </ul>
     </div>
   Add .is-dragover on dragenter, .is-busy while uploading.
   ========================================================================= */

.upload { display: grid; gap: var(--sp-3); }

.upload__drop {
  display: grid;
  gap: var(--sp-1);
  justify-items: center;
  text-align: center;
  min-height: 120px;
  align-content: center;
  padding: var(--sp-5) var(--sp-4);
  background: var(--fill);
  border: 1.5px dashed var(--rule);
  border-radius: var(--r-3);
  color: var(--heading);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease-out),
              border-color var(--dur-1) var(--ease-out);
}
.upload__drop:hover { background: var(--fill-strong); }
.upload:has(input:focus-visible) .upload__drop {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
}
.upload.is-dragover .upload__drop {
  background: var(--band);
  border-color: var(--spine);
  border-style: solid;
}
.upload.is-busy .upload__drop { opacity: .7; cursor: progress; }
.upload__title { font-weight: 500; }
/* .upload__hint - accepted formats and size limit, inside .upload__drop. */
.upload__hint { color: var(--ink-2); }

/* .upload__grid - the files already uploaded, one per row.
   It was a tile grid on a 4:3 box, which only made sense if the tile held a
   picture. The renderer emits a name and a size and nothing else, so every
   tile was two lines of text at the top of a 200px empty rectangle. A file
   list is a list of rows, and a row is as tall as its contents.

   To show real thumbnails instead, the renderer has to emit an <img> in
   .upload__thumb; say so and the leading image column can be styled. */
.upload__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-2);
}
/* .upload__thumb - one uploaded file: name, size, remove. The three have
   columns of their own, so none of them can be drawn over another.
   .is-busy and .is-failed carry the same two spans and lay out the same. */
.upload__thumb {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1);
  padding-inline-start: var(--sp-3);
  border-radius: var(--r-2);
  background: var(--surface-sunk);
  outline: 1px solid rgba(0,0,0,.1);
  outline-offset: -1px;
}
/* .upload__name - the filename, in an ltr run so IMG_4172.jpeg reads as
   itself inside Farsi. It is the only part of the row allowed to give way:
   it truncates, rather than pushing the size and the remove button out. */
.upload__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* .upload__size - the size, or the state sentence under .is-busy and
   .is-failed. It never wraps: "۴۲۲۱" on one line with "کیلوبایت" orphaned
   under it is not a file size, it is two fragments. */
.upload__size { white-space: nowrap; }
.upload__thumb.is-failed .upload__size { color: var(--seal-ink); }

/* .upload__remove - icon-only, so it needs an aria-label in the markup.
   It used to be absolutely positioned over the row, which put it on top of
   the start of the filename. It has a column now. The 44px target is the
   button itself rather than an invisible extension of a 28px one, so it is
   the same size to a measuring tool as it is to a thumb. */
.upload__remove {
  position: relative;
  isolation: isolate;
  width: var(--tap);
  height: var(--tap);
  display: grid; place-items: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--paper);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
/* The visible 28px chip, centred inside the 44px target. */
.upload__remove::before {
  content: "";
  position: absolute;
  inset: 8px;
  border-radius: 50%;
  background: rgba(36,38,93,.82);
  z-index: -1;
}
.upload__remove:hover::before { background: var(--plate); }
/* .upload__thumb.is-failed - an upload that did not land. Never rendered as
   an ordinary row. */
.upload__thumb.is-failed {
  background: var(--seal-wash);
  outline-color: rgba(192,57,43,.35);
}


/* =========================================================================
   12. VALIDATION STATES
   Colour is never the only cue: the error rule is thicker as well as red,
   and .field__msg carries the sentence. Link it with aria-describedby and
   set aria-invalid="true" on the control.
   ========================================================================= */

/* .field.is-error - on the .field wrapper. */
.field.is-error .field__label { color: var(--seal-ink); }
.field.is-error .input,
.field.is-error .select,
.field.is-error .textarea {
  background: var(--seal-wash);
  border-color: rgba(192,57,43,.4);
  border-block-end: 2.5px solid var(--seal);
}
.field.is-error .cells__input {
  background-image:
    repeating-linear-gradient(to right,
      transparent 0 calc(var(--cell-w) - 1px),
      rgba(192,57,43,.35) calc(var(--cell-w) - 1px) var(--cell-w)),
    linear-gradient(var(--seal), var(--seal)),
    linear-gradient(var(--seal-wash), var(--seal-wash));
  background-size:
    calc((var(--cells) - 1) * var(--cell-w)) 100%,
    calc(var(--cells) * var(--cell-w)) 2.5px,
    calc(var(--cells) * var(--cell-w)) 100%;
}
.field.is-error .field__msg { color: var(--seal-ink); font-weight: 500; }
/* The warning glyph, so the message is not red-only. */
.field.is-error .field__msg::before {
  content: "⚠ ";
  font-weight: 700;
}

/* .field.is-done - filled and validated. A quiet tick, no colour shout. */
.field.is-done .field__control::after {
  content: "✓";
  flex: none;
  color: var(--teal-ink);
  font-weight: 700;
  font-size: var(--fs-body);
  line-height: 1;
}
.field.is-done .input,
.field.is-done .select,
.field.is-done .textarea { border-block-end-color: var(--teal-ink); }

/* .form-error - a summary block at the top of a section listing the fields
   that failed. Markup: <div class="form-error" role="alert" tabindex="-1">
   with a <ul> of anchors to each failing control. */
.form-error {
  padding: var(--sp-3) var(--sp-4);
  background: var(--seal-wash);
  border: 1px solid rgba(192,57,43,.35);
  border-inline-start: 4px solid var(--seal);
  border-radius: var(--r-2);
  color: var(--seal-ink);
  font-size: var(--fs-meta);
}
.form-error ul { margin: var(--sp-1) 0 0; padding-inline-start: var(--sp-4); }


/* =========================================================================
   13. BUTTONS
   Markup: <button type="submit" class="btn btn--primary">ثبت و ادامه</button>
   A loading button keeps its label and adds .is-loading (see below).
   ========================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-5);
  border-radius: var(--r-2);
  border: 1px solid transparent;
  font-size: var(--fs-body);
  font-weight: 500;
  cursor: pointer;
  background: var(--surface);
  color: var(--heading);
  border-color: var(--rule-quiet);
  transition: background-color var(--dur-1) var(--ease-out),
              border-color var(--dur-1) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}
.btn:hover { background: var(--mint); border-color: var(--rule-soft); }
.btn:active { transform: scale(.98); }

.btn--primary { background: var(--plate); color: var(--on-plate); border-color: var(--plate); }
.btn--primary:hover { background: var(--plate-hover); border-color: var(--plate-hover); }

.btn--accent { background: var(--teal); color: #06302B; border-color: var(--teal); }
.btn--accent:hover { background: #2E9A8C; border-color: #2E9A8C; }

.btn--ghost {
  background: transparent;
  border-color: var(--rule-soft);
  color: var(--teal-ink);
  min-height: 36px;
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-meta);
  position: relative;
}
/* 36px visible, extended to a 44px hit area without moving the pill. */
.btn--ghost::after {
  content: "";
  position: absolute;
  inset-block: -4px;
  inset-inline: 0;
}
.btn--ghost:hover { background: var(--mint); }

.btn--danger { background: var(--seal); color: var(--paper); border-color: var(--seal); }

.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--surface-sunk);
  color: var(--ink-muted);
  border-color: var(--rule-grey);
  cursor: not-allowed;
  transform: none;
}

/* .btn.is-loading - keeps the original label and adds a spinner. Pair with
   aria-busy="true"; disable only after the request has actually started. */
.btn.is-loading::before {
  content: "";
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-block-start-color: transparent;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* .btnbar - the sticky action row at the foot of the content column. */
.btnbar {
  position: sticky;
  inset-block-end: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  padding-block-end: calc(var(--sp-3) + env(safe-area-inset-bottom));
  background: var(--surface);
  border-block-start: 1px solid var(--rule-quiet);
}
.btnbar__spacer { flex: 1 1 auto; }


/* =========================================================================
   14. CONTRACT VIEWER
   About twelve pages of scanned Farsi legal text, shown as page images. The
   accept row is gated: it stays dimmed and the checkbox stays `disabled` in
   the markup until the reader has reached the last page. The hint says why,
   because a dimmed control with no explanation is a dead end.

   Markup:
     <section class="contract" aria-labelledby="c-t">
       <h2 id="c-t" class="sr-only">متن قرارداد</h2>
       <div class="contract__bar no-print">
         <p class="contract__count nums" aria-live="polite">صفحه ۳ از ۱۲</p>
         <button type="button" class="contract__zoom btn btn--ghost"
                 aria-label="نمایش تمام‌صفحه">تمام‌صفحه</button>
       </div>
       <div class="contract__viewer">
         <ol class="contract__pages">
           <li class="contract__page">
             <img src="/pages/i01.jpg" alt="صفحه ۱ قرارداد" loading="lazy"
                  width="1240" height="1754">
           </li>
         </ol>
         <div class="contract__end" aria-hidden="true"></div>  <!-- IO sentinel -->
       </div>
       <div class="contract__accept">
         <label class="choice">
           <input class="choice__input" type="checkbox" id="agree" disabled
                  aria-describedby="agree-gate">
           <span class="choice__mark" aria-hidden="true"></span>
           <span class="choice__text">خوانده و پذیرفتم</span>
         </label>
         <p class="contract__gate hint" id="agree-gate">…</p>
       </div>
     </section>

   Add .is-read on .contract when the sentinel is seen, and remove `disabled`
   from the checkbox at the same moment.
   ========================================================================= */

.contract {
  background: var(--surface);
  border: 1px solid var(--rule-quiet);
  border-radius: var(--r-3);
  overflow: hidden;
}

.contract__bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-block-end: 1px solid var(--rule-quiet);
}
.contract__count { margin: 0; font-size: var(--fs-meta); color: var(--ink-2); }

/* .contract__zoom - the way to read the scan at full size. The markup gives
   it no .btn class, so left alone it renders as the browser's default control:
   grey, 32px, indistinguishable from nothing. On a phone it is the only way
   to read the text somebody is being asked to accept, so it is the loudest
   thing in the bar rather than an afterthought at the end of it. */
.contract__zoom {
  margin-inline-start: auto;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--teal);
  border-radius: var(--r-2);
  background: var(--teal);
  color: #06302B;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease-out);
}
.contract__zoom:hover { background: #2E9A8C; border-color: #2E9A8C; }

/* .contract__viewer - the scroll container. Gets `overscroll-behavior:
   contain` so flicking past the last page never scrolls the page behind it. */
.contract__viewer {
  max-height: min(70vh, 640px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface-sunk);
  -webkit-overflow-scrolling: touch;
}

.contract__pages {
  list-style: none;
  margin: 0;
  padding: var(--sp-3);
  display: grid;
  gap: var(--sp-3);
}
/* .contract__page - one scanned page. Constrained to the column, never wider.
   `touch-action: pinch-zoom` keeps two-finger zoom available on the image
   while one-finger drag still scrolls the list. */
.contract__page {
  background: var(--paper);
  border: 1px solid var(--rule-quiet);
  border-radius: var(--r-2);
  overflow: hidden;
}
.contract__page img {
  width: 100%;
  height: auto;
  touch-action: pinch-zoom;
}
.contract__end { height: 1px; }

/* Fullscreen. Requested on .contract__viewer from JS; the pseudo-class does
   the rest, so there is no second layout to maintain. */
.contract__viewer:fullscreen,
.contract__viewer:-webkit-full-screen {
  max-height: none;
  height: 100%;
  padding: 0;
  background: #0B0C1A;
}
.contract__viewer:fullscreen .contract__pages { padding: var(--sp-2); }
.contract__viewer:fullscreen .contract__page img { touch-action: pinch-zoom; }

/* .contract__accept - the gated accept row. */
.contract__accept {
  display: grid;
  gap: var(--sp-1);
  padding: var(--sp-4);
  border-block-start: 2px solid var(--rule);
  background: var(--fill);
  transition: opacity var(--dur-2) var(--ease-out),
              background-color var(--dur-2) var(--ease-out);
}
/* Dimmed until read. The checkbox also carries `disabled`; this is the
   visual half of the gate, never the whole gate. */
.contract:not(.is-read) .contract__accept {
  opacity: .55;
  background: var(--surface-sunk);
}
.contract.is-read .contract__accept {
  background: var(--fill);
  opacity: 1;
}
.contract__accept .choice__text {
  font-weight: 500;
  font-size: var(--fs-lead);
  color: var(--heading);
}
/* .contract__gate - the sentence that says why the box cannot be ticked yet.
   Swap its text once .is-read lands; never leave it saying nothing. */
.contract__gate { margin: 0; }
.contract.is-read .contract__gate { color: var(--teal-ink); }

/* THE SCAN ON A PHONE. -----------------------------------------------------
   Fitted to the column, an A4 page of dense Farsi legal text renders about
   266px wide at a 360px viewport. Nobody can read that, and it is the text
   the customer is ticking "متن این برگه را خوانده و می‌پذیرم" against.

   Measuring it turned up something worse than illegibility. At that size the
   whole page fits inside the viewer: clientHeight 403, scrollHeight 403.
   Nothing scrolls, so the renderer's own "this scan needed no scrolling, it
   has been seen" check is satisfied on the first frame and the accept gate
   opens before a single line has been looked at. On a phone the gate was not
   a gate.

   Rendering the scan at a readable width fixes both, and the second one falls
   out of the first: the document is taller than the viewer again, so reaching
   the end of it means going there. VERTICAL SCROLLING IS STILL WHAT REACHES
   THE END. The gate opens on scrollTop + clientHeight >= scrollHeight, and
   adding a horizontal axis does not touch that, so the JS needs no change. */
@media (max-width: 700px) {
  .contract__viewer { overflow: auto; }
  /* max-content so the page keeps its padding on BOTH sides once it is wider
     than the viewer; a percentage width would lose the far edge to the
     scroll. */
  .contract__pages {
    width: max-content;
    padding: var(--sp-2);
  }
  .contract__page img {
    width: 900px;
    max-width: none;
    /* pinch-zoom on its own forbids one-finger panning. That was harmless
       while the scan fitted and had nowhere to pan to; now that the page has
       to be dragged around, it would have left the scan immovable. */
    touch-action: pan-x pan-y pinch-zoom;
  }
}


/* =========================================================================
   15. DARK MODE (SCREEN ONLY)
   Token overrides, nothing else. Both directions are declared so an explicit
   toggle wins over the OS in either direction. The teal accent is preserved
   at its exact brand value; navy becomes a lighter plate because #24265D is
   1.6:1 against a dark surface and would vanish.

   The printed sheet and its on-screen preview are NOT themed. See .sheet.
   ========================================================================= */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --surface:      #171A38;
    --surface-sunk: #0E1026;
    --surface-rail: #14172F;

    --mint:         #17323C;
    --band:         #1D4148;
    --fill:         #152C38;
    --fill-strong:  #1C3B49;
    --fill-focus:   #1E4351;

    --ink:          #E9EBF5;
    --ink-2:        #ADB3D0;
    --ink-3:        #9CA2C2;
    --ink-muted:    #6E7395;
    --teal-ink:     #4FC3B3;

    --heading:      #E9EBF5;
    --on-band:      #D9F0EC;
    --plate:        #33377A;
    --plate-hover:  #3D4290;
    --on-plate:     #FFFFFF;
    --spine:        #4A4E9C;

    --rule-strong:  var(--teal);
    --rule-soft:    rgba(55,171,156,.45);
    --rule-quiet:   rgba(255,255,255,.10);
    --rule-grey:    rgba(255,255,255,.18);

    --seal:         #E4614F;
    --seal-ink:     #FF8A7A;
    --seal-wash:    #35181B;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --surface:      #171A38;
  --surface-sunk: #0E1026;
  --surface-rail: #14172F;

  --mint:         #17323C;
  --band:         #1D4148;
  --fill:         #152C38;
  --fill-strong:  #1C3B49;
  --fill-focus:   #1E4351;

  --ink:          #E9EBF5;
  --ink-2:        #ADB3D0;
  --ink-3:        #9CA2C2;
  --ink-muted:    #6E7395;
  --teal-ink:     #4FC3B3;

  --heading:      #E9EBF5;
  --on-band:      #D9F0EC;
  --plate:        #33377A;
  --plate-hover:  #3D4290;
  --on-plate:     #FFFFFF;
  --spine:        #4A4E9C;

  --rule-strong:  var(--teal);
  --rule-soft:    rgba(55,171,156,.45);
  --rule-quiet:   rgba(255,255,255,.10);
  --rule-grey:    rgba(255,255,255,.18);

  --seal:         #E4614F;
  --seal-ink:     #FF8A7A;
  --seal-wash:    #35181B;
}


/* =========================================================================
   16. PRINT AND THE .sheet FACSIMILE
   A .sheet is one A4 page of the printed form, reproduced exactly: the navy
   tab strip down the outer edge with rotated white-on-navy text, the logo in
   the top corner, the two-cell signature footer, and the sheet number in the
   bottom outer corner.

   Under RTL, `inline-end` is the LEFT side, which is where the strip and the
   sheet number sit on the paper. Nothing here uses left/right literals, so a
   sheet still assembles correctly if a page is ever rendered LTR.

   Markup:
     <article class="sheet">
       <aside class="sheet__spine" aria-hidden="true">
         <span class="sheet__tab is-active">فرم مشخصات مشتریان</span>
         <span class="sheet__tab">مشخصات مشتری / نماینده</span>
         <span class="sheet__tab">اطلاعات شغلی</span>
         <span class="sheet__tab">اطلاعات مالی</span>
         <span class="sheet__no nums">برگه ۱/۱۶</span>
       </aside>
       <header class="sheet__head">
         <img class="sheet__logo" src="/logo.png" alt="کارگزاری اعتبار تابان">
       </header>
       <div class="sheet__body"> … </div>
       <footer class="sheet__sign">
         <div class="sheet__sign-cell">امضا، مهر یا اثر انگشت ( مشتری | نماینده )</div>
         <div class="sheet__sign-cell">مهر و امضا شرکت کارگزاری اعتبار تابان</div>
       </footer>
     </article>

   A full-page scan prints as <article class="sheet sheet--image">
   with a single <img> child and no other furniture.
   ========================================================================= */

.sheet {
  /* The sheet is a printed artefact. It is always light, in both themes, so
     it declares its own palette rather than inheriting the app's. */
  color-scheme: light;
  --surface:      #FFFFFF;
  --surface-sunk: #FFFFFF;
  --mint:         #E6F4F3;
  --band:         #B8E1DC;
  --fill:         #E6F4F3;
  --fill-strong:  #E6F4F3;
  --fill-focus:   #FFFFFF;
  --ink:          #24265D;
  --ink-2:        #5F5D5D;
  --ink-3:        #6E6C6C;
  --heading:      #24265D;
  --on-band:      #24265D;
  --plate:        #24265D;
  --on-plate:     #FFFFFF;
  --spine:        #24265D;
  --rule:         #37AB9C;
  --rule-soft:    rgba(55,171,156,.42);
  --rule-quiet:   rgba(36,38,93,.12);
  --rule-strong:  #24265D;
  --seal:         #C0392B;
  --seal-ink:     #A5301F;
  --seal-wash:    #FBEDEB;

  --spine-w: 24mm;
  --sheet-pad: 12mm;

  /* The signature band: the strip at the foot of the paper that belongs to
     the signature box and nothing else. Answers never enter it, on any page.
     --sign-box is the box itself, --sign-foot the white paper under it and
     --sign-lead the clear space above it. */
  --sign-box:  23mm;
  --sign-foot: 10mm;
  --sign-lead: 10mm;
  --sign-band: calc(var(--sign-lead) + var(--sign-box) + var(--sign-foot));

  position: relative;
  box-sizing: border-box;
  width: 210mm;
  height: 297mm;
  background: #FFFFFF;
  color: var(--ink);
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--spine-w);
  grid-template-rows: auto minmax(0, 1fr) auto;
  overflow: hidden;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

/* On screen a sheet reads as a sheet of paper on the canvas. Both of these
   are removed for print. */
@media screen {
  .sheet {
    margin-inline: auto;
    border: 1px solid var(--rule-quiet);
    box-shadow: 0 1px 2px -1px rgba(36,38,93,.10), 0 8px 24px -12px rgba(36,38,93,.18);
  }
}

/* A FIELD SHEET IS A DOCUMENT, NOT A FIXED RECTANGLE.
   The .sheet rule above is exactly A4 with a body row clamped by
   minmax(0, 1fr). That is correct for a scan, where the content is one image
   of known size, and it is wrong for a sheet whose content is the customer's
   own answers, because how much of that there is cannot be known when the
   stylesheet is written. One longer address, one more trading code, one more
   signatory is enough to overrun the body row, and a clamped row does not
   push the footer down: it lets the answers paint underneath the signature
   box and off the bottom of the paper. On a document somebody signs, that is
   silent data loss, so the geometry has to make it impossible rather than
   unlikely.

   A sheet that is not a scan therefore grows with its content, and in print
   it is allowed to run onto a second page (see the print block below):

     min-height           the paper's height is a floor, never a ceiling
     minmax(min-content,)  the body row can never be shorter than the answers
                          inside it, so a long body DISPLACES the signature
                          footer instead of being overlapped by it
     overflow: visible    nothing is quietly clipped at the paper edge

   The 1fr maximum keeps the ordinary single-page case looking like the paper:
   leftover space still goes to the body row, so the signature box still sits
   at the foot of the page rather than floating up under the last answer.

   .sheet--image is deliberately excluded. It keeps the definite 297mm height
   its <img height:100%> depends on, so a scan stays one page, exactly A4. */
.sheet:not(.sheet--image) {
  height: auto;
  min-height: 297mm;
  grid-template-rows: auto minmax(min-content, 1fr) auto;
  overflow: visible;
}
/* The row's automatic minimum is the height of the answers in it. A zero
   min-height on the item hands that clamp straight back, so it is undone
   here rather than left to be rediscovered from a broken PDF.

   The body also reserves the signature band at its own foot. box-decoration-
   break: clone is the load-bearing word: it repeats that padding on EVERY
   fragment of the body, so a sheet that runs onto a second page keeps the
   band clear at the foot of the first page too, where there is no signature
   box to see but where a printer's dead zone still is. Without clone the
   padding lands only on the last fragment and the continued page prints its
   last row flush against the paper edge. */
.sheet:not(.sheet--image) > .sheet__body {
  min-height: auto;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  padding-block-end: var(--sign-band);
}
/* On the LAST fragment the band is where the signature box goes, so the box
   is pulled back up into it. Written as (lead - band) rather than as a number
   so it stays correct whatever the box's real height turns out to be: the box
   always begins exactly --sign-lead below the final answer.

   On a single-page sheet the row this collapses to is zero-height, the body
   row's 1fr takes the slack, and the box lands at the foot of the page just
   as it does on the paper. On a sheet that ran to two pages it lands under
   the last row, on the page that row is on. */
.sheet:not(.sheet--image) > .sheet__sign {
  margin-block-start: calc(var(--sign-lead) - var(--sign-band));
  /* A page break immediately above the box would strand it at the top of an
     otherwise empty page, which reads as a loose sheet rather than as the end
     of this one. Refusing the break there pulls the last of the answers over
     with it, so the box always arrives under content. */
  break-before: avoid;
  page-break-before: avoid;
}

/* .sheet__head - top corner block. Under RTL the logo lands on the right,
   as printed. */
.sheet__head {
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  padding: var(--sheet-pad) var(--sheet-pad) 4mm;
}
.sheet__logo { height: 15mm; width: auto; }

/* .sheet__body - the field area. */
.sheet__body {
  grid-column: 1;
  grid-row: 2;
  padding-inline: var(--sheet-pad);
  padding-block-end: 4mm;
  min-height: 0;
  font-size: 9.5pt;
  line-height: 1.6;
}

/* .sheet__sign - the two-cell signature footer box. First cell is the
   customer, second is the company, which under RTL puts the customer on the
   right exactly as on the paper. */
.sheet__sign {
  grid-column: 1;
  grid-row: 3;
  display: grid;
  grid-template-columns: 1fr 1fr;
  margin: 0 var(--sheet-pad) 10mm;
  border: 1px solid var(--navy);
  break-inside: avoid;
  page-break-inside: avoid;
}
.sheet__sign-cell {
  min-height: 23mm;
  padding: 2.5mm 3mm;
  font-size: 8.5pt;
  font-weight: 500;
  color: var(--navy);
  text-align: center;
}
.sheet__sign-cell + .sheet__sign-cell { border-inline-start: 1px solid var(--navy); }
/* A captured signature is a canvas export of unknown size. Uncapped it grows
   the box, and the box's height is what the band above is measured from. */
.sheet__sign-cell img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: calc(var(--sign-box) - 12mm);
  margin-inline: auto;
  object-fit: contain;
}

/* .sheet__spine - the vertical tab strip down the outer edge. */
.sheet__spine {
  grid-column: 2;
  grid-row: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 2mm;
  padding: var(--sheet-pad) 3mm 8mm;
  border-inline-end: 0;
}

/* .sheet__tab - one rotated tab. `vertical-rl` plus a 180deg turn makes the
   Farsi read bottom to top with the glyphs upright to a reader tilting their
   head to the right, which is how the printed strip reads. */
.sheet__tab {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 1 auto;
  min-height: 26mm;
  padding: 3mm 1.5mm;
  background: var(--navy);
  color: #FFFFFF;
  font-size: 8.5pt;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: .01em;
  text-align: center;
  border-radius: 1mm;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
/* .sheet__tab.is-active - the section this sheet belongs to. The paper fills
   only this one solid; the rest print as an outline. */
.sheet__tab:not(.is-active) {
  background: var(--mint);
  color: var(--navy);
  box-shadow: inset 0 0 0 .4mm var(--rule-soft);
}

/* .sheet__no - the sheet number, bottom outer corner, rotated with the strip.
   Markup: <span class="sheet__no nums">برگه ۱/۱۶</span>

   The renderer emits this as a sibling of the spine, not a child of it, so in
   the grid it used to auto-place into an implicit FOURTH row and steal height
   from the body on every sheet. It is taken out of flow instead, which also
   keeps it out of the fragmentation: as an in-flow item spanning the sheet it
   forced a blank band down the top of a continued page, and as an item inside
   the body row it added pages.

   The offsets are PHYSICAL on purpose, and it is the one place in this file
   that is. This element sets vertical-rl, and the logical inset properties of
   a positioned box resolve in the box's OWN writing mode, where block-start
   is the right edge and inline-end is the top. inset-block-start: 278mm here
   does not mean "278mm down the page", it means "278mm in from the right",
   which puts the folio off the paper entirely and silently. top and left do
   not lie, and the one direction-dependent value gets an explicit LTR pair
   below rather than a mapping nobody can read. */
.sheet__no {
  position: absolute;
  top: 278mm;                              /* the foot of the sheet's 1st page */
  left: calc(var(--spine-w) + 1mm);        /* RTL: just inboard of the strip */
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: 7.5pt;
  color: var(--ink-2);
  letter-spacing: .02em;
}
[dir="ltr"] .sheet__no {
  left: auto;
  right: calc(var(--spine-w) + 1mm);
}

/* .sheet__stamp - the electronic-acceptance stamp laid over a sheet, most
   often a full-page contract scan. It is FURNITURE, not flow content: left
   in the flow it adds its own height to a box that is already exactly A4,
   which pushes every scan past 297mm and prints it clipped or spilled onto
   a second page.

   It sits in the bottom OUTER corner, which under RTL is the bottom left,
   because the paper's own signature box occupies the bottom right.

   Markup, inside <article class="sheet sheet--image"> after the <img>:
     <div class="sheet__stamp">
       <span>پذیرفته شده به صورت الکترونیکی</span>
       <span>۱۱ شهریور ۱۴۰۵، ۱۱:۴۸</span>
       <img src="…" alt="امضای مشتری">      <!-- optional -->
     </div>
   The first span is the claim and is set bold; any further span is metadata.
   The plate is nearly opaque so the words stay readable over the scan's own
   ink, and it carries print-color-adjust so it survives printing. */
.sheet__stamp {
  position: absolute;
  z-index: 2;
  inset-block-end: 8mm;
  inset-inline-end: 8mm;
  display: grid;
  justify-items: center;
  gap: 1mm;
  max-width: 62mm;
  padding: 2mm 3mm;
  background: rgba(255, 255, 255, .93);
  border: .3mm solid var(--teal);
  border-radius: 1.5mm;
  color: var(--navy);
  font-size: 7pt;
  line-height: 1.45;
  text-align: center;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.sheet__stamp > span { display: block; }
.sheet__stamp > span:first-child { font-weight: 700; }
.sheet__stamp > span ~ span { font-size: 6.5pt; color: var(--ink-2); }
/* The captured signature, capped so a large canvas export cannot blow the
   stamp up. max-width never upscales, so a small image stays small. */
.sheet__stamp > img {
  width: auto;
  height: auto;
  max-width: 90px;
  max-height: 40px;
  object-fit: contain;
  margin-block-start: .5mm;
}

/* .sheet--image - a scanned contract page. Full bleed, no furniture. */
.sheet--image {
  display: block;
  padding: 0;
}
.sheet--image > img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}
/* The stamp is deliberately absent from this list: it is the one piece of
   furniture a scan sheet keeps. */
.sheet--image .sheet__spine,
.sheet--image .sheet__head,
.sheet--image .sheet__sign { display: none; }

/* .sheet-scaler - on-screen preview wrapper so a 210mm sheet fits a phone.
   Markup: <div class="sheet-scaler" style="--sheet-scale:.42">
             <article class="sheet"> … </article>
           </div>
   Set --sheet-scale from JS as (containerWidth / 794). Print ignores it. */
/* The default scales are chosen so a preview never overflows even if the app
   never sets --sheet-scale: .92 fits the 760px column, .38 fits a 360px
   phone. Override it from JS as (containerWidth / 794) for an exact fit. */
.sheet-scaler {
  --sheet-scale: .92;
  width: calc(210mm * var(--sheet-scale));
  max-width: 100%;
  height: calc(297mm * var(--sheet-scale));
  margin-inline: auto;
  overflow: hidden;
}
@media (max-width: 720px) {
  .sheet-scaler { --sheet-scale: .38; }
}
.sheet-scaler > .sheet {
  transform: scale(var(--sheet-scale));
  transform-origin: top right;   /* RTL: the sheet pins to its start edge */
  margin: 0;
}
[dir="ltr"] .sheet-scaler > .sheet { transform-origin: top left; }

/* --- the print stylesheet ------------------------------------------------ */

@page { size: A4; margin: 0; }

@media print {
  /* Force the mint plates and the banded headers to actually appear. Without
     this, every browser drops them and the form prints as bare rules. */
  html, body, .sheet, .sheet * {
    print-color-adjust: exact !important;
    -webkit-print-color-adjust: exact !important;
  }

  html, body {
    width: 210mm;
    margin: 0;
    padding: 0;
    background: #FFFFFF;
    color: #24265D;
    font-size: 9.5pt;
  }

  /* App chrome never prints. */
  .no-print,
  .appbar,
  .chips,
  .rail,
  .btnbar,
  .skip-link,
  .contract__bar,
  .contract__zoom,
  .sigpad__clear,
  .upload__drop,
  .upload__remove,
  .savechip,
  .progress { display: none !important; }

  .print-only { display: revert !important; }

  /* The app shell collapses; only sheets have geometry. */
  .app { display: block; padding: 0; }
  .col { max-width: none; padding: 0; gap: 0; display: block; }

  .sheet {
    margin: 0;
    border: 0;
    box-shadow: none;
    break-after: page;
    page-break-after: always;
  }
  .sheet:last-child { break-after: auto; page-break-after: auto; }

  /* A scan is one image and must stay one page, exactly A4. */
  .sheet--image {
    break-inside: avoid;
    page-break-inside: avoid;
  }
  /* A field sheet must be allowed to continue onto a second page. Forbidding
     the break here is what forced the answers to overlap the signature box
     and run off the paper: the content still did not fit, it just had nowhere
     legal to go. Each ROW is kept whole instead (see section 21), which is
     the constraint that actually matters on a form. */
  .sheet:not(.sheet--image) {
    break-inside: auto;
    page-break-inside: auto;
  }

  /* A preview scaler is a screen affordance only. */
  .sheet-scaler {
    width: auto; height: auto; overflow: visible;
  }
  .sheet-scaler > .sheet { transform: none; }

  /* Rows, cards and grids must not split across a page. */
  .card, .rows, .row, .field, .choices, .field__grid tr, .sigpad {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* Plates and bands keep their fill on paper. */
  .input, .select, .textarea, .cells__input, .sigpad {
    background-color: #E6F4F3 !important;
    border-color: #37AB9C;
  }
  .section-bar, .field__grid th {
    background-color: #B8E1DC !important;
    color: #24265D !important;
  }
  .card {
    border: 0;
    border-radius: 0;
    background: transparent;
  }
  .card__head { border-block-end: 1.5pt solid #37AB9C; }

  /* Segmented cells must survive: redeclare the plate as a print colour. */
  .cells { overflow: visible; }
  .cells__input {
    background-image:
      repeating-linear-gradient(to right,
        transparent 0 calc(var(--cell-w) - 1px),
        rgba(55,171,156,.6) calc(var(--cell-w) - 1px) var(--cell-w)),
      linear-gradient(#37AB9C, #37AB9C),
      linear-gradient(#E6F4F3, #E6F4F3) !important;
  }

  /* Links keep their text, not a URL dump: this is a legal form, not an
     article. Nothing is appended. */
  a { color: inherit; text-decoration: none; }

  /* Contract scans print one to a page, full bleed. */
  .contract { border: 0; border-radius: 0; background: none; }
  .contract__viewer { max-height: none; overflow: visible; background: none; }
  .contract__pages { display: block; padding: 0; }
  .contract__page {
    width: 210mm;
    height: 297mm;
    border: 0;
    border-radius: 0;
    break-after: page;
    page-break-after: always;
    break-inside: avoid;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .contract__page img {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }
  .contract__accept {
    border-block-start: 1.5pt solid #37AB9C;
    background: #E6F4F3 !important;
    opacity: 1 !important;
  }
}


/* =========================================================================
   18. NOTICES, BADGES, LISTS AND OVERLAYS
   Additional app surfaces. Same tokens, same borders-only depth. The only
   two things in this file that lift off the page are .lightbox and .toast,
   because both genuinely float above everything else.
   ========================================================================= */

/* .callout - an inline notice block. Markup:
     <div class="callout callout--success" role="status">
       <p class="callout__title">فرم شما ثبت شد</p>
       <p>کد پیگیری شما ۴۸۲۱ است.</p>
     </div>
   Use role="status" for info and success, role="alert" for danger.
   The rule on the start edge is the shape cue, so the state does not depend
   on colour alone. */
.callout {
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--rule-quiet);
  border-inline-start: 4px solid var(--grey);
  border-radius: var(--r-2);
  background: var(--surface-sunk);
  color: var(--ink-2);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
/* .callout__title - the one sentence that says what happened. */
.callout__title {
  margin: 0 0 var(--sp-1);
  font-weight: 700;
  color: var(--heading);
}
.callout p:last-child { margin-bottom: 0; }
.callout a { font-weight: 500; }

.callout--info    { background: var(--mint); border-color: var(--rule-soft); border-inline-start-color: var(--teal); }
.callout--info    .callout__title { color: var(--teal-ink); }
.callout--success { background: var(--mint); border-color: var(--rule-soft); border-inline-start-color: var(--teal-ink); }
.callout--success .callout__title::before { content: "✓ "; }
.callout--warn    { background: #FDF4E7; border-color: rgba(180,120,20,.28); border-inline-start-color: #B4780F; color: #6B4A08; }
.callout--warn    .callout__title { color: #7A5209; }
.callout--warn    .callout__title::before { content: "⚠ "; }
.callout--danger  { background: var(--seal-wash); border-color: rgba(192,57,43,.3); border-inline-start-color: var(--seal); color: var(--seal-ink); }
.callout--danger  .callout__title { color: var(--seal-ink); }
.callout--danger  .callout__title::before { content: "⚠ "; }

/* .badge - a status pill. Markup: <span class="badge badge--success">تأیید شده</span>
   The word is the status; the tint is a second cue. Never a badge with no text. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  border-radius: var(--r-pill);
  border: 1px solid var(--rule-quiet);
  background: var(--surface-sunk);
  color: var(--ink-2);
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 1.7;
  white-space: nowrap;
}
.badge--neutral { background: var(--surface-sunk); color: var(--ink-2); }
.badge--info    { background: var(--mint); border-color: var(--rule-soft); color: var(--teal-ink); }
.badge--success { background: var(--mint); border-color: var(--teal); color: var(--teal-ink); }
.badge--warn    { background: #FDF4E7; border-color: rgba(180,120,20,.3); color: #7A5209; }

/* .btn--sm / .btn--lg - size modifiers on .btn. .btn--sm keeps a 44px hit
   area through the same pseudo-element trick as .btn--ghost. */
.btn--sm {
  min-height: 34px;
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-meta);
  position: relative;
}
.btn--sm::after {
  content: "";
  position: absolute;
  inset-block: -5px;
  inset-inline: 0;
}
.btn--lg {
  min-height: 52px;
  padding: var(--sp-3) var(--sp-8);
  font-size: var(--fs-lead);
  border-radius: var(--r-3);
}

/* .bullets - the paper's teal-marker clause list.
   Markup: <ul class="bullets"><li>…</li></ul> */
.bullets {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-2);
}
.bullets > li {
  position: relative;
  padding-inline-start: var(--sp-5);
  line-height: var(--lh-body);
  text-wrap: pretty;
}
.bullets > li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: .72em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--teal);
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

/* .repeat - a repeating row group: trading codes, board members, signatories.
   Markup:
     <div class="repeat">
       <div class="repeat__row">
         <span class="repeat__num nums" aria-hidden="true">۱</span>
         <div class="field">…</div>
         <div class="field">…</div>
         <button type="button" class="repeat__remove btn btn--ghost"
                 aria-label="حذف ردیف ۱">حذف</button>
       </div>
       <button type="button" class="repeat__add btn btn--ghost">افزودن ردیف</button>
     </div>
   On phones each row is its own bordered block. From 640px the fields inside
   a row lay out side by side. */
.repeat { display: grid; gap: var(--sp-3); }

.repeat__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--sp-1) var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-2);
  background: var(--surface);
}
.repeat__row > .field {
  grid-column: 2;
  padding-inline: 0;
  padding-block: var(--sp-2);
}
.repeat__row > .field + .field { border-block-start: 1px solid var(--rule-soft); }

/* .repeat__num - the row ordinal, matching the rail's badge. */
.repeat__num {
  grid-row: 1;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--mint);
  color: var(--teal-ink);
  font-size: var(--fs-micro);
  font-weight: 500;
}
.repeat__remove { grid-column: 2; justify-self: start; }
.repeat__add { justify-self: start; }

@media (min-width: 640px) {
  .repeat__row {
    grid-template-columns: auto repeat(auto-fit, minmax(180px, 1fr)) auto;
    align-items: center;
  }
  .repeat__row > .field {
    grid-column: auto;
    grid-template-columns: minmax(0, 1fr);
    border-block-start: 0;
    padding-block: 0;
  }
  .repeat__row > .field + .field { border-block-start: 0; }
  .repeat__row > .field .field__label { font-size: var(--fs-meta); }
  .repeat__remove { grid-column: auto; justify-self: end; }
}

/* .lightbox - full-screen image viewer for a tapped contract scan.
   Markup: <div class="lightbox" role="dialog" aria-modal="true"
               aria-label="نمای بزرگ صفحه ۳" hidden>
             <img class="lightbox__img" src="…" alt="صفحه ۳ قرارداد">
             <button class="lightbox__close" aria-label="بستن">×</button>
           </div>
   Toggle with the `hidden` attribute. Trap focus, return it to the trigger
   on close, and close on Escape: this file cannot do any of that for you. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  padding-block-start: max(var(--sp-4), env(safe-area-inset-top));
  background: rgba(11, 12, 26, .92);
  overflow: auto;
  overscroll-behavior: contain;
}
.lightbox[hidden] { display: none; }
.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  object-fit: contain;
  touch-action: pinch-zoom;
  background: var(--paper);
  border-radius: var(--r-2);
}
.lightbox__close {
  position: fixed;
  inset-block-start: max(var(--sp-3), env(safe-area-inset-top));
  inset-inline-end: var(--sp-3);
  width: var(--tap); height: var(--tap);
  display: grid; place-items: center;
  border: 1px solid rgba(255,255,255,.28);
  border-radius: 50%;
  background: rgba(255,255,255,.12);
  color: #FFFFFF;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.lightbox__close:hover { background: rgba(255,255,255,.22); }

/* .toast - a transient confirmation, bottom centre. Markup:
     <div class="toast" role="status" aria-live="polite">ذخیره شد</div>
   Remove the node when it is gone; do not leave an invisible live region
   holding stale text. */
.toast {
  position: fixed;
  inset-block-end: calc(var(--sp-6) + env(safe-area-inset-bottom));
  inset-inline: 0;
  z-index: 70;
  width: max-content;
  max-width: calc(100% - var(--sp-8));
  margin-inline: auto;
  padding: var(--sp-2) var(--sp-5);
  border-radius: var(--r-pill);
  background: var(--navy);
  color: #FFFFFF;
  font-size: var(--fs-meta);
  text-align: center;
  box-shadow: 0 4px 16px -6px rgba(11,12,26,.5);
  animation: toast-in var(--dur-2) var(--ease-out);
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
/* .toast.is-leaving - set before removing the node. */
.toast.is-leaving { opacity: 0; transition: opacity var(--dur-1) var(--ease-out); }

/* .loading - a centred placeholder while something is fetched. Say what is
   loading; "…" alone is a dead end. Markup:
     <p class="loading" role="status">در حال بارگذاری فرم…</p> */
.loading {
  margin: 0;
  padding: var(--sp-10) var(--sp-4);
  text-align: center;
  color: var(--ink-2);
  font-size: var(--fs-body);
}

/* .link-box - a generated URL. Wraps rather than overflowing, and stays LTR
   inside Farsi copy. Markup: <code class="link-box ltr-run">https://…</code> */
.link-box {
  display: block;
  direction: ltr;
  unicode-bidi: isolate;
  text-align: left;
  padding: var(--sp-2) var(--sp-3);
  background: var(--fill);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-2);
  font-family: var(--font-mono);
  font-size: var(--fs-meta);
  line-height: 1.6;
  color: var(--ink);
  overflow-wrap: anywhere;
  word-break: break-all;
}


/* =========================================================================
   19. ADMIN CONSOLE
   A separate page at /admin for brokerage staff on a laptop. Denser than the
   customer form on purpose: staff work through many submissions a day, and
   the airy field rhythm that helps one customer fill one form once would
   make a queue unreadable.
   ========================================================================= */

/* .login - the centred sign-in card. Reuses .field and .input inside.
   Markup: <main class="login"><form class="login__card"> … </form></main> */
.login {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--sp-5);
  background: var(--surface-sunk);
}
.login__card {
  width: 100%;
  max-width: 380px;
  padding: var(--sp-6);
  background: var(--surface);
  border: 1px solid var(--rule-quiet);
  border-top: 3px solid var(--teal);
  border-radius: var(--r-3);
  display: grid;
  gap: var(--sp-4);
}
.login__logo { height: 30px; width: auto; justify-self: center; }
.login__title { font-size: var(--fs-h3); text-align: center; }
.login .field { padding-inline: 0; grid-template-columns: minmax(0, 1fr); }
.login .field__label { text-align: start; }

/* .toolbar - a wrapping row of filter controls above .list.
   Markup: <div class="toolbar"> <input class="input"> <select class="select">
           <span class="btnbar__spacer"></span> <button class="btn btn--sm"> </div> */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) 0;
}
.toolbar .input,
.toolbar .select { width: auto; min-width: 10rem; flex: 0 1 16rem; min-height: 38px; }
.toolbar > .btnbar__spacer { flex: 1 1 auto; }

/* .list - the submissions queue.
   Markup:
     <ul class="list">
       <li class="list__row">
         <div class="list__main">
           <p class="list__title">هاشمی، وحید</p>
           <p class="list__meta nums">کد ۴۸۲۱، ۱۴۰۴/۰۶/۱۱</p>
         </div>
         <div class="list__tags"><span class="badge badge--success">کامل</span></div>
         <div class="list__actions"><button class="btn btn--sm">مشاهده</button></div>
       </li>
     </ul>
   On a phone the row stacks: title, meta, tags, then the actions. */
.list {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--rule-quiet);
  border-radius: var(--r-3);
  background: var(--surface);
  overflow: hidden;
}
.list__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  align-items: center;
}
.list__row + .list__row { border-block-start: 1px solid var(--rule-quiet); }
.list__row:hover { background: var(--surface-sunk); }
.list__main { min-width: 0; }
.list__title {
  margin: 0;
  font-weight: 500;
  color: var(--heading);
  overflow-wrap: anywhere;
}
.list__meta {
  margin: 0;
  font-size: var(--fs-meta);
  color: var(--ink-2);
  overflow-wrap: anywhere;
}
.list__tags { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.list__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
@media (min-width: 720px) {
  .list__row {
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: var(--sp-4);
  }
  .list__actions { justify-content: flex-end; }
}
/* .list.is-empty - use with a real sentence inside, never a blank box. */
.list.is-empty { padding: var(--sp-8) var(--sp-4); text-align: center; color: var(--ink-2); }

/* .drawer - the submission detail panel. Under RTL it comes in from the LEFT,
   which is the inline-end edge.
   Markup:
     <div class="drawer" hidden>
       <div class="drawer__scrim" data-close></div>
       <aside class="drawer__panel" role="dialog" aria-modal="true" aria-labelledby="dt">
         <header class="drawer__head">
           <h2 class="drawer__title" id="dt">هاشمی، وحید</h2>
           <button class="drawer__close" aria-label="بستن">×</button>
         </header>
         <div class="drawer__body">
           <h3 class="drawer__group">برگه ۱ - مشخصات مشتری</h3>
           <dl class="kv"> … </dl>
         </div>
       </aside>
     </div>
   Toggle the `hidden` attribute on .drawer. Focus trap, focus return and
   Escape are the app's job. */
.drawer {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
}
.drawer[hidden] { display: none; }
.drawer__scrim {
  position: absolute;
  inset: 0;
  background: rgba(11, 12, 26, .5);
}
.drawer__panel {
  position: relative;
  margin-inline-start: auto;   /* RTL: pins the panel to the left edge */
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 560px;
  background: var(--surface);
  border-inline-end: 0;
  border-inline-start: 1px solid var(--rule-quiet);
  overscroll-behavior: contain;
  animation: drawer-in var(--dur-2) var(--ease-out);
}
@keyframes drawer-in {
  from { opacity: 0; transform: translateX(-16px); }
  to   { opacity: 1; transform: none; }
}
/* Under LTR the auto inline-start margin already pins the panel to the right
   edge, so nothing extra is needed; only the entry offset flips. */
[dir="ltr"] .drawer__panel { animation-name: drawer-in-ltr; }
@keyframes drawer-in-ltr {
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: none; }
}

.drawer__head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-block-end: 2px solid var(--rule);
  flex: none;
}
.drawer__title { font-size: var(--fs-lead); min-width: 0; overflow-wrap: anywhere; }
.drawer__close {
  margin-inline-start: auto;
  flex: none;
  width: var(--tap); height: var(--tap);
  display: grid; place-items: center;
  border: 1px solid var(--rule-quiet);
  border-radius: var(--r-2);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.drawer__close:hover { background: var(--mint); color: var(--heading); }

/* .drawer__body - the panel's own scroll. */
.drawer__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-4);
  display: grid;
  gap: var(--sp-5);
  align-content: start;
}
/* .drawer__group - one sheet's answers. Reads as the paper's banded row. */
.drawer__group {
  margin: 0 0 var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  background: var(--band);
  color: var(--on-band);
  font-size: var(--fs-meta);
  font-weight: 700;
  border-radius: var(--r-1);
}

/* .kv - label and value pairs. Markup: <dl class="kv"><dt>…</dt><dd>…</dd></dl> */
.kv {
  margin: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  font-size: var(--fs-meta);
}
.kv > dt {
  color: var(--ink-2);
  padding-block: var(--sp-1);
}
.kv > dd {
  margin: 0;
  color: var(--heading);
  font-weight: 500;
  padding-block: var(--sp-1);
  padding-block-end: var(--sp-2);
  border-block-end: 1px solid var(--rule-quiet);
  overflow-wrap: anywhere;
}
@media (min-width: 480px) {
  .kv { grid-template-columns: minmax(7rem, 12rem) minmax(0, 1fr); column-gap: var(--sp-4); }
  .kv > dt { padding-block: var(--sp-2); border-block-end: 1px solid var(--rule-quiet); }
  .kv > dd { padding-block: var(--sp-2); }
}
/* .kv > dd:empty renders as a visible gap, which reads as "we have no answer"
   rather than "the answer is blank". Put a real dash in the markup instead. */

/* .trail - the audit log. Markup:
     <ul class="trail"><li><time class="trail__at ltr-run">1404-06-11 09:12</time>
     ارسال توسط مشتری</li></ul> */
.trail {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-1);
  font-size: var(--fs-meta);
  color: var(--ink-2);
}
.trail > li {
  display: flex;
  gap: var(--sp-2);
  align-items: baseline;
  padding-block: 2px;
}
.trail__at {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--ink-3);
}

/* .admin-sig - the captured signature, shown as the customer drew it. */
.admin-sig {
  padding: var(--sp-3);
  background: var(--fill);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-2);
}
.admin-sig img {
  width: 100%;
  max-height: 140px;
  object-fit: contain;
  object-position: center;
}
/* .admin-sig.is-missing - no signature captured. Never an empty plate that
   could be mistaken for a blank one the customer signed. */
.admin-sig.is-missing {
  background: var(--surface-sunk);
  border-style: dashed;
  color: var(--ink-2);
  text-align: center;
  font-size: var(--fs-meta);
}


/* =========================================================================
   20. LANDING
   The public index. It has one job: tell someone who arrived without a link
   that they need the link they were sent.
   Markup:
     <main class="landing">
       <img class="landing__logo" src="/logo.png" alt="کارگزاری اعتبار تابان">
       <h1 class="landing__title">فرم‌های ثبت‌نام</h1>
       <p class="landing__lead">…</p>
       <ol class="landing__steps">
         <li class="landing__step"><span class="landing__num nums">۱</span>
             <p>لینک اختصاصی خود را باز کنید.</p></li>
       </ol>
       <p class="landing__note">…</p>
     </main>
   ========================================================================= */

.landing {
  min-height: 100dvh;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--sp-4);
  max-width: 520px;
  margin-inline: auto;
  padding: var(--sp-8) var(--sp-5);
  text-align: center;
}
.landing__logo { height: 40px; width: auto; }
.landing__title { font-size: var(--fs-h1); }
.landing__lead {
  margin: 0;
  color: var(--ink-2);
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  text-wrap: pretty;
}
.landing__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-2);
  width: 100%;
  text-align: start;
}
.landing__step {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--rule-quiet);
  border-radius: var(--r-2);
}
.landing__step p { margin: 0; }
.landing__num {
  flex: none;
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--mint);
  color: var(--teal-ink);
  font-size: var(--fs-meta);
  font-weight: 700;
}
.landing__note {
  margin: 0;
  color: var(--ink-3);
  font-size: var(--fs-meta);
  line-height: 1.7;
}


/* =========================================================================
   21. PRINT GRID INTERNALS
   What the print renderer emits inside .sheet__body. These are deliberately
   NOT the screen field classes: a printed sheet is a fixed A4 rectangle with
   about 25 answers on it, so the row rhythm is set in mm and the type in pt,
   and the label column is a fixed measure so every row aligns down the page.

   All of these inherit .sheet's local light palette, so they look identical
   on screen inside .sheet-scaler and on paper. Nothing here depends on the
   app theme.

   Density budget for one A4 sheet:
     297mm - 26mm head - 30mm signature footer - 12mm bottom pad ≈ 200mm body.
     A .print-row is 7mm and .print-grid packs two per line, so 25 answers is
     13 lines ≈ 95mm, leaving room for two group bars and an intro note.

   Markup inside <div class="sheet__body">:
     <p class="print-note">…</p>
     <h3 class="print-bar">مشخصات مشتری</h3>
     <div class="print-grid">
       <div class="print-row">
         <span class="print-label">نام</span>
         <span class="print-val">وحید</span>
       </div>
       <div class="print-row print-row--wide">
         <span class="print-label">نشانی</span>
         <span class="print-val">…</span>
       </div>
       <div class="print-row">
         <span class="print-label">جنسیت</span>
         <span class="print-val">
           <span class="print-check is-on">مرد</span>
           <span class="print-check">زن</span>
         </span>
       </div>
       <div class="print-row print-row--wide">
         <span class="print-label">شماره شبا</span>
         <span class="print-val">
           <span class="print-cells">
             <span class="print-cells-prefix">IR</span>
             <span class="print-cell">0</span> …
           </span>
         </span>
       </div>
     </div>
     <table class="print-table"> … </table>
     <ul class="print-bullets"><li>…</li></ul>
   ========================================================================= */

/* .print-note - the intro paragraph at the head of a sheet. */
.print-note {
  margin: 0 0 3mm;
  font-size: 7.5pt;
  line-height: 1.6;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* .print-bar - a group heading. The banded row from the paper, tightened. */
.print-bar {
  margin: 0 0 1.5mm;
  padding: 1mm 3mm;
  background: var(--band);
  color: var(--on-band);
  font-size: 8.5pt;
  font-weight: 700;
  text-align: center;
  border: .25mm solid var(--rule-soft);
  border-radius: .8mm;
  break-after: avoid;
  page-break-after: avoid;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.print-bar + .print-grid { margin-block-end: 2mm; }
.print-grid + .print-bar { margin-block-start: 2mm; }

/* .print-grid - two answers per line, so a 25-field sheet fits one page. */
.print-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 4mm;
  border-block-start: .25mm solid var(--rule-soft);
}

/* The label column. It is the single biggest lever on how tall a sheet is,
   because a label that does not fit does not overflow, it WRAPS, and six
   wrapped lines turn a 7mm row into a 22mm one. A question like
   "ارزش روز دارایی‌های تحت مالکیت شما تقریبا چقدر است؟" is a sentence, not a
   word, and at 22mm it took six lines on its own.

   Two widths, because the two kinds of row have genuinely different budgets:

   --print-label      half-width rows. The widest thing that has to share one
                      with a label is a run of 12 segmented cells (the renderer
                      sends anything longer to a full-width row). Those cells
                      now tighten a little rather than overflow, so this is set
                      for the labels' sake and the cells cope.
   --print-label-wide full-width rows, which is where the long question stems
                      live. 46mm turns most of them from five or six lines
                      into two, and still leaves 116mm, enough for the widest
                      run there is: a 24-cell IBAN with its IR prefix. */
.sheet {
  --print-label: 32mm;
  --print-label-wide: 46mm;
}

/* .print-row - one answer. Label right (RTL start), value plate left. */
.print-row {
  display: grid;
  grid-template-columns: var(--print-label) minmax(0, 1fr);
  align-items: stretch;
  min-height: 7mm;
  border-block-end: .25mm solid var(--rule-soft);
  break-inside: avoid;
  page-break-inside: avoid;
}
/* .print-row--wide - spans both columns: addresses, IBANs, long free text. */
.print-row--wide { grid-column: 1 / -1; }
.print-row--wide { grid-template-columns: var(--print-label-wide) minmax(0, 1fr); }

/* .print-label - navy, set against the ruling on the start edge. */
.print-label {
  display: flex;
  align-items: center;
  padding: 1mm 2mm 1mm 0;
  font-size: 7.5pt;
  font-weight: 500;
  line-height: 1.25;
  color: var(--navy);
  overflow-wrap: anywhere;
}

/* .print-val - the mint plate the answer sits in. */
.print-val {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1mm 3mm;
  padding: 1mm 2mm;
  background: var(--fill);
  font-size: 8.5pt;
  line-height: 1.3;
  color: var(--ink);
  overflow-wrap: anywhere;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
/* An answer that is genuinely absent prints as a dash, not as an empty plate
   that reads like a box the customer forgot to fill. This is a safety net:
   emit the real reason in the markup where you know it, because "-" and
   "the customer left this blank" and "this field does not apply" are three
   different facts and only the renderer can tell them apart. */
.print-val:empty::before { content: "-"; color: var(--ink-3); }

/* .print-check - one option in a choice row. .is-on marks the chosen one. */
.print-check {
  display: inline-flex;
  align-items: center;
  gap: 1mm;
  font-size: 8pt;
  color: var(--ink-2);
  white-space: nowrap;
}
.print-check::before {
  content: "☐";
  font-size: 9.5pt;
  line-height: 1;
  color: var(--grey);
}
.print-check.is-on { color: var(--navy); font-weight: 700; }
.print-check.is-on::before { content: "☑"; color: var(--navy); }

/* .print-cells - the segmented boxes, drawn as REAL boxes.
   The screen field fakes cells with a gradient because it has to stay one
   <input>. The print renderer has no such constraint, so it emits one
   .print-cell per character. That is exact at any font, needs no
   letter-spacing, and works with Persian digits, which the screen trick
   cannot do (see section 08). */
/* LTR, because an IBAN and a national id are read left to right. Left to
   inherit RTL, the IR prefix lands on the right and the digits run backwards
   away from it. Isolated so it cannot reorder the Farsi label beside it. */
.print-cells {
  direction: ltr;
  unicode-bidi: isolate;
  display: flex;
  align-items: stretch;
  gap: 0;
  flex-wrap: nowrap;
  max-width: 100%;
}
/* A cell keeps its 4.2mm unless the run genuinely will not fit its column, in
   which case the whole run tightens rather than overflowing into the answer
   beside it. The floor is the width below which a Persian digit stops being
   comfortable to read. */
.print-cell {
  flex: 0 1 auto;
  width: 4.2mm;
  min-width: 3.4mm;
  height: 5mm;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 .3mm;
  border: .25mm solid var(--rule-soft);
  background: var(--paper);
  font-size: 8.5pt;
  line-height: 1;
  color: var(--ink);
}
/* Adjacent cells share one rule rather than drawing two side by side. */
.print-cell + .print-cell { border-inline-start-width: 0; }
/* .print-cells-prefix - the fixed lead-in the customer does not write, e.g.
   the IR of an IBAN. Marked as given, not as an answer. */
.print-cells-prefix {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding-inline-end: 1mm;
  font-family: var(--font-mono);
  font-size: 8pt;
  font-weight: 700;
  color: var(--ink-2);
}

/* .print-table - a repeat group on paper: trading codes, signatories. */
.print-table {
  width: 100%;
  border-collapse: collapse;
  margin-block: 1.5mm 2mm;
  font-size: 7.5pt;
  break-inside: avoid;
  page-break-inside: avoid;
}
.print-table th,
.print-table td {
  border: .25mm solid var(--rule-soft);
  padding: 1mm 1.5mm;
  text-align: center;
  vertical-align: middle;
  overflow-wrap: anywhere;
}
.print-table th {
  background: var(--mint);
  color: var(--navy);
  font-weight: 700;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.print-table td { height: 7mm; }
.print-table tbody tr:nth-child(even) td { background: rgba(230,244,243,.5); }

/* .print-sig - a captured signature inside a table cell or a value plate. */
.print-sig {
  display: block;
  max-height: 10mm;
  max-width: 100%;
  width: auto;
  margin-inline: auto;
  object-fit: contain;
}

/* .print-bullets - the accepted clauses, with the paper's teal marker. */
.print-bullets {
  list-style: none;
  margin: 0 0 3mm;
  padding: 0;
  display: grid;
  gap: 1mm;
  font-size: 7.5pt;
  line-height: 1.55;
}
.print-bullets > li {
  position: relative;
  padding-inline-start: 4mm;
  text-wrap: pretty;
}
.print-bullets > li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: .55em;
  width: 1.2mm; height: 1.2mm;
  border-radius: 50%;
  background: var(--teal);
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

/* Very tight sheets: drop to one answer per line only if the renderer asks
   for it, by putting .print-grid--single on the grid. */
.print-grid--single { grid-template-columns: minmax(0, 1fr); }

@media print {
  /* Nothing in the print grid may be dimmed or hidden by an app state. */
  .print-val, .print-bar, .print-table th, .print-cell {
    print-color-adjust: exact !important;
    -webkit-print-color-adjust: exact !important;
  }

  /* WHERE A SHEET IS ALLOWED TO BREAK.
     A field sheet may now run onto a second page, so every container in it
     has to say whether a page boundary may pass through it. The rule is the
     same one the paper follows: a page may break BETWEEN rows, never through
     one. A half-row at a page edge is a value the reader cannot trust. */

  /* A scroll box is monolithic in print: the printer cannot scroll it, so the
     browser refuses to fragment it and the tail is lost. On paper the wrapper
     has no job, so it stops being a scroll container. */
  .table-scroll { overflow: visible; }

  /* Groups may break between their rows... */
  .print-grid,
  .print-table,
  .print-table tbody,
  .print-bullets {
    break-inside: auto;
    page-break-inside: auto;
  }
  /* ...but a row, a cell and a clause are each atomic. */
  .print-row,
  .print-table tr,
  .print-table th,
  .print-table td,
  .print-bullets > li {
    break-inside: avoid;
    page-break-inside: avoid;
  }
  /* A continued table repeats its column headings, so the second page's
     figures are still labelled. */
  .print-table thead { display: table-header-group; }
  .print-table tfoot { display: table-footer-group; }

  /* A group heading never ends a page on its own, and prose does not leave a
     single line stranded on either side of the break. */
  .print-bar { break-after: avoid; page-break-after: avoid; }
  .print-note, .print-bullets > li { orphans: 2; widows: 2; }

  /* The signature box is a single unit wherever it lands. */
  .sheet__sign { break-inside: avoid; page-break-inside: avoid; }
}


/* =========================================================================
   22. CLASS CONTRACT
   Every class this file defines, grouped by area. This is the list to code
   the HTML against. State classes are listed under the block they attach to.
   ---------------------------------------------------------------------------

   UTILITIES
     .sr-only            visually hidden, still announced
     .skip-link          first child of <body>, an <a href="#main">
     .nums               tabular figures (does not convert to Persian digits,
                         and must NOT be put on a .cells__input)
     .ltr-run            <span> around a Latin or numeric run inside Farsi copy
     [dir="ltr"]         attribute escape hatch, isolates any subtree
     .no-print           removed from the printed output
     .print-only         hidden on screen, shown when printing
     .stack .stack-tight vertical rhythm between block children
     .muted .hint        secondary and metadata copy
     .table-scroll       wrapper that lets a wide table scroll, not break

   APP SHELL
     .appbar             sticky compact header
       .appbar__logo     <img> of /logo.png
       .appbar__spacer   empty flex spacer
     .progress           completion meter, needs role="progressbar" + a label
       .progress__bar    the track
       .progress__fill   the fill, width from --progress
       .progress__text   the count in words, required
       .progress.is-complete
     .savechip           save state, aria-live="polite"
       .savechip__dot    the shape cue
       .savechip__text   the sentence, required
       states: .is-idle .is-saving .is-saved .is-offline .is-error
     .app                the shell grid, rail then column from 900px
     .col                the content column, max 760px, id="main"

   SECTION NAVIGATOR
     .rail               desktop right-hand rail, hidden below 900px
       .rail__list       <ol>
       .rail__item       <a href="#section">
       .rail__num        the sheet number badge
       .rail__label      the section name
       states: .rail__item.is-active (aria-current="step") .is-done .is-error
     .chips              mobile horizontal scroller, hidden from 900px
       .chip             <a href="#section">
       states: .chip.is-active .is-done .is-error

   CARDS
     .card               one form section
       .card__head       title block
       .card__title      the <h2>
       .card__note       supporting line
       .card__body       padded content
     .section-bar        the banded header row, a DIRECT child of .card
       .section-bar--start  set to the start edge instead of centred

   FIELDS
     .rows               a run of ruled field rows, on <fieldset> or <div>
     .row                two or three fields on one line
       .row--2 .row--3   column counts from 640px
     .field              one label plus control
       .field__label     the <label>
       .field__control   wraps the control, may hold a suffix
       .field__unit      the parenthetical unit, e.g. به ریال
       .field__msg       helper or error text, link with aria-describedby
       .req              the required marker, aria-hidden
       states: .field.is-error .field.is-done
     .form-error         section-level error summary, role="alert"
     .input .select .textarea    the mint plate controls
       .input--short .input--num .input.is-latin
     .field__grid        the paper's small tables, on <table>

   CHARACTER CELLS
     .cells              the scroller, set --cells inline (10, 10, 24)
       .cells__input     the single <input> that renders as cells.
                         ASCII digits only, and never .nums on it. Both
                         constraints are measured, see section 08.
       .cells__count     optional live counter
       .cells--latin     fixed-width Latin, for the IBAN
       --cell-adv        escape hatch if you change the typeface

   CHOICES
     .choices            <fieldset> group
       .choices--stack   one option per line
       .choices__legend  the group question
     .choice             the wrapping <label>, the whole hit target
       .choice__input    the real radio or checkbox
       .choice__mark     the drawn circle or box, aria-hidden
       .choice__text     the option text
       .choice--boxed    the paper's banded option rows

   SIGNATURE
     .sigpad             container, data-empty="true|false"
       .sigpad__canvas   the <canvas>
       .sigpad__hint     the prompt, hidden once drawn
       .sigpad__clear    the پاک کردن button

   UPLOAD
     .upload             container
       .upload__drop     the <label for> dropzone wrapping the file input
       .upload__title    the instruction
       .upload__hint     formats and size limit
       .upload__grid     <ul> of thumbnails
       .upload__thumb    <li>, holds the <img>
       .upload__remove   icon-only button, needs aria-label
       states: .upload.is-dragover .upload.is-busy .upload__thumb.is-failed

   BUTTONS
     .btn                base
       .btn--primary .btn--accent .btn--ghost .btn--danger
       .btn--sm .btn--lg size modifiers
       .btn.is-loading   keeps the label, adds a spinner, pair with aria-busy
     .btnbar             sticky action row
       .btnbar__spacer

   NOTICES AND OVERLAYS
     .callout            inline notice, role="status" or role="alert"
       .callout__title   the one sentence that says what happened
       .callout--info .callout--success .callout--warn .callout--danger
     .badge              status pill, the word is the status
       .badge--neutral .badge--info .badge--success .badge--warn
     .bullets            the teal-marker clause list, on <ul>
     .repeat             a repeating row group
       .repeat__row      one row, its own block on phones
       .repeat__num      the row ordinal
       .repeat__remove   per-row remove, needs a row-specific aria-label
       .repeat__add      the add control
     .lightbox           full-screen image viewer, toggle with `hidden`
       .lightbox__img    the enlarged scan
       .lightbox__close  icon-only, needs aria-label
     .toast              transient confirmation, role="status"
       .toast.is-leaving set before removing the node
     .loading            centred placeholder, say what is loading
     .link-box           a generated URL in <code>, wraps rather than overflows

   CONTRACT VIEWER
     .contract           the section, add .is-read when the end is reached
       .contract__bar    page count and fullscreen control
       .contract__count  the page count
       .contract__zoom   the fullscreen button
       .contract__viewer the scroller, request fullscreen on this
       .contract__pages  <ol> of pages
       .contract__page   <li> holding one scan
       .contract__end    the intersection sentinel
       .contract__accept the gated accept row
       .contract__gate   the sentence explaining the gate
       state: .contract.is-read

   ADMIN CONSOLE
     .login              centred sign-in page
       .login__card      the <form>
       .login__logo .login__title
     .toolbar            wrapping row of filter controls
     .list               submissions queue, on <ul>
       .list__row        <li>
       .list__main .list__title .list__meta
       .list__tags       badge cluster
       .list__actions    right-aligned action cluster
       state: .list.is-empty (put a real sentence inside)
     .drawer             detail panel over a scrim, toggle with `hidden`
       .drawer__scrim    the backdrop
       .drawer__panel    full width on mobile, 560px from there
       .drawer__head .drawer__title .drawer__close
       .drawer__body     the panel's own scroll
       .drawer__group    a subheading per sheet
     .kv                 label/value pairs on <dl>, two columns from 480px
     .trail              audit log on <ul>
       .trail__at        the monospace timestamp
     .admin-sig          the captured signature box
       state: .admin-sig.is-missing

   LANDING
     .landing            the public index page
       .landing__logo .landing__title .landing__lead
       .landing__steps   <ol>
       .landing__step    <li>
       .landing__num     the step ordinal
       .landing__note    the closing line

   PRINTED SHEET
     .sheet              one A4 page, always light in both themes
       .sheet--image     a full-bleed scan, no furniture
       .sheet__head      top corner block
       .sheet__logo      the mark
       .sheet__body      the field area, holds the print grid below
       .sheet__spine     the vertical tab strip on the outer edge
       .sheet__tab       one rotated tab, .is-active fills it navy
       .sheet__no        the sheet number, bottom outer corner
       .sheet__sign      the two-cell signature footer
       .sheet__sign-cell customer first, company second
       .sheet__stamp     the acceptance stamp laid over a sheet, bottom outer
                         corner. Absolutely positioned on purpose: in the
                         flow it pushes the sheet past 297mm. Holds one or
                         two <span> lines and an optional signature <img>.
     .sheet-scaler       on-screen preview wrapper, set --sheet-scale

   PRINT GRID (emitted inside .sheet__body)
     .print-note         the intro paragraph
     .print-bar          a group heading, the banded look
     .print-grid         two answers per line
       .print-grid--single  one answer per line
     .print-row          one answer
       .print-row--wide  spans both columns
     .print-label        the navy label
     .print-val          the mint value plate (empty prints a dash)
     .print-check        one choice option, .is-on marks the chosen one
     .print-cells        real per-character boxes, not the screen gradient
       .print-cell       one box
       .print-cells-prefix  a fixed lead-in such as IR
     .print-table        a repeat group on paper
     .print-sig          a small signature image in a cell
     .print-bullets      the accepted clauses

   CUSTOM PROPERTIES YOU SET FROM MARKUP
     --progress          on .progress, e.g. 44%
     --cells             on .cells, the cell count
     --cell-w --cell-h   on .cells, to retune a field
     --cell-adv          on .cells, only if you change the typeface
     --sheet-scale       on .sheet-scaler
     data-theme          on <html>, "light" or "dark", overrides the OS
     data-empty          on .sigpad, "true" until the first stroke
     hidden              on .lightbox and .drawer, the open/closed switch
   ========================================================================= */

/* =========================================================================
   22. OVERLAY SHEETS
   A printed page is the original scan with the customer's answers positioned
   on top of it. The scan carries the layout, so nothing here may change the
   page's size or flow: every value is absolutely placed inside the sheet.
   ------------------------------------------------------------------------- */

/* .sheet--overlay - one page of the official booklet. Exactly A4, never grows,
   never breaks, because the paper it reproduces does not. */
.sheet--overlay {
  position: relative;
  width: 210mm;
  height: 297mm;
  min-height: 0;
  padding: 0;
  margin: 0 auto;
  overflow: hidden;
  background: #fff;
  break-after: page;
  page-break-after: always;
  break-inside: avoid;
  page-break-inside: avoid;
}
.sheet--overlay:last-child { break-after: auto; page-break-after: auto; }

/* .sheet__scan - the untouched page image, filling the sheet exactly. */
.sheet__scan {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: fill;
}

/* .fill - one answer, sitting in the box it belongs to on the form. Text is
   clipped rather than allowed to overflow: an answer that does not fit its box
   must look wrong, not silently print over the neighbouring field. */
.fill {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 0 1.2mm;
  font-family: Vazirmatn, "IRANSans", "Segoe UI", Tahoma, sans-serif;
  font-size: 8.5pt;
  line-height: 1.15;
  color: #24265D;
  white-space: nowrap;
  overflow: hidden;
  direction: rtl;
}
.fill--center { justify-content: center; }

/* .fill--cells - a segmented value, one glyph per printed box. The letter
   spacing is derived from the box width and the cell count, so it stays
   aligned whatever the field's length. */
.fill--cells {
  justify-content: center;
  direction: ltr;
  font-variant-numeric: tabular-nums;
  letter-spacing: calc((100% - (var(--cells) * 1ch)) / var(--cells));
  padding: 0;
}

@media print {
  .sheet--overlay { box-shadow: none; margin: 0; }
  .sheet__scan { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .fill { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
}

/* .fill-mark - the tick on a printed choice circle. Positioned by its centre,
   so a layout entry names the circle, not a corner. */
.fill-mark {
  position: absolute;
  width: 2.1mm;
  height: 2.1mm;
  margin: -1.05mm 0 0 -1.05mm;
  border-radius: 50%;
  background: #24265D;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

/* .fill--wrap - a value in a table cell. The printed rows are tall enough for
   two lines, so a long entry wraps rather than losing its last characters to
   the clip that keeps a single-line value out of its neighbour's box. */
.fill--wrap {
  white-space: normal;
  text-align: center;
  line-height: 1.15;
  font-size: 7.5pt;
  overflow-wrap: anywhere;
}
