@layer legacy {
/* ── Vazirmatn (self-hosted) ─────────────────────────────────────────────── */
@font-face {
  font-family: 'Vazirmatn';
  src: url('/public/fonts/Vazirmatn-RD[wght].woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}

/* ── Reset ───────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── Design tokens ─────────────────────────────────────────────────────────────
   Every colour used anywhere in this file is defined here and consumed as
   var(--…). NOTHING below this block hardcodes a colour — so the entire
   light→dark switch is the single [data-theme="dark"] override that follows,
   nothing per-component. If a new rule needs a colour, add a token; don't inline
   a hex. */
:root {
  /* accent — green (buy / positive / برند) */
  --green:          #2d6a4f;
  --green-hover:    #245a41;
  --green-dot:      #40b07a;              /* «موجود» status dot */
  --green-tint:     rgba(45,106,79,.08);  /* faint green wash: settle / attach panels */
  /* accent — red (sell / negative / danger) */
  --red:            #c1121f;
  --red-hover:      #a50f1a;
  --red-dot:        #e63946;              /* «فروخته‌شده» status dot */
  --red-tint:       #fee2e2;              /* solid light-red fill: sold badge, auth error */
  --red-tint-border:#fecaca;
  --red-tint-soft:  rgba(200,50,50,.08);  /* faint red wash: settle-neg badge */
  /* warning / amber */
  --warn:           #d97706;              /* stale-rate caution */
  --warn-text:      #8a6d1f;              /* «تقریبی» rate note */
  /* chart profit bars (gold) */
  --chart-profit-1: #b8860b;
  --chart-profit-2: #e0a800;
  /* neutrals / surfaces */
  --bg:             #f4f2ed;
  --surface:        #ffffff;              /* cards, modals, popovers, dropdowns */
  --raised:         #ffffff;              /* focused input / hover-raised surface */
  --hover:          #e8e5de;              /* subtle hover fill, a touch darker than --bg */
  --warm-panel:     #fbf7ef;              /* overdue alert boxes */
  --border:         #ddd9d2;
  --text:           #1a1a1a;
  --text-muted:     #6b6b6b;
  --on-accent:      #ffffff;              /* text / icon sitting on a solid green|red fill */
  /* backdrop + elevation shadows */
  --backdrop:       rgba(20,18,14,.5);
  --shadow:         0 1px 4px rgba(0,0,0,.08);
  --shadow-md:      0 4px 16px rgba(0,0,0,.10);
  --shadow-toast:   0 4px 12px rgba(0,0,0,.20);
  --shadow-menu:    0 8px 28px rgba(0,0,0,.16);
  --shadow-modal:   0 12px 40px rgba(0,0,0,.28);
  /* brand mark — see .brand-logo. The logo is a fixed navy/orange PNG that
     cannot be recoloured per theme, and its navy half is nearly invisible on a
     near-black surface. On light surfaces it needs nothing, so: none. */
  --logo-plate:     none;
  /* geometry / type (never themed) */
  --radius:         10px;
  --font:           'Vazirmatn', Tahoma, Arial, sans-serif;
}

/* ── Dark theme ────────────────────────────────────────────────────────────────
   Only tokens are overridden — no component rule below knows the theme exists.
   Legibility over inversion:
   • Surfaces are near-black and LAYERED (--bg < --surface < --raised) so a card
     or modal still reads as raised — never pure #000, which smears Vazirmatn on
     OLED and kills elevation.
   • Green/red are LIGHTENED: saturated dark accents go muddy on black, and the
     status dots + profit/loss figures must stay clearly distinguishable.
   • Product photos get no filter/opacity anywhere, so nothing here touches them. */
:root[data-theme="dark"] {
  --green:          #2f9e6b;
  --green-hover:    #37b57c;
  --green-dot:      #4cd08f;
  --green-tint:     rgba(76,208,143,.14);
  --red:            #ec5766;
  --red-hover:      #ff6b7a;
  --red-dot:        #ff5a67;
  --red-tint:       rgba(236,87,102,.16);
  --red-tint-border:rgba(236,87,102,.38);
  --red-tint-soft:  rgba(236,87,102,.14);
  --warn:           #f0a52a;
  --warn-text:      #d9b25f;
  --chart-profit-1: #d9a520;
  --chart-profit-2: #ffc233;
  --bg:             #14161a;
  --surface:        #1e2126;
  --raised:         #2b2f37;
  --hover:          #2b2f37;
  --warm-panel:     #221f1a;
  --border:         #333a44;
  --text:           #e8e6e1;
  --text-muted:     #9aa0a8;
  --on-accent:      #ffffff;
  --backdrop:       rgba(0,0,0,.66);
  --shadow:         0 1px 4px rgba(0,0,0,.45);
  --shadow-md:      0 4px 16px rgba(0,0,0,.55);
  --shadow-toast:   0 4px 12px rgba(0,0,0,.60);
  --shadow-menu:    0 8px 28px rgba(0,0,0,.60);
  --shadow-modal:   0 12px 40px rgba(0,0,0,.70);
}

/* No cookie yet → follow the OS, but ONLY while the user hasn't chosen: scoped to
   :root:not([data-theme]) so an explicit light choice always wins over a dark OS.
   Same values as the block above (kept in sync deliberately — a no-JS first paint
   can't read a shared JS constant). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --green:          #2f9e6b;
    --green-hover:    #37b57c;
    --green-dot:      #4cd08f;
    --green-tint:     rgba(76,208,143,.14);
    --red:            #ec5766;
    --red-hover:      #ff6b7a;
    --red-dot:        #ff5a67;
    --red-tint:       rgba(236,87,102,.16);
    --red-tint-border:rgba(236,87,102,.38);
    --red-tint-soft:  rgba(236,87,102,.14);
    --warn:           #f0a52a;
    --warn-text:      #d9b25f;
    --chart-profit-1: #d9a520;
    --chart-profit-2: #ffc233;
    --bg:             #14161a;
    --surface:        #1e2126;
    --raised:         #2b2f37;
    --hover:          #2b2f37;
    --warm-panel:     #221f1a;
    --border:         #333a44;
    --text:           #e8e6e1;
    --text-muted:     #9aa0a8;
    --on-accent:      #ffffff;
    --backdrop:       rgba(0,0,0,.66);
    --shadow:         0 1px 4px rgba(0,0,0,.45);
    --shadow-md:      0 4px 16px rgba(0,0,0,.55);
    --shadow-toast:   0 4px 12px rgba(0,0,0,.60);
    --shadow-menu:    0 8px 28px rgba(0,0,0,.60);
    --shadow-modal:   0 12px 40px rgba(0,0,0,.70);
  }
}

/* ── Base ────────────────────────────────────────────────────────────────── */
html { font-size: 16px; }

body {
  font-family: var(--font);
  direction: rtl;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

/* ── Long-press policy (app shell) ─────────────────────────────────────────────
   Rendered app text does not select on long-press, and iOS shows no callout
   («کپی / جست‌وجو») over it — the page should feel like an app screen, not a
   document. Explicit owner request, with an explicit accessibility tradeoff:
   users lose the ability to select and copy figures, names and dates out of the
   UI, and lose the iOS lookup/share menu on them. That is accepted here; it is a
   product decision, not a default worth copying.
   Two things this must NOT do, hence the shape below:
   • Editing keeps working. `user-select:none` INHERITS, and on iOS an inherited
     `none` is enough to break the caret and the paste menu inside a field, so
     every form control — and anything rendered inside one — restates
     `text`/`default`. Without that second rule this would quietly make the app
     unusable, not just unselectable.
   • Scrolling and gestures keep working. No `touch-action` is set anywhere here:
     selection is not a touch-action concern, and even `pan-x pan-y` would fight
     the drag-to-close sheet and the swipe rows, which own their own
     `touch-action` on purpose. Nothing below blocks a scroll, a swipe or a
     modal gesture. */
body {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
input, textarea, select, [contenteditable],
input *, textarea *, select *, [contenteditable] * {
  -webkit-touch-callout: default;
  -webkit-user-select: text;
  user-select: text;
}

/* ── App shell ───────────────────────────────────────────────────────────── */
.app {
  max-width: 720px;
  margin: 0 auto;
  padding: 1rem 1rem 3rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* ── Header ──────────────────────────────────────────────────────────────── */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: .25rem 0 .25rem;
  border-bottom: 2px solid var(--green);
}
.app-title {
  font-size: 1.4rem;
  font-weight: 700;
  color: var(--green);
  letter-spacing: .02em;
}
.logout-link {
  font-size: .85rem;
  color: var(--text-muted);
  text-decoration: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .25rem .6rem;
  transition: background .12s;
}
.logout-link:hover { background: var(--bg); }

/* ── Zones ───────────────────────────────────────────────────────────────── */
.zone {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: .9rem;
}

/* ── Search zone ─────────────────────────────────────────────────────────── */
.search-input {
  width: 100%;
  padding: .65rem .85rem;
  font-family: var(--font);
  font-size: 1rem;
  direction: rtl;
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  outline: none;
  transition: border-color .15s;
}
.search-input:focus { border-color: var(--green); background: var(--raised); }
.search-input::placeholder { color: var(--text-muted); }

#search-results { margin-top: .5rem; }

.search-result {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .55rem .75rem;
  border-radius: 6px;
  cursor: pointer;
  transition: background .12s;
}
.search-result:hover { background: var(--bg); }

.dot {
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}
.dot-green { background: var(--green-dot); }
.dot-red   { background: var(--red-dot); }
.dot-gray  { background: var(--border); }

.result-title { flex: 1; font-size: .95rem; }
.result-price { font-size: .85rem; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* ── Action zone (boxed panel, like the search box) ──────────────────────── */
.zone-actions { padding: .9rem; }
.action-buttons { display: flex; gap: .75rem; }

.btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  padding: .8rem 1rem;
  font-family: var(--font);
  font-size: 1rem;
  font-weight: 700;
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: background .15s, transform .1s;
  user-select: none;
}
.btn:active { transform: scale(.97); }

.btn-icon { font-size: 1.4rem; line-height: 1; }

.btn-add  { background: var(--green); color: var(--on-accent); }
.btn-add:hover  { background: var(--green-hover); }

.btn-sell { background: var(--red); color: var(--on-accent); }
.btn-sell:hover { background: var(--red-hover); }

/* ── Inline panel (forms) ────────────────────────────────────────────────── */
#panel:not(:empty) {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  border: 1.5px solid var(--border);
  padding: 1rem;
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: .85rem;
}
.panel-title { font-size: 1.05rem; font-weight: 700; }
.panel-close {
  background: none;
  border: none;
  font-size: 1.3rem;
  cursor: pointer;
  color: var(--text-muted);
  line-height: 1;
  padding: .1rem .3rem;
  border-radius: 4px;
}
.panel-close:hover { background: var(--bg); }

/* ── Forms ───────────────────────────────────────────────────────────────── */
.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .65rem;
}
.form-grid .full { grid-column: 1 / -1; }

.field { display: flex; flex-direction: column; gap: .25rem; }
.field label { font-size: .85rem; font-weight: 500; color: var(--text-muted); }

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: .55rem .7rem;
  font-family: var(--font);
  font-size: .95rem;
  direction: rtl;
  border: 1.5px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  outline: none;
  transition: border-color .15s;
}
.field input:focus,
.field select:focus,
.field textarea:focus { border-color: var(--green); background: var(--raised); }
.field textarea { resize: vertical; min-height: 72px; }

/* ── Password show/hide (login) ──────────────────────────────────────────── */
.password-field { position: relative; }
.password-field input { padding-inline-end: 2.4rem; }
.password-toggle {
  position: absolute;
  inset-inline-end: .3rem;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  padding: .35rem;
  border-radius: 6px;
  min-width: 32px;
  min-height: 32px;
}
.password-toggle:hover { background: var(--bg); }
.password-toggle:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }

.form-actions { display: flex; gap: .5rem; margin-top: .75rem; }

.btn-submit {
  padding: .6rem 1.2rem;
  font-family: var(--font);
  font-size: .95rem;
  font-weight: 700;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: background .15s;
}
.btn-submit-green { background: var(--green); color: var(--on-accent); }
.btn-submit-green:hover { background: var(--green-hover); }
.btn-submit-red   { background: var(--red); color: var(--on-accent); }
.btn-submit-red:hover   { background: var(--red-hover); }

.btn-cancel {
  padding: .6rem 1rem;
  font-family: var(--font);
  font-size: .95rem;
  border: 1.5px solid var(--border);
  border-radius: 6px;
  background: none;
  cursor: pointer;
  color: var(--text-muted);
  transition: background .15s;
}
.btn-cancel:hover { background: var(--bg); }

/* ── Reports zone ────────────────────────────────────────────────────────── */
.zone-reports { padding: 1rem; }

.reports-title {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
  border-bottom: 2px solid var(--green);
  padding-bottom: .3rem;
  margin-bottom: .85rem;
}

.report-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: .65rem;
}

.report-card {
  background: var(--bg);
  border-radius: 8px;
  padding: .75rem .9rem;
  border: 1px solid var(--border);
}
.report-card-label { font-size: .8rem; color: var(--text-muted); margin-bottom: .2rem; }
.report-card-value { font-size: 1.1rem; font-weight: 700; }
.report-card-sub   { font-size: .8rem; color: var(--text-muted); margin-top: .1rem; }

/* Compact half-height cards for the four headline metrics — always two
   columns, on mobile and desktop alike (not width-gated). */
.report-grid.report-grid-compact { grid-template-columns: 1fr 1fr; gap: .5rem; }
.report-card-compact { padding: .5rem .65rem; }
.report-card-compact .report-card-label { font-size: .72rem; margin-bottom: .1rem; }
.report-card-compact .report-card-value { font-size: .95rem; }
.report-card-compact .report-card-sub   { font-size: .72rem; margin-top: 0; }
.report-card-compact .report-card-more  { font-size: .68rem; margin-top: .2rem; }
.report-card-wide { margin-top: .5rem; }

/* ── Auth page ───────────────────────────────────────────────────────────── */
.auth-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
.auth-box {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 2rem;
  width: 100%;
  max-width: 360px;
}
.auth-title {
  text-align: center;
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--green);
  margin-bottom: .4rem;
}
.auth-tagline {
  text-align: center;
  font-size: .95rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: .3rem;
}
.auth-subtitle {
  text-align: center;
  font-size: .82rem;
  color: var(--text-muted);
  line-height: 1.6;
  margin-bottom: 1.4rem;
}
.auth-error {
  background: var(--red-tint);
  border: 1px solid var(--red-tint-border);
  color: var(--red);
  border-radius: 6px;
  padding: .5rem .75rem;
  font-size: .9rem;
  margin-bottom: .75rem;
}

/* ── App-lock unlock screen: four-box PIN entry (client.js-enhanced) ────────
   #pin-boxes-wrap starts `hidden` in the markup, so a no-JS browser never
   shows this at all — client.js is what reveals it and hides the single
   #pin-fallback-field it stands in for. A wrong PIN re-renders the whole page
   (.auth-error above is untouched by this), so the boxes always come back
   empty; nothing here needs its own error-reset logic.
   .pin-boxes is a fixed LEFT-TO-RIGHT island inside the RTL .auth-box: a
   four-digit code reads in one direction regardless of page direction, same
   as the digits inside the single fallback field it replaces. */
/* `.field` sets its own display value, which outranks the browser's weak
   user-agent [hidden] declaration; make the progressive-enhancement swap
   unambiguous without affecting any other field. */
.auth-box #pin-fallback-field[hidden],
.auth-box #pin-boxes-wrap[hidden] { display: none !important; }
.pin-boxes-label {
  display: block;
  font-size: .85rem;
  font-weight: 500;
  color: var(--text-muted);
  margin-bottom: .35rem;
}
.pin-boxes {
  direction: ltr;
  display: flex;
  justify-content: center;
  gap: .6rem;
}
/* type="tel" (see app-lock.js) is not natively masked the way type="password"
   is, so the lock screen's two PIN controls mask their own digits here — and
   ONLY here: this must never spread to Login's password field (already
   natively masked, untouched) or any other input in the app. */
.auth-box #unlock-pin,
.auth-box .pin-box {
  -webkit-text-security: disc;
}
.pin-box {
  width: 3rem;
  height: 3.2rem;
  padding: 0;
  font-family: var(--font);
  font-size: 1.4rem;
  text-align: center;
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  outline: none;
  transition: border-color .15s, background .15s;
}
.pin-box:focus-visible {
  border-color: var(--green);
  background: var(--raised);
  outline: 2px solid var(--green);
  outline-offset: 2px;
}
/* The brief "four digits typed" cue client.js shows before it submits —
   cosmetic only, never proof the PIN was correct; only the server's redirect
   (or the .auth-error re-render above) decides that. */
.pin-boxes-ready .pin-box {
  border-color: var(--green);
  background: var(--green-tint);
}
.pin-boxes-status {
  display: block;
  text-align: center;
  margin-top: .5rem;
  min-height: 1.2em;
  color: var(--green);
}
/* Narrow phones: the 360px .auth-box leaves too little room for four 3rem
   boxes plus gaps once its own 2rem padding is subtracted — shrink both. */
@media (max-width: 380px) {
  .pin-boxes { gap: .4rem; }
  .pin-box { width: 2.6rem; height: 2.9rem; font-size: 1.25rem; }
}

/* ── Brand mark ────────────────────────────────────────────────────────────────
   The logo is a transparent PNG in fixed navy/orange — the one image in the app
   whose colours can't follow a token. Nothing here paints a fill behind it, so
   it never lands as a white rectangle on the warm --bg or on a dark surface. */
.brand-logo {
  display: block;
  flex: 0 0 auto;
  background: var(--logo-plate) no-repeat 50% 50%;
}
/* Dark theme is the exception: the navy half sinks into a near-black surface.
   A round plate — opaque to 78% of the half-width, faded fully out by the edge —
   restores contrast with the mark's own colours intact. It has no edge and no
   corners, so it reads as a coin under the mark, never as a box around it.
   Overriding --logo-plate here (rather than in the theme token blocks) keeps the
   only rule that consumes it and the only rule that sets it side by side. */
:root[data-theme="dark"] .brand-logo {
  --logo-plate: radial-gradient(circle farthest-side,
                  rgba(244,242,237,.93) 0 78%, rgba(244,242,237,0) 100%);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .brand-logo {
    --logo-plate: radial-gradient(circle farthest-side,
                    rgba(244,242,237,.93) 0 78%, rgba(244,242,237,0) 100%);
  }
}

/* Login — mark stacked over the wordmark, inside the <h1>. */
.brand-lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .5rem;
}
/* Prominent without crowding the 360px .auth-box: ~86px on a 360px phone,
   capped at 104px so it doesn't dominate a desktop viewport. */
.brand-logo-lg { width: clamp(76px, 24vw, 104px); height: auto; }

/* Chooser header — mark beside the wordmark on one line. */
.brand-inline { display: flex; align-items: center; gap: .5rem; }
/* em, not px: sized off .app-title, so the mark tracks the heading on any
   viewport. The source carries ~30% internal padding, so 1.75em of box paints
   a ~27px mark against 22px text. */
.brand-logo-sm { width: 1.75em; height: 1.75em; }

/* ── Utilities ───────────────────────────────────────────────────────────── */
.loading { text-align: center; color: var(--text-muted); padding: 1.5rem; }
.text-muted { color: var(--text-muted); font-size: .9rem; }
.empty-state { text-align: center; color: var(--text-muted); padding: 2rem 1rem; }

/* HTMX loading indicator */
.htmx-indicator { opacity: 0; transition: opacity .2s; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { opacity: 1; }

/* ── Search results ──────────────────────────────────────────────────────── */
.search-result-list { display: flex; flex-direction: column; gap: 2px; }

/* The 48px track is the thumbnail column. It is a FIXED track on purpose: every
   row reserves it whether or not the item has a photo, so a mixed list keeps one
   straight title edge instead of jogging left and right. */
.search-result {
  display: grid;
  grid-template-columns: 14px 48px 1fr auto auto;
  align-items: center;
  gap: .5rem;
  padding: .4rem .6rem;
  border-radius: 6px;
  cursor: pointer;
  transition: background .1s;
  user-select: none;
}
.search-result:hover { background: var(--bg); }

/* ── Row thumbnail ───────────────────────────────────────────────────────────
   48px: big enough to recognise a product at a glance, small enough that the row
   stays a LIST row. Height is pinned equal to width so a portrait photo cannot
   stretch the row taller than its neighbours — object-fit crops instead. */
.row-thumb {
  width: 48px;
  height: 48px;
  border-radius: 5px;
  object-fit: cover;
  background: var(--bg);
  display: block;
  flex-shrink: 0;
}
/* Photo-less rows: hold the column open, but stay quiet — a hard empty box on
   every row would read as a broken image. */
.row-thumb-empty {
  border: 1px dashed var(--border);
  background: transparent;
  opacity: .35;
}

/* On a phone the 48px thumb has to come out of SOMETHING, and left alone it comes
   out of the title — measured at 375px, the title fell from 116px to 60px, i.e.
   «گوشی سامسونگ گلکسی» clipped to «گوشی …».
   Price and status are the slack: they are two narrow columns holding short
   strings, so stacking them into one returns ~60px to the title — more than the
   thumb took — and costs no information. The two stacked lines are still shorter
   than the 48px thumb, so the row height doesn't grow because of this. */
@media (max-width: 480px) {
  .search-result {
    grid-template-columns: 14px 48px 1fr auto;
    grid-template-rows: auto auto;
    /* Tighter gutters than the desktop row: with five tracks the 8px gaps and
       side padding add up to real title width on a 375px screen. */
    column-gap: .4rem;
    row-gap: 0;
    padding: .4rem .45rem;
  }
  .search-result > .dot       { grid-column: 1; grid-row: 1 / 3; }
  .search-result > .row-thumb { grid-column: 2; grid-row: 1 / 3; }
  .search-result > .result-title  { grid-column: 3; grid-row: 1 / 3; }
  .search-result > .result-price  { grid-column: 4; grid-row: 1; text-align: left; }
  .search-result > .result-status { grid-column: 4; grid-row: 2; text-align: left; }
}

.result-title  { font-size: .95rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result-price  { font-size: .82rem; color: var(--text-muted); white-space: nowrap; }
.result-status { font-size: .78rem; color: var(--text-muted); white-space: nowrap; }

/* ── Detail-sheet photo ──────────────────────────────────────────────────────
   Capped, never full-bleed. max-height is the binding constraint: the point of
   the sheet is the numbers underneath, and a tall portrait photo must not push
   them off screen. 340px keeps price/profit visible on a 375×667 phone.
   The wrapper's min-height reserves space so the details don't jump when the
   lazy image arrives. */
.detail-photo-wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: .75rem;
  background: var(--bg);
  border-radius: 8px;
  overflow: hidden;
  min-height: 120px;
}
.detail-photo {
  max-width: 100%;
  max-height: 340px;
  width: auto;
  height: auto;
  /* The server now always renders the "full" variant as an exact square
     (photos.js: fit:'cover' on new uploads), so a plain cover fill here can
     never crop content differently than what was already baked into the
     file — no letterboxing to prevent, no offset math needed. */
  object-fit: cover;
  aspect-ratio: 1 / 1;
  display: block;
}

/* ── Photo controls (buy form + detail sheet) ────────────────────────────── */
.photo-picker { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.photo-edit {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  margin-bottom: .85rem;
}
/* A styled <label> standing in for the hidden file input. */
.btn-photo {
  display: inline-block;
  padding: .35rem .7rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  font-size: .82rem;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: background .1s;
}
.btn-photo:hover { background: var(--border); }
.btn-photo-danger { color: var(--red); }
.photo-name, .photo-hint { font-size: .78rem; }
.photo-busy { font-size: .78rem; color: var(--text-muted); }

.photo-preview { margin-top: .5rem; }
/* The pre-upload preview is a confirmation, not a viewer — keep it thumbnail-ish. */
.photo-preview-img {
  max-width: 110px;
  max-height: 110px;
  border-radius: 6px;
  object-fit: cover;
  display: block;
}

/* ── Square photo crop panel (client.js: dallalCropOpen/Confirm/Cancel) ────
   Canvas is the crop "stage": fixed intrinsic 280×280 so client.js's pixel
   math is exact, sized down to fit narrow phones via CSS (max-width, still
   square because height follows width at aspect-ratio 1/1). touch-action:none
   stops the browser from scrolling the page while a finger drags the photo —
   the canvas's own pointer handlers own that gesture entirely. */
.photo-crop { margin-top: .6rem; }
.photo-crop-canvas {
  display: block;
  width: 100%;
  max-width: 280px;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  border-radius: 8px;
  background: var(--bg);
  border: 1.5px solid var(--border);
  touch-action: none;
  cursor: grab;
  user-select: none;
}
.photo-crop-canvas:active { cursor: grabbing; }
.photo-crop-zoom { display: block; width: 100%; max-width: 280px; margin: .6rem auto 0; }
.photo-crop-actions {
  display: flex;
  gap: .5rem;
  justify-content: center;
  margin-top: .5rem;
}

/* Photo-present marker in the warehouse table (see photoGlyph()). */
/* Warehouse name cell: a ~48×48 thumb (or aligned empty box) leading the stacked
   name + code + category, so a photo-less row stays flush. min-width:0 +
   overflow-wrap let the text shrink/wrap instead of forcing the column wide (and
   the table into horizontal scroll) at 375px. */
.wh-title-cell { display: flex; align-items: flex-start; gap: .5rem; }
.wh-title-cell > .row-thumb { margin-top: .1rem; }
.wh-title-text { min-width: 0; overflow-wrap: anywhere; }

/* ── Item detail ─────────────────────────────────────────────────────────── */
.detail-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .45rem .9rem;
  font-size: .93rem;
}
.detail-grid dt {
  color: var(--text-muted);
  font-weight: 500;
  white-space: nowrap;
  padding-top: .05rem;
}
.detail-grid dd { font-weight: 500; }

.sold-badge {
  display: inline-block;
  background: var(--red-tint);
  color: var(--red);
  border-radius: 6px;
  padding: .35rem .75rem;
  font-size: .88rem;
  font-weight: 700;
}

/* ── Form helpers ────────────────────────────────────────────────────────── */
.req { color: var(--red); font-size: .8rem; }

.field-error {
  display: block;
  font-size: .82rem;
  color: var(--red);
  margin-top: .2rem;
}

.item-form { margin-top: .25rem; }

/* ── Rate banner ─────────────────────────────────────────────────────────── */
.rate-banner {
  padding: .4rem .65rem;
  background: var(--bg);
  border-radius: 6px;
  font-size: .88rem;
  border-right: 3px solid var(--green);
}
.rate-banner.rate-stale { border-right-color: var(--warn); }

.rate-banner-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.rate-meta { display: flex; align-items: center; gap: .45rem; }
.rate-freshness { font-size: .78rem; color: var(--text-muted); }
.rate-stale .rate-freshness { color: var(--warn); }

.rate-refresh-btn {
  padding: .15rem .5rem;
  font-family: var(--font);
  font-size: .75rem;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: none;
  cursor: pointer;
  color: var(--text-muted);
  transition: background .12s, color .12s;
  white-space: nowrap;
  line-height: 1.6;
}
.rate-refresh-btn:hover:not([disabled]) { background: var(--hover); color: var(--text); }
.rate-refresh-btn[disabled] { opacity: .55; cursor: not-allowed; }

.rate-msg {
  font-size: .78rem;
  color: var(--text-muted);
  margin-top: .2rem;
}
.rate-msg-error { color: var(--red); }

/* ── Reports ─────────────────────────────────────────────────────────────── */
.report-period { margin-top: .9rem; }
.report-period + .report-period { margin-top: 1.1rem; }

.report-period-label {
  font-size: .82rem;
  font-weight: 700;
  color: var(--text-muted);
  border-right: 3px solid var(--green);
  padding-right: .5rem;
  margin-bottom: .5rem;
}

.report-note {
  font-size: .72rem;
  color: var(--text-muted);
  margin-top: .25rem;
  line-height: 1.4;
}

/* ── Report tabs / drill-down / chart ────────────────────────────────────── */
.report-tabs {
  display: flex;
  gap: .4rem;
  margin: .3rem 0 .2rem;
}
.report-tab {
  flex: 1;
  padding: .4rem .5rem;
  font-family: var(--font);
  font-size: .82rem;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text-muted);
  cursor: pointer;
  transition: all .12s;
}
.report-tab.active { background: var(--green); color: var(--on-accent); border-color: var(--green); font-weight: 700; }

.report-card-link { cursor: pointer; transition: border-color .12s, background .12s; position: relative; }
.report-card-link:hover { border-color: var(--green); background: var(--raised); }
.report-card-more { font-size: .72rem; color: var(--green); margin-top: .3rem; font-weight: 600; }

.chart-bars {
  display: flex;
  align-items: flex-end;
  gap: .3rem;
  height: 128px;
  padding-top: .4rem;
}
.chart-col { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: .25rem; height: 100%; }
.chart-bar-track { flex: 1; width: 100%; display: flex; align-items: flex-end; }
.chart-bar {
  width: 100%;
  background: linear-gradient(to top, var(--green), var(--green-dot));
  border-radius: 4px 4px 0 0;
  min-height: 2px;
  transition: height .3s;
}
/* Full Persian month names (e.g. «اردیبهشت») must stay readable and never
   clip/overlap at 375px — allow wrapping to 2 lines instead of truncating
   or forcing the row wider than the viewport. */
.chart-label {
  font-size: .64rem;
  color: var(--text-muted);
  white-space: normal;
  overflow-wrap: break-word;
  text-align: center;
  line-height: 1.15;
  max-width: 100%;
}

.cat-breakdown {
  margin-top: .6rem;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.cat-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: .5rem;
  padding: .35rem .55rem;
  background: var(--bg);
  border-radius: 5px;
  font-size: .88rem;
}
.cat-name  { font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-count { color: var(--text-muted); font-size: .82rem; white-space: nowrap; }
.cat-value { font-size: .82rem; white-space: nowrap; }

/* ── Sale form ───────────────────────────────────────────────────────────── */
.sale-item-info {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .6rem;
  background: var(--bg);
  border-radius: 6px;
  margin-bottom: .75rem;
  font-size: .9rem;
}
.sale-item-title { flex: 1; font-weight: 600; }
.sale-item-price { color: var(--text-muted); font-size: .85rem; white-space: nowrap; }

/* ── Profit display ──────────────────────────────────────────────────────── */
.profit-pos   { color: var(--green); font-weight: 700; }
.profit-neg   { color: var(--red);   font-weight: 700; }
.profit-label { font-weight: 600; }

/* ── Toast notification ──────────────────────────────────────────────────── */
#toast { position: fixed; bottom: 1.25rem; left: 50%; transform: translateX(-50%); z-index: 100; pointer-events: none; }

.toast {
  background: var(--green);
  color: var(--on-accent);
  font-size: .95rem;
  font-weight: 500;
  padding: .6rem 1.25rem;
  border-radius: 8px;
  box-shadow: var(--shadow-toast);
  white-space: nowrap;
  animation: toastFade 2.5s ease forwards;
}

@keyframes toastFade {
  0%   { opacity: 0; transform: translateY(6px); }
  15%  { opacity: 1; transform: translateY(0); }
  75%  { opacity: 1; }
  100% { opacity: 0; }
}

/* ── Tagline ─────────────────────────────────────────────────────────────── */
/* .logout-btn/.logout-icon were removed: «خروج از حساب» moved off the page body
   and into the header ☰ panel (.menu-item). */

.tagline {
  text-align: center;
  color: var(--text-muted);
  font-size: .92rem;
  margin: -.25rem 0 .25rem;
}

.back-link {
  font-size: .85rem;
  color: var(--green);
  text-decoration: none;
  font-weight: 600;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: .3rem .7rem;
  transition: background .12s;
}
.back-link:hover { background: var(--bg); }
.header-spacer { width: 4.5rem; }

/* ── Computed helper lines (buy total, USD-of-price, worth) ───────────────── */
.computed-total {
  font-size: .9rem;
  color: var(--text);
  min-height: 1.2em;
  margin-top: .1rem;
}
.hint { color: var(--text-muted); font-size: .82rem; font-weight: 400; }
.usd-inline, .usd-suffix { color: var(--green); font-weight: 600; }
.usd-suffix { font-size: .82rem; }
.worth-line {
  font-size: .85rem;
  color: var(--text);
  background: var(--bg);
  border-radius: 6px;
  padding: .45rem .6rem;
  margin-bottom: .7rem;
  border-right: 3px solid var(--green);
}

/* The .summary-* rules were removed with the main screen's figures card — the
   whole point of that change is that no styling for on-screen money should exist
   on the home route. The reports page has its own .report-* rules. */

/* ── Title autocomplete ──────────────────────────────────────────────────── */
.suggest-box:not(:empty) { margin-top: 2px; }
.suggest-list {
  border: 1.5px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow);
}
.suggest-list { max-height: 15rem; overflow-y: auto; }
.suggest-item {
  padding: .6rem .7rem;
  cursor: pointer;
  font-size: .95rem;
  border-bottom: 1px solid var(--border);
}
.suggest-item:last-child { border-bottom: none; }
/* Selection is the obvious path (§6): hover AND keyboard focus both highlight the
   row, with a coloured leading bar so the active choice is unmistakable. */
.suggest-item:hover,
.suggest-item:focus {
  background: var(--bg);
  outline: none;
  box-shadow: inset 3px 0 0 var(--green);
}

/* ── Round 4A: brand/model, approximate-rate note ────────────────────────── */
/* Brand/model sit beside or under the title everywhere an item is listed.
   Legacy items have neither and render nothing, so rows stay single-line. */
.result-brand { font-size: .78rem; }
.wh-brand { font-size: .75rem; line-height: 1.3; }
.item-idline { margin: -.35rem 0 .5rem; font-size: .82rem; }

/* Shown when the prefilled rate is NOT that day's own rate (market closed, or
   the date predates our history). Amber, not red: it's a caveat, not an error —
   the value is usable and the field stays editable. */
.rate-note {
  display: block;
  margin-top: .25rem;
  font-size: .75rem;
  color: var(--text-muted);   /* neutral/informational: live / reference / recorded / entered */
  line-height: 1.4;
}
.rate-note:empty { display: none; }   /* no stray gap when there's no label */
.rate-note-warn { color: var(--warn-text); }   /* amber caution: «تقریبی» — not that date's own rate */

/* ── Varied per-unit prices + mode pills ─────────────────────────────────── */
/* Stack the two options vertically — full-width, tappable rows, never clipped. */
.radio-row { display: flex; flex-direction: column; gap: .5rem; }
.radio-pill {
  display: flex;
  align-items: center;
  gap: .55rem;
  width: 100%;
  min-height: 44px;
  padding: .5rem .75rem;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: .9rem;
  line-height: 1.4;
  cursor: pointer;
  background: var(--bg);
}
.radio-pill input { flex-shrink: 0; width: 1.05rem; height: 1.05rem; }
.radio-pill:has(input:checked) { border-color: var(--green); background: var(--raised); font-weight: 600; }

.unit-prices { display: flex; flex-direction: column; gap: .4rem; }
.unit-price-field {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  align-items: center;
  gap: .5rem;
}
.unit-price-label { font-size: .82rem; color: var(--text-muted); white-space: nowrap; }
.unit-price-field input[type=text] {
  width: 100%;
  min-width: 0;
  padding: .5rem .7rem;
  font-family: var(--font);
  font-size: .95rem;
  direction: rtl;
  border: 1.5px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  outline: none;
}
.unit-price-field input:focus { border-color: var(--green); background: var(--raised); }

.group-badge {
  display: inline-block;
  background: var(--green);
  color: var(--on-accent);
  font-size: .7rem;
  font-weight: 700;
  border-radius: 10px;
  padding: .05rem .45rem;
  margin-right: .3rem;
}

/* ── Date field + Jalali calendar ────────────────────────────────────────── */
.date-field { position: relative; }
.date-input { cursor: pointer; background: var(--bg); }
.date-cal {
  position: absolute;
  left: .6rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: .95rem;
  pointer-events: none;
  opacity: .7;
}
.datepicker-pop:not(:empty) { margin-top: .4rem; }

.dp {
  border: 1.5px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  padding: .5rem;
  box-shadow: var(--shadow);
}
.dp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: .4rem;
}
.dp-title { font-weight: 700; font-size: .92rem; }
.dp-nav {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  width: 1.9rem;
  height: 1.9rem;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  color: var(--green);
}
.dp-nav:hover { background: var(--hover); }
.dp-week, .dp-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.dp-week span {
  text-align: center;
  font-size: .72rem;
  color: var(--text-muted);
  padding: .2rem 0;
}
.dp-day {
  aspect-ratio: 1 / 1;
  border: none;
  background: var(--bg);
  border-radius: 6px;
  font-family: var(--font);
  font-size: .85rem;
  cursor: pointer;
  color: var(--text);
  transition: background .1s;
}
.dp-day:hover { background: var(--green); color: var(--on-accent); }
.dp-sel { background: var(--green); color: var(--on-accent); font-weight: 700; }
.dp-empty { aspect-ratio: 1 / 1; }
.dp-foot { text-align: left; margin-top: .35rem; }
.dp-close {
  background: none; border: none; cursor: pointer;
  color: var(--text-muted); font-size: .8rem; font-family: var(--font);
}

/* ── Header ☰ menu ───────────────────────────────────────────────────────── */
/* The wrap is the positioning context for the dropdown; the header itself must
   NOT become one, or the panel would be clipped by its border-bottom. */
.menu-wrap { position: relative; }

.menu-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  /* 40px: a comfortable thumb target. The glyph alone is far too small to hit. */
  width: 40px;
  height: 40px;
  background: none;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  color: var(--text);
  font-family: var(--font);
  transition: background .12s, border-color .12s;
}
.menu-btn:hover { background: var(--bg); border-color: var(--green); }
.menu-btn[aria-expanded="true"] { background: var(--bg); border-color: var(--green); }
.menu-btn-icon { font-size: 1.15rem; line-height: 1; }

.menu-panel {
  position: absolute;
  top: calc(100% + .4rem);
  /* Anchored to the button's own edge (its LEFT, since the button sits at the
     header's left in RTL) so the panel opens inward and cannot overflow the
     viewport on a narrow screen. */
  left: 0;
  min-width: 200px;
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-menu);
  padding: .35rem;
  z-index: 300;
  animation: menuIn .12s ease;
}
.menu-panel[hidden] { display: none; }
@keyframes menuIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.menu-user {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .55rem;
  border-radius: 6px;
  text-decoration: none;
  color: inherit;
  transition: background .12s;
}
.menu-user:hover { background: var(--bg); }
.menu-user-icon { font-size: 1.1rem; }
.menu-user-text { display: flex; flex-direction: column; min-width: 0; }
.menu-user-label { font-size: .7rem; color: var(--text-muted); }
.menu-user-name {
  font-size: .92rem;
  font-weight: 700;
  /* A long username must not stretch the panel off-screen. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.menu-user-meta { font-size: .68rem; color: var(--text-muted); line-height: 1.5; }
.menu-sep { height: 1px; background: var(--border); margin: .25rem .3rem; }

.menu-item {
  display: block;
  padding: .55rem;
  border-radius: 6px;
  text-decoration: none;
  color: var(--text);
  font-size: .88rem;
  transition: background .12s;
}
.menu-item:hover { background: var(--bg); }
.menu-item-danger { color: var(--red); }

/* «تنظیمات» row only: keeps the gear glyph and label on one line so the row
   never needs a second line of height — scoped to this class alone, every
   other .menu-item (account/theme/logout) is untouched. */
.menu-item-settings-ico {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
}

/* «حالت شب» row: label + glyph + switch. The whole row is a ≥44px tap target. */
.menu-theme {
  display: flex;
  align-items: center;
  gap: .55rem;
  min-height: 44px;
  cursor: pointer;
  user-select: none;
}
.menu-theme:hover { background: var(--bg); }
.menu-theme-glyph { font-size: 1.05rem; line-height: 1; }
.menu-theme-label { flex: 1; color: var(--text); }

/* Panel top row: the active account and «حالت شب» on one line, the account at
   the leading edge (RTL: right) and the switch at the opposite one (RTL: left).
   Every rule here is scoped to .menu-top, so .menu-item and .menu-theme keep the
   full-row geometry they have everywhere else in the panel and on every page. */
.menu-top {
  display: flex;
  align-items: center;
  gap: .25rem;
  /* The row imposes no width of its own: the panel keeps the 200px intrinsic
     width its own rule gives it, and the hamburger stays as narrow as it was.
     The switch half cannot shrink — glyph + label + the 40px switch come to
     roughly 8.5rem — so it is the account that yields, via the min-width:0 and
     ellipsis below, rather than the row growing the panel to fit it. */
  min-inline-size: 0;
}
/* The account is the side that gives way: min-width:0 lets it shrink past its
   own content, so a long username hits the ellipsis .menu-user-name already
   declares rather than pushing the switch out of the panel. */
.menu-top .menu-user { flex: 1 1 auto; min-width: 0; }
/* The switch sizes to its content now that it has a neighbour instead of a row.
   It keeps min-height:44px from .menu-theme, and it is still the <label> — glyph
   and text included — that toggles, so the tap target stays a full one. */
.menu-top .menu-theme { flex: 0 0 auto; padding-inline: .4rem; gap: .4rem; }
/* No row left to fill, and no room to wrap onto a second line. */
.menu-top .menu-theme-label { flex: 0 0 auto; white-space: nowrap; }

/* Track + sliding thumb. Everything is a token, so it recolours with the theme
   like anything else. RTL-correct: the thumb rests at the START (right) edge and
   slides toward the END (left) when checked — driven by inset-inline-start. */
.switch { position: relative; flex-shrink: 0; width: 40px; height: 24px; }
.switch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.switch-track {
  display: block;
  width: 40px;
  height: 24px;
  border-radius: 999px;
  background: var(--border);
  transition: background .15s;
}
.switch-thumb {
  position: absolute;
  top: 3px;
  inset-inline-start: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--on-accent);
  box-shadow: var(--shadow);
  transition: inset-inline-start .15s;
}
.switch input:checked ~ .switch-track { background: var(--green); }
.switch input:checked ~ .switch-track .switch-thumb { inset-inline-start: 19px; }
.switch input:focus-visible ~ .switch-track { outline: 2px solid var(--green); outline-offset: 2px; }

/* ── Search + status toggle ──────────────────────────────────────────────── */
.search-form { display: flex; flex-direction: column; gap: .5rem; }

/* Segmented control. Real radios, visually hidden — the browser handles
   single-select and keyboard arrows; :checked drives the pill styling. */
.seg {
  display: flex;
  gap: .25rem;
  background: var(--bg);
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: .2rem;
}
.seg-item { flex: 1; margin: 0; }
.seg-item input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.seg-item span {
  display: block;
  text-align: center;
  padding: .35rem .2rem;
  border-radius: 6px;
  font-size: .8rem;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  transition: background .12s, color .12s;
  white-space: nowrap;
}
.seg-item input:checked + span {
  background: var(--surface);
  color: var(--text);
  font-weight: 700;
  box-shadow: var(--shadow);
}
/* The radio is invisible, so its focus ring has to be drawn on the label. */
.seg-item input:focus-visible + span { outline: 2px solid var(--green); outline-offset: 1px; }

/* ── Find-to-sell panel (fixed input, scrolling results) ─────────────────── */
/* Constant height is the whole point: the dialog's size no longer depends on how
   many results came back, so the backdrop has nothing to re-centre and the input
   cannot move under the user's finger. See renderFindToSell(). */
.find-panel {
  display: flex;
  flex-direction: column;
  height: 58vh;
  min-height: 260px;
}
.find-panel-hint { margin-bottom: .5rem; flex: 0 0 auto; }
.find-panel-input { flex: 0 0 auto; }

.results-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  margin-top: .5rem;
  /* Own the scroll: a flick over the results must not chain out to the page. */
  overscroll-behavior: contain;
}

/* ── Nav cards (reports + warehouse) ─────────────────────────────────────── */
.nav-cards { display: flex; flex-direction: column; gap: .6rem; }

/* ── Nav card (warehouse link) ───────────────────────────────────────────── */
.nav-card {
  display: flex;
  align-items: center;
  gap: .7rem;
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: .8rem .9rem;
  text-decoration: none;
  color: var(--text);
  transition: border-color .12s;
}
.nav-card:hover { border-color: var(--green); }
.nav-card-icon { font-size: 1.4rem; }
.nav-card-text { display: flex; flex-direction: column; flex: 1; }
.nav-card-title { font-weight: 700; font-size: .95rem; }
.nav-card-sub { font-size: .78rem; color: var(--text-muted); }
.nav-card-arrow { color: var(--green); font-size: 1.1rem; }

/* ── Public legal page ───────────────────────────────────────────────────── */
.legal-page { max-width: 760px; }
.legal-hero,
.legal-section {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1rem;
}
.legal-title { margin: 0; font-size: 1.4rem; color: var(--green); }
.legal-updated { margin: .4rem 0 0; color: var(--text-muted); font-size: .9rem; }
.legal-section h2,
.legal-section h3 { margin-top: 0; }
.legal-section h2 { color: var(--green); }
.legal-section p,
.legal-section li { line-height: 1.95; }
.legal-section ul,
.legal-section ol { padding-inline-start: 1.35rem; }

/* ── Top-level chooser (GET /) — مدیریت کسب‌وکار / مدیریت مالی شخصی ──────── */
.chooser-app { display: flex; flex-direction: column; }
.chooser-grid {
  display: flex;
  flex-direction: column;
  gap: .9rem;
  margin-top: 1.2rem;
}
.chooser-card {
  display: flex;
  align-items: center;
  gap: .7rem;
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: .8rem 1.1rem;
  text-decoration: none;
  color: var(--text);
  transition: border-color .12s, transform .12s;
}
.chooser-card:hover { border-color: var(--green); transform: translateY(-1px); }
.chooser-card-icon { font-size: 1.9rem; flex-shrink: 0; }
.chooser-card-text {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: .15rem;
  text-align: right;
}
.chooser-card-title { font-weight: 700; font-size: 1rem; }
.chooser-card-sub { font-size: .8rem; color: var(--text-muted); }
@media (min-width: 520px) {
  .chooser-grid { flex-direction: row; }
  .chooser-card { flex: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .chooser-card { transition: none; }
}

/* ── Product category management (/categories, Business-only) ─────────────── */
.cat-add-form { display: flex; gap: .5rem; margin-bottom: 1rem; flex-wrap: wrap; align-items: flex-start; }
.cat-add-form input { flex: 1; min-width: 160px; }
.cat-row-main { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.cat-rename-form { display: flex; gap: .4rem; flex: 1; min-width: 160px; }
.cat-rename-form input { flex: 1; }
/* The add input and its «+ افزودن دسته» button are one control pair, so they
 * share an exact 44px box (touch target) instead of each sizing themselves. */
.cat-add-form { align-items: center; }
.cat-add-form input,
.cat-add-form .btn-submit { box-sizing: border-box; height: 44px; min-height: 44px; }
.cat-add-form .btn-submit { align-self: center; }
#cat-body .txn-list > .txn-card { padding: .35rem .55rem; }
#cat-body .cat-row-main { gap: .35rem; }
#cat-body .cat-rename-form input { min-height: 32px; }
#cat-body .cat-row-main > .icon-btn { min-width: 32px; min-height: 32px; }

/* ── Staged editing (renderCategoriesStagedBody) ──────────────────────────────
 * Nothing on this page is saved until «ثبت تغییرات», so each row has to carry
 * its own pending state and the button has to say whether there is anything to
 * submit at all. */

/* The reports category-breakdown row is ALSO `.cat-row` (a three-column grid);
 * inside #cat-body a row is a stacked card, so undo that bleed. */
#cat-body .cat-row { display: block; font-size: inherit; }
#cat-body .cat-name-input { flex: 1; min-width: 140px; min-height: 32px; }
.cat-staged-hint { margin: 0 0 .8rem; }

/* An empty slot marked `hidden` must actually be hidden: .field-error sets
 * display:block, which otherwise beats the browser's own [hidden] rule. */
.field-error[hidden], .cat-badge[hidden], .cat-row-note[hidden] { display: none; }

.cat-badge {
  font-size: .74rem;
  font-weight: 700;
  padding: .1rem .45rem;
  border-radius: 999px;
  white-space: nowrap;
  background: var(--hover);
  color: var(--text-muted);
}
.cat-row[data-state="new"]     .cat-badge { background: var(--green); color: var(--on-accent); }
.cat-row[data-state="deleted"] .cat-badge { background: var(--red);   color: var(--on-accent); }
.cat-row[data-state="deleted"] .cat-name-input { text-decoration: line-through; color: var(--text-muted); }
.cat-row-note { display: block; margin-top: .25rem; font-size: .8rem; color: var(--warn-text); }

.cat-apply-bar { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-top: 1rem; }
.cat-apply-note { color: var(--text-muted); }
.cat-apply-error { margin-top: .4rem; }

/* Gray while nothing is staged, green the moment something is — the button is
 * this page's only statement that a change has not been saved yet. */
.cat-apply-btn { background: var(--green); color: var(--on-accent); }
.cat-apply-btn:hover:not(:disabled) { background: var(--green-hover); }
.cat-apply-btn:disabled {
  background: var(--border);
  color: var(--text-muted);
  cursor: not-allowed;
}

/* ── Channel management (/channels) — aliases of the category page above ─────
 * The staged channel page is the category page with channel-scoped hooks, so
 * every rule here mirrors its cat-* twin declaration-for-declaration: same
 * 44px add-control pair, same compact cards, same 32px inputs and icon
 * buttons, same badge/note/apply-bar treatment. Kept as a separate block
 * rather than appended to the cat-* selector lists so neither page can be
 * restyled by accident while editing the other — if you change a value there,
 * change it here.
 *
 * The one rule with no counterpart is `#cat-body .cat-row { display: block }`:
 * that undoes the reports category-breakdown grid, which also claims
 * `.cat-row`. `.chan-row` has no second meaning, so a bare `.txn-card` already
 * lays out exactly as a neutralised `.cat-row` does — there is nothing to undo.
 */
.chan-add-form { display: flex; gap: .5rem; margin-bottom: 1rem; flex-wrap: wrap; align-items: center; }
.chan-add-form input { flex: 1; min-width: 160px; }
.chan-row-main { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
/* The add input and its «+ افزودن مسیر» button are one control pair, so they
 * share an exact 44px box (touch target) instead of each sizing themselves. */
.chan-add-form input,
.chan-add-form .btn-submit { box-sizing: border-box; height: 44px; min-height: 44px; }
.chan-add-form .btn-submit { align-self: center; }
.chan-count { color: var(--text-muted); font-size: .82rem; white-space: nowrap; }

/* Compact cards — same figures as #cat-body. The two legacy-body classes are
 * listed too: renderChannelsBody() still borrows .cat-row-main/.cat-rename-form
 * and renders inside #channel-body, so without these it would be the one body
 * in the app laying its rows out at full padding. */
#channel-body .txn-list > .txn-card { padding: .35rem .55rem; }
#channel-body .chan-row-main,
#channel-body .cat-row-main { gap: .35rem; }
#channel-body .cat-rename-form input { min-height: 32px; }
#channel-body .chan-row-main > .icon-btn,
#channel-body .cat-row-main > .icon-btn { min-width: 32px; min-height: 32px; }
#channel-body .chan-name-input { flex: 1; min-width: 140px; min-height: 32px; }
.chan-staged-hint { margin: 0 0 .8rem; }

/* An empty slot marked `hidden` must actually be hidden — .chan-badge and
 * .chan-row-note both set display, which otherwise beats the browser's own
 * [hidden] rule (.field-error[hidden] is handled with the category block). */
.chan-badge[hidden], .chan-row-note[hidden] { display: none; }

.chan-badge {
  font-size: .74rem;
  font-weight: 700;
  padding: .1rem .45rem;
  border-radius: 999px;
  white-space: nowrap;
  background: var(--hover);
  color: var(--text-muted);
}
.chan-row[data-state="new"]     .chan-badge { background: var(--green); color: var(--on-accent); }
.chan-row[data-state="deleted"] .chan-badge { background: var(--red);   color: var(--on-accent); }
.chan-row[data-state="deleted"] .chan-name-input { text-decoration: line-through; color: var(--text-muted); }
.chan-row-note { display: block; margin-top: .25rem; font-size: .8rem; color: var(--warn-text); }

.chan-apply-bar { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-top: 1rem; }
.chan-apply-note { color: var(--text-muted); }
.chan-apply-error { margin-top: .4rem; }

/* Gray while nothing is staged, green the moment something is — the button is
 * this page's only statement that a change has not been saved yet. */
.chan-apply-btn { background: var(--green); color: var(--on-accent); }
.chan-apply-btn:hover:not(:disabled) { background: var(--green-hover); }
.chan-apply-btn:disabled {
  background: var(--border);
  color: var(--text-muted);
  cursor: not-allowed;
}

/* ── Warehouse table ─────────────────────────────────────────────────────── */
.wh-filters {
  display: flex;
  gap: .5rem;
  margin-bottom: .8rem;
  flex-wrap: wrap;
}
.wh-filters select, .wh-filters input {
  flex: 1;
  min-width: 6rem;
  padding: .5rem .6rem;
  font-family: var(--font);
  font-size: .88rem;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  outline: none;
}
.wh-filters select:focus, .wh-filters input:focus { border-color: var(--green); background: var(--raised); }

.wh-table-wrap { overflow-x: auto; }
.wh-table {
  width: 100%;
  border-collapse: collapse;
  font-size: .85rem;
}
.wh-table th {
  text-align: right;
  font-weight: 600;
  color: var(--text-muted);
  font-size: .78rem;
  padding: .45rem .5rem;
  border-bottom: 1.5px solid var(--border);
  white-space: nowrap;
}
.wh-table td {
  padding: .5rem .5rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.wh-table tbody tr { cursor: pointer; transition: background .1s; }
.wh-table tbody tr:hover { background: var(--bg); }
.wh-title { font-weight: 600; }

/* وضعیت column: a coloured dot + the short status word, both carrying the state.
   Kept narrow so the title column keeps its width — nowrap on a 4-6 char word,
   the dot sitting inline before it (gap on its LEFT, the text side, for RTL). */
/* At phone width the 48px thumb needs its width to come from somewhere: trim the
   cell gutters (the status/price columns hold short strings and don't miss it). */
@media (max-width: 480px) {
  .wh-table td, .wh-table th { padding: .45rem .3rem; }
  .wh-title-cell { gap: .35rem; }
}
.wh-status { white-space: nowrap; }
.wh-status .dot {
  display: inline-block;
  vertical-align: middle;
  margin-left: .3rem;
}
.wh-status-text { font-size: .74rem; font-weight: 700; vertical-align: middle; }
.wh-status-text.st-in  { color: var(--green); }
.wh-status-text.st-out { color: var(--red); }

/* ── Warehouse stock total ───────────────────────────────────────────────── */
.wh-total {
  margin-top: .8rem;
  padding: .7rem .8rem;
  background: var(--bg);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: .15rem;
}
.wh-total-head { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; }
.wh-total-label { font-size: .82rem; font-weight: 700; color: var(--text-muted); }
/* Names the active filter, so a narrowed total can't read as vanished stock. */
.wh-total-scope { font-size: .72rem; color: var(--text-muted); }
.wh-total-figures { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.wh-total-value {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--green);
  font-variant-numeric: tabular-nums;
}
.wh-total-usd { font-size: .82rem; }
.wh-total-count { font-size: .74rem; color: var(--text-muted); }
.wh-total-muted { flex-direction: row; align-items: baseline; gap: .4rem; flex-wrap: wrap; }
.wh-total-note { font-size: .76rem; color: var(--text-muted); }

/* ── Modal / pop-up dialog ───────────────────────────────────────────────── */
body.modal-open { overflow: hidden; }

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: var(--backdrop);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 200;
  animation: backdropIn .15s ease;
}
@keyframes backdropIn { from { opacity: 0; } to { opacity: 1; } }

.modal {
  background: var(--surface);
  border-radius: 14px;
  box-shadow: var(--shadow-modal);
  width: 100%;
  max-width: 440px;
  max-height: calc(100vh - 2rem);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: modalIn .16s ease;
}
/* dvh tracks the on-screen keyboard on browsers that support it (most
   current mobile browsers); 100vh above is the fallback for those that
   don't. @supports guards it so an unsupporting browser never parses a
   value it can't use. */
@supports (height: 100dvh) {
  .modal { max-height: calc(100dvh - 2rem); }
}
@keyframes modalIn {
  from { opacity: 0; transform: translateY(10px) scale(.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Drag-to-close surface (handle + header). touch-action:none hands vertical
   drags starting here entirely to dallalSwipeBind() (client.js) instead of
   letting the browser try to pan/scroll — mirrors the crop canvas's own
   touch-action:none for the same reason. The handle itself is only a mobile
   bottom-sheet affordance (see @media below); the header remains part of the
   drag zone at every width. */
.modal-drag-zone {
  flex-shrink: 0;
  touch-action: none;
}
.modal-drag-handle {
  display: none;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: .8rem 1rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.modal-green .modal-header { border-top: 3px solid var(--green); }
.modal-red   .modal-header { border-top: 3px solid var(--red); }
.modal-title { font-size: 1.05rem; font-weight: 700; }
.modal-close {
  background: none;
  border: none;
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  color: var(--text-muted);
  padding: .15rem .4rem;
  border-radius: 6px;
}
.modal-close:hover { background: var(--bg); color: var(--text); }

.modal-body {
  padding: 1rem;
  overflow-y: auto;
}

@media (max-width: 480px) {
  .modal-backdrop { padding: 0; align-items: flex-end; }
  .modal {
    max-width: 100%;
    border-radius: 16px 16px 0 0;
    max-height: 92vh;
  }
  @supports (height: 100dvh) {
    .modal { max-height: 92dvh; }
  }
  @keyframes modalIn {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  /* The grab-handle affordance only makes sense on the bottom-sheet layout —
     a centered desktop dialog has nowhere "down" to sheet into. */
  .modal-drag-handle {
    display: block;
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--border);
    margin: .5rem auto 0;
  }
}

/* Keyboard-open state (client.js toggles this on <body> from the
   visualViewport listener below) — belt-and-braces alongside dvh: on a
   browser where dvh doesn't yet shrink for the keyboard, this caps the modal
   to the space visualViewport reports is ACTUALLY visible, via a custom
   property client.js keeps updated (--dallal-vvh, in px). Inert (falls back
   to the dvh/vh rules above) wherever the property is never set. */
body.dallal-kb-open .modal { max-height: min(92dvh, calc(var(--dallal-vvh, 100vh) - 1.5rem)); }

@media (prefers-reduced-motion: reduce) {
  .modal-backdrop, .modal { animation: none; }
}

/* ══ Round 4D: item edit/delete + rebuilt reports ══════════════════════════ */

/* ── Item detail footer: primary actions + bottom-left edit/delete tools ──── */
.detail-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  margin-top: 1rem;
  flex-wrap: wrap;
}
.detail-primary { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.detail-tools   { display: flex; gap: .4rem; }

/* Icon buttons — ≥44px tap targets (WCAG), colour + affordance on hover. */
.icon-btn {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.15rem;
  line-height: 1;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.icon-btn:hover        { border-color: var(--green); background: var(--raised); }
.icon-btn-danger:hover { border-color: var(--red); }

/* Edit form: «حذف عکس فعلی» checkbox row */
.remove-photo-row {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-size: .85rem;
  color: var(--red);
  margin-top: .5rem;
  cursor: pointer;
}
.remove-photo-row input { width: auto; }

/* ── Reports: period heading ──────────────────────────────────────────────── */
.report-period-heading {
  font-size: .95rem;
  font-weight: 700;
  margin: .55rem 0 .35rem;
}

.period-nav { display:grid; grid-template-columns: 4.75rem minmax(0,1fr) 4.75rem; align-items:center; gap:.4rem; }
.period-nav-btn { width:4.75rem; min-width:4.75rem; min-height:2.5rem; display:flex; align-items:center; justify-content:center; padding:0; line-height:1; }
.period-nav-heading { min-width:0; text-align:center; margin:.55rem 0 .35rem; }

/* 4 period tabs need to fit a 375px screen — tighten and allow wrapping. */
.report-tabs { flex-wrap: wrap; }
.report-tab  { font-size: .78rem; padding: .4rem .35rem; line-height: 1.25; min-width: 4rem; }

/* ── Breakdown tables (category / channel) ────────────────────────────────── */
.bd-table-wrap { overflow-x: auto; margin-top: .4rem; }
.bd-table { width: 100%; border-collapse: collapse; font-size: .86rem; }
.bd-table th {
  text-align: right;
  font-weight: 600;
  color: var(--text-muted);
  font-size: .74rem;
  padding: .3rem .4rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.bd-table td {
  padding: .45rem .4rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.bd-table tbody tr { cursor: pointer; transition: background .12s; }
.bd-table tbody tr:hover { background: var(--bg); }
.bd-table tbody tr:last-child td { border-bottom: none; }
.bd-name {
  font-weight: 500;
  max-width: 8.5rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Bordered box around a report section (category / channel breakdown), and
   compact stacked rows so every column fits a 375px screen with no
   horizontal scrolling — a name line, then wrapping label/value stat pairs
   instead of a wide table. */
.report-box {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: .7rem .8rem;
  background: var(--surface);
}
.bd-cards { display: flex; flex-direction: column; gap: .5rem; margin-top: .4rem; }
.bd-row {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: .5rem .65rem;
  background: var(--bg);
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.bd-row:hover { border-color: var(--green); background: var(--raised); }
.bd-row-name {
  font-weight: 700;
  font-size: .9rem;
  margin-bottom: .35rem;
  overflow-wrap: anywhere;
}
.bd-row-stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
  gap: .3rem .6rem;
}
.bd-stat { min-width: 0; }
.bd-stat-label { display: block; font-size: .68rem; color: var(--text-muted); }
.bd-stat-val   { display: block; font-size: .84rem; font-weight: 600; font-variant-numeric: tabular-nums; }

/* ── Best / worst deals ───────────────────────────────────────────────────── */
.deals-wrap { display: flex; flex-direction: column; gap: .8rem; margin-top: .4rem; }
.deals-col-head { font-size: .82rem; font-weight: 700; margin-bottom: .3rem; }
.deals-best  { color: var(--green); }
.deals-worst { color: var(--red); }

/* ── Capital rows clickable + stale day count ─────────────────────────────── */
.cat-row-link { cursor: pointer; transition: box-shadow .12s; }
.cat-row-link:hover { box-shadow: inset 0 0 0 1.5px var(--green); }
.stale-days { color: var(--red); font-weight: 600; }

/* ── Chart: two bars per month (sales + profit) + legend ──────────────────── */
.chart-bar-track { gap: 3px; justify-content: center; }
.chart-bar { flex: 1; width: auto; }
.chart-bar-sale   { background: linear-gradient(to top, var(--green), var(--green-dot)); }
.chart-bar-profit { background: linear-gradient(to top, var(--chart-profit-1), var(--chart-profit-2)); }
.chart-legend {
  display: flex;
  gap: 1rem;
  font-size: .74rem;
  color: var(--text-muted);
  margin: .3rem 0 .1rem;
}
.chart-swatch {
  display: inline-block;
  width: .7rem;
  height: .7rem;
  border-radius: 2px;
  vertical-align: middle;
  margin-left: .2rem;
}

/* ══ Round 5: codes, boxed drill rows, custom range, warehouse summary ══════ */

/* Product-code chip */
.result-code {
  display: inline-block;
  font-size: .72rem;
  color: var(--text-muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: .05rem .3rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Boxed, two-line transaction rows (drill-downs, best/worst, stale) */
.txn-list { display: flex; flex-direction: column; gap: .5rem; }
.txn-card {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: .55rem .65rem;
  background: var(--surface);
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.txn-card:hover { border-color: var(--green); background: var(--bg); }
.txn-card:last-child { margin-bottom: 0; }
.txn-line1 { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; min-width: 0; }
.txn-line1 .dot { align-self: center; }
.txn-name {
  font-weight: 600; font-size: .92rem; flex: 1; min-width: 0;
  overflow-wrap: break-word; line-height: 1.4;
}
.txn-line2 {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-top: .35rem; padding-right: 1rem; min-width: 0;
  font-size: .82rem; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.txn-amt { font-weight: 600; color: var(--text); }
.txn-date { margin-inline-start: auto; font-size: .78rem; white-space: nowrap; }

/* Custom date range (reports «بازه دلخواه») */
.custom-range {
  margin: .5rem 0;
  padding: .6rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
}
.custom-range-fields { display: flex; gap: .6rem; flex-wrap: wrap; }
.custom-range-fields .field { flex: 1; min-width: 8.5rem; }
.custom-range-apply { margin-top: .55rem; width: 100%; }

/* گزارش انبار summary block (collapsible) */
.wh-summary {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: .7rem .8rem;
  margin-bottom: .9rem;
  background: var(--surface);
}
.wh-summary-title {
  font-weight: 700; font-size: 1rem; cursor: pointer;
  color: var(--green); list-style: none;
}
.wh-summary-title::-webkit-details-marker { display: none; }
.wh-summary-title::before { content: '▾ '; font-size: .8rem; }
.wh-summary[open] .wh-summary-title::before { content: '▴ '; }
.wh-summary-note { margin: .3rem 0 .55rem; line-height: 1.5; }
.wh-sub { margin-top: .85rem; }
.wh-summary .wh-total { margin-top: .2rem; }

/* Undo-sale button */
.btn-undo { color: var(--red); }
.btn-undo:hover { border-color: var(--red); }

/* ── طلب و بدهی (Round 5B) ─────────────────────────────────────────────────── */
.debts-filter { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .8rem; }
a.txn-card { text-decoration: none; color: inherit; display: block; }

.debts-totals { display: flex; gap: .6rem; margin-bottom: .9rem; }
.debts-total {
  flex: 1; border: 1px solid var(--border); border-radius: 10px;
  padding: .7rem .6rem; text-align: center; background: var(--surface);
}
.debts-total-label { display: block; font-size: .8rem; color: var(--text-muted); margin-bottom: .3rem; }
.debts-total-val { display: block; font-weight: 700; font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.debts-total-pos { border-color: var(--green); }
.debts-total-pos .debts-total-val { color: var(--green); }
.debts-total-neg { border-color: var(--red); }
.debts-total-neg .debts-total-val { color: var(--red); }

.debts-overdue { margin-bottom: .9rem; }
.debts-overdue > summary {
  cursor: pointer; font-weight: 700; font-size: .95rem; padding: .3rem 0;
  list-style: none; display: flex; align-items: center; gap: .4rem;
}
.debts-overdue > summary::-webkit-details-marker { display: none; }
.debts-overdue > summary::before { content: '⏰'; }
.count-chip, .age-chip {
  font-size: .75rem; font-weight: 600; background: var(--bg);
  border: 1px solid var(--border); border-radius: 999px; padding: .05rem .5rem;
  color: var(--text-muted); font-variant-numeric: tabular-nums;
}
.age-chip { margin-inline-start: auto; }

.bal { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bal-word { font-weight: 600; font-size: .82em; }
.bal-pos { color: var(--green); }
.bal-neg { color: var(--red); }
.bal-zero { color: var(--text-muted); }
.txn-meta { color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; min-width: 0; }

.debts-empty {
  text-align: center; color: var(--text-muted); padding: 2rem 1rem;
  font-size: .95rem; line-height: 1.9;
}

/* person page */
.person-info { color: var(--text-muted); margin: -.3rem 0 .6rem; }
.person-net {
  font-size: 1.25rem; font-weight: 800; text-align: center;
  padding: .7rem; border-radius: 10px; background: var(--surface);
  border: 1px solid var(--border); font-variant-numeric: tabular-nums;
}
.person-net.bal-pos { border-color: var(--green); }
.person-net.bal-neg { border-color: var(--red); }
.person-split {
  display: flex; justify-content: space-between; gap: .5rem;
  margin: .5rem 0 .9rem; font-size: .9rem; font-variant-numeric: tabular-nums;
}
.split-pos { color: var(--green); font-weight: 600; }
.split-neg { color: var(--red); font-weight: 600; }
.person-actions { display: flex; gap: .6rem; margin-bottom: 1rem; }
.person-actions .btn-submit { flex: 1; }
.person-h2 { font-size: 1rem; font-weight: 700; margin: 1rem 0 .5rem; }

.txn-row-main { display: flex; align-items: center; gap: .55rem; }
.txn-row-text { flex: 1; min-width: 0; }
.txn-rem {
  font-weight: 700; font-size: .85rem; white-space: nowrap; text-align: left;
  font-variant-numeric: tabular-nums; min-width: 0;
}
.txn-badge {
  font-size: .72rem; font-weight: 700; border-radius: 6px; padding: .1rem .4rem;
  background: var(--bg); border: 1px solid var(--border); color: var(--text-muted);
  white-space: nowrap;
}
.txn-badge-sale { color: var(--green); border-color: var(--green); }
.txn-badge-buy  { color: var(--red);   border-color: var(--red); }

/* On a phone the flex row let .txn-rem's nowrap amount steal space from the
   thumbnail+text, squeezing .txn-name down until it wrapped letter-by-letter
   (same class of bug as .search-result above, same fix shape). Stack it: the
   thumbnail and text keep row 1, .txn-rem drops to its own full-width row 2.
   Rows without a thumbnail (credits/payments/manual/loan rows) have
   .txn-row-text as the FIRST child — :first-child lets it claim the full
   width in that case instead of leaving an empty thumbnail gutter. */
@media (max-width: 480px) {
  .txn-row-main {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: start;
    gap: .3rem .55rem;
  }
  .txn-row-main > .row-thumb { grid-column: 1; grid-row: 1; }
  .txn-row-main > .txn-row-text { grid-column: 2; grid-row: 1; }
  .txn-row-main > .txn-row-text:first-child { grid-column: 1 / -1; }
  .txn-row-main > .txn-rem {
    grid-column: 1 / -1;
    grid-row: 2;
    text-align: right;
    white-space: normal;
    overflow-wrap: break-word;
  }
}

/* §5 settle badge on item detail */
.settle-badge {
  display: block; margin: 0 0 .7rem; padding: .5rem .7rem; border-radius: 8px;
  font-size: .85rem; font-weight: 600; text-decoration: none; text-align: center;
}
.settle-badge-pos { color: var(--green); background: var(--green-tint); border: 1px solid var(--green); }
.settle-badge-neg { color: var(--red);   background: var(--red-tint-soft);  border: 1px solid var(--red); }

/* ── طلب و بدهی 5D: add-person, split alert-style معوق sections ────────────── */
.btn-add-person {
  display: inline-flex; align-items: center; gap: .4rem; margin-bottom: .8rem;
  padding: .5rem .9rem; font-family: var(--font); font-size: .9rem; font-weight: 700;
  border: 1.5px solid var(--green); border-radius: 8px; background: var(--surface);
  color: var(--green); cursor: pointer; transition: background .12s;
}
.btn-add-person:hover { background: var(--green-tint); }
.btn-add-person .btn-icon { font-size: 1.1rem; line-height: 1; }

/* ── 5F: manual طلب/بدهی buttons + collapsible «پرداخت و جزئیات» ───────────── */
.debts-actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.debts-actions .btn-add-person { margin-bottom: 0; }
.btn-manual {
  padding: .5rem .9rem; font-family: var(--font); font-size: .9rem; font-weight: 700;
  border-radius: 8px; border: 1.5px solid var(--border); background: var(--surface);
  cursor: pointer; transition: background .12s;
}
.btn-manual-credit { color: var(--green); border-color: var(--green); }
.btn-manual-credit:hover { background: var(--green-tint); }
.btn-manual-debt { color: var(--red); border-color: var(--red); }
.btn-manual-debt:hover { background: var(--red-tint-soft); }
.person-actions-manual { margin-bottom: 1rem; }
.person-actions-manual .btn-manual { flex: 1; }

/* Manual obligation rows in the ledger — non-product, no photo. Compact:
   the remaining amount sits in the main text column, directly under the
   طلب/بدهی badge (it's the number that matters most, so it belongs with the
   badge, not tucked beside the icon buttons); edit/delete + the entry date
   live stacked (date above icons) in an "aside" that is the LAST flex child,
   i.e. the visual left in RTL — narrow and fixed-width instead of a
   separate full-width actions row, so the card doesn't grow an extra tall
   row just to host two icon buttons. */
.txn-card-manual { padding: .4rem .55rem; }
.txn-card-manual .txn-line2 { padding-right: 0; margin-top: .2rem; }
.txn-manual-row { display: flex; align-items: center; gap: .5rem; }
.txn-manual-row > .txn-row-text { flex: 1; min-width: 0; }
.txn-manual-rem { margin-top: .2rem; }
.txn-manual-rem .txn-rem {
  display: inline-block; text-align: right; white-space: nowrap; font-size: .85rem;
}
.txn-manual-aside {
  display: flex; flex-direction: column; align-items: center; gap: .25rem;
  flex-shrink: 0;
}
.txn-manual-date { font-size: .72rem; white-space: nowrap; }
.txn-manual-actions { display: flex; gap: .3rem; flex-shrink: 0; }
.txn-manual-actions .icon-btn {
  min-width: 34px;
  min-height: 34px;
  font-size: 1rem;
}
.txn-badge-manual-credit { color: var(--green); border-color: var(--green); }
.txn-badge-manual-debt   { color: var(--red);   border-color: var(--red); }

/* Locked (readonly) name field on a person-scoped manual entry. */
.locked-input { background: var(--raised); color: var(--text-muted); cursor: default; }

/* «موعد وصول» clear button, sits inside the date field row. */
.date-clear {
  position: absolute; left: 2.2rem; top: 50%; transform: translateY(-50%);
  font-family: var(--font); font-size: .72rem; font-weight: 700; color: var(--text-muted);
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: .1rem .4rem; cursor: pointer;
}
.date-clear:hover { color: var(--red); border-color: var(--red); }

/* The «پرداخت و جزئیات» disclosure — the collapsed crowded half of the deal modals. */
.pay-details { grid-column: 1 / -1; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); }
.pay-details[open] { background: var(--surface); }
.pay-summary {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  padding: .6rem .75rem; cursor: pointer; font-weight: 700; list-style: none;
  border-radius: 10px; user-select: none;
}
.pay-summary::-webkit-details-marker { display: none; }
.pay-summary::before { content: '▸'; color: var(--text-muted); font-weight: 700; }
.pay-details[open] .pay-summary::before { content: '▾'; }
.pay-summary-title { flex: 0 0 auto; }
.pay-summary-chip {
  font-size: .78rem; font-weight: 700; color: var(--green);
  background: var(--green-tint); border-radius: 6px; padding: .1rem .5rem;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.pay-details-body { padding: .2rem .75rem .8rem; }

.debts-list-h { font-size: .85rem; color: var(--text-muted); font-weight: 700; margin: 1rem 0 .5rem; }

/* Compact, alert-like overdue boxes — visually distinct from the main ledger. */
.overdue-box {
  border: 1px solid var(--border); border-radius: 10px; padding: .6rem .7rem;
  margin-bottom: .8rem; background: var(--warm-panel);
}
.overdue-box-receivable { border-inline-start: 4px solid var(--green); }
.overdue-box-payable    { border-inline-start: 4px solid var(--red); }
.overdue-head { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem; }
.overdue-title { font-weight: 800; font-size: .95rem; }
.overdue-title::before { content: '⏰ '; }
.overdue-sum { font-weight: 800; font-variant-numeric: tabular-nums; }
.overdue-hint { font-size: .78rem; color: var(--text-muted); margin: .15rem 0 .5rem; line-height: 1.6; }
.overdue-rows { display: flex; flex-direction: column; gap: .35rem; }
.overdue-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: .5rem;
  padding: .4rem .5rem; border-radius: 7px; background: var(--surface);
  border: 1px solid var(--border); text-decoration: none; color: inherit;
}
.overdue-row:hover { border-color: var(--text-muted); }
.overdue-name { font-weight: 700; font-size: .9rem; min-width: 0; overflow-wrap: anywhere; }
.overdue-meta { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; justify-content: flex-end; }
.overdue-amt { font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }
.overdue-total { font-size: .74rem; color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ── 5E: zero-balance rows + «بابت»/attach deal search ────────────────────── */
/* Settled/bare people sit below the balances, visibly muted. */
.txn-card-zero { opacity: .82; }
.txn-card-zero .txn-name { color: var(--text-muted); font-weight: 600; }

/* Standalone attach button reuses .btn-add-person but goes full-width under the
   two payment buttons. */
.btn-attach-deal { width: 100%; justify-content: center; margin-bottom: 1rem; }

/* Deal search dropdown (reuses the product-search look). */
.deal-search { margin-top: .5rem; }
.deal-results { margin-top: .35rem; }
.deal-result-list { display: flex; flex-direction: column; gap: 3px; max-height: 16rem; overflow-y: auto; }
.deal-result {
  display: flex; align-items: center; gap: .5rem; width: 100%; text-align: start;
  padding: .4rem .45rem; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); cursor: pointer; transition: border-color .12s, background .12s;
}
.deal-result:hover, .deal-result:focus { border-color: var(--green); background: var(--bg); outline: none; }
.deal-result-text { flex: 1; min-width: 0; }
.deal-result-name { font-size: .9rem; display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.deal-result-price { font-weight: 700; font-size: .85rem; white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--text-muted); }
.deal-status { display: block; font-size: .75rem; margin-top: .15rem; line-height: 1.5; }
.deal-status-attach  { color: var(--green); }
.deal-status-own     { color: var(--text-muted); }
.deal-status-blocked { color: var(--red); }
/* A deal owned by someone else can't be selected — dimmed, no pointer. */
.deal-result-blocked { cursor: not-allowed; opacity: .7; background: var(--bg); }
.deal-result-blocked:hover { border-color: var(--border); background: var(--bg); }

/* Round 5G — merged deal modal. The two buttons reuse btn-submit-green/red, so
   their colour tracks «کل طلبم»/«کل بدهی‌ام» in both themes (§1). */
.deal-actions { display: flex; gap: .6rem; margin-bottom: 1rem; }
.deal-actions .btn-submit { flex: 1; }
/* The «بابت» mode hint — updates with the selection, never hidden. */
.babat-hint { margin: .35rem 0 .1rem; font-size: .82rem; color: var(--text-muted); }
.babat-hint:empty { display: none; }
/* The chosen unsettled deal (option 1). */
.deal-result-sel, .deal-result-sel:hover, .deal-result-sel:focus {
  border-color: var(--green); background: var(--green-tint);
}

/* «به نام … ثبت می‌شود» — the explicit, never-silent attach warning. */
.deal-attach-note:not(:empty) {
  margin: .5rem 0 0; padding: .45rem .6rem; border-radius: 8px;
  background: var(--green-tint); border: 1px solid var(--green);
  color: var(--green); font-size: .82rem; font-weight: 600; line-height: 1.6;
}
.deal-picked { font-weight: 700; font-size: .9rem; }

/* ── قسط و وام: the installment card inside a group ────────────────────────────
   Scoped ON PURPOSE to `.loan-inst-*`. `.txn-card`, `.txn-line2`, `.txn-meta`
   and `.icon-btn` are shared by the drill-downs, the ledger, best/worst, stale
   and the manual rows, so nothing here touches them globally — the card just
   carries an extra class and every rule below hangs off it. */

/* 1. Meta line starts at the card's own padding edge (the right edge in RTL).
      `.txn-line2` indents 1rem from the inline start for the product rows,
      where the text sits under a name that begins after a badge; an installment
      has no such hanging indent to line up with. `padding-inline: 0` overrides
      the physical `padding-right` regardless of direction. */
.loan-inst-meta { padding-inline: 0; }

/* 2. Status word: top inline-end (visual left in RTL), stacked under the
      «قسط N از M» hint. `margin-inline-start:auto` keeps it pinned to that edge
      even when `.txn-line1` wraps on a narrow phone. */
.loan-inst-tags {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .1rem;
  margin-inline-start: auto;
  flex-shrink: 0;
  text-align: end;
  line-height: 1.5;
}
.loan-inst-status { font-size: .78rem; font-weight: 700; white-space: nowrap; }
.loan-inst-status.is-pending { color: var(--red); }
.loan-inst-status.is-done    { color: var(--green); }

/* 3+4. Action row: compacted controls, ≥32px tap targets, no inline style. */
.loan-inst-actions {
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
  margin-top: .45rem;
}

/* The «پرداخت شد» control. One label, two states, and the state class is what
   carries the colour: while pending it is an unconfirmed ACTION, so it stays
   neutral gray — the same family as ordinary text — and green is reserved for
   what has actually happened. It fills solid green only on activation, both on
   :active and for the whole in-flight request (htmx puts .htmx-request on the
   issuing element), so the tap is acknowledged before the group body swaps.
   After the swap the same words come back as `.is-done`: green, and still the
   same control — the markup keeps its hx-post, and the route toggles, so a
   second press sends the row back to «در انتظار». */
.loan-inst-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: .15rem .7rem;
  font-family: var(--font);
  font-size: .82rem;
  font-weight: 700;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.loan-inst-toggle.is-pending { color: var(--text-muted); border-color: var(--border); }
.loan-inst-toggle.is-pending:hover { background: var(--hover); color: var(--text); }
.loan-inst-toggle.is-pending:active,
.loan-inst-toggle.is-pending.htmx-request {
  background: var(--green);
  border-color: var(--green);
  color: var(--on-accent);
}
/* Confirmed — a state that is still pressable. Green tint says what happened;
   `cursor:pointer` (restated rather than left to the base rule, because this
   block used to override it with `default`) says the state is reversible. The
   activation fill mirrors `.is-pending`'s so the undo tap is acknowledged the
   same way the first one was, before the group body swaps back to «در انتظار». */
.loan-inst-toggle.is-done {
  color: var(--green);
  border-color: var(--green);
  background: var(--green-tint);
  cursor: pointer;
}
.loan-inst-toggle.is-done:active,
.loan-inst-toggle.is-done.htmx-request {
  background: var(--green);
  border-color: var(--green);
  color: var(--on-accent);
}
.loan-inst-toggle:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }

/* The 🖼/✏️/🗑 controls, ~44px → 32px. Descendant-scoped so the global
   .icon-btn (44px, WCAG) still governs every other screen. */
.loan-inst-actions .icon-btn {
  min-width: 32px;
  min-height: 32px;
  padding: 0;
  font-size: .95rem;
}

#warehouse-back-to-top { position: fixed; left: max(1rem, env(safe-area-inset-left)); bottom: max(1rem, env(safe-area-inset-bottom)); z-index: 50; width: 44px; height: 44px; border: 1px solid var(--border); border-radius: 50%; background: var(--surface); color: var(--text); box-shadow: 0 2px 8px rgba(0,0,0,.18); font-size: 1.35rem; cursor: pointer; } #warehouse-back-to-top[hidden] { display: none; }
}
