@font-face {
  font-family: 'Vazirmatn';
  src: url('/fonts/vazirmatn-regular.woff2') format('woff2');
  font-weight: 400; font-display: swap;
}
@font-face {
  font-family: 'Vazirmatn';
  src: url('/fonts/vazirmatn-bold.woff2') format('woff2');
  font-weight: 700; font-display: swap;
}

/* ------------------------------------------------------------------ *
 * Tokens
 *
 * One breakpoint decides everything: 860px. Below it the app is a
 * touch app — bottom tab bar, sheets instead of dialogs, no hover
 * affordances — and above it a pointer app. JS reads the same number
 * (ui.js MOBILE_Q); keep the two in sync.
 * ------------------------------------------------------------------ */
:root {
  --bg:        #f1f5f9;
  --bg-soft:   #e2e8f0;
  --surface:   #ffffff;
  --surface-2: #f8fafc;
  --surface-3: #f1f5f9;
  --line:      #e2e8f0;
  --line-2:    #cbd5e1;
  --text:      #0f172a;
  --text-2:    #475569;
  --text-3:    #94a3b8;
  --brand:     #2563eb;
  --brand-2:   #1d4ed8;
  --brand-soft:#dbeafe;
  --ok:        #16a34a;
  --warn:      #d97706;
  --danger:    #dc2626;
  --danger-soft:#fee2e2;
  --shadow:    0 1px 2px rgba(15,23,42,.06), 0 8px 24px -12px rgba(15,23,42,.18);
  --shadow-lg: 0 24px 60px -18px rgba(15,23,42,.32);
  --shadow-up: 0 -8px 32px -12px rgba(15,23,42,.22);
  --r:   12px;
  --r-s: 8px;
  --r-l: 18px;
  --r-sheet: 22px;
  --sidebar: 248px;

  /* Touch geometry. --tap is the minimum comfortable target (WCAG 2.5.8
     asks 24px, real thumbs want ~44); --nav-h is the tab bar; --safe-b is
     the iOS home-indicator inset, zero everywhere else. */
  --tap: 44px;
  --nav-h: 60px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
  --topbar-h: 58px;

  --ease: cubic-bezier(.22,.61,.36,1);
}
:root[data-theme="dark"] {
  --bg:        #0b1120;
  --bg-soft:   #131c2e;
  --surface:   #151f33;
  --surface-2: #1b2740;
  --surface-3: #22304d;
  --line:      #263349;
  --line-2:    #35455f;
  --text:      #e8eef8;
  --text-2:    #9fb0c9;
  --text-3:    #6b7d97;
  --brand:     #4f8cff;
  --brand-2:   #6ba0ff;
  --brand-soft:#1c2f52;
  --danger-soft:#3b1d21;
  --shadow:    0 1px 2px rgba(0,0,0,.4), 0 10px 28px -14px rgba(0,0,0,.7);
  --shadow-lg: 0 28px 70px -20px rgba(0,0,0,.8);
  --shadow-up: 0 -10px 34px -14px rgba(0,0,0,.75);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
html {
  -webkit-text-size-adjust: 100%;
  /* Reserve the scrollbar's width permanently: opening a dialog locks page
     scrolling, and without this the whole layout jumps sideways as the bar
     disappears. */
  scrollbar-gutter: stable;
}
body {
  margin: 0;
  font-family: 'Vazirmatn', system-ui, 'Segoe UI', sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  /* A tap must never be mistaken for a text selection or a double-tap zoom:
     both make a touch UI feel broken before anything has even happened. */
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  overscroll-behavior-y: none;
}
/* Set while a sheet is open — the page behind must not scroll under it. */
html.sheet-open, html.sheet-open body { overflow: hidden; }

a { color: var(--brand); text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
img, video, canvas, iframe { max-width: 100%; }

/* Rings only for keyboard users: a permanent ring on every tap is noise,
   no ring at all locks keyboard users out of knowing where they are. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 4px;
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 8px; border: 3px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }

/* Horizontal strips (tabs, filters) scroll but must not show a bar —
   the fading edge and the snap are the affordance. */
.hscroll {
  display: flex; gap: 8px; overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity; overscroll-behavior-x: contain;
}
.hscroll::-webkit-scrollbar { display: none; }
.hscroll > * { scroll-snap-align: start; flex: none; }

/* ------------------------------------------------------------------ *
 * Boot / login
 * ------------------------------------------------------------------ */
.boot {
  position: fixed; inset: 0; display: grid; place-content: center; gap: 18px;
  justify-items: center; background: var(--bg); z-index: 100;
}
.boot.hide { display: none; }
.boot-logo { font-weight: 700; font-size: 22px; letter-spacing: .5px; color: var(--brand); }
.spinner {
  width: 26px; height: 26px; border: 3px solid var(--line-2);
  border-top-color: var(--brand); border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.login-wrap {
  min-height: 100dvh; display: grid; place-items: center;
  padding: calc(24px + var(--safe-t)) 20px calc(24px + var(--safe-b));
  background:
    radial-gradient(900px 500px at 80% -10%, color-mix(in srgb, var(--brand) 22%, transparent), transparent),
    radial-gradient(700px 400px at 10% 110%, color-mix(in srgb, var(--brand) 14%, transparent), transparent),
    var(--bg);
}
.login-card {
  width: min(400px, 100%); background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-l); padding: 32px 28px; box-shadow: var(--shadow-lg);
}
.login-card h1 { margin: 0 0 4px; font-size: 24px; }
.login-card .sub { color: var(--text-2); margin: 0 0 24px; font-size: 13px; }
.login-mark {
  width: 46px; height: 46px; border-radius: 13px; background: var(--brand); color: #fff;
  display: grid; place-items: center; font-weight: 700; font-size: 20px; margin-bottom: 16px;
}

/* ------------------------------------------------------------------ *
 * App shell
 * ------------------------------------------------------------------ */
/* The sidebar is the first grid column. The document is RTL, so column 1
   lands on the right — which is where a Persian-language sidebar belongs.
   Do not reach for grid-template-areas here: area strings are read in the
   inline direction too, which silently mirrors the whole layout. */
.shell { display: grid; grid-template-columns: var(--sidebar) 1fr; min-height: 100dvh; }

.sidebar {
  background: var(--surface); border-inline-start: 1px solid var(--line);
  display: flex; flex-direction: column; position: sticky; top: 0; height: 100dvh;
}
.main { min-width: 0; }

.brand { display: flex; align-items: center; gap: 10px; padding: 18px 16px 14px; }
.brand-mark {
  width: 34px; height: 34px; border-radius: 10px; background: var(--brand); color: #fff;
  display: grid; place-items: center; font-weight: 700; flex: none;
}
.brand-txt strong { display: block; font-size: 15px; line-height: 1.3; }
.brand-txt span { font-size: 11px; color: var(--text-3); }

.nav { flex: 1; overflow: auto; padding: 4px 10px 16px; -webkit-overflow-scrolling: touch; }
.nav-label {
  font-size: 10.5px; color: var(--text-3); letter-spacing: .8px; font-weight: 700;
  padding: 14px 8px 6px; text-transform: uppercase;
}
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: var(--r-s);
  color: var(--text-2); cursor: pointer; margin-bottom: 2px; border: 0; background: none;
  width: 100%; text-align: start; font-size: 13.5px; position: relative;
  transition: background .12s, color .12s;
}
.nav-item.active { background: var(--brand-soft); color: var(--brand); font-weight: 700; }
/* A left accent on the active row: colour alone is a weak signal for the
   ~8% of men who cannot separate these blues from grey. */
.nav-item.active::before {
  content: ''; position: absolute; inset-inline-start: 0; top: 20%; bottom: 20%;
  width: 3px; border-radius: 3px; background: var(--brand);
}
.nav-item .ico { width: 20px; text-align: center; flex: none; font-size: 15px; }
.nav-item .lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-item .count {
  margin-inline-start: auto; font-size: 11px; color: var(--text-3);
  background: var(--surface-2); padding: 1px 7px; border-radius: 20px; flex: none;
}
.nav-item.active .count { background: rgba(255,255,255,.6); color: var(--brand); }
:root[data-theme="dark"] .nav-item.active .count { background: rgba(0,0,0,.3); }

.side-foot { border-top: 1px solid var(--line); padding: 10px; }
.user-chip {
  display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: var(--r-s);
  cursor: pointer; width: 100%; border: 0; background: none; text-align: start;
}
.avatar {
  width: 32px; height: 32px; border-radius: 50%; flex: none; display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand); font-weight: 700; font-size: 13px;
}
.user-chip .nm { font-size: 13px; font-weight: 700; line-height: 1.3; }
.user-chip .rl { font-size: 11px; color: var(--text-3); }

/* topbar */
.topbar {
  position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 12px;
  padding: 12px 22px; background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
.crumbs { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-2); min-width: 0; }
.crumbs b { color: var(--text); font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crumbs a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crumbs .sep { color: var(--text-3); flex: none; }
.topbar-search { margin-inline-start: auto; position: relative; }
.topbar-search input {
  width: 260px; padding: 8px 34px 8px 12px; border-radius: 20px; border: 1px solid var(--line);
  background: var(--surface); outline: none;
}
.topbar-search input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }
.topbar-search .mag { position: absolute; inset-inline-end: 12px; top: 50%; transform: translateY(-50%); color: var(--text-3); pointer-events: none; }

.page { padding: 22px; max-width: 1500px; }
.page-head { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; margin-bottom: 18px; }
.page-head h1 { margin: 0; font-size: 22px; line-height: 1.45; }
.page-head .desc { color: var(--text-2); font-size: 13px; }
.page-head .spacer { flex: 1; }
.page-head .actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* Two-column reading layouts. Declared once here instead of inline in every
   view, so the collapse point is a single line to change. */
.split { display: grid; grid-template-columns: minmax(0,2fr) minmax(0,1fr); gap: 16px; align-items: start; }
.split > .col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.auto-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px,1fr)); gap: 16px; align-items: start; }
.card-cols { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px,1fr)); gap: 14px; }

/* Embedded sub-application: it gets the rest of the viewport, minus the
   topbar and this page's own header. Full width — an app is not a document. */
.app-page { max-width: none; }
.app-frame {
  /* Explicit height, not flex:1 — the page host is content-sized, so a flex
     child would collapse to its minimum instead of filling the viewport. */
  width: 100%; min-height: 520px; height: calc(100dvh - 168px);
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface); box-shadow: var(--shadow); display: block;
}

/* ------------------------------------------------------------------ *
 * Primitives
 * ------------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 8px 14px; border-radius: var(--r-s); border: 1px solid var(--line-2);
  background: var(--surface); cursor: pointer; font-size: 13px; font-weight: 500;
  transition: background .12s, border-color .12s, transform .06s; white-space: nowrap;
  min-height: 36px;
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn.primary { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 700; }
.btn.danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, var(--line-2)); }
.btn.ghost { border-color: transparent; background: transparent; }
.btn.sm { padding: 5px 10px; font-size: 12px; min-height: 30px; }
.btn.block { width: 100%; }
/* Square icon button — used where a label would only add noise. */
.btn.icon { padding: 0; width: 38px; min-width: 38px; height: 38px; font-size: 16px; }

.field { margin-bottom: 14px; }
.field > label { display: block; font-size: 12.5px; color: var(--text-2); margin-bottom: 5px; font-weight: 500; }
.input, .select, textarea.input {
  width: 100%; padding: 9px 12px; border-radius: var(--r-s); border: 1px solid var(--line-2);
  background: var(--surface); outline: none; transition: border-color .12s, box-shadow .12s;
  min-height: 38px;
}
.input:focus, .select:focus, textarea.input:focus {
  border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); outline: none;
}
textarea.input { resize: vertical; min-height: 78px; line-height: 1.8; }
.select { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--text-3) 50%), linear-gradient(135deg, var(--text-3) 50%, transparent 50%); background-position: left 14px top 55%, left 9px top 55%; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-inline-start: 30px; }
input[type="file"].input { padding: 7px 10px; line-height: 1.6; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.row3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.hint { font-size: 11.5px; color: var(--text-3); margin-top: 4px; }

.card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow);
}
.card-pad { padding: 18px; }
.card-head {
  display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid var(--line);
}
.card-head h2 { margin: 0; font-size: 15px; }
.card-head .spacer { flex: 1; }

.chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 12px; border-radius: 20px;
  font-size: 12px; background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text-2); cursor: pointer; white-space: nowrap;
}
.chip.on { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 700; }
.chips { display: flex; gap: 7px; flex-wrap: wrap; }

.badge {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 20px;
  font-size: 11px; font-weight: 700; line-height: 1.7;
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

.empty { text-align: center; padding: 54px 20px; color: var(--text-3); }
.empty .em { font-size: 40px; display: block; margin-bottom: 10px; opacity: .55; }
.empty h3 { margin: 0 0 6px; color: var(--text-2); font-size: 15px; }

.muted { color: var(--text-3); }
.tnum { font-variant-numeric: tabular-nums; }
/* For raw filenames. They arrive as one unbreakable token often enough
   (`1786947458925250182958184434910.jpg` straight off a camera) that any
   layout printing one without this ends up with a sideways scroll. */
.brk { overflow-wrap: anywhere; }

/* ------------------------------------------------------------------ *
 * Stats
 * ------------------------------------------------------------------ */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; }
.stat {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
  padding: 16px 18px; box-shadow: var(--shadow);
}
.stat .k { font-size: 12px; color: var(--text-2); display: flex; align-items: center; gap: 6px; }
.stat .v { font-size: 26px; font-weight: 700; line-height: 1.4; font-variant-numeric: tabular-nums; }
.stat .s { font-size: 11.5px; color: var(--text-3); }
/* A stat tile that is a link. `display:block` because <a> is inline and would
   otherwise collapse the padding the tile is built from. */
.stat-link { display: block; text-decoration: none; color: inherit; }
.stat-urgent { border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }
.stat-urgent .v { color: var(--danger); }

.dept-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; }
.dept-tile {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--line); cursor: pointer; text-align: start;
  box-shadow: var(--shadow); transition: transform .1s, box-shadow .12s;
  min-width: 0;
}
.dept-tile .ic {
  width: 40px; height: 40px; border-radius: 11px; display: grid; place-items: center;
  font-size: 19px; flex: none;
}
.dept-tile > div:last-child { min-width: 0; }
.dept-tile .nm { font-weight: 700; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dept-tile .ct { font-size: 11.5px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ------------------------------------------------------------------ *
 * Tables
 * ------------------------------------------------------------------ */
.tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
table.tbl th {
  text-align: start; font-size: 11.5px; color: var(--text-3); font-weight: 700;
  padding: 9px 12px; border-bottom: 1px solid var(--line); white-space: nowrap;
}
table.tbl td { padding: 10px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
table.tbl tbody tr:last-child td { border-bottom: 0; }
table.tbl tr.clickable { cursor: pointer; }

/* Column priority: a table cannot be made narrow, so on a phone the
   low-value columns leave rather than forcing a sideways scroll through the
   ones that matter. `.col-lo` goes at 860px, `.col-mid` at 400px; both come
   back for print. Marked on the th and every matching td.
   See the phone block below for where they are actually hidden. */

/* ------------------------------------------------------------------ *
 * File browser
 * ------------------------------------------------------------------ */
.toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px;
}
.toolbar .grow { flex: 2 1 240px; min-width: 200px; }
/* .select/.input default to width:100%, which makes every control claim a whole
   row inside the flex toolbar — pin them back to their content width here. */
.toolbar > .select, .toolbar > .input { width: auto; }
.toolbar > .select { min-width: 140px; }
.seg { display: inline-flex; border: 1px solid var(--line-2); border-radius: var(--r-s); overflow: hidden; flex: none; }
.seg button { border: 0; background: var(--surface); padding: 7px 12px; cursor: pointer; font-size: 12px; color: var(--text-2); min-height: 36px; }
.seg button.on { background: var(--brand); color: #fff; font-weight: 700; }

.dropzone {
  border: 2px dashed var(--line-2); border-radius: var(--r); padding: 20px; text-align: center;
  color: var(--text-3); background: var(--surface-2); margin-bottom: 14px; cursor: pointer;
  transition: border-color .12s, background .12s;
}
.dropzone.over { border-color: var(--brand); background: var(--brand-soft); color: var(--brand); }
.dropzone strong { display: block; color: var(--text); font-size: 14px; margin-bottom: 2px; }
.dropzone.over strong { color: var(--brand); }

.file-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 12px; }
.file-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
  padding: 14px; cursor: pointer; box-shadow: var(--shadow); position: relative;
  transition: transform .1s, box-shadow .12s, border-color .12s;
  display: flex; flex-direction: column; gap: 8px; min-height: 132px;
}
.file-card:active { transform: scale(.985); }
.file-top { display: flex; align-items: flex-start; gap: 10px; }
.file-ico {
  width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  font-size: 17px; flex: none; font-weight: 700;
}
.file-name {
  font-weight: 700; font-size: 13.5px; line-height: 1.55; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; word-break: break-word;
}
/* The raw filename under the title.
   `overflow-wrap: anywhere` is the load-bearing line: uploads arrive named
   `طرح-پایش-تصویری-بافت-تاریخی-یزد.json` or `178694745892525018.jpg`, both of
   which are a single unbreakable token as far as the line breaker is concerned.
   Without it the token is laid out at its full width and spills straight out of
   the card — which is exactly what it did. The clamp keeps a 40-character name
   from turning one card into a paragraph. */
.file-sub {
  font-size: 11px; color: var(--text-3); line-height: 1.6;
  overflow-wrap: anywhere; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.file-meta { font-size: 11.5px; color: var(--text-3); display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: auto; }
.file-tag {
  font-size: 10.5px; padding: 1px 8px; border-radius: 20px; background: var(--surface-2);
  border: 1px solid var(--line); color: var(--text-2);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 0 1 auto; min-width: 0;
}
/* Which project a file belongs to is the one thing a department listing must
   answer at a glance, so it is a filled chip rather than another grey one. */
.file-tag.proj {
  background: var(--brand-soft); border-color: transparent;
  color: color-mix(in srgb, var(--brand) 82%, var(--text)); font-weight: 700;
}
.file-tag.none { opacity: .75; font-style: italic; }
.file-flag { position: absolute; top: 10px; inset-inline-end: 10px; font-size: 13px; }

.file-row { display: flex; align-items: center; gap: 12px; min-width: 0; }
.file-row .file-ico { width: 32px; height: 32px; font-size: 15px; border-radius: 9px; }
/* Uploaded names are often one long unbroken token (a camera's
   `1786947458925250182958184434910.jpg`). Without this the table cell cannot
   shrink and drags the whole list into a sideways scroll on a phone. */
.file-row > div > div { overflow-wrap: anywhere; }

/* ------------------------------------------------------------------ *
 * Modals, sheets & drawer
 * ------------------------------------------------------------------ */
.backdrop {
  /* Above the drawer (61): a viewer opened from inside a drawer must cover it. */
  position: fixed; inset: 0; background: rgba(15,23,42,.5);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  z-index: 80; display: grid; place-items: center; padding: 20px; animation: fade .14s ease;
}
@keyframes fade { from { opacity: 0; } }
/* Flex column with an overflowing body, rather than one scrolling box with
   sticky children: the header and footer are then simply always there, and
   a mobile sheet gets its bottom action bar for free. */
.modal {
  width: min(560px, 100%); max-height: 88dvh; overflow: hidden; background: var(--surface);
  border-radius: 16px; box-shadow: var(--shadow-lg); border: 1px solid var(--line);
  animation: pop .16s ease; display: flex; flex-direction: column;
}
.modal.wide { width: min(900px, 100%); }
.modal.full { width: min(1400px, 100%); height: 92dvh; }
@keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.985); } }
.modal-head {
  display: flex; align-items: center; gap: 10px; padding: 16px 20px;
  border-bottom: 1px solid var(--line); background: var(--surface); z-index: 2; flex: none;
}
.modal-head h2 { margin: 0; font-size: 16px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.modal-body { padding: 20px; overflow: auto; min-height: 0; -webkit-overflow-scrolling: touch; }
.modal-foot {
  display: flex; gap: 10px; padding: 14px 20px; border-top: 1px solid var(--line);
  background: var(--surface); flex: none; flex-wrap: wrap;
}
.modal-foot .spacer { flex: 1; }
.x-btn {
  border: 0; background: none; cursor: pointer; color: var(--text-3); font-size: 20px;
  line-height: 1; padding: 4px 8px; border-radius: 6px;
}

/* The grab handle. Present in the DOM on every sheet-capable surface and
   only shown at phone width, where it is both the drag target and the
   signal that this thing can be flung away. */
.grab { display: none; }

.drawer {
  position: fixed; top: 0; bottom: 0; inset-inline-start: 0; width: min(520px, 100%);
  background: var(--surface); z-index: 61; box-shadow: var(--shadow-lg); border-inline-end: 1px solid var(--line);
  display: flex; flex-direction: column; animation: slide .18s var(--ease);
}
/* The drawer sits at the inline-start edge, which is the right in this RTL
   document — so it must slide in from the right, not the left. */
@keyframes slide { from { transform: translateX(100%); } }
.drawer-body { flex: 1; overflow: auto; padding: 16px 20px 20px; min-height: 0; -webkit-overflow-scrolling: touch; }
.drawer-head {
  flex: none; display: flex; flex-direction: column;
  padding: 16px 20px 12px; border-bottom: 1px solid var(--line);
}
/* Docked action bar. Only built on phones (see openDoc), where the top of a
   tall sheet is out of thumb reach; it scrolls sideways because a document
   can offer seven verbs and they must not wrap into three rows. */
.drawer-foot {
  flex: none; display: flex; gap: 8px; align-items: center;
  /* No safe-area inset here: the sheet itself reserves it (.drawer below),
     and adding it twice leaves a visible gap above the home indicator. */
  padding: 10px 16px; border-top: 1px solid var(--line);
  background: var(--surface); overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.drawer-foot::-webkit-scrollbar { display: none; }
.drawer-foot .doc-actions { flex-wrap: nowrap; margin: 0; }
.drawer-foot .doc-actions > * { flex: none; }

.doc-actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 16px 0; }
.list-head { display: flex; align-items: center; gap: 10px; min-height: 20px; margin-bottom: 10px; }

.viewer-frame { flex: 1; width: 100%; border: 0; background: #fff; }
.viewer-img { flex: 1; object-fit: contain; background: var(--bg-soft); min-height: 0; width: 100%; }

/* version list */
.ver {
  display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--line);
}
.ver:last-child { border-bottom: 0; }
.ver .no {
  width: 30px; height: 30px; border-radius: 8px; background: var(--surface-2); border: 1px solid var(--line);
  display: grid; place-items: center; font-size: 11.5px; font-weight: 700; flex: none;
}
.ver.current .no { background: var(--brand); border-color: var(--brand); color: #fff; }
/* A version whose bytes retention released. Dimmed rather than hidden: the
   history is meant to still show that it existed. */
.ver.pruned { opacity: .6; }
.ver.pruned .no { background: var(--surface-3); border-style: dashed; }

/* ------------------------------------------------------------------ *
 * File manager
 *
 * The advanced screen behind "همه‌ی فایل‌ها": a filter panel, optional
 * grouping, and a selection bar for acting on many files at once.
 * ------------------------------------------------------------------ */
.fm-filters {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px;
  padding: 14px 16px;
}
.fm-filters .field { margin: 0; }
.fm-filters .field > label { font-size: 11.5px; margin-bottom: 3px; }

/* Sticky, because a selection made at the bottom of 400 rows must not need a
   scroll back to the top to be acted on. */
.fm-selbar {
  position: sticky; top: calc(var(--topbar-h) + 6px); z-index: 16;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 12px; border-radius: var(--r);
  background: var(--brand-soft); border: 1px solid color-mix(in srgb, var(--brand) 34%, transparent);
  box-shadow: var(--shadow);
}
.fm-selbar .n { font-weight: 700; color: var(--brand); }
.fm-selbar .grow { flex: 1; }

.fm-group { margin-bottom: 20px; }
.fm-group > h3 {
  display: flex; align-items: center; gap: 8px; margin: 0 0 10px;
  font-size: 14px; padding-bottom: 7px; border-bottom: 2px solid var(--line);
}
.fm-group > h3 .c {
  font-size: 11px; font-weight: 400; color: var(--text-3);
  background: var(--surface-2); padding: 1px 9px; border-radius: 20px;
}
.fm-group > h3 .grow { flex: 1; }

/* Checkbox cell — wide enough to hit, and it must not trigger the row's own
   click handler (see the stopPropagation in filemanager.js). */
.pick { width: 34px; text-align: center; }
.pick input { width: 17px; height: 17px; accent-color: var(--brand); cursor: pointer; }
tr.picked > td { background: var(--brand-soft) !important; }
.file-card.picked { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand-soft), var(--shadow); }

/* A labelled proportion bar, used for per-department storage. */
.meter { display: flex; flex-direction: column; gap: 4px; }
.meter .lbl { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }
.meter .lbl .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.meter .track { height: 7px; border-radius: 20px; background: var(--surface-3); overflow: hidden; }
.meter .track > i { display: block; height: 100%; border-radius: 20px; }
/* The saved-by-compression slice, drawn over the same track. */
.meter .track > i.saved { background: var(--ok); opacity: .55; }

/* Two numbers side by side, for "قبل / بعد" style readouts. */
.pair { display: flex; gap: 18px; flex-wrap: wrap; }
.pair > div { min-width: 0; }
.pair .k { font-size: 11.5px; color: var(--text-3); }
.pair .v { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.5; }

/* timeline */
.tl { position: relative; padding-inline-start: 20px; }
.tl::before { content: ''; position: absolute; inset-inline-start: 5px; top: 6px; bottom: 6px; width: 2px; background: var(--line); }
.tl-item { position: relative; padding: 7px 0; font-size: 12.5px; }
.tl-item::before {
  content: ''; position: absolute; inset-inline-start: -19px; top: 14px; width: 9px; height: 9px;
  border-radius: 50%; background: var(--surface); border: 2px solid var(--line-2);
}
.tl-item.hot::before { border-color: var(--brand); background: var(--brand); }

/* toasts */
.toasts {
  position: fixed; bottom: calc(18px + var(--safe-b)); inset-inline-start: 18px; z-index: 90;
  display: flex; flex-direction: column; gap: 8px; pointer-events: none;
}
.toast {
  background: var(--surface); border: 1px solid var(--line); border-inline-start: 4px solid var(--brand);
  border-radius: var(--r-s); padding: 11px 15px; box-shadow: var(--shadow-lg); font-size: 13px;
  max-width: 380px; animation: pop .16s ease; pointer-events: auto;
}
.toast.err { border-inline-start-color: var(--danger); }
.toast.ok { border-inline-start-color: var(--ok); }

/* progress */
.bar { height: 6px; background: var(--surface-2); border-radius: 20px; overflow: hidden; }
.bar > i { display: block; height: 100%; background: var(--brand); border-radius: 20px; transition: width .15s; }

.warn-banner {
  background: color-mix(in srgb, var(--warn) 15%, var(--surface)); border: 1px solid color-mix(in srgb, var(--warn) 40%, var(--line));
  color: color-mix(in srgb, var(--warn) 75%, var(--text)); padding: 10px 16px; border-radius: var(--r-s);
  font-size: 12.5px; margin-bottom: 16px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}

/* ------------------------------------------------------------------ *
 * Service form
 *
 * Built for a phone held in one hand on a roof. Every grid here is
 * minmax(0, …) rather than a bare 1fr: a bare 1fr floors the track at its
 * content's min-content width, and one Persian label with a long unbroken
 * device name in it silently pushes the column past the screen edge.
 * ------------------------------------------------------------------ */
.sv-page { padding-bottom: 96px; }
.sv-page > .card { max-width: 720px; margin-inline: auto; }
.sv-sec { margin-bottom: 14px; }
.sv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 12px; }

/* ---- wizard chrome ------------------------------------------------ *
 * One question per screen. The progress strip is the only thing that shows
 * how much is left, so it has to stay honest: the dots are the real step
 * list, which grows when the technician opts into an optional section.
 * ------------------------------------------------------------------- */
.sv-steps { max-width: 720px; margin: 0 auto 12px; }
.sv-progress { height: 4px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.sv-progress i { display: block; height: 100%; background: var(--brand); border-radius: 3px; transition: width .25s var(--ease); }
.sv-dots { display: flex; gap: 6px; margin-top: 10px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.sv-dots::-webkit-scrollbar { display: none; }
.sv-dot {
  position: relative; flex: none; width: 36px; height: 36px; border-radius: 50%;
  cursor: pointer; font-size: 15px; padding: 0;
  background: var(--surface); border: 1px solid var(--line); color: var(--text-2);
  display: grid; place-items: center;
}
.sv-dot.done { background: var(--brand-soft); border-color: var(--brand-soft); }
.sv-dot.on { background: var(--brand); border-color: var(--brand); color: #fff; transform: scale(1.08); }
/* The label and the tick are always in the DOM; the rail on the wide tier
   shows them, the circle here does not have room for either. */
.sv-dot .lb, .sv-dot .mk { display: none; }
/* A required step that is still empty is flagged where the user is looking,
   not only on the last screen — a strip that stays silent until "ثبت نهایی"
   is a strip that walks somebody into a refusal. */
.sv-dot.req::after {
  content: ''; position: absolute; top: 1px; inset-inline-end: 1px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--warn); border: 2px solid var(--surface);
}
.sv-dot.on.req::after { border-color: var(--brand); }

/* The wizard is one column on a phone and rail + card on a desktop. */
.sv-wrap { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; }
.sv-main { min-width: 0; }

.sv-step { max-width: 720px; margin: 0 auto; }
.sv-stephead { padding: 16px 18px 4px; }
.sv-stephead h2 { margin: 0; font-size: 19px; display: flex; align-items: center; gap: 9px; }
.sv-stephead h2 .ic { font-size: 22px; }
.sv-stepno { font-size: 11.5px; color: var(--text-3); margin-bottom: 4px; }
.sv-req { font-size: 11.5px; color: var(--warn); margin-top: 4px; }
.sv-stepbody { padding: 12px 18px 20px; }
.sv-stepbody .field { margin-bottom: 14px; }

.sv-nav {
  position: sticky; bottom: 0; z-index: 20; display: flex; align-items: center; gap: 10px;
  max-width: 720px; margin: 12px auto 0; padding: 12px 0;
  background: linear-gradient(to top, var(--bg) 68%, transparent);
}
.sv-nav .btn { min-height: var(--tap); }
.sv-next { min-width: 120px; justify-content: center; }
/* Naming what is missing and going there are the same button. */
.sv-block {
  font: inherit; font-size: 11.5px; color: var(--warn); text-align: end; max-width: 190px;
  background: none; border: 0; padding: 0; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}

/* ---- the review checklist ---- */
.sv-check { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; }
.sv-checkrow {
  display: flex; align-items: center; gap: 11px; width: 100%; min-height: 56px;
  padding: 11px 13px; border-radius: var(--r-s); font: inherit; text-align: start;
  cursor: pointer; color: var(--text);
  border: 1px solid color-mix(in srgb, var(--warn) 42%, var(--line));
  background: color-mix(in srgb, var(--warn) 8%, transparent);
}
.sv-checkrow.ok {
  border-color: color-mix(in srgb, var(--ok) 34%, var(--line));
  background: color-mix(in srgb, var(--ok) 7%, transparent);
}
.sv-checkrow .mk {
  flex: none; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  background: var(--warn); color: #fff; font-weight: 700; font-size: 14px;
}
.sv-checkrow.ok .mk { background: var(--ok); }
.sv-checkrow .tx { flex: 1; min-width: 0; }
.sv-checkrow .tx b { display: block; font-size: 13.5px; }
.sv-checkrow .ds { font-size: 11.5px; color: var(--text-3); overflow-wrap: anywhere; }
.sv-checkrow .go { flex: none; font-size: 12px; color: var(--brand); }

/* The "what else did this visit have" chooser. */
.sv-optgrid {
  display: grid; gap: 9px;
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
}
.sv-opt {
  display: flex; align-items: center; gap: 12px; text-align: start; cursor: pointer;
  padding: 12px 14px; border-radius: var(--r); background: var(--surface-2);
  border: 1px solid var(--line); color: inherit; min-height: 62px;
}
.sv-opt.on { border-color: var(--brand); background: var(--brand-soft); }
.sv-opt .ic { font-size: 21px; flex: none; }
.sv-opt .tx { flex: 1; min-width: 0; }
.sv-opt .tx b { display: block; font-size: 13.5px; }
.sv-opt .ds { font-size: 11.5px; color: var(--text-3); overflow-wrap: anywhere; }
.sv-opt.on .ds { color: var(--brand); }
.sv-opt .mark { flex: none; font-size: 17px; color: var(--text-3); }
.sv-opt.on .mark { color: var(--brand); }

/* Visit kind, as cards rather than a select — four options that are the
   first thing asked deserve to be visible, not hidden behind a tap. */
.sv-kinds { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); gap: 8px; }
.sv-kind {
  display: flex; align-items: center; gap: 9px; padding: 11px 13px; cursor: pointer;
  border-radius: var(--r-s); background: var(--surface-2); border: 1px solid var(--line);
  color: inherit; font-size: 13px; min-height: var(--tap); text-align: start;
}
.sv-kind.on { border-color: var(--brand); background: var(--brand-soft); color: var(--brand); }
.sv-kind .ic { font-size: 17px; }

/* Read-mode summary tiles. */
.sv-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: 10px; margin-bottom: 14px; }
.sv-tile { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
  padding: 13px 15px; box-shadow: var(--shadow); }
.sv-tile .k { font-size: 11.5px; color: var(--text-2); }
.sv-tile .v { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.6; }
.sv-note { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 8px; font-size: 13px; }
.sv-foot { font-size: 11.5px; color: var(--text-3); text-align: center; padding: 10px 0 4px; }
.sv-filters { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: 10px; }
.sv-filters .field { margin: 0; }
.sv-filters .field > label { font-size: 11.5px; margin-bottom: 3px; }

.sv-save { font-size: 12px; color: var(--text-3); min-width: 92px; text-align: end; }
/* In the nav bar it is one line among five, so it shrinks instead of pushing
   "بعدی" off a 360px screen. */
.sv-nav .sv-save {
  min-width: 0; text-align: start; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.sv-save.ok { color: var(--ok); }
.sv-save.err { color: var(--danger); }

/* The two buttons that replaced "ساعت ورود ……" on the paper form. Deliberately
   oversized: this is the control the whole hours report rests on. */
.sv-big { min-height: 56px; font-size: 16px; font-weight: 700; justify-content: center; }
.sv-running { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-2); }
.sv-running .dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--ok); flex: none;
  animation: sv-pulse 1.8s var(--ease) infinite;
}
@keyframes sv-pulse { 50% { opacity: .25; } }
.sv-timer {
  font-size: 30px; font-weight: 700; text-align: center; padding: 10px 0 14px;
  font-variant-numeric: tabular-nums;
}
.sv-done { display: grid; gap: 4px; font-size: 13px; color: var(--text-2); padding-bottom: 6px; }
.sv-done .sv-total { font-size: 15px; color: var(--text); padding-top: 4px; }
.sv-manual { margin-top: 12px; font-size: 13px; }
.sv-manual summary { cursor: pointer; color: var(--brand); padding: 6px 0; }
.sv-manual > .sv-grid { padding-top: 8px; }

.sv-yesno-wrap { margin-bottom: 14px; }
.sv-yesno-lb { font-size: 12.5px; color: var(--text-2); margin-bottom: 6px; }
.sv-yesno, .sv-rate { display: flex; gap: 6px; }
.sv-yesno .btn, .sv-rate .btn { flex: 1; justify-content: center; min-height: var(--tap); }

.sv-chips { gap: 7px; }
.sv-chips .chip .x { opacity: .6; margin-inline-start: 5px; }
.sv-chips .sv-more { border-style: dashed; color: var(--brand); }
.sv-picklist { max-height: 52vh; overflow: auto; margin-top: 10px; display: grid; gap: 4px; }
.sv-pick {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: start;
  min-height: var(--tap); padding: 8px 12px; border-radius: var(--r-s);
  background: var(--surface-2); border: 1px solid var(--line); cursor: pointer; color: inherit;
}
.sv-pick.on { background: var(--brand-soft); border-color: var(--brand); color: var(--brand); }
.sv-pick.new { border-style: dashed; color: var(--brand); }
.sv-pick .tick { flex: none; width: 16px; opacity: .8; }

/* One replaced part: a plain stacked block with its own header row. The
   earlier side-by-side version needed a negative margin to keep the delete
   button in place on a phone, which is always the sign of a layout fighting
   itself. */
.sv-part {
  padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--r);
  margin-bottom: 10px; background: var(--surface-2);
}
.sv-part-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.sv-part-no {
  width: 26px; height: 26px; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface); border: 1px solid var(--line); font-size: 12px; font-weight: 700;
}
.sv-part .field:last-child { margin-bottom: 0; }

.sv-thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 10px; margin-top: 12px; }
.sv-thumb { position: relative; border: 1px solid var(--line); border-radius: var(--r-s); overflow: hidden; background: var(--surface-2); }
.sv-thumb img { display: block; width: 100%; height: 88px; object-fit: cover; }
.sv-thumb-ico { height: 88px; display: grid; place-items: center; font-size: 30px; }
.sv-thumb-nm { font-size: 10.5px; padding: 4px 6px; color: var(--text-2); line-height: 1.5; }
.sv-thumb-sz { font-size: 10px; padding: 0 6px 5px; color: var(--text-3); }
.sv-thumb .x-btn { position: absolute; inset-block-start: 2px; inset-inline-end: 2px; background: var(--surface); border-radius: 50%; }

/* The warranty verdict on the service form. Green is money nobody owes and
   red is money somebody does, so it is a banner rather than a chip. */
.sv-warranty {
  margin-top: 10px; padding: 10px 13px; border-radius: var(--r-s); font-size: 13px;
  font-weight: 700; background: color-mix(in srgb, #16a34a 14%, transparent); color: #16a34a;
}
.sv-warranty.out { background: var(--danger-soft); color: var(--danger); }

.sv-signs { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 14px; margin-top: 10px; }
.sv-sign, .sv-sign-view { border: 1px solid var(--line); border-radius: var(--r); padding: 12px; background: var(--surface-2); }
.sv-sign-lb { font-size: 12.5px; color: var(--text-2); margin-bottom: 8px; }
.sv-sign-img { display: block; width: 100%; max-height: 130px; object-fit: contain; background: #fff; border-radius: var(--r-s); }
.sv-sign-blank { height: 70px; display: grid; place-items: center; color: var(--text-3); border: 1px dashed var(--line-2); border-radius: var(--r-s); }
/* touch-action:none, or the first stroke scrolls the sheet instead of drawing. */
.sv-canvas {
  display: block; width: 100%; height: auto; aspect-ratio: 640 / 260; margin-top: 10px;
  background: #fff; border: 1px solid var(--line-2); border-radius: var(--r-s);
  touch-action: none; cursor: crosshair;
}

.sv-row { cursor: pointer; }
.sv-card .file-meta { flex-wrap: wrap; }

.sv-rank { display: flex; align-items: center; gap: 10px; padding: 5px 0; }
.sv-rank-lb { flex: 1; min-width: 0; font-size: 12.5px; }
.sv-rank-bar { width: 34%; flex: none; height: 7px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.sv-rank-bar i { display: block; height: 100%; background: var(--brand); border-radius: 4px; }
.sv-rank-n { width: 34px; flex: none; text-align: end; font-size: 12px; color: var(--text-2); }

.sv-sheet .card { margin-bottom: 14px; }

/* ---- charts ------------------------------------------------------- *
 * Hand-drawn SVG, no library. They scroll sideways inside their own box
 * rather than squeezing: twelve months at 40px each does not fit a phone,
 * and a squeezed bar chart is worse than one you swipe.
 * ------------------------------------------------------------------- */
.sv-chart-wrap { overflow-x: auto; padding-bottom: 4px; }
.sv-chart { display: block; width: 100%; min-width: 320px; height: 150px; }
.sv-chart-v { font-size: 11px; fill: var(--text-2); font-family: inherit; }
.sv-chart-l { font-size: 10px; fill: var(--text-3); font-family: inherit; }
.sv-chart-title { font-size: 12px; color: var(--text-2); margin-bottom: 6px; }

.sv-stack { display: flex; height: 14px; border-radius: 7px; overflow: hidden; background: var(--surface-3); }
.sv-stack i { display: block; height: 100%; }
.sv-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 7px; font-size: 11.5px; color: var(--text-2); }
.sv-legend span { display: inline-flex; align-items: center; gap: 5px; }
.sv-legend i { width: 9px; height: 9px; border-radius: 3px; display: inline-block; }

/* ------------------------------------------------------------------ *
 * Inbox — documents handed to a person
 * ------------------------------------------------------------------ */
.inbox-row { padding: 14px 16px; margin-bottom: 10px; }
.inbox-row.new { border-inline-start: 3px solid var(--brand); }
.inbox-head { display: flex; align-items: flex-start; gap: 11px; }
.inbox-meta {
  display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px;
  font-size: 11.5px; color: var(--text-3);
}
.inbox-ask { color: var(--warn); }
.inbox-note {
  margin-top: 9px; padding: 8px 11px; border-radius: var(--r-s);
  background: var(--surface-2); font-size: 12.5px; white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.inbox-note.reply { border-inline-start: 3px solid var(--ok); }
.inbox-bar { display: flex; align-items: center; gap: 7px; margin-top: 11px; flex-wrap: wrap; }
.inbox-mini {
  display: flex; align-items: center; gap: 10px; padding: 7px 0; color: inherit;
  border-bottom: 1px solid var(--line);
}
.inbox-mini:last-child { border-bottom: 0; }

/* A toast that is also a link — every live event has somewhere to go. */
.toast.live { display: block; cursor: pointer; max-width: 330px; }

/* The empty inbox explains itself. A screen that only says "خالی است" is
   where people conclude the feature is broken. */
.inbox-help { text-align: center; max-width: 620px; margin: 20px auto; }
.inbox-help-ic { font-size: 40px; line-height: 1.4; }
.inbox-help h3 { margin: 4px 0 6px; font-size: 17px; }
.inbox-help p { margin: 0 auto; max-width: 460px; color: var(--text-2); font-size: 13px; }
.inbox-steps { display: grid; gap: 8px; margin: 18px auto 0; max-width: 480px; text-align: start; }
.inbox-steps > div {
  display: flex; gap: 10px; align-items: flex-start; font-size: 12.5px; color: var(--text-2);
  background: var(--surface-2); border-radius: var(--r-s); padding: 9px 12px;
}
.inbox-steps b {
  flex: none; width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center;
  background: var(--brand); color: #fff; font-size: 11px;
}

/* ------------------------------------------------------------------ *
 * Chat
 *
 * Two panes on a pointer screen, one at a time on a phone. The height is
 * dvh-based rather than a percentage: a phone's viewport shrinks when the
 * keyboard opens, and a composer measured in % ends up behind it.
 * ------------------------------------------------------------------ */
.chat-wrap {
  display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 14px;
  height: calc(100dvh - var(--topbar-h) - 36px); padding: 18px 22px;
}
.chat-list, .chat-thread {
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--shadow); overflow: hidden;
}
.chat-list-head {
  display: flex; align-items: center; gap: 8px; padding: 12px 14px;
  border-bottom: 1px solid var(--line); flex: none;
}
.chat-list-body { overflow: auto; flex: 1; }

.chat-row {
  display: flex; align-items: center; gap: 10px; padding: 10px 13px; color: inherit;
  border-bottom: 1px solid var(--line); min-height: 60px;
}
.chat-row.on { background: var(--brand-soft); }
.chat-row.unread .chat-row-sub { color: var(--text); font-weight: 700; }
.chat-row-top { display: flex; align-items: baseline; gap: 8px; }
.chat-row-top b { flex: 1; min-width: 0; font-size: 13px; }
.chat-row-sub { font-size: 11.5px; color: var(--text-3); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.chat-badge {
  background: var(--brand); color: #fff; border-radius: 10px; padding: 1px 7px;
  font-size: 11px; flex: none;
}
.chat-av {
  width: 38px; height: 38px; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface-3); color: var(--text-2); font-size: 13px; font-weight: 700;
  position: relative;
}
.chat-av.online::after {
  content: ''; position: absolute; inset-block-end: 0; inset-inline-end: 0;
  width: 10px; height: 10px; border-radius: 50%; background: var(--ok);
  border: 2px solid var(--surface);
}

.chat-head {
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
  border-bottom: 1px solid var(--line); flex: none;
}
.chat-msgs {
  flex: 1; overflow: auto; padding: 16px; display: flex; flex-direction: column;
  gap: 8px; background: var(--surface-2);
}

/* Outgoing sits at the inline end, which in an RTL document is the left —
   the mirror of what a Latin-script messenger does, and what every Persian
   user already expects. */
.chat-msg {
  position: relative; max-width: min(560px, 78%); align-self: flex-start;
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  padding: 8px 12px; font-size: 13.5px; line-height: 1.85;
}
.chat-msg.mine { align-self: flex-end; background: var(--brand-soft); border-color: transparent; }
.chat-who { font-size: 11px; color: var(--brand); font-weight: 700; margin-bottom: 2px; }
.chat-body { white-space: pre-wrap; }
.chat-time { font-size: 10px; color: var(--text-3); margin-top: 3px; }
.chat-quote {
  border-inline-start: 3px solid var(--brand); padding: 2px 8px; margin-bottom: 5px;
  font-size: 11.5px; color: var(--text-2); background: var(--surface-2); border-radius: 5px;
}
.chat-quote b { display: block; color: var(--brand); }
.chat-img { display: block; max-width: 100%; max-height: 260px; border-radius: 9px; }
.chat-file-row {
  display: flex; align-items: center; gap: 9px; color: inherit; padding: 5px 0;
}
.chat-keep, .chat-del {
  background: none; border: 0; cursor: pointer; padding: 2px 4px; color: var(--text-3);
  font-size: 11px;
}
.chat-keep { color: var(--brand); display: block; margin-top: 2px; }
.chat-del { position: absolute; inset-block-start: 2px; inset-inline-start: 4px; opacity: 0; font-size: 15px; }
.chat-msg:hover .chat-del, .chat-msg:focus-within .chat-del { opacity: 1; }

.chat-composer {
  display: flex; align-items: flex-end; gap: 7px; padding: 10px 12px;
  border-top: 1px solid var(--line); flex: none; background: var(--surface);
}
textarea.chat-input { resize: none; min-height: var(--tap); max-height: 140px; overflow-y: auto; }
.chat-composer .btn.icon { flex: none; min-height: var(--tap); }

/* ================================================================== *
 * CRM
 * ================================================================== */

/* ---- customer cards ---- */
.cust-card { padding: 15px 16px; cursor: pointer; display: flex; flex-direction: column; gap: 11px; }
.cust-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
.cust-av {
  width: 38px; height: 38px; border-radius: 11px; flex: none;
  display: grid; place-items: center; font-weight: 700; font-size: 13px;
}
.cust-nm { font-weight: 700; font-size: 14.5px; line-height: 1.5; }
.cust-stats { display: flex; gap: 6px; flex-wrap: wrap; }
.cust-stat {
  font-size: 11.5px; color: var(--text-2); background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 8px; padding: 3px 8px;
  font-variant-numeric: tabular-nums;
}
.cust-foot {
  display: flex; align-items: center; gap: 8px; font-size: 11.5px;
  padding-top: 9px; border-top: 1px solid var(--line); min-width: 0;
}

/* ---- contacts ---- */
.contact-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.contact-av {
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  display: grid; place-items: center; font-size: 11.5px; font-weight: 700;
  background: var(--surface-2); color: var(--text-2);
}
.contact-nums { display: flex; gap: 5px; flex-wrap: wrap; }
.contact-nums .chip { text-decoration: none; }

/* ---- follow-ups ---- */
.followup-row {
  display: flex; align-items: center; gap: 8px; padding: 7px 0;
  border-bottom: 1px solid var(--line); font-size: 12.5px; min-width: 0;
}
.followup-row:last-child { border-bottom: 0; }
.followup-card {
  display: flex; align-items: center; gap: 12px; min-width: 0;
  background: var(--surface-2); border: 1px solid var(--line);
  /* Logical property, not border-left: in RTL the accent belongs on the right
     and `border-left` would pin it to the wrong edge. */
  border-inline-start: 3px solid var(--line-2);
  border-radius: var(--r-s); padding: 11px 13px;
  /* Wraps rather than squeezes. `flex: 1` on the text column is
     `flex-basis: 0`, so on a 375px screen the side controls (a phone number
     button is 130px on its own) crushed it to a few pixels and the customer
     name laid itself out one character per line. A real basis plus wrapping
     means the controls drop to their own row instead. */
  flex-wrap: wrap;
}
.followup-card > :first-child { flex: 1 1 210px; min-width: 0; }
.followup-side {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  flex: 0 1 auto; justify-content: flex-end;
}
.followup-side .btn { text-decoration: none; }

/* A tag or a cell whose date has already passed. */
.file-tag.late { background: var(--danger-soft); color: var(--danger); font-weight: 700; }
.file-tag.warn { background: color-mix(in srgb, #ea580c 15%, transparent); color: #ea580c; }
.late-txt { color: var(--danger); font-weight: 700; }
.card-warn { border-color: color-mix(in srgb, var(--danger) 40%, var(--line)); }

/* ---- pipeline board ---- */
/* One horizontally scrolling row of fixed-width columns — the same DOM on
   both form factors, because a board that reflows into a single column on a
   phone stops being a board. */
.kanban {
  display: flex; gap: 12px; overflow-x: auto; padding-bottom: 8px;
  scroll-snap-type: x proximity;
}
.kan-col {
  flex: 0 0 264px; min-width: 0; scroll-snap-align: start;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r); display: flex; flex-direction: column;
}
.kan-head {
  display: flex; align-items: center; gap: 7px; padding: 10px 12px;
  border-bottom: 1px solid var(--line); font-size: 12.5px;
}
.kan-head .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.kan-n {
  background: var(--surface); border: 1px solid var(--line); border-radius: 20px;
  padding: 0 7px; font-size: 11px; font-variant-numeric: tabular-nums;
}
.kan-body { padding: 10px; display: flex; flex-direction: column; gap: 9px; min-height: 60px; }
.kan-empty { text-align: center; color: var(--text-3); font-size: 12px; padding: 14px 0; }
.kan-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-s);
  padding: 11px 12px; cursor: pointer; display: flex; flex-direction: column; gap: 6px;
  box-shadow: var(--shadow); min-width: 0;
}
.kan-title { font-weight: 700; font-size: 13px; line-height: 1.55; }
.kan-cust { font-size: 11.5px; color: var(--brand); text-decoration: none; }
.kan-meta { display: flex; align-items: center; gap: 6px; font-size: 11.5px; min-width: 0; }
.kan-foot { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.kan-actions {
  display: flex; align-items: center; gap: 5px; padding-top: 7px;
  border-top: 1px solid var(--line);
}
.kan-actions .btn { padding: 3px 8px; min-height: 28px; font-size: 11px; }

/* ---- misc CRM ---- */
.contract-mini { display: flex; align-items: center; gap: 8px; font-size: 12.5px; min-width: 0; }
.form-sec {
  font-size: 12px; font-weight: 700; color: var(--text-2); margin: 16px 0 8px;
  padding-top: 12px; border-top: 1px solid var(--line);
}
.warn-inline {
  background: var(--danger-soft); color: var(--danger); border-radius: var(--r-s);
  padding: 9px 12px; font-size: 12.5px; margin-bottom: 12px; line-height: 1.8;
}
.btn.warn { background: #ea580c; border-color: #ea580c; color: #fff; }
.check { display: flex; align-items: center; gap: 7px; font-size: 12.5px; margin: 8px 0; cursor: pointer; }
.check input { width: 16px; height: 16px; accent-color: var(--brand); }
.won-n { color: #16a34a; font-size: 11px; font-weight: 700; }

/* ------------------------------------------------------------------ *
 * Bottom tab bar — phone only
 *
 * Primary navigation lives under the thumb, not behind a hamburger at the
 * far top corner: on a 6" phone held one-handed that corner is the single
 * hardest pixel to reach, and it is where this app used to keep every
 * route. The centre slot is the raised action button, because the job this
 * app exists for is "put a file somewhere", and it changes meaning with
 * the page you are on (see setPageAction in app.js).
 * ------------------------------------------------------------------ */
.tabbar { display: none; }
.tabbar-fab { display: none; }

/* ------------------------------------------------------------------ *
 * Pointer-only affordances
 *
 * Hover styles are quarantined here. On a touch screen there is no hover:
 * the browser fakes one on tap and then leaves it stuck on the last thing
 * you touched, so a card stays lifted and highlighted long after you have
 * moved on. Everything below simply does not exist on a phone.
 * ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ *
 * Project locations
 *
 * The card is mostly output: a coordinate nobody can act on is a
 * coordinate nobody stores twice. Everything below the address line is a
 * way *out* of the app — navigate, open elsewhere, copy, share.
 * ------------------------------------------------------------------ */
.loc-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
.loc-sum { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 13px; }
.loc-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
  padding: 14px; display: grid; gap: 10px; box-shadow: var(--shadow);
}
.loc-card.archived { opacity: .62; }
.loc-head { display: flex; align-items: flex-start; gap: 10px; }
.loc-ic {
  flex: none; width: 36px; height: 36px; border-radius: 11px; display: grid; place-items: center;
  background: var(--brand-soft); font-size: 18px;
}
.loc-title { min-width: 0; }
.loc-title b { display: block; font-size: 14.5px; }
.loc-sub { font-size: 11.5px; color: var(--text-3); }
.loc-facts { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; font-size: 12.5px; color: var(--text-2); }
.loc-addr { display: flex; gap: 6px; align-items: baseline; }
.loc-tel { margin-inline-start: auto; text-decoration: none; }
.loc-coordbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 10px; border-radius: var(--r-s); background: var(--surface-2);
}
/* A coordinate is Latin digits, monospaced and left-to-right in every script:
   putting it through fmtNum would give Persian digits nothing can parse back. */
.loc-coord {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px; direction: ltr; unicode-bidi: isolate; overflow-wrap: anywhere;
}
.loc-acts { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.loc-mini {
  display: flex; align-items: center; gap: 8px; font-size: 13px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.loc-mini:last-child { border-bottom: 0; }
.loc-mini .ic { flex: none; }
.loc-mini .nm { min-width: 0; }
.loc-arch > summary { cursor: pointer; color: var(--text-3); font-size: 12.5px; padding: 6px 0; }
.loc-arch > div { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; padding-top: 8px; }

/* Rows in the "open in…" / "export" sheets. Full-width targets, because on a
   phone this list is the whole reason the screen was opened. */
.loc-openlist { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; }
.loc-open {
  display: flex; align-items: center; gap: 11px; width: 100%; min-height: 56px;
  padding: 12px 13px; border: 1px solid var(--line); border-radius: var(--r-s);
  background: var(--surface); color: var(--text); font: inherit; text-align: start;
  cursor: pointer; text-decoration: none;
}
.loc-open .ic { flex: none; font-size: 20px; }
.loc-open .tx { flex: 1; min-width: 0; }
.loc-open .tx b { display: block; font-size: 13.5px; }
.loc-open .ds { font-size: 11.5px; color: var(--text-3); overflow-wrap: anywhere; }
.loc-open .go { flex: none; color: var(--text-3); }
.loc-open.danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 32%, var(--line)); }

/* ---- the pin editor ---- */
.loc-pin { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
.loc-pin-bar { display: flex; gap: 8px; flex-wrap: wrap; }
.loc-pin-bar .btn { flex: 1 1 auto; justify-content: center; }
.loc-pin-empty {
  display: flex; gap: 10px; align-items: flex-start; font-size: 12.5px;
  padding: 12px; border: 1px dashed var(--line-2); border-radius: var(--r-s);
}
.loc-pin-val {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; padding: 12px;
  border-radius: var(--r-s); border: 1px solid var(--brand-soft);
  background: color-mix(in srgb, var(--brand) 6%, transparent);
}
.loc-pin-val .loc-coord { font-size: 14px; font-weight: 700; }
.loc-pin-meta { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.loc-pin-act { display: flex; gap: 8px; flex-wrap: wrap; }
.loc-warn { color: var(--warn); }
.loc-linkbox {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; padding: 12px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-s);
}
.loc-two { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); gap: 10px; }
.loc-two .field { margin-bottom: 0; }
.loc-quick { margin-top: 8px; }
.loc-more > summary { cursor: pointer; color: var(--brand); font-size: 13px; padding: 8px 0; }

/* ---- the live GPS panel ----
   The border carries the accuracy, because that is the number that decides
   whether the pin is on the building or on the district. */
.loc-gps {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; text-align: center;
  padding: 14px; border: 1px solid var(--line); border-radius: var(--r-s); background: var(--surface-2);
}
.loc-gps.good { border-color: var(--ok); }
.loc-gps.fair { border-color: var(--warn); }
.loc-gps.poor { border-color: var(--danger); }
.loc-gps-lb {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 12.5px; color: var(--text-2);
}
.loc-gps-lb .pulse {
  width: 9px; height: 9px; border-radius: 50%; background: var(--brand);
  animation: sv-pulse 1.8s var(--ease) infinite;
}
.loc-gps-read {
  font-size: 19px; font-weight: 700; direction: ltr; unicode-bidi: isolate;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.loc-gps-act { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.loc-gps-act .btn { min-height: var(--tap); }

/* ------------------------------------------------------------------ *
 * Dashboard — a stack of bands, not one grid
 *
 * It *was* a twelve-column grid, and that is precisely what broke: a CSS grid
 * row is as tall as its tallest item, so a 175px card beside a 584px one
 * leaves a 400px hole that nothing can fill — `grid-auto-flow: dense` only
 * back-fills holes it finds in the flow, and all it actually achieved was
 * scattering cards across five different column offsets. On a 1298px desktop
 * that produced a 2950px page of ragged gaps.
 *
 * So: widgets that want the whole width (the action queue, the stat row, the
 * department tiles, the notice board — each of which is already responsive
 * inside itself) get their own band; everything else flows into a masonry
 * band built from CSS multi-column, which has no rows and therefore no holes.
 * ------------------------------------------------------------------ */
.dash { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.dash-full { min-width: 0; }

/* `columns: <min-width> <max-count>` — both halves are load-bearing.
   The width makes the track count fall out of the available space with no
   breakpoint to keep in sync, exactly the way `repeat(auto-fill, minmax(...))`
   does for the grids elsewhere; the count caps it at three, or a 1920px screen
   would take four 350px columns and the board would read as scattered.
   Result: three columns on a desktop, two on a tablet, one on a phone, with
   nothing to keep in sync. In RTL the first column is the rightmost, which is
   the reading order. */
.dash-band { columns: 340px 3; column-gap: 16px; }
.dash-band > .dash-w {
  /* A card must never be split down the middle of a column break. */
  break-inside: avoid; -webkit-column-break-inside: avoid; page-break-inside: avoid;
  /* flow-root so the card's own margins cannot escape and confuse balancing. */
  display: flow-root; margin: 0 0 16px; min-width: 0;
}
.dash-w > .card { margin: 0; }
.dash-h { font-size: 15px; margin: 0 0 12px; }
.dash-sep { height: 1px; background: var(--line); margin: 14px 0; }

.dash-foot {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--line);
}
.dash-foot .spacer { flex: 1; }

/* ---- the greeting strip ----
   The one piece of colour on the screen. It carries the shortcuts rather
   than the page-head action row, because on a phone that row scrolls
   sideways and the button somebody came for ends up off-screen. */
.hero {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 18px 20px; margin-bottom: 18px; border-radius: var(--r-l);
  background:
    radial-gradient(120% 160% at 100% 0%, color-mix(in srgb, var(--brand) 26%, transparent), transparent 60%),
    linear-gradient(180deg, var(--surface), var(--surface-2));
  border: 1px solid var(--line); box-shadow: var(--shadow);
}
.hero-txt { min-width: 0; flex: 1 1 240px; }
.hero-hi { font-size: 21px; font-weight: 700; line-height: 1.5; }
.hero-sub { font-size: 12.5px; color: var(--text-2); }
.hero-acts { display: flex; gap: 8px; flex-wrap: wrap; }
.hero-act {
  display: inline-flex; align-items: center; gap: 7px; padding: 9px 14px;
  border-radius: var(--r); border: 1px solid var(--line-2); background: var(--surface);
  cursor: pointer; font-size: 12.5px; font-weight: 700; min-height: var(--tap);
  transition: transform .1s, box-shadow .12s;
}
.hero-act:active { transform: translateY(1px); }
.hero-act .ic { font-size: 16px; }

/* ---- the action queue ----
   Every row is a link. A count on this card that cannot be tapped is
   decoration, and decoration is what makes a dashboard stop being read. */
.todo-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); gap: 10px;
}
.todo {
  display: flex; align-items: center; gap: 11px; padding: 12px 14px; min-width: 0;
  border: 1px solid var(--line); border-radius: var(--r-s); background: var(--surface-2);
  color: inherit; text-decoration: none; min-height: 62px; transition: background .12s, border-color .12s;
}
.todo-ic {
  width: 34px; height: 34px; border-radius: 10px; flex: none; display: grid;
  place-items: center; font-size: 16px; background: var(--surface-3);
}
.todo-tx { flex: 1; min-width: 0; }
.todo-tx b { display: block; font-size: 13px; line-height: 1.5; }
.todo-tx .s { font-size: 11px; color: var(--text-3); }
.todo-n { font-size: 20px; font-weight: 700; flex: none; }
/* Red is spent only on work whose date has already passed, so it keeps
   meaning something when it does appear. */
.todo.hot { border-color: color-mix(in srgb, var(--danger) 34%, var(--line)); }
.todo.hot .todo-ic { background: var(--danger-soft); }
.todo.hot .todo-n { color: var(--danger); }

.dash-clear .card-pad { padding: 20px; }

/* ---- a compact list row, shared by four widgets ---- */
.dash-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 6px; min-width: 0;
  border-radius: var(--r-s); cursor: pointer; color: inherit; text-decoration: none;
}
/* Title and subtitle are separate lines, not one run of inline text: in a
   third-width column a filename plus its department plus its uploader wraps
   into a paragraph, and the row stops being scannable. The title wraps (it is
   often one unbreakable token — see .brk), the subtitle never does. */
.dash-row b { display: block; font-size: 13px; font-weight: 700; line-height: 1.5; }
.dash-row .s {
  display: block; font-size: 11.5px; color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dash-row > span:nth-child(2) { overflow: hidden; }
.dash-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }

.dash-call {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px; min-width: 0;
  border: 1px solid var(--line); border-radius: var(--r-s); background: var(--surface-2);
  border-inline-start: 3px solid var(--brand);
}
.dash-call.late { border-inline-start-color: var(--danger); background: var(--danger-soft); }
.dash-call .btn { flex: none; text-decoration: none; }

/* ---- the layout editor ---- */
.dash-cfg { display: flex; flex-direction: column; gap: 2px; }
.dash-cfg-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border-radius: var(--r-s); border: 1px solid var(--line); background: var(--surface-2);
}
.dash-cfg-row.off { opacity: .55; }
.dash-cfg-row .btn { flex: none; }

/* ------------------------------------------------------------------ *
 * Announcements
 *
 * The colour is carried by one custom property (`--ann`, set inline from the
 * level) so the border, the icon tile and the read-progress bar cannot drift
 * apart the way three separate rules would.
 * ------------------------------------------------------------------ */
.ann-list { display: flex; flex-direction: column; gap: 12px; }
/* The dashboard's notice board sits at full page width, where a single column
   would run one sentence across 1300px. Side by side instead; the archive
   screen keeps the one-column list, where the whole text is the point. */
.ann-list.tiles {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  align-items: start;
}
.ann {
  border: 1px solid var(--line); border-radius: var(--r); background: var(--surface);
  border-inline-start: 4px solid var(--ann, var(--brand)); padding: 14px 16px;
  box-shadow: var(--shadow); min-width: 0;
}
/* Unread is a tint of the notice's own colour, not a generic blue dot: on a
   board of four the eye should land on the فوری one first. */
.ann.new { background: color-mix(in srgb, var(--ann, var(--brand)) 7%, var(--surface)); }
.ann.ask { border-color: color-mix(in srgb, var(--ann, var(--brand)) 45%, var(--line)); }
.ann.off { opacity: .62; }
.ann-head { display: flex; align-items: flex-start; gap: 11px; }
.ann-ic {
  width: 34px; height: 34px; border-radius: 10px; flex: none; display: grid;
  place-items: center; font-size: 17px;
  background: color-mix(in srgb, var(--ann, var(--brand)) 15%, transparent);
}
.ann-title { font-weight: 700; font-size: 14.5px; line-height: 1.55; }
.ann-meta {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: 11px; color: var(--text-3); margin-top: 3px;
}
.ann-pin { flex: none; font-size: 14px; }
.ann-body { margin-top: 9px; font-size: 13px; line-height: 1.9; white-space: pre-wrap; }
.ann-body.clamp {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.ann-body.tap { cursor: pointer; }
.ann-acts { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 11px; }
.ann-acts .btn { text-decoration: none; }
.ann-done { font-size: 11.5px; color: var(--ok); }
.ann-read { margin-top: 12px; }
.ann-receipt {
  display: flex; align-items: center; gap: 10px; padding: 7px 4px;
  border-bottom: 1px solid var(--line);
}
.ann-receipt:last-child { border-bottom: 0; }
.ann-pick-lb { font-size: 12.5px; color: var(--text-2); font-weight: 700; margin-bottom: 7px; }

@media (hover: hover) and (pointer: fine) {
  .btn:hover { background: var(--surface-2); }
  .btn.primary:hover { background: var(--brand-2); border-color: var(--brand-2); }
  .btn.danger:hover { background: var(--danger-soft); }
  .btn.ghost:hover { background: var(--surface-2); }
  .nav-item:hover { background: var(--surface-2); color: var(--text); }
  .user-chip:hover { background: var(--surface-2); }
  .dropzone:hover { border-color: var(--brand); background: var(--brand-soft); color: var(--brand); }
  .dept-tile:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
  .file-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); border-color: var(--line-2); }
  table.tbl tbody tr:hover { background: var(--surface-2); }
  .x-btn:hover { background: var(--surface-2); color: var(--text); }
  .sv-pick:hover { border-color: var(--brand); }
  .sv-thumb:hover { border-color: var(--line-2); }
  .chat-row:hover { background: var(--surface-2); }
  .chat-row.on:hover { background: var(--brand-soft); }
  .inbox-mini:hover { background: var(--surface-2); }
  .hero-act:hover { background: var(--surface-2); box-shadow: var(--shadow); }
  .todo:hover { background: var(--surface-2); }
  .dash-row:hover { background: var(--surface-2); }
}

/* ------------------------------------------------------------------ *
 * Tablet
 * ------------------------------------------------------------------ */
@media (max-width: 1180px) {
  :root { --sidebar: 214px; }
  .topbar-search input { width: 190px; }
  .page { padding: 18px; }
}

/* ------------------------------------------------------------------ *
 * Phone — 860px and below
 * ------------------------------------------------------------------ */
/* Phone-only chrome. Kept as an explicit `display` per element rather than a
   generic `.only-mobile` helper: a shared helper would have to guess whether
   the thing it reveals is a flex button or a block, and guessing wrong flattens
   the element it was meant to show. */
#back-btn { display: none; }

@media (max-width: 860px) {
  #back-btn.on { display: inline-flex; }

  /* 8 + 44 + 8: the bar is exactly one tap target tall, which is what the
     sticky filter strip below offsets itself by. */
  :root { --topbar-h: 60px; }

  /* 15px body text and 16px inputs. The second number is not taste: iOS
     Safari zooms the whole page in when a focused field is under 16px,
     and the user is then left panned sideways on a form. */
  body { font-size: 15px; }
  .input, .select, textarea.input, .topbar-search input, input[type="file"].input { font-size: 16px; }

  .no-mobile { display: none !important; }

  .shell { grid-template-columns: 1fr; }
  .hero { padding: 16px; }
  .hero-hi { font-size: 19px; }
  /* The shortcuts scroll sideways rather than wrapping into three rows that
     push the first widget below the fold. */
  .hero-acts { width: 100%; overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; }
  .hero-acts::-webkit-scrollbar { display: none; }
  .hero-act { flex: none; }
  .todo { min-height: var(--tap); }

  /* The sidebar becomes a bottom sheet. Same DOM, same nav list, but it
     now arrives from the edge the thumb is already resting on. */
  .sidebar {
    /* Tall enough that the whole menu is one glance on a normal phone, short
       enough that the page still shows above it — that strip of page is what
       says "sheet", i.e. dismissible, rather than "new screen". */
    position: fixed; inset: auto 0 0 0; width: 100%; height: min(88dvh, 720px);
    z-index: 70; border: 0; border-radius: var(--r-sheet) var(--r-sheet) 0 0;
    box-shadow: var(--shadow-up); transform: translateY(101%);
    transition: transform .26s var(--ease); padding-bottom: var(--safe-b);
    visibility: hidden;
  }
  .sidebar.open { transform: none; visibility: visible; }
  .sidebar .brand { padding: 6px 18px 12px; }
  .sidebar .nav { padding-inline: 12px; }
  .sidebar .nav-item { padding: 11px 12px; font-size: 14.5px; min-height: var(--tap); }
  .sidebar .nav-item .ico { font-size: 17px; }

  .grab {
    display: block; flex: none; width: 42px; height: 4px; border-radius: 99px;
    background: var(--line-2); margin: 10px auto 2px; cursor: grab;
  }
  /* The whole strip above the content is the drag target, not just the
     4px pill — a 4px target would be a joke on a touch screen. */
  .sheet-grip { touch-action: none; }

  .topbar {
    padding: calc(8px + var(--safe-t)) 12px 8px; gap: 8px; min-height: var(--topbar-h);
  }
  .topbar .crumbs { font-size: 12px; }
  /* On a phone the breadcrumb trail is noise; only the current page is
     kept, and it doubles as the screen title. */
  .topbar .crumbs .sep, .topbar .crumbs a { display: none; }
  .topbar .crumbs b { font-size: 16px; font-weight: 700; }
  .topbar-search { display: none; }

  .page { padding: 14px 14px calc(var(--nav-h) + var(--safe-b) + 22px); }
  .page-head { gap: 10px; margin-bottom: 14px; align-items: flex-start; }
  .page-head h1 { font-size: 19px; }
  .page-head .spacer { display: none; }
  .page-head > div:first-child { flex: 1 1 100%; min-width: 0; }
  /* Page actions become one horizontally scrolling row instead of a wall
     of wrapped buttons pushing the content below the fold. */
  .page-head .actions {
    flex: 1 1 100%; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    padding-bottom: 2px; -webkit-overflow-scrolling: touch;
  }
  .page-head .actions::-webkit-scrollbar { display: none; }
  .page-head .actions > * { flex: none; }

  /* minmax(0,1fr), never plain 1fr: a bare `1fr` track is minmax(auto,1fr),
     so its floor is the item's min-content width — one card head with a
     nowrap button in it then pushes the track wider than the screen and the
     whole column scrolls sideways. The 0 floor lets the track actually
     shrink and the content wrap instead. */
  .split, .auto-cols { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .card-cols { grid-template-columns: minmax(0, 1fr); }
  .row, .row3 { grid-template-columns: minmax(0, 1fr); }

  /* Two across, not one. Four KPI cards stacked vertically push the real
     content a full screen down; side by side they stay a glance. */
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .stat { padding: 13px 14px; }
  .stat .v { font-size: 22px; }
  .stat .k { font-size: 11.5px; }

  .dept-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .dept-tile { padding: 12px; gap: 10px; flex-direction: column; align-items: flex-start; }
  .dept-tile .ic { width: 34px; height: 34px; font-size: 17px; }
  .dept-tile > div:last-child { width: 100%; }

  .file-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .file-card { padding: 12px; min-height: 118px; gap: 6px; }
  .file-card .file-ico { width: 32px; height: 32px; font-size: 15px; }
  .file-name { font-size: 12.5px; }

  /* Filters: one scrolling strip, sticky under the topbar so changing the
     filter never means scrolling back up. */
  .toolbar {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; gap: 8px;
    padding: 8px 14px; margin: 0 -14px 12px; -webkit-overflow-scrolling: touch;
    position: sticky; top: calc(var(--topbar-h) + var(--safe-t)); z-index: 15;
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--line);
  }
  .toolbar::-webkit-scrollbar { display: none; }
  .toolbar > * { flex: none; }
  .toolbar .grow { flex: 1 0 62%; min-width: 0; max-width: none !important; }
  .toolbar > .select { min-width: 0; }
  .toolbar .spacer, .toolbar .hint { display: none; }

  .chips { gap: 6px; }
  .chip { padding: 7px 13px; font-size: 12.5px; min-height: 34px; }
  /* Tab strips scroll instead of wrapping into three ragged lines. */
  .chips.tabs {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    margin-inline: -14px; padding-inline: 14px; scroll-snap-type: x proximity;
  }
  .chips.tabs::-webkit-scrollbar { display: none; }
  .chips.tabs > * { flex: none; scroll-snap-align: start; }

  .btn { min-height: var(--tap); padding: 10px 15px; }
  .btn.sm { min-height: 34px; padding: 6px 11px; }
  .btn.icon { width: var(--tap); min-width: var(--tap); height: var(--tap); }
  .seg button { min-height: var(--tap); padding: 8px 14px; }
  .x-btn { min-width: 38px; min-height: 38px; }

  .card { border-radius: var(--r); }
  .card-pad { padding: 14px; }
  .card-head { padding: 12px 14px; }

  .empty { padding: 40px 16px; }

  /* Column priority in action. */
  .col-lo { display: none !important; }
  table.tbl th, table.tbl td { padding: 10px 8px; }

  /* Drag-and-drop is a mouse idea. On touch the same box is just a big
     button, and it says so. */
  .dropzone { padding: 16px 14px; }

  /* --- dialogs become sheets --- */
  .backdrop { padding: 0; place-items: end stretch; }
  .modal {
    width: 100% !important; max-width: none; max-height: 92dvh;
    border-radius: var(--r-sheet) var(--r-sheet) 0 0; border-bottom: 0;
    animation: sheet-up .24s var(--ease); padding-bottom: var(--safe-b);
  }
  .modal.full { height: 100dvh; max-height: 100dvh; border-radius: 0; }
  @keyframes sheet-up { from { transform: translateY(100%); } }
  .modal-head { padding: 4px 14px 12px; flex-wrap: wrap; }
  .modal-head .grab { order: -1; width: 100%; height: auto; background: none; margin: 0; }
  .modal-head .grab::before {
    content: ''; display: block; width: 42px; height: 4px; border-radius: 99px;
    background: var(--line-2); margin: 8px auto;
  }
  .modal-body { padding: 4px 14px 16px; }
  /* The sheet reserves the home-indicator inset itself — see .modal above. */
  .modal-foot { padding: 10px 14px; gap: 8px; flex-wrap: wrap; }
  /* The primary action becomes a full-width bar and jumps to the top of the
     footer; the secondary ones share the row under it. Three equal buttons
     squeezed into 360px would each be too narrow to read their own label. */
  .modal-foot .spacer { display: none; }
  .modal-foot > * { flex: 1 1 auto; min-width: 0; }
  .modal-foot > .btn.primary { flex: 1 0 100%; order: -1; }
  .modal-foot .btn { overflow: hidden; }

  /* The document drawer is the same story: a sheet, dragged from the
     bottom, with its actions kept above the fold. */
  .drawer {
    inset: auto 0 0 0; width: 100%; height: 92dvh; border: 0;
    border-radius: var(--r-sheet) var(--r-sheet) 0 0;
    box-shadow: var(--shadow-up); animation: sheet-up .24s var(--ease);
    padding-bottom: var(--safe-b);
  }
  .drawer-body { padding: 12px 16px 20px; }
  .drawer-head { padding: 0 16px 12px; }
  .drawer-head .grab { margin: 10px auto 8px; }

  .toasts {
    inset-inline: 12px; bottom: calc(var(--nav-h) + var(--safe-b) + 12px);
    align-items: stretch;
  }
  .toast { max-width: none; }

  /* The file manager on a phone: filters stack, and the selection bar docks to
     the bottom above the tab bar instead of sticking under the topbar — the
     thumb is already down there. */
  .fm-filters { grid-template-columns: minmax(0, 1fr); padding: 12px 14px; }

  /* The sticky action bar has to clear the tab bar and the home indicator,
     or "ثبت نهایی" ends up underneath both of them. `.page` already reserves
     that much at the bottom; this only moves the bar up to sit on top of it. */
  .sv-timer { font-size: 26px; }

  /* --tap is 44px and the form is the one screen where that number is not a
     suggestion: a technician taps these with a glove on, in the sun, standing
     on a ladder. The chips were 37px, which is where the mis-taps came from. */
  .sv-page .chip, .sv-chips .chip { min-height: var(--tap); padding-inline: 14px; font-size: 13.5px; }
  .sv-pick { min-height: 48px; }
  .sv-dot { width: var(--tap); height: var(--tap); font-size: 17px; }
  .sv-stepbody .btn, .sv-stepbody .sv-kind { min-height: var(--tap); }
  .sv-stephead { padding: 14px 15px 4px; }
  .sv-stepbody { padding: 10px 15px 18px; }
  .sv-stephead h2 { font-size: 17px; }

  /* Locations: every action becomes a full-width-ish target and the readout
     grows — this card is read at arm's length in a van. */
  .loc-acts .btn { flex: 1 1 auto; justify-content: center; }
  .loc-open { min-height: 60px; }
  .loc-gps-read { font-size: 22px; }
  .loc-card { padding: 13px; }

  /* The nav bar has to clear the tab bar and the home indicator, or "بعدی"
     ends up underneath both of them. */
  .sv-nav { bottom: calc(var(--nav-h) + var(--safe-b)); }
  .sv-page { padding-bottom: 0; }
  .sv-block { max-width: 130px; font-size: 11px; }
  .sv-nav .sv-save { font-size: 11px; }
  .sv-checkrow { min-height: 60px; }
  .sv-next { min-width: 96px; }

  /* One pane at a time, and full-bleed: a chat thread inside a 14px page
     gutter wastes the only width a phone has. The height leaves room for the
     tab bar so the composer never lands underneath it. */
  .chat-wrap {
    grid-template-columns: minmax(0, 1fr); padding: 0; gap: 0;
    height: calc(100dvh - var(--topbar-h) - var(--nav-h) - var(--safe-b));
  }
  .chat-list, .chat-thread { border: 0; border-radius: 0; box-shadow: none; }
  .chat-msg { max-width: 88%; }
  /* No hover on touch, so the delete affordance has to be permanent. */
  .chat-del { opacity: .55; }
  .fm-selbar {
    position: fixed; inset: auto 12px calc(var(--nav-h) + var(--safe-b) + 10px) 12px;
    top: auto; margin: 0; z-index: 56;
  }
  .fm-group > h3 { font-size: 13px; }

  /* --- the tab bar itself --- */
  .tabbar {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; inset: auto 0 0 0; z-index: 55; height: calc(var(--nav-h) + var(--safe-b));
    padding-bottom: var(--safe-b);
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--line);
  }
  .tab-item {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    border: 0; background: none; color: var(--text-3); font-size: 10.5px; cursor: pointer;
    padding: 0; min-height: var(--tap); position: relative;
  }
  .tab-item .ico { font-size: 19px; line-height: 1.25; }
  .tab-item .lb { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tab-item.active { color: var(--brand); font-weight: 700; }
  .tab-item.active::after {
    content: ''; position: absolute; top: 0; width: 26px; height: 3px;
    border-radius: 0 0 3px 3px; background: var(--brand);
  }
  .tab-item:active { background: var(--surface-2); }

  .tabbar-fab {
    display: grid; place-items: center; justify-self: center; align-self: start;
    width: 52px; height: 52px; margin-top: -18px; border-radius: 17px;
    background: var(--brand); color: #fff; border: 3px solid var(--bg);
    font-size: 20px; cursor: pointer; box-shadow: 0 8px 20px -6px color-mix(in srgb, var(--brand) 70%, transparent);
    transition: transform .12s var(--ease);
  }
  .tabbar-fab:active { transform: scale(.92); }

  /* An embedded app gets the whole screen between the two bars. */
  .app-frame { height: calc(100dvh - var(--topbar-h) - var(--nav-h) - var(--safe-b) - 96px); min-height: 380px; }

  .login-card { padding: 26px 20px; border-radius: var(--r-l); }
  .login-card h1 { font-size: 21px; }
}


/* ------------------------------------------------------------------ *
 * The service wizard on a pointer screen
 *
 * A phone gets one question at a time and a strip of dots, because that is
 * all that fits. A desktop has a whole empty column next to the card, and
 * filling it with the step list turns "چند مرحله مانده" from a row of
 * anonymous circles into a table of contents you can read and click.
 *
 * Same DOM as the phone — only the stylesheet differs, so a tablet that is
 * rotated moves between the two without the form being rebuilt.
 * ------------------------------------------------------------------ */
@media (min-width: 861px) {
  .sv-wrap {
    grid-template-columns: 236px minmax(0, 1fr);
    align-items: start; max-width: 1000px; margin-inline: auto;
  }
  .sv-steps { position: sticky; top: 14px; margin: 0; max-width: none; }
  .sv-progress { margin-bottom: 12px; }
  .sv-dots {
    flex-direction: column; gap: 2px; margin-top: 0;
    max-height: calc(100dvh - 190px); overflow-y: auto; overflow-x: hidden;
  }
  .sv-dot {
    display: flex; align-items: center; gap: 9px; justify-content: flex-start;
    width: 100%; height: auto; min-height: 38px; padding: 7px 10px; text-align: start;
    border-radius: var(--r-s); background: transparent; border-color: transparent;
  }
  .sv-dot .ic { flex: none; width: 20px; text-align: center; }
  .sv-dot .lb {
    display: block; font-size: 12.5px; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .sv-dot .mk { display: block; margin-inline-start: auto; font-size: 12px; color: var(--ok); }
  .sv-dot.done { background: transparent; border-color: transparent; color: var(--text-2); }
  .sv-dot.on {
    background: var(--brand-soft); border-color: transparent; color: var(--brand);
    font-weight: 700; transform: none;
  }
  .sv-dot.req { color: var(--warn); }
  .sv-dot.req .mk { color: var(--warn); }
  .sv-dot.req::after { display: none; }
  /* The card fills its column instead of centring inside it — the rail is
     already carrying the left margin. */
  .sv-step, .sv-nav { max-width: none; margin-inline: 0; }
}

/* ------------------------------------------------------------------ *
 * Small phones — 360px and below
 *
 * Deliberately *not* 400px. A 375px iPhone SE and a 390px iPhone 13 are the
 * middle of the market, not the edge of it; a 400px cutoff would have quietly
 * pushed the two-column grids above back into one column for most real
 * phones. This tier is for the genuinely narrow ones.
 * ------------------------------------------------------------------ */
@media (max-width: 360px) {
  .col-mid { display: none !important; }
  .file-grid, .dept-tiles { grid-template-columns: minmax(0, 1fr); }
  .tab-item { font-size: 10px; }
  .tab-item .lb { display: none; }
  .tab-item .ico { font-size: 21px; }
  .page { padding-inline: 12px; }
  .toolbar { padding-inline: 12px; margin-inline: -12px; }
}

/* Landscape phones have almost no vertical room: a sheet that takes 92dvh
   there leaves nothing behind it, so it takes the screen instead. */
@media (max-width: 860px) and (max-height: 480px) {
  .drawer, .modal { height: 100dvh; max-height: 100dvh; border-radius: 0; }
  .sidebar { height: 100dvh; border-radius: 0; }
}

.scrim {
  position: fixed; inset: 0; background: rgba(15,23,42,.45); z-index: 65;
  animation: fade .16s ease; backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
@media (min-width: 861px) { .scrim { display: none; } }

/* Vestibular safety: honour the OS switch instead of animating anyway. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

@media print {
  .sidebar, .topbar, .toolbar, .toasts, .chips.tabs, .page-head .btn, .page-head .actions,
  .backdrop, .drawer, .tabbar, .scrim, .sv-nav, .sv-steps,
  .hero-acts, .dash-foot, .ann-acts { display: none !important; }
  /* The signature is the point of printing a service form — it must not be
     dropped as a background image the way most browsers drop them. */
  .sv-sign-img, .sv-thumb img { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .sv-thumbs { grid-template-columns: repeat(4, 1fr); }
  .sv-page { padding-bottom: 0; }
  .shell { grid-template-columns: 1fr; }
  .page { padding: 0; max-width: none; }
  .card { box-shadow: none; border-color: #ccc; break-inside: avoid; }
  body { background: #fff; }
  .col-lo, .col-mid { display: table-cell !important; }
  :root { --surface: #fff; --surface-2: #fff; --text: #000; --text-2: #333; --text-3: #666; --line: #ddd; }
}
