html, body {
  margin: 0;
  padding: 0;
  /* Post-Stage13 change request #25: --pb-font-body is injected per-request by _Layout.cshtml from
     the admin's real ThemeAndAppearance BodyFont/BodyFontFile settings; falls back to this same
     Tahoma stack when the variable is unset for any reason. MASTER_CHANGES_PLAN.md queue item 14
     ("متن اصلی"): color/size/weight now come from --pb-text-primary-* too - _Layout.cshtml always
     emits a real resolved value for all three (no CSS-level fallback needed here). */
  font-family: var(--pb-text-primary-font, var(--pb-font-body, Tahoma, Arial, sans-serif));
  color: var(--pb-text-primary-color);
  font-size: var(--pb-text-primary-size);
  font-weight: var(--pb-text-primary-weight);
}

/* MASTER_CHANGES_PLAN.md queue item 14 ("تیتر اصلی"/"تیتر ثانویه"): h1/h2 are the site's "primary"
   heading level, h3-h6 "secondary" - both element selectors are low-specificity, so any page's own
   more specific heading class (e.g. .cat-collection-title) still wins the cascade untouched; these
   two rules only ever reach a truly unstyled heading. --pb-heading-primary-font/--pb-heading-secondary-font
   already fall back to the plain sitewide --pb-font-heading stack when no font is picked for that
   bundle (see _Layout.cshtml's FontFamilyFor), so this is a strict superset of the old shared rule
   below, not a behavior change for an untouched install. */
h1, h2 {
  font-family: var(--pb-heading-primary-font, inherit);
  color: var(--pb-heading-primary-color, inherit);
  font-size: var(--pb-heading-primary-size);
  font-weight: var(--pb-heading-primary-weight);
}

h3, h4, h5, h6 {
  font-family: var(--pb-heading-secondary-font, inherit);
  color: var(--pb-heading-secondary-color, inherit);
  font-size: var(--pb-heading-secondary-size);
  font-weight: var(--pb-heading-secondary-weight);
}

/* MASTER_CHANGES_PLAN.md queue item 14 ("متن ثانویه"): also lights up every pre-existing
   var(--pb-fg-muted, var(--pb-fg)) reference sitewide (that variable was defined nowhere before this
   session, so those rules always fell through to --pb-fg) - _Layout.cshtml sets --pb-fg-muted from
   this same TextSecondaryColorHex. */
.pb-text-secondary, small {
  font-family: var(--pb-text-secondary-font, inherit);
  color: var(--pb-text-secondary-color, inherit);
  font-size: var(--pb-text-secondary-size);
  font-weight: var(--pb-text-secondary-weight);
}

body.dir-rtl {
  direction: rtl;
  text-align: right;
}

body.dir-ltr {
  direction: ltr;
  text-align: left;
}

.site-main {
  /* بازبینی گالری فایل‌ها: exposed as custom properties (not just literal padding) so a full-width
     Section can cancel exactly this same padding via calc() below - one source of truth instead of a
     second hardcoded guess at .site-main's own spacing.
     box-sizing: pre-existing bug, surfaced while chasing the Section fix - .site-main had width:100%
     (below) plus this padding with the default content-box model, so the padding added on top of that
     100% instead of insetting within it, same class of bug as .pb-block/.pb-section/.pb-inner-section
     above. Small enough at typical desktop widths to go unnoticed as "a few px past the sidebar", but a
     real horizontal-scroll site-wide at narrow/mobile widths - independent of any Section setting. */
  box-sizing: border-box;
  --site-main-pad-block: 1.5rem;
  --site-main-pad-inline: 1rem;
  padding-block: var(--site-main-pad-block);
  padding-inline: var(--site-main-pad-inline);
}

/* Change session 24 (MASTER_CHANGES_PLAN.md بخش ۶.۵): the storefront had no content container at all
   before this session - text/cards stretched edge-to-edge on wide monitors. Scoped away from
   .pb-admin-body so admin tables/lists keep their full-width layout (unchanged). This is a fixed
   layout constant, not a themed value - unlike radius/shadow/gradient/spacing, no admin group in
   docs/1 ever calls out a "page width" setting. */
/* Change session 36 (MASTER_CHANGES_PLAN.md بخش ۸.۸): optional background color/image for the admin
   panel's main content area - --admin-content-bg(-image) default to transparent/none (_Layout.cshtml),
   so this rule paints nothing extra on an untouched install. */
.pb-admin-body .site-main {
  background-color: var(--admin-content-bg);
  background-image: var(--admin-content-bg-image);
  background-size: var(--admin-content-bg-size, cover);
  background-repeat: var(--admin-content-bg-repeat, no-repeat);
  background-position: center;
  background-attachment: var(--admin-content-bg-attachment, scroll);
}

body:not(.pb-admin-body) .site-main {
  max-width: 1320px;
  width: 100%;
  margin-inline: auto;
  --site-main-pad-inline: clamp(1rem, 4vw, 2.5rem);
  padding-inline: var(--site-main-pad-inline);
}

/* بازبینی PageBuilder (2026-09-14, client's own confirmed call - "تمام‌عرض با CSS خالص"): on the four
   PageBuilder-rendered pages (home / custom pages / contact / cooperation - _Layout.cshtml adds this
   class when the view sets ViewData["PageBuilderPage"]) the content container moves INSIDE each
   Section instead of wrapping the whole page: <main> itself is edge-to-edge and unpadded, a Full Width
   Section simply spans it, and a Box Section centres itself at the same 1320px content width with the
   fixed 30/10/5px outer inset (see .pb-section--box below). This replaces the old approach - the
   session-24 page container plus a JS breakout (page-blocks.js fitFullWidthSections, now deleted)
   that measured the container's gap after load and applied negative margins, which flashed the
   narrower width on every load and lagged 150ms on resize. Every non-PageBuilder page (catalog, cart,
   account...) keeps the rule above untouched. */
body:not(.pb-admin-body) .site-main.site-main--pb {
  max-width: none;
  --site-main-pad-inline: 0px;
  --site-main-pad-block: 0px;
  padding: 0;
  --pb-content-max: 1320px;
  --pb-box-inset: 30px;
}

.date-input {
  padding: 0.4rem;
  font-family: inherit;
}

/* Change session 11 (section 2.8 #33/#34/#35/#36): floating auto-dismiss toast replacing the old
   inline TempData paragraph that used to render inside the page body (and, on a few pages, twice). */
.pb-toast {
  position: fixed;
  inset-block-end: 1.5rem;
  inset-inline-end: 1.5rem;
  z-index: 1000;
  max-width: 22rem;
  padding: 0.85rem 1.1rem;
  border-radius: var(--pb-radius-sm, 8px);
  box-shadow: var(--pb-shadow-lg, 0 4px 16px rgba(0, 0, 0, 0.2));
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.pb-toast-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.pb-toast-info {
  background: var(--pb-primary, #7a1f3d);
}

.pb-toast-error {
  background: #b3261e;
}

/* Change session 11 (section 2.8 #35): add-to-cart quantity/variant popup - native <dialog>. */
.cat-add-to-cart-dialog,
.pb-info-dialog {
  border: none;
  border-radius: var(--pb-radius, 10px);
  padding: 1.25rem;
  min-width: 16rem;
  box-shadow: var(--pb-shadow-lg, 0 8px 30px rgba(0, 0, 0, 0.25));
}

.pb-info-dialog {
  max-width: min(32rem, calc(100% - 2rem));
}

.cat-add-to-cart-dialog::backdrop,
.pb-info-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.cat-add-to-cart-dialog form {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

/* Item 40-A (2026-10-22): the ready-made product page's size guide (Views/Products/_SizeGuideDialog.cshtml) - a wider info
   dialog that scrolls on small screens; the opener is a plain text link next to the size choice. */
.pb-info-dialog.cat-size-guide-dialog {
  max-width: min(44rem, calc(100% - 2rem));
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
}

.cat-size-guide h2 { margin: 0 0 0.5rem; }
.cat-size-guide h3 { margin: 1rem 0 0.4rem; font-size: 1.05rem; }
.cat-size-guide-table-wrap { overflow-x: auto; }

.cat-size-guide-table {
  width: 100%;
  border-collapse: collapse;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.cat-size-guide-table th,
.cat-size-guide-table td {
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--pb-border, #ddd);
}

.cat-size-guide-table thead th { font-weight: 700; background: color-mix(in srgb, var(--pb-secondary, #c9a24b) 14%, transparent); }
.cat-size-guide-notes { margin: 0; padding-inline-start: 1.2rem; line-height: 1.9; }
.cat-size-guide > button[data-close-cart-dialog] { margin-top: 1rem; }

.cat-size-guide-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--pb-primary, #7a1f3d);
  text-decoration: underline;
  cursor: pointer;
  font: inherit;
  align-self: flex-start;
}

.cat-size-guide-link-row { margin: 0 0 0.75rem; }

/* --pb-bg/--pb-fg/--pb-primary/--pb-secondary are injected per-request by _Layout.cshtml from the
   admin's real ThemeAndAppearance settings (light values in :root, dark values under html.pb-dark);
   --pb-border stays a fixed neutral in both modes since no admin setting exists for it. */
:root {
  --pb-border: #dddddd;
}

html.pb-dark {
  --pb-border: #3a3a3a;
}

/* Post-Stage-13 change requests (second follow-up, item #43): body background/text now follow the
   admin's palette in BOTH modes, not only dark mode - previously light mode (the site's default)
   never read --pb-bg/--pb-fg at all, so a color change in the admin panel had no visible effect. */
/* Change session 23 بند ب: split from a single `background: var(--pb-bg)` shorthand into
   background-color/background-image so a per-page background override (_Layout.cshtml, resolved
   from PageBackgroundOverride rows) can layer an image on top without any regression when no
   override applies (--pb-bg-image stays "none" by default). */
body {
  background-color: var(--pb-bg);
  background-image: var(--pb-bg-image, none);
  background-size: var(--pb-bg-size, cover);
  background-position: center;
  background-repeat: var(--pb-bg-repeat, no-repeat);
  background-attachment: var(--pb-bg-attachment, scroll);
  color: var(--pb-fg);
}

.pb-theme-toggle {
  cursor: pointer;
  border: none;
  background: none;
  font-size: 1.1rem;
}

/* Brand palette applied to headings/links/buttons in BOTH modes now (previously html.pb-dark-only,
   so the default light-mode site never visibly reacted to a color change in the admin panel). Change
   session 24: added an actual size scale (h1-h3 previously inherited the plain browser default, same
   size regardless of level) and tighter line-height, both fluid (clamp) so they scale down on mobile
   without a separate breakpoint rule. */
h1, h2, h3 {
  color: var(--pb-secondary);
  line-height: 1.25;
  font-weight: 700;
}

h1 { font-size: clamp(1.6rem, 1.15rem + 1.6vw, 2.5rem); }
h2 { font-size: clamp(1.3rem, 1.05rem + 1vw, 1.9rem); }
h3 { font-size: clamp(1.1rem, 1rem + 0.5vw, 1.4rem); }

/* Change session 24: every PageBuilder block (_PageBlocks.cshtml wraps each one in <section
   class="pb-block">) had zero vertical spacing rule - homepage sections rendered stacked flush
   against each other with no breathing room. --pb-section-spacing is the admin's
   Design.Theme.SectionSpacingPx setting, injected by _Layout.cshtml.
   MASTER_CHANGES_PLAN.md queue item 11: a block's own background photo (BackgroundMode.Image,
   _PageBlocks.cshtml's BuildBlockStyle) is this project's "background image" category - square
   (0) by default, same look as every block rendered before this setting existed; the admin can opt
   it into the sitewide radius via Design.Theme.RadiusAppliesToBackgroundImages. Border-radius alone
   clips a CSS background-image to the same curve without needing overflow:hidden, which would also
   wrongly clip a block's own overflowing children (dropdowns, absolutely-positioned decorations). */
.pb-block {
  border-radius: var(--pb-bg-image-radius, 0px);
}

/* Session 66 (MASTER_CHANGES_PLAN.md آیتم ۱۷الف - موتور Section/InnerSection): shared layout rules
   for every node in the block tree - leaf blocks (.pb-block), Section (.pb-section) and InnerSection
   (.pb-inner-section) wrappers alike, since the client's common-field list applies to all three the
   same way. Each level's own children render inside a .pb-block-list flex column, which is what makes
   --pb-order-desktop/--pb-order-mobile (independent per-device ordering) actually take effect - CSS
   order only works on a flex/grid item. Margin/padding fall back through desktop, so an admin who only sets a desktop value
   gets it on every breakpoint, matching every other admin-optional field in this codebase. */
.pb-block-list {
  display: flex;
  flex-direction: column;
}

.pb-block,
.pb-section,
.pb-inner-section {
  position: relative;
  z-index: 0;
  /* بازبینی گالری فایل‌ها: without border-box, a block that stretches to its container's full width
     (flex-stretch for .pb-block/.pb-inner-section, .pb-section's own width:100% below) then adds any
     left+right padding (e.g. Section's Box-mode +30px/side, or an admin's own custom padding) grows
     past that width under the default content-box model instead of insetting within it. In this RTL
     site that overflow only ever appeared on the block's end side (visually "left"), while the start
     side (visually "right", flush against the post-login sidebar) never moved - looking like Full
     Width and Box had swapped, when really Box was just overflowing the page instead of shrinking. */
  box-sizing: border-box;
  order: var(--pb-order-desktop, 0);
  margin-top: var(--pb-mt-d, 0px);
  /* بازبینی گالری فایل‌ها: client feedback reversed the session-24 default above - blocks/sections/
     inner-sections must never carry an automatic gap, only whatever margin the admin explicitly set
     for that exact block (same "unset = 0px" contract every other side already uses here). Colored
     full-width Sections stacked directly under each other made the old var(--pb-section-spacing, 64px)
     fallback's gap plainly visible as unwanted whitespace. --pb-section-spacing itself is untouched -
     .cat-collection-banner and others below still use it for their own default spacing. */
  margin-bottom: var(--pb-mb-d, 0px);
  margin-left: var(--pb-ml-d, 0px);
  margin-right: var(--pb-mr-d, 0px);
  padding-top: var(--pb-pt-d, 0px);
  padding-bottom: var(--pb-pb-d, 0px);
  padding-left: var(--pb-pl-d, 0px);
  padding-right: var(--pb-pr-d, 0px);
}

/* بازبینی گالری فایل‌ها: this project's whole spacing model routes every gap through the explicit
   --pb-pt/pb/ml/mr custom properties above - an admin who leaves them unset expects zero space, not
   "whatever the browser's UA stylesheet happens to default a heading/paragraph to". Most block
   templates (_PageBlocks.cshtml) use a plain <div> for their body text, which has no default margin,
   but a few (e.g. Banner) use <p>, which carries the browser's own ~1em top/bottom margin - same
   heading tag, same configured spacing, different actual height/bottom-space between block types for
   no configured reason. Resetting margin sitewide within .pb-block (every leaf block's own content
   always lives here, per the Section/InnerSection nesting rules - never inside .pb-section/.pb-inner-
   section directly) closes that gap for every current and future block type at once. */
.pb-block h1,
.pb-block h2,
.pb-block h3,
.pb-block h4,
.pb-block h5,
.pb-block h6,
.pb-block p {
  margin: 0;
}

/* Client feedback: gap between a block's own displayed title (.pb-block-title, _PageBlocks.cshtml -
   the handful of block types that render one) and whatever follows it must be admin-configurable.
   !important beats the zero-margin reset above, same precedent as the mobile column overrides
   further down.

   Client request 2026-10-02: the fallback is no longer 0px - that made every block whose admin never
   filled the field render its title stuck to the text/image below it (the reset above having already
   removed the heading's own margin). It is deliberately RELATIVE: the title is an <h2> whose size is
   itself a designer-admin setting (--pb-heading-primary-size, 32px today), so 0.6em keeps the
   proportion right at any size the client picks later instead of needing a second re-tune. The value
   shrinks per breakpoint (0.6 / 0.55 / 0.5em) because the heading itself gets relatively larger on a
   narrow screen. A number typed in the block form still overrides it - including an explicit 0. */
.pb-block-title {
  margin-bottom: var(--pb-tg-d, 0.6em) !important;
}

/* بازبینی گالری فایل‌ها: this only avoided doubling up the 64px page-block gap right before the
   footer - scoped to the top-level page block-list. A plain ":last-child" selector (no scope) matched
   ANY block that happened to be last in ITS OWN list, including one nested inside a Section/
   InnerSection's own block-list/grid - forcibly zeroing an admin-configured margin-bottom there even
   though nothing about "last inside this Section" implies "right before the footer, avoid double
   spacing". That margin is exactly what should grow the Section's own background to contain the gap
   (flex items don't collapse margins into their container, so once this stops overriding it the
   Section's auto height already picks it up), and is exactly the gap that should appear between this
   block and a sibling block added after it inside the same Section. */
.site-main > .pb-block-list > .pb-block:last-child,
.site-main > .pb-block-list > .pb-section:last-child,
.site-main > .pb-block-list > .pb-inner-section:last-child {
  margin-bottom: 0;
}

/* Background color+image render together (client: "هم‌زمان نه یا‌این‌یا‌آن") on a ::before layer so
   --pb-bg-opacity fades only the background, never the block's own text/children - z-index:0 on the
   block itself (above) gives this pseudo-element its own local stacking context. */
.pb-block::before,
.pb-section::before,
.pb-inner-section::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  border-radius: inherit;
  background-color: var(--pb-bg-color, transparent);
  background-image: var(--pb-bg-image, none);
  background-size: var(--pb-bg-size, cover);
  background-repeat: var(--pb-bg-repeat, no-repeat);
  background-position: center;
  background-attachment: var(--pb-bg-attachment, scroll);
  opacity: var(--pb-bg-opacity, 1);
}

.pb-section {
  width: 100%;
}

/* Client request 2026-10-18 (SectionBlockConfig.MirrorForLtr): in a left-to-right language the section's background is
   flipped horizontally and its left/right padding swapped, so a hero whose text sits on the right in Persian sits on the
   left in English. Only the ::before background layer is flipped - the content is not. With its own LTR image the
   section uses that picture un-flipped instead. */
html[dir="ltr"] .pb-section.pb-section--mirror-ltr {
  padding-left: var(--pb-pr-d, 0px);
  padding-right: var(--pb-pl-d, 0px);
}

html[dir="ltr"] .pb-section.pb-section--mirror-ltr:not(.pb-section--ltr-image)::before {
  transform: scaleX(-1);
}

html[dir="ltr"] .pb-section.pb-section--ltr-image::before {
  background-image: var(--pb-bg-image-ltr, var(--pb-bg-image, none));
}

@media (min-width: 901px) and (max-width: 1200px) {
  html[dir="ltr"] .pb-section.pb-section--mirror-ltr {
    padding-left: var(--pb-pr-t, var(--pb-pr-d, 0px));
    padding-right: var(--pb-pl-t, var(--pb-pl-d, 0px));
  }
}

@media (max-width: 900px) {
  html[dir="ltr"] .pb-section.pb-section--mirror-ltr {
    padding-left: var(--pb-pr-m, var(--pb-pr-d, 0px));
    padding-right: var(--pb-pl-m, var(--pb-pl-d, 0px));
  }
}

/* بازبینی PageBuilder (2026-09-14): Full Width vs Box, CSS only (see .site-main--pb above).
   Full Width: the Section spans <main> edge to edge (flush with header/footer/sidebar - the client's
   "تمام‌عرض یعنی واقعاً تا لبه" feedback), its content likewise unless the admin adds padding.
   Box (client's own confirmed meaning, 2026-09-14): the WHOLE Section - background included - is a
   centred card at the site's 1320px content width, never closer than 30px (desktop) / 10px (tablet)
   / 5px (mobile) to the edge; the admin's own left/right margins are not applied to a Box (it is
   centred), top/bottom still are. The old Box merely padded the content inward while the background
   still ran to the container edge. */
.pb-section.pb-section--full {
  width: 100%;
}

.pb-section.pb-section--box {
  width: min(100% - 2 * var(--pb-box-inset, 30px), var(--pb-content-max, 1320px));
  margin-left: auto;
  margin-right: auto;
  border-radius: var(--pb-bg-image-radius, 0px);
}

/* بازبینی PageBuilder (2026-09-14, client: "جبران خودکار با تیک قابل خاموش‌کردن"): while the header
   overlays the hero (transparent-hero mode, _Layout.cshtml), the first Section is pushed down by the
   header's measured height (--pb-header-h, set by transparent-header.js) so its content never starts
   hidden under the header - the Section's own background still runs up behind the header, which is the
   whole point of the effect. A Section whose content is meant to sit under the header (a full-bleed
   slide) opts out with SectionBlockConfig.ContentUnderTransparentHeader → .pb-section--under-header. */
.pb-site-header.pb-header-transparent-hero ~ .pb-body-row .site-main--pb > .pb-block-list > .pb-section:first-child:not(.pb-section--under-header) {
  padding-top: calc(var(--pb-pt-d, 0px) + var(--pb-header-h, 0px));
}

.pb-inner-section {
  display: grid;
  grid-template-columns: var(--pb-grid-cols, repeat(var(--pb-columns, 2), 1fr));
  /* بازبینی گالری فایل‌ها: client - "این مقدار gap همیشه باید صفر باشد" - no longer admin-configurable,
     never a --pb-gap custom property to read here. */
  gap: 0;
  align-items: var(--pb-valign, stretch);
}

.pb-column {
  min-width: 0;
}

@media (min-width: 901px) and (max-width: 1200px) {
  .pb-block,
  .pb-section,
  .pb-inner-section {
    margin-top: var(--pb-mt-t, var(--pb-mt-d, 0px));
    margin-bottom: var(--pb-mb-t, var(--pb-mb-d, 0px));
    margin-left: var(--pb-ml-t, var(--pb-ml-d, 0px));
    margin-right: var(--pb-mr-t, var(--pb-mr-d, 0px));
    padding-top: var(--pb-pt-t, var(--pb-pt-d, 0px));
    padding-bottom: var(--pb-pb-t, var(--pb-pb-d, 0px));
    padding-left: var(--pb-pl-t, var(--pb-pl-d, 0px));
    padding-right: var(--pb-pr-t, var(--pb-pr-d, 0px));
  }

  .pb-block-title {
    margin-bottom: var(--pb-tg-t, var(--pb-tg-d, 0.55em)) !important;
  }

  .pb-section.pb-section--box {
    --pb-box-inset: 10px;
  }

  .pb-site-header.pb-header-transparent-hero ~ .pb-body-row .site-main--pb > .pb-block-list > .pb-section:first-child:not(.pb-section--under-header) {
    padding-top: calc(var(--pb-pt-t, var(--pb-pt-d, 0px)) + var(--pb-header-h, 0px));
  }
}

@media (max-width: 900px) {
  .pb-block,
  .pb-section,
  .pb-inner-section {
    order: var(--pb-order-mobile, var(--pb-order-desktop, 0));
    margin-top: var(--pb-mt-m, var(--pb-mt-d, 0px));
    margin-bottom: var(--pb-mb-m, var(--pb-mb-d, 0px));
    margin-left: var(--pb-ml-m, var(--pb-ml-d, 0px));
    margin-right: var(--pb-mr-m, var(--pb-mr-d, 0px));
    padding-top: var(--pb-pt-m, var(--pb-pt-d, 0px));
    padding-bottom: var(--pb-pb-m, var(--pb-pb-d, 0px));
    padding-left: var(--pb-pl-m, var(--pb-pl-d, 0px));
    padding-right: var(--pb-pr-m, var(--pb-pr-d, 0px));
  }

  .pb-block-title {
    margin-bottom: var(--pb-tg-m, var(--pb-tg-d, 0.5em)) !important;
  }

  .pb-section.pb-section--box {
    --pb-box-inset: 5px;
  }

  .pb-site-header.pb-header-transparent-hero ~ .pb-body-row .site-main--pb > .pb-block-list > .pb-section:first-child:not(.pb-section--under-header) {
    padding-top: calc(var(--pb-pt-m, var(--pb-pt-d, 0px)) + var(--pb-header-h, 0px));
  }

  /* Client's exact mobile-stacking spec: 2 columns stay side-by-side (no override needed - the base
     --pb-grid-cols rule above already applies at every width), 3 stack fully, 4 becomes two rows of 2.
     !important matches this file's existing .pb-hide-desktop/tablet/mobile precedent for a mobile
     override that must always win regardless of any per-instance inline custom property. */
  .pb-inner-section[data-columns="3"] { grid-template-columns: 1fr !important; }
  .pb-inner-section[data-columns="4"] { grid-template-columns: repeat(2, 1fr) !important; }
  /* Item 40-A (2026-10-22): an InnerSection with "stack on mobile" ticked puts its columns one under the other
     (InnerSectionBlockConfig.StackOnMobile) - the About page's text + photo/video rows. Empty columns stay collapsed. */
  .pb-inner-section[data-stack-mobile="true"] { grid-template-columns: 1fr !important; }
  .pb-inner-section[data-stack-mobile="true"] > .pb-column--empty { display: none; }
}

/* Change session 37 (MASTER_CHANGES_PLAN.md بخش ۱۰.۱ بند ۱): per-block "ظاهر بلوک" text-color override
   - "روشن" means the admin set a dark background on this block, so headings/body/links need to switch
   to a light color to stay readable; "تیره" is the opposite, explicit forced dark text. Both empty/
   "خودکار" (no class at all) leave the normal inherited color untouched, exactly like every block
   before this session. */
.pb-block--text-light,
.pb-block--text-light h1,
.pb-block--text-light h2,
.pb-block--text-light h3,
.pb-block--text-light a {
  color: #fff;
}

.pb-block--text-dark,
.pb-block--text-dark h1,
.pb-block--text-dark h2,
.pb-block--text-dark h3,
.pb-block--text-dark a {
  color: #1a1a1a;
}

/* Change session 37 (بند ۵): sitewide content-image treatment, scoped to block content only - never
   the header/footer logo or social icons, which live outside .pb-block. Duotone's actual two colors
   come from the SVG filter _Layout.cshtml emits (from the site's own Primary/Secondary), not a fixed
   filter recipe, so it always matches the current theme. */
.pb-photo-grayscale .pb-block img {
  filter: grayscale(1);
}

.pb-photo-duotone .pb-block img {
  filter: url(#pb-duotone);
}

/* Change session 37 (بند ۷): shared scroll-reveal, added only by wwwroot/js/scroll-reveal.js (itself
   only included when Design.Theme.ScrollRevealEnabled is on) - a block with no .pb-reveal class (the
   feature off, or JS not yet run) renders at its normal, fully-visible state. */
.pb-reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.pb-reveal-visible {
  opacity: 1;
  transform: none;
}

.pb-card {
  border-color: var(--pb-primary);
}

.pb-lang-menu li button.active {
  color: var(--pb-primary);
  font-weight: bold;
}

a {
  color: var(--pb-primary);
}

/* Change session 24: flat single-color buttons with no radius/shadow/hover-motion replaced with the
   sitewide gradient (falls back to a flat --pb-primary when the admin disables the gradient toggle -
   see --pb-gradient in _Layout.cshtml, so no separate "if disabled" CSS branch is needed here). */
button[type="submit"], .pb-btn {
  background: var(--pb-gradient, var(--pb-primary));
  color: #fff;
  border: none;
  border-radius: var(--pb-radius-sm, 8px);
  padding: 0.6rem 1.4rem;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--pb-shadow);
  transition: filter 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}

button[type="submit"]:hover, .pb-btn:hover {
  filter: brightness(0.94);
  transform: translateY(-1px);
  box-shadow: var(--pb-shadow-lg);
}

button[type="submit"]:active, .pb-btn:active {
  transform: translateY(0);
  box-shadow: var(--pb-shadow);
}

button[type="submit"]:disabled, .pb-btn:disabled {
  cursor: not-allowed;
  filter: grayscale(0.3) brightness(1.05);
  transform: none;
  box-shadow: none;
}

.pb-main-menu a.active {
  color: var(--pb-secondary);
  font-weight: bold;
}

/* Post-Stage-6 change request #16: small header (1 row), main header (right=logo/menu/lang/search,
   left=cart/compare/wishlist/auth in fa - flexbox row direction mirrors automatically under
   dir="ltr" with zero extra markup). */
.pb-small-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.4rem 1rem;
  /* MASTER_CHANGES_PLAN.md queue item 14 ("خط جداکننده‌ی هدر/فوتر"): independently admin-configurable,
     falls back to the same shared --pb-border every other divider on the site uses when unset. */
  border-bottom: 1px solid var(--pb-hf-divider-color);
  gap: 1rem;
}

.pb-main-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--pb-hf-divider-color);
  flex-wrap: wrap;
  gap: 1rem;
}

/* Change session 37 (MASTER_CHANGES_PLAN.md بخش ۱۰.۱ بند ۶), condition replaced per user request
   2026-09-12: only ever applied when the Design.Header.TransparentOnHero admin toggle is on AND the
   current page's first top-level Section is itself Full Width AND the visitor isn't signed in
   (_Layout.cshtml) - overlays the header on the hero block with a transparent background, then
   wwwroot/js/site-header.js adds .pb-header-solid once the visitor scrolls past it.
   بازبینی هدر (2026-09-14): the transparent state's text/icon colour is the admin's own
   --pb-header-transparent-color (default white); the optional second logo swaps in via
   .pb-logo-transparent; and what happens after the visitor scrolls now follows the sitewide sticky
   setting (below) instead of always pinning: with sticky "none" the overlaid header simply scrolls
   away like any other page's header. Transitions cover colour, shadow and background so the
   transparent→solid switch no longer snaps. */
.pb-site-header {
  transition: box-shadow 0.25s ease;
}

.pb-site-header .pb-small-header,
.pb-site-header .pb-main-header {
  transition: background-color 0.25s ease, border-color 0.25s ease;
}

.pb-site-header.pb-header-transparent-hero {
  position: absolute;
  inset-inline: 0;
  top: 0;
  z-index: 40;
}

.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) .pb-small-header,
.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) .pb-main-header {
  background-color: transparent;
  border-bottom-color: transparent;
}

.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid),
.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) a,
.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) .pb-icon-link,
.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) .pb-main-menu a,
.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) .pb-main-menu .pb-submenu-toggle,
.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) .pb-theme-toggle {
  color: var(--pb-header-transparent-color, #fff);
}

.pb-site-header.pb-header-transparent-hero.pb-header-solid {
  background-color: var(--pb-bg);
}

/* Second logo for the transparent state: both images are in the markup, exactly one is shown. */
.pb-logo .pb-logo-transparent {
  display: none;
}

.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) .pb-logo .pb-logo-normal {
  display: none;
}

.pb-site-header.pb-header-transparent-hero:not(.pb-header-solid) .pb-logo .pb-logo-transparent {
  display: inline;
}

/* بازبینی هدر (2026-09-14, client's own confirmed three-state setting Design.Header.StickyMode):
   "both" pins the whole header; "main" pins only the main bar - the header sticks with a negative
   top equal to the small bar's measured height (--pb-small-header-h, site-header.js), so the small
   bar scrolls out of view and the main bar stays. A pinned header gets the theme shadow once the
   page is scrolled (.pb-scrolled). The transparent-hero header, once solid, obeys the same modes
   (fixed instead of sticky, since it is already out of flow). */
.pb-site-header.pb-header-sticky-both:not(.pb-header-transparent-hero),
.pb-site-header.pb-header-sticky-main:not(.pb-header-transparent-hero) {
  position: sticky;
  z-index: 40;
  top: 0;
}

.pb-site-header.pb-header-sticky-main:not(.pb-header-transparent-hero) {
  top: calc(-1 * var(--pb-small-header-h, 0px));
}

.pb-site-header.pb-header-sticky-both.pb-scrolled,
.pb-site-header.pb-header-sticky-main.pb-scrolled {
  box-shadow: var(--pb-shadow);
}

.pb-site-header.pb-header-transparent-hero.pb-header-solid.pb-header-sticky-both,
.pb-site-header.pb-header-transparent-hero.pb-header-solid.pb-header-sticky-main {
  position: fixed;
  box-shadow: var(--pb-shadow);
}

.pb-site-header.pb-header-transparent-hero.pb-header-solid.pb-header-sticky-main {
  top: calc(-1 * var(--pb-small-header-h, 0px));
}

/* Small-header phone (the seeded "Phone" element, rendered for the first time 2026-09-14). */
.pb-header-phone {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  text-decoration: none;
  color: inherit;
  font-size: 0.9rem;
}

.pb-header-group-a, .pb-header-group-b {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Client request 2026-09-21: header icon spacing (cart/compare/wishlist/user/admin group) 25% tighter on
   every breakpoint - both halves of the old 1rem gap + 0.75rem end-margin pair scaled by 0.75, and no
   media query anywhere overrides either value, so the same reduction holds on tablet and phone. */
.pb-header-group-b {
  gap: 0.75rem;
}

.pb-header-group-b .pb-icon-link {
  margin-inline-end: 0.5625rem;
}

/* Change session 20 بند ث: hamburger toggle for the main menu/language-switcher/search, hidden above
   900px (desktop keeps the plain inline layout); .pb-mobile-menu-panel mirrors .pb-header-group-a's own
   flex layout on desktop so wrapping it in a panel changes nothing visually above 900px. */
.pb-mobile-menu-toggle {
  display: none;
  border: none;
  background: none;
  font-size: 1.4rem;
  cursor: pointer;
  padding: 0.5rem;
  /* A <button> defaults to black text; take the menu's own colour so the icon stays visible on a dark/transparent header. */
  color: var(--pb-menu-color, inherit);
}

/* Client request 2026-10-18: the captcha (<captcha /> tag helper): label on top, then ONE row - question picture, "new question"
   button and a small answer box. The picture shrinks to whatever width the other two leave. */
.captcha {
  margin: 0.6rem 0;
  text-align: start;
}

.captcha-label {
  display: block;
  font-size: 0.85rem;
  margin-bottom: 0.3rem;
}

.captcha-row {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
}

.captcha-image {
  flex: 0 1 200px;
  min-width: 0;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--pb-border, #ddd);
  border-radius: var(--pb-radius-sm, 6px);
  background: #fff;
}

.captcha .captcha-refresh {
  flex: 0 0 auto;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 1px solid var(--pb-border, #ddd);
  border-radius: 50%;
  background: none;
  color: inherit;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.captcha .captcha-row .captcha-answer {
  flex: 0 0 4.2rem;
  width: 4.2rem;
  min-width: 0;
  box-sizing: border-box;
  margin: 0;
  padding-inline: 0.3rem;
  text-align: center;
}

/* Client request 2026-10-18: in the sign-in / register forms (popup and full pages) the captcha is centred. */
.js-auth-form .captcha,
.auth-page-form .captcha {
  text-align: center;
}

/* Item 45: the forgot-password and set-new-password pages centre it the same way. */
.js-auth-form .captcha-row,
.auth-page-form .captcha-row {
  justify-content: center;
}

/* Item 45: the resend-code form under the code card - its captcha and button sit centred together. */
.auth-resend-form {
  text-align: center;
}

/* Client request 2026-10-18: sign-in method tabs (_LoginFormPartial). The id selectors beat the popup's generic button rules. */
.auth-tabs {
  display: flex;
  margin-bottom: 0.9rem;
  border-bottom: 1px solid var(--pb-border, #ddd);
}

#auth-modal-content .auth-tabs .auth-tab,
.auth-tabs .auth-tab {
  flex: 1 1 0;
  padding: 0.55rem 0.3rem;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  opacity: 0.7;
}

#auth-modal-content .auth-tabs .auth-tab[aria-selected="true"],
.auth-tabs .auth-tab[aria-selected="true"] {
  border-bottom-color: var(--pb-primary, currentColor);
  font-weight: 700;
  opacity: 1;
}

.auth-panel[hidden] {
  display: none;
}

.form-error-detail {
  font-size: 0.9em;
}

/* Item 36: the one-time reactivation notice at the top of the page after the first sign-in. */
.pb-account-notice {
  margin: 0 0 1rem;
  padding: 0.7rem 1rem;
  border: 1px solid var(--pb-primary, #888);
  border-radius: var(--pb-radius-sm, 6px);
  background: var(--pb-bg, #fff);
}

/* The chat panel has a fixed height: its guest form scrolls instead of being cut off. */
.chat-guest-name-form {
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}

/* Client request 2026-10-18: the header's language icon uses the menu colour (a bare <summary> is dark grey on a dark header). */
.pb-site-header .pb-lang-trigger {
  color: var(--pb-menu-color, inherit);
}

/* Client request 2026-10-18: the language icon lives in the icon row (--bar) up to 900px and in the menu panel above it. */
.pb-language-switcher--bar {
  display: none;
}

@media (max-width: 900px) {
  .pb-language-switcher--bar {
    display: block;
  }

  .pb-mobile-menu-panel .pb-language-switcher {
    display: none;
  }
}

.pb-mobile-menu-panel {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.pb-main-menu a, .pb-icon-link {
  margin-inline-end: 0.75rem;
  text-decoration: none;
  position: relative;
  /* MASTER_CHANGES_PLAN.md queue item 14 ("رنگ متن+آیکون‌های منو (سبد/مقایسه/علاقه‌مندی/کاربر)"):
     sitewide default - a per-item HeaderFooterElement.ColorHex (set from the هدر و فوتر screen,
     emitted as a higher-specificity [data-hf-key="..."] rule) still overrides this when an admin sets
     one there; this is only the fallback for the (default, untouched) case. */
  color: var(--pb-menu-color, inherit);
  transition: color 0.2s ease;
}

/* بازبینی هدر (2026-09-14, client's confirmed "افکت hover + فاصله‌ی آیتم‌ها"): the MainHeader
   Container row's MenuItemGapPx / MenuFontSizePx (HeaderFooterCssBuilder emits them as custom
   properties) and its MenuHoverStyle - "color only" is the pre-existing look (the hover colour rule
   the builder already emitted), "underline" adds an animated underline in the same hover colour
   growing from the start edge, "none" suppresses any hover change. */
.pb-main-menu a {
  margin-inline-end: var(--pb-menu-gap, 0.75rem);
  font-size: var(--pb-menu-font-size, inherit);
}

.pb-menu-hover-underline .pb-main-menu a::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  bottom: -0.2em;
  width: 100%;
  height: 2px;
  background: var(--pb-menu-hover-color, currentColor);
  transform: scaleX(0);
  transform-origin: inline-start;
  transition: transform 0.25s ease;
}

.pb-menu-hover-underline .pb-main-menu a:hover::after,
.pb-menu-hover-underline .pb-main-menu a.active::after {
  transform: scaleX(1);
}

.pb-menu-hover-none .pb-main-menu a:hover {
  color: inherit;
}

/* Client request: the header/chat/profile icons are inline SVG instead of emoji characters, because
   emoji render from whatever font the visitor's device has - and several of them (search, cart,
   account, the "slightly sad" chat face) have no glyph at all on a machine without an emoji font,
   which is exactly how this was reported. vertical-align keeps them optically centred against the
   adjacent text the way the emoji glyphs used to sit; stroke/colour come from currentColor, so the
   theme colour, dark mode, and the white transparent-hero header all keep working untouched. */
.pb-icon {
  vertical-align: -0.18em;
  flex-shrink: 0;
}

/* Change session 11 (section 2.8 #33): cart/wishlist/compare header icon item count. */
.pb-icon-badge {
  position: absolute;
  inset-block-start: -0.5rem;
  inset-inline-end: -0.6rem;
  min-width: 1.1rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--pb-secondary, #c9a24b);
  color: #fff;
  font-size: 0.7rem;
  line-height: 1.1rem;
  text-align: center;
}

.pb-social-icons img {
  max-height: 32px;
}

/* Change session 17 (بند ۳.۴ - "نمودار در گزارش‌ها"): plain canvas bar chart, admin-chart.js sizes
   the drawing buffer itself off clientWidth, this just gives it a block-level footprint to measure. */
.pb-admin-chart {
  display: block;
  width: 100%;
  max-width: 640px;
  margin: 0.5rem 0 1rem;
}

/* MASTER_CHANGES_PLAN.md queue item 19 s5: the new report pages - period bar, headline figures, change chips. */
.pb-admin-chart-wide { max-width: none; }
.pb-report-month-nav { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin: 0 0 0.75rem; }
.pb-report-month-nav a { display: inline-flex; align-items: center; gap: 0.2rem; }
.pb-report-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0.75rem; }
.pb-report-figure { display: flex; flex-direction: column; gap: 0.25rem; padding: 0.75rem; border: 1px solid rgba(127, 127, 127, 0.25); border-radius: var(--pb-radius, 8px); }
.pb-report-figure strong { font-size: 1.35rem; }
.pb-report-figure-label { opacity: 0.8; }
.pb-report-compare { font-size: 0.82rem; opacity: 0.85; }
.pb-report-delta { display: inline-block; font-weight: 600; }
.pb-report-delta-up { color: #2e7d4f; }
.pb-report-delta-down { color: #b3261e; }
.pb-report-total-row td { font-weight: 600; }
.pb-dashboard-change { font-size: 0.78rem; }

/* Change session 9 بند ۲۳ ("اندازه‌ی لوگو در هدر و فوتر"): independent, admin-set header/footer logo
   height - --pb-logo-header-h/--pb-logo-footer-h are injected by _Layout.cshtml from ThemeSettings. */
.pb-logo img {
  max-height: var(--pb-logo-header-h, 32px);
}

/* Client request 2026-10-18 (header height): an inline image sits on the text baseline, so the logo link was ~9px taller than the
   logo itself; as a flex container the link is exactly as tall as the logo (the image becomes a flex item, no baseline gap). */
.pb-logo {
  display: flex;
  align-items: center;
}

/* Client request 2026-09-20: the footer logo used to be max-height-only, so a landscape logo file
   rendered much wider than tall. Forced into a square box (same admin-set --pb-logo-footer-h side on
   both axes) with object-fit:contain so the whole logo still shows, scaled down, never cropped - no
   border, matching how every other image box on this page (trust-symbol embed, contact icons) sits
   border-free too. */
.pb-footer-brand img {
  width: var(--pb-logo-footer-h, 48px);
  height: var(--pb-logo-footer-h, 48px);
  object-fit: contain;
}

/* Post-Stage-6 change request #16: main footer (3 columns), small footer (2 rows). Change session 20
   بند ج (part 3): align-items:center vertically centers the three columns relative to each other when
   their content heights differ. */
.pb-main-footer {
  display: grid;
  /* بازبینی فوتر (2026-09-14): column count comes from the MainFooter Container row
     (FooterColumnCount, _SiteFooter.cshtml sets --pb-footer-cols); columns now align to the top so a
     short column doesn't float mid-way beside a tall one. */
  grid-template-columns: repeat(var(--pb-footer-cols, 3), 1fr);
  /* Client request 2026-09-21: the Container row's own "تراز عمودی ستون‌ها" (start/center/end,
     _SiteFooter.cshtml emits --pb-footer-valign / --pb-footer-valign-t); empty = top as before. */
  align-items: var(--pb-footer-valign, start);
  gap: 2rem;
  padding: 2rem clamp(1rem, 4vw, 2.5rem);
}

@media (min-width: 901px) and (max-width: 1200px) {
  .pb-main-footer {
    align-items: var(--pb-footer-valign-t, var(--pb-footer-valign, start));
  }
}

/* بازبینی فوتر (2026-09-14): the column contents had no styling at all - the useful-links menu
   rendered as inline text run together, brand intro / contact lines sat flush. Vertical link lists,
   consistent heading spacing, contact lines with icons. Colours still come from the Container row
   (HeaderFooterCssBuilder) / the theme, never hard-coded here. */
.pb-footer-col {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  min-width: 0;
}

.pb-main-footer h3 {
  margin: 0 0 0.75rem;
  font-size: 1.05rem;
}

.pb-main-footer h3 a {
  text-decoration: none;
  color: inherit;
}

/* Client request 2026-09-21: the two titled footer menus sit side by side in one column. */
.pb-footer-links-pair {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 2.5rem;
}

.pb-footer-links-pair .pb-footer-links {
  flex: 1 1 8rem;
  min-width: 0;
}

.pb-footer-links ul,
.pb-contact-lines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.pb-footer-links a {
  text-decoration: none;
  opacity: 0.88;
  transition: opacity 0.2s ease;
}

.pb-footer-links a:hover {
  opacity: 1;
}

/* Client question 2026-09-21 ("چرا لوگوی فوتر با تراز متن وسط‌چین نمی‌شود؟"): this used to be a flex
   column, and text-align (the only alignment the هدر و فوتر screen emits, per element and per device)
   never moves a flex item - so the intro text centred while the logo stayed at the start edge. Block
   flow with an inline-block logo makes the logo follow the same text-align as the text under it. */
.pb-footer-brand {
  display: block;
}

.pb-footer-brand > img,
.pb-footer-brand > strong {
  display: inline-block;
  margin-bottom: 0.75rem;
}

.pb-footer-brand-intro {
  line-height: 1.8;
  opacity: 0.88;
}

/* Client request 2026-09-21: the shared newsletter form (_NewsletterForm.cshtml, used by the
   NewsletterForm page block and the footer row) - heading and the input+button are two parts; the
   page block keeps them on one line, the footer stacks them (heading row above, field + button row
   below). Both rows follow the element's text-align from the هدر و فوتر screen. */
.pb-newsletter-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
}

.pb-newsletter-fields {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

/* Client request 2026-11-04: on the page block a guest's captcha row, the heading and the email box share one vertical centre. The
   captcha's label sits above its row, so the same empty height below the row keeps that row in the middle of the captcha box. */
.pb-newsletter-block .captcha {
  margin: 0;
  padding-bottom: calc(0.85rem * 1.5 + 0.3rem);
}

.pb-newsletter-block .captcha-label {
  line-height: 1.5;
}

.pb-newsletter-block .captcha-row {
  margin-bottom: 0;
}

@media (max-width: 900px) {
  .pb-newsletter-block .captcha {
    padding-bottom: 0;
  }
}

/* The block's one colour box (NewsletterFormBlockConfig.TextColorHex). */
.pb-newsletter-block--custom-text,
.pb-newsletter-block--custom-text .pb-block-title {
  color: var(--pb-newsletter-text);
}

.pb-newsletter-block--custom-text .captcha .captcha-refresh {
  color: var(--pb-newsletter-text);
  border-color: var(--pb-newsletter-text);
}

.pb-footer-newsletter .pb-newsletter-form {
  display: block;
}

.pb-footer-newsletter .pb-newsletter-label {
  display: block;
  margin-bottom: 0.5rem;
}

.pb-footer-newsletter .pb-newsletter-fields input {
  flex: 1 1 10rem;
  min-width: 0;
}

/* Client question 2026-09-21 (same flex-vs-text-align issue as .pb-footer-brand above): contact lines
   and the trust-symbol embeds now flow inline so the element's per-device text-align from the هدر و
   فوتر screen (start / center / end) really moves them - previously only the headings obeyed it. */
.pb-contact-lines li {
  display: block;
  line-height: 1.6;
}

.pb-contact-lines a {
  text-decoration: none;
}

.pb-contact-lines .pb-icon {
  vertical-align: -0.2em;
  margin-inline-end: 0.5rem;
}

/* Client request 2026-10-18: the site's social icons under the footer contact lines - the same 16px as the phone / clock icons.
   Centered in the column (client 2026-11-09). */
.pb-contact-lines .pb-contact-social {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
}

.pb-contact-lines .pb-contact-social a {
  display: inline-flex;
  line-height: 0;
}

.pb-contact-lines .pb-contact-social img {
  display: block;
  width: 16px;
  height: 16px;
  object-fit: contain;
}

.pb-footer-trust {
  display: block;
  line-height: 0;
}

.pb-footer-trust > * {
  display: inline-block;
  vertical-align: middle;
  margin: 0.375rem;
  line-height: normal;
}

.pb-footer-trust img {
  max-height: 96px;
  width: auto;
}

/* MASTER_CHANGES_PLAN.md queue item 14 ("فوتر: رنگ+فونت تیتر مستقل از تنظیم سراسری"): the footer's
   own column-heading style, independent from the sitewide h3/h4/h5/h6 heading-secondary rule above -
   higher specificity than that plain element selector, so it always wins here. A per-row
   HeaderFooterElement.TitleColorHex (HeaderFooterCssBuilder) is more specific still and keeps winning
   over this when an admin sets one on the "هدر و فوتر" screen. */
.pb-main-footer h3 {
  color: var(--pb-hf-title-color, inherit);
  font-family: var(--pb-hf-title-font, inherit);
}

.pb-small-footer {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border-top: 1px solid var(--pb-hf-divider-color);
}

/* بند ۲۱ ("فوتر پایینی ... بدون آیکون شبکه‌ی اجتماعی → یک ستون وسط‌چین"). */
.pb-small-footer.pb-small-footer-solo {
  justify-content: center;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* Post-Stage-6 change request #10: sidebar on desktop, hamburger on tablet/mobile - shared by the
   customer account panel and the admin panel. */
.pb-hamburger-toggle {
  display: none;
  border: none;
  background: none;
  font-size: 1.4rem;
  cursor: pointer;
  padding: 0.5rem;
}

/* Post-Stage-13 change requests (third round): sidebar now sits in a flex row beside <main>, both
   below the (always full-width) header, instead of a float that used to pull the header up beside
   it - see _Layout.cshtml's .pb-body-row wrapper. */
.pb-body-row {
  display: flex;
  align-items: flex-start;
}

.pb-body-row > .site-main {
  flex: 1;
  min-width: 0;
}

/* Change session 23 بند پ: every --sidebar-* custom property is only ever set inline on the
   site-side `_SidebarNav` include (`_Layout.cshtml`/`_SidebarNav.cshtml`), never on the admin
   panel's own sidebar - so the admin panel keeps exactly these fallback values, unchanged. */
.pb-sidebar-nav {
  width: var(--sidebar-width, 220px);
  flex-shrink: 0;
  border-inline-end: 1px solid var(--sidebar-divider, var(--pb-border));
  padding: 1rem 0.5rem;
  background-color: var(--sidebar-bg, transparent);
  background-image: var(--sidebar-bg-image, none);
  background-size: var(--sidebar-bg-size, cover);
  background-repeat: var(--sidebar-bg-repeat, no-repeat);
  background-position: center;
  background-attachment: var(--sidebar-bg-attachment, scroll);
  color: var(--sidebar-text, inherit);
  /* Client request: admin-configurable drop shadow (color + blur) - "none" unless a shadow color was
     actually set in ظاهر و قالب, so an untouched install looks exactly as before. */
  box-shadow: var(--sidebar-shadow, none);
}

.pb-sidebar-group {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pb-sidebar-group li a {
  display: block;
  padding: 0.4rem 0.5rem;
  text-decoration: none;
  border-radius: var(--pb-radius-sm, 6px);
  /* Client request 2026-10-18: sub-items follow the sidebar's own text colour (set in ظاهر و قالب), not the
     sitewide link colour; unset = the link colour as before. */
  color: var(--sidebar-text, var(--pb-primary));
}

.pb-sidebar-group li a:hover {
  background: var(--sidebar-hover-bg, color-mix(in srgb, var(--pb-primary) 10%, transparent));
  color: var(--sidebar-hover-text, inherit);
}

.pb-sidebar-group li a[aria-current="page"] {
  background: color-mix(in srgb, var(--pb-primary) 16%, transparent);
  font-weight: 600;
  color: var(--sidebar-hover-text, var(--pb-primary));
}

/* Item 33 (2026-10-18): personal-pages tab bar on top of those pages (_AccountNavBar.cshtml), styled by the
   former customer-sidebar settings (--sidebar-* set inline on the bar only). Phone: 3-column grid, never a
   horizontal scroll. */
.pb-account-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-block-end: 1.25rem;
  padding: var(--sidebar-padding, 8px);
  border-block-end: 1px solid var(--sidebar-divider, var(--pb-border));
  border-radius: var(--pb-radius-sm, 6px);
  background-color: var(--sidebar-bg, transparent);
  background-image: var(--sidebar-bg-image, none);
  background-size: var(--sidebar-bg-size, cover);
  background-repeat: var(--sidebar-bg-repeat, no-repeat);
  background-position: center;
  background-attachment: var(--sidebar-bg-attachment, scroll);
  box-shadow: var(--sidebar-shadow, none);
}

.pb-account-bar a {
  padding: 0.45rem 0.85rem;
  text-decoration: none;
  text-align: center;
  border-radius: var(--pb-radius-sm, 6px);
  color: var(--sidebar-text, var(--pb-primary));
}

.pb-account-bar a:hover {
  background: var(--sidebar-hover-bg, color-mix(in srgb, var(--pb-primary) 10%, transparent));
  color: var(--sidebar-hover-text, inherit);
}

.pb-account-bar a[aria-current="page"] {
  background: color-mix(in srgb, var(--pb-primary) 16%, transparent);
  font-weight: 600;
  color: var(--sidebar-hover-text, var(--pb-primary));
}

@media (max-width: 600px) {
  .pb-account-bar {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .pb-account-bar a {
    padding: 0.45rem 0.25rem;
    font-size: 0.85rem;
    overflow-wrap: anywhere;
  }
}

/* Change session 36 (MASTER_CHANGES_PLAN.md بخش ۸.۸): admin panel's own sidebar controls - mirrors the
   customer-sidebar block above field-for-field, scoped to .pb-admin-body so it never reaches the
   customer sidebar or the site-side "admin viewing the site" nav (both render the same base classes
   but never get pb-admin-body on <body>). Every --admin-sidebar-* variable (_Layout.cshtml) already
   defaults to this same fallback value, so this rule changes nothing on an untouched install. */
.pb-admin-body .pb-sidebar-nav {
  width: var(--admin-sidebar-width);
  background-color: var(--admin-sidebar-bg);
  background-image: var(--admin-sidebar-bg-image);
  background-size: var(--admin-sidebar-bg-size, cover);
  background-repeat: var(--admin-sidebar-bg-repeat, no-repeat);
  background-attachment: var(--admin-sidebar-bg-attachment, scroll);
  border-inline-end-color: var(--admin-sidebar-divider);
  color: var(--admin-sidebar-text);
  box-shadow: var(--admin-sidebar-shadow, none);
}

.pb-admin-body .pb-sidebar-group li a {
  color: var(--admin-sidebar-link, var(--pb-primary));
}

.pb-admin-body .pb-sidebar-group li a[aria-current="page"] {
  color: var(--admin-sidebar-link-active, var(--pb-primary));
}

.pb-admin-body .pb-sidebar-group li a:hover {
  background: var(--admin-sidebar-hover-bg);
  color: var(--admin-sidebar-hover-text);
}

/* Session 12 (MASTER_CHANGES_PLAN.md section 2.9 #40/#48): each admin sidebar group is an
   independently collapsible <details> - native disclosure widget, no JS required for the
   expand/collapse itself (sidebar-nav.js only adds cross-navigation persistence via localStorage). */
.pb-sidebar-collapsible {
  margin-bottom: 0.25rem;
}

.pb-sidebar-collapsible summary {
  cursor: pointer;
  list-style: none;
  padding: 0.5rem 0.5rem;
  font-size: 0.85rem;
  font-weight: 700;
  /* MASTER_CHANGES_PLAN.md queue item 14 ("سایدبار: رنگ+فونت تیتر مستقل از تنظیم سراسری"): the
     customer sidebar's own group-title color/font, independent from --pb-heading-*. The admin panel's
     own sidebar keeps its separate, more specific .pb-admin-body override below unchanged. */
  color: var(--pb-hf-title-color, var(--pb-fg));
  font-family: var(--pb-hf-title-font, inherit);
  opacity: 0.7;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: var(--pb-radius-sm, 6px);
}

.pb-sidebar-collapsible summary::-webkit-details-marker {
  display: none;
}

.pb-sidebar-collapsible summary::before {
  content: "▸";
  display: inline-block;
  margin-inline-end: 0.4rem;
  transition: transform 0.15s ease;
}

.pb-sidebar-collapsible[open] summary::before {
  transform: rotate(90deg);
}

.pb-sidebar-collapsible summary:hover {
  background: color-mix(in srgb, var(--pb-primary) 8%, transparent);
}

/* Change session 27 (MASTER_CHANGES_PLAN.md بخش ۶.۹): admin-configurable background color, scoped to
   the real admin panel only - .pb-sidebar-collapsible is shared with the site-side "admin viewing the
   site" nav (_Layout.cshtml), which must stay untouched. --admin-menu-group-bg defaults to transparent
   (today's look) when the admin hasn't set a color. Change session 36 (بخش ۸.۸) added the text-color
   line - --admin-sidebar-text defaults to "inherit" (today's --pb-fg via inheritance, unchanged look). */
.pb-admin-body .pb-sidebar-collapsible summary {
  background: var(--admin-menu-group-bg);
  color: var(--admin-sidebar-text);
}

/* Change session 27 (MASTER_CHANGES_PLAN.md بخش ۶.۹): public FAQ page accordion (Views/Faq/Index.cshtml)
   had no CSS at all before this session - --faq-accordion-bg defaults to transparent (today's unstyled
   look) when the admin hasn't set a color. */
.pb-faq-item summary {
  background: var(--faq-accordion-bg);
}

/* Client request 2026-10-19: the FAQ page's category links (each jumps to its section, clear of the sticky
   header), the pages' "related questions" boxes and the FAQ block. Answers keep the admin's line breaks. */
.pb-faq-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-block-end: 1.5rem;
}

.pb-faq-nav a {
  padding: 0.35rem 0.9rem;
  border: 1px solid var(--pb-border);
  border-radius: 999px;
  color: inherit;
  text-decoration: none;
  font-size: 0.9rem;
}

.pb-faq-nav a:hover,
.pb-faq-nav a:focus-visible {
  border-color: var(--pb-primary);
  color: var(--pb-primary);
}

.pb-faq-category {
  scroll-margin-top: 140px;
}

.pb-faq-item p {
  white-space: pre-line;
}

.pb-faq-context {
  margin-block: 2.5rem 1rem;
}

.pb-faq-all {
  display: inline-block;
  margin-block-start: 0.75rem;
}

.pb-sidebar-collapsible ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Post-Stage-13 change requests #8/#9/#10: admin panel top bar (view-site link / signed-in admin
   icon with a name tooltip / logout) - previously _Layout.cshtml rendered no header at all under
   /admin/**. Border uses the same --pb-primary brand color variable _Layout.cshtml already sets
   from ThemeAndAppearance, so the bar carries the site's real identity instead of a separate skin. */
/* Change session 36 (MASTER_CHANGES_PLAN.md بخش ۸.۸): optional background color/image for the admin
   top bar itself - --admin-topbar-bg(-image) default to transparent/none (_Layout.cshtml), so this
   rule paints nothing extra on an untouched install. */
.pb-admin-body .pb-admin-topbar {
  background-color: var(--admin-topbar-bg);
  background-image: var(--admin-topbar-bg-image);
  background-size: var(--admin-topbar-bg-size, cover);
  background-repeat: var(--admin-topbar-bg-repeat, no-repeat);
  background-position: center;
  background-attachment: var(--admin-topbar-bg-attachment, scroll);
}

.pb-admin-topbar-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.6rem 1rem;
  border-bottom: 3px solid var(--pb-primary, #7a1f3d);
  gap: 1rem;
}

.pb-admin-topbar-brand img {
  max-height: 32px;
}

.pb-admin-topbar-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.pb-admin-topbar-link {
  text-decoration: none;
  border: none;
  background: none;
  font: inherit;
  cursor: pointer;
  color: inherit;
}

.pb-user-menu {
  font-size: 1.2rem;
}

.pb-admin-back-link {
  display: inline-block;
  margin-bottom: 0.75rem;
  text-decoration: none;
  color: var(--pb-primary);
  font-size: 0.9rem;
}

/* Post-Stage-13 change requests (third round, item #19): unanswered-chat badge/row highlight. */
.pb-chat-awaiting-badge {
  display: inline-block;
  background: var(--pb-primary);
  color: #fff;
  border-radius: 999px;
  padding: 0.1rem 0.6rem;
  font-size: 0.9rem;
  margin-inline-start: 0.5rem;
}

.pb-chat-awaiting-badge[hidden] {
  display: none;
}

.pb-chat-awaiting-row {
  background: color-mix(in srgb, var(--pb-primary) 12%, transparent);
}

/* Post-Stage13 change requests item #20 (final approved design): floating draggable chat popups. */
.chat-popup-dock {
  position: fixed;
  inset-inline-end: 1rem;
  bottom: 0;
  z-index: 1200;
  pointer-events: none;
}

.chat-popup {
  position: fixed;
  width: 300px;
  height: 456px; /* Change session 39 (بند الف): 380px -> 456px (20% larger), same ratio as the
                    Crm.PublicChat.PanelMaxHeightPx seed change (320 -> 384) for the customer widget. */
  display: flex;
  flex-direction: column;
  background: var(--pb-bg, #fff);
  color: var(--pb-fg, #1a1a1a);
  border: 1px solid var(--pb-border, #ddd);
  border-radius: 10px 10px 0 0;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  overflow: hidden;
  pointer-events: auto;
}

.chat-popup-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.75rem;
  background: var(--pb-primary);
  color: #fff;
  cursor: move;
  user-select: none;
}

.chat-popup.js-chat-flash .chat-popup-header {
  background: #e0862e;
  animation: chat-popup-flash 1s ease-in-out infinite;
}

/* Change session 39 (بند ب): visual-only "ended" marker - the popup/form stay usable, only the header
   dims and the end-conversation button disables until a fresh message is sent. */
.chat-popup.chat-popup-ended .chat-popup-header {
  background: #8a8f98;
}

.chat-popup-end-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

@keyframes chat-popup-flash {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

.chat-popup-title {
  font-size: 0.85rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-popup-actions {
  display: flex;
  gap: 0.4rem;
  flex-shrink: 0;
}

.chat-popup-actions button {
  border: none;
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.75rem;
  padding: 0.15rem 0.5rem;
}

.chat-popup-messages {
  flex: 1;
}

/* .chat-popup-form reuses .js-chat-form's display:flex/border-top (site.css chat-widget section) -
   no separate rule needed. */

/* Post-Stage-13 change requests (third round, item #23): translation-table horizontal scroll. */
.pb-table-scroll {
  overflow-x: auto;
  max-width: 100%;
}

/* Session 12 (MASTER_CHANGES_PLAN.md section 2.9 #38/#47): shared admin list pager. */
/* Client request 2026-10-18: the catalog/block "next page" links (.pagination.cat-pager, _BlockPager and Products/Index)
   get 25px of free space above and below. */
.pagination.cat-pager,
.pagination {
  margin-block: 25px;
}

.pb-pagination {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 1rem 0;
  font-size: 0.9rem;
}

.pb-pagination a {
  padding: 0.35rem 0.8rem;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 6px);
  text-decoration: none;
  color: var(--pb-primary);
}

.pb-pagination a:hover {
  background: color-mix(in srgb, var(--pb-primary) 10%, transparent);
}

/* Session 12 (MASTER_CHANGES_PLAN.md section 2.9 #42): "در همه‌ی فرم‌ها، فیلدهای هر زبان دیگر با یک
   خط جداکننده مجزا شود" - every admin Create/Edit form's per-language field group already uses a
   plain <fieldset><legend>, this just gives that native element a real "separate box" look instead
   of the unstyled browser-default thin gray line. */
.pb-page-shell fieldset {
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 8px);
  padding: 1rem;
  margin: 1rem 0;
}

.pb-page-shell fieldset legend {
  padding: 0 0.5rem;
  font-weight: 700;
  color: var(--pb-primary);
  text-transform: uppercase;
  font-size: 0.85rem;
}

/* Session 66 (MASTER_CHANGES_PLAN.md آیتم ۱۷الف): the admin block-tree screen (_BlockTree.cshtml) -
   nested <ul>/<li>, not a <table>, since a block list now nests (Section > block|InnerSection >
   column > block) and a flat table can't show that. Scoped to .pb-admin-body like every other admin
   list/form rule in this stylesheet. */
.pb-admin-body .pb-block-tree {
  list-style: none;
  margin: 0 0 1rem;
  padding-inline-start: 1rem;
  border-inline-start: 2px solid var(--pb-border);
}

.pb-admin-body .pb-block-tree > li {
  margin-block: 0.5rem;
}

.pb-admin-body .pb-block-tree-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.pb-admin-body .pb-block-tree-title {
  color: var(--pb-muted-text, #666);
  font-size: 0.9rem;
}

.pb-admin-body .pb-block-tree-lock {
  font-size: 0.75rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--pb-primary) 15%, transparent);
  color: var(--pb-primary);
}

.pb-admin-body .pb-block-tree-columns {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin-inline-start: 1rem;
}

.pb-admin-body .pb-block-tree-column {
  flex: 1;
  min-width: 200px;
  border: 1px dashed var(--pb-border);
  border-radius: var(--pb-radius-sm, 6px);
  padding: 0.5rem;
}

/* بازبینی PageBuilder (2026-09-14): richer tree rows - translated type badge, text excerpt, order
   numbers, status badges and the ↑/↓/duplicate/delete action cluster pushed to the row's end. */
.pb-admin-body .pb-block-tree-row {
  flex-wrap: wrap;
  padding: 0.35rem 0.5rem;
  border-radius: var(--pb-radius-sm, 6px);
}

.pb-admin-body .pb-block-tree-row:hover {
  background: color-mix(in srgb, var(--pb-primary) 5%, transparent);
}

.pb-admin-body .pb-block-tree-type {
  font-size: 0.8rem;
  font-weight: 700;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--pb-secondary) 18%, transparent);
  white-space: nowrap;
}

.pb-admin-body .pb-block-tree-type--section,
.pb-admin-body .pb-block-tree-type--innersection {
  background: color-mix(in srgb, var(--pb-primary) 18%, transparent);
}

.pb-admin-body .pb-block-tree-excerpt {
  color: var(--pb-muted-text, #666);
  font-size: 0.85rem;
  max-width: 28ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pb-admin-body .pb-block-tree-order,
.pb-admin-body .pb-block-tree-badge {
  font-size: 0.72rem;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  border: 1px solid var(--pb-border);
  color: var(--pb-muted-text, #666);
  white-space: nowrap;
}

.pb-admin-body .pb-block-tree-badge--off {
  border-color: #c0392b;
  color: #c0392b;
}

.pb-admin-body .pb-block-tree-unpublished > .pb-block-tree-row {
  opacity: 0.6;
}

.pb-admin-body .pb-block-tree-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-inline-start: auto;
}

.pb-admin-body .pb-inline-form {
  display: inline;
}

.pb-admin-body .pb-tree-btn {
  font: inherit;
  font-size: 0.8rem;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 6px);
  background: transparent;
  cursor: pointer;
  color: inherit;
}

.pb-admin-body .pb-tree-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.pb-admin-body .pb-tree-btn--danger {
  color: #c0392b;
  border-color: color-mix(in srgb, #c0392b 40%, transparent);
}

.pb-admin-body .pb-block-tree-add {
  display: inline-block;
  font-size: 0.85rem;
  margin: 0.25rem 0 0.5rem;
}

/* Compact breakpoint × side spacing grid and the one-line breakpoint rows on the block form. */
.pb-admin-body .pb-advanced-spacing > summary {
  cursor: pointer;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.pb-admin-body .pb-spacing-grid input,
.pb-admin-body .pb-breakpoint-row input {
  width: 6rem;
}

.pb-admin-body .pb-spacing-grid th,
.pb-admin-body .pb-spacing-grid td {
  padding: 0.3rem 0.5rem;
  text-align: center;
}

.pb-admin-body .pb-breakpoint-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 0.85rem;
}

.pb-admin-body .pb-breakpoint-row label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  font-size: 0.9rem;
}

.pb-admin-body .pb-nesting-warning {
  border: 1px solid var(--pb-border);
  border-inline-start: 4px solid var(--pb-primary);
  padding: 0.75rem 1rem;
  border-radius: var(--pb-radius-sm, 6px);
  background: color-mix(in srgb, var(--pb-primary) 6%, transparent);
}

/* Change session 36 (MASTER_CHANGES_PLAN.md بخش ۸.۸): optional background color/image for the admin
   form-card fieldset above - scoped to .pb-admin-body only (unlike the border/padding rule above,
   which is sitewide - login/checkout/profile forms also use <fieldset>, and must stay untouched).
   --admin-formcard-bg(-image) default to transparent/none, so this paints nothing extra by default. */
.pb-admin-body .pb-page-shell fieldset {
  background-color: var(--admin-formcard-bg);
  background-image: var(--admin-formcard-bg-image);
  background-size: var(--admin-formcard-bg-size, cover);
  background-repeat: var(--admin-formcard-bg-repeat, no-repeat);
  background-position: center;
  background-attachment: var(--admin-formcard-bg-attachment, scroll);
}

/* Session 12 (MASTER_CHANGES_PLAN.md section 2.9 #46): "ظاهر پنل‌های ادمین: مدرن، حرفه‌ای،
   قابل‌تنظیم" - admin tables/forms had no styling at all before this (raw browser-default <table>/
   <input>), scoped to .pb-admin-body only so the public storefront's own card/grid design (session
   9/10) is untouched. Colors still come from the same ThemeAndAppearance variables as the rest of the
   site, so this stays "قابل‌تنظیم" through the existing theme settings rather than a hardcoded skin. */
.pb-admin-body .pb-page-shell table {
  width: 100%;
  border-collapse: collapse;
  margin: 1rem 0;
  font-size: 0.92rem;
}

.pb-admin-body .pb-page-shell th,
.pb-admin-body .pb-page-shell td {
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--pb-border);
  text-align: start;
}

.pb-admin-body .pb-page-shell thead th {
  background: color-mix(in srgb, var(--pb-primary) 8%, transparent);
  font-weight: 700;
}

.pb-admin-body .pb-page-shell tbody tr:hover {
  background: color-mix(in srgb, var(--pb-primary) 4%, transparent);
}

/* Client request 2026-10-07: a long unbroken value (the maintenance bypass link, a token) wraps instead of
   widening the page on a phone. */
.pb-admin-body .pb-page-shell code {
  overflow-wrap: anywhere;
}

/* Change session 24: this block used to be scoped to .pb-admin-body only, so every site-facing form
   (login/register, checkout, profile, contact, measurement) rendered raw unstyled browser inputs. Not
   scoped to .pb-page-shell either (unlike the table rules right below, deliberately still admin-only):
   #auth-modal/#media-library-modal/#entity-picker-modal (_Layout.cshtml) all render as dialogs OUTSIDE
   .pb-page-shell, so a .pb-page-shell-scoped rule would silently miss the login/register popup and
   every admin file-picker input - this applies to the whole document instead. Added a focus ring
   (previously no element on the whole site had any :focus style at all). */
.form-field {
  margin-bottom: 0.85rem;
}

.form-field label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.3rem;
  font-size: 0.9rem;
}

/* .form-field never had its own field-width rule, so a plain input fell back to the browser's
   ~20-character intrinsic default while .pb-password-field's extra end-padding (reserved for the
   eye button below) added on top of that same default under content-box sizing - two fields in the
   same form rendered at visibly different widths (e.g. the login popup's phone/email vs password
   field). Same width:100%/border-box idiom already used by .chat-guest-name-form input and
   .ml-metadata-field input elsewhere in this file. .cat-filter-bar's .form-field already lays its
   fields out with its own flex/min-width rule (below), so this is unaffected there either way.
   Checkbox/radio inputs (e.g. Register's UserType choice) are excluded - those are small
   fixed-size native controls, not text fields, and stretching them to 100% width badly distorts
   them. Same reasoning covers type="color" (بازبینی گالری فایل‌ها) - <color-input>'s own swatch
   (.color-input-swatch, further down this file) is a small fixed-size square; without this exclusion
   this higher-specificity rule stretched it to the full field width on every colour field site-wide,
   crushing it into an unusable bar instead of a clickable swatch. */
.form-field input:not([type="checkbox"]):not([type="radio"]):not([type="color"]),
.form-field select,
.form-field textarea {
  width: 100%;
  box-sizing: border-box;
}

/* Client request: every colour-code field opens a colour dialog. Rendered by the <color-input> tag
   helper as a swatch + the real (still empty-able) text field + an optional clear button; see that
   class's doc comment for why the bound field stays text rather than becoming input[type=color]. */
/* Item 32: light/dark background slider (<tone-input>). */
.tone-input {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.tone-input input[type="range"] {
  flex: 1;
  min-inline-size: 8rem;
  accent-color: var(--pb-primary);
}

.tone-input output {
  min-inline-size: 4ch;
  font-variant-numeric: tabular-nums;
  direction: ltr;
  text-align: center;
}

.color-input {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.color-input-swatch {
  inline-size: 2.4rem;
  block-size: 2.1rem;
  padding: 0;
  border: 1px solid var(--pb-border, #ccc);
  border-radius: var(--pb-radius-sm, 8px);
  background: none;
  cursor: pointer;
  flex-shrink: 0;
}

.color-input-text {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  text-transform: lowercase;
  inline-size: 10ch;
}

/* An unset optional colour reads as "not customized", not as an empty required field. */
.color-input-text--empty {
  opacity: 0.75;
}

.color-input-clear {
  border: 1px solid var(--pb-border, #ccc);
  border-radius: var(--pb-radius-sm, 8px);
  background: none;
  color: inherit;
  cursor: pointer;
  line-height: 1;
  padding: 0.35rem 0.55rem;
  font-size: 1rem;
  flex-shrink: 0;
}

.color-input-clear:hover {
  background: color-mix(in srgb, currentColor 10%, transparent);
}

/* Session 71 (client request: "قابلیت حذف تصویر ... باید وجود داشته باشد") - the media-picker's own
   clear button, same look as .color-input-clear above for the same "×, small, outlined" affordance. */
/* Client request 2026-09-21: preview of the file chosen through _MediaPickerField.cshtml. */
.media-picker-preview {
  display: block;
  max-height: 96px;
  max-width: 100%;
  margin-bottom: 0.5rem;
  border-radius: var(--pb-radius-sm, 8px);
}

.media-picker-clear {
  border: 1px solid var(--pb-border, #ccc);
  border-radius: var(--pb-radius-sm, 8px);
  background: none;
  color: inherit;
  cursor: pointer;
  line-height: 1;
  padding: 0.35rem 0.55rem;
  font-size: 1rem;
  margin-inline-start: 0.4rem;
}

.media-picker-clear:hover {
  background: color-mix(in srgb, currentColor 10%, transparent);
}

/* Session 48: shared inline "add the missing color right here" dialog (_ColorQuickAddDialog.cshtml),
   reused by every admin ColorId picker. */
.color-quickadd-dialog {
  max-width: 360px;
  width: 90vw;
  border: 1px solid var(--pb-border, #ccc);
  border-radius: var(--pb-radius-sm, 8px);
  padding: 1.25rem;
}

.color-quickadd-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.color-quickadd-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

.color-quickadd-error {
  color: #b3261e;
  font-size: 0.85rem;
}

[data-color-select-wrapper] {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

/* Session 49: per-color product photo groups (CatalogModelVariants admin screen). */
.color-image-group {
  border: 1px solid var(--pb-border, #ccc);
  border-radius: var(--pb-radius-sm, 8px);
  padding: 0.75rem;
  margin-bottom: 0.75rem;
}

.color-image-group-existing {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.5rem 0;
}

input[type="text"],
input[type="number"],
input[type="email"],
input[type="password"],
input[type="tel"],
input[type="search"],
input[type="date"],
input:not([type]),
select,
textarea {
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 6px);
  font: inherit;
  background: var(--pb-bg);
  color: var(--pb-fg);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

input[type="text"]:focus,
input[type="number"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="tel"]:focus,
input[type="search"]:focus,
input[type="date"]:focus,
input:not([type]):focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--pb-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pb-primary) 20%, transparent);
}

/* Client request: every real form (public + admin) centered in its section, filling at least 3/5 of
   the content width, in a shadowed card, with orderly field spacing. :has(.form-field) targets exactly
   the forms that already wrap their fields in .form-field (every real multi-field form site-wide) and
   naturally skips one-button/inline forms that never use that wrapper (logout, add-to-wishlist, chat,
   language switcher, newsletter, search) - no per-view changes needed. .cat-filter-bar is the one
   .form-field user that already has its own horizontal-bar look, so it's excluded. Field spacing is
   already handled by .form-field's own margin-bottom above; the card just gives it room to read as
   "orderly" instead of packed against the page edge.
   Scoped to .pb-page-shell (real page-body forms only): #auth-modal/#media-library-modal/
   #entity-picker-modal/#chat-widget all render as siblings AFTER .pb-page-shell (_Layout.cshtml) and
   are already their own appropriately-sized shadowed card/dialog - #auth-modal is a fixed 360px
   <dialog> with its own background/border/radius/shadow already, so applying this rule inside it too
   would nest a second, smaller shadowed box inside the first. The full-page (non-modal) Login view at
   /Account/Login renders the same _LoginFormPartial inside .pb-page-shell, so it still gets styled. */
.pb-page-shell form:has(.form-field):not(.cat-filter-bar),
.pb-admin-body .pb-page-shell .pb-admin-card {
  width: var(--forms-width, 60%);
  margin: 0 auto 2rem;
  padding: 1.75rem;
  background-color: var(--forms-bg, var(--pb-bg));
  background-image: var(--forms-bg-image, none);
  background-size: var(--forms-bg-size, cover);
  background-repeat: var(--forms-bg-repeat, no-repeat);
  background-position: center;
  background-attachment: var(--forms-bg-attachment, scroll);
  color: var(--forms-text, inherit);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  box-shadow: var(--pb-shadow-lg, 0 12px 30px rgba(0, 0, 0, 0.1));
}

/* Client request 2026-09-20 ("تمام فرم‌های پنل ادمین به عرض فضای آزاد کنار سایدپنل ... گزینه‌ها کنار
   یکدیگر"): inside the admin panel every form card spans the full content width - the --forms-width
   percentage above stays a public-site setting (login/contact/profile/checkout forms are explicitly
   exempt from this change) - and lays its fields out on an auto-fill grid: plain fields (text /
   number / select / date / colour / checkbox / file) sit side by side in ~280px columns, while
   anything that is not a plain field - headings, notes, tables, lists, links, buttons, fieldsets
   (which become grids of their own) and textarea fields - takes a whole row, so DOM order is kept
   and nothing is ever squeezed. .pb-admin-card is the same card for the non-form sections (media
   lists, option tables, management links, per-section settings) that used to sit on the bare page
   below a main form ("در باکسی مانند فرم اصلی قرار گیرند نه در فضای آزاد"); it shares the card look
   through the selector list above and only this block's sizing. */
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar),
.pb-admin-body .pb-page-shell .pb-admin-card {
  width: 100%;
  box-sizing: border-box;
  margin: 0 0 1.5rem;
  padding: 1.25rem 1.5rem;
}

.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar),
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) fieldset {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
  column-gap: 1rem;
  align-items: end;
}

.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) > :not(.form-field):not(label),
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) fieldset > :not(.form-field):not(label),
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) .form-field:has(textarea),
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) .form-field--wide {
  grid-column: 1 / -1;
}

/* Bare <label> children (checkbox lists such as "related models", per-item quantity) stay ordinary
   cells - one checkbox per column instead of one per row - and centre on their row. */
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) > label,
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) fieldset > label {
  margin-bottom: 0.5rem;
}

/* A grid item stretches to its cell by default - buttons and links must keep their natural width. */
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) > button,
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) > a,
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) fieldset > button,
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) fieldset > a {
  justify-self: start;
}

.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) > button[type="submit"] {
  margin-top: 0.5rem;
}

.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) fieldset {
  margin: 0 0 1rem;
}

/* The grid display above would otherwise beat the UA's [hidden]{display:none} on the block-type
   fieldsets pagebuilder-block-form.js toggles with el.hidden - keep hidden things hidden. */
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) [hidden] {
  display: none;
}

/* Card titles: the h2 (and any h3 sub-heading) now lives INSIDE its card, as the card's title. */
.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) > h2,
.pb-admin-body .pb-page-shell .pb-admin-card > h2 {
  font-size: 1.15rem;
  margin: 0 0 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--pb-border);
}

.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) > h2:not(:first-child),
.pb-admin-body .pb-page-shell .pb-admin-card > h2:not(:first-child) {
  margin-top: 1.25rem;
}

.pb-admin-body .pb-page-shell form:has(.form-field):not(.cat-filter-bar) h3,
.pb-admin-body .pb-page-shell .pb-admin-card h3 {
  font-size: 1rem;
  margin: 0.75rem 0 0.5rem;
}

.pb-admin-body .pb-page-shell .pb-admin-card > :last-child {
  margin-bottom: 0;
}

/* A form that lives INSIDE a section card (e.g. "add option" under the options table) is part of that
   card: it keeps the field grid but drops its own border/shadow/padding so the section reads as one box. */
.pb-admin-body .pb-page-shell .pb-admin-card form:has(.form-field):not(.cat-filter-bar) {
  padding: 0;
  margin: 0.75rem 0 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: none;
}

/* Inline action rows inside a card (attach-media pickers, filter rows, per-row save forms). */
.pb-admin-body .pb-page-shell .pb-form-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0.5rem 0;
}

/* Health page trace view (client request 2026-10-04): a stack trace keeps its line breaks but wraps
   instead of pushing the card wider than a phone screen; long values (User-Agent, paths) break too. */
.pb-admin-body .pb-page-shell .pb-log-pre {
  text-align: left;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 0.8rem;
  max-height: 32rem;
  overflow: auto;
  margin: 0 0 1rem;
}

.pb-admin-body .pb-page-shell .pb-log-value {
  overflow-wrap: anywhere;
  text-align: left;
}

/* Security report (client request 2026-10-04): severity reads at a glance - Critical red, Warning
   amber, Info muted. Mixed from fixed hues (not theme tokens) so it never vanishes in a palette. */
.pb-admin-body .pb-page-shell .pb-sev {
  display: inline-block;
  padding: 0.05rem 0.55rem;
  border-radius: 999px;
  font-size: 0.8rem;
  white-space: nowrap;
}

.pb-admin-body .pb-page-shell .pb-sev-Critical { background: color-mix(in srgb, #c62828 18%, transparent); color: #a31515; font-weight: 600; }
.pb-admin-body .pb-page-shell .pb-sev-Warning { background: color-mix(in srgb, #ef8f00 20%, transparent); color: #8a5300; }
.pb-admin-body .pb-page-shell .pb-sev-Info { background: color-mix(in srgb, #666 14%, transparent); }
/* Client request 2026-10-06: online (blue) / idle (grey) session dot of the traffic report. */
.pb-online-dot { display: inline-block; width: .75em; height: .75em; border-radius: 50%; background: #9e9e9e; vertical-align: middle; }
.pb-online-dot-on { background: #1e88e5; }
/* Client request 2026-10-06: visitor country map (shade = visitors, log scale) and its flag legend. */
.pb-geo-map { display: block; width: 100%; height: auto; max-height: 520px; }
.pb-geo-map path { fill: color-mix(in srgb, currentColor 10%, transparent); stroke: color-mix(in srgb, currentColor 35%, transparent); stroke-width: .3; }
.pb-geo-map path:hover { stroke: currentColor; stroke-width: .8; }
.pb-geo-map path.pb-geo-l1 { fill: color-mix(in srgb, #1e88e5 25%, transparent); }
.pb-geo-map path.pb-geo-l2 { fill: color-mix(in srgb, #1e88e5 42%, transparent); }
.pb-geo-map path.pb-geo-l3 { fill: color-mix(in srgb, #1e88e5 60%, transparent); }
.pb-geo-map path.pb-geo-l4 { fill: color-mix(in srgb, #1e88e5 80%, transparent); }
.pb-geo-map path.pb-geo-l5 { fill: #1e88e5; }
.pb-geo-legend { display: flex; flex-wrap: wrap; gap: .75rem 1.25rem; margin: 1rem 0 0; padding: 0; list-style: none; }
.pb-geo-legend li { display: flex; flex-direction: column; align-items: center; gap: .15rem; min-width: 4.5rem; text-align: center; }
.pb-geo-legend img { width: 48px; height: 36px; object-fit: cover; border-radius: 3px; box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); }
.pb-geo-legend span { font-size: .85em; opacity: .8; }
/* Client request 2026-10-07: a long field explanation as a tooltip instead of a paragraph - shown on hover,
   keyboard focus, or a tap (the <details> toggles open, which phones need since they have no hover). */
.pb-label-row { position: relative; display: flex; align-items: center; gap: .4em; margin-bottom: .3rem; }
.pb-label-row label { margin-bottom: 0 !important; }
/* The tip is anchored to the whole label row (not the small icon), so it spans the field and never runs off a narrow screen. */
.pb-help { display: inline-block; }
.pb-help > summary { display: inline-flex; align-items: center; justify-content: center; width: 1.25em; height: 1.25em; border-radius: 50%; list-style: none; cursor: help; font-size: .85rem; font-weight: 700; font-style: italic; background: color-mix(in srgb, currentColor 14%, transparent); }
.pb-help > summary::-webkit-details-marker { display: none; }
.pb-help > summary::after { content: attr(data-tip); position: absolute; inset-block-start: calc(100% + .4em); inset-inline: 0; z-index: 30; max-width: 32rem; padding: .55em .75em; border-radius: 6px; background: #2b2b2b; color: #fff; font-size: .85rem; font-weight: 400; font-style: normal; line-height: 1.7; white-space: normal; box-shadow: 0 4px 14px rgb(0 0 0 / .25); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .15s; }
.pb-help > summary:hover::after, .pb-help > summary:focus-visible::after, .pb-help[open] > summary::after { opacity: 1; visibility: visible; }

/* Security log layer 4: unseen security alerts beside the sidebar link (and its group title). */
.pb-nav-badge {
  display: inline-block;
  min-width: 1.4em;
  margin-inline-start: 0.4rem;
  padding: 0 0.4em;
  border-radius: 999px;
  background: #c62828;
  color: #fff;
  font-size: 0.75rem;
  line-height: 1.4;
  text-align: center;
  font-weight: 600;
}

.pb-admin-body .pb-page-shell .pb-admin-filter-chip {
  display: inline-flex;
  gap: 0.6rem;
  align-items: center;
  padding: 0.2rem 0.75rem;
  border-radius: 999px;
  background: color-mix(in srgb, #666 12%, transparent);
}

.pb-admin-body .pb-page-shell .pb-admin-card ul {
  margin: 0 0 1rem;
  padding-inline-start: 1.25rem;
}

.pb-admin-body .pb-page-shell .pb-admin-card li {
  margin-bottom: 0.4rem;
}

/* Change session 36 (MASTER_CHANGES_PLAN.md بخش ۸.۸): the page-title background properties added below
   default to transparent/none (--admin-pagetitle-bg(-image), _Layout.cshtml), so an untouched install
   keeps exactly this margin-only look. */
.pb-admin-body .pb-page-shell h1 {
  margin-bottom: 1.25rem;
  background-color: var(--admin-pagetitle-bg);
  background-image: var(--admin-pagetitle-bg-image);
  background-size: var(--admin-pagetitle-bg-size, cover);
  background-repeat: var(--admin-pagetitle-bg-repeat, no-repeat);
  background-position: center;
  background-attachment: var(--admin-pagetitle-bg-attachment, scroll);
}

@media (max-width: 900px) {
  .pb-page-shell form:has(.form-field):not(.cat-filter-bar) {
    width: 100%;
    padding: 1.25rem;
    /* Item 34a: content-box + 100% width + padding made every form card 26px wider than the column. */
    box-sizing: border-box;
  }

  .pb-hamburger-toggle {
    display: inline-block;
  }

  .pb-body-row {
    flex-direction: column;
  }

  /* Client request 2026-10-07: in the stacked (phone) layout the row's align-items: flex-start sized <main>
     to its widest content (a chart or table), so the whole page scrolled sideways; it takes the screen
     width now and wide content scrolls inside its own box (.pb-table-scroll, admin-tables.js). */
  .pb-body-row > .site-main {
    align-self: stretch;
  }

  .pb-sidebar-nav {
    display: none;
    width: auto;
    border-inline-end: none;
    border-bottom: 1px solid var(--pb-border);
  }

  .pb-sidebar-nav.pb-open {
    display: block;
  }

  /* Change session 20 بند ث: replaces the old flat "display:none" - the panel itself (holding
     main-menu/language-switcher/search) is hidden by default below 900px and only shown once toggled
     open, instead of vanishing with no reopening affordance. */
  .pb-mobile-menu-toggle {
    display: inline-block;
  }

  .pb-mobile-menu-panel {
    display: none;
    flex-direction: column;
    align-items: flex-start;
    width: 100%;
  }

  .pb-mobile-menu-panel.pb-open {
    display: flex;
  }

  .pb-main-footer {
    grid-template-columns: 1fr;
  }
}

/* بازبینی هدر (2026-09-14): the tablet-only "hide search + social icons" rules that used to live here
   (بازبینی گالری فایل‌ها) silently overrode the admin's own per-device visibility checkboxes. The
   same look is now DATA: V158 sets IsVisibleTablet=false on those two rows, and the admin can flip
   it back from "هدر و فوتر" like any other element. */

/* Post-Stage-6 change request #9/#18: PageBuilder content blocks + free HTML block. */
.pb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
}

/* Change session 10 (docs/2 section 2.11 #63 "همه‌ی بخش‌های نمایشی دیگر مدرن/کارتی مشابه داشته
   باشند"): every generic block card (blog, testimonials, corporate logos, non-catalog grids) gets the
   same lift+shadow hover language as the dedicated .cat-card below, just without the admin-configurable
   knobs (column count/shadow level/gap/aspect/radius) that only Catalog's own settings screen owns. */
.pb-card {
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  padding: 0.5rem;
  text-decoration: none;
  color: inherit;
  display: block;
  overflow: hidden;
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}

.pb-card:hover, .pb-card:focus-visible {
  transform: translateY(-4px);
  box-shadow: var(--pb-shadow-lg);
  border-color: var(--pb-primary);
}

.pb-card img {
  max-width: 100%;
  transition: transform 0.4s ease;
}

.pb-card:hover img {
  transform: scale(1.04);
}

/* Change session 33 (MASTER_CHANGES_PLAN.md بخش ۸.۵): testimonials page's own centered/directional
   auto-scroll strip. testimonials-carousel.js toggles .is-scrolling only when the track overflows the
   wrapper (otherwise it stays centered and static via the wrapper's justify-content); .dir-rtl/.dir-ltr
   is set server-side from the active language, unlike the physical-only .pb-icon-carousel/.pb-image-
   marquee below. */
.testimonials-carousel-wrapper {
  display: flex;
  justify-content: center;
  overflow: hidden;
}

.testimonials-carousel-track {
  display: flex;
  gap: 1rem;
  width: max-content;
}

.testimonials-carousel-item {
  flex: 0 0 260px;
}

.testimonials-carousel-track.is-scrolling.dir-rtl {
  animation: testimonials-scroll-rtl 30s linear infinite;
}

.testimonials-carousel-track.is-scrolling.dir-ltr {
  animation: testimonials-scroll-ltr 30s linear infinite;
}

.testimonials-carousel-wrapper[data-pause-on-hover="true"]:hover .testimonials-carousel-track.is-scrolling {
  animation-play-state: paused;
}

/* Bugfix (post-session-55): these two were swapped - dir-ltr animated -50%→0 (drifting right on
   screen) and dir-rtl animated 0→-50% (drifting left), the reverse of each language's reading-flow
   direction and also the reverse of this project's own non-direction-aware baseline marquee
   (pb-marquee-scroll above: 0→-50%, i.e. drifts left - the correct LTR-equivalent default). Fixed so
   ltr matches that baseline (drifts left, content flows the way LTR text reads) and rtl is its mirror
   (drifts right, matching RTL reading flow). */
@keyframes testimonials-scroll-rtl {
  from { transform: translateX(-50%); }
  to { transform: translateX(0); }
}

@keyframes testimonials-scroll-ltr {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Change session 55 (MASTER_CHANGES_PLAN.md صف آیتم ۶ "عکس دایره‌ای نظرات مشتریان با کراپ
   قابل‌جابه‌جایی"): the final circular testimonial photo, applied everywhere it renders (site
   carousel/grid, PageBuilder's FeaturedTestimonials block, admin form preview). object-position is set
   inline per-item from the dragged offset (TestimonialDto.PhotoCropOffsetXPercent/YPercent); rows saved
   before this feature have no offset, so they fall back to 50%/50% - identical to plain
   object-fit:cover, i.e. unchanged from how these photos looked before. */
.testimonial-photo {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  object-fit: cover;
  display: block;
  flex-shrink: 0;
}

/* Item 39-20: the one schematic image (wwwroot/images/testimonial-placeholder.svg) for every testimonial without an allowed
   photo and every sample - a self-contained circle, so it reads the same on light and dark cards. */
.testimonial-photo-placeholder {
  background: #EEE5DA;
}

/* Item 39-20: the owner's four consents (submission form and «نظرهای من»). */
.testimonial-consents {
  border: 1px solid var(--pb-border);
  border-radius: 8px;
  padding: 0.6rem 0.9rem;
  margin: 0.75rem 0;
}

.testimonial-consents legend {
  padding: 0 0.3rem;
  font-weight: 600;
}

.testimonial-consents label {
  display: flex;
  gap: 0.45rem;
  align-items: flex-start;
  margin: 0.3rem 0;
}

.testimonial-consents .tc-hint {
  display: block;
  font-size: 0.82rem;
  color: var(--pb-fg-muted, var(--pb-fg));
}

.my-testimonials {
  display: grid;
  gap: 1rem;
}

.my-testimonial {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.9rem;
  align-items: start;
}

.my-testimonial .testimonial-photo {
  width: 72px;
  height: 72px;
}

.my-testimonial-status {
  display: inline-block;
  font-size: 0.85rem;
  padding: 0.1rem 0.55rem;
  border-radius: 999px;
  border: 1px solid var(--pb-border);
}

.my-testimonial-status.is-shown {
  border-color: var(--pb-success, #2e7d32);
}

@media (max-width: 560px) {
  .my-testimonial {
    grid-template-columns: 1fr;
  }
}

/* testimonial-photo-crop.js: the drag-to-reposition widget on the submission forms (site + admin) is
   itself a live circular preview the user drags directly - what is seen while dragging is exactly the
   final framing, not a separate differently-shaped crop stage. */
.tphoto-crop-img {
  width: 140px;
  height: 140px;
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
  touch-action: none;
  border: 2px solid var(--pb-border);
}

.tphoto-crop-img.is-dragging {
  cursor: grabbing;
}

.tphoto-crop-hint {
  display: block;
  font-size: 0.8rem;
  color: var(--pb-fg-muted, var(--pb-fg));
  margin-top: 0.35rem;
  max-width: 220px;
}

/* Change session 32 (MASTER_CHANGES_PLAN.md بخش ۸.۴ بند ب): blog cover image (list-card thumbnail and
   top-of-post image) had no size/ratio control - unlike .cat-card-media above, it uses one sitewide
   --blog-cover-aspect (Design.Theme admin form, PageBuilder.Blog.CoverImageAspectRatio) rather than a
   per-request inline style, since there is no per-instance display-settings object for the blog module. */
.blog-cover {
  display: block;
  width: 100%;
  aspect-ratio: var(--blog-cover-aspect, 3 / 4);
  object-fit: cover;
}

/* Item 40-B (2026-10-22): the article page had no reading layout - text ran the full 1320px container and the 3:4 cover
   filled it (≈1650px tall). A single readable column, the cover at a portrait-friendly width, and plain element rules
   for what an article body holds (the sanitizer keeps no class attribute, so h2/h3/figure/img are styled by element;
   inline styles a body sets still win). On phones a floated figure drops back into the column. */
.blog-article {
  max-width: 46rem;
  margin-inline: auto;
}

.blog-article > h1 {
  margin-block-end: 0.4rem;
}

.blog-article-meta {
  margin-block: 0 1.5rem;
  color: var(--pb-fg-muted, var(--pb-fg));
}

.blog-article .blog-article-cover {
  max-width: 35rem;
  margin: 0 auto 2.25rem;
  border-radius: var(--pb-wide-image-radius, 0px);
}

.blog-article-body {
  line-height: 1.95;
}

.blog-article-body::after {
  content: "";
  display: block;
  clear: both;
}

.blog-article-body h2 {
  margin-block: 2.4rem 0.9rem;
}

.blog-article-body h3 {
  margin-block: 1.7rem 0.6rem;
}

.blog-article-body p,
.blog-article-body ul,
.blog-article-body ol {
  margin-block: 0 1.1em;
}

.blog-article-body ul,
.blog-article-body ol {
  padding-inline-start: 1.4rem;
}

.blog-article-body li {
  margin-block-end: 0.45em;
}

.blog-article-body img {
  max-width: 100%;
  height: auto;
}

.blog-article-body figure {
  margin: 2rem auto;
  text-align: center;
}

.blog-article-body figcaption {
  margin-top: 0.6rem;
  font-size: 0.88em;
  line-height: 1.7;
  color: var(--pb-fg-muted, var(--pb-fg));
}

.blog-article-body blockquote {
  margin: 1.75rem 0;
  padding-inline-start: 1.1rem;
  border-inline-start: 3px solid var(--pb-secondary);
  font-style: italic;
}

@media (max-width: 640px) {
  .blog-article-body figure {
    float: none !important;
    width: auto !important;
    max-width: 12rem !important;
    margin: 1.75rem auto !important;
  }
}

/* Change session 10 (docs/2 section 2.11 #58/#59/#65) - the dedicated Catalog product/collection card
   grid. --cat-cols-N and --cat-gap are injected per-request from admin settings (CatalogCardDisplaySettings,
   same "inline custom property from an admin value" pattern used throughout this file); --cat-aspect is
   injected per-card for the same reason. .cat-shadow-{none|sm|md|lg} controls both the resting and
   the (stronger) hover shadow, so the single ⭐⭐⭐-marked "whole card changes on mouseover" requirement
   reads as a genuinely designed effect (lift + shadow bloom + image zoom + title color) rather than a
   flat opacity change. MASTER_CHANGES_PLAN.md queue item 11: the card's own corner radius (previously
   a per-card inline --cat-radius) was retired in favor of the sitewide --pb-radius below, same token
   every other image display (galleries, collection hero, compare thumbnails) already read. */
.cat-grid {
  display: grid;
  grid-template-columns: repeat(var(--cat-cols-desktop, 4), 1fr);
  column-gap: var(--cat-gap-h-desktop, 24px);
  row-gap: var(--cat-gap-v-desktop, 24px);
}

@media (max-width: 1200px) {
  .cat-grid {
    grid-template-columns: repeat(var(--cat-cols-tablet, 3), 1fr);
    column-gap: var(--cat-gap-h-tablet, 24px);
    row-gap: var(--cat-gap-v-tablet, 24px);
  }
}

@media (max-width: 900px) {
  .cat-grid {
    grid-template-columns: repeat(var(--cat-cols-mobile, 2), 1fr);
    column-gap: var(--cat-gap-h-mobile, 14px);
    row-gap: var(--cat-gap-v-mobile, 14px);
  }
}

.cat-card-collection {
  display: block;
  font-size: 0.8rem;
  opacity: 0.7;
  margin-bottom: 0.15rem;
}

/* Client request 2026-10-19: on the Products page the collection name links to its collection page
   (raised above the card's stretched link by .cat-card-action); only the text itself is clickable. */
a.cat-card-collection {
  width: fit-content;
  color: inherit;
  text-decoration: none;
}

a.cat-card-collection:hover, a.cat-card-collection:focus-visible {
  opacity: 1;
  color: var(--pb-primary);
  text-decoration: underline;
}

.cat-grid-empty {
  padding: 2rem 0;
  text-align: center;
  opacity: 0.7;
}

/* MASTER_CHANGES_PLAN.md آیتم ۱۷ه "کالکشن": "سطر آخر ناقص=وسط‌چین با همان فاصله" - flexbox instead of
   .cat-grid's own CSS Grid so a ragged last row centers itself with the same gap for free, same
   technique .cat-model-grid/.cat-model-card below already established for this exact requirement.
   Scoped to this one modifier class (only _CollectionGrid.cshtml, the PageBuilder کالکشن block, uses
   it) - .cat-grid's own plain Grid layout (Products/Index and _ProductGrid) is untouched. */
.cat-grid-centered {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

.cat-grid-centered > .cat-card {
  flex: 0 1 calc((100% - (var(--cat-cols-desktop, 4) - 1) * var(--cat-gap-h-desktop, 24px)) / var(--cat-cols-desktop, 4));
  max-width: calc((100% - (var(--cat-cols-desktop, 4) - 1) * var(--cat-gap-h-desktop, 24px)) / var(--cat-cols-desktop, 4));
}

@media (max-width: 1200px) {
  .cat-grid-centered > .cat-card {
    flex-basis: calc((100% - (var(--cat-cols-tablet, 3) - 1) * var(--cat-gap-h-tablet, 24px)) / var(--cat-cols-tablet, 3));
    max-width: calc((100% - (var(--cat-cols-tablet, 3) - 1) * var(--cat-gap-h-tablet, 24px)) / var(--cat-cols-tablet, 3));
  }
}

@media (max-width: 900px) {
  .cat-grid-centered > .cat-card {
    flex-basis: calc((100% - (var(--cat-cols-mobile, 2) - 1) * var(--cat-gap-h-mobile, 14px)) / var(--cat-cols-mobile, 2));
    max-width: calc((100% - (var(--cat-cols-mobile, 2) - 1) * var(--cat-gap-h-mobile, 14px)) / var(--cat-cols-mobile, 2));
  }
}

/* آیتم ۱۷ه "کالکشن": short plain-text teaser under the title (_CollectionGrid.cshtml's own
   ShortDescription local function). */
.cat-card-desc {
  font-size: 0.82rem;
  opacity: 0.75;
  line-height: 1.4;
}

.cat-card {
  position: relative;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  background: var(--pb-bg);
  /* border-box so the 1px border stays inside .cat-grid-centered's calc()'d per-column flex-basis -
     under the default content-box model each card ran 2px past its share, and a full row of N cards
     overflowed the container by 2N px, wrapping the last card onto its own row (a 4-column setting
     rendered as 3 + 1). Same class of bug as .site-main/.pb-block above. */
  box-sizing: border-box;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  overflow: hidden;
  transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.28s ease, border-color 0.28s ease;
}

.cat-card-media {
  display: block;
  position: relative;
  aspect-ratio: var(--cat-aspect, 3 / 4);
  overflow: hidden;
  /* Change session 25 (MASTER_CHANGES_PLAN.md بخش ۶.۶): admin-toggleable via --cat-media-bg
     (_Layout.cshtml, Design.Theme.CardImageBackgroundEnabled) so a transparent product photo can
     actually show through instead of always painting a solid box behind it. */
  background: var(--cat-media-bg, var(--pb-border));
}

.cat-card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.cat-card-badge {
  position: absolute;
  inset-block-start: 0.6rem;
  inset-inline-start: 0.6rem;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 600;
  color: #fff;
  background: var(--pb-secondary, #333);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}

.cat-card-badge-sale {
  background: #c0392b;
}

.cat-card-badge-outofstock {
  inset-inline-start: auto;
  inset-inline-end: 0.6rem;
  background: #555;
}

.cat-card-body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.75rem 1rem 1rem;
}

.cat-card-code {
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  opacity: 0.55;
}

.cat-card-title {
  font-weight: 600;
  transition: color 0.2s ease;
}

.cat-card-price {
  margin-top: 0.15rem;
  font-size: 0.95rem;
}

.cat-card-price del {
  opacity: 0.55;
  margin-inline-end: 0.35rem;
  font-weight: 400;
}

.cat-card-price strong {
  color: var(--pb-primary);
}

.cat-card:hover, .cat-card:focus-visible, .cat-card:focus-within {
  transform: translateY(-8px);
  border-color: var(--pb-primary);
}

.cat-card:hover .cat-card-media img, .cat-card:focus-visible .cat-card-media img, .cat-card:focus-within .cat-card-media img {
  transform: scale(1.08);
}

.cat-card:hover .cat-card-title, .cat-card:focus-visible .cat-card-title, .cat-card:focus-within .cat-card-title {
  color: var(--pb-primary);
}

.cat-shadow-none { box-shadow: none; }
.cat-shadow-small { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); }
.cat-shadow-medium { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); }
.cat-shadow-large { box-shadow: 0 8px 28px rgba(0, 0, 0, 0.14); }

/* docs/2 section 2.11 #59 "کلیک هر بخش کارت" needs a single real <a>; Wishlist's remove button is the
   one place a card also needs its own separate clickable action, so that card variant is a plain <div
   class="cat-card"> with a "stretched link" <a> (inset:0, z-index:1) covering the navigable area and the
   action button raised above it (z-index:2) instead of (invalidly) nesting a <form> inside an <a>. */
.cat-card-stretched-link {
  position: absolute;
  inset: 0;
  z-index: 1;
}

.cat-card-action {
  position: relative;
  z-index: 2;
}

.cat-shadow-none:hover, .cat-shadow-none:focus-visible { box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1); }
.cat-shadow-small:hover, .cat-shadow-small:focus-visible { box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16); }
.cat-shadow-medium:hover, .cat-shadow-medium:focus-visible { box-shadow: 0 16px 34px rgba(0, 0, 0, 0.2); }
.cat-shadow-large:hover, .cat-shadow-large:focus-visible { box-shadow: 0 22px 46px rgba(0, 0, 0, 0.26); }

/* MASTER_CHANGES_PLAN.md queue item 12 ("بازطراحی کامل صفحه‌ی اختصاصی کالکشن") - full collection
   detail page redesign (Views/Collections/Details.cshtml): 5 fixed sections replace the old single
   hero-with-text-overlay. Retires .cat-collection-hero/-overlay/-nohero entirely (this was their only
   consumer) in favor of a pure-image banner (title/description moved to the new two-column intro
   section below), per MASTER_CHANGES_PLAN.md section 0 rule #2 ("چیزی که دیگر واقعاً استفاده نشود...
   حذف شود"). */
.cat-collection-banner {
  position: relative;
  aspect-ratio: 16 / 3;
  /* MASTER_CHANGES_PLAN.md queue item 11: same wide-image radius exemption .cat-collection-hero used. */
  border-radius: var(--pb-wide-image-radius, 16px);
  overflow: hidden;
  margin-block-end: var(--pb-section-spacing, 64px);
  background: var(--cat-media-bg, var(--pb-border));
}

.cat-collection-banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Queue item 35 (client request 2026-10-18): the Collection's name sits in the exact centre of its
   banner - white with a soft shadow so it stays readable on any picture; the size follows the banner's
   own width (vw fallback, then container units) so the thin 16:3 strip never clips it on a phone. */
.cat-collection-banner {
  container-type: inline-size;
}

.cat-collection-banner-title {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: 1rem;
  text-align: center;
  line-height: 1.2;
  font-family: var(--pb-heading-primary-font, inherit);
  font-weight: 700;
  font-size: clamp(1.1rem, 4vw, 3rem);
  color: #fff;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.55), 0 1px 3px rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

@supports (width: 1cqw) {
  .cat-collection-banner-title {
    font-size: clamp(1.1rem, 5cqw, 3rem);
  }
}

.cat-collection-intro,
.cat-product-intro {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
  align-items: center;
  margin-block-end: var(--pb-section-spacing, 64px);
}

.cat-collection-intro-text h1,
.cat-product-intro-text h1 {
  margin-block-start: 0;
}

.cat-collection-intro-media,
.cat-product-intro-media {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--pb-radius, 12px);
  overflow: hidden;
  background: var(--cat-media-bg, var(--pb-border));
}

.cat-collection-intro-media img,
.cat-product-intro-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

@media (max-width: 900px) {
  .cat-collection-intro,
  .cat-product-intro {
    grid-template-columns: 1fr;
  }
}

.cat-collection-models,
.cat-collection-supplementary,
.cat-product-supplementary {
  margin-block-end: var(--pb-section-spacing, 64px);
}

/* MASTER_CHANGES_PLAN.md آیتم ۱۷ه "کالکشن+مدل": one Collection's banner+model-grid group - reuses
   .cat-collection-banner as-is (wrapped in a plain link here instead of Collections/Details.cshtml's
   own unlinked banner, since this block's own banner click must lead to that Collection's page). */
.cat-collection-banner-link {
  display: block;
  text-decoration: none;
  color: inherit;
}

/* Client request 2026-10-19: inside the "کالکشن+مدل" block the banner sits one card-row gap above its
   models and each collection two row gaps below the previous one; on the collection page the banner sits
   the same gap above its intro (all used to follow the theme's section spacing, which can be 0). The gap
   variables come from the block's / the page's own style attribute. */
.pb-collection-models .cat-collection-banner,
.cat-collection-detail > .cat-collection-banner {
  margin-block-end: var(--cat-gap-v-desktop, 24px);
}

.pb-collection-models-group {
  margin-block-end: calc(2 * var(--cat-gap-v-desktop, 24px));
}

/* Item 45-1: /collections - each banner with its short description right under it, collections two card-row gaps apart. */
.cat-collections-index-item {
  margin-block-end: calc(2 * var(--cat-gap-v-desktop, 24px));
}

.cat-collections-index-item .cat-collection-banner {
  margin-block-end: 0.75rem;
}

.cat-collections-index-title,
.cat-collections-index-desc {
  text-align: center;
}

.cat-collections-index-desc {
  margin: 0;
  color: var(--pb-text-secondary-color, inherit);
}

@media (max-width: 1200px) {
  .pb-collection-models .cat-collection-banner,
  .cat-collection-detail > .cat-collection-banner {
    margin-block-end: var(--cat-gap-v-tablet, 24px);
  }

  .pb-collection-models-group {
    margin-block-end: calc(2 * var(--cat-gap-v-tablet, 24px));
  }
}

@media (max-width: 900px) {
  .pb-collection-models .cat-collection-banner,
  .cat-collection-detail > .cat-collection-banner {
    margin-block-end: var(--cat-gap-v-mobile, 14px);
  }

  .pb-collection-models-group {
    margin-block-end: calc(2 * var(--cat-gap-v-mobile, 14px));
  }
}

.pb-collection-models-group:last-child {
  margin-block-end: 0;
}

.pb-collection-models-group-title {
  margin-block-end: 1rem;
}

/* --- richer collection-grid model card (queue item 12) --- */

/* Flexbox (not .cat-grid's CSS Grid) so a ragged last row centers itself with the same gap for free -
   "سطر آخر ناقص وسط‌چین شود با همان gap" - no JS needed. Reuses the same --cat-cols-N and --cat-gap
   custom properties CatalogCardDisplaySettings.GridStyleAttribute already computes for .cat-grid. */
.cat-model-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  column-gap: var(--cat-gap-h-desktop, 24px);
  row-gap: var(--cat-gap-v-desktop, 24px);
}

.cat-model-card {
  flex: 0 1 calc((100% - (var(--cat-cols-desktop, 3) - 1) * var(--cat-gap-h-desktop, 24px)) / var(--cat-cols-desktop, 3));
  max-width: calc((100% - (var(--cat-cols-desktop, 3) - 1) * var(--cat-gap-h-desktop, 24px)) / var(--cat-cols-desktop, 3));
  display: flex;
  flex-direction: column;
  /* border-box for the same reason as .cat-card above: the calc()'d flex-basis must be the card's
     outer width, or the 1px border pushes a full row 2N px past the container and wraps the last card. */
  box-sizing: border-box;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  overflow: hidden;
  background: var(--pb-bg);
}

/* "کادر پایین بدون‌هاور" - resting shadow only, deliberately no :hover growth rule (unlike .cat-shadow-*
   above, which .cat-card intentionally does grow on hover) since the footer shares this one bordered
   box with the image, so any hover-triggered change here would visually reach the footer too. */
.cat-model-shadow-none { box-shadow: none; }
.cat-model-shadow-small { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); }
.cat-model-shadow-medium { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); }
.cat-model-shadow-large { box-shadow: 0 8px 28px rgba(0, 0, 0, 0.14); }

@media (max-width: 1200px) {
  .cat-model-grid {
    column-gap: var(--cat-gap-h-tablet, 24px);
    row-gap: var(--cat-gap-v-tablet, 24px);
  }
  .cat-model-card {
    flex-basis: calc((100% - (var(--cat-cols-tablet, 3) - 1) * var(--cat-gap-h-tablet, 24px)) / var(--cat-cols-tablet, 3));
    max-width: calc((100% - (var(--cat-cols-tablet, 3) - 1) * var(--cat-gap-h-tablet, 24px)) / var(--cat-cols-tablet, 3));
  }
}

@media (max-width: 900px) {
  .cat-model-grid {
    column-gap: var(--cat-gap-h-mobile, 14px);
    row-gap: var(--cat-gap-v-mobile, 14px);
  }
  .cat-model-card {
    flex-basis: calc((100% - (var(--cat-cols-mobile, 2) - 1) * var(--cat-gap-h-mobile, 14px)) / var(--cat-cols-mobile, 2));
    max-width: calc((100% - (var(--cat-cols-mobile, 2) - 1) * var(--cat-gap-h-mobile, 14px)) / var(--cat-cols-mobile, 2));
  }
}

.cat-model-card-visual {
  position: relative;
  display: block;
  text-decoration: none;
  color: inherit;
}

.cat-model-card-media {
  display: block;
  position: relative;
  aspect-ratio: var(--cat-aspect, 3 / 4);
  overflow: hidden;
  background: var(--cat-media-bg, var(--pb-border));
}

.cat-model-card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.cat-model-card-visual:hover .cat-model-card-media img,
.cat-model-card-visual:focus-visible .cat-model-card-media img {
  transform: scale(1.06);
}

/* Model-code gradient bar: height/opacity are Catalog.Card.ModelCodeBar* (admin-configurable,
   CatalogCardDisplaySettings.ModelCodeBarStyleAttribute); the fill itself reuses the sitewide
   --pb-gradient token (Design.Theme Primary/Secondary/GradientEnabled) rather than a dedicated color
   setting of its own. */
.cat-model-card-codebar {
  position: absolute;
  z-index: 1;
  inset-inline: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--cat-codebar-height, 30%);
  background: var(--pb-gradient, var(--pb-primary));
  opacity: var(--cat-codebar-opacity, 0.5);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-align: center;
  padding-inline: 0.5rem;
}

.cat-model-codebar-top { inset-block-start: 0; }
.cat-model-codebar-bottom { inset-block-end: 0; }

/* "کادر پایین بدون‌هاور" - deliberately no hover/transform rule on the footer box itself (unlike
   .cat-model-card-visual's image-zoom above). */
.cat-model-card-footer {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.85rem 1rem 1rem;
  text-align: center;
}

.cat-model-footer-align-start { text-align: start; }

.cat-model-card-title {
  font-weight: 600;
  text-decoration: none;
  color: inherit;
}

.cat-model-card-title:hover, .cat-model-card-title:focus-visible {
  color: var(--pb-primary);
}

.cat-model-card-price { font-size: 0.95rem; }
.cat-model-card-price del { opacity: 0.55; margin-inline-end: 0.35rem; font-weight: 400; }
.cat-model-card-price strong { color: var(--pb-primary); }

.cat-model-card-cta {
  display: inline-block;
  padding: 0.4rem 0.9rem;
  border-radius: var(--pb-radius-sm, 8px);
  background: var(--pb-gradient, var(--pb-primary));
  color: #fff;
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 600;
}

.cat-model-card-cta-disabled {
  background: none;
  border: 1px solid var(--pb-border);
  color: inherit;
  opacity: 0.6;
}

/* Per-device pager-visibility fallback (SettingKeys.CatalogGridPaginationEnabledTablet/Mobile) - the
   fetched dataset is unchanged, only whether the Prev/Next control itself renders at that breakpoint. */
@media (max-width: 1200px) and (min-width: 901px) {
  .cat-pager-hide-tablet { display: none; }
}
@media (max-width: 900px) {
  .cat-pager-hide-mobile { display: none; }
}

.cat-filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: end;
  margin-bottom: 1.5rem;
  padding: 1rem;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  background: var(--pb-bg);
}

.cat-filter-bar .form-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 140px;
}

/* docs/2 section 2.11 #62/#63 - product detail page polish: color swatches (real hex chips instead
   of a bare text list), item cards, and a lighter attribute list, all reusing the same visual language
   (rounded corners, subtle border, hover lift) as the rest of the catalog's card system. */
.cat-product-breadcrumb {
  opacity: 0.7;
  font-size: 0.9rem;
}

.cat-product-code {
  font-size: 0.8rem;
  opacity: 0.6;
  margin-top: -0.5rem;
}

/* Queue item 13: price + add-to-cart/compare/wishlist/custom-order actions "همان‌جای فعلی (بعد از
   سکشن اول) بمانند" - kept as one unlabeled block right after the section-1 intro, never moved into
   the section-3 color/item cards below it. */
.cat-product-purchase {
  margin-block-end: var(--pb-section-spacing, 64px);
}

/* Client request 2026-11-09: tailoring button + wishlist/compare icon buttons on one row, 16px apart, equal height. */
.cat-model-action-row {
  display: flex;
  align-items: stretch;
  gap: 16px;
}

.cat-model-action-row form {
  display: flex;
  margin: 0;
}

.cat-model-action-row .pb-btn {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.cat-model-action-row form button {
  padding-inline: 0.8rem;
}

/* Client request 2026-10-16: model gallery = 3:4 portrait cards, 4/3/2 per row (desktop/tablet/mobile, the
   site's 1200/900 breakpoints), the picture scaled to cover the whole card. Explicit size also overrides the
   300x150 box browsers give a sizes="auto" image (which squashed these). */
/* Client request 2026-11-09: the gallery is apart from the product-parts cards above it (it is the model's own photos, not
   theirs) and a part-filled row sits centered - flex rows of the same 4/3/2 widths instead of grid columns. */
.cat-product-gallery.pb-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin-block-start: 2rem; /* fixed: the panel's section spacing is 0 on this site */
  --cat-gallery-columns: 4;
}

.cat-product-gallery .pb-card {
  padding: 0;
  aspect-ratio: 3 / 4;
  flex: 0 0 calc((100% - (var(--cat-gallery-columns) - 1) * 1rem) / var(--cat-gallery-columns));
}

.cat-product-gallery .pb-card img,
.cat-product-gallery .pb-card video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (min-width: 901px) and (max-width: 1200px) {
  .cat-product-gallery.pb-grid { --cat-gallery-columns: 3; }
}

@media (max-width: 900px) {
  .cat-product-gallery.pb-grid { --cat-gallery-columns: 2; }
}

.cat-color-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.cat-color-swatch {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.6rem 0.3rem 0.3rem;
  border: 1px solid var(--pb-border);
  border-radius: 999px;
  font-size: 0.85rem;
  transition: border-color 0.2s ease, transform 0.2s ease;
  /* Session 49: this is now a real <button> (product page, clickable to switch the gallery) - reset
     button-only defaults so it still looks identical to the plain decorative swatch it replaced. */
  background: none;
  color: inherit;
  font-family: inherit;
  cursor: pointer;
}

.cat-color-swatch:hover {
  border-color: var(--pb-primary);
  transform: translateY(-2px);
}

.cat-color-swatch--active {
  border-color: var(--pb-primary);
  border-width: 2px;
}

.cat-color-swatch-chip {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  /* Session 48: a color's sample photo (fabric/leather), when set, takes priority over the flat hex -
     --cat-swatch-image is a url(...) or unset, so the flat colour stays the fallback either way. */
  background: var(--cat-swatch-image, none) center/cover, var(--cat-swatch-color, #ccc);
  border: 1px solid rgba(0, 0, 0, 0.15);
  box-shadow: inset 0 0 0 2px var(--pb-bg);
}

.cat-attribute-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0.35rem;
}

/* Client request 2026-09-21: attribute / option SVG icons at "small icon" size beside their text. */
.cat-attribute-icon {
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.2em;
  margin-inline-end: 0.3em;
  object-fit: contain;
}

/* MASTER_CHANGES_PLAN.md queue item 13 ("بازطراحی کامل صفحه‌ی مدل"): item cards "هم‌الگوی کارت
   کالکشن" - same bordered/rounded/shadowed card language as .cat-card/.cat-model-card. */
.cat-item-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  column-gap: var(--cat-gap-h-desktop, 1rem);
  row-gap: var(--cat-gap-v-desktop, 1rem);
}

.cat-item-card {
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  padding: 0.9rem 1.1rem;
  background: var(--pb-bg);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.cat-item-card h3 {
  margin-top: 0;
}

/* Queue item 13, non-custom model, section 3: the plain pill swatches (.cat-color-swatch*, still used
   as-is by Views/Products/StartCustomOrder.cshtml) become full cards here - visual + label + (only when
   Model.SizeScaleId is set) an independent size <select>. Per the "انتخاب مستقل رنگ = خیر" finding, no
   add-to-cart/compare/wishlist ever goes on this card - those stay in .cat-product-purchase, right
   after section 1; the size <select> only pre-fills and opens that same dialog (product-color-size-
   select.js), it never submits anything itself. */
.cat-color-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  column-gap: var(--cat-gap-h-desktop, 1rem);
  row-gap: var(--cat-gap-v-desktop, 1rem);
}

.cat-color-card {
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  overflow: hidden;
  background: var(--pb-bg);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

/* MASTER_CHANGES_PLAN.md queue item 15: these two grids stay auto-fill (card count reflows by
   available width, not an admin-set column count - "auto-fill بماند" finding) but their gap still
   follows the per-breakpoint admin values, same var names .cat-grid/.cat-model-grid read. */
@media (max-width: 1200px) {
  .cat-item-cards,
  .cat-color-cards {
    column-gap: var(--cat-gap-h-tablet, 1rem);
    row-gap: var(--cat-gap-v-tablet, 1rem);
  }
}

@media (max-width: 900px) {
  .cat-item-cards,
  .cat-color-cards {
    column-gap: var(--cat-gap-h-mobile, 1rem);
    row-gap: var(--cat-gap-v-mobile, 1rem);
  }
}

.cat-color-card-visual {
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  font: inherit;
  color: inherit;
}

.cat-color-card-media {
  display: block;
  aspect-ratio: 1 / 1;
  /* Same --cat-swatch-color/--cat-swatch-image custom properties the pill swatch already sets. */
  background: var(--cat-swatch-image, none) center/cover, var(--cat-swatch-color, #ccc);
}

.cat-color-card-footer {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.65rem 0.85rem 0.85rem;
}

.cat-color-card-label {
  font-weight: 600;
  font-size: 0.9rem;
}

.cat-color-card-size-select {
  width: 100%;
}

/* docs/1 section 2.3 "صفحه مقایسه محصولات: جدول مقایسه فیلدهای کلیدی" - a real comparison table stays
   a table (not a card grid, per that same doc line), just visually modernized (#63) with rounded
   corners, zebra rows, and a proper thumbnail box per compared product. */
.cat-compare-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  overflow: hidden;
  border-radius: var(--pb-radius, 12px);
  border: 1px solid var(--pb-border);
}

.cat-compare-table th, .cat-compare-table td {
  padding: 0.6rem 0.9rem;
  border-bottom: 1px solid var(--pb-border);
  text-align: center;
  vertical-align: top;
}

.cat-compare-table td:first-child {
  text-align: start;
  font-weight: 600;
  background: color-mix(in srgb, var(--pb-primary) 6%, transparent);
}

.cat-compare-table tbody tr:last-child th, .cat-compare-table tbody tr:last-child td {
  border-bottom: none;
}

.cat-compare-thumb {
  display: block;
  width: 96px;
  aspect-ratio: 3 / 4;
  margin: 0 auto 0.5rem;
  border-radius: var(--pb-radius-sm, 8px);
  overflow: hidden;
  /* Change session 25: same admin-toggleable fill as .cat-card-media (this is also a product-image
     container, and the client's own ask covered every image-displaying section of the site). */
  background: var(--cat-media-bg, var(--pb-border));
}

.cat-compare-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cat-compare-name {
  display: block;
  font-weight: 600;
  margin-bottom: 0.4rem;
  text-decoration: none;
  color: inherit;
}

.cat-compare-name:hover {
  color: var(--pb-primary);
}

/* docs/2 section 2.11 #60 banner display mode for FeaturedCollections. */
.cat-collections-banner {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.cat-collections-banner-item {
  position: relative;
  display: block;
  border-radius: var(--pb-radius, 16px);
  overflow: hidden;
  min-height: 200px;
  text-decoration: none;
  color: #fff;
  transition: transform 0.28s ease, box-shadow 0.28s ease;
}

.cat-collections-banner-item:hover, .cat-collections-banner-item:focus-visible {
  transform: translateY(-4px);
  box-shadow: 0 16px 34px rgba(0, 0, 0, 0.22);
}

.cat-collections-banner-item img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s ease;
}

.cat-collections-banner-item:hover img {
  transform: scale(1.05);
}

.cat-collections-banner-item-label {
  position: relative;
  z-index: 1;
  display: block;
  padding: 1.25rem;
  font-size: 1.1rem;
  font-weight: 600;
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.65) 0%, rgba(0, 0, 0, 0) 100%);
}

.pb-hide-desktop, .pb-hide-tablet, .pb-hide-mobile {
  display: initial;
}

@media (min-width: 1201px) {
  .pb-hide-desktop { display: none !important; }
}

@media (min-width: 901px) and (max-width: 1200px) {
  .pb-hide-tablet { display: none !important; }
}

@media (max-width: 900px) {
  .pb-hide-mobile { display: none !important; }
}

/* Post-Stage-13 change request #4: globe-icon trigger + dropdown, replacing the old plain <select>.
   Native <details>/<summary> supplies the open/close toggle - no JS needed. */
.pb-lang-dropdown {
  position: relative;
}

.pb-lang-trigger {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
}

.pb-lang-trigger::-webkit-details-marker {
  display: none;
}

.pb-lang-menu {
  position: absolute;
  inset-inline-end: 0;
  top: 100%;
  margin: 0.25rem 0 0;
  padding: 0.25rem;
  list-style: none;
  background: var(--pb-bg);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 8px);
  box-shadow: var(--pb-shadow-lg, 0 2px 10px rgba(0, 0, 0, 0.2));
  min-width: 120px;
  z-index: 50;
}

.pb-lang-menu li button, .pb-lang-menu li a {
  display: block;
  width: 100%;
  border: none;
  background: none;
  padding: 0.4rem 0.6rem;
  text-align: inherit;
  cursor: pointer;
  color: inherit;
  text-decoration: none;
  font: inherit;
}

/* Client request 2026-10-18: the popup has its own page-coloured background, but its items inherited the header's
   text colour - light on the transparent hero header - so the non-active language (and every user-menu link) was
   almost invisible on the ivory panel. Items now always use the page text colour; the active/current one keeps the
   brand colour. Specificity beats the header's own link rules.
   Client request 2026-11-09: the transparent hero header's `... a` rule (3 classes) still won over the links, and the
   user's name inherited the header colour - the panel now sets the colour itself and the item rules carry 3 classes. */
.pb-site-header .pb-lang-menu,
.pb-site-header .pb-lang-dropdown .pb-lang-menu li button,
.pb-site-header .pb-lang-dropdown .pb-lang-menu li a,
.pb-site-header .pb-lang-menu .pb-lang-menu-logout {
  color: var(--pb-fg);
}

.pb-site-header .pb-lang-dropdown .pb-lang-menu li button.active,
.pb-site-header .pb-lang-dropdown .pb-lang-menu li a.is-active {
  color: var(--pb-primary);
}

/* Client request 2026-11-09: a signed-in user's icon sits on green. */
.pb-lang-dropdown.pb-user-menu > .pb-lang-trigger.pb-icon-link {
  background: var(--pb-success, #2e7d32);
  color: #fff;
}

/* Client request 2026-09-21: the user-icon popup holds every personal page (same groups the removed
   site sidebar had), the user's name on top, logout last - groups separated by a divider. */
.pb-user-menu-list {
  min-width: 200px;
}

.pb-user-menu-list .pb-user-menu-group ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pb-user-menu-list .pb-user-menu-group + .pb-user-menu-group {
  border-top: 1px solid var(--pb-border);
  margin-top: 0.25rem;
  padding-top: 0.25rem;
}

.pb-user-menu-list .pb-user-menu-name {
  padding: 0.4rem 0.6rem;
  font-weight: 600;
  opacity: 0.85;
  border-bottom: 1px solid var(--pb-border);
  margin-bottom: 0.25rem;
}

.pb-user-menu-list li a.is-active {
  color: var(--pb-primary);
  font-weight: 600;
}

.pb-lang-menu-logout {
  display: block;
  width: 100%;
  border: none;
  background: none;
  padding: 0.4rem 0.6rem;
  text-align: inherit;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

/* Post-Stage-13 change request #2/#3: chat widget as a floating round icon + popup panel (not the
   full-width, unstyled block it rendered as with zero CSS before). Positioning class
   (chat-widget-start/end) is already computed server-side by _ChatWidget.cshtml based on language
   direction. */
.chat-widget {
  position: fixed;
  bottom: 1.5rem;
  z-index: 1000;
}

.chat-widget.chat-widget-start {
  inset-inline-start: 1.5rem;
}

.chat-widget.chat-widget-end {
  inset-inline-end: 1.5rem;
}

.chat-widget-toggle {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: none;
  background: var(--chat-widget-accent, var(--pb-primary, #7a1f3d));
  color: #fff;
  font-size: 0.7rem;
  cursor: pointer;
  box-shadow: var(--pb-shadow-lg, 0 2px 10px rgba(0, 0, 0, 0.3));
  transition: transform 0.2s ease;
}

.chat-widget-toggle:hover {
  transform: translateY(-2px);
}

.chat-widget-whatsapp {
  display: block;
  margin-top: 0.5rem;
  text-align: center;
}

.chat-widget-panel {
  position: absolute;
  bottom: 68px;
  width: 320px;
  max-width: calc(100vw - 2rem);
  /* height comes from an inline style (admin-configurable, _ChatWidget.cshtml, change session 21 بند
     الف; changed from max-height to a fixed height in change session 30 بند ت so the panel never
     shrinks/grows with message count) - no CSS fallback needed since the Razor default (320px) is
     always rendered. */
  background: var(--pb-bg, #fff);
  color: var(--pb-fg, #1a1a1a);
  border: 1px solid var(--pb-border, #ddd);
  border-radius: var(--pb-radius, 12px);
  box-shadow: var(--pb-shadow-lg, 0 4px 20px rgba(0, 0, 0, 0.3));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: chat-widget-pop 0.15s ease-out;
}

.chat-widget-panel[hidden] {
  display: none;
}

.chat-widget-start .chat-widget-panel {
  inset-inline-start: 0;
}

.chat-widget-end .chat-widget-panel {
  inset-inline-end: 0;
}

/* Change session 16 (item #66): widget title bar + end-of-chat satisfaction rating. Change session 21
   (بند پ/ت): title bar background now uses the shared chat accent color (was unstyled, inheriting the
   panel's own --pb-bg); the end-conversation button was added to this same bar. */
.chat-widget-title {
  padding: 0.6rem 0.75rem;
  font-weight: 600;
  background: var(--chat-widget-accent, var(--pb-primary, #7a1f3d));
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.chat-widget-end-btn {
  border: 1px solid rgba(255, 255, 255, 0.6);
  background: none;
  color: inherit;
  font: inherit;
  font-size: 0.75rem;
  border-radius: var(--pb-radius-sm, 6px);
  padding: 0.15rem 0.5rem;
  cursor: pointer;
}

/* Client request 2026-10-18: the end button stays out until the conversation has started (display above would beat [hidden]). */
.chat-widget-end-btn[hidden],
.js-chat-form[hidden],
.chat-guest-name-form[hidden] {
  display: none;
}

.chat-guest-name-form .chat-guest-error {
  color: #b00020;
  font-size: 0.8rem;
}

/* Change session 30 (بخش ۸.۲ بند ج): groups the end-conversation button with the new X close button. */
.chat-widget-title-actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}

.chat-widget-close-btn {
  border: none;
  background: none;
  color: inherit;
  font-size: 1.1rem;
  line-height: 1;
  padding: 0 0.2rem;
  cursor: pointer;
}

/* Change session 30 (بخش ۸.۲ بند ب): mandatory guest name form shown before a first-time visitor's
   conversation actually starts (see _ChatWidget.cshtml/chat-widget.js). */
.chat-guest-name-form {
  padding: 0.75rem;
  text-align: center;
}

.chat-guest-name-form p {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
}

.chat-guest-name-form input {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--pb-border, #ddd);
  border-radius: var(--pb-radius-sm, 6px);
  padding: 0.4rem 0.5rem;
  margin-bottom: 0.5rem;
}

.chat-guest-name-form button {
  border: none;
  background: var(--chat-widget-accent, var(--pb-primary, #7a1f3d));
  color: #fff;
  border-radius: var(--pb-radius-sm, 6px);
  padding: 0.4rem 0.9rem;
  cursor: pointer;
}

.chat-satisfaction {
  padding: 0.6rem 0.75rem;
  border-top: 1px solid var(--pb-border, #ddd);
  text-align: center;
}

.chat-satisfaction p {
  margin: 0 0 0.4rem;
  font-size: 0.85rem;
}

.chat-satisfaction-buttons {
  display: flex;
  justify-content: center;
  gap: 0.4rem;
}

.js-chat-rate {
  border: none;
  background: none;
  font-size: 1.4rem;
  cursor: pointer;
  line-height: 1;
  padding: 0.2rem;
}

.js-chat-rate:hover {
  transform: scale(1.15);
}

.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* Change session 39 (بند الف): shared message-bubble styling for .chat-message, rendered by
   wwwroot/js/live-chat.js's render() and reused unmodified by the customer widget, the admin popup
   console, and the tailor-chat panel - previously these had zero CSS, so Persian text (long, or mixed
   with Latin/numbers) rendered without wrapping inside the narrow panel. */
.chat-message {
  max-width: 80%;
  padding: 0.45rem 0.7rem;
  border-radius: var(--pb-radius-sm, 10px);
  overflow-wrap: anywhere;
  word-break: break-word;
  white-space: pre-wrap;
  line-height: 1.5;
}

.chat-message-mine {
  align-self: flex-end;
  background: var(--chat-widget-accent, var(--pb-primary, #7a1f3d));
  color: #fff;
  border-end-end-radius: 2px;
}

.chat-message-theirs {
  align-self: flex-start;
  background: color-mix(in srgb, var(--pb-fg, #1a1a1a) 8%, var(--pb-bg, #fff));
  color: var(--pb-fg, #1a1a1a);
  border-end-start-radius: 2px;
}

.js-chat-form {
  display: flex;
  border-top: 1px solid var(--pb-border, #ddd);
}

.js-chat-input {
  flex: 1;
  border: none;
  padding: 0.5rem;
}

/* Change session 21 (بند پ): send button previously had zero CSS (bare browser button), now shares
   the same accent color as the toggle icon and title bar. */
.js-chat-form button[type="submit"] {
  border: none;
  background: var(--chat-widget-accent, var(--pb-primary, #7a1f3d));
  color: #fff;
  padding: 0.5rem 0.9rem;
  cursor: pointer;
}

/* MASTER_CHANGES_PLAN.md queue item 19 s11: chat photos - the picker before the text box, the error on its own line,
   thumbnails inside the bubble, and the note left where the retention sweep removed one. */
.js-chat-form { flex-wrap: wrap; align-items: center; }
.js-chat-form .js-chat-input { min-width: 0; }
.chat-image-controls { display: flex; align-items: center; gap: 0.25rem; padding-inline: 0.4rem; }
.chat-image-pick { display: inline-flex; cursor: pointer; color: var(--pb-muted-text, #666); }
.chat-image-picked { font-size: 0.8rem; color: var(--pb-muted-text, #666); }
.js-chat-form .chat-error { flex-basis: 100%; margin: 0; padding: 0.25rem 0.5rem; font-size: 0.85rem; color: #b42318; }
.chat-message-images { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.3rem; white-space: normal; }
.chat-message-images img { display: block; width: 5.5rem; height: 5.5rem; object-fit: cover; border-radius: var(--pb-radius-sm, 8px); }
.chat-image-purged { font-size: 0.8rem; font-style: italic; opacity: 0.85; }

@keyframes chat-widget-pop {
  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (max-width: 480px) {
  .chat-widget-panel {
    width: calc(100vw - 2rem);
  }
}

/* Post-Stage-13 change requests (second follow-up, item #43): the login/register popup previously
   had zero CSS of its own (plain browser <dialog> chrome), fully disconnected from the admin's
   palette - now themed the same way as the chat popup panel. */
#auth-modal {
  background: var(--pb-bg);
  color: var(--pb-fg);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  box-shadow: var(--pb-shadow-lg, 0 4px 24px rgba(0, 0, 0, 0.35));
  padding: 1.5rem;
  width: 360px;
  max-width: calc(100vw - 2rem);
  position: relative;
}

#auth-modal::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

/* Login/register popup content must stay centered regardless of the active language's
   direction - body.dir-rtl/dir-ltr above set text-align:right/left per language, which the
   modal otherwise inherits. text-align:center on the element itself wins over that inherited
   value in every language. Labels/links/errors inherit this fine, but form controls need it
   restated directly: the UA stylesheet gives input/button their own text-align, which (being a
   specified value) wins over the inherited one. */
#auth-modal-content {
  text-align: center;
}

#auth-modal-content input:not([type="checkbox"]),
#auth-modal-content button {
  text-align: center;
}

/* Was float:inline-end, which pulled the close button out of flow but still let it shrink the
   line box of whatever content sat beside it - here, the first form field's label - so that one
   line rendered visibly off-center while everything below it (clear of the float's height) was
   fine. position:absolute removes it from flow entirely instead of just displacing it. */
.js-auth-close {
  position: absolute;
  inset-inline-end: 0;
  top: 0;
  border: none;
  background: none;
  font-size: 1.3rem;
  cursor: pointer;
  color: var(--pb-fg);
}

/* Post-Stage-13 change requests (second follow-up, item #41): show/hide toggle for password fields.
   MASTER_CHANGES_PLAN.md section 2.1 item #11 ("آیکون چشم ... خارج از باکس"): plain "display: flex"
   is a block-level flex container, so it stretched to the full width of its parent form-field (no
   input in this codebase has its own width/flex-grow) while the actual <input> kept its small
   browser-default width - the absolutely-positioned toggle (anchored to the wrapper's far edge) then
   landed nowhere near the visible input box. "inline-flex" shrink-wraps to the input's real size. */
.pb-password-field {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 100%;
}

.pb-password-field input {
  padding-inline-end: 1.75rem;
}

/* Client 2026-11-10: no eye button for a saved (autofilled) or already-left password (password-toggle.js). */
.pb-password-toggle[hidden] {
  display: none;
}

@keyframes pb-autofill-start {
  from { opacity: 1; }
  to { opacity: 1; }
}

.pb-password-field input:-webkit-autofill {
  animation: pb-autofill-start 1ms;
}

.pb-password-toggle {
  position: absolute;
  inset-inline-end: 0.5rem;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  line-height: 0;
}

/* Post-Stage13 change request #27 (final approved design); بازطراحی آیتم ۱۷د: event text column
   (title/body, the block's shared caption fields) beside the day/hour/minute/second segments -
   desktop/tablet side by side, mobile stacked with the event text on top (client's own confirmed
   rule). */
.pb-countdown-block {
  display: flex;
  align-items: center;
  gap: 2rem;
}

.pb-countdown-event-col,
.pb-countdown {
  flex: 1 1 50%;
  min-width: 0;
}

.pb-countdown {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

.pb-countdown-segment {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 3.5rem;
}

/* آیتم ۱۷د: fluid clamp()-based size (percent of viewport width, --pb-fluid-vw set inline per block) -
   client's own confirmed alternative to separate desktop/tablet/mobile pixel fields. */
.pb-countdown-segment span {
  font-size: clamp(1.2rem, calc(var(--pb-fluid-vw, 3) * 1vw), 4rem);
  font-weight: bold;
  color: var(--pb-primary);
}

@media (max-width: 900px) {
  .pb-countdown-block {
    flex-direction: column;
    align-items: stretch;
  }
}

/* Session 74: .pb-image-marquee's own rules went with that block type; the keyframe below stays -
   .pb-testimonials-carousel still animates with it. */
@keyframes pb-marquee-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* MASTER_CHANGES_PLAN.md آیتم ۱۷و: shared "Line" auto-scroll strip for CorporateCustomers/IconCarousel
   (replacing the old fixed-direction .pb-icon-carousel-* rules IconCarousel alone used to own) - same
   duplicate-once-in-markup technique as .pb-image-marquee above, but direction-aware by default
   (:dir(), same precedent as .pb-image-carousel.pb-carousel-continuous) with an admin "Reverse
   Direction" override (data-reversed, XORed via animation-direction:reverse). */
.pb-line-scroll-wrapper {
  overflow: hidden;
}

.pb-line-scroll {
  display: flex;
  align-items: center;
  gap: 2rem;
  width: max-content;
  animation-duration: 20s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.pb-line-scroll:dir(rtl) { animation-name: pb-line-scroll-rtl; }
.pb-line-scroll:dir(ltr) { animation-name: pb-line-scroll-ltr; }

.pb-line-scroll[data-reversed="True"] {
  animation-direction: reverse;
}

.pb-line-scroll-wrapper:hover .pb-line-scroll {
  animation-play-state: paused;
}

.pb-line-scroll-item {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  flex: 0 0 auto;
  text-decoration: none;
  color: inherit;
}

.pb-line-scroll-item img {
  max-height: 48px;
}

@keyframes pb-line-scroll-rtl {
  from { transform: translateX(0); }
  to { transform: translateX(50%); }
}

@keyframes pb-line-scroll-ltr {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* آیتم ۱۷و "شرکای تجاری": client's spec text "سیاه‌وسفید-با-هاور-رنگی". */
.pb-grayscale-hover img {
  filter: grayscale(1);
  transition: filter 0.25s ease;
}

.pb-grayscale-hover a:hover img,
.pb-grayscale-hover a:focus-visible img {
  filter: grayscale(0);
}

/* آیتم ۱۷و: "سطر ناقص = وسط‌چین" for CorporateCustomers/FeaturedTestimonials/IconCarousel/LatestBlogPosts'
   own Grid modes only - CSS Grid's auto-fill (.pb-grid's own display:grid) can't center a short last
   row, so these specific blocks opt into this flex-wrap version instead; other .pb-grid consumers (e.g.
   the Catalog product gallery) are out of this session's scope and keep the plain grid layout. */
.pb-grid-center-last {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

.pb-grid-center-last > * {
  flex: 0 1 180px;
}

/* آیتم ۱۷و "آیکون شبکه‌اجتماعی": every active SocialIconLink (the header/footer's own single source of
   truth), one centered row that wraps onto further centered rows if the block is narrower than the
   icons need. */
.pb-social-icon-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-content: center;
  gap: 1rem;
}

/* Client request 2026-11-04 (V299) «وسط‌چین»: pb-block--centered comes from PageBlock.ContentCentered. Off, the social
   row and the logo/blog grids start at the page's starting edge; V299 ticked the existing ones so they look as before. */
.pb-block[data-block-type="SocialIcon"]:not(.pb-block--centered) .pb-social-icon-row,
.pb-block[data-block-type="IconCarousel"]:not(.pb-block--centered) .pb-grid-center-last,
.pb-block[data-block-type="LatestBlogPosts"]:not(.pb-block--centered) .pb-blog-grid {
  justify-content: flex-start;
}

/* Client request 2026-11-09: blog cards per row from the blog admin page (BlogGridSettings' --pb-blog-cols-*), same breakpoints
   as .cat-grid. Flex, not grid, so fewer posts than the columns - or a ragged last row - sit centered (.cat-grid-centered's technique). */
.pb-blog-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
}

.pb-blog-grid > * {
  min-width: 0;
  flex: 0 1 calc((100% - (var(--pb-blog-cols-desktop, 3) - 1) * 1rem) / var(--pb-blog-cols-desktop, 3));
  max-width: calc((100% - (var(--pb-blog-cols-desktop, 3) - 1) * 1rem) / var(--pb-blog-cols-desktop, 3));
}

@media (max-width: 1200px) {
  .pb-blog-grid > * {
    flex-basis: calc((100% - (var(--pb-blog-cols-tablet, 2) - 1) * 1rem) / var(--pb-blog-cols-tablet, 2));
    max-width: calc((100% - (var(--pb-blog-cols-tablet, 2) - 1) * 1rem) / var(--pb-blog-cols-tablet, 2));
  }
}

@media (max-width: 900px) {
  .pb-blog-grid > * {
    flex-basis: calc((100% - (var(--pb-blog-cols-mobile, 1) - 1) * 1rem) / var(--pb-blog-cols-mobile, 1));
    max-width: calc((100% - (var(--pb-blog-cols-mobile, 1) - 1) * 1rem) / var(--pb-blog-cols-mobile, 1));
  }
}

/* Client request 2026-11-09: in a blog card the title sat right on the cover and the text almost touched the card's edge -
   now 1rem under the cover, 0.5rem between the lines, 1rem from the sides and the bottom. */
.pb-blog-grid > .pb-card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.5rem 0.5rem 1rem;
}

.pb-blog-grid > .pb-card > img {
  margin-bottom: 0.5rem;
}

.pb-blog-grid > .pb-card > :not(img) {
  margin: 0;
  padding-inline: 0.5rem;
}

.pb-block--centered .pb-block-title,
.pb-block--centered .pb-newsletter-block,
.pb-block--centered .pb-countdown-event-col {
  text-align: center;
}

.pb-block--centered .pb-newsletter-form {
  justify-content: center;
}

.pb-block--centered .pb-countdown-block {
  flex-direction: column;
}

.pb-block--centered .pb-countdown-event-col,
.pb-block--centered .pb-countdown {
  flex: 0 0 auto;
  width: 100%;
}

/* A full-width tile has nothing to be centered in, so its height is capped and its width follows the aspect ratio. */
.pb-block--centered .pb-video-block {
  width: min(100%, calc(80vh * var(--pb-image-aspect, 16/9)));
  margin-inline: auto;
}

.pb-block--centered .pb-form-builder {
  max-width: 720px;
  margin-inline: auto;
}

/* pb-line-center.js: a Line strip whose items fit the page shows one still, centered set instead of the scrolling double. */
.pb-line-scroll.pb-line-scroll--fit {
  width: 100%;
  justify-content: center;
  animation: none;
}

.pb-line-scroll--fit > .pb-line-scroll-duplicate {
  display: none;
}

@media (min-width: 901px) {
  .pb-block--centered .pb-stat-counter-grid .pb-stat-counter-item {
    flex: 0 1 auto;
  }
}

.pb-social-icon-row img {
  max-height: 32px;
}

/* MASTER_CHANGES_PLAN.md آیتم ۱۷د "متن متحرک" (بازبینی ۱۴۰۵/۰۷/۲۶): the WHOLE line - start, middle and end text
   together - travels through the block (enter from one edge, pause at the centre, leave by the opposite edge),
   driven by wwwroot/js/page-blocks.js's startMovingText() (an enter → pause → exit → reset cycle a plain infinite
   CSS keyframe can't express without per-block percentage stops). The block's overflow:hidden is what clips the
   line while it is outside; the line wraps if it is wider than the block so it never gets cut at rest. */
.pb-moving-text {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(1rem, calc(var(--pb-fluid-vw, 4) * 1vw), 3.5rem);
  overflow: hidden;
}

.pb-moving-text-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  will-change: transform;
}

.pb-moving-text-line > span {
  white-space: nowrap;
}

/* MASTER_CHANGES_PLAN.md آیتم ۱۷ج - "N images visible at once" carousel (ImageCarousel=23) and its
   always-wide/always-1-visible sibling (Slide=24) - both share this same track/slide/dots/nav markup
   (_PageBlocks.cshtml). Not a reuse of the old (retired) .pb-slideshow/.pb-image-carousel rules this
   replaces - those were a single-image-at-a-time fade/scroll widget, this is a genuinely different
   multi-item track.
   بازبینی گالری فایل‌ها: client feedback replaced the old "count strictly tied to ratio, no admin field"
   rule with real per-breakpoint control - each data-ratio selector below now reads the admin's own
   --pb-carousel-count-d/t/m (set inline per block, _PageBlocks.cshtml) first and only falls back to
   that ratio's original automatic number when the admin left it unset, same "tablet falls back to
   desktop, mobile falls back to tablet" chain every other breakpoint-aware value in this file already
   uses. The "enlarge to fill if fewer real images than the target count" rule (client's own spec) still
   needs no JS - CSS min() against --pb-actual-count (the real resolved slide count) does it either way. */
.pb-image-carousel {
  position: relative;
  --pb-carousel-gap: var(--pb-carousel-gap-d, 1rem);
  --pb-carousel-effective-count: min(var(--pb-carousel-count, 1), var(--pb-actual-count, 1));
  /* بازبینی گالری فایل‌ها: unset = "advance by one full screenful" (today's exact behavior) - falls
     back to whatever --pb-carousel-count already resolved to on this element, ratio-automatic or
     admin-overridden either way, rather than duplicating that same fallback chain a second time here. */
  --pb-carousel-step: var(--pb-carousel-step-d, var(--pb-carousel-count));
  /* Session 70: Continuous-motion slide height only (Stepped's own sizing is unaffected, see
     --pb-carousel-effective-count above) - unset falls back to the original automatic formula. */
  --pb-carousel-height: var(--pb-carousel-height-d, clamp(160px, 22vw, 320px));
}

.pb-image-carousel[data-ratio="wide"] { --pb-carousel-count: var(--pb-carousel-count-d, 1); }
.pb-image-carousel[data-ratio="square"],
.pb-image-carousel[data-ratio="portrait3x4"] { --pb-carousel-count: var(--pb-carousel-count-d, 5); }
.pb-image-carousel[data-ratio="landscape4x3"] { --pb-carousel-count: var(--pb-carousel-count-d, 4); }
/* Session 75: FeaturedTestimonials' own group - a card is sized by its text, so its default count is a
   readable 3/2/1 rather than one of the image ratios' denser defaults. */
.pb-image-carousel[data-ratio="cards"] { --pb-carousel-count: var(--pb-carousel-count-d, 3); }

@media (min-width: 901px) and (max-width: 1200px) {
  .pb-image-carousel { --pb-carousel-gap: var(--pb-carousel-gap-t, var(--pb-carousel-gap-d, 1rem)); --pb-carousel-step: var(--pb-carousel-step-t, var(--pb-carousel-step-d, var(--pb-carousel-count))); --pb-carousel-height: var(--pb-carousel-height-t, var(--pb-carousel-height-d, clamp(160px, 22vw, 320px))); }
  .pb-image-carousel[data-ratio="wide"] { --pb-carousel-count: var(--pb-carousel-count-t, var(--pb-carousel-count-d, 1)); }
  .pb-image-carousel[data-ratio="square"],
  .pb-image-carousel[data-ratio="portrait3x4"] { --pb-carousel-count: var(--pb-carousel-count-t, var(--pb-carousel-count-d, 3)); }
  .pb-image-carousel[data-ratio="landscape4x3"] { --pb-carousel-count: var(--pb-carousel-count-t, var(--pb-carousel-count-d, 2)); }
  .pb-image-carousel[data-ratio="cards"] { --pb-carousel-count: var(--pb-carousel-count-t, var(--pb-carousel-count-d, 2)); }
}

@media (max-width: 900px) {
  .pb-image-carousel { --pb-carousel-gap: var(--pb-carousel-gap-m, var(--pb-carousel-gap-t, var(--pb-carousel-gap-d, 1rem))); --pb-carousel-step: var(--pb-carousel-step-m, var(--pb-carousel-step-t, var(--pb-carousel-step-d, var(--pb-carousel-count)))); --pb-carousel-height: var(--pb-carousel-height-m, var(--pb-carousel-height-t, var(--pb-carousel-height-d, clamp(160px, 22vw, 320px)))); }
  .pb-image-carousel[data-ratio="wide"] { --pb-carousel-count: var(--pb-carousel-count-m, var(--pb-carousel-count-t, var(--pb-carousel-count-d, 1))); }
  .pb-image-carousel[data-ratio="square"],
  .pb-image-carousel[data-ratio="portrait3x4"] { --pb-carousel-count: var(--pb-carousel-count-m, var(--pb-carousel-count-t, var(--pb-carousel-count-d, 2))); }
  .pb-image-carousel[data-ratio="landscape4x3"] { --pb-carousel-count: var(--pb-carousel-count-m, var(--pb-carousel-count-t, var(--pb-carousel-count-d, 1))); }
  .pb-image-carousel[data-ratio="cards"] { --pb-carousel-count: var(--pb-carousel-count-m, var(--pb-carousel-count-t, var(--pb-carousel-count-d, 1))); }
}

/* Session 75: a testimonial card has no image aspect ratio and no fixed height - it is as tall as its
   own text, and as wide as its share of the row. Both carousel motions otherwise size a slide from
   --pb-image-aspect/--pb-carousel-height, so this group opts out of both (that is also why
   page-blocks.js's fitCarousels() skips its JS height computation for .pb-carousel-cards).
   Stepped keeps the ordinary width-per-visible-count formula; Continuous cannot use it at all, because
   its track is width:max-content and a percentage against that is circular (the same reason the image
   carousel sizes its own continuous slides by height) - so cards there take a fixed, breakpoint-aware
   width instead, and how many are on screen simply follows from it. */
.pb-image-carousel.pb-carousel-cards .pb-media-slide {
  aspect-ratio: auto;
  height: auto;
  align-self: stretch;
}

.pb-image-carousel.pb-carousel-cards.pb-carousel-continuous .pb-media-slide {
  flex: 0 0 auto;
  height: auto;
  width: var(--pb-card-width, clamp(260px, 26vw, 380px));
}

.pb-image-carousel.pb-carousel-cards .pb-media-slide img {
  height: auto;
}

.pb-image-carousel.pb-image-shadow {
  box-shadow: var(--pb-shadow-lg, 0 14px 34px rgb(0 0 0 / 0.26));
}

.pb-image-carousel .pb-media-slide {
  position: relative;
  aspect-ratio: var(--pb-image-aspect, 1/1);
  overflow: hidden;
  border-radius: inherit;
}

.pb-image-carousel .pb-media-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Stepped: native scroll-snap paging - page-blocks.js drives dots/prev-next/autoplay and (بازبینی
   گالری فایل‌ها) a truly seamless wrap from the last image back to the first, same duplicated-track
   technique Continuous already used, adapted to a discrete auto-advancing step instead of a CSS
   animation - see page-blocks.js's startImageCarousels(). */
.pb-image-carousel.pb-carousel-stepped .pb-media-track {
  display: flex;
  gap: var(--pb-carousel-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
}

.pb-image-carousel.pb-carousel-stepped .pb-media-track::-webkit-scrollbar {
  display: none;
}

/* بازبینی گالری فایل‌ها: same direction concept/XOR as Continuous's animation-direction above, but
   flex-direction here instead - page-blocks.js's autoplay/seamless-wrap logic always just "steps
   forward through DOM order"; which visual (left/right) edge that scrolls toward is entirely this CSS
   flip, so the JS never needs its own notion of forward vs backward. */
.pb-image-carousel.pb-carousel-stepped[data-direction="ltr"]:not([data-direction-reversed="True"]) .pb-media-track,
.pb-image-carousel.pb-carousel-stepped[data-direction="rtl"][data-direction-reversed="True"] .pb-media-track {
  flex-direction: row-reverse;
}

.pb-image-carousel.pb-carousel-stepped .pb-media-slide {
  flex: 0 0 calc((100% - (var(--pb-carousel-effective-count) - 1) * var(--pb-carousel-gap)) / var(--pb-carousel-effective-count));
  scroll-snap-align: start;
}

/* Session 70: client - Stepped's height/sizing should work like Continuous's. Left fully unconfigured a
   block keeps the ratio-based width formula above untouched (page-blocks.js only adds this class when an
   explicit Height or VisibleCount was actually set for the active breakpoint - a plain CSS rule can't
   tell "admin chose N" apart from "using the automatic ratio-based N", so the class is JS-decided). Same
   "fixed height, width auto via aspect-ratio" technique Continuous already uses - --pb-carousel-height
   itself already resolves per-breakpoint (admin's own Height field, or page-blocks.js's JS-computed
   override when VisibleCount is what's actually driving it, client: "اولویت با تعداد تصویر هم‌زمان است"). */
.pb-image-carousel.pb-carousel-stepped.pb-carousel-sized-by-height .pb-media-slide {
  flex: 0 0 auto;
  width: auto;
  height: var(--pb-carousel-height);
}

/* Continuous: same duplicate-once-in-markup technique as .pb-image-marquee/.pb-icon-carousel above -
   an unbroken glide, sized by height (not a %-of-track width, which would be circular against this
   track's own max-content width) - same "fixed height, width:auto via aspect-ratio" technique as
   .pb-image-marquee's own img sizing. Dots/nav never render for this mode (_PageBlocks.cshtml). */
.pb-image-carousel.pb-carousel-continuous {
  overflow: hidden;
}

/* پیوسته و بی‌فاصله در تکرار، دو مشکل:
   1) pb-marquee-scroll's plain -50% assumes the duplicated half starts exactly at the track's
      midpoint, but with N slides + (N-1) gaps that midpoint is half a gap short of where the real
      duplicate seam actually is (7 widths + 7 gaps needed, -50% only covers 7 widths + 6.5 gaps) -
      the shortfall showed up as a small snap once per loop.
   2) far more serious: an untransformed row.pb-media-track (flex, direction inherited from this
      RTL site) rests flush against the carousel's RIGHT edge, with all its "runway" extending to the
      left - so the shared pb-marquee-scroll (0 -> negative%, written for an LTR flush-left row whose
      runway extends right) drags this row in exactly the wrong direction: for most of every cycle the
      whole track sits left of the frame entirely, frame shows nothing. The runway is only on the
      LEFT here, so this row must animate 0 -> positive%. An LTR-page render of this same block
      genuinely is flush-left (mirrored), where the original negative direction is the correct one -
      :dir() below picks the sign that matches whichever is actually true, instead of assuming rtl. */
@keyframes pb-marquee-scroll-image-rtl {
  from { transform: translateX(0); }
  to { transform: translateX(calc(50% + (var(--pb-carousel-gap) / 2))); }
}

@keyframes pb-marquee-scroll-image-ltr {
  from { transform: translateX(0); }
  to { transform: translateX(calc(-50% - (var(--pb-carousel-gap) / 2))); }
}

.pb-image-carousel.pb-carousel-continuous .pb-media-track {
  display: flex;
  gap: var(--pb-carousel-gap);
  width: max-content;
  animation-duration: var(--pb-carousel-duration, 25s);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.pb-image-carousel.pb-carousel-continuous .pb-media-track:dir(rtl) {
  animation-name: pb-marquee-scroll-image-rtl;
}

.pb-image-carousel.pb-carousel-continuous .pb-media-track:dir(ltr) {
  animation-name: pb-marquee-scroll-image-ltr;
}

/* بازبینی گالری فایل‌ها: default direction is the opposite of the active language's reading direction
   (client: "پیش‌فرض جهت ورود تصاویر خلاف جهت زبان") - rtl's "opposite" is this file's existing
   non-direction-aware baseline drift (pb-marquee-scroll's own 0→-50%, unchanged/"normal"); ltr's
   "opposite" is that same drift mirrored (animation-direction:reverse). data-direction-reversed flips
   whichever of those two the language alone would have picked. */
.pb-image-carousel[data-direction="ltr"]:not([data-direction-reversed="True"]) .pb-media-track,
.pb-image-carousel[data-direction="rtl"][data-direction-reversed="True"] .pb-media-track {
  animation-direction: reverse;
}

.pb-image-carousel.pb-carousel-continuous .pb-media-slide {
  flex: 0 0 auto;
  width: auto;
  height: var(--pb-carousel-height);
}

/* Session 70: too few real images to fill the frame - one leftover-space rule (now shared by both
   motions, page-blocks.js's fitCarousels()) covers the ≤2-image case (data-can-move="False", never
   duplicated/animated at all, client's own "بدون حرکت اگر ≤۲") and the >2-but-still-not-enough case
   (data-can-move="True", duplicated for the loop) alike - CSS alone can't compare rendered content width
   against a dynamic admin-chosen image count/height, so JS measures it and toggles this one class. Stay
   centered and static instead of looping/scrolling images around a mostly-empty frame; JS hides the
   duplicated second half's slides inline (display:none) while this class is active (a no-op when there
   was no duplicate half to begin with) and (Stepped only) stops autoplay and hides prev/next/dots -
   client: "حرکت خودکار غیرفعال و کلیدهای حرکت نیز نمایش داده نمی‌شوند". animation-name is restated here
   (not just the animation:none shorthand) because it otherwise loses to the :dir(rtl)/:dir(ltr) rules
   below on specificity (both select 4 classes; :dir() counts as one) - this class, being the later
   selector in the file, wins. */
.pb-image-carousel.pb-carousel-continuous.pb-carousel-few-images .pb-media-track {
  width: 100%;
  justify-content: center;
  animation-name: none;
  transform: none;
}

.pb-image-carousel.pb-carousel-stepped.pb-carousel-few-images .pb-media-track {
  justify-content: center;
  overflow-x: hidden;
}

.pb-image-carousel[data-pause-on-hover="True"]:hover .pb-media-track {
  animation-play-state: paused;
}

/* User request 2026-09-10 "کاروسل ویدئو": stays paused while any tile inside is actively playing, even
   after the cursor leaves - page-blocks.js's startVideoCarousels() toggles this class from each tile's
   own play/pause/ended events. Independent of (and in addition to) the hover rule above - resuming
   needs neither condition to still hold. */
.pb-video-carousel.pb-carousel-video-active .pb-media-track {
  animation-play-state: paused;
}

.pb-image-carousel[data-can-move="False"] .pb-media-track {
  animation: none;
  overflow: hidden;
}

/* آیتم ۱۷ج "اسلاید" - the caption box's own independent overlay, not the Image block's auto-caption
   text bar above (Slide never renders one - see _PageBlocks.cshtml). Horizontal align reuses the exact
   right/center/left-as-flex-start/center/end direction-aware technique .pb-image-textbar-right/left
   already established below; vertical align is simpler than the text bar's (no "middle" absolute-center
   split needed) since this box wraps its own content rather than sitting at a fixed height. */
.pb-slide-box {
  position: absolute;
  max-width: min(32rem, calc(100% - 3rem));
  width: max-content;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.25rem 1.5rem;
  color: #fff;
}

.pb-slide-box h2 { margin: 0; }
.pb-slide-box p { margin: 0; }

.pb-slide-box-bottom { bottom: 1.5rem; }
.pb-slide-box-top { top: 1.5rem; }
.pb-slide-box-middle { top: 50%; transform: translateY(-50%); }

/* راست/چپ = منطقی (شروع/پایان)، همان قاعده‌ی .pb-image-textbar-right/left بالا. */
.pb-slide-box-right { inset-inline-start: 1.5rem; text-align: start; }
.pb-slide-box-left { inset-inline-end: 1.5rem; text-align: end; }
.pb-slide-box-center { inset-inline: 1.5rem; margin-inline: auto; text-align: center; align-items: center; }

.pb-slide-button {
  align-self: inherit;
  display: inline-block;
  padding: 0.5rem 1.25rem;
  border-radius: var(--pb-radius-sm, 8px);
  background: var(--pb-primary);
  color: #fff;
  text-decoration: none;
}

.pb-media-slide img {
  width: 100%;
  display: block;
}

/* Session 75: the LatestBlogPosts card grew a date and a summary line (client: "کارت غنی"). */
.pb-blog-card-date {
  font-size: 0.85rem;
  opacity: 0.7;
}

.pb-blog-card-summary {
  margin: 0.25rem 0 0;
  font-size: 0.92rem;
  line-height: 1.7;
}

/* Session 75: optional per-tile caption under a VideoGrid/VideoCarousel tile. */
.pb-video-tile-title {
  display: block;
  margin-top: 0.5rem;
  text-align: center;
  font-size: 0.95rem;
}

/* Session 75: the newsletter block may now carry its own heading + intro line above the form. */
.pb-newsletter-intro {
  margin: 0 0 1rem;
}

/* Session 74: the .pb-fit-cover/contain/stretch and .pb-media-caption rules were ImageMarquee-only
   and went with that block type. */
.pb-media-slide-link {
  position: absolute;
  inset: 0;
}

/* آیتم ۱۷ب "تصویر" - single fixed-aspect-ratio auto-cycling frame, no manual controls; its own
   .pb-active-toggle autoplay engine (page-blocks.js's startMediaWidgets) with its own CSS transition
   per Effect. --pb-image-aspect/width are set inline per block (per-block admin config, native CSS
   aspect-ratio - same technique as --cat-aspect/--blog-cover-aspect elsewhere in this file). */
.pb-image-block {
  position: relative;
  max-width: 100%;
  /* بازبینی گالری فایل‌ها: client - a width below 100% must center inside the Section/InnerSection-
     column space, not sit flush against the block's start edge (this block's own parent, .pb-block, is
     a flex item that always stretches to the full column width regardless of this element's own
     narrower width). */
  margin-inline: auto;
  aspect-ratio: var(--pb-image-aspect, 1/1);
  /* Session 70: unset (auto) keeps today's aspect-ratio-only sizing untouched (aspect-ratio only
     resolves a dimension left auto - an explicit admin height here makes both width and height
     explicit, so aspect-ratio stops applying and object-fit:cover below crops the image instead). */
  height: var(--pb-image-height-d, auto);
  overflow: hidden;
}

@media (min-width: 901px) and (max-width: 1200px) {
  .pb-image-block { height: var(--pb-image-height-t, var(--pb-image-height-d, auto)); }
}

@media (max-width: 900px) {
  .pb-image-block { height: var(--pb-image-height-m, var(--pb-image-height-t, var(--pb-image-height-d, auto))); }
}

.pb-image-block.pb-image-shadow {
  box-shadow: var(--pb-shadow-lg, 0 14px 34px rgb(0 0 0 / 0.26));
}

.pb-image-block .pb-media-track {
  position: relative;
  width: 100%;
  height: 100%;
}

.pb-image-block .pb-media-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.pb-image-block .pb-media-slide.pb-active {
  opacity: 1;
}

.pb-image-block .pb-media-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pb-image-block.pb-effect-toptobottom .pb-media-slide { transform: translateY(-100%); }
.pb-image-block.pb-effect-toptobottom .pb-media-slide.pb-active { transform: translateY(0); }
.pb-image-block.pb-effect-bottomtotop .pb-media-slide { transform: translateY(100%); }
.pb-image-block.pb-effect-bottomtotop .pb-media-slide.pb-active { transform: translateY(0); }
/* SlideDirectional: enters from the inline-end edge toward inline-start - flips automatically between
   fa(rtl)/en(ltr) because it rides the element's own logical `direction`, no [dir]/.dir-rtl override
   needed.
   Session 71 bug fix (real, reproducible - client-reported "1px sliver of the image, RTL only"): the
   RTL+reversed selector below has higher specificity (4 classes) than the plain ".pb-active" override
   3 lines down (3 classes), so on this site's default Persian/RTL locale with Effect=SlideDirectional+
   Reversed, the "active" slide's transform NEVER actually resolved to translateX(0) - it stayed
   translateX(100%), fully off-screen, with only a sub-pixel rounding sliver peeking past
   .pb-image-block's overflow:hidden clip. ":not(.pb-active)" on every "off-state" selector below makes
   this whole class of bug structurally impossible instead of just re-balancing specificity numbers by
   hand (which the next new variant added here could just as easily re-break). */
.pb-image-block.pb-effect-slidedirectional .pb-media-slide:not(.pb-active) { transform: translateX(100%); }
[dir="rtl"] .pb-image-block.pb-effect-slidedirectional .pb-media-slide:not(.pb-active),
.dir-rtl .pb-image-block.pb-effect-slidedirectional .pb-media-slide:not(.pb-active) { transform: translateX(-100%); }
/* بازبینی گالری فایل‌ها: .pb-effect-reversed flips only this effect (client confirmed) - one extra
   class beats the plain rules above on specificity alone, so order here doesn't matter. */
.pb-image-block.pb-effect-slidedirectional.pb-effect-reversed .pb-media-slide:not(.pb-active) { transform: translateX(-100%); }
[dir="rtl"] .pb-image-block.pb-effect-slidedirectional.pb-effect-reversed .pb-media-slide:not(.pb-active),
.dir-rtl .pb-image-block.pb-effect-slidedirectional.pb-effect-reversed .pb-media-slide:not(.pb-active) { transform: translateX(100%); }
.pb-image-block.pb-effect-slidedirectional .pb-media-slide.pb-active { transform: translateX(0); }
.pb-image-block.pb-effect-flash .pb-media-slide { transition: opacity 0.15s linear; }
.pb-image-block.pb-effect-fade .pb-media-slide { transition: opacity 0.8s ease; }

/* User request 2026-08-30 "بلوک ویدئو"/"بلوک گرید ویدئو": same --pb-image-aspect/object-fit:cover
   sizing technique as .pb-image-block above - deliberately no border-radius rule anywhere in this block
   (client's own explicit requirement: "بدون گرد شدن لبه‌ها"), unlike every other image-like block here. */
.pb-video-block {
  position: relative;
  aspect-ratio: var(--pb-image-aspect, 16/9);
  overflow: hidden;
}

/* User request 2026-09-10 "عدم امکان دانلود تصاویر و ویدئو در کل سایت": best-effort deterrent, public
   site only (body never gets .pb-admin-body there - see _Layout.cshtml) - the admin file gallery still
   needs normal drag/select of the files it manages. Paired with media-protection.js's contextmenu/
   dragstart preventDefault (this alone stops neither, browsers still allow a right-click "save image as"
   even with user-select:none - the JS is the part that actually blocks it). */
body:not(.pb-admin-body) img,
body:not(.pb-admin-body) video {
  -webkit-user-drag: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.pb-video-block .pb-video-thumb,
.pb-video-block .pb-video-player {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pb-video-block .pb-video-player {
  display: none;
  background: #000;
}

.pb-video-block.pb-video-playing .pb-video-thumb,
.pb-video-block.pb-video-playing .pb-video-play-button {
  display: none;
}

.pb-video-block.pb-video-playing .pb-video-player {
  display: block;
}

.pb-video-play-button {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 64px;
  height: 64px;
  border: none;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.55);
  color: #fff;
  font-size: 1.3rem;
  line-height: 64px;
  text-align: center;
  cursor: pointer;
}

/* بلوک گرید ویدئو: admin-selectable column count (2-4, same shape as .pb-inner-section) - the actual
   column count is set inline per-block (grid-template-columns), same "literal computed value" technique
   as imageRadiusVar/carouselRadiusVar in _PageBlocks.cshtml, since it is a single admin-picked number
   with no other breakpoint variation asked for. */
.pb-video-grid {
  display: grid;
  gap: 1rem;
}

@media (max-width: 900px) {
  .pb-video-grid { grid-template-columns: 1fr !important; }
}

.pb-image-textbar {
  position: absolute;
  inset-inline: 0;
  display: flex;
  align-items: center;
  padding: 0 0.75rem;
  color: #fff;
  overflow: hidden;
}

.pb-image-textbar span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pb-image-textbar-bottom { bottom: 0; }
.pb-image-textbar-top { top: 0; }
.pb-image-textbar-middle { top: 50%; transform: translateY(-50%); }
/* راست/چپ = منطقی (شروع/پایان) نه فیزیکی - flex-start/flex-end خودشان بر اساس direction برمی‌گردند. */
.pb-image-textbar-right { justify-content: flex-start; }
.pb-image-textbar-left { justify-content: flex-end; }
.pb-image-textbar-center { justify-content: center; }

.pb-media-prev, .pb-media-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  border: none;
  background: color-mix(in srgb, var(--pb-bg) 70%, transparent);
  color: var(--pb-fg);
  font-size: 1.5rem;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  cursor: pointer;
}

.pb-media-prev { inset-inline-start: 0.5rem; }
.pb-media-next { inset-inline-end: 0.5rem; }
/* directional chevrons follow the writing direction */
.dir-rtl .pb-media-prev .pb-icon, .dir-rtl .pb-media-next .pb-icon { transform: scaleX(-1); }

/* بازبینی یکپارچگی F7: yes/no cells and admin icon buttons */
.pb-bool { display: inline-flex; align-items: center; vertical-align: middle; }
.pb-bool-yes { color: #2C7A57; }
.pb-bool-no { color: #B23A3A; }
.btn-icon { display: inline-flex; align-items: center; justify-content: center; padding: 0.3rem 0.45rem; }
.form-inline { display: inline; }
/* Queue item 19 s2: reject/void a payment with a reason - folded until needed. */
.pb-inline-details { display: inline-block; vertical-align: top; margin-inline-end: .5rem; }
.pb-inline-details > summary { cursor: pointer; text-decoration: underline; }
.pb-inline-details[open] { display: block; margin-block: .25rem; }
/* destructive posts (safe-delete + confirm forms) read as danger without per-button classes */
form[data-safe-delete] button[type="submit"], form[data-confirm] button[type="submit"], .btn-danger {
  background: #B23A3A;
  border-color: #B23A3A;
  color: #fff;
}
form[data-safe-delete] button[type="submit"]:hover, form[data-confirm] button[type="submit"]:hover, .btn-danger:hover {
  background: #952f2f;
  border-color: #952f2f;
}
/* empty table state (admin-tables.js fills the row text) */
.pb-empty-row td { text-align: center; color: var(--pb-muted-text, #666); padding: 1.25rem 0.75rem; }
/* A row added in the page (variant matrix, colour options, set components) hides the "nothing here" row. */
tbody:has(tr:not(.pb-empty-row)) > .pb-empty-row { display: none; }
.pb-empty-state { color: var(--pb-muted-text, #666); }
/* dashboard */
.pb-dashboard-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; margin-top: 1rem; }
.pb-dashboard-tile { display: flex; flex-direction: column; gap: 0.35rem; padding: 1rem 1.1rem; border: 1px solid var(--pb-border); border-radius: var(--pb-radius, 8px); text-decoration: none; color: inherit; background: color-mix(in srgb, var(--pb-primary) 4%, transparent); }
.pb-dashboard-tile:hover { background: color-mix(in srgb, var(--pb-primary) 10%, transparent); }
.pb-dashboard-tile-attention { border-color: var(--pb-primary); }
.pb-dashboard-count { font-size: 1.9rem; font-weight: 700; line-height: 1.1; }
.pb-dashboard-tile-attention .pb-dashboard-count { color: var(--pb-primary); }
.pb-dashboard-label { font-size: 0.9rem; }
.pb-preview-thumb { max-height: 48px; }
.pb-preview-thumb-sm { max-height: 32px; }
.pb-preview-thumb-dark { background: #333; }
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.pb-badge-muted { display: inline-block; font-size: 0.75rem; padding: 0 0.45rem; border-radius: 999px; background: color-mix(in srgb, var(--pb-muted-text, #666) 15%, transparent); margin-inline-start: 0.4rem; }
.pb-sidebar-collapsible summary .pb-icon { margin-inline-end: 0.45rem; vertical-align: -3px; }

.pb-media-dots {
  display: flex;
  justify-content: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
}

.pb-media-dots button {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  border: none;
  background: var(--pb-border);
  cursor: pointer;
  padding: 0;
}

.pb-media-dots button.pb-active {
  background: var(--pb-primary);
}

/* Shared media-gallery change request: the one file-picker popup every admin form is meant to use -
   same themed <dialog> approach as #auth-modal, wider and internally scrollable since it hosts a
   thumbnail grid plus a metadata form instead of one short login form. */
#media-library-modal {
  background: var(--pb-bg);
  color: var(--pb-fg);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  box-shadow: var(--pb-shadow-lg, 0 4px 24px rgba(0, 0, 0, 0.35));
  padding: 0;
  width: 720px;
  max-width: calc(100vw - 2rem);
  max-height: 85vh;
  /* MASTER_CHANGES_PLAN.md queue item 7 (پیش‌یافته - دو اسکرول‌بار): max-height alone does not clip
     content - without an explicit overflow, the dialog simply lets content spill past its own rounded
     corners while .ml-modal-body (below) already scrolls internally. Hidden here so exactly one
     scrollbar (the body's own) ever appears. */
  overflow: hidden;
}

/* بازبینی گالری فایل‌ها: .ml-modal-body used to reserve its scroll height as a hardcoded
   calc(85vh - 7rem), guessing the header+tabs height - real header+tabs height varies with the
   language count/tab wrapping and was already taller than 7rem, so the guess undershot and the
   dialog's own overflow:hidden clipped off the bottom of the body's scrollable content (e.g. the
   Save/Delete action row) with no way to scroll to it. Flex layout sizes the body off the header/tabs'
   real rendered height instead of a guessed constant.
   [open] here matters: a bare #media-library-modal { display: flex } would (by ID specificity) beat
   the UA stylesheet's dialog:not([open]) { display: none }, forcing the dialog to always render even
   while closed - two permanently-visible, unclosable popups stacked on every admin page. Scoping to
   [open] only overrides the UA's own dialog[open] { display: block }, leaving the closed state alone. */
#media-library-modal[open] {
  display: flex;
  flex-direction: column;
}

#media-library-modal::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.ml-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--pb-border);
}

.ml-modal-header .js-ml-close {
  border: none;
  background: none;
  font-size: 1.3rem;
  cursor: pointer;
  color: var(--pb-fg);
}

.ml-modal-tabs {
  display: flex;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem 0;
}

.ml-tab {
  border: none;
  background: none;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  color: var(--pb-fg);
  border-bottom: 2px solid transparent;
}

.ml-tab.is-active {
  border-bottom-color: var(--pb-primary);
  font-weight: 600;
}

.ml-modal-body {
  padding: 1rem 1.5rem 1.5rem;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.ml-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 0.5rem;
}

.ml-grid-item {
  position: relative;
  aspect-ratio: 1;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 8px);
  overflow: hidden;
  padding: 0;
  cursor: pointer;
  background: none;
}

.ml-grid-item img {
  width: 100%;
  height: 100%;
  object-fit: contain; /* client 2026-10-17: whole image visible inside the tile */
}

.ml-grid-item.is-selected {
  border-color: var(--pb-primary);
  outline: 2px solid var(--pb-primary);
}

/* MASTER_CHANGES_PLAN.md queue item 7 (بند ۶ - "تیک انتخاب واضح"): the border/outline above is easy
   to miss across a dense grid of similar thumbnails - an explicit checkmark badge makes a selected
   item unambiguous at a glance. Opposite corner from .ml-grid-item-duration (bottom-end) so a
   selected video's duration label never overlaps it. */
.ml-grid-item.is-selected::after {
  content: '\2713';
  position: absolute;
  inset-inline-start: 4px;
  inset-block-end: 4px;
  width: 18px;
  height: 18px;
  line-height: 18px;
  text-align: center;
  background: var(--pb-primary);
  color: #fff;
  border-radius: 50%;
  font-size: 0.75rem;
  font-weight: 700;
}

.js-ml-use-multi {
  display: block;
  margin: 1rem auto 0;
}

.ml-grid-item-label {
  display: block;
  font-size: 0.7rem;
  padding: 0.5rem;
  word-break: break-word;
}

.ml-grid-item-video {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: #111;
  position: relative;
}

.ml-grid-item-play {
  color: #fff;
  font-size: 1.5rem;
  opacity: 0.85;
}

.ml-grid-item-duration {
  position: absolute;
  inset-inline-end: 4px;
  inset-block-end: 4px;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  font-size: 0.65rem;
  padding: 1px 4px;
  border-radius: 4px;
}

.ml-grid-item-badge {
  position: absolute;
  inset-inline-start: 4px;
  inset-block-start: 4px;
  font-size: 0.6rem;
  padding: 1px 5px;
  border-radius: 4px;
  color: #fff;
}

.ml-grid-item-badge.is-in-use {
  background: rgba(217, 119, 6, 0.85);
}

.ml-grid-item-badge.is-unused {
  background: rgba(22, 163, 74, 0.75);
}

.js-ml-load-more {
  display: block;
  margin: 1rem auto 0;
}

/* MASTER_CHANGES_PLAN.md queue item 7 (بند ۲) - "گالری فایل‌ها" standalone admin page. Its grid reuses
   .ml-grid/.ml-grid-item as-is (see file-gallery.js); only the toolbar/detail-panel wrapper below it
   are new. */
.fg-toolbar {
  margin-bottom: 1rem;
  max-width: 22rem;
}

.fg-detail {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--pb-border);
}

.fg-preview-image {
  display: block;
  max-width: 320px;
  max-height: 320px;
  border-radius: var(--pb-radius-sm, 8px);
}

.fg-preview-caption {
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
  opacity: 0.8;
  overflow-wrap: anywhere;
}

.fg-detail-meta {
  display: flex;
  gap: 0.4rem;
  margin: 0.75rem 0;
}

.fg-detail-meta dt::after {
  content: ':';
}

.fg-detail-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.js-fg-load-more {
  display: block;
  margin: 1rem auto 0;
}

.ml-metadata {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--pb-border);
}

.ml-metadata-field {
  margin-bottom: 0.5rem;
}

.ml-metadata-field label {
  display: block;
  font-size: 0.85rem;
  margin-bottom: 0.25rem;
}

.ml-metadata-field input {
  width: 100%;
}

.ml-metadata-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
  align-items: center;
}

.ml-replace-label {
  cursor: pointer;
  text-decoration: underline;
}

/* MASTER_CHANGES_PLAN.md queue item 4: linked-media list on Collection/Model/Item admin forms,
   grouped into separate visual boxes (thumbnail/banner/gallery) with a real image preview per
   card instead of the old flat "Role - FileId" text list. Grid sizing mirrors .ml-grid so cards
   stay evenly sized per row. */
.cc-media-boxes {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

.cc-media-box {
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 8px);
  padding: 1rem;
}

.cc-media-box h3 {
  margin: 0 0 0.75rem;
}

.cc-media-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.75rem;
}

.cc-media-card {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 8px);
  padding: 0.5rem;
  min-width: 0;
  box-sizing: border-box;
}

/* Client request 2026-09-20: scaled to fit (never cropped) so the whole image - a 16:3 banner
   in particular - is visible inside the square card; also sizes the <video> preview card. */
.cc-media-card-thumb {
  width: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  border-radius: var(--pb-radius-sm, 6px);
}

.cc-media-card-role {
  font-size: 0.75rem;
  text-align: center;
  opacity: 0.85;
}

/* Client feedback (session 53 follow-up): the old text buttons ("Save"/"Delete") overflowed this
   ~140px card. Both forms sit in one row, icon-only buttons at a fixed small size, and every piece
   is box-sizing:border-box + min-width:0 so the row can never grow past the card's own padding. */
.cc-media-card-actions {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

.cc-media-card-sort {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.cc-media-card-sort input {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  padding: 0.15rem 0.25rem;
  font-size: 0.75rem;
}

.cc-media-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  box-sizing: border-box;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 6px);
  background: none;
  color: inherit;
  cursor: pointer;
}

.cc-media-icon-btn svg {
  width: 14px;
  height: 14px;
}

.cc-media-empty {
  opacity: 0.6;
  font-size: 0.85rem;
  margin: 0;
}

/* DRAFT_ChangeRequests_PostStage13.md items #46/#55 - same themed <dialog> approach as
   #media-library-modal, reuses its .ml-modal-header/.ml-modal-body classes since the header/body
   chrome is identical; only the list/row styling below is new. */
#entity-picker-modal {
  background: var(--pb-bg);
  color: var(--pb-fg);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  box-shadow: var(--pb-shadow-lg, 0 4px 24px rgba(0, 0, 0, 0.35));
  padding: 0;
  width: 480px;
  max-width: calc(100vw - 2rem);
  max-height: 85vh;
  overflow: hidden;
}

/* بازبینی گالری فایل‌ها: .ml-modal-body (shared with #media-library-modal) now sizes itself with
   flex instead of a hardcoded calc(85vh - Nrem) guess - needs the same flex container here so it
   actually fills/scrolls instead of growing unbounded past this dialog's own rounded corners.
   [open]-scoped for the same reason as #media-library-modal[open] above: a bare display:flex on the
   id would out-specificity the UA's dialog:not([open]) { display: none } and leave this dialog
   permanently visible and unclosable even before .showModal() is ever called. */
#entity-picker-modal[open] {
  display: flex;
  flex-direction: column;
}

#entity-picker-modal::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

/* MASTER_CHANGES_PLAN.md queue item 7 (بند ۴) - shared themed delete-confirm dialog, same small-dialog
   sizing as #auth-modal, used by both media-library.js and file-gallery.js instead of window.confirm(). */
#file-delete-confirm-modal {
  background: var(--pb-bg);
  color: var(--pb-fg);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  box-shadow: var(--pb-shadow-lg, 0 4px 24px rgba(0, 0, 0, 0.35));
  padding: 1.25rem 1.5rem;
  width: 360px;
  max-width: calc(100vw - 2rem);
}

#file-delete-confirm-modal::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.fdc-header h2 {
  margin: 0 0 0.75rem;
  font-size: 1.05rem;
}

.fdc-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
  margin-top: 1rem;
}

.js-ep-filter {
  width: 100%;
  margin-bottom: 0.75rem;
}

.ep-list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  max-height: calc(85vh - 12rem);
  overflow-y: auto;
}

.ep-row {
  text-align: start;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 6px);
  background: none;
  color: var(--pb-fg);
  cursor: pointer;
}

.ep-row.is-selected {
  border-color: var(--pb-primary);
  background: color-mix(in srgb, var(--pb-primary) 12%, transparent);
}

.js-ep-use {
  margin-top: 0.75rem;
}

.ep-chips {
  font-size: 0.85rem;
  color: var(--pb-fg-muted, var(--pb-fg));
  margin-bottom: 0.25rem;
}

/* آیتم ۱۷ب "تصویر" - the admin-facing add/remove/reorder preview strip for a block's direct-gallery
   image list (wwwroot/js/pagebuilder-image-list.js). */
[data-image-list] {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.pb-image-list-item {
  display: inline-flex;
  flex-direction: column;
  gap: 0.25rem;
  width: 96px;
}

/* بازبینی گالری فایل‌ها: the thumbnail + its 3 overlay buttons keep exactly the old .pb-image-list-item
   look/behavior, just under a new name - moved into its own box so the manual-caption inputs below
   can grow .pb-image-list-item taller without dragging the overlay buttons (position:absolute against
   the nearest positioned ancestor) away from the image's own corners. */
.pb-image-list-thumb {
  position: relative;
  display: inline-flex;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm, 6px);
  overflow: hidden;
}

.pb-image-list-thumb img {
  width: 72px;
  height: 72px;
  object-fit: cover;
  display: block;
}

.pb-image-list-thumb button {
  position: absolute;
  width: 1.25rem;
  height: 1.25rem;
  line-height: 1.25rem;
  padding: 0;
  font-size: 0.75rem;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  border: none;
  cursor: pointer;
}

.pb-image-list-thumb .js-image-list-remove {
  top: 2px;
  inset-inline-end: 2px;
}

.pb-image-list-thumb .js-image-list-up {
  bottom: 2px;
  inset-inline-start: 2px;
}

.pb-image-list-thumb .js-image-list-down {
  bottom: 2px;
  inset-inline-start: 22px;
}

.pb-image-list-captions {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.pb-image-list-captions input {
  width: 100%;
  box-sizing: border-box;
  font-size: 0.7rem;
  padding: 0.15rem 0.3rem;
}

/* آیتم ۱۷و: same shape as .pb-image-list-captions above, one plain (non-per-language) link input
   instead of one caption input per language. */
.pb-image-list-link {
  display: flex;
  margin-top: 0.15rem;
}

.pb-image-list-link input {
  width: 100%;
  box-sizing: border-box;
  font-size: 0.7rem;
  padding: 0.15rem 0.3rem;
}

/* Session 75: per-tile "pick a gallery video" control inside the same strip item (VideoGrid/
   VideoCarousel), rendered by pagebuilder-image-list.js next to the URL box. */
.pb-image-list-video {
  display: flex;
  gap: 0.25rem;
  margin-top: 0.15rem;
}

.pb-image-list-video button {
  flex: 1 1 auto;
  font-size: 0.7rem;
  padding: 0.15rem 0.3rem;
}

.pb-image-list-video .js-image-list-video-clear {
  flex: 0 0 auto;
}

/* Session 7 ("safe delete everywhere") - same themed <dialog> approach as #media-library-modal,
   narrower since it only ever shows a confirm message or a short usage-report list. */
#safe-delete-modal {
  background: var(--pb-bg);
  color: var(--pb-fg);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius, 12px);
  box-shadow: var(--pb-shadow-lg, 0 4px 24px rgba(0, 0, 0, 0.35));
  padding: 0;
  width: 420px;
  max-width: calc(100vw - 2rem);
}

#safe-delete-modal::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.sd-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--pb-border);
}

.sd-modal-header .js-sd-close {
  border: none;
  background: none;
  font-size: 1.3rem;
  cursor: pointer;
  color: var(--pb-fg);
}

.sd-modal-body {
  padding: 1rem 1.5rem;
}

.js-sd-item-label {
  font-weight: 700;
  padding: 0.4rem 0.6rem;
  margin: 0 0 0.75rem;
  border-radius: var(--pb-radius-sm, 6px);
  background: color-mix(in srgb, var(--pb-primary) 10%, transparent);
}

.sd-usage-list {
  margin: 0.5rem 0 0;
  padding-inline-start: 1.25rem;
}

.sd-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--pb-border);
}

.js-sd-force {
  color: #b00020;
  border-color: #b00020;
}

/* Change session 9 بند ۲۶: PageBuilder Spacer block - height/background are inline styles built
   server-side from SpacerBlockConfig (_PageBlocks.cshtml), this rule just makes it a real box. */
.pb-spacer {
  display: block;
  width: 100%;
}

/* docs/4 "Stepper — نوار پیشرفت رنگی سفارش": 5-step colored order-progress bar, colors themselves are
   inline styles driven by admin-configurable SystemSettings (Views/Shared/_OrderStepper.cshtml) -
   this block only lays the steps out and styles the fixed parts (index circle, connecting line). */
.order-stepper {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin: 1rem 0;
  min-height: 3rem;
}

.order-stepper-step {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  padding: 0.5rem 0.25rem;
  border-radius: var(--pb-radius-sm, 0.5rem);
  color: #fff;
  text-align: center;
  transition: box-shadow 0.2s ease;
}

.order-stepper-index {
  font-weight: 700;
}

.order-stepper-label {
  font-size: 0.8rem;
}

.order-stepper-substeps {
  font-size: 0.7rem;
  opacity: 0.9;
}

.order-card {
  border: 1px solid var(--pb-border, #e5e7eb);
  border-radius: var(--pb-radius-sm, 0.5rem);
  padding: 1rem;
  margin-bottom: 1rem;
  transition: box-shadow 0.2s ease;
}

.order-card:hover {
  box-shadow: var(--pb-shadow);
}

.order-card-header {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
}

.production-line-card {
  border: 1px dashed var(--pb-border, #e5e7eb);
  border-radius: var(--pb-radius-sm, 0.5rem);
  padding: 0.75rem;
  margin: 0.75rem 0;
}

.returned-badge,
.cancelled-badge {
  color: #b91c1c;
  font-weight: 600;
}

/* Order-status redesign (client session 2026-09-13): page header, admin status panel, "این مرحله"
   block, per-line production bars under the Stepper, accordion tabs on the admin line card, and the
   timeline. Colors come from the theme tokens; the stepper's own three colors stay inline (admin
   setting-driven, see _OrderStepper.cshtml). Semantic ok/warn/bad tints are fixed, not the accent. */
.order-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin-bottom: 0.75rem;
}

.order-head-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.order-head-main h1 {
  margin: 0;
}

.order-head-date {
  color: var(--pb-fg-muted, #6b7280);
  font-size: 0.85rem;
}

.order-chip {
  display: inline-block;
  padding: 0.1rem 0.6rem;
  border: 1px solid var(--pb-border, #e5e7eb);
  border-radius: 999px;
  font-size: 0.8rem;
  line-height: 1.6;
  white-space: nowrap;
  vertical-align: middle;
}

.order-chip-status {
  border-color: var(--pb-primary, #ff6b00);
  color: var(--pb-primary, #ff6b00);
  font-weight: 700;
}

.order-chip-current {
  border-color: #ff6b00;
  color: #ff6b00;
  font-weight: 600;
}

.order-chip-muted {
  color: var(--pb-fg-muted, #6b7280);
}

.order-chip-ok {
  background: #eaf8ef;
  border-color: transparent;
  color: #1e7a3e;
}

.order-chip-bad {
  background: #fbefe8;
  border-color: transparent;
  color: #b4471b;
}

.order-chip-auto {
  border-style: dashed;
  color: var(--pb-fg-muted, #6b7280);
  font-size: 0.7rem;
}

.order-btn {
  display: inline-block;
  padding: 0.35rem 0.9rem;
  border: 1.5px solid var(--pb-fg, #1f2430);
  border-radius: var(--pb-radius-sm, 0.5rem);
  background: var(--pb-bg, #fff);
  color: var(--pb-fg, #1f2430);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
  text-decoration: none;
}

.order-btn-primary {
  background: var(--pb-fg, #1f2430);
  color: var(--pb-bg, #fff);
}

.order-btn-danger {
  border-color: #b4471b;
  color: #b4471b;
  background: transparent;
}

.order-btn[disabled],
.order-btn:disabled {
  border-color: var(--pb-border, #e5e7eb);
  background: transparent;
  color: var(--pb-fg-muted, #9aa1ae);
  cursor: not-allowed;
}

.order-kv {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.25rem 1rem;
  margin: 0;
  font-size: 0.95rem;
}

.order-kv dt {
  color: var(--pb-fg-muted, #6b7280);
}

.order-kv dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.order-code {
  font-family: ui-monospace, Consolas, monospace;
  font-size: 0.8rem;
  direction: ltr;
  unicode-bidi: embed;
}

.order-hint {
  margin: 0.25rem 0 0;
  font-size: 0.85rem;
  color: var(--pb-fg-muted, #6b7280);
}

.order-hint-warn {
  color: #8a5a00;
}

.order-warn {
  margin: 0.5rem 0 0;
  padding: 0.5rem 0.75rem;
  background: #fff4e5;
  border-radius: var(--pb-radius-sm, 0.5rem);
  color: #8a5a00;
  font-size: 0.9rem;
}

.order-inline-check {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.85rem;
}

.order-status-panel {
  border: 2px solid var(--pb-fg, #1f2430);
  border-radius: var(--pb-radius-sm, 0.5rem);
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
}

.order-status-panel h2 {
  margin: 0 0 0.5rem;
  font-size: 1rem;
}

.order-status-panel-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem 2rem;
}

.order-status-panel-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  min-width: 16rem;
}

.order-next-form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

.order-cancel > summary {
  list-style: none;
  cursor: pointer;
}

.order-cancel > summary::-webkit-details-marker {
  display: none;
}

.order-cancel form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
  margin-top: 0.5rem;
}

.order-stage {
  border: 2px solid var(--pb-fg, #1f2430);
  border-radius: var(--pb-radius-sm, 0.5rem);
  padding: 0.75rem 1rem;
  margin: 1rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  align-items: flex-start;
}

.order-stage h2 {
  margin: 0;
  font-size: 1rem;
}

.order-stage form,
.order-pay-final {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.order-return-form {
  flex-direction: column;
  align-items: flex-start;
}

.order-delivery-details,
.delivery-batches {
  width: 100%;
}

.production-line-card.is-cancelled {
  opacity: 0.7;
}

.production-line-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.production-line-head h3 {
  margin: 0;
  margin-inline-end: auto;
}

.production-line-advance {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  flex-basis: 100%;
}

.production-line-admin {
  margin-top: 0.75rem;
}

.order-tab {
  border: 1px solid var(--pb-border, #e5e7eb);
  border-radius: var(--pb-radius-sm, 0.5rem);
  padding: 0.35rem 0.75rem;
  margin: 0.35rem 0;
}

.order-tab > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.9rem;
}

.order-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}

.order-timeline-item {
  display: grid;
  grid-template-columns: 0.75rem 8.5rem 1fr;
  gap: 0.6rem;
  align-items: baseline;
  font-size: 0.9rem;
}

.order-timeline-dot {
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 50%;
  background: var(--pb-fg, #1f2430);
  align-self: center;
}

.order-timeline-item.is-production .order-timeline-dot {
  background: transparent;
  border: 2px solid #ff6b00;
}

.order-timeline-item.is-payment .order-timeline-dot {
  background: #1e7a3e;
}

.order-timeline-item.is-shipment .order-timeline-dot {
  background: #2b57c9;
}

.order-timeline-time {
  color: var(--pb-fg-muted, #6b7280);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

.order-timeline-note {
  color: var(--pb-fg-muted, #6b7280);
}

@media (max-width: 600px) {
  .order-timeline-item {
    grid-template-columns: 0.75rem 1fr;
  }

  .order-timeline-time {
    grid-column: 2;
  }
}

/* Change session 23 بند الف: floating "back to top" button - built from scratch, did not exist
   before this session. Hidden by default; back-to-top.js toggles .pb-back-to-top-visible once the
   page scrolls past the admin-configured threshold. Color comes from an inline style set by
   _Layout.cshtml (theme.BackToTopColorHex), not a CSS variable, matching the chat widget's own
   `--chat-widget-accent` precedent closely enough without adding a new global variable no other
   element needs. */
/* Change session 34 بند ب: sits on the OPPOSITE side from the floating chat bubble (.chat-widget,
   see _BackToTop.cshtml's doc comment for the side computation) instead of stacking above it on the
   same side - so it now shares the chat bubble's own vertical offset (inset-block-end: 1.5rem)
   instead of needing an extra 6rem of clearance above it. */
.pb-back-to-top {
  position: fixed;
  inset-block-end: 1.5rem;
  z-index: 900;
  width: 2.75rem;
  height: 2.75rem;
  border: none;
  border-radius: 50%;
  color: #fff;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--pb-shadow-lg, 0 2px 10px rgba(0, 0, 0, 0.25));
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.pb-back-to-top.pb-back-to-top-start {
  inset-inline-start: 1.5rem;
}

.pb-back-to-top.pb-back-to-top-end {
  inset-inline-end: 1.5rem;
}

.pb-back-to-top-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* Change session 23 بند ت: the contact page's PageBuilder blocks (above/below) render full-width
   like every other page block, but the fixed form itself must stay centered with a device-dependent
   minimum width - never the blocks around it. */
.pb-contact-form-wrap {
  display: flex;
  justify-content: center;
}

.pb-contact-form-wrap form {
  width: 100%;
  max-width: 100%;
}

@media (min-width: 900px) {
  .pb-contact-form-wrap form {
    width: 40%;
    min-width: 40%;
  }
}

/* Change session 38 (MASTER_CHANGES_PLAN.md بخش ۱۰.۲): new PageBuilder content blocks. */

/* بند ۱ - StatCounter؛ بازطراحی آیتم ۱۷د: "۱ تا ۶ شمارنده هم‌سطر در همه‌ی رسانه‌ها" - desktop/tablet
   always keep every item on one row (flex:1 each); the mobile-only row-wrap matrix below is the
   client's own confirmed exception. */
.pb-stat-counter-grid {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  gap: 1.5rem;
  text-align: center;
}

.pb-stat-counter-grid .pb-stat-counter-item {
  flex: 1 1 0;
  min-width: 0;
  /* border-box, not the default content-box: the Frame/Line divider styles below add real border+
     padding to this item - without border-box that extra width sits ON TOP of the flex-basis instead
     of inside it, which silently broke the mobile row-wrap math further down (an item with divider
     padding no longer fit the row its basis said it should) - caught live in the browser. */
  box-sizing: border-box;
}

.pb-stat-counter-icon {
  display: block;
  max-height: 40px;
  margin: 0 auto 0.5rem;
}

/* آیتم ۱۷د: fluid clamp()-based size (percent of viewport width, --pb-fluid-vw set inline per block). */
.pb-stat-counter-value {
  display: block;
  font-size: clamp(1.25rem, calc(var(--pb-fluid-vw, 5) * 1vw), 4.5rem);
  font-weight: bold;
  color: var(--pb-primary);
}

.pb-stat-counter-label {
  display: block;
  color: var(--pb-fg);
  opacity: 0.8;
}

/* آیتم ۱۷د: "برای هر شمارنده قاب یا خط جداکننده عمودی، با رنگ دلخواه" - one admin-wide choice, not
   per-item; Line uses a leading inline-start border on every item but the first (a true separator
   between items, not a frame). */
.pb-stat-counter-grid.pb-divider-frame .pb-stat-counter-item {
  border: 1px solid var(--pb-divider-color, currentColor);
  border-radius: var(--pb-radius, 12px);
  padding: 1rem;
}

.pb-stat-counter-grid.pb-divider-line .pb-stat-counter-item:not(:first-child) {
  border-inline-start: 1px solid var(--pb-divider-color, currentColor);
  padding-inline-start: 1.5rem;
}

/* آیتم ۱۷د: mobile-only row-wrap matrix - client's own confirmed exact spec: 1-3 items stay one row;
   4 becomes two rows of 2+2; 5/6 become two rows sized as if 3-per-row (5's shorter second row of 2
   centers itself via justify-content:center on the grid above, at the same per-item width/font-size
   as a full row - no separate rule needed for that "همان اندازه فونت" part since font-size is already
   one shared fluid value, never row-count-dependent). */
@media (max-width: 900px) {
  .pb-stat-counter-grid {
    flex-wrap: wrap;
  }

  /* flex-shrink:1 (not 0) + the gap subtracted out of each basis - with flex-shrink:0 the row's own
     gaps had no room to fit alongside 3 full 33.333%-wide items, so the 3rd item silently wrapped
     early (2-per-row instead of 3) - caught live in the browser, fixed together with the border-box
     change above (both were needed; the padding-outside-basis bug alone fully explained the wrap). */
  .pb-stat-counter-grid .pb-stat-counter-item {
    flex: 1 1 calc(100% / 3 - 1rem);
  }

  .pb-stat-counter-grid[data-counter-count="1"] .pb-stat-counter-item { flex-basis: 100%; }
  .pb-stat-counter-grid[data-counter-count="2"] .pb-stat-counter-item { flex-basis: calc(50% - 0.75rem); }
  .pb-stat-counter-grid[data-counter-count="4"] .pb-stat-counter-item { flex-basis: calc(50% - 0.75rem); }
}

/* بند ۲ - ImageMarquee "متن آزاد" source (formerly also Slideshow/ImageCarousel, retired
   MASTER_CHANGES_PLAN.md آیتم ۱۷ج): a plain HTML slide instead of an image, same track/speed/
   pause-on-hover infrastructure as every other slide source. */
.pb-media-text-slide {
  display: flex;
  align-items: center;
  padding: 0 1.5rem;
  white-space: nowrap;
  font-weight: 600;
}

/* Session 74: free-text slides carry no image, so they size to their own content instead of the
   carousel's image height (.pb-carousel-continuous .pb-media-slide's own var(--pb-carousel-height)). */
.pb-image-carousel.pb-carousel-text .pb-media-slide {
  height: auto;
}

/* بند ۴ - FeaturedTestimonials carousel display mode: same duplicate-once marquee technique as
   .pb-icon-carousel/.pb-image-marquee (pb-marquee-scroll keyframe defined above), deliberately not
   the Testimonials page's own JS overflow-detection/direction-aware carousel (change session 33). */
.pb-testimonials-carousel-wrapper {
  overflow: hidden;
}

.pb-testimonials-carousel {
  display: flex;
  gap: 1rem;
  width: max-content;
  animation: pb-marquee-scroll 25s linear infinite;
}

.pb-testimonials-carousel .pb-card {
  flex: 0 0 260px;
}

.pb-testimonials-carousel-wrapper:hover .pb-testimonials-carousel {
  animation-play-state: paused;
}

/* بند ۵ - "1 big + several small" layout, shared by FeaturedCollections/FeaturedModels/LatestBlogPosts'
   own Asymmetric display mode - deliberately not .cat-grid (that class is shared with plain Catalog
   listing pages this per-block layout choice must never affect). */
.pb-grid-asymmetric {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
}

.pb-grid-asymmetric > :first-child {
  grid-column: span 2;
  grid-row: span 2;
}

@media (max-width: 700px) {
  .pb-grid-asymmetric {
    grid-template-columns: repeat(2, 1fr);
  }

  .pb-grid-asymmetric > :first-child {
    grid-column: span 2;
    grid-row: span 1;
  }
}

/* ---------- Order console (order-UX redesign phase 2, client decisions 2026-09-21) ----------
   Areas/Admin/Views/Orders/Details.cshtml: an operational header bar, topical tabs in the main
   column and a sticky side column. Reuses the .order-chip/.order-btn/.order-kv/.order-timeline
   vocabulary of the public order page; everything here is scoped to .pb-admin-body. */
.pb-admin-body .oc-head { display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; align-items: flex-start; justify-content: space-between; margin-bottom: 1rem; }
.pb-admin-body .oc-head-main { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.pb-admin-body .oc-head-main h1 { margin: 0; font-size: 1.4rem; }
.pb-admin-body .oc-back { display: inline-flex; align-items: center; gap: 0.2rem; font-size: 0.85rem; text-decoration: none; opacity: 0.8; margin-inline-end: 0.5rem; }
.pb-admin-body .oc-head-date { font-size: 0.85rem; opacity: 0.7; }
.pb-admin-body .oc-head-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: flex-start; }
.pb-admin-body .oc-next-form { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; max-width: 34rem; }
.pb-admin-body .oc-next-form input[name="note"] { flex: 1 1 10rem; min-width: 0; }
.pb-admin-body .oc-grid { display: grid; grid-template-columns: minmax(0, 1fr) 18rem; gap: 1.25rem; align-items: start; margin-top: 1rem; }
.pb-admin-body .oc-side { position: sticky; top: calc(var(--pb-admin-topbar-h, 56px) + 0.75rem); display: flex; flex-direction: column; gap: 0.75rem; }
.pb-admin-body .oc-card { border: 1px solid var(--pb-border); border-radius: var(--pb-radius-sm, 8px); padding: 0.85rem 1rem; background: var(--pb-bg); }
.pb-admin-body .oc-card h3 { margin: 0 0 0.5rem; font-size: 0.95rem; }
.pb-admin-body .oc-card .order-kv { margin: 0; }
.pb-admin-body .oc-card .order-btn { margin-top: 0.5rem; display: inline-block; }
.pb-admin-body .oc-links { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0.4rem 0 0; font-size: 0.9rem; }
.pb-admin-body .oc-progress { height: 8px; border-radius: 4px; background: color-mix(in srgb, var(--pb-primary) 15%, transparent); overflow: hidden; margin-bottom: 0.5rem; }
.pb-admin-body .oc-progress span { display: block; height: 100%; background: var(--pb-primary); border-radius: 4px; }
.pb-admin-body .oc-tabs { display: flex; flex-wrap: wrap; gap: 0.25rem; border-bottom: 2px solid var(--pb-border); margin-bottom: 1rem; }
.pb-admin-body .oc-tabs button, .pb-admin-body .oc-subtabs button { border: none; background: none; font: inherit; color: inherit; cursor: pointer; padding: 0.55rem 0.9rem; border-radius: var(--pb-radius-sm, 8px) var(--pb-radius-sm, 8px) 0 0; margin-bottom: -2px; border-bottom: 2px solid transparent; display: inline-flex; align-items: center; gap: 0.35rem; }
.pb-admin-body .oc-tabs button.is-active { border-bottom-color: var(--pb-primary); color: var(--pb-primary); font-weight: 700; }
/* Security log layer 5: the same tab bar as links between pages (security report: events / admin actions). */
.pb-admin-body .pb-page-tabs a { padding: 0.55rem 0.9rem; margin-bottom: -2px; border-bottom: 2px solid transparent; text-decoration: none; color: inherit; }
.pb-admin-body .pb-page-tabs a[aria-current="page"] { border-bottom-color: var(--pb-primary); color: var(--pb-primary); font-weight: 700; }
.pb-admin-body .oc-tabs button:hover, .pb-admin-body .oc-subtabs button:hover { background: color-mix(in srgb, var(--pb-primary) 8%, transparent); }
.pb-admin-body .oc-tab-count { font-size: 0.72rem; padding: 0 0.45rem; border-radius: 999px; background: color-mix(in srgb, var(--pb-primary) 15%, transparent); }
.pb-admin-body .oc-tab-dot { width: 8px; height: 8px; border-radius: 50%; background: #d9534f; }
.pb-admin-body .oc-subtabs { display: flex; flex-wrap: wrap; gap: 0.25rem; border-bottom: 1px solid var(--pb-border); margin-bottom: 0.75rem; }
.pb-admin-body .oc-subtabs button.is-active { border-bottom-color: var(--pb-primary); font-weight: 700; }
.pb-admin-body .oc-panel > h3, .pb-admin-body .oc-two > div > h3 { margin: 1rem 0 0.5rem; font-size: 1rem; }
.pb-admin-body .oc-two { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1rem; align-items: start; }
.pb-admin-body .oc-table { width: 100%; border-collapse: collapse; }
.pb-admin-body .oc-table th, .pb-admin-body .oc-table td { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--pb-border); vertical-align: top; text-align: start; }
.pb-admin-body .oc-table th { font-size: 0.8rem; opacity: 0.75; }
/* Client 2026-11-10: the one price/colour form (_ItemPricing) - full width under the "کار بعدی" card's text, no scroll. */
.pb-admin-body .oc-pricing { flex: 1 1 100%; min-width: 0; }
.pb-admin-body .oc-pricing .oc-table td { vertical-align: middle; }
.pb-admin-body .oc-pricing select, .pb-admin-body .oc-pricing input[type="number"] { width: 100%; min-width: 0; box-sizing: border-box; }
.pb-admin-body .oc-pricing .oc-table th, .pb-admin-body .oc-pricing .oc-table td { padding-inline: 0.35rem; }
.pb-admin-body .oc-pricing-foot { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: flex-end; gap: 0.75rem; margin-top: 0.75rem; }
.pb-admin-body .oc-pricing-foot .oc-field { flex: 1 1 20rem; }
.pb-admin-body details.oc-process > summary { cursor: pointer; display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.pb-admin-body .oc-nowrap { white-space: nowrap; }
.pb-admin-body .oc-line-row.is-cancelled { opacity: 0.55; }
.pb-admin-body .oc-line-actions { min-width: 16rem; }
.pb-admin-body .oc-line-actions .production-line-advance { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; margin-bottom: 0.35rem; }
.pb-admin-body .oc-line-actions .production-line-advance input[name="note"] { flex: 1 1 8rem; min-width: 0; }
.pb-admin-body .oc-ghost { border: 1px solid var(--pb-border); background: none; color: inherit; font: inherit; border-radius: var(--pb-radius-sm, 8px); padding: 0.3rem 0.7rem; cursor: pointer; }
.pb-admin-body .oc-ghost.is-active { background: color-mix(in srgb, var(--pb-primary) 12%, transparent); }
/* Item 39-18 ف۷: a form's fields shown read-only to the view-only order row (no frame, values stay readable). */
.pb-admin-body fieldset.oc-readonly-fields { border: 0; padding: 0; margin: 0; min-width: 0; }
.pb-admin-body fieldset.oc-readonly-fields[disabled] :is(input, select, textarea) { opacity: 1; color: inherit; cursor: default; }
.pb-admin-body .oc-readonly-notice { margin: 0.4rem 0 0.8rem; }
.pb-admin-body .oc-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0.5rem 0.75rem; margin-bottom: 0.6rem; }
.pb-admin-body .oc-fields label { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.85rem; }
.pb-admin-body .oc-fields label > span { opacity: 0.8; }
.pb-admin-body .oc-fields-wide { grid-column: 1 / -1; }
.pb-admin-body .oc-inline-edit > summary { cursor: pointer; font-size: 0.85rem; display: inline-flex; align-items: center; gap: 0.25rem; }
.pb-admin-body .oc-inline-edit .pb-form-row { margin: 0.4rem 0; }
.pb-admin-body .oc-list { list-style: none; margin: 0 0 0.6rem; padding: 0; display: grid; gap: 0.3rem; }
.pb-admin-body .oc-list li { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.pb-admin-body .oc-lead { margin: 0 0 0.75rem; }
.pb-admin-body .oc-kv-inline { display: flex; flex-wrap: wrap; gap: 0.3rem 1.5rem; margin: 0 0 1rem; }
.pb-admin-body .oc-kv-inline dt { font-size: 0.8rem; opacity: 0.75; }
.pb-admin-body .oc-kv-inline dd { margin: 0 0 0.3rem; }
.pb-admin-body .oc-line-returns { margin-top: 1rem; }
.pb-admin-body .oc-line-returns.is-cancelled { opacity: 0.6; }
/* Client decision 1A/2A/3A (2026-09-24, wireframes artifact 2kupPh75ZkvHJcBd3mTJcW): the console's
   header is identity only, the single decision sits in .oc-next, each production line opens an
   off-canvas .oc-drawer-panel, and one .oc-pay-form replaces the six payment forms. */
.pb-admin-body .oc-head-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.pb-admin-body .oc-head-meta { margin: 0.35rem 0 0; font-size: 0.85rem; opacity: 0.75; }

/* "···" menu: everything that is not the next step (invoice, CRM, chat, cancel). */
.pb-admin-body .oc-menu { position: relative; }
.pb-admin-body .oc-menu > summary { list-style: none; cursor: pointer; border: 1px solid var(--pb-border); border-radius: var(--pb-radius-sm, 8px); padding: 0.25rem 0.75rem; line-height: 1.2; font-size: 1.1rem; letter-spacing: 0.1em; }
.pb-admin-body .oc-menu > summary::-webkit-details-marker { display: none; }
.pb-admin-body .oc-menu > summary:hover { background: color-mix(in srgb, var(--pb-primary) 8%, transparent); }
.pb-admin-body .oc-menu-items { position: absolute; inset-inline-end: 0; top: calc(100% + 0.35rem); z-index: 5; min-width: 13rem; display: flex; flex-direction: column; gap: 0.15rem; padding: 0.4rem; background: var(--pb-bg); border: 1px solid var(--pb-border); border-radius: var(--pb-radius-sm, 8px); box-shadow: var(--pb-shadow-lg, 0 12px 30px rgb(0 0 0 / 0.15)); }
.pb-admin-body .oc-menu-items a { padding: 0.35rem 0.5rem; border-radius: var(--pb-radius-sm, 8px); text-decoration: none; font-size: 0.9rem; }
.pb-admin-body .oc-menu-items a:hover { background: color-mix(in srgb, var(--pb-primary) 8%, transparent); }
.pb-admin-body .oc-menu-danger { color: #b23a3a; }

/* The one decision card. */
.pb-admin-body .oc-next { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem 1.5rem; margin: 1rem 0 0; padding: 0.9rem 1.1rem; border: 2px solid var(--pb-primary); border-radius: var(--pb-radius, 12px); background: color-mix(in srgb, var(--pb-primary) 5%, transparent); }
.pb-admin-body .oc-next-text { flex: 1 1 20rem; }
.pb-admin-body .oc-next h2 { margin: 0; font-size: 1.05rem; }
.pb-admin-body .oc-next .order-warn, .pb-admin-body .oc-next .order-hint { margin: 0.35rem 0 0; }
.pb-admin-body .oc-next-actions { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0.5rem; }
/* align-items:flex-start, not the flex default stretch - that is what blew the old note box up to 178px tall. */
.pb-admin-body .oc-next-form { display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem; }
.pb-admin-body .oc-next-more > summary { cursor: pointer; font-size: 0.82rem; opacity: 0.8; }
.pb-admin-body .oc-next-more-body { display: grid; gap: 0.4rem; margin-top: 0.4rem; min-width: 16rem; }
.pb-admin-body .oc-field { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.85rem; }
.pb-admin-body .oc-field > span { opacity: 0.8; }

/* Off-canvas line drawer (tabs.js data-drawer-toggle + data-drawer-group). */
.pb-admin-body .oc-drawer-panel { position: fixed; inset-block: var(--pb-admin-topbar-h, 56px) 0; inset-inline-end: 0; z-index: 40; width: min(34rem, 100%); display: flex; flex-direction: column; background: var(--pb-bg); border-inline-start: 1px solid var(--pb-border); box-shadow: -8px 0 28px rgb(0 0 0 / 0.18); }
.pb-admin-body .oc-drawer-panel[hidden] { display: none; }
.pb-admin-body .oc-drawer-head { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.75rem 1rem; border-bottom: 1px solid var(--pb-border); }
.pb-admin-body .oc-drawer-close { border: none; background: none; color: inherit; cursor: pointer; padding: 0.25rem; line-height: 1; }
.pb-admin-body .oc-drawer-body { padding: 0.9rem 1rem 2rem; overflow-y: auto; flex: 1; }
.pb-admin-body .oc-drawer-actions { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0.5rem 0.75rem; padding-bottom: 0.9rem; margin-bottom: 0.9rem; border-bottom: 1px solid var(--pb-border); }
.pb-admin-body .oc-drawer-actions .production-line-advance { display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem; }
.pb-admin-body .oc-drawer-actions .wb-assign { display: flex; align-items: center; gap: 0.35rem; }

/* One payment form (3A): the method's own fields sit in the same grid, the others are hidden+disabled. */
.pb-admin-body .oc-pay-form { margin-bottom: 1.25rem; }
.pb-admin-body .oc-pay-form .oc-fields label[hidden] { display: none; }

/* Orders list (1A): six columns, the secondary facts as a muted second line. */
.pb-admin-body .ol-meta { opacity: 0.7; }

@media (max-width: 1100px) {
  .pb-admin-body .oc-grid { grid-template-columns: 1fr; }
  .pb-admin-body .oc-side { position: static; }
}

@media (max-width: 700px) {
  .pb-admin-body .oc-drawer-panel { width: 100%; }
}

/* Order-UX redesign phase 1 (client decisions 2026-09-21, wireframes W1-W3): customer order page and
   "سفارش‌های من". Reuses the order-chip/order-btn/order-kv/order-timeline vocabulary above; the tab
   markup is the same data-tabs contract as the admin console (wwwroot/js/tabs.js). */
.order-page { position: relative; }

/* W1: filter chips + card footer */
.order-filter-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem; }
.order-filter-chip { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.3rem 0.85rem; border: 1px solid var(--pb-border, #e5e7eb); border-radius: 999px; font-size: 0.85rem; color: inherit; text-decoration: none; }
.order-filter-chip.is-active { border-color: var(--pb-primary, #ff6b00); color: var(--pb-primary, #ff6b00); font-weight: 700; }
.order-filter-count { font-size: 0.72rem; padding: 0 0.45rem; border-radius: 999px; background: color-mix(in srgb, var(--pb-primary, #ff6b00) 14%, transparent); }
.order-card-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.order-card-number { font-weight: 700; color: inherit; text-decoration: none; }
.order-card-footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1rem; margin-top: 0.75rem; }
.order-card-meta { color: var(--pb-fg-muted, #6b7280); font-size: 0.85rem; margin-inline-end: auto; }
.order-chip-new { display: inline-flex; align-items: center; gap: 0.3rem; border-color: transparent; background: color-mix(in srgb, var(--pb-primary, #ff6b00) 14%, transparent); color: var(--pb-primary, #ff6b00); text-decoration: none; font-weight: 600; }


/* W2: "اقدام شما" card */
.order-action { border: 2px solid var(--pb-fg, #1f2430); border-radius: var(--pb-radius-sm, 0.5rem); padding: 0.9rem 1.1rem; margin: 1rem 0; display: grid; gap: 0.75rem; }
.order-action h2 { margin: 0; font-size: 1rem; }
.order-action-body { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem 1.5rem; }
.order-action-text { flex: 1 1 18rem; }
.order-action-text p { margin: 0 0 0.35rem; }
.order-action-text p:last-child { margin-bottom: 0; }
.order-action-primary form { display: inline; }
.order-action-primary .order-btn-primary { font-size: 1rem; padding: 0.55rem 1.3rem; }
/* Client decision 4A (2026-09-24): the secondary action row is gone - references moved into the
   "سفارش من" tab, and the two requests that change the order live in this quiet menu, so the card
   keeps exactly one primary button. */
.order-menu { position: relative; align-self: start; }
.order-menu > summary { list-style: none; cursor: pointer; border: 1px solid var(--pb-border, #e5e7eb); border-radius: var(--pb-radius-sm, 0.5rem); padding: 0.2rem 0.7rem; font-size: 1.1rem; line-height: 1.3; letter-spacing: 0.1em; width: max-content; }
.order-menu > summary::-webkit-details-marker { display: none; }
.order-menu-items { position: absolute; inset-inline-end: 0; top: calc(100% + 0.35rem); z-index: 5; min-width: 15rem; display: grid; gap: 0.35rem; padding: 0.6rem; background: var(--pb-bg, #fff); border: 1px solid var(--pb-border, #e5e7eb); border-radius: var(--pb-radius-sm, 0.5rem); box-shadow: var(--pb-shadow-lg, 0 12px 30px rgb(0 0 0 / 0.15)); }
.order-menu-items .order-cancel > summary { cursor: pointer; font-size: 0.9rem; }
.order-link-danger { color: #b4471b; cursor: pointer; font-size: 0.9rem; text-decoration: underline; list-style: none; }
.order-link-danger::-webkit-details-marker { display: none; }
.order-fold > summary { cursor: pointer; font-weight: 600; }
.order-fold[open] > summary { margin-bottom: 0.5rem; }
.order-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0.5rem 0.75rem; align-items: end; }
.order-fields label { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.85rem; }
.order-fields label > span { opacity: 0.8; }
.order-fields-row { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; font-size: 0.85rem; }

/* W2: topical tabs */
.order-tabs { margin-top: 1.25rem; }
.order-tabs-nav { display: flex; flex-wrap: wrap; gap: 0.25rem; border-bottom: 2px solid var(--pb-border, #e5e7eb); margin-bottom: 1rem; overflow-x: auto; }
.order-tabs-nav button { border: none; background: none; font: inherit; color: inherit; cursor: pointer; padding: 0.55rem 0.9rem; margin-bottom: -2px; border-bottom: 2px solid transparent; display: inline-flex; align-items: center; gap: 0.35rem; white-space: nowrap; }
.order-tabs-nav button.is-active { border-bottom-color: var(--pb-primary, #ff6b00); color: var(--pb-primary, #ff6b00); font-weight: 700; }
.order-tabs-nav button:hover { background: color-mix(in srgb, var(--pb-primary, #ff6b00) 8%, transparent); }
.order-tab-count { font-size: 0.72rem; padding: 0 0.45rem; border-radius: 999px; background: color-mix(in srgb, var(--pb-primary, #ff6b00) 15%, transparent); }
.order-tab-dot { width: 8px; height: 8px; border-radius: 50%; background: #d9534f; }
.order-tab-panel { display: grid; gap: 0.9rem; }
/* Bug found while verifying the 4A pass (2026-09-24): this display:grid beat the browser's own
   [hidden]{display:none}, so every tab panel of the customer order page rendered at once and the tabs
   only moved the highlight - the page was the whole order stacked vertically. */
.order-tab-panel[hidden] { display: none; }
.order-tab-panel > h3 { margin: 0.5rem 0 0; font-size: 1rem; }
.order-table-wrap { overflow-x: auto; }
.order-links { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }
.production-line-card h3 { display: inline-flex; align-items: center; gap: 0.35rem; }
.production-line-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; margin-top: 0.5rem; }
.production-line-gallery .gallery { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.production-line-gallery img, .production-line-gallery video { border-radius: var(--pb-radius-sm, 0.5rem); object-fit: cover; }

/* decision 3: floating tailor chat - button at the inline end (the public chat widget sits at the
   inline start), panel slides in from the same edge; full-screen on phones. */
.tailor-chat-toggle { position: fixed; bottom: 1.5rem; inset-inline-end: 1.5rem; z-index: 1000; display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.7rem 1.1rem; border: none; border-radius: 999px; background: var(--pb-primary, #7a1f3d); color: #fff; font: inherit; font-weight: 600; cursor: pointer; box-shadow: var(--pb-shadow-lg, 0 2px 10px rgba(0, 0, 0, 0.3)); }
.tailor-chat-toggle.is-active { display: none; }
.tailor-chat-dot { width: 10px; height: 10px; border-radius: 50%; background: #d9534f; box-shadow: 0 0 0 2px #fff; }
.tailor-chat-panel { position: fixed; top: 0; bottom: 0; inset-inline-end: 0; width: 24rem; max-width: 100vw; z-index: 1001; display: flex; flex-direction: column; background: var(--pb-bg, #fff); color: var(--pb-fg, #1a1a1a); border-inline-start: 1px solid var(--pb-border, #ddd); box-shadow: var(--pb-shadow-lg, 0 4px 20px rgba(0, 0, 0, 0.3)); animation: tailor-chat-slide 0.18s ease-out; }
.tailor-chat-panel[hidden] { display: none; }
.tailor-chat-panel .chat-messages { flex: 1 1 auto; height: auto; max-height: none; }
.tailor-chat-fallback { padding: 1rem; display: grid; gap: 0.75rem; justify-items: start; }
@keyframes tailor-chat-slide { from { transform: translateX(var(--pb-slide-from, 100%)); opacity: 0.6; } to { transform: none; opacity: 1; } }
[dir="rtl"] .tailor-chat-panel { --pb-slide-from: -100%; }

/* W3: sticky bottom action bar on phones */
.order-action-bar { display: none; }
@media (max-width: 767px) {
  .order-page { padding-bottom: 5rem; }
  .order-action-bar { position: fixed; bottom: 0; inset-inline: 0; z-index: 999; display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.6rem 1rem; padding-bottom: calc(0.6rem + env(safe-area-inset-bottom, 0px)); background: var(--pb-bg, #fff); border-top: 1px solid var(--pb-border, #e5e7eb); box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.08); }
  .order-action-bar-status { font-size: 0.85rem; color: var(--pb-fg-muted, #6b7280); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .order-action-bar form { display: inline; }
  .order-action-bar .order-btn { white-space: nowrap; }
  .tailor-chat-toggle { bottom: 4.75rem; }
  .tailor-chat-toggle span { display: none; }
  .tailor-chat-toggle { padding: 0.85rem; }
  .tailor-chat-panel { width: 100vw; border: none; }
}

/* Order-UX redesign phase 3 (W6): workshop board - one column per production sub-stage, cards with a
   drawer. Reuses the console's oc-subtabs/oc-fields/oc-ghost and the site's order-chip vocabulary. */
.pb-admin-body .wb-board { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(17rem, 1fr); gap: 0.75rem; overflow-x: auto; align-items: start; padding-bottom: 0.5rem; }
.pb-admin-body .wb-column { background: color-mix(in srgb, var(--pb-fg, #1a1a1a) 4%, transparent); border-radius: var(--pb-radius-sm, 8px); padding: 0.5rem; display: grid; gap: 0.5rem; align-content: start; min-height: 8rem; }
.pb-admin-body .wb-column-head { display: flex; align-items: center; gap: 0.4rem; margin: 0.15rem 0.25rem 0.35rem; font-size: 0.95rem; }
.pb-admin-body .wb-card { background: var(--pb-bg, #fff); border: 1px solid var(--pb-border, #e5e7eb); border-radius: var(--pb-radius-sm, 8px); display: grid; }
.pb-admin-body .wb-card.is-overdue { border-color: #d9534f; box-shadow: inset 4px 0 0 #d9534f; }
.pb-admin-body .wb-card.is-open { border-color: var(--pb-primary); }
.pb-admin-body .wb-card-main { display: grid; gap: 0.3rem; text-align: start; border: none; background: none; font: inherit; color: inherit; cursor: pointer; padding: 0.6rem 0.7rem 0.3rem; }
.pb-admin-body .wb-card-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; }
.pb-admin-body .wb-card-order { font-size: 0.75rem; opacity: 0.7; }
.pb-admin-body .wb-card-meta { font-size: 0.82rem; opacity: 0.85; display: flex; flex-wrap: wrap; gap: 0.5rem; }
.pb-admin-body .wb-due { display: inline-flex; align-items: center; gap: 0.2rem; }
.pb-admin-body .wb-card.is-overdue .wb-due { color: #d9534f; font-weight: 700; }
.pb-admin-body .wb-card-badges { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.pb-admin-body .wb-card-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; padding: 0.3rem 0.7rem 0.6rem; }
.pb-admin-body .wb-advance button { font-size: 0.8rem; padding: 0.25rem 0.6rem; }
.pb-admin-body .wb-assign { display: inline-flex; align-items: center; gap: 0.25rem; }
.pb-admin-body .wb-assign select { max-width: 11rem; font-size: 0.8rem; padding: 0.2rem 0.4rem; }
.pb-admin-body .wb-drawer { border-top: 1px solid var(--pb-border, #e5e7eb); padding: 0.6rem 0.7rem; background: color-mix(in srgb, var(--pb-primary) 4%, transparent); }
.pb-admin-body .wb-drawer[hidden] { display: none; }
.pb-admin-body .wb-drawer .order-kv { font-size: 0.85rem; }
.pb-admin-body .wb-measure-head { margin: 0.5rem 0 0.2rem; font-size: 0.85rem; opacity: 0.8; }
.pb-admin-body .wb-measures { grid-template-columns: max-content 1fr max-content 1fr; }
.pb-admin-body .wb-chat { display: flex; flex-direction: column; height: 18rem; border: 1px solid var(--pb-border, #e5e7eb); border-radius: var(--pb-radius-sm, 8px); background: var(--pb-bg, #fff); }
.pb-admin-body .wb-drawer-foot { margin: 0.6rem 0 0; font-size: 0.85rem; }
.pb-admin-body .oc-table .wb-assign select { max-width: 9rem; }
@media (max-width: 767px) {
  .pb-admin-body .wb-board { grid-auto-flow: row; grid-auto-columns: auto; }
}

/* Order-UX redesign phase 4: /admin/orders operational list (W5) and the store-order wizard (W7). */
.pb-admin-body .ol-table small { opacity: 0.75; }
.pb-admin-body .ol-table tr.ol-debt > td:first-child { box-shadow: inset 4px 0 0 #e0a100; }
.pb-admin-body .ol-table tr.ol-overdue > td:first-child { box-shadow: inset 4px 0 0 #d9534f; }
.pb-admin-body .ol-table .order-chip { text-decoration: none; }
.pb-admin-body .sw-steps { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem; }
.pb-admin-body .sw-steps button { display: inline-flex; align-items: center; gap: 0.4rem; border: 1px solid var(--pb-border); background: none; font: inherit; color: inherit; padding: 0.45rem 0.9rem; border-radius: 999px; cursor: pointer; }
.pb-admin-body .sw-steps button.is-active { border-color: var(--pb-primary); color: var(--pb-primary); font-weight: 700; }
.pb-admin-body .sw-steps button[disabled] { opacity: 0.5; cursor: not-allowed; }
.pb-admin-body .sw-step-no { width: 1.4rem; height: 1.4rem; border-radius: 50%; display: inline-grid; place-items: center; font-size: 0.75rem; background: color-mix(in srgb, var(--pb-primary) 15%, transparent); }
.pb-admin-body .sw-selected { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin: 0; }
.pb-admin-body .sw-results { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.4rem; }
.pb-admin-body .sw-results li { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.4rem 0.6rem; border: 1px solid var(--pb-border); border-radius: var(--pb-radius-sm, 8px); }
.pb-admin-body .sw-two { display: grid; grid-template-columns: minmax(0, 1fr) 18rem; gap: 1rem; align-items: start; }
.pb-admin-body .sw-summary { position: sticky; top: 1rem; }
.pb-admin-body .sw-nav { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.5rem; margin-top: 0.75rem; }
.pb-admin-body .sw-amount { display: flex; gap: 0.35rem; align-items: center; }
.pb-admin-body .sw-form .oc-table select, .pb-admin-body .sw-form .oc-table input { max-width: 100%; }
.pb-admin-body .sw-form .sw-qty { width: 4.5rem; }
.pb-admin-body .sw-form .sw-agreed { width: 9rem; }
@media (max-width: 900px) {
  .pb-admin-body .sw-two { grid-template-columns: 1fr; }
  .pb-admin-body .sw-summary { position: static; }
}

/* Order-UX redesign phase 5: a form being saved in place (wwwroot/js/inline-save.js). */
.pb-admin-body form.is-saving { opacity: 0.6; pointer-events: none; transition: opacity 0.15s ease; }

/* Measurement form guides (session 74 image, client request 2026-09-30 video). Both were rendered
   with no size rule at all, so a full-size guide photo or clip could dwarf the field it explains. */
.measurement-guide-image,
.measurement-guide-video {
  display: block;
  max-width: 100%;
  width: 320px;
  height: auto;
  margin: 0.5rem 0;
  border-radius: 8px;
}

/* Header submenu (client request 2026-09-30): one level deep, header only. The panel is absolutely
   positioned on pointer/desktop widths and falls back to an inline block inside the mobile hamburger
   panel, where absolute positioning would push it off-screen. */
.pb-menu-parent {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-inline-end: var(--pb-menu-gap, 0.75rem);
}

/* Client request 2026-10-19: the item gap belongs after the arrow, not between the label and its arrow. */
.pb-main-menu .pb-menu-parent-link {
  margin-inline-end: 0;
}

.pb-submenu-toggle {
  background: none;
  border: 0;
  padding: 0 0.15rem;
  cursor: pointer;
  /* Client request 2026-10-19: same colour as the menu links (it used to inherit the page text colour,
     invisible on a dark solid header). */
  color: var(--pb-menu-color, inherit);
  display: inline-flex;
  align-items: center;
  transition: transform 0.2s ease;
}

/* Client request 2026-10-19: a bold arrow (the shared icon's 1.5 stroke was too thin at 14px). */
.pb-submenu-toggle .pb-icon {
  stroke-width: 3;
}

.pb-menu-parent.pb-submenu-open .pb-submenu-toggle {
  transform: rotate(180deg);
}

.pb-submenu {
  display: none;
  position: absolute;
  top: 100%;
  inset-inline-start: 0;
  z-index: 40;
  min-width: 11rem;
  padding: 0.5rem 0;
  background: var(--pb-body-bg, #fff);
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.pb-menu-parent.pb-submenu-open .pb-submenu {
  display: block;
}

.pb-submenu a {
  display: block;
  margin-inline-end: 0;
  padding: 0.4rem 1rem;
  white-space: nowrap;
}

/* Client request 2026-10-19 (collections submenu): the desktop dropdown has the page background, so its
   links take the page text colour instead of the bar's menu colour (light-on-dark menu text was
   unreadable on the white panel, transparent-hero state included). */
@media (min-width: 901px) {
  .pb-site-header .pb-main-menu .pb-submenu a.pb-submenu-link {
    color: var(--pb-text-primary-color, #1a1a1a);
  }

  .pb-site-header .pb-main-menu .pb-submenu a.pb-submenu-link:hover,
  .pb-site-header .pb-main-menu .pb-submenu a.pb-submenu-link:focus-visible {
    color: var(--pb-primary);
  }
}

/* Inside the mobile hamburger panel the dropdown opens in flow, so a long submenu scrolls the panel
   instead of hanging off the viewport edge. */
.pb-mobile-menu-panel.pb-open .pb-submenu {
  position: static;
  background: none;
  box-shadow: none;
  border: 0;
  min-width: 0;
  padding-inline-start: 1rem;
}

.pb-menu-child-marker {
  color: var(--pb-muted, #888);
  margin-inline-end: 0.35rem;
}

/* Admin list filter bar (client request 2026-09-30) - sits between the page title and the table. */
.pb-admin-filter-bar {
  margin-block-end: 1rem;
  padding: 0.75rem 1rem;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.02);
}

.pb-admin-filter-bar .pb-form-row {
  align-items: end;
}

.pb-admin-filter-reset {
  margin-inline-start: 0.75rem;
  font-size: 0.9em;
}

.pb-dark .pb-admin-filter-bar {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.04);
}

/* Client request 2026-10-09 (form-validation.js): "*" before every required field's label, same colour as
   the label; red only for a field a failed submit left invalid, with the message under it. */
.pb-req-star {
  color: inherit;
  margin-inline-end: 0.25em;
  font-weight: bold;
}

.pb-req-star.pb-req-star--error,
.pb-field-error {
  color: #c62828;
}

html.pb-dark .pb-req-star.pb-req-star--error,
html.pb-dark .pb-field-error {
  color: #ef9a9a;
}

.pb-field-error {
  display: block;
  font-size: 0.85em;
  margin-top: 0.2em;
}

[aria-invalid="true"] {
  border-color: #c62828 !important;
}

[aria-invalid="true"]::placeholder {
  color: #c62828;
  opacity: 1;
}

html.pb-dark [aria-invalid="true"]::placeholder {
  color: #ef9a9a;
}

/* a required select with no title: its empty first option carries the star, red after a failed submit */
select[aria-invalid="true"] {
  color: #c62828;
}

/* Queue item 30 P2: "سفارشی / آماده" tabs above every model list - ready-made and custom never share a list. */
.cat-kind-tabs { display: flex; gap: 0.25rem; border-bottom: 1px solid color-mix(in srgb, var(--pb-fg, #1a1a1a) 15%, transparent); margin: 0 0 1rem; flex-wrap: wrap; }
.cat-kind-tab { padding: 0.55rem 1rem; text-decoration: none; color: var(--pb-fg, #1a1a1a); border-bottom: 3px solid transparent; margin-bottom: -1px; }
.cat-kind-tab.is-active { border-bottom-color: var(--pb-primary, #ff6b00); color: var(--pb-primary, #ff6b00); font-weight: 700; }
.cat-kind-tab:hover, .cat-kind-tab:focus-visible { color: var(--pb-primary, #ff6b00); }
.cat-kind-count { opacity: 0.7; font-weight: 400; }

/* MASTER_CHANGES_PLAN.md item 39-5 / 39-6 (ف۴): fitting appointments (console, workshop board, customer order page) and the
   estimated delivery date. */
.oc-fitting, .order-fitting { border: 1px solid color-mix(in srgb, var(--pb-fg, #1a1a1a) 12%, transparent); border-radius: 8px; padding: 0.6rem 0.8rem; margin: 0 0 0.6rem; }
.oc-fitting.is-open, .order-fitting.is-open { border-color: color-mix(in srgb, var(--pb-primary, #ff6b00) 45%, transparent); }
.oc-fitting-head, .order-fitting-head { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin: 0 0 0.3rem; }
.oc-fitting-pieces, .order-fitting-pieces { list-style: none; margin: 0.3rem 0; padding: 0; display: grid; gap: 0.35rem; }
.oc-fitting-pieces > li { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.oc-fitting-actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }
.oc-fitting-actions form { margin: 0; }
.oc-fitting-candidates { border: 0; padding: 0; margin: 0 0 0.5rem; display: grid; gap: 0.25rem; }
.oc-fitting-candidates > legend { font-weight: 600; margin-bottom: 0.25rem; }
.order-estimated-delivery { display: flex; align-items: center; gap: 0.35rem; margin: 0.5rem 0 0; }

/* MASTER_CHANGES_PLAN.md item 39-12 / 39-18 ف۶ (د): the refund IBAN fields and the refunds card; an overdue refund in the panel list. */
[data-refund-third-party][hidden], [data-refund-own][hidden] { display: none !important; }
.refund-account { border: 1px solid var(--color-border, #ddd); border-radius: 8px; padding: .75rem 1rem; margin: .75rem 0; }
.refund-account legend { padding: 0 .35rem; font-weight: 600; }
.refund-account .refund-account-types { display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; margin: .25rem 0 .5rem; }
.refund-account input[name="refund.Iban"] { direction: ltr; text-align: left; width: 100%; max-width: 26rem; font-family: monospace; }
.order-refunds .order-refund-row { display: flex; flex-wrap: wrap; gap: .25rem 1rem; align-items: baseline; padding: .4rem 0; border-bottom: 1px dashed var(--color-border, #ddd); }
.order-refunds .order-refund-iban { direction: ltr; unicode-bidi: isolate; font-family: monospace; }
tr.refund-overdue td { background: color-mix(in srgb, #c0392b 10%, transparent); }
.fix-photos { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0; }
.fix-photos img { width: 84px; height: 84px; object-fit: cover; border-radius: 6px; }

/* Item 46 (2026-10-22): a customer/staff name the automatic split flagged (one word, more than two) or left incomplete. */
.pb-name-review-badge { display: inline-block; font-size: 0.75rem; padding: 0 0.45rem; border-radius: 999px; background: color-mix(in srgb, #e6a100 22%, transparent); color: inherit; margin-inline-start: 0.4rem; white-space: nowrap; }

/* Item 41 (2026-10-22): colour theme list preview (admin «قالب‌ها» card). */
.pb-theme-preview { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.pb-theme-sample { width: 150px; border: 1px solid rgb(0 0 0 / 0.15); border-radius: 6px; overflow: hidden; font-size: 0.8rem; }
.pb-theme-sample-bar { min-height: 14px; padding: 2px 6px; font-size: 0.7rem; }
.pb-theme-sample-body { display: flex; align-items: center; gap: 0.35rem; padding: 0.45rem 0.5rem; }
.pb-theme-sample-btn { display: inline-block; width: 18px; height: 12px; border-radius: 3px; }
.pb-theme-swatch { display: inline-block; width: 22px; height: 40px; border-radius: 4px; border: 1px solid rgb(0 0 0 / 0.15); }
.pb-theme-list td form.form-inline { margin-block: 0.15rem; }
@media (max-width: 700px) { .pb-theme-list input[type="text"] { max-width: 9rem; } }

/* MASTER_CHANGES_PLAN.md item 43 «فرم‌ساز»: a form-builder form (block or /forms/{name}) - the site's usual form card (the form
   carries .form-field cells), its fields on a two-column grid where a "half" field takes one column and a "full" one the row;
   one column on a phone. A field hidden by its display conditions keeps the [hidden] attribute (form-builder.js). */
.pb-form-builder {
  margin-block: 1rem;
}

.pb-form-builder-title {
  text-align: center;
}

.pb-form-builder-description {
  text-align: center;
  white-space: pre-line;
  margin-block-end: 1rem;
}

.pb-form-builder-success {
  margin: 0 auto 1rem;
  padding: 0.8rem 1rem;
  border-radius: var(--pb-radius, 12px);
  background: color-mix(in srgb, #2e7d32 12%, transparent);
  border: 1px solid color-mix(in srgb, #2e7d32 45%, transparent);
  white-space: pre-line;
  text-align: center;
}

.pb-form-builder-login {
  text-align: center;
}

.pb-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 1.25rem;
}

.pb-form-cell--full {
  grid-column: 1 / -1;
}

.pb-form-cell[hidden] {
  display: none;
}

@media (max-width: 640px) {
  .pb-form-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.pb-form-heading {
  margin: 0.5rem 0 0.25rem;
}

.pb-form-help-text,
.pb-form-field-help {
  white-space: pre-line;
}

.pb-form-choices {
  border: 0;
  padding: 0;
  margin: 0;
}

.pb-form-choices legend {
  font-weight: 600;
  font-size: 0.9rem;
  margin-bottom: 0.3rem;
}

.form-field label.pb-form-choice {
  font-weight: 400;
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.pb-form-cell--error input,
.pb-form-cell--error select,
.pb-form-cell--error textarea {
  border-color: #c62828;
}

.pb-form-error {
  color: #c62828;
  font-size: 0.85rem;
  margin: 0.25rem 0 0;
}

/* Item 43 panel: an unread message reads bold; a message's answers and replies keep the sender's line breaks. */
.pb-row-unread td {
  font-weight: 700;
}

.pb-form-answers dt,
.pb-form-message-meta dt {
  font-weight: 600;
  margin-top: 0.6rem;
}

.pb-form-answers dd,
.pb-form-message-meta dd {
  margin-inline-start: 0;
}

.pb-form-answer-text {
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.pb-form-reply {
  border-inline-start: 3px solid var(--pb-border);
  padding-inline-start: 0.75rem;
  margin-block: 0.75rem;
}

/* MASTER_CHANGES_PLAN.md item 42 ف۴: the organizations' group appointments - the panel's employee picks and member blocks, the employee's
   and the representative's appointment cards. */
.org-appointment-picks {
  list-style: none;
  padding-inline-start: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 0.35rem 1rem;
}

.org-appointment-member {
  border-top: 1px solid var(--pb-border);
  padding-block: 0.75rem;
}

.org-appointment-actions,
.org-appointment .form-inline,
.org-appointment-member .form-inline {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-inline-end: 0.5rem;
}

.org-appointment {
  margin-block: 0.75rem;
}

.org-appointment-cancel {
  margin-top: 1rem;
}

/* MASTER_CHANGES_PLAN.md item 42-3 (۱۰)(ب): «مدیریت پیامک‌ها» - each message's own text under its name. */
.sms-management-table .sms-management-text {
  display: block;
  white-space: pre-line;
  margin-top: 0.2rem;
}

.sms-management-table td:not(:first-child),
.sms-management-table th:not(:first-child) {
  white-space: nowrap;
  text-align: center;
}

/* «▶ سامانه‌ی پیامکی تازه 0098vip» (س۴): «گزارش ارسال پیامک» - the full text and the error wrap, the short columns do not. */
.sms-report-table td {
  vertical-align: top;
}

.sms-report-table .sms-report-short {
  white-space: nowrap;
}

.sms-report-table .sms-report-kind {
  min-width: 10rem;
}

.sms-report-table .sms-report-text {
  white-space: pre-line;
  min-width: 16rem;
}
