/* app.css — small static, plain-CSS layer on top of the UI Kit theme.
   The UI Kit tokens/base live in the inline tailwindcss theme (views/shared/layouts/_theme.ejs).
   Keep this minimal: only safe defaults that don't belong to the Tailwind theme. */

html,
body {
  margin: 0;
  padding: 0;
}

/* NOTE: deliberately NO unlayered `a { color: inherit }` here. Tailwind's preflight already
   resets links (`a { color: inherit; text-decoration: inherit }`) inside `@layer base`, which
   correctly sits BELOW `@layer utilities`. An UNLAYERED rule like that would WIN over every
   text-color utility (e.g. `text-white`, `text-primary`) on <a> elements — because in the CSS
   cascade unlayered styles beat layered ones regardless of specificity — turning navy links with
   `text-white` into dark-on-dark. Let preflight + utilities handle links. (Buttons were unaffected,
   which is why only <a> links showed the bug.) */

/* RTL-safe helpers also defined in the kit theme; duplicated here as plain CSS so they
   apply even before the Tailwind browser build finishes processing. */
.rtl-inline-meta {
  direction: rtl;
  unicode-bidi: isolate;
}
.ltr-data {
  direction: ltr;
  unicode-bidi: plaintext;
}

/* Alpine: hide x-cloak elements until Alpine initializes (avoids drawer flash). */
[x-cloak] {
  display: none !important;
}
@media (min-width: 1024px) {
  /* On desktop the sidebar is always shown even before Alpine loads. */
  aside[x-cloak] {
    display: flex !important;
  }
}

/* File-upload component: reveal the saved-file row when a file is present. */
[data-uploader][data-has-file] [data-file-display] {
  display: flex !important;
}

/* Compact file-upload variant: the empty row and the filled row are mutually exclusive, so
   the control keeps ONE fixed height whether or not a file is attached. */
[data-uploader][data-has-file] [data-file-empty] {
  display: none !important;
}

/* ── Switch (used by the hook form's sticky publish control) ────────────────────────────────
   Plain CSS rather than a peer-checked Tailwind chain: the toggle is drawn from an ::after
   knob whose transform must flip for RTL, which is exactly the kind of thing the browser
   Tailwind build should not have to express. */
.mm-switch {
  position: relative;
  display: inline-block;
  box-sizing: border-box;
  width: 38px;
  height: 22px;
  flex: none;
  border-radius: 9999px;
  background: var(--color-border-dark);
  transition: background-color 0.18s ease;
}
.mm-switch::after {
  content: '';
  position: absolute;
  top: 3px;
  right: 3px;
  width: 16px;
  height: 16px;
  border-radius: 9999px;
  background: #fff;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.3);
  transition: transform 0.18s ease;
}
.mm-switch-input:checked + .mm-switch {
  background: var(--color-status-done);
}
.mm-switch-input:checked + .mm-switch::after {
  transform: translateX(-16px);
}
.mm-switch-input:focus-visible + .mm-switch {
  box-shadow: var(--shadow-focus);
}

/* ── Rich editor (email campaign wizard) ────────────────────────────────────────────────────
   The contenteditable is a WYSIWYG surface: what it shows has to match what the email template
   will render, or the preview tab is the only honest view and the editor is a lie. These rules
   mirror email-template.ts — same block spacing, same list indent, same quote rule — expressed
   in the panel's own tokens so they follow the theme.

   Plain CSS, not Tailwind utilities: the content is authored at runtime, so there is no markup
   for the browser Tailwind build to scan and no class to attach. */
.mm-rich > *:first-child { margin-top: 0; }
.mm-rich > *:last-child { margin-bottom: 0; }
.mm-rich p { margin: 0 0 0.85rem; }
.mm-rich h2 { margin: 1.35rem 0 0.6rem; font-size: 1.15rem; font-weight: 700; line-height: 1.8; }
.mm-rich h3 { margin: 1.15rem 0 0.5rem; font-size: 1rem; font-weight: 700; line-height: 1.8; }
.mm-rich ul, .mm-rich ol { margin: 0 0 0.85rem; padding-right: 1.4rem; padding-left: 0; }
.mm-rich ul { list-style: disc; }
.mm-rich ol { list-style: decimal; }
.mm-rich li { margin-bottom: 0.3rem; }
.mm-rich blockquote {
  margin: 0 0 0.85rem;
  padding: 0.6rem 0.9rem;
  border-right: 3px solid var(--color-primary);
  background: var(--color-bg-secondary);
  border-radius: 0.5rem;
}
.mm-rich a { color: var(--color-primary); text-decoration: underline; }
.mm-rich hr { border: 0; border-top: 1px solid var(--color-border-light); margin: 1.2rem 0; }

/* Empty state. `:empty` never matches a contenteditable (browsers keep a <br> inside), so
   rich-editor.js sets data-empty on the wrapper and the placeholder hangs off that. */
[data-rich-editor][data-empty] [data-rich-surface]::before {
  content: attr(data-placeholder);
  color: var(--color-text-muted);
  pointer-events: none;
  display: block;
}

/* Active toolbar buttons. Written against the aria state rather than a Tailwind `aria-*`
   variant, so the styling does not depend on which variants the browser build ships. */
[data-rich-toolbar] button[aria-pressed='true'] {
  background: var(--color-bg-primary);
  color: var(--color-primary);
  box-shadow: inset 0 0 0 1px var(--color-border-light);
}

/* ── Lead-form field chips (hook form) ──────────────────────────────────────────────────────
   Every row of «فیلدهای فعال در فرم» is the SAME chip, whether it can be switched off or not:
   the optional fields toggle, the mobile number and its OTP are locked on. Plain CSS rather
   than a peer-checked utility chain because the checked state has to restyle DESCENDANTS of
   the label (the leading icon and the state icon), which `peer-*` cannot reach.
   The accent is the theme's blue (`--color-status-doing`), darkened one step on light so blue
   label text still clears the contrast bar against the pale blue fill. */
.mm-fieldchip {
  --chip-accent: #2563eb;
  display: flex;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--color-border-medium);
  border-radius: var(--radius-xl);
  background: var(--color-bg-secondary);
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}
.theme-dark .mm-fieldchip {
  --chip-accent: #60a5fa;
}
.mm-fieldchip-ico {
  flex: none;
  width: 16px;
  text-align: center;
  color: var(--color-text-muted);
}
.mm-fieldchip-label {
  font-weight: 500;
}
.mm-fieldchip-state {
  flex: none;
  margin-inline-start: auto;
  font-size: var(--text-2xs);
  color: var(--color-text-muted);
}
.mm-fieldchip-on {
  display: none;
}

.mm-fieldchip-wrap {
  position: relative;
  display: block;
  cursor: pointer;
}
/* The real checkbox stays in the DOM (it is what POSTs) but out of sight. Not `display:none`:
   that would drop it out of the focus order and off the keyboard entirely. */
.mm-fieldchip-input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
}
.mm-fieldchip-wrap:hover .mm-fieldchip {
  border-color: var(--color-border-dark);
}
.mm-fieldchip-input:focus-visible + .mm-fieldchip {
  box-shadow: var(--shadow-focus);
}
.mm-fieldchip-input:checked + .mm-fieldchip,
.mm-fieldchip.is-locked {
  border-color: var(--chip-accent);
  background: rgba(59, 130, 246, 0.08);
  color: var(--chip-accent);
}
.mm-fieldchip-input:checked + .mm-fieldchip .mm-fieldchip-ico,
.mm-fieldchip-input:checked + .mm-fieldchip .mm-fieldchip-state,
.mm-fieldchip.is-locked .mm-fieldchip-ico,
.mm-fieldchip.is-locked .mm-fieldchip-state {
  color: var(--chip-accent);
}
.mm-fieldchip-input:checked + .mm-fieldchip .mm-fieldchip-off {
  display: none;
}
.mm-fieldchip-input:checked + .mm-fieldchip .mm-fieldchip-on {
  display: inline-block;
}

/* ── Tag multi-select (hook form sidebar) ───────────────────────────────────────────────────
   A search-and-pick control over the tag list: the closed control carries the selected tags as
   removable badges, the panel filters as you type. Everything hangs off real checkboxes, so
   the form posts exactly what it posted before.

   PROGRESSIVE ENHANCEMENT: the panel is open and the control hidden until hook-form.js sets
   `data-ready`. If the script never runs the user still sees a plain, usable checkbox list
   instead of an empty box that eats their tags. */
.mm-tagselect {
  position: relative;
}
.mm-tagselect-control {
  display: none;
}
.mm-tagselect[data-ready] .mm-tagselect-control {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
  box-sizing: border-box;
  width: 100%;
  min-height: 34px;
  padding: 5px 8px;
  border: 1px solid var(--color-border-medium);
  border-radius: var(--radius-lg);
  background: var(--color-bg-primary);
  cursor: pointer;
}
.mm-tagselect[data-ready][data-open] .mm-tagselect-control,
.mm-tagselect[data-ready] .mm-tagselect-control:focus-visible {
  outline: none;
  border-color: var(--color-status-doing);
  box-shadow: var(--shadow-focus);
}
/* `display:contents` so each chip is a direct flex item of the control and wraps on its own. */
.mm-tagselect-chips {
  display: contents;
}
.mm-tagselect-placeholder {
  font-size: var(--text-2xs);
  color: var(--color-text-muted);
}
.mm-tagselect-caret {
  margin-inline-start: auto;
  font-size: var(--text-3xs);
  color: var(--color-text-muted);
  transition: transform 0.15s ease;
}
.mm-tagselect[data-open] .mm-tagselect-caret {
  transform: rotate(180deg);
}

/* The panel is styled off ITSELF, never off an ancestor, because the script hands it to
   `kitLayer` (uikit.js) and it stops being a descendant of .mm-tagselect: it is parked on
   <body>, in the top layer, and positioned against the viewport. That is what stops it being
   painted under the sticky save bar — the sidebar card is a `.glass-panel`, and
   `backdrop-filter` opens a stacking context that no z-index inside it can climb out of.
   `display`, `width`, `left/top` and `max-height` all belong to the layer engine; everything
   below is the panel's SKIN, plus the column layout that lets the engine's max-height shrink
   the list instead of overflowing the box. */
.mm-tagselect-panel[data-kit-layer] {
  display: flex;
  flex-direction: column;
  padding: 6px;
  border: 1px solid var(--color-border-medium);
  border-radius: var(--radius-xl);
  background: var(--color-bg-primary);
  box-shadow: var(--shadow-card);
}
.mm-tagselect-search {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 6px;
  padding: 2px 6px 6px;
  border-bottom: 1px solid var(--color-border-light);
  margin-bottom: 4px;
}
.mm-tagselect-search i {
  font-size: var(--text-3xs);
  color: var(--color-text-muted);
}
.mm-tagselect-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--color-text-primary);
  font-size: var(--text-2xs);
}
.mm-tagselect-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 0;
  max-height: 216px;
  overflow-y: auto;
}
.mm-tagselect-opt {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border-radius: var(--radius-md);
  cursor: pointer;
}
/* A utility class sets `display:flex` above, which outranks the UA rule behind [hidden]. */
.mm-tagselect-opt[hidden] {
  display: none;
}
.mm-tagselect-opt:hover {
  background: var(--color-bg-secondary);
}
.mm-tagselect-opt input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
}
.mm-tagselect-opt input:focus-visible ~ .mm-tag-badge {
  box-shadow: var(--shadow-focus);
}
.mm-tagselect-tick {
  margin-inline-start: auto;
  font-size: var(--text-3xs);
  color: var(--color-status-doing);
  visibility: hidden;
}
.mm-tagselect-opt input:checked ~ .mm-tagselect-tick {
  visibility: visible;
}
.mm-tagselect-empty {
  display: none;
  padding: 8px 6px;
  font-size: var(--text-2xs);
  color: var(--color-text-muted);
}
.mm-tagselect-panel[data-no-match] .mm-tagselect-empty {
  display: block;
}

/* The badge — the shape the tags already had on this form, kept verbatim so the control, the
   option rows and the chips all read as the same object in three places. */
.mm-tag-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  padding: 2px 8px;
  border: 1px solid var(--color-border-medium);
  border-radius: 9999px;
  background: var(--color-bg-primary);
  color: var(--color-text-secondary);
  font-size: var(--text-3xs);
  line-height: 1.7;
}
.mm-tag-badge > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mm-tagselect-chip button {
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-3xs);
  line-height: 1;
  cursor: pointer;
}
.mm-tagselect-chip button:hover {
  color: var(--color-status-blocked);
}

/* ── Brand marks that Font Awesome does not carry ───────────────────────────────────────────
   `mm-icon mm-icon--x` is drop-in interchangeable with a Font Awesome class: it is worn by an
   `<i>`, it sizes itself from `font-size` (1em square), and it paints itself in `currentColor`
   — so every place that already writes `<i class="{{icon}}">` and tints it (`.mm-ch-ink`, a
   muted grey on a disabled tile, white inside a filled pill) keeps working untouched. That is
   the whole reason it is a MASK and not an `<img>`: an <img> would ignore the ink around it and
   force a second, colour-aware branch into ten templates.

   Bale is the one that needs it. It was drawn as `fa-comment-dots` — a generic speech balloon
   that says «a messenger», not «Bale» — while the channel it stands for has a real, published
   mark. `public/assets/img/bale.svg` IS that mark: the app-icon half of the official logo at
   bale.ai, single-colour, so tinting it with the brand's own #00b894 reproduces the logo exactly
   rather than approximating it.

   The URL is RELATIVE, deliberately: assets are served from their own origin (see app.ts), and
   a relative url() resolves against THIS stylesheet, so it follows the assets wherever they are
   mounted. A root-absolute `/assets/...` would resolve against the tenant subdomain instead. */
.mm-icon {
  display: inline-block;
  /* The BOX is 1em square so the inline metrics match a Font Awesome glyph exactly; the MARK is
     painted at 0.86em inside it, because an FA glyph does not fill its own em box either and a
     logo that does would sit visibly larger than the icons beside it. */
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  background-color: currentColor;
  -webkit-mask: var(--mm-icon) center / 0.86em 0.86em no-repeat;
  mask: var(--mm-icon) center / 0.86em 0.86em no-repeat;
}
.mm-icon--bale {
  --mm-icon: url('../img/bale.svg');
}

/* ── Channel identity (promotion campaigns) ─────────────────────────────────────────────────
   Five channels rendered in the same navy made the picker a wall of identical tiles: the
   operator had to READ five labels to find one. Each channel now carries its own brand hue —
   Bale's own #00b894 (lifted from its own logo — see `public/assets/img/bale.svg`), WhatsApp's
   green, a sky blue for email, a violet for the voice call, and the app's own ink for SMS — so
   the card is recognised before it is read.

   EMAIL IS BLUE, NOT GMAIL RED. It wore #ea4335 because that is the colour of the client most
   recipients read it in. But in THIS product red is the failure colour — it is what a blocked
   send, a bounced address and a destructive action are all painted in — so an email tile read
   as an alert, and the picker's most ordinary channel looked like its most dangerous one. Sky
   blue carries no such freight and still reads as "mail".

   The hue is bound to `data-channel` HERE and not emitted from a ViewModel, because it has to
   change with the theme: Bale's green survives a dark background, but SMS's near-black does not
   and has to flip to a light slate. Which theme is on is a CSS fact. Templates stamp
   `data-channel="BALE"` and everything downstream reads var(--ch).

   `--ch-soft` / `--ch-edge` are DERIVED, so a hue is corrected in exactly one place. */
[data-channel] {
  --ch: var(--color-primary);
  --ch-soft: color-mix(in srgb, var(--ch) 12%, transparent);
  --ch-edge: color-mix(in srgb, var(--ch) 34%, transparent);
}
[data-channel='SMS'] {
  --ch: #0f172a;
}
[data-channel='BALE'] {
  --ch: #00b894;
}
[data-channel='WHATSAPP'] {
  --ch: #1faa54;
}
[data-channel='EMAIL'] {
  --ch: #0ea5e9;
}
[data-channel='VOICE'] {
  --ch: #7c3aed;
}

/* Dark theme: the same identities, lifted off a slate background. WhatsApp gets its true
   #25d366 here — on white that green is too pale to read as an icon, so light mode uses a
   deeper shade of the same hue. */
.theme-dark [data-channel='SMS'] {
  --ch: #e2e8f0;
}
.theme-dark [data-channel='BALE'] {
  --ch: #00d6ac;
}
.theme-dark [data-channel='WHATSAPP'] {
  --ch: #25d366;
}
.theme-dark [data-channel='EMAIL'] {
  --ch: #38bdf8;
}
.theme-dark [data-channel='VOICE'] {
  --ch: #a78bfa;
}

/* The picker's grid. Two tiles on a phone, three on a tablet, and from `lg` up as many as fit
   — `auto-fit`, not a fixed column count, because the channel list is five today and six the
   moment one more is added. A fixed `grid-cols-6` would leave today's row with a dead cell in
   it; `auto-fit` collapses the empty tracks so the tiles it DOES have share the row evenly and
   a sixth channel simply joins them. */
.mm-ch-grid {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 640px) {
  .mm-ch-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (min-width: 1024px) {
  .mm-ch-grid {
    grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
  }
}

/* ── The picker tile's head: the mark, oversized, with the words facing it ──────────────────
   The tile used to stack a 40px chip, a label and a tagline down its right edge, which left the
   whole left half of every card empty — five cards, five holes, on the row the page exists for.

   So the mark stops being a chip and becomes the tile's GROUND: one big glyph pinned to the
   outer edge at low opacity, bleeding a little past it, with the name and its line of small
   print set opposite. The channel is now recognisable at a glance and from across the room —
   the colour was already doing that job at 40px, but only barely — and nothing about the card
   grew: the same 3.25rem band that held a chip and two lines of text now holds all three.

   The glyph is `position: absolute`, so it can be as large as it likes without pushing the text
   around, and the text reserves its footprint with a padding rather than overlapping it: at 15%
   opacity a mark UNDER a Persian tagline is not a watermark, it is noise behind small type.

   Logical properties, not `left`: the app is RTL, so the outer edge of the card is its inline
   END. Written as `left` this whole layout would mirror itself the moment the tile is rendered
   in an LTR context. */
.mm-ch-head {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 3.25rem;
  padding-inline-end: 2.85rem;
}
.mm-ch-glyph {
  position: absolute;
  inset-inline-end: -0.45rem;
  top: 50%;
  translate: 0 -50%;
  font-size: 3.15rem;
  line-height: 1;
  color: var(--ch);
  opacity: 0.15;
  pointer-events: none;
}
/* The call to action names the campaign it is about to create — «ساختِ کمپینِ پیامکی», not
   «ساختِ کمپین». Five identical buttons under five different cards made the operator re-read
   the card above to know what they were about to click; the wording is declared once, per
   channel, in campaigns/channel-theme.ts. */
.mm-ch-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  width: 100%;
  margin-top: auto;
  border-radius: var(--radius-lg);
  padding: 0.4375rem 0.5rem;
  font-size: var(--text-3xs);
  font-weight: 600;
  line-height: 1.6;
  min-width: 0;
}
/* The label is a flex item, and a flex item's default `min-width: auto` refuses to shrink below
   its text — without this the ellipsis never engages and a long CTA widens the whole grid. */
.mm-ch-cta > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* An unconfigured channel keeps its identity at a whisper — the mark is still there, in grey,
   so the tile is still scannable, but nothing on it claims to be ready. */
.mm-ch-card--off .mm-ch-glyph {
  color: var(--color-text-muted);
  opacity: 0.12;
}
.theme-dark .mm-ch-glyph {
  opacity: 0.24;
}
.theme-dark .mm-ch-card--off .mm-ch-glyph {
  opacity: 0.16;
}

.mm-ch-ink {
  color: var(--ch);
}
.mm-ch-tint {
  background: var(--ch-soft);
}
.mm-ch-edge {
  border-color: var(--ch-edge);
}
/* The preview frame — a phone shell or an inbox card — wears the channel it is previewing, so
   the operator can tell at a glance WHICH message they are approving. */
.mm-ch-frame {
  border-color: color-mix(in srgb, var(--ch) 52%, transparent);
  box-shadow: 0 18px 40px -22px color-mix(in srgb, var(--ch) 70%, transparent);
}
.mm-ch-fill {
  background: var(--ch);
}

/* The picker card: a hairline accent along its top edge at rest, and on hover the border and
   the lift both take the channel's colour. Nothing else changes — the restraint is the point. */
.mm-ch-card {
  position: relative;
  overflow: hidden;
  transition:
    border-color 0.18s ease,
    box-shadow 0.18s ease,
    transform 0.18s ease;
}
.mm-ch-card::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 3px;
  background: var(--ch);
  opacity: 0.85;
}
.mm-ch-card:hover {
  border-color: var(--ch-edge);
  box-shadow: 0 14px 30px -16px color-mix(in srgb, var(--ch) 65%, transparent);
  transform: translateY(-2px);
}
/* A channel that isn't configured yet has nothing to offer: it keeps its identity, quietly,
   and does not lift under the cursor — the tile itself is not a target, only the gear inside
   it is, and a card that rises when hovered promises a click it will not honour. */
.mm-ch-card--off::before {
  opacity: 0.28;
}
.mm-ch-card--off:hover {
  transform: none;
  border-color: var(--color-border-light);
  box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
  .mm-ch-card:hover {
    transform: none;
  }
}

/* ── Links inside a previewed message ───────────────────────────────────────────────────────
   The class every anchor minted by public/assets/js/linkify.js wears — the campaign report's
   «پیامِ ارسال‌شده» panel, the wizard's live mock, and anywhere else a message body is drawn.

   PLAIN CSS, NOT A TAILWIND UTILITY CHAIN, and that is the load-bearing decision. The wizard
   re-renders its preview from JavaScript on every keystroke, so those anchors enter the DOM long
   after the browser Tailwind build has scanned the page; a utility class that first appears then
   is a class with no rule behind it, and the link would render as unstyled body text.

   Blue rather than --color-primary: the theme's primary is a near-black navy, which is the right
   ink for a heading and completely wrong for a link — «this is clickable» is a colour convention,
   and the convention is blue. `unicode-bidi: plaintext` matches .ltr-data: a latin URL sitting in
   a Persian sentence must not have its punctuation dragged to the wrong end by the bidi
   algorithm, and a link whose text IS Persian (an IRI path) still reads right-to-left. */
.mm-link {
  color: #2563eb;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  direction: ltr;
  unicode-bidi: plaintext;
  /* A short link is short; a campaign body pasted from a browser bar is not. `anywhere` keeps the
     longest of them inside the bubble instead of widening the panel that holds it. */
  overflow-wrap: anywhere;
}
.mm-link:hover {
  color: #1d4ed8;
}
.theme-dark .mm-link {
  color: #60a5fa;
}
.theme-dark .mm-link:hover {
  color: #93c5fd;
}

/* ── Disclosure (<details>) ──────────────────────────────────────────────────────────────────
   Used by «وضعیت سرویس‌ها» → «جزئیات» to fold the incident history away while nothing is wrong.

   Plain CSS rather than Tailwind's `open:`/`group-open:` variants for one reason: this markup is
   delivered by an htmx swap into an already-rendered page, and the browser Tailwind build only
   scans what it has seen. A variant that happens not to have been emitted yet would leave the
   chevron stuck and both labels showing at once — a state the reader would read as a bug. Nothing
   here is load-bearing for the toggle itself; <details> opens with or without it. */
.mm-disclosure > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
}
/* Safari/older WebKit draw their own triangle, which ignores `list-style`. */
.mm-disclosure > summary::-webkit-details-marker {
  display: none;
}
.mm-disclosure > summary .mm-disclosure-chevron {
  transition: transform 0.15s ease;
}
.mm-disclosure[open] > summary .mm-disclosure-chevron {
  transform: rotate(180deg);
}
/* One control, two labels: whichever one is not the current action is removed. */
.mm-disclosure-hide {
  display: none;
}
.mm-disclosure[open] > summary .mm-disclosure-show {
  display: none;
}
.mm-disclosure[open] > summary .mm-disclosure-hide {
  display: inline;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Persian datepicker (public/assets/js/persian-datepicker.js)

   Every value here is a THEME TOKEN from views/shared/layouts/_theme.ejs — no new colour, no
   new radius, no new shadow. The popup is appended to <body> (or to the enclosing <dialog>),
   so it inherits the tokens from :root and follows `.theme-dark` for free.

   Written as plain CSS rather than Tailwind classes because the markup is BUILT IN SCRIPT: the
   browser Tailwind build only emits utilities it has seen in the served HTML, and a class that
   first appears from `innerHTML` after load would simply have no rule behind it.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
.mm-dp-input {
  cursor: pointer;
  /* The value is Persian text now, not an ISO string, so it reads right-to-left like the rest
     of the panel. `text-align: right` is not enough on its own inside `.fbar-ctl--date`, which
     still carries the native control's `dir="ltr"` in some render paths. */
  text-align: right;
}

/* The panel is placed and kept on top by `kitLayer` (uikit.js) — the house rule for anything
   that has to float over the page, because `.glass-panel`'s backdrop-filter opens a stacking
   context AND a fixed-position containing block that no z-index can climb out of.

   So the skin is written on `[data-kit-layer]`, the attribute `attach` stamps: uikit injects its
   own `.kit-layer` reset (transparent, no padding, no border) from a runtime <style> that lands
   AFTER this file, and at equal specificity the later rule would win. The attribute selector is
   one step more specific, which is exactly what the tag-select panel does for the same reason.

   `overflow-y: auto` matters: `place()` clamps the panel's height to the room beside its anchor,
   and a calendar clamped shorter than its grid must scroll rather than spill. */
.mm-dp[data-kit-layer] {
  box-sizing: border-box;
  width: 268px;
  max-width: calc(100vw - 16px);
  padding: 10px;
  direction: rtl;
  overflow-y: auto;
  background: var(--color-bg-primary);
  border: 1px solid var(--color-border-medium);
  border-radius: var(--radius-2xl);
  box-shadow: var(--shadow-card);
  font-family: 'Sahel', sans-serif;
  color: var(--color-text-primary);
}

.mm-dp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 8px;
}
.mm-dp-title {
  font-size: var(--text-xs);
  font-weight: 700;
}
.mm-dp-nav {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-lg);
  background: var(--color-bg-secondary);
  color: var(--color-text-secondary);
  font-size: var(--text-3xs);
  cursor: pointer;
}
.mm-dp-nav:hover {
  background: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

.mm-dp-week,
.mm-dp-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.mm-dp-week {
  margin-bottom: 4px;
}
.mm-dp-week span {
  text-align: center;
  font-size: var(--text-3xs);
  color: var(--color-text-muted);
  padding: 3px 0;
}

.mm-dp-day {
  height: 30px;
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  background: none;
  color: var(--color-text-secondary);
  font-family: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
}
.mm-dp-day.is-empty {
  cursor: default;
}
.mm-dp-day:hover:not(.is-empty) {
  background: var(--color-bg-secondary);
  color: var(--color-text-primary);
}
/* Today is an outline, the selection is a fill — so «today» never looks like «chosen». */
.mm-dp-day.is-today {
  border-color: var(--color-border-dark);
  font-weight: 700;
}
.mm-dp-day.is-sel {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-bg-primary);
  font-weight: 700;
}

.mm-dp-time {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 9px;
  padding-top: 9px;
  border-top: 1px solid var(--color-border-light);
}
.mm-dp-time-lbl {
  flex: 1;
  font-size: var(--text-2xs);
  color: var(--color-text-muted);
}
.mm-dp-colon {
  color: var(--color-text-muted);
}
.mm-dp-sel {
  height: 30px;
  padding: 0 6px;
  border: 1px solid var(--color-border-medium);
  border-radius: var(--radius-lg);
  background: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
}

.mm-dp-foot {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 9px;
  padding-top: 9px;
  border-top: 1px solid var(--color-border-light);
}
.mm-dp-act {
  flex: 1;
  height: 30px;
  border: 1px solid var(--color-border-medium);
  border-radius: var(--radius-lg);
  background: var(--color-bg-secondary);
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: var(--text-2xs);
  font-weight: 600;
  cursor: pointer;
}
.mm-dp-act:hover {
  background: var(--color-bg-tertiary);
}
.mm-dp-act--muted {
  color: var(--color-text-muted);
}

/* On a phone the calendar takes the screen minus the margin kitLayer keeps at the rim. */
@media (max-width: 400px) {
  .mm-dp[data-kit-layer] {
    width: calc(100vw - 16px);
  }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Cover cropper (public/assets/js/uploader.js)

   Theme tokens only — the dialog chrome deliberately matches `.kit-modal` in _theme.ejs: a
   tinted head, a flat body, a tinted foot. Plain CSS for the same reason the datepicker is:
   this markup is built in script, and the browser Tailwind build only emits utilities it has
   already seen in the served HTML.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
.mm-crop {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius-3xl);
  border: 1px solid var(--color-border-light);
  background: var(--color-bg-primary);
  box-shadow: var(--shadow-card);
}
.mm-crop-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--color-border-light);
  background: var(--color-bg-secondary);
}
.mm-crop-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-primary);
}
.mm-crop-sub {
  margin-top: 2px;
  font-size: var(--text-2xs);
  line-height: 1.7;
  color: var(--color-text-muted);
}
.mm-crop-x {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-xl);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}
.mm-crop-x:hover {
  background: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

/* The frame IS the answer to «at what size is my cover shown?» — it is the landing hero's own
   box, and its aspect ratio is set from the upload policy in script. */
.mm-crop-stage {
  position: relative;
  overflow: hidden;
  margin: 14px 18px 0;
  border-radius: var(--radius-2xl);
  background: var(--color-bg-secondary);
  cursor: grab;
  touch-action: none;
  user-select: none;
}
.mm-crop-stage:active {
  cursor: grabbing;
}
.mm-crop-stage img {
  position: absolute;
  top: 0;
  right: auto;
  left: auto;
  transform-origin: top left;
  max-width: none;
  /* The image is positioned by an explicit translate in script; `inset-inline` would fight it
     under RTL, so the box is pinned to the physical left edge. */
  inset-inline-start: auto;
  left: 0;
  pointer-events: none;
  -webkit-user-drag: none;
}

.mm-crop-zoom {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

.mm-crop-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
  padding: 12px 18px;
  border-top: 1px solid var(--color-border-light);
  background: var(--color-bg-secondary);
}
.mm-crop-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 38px;
  padding: 0 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-xl);
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
}
.mm-crop-btn--ghost {
  background: var(--color-bg-primary);
  border-color: var(--color-border-medium);
  color: var(--color-text-secondary);
}
.mm-crop-btn--ghost:hover {
  background: var(--color-bg-tertiary);
}
.mm-crop-btn--primary {
  background: var(--color-primary);
  color: var(--color-bg-primary);
  box-shadow: var(--shadow-button);
}
.mm-crop-btn--primary:hover {
  opacity: 0.95;
}
