/* The fonts the picker offers, actually delivered.
   Every face below already sat in static/fonts/ and none of them was ever
   declared, so a browser could only render a choice that happened to be
   installed on the reader's own machine -- which is why exactly two of the six
   appeared to work and the rest silently fell through to Tahoma.
   swap, so text is readable while the face downloads instead of invisible. */
@font-face {
  font-family: IRAN;
  src: url("/static/fonts/IRAN.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: IRAN;
  src: url("/static/fonts/IRAN_SemiBold.woff2") format("woff2");
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: IRAN;
  src: url("/static/fonts/IRANBold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}
@font-face {
  font-family: IRAN;
  src: url("/static/fonts/IRANBlack.woff2") format("woff2");
  font-weight: 900;
  font-display: swap;
}
@font-face {
  font-family: IRANSansX;
  src: url("/static/fonts/IRANSansX-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: IRANSansX;
  src: url("/static/fonts/IRANSansX-Bold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}
@font-face {
  font-family: IRANYekanX;
  src: url("/static/fonts/IRANYekanX-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: IRANYekanX;
  src: url("/static/fonts/IRANYekanX-Bold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}
/* The three Yekan Bakh files are variable fonts: one file covers the whole
   weight range, so the range is declared rather than a single weight. */
@font-face {
  font-family: YekanBakh;
  src: url("/static/fonts/YekanBakh-VF.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: YekanBakhNoEn;
  src: url("/static/fonts/YekanBakhNoEn-VF.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: YekanBakhFaNum;
  src: url("/static/fonts/YekanBakhFaNum-VF.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: swap;
}

:root {
  color-scheme: light;
  --app-font: IRANSansX, Tahoma, sans-serif;
  /* A lavender page and white cards, so a card is a card because it is lighter
     than the page rather than because it has a heavy border. The neutrals carry
     the same violet bias as the brand: a pure grey beside a purple accent reads
     as two unrelated decisions. */
  --ui-bg: #f3f1fa;
  --ui-surface: #ffffff;
  --ui-surface-2: #faf9fe;
  --ui-field: #ffffff;
  --ui-text: #1c1832;
  --ui-muted: #655f7d;
  --ui-line: #d8d2ec;
  --ui-brand: #6d3fd4;
  --ui-brand-ink: #ffffff;
  --ui-danger: #c62a49;
  --ui-shadow: #2a1f5714;
  /* Accent text colours. A single fixed amber or cyan cannot stay legible on
     both a near-black and a near-white surface, so each theme names its own.
     Info is cyan and not another violet: with a violet brand, an informational
     note in violet reads as a call to action. */
  --ui-warn: #8f5600;
  --ui-info: #0f6a86;
  --ui-knob: #ffffff;
  --ui-scrim: #1c183299;

  /* A categorical series, for charts where every bar is a different thing.
     One brand-coloured bar per category told the reader nothing about which
     bar was which, and the labels had to be crammed under the plot to say so.
     Six hues spaced around the wheel, each measured at 3:1 or better against
     its own card surface -- palette_smoke keeps that true. A seventh category
     wraps rather than inventing a colour nobody checked. */
  --ui-series-1: #6d3fd4;
  --ui-series-2: #0f6a86;
  --ui-series-3: #8f5600;
  --ui-series-4: #1f7a4d;
  --ui-series-5: #b3277a;
  --ui-series-6: #4a5bbf;

  /* The live-lesson stage. Dark in both themes, on purpose and not by
     omission: a video call is drawn on black the way a cinema is, faces and
     slides read against it, and a white stage around a dark classroom camera
     is a frame brighter than the picture. So the values are the same in the
     dark block below -- written twice so each theme names every colour it
     uses. Off is the red of a muted microphone; on is the brand, lifted. */
  --ui-stage: #15121d;
  --ui-stage-tile: #262231;
  --ui-stage-ctl: #3b3548;
  --ui-stage-ink: #f4f1fd;
  --ui-stage-off: #d93a4a;
  --ui-stage-on: #cdbcff;
  --ui-stage-on-ink: #170c33;

  /* Size scale. Every one of these is relative, and that is the point: the
     platform never sets a base font size, so text comes out at whatever the
     browser is configured for -- 13px on one machine, 16px on a default one.
     The type scale was already relative while spacing and control heights were
     fixed px, so the two could not stay in proportion: small text sat inside
     46px controls with 20px padding, which is what reads as clumsy rather than
     as big. Sizing the furniture in rem keeps the ratio constant at any base.

     Touch targets keep a px floor below 900px, where a finger, not the type
     scale, sets the minimum. */
  --ui-control-h: 2.7rem;
  --ui-nav-h: 2.25rem;
  --ui-radius: 0.75rem;
  --ui-radius-lg: 1.15rem;
  --ui-radius-pill: 999px;
  --ui-pad-card: 1.15rem;
  --ui-pad-control: 0.5rem 0.75rem;
  --ui-cell-pad: 0.62rem 0.72rem;
  --ui-gap: 0.75rem;

  /* Type scale.
     Headings carried a colour and no size, so a page title and a card title
     were separated by whatever the browser thought an h2 and an h3 should be.
     A scale in rem keeps the ratio at any base size, which is the same reason
     the furniture above is in rem.

     Every step is a ratio of the one below (about 1.22), which is close enough
     to a major third to read as deliberate and wide enough that two adjacent
     levels are never mistaken for each other. */
  --ui-step--1: 0.82rem;
  --ui-step-0: 1rem;
  --ui-step-1: 1.16rem;
  --ui-step-2: 1.42rem;
  --ui-step-3: 1.74rem;
  --ui-step-4: 2.1rem;
  /* The two display steps are the only ones that need to give way on a narrow
     screen: at 2.1rem a Persian headline wraps to three lines on a phone and
     stops being a headline. clamp keeps them fluid between the two ends
     instead of jumping at a breakpoint. */
  --ui-step-3: clamp(1.32rem, 1.1rem + 1.1vw, 1.74rem);
  --ui-step-4: clamp(1.5rem, 1.2rem + 1.6vw, 2.1rem);

  /* Leading. Persian is not Latin here: the script's ascenders, descenders and
     diacritics need more room between lines than an English paragraph does, so
     body runs looser than a Latin system would set it. Display sizes tighten,
     because a two-line headline at body leading falls apart. */
  --ui-lead-display: 1.35;
  --ui-lead-body: 1.85;

  /* Tracking stays at normal and is written down so it stays that way.
     Tightening tracking is the signature of the design language this pass
     draws on, and it cannot be used here: Persian letterforms connect, and
     negative letter-spacing pulls the joins apart into something that reads as
     a rendering fault. It is allowed on Latin and on digits, which is what
     --ui-track-tabular is for. */
  --ui-track: normal;
  --ui-track-tabular: -0.01em;

  /* Spacing rhythm, 0.5rem base. Structural spacing snaps to these; optical
     nudges below the smallest step stay local to the rule that needs them. */
  --ui-space-1: 0.375rem;
  --ui-space-2: 0.625rem;
  --ui-space-3: 1rem;
  --ui-space-4: 1.5rem;
  --ui-space-5: 2.25rem;
  --ui-space-6: 3.25rem;

  /* Elevation, as a ladder rather than as twenty-nine separate decisions.
     Depth comes from surface colour first and from a hairline second; a real
     shadow is reserved for something that genuinely floats over the page and
     has to be read as detached from it. A card is not that. */
  --ui-elev-hairline: 0 0 0 1px var(--ui-line);
  --ui-elev-raised: 0 1.5rem 3rem -1rem var(--ui-shadow);

  /* Motion. One curve: fast out of the gate and long to settle, so a change
     announces itself and then gets out of the way. Two durations, because a
     third is a decision nobody can hear. */
  /* The content column's own gutter. The sticky bar bleeds out by exactly
     this to cover the column, so the two must be one number and not two that
     happen to be close. */
  --ui-content-pad: 1.875rem;

  --ui-ease: cubic-bezier(0.32, 0.72, 0, 1);
  --ui-quick: 150ms;
  --ui-settle: 320ms;
}
/* The same identity after dark, not a different product: the ground keeps the
   violet bias and the brand is the same hue lightened until it reads on it. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --ui-bg: #14111f;
  --ui-surface: #1e1a2e;
  --ui-surface-2: #191527;
  --ui-field: #15121f;
  --ui-text: #f0edf9;
  --ui-muted: #a79fc0;
  --ui-line: #322b47;
  --ui-brand: #b69bff;
  --ui-brand-ink: #170c33;
  --ui-danger: #ff7d92;
  --ui-shadow: #00000066;
  --ui-warn: #f5b544;
  --ui-info: #58c8e8;
  --ui-knob: #f4f1fd;
  --ui-scrim: #07050ee0;
  /* The same six hues, lifted for a dark card. */
  --ui-series-1: #b69bff;
  --ui-series-2: #58c8e8;
  --ui-series-3: #f5b544;
  --ui-series-4: #5ad39a;
  --ui-series-5: #ff8ac0;
  --ui-series-6: #93a4ff;
  /* The stage is the same after dark: see the light block. */
  --ui-stage: #15121d;
  --ui-stage-tile: #262231;
  --ui-stage-ctl: #3b3548;
  --ui-stage-ink: #f4f1fd;
  --ui-stage-off: #d93a4a;
  --ui-stage-on: #cdbcff;
  --ui-stage-on-ink: #170c33;
}
html,
body,
button,
input,
select,
textarea,
option {
  font-family: var(--app-font) !important;
}
body {
  background: var(--ui-bg) !important;
  color: var(--ui-text) !important;
  transition:
    background 0.2s,
    color 0.2s;
}
.card,
.panel,
.uiModalCard,
.sidebar,
.content article {
  background-color: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border-color: var(--ui-line) !important;
}
/* A slider is the one input that takes its width from the shared rule and
   keeps the browser's own side margin. `input, select { width: 100% }` makes
   it exactly as wide as its column, and the user agent's `margin: 2px` then
   pushes it two pixels past each edge -- four pixels of overflow on the font
   size control, on every page that draws one, at every width. Measured, not
   guessed: .formGrid reported scrollWidth 303 against clientWidth 299 from
   320px to 900px, and this line takes it to 299.

   The side margin only. The vertical one is the browser's and harms nothing. */
input[type="range"] {
  margin-inline: 0;
}
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
select,
textarea {
  background: var(--ui-field) !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
  border-radius: 12px !important;
  outline: 0;
  box-shadow: none !important;
}
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible {
  border-color: var(--ui-brand) !important;
  outline: 3px solid color-mix(in srgb, var(--ui-brand) 25%, transparent) !important;
  outline-offset: 1px;
}
textarea {
  min-height: 112px;
  line-height: 1.9;
  padding: 14px 16px;
  resize: vertical;
}
#ticketBody,
#memberTicketReply,
#platformTicketReply,
#questionStem,
#questionExplanation {
  min-height: 190px;
}
.muted,
.hint,
.fieldHelp,
label {
  color: var(--ui-muted) !important;
}
.themeToggle {
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: 10050;
  width: 48px;
  height: 48px;
  border: 1px solid var(--ui-line);
  border-radius: 50%;
  background: var(--ui-surface);
  color: var(--ui-text);
  box-shadow: 0 10px 30px var(--ui-shadow);
  cursor: pointer;
}
.dtEditor {
  border: 1px solid var(--ui-line);
  border-radius: 16px;
  background: var(--ui-surface-2);
  overflow: hidden;
  margin: 8px 0 14px;
}
.dtEditor textarea {
  border: 0 !important;
  border-radius: 0 !important;
  margin: 0 !important;
  min-height: 170px !important;
  background: var(--ui-field) !important;
}
.dtEditorBar,
.dtEditorFoot {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  padding: 9px 11px;
  background: var(--ui-surface-2);
}
.dtEditorBar button {
  margin: 0 !important;
  padding: 7px 10px !important;
  min-height: 36px !important;
  background: transparent !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
  border-radius: 9px !important;
}
.dtEditorFoot {
  justify-content: space-between;
  color: var(--ui-muted);
  font-size: 0.82em;
}
.dtEditorPreview {
  white-space: pre-wrap;
  padding: 13px;
  border-top: 1px dashed var(--ui-line);
  color: var(--ui-text);
  min-height: 52px;
}
.jalaliWrap {
  position: relative;
}
.jalaliInput {
  cursor: pointer;
}
.jalaliCalendar {
  position: absolute;
  z-index: 10100;
  top: calc(100% + 7px);
  right: 0;
  width: min(340px, 92vw);
  padding: 13px;
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-radius: 16px;
  box-shadow: 0 18px 55px var(--ui-shadow);
}
.jalaliHead {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.jalaliHead button {
  margin: 0 !important;
  min-width: 42px;
  min-height: 42px;
}
.jalaliGrid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
}
.jalaliDay {
  min-width: 38px;
  min-height: 38px;
  margin: 0 !important;
  padding: 4px !important;
  border: 0 !important;
  border-radius: 9px !important;
  background: transparent !important;
  color: var(--ui-text) !important;
}
.jalaliDay.friday,
.jalaliDay.holiday {
  color: var(--ui-danger) !important;
}
/* Today is outlined; the chosen day is filled. Two different statements, so a
   calendar where today is also the chosen day still reads as both -- an outline
   and a fill can be worn at once, two fills cannot. */
.jalaliDay.today {
  box-shadow: inset 0 0 0 2px var(--ui-brand);
  font-weight: 700;
}
.jalaliDay.today::after {
  content: "";
  display: block;
  width: 4px;
  height: 4px;
  margin: 2px auto 0;
  border-radius: 50%;
  background: var(--ui-brand);
}
.jalaliDay.selected {
  background: var(--ui-brand) !important;
  color: var(--ui-brand-ink) !important;
}
.jalaliDay.today.selected::after {
  background: var(--ui-brand-ink);
}
.jalaliWeek {
  font-size: 0.78em;
  text-align: center;
  color: var(--ui-muted);
  padding: 5px;
}
.jalaliHoliday {
  font-size: 0.78em;
  color: var(--ui-danger);
  min-height: 20px;
  margin-top: 7px;
}
@media (max-width: 650px) {
  .themeToggle {
    left: 10px;
    bottom: 10px;
  }
  .jalaliCalendar {
    position: fixed;
    right: 4%;
    left: 4%;
    top: auto;
    bottom: 14px;
    width: 92%;
  }
}
@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

/* Shared dashboard visual language: translucent layered surfaces, a quiet
   gradient wash, and generous rhythm. It is token-based so light/dark themes
   keep the same contrast contract instead of introducing page-specific colors. */
@supports (backdrop-filter: blur(12px)) {
  .dtDashboardShell .content article,
  .dtDashboardShell .content .card,
  .dtDashboardShell .content .panel,
  .dtDashboardShell .content .metricCard,
  .dtDashboardShell .content .kpi {
    background: color-mix(in srgb, var(--ui-surface) 78%, transparent) !important;
    backdrop-filter: blur(14px) saturate(120%);
    -webkit-backdrop-filter: blur(14px) saturate(120%);
    box-shadow:
      0 18px 45px var(--ui-shadow),
      inset 0 1px 0 color-mix(in srgb, var(--ui-text) 8%, transparent);
  }
}
.dtDashboardShell .content {
  background:
    radial-gradient(
      circle at 8% 0%,
      color-mix(in srgb, var(--ui-brand) 14%, transparent),
      transparent 34%
    ),
    radial-gradient(
      circle at 92% 18%,
      color-mix(in srgb, var(--ui-info) 10%, transparent),
      transparent 30%
    ),
    var(--ui-bg);
}
.dtDashboardShell .content > .top,
.dtDashboardShell .content .sectionHead {
  gap: 1rem;
}
.dtDashboardShell .content .metricGrid,
.dtDashboardShell .content .kpis,
.dtDashboardShell .content .stack {
  gap: 1rem;
}
.dtDashboardShell .content .metricCard,
.dtDashboardShell .content .kpi,
.dtDashboardShell .content .chartCard {
  border-radius: var(--ui-radius-lg);
  border-color: color-mix(in srgb, var(--ui-brand) 22%, var(--ui-line)) !important;
}
.dtDashboardShell .content .metricCard b,
.dtDashboardShell .content .kpi b {
  /* Digits, so the tighten is welcome and is named as the digit token. */
  letter-spacing: var(--ui-track-tabular);
}
.dtDashboardShell .content .chartCard {
  padding: clamp(1rem, 2vw, 1.5rem);
  overflow: hidden;
}
.dtDashboardShell .content .chartCard h3 {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.dtDashboardShell .content .chartCard h3::before {
  content: "◈";
  color: var(--ui-brand);
  font-size: 0.85em;
}
@media (max-width: 700px) {
  .dtDashboardShell .content {
    padding-inline: 0.75rem;
  }
  .dtDashboardShell .content .metricGrid,
  .dtDashboardShell .content .kpis {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .dtDashboardShell .content .chartCard {
    padding: 0.9rem;
  }
}

/* Complete semantic theming for legacy dashboard surfaces. */
.shell,
.content,
main,
.section {
  color: var(--ui-text) !important;
}
.side,
.sidebar {
  background: var(--ui-surface-2) !important;
  border-color: var(--ui-line) !important;
}
/* An id, a token, a bot username, an email, a phone number: every one of them
   is marked `dir="ltr"` so it reads correctly in a Persian page, and every one
   of them is a single unbreakable word. In a grid cell the floor of a track is
   its widest unbreakable child, so one long token sets the width of the column,
   the column sets the width of the grid, and the page scrolls sideways -- in
   RTL that runs off the *left* edge, so what the reader loses is the start of
   every line.

   This was fixed three times on three elements and reported three times more,
   because the next screen showing an id had the same problem and no rule said
   so. One rule, at the one place the page says "this is a machine value". */
/* The server row, at the head of the platform overview.
   Gauges rather than tiles with a bar: the ring reads as an instrument, which
   is what a reader scanning for trouble is looking for, and the colour carries
   the state before any label does. */
/* The pagination control, shared by every list that has one. */
.dtPager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-block-start: 12px;
}
.dtPager .dtPageInfo {
  margin-inline-start: auto;
  font-size: 0.84em;
}
.dtPageGap {
  padding: 0 2px;
  color: var(--ui-muted);
}
button.dtPageBtn {
  min-inline-size: 34px;
  min-height: 32px !important;
  padding: 4px 9px !important;
}
.dtPageInfo {
  color: var(--ui-muted);
  font-size: 0.84em;
}

.smPanel {
  margin-block-end: 18px;
  padding: 14px 16px 16px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
}
.smHead {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-block-end: 12px;
}
.smHead h3 {
  margin: 0;
  font-size: 1.02rem;
}
.smWhen {
  font-size: 0.82em;
}
/* The refresh sits at the far end, so the heading and the state stay together
   as one sentence. */
.smHead button {
  margin-inline-start: auto;
}
.smRow {
  display: grid;
  /* 148px, not 160: the five cards need 5×min + 4×gap, and at the console's
     own content width 160 leaves the fifth one stranded on a row of its own
     with four empty columns beside it. `min(100%, …)` keeps it folding rather
     than overflowing when the pane is genuinely narrow. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 148px), 1fr));
  gap: 12px;
}
.smCard {
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: 12px 10px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
  text-align: center;
}
/* Critical states carry it on the card as well as in the ring: at a glance
   across five cards, the one to look at should be obvious without reading. */
.smCard[data-state="critical"] {
  border-color: color-mix(in srgb, var(--ui-danger) 55%, var(--ui-line));
  background: color-mix(in srgb, var(--ui-danger) 8%, var(--ui-surface-2));
}
.smCard[data-state="warn"] {
  border-color: color-mix(in srgb, var(--ui-warn) 45%, var(--ui-line));
  background: color-mix(in srgb, var(--ui-warn) 7%, var(--ui-surface-2));
}
.smCardHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  inline-size: 100%;
  font-size: 0.86em;
  color: var(--ui-muted);
}
.smGauge {
  /* A fixed ceiling, not a percentage of the card: five rings that each size
     themselves to their own text are five different instruments. */
  inline-size: 108px;
  max-inline-size: 100%;
  block-size: auto;
}
.smCard small {
  font-size: 0.78em;
  line-height: 1.6;
}
@media (max-width: 560px) {
  .smRow {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));
  }
}

/* Placeholder chips: a token you click, not a token you retype.
   They were listed as prose -- «جایگزین‌ها: {name} و {mobile}» -- which asks a
   person writing Persian to read Latin script, switch keyboard and type a
   brace correctly. The chip carries the Persian title and inserts the token at
   the cursor. */
.mkChips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 6px 0 12px;
}
.mkChipsLabel {
  color: var(--ui-muted);
  font-size: 0.85em;
}
button.mkChip {
  min-height: 30px !important;
  padding: 4px 10px !important;
  border-radius: 999px !important;
  font-size: 0.85em;
}
/* Each follow-up is one object, and looks like one.
   They were `.card` inside `.uiAccordionBody` inside the section's own card --
   three nested surfaces with the same border and the same radius, so where one
   rule ended and the next began was invisible. One surface per rule, spaced,
   numbered, and the accordion around them stays plain. */
.mkRulesHead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin: 18px 0 10px;
}
.mkRulesHead h4 {
  margin: 0 0 2px;
}
.mkRulesHead p {
  margin: 0;
  font-size: 0.86em;
}
.mkRuleList {
  display: grid;
  gap: 14px;
}
.mkRule {
  padding: clamp(0.85rem, 1.5vw, 1.15rem);
  border: 1px solid var(--ui-line);
  border-inline-start: 4px solid var(--ui-muted);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface-2);
}
.mkRule.mkRuleNew {
  border-inline-start-color: var(--ui-brand);
  background: color-mix(in srgb, var(--ui-brand) 6%, var(--ui-surface-2));
}
summary.mkRuleHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  cursor: pointer;
  /* The row is the whole hit area, and the default marker would sit before the
     number and read as a second bullet. */
  list-style: none;
}
summary.mkRuleHead::-webkit-details-marker {
  display: none;
}
summary.mkRuleHead:hover .mkRuleWho h4 {
  color: var(--ui-brand);
}
.mkRule[open] > summary.mkRuleHead {
  margin-block-end: 14px;
  padding-block-end: 12px;
  border-block-end: 1px solid var(--ui-line);
}
/* Which way the row will move when clicked. */
summary.mkRuleHead::after {
  content: "⌄";
  margin-inline-start: auto;
  color: var(--ui-muted);
  font-size: 1.1em;
  line-height: 1;
}
.mkRule[open] > summary.mkRuleHead::after {
  content: "⌃";
}
.mkRuleBody {
  min-width: 0;
}
.mkRuleWho {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-width: 0;
}
.mkRuleWho h4 {
  margin: 0 0 2px;
  font-size: 1em;
}
.mkRuleWho p {
  margin: 0;
  font-size: 0.85em;
}
/* The number is what makes a list of four rules read as four things. */
.mkRuleIndex {
  display: inline-grid;
  place-items: center;
  inline-size: 26px;
  block-size: 26px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ui-brand) 14%, var(--ui-surface));
  color: var(--ui-text);
  font-size: 0.82em;
  font-weight: 700;
  line-height: 1;
}
/* The shared ceiling is not one of the rules, so it does not sit among them. */
/* The conditions are a block within the rule, not more fields in its form. */
.mkConditions {
  margin: 14px 0;
  padding: 12px 14px;
  border: 1px dashed var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
}
.mkConditions h5 {
  margin: 0 0 8px;
  font-size: 0.92em;
  color: var(--ui-muted);
}
.mkConditions h5 + .formGrid {
  margin-block-end: 6px;
}
.mkConditions .formGrid + h5 {
  margin-block-start: 16px;
  padding-block-start: 12px;
  border-block-start: 1px solid var(--ui-line);
}

.mkCapBlock {
  margin-block-start: 22px;
  padding-block-start: 18px;
  border-block-start: 1px solid var(--ui-line);
}
.mkCapBlock h4 {
  margin: 0 0 10px;
}

[dir="ltr"] {
  min-width: 0;
  overflow-wrap: anywhere;
}

.hero,
.notice,
.audienceHint,
.audienceAll,
.questionAttempt,
.tableWrap,
fieldset {
  background: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border-color: var(--ui-line) !important;
}
h1,
h2,
h3,
h4,
strong,
b,
legend,
td {
  color: var(--ui-text);
}
p,
small,
.meta {
  color: var(--ui-muted);
}
th {
  background: var(--ui-surface-2) !important;
  color: var(--ui-text) !important;
  border-color: var(--ui-line) !important;
}
td,
tr,
table {
  border-color: var(--ui-line) !important;
}
nav button,
.secondary,
button {
  color: var(--ui-text) !important;
  border-color: var(--ui-line) !important;
}
nav button {
  background: transparent !important;
}
nav button.active,
.secondary {
  background: var(--ui-surface-2) !important;
}
.primary,
button:not(.secondary):not(.danger):not(.themeToggle):not(.jalaliDay):not(.dtMetricCard, .scLevelCard) {
  background-color: var(--ui-brand);
  color: var(--ui-brand-ink) !important;
}
/* The page stylesheets predate the light theme and still set dark backgrounds
   on these. Text and border were themed here, but the background was not, so a
   danger button, a badge and a status pill each kept a near-black fill on a
   white page. Tint them from the theme instead of naming a colour. */
.danger {
  color: var(--ui-danger) !important;
  background: color-mix(in srgb, var(--ui-danger) 12%, var(--ui-surface));
  border-color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-line)) !important;
}
.badge {
  background: color-mix(in srgb, var(--ui-brand) 14%, var(--ui-surface-2));
  color: var(--ui-text);
  border-color: var(--ui-line) !important;
}
/* No !important on the fill: this stylesheet already loads after the page's own
   <style>, so it wins on order alone -- and a variant like .statusChip.upcoming
   must still be able to paint over it, which an !important here would prevent. */
.statusChip,
.status {
  background: var(--ui-surface-2);
  color: var(--ui-text);
  /* Nor on the border, for the same reason the comment above gives about the
     fill. It stayed !important because the only variant at the time did not
     set a border-colour, so nothing noticed; the tones below all do. This
     sheet loads after the pages' own <style>, so order is enough. */
  border-color: var(--ui-line);
}
/* A bare status dot inherits nothing, so it stays legible only if it is told
   which colour it is; connected states override this with the brand colour. */
.status .dot {
  background: var(--ui-muted) !important;
}
.status.connected .dot {
  background: var(--ui-brand) !important;
}
::placeholder {
  color: color-mix(in srgb, var(--ui-muted) 76%, transparent) !important;
  opacity: 1;
}
.progress {
  background: var(--ui-surface-2) !important;
}

/* The bot linking QR. White plate regardless of theme, because a scanner
   needs the contrast the code was generated for -- a dark-inverted QR is one
   many phone cameras will not read. */
.botQr {
  background: #fff;
  padding: 12px;
  border-radius: var(--ui-radius);
  width: fit-content;
  margin: 10px 0;
}
.botQr svg {
  display: block;
  width: 180px;
  height: 180px;
}

/* A menu entry with work waiting behind it. Weight and the brand colour
   rather than a dot, so it reads the same in both themes and at any size. */
nav button.navBadge {
  font-weight: 700;
  color: var(--ui-brand) !important;
}

/* The institution quota cell, narrow because it holds a small number. */
.quotaInput {
  max-width: 120px;
}

/* The plan someone arrived from the landing page to buy, so the panel opens
   on it rather than making them find it again. */
.planChosen {
  border-color: var(--ui-brand) !important;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-brand) 30%, transparent);
}

/* The three steps for linking a challenge group, and the code itself.
   The code is read off this screen and typed into a chat, so it is set large,
   spaced and left-to-right -- a Persian-numeral rendering or a tight tracking
   would make it a transcription error waiting to happen. */
.ctSteps {
  margin: 10px 0;
  padding-inline-start: 22px;
  display: grid;
  gap: 8px;
  line-height: 2;
}
.bcCode {
  display: inline-block;
  margin-top: 6px;
  padding: 8px 14px;
  border: 1px dashed var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
  font-size: 1.15em;
  letter-spacing: 0.14em;
  user-select: all;
}

/* The curriculum tree: grade → subject → topic → sub-topic.
   Depth is a custom property rather than nested selectors, so a fourth level
   indents correctly without another rule being written for it. Layout only --
   the rows use the platform's own surface and line tokens. */
.ctRoot {
  margin: 14px 0 0;
}
.ctList {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
}
.ctNode > .ctList {
  margin-top: 6px;
}
.ctRow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 12px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
  margin-inline-start: calc(var(--depth, 0) * 18px);
}
.ctGrade > .ctRow,
.ctBranch > .ctRow {
  background: var(--ui-surface-2);
}
/* Every level is a <details>, so the whole curriculum arrives collapsed and a
   full one -- three stages, thirteen years, three streams each, a dozen
   subjects apiece -- stops rendering as a column thousands of rows long.
   The marker is drawn on the row itself: summary's own triangle sits outside
   the flex layout and lands in the wrong corner under RTL. */
.ctBranch > summary.ctRow {
  cursor: pointer;
  list-style: none;
}
.ctBranch > summary.ctRow::-webkit-details-marker {
  display: none;
}
.ctBranch > summary.ctRow .ctLabel::before {
  content: "+";
  display: inline-grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  margin-inline-end: 0.45rem;
  border: 1px solid var(--ui-line);
  border-radius: 0.3rem;
  color: var(--ui-muted);
  font-family: monospace;
  line-height: 1;
}
.ctBranch[open] > summary.ctRow .ctLabel::before {
  content: "−";
  color: var(--ui-brand);
  border-color: var(--ui-brand);
}
.ctBranch > summary.ctRow:hover {
  border-color: var(--ui-brand);
}
/* A leaf keeps the row's alignment without claiming to open anything. */
.ctLeaf > .ctRow {
  padding-inline-start: calc(12px + 1.6rem);
}
/* The box scrolls; the page does not grow with the curriculum. */
.ctScroll {
  max-height: min(60vh, 34rem);
  overflow: auto;
  padding: 4px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface-2);
}
.ctLabel {
  min-width: 0;
  overflow-wrap: anywhere;
}
.ctEmpty {
  color: var(--ui-muted);
  font-size: 0.84em;
  padding: 4px 12px;
  margin-inline-start: calc((var(--depth, 0) + 1) * 18px);
}

/* The study plan's target-vs-actual cell: the bar and its percentage sit on
   one line and read as a single value. Layout only -- the bar keeps the
   platform's own colours. */
.planCell {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 150px;
}
.planCell .progress {
  flex: 1;
}

/* The factory's produced questions: a numbered read-through, so an
   administrator can see what arrived before opening the bank to review it. */
/* Reviewing what the question factory made (ai-questions.js): each question
   as it will read, options lettered, the correct one marked in words as well
   as colour, and an edit form in place. */
.aiqItems {
  display: grid;
  gap: var(--ui-space-3);
}
.aiqQ,
.aiqEdit {
  display: grid;
  gap: var(--ui-space-2);
  padding: var(--ui-space-3);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
}
.aiqQHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.aiqStem {
  margin: 0;
  white-space: pre-wrap;
  line-height: 1.9;
}
.aiqOptions {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}
.aiqOptions li {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.aiqOptions li.aiqRight {
  border-color: var(--ui-brand);
  background: color-mix(in srgb, var(--ui-brand) 10%, var(--ui-surface));
}
.aiqLetter {
  flex: none;
  min-width: 1.6rem;
  font-weight: 700;
  color: var(--ui-muted);
}
.aiqMark {
  margin-inline-start: auto;
  flex: none;
  font-size: 0.8em;
  font-weight: 700;
  color: var(--ui-brand);
}
.aiqWhy summary {
  cursor: pointer;
}
.aiqEOptions {
  display: grid;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  border: 0;
}
.aiqEOption {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.aiqEOption input:not([type="radio"]) {
  flex: 1 1 auto;
  min-width: 0;
}
.aiqList {
  margin: 12px 0 0;
  padding-inline-start: 22px;
  display: grid;
  gap: 8px;
  line-height: 1.9;
}
.aiqList li {
  padding-inline-start: 4px;
}

/* A card that goes somewhere should look like it does. */
/* The base for a metric grid lived in each page's own <style>, so a page that
   did not carry a copy -- the public registration page -- rendered one as plain
   stacked text. Written with :where() so it has no specificity at all: a page
   that already styles these keeps its own numbers exactly, and a page that does
   not gets something to look at. */
:where(.metricGrid) {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9.5rem), 1fr));
}
:where(.metricCard) {
  display: grid;
  gap: 0.35rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
}
:where(.metricCard) span {
  color: var(--ui-muted);
  font-size: 0.85em;
}
:where(.metricCard) b {
  font-size: 1.4em;
  color: var(--ui-brand);
}
/* A row of insertable tokens under a template box. Left to .actions alone it
   wraps into a wall of identical buttons; these are labels to scan, so they
   stay small, keep their Latin braces the right way round, and sit closer
   together than a row of real actions would. */
/* A ceiling and its editor in one table cell: the state above, the number and
   its save button below, so the column stays narrow enough to sit beside the
   contact details. */
/* The three ways to pay, side by side and each saying the same three things:
   how many payments, what the total is, and when the first is due. They were a
   radio with a sentence beside it, and only the option already chosen carried a
   figure -- which is not a comparison. */
.payOption {
  /* align-items does nothing without a flex display, and this rule had none:
     the radio and the method's description were inline content, so in an RTL
     paragraph the bidi algorithm decided where the radio went. */
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.85rem 0.9rem;
}
.payOption > span {
  display: grid;
  gap: 0.25rem;
  min-width: 0;
}
.payOption b {
  font-size: 1.02em;
}
.payOption small {
  color: var(--ui-muted);
}
.payFigures {
  display: grid;
  gap: 0.15rem;
  margin-top: 0.25rem;
}
.payFigures i {
  font-style: normal;
  font-size: 0.85em;
  color: var(--ui-text);
  font-variant-numeric: tabular-nums;
}
.payFigures .payWarn {
  color: var(--ui-warn);
}

/* The colour key under a chart. Wraps, because nine Persian grade names do not
   fit on one line and a legend that scrolls sideways is a legend nobody reads.
   The swatch is a solid block rather than a dot: at this size a dot of a hue is
   not enough of it to recognise. */
.chartKeys {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  margin-top: 0.7rem;
}
.chartKey {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--ui-muted);
  font-size: 0.85em;
}
.chartKey i {
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 0.25rem;
  flex: 0 0 auto;
}
.chartKey b {
  color: var(--ui-text);
  font-variant-numeric: tabular-nums;
}

/* A row whose moment has come. Tinted rather than coloured over, so the text
   keeps the contrast the palette was measured for. */
.rowDue > td {
  background: color-mix(in srgb, var(--ui-warn) 12%, transparent);
}
.capacityCell {
  display: grid;
  gap: 0.3rem;
  min-width: 11rem;
}
.capacityCell .actions {
  margin-top: 0;
  flex-wrap: nowrap;
}
.capacityCell input {
  max-width: 6rem;
}
.placeholderBar {
  gap: 0.35rem;
  margin: 0.35rem 0 0.5rem;
}
.placeholderBar button {
  direction: ltr;
  font-size: 0.82em;
  font-weight: 400;
}
.metricCard.actionable {
  cursor: pointer;
  transition:
    transform 0.15s ease,
    border-color 0.15s ease;
}
.metricCard.actionable:hover,
.metricCard.actionable:focus-visible {
  transform: translateY(-2px);
  border-color: var(--ui-brand);
}
/* Where a jump landed. Smooth scrolling through a page of similar cards
   otherwise leaves you unsure which one you were sent to. */
.jumpTarget {
  outline: 2px solid var(--ui-brand);
  outline-offset: 4px;
  border-radius: var(--ui-radius);
  transition: outline-color 0.4s ease;
}
@media (prefers-reduced-motion: reduce) {
  .metricCard.actionable {
    transition: none;
  }
  .metricCard.actionable:hover,
  .metricCard.actionable:focus-visible {
    transform: none;
  }
}

/* Booking slots as a calendar, not a stack.
   One full-width card per day put the fifth day below the fold to hold two
   buttons, so a student could not see the week they were choosing from. Tiles
   put the whole week on one screen and let the eye run down the weekday names.
   Colours are the platform's own tokens; this rule is layout. */
.calGrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 160px), 1fr));
  gap: 12px;
  margin-top: 14px;
}
.calDay {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
}
.calDay > header {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--ui-line);
}
.calDay > header b {
  font-size: 1.05em;
}
.calDay > header span,
.calDay > small {
  color: var(--ui-muted);
  font-size: 0.82em;
}
.calSlots {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}
/* The time is the whole label, so these do not need a button's usual width. */
.calSlots button {
  flex: 1 1 auto;
  min-width: 68px;
  justify-content: center;
}
@media (max-width: 520px) {
  .calGrid {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 135px), 1fr));
  }
}

/* Compact, aligned checkbox rows. */
.checks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: 8px 12px;
  align-items: start;
}
.check,
label:has(> input[type="checkbox"]) {
  display: inline-flex !important;
  align-items: center !important;
  justify-content: flex-start !important;
  gap: 9px !important;
  min-height: 40px;
  margin: 4px 0 !important;
  width: auto !important;
  color: var(--ui-text) !important;
}
.check > span,
label:has(> input[type="checkbox"]) > span {
  flex: 1;
  line-height: 1.65;
}
.check input[type="checkbox"],
label:has(> input[type="checkbox"]) input[type="checkbox"] {
  flex: 0 0 auto !important;
  margin: 0 !important;
}
/* A tick box is not a text field.
   
   This is what three reports about «the checkbox is in the middle of the
   word» actually were, and the display was never the problem. Every page
   sizes its text inputs with a shared rule, and the platform-admin page's
   copy is `input, select, textarea { width: 100% }` with no exclusion --
   so a checkbox inside a chip was stretched to the chip's full width, spilled
   out of it, and landed on top of the next label's text. The radio buttons on
   that page had already been repaired one by one; the checkboxes had not.
   
   Sized here rather than in each page, because the next page somebody writes
   will copy that shared rule too. Only `.choiceOption` -- `.check` carries an
   appearance:none switch on some pages and is not this to fix. */
.choiceOption input[type="checkbox"] {
  inline-size: 1.15rem !important;
  block-size: 1.15rem;
  min-block-size: 0;
  flex: 0 0 auto !important;
  accent-color: var(--ui-brand);
}

.choiceOption {
  /* The display belongs on the same rule as the justify-content, and with the
     same weight.
     
     These two were separated -- justify-content here, with !important, and no
     display anywhere -- which is a declaration that cannot do anything, on a
     selector that looks like it already decided the layout. A later rule did
     add `display: flex`, and in every browser this was tested in that rule
     won; on the screen that kept reporting the fault it evidently did not.
     Rather than keep guessing which cascade beat it, the property that makes
     the other one meaningful now sits beside it and carries the same weight.
     
     This is the one place in this stylesheet where !important on a display is
     the honest choice: the adjacent declaration is already !important, and a
     tick box that lands in the middle of its own Persian label is not a
     cosmetic difference -- inline children are ordered by the bidi algorithm,
     so the box goes wherever the surrounding text direction puts it. */
  display: flex !important;
  align-items: center;
  gap: 0.5rem;
  justify-content: flex-start !important;
}
.choiceOption input {
  margin: 0 !important;
}
.choiceOption span,
.choiceOption div {
  flex: 1;
}

/* Component-specific button colors override the legacy broad button rule. */
.sidebar nav.nav button,
.side nav button {
  background: transparent !important;
  color: var(--ui-muted) !important;
  border-color: transparent !important;
  box-shadow: none !important;
}
.sidebar nav.nav button:hover,
.side nav button:hover {
  background: color-mix(in srgb, var(--ui-brand) 10%, var(--ui-surface-2)) !important;
  color: var(--ui-text) !important;
}
.sidebar nav.nav button.active,
.side nav button.active {
  background: color-mix(in srgb, var(--ui-brand) 16%, var(--ui-surface-2)) !important;
  color: var(--ui-text) !important;
  border-color: color-mix(in srgb, var(--ui-brand) 34%, var(--ui-line)) !important;
}
.tabs button {
  background: transparent !important;
  color: var(--ui-muted) !important;
  border-color: transparent !important;
}
.tabs button.active,
.tabs .active {
  background: var(--ui-brand) !important;
  color: var(--ui-brand-ink) !important;
}
.wide:not(.secondary),
button.primary {
  background: var(--ui-brand) !important;
  color: var(--ui-brand-ink) !important;
  border-color: transparent !important;
}
.secondary {
  background: var(--ui-surface-2) !important;
  color: var(--ui-text) !important;
}
.logout {
  background: var(--ui-surface-2) !important;
  color: var(--ui-text) !important;
  border-color: var(--ui-line) !important;
}
.jalaliHead button,
.selectionToolbar button,
.dtEditorBar button {
  background: var(--ui-surface-2) !important;
  color: var(--ui-text) !important;
  border-color: var(--ui-line) !important;
}

/* Theme-aware checkbox/switch rows replace legacy fixed navy tiles. */
.check,
label.check {
  background: var(--ui-surface-2) !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
  border-radius: 11px !important;
  padding: 9px 11px !important;
}
.check:has(input:checked),
label.check:has(input:checked) {
  background: color-mix(in srgb, var(--ui-brand) 10%, var(--ui-surface)) !important;
  border-color: color-mix(in srgb, var(--ui-brand) 42%, var(--ui-line)) !important;
}
.check > span,
label.check > span {
  color: var(--ui-text) !important;
}
.sidebar
  button.logout:not(.primary):not(.secondary):not(.danger):not(.themeToggle):not(.jalaliDay) {
  background: var(--ui-surface-2) !important;
  color: var(--ui-text) !important;
  border-color: var(--ui-line) !important;
}

/* High-specificity repairs for legacy button selectors and the login surface. */
.box {
  background: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border-color: var(--ui-line) !important;
  box-shadow: 0 25px 70px var(--ui-shadow) !important;
}
#otpTab:not(.active),
#passTab:not(.active) {
  background: transparent !important;
  color: var(--ui-muted) !important;
  border-color: transparent !important;
}
.shell
  .sidebar
  nav.nav
  button:not(.primary):not(.secondary):not(.danger):not(.themeToggle):not(.jalaliDay),
.shell
  .side
  nav
  button:not(.primary):not(.secondary):not(.danger):not(.themeToggle):not(.jalaliDay) {
  background: transparent !important;
  color: var(--ui-muted) !important;
  border-color: transparent !important;
}
.shell
  .sidebar
  nav.nav
  button.active:not(.primary):not(.secondary):not(.danger):not(.themeToggle):not(.jalaliDay),
.shell
  .side
  nav
  button.active:not(.primary):not(.secondary):not(.danger):not(.themeToggle):not(.jalaliDay) {
  background: color-mix(in srgb, var(--ui-brand) 16%, var(--ui-surface-2)) !important;
  color: var(--ui-text) !important;
  border-color: color-mix(in srgb, var(--ui-brand) 34%, var(--ui-line)) !important;
}

/* Jalali date-time fields keep the API value hidden and expose Persian date + Tehran time. */
.jalaliDateTime {
  display: grid;
  grid-template-columns: minmax(min(100%, 190px), 1fr) minmax(min(100%, 108px), 0.42fr);
  gap: 8px;
  align-items: start;
}
.jalaliDateTime .jalaliWrap {
  min-width: 0;
}
.jalaliDateTime .jalaliTime {
  direction: ltr;
  text-align: center;
}
.jalaliDateTime .jalaliZone {
  grid-column: 1/-1;
  margin: 0 2px;
}
.jalaliDateOnly {
  display: block;
}
@media (max-width: 520px) {
  .jalaliDateTime {
    grid-template-columns: 1fr;
  }
  .jalaliDateTime .jalaliZone {
    grid-column: auto;
  }
}

/* Message templates are independent cards with clear internal rhythm. Every
   card is the height of the tallest, so the grid reads as rows rather than a
   staircase; the editor takes the slack and the actions sit on the bottom. */
#templatePanel {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 420px), 1fr));
  grid-auto-rows: 1fr;
  gap: 18px;
  align-items: stretch;
  padding-top: 14px;
}
#templatePanel .templateCard {
  display: flex;
  flex-direction: column;
}
#templatePanel .templateCard .dtEditor {
  flex: 1;
}
#templatePanel .templateActions {
  margin-top: auto;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
/* The one bar the templates fold into. */
.templateFold {
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
  padding: 14px 18px;
  margin-bottom: 18px;
}
.templateFold > summary {
  cursor: pointer;
  font-weight: 700;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}
.templateFold > summary::-webkit-details-marker {
  display: none;
}
.templateFold > summary::after {
  content: '▾';
  color: var(--ui-muted);
  transition: transform 0.2s;
}
.templateFold[open] > summary::after {
  transform: rotate(180deg);
}
/* The sent-SMS log: full width, its own vertical scroll, the header stays. */
.smsLogScroll {
  max-height: 70vh;
  overflow: auto;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.smsLogScroll thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--ui-surface-2);
}
.smsLogText {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 6px 0 0;
  font: inherit;
  font-size: 0.86rem;
  max-width: 38ch;
}
.templateCard {
  display: grid;
  gap: 13px;
  margin: 0 !important;
  min-width: 0;
  overflow: hidden;
}
.templateCardHead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--ui-line);
}
.templateCardHead h3 {
  margin: 0 0 4px;
}
.templatePlaceholders {
  display: grid;
  gap: 8px;
  padding: 12px;
  border-radius: 12px;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
}
.templatePlaceholders > div {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}
.templatePlaceholders code {
  direction: rtl;
  unicode-bidi: plaintext;
  padding: 5px 8px;
  border-radius: 8px;
  background: var(--ui-field);
  color: var(--ui-text);
  border: 1px solid var(--ui-line);
}
.templateCard .dtEditor {
  width: 100%;
  min-width: 0;
  margin: 0;
}
.templateCard .dtEditorPreview {
  max-height: 150px;
  overflow: auto;
}
.templateActions {
  display: flex;
  justify-content: flex-end;
  padding-top: 2px;
}
.templateActions button {
  margin: 0 !important;
}
@media (max-width: 650px) {
  #templatePanel {
    grid-template-columns: 1fr;
  }
  .templateCard {
    padding: 16px !important;
  }
  .templateCardHead {
    align-items: stretch;
    flex-direction: column;
  }
  .checks {
    grid-template-columns: 1fr;
  }
}

/* Admin overview and notification settings use semantic theme surfaces. */
.summary .kpi,
#summary .kpi {
  background: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
  box-shadow: 0 10px 28px var(--ui-shadow) !important;
}
.kpi span {
  color: var(--ui-muted) !important;
}
.kpi b {
  color: var(--ui-text) !important;
}
.notificationGrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
  gap: 10px;
  margin: 14px 0;
}
.notificationGrid .check {
  width: 100% !important;
  margin: 0 !important;
  justify-content: flex-start !important;
}
.notificationGrid .check span {
  color: var(--ui-text) !important;
}
.templateCard .dtEditorPreview {
  display: none !important;
}
@media (max-width: 650px) {
  .notificationGrid {
    grid-template-columns: 1fr;
  }
}

/* Student answer review. */
.reviewModal {
  width: min(920px, 100%) !important;
}
.reviewQuestion {
  display: grid;
  gap: 14px;
  padding: 18px;
  border: 1px solid var(--ui-line);
  border-radius: 16px;
  background: var(--ui-surface-2);
}
.reviewQuestion > header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}
.reviewOptions {
  display: grid;
  gap: 8px;
}
.reviewOption {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 13px;
  border: 1px solid var(--ui-line);
  border-radius: 11px;
  background: var(--ui-surface);
}
.reviewOption.correct {
  border-color: color-mix(in srgb, var(--ui-brand) 55%, var(--ui-line));
  background: color-mix(in srgb, var(--ui-brand) 9%, var(--ui-surface));
}
.reviewOption.selected:not(.correct) {
  border-color: color-mix(in srgb, var(--ui-danger) 55%, var(--ui-line));
}
.reviewOption small {
  white-space: nowrap;
}
.reviewExplanation {
  padding: 13px;
  border-radius: 12px;
  background: var(--ui-surface);
  border-inline-start: 4px solid var(--ui-brand);
}
.reviewExplanation p {
  margin: 6px 0 0;
  white-space: pre-wrap;
}
@media (max-width: 650px) {
  .reviewQuestion > header,
  .reviewOption {
    flex-direction: column;
  }
  .reviewOption small {
    white-space: normal;
  }
}

/* Staff-facing student learning insights. */
/* The student insight cards became a table; .topicInsight small is all that
   still has a reader. Two !important declarations left with them. */
.topicInsight small {
  color: var(--ui-muted);
}
.insightDetail {
  display: grid;
  gap: 14px;
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--ui-line);
}
.topicInsight {
  display: grid;
  grid-template-columns: minmax(min(100%, 160px), 1fr) minmax(min(100%, 150px), 2fr) auto;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
}
.topicInsight > div:first-child {
  display: grid;
  gap: 3px;
}
@media (max-width: 650px) {
  .topicInsight {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .topicInsight .progress {
    grid-column: 1/-1;
  }
}

/* Student guidance workspace. */
.guidanceCase {
  width: min(1050px, 100%) !important;
}
.guidanceColumns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
  margin: 18px 0;
}
.guidanceColumns > section {
  padding: 16px;
  border: 1px solid var(--ui-line);
  border-radius: 16px;
  background: var(--ui-surface-2);
}
.guidanceColumns textarea {
  min-height: 110px !important;
}
.guidanceCase fieldset {
  margin: 16px 0;
}
@media (max-width: 760px) {
  .guidanceColumns {
    grid-template-columns: 1fr;
  }
}

/* Role-focused mentor/counselor workspace. */
.guidanceWorkspaceGrid {
  display: grid;
  /* Two columns while both have room, one when they do not. The fixed
     0.9fr/1.1fr split held the pair side by side at every width above the
     phone breakpoint, so on a laptop the reports table -- four columns and a
     date -- was squeezed into half a dashboard and scrolled inside its own
     card, next to a reminders card that was usually empty. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 23rem), 1fr));
  gap: 16px;
}
.guidanceFilterRow {
  display: flex;
  align-items: end;
  gap: 14px;
  flex-wrap: wrap;
}
.guidanceFilterRow > div {
  flex: 1;
  min-width: 240px;
}
.guidanceTable td:first-child b,
.guidanceTable td:first-child small {
  display: block;
}
.guidanceTable td:first-child small {
  margin-top: 4px;
  color: var(--ui-muted);
}
.reminderItem {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--ui-line);
  border-radius: 14px;
  background: var(--ui-surface-2);
  margin-top: 9px;
}
.reminderItem > div {
  display: grid;
  gap: 4px;
}
.reminderItem span,
.reminderItem small {
  color: var(--ui-muted);
}
.reminderItem.due {
  border-color: color-mix(in srgb, var(--ui-danger) 55%, var(--ui-line));
  background: color-mix(in srgb, var(--ui-danger) 8%, var(--ui-surface));
}
.metricCard.warning {
  background: color-mix(in srgb, var(--ui-warn) 10%, var(--ui-surface)) !important;
  border-color: color-mix(in srgb, var(--ui-warn) 45%, var(--ui-line)) !important;
}
.metricCard.danger {
  background: color-mix(in srgb, var(--ui-danger) 10%, var(--ui-surface)) !important;
  border-color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-line)) !important;
}
.compact {
  min-height: 38px !important;
  padding: 7px 11px !important;
  margin: 0 !important;
}
.jalaliCalendarFoot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 9px;
  padding-top: 9px;
  border-top: 1px solid var(--ui-line);
}
.jalaliCalendarFoot .jalaliToday {
  min-height: 38px !important;
  padding: 6px 14px !important;
  margin: 0 !important;
}
.jalaliCalendarFoot .jalaliHoliday {
  margin: 0;
  min-height: 0;
}
@media (max-width: 850px) {
  .guidanceWorkspaceGrid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 620px) {
  .reminderItem {
    align-items: stretch;
    flex-direction: column;
  }
  .reminderItem button {
    width: 100%;
  }
  .guidanceFilterRow {
    align-items: stretch;
  }
}

/* Exam lifecycle, grouping, charts and menu theme control. */
.questionGroup {
  grid-column: 1/-1;
  padding: 14px !important;
  border-radius: 15px !important;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 9px;
}
.questionGroup legend {
  display: flex;
  align-items: center;
  gap: 8px;
}
.questionGroup legend span {
  color: var(--ui-muted);
  font-size: 0.8em;
  font-weight: 400;
}
.studentExamTable td > b,
.studentExamTable td > small,
.studentExamTable td > span {
  display: block;
}
.studentExamTable td > small {
  margin-top: 5px;
  color: var(--ui-muted);
}
.statusChip.upcoming {
  background: color-mix(in srgb, #f5a524 17%, var(--ui-surface-2));
  color: var(--ui-warn);
}
.statusChip.expired {
  background: color-mix(in srgb, var(--ui-muted) 13%, var(--ui-surface-2));
  color: var(--ui-muted);
}
.examCountdown {
  font-variant-numeric: tabular-nums;
  color: var(--ui-brand) !important;
}
.analyticsChart .topicRow {
  padding: 8px;
  border-radius: 10px;
  background: var(--ui-surface-2);
}
.side .themeToggle.menuThemeToggle,
.sidebar .themeToggle.menuThemeToggle {
  position: static !important;
  inset: auto !important;
  width: 100% !important;
  height: auto !important;
  min-height: 44px;
  border-radius: 11px !important;
  margin-top: auto !important;
  margin-bottom: 8px !important;
  text-align: right;
  padding: 10px 13px !important;
  box-shadow: none !important;
  background: var(--ui-surface-2) !important;
}
.side:has(.menuThemeToggle),
.sidebar:has(.menuThemeToggle) {
  display: flex !important;
  flex-direction: column;
}
.side:has(.menuThemeToggle) nav,
.sidebar:has(.menuThemeToggle) nav {
  flex: 1;
}
@media (max-width: 720px) {
  .questionGroup {
    grid-template-columns: 1fr;
  }
  .studentExamTable {
    min-width: 720px;
  }
}

/* Aggregate item analysis: readable in RTL, responsive, and theme-safe. */
.psychometrics {
  display: grid;
  gap: 14px;
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--ui-line);
}
.psychometricTable {
  min-width: 940px;
}
.psychometricTable td {
  vertical-align: top;
}
.psychometricTable td > b,
.psychometricTable td > small {
  display: block;
  max-width: 330px;
}
.psychometricTable td > small {
  margin-top: 6px;
  color: var(--ui-muted);
}
.psychometricMeter {
  width: 140px;
  height: 9px;
  margin: 4px 0 7px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
}
.psychometricMeter i {
  display: block;
  height: 100%;
  background: var(--ui-brand);
}
.qualityChip {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 4px 9px;
  border: 1px solid var(--ui-line);
  border-radius: 999px;
  font-size: 0.82em;
  white-space: nowrap;
  background: var(--ui-surface-2);
  color: var(--ui-text);
}
.qualityChip.good,
.qualityChip.functional,
.qualityChip.correct {
  border-color: color-mix(in srgb, var(--ui-brand) 52%, var(--ui-line));
  background: color-mix(in srgb, var(--ui-brand) 11%, var(--ui-surface));
}
.qualityChip.imbalanced,
.qualityChip.weak_discrimination,
.qualityChip.non_functioning {
  border-color: color-mix(in srgb, #f5a524 52%, var(--ui-line));
  background: color-mix(in srgb, #f5a524 10%, var(--ui-surface));
}
.qualityChip.critical,
.qualityChip.misleading {
  border-color: color-mix(in srgb, var(--ui-danger) 55%, var(--ui-line));
  background: color-mix(in srgb, var(--ui-danger) 9%, var(--ui-surface));
}
.distractorDetails {
  min-width: 160px;
}
.distractorDetails summary {
  cursor: pointer;
  min-height: 44px;
  padding: 10px;
  border-radius: 10px;
  color: var(--ui-text);
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
}
.distractorDetails[open] {
  position: relative;
}
.distractorList {
  display: grid;
  gap: 8px;
  width: min(480px, 75vw);
  margin-top: 8px;
}
.distractorItem {
  display: grid;
  gap: 7px;
  padding: 10px;
  border: 1px solid var(--ui-line);
  border-radius: 11px;
  background: var(--ui-surface);
}
.distractorItem > div {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}
.distractorItem small {
  color: var(--ui-muted);
}
.inlineError {
  padding: 12px;
  border: 1px solid color-mix(in srgb, var(--ui-danger) 55%, var(--ui-line));
  border-radius: 12px;
  color: var(--ui-danger);
  background: color-mix(in srgb, var(--ui-danger) 8%, var(--ui-surface));
}
@media (max-width: 720px) {
  .psychometricTable {
    min-width: 0;
  }
  .psychometricTable thead {
    display: none;
  }
  .psychometricTable,
  .psychometricTable tbody,
  .psychometricTable tr,
  .psychometricTable td {
    display: block;
    width: 100%;
  }
  .psychometricTable tr {
    display: grid;
    gap: 10px;
    padding: 14px;
    border-bottom: 1px solid var(--ui-line);
  }
  .psychometricTable td {
    display: grid;
    grid-template-columns: 110px minmax(0, 1fr);
    gap: 8px;
    padding: 4px !important;
    border: 0 !important;
  }
  .psychometricTable td:before {
    content: attr(data-label);
    color: var(--ui-muted);
  }
  .psychometricTable td > * {
    grid-column: 2;
  }
  .distractorDetails {
    grid-column: 1/-1 !important;
  }
  .distractorList {
    width: 100%;
  }
}

/* Validation, loading feedback and quick Jalali navigation. */
input.fieldInvalid,
select.fieldInvalid,
textarea.fieldInvalid {
  border-color: var(--ui-danger) !important;
  background: color-mix(in srgb, var(--ui-danger) 7%, var(--ui-field)) !important;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-danger) 18%, transparent) !important;
}
.fieldError {
  display: block;
  margin-top: 6px;
  color: var(--ui-danger) !important;
  font-size: 0.84em;
  line-height: 1.65;
}
.isLoading {
  position: relative !important;
  pointer-events: none !important;
  cursor: wait !important;
  padding-inline-start: 42px !important;
  opacity: 0.88;
}
.isLoading:before {
  content: "";
  position: absolute;
  inset-inline-start: 14px;
  top: 50%;
  width: 17px;
  height: 17px;
  margin-top: -9px;
  border: 2px solid currentColor;
  border-inline-start-color: transparent;
  border-radius: 50%;
  animation: dtSpin 0.75s linear infinite;
}
@keyframes dtSpin {
  to {
    transform: rotate(360deg);
  }
}
.jalaliQuick {
  display: grid;
  grid-template-columns: minmax(min(100%, 105px), 1fr) minmax(min(100%, 90px), 0.8fr);
  gap: 6px;
  flex: 1;
}
.jalaliQuick select {
  min-height: 40px !important;
  padding: 6px 8px !important;
  margin: 0 !important;
  text-align: center;
}
.jalaliHead {
  gap: 7px;
}
.jalaliHead > button {
  flex: 0 0 42px;
}
@media (max-width: 420px) {
  .jalaliCalendar {
    padding: 10px;
  }
  .jalaliQuick {
    grid-template-columns: minmax(0, 1fr) 90px;
  }
  .jalaliHead > button {
    flex-basis: 38px;
    min-width: 38px !important;
  }
}
@media (prefers-reduced-motion: reduce) {
  .isLoading:before {
    animation-duration: 1.5s;
  }
}

/* Student performance trend with aggregate class and institution benchmarks. */
.studentTrend {
  display: grid;
  gap: 14px;
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--ui-line);
}
.trendLegend {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}
.trendLegend span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--ui-muted);
}
.trendLegend i {
  width: 24px;
  height: 4px;
  border-radius: 9px;
  background: currentColor;
}
.trendStudent {
  color: var(--ui-brand);
  stroke: var(--ui-brand);
  fill: var(--ui-brand);
}
.trendClass {
  color: #f5a524;
  stroke: #f5a524;
}
.trendInstitution {
  color: #8b7cf6;
  stroke: #8b7cf6;
}
.trendChartWrap {
  overflow: auto;
  min-width: 0;
  padding: 10px;
  border: 1px solid var(--ui-line);
  border-radius: 14px;
  background: var(--ui-surface-2);
}
.trendChart {
  display: block;
  /* Both bounds. width:100% with no ceiling let a 760-wide drawing render at
     1500 on a desktop, which multiplies every stroke and every label by two --
     the chart filled the screen and its axis text came out larger than the
     headings around it. min-width keeps it readable on a phone, where the wrap
     scrolls. */
  min-width: 620px;
  width: 100%;
  max-width: 760px;
  height: auto;
  margin-inline: auto;
}
.trendChart line {
  stroke: var(--ui-line);
  stroke-width: 1;
}
.trendChart text {
  fill: var(--ui-muted);
  font-family: var(--app-font);
  font-size: 12px;
  /* Same rtl trap as .chartGrid text above: without direction:ltr the axis
     labels start at the right margin and run out of the viewBox. */
  direction: ltr;
  text-anchor: end;
}
.trendChart polyline {
  fill: none;
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.trendTable {
  min-width: 720px;
}
@media (max-width: 650px) {
  .trendChart {
    min-width: 540px;
  }
  .trendLegend {
    gap: 10px;
  }
}

.studyRecommendations {
  display: grid;
  gap: 14px;
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--ui-line);
}
.recommendationGrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 12px;
}
.recommendationCard {
  display: grid;
  gap: 12px;
  padding: 15px;
  border: 1px solid var(--ui-line);
  border-radius: 15px;
  background: var(--ui-surface-2);
}
.recommendationCard header,
.recommendationCard footer {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}
.recommendationCard header > div {
  display: grid;
  gap: 4px;
}
.recommendationCard small,
.recommendationCard footer span {
  color: var(--ui-muted);
}
.recommendationCard p {
  margin: 0;
  line-height: 1.8;
}
.recommendationCard.urgent {
  border-color: color-mix(in srgb, var(--ui-danger) 55%, var(--ui-line));
}
.recommendationCard.high {
  border-color: color-mix(in srgb, #f5a524 55%, var(--ui-line));
}
.qualityChip.urgent {
  color: var(--ui-danger);
}
.qualityChip.high {
  color: var(--ui-warn);
}
.qualityChip.medium {
  color: var(--ui-info);
}
.qualityChip.maintain {
  color: var(--ui-brand);
}
@media (max-width: 560px) {
  .recommendationCard header,
  .recommendationCard footer {
    flex-direction: column;
  }
}

.reportActions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.reportActions button {
  margin: 0 !important;
}
.classReportToolbar {
  margin-bottom: 16px;
}
.classReportToolbar .formGrid {
  margin: 12px 0;
}
@media (max-width: 560px) {
  .reportActions {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .reportActions button {
    width: 100%;
  }
}

/* Versioned Iranian official-holiday calendar. */
.jalaliHoliday {
  display: grid;
  gap: 3px;
  line-height: 1.55;
}
.jalaliHoliday small {
  color: var(--ui-muted);
  font-size: 0.72rem;
}
.jalaliDay {
  position: relative;
}
.jalaliDay.holiday:after {
  content: "•";
  position: absolute;
  inset-inline-end: 4px;
  inset-block-start: 1px;
  color: var(--ui-danger);
  font-weight: 900;
}
/* Student AI tutor, as a chat (ai-tutor.js; owner, 1405-07-13). Past
   conversations down the side as a plain list; the conversation fills the
   rest: title, today's allowance as a bar right on top of it, messages that
   scroll, and the box at the bottom. Mine at the end side, the assistant's at
   the start beside its mark. An id's weight where a global button rule with
   !important has to be beaten. */
.aiTutorShell {
  display: grid;
  grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
  /* The height is set to the screen by ai-tutor.js fit(); this is the
     first frame's guess before it runs. */
  height: calc(100dvh - 250px);
  min-height: 420px;
  border: 1px solid var(--ui-line);
  border-radius: 22px;
  overflow: hidden;
  background: var(--ui-surface);
  box-shadow: 0 16px 42px var(--ui-shadow);
}
.aiSessions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
  padding: 14px;
  border-inline-end: 1px solid var(--ui-line);
  background: var(--ui-surface-2);
}
.aiNewChat:not(#dt-none) {
  display: flex;
  width: 100%;
  justify-content: center;
}
.aiSessionsTitle {
  font-size: 0.78em;
  font-weight: 700;
  color: var(--ui-muted);
  padding-inline: 6px;
}
.aiSessionList {
  display: grid;
  gap: 2px;
  align-content: start;
  overflow: auto;
  flex: 1 1 auto;
  min-height: 0;
}
.aiSessionItem:not(#dt-none) {
  display: grid;
  gap: 2px;
  justify-items: start;
  text-align: start;
  width: 100%;
  min-height: 0 !important;
  padding: 8px 10px !important;
  border: 1px solid transparent !important;
  border-radius: 12px !important;
  background: transparent !important;
  color: var(--ui-text) !important;
  box-shadow: none !important;
  font-weight: 400 !important;
}
.aiSessionItem:not(#dt-none):hover {
  background: color-mix(in srgb, var(--ui-brand) 8%, var(--ui-surface-2)) !important;
}
.aiSessionItem.active:not(#dt-none) {
  background: color-mix(in srgb, var(--ui-brand) 14%, var(--ui-surface)) !important;
  border-color: color-mix(in srgb, var(--ui-brand) 35%, var(--ui-line)) !important;
}
.aiSessionItem b {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.aiSessionItem small,
.aiNoSessions {
  color: var(--ui-muted);
  font-size: 0.78em;
}
.aiRevoke:not(#dt-none) {
  margin-top: auto;
  min-height: 0 !important;
  padding: 6px 10px !important;
  background: transparent !important;
  color: var(--ui-danger) !important;
  border: 1px solid color-mix(in srgb, var(--ui-danger) 45%, var(--ui-line)) !important;
  box-shadow: none !important;
  font-size: 0.85em !important;
}
.aiChat {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.aiChatHead,
.aiComposeFoot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.aiChatHead {
  padding: 12px 18px;
  border-bottom: 1px solid var(--ui-line);
}
.aiChatHead > div {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.aiChatHead b {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.aiChatHead small {
  color: var(--ui-muted);
}
/* Today's allowance, flush on top of the conversation. */
.aiQuota {
  display: grid;
  gap: 6px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--ui-line);
  background: var(--ui-surface-2);
}
.aiQuotaText {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  font-size: 0.88em;
}
.aiQuotaText small {
  margin-inline-start: auto;
  color: var(--ui-muted);
}
.aiQuotaBar {
  height: 8px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--ui-line) 70%, transparent);
  overflow: hidden;
}
.aiQuotaBar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--ui-brand);
  transition: inline-size 0.3s ease;
}
.aiQuota.isLow .aiQuotaBar i {
  background: var(--ui-warn);
}
.aiQuota.isOut .aiQuotaBar i {
  background: var(--ui-danger);
}
.aiQuota.isOut .aiQuotaText small {
  color: var(--ui-danger);
}
.aiMessages {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 20px 22px;
  background: var(--ui-bg);
}
.aiMsg {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  max-width: 100%;
}
.aiMsg.mine {
  display: flex;
  justify-content: flex-end;
}
.aiMsgBody {
  max-width: min(78%, 760px);
  min-width: 0;
}
.aiMsg.mine .aiMsgBody {
  padding: 10px 14px;
  border-radius: 18px;
  border-end-end-radius: 6px;
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
}
.aiMsg.theirs .aiMsgBody {
  padding: 12px 16px;
  border: 1px solid var(--ui-line);
  border-radius: 18px;
  border-start-start-radius: 6px;
  background: var(--ui-surface);
  color: var(--ui-text);
}
.aiMsgText {
  line-height: 1.95;
  overflow-wrap: anywhere;
}
.aiMsgText p {
  margin: 0 0 0.5em;
}
.aiMsgText p:last-child,
.aiMsgText ul:last-child {
  margin-bottom: 0;
}
.aiMsgText ul {
  margin: 0.2em 0 0.6em;
  padding-inline-start: 1.3em;
}
.aiMsg footer {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
  font-size: 0.74em;
  opacity: 0.75;
}
.aiAvatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ui-brand) 15%, var(--ui-surface));
  color: var(--ui-brand);
  font-size: 16px;
}
.aiAvatar.big {
  width: 56px;
  height: 56px;
  font-size: 26px;
}
.aiRedacted {
  color: var(--ui-danger);
  font-weight: 700;
}
.aiDots {
  display: inline-flex;
  gap: 5px;
  padding: 6px 2px;
}
.aiDots i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ui-muted);
  animation: aiDot 1.2s infinite ease-in-out;
}
.aiDots i:nth-child(2) {
  animation-delay: 0.15s;
}
.aiDots i:nth-child(3) {
  animation-delay: 0.3s;
}
@keyframes aiDot {
  0%,
  80%,
  100% {
    opacity: 0.3;
    transform: translateY(0);
  }
  40% {
    opacity: 1;
    transform: translateY(-4px);
  }
}
.aiWelcome {
  margin: auto;
  display: grid;
  justify-items: center;
  gap: 10px;
  text-align: center;
  max-width: 34rem;
  color: var(--ui-muted);
}
.aiWelcome b {
  color: var(--ui-text);
  font-size: 1.1em;
}
.aiSuggest {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 6px;
}
.aiSuggestChip:not(#dt-none) {
  min-height: 0 !important;
  padding: 8px 14px !important;
  border-radius: 99px !important;
  background: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
  box-shadow: none !important;
  font-weight: 400 !important;
}
.aiSuggestChip:not(#dt-none):hover {
  border-color: var(--ui-brand) !important;
}
.aiComposer {
  display: grid;
  gap: 6px;
  padding: 12px 18px 14px;
  border-top: 1px solid var(--ui-line);
  background: var(--ui-surface);
}
.aiInputRow {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 6px;
  border: 1px solid var(--ui-line);
  border-radius: 18px;
  background: var(--ui-field, var(--ui-surface));
}
.aiInputRow:focus-within {
  border-color: var(--ui-brand);
}
.aiInputRow textarea:not(#dt-none) {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 40px !important;
  max-height: 220px;
  resize: none;
  border: 0 !important;
  background: transparent !important;
  box-shadow: none !important;
  padding: 8px 10px;
  line-height: 1.8;
}
.aiSendBtn:not(#dt-none) {
  flex: none;
  border-radius: 14px !important;
}
.aiComposeFoot small {
  font-size: 0.74em;
}
.aiTutorLoading,
.aiMiniLoading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 120px;
  color: var(--ui-muted);
}
.aiMiniLoading {
  min-height: 70px;
}
.uiSpinner {
  display: inline-block;
  width: 18px;
  height: 18px;
  border: 2px solid currentColor;
  border-inline-start-color: transparent;
  border-radius: 50%;
  animation: uiSpin 0.75s linear infinite;
}
@keyframes uiSpin {
  to {
    transform: rotate(360deg);
  }
}
/* The consent card: one column, the three points as a list with their names
   in bold, the agreement as the panel's own .check row. */
.aiConsent {
  max-width: 720px;
  margin: auto;
  display: grid;
  justify-items: start;
  gap: 14px;
  padding: 28px;
}
.aiConsent h3,
.aiConsent p {
  margin: 0;
}
.aiConsent p {
  line-height: 1.9;
  color: var(--ui-muted);
}
.aiConsentList {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 14px 16px 14px 14px;
  padding-inline-start: 34px;
  border-radius: 14px;
  background: var(--ui-surface-2);
  line-height: 1.9;
  width: 100%;
  box-sizing: border-box;
}
.aiConsentAgree {
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  border: 1px solid var(--ui-line);
  border-radius: 12px;
}
.aiConsentAgree span {
  white-space: normal;
  line-height: 1.8;
}
.aiIcon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 16px;
  background: color-mix(in srgb, var(--ui-brand) 15%, var(--ui-surface));
  color: var(--ui-brand);
  font-size: 26px;
}
.aiDialog {
  max-width: 480px;
  border: 1px solid var(--ui-line);
  border-radius: 18px;
  background: var(--ui-surface);
  color: var(--ui-text);
  padding: 24px;
}
.aiDialog::backdrop {
  background: color-mix(in srgb, var(--ui-text) 55%, transparent);
  backdrop-filter: blur(3px);
}
@media (max-width: 900px) {
  .aiTutorShell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    height: calc(100dvh - 150px);
  }
  .aiSessions {
    border-inline-end: 0;
    border-bottom: 1px solid var(--ui-line);
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    padding: 10px;
  }
  .aiSessionsTitle,
  .aiRevoke:not(#dt-none) {
    display: none;
  }
  .aiNewChat:not(#dt-none) {
    width: auto;
  }
  .aiSessionList {
    display: flex;
    flex: 1 1 100%;
    gap: 6px;
    overflow-x: auto;
    order: 3;
  }
  .aiSessionItem:not(#dt-none) {
    flex: 0 0 auto;
    width: auto;
    max-width: 14rem;
    border-color: var(--ui-line) !important;
  }
  .aiMessages {
    padding: 14px;
  }
  .aiMsgBody {
    max-width: 88%;
  }
  .aiChatHead,
  .aiComposer,
  .aiQuota {
    padding-inline: 12px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .uiSpinner {
    animation-duration: 1.8s;
  }
  .aiDots i {
    animation: none;
  }
  .aiQuotaBar i {
    transition: none;
  }
}
.notificationSettingsGrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.notificationSwitch {
  display: flex !important;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 68px;
  padding: 12px 14px;
  margin-top: 10px;
  border: 1px solid var(--ui-line);
  border-radius: 14px;
  background: var(--ui-surface-2);
  cursor: pointer;
}
.notificationSwitch > span:first-child {
  display: grid;
  gap: 4px;
}
.notificationSwitch small {
  color: var(--ui-muted);
  line-height: 1.7;
}
.notificationSwitch.isDisabled {
  opacity: 0.62;
  cursor: not-allowed;
}
.notificationQuiet {
  margin-top: 16px;
}
.notificationQuiet .formGrid {
  max-width: 620px;
}
.notificationQuiet input[type="time"] {
  direction: ltr;
  text-align: left;
}
.notificationSwitch:focus-within {
  outline: 3px solid color-mix(in srgb, var(--ui-brand) 35%, transparent);
  outline-offset: 2px;
}
@media (max-width: 760px) {
  .notificationSettingsGrid {
    grid-template-columns: 1fr;
  }
  .notificationSwitch {
    min-height: 76px;
    align-items: flex-start;
  }
  .notificationQuiet .formGrid {
    grid-template-columns: 1fr;
  }
}

/* Grouped role-aware dashboard navigation and responsive right-side drawer. */
.dtDashboardShell {
  grid-template-columns: minmax(min(100%, 260px), 286px) minmax(0, 1fr) !important;
  gap: 24px !important;
  max-width: 1560px;
  margin-inline: auto;
}
.dtDashboardSidebar {
  position: sticky !important;
  top: 16px !important;
  max-height: calc(100dvh - 32px);
  overflow: hidden !important;
  padding: 16px !important;
  border-radius: 22px !important;
  box-shadow: 0 18px 48px var(--ui-shadow) !important;
}
.dtDashboardSidebar > .logo {
  padding: 8px 10px 18px !important;
  border-bottom: 1px solid var(--ui-line);
  margin-bottom: 12px;
}
.dtGroupedNav {
  display: block !important;
  overflow-y: auto !important;
  overflow-x: hidden !important;
  padding-inline: 2px 5px;
  scrollbar-width: thin;
  scrollbar-color: var(--ui-line) transparent;
}
.dtNavGroup {
  display: grid;
  gap: 4px;
  margin: 0 0 14px;
}
.dtNavGroup[hidden] {
  display: none !important;
}
.dtNavGroupTitle {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 5px 5px !important;
  padding: 8px 6px 5px;
  font-size: 0.75em !important;
  font-weight: 800 !important;
  letter-spacing: 0.02em;
  color: var(--ui-text) !important;
  opacity: 0.75;
  background: var(--ui-surface-2) !important;
}
/* A required field has to say so. The marker is generated from the control's
   own required attribute rather than typed into each label, so it cannot drift
   away from what validation actually enforces -- the enrolment form had the
   attribute on six of its seven fields and showed the asterisk on none. */
/* A descendant, not only a direct child: the Jalali picker wraps its input in
   a container, so the birth-date field kept its required attribute and lost its
   asterisk -- the one field on the form where the marker was generated from a
   relationship the enhancement had already broken. */
:is(.grid, .formGrid) > div:has(:is(input, select, textarea)[required]) > label::after,
label:has(:is(input, select, textarea)[required])::after {
  content: "\00a0*";
  color: var(--ui-danger);
  font-weight: 700;
}
/* The role gate hides menu items with .hidden, and the layout rules below are
   more specific than that class, so without this every hidden item came back
   the moment the nav was grouped: students saw the institution's own menus.
   Keep this rule above them -- specificity, not order, is what settles it. */
.dtDashboardSidebar .hidden,
.dtNavGroup button.hidden,
.dtNavGroup button[hidden] {
  display: none !important;
}
.dtNavGroup button {
  position: relative;
  display: flex !important;
  align-items: center;
  gap: 10px;
  width: 100% !important;
  min-height: 46px !important;
  margin: 0 !important;
  padding: 10px 12px !important;
  border-radius: 12px !important;
  text-align: right !important;
  line-height: 1.5 !important;
}
/* The badge used to print data-nav-icon as text through ::before. Those were
   typographic glyphs -- a chess pawn for members, a lozenge for four different
   things -- which render differently on every platform and meant nothing. It
   is a real svg now, so it also inherits currentColor for the active state. */
.dtNavIconBox {
  display: grid;
  place-items: center;
  flex: 0 0 28px;
  width: 28px;
  height: 28px;
  border-radius: 9px;
  color: var(--ui-muted);
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
}
.dtNavGlyph {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.dtNavGroup button.active > .dtNavIconBox {
  color: var(--ui-brand);
  border-color: color-mix(in srgb, var(--ui-brand) 45%, var(--ui-line));
  background: color-mix(in srgb, var(--ui-brand) 10%, var(--ui-surface));
}
.dtDashboardSidebar > .logout {
  flex: 0 0 auto !important;
  margin-top: 8px !important;
  min-height: 46px !important;
}
.dtNavToggle,
.dtNavBackdrop {
  display: none;
}
.dtDashboardShell > .content {
  width: 100%;
  max-width: 1240px;
  min-width: 0;
}
.dtDashboardShell > .content > .top {
  padding: 2px 2px 12px;
  border-bottom: 1px solid var(--ui-line);
  margin-bottom: 24px !important;
}
.dtDashboardShell .tableWrap {
  max-width: 100%;
}
@media (max-width: 1100px) and (min-width: 901px) {
  .dtDashboardShell {
    grid-template-columns: 264px minmax(0, 1fr) !important;
    gap: 16px !important;
  }
  .dtDashboardSidebar {
    padding: 12px !important;
  }
  .dtNavGroup button {
    font-size: 0.9em !important;
    padding-inline: 9px !important;
  }
}
@media (max-width: 900px) {
  html.dtNavOpen,
  html.dtNavOpen body {
    overflow: hidden;
  }
  .dtDashboardShell {
    display: block !important;
    padding-top: 62px !important;
  }
  .dtDashboardSidebar {
    position: fixed !important;
    z-index: 12020 !important;
    inset: 0 0 0 auto !important;
    width: min(86vw, 330px) !important;
    max-height: 100dvh !important;
    border-radius: 0 !important;
    border-block: 0 !important;
    border-inline-start: 1px solid var(--ui-line) !important;
    transform: translateX(105%);
    transition: transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1) !important;
    box-shadow: -20px 0 70px var(--ui-shadow) !important;
  }
  .dtNavOpen .dtDashboardSidebar {
    transform: translateX(0);
  }
  .dtNavToggle {
    position: fixed;
    z-index: 12010;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    top: max(10px, env(safe-area-inset-top));
    right: 12px;
    min-height: 44px;
    padding: 8px 13px;
    border: 1px solid var(--ui-line) !important;
    border-radius: 12px !important;
    background: var(--ui-surface) !important;
    color: var(--ui-text) !important;
    box-shadow: 0 8px 24px var(--ui-shadow);
  }
  .dtNavToggle span {
    font-size: 20px;
  }
  /* The global button rule paints every unclassed button's text white with
     five classes' weight; this toggle's one class kept its white background
     and lost its colour -- white on white, an empty square (Codex's live
     audit, 1405-07-09). An id's weight settles it in both themes. */
  .dtNavToggle:not(#dt-none) {
    background: var(--ui-surface) !important;
    color: var(--ui-text) !important;
  }
  .dtNavToggle:not(#dt-none):focus-visible {
    outline: 3px solid var(--ui-brand) !important;
    outline-offset: 2px;
  }
  .dtNavBackdrop {
    position: fixed;
    display: block;
    z-index: 12015;
    inset: 0;
    border: 0 !important;
    border-radius: 0 !important;
    background: #020812b8 !important;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
      opacity 0.2s,
      visibility 0.2s;
  }
  .dtNavOpen .dtNavBackdrop {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
  .dtDashboardShell > .content {
    max-width: none;
  }
  .dtDashboardShell > .content > .top {
    min-height: 48px;
    padding-inline-start: 4px;
    padding-inline-end: 70px;
  }
  .dtDashboardShell .coreColumns,
  .dtDashboardShell .guidanceColumns,
  .dtDashboardShell .guidanceWorkspaceGrid,
  .dtDashboardShell .formGrid,
  .dtDashboardShell .grid,
  .dtDashboardShell .kpis {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .dtDashboardShell .card {
    padding: 18px !important;
  }
  .dtDashboardShell .section {
    min-width: 0;
  }
  .dtDashboardShell .actions > button {
    min-height: 44px;
  }
  .dtDashboardShell .tableWrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}
@media (max-width: 520px) {
  .dtDashboardShell {
    padding-top: 58px !important;
  }
  .dtDashboardShell > .content > .top {
    padding-inline-end: 64px;
  }
  .dtDashboardShell > .content > .top h1 {
    font-size: 1.45rem !important;
  }
  .dtDashboardShell .card {
    padding: 15px !important;
    border-radius: 16px !important;
  }
  .dtDashboardShell .actions {
    display: grid !important;
    grid-template-columns: 1fr;
  }
  .dtDashboardShell .actions > button,
  .dtDashboardShell button.wide {
    width: 100% !important;
  }
  .dtDashboardShell .sectionHead {
    display: grid !important;
    gap: 10px;
  }
  .dtDashboardShell .uiModal {
    padding: 8px !important;
  }
  .dtDashboardShell .uiModalCard {
    padding: 16px !important;
    border-radius: 18px !important;
  }
  .dtNavToggle b {
    display: none;
  }
}
.dtAppearanceThemeControl {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin: 0 0 18px;
  padding: 14px 16px;
  border: 1px solid var(--ui-line);
  border-radius: 16px;
  background: var(--ui-surface);
}
.dtAppearanceThemeControl > div {
  display: grid;
  gap: 4px;
}
.dtAppearanceThemeControl small {
  color: var(--ui-muted);
}
.themeToggle.appearanceThemeToggle {
  position: static !important;
  inset: auto !important;
  flex: 0 0 48px;
  width: 48px !important;
  height: 48px !important;
  margin: 0 !important;
  border-radius: 14px !important;
  font-size: 22px;
  box-shadow: none !important;
  background: var(--ui-surface-2) !important;
}
@media (max-width: 520px) {
  .dtAppearanceThemeControl {
    padding: 12px;
  }
  .themeToggle.appearanceThemeToggle {
    flex-basis: 46px;
    width: 46px !important;
    height: 46px !important;
  }
}

/* Dashboard overview, independent navigation scroll, and cross-theme button contrast. */
.dtDashboardSidebar {
  display: flex !important;
  flex-direction: column !important;
  height: calc(100dvh - 32px) !important;
}
.dtGroupedNav {
  flex: 1 1 auto !important;
  min-height: 0 !important;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}
.dtGroupedNav::-webkit-scrollbar {
  width: 8px;
}
.dtGroupedNav::-webkit-scrollbar-thumb {
  background: var(--ui-line);
  border-radius: 99px;
}
.dtDashboardSidebar > .danger,
.dtDashboardSidebar > .logout {
  flex: 0 0 auto !important;
  margin-top: 8px !important;
}
.dtOverviewHead {
  text-align: center;
  margin: 0 auto 24px;
}
.dtOverviewHead h2 {
  margin: 0;
  font-size: clamp(1.55rem, 2.3vw, 2.1rem);
}
.dtOverviewHead p {
  margin: 7px 0 0;
  color: var(--ui-muted);
}
.dtOverviewIntro {
  min-height: 0 !important;
  padding: 22px !important;
  margin-bottom: 18px !important;
}
.dtOverviewGrid > .card {
  min-height: 6.4rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  transition:
    transform 0.18s ease,
    border-color 0.18s ease,
    box-shadow 0.18s ease;
}
.dtOverviewGrid > .card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--ui-brand) 38%, var(--ui-line)) !important;
  box-shadow: 0 14px 34px var(--ui-shadow);
}
button,
.btn {
  transition:
    background-color 0.18s ease,
    color 0.18s ease,
    border-color 0.18s ease,
    box-shadow 0.18s ease,
    transform 0.18s ease;
}
button:not(:disabled):active,
.btn:not(:disabled):active {
  transform: translateY(1px);
}
button:focus-visible,
.btn:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--ui-brand) 38%, transparent) !important;
  outline-offset: 2px;
}
.primary,
.wide:not(.secondary),
button:not(.secondary):not(.danger):not(.themeToggle):not(.jalaliDay):not(.dtNavBackdrop):not(
    .dtNavToggle
  ):not(.dtMetricCard, .scLevelCard) {
  background: var(--ui-brand) !important;
  color: var(--ui-brand-ink) !important;
  border: 1px solid color-mix(in srgb, var(--ui-brand) 78%, #000) !important;
}
.primary:hover,
.wide:not(.secondary):hover {
  background: color-mix(in srgb, var(--ui-brand) 86%, #000) !important;
}
.secondary {
  background: var(--ui-surface-2) !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
}
.secondary:hover {
  background: color-mix(in srgb, var(--ui-brand) 9%, var(--ui-surface-2)) !important;
  border-color: color-mix(in srgb, var(--ui-brand) 35%, var(--ui-line)) !important;
}
.danger {
  background: color-mix(in srgb, var(--ui-danger) 10%, var(--ui-surface)) !important;
  color: var(--ui-danger) !important;
}
button:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.52 !important;
  cursor: not-allowed !important;
  filter: saturate(0.65);
  transform: none !important;
}
.tabs {
  background: var(--ui-surface-2) !important;
  border: 1px solid var(--ui-line) !important;
}
.tabs button {
  background: transparent !important;
  color: var(--ui-muted) !important;
  border: 1px solid transparent !important;
}
.tabs button:hover {
  color: var(--ui-text) !important;
  background: color-mix(in srgb, var(--ui-brand) 8%, var(--ui-surface-2)) !important;
}
.tabs button.active,
.tabs .active {
  background: var(--ui-brand) !important;
  color: var(--ui-brand-ink) !important;
  border-color: color-mix(in srgb, var(--ui-brand) 70%, var(--ui-line)) !important;
}
@media (max-width: 900px) {
  .dtDashboardSidebar {
    height: 100dvh !important;
    max-height: 100dvh !important;
  }
  .dtOverviewHead {
    margin-bottom: 18px;
  }
  .dtOverviewGrid > .card {
    min-height: 120px;
  }
}
/* :not(#dt-none) matches every button and weighs one id: the global rule that
   fills buttons with the brand colour carries seven :not(.class) groups, and a
   menu rule of one class never beat it -- every menu item came out solid
   purple. An id outweighs any number of classes, so this settles it without
   touching the global rule. global_button_smoke holds it. */
.dtNavGroup button:not(#dt-none) {
  background: transparent !important;
  color: var(--ui-muted) !important;
  border-color: transparent !important;
}
.dtNavGroup button:not(#dt-none):hover {
  background: color-mix(in srgb, var(--ui-brand) 10%, var(--ui-surface-2)) !important;
  color: var(--ui-text) !important;
}
.dtNavGroup button.active:not(#dt-none) {
  background: color-mix(in srgb, var(--ui-brand) 16%, var(--ui-surface-2)) !important;
  color: var(--ui-text) !important;
  border-color: color-mix(in srgb, var(--ui-brand) 34%, var(--ui-line)) !important;
}
.dtMetricCard {
  text-align: right !important;
  background: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
  padding: 20px !important;
  align-items: flex-start !important;
}
.dtMetricCard > span {
  color: var(--ui-muted);
  font-weight: 700;
}
.dtMetricCard > b {
  font-size: clamp(1.8rem, 3vw, 2.5rem);
  line-height: 1.2;
  color: var(--ui-text) !important;
}
.dtMetricCard > small {
  color: var(--ui-muted);
  font-weight: 400;
}
.dtMetricCard:hover {
  background: color-mix(in srgb, var(--ui-brand) 6%, var(--ui-surface)) !important;
  color: var(--ui-text) !important;
}

/* --- Layout: keep the menu on screen, keep wide content inside its own box ---
   The side menu used to scroll away with the page, so on a long list a person
   had to scroll back to the top to switch section. It now stays put and, when
   the menu itself is taller than the window, scrolls inside itself.
   Only from the tablet breakpoint up: below it the menu is a horizontal strip
   at the top of the page and must not be pinned. */
@media (min-width: 801px) {
  .side,
  .sidebar {
    position: sticky;
    top: 0;
    align-self: start;
    max-height: 100vh;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}
/* Nothing should widen the page itself. A table or a long code string scrolls
   inside its own container instead of dragging the whole layout sideways. */
body {
  overflow-x: hidden;
}
.content,
.section {
  min-width: 0;
}
.tableWrap {
  overflow-x: auto;
  max-width: 100%;
  /* And a floor of zero. `max-width: 100%` resolves against the container, so
     it holds a wrap inside a block -- but inside a grid or flex track sized by
     its content, the track takes its width from the wrap's min-content, which
     is the table's `min-width: 760px`. The table then sets the column, the
     column sets the page, and the whole dashboard scrolls sideways to reach
     one table that was supposed to scroll inside its own box. */
  min-width: 0;
}

/* The ticket composer and list are shared by every institution role.  The old
   `grid` class made the single table occupy only one third of the page. */
#tickets,
#ticketCentreSection,
#ticketList,
.ticketListRoot,
#tcList,
#tcThread {
  min-width: 0;
  width: 100%;
}
#ticketList.grid,
.ticketListRoot {
  display: block;
}
#tickets #ticketBody {
  min-height: 180px;
  max-height: 320px;
}
#tickets .tableWrap,
#ticketCentreSection .tableWrap {
  max-height: 34rem;
  overflow: auto;
}
#tickets .tableWrap table,
#ticketCentreSection .tableWrap table,
.aiqQueueTable table {
  width: 100%;
}
@media (max-width: 780px) {
  #tickets .card,
  #ticketCentreSection .card {
    padding: 16px !important;
  }
  #tickets #ticketBody {
    max-height: 260px;
  }
}
.publicBankTable,
.tableScroll {
  max-height: 34rem;
  overflow: auto;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
}
.tableScroll {
  /* Shorter than the bank's: these are lists somebody scans, not a catalogue
     they browse, and a screen full of rows is the point of the limit. */
  max-height: 26rem;
}
.publicBankTable thead,
.tableScroll thead {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--ui-surface-2);
}

/* A column that holds only a tick box, and a label only a screen reader hears. */
.pickCell {
  width: 2.5rem;
  text-align: center;
}
.pickCell input[type="checkbox"] {
  width: 1.15rem !important;
  height: 1.15rem;
  min-height: 0;
  accent-color: var(--ui-brand);
}
.srOnly {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.publicBankTable .questionStemCell {
  min-width: 20rem;
  max-width: 34rem;
  white-space: normal;
  line-height: 1.8;
}
.publicBankTable .compactChoice {
  min-height: 2.25rem;
  padding: 0.25rem 0.5rem;
  white-space: nowrap;
}
@media (max-width: 560px) {
  .publicBankTable {
    max-height: 28rem;
  }
  .publicBankTable .questionStemCell {
    min-width: 16rem;
  }
}
table {
  min-width: max-content;
}

/* Member permission panel: one block per area of the product, so a list that
   is now four times longer stays readable. */
.permGroup {
  margin-top: 14px;
}
.permGroup > h4 {
  margin: 0 0 8px;
  font-size: 0.82em;
  font-weight: 700;
  color: var(--ui-muted);
}
.permGroup .check input[type="checkbox"]:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Dashboard charts. Every colour is a token, so both themes are covered by the
   same rules and there is no second palette to keep in step. The two series
   are told apart by brand and info, which stay distinguishable in both themes
   and do not rely on hue alone -- the legend and the tooltips carry the
   meaning for anyone who cannot separate them by colour. */
.chartCard > h3 {
  margin: 0 0 12px;
}
.chartWrap {
  overflow-x: auto;
  /* Same reason as .tableWrap: the chart inside carries a min-width of its
     own, and without a floor here that minimum becomes the column's. */
  min-width: 0;
}
.chartSvg {
  /* A ceiling as well as a floor, the same fault the trend chart had: a
     760-wide drawing with width:100% and no maximum rendered at 1500 on a
     desktop, so the bars were the height of the viewport and every label was
     drawn at twice its intended size. */
  width: 100%;
  min-width: 460px;
  max-width: 760px;
  height: auto;
  display: block;
  margin-inline: auto;
}
/* The count above each bar and the grade under it. Drawn ltr for the same
   reason the axis labels are: text-anchor resolves against the inline
   direction, and "middle" is the only value that is safe either way. */
.chartLabel {
  fill: var(--ui-text);
  font-size: 11px;
  font-weight: 700;
  text-anchor: middle;
}
.chartTick {
  fill: var(--ui-muted);
  font-size: 10px;
  text-anchor: end;
}
.chartAxis {
  stroke: var(--ui-line);
  stroke-width: 1.5;
}
.chartBar {
  transition: fill 0.15s ease;
}
.chartBar:hover {
  fill: color-mix(in srgb, var(--ui-brand) 78%, var(--ui-text));
}
.chartGrid line {
  stroke: var(--ui-line);
  stroke-width: 1;
}
.chartGrid text {
  fill: var(--ui-muted);
  font-size: 11px;
  /* text-anchor resolves against the inline direction, and the page is rtl, so
     "end" meant the left edge: every axis label started at the right margin
     and ran off the viewBox, leaving one visible digit of "۱۰۰٪". Pinning the
     labels to ltr makes "end" mean the right edge, which is where they sit. */
  direction: ltr;
  text-anchor: end;
}
.chartLine {
  fill: none;
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.chartSeriesA {
  stroke: var(--ui-brand);
}
.chartSeriesB {
  stroke: var(--ui-info);
}
circle.chartSeriesA {
  fill: var(--ui-brand);
  stroke: none;
}
.chartBar {
  fill: var(--ui-brand);
}
.chartLegend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 10px;
  font-size: 0.85em;
  color: var(--ui-muted);
}
.chartLegend span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.chartLegend i {
  width: 14px;
  height: 3px;
  border-radius: 999px;
  background: currentColor;
}
.chartLegend .chartSeriesA i {
  background: var(--ui-brand);
}
.chartLegend .chartSeriesB i {
  background: var(--ui-info);
}
.chartAxisRow {
  display: flex;
  justify-content: space-between;
  margin: 6px 2px 0;
  font-size: 0.78em;
  color: var(--ui-muted);
}
.chartEmpty {
  margin: 0;
  padding: 22px 4px;
  color: var(--ui-muted);
  text-align: center;
}
.roleBars {
  display: grid;
  gap: 10px;
}
.roleBar {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr) 3rem;
  align-items: center;
  gap: 10px;
  font-size: 0.9em;
}
.roleBar > b {
  text-align: left;
}
.roleTrack {
  height: 10px;
  border-radius: 999px;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  overflow: hidden;
}
.roleTrack > i {
  display: block;
  height: 100%;
  background: var(--ui-brand);
}
@media (max-width: 560px) {
  .roleBar {
    grid-template-columns: 6rem minmax(0, 1fr) 2.5rem;
    font-size: 0.85em;
  }
}

/* Platform overview: the counters are grouped so fifteen numbers read as four
   subjects rather than one wall. */
.overviewGroup {
  margin-bottom: 18px;
}
.overviewGroup > h3 {
  margin: 0 0 10px;
  font-size: 0.82em;
  font-weight: 700;
  color: var(--ui-muted);
}
.kpi > small {
  display: block;
  margin-top: 3px;
  font-size: 0.75em;
  color: var(--ui-muted);
}

/* ===========================================================================
   Density. One place that decides how large the furniture is relative to the
   text, so the platform reads the same on a 13px browser and a 16px one.
   Selectors here mirror the shape of the page stylesheets they override; this
   file loads after them, so equal specificity is enough and no !important is
   added. Read DESIGN.md before changing a number here -- it is every screen.
   =========================================================================== */

input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
select {
  min-height: var(--ui-control-h);
  padding: var(--ui-pad-control);
  border-radius: var(--ui-radius);
}
textarea {
  padding: var(--ui-pad-control);
  border-radius: var(--ui-radius);
  line-height: 1.75;
}
button,
.btn {
  min-height: var(--ui-control-h);
  padding: var(--ui-pad-control);
  border-radius: var(--ui-radius);
}
label {
  margin: 0.55rem 0 0.3rem;
}

.hero,
.card,
.panel,
.audienceBox,
.questionAttempt,
.emptyState {
  border-radius: var(--ui-radius-lg);
}
.card,
.panel {
  padding: var(--ui-pad-card);
}
.hero {
  padding: var(--ui-pad-card);
  margin: 1rem 0;
}
.emptyState {
  padding: 1.4rem;
}

.grid,
.formGrid,
.metricGrid,
.stack,
.choiceGrid {
  gap: var(--ui-gap);
}
/* Only the tick-box lists. This template was briefly written into the rule
   above, which sets the gap for five different layouts -- and .stack, whose
   whole job is one column, became a multi-column grid: the dashboard's three
   chart panels went side by side and the exam list ran off the page. A shared
   selector list is for what they share. */
.choiceGrid {
  display: grid;
  /* min(100%, 16rem) rather than a bare 16rem: a fixed minimum wider than the
     viewport is what makes a grid overflow its own container. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
}
.metricGrid {
  margin: var(--ui-gap) 0;
}
.actions {
  gap: 0.5rem;
  margin-top: var(--ui-gap);
}

.tableWrap {
  margin-top: var(--ui-gap);
  border-radius: var(--ui-radius);
}
th,
td {
  padding: var(--ui-cell-pad);
}

/* The scale, in tokens. These were four hand-set sizes that predated the
   scale at the top of this file; scoped to .content, they quietly outranked
   any bare h1/h2 rule, so a second set of sizes could be added and appear to
   do nothing. They are the same ladder now. */
.content h1 {
  font-size: var(--ui-step-4);
}
.content h2,
.sectionHead h2 {
  font-size: var(--ui-step-3);
}
.content h3,
.sectionHead h3 {
  font-size: var(--ui-step-2);
}
.dtOverviewHead h2 {
  font-size: var(--ui-step-3);
}

.metricCard {
  padding: var(--ui-pad-card);
  border-radius: var(--ui-radius);
  gap: 0.35rem;
}
.metricCard b {
  font-size: var(--ui-step-3);
}
.dtMetricCard {
  padding: var(--ui-pad-card) !important;
  border-radius: var(--ui-radius) !important;
}
.dtMetricCard > b {
  font-size: clamp(1.35rem, 2.1vw, 1.8rem);
}

.notice,
.choiceOption,
.statusChip,
.badge,
.audienceTag {
  border-radius: var(--ui-radius);
}
.statusChip,
.badge,
.audienceTag {
  border-radius: var(--ui-radius-pill);
  padding: 0.2rem 0.55rem;
}
.notice {
  padding: 0.6rem 0.8rem;
  margin: var(--ui-gap) 0;
}
.choiceOption {
  /* The same omission, and the one this project has now fixed twice in one
     place and never in the others. `justify-content: flex-start !important`
     sits on .choiceOption a thousand lines above with no display beside it, so
     every tick-box group *except* the compact ones was inline content -- and
     in Persian text the bidi algorithm is what decided whether the box landed
     before the words, after them, or between two of them.

     It belongs here, on the class itself, rather than inside each container
     that happens to hold these labels: there are five such containers and the
     next one somebody writes would have the bug again. */
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.65rem;
}

/* Sidebar: wide enough for the longest label it carries.
   15.5rem is 248px, and «حساب‌های متصل به ربات‌ها» needs about 246px once the
   icon, the gap and two lots of padding are counted -- so the longest few items
   in every role's menu wrapped onto two lines, which is what made the sidebar
   look ragged. Measured against the longest label rather than guessed. */
.dtDashboardShell {
  grid-template-columns: minmax(min(100%, 15rem), 18rem) minmax(0, 1fr) !important;
}
.dtDashboardSidebar {
  padding: 0.8rem !important;
}
.dtNavGroup button {
  min-height: var(--ui-nav-h) !important;
  padding: 0.4rem 0.6rem !important;
  border-radius: var(--ui-radius) !important;
  gap: 0.55rem;
}
.dtNavIconBox {
  flex: 0 0 1.7rem;
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 0.55rem;
}
.dtNavGlyph {
  width: 1rem;
  height: 1rem;
}
.dtNavGroupTitle {
  font-size: 0.78em !important;
  padding: 0.5rem 0.35rem 0.25rem !important;
  margin: 0 0.25rem 0.25rem !important;
}

/* Below 900px a finger sets the minimum, not the type scale. */
@media (max-width: 900px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  select,
  button,
  .btn {
    min-height: max(var(--ui-control-h), 44px);
  }
  .dtNavGroup button {
    min-height: max(var(--ui-nav-h), 44px) !important;
  }
}
/* The page stylesheet fixes this grid at three columns, so a fourth card
   dropped onto a row of its own with two empty slots beside it. Let the row
   decide how many fit. */
.dtOverviewGrid {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)) !important;
}
/* The menu fits the window instead of scrolling inside it.
   Measured: thirteen items with five group headings came to 706px of content
   in a 527px column, so a third of the menu was always below the fold. The
   headings were 135px of that -- more than the five items they introduced --
   and the labels beneath them already say what each one is. A hairline between
   groups keeps the grouping and costs nothing.
   This fits a 639px window with thirteen items. A shorter window or a longer
   menu still scrolls; the scrollbar below is styled for when it does. */
.dtDashboardSidebar {
  padding: 0.7rem !important;
}
.dtDashboardSidebar > .logo {
  font-size: 1.15rem;
  line-height: 1.2;
  padding: 0.2rem 0.3rem !important;
  margin: 0 0 0.35rem !important;
}
.dtNavGroupTitle {
  display: none !important;
}
.dtNavGroup {
  margin: 0 !important;
  padding: 0 !important;
}
.dtNavGroup + .dtNavGroup {
  border-top: 1px solid var(--ui-line);
  margin-top: 0.3rem !important;
  padding-top: 0.3rem !important;
}
.dtNavGroup button {
  min-height: var(--ui-nav-h) !important;
  padding: 0.28rem 0.55rem !important;
  margin: 0.08rem 0 !important;
}
.dtNavIconBox {
  flex: 0 0 1.5rem;
  width: 1.5rem;
  height: 1.5rem;
}
.dtNavGlyph {
  width: 0.92rem;
  height: 0.92rem;
}
.dtDashboardSidebar > .logout {
  min-height: 2.3rem !important;
  margin-top: 0.45rem !important;
}

/* Cards sit on the page rather than floating above it: one hairline, no
   shadow. The reference does the same -- structure comes from the border and
   the ground, and a shadow is reserved for something that actually overlays,
   which is the modal. */
.card,
.panel,
.metricCard,
.dtMetricCard,
.audienceBox,
.questionAttempt,
.tableWrap {
  box-shadow: none !important;
}
.uiModalCard {
  box-shadow: 0 24px 64px var(--ui-shadow) !important;
}

/* The selected item gets an edge as well as a tint, so it is findable without
   relying on colour alone. */
.dtNavGroup button.active {
  position: relative;
}
.dtNavGroup button.active::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 20%;
  bottom: 20%;
  width: 2px;
  border-radius: 2px;
  background: var(--ui-brand);
}

/* The app shell holds the window instead of growing past it.
   The sidebar was a sticky block of fixed height inside a page that scrolled
   as a whole, so it drifted out of view and never matched the window. Here the
   shell is exactly one screen tall, the sidebar fills it, and only the content
   column scrolls -- which also means the menu is always reachable.
   Scoped with :has() so the pages that have no shell, the public enrolment
   form and the login page, keep scrolling normally. Below 901px the shell is a
   single column and the page scrolls as it always did. */
@media (min-width: 901px) {
  html:has(.dtDashboardShell),
  html:has(.dtDashboardShell) body {
    height: 100%;
    overflow: hidden;
  }
  .dtDashboardShell {
    height: 100dvh;
    min-height: 0 !important;
    overflow: hidden;
  }
  .dtDashboardSidebar {
    position: static !important;
    height: 100% !important;
    max-height: none !important;
    align-self: stretch !important;
  }
  /* The shell sits inside main.wrap, whose padding pushed it 42px down the
     page. 100dvh measured from there ends 42px below the window, which is
     exactly where the sign-out button was being cut off. */
  :has(> .dtDashboardShell) {
    margin: 0 !important;
    padding: 0 !important;
    max-width: none !important;
    width: 100% !important;
  }
  .dtDashboardSidebar > nav {
    /* A column flex item will not shrink past its content without this, so the
       menu pushed the sign-out button out of the shell instead of scrolling. */
    min-height: 0 !important;
  }
  /* Scrolled to the end, the last card finished flush against the window
     edge -- measured at exactly zero. A scroll container does not add room
     after its content, so the bottom of every page read as cut off. This is
     that room, and it works whatever padding a given page already has. */
  .dtDashboardShell > .content::after {
    content: "";
    display: block;
    height: 1.5rem;
  }
  .dtDashboardShell > .content {
    height: 100%;
    overflow-y: auto;
    /* The scroll belongs to the column, so the bar sits at the column's edge
       rather than floating 40px inside it. */
    max-width: none !important;
    margin: 0 !important;
  }
}

/* The trend beside a headline number. Up is not always good -- more open
   tickets is worse -- so the colour says direction, not judgement, and the
   arrow repeats it for anyone who cannot separate the two hues. */
.dtMetricCard {
  display: flex !important;
  flex-direction: column;
  align-items: flex-start !important;
  gap: 0.15rem;
}
.dtTrend {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1rem 0.4rem;
  border-radius: var(--ui-radius-pill);
  font-size: 0.8em;
  font-weight: 700;
  line-height: 1.6;
}
.dtTrend i {
  font-style: normal;
  font-size: 0.75em;
}
.dtTrendUp {
  color: var(--ui-brand);
  background: color-mix(in srgb, var(--ui-brand) 12%, transparent);
}
.dtTrendDown {
  color: var(--ui-danger);
  background: color-mix(in srgb, var(--ui-danger) 12%, transparent);
}
.dtTrendFlat {
  color: var(--ui-muted);
  background: var(--ui-surface-2);
}

/* The browser's own file control renders its text in the browser's language,
   so a Persian page showed "Choose Files". A styled label drives a visually
   hidden input instead -- hidden by clipping rather than display:none, which
   would take it out of the accessibility tree along with the keyboard. */
.fileButton {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: var(--ui-control-h);
  padding: var(--ui-pad-control);
  border-radius: var(--ui-radius);
  border: 1px solid var(--ui-line);
  background: var(--ui-surface-2);
  color: var(--ui-text);
  cursor: pointer;
  font: inherit;
  font-weight: 700;
}
.fileButton:hover {
  border-color: var(--ui-brand);
}
input[type="file"].hiddenFile {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Messages live in one fixed strip at the top centre, whatever page raised
   them, so a person learns one place to look. Green confirms, red reports a
   failure, and both carry a close button as well as a timer -- a message that
   can only be waited out is a message that traps someone mid-task. */
.dtToastStrip {
  position: fixed;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10050;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(560px, calc(100vw - 2rem));
  pointer-events: none;
}
.dtToast {
  position: relative;
  margin: 0 !important;
  padding: 0.7rem 2.4rem 0.7rem 1rem !important;
  border-radius: var(--ui-radius) !important;
  box-shadow: 0 10px 30px var(--ui-shadow) !important;
  pointer-events: auto;
  font-weight: 700;
  line-height: 1.7;
  opacity: 0;
  transform: translateY(-0.5rem);
  transition:
    opacity 0.18s ease,
    transform 0.18s ease;
}
.dtToastIn {
  opacity: 1;
  transform: translateY(0);
}
.dtToastOk {
  color: var(--ui-brand) !important;
  background: color-mix(in srgb, var(--ui-brand) 14%, var(--ui-surface)) !important;
  border: 1px solid color-mix(in srgb, var(--ui-brand) 42%, var(--ui-line)) !important;
}
.dtToastError {
  color: var(--ui-danger) !important;
  background: color-mix(in srgb, var(--ui-danger) 14%, var(--ui-surface)) !important;
  border: 1px solid color-mix(in srgb, var(--ui-danger) 42%, var(--ui-line)) !important;
}
.dtToastClose {
  position: absolute;
  inset-inline-end: 0.4rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1.75rem !important;
  height: 1.75rem !important;
  min-height: 0 !important;
  padding: 0 !important;
  border: 0 !important;
  border-radius: 50% !important;
  background: transparent !important;
  color: inherit !important;
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
}
.dtToastClose:hover {
  background: color-mix(in srgb, currentColor 16%, transparent) !important;
}
@media (prefers-reduced-motion: reduce) {
  .dtToast {
    transition: none;
  }
}

/* One button size, everywhere.
   Buttons sitting next to each other had different heights because each got
   its height from its own text and padding -- a two-word button and a
   five-word button are not the same height unless something says so. They
   share a minimum height and a line box now, and a row of them keeps a gap
   instead of letting them touch. */
button,
.btn,
.fileButton {
  min-height: 2.5rem;
  padding-block: 0.45rem;
  padding-inline: 0.9rem;
  border-radius: var(--ui-radius);
  font: inherit;
  font-weight: 700;
  line-height: 1.6;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* em, like the icon it separates: a fixed gap beside a scaling icon crowds
     the label on a compact button and strands it on a large one. */
  gap: 0.45em;
  cursor: pointer;
}
button:disabled,
.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
/* A row of actions: even gaps, and they wrap rather than overflow the cell. */
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
.actions > button,
.actions > .btn {
  flex: 0 0 auto;
}
/* Inside a table the same row of actions has to stay compact. */
td .actions > button {
  min-height: 2.25rem;
  padding-inline: 0.7rem;
  font-size: 0.92em;
}

/* The footer is part of the shell, so it does not scroll away with the
   content and does not overlap the last card either -- the content column
   ends above it rather than behind it. */
.dtFooter {
  flex: 0 0 auto;
  padding: 0.5rem 1rem;
  border-top: 1px solid var(--ui-line);
  background: var(--ui-surface);
  color: var(--ui-muted);
  font-size: 0.82em;
  text-align: center;
}
@media (min-width: 901px) {
  .dtDashboardShell:has(> .dtFooter) {
    grid-template-rows: minmax(0, 1fr) auto;
  }
  .dtFooter {
    grid-column: 1 / -1;
  }
}

/* The whole modal card scrolls, header included, so on a long dialog the
   close button scrolled off the top and the only way out was to scroll back
   up and find it. The header stays put now: pulled out to the card's edges so
   it spans them, and stuck to the top of the scroll. */
.uiModalCard > .uiModalHead {
  position: sticky;
  top: -22px;
  z-index: 2;
  margin: -22px -22px 14px;
  padding: 14px 22px;
  background: var(--ui-surface);
  border-bottom: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg) var(--ui-radius-lg) 0 0;
}

/* The footer belongs to the shell, so it meets the sidebar with no seam: the
   grid's row gap is what put a strip of page between them. */
@media (min-width: 901px) {
  .dtDashboardShell:has(> .dtFooter) {
    row-gap: 0 !important;
  }
}
.dtFooter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.25rem 1rem;
}
.dtFooter a {
  color: var(--ui-muted);
  text-decoration: none;
}
.dtFooter a:hover {
  color: var(--ui-brand);
  text-decoration: underline;
}
.dtFooterVersion {
  opacity: 0.75;
}

/* A menu item reads from its own edge, not from the middle. The shared button
   rule centres content, which is right for an action button and wrong for a
   row in a list. */
.dtNavGroup button {
  justify-content: flex-start !important;
  text-align: start !important;
}

/* An icon on every action button, from one place.
   There are more than ninety buttons across the panels; giving each one an
   icon by hand would be ninety edits and ninety chances to break the markup,
   and the next button added would not have one. ui.js reads what a button says
   and tags it, and these rules draw it.
   The shape is a mask rather than an image, so the colour is whatever the
   button's text colour already is -- one definition covers primary, secondary,
   danger and disabled without a second copy in another colour. */
button[data-btn-icon]::before,
.btn[data-btn-icon]::before {
  content: "";
  flex: 0 0 auto;
  /* Sized from the button's own text, not from the root. A fixed 1rem was
     right on a default button and wrong everywhere else: oversized next to
     .compact's 0.82em label, undersized on the larger ones, and the mismatch
     read as icons of random size down a row of buttons. */
  width: 1.05em;
  height: 1.05em;
  background: currentColor;
  mask: var(--btn-icon) center / contain no-repeat;
  -webkit-mask: var(--btn-icon) center / contain no-repeat;
}
/* The menu draws its own icons, and a toast's close button is a glyph. */
.dtNavGroup button::before,
.dtToastClose::before {
  content: none !important;
}
button[data-btn-icon="save"],
.btn[data-btn-icon="save"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E");
}
button[data-btn-icon="add"],
.btn[data-btn-icon="add"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14M5 12h14'/%3E%3C/svg%3E");
}
button[data-btn-icon="edit"],
.btn[data-btn-icon="edit"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 20h4L18.5 9.5a2.1 2.1 0 0 0-3-3L5 17v3z'/%3E%3C/svg%3E");
}
button[data-btn-icon="delete"],
.btn[data-btn-icon="delete"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 7h16M9 7V5h6v2M6 7l1 13h10l1-13'/%3E%3C/svg%3E");
}
button[data-btn-icon="close"],
.btn[data-btn-icon="close"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 6l12 12M18 6L6 18'/%3E%3C/svg%3E");
}
button[data-btn-icon="send"],
.btn[data-btn-icon="send"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12h16M13 5l7 7-7 7'/%3E%3C/svg%3E");
}
button[data-btn-icon="download"],
.btn[data-btn-icon="download"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v11M7 11l5 5 5-5M5 20h14'/%3E%3C/svg%3E");
}
button[data-btn-icon="copy"],
.btn[data-btn-icon="copy"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 9h10v10H9zM5 15V5h10'/%3E%3C/svg%3E");
}
button[data-btn-icon="search"],
.btn[data-btn-icon="search"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM21 21l-4.3-4.3'/%3E%3C/svg%3E");
}
button[data-btn-icon="refresh"],
.btn[data-btn-icon="refresh"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 12a8 8 0 1 1-2.3-5.6M20 4v4h-4'/%3E%3C/svg%3E");
}
button[data-btn-icon="key"],
.btn[data-btn-icon="key"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 10a4 4 0 1 0-4 4l1 1 2-2 2 2 2-2-2-2 1-1z'/%3E%3C/svg%3E");
}
button[data-btn-icon="check"],
.btn[data-btn-icon="check"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E");
}
button[data-btn-icon="pause"],
.btn[data-btn-icon="pause"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 5v14M15 5v14'/%3E%3C/svg%3E");
}
button[data-btn-icon="sound"],
.btn[data-btn-icon="sound"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 9v6h4l5 4V5L8 9zM17 9a4 4 0 0 1 0 6'/%3E%3C/svg%3E");
}
button[data-btn-icon="camera"],
.btn[data-btn-icon="camera"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 10.5V7a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-3.5l6 4.5V6z'/%3E%3C/svg%3E");
}
button[data-btn-icon="mic"],
.btn[data-btn-icon="mic"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V6a3 3 0 0 0-3-3zM5 11a7 7 0 0 0 14 0M12 18v3'/%3E%3C/svg%3E");
}
button[data-btn-icon="screen"],
.btn[data-btn-icon="screen"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 5h18v11H3zM8 20h8M12 16v4'/%3E%3C/svg%3E");
}
button[data-btn-icon="expand"],
.btn[data-btn-icon="expand"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 3H3v6M15 3h6v6M9 21H3v-6M15 21h6v-6'/%3E%3C/svg%3E");
}
button[data-btn-icon="play"],
.btn[data-btn-icon="play"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 4l13 8-13 8z'/%3E%3C/svg%3E");
}
button[data-btn-icon="hand"],
.btn[data-btn-icon="hand"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 11V4.5a1.5 1.5 0 1 1 3 0V11m0 0V3.5a1.5 1.5 0 1 1 3 0V11m0 0V6.5a1.5 1.5 0 1 1 3 0V14a7 7 0 0 1-7 7h-1a6 6 0 0 1-6-6v-4a1.5 1.5 0 1 1 3 0'/%3E%3C/svg%3E");
}
button[data-btn-icon="exit"],
.btn[data-btn-icon="exit"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 4h4a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-4M10 8l-4 4 4 4M6 12h11'/%3E%3C/svg%3E");
}
button[data-btn-icon="back"],
.btn[data-btn-icon="back"] {
  --btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 12H4M11 5l-7 7 7 7'/%3E%3C/svg%3E");
}

/* A page with no shell -- login, the public enrolment form -- has no grid row
   to put the footer in. On login the body is a centring grid, so appending the
   footer made it a second centred item sitting in the middle of the screen.
   Pinned to the bottom instead, with room reserved so it never covers the end
   of a long form. */
body:not(:has(.dtDashboardShell)) {
  padding-bottom: 2.75rem;
}
body:not(:has(.dtDashboardShell)) > .dtFooter {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 5;
}

/* Registration: the footer stays pinned to the bottom of the window whatever
   the form's height (owner, 1405-07-13) -- left in the flow, it rose to the
   middle of the screen whenever the form was short. What hid the end of a
   long form before was a pin with too little room under the page; the room
   reserved here is the footer's own height plus a gap, and the phone's
   home-bar inset. */
body:has(> main.wrap) {
  min-height: 100dvh;
  padding-bottom: calc(4.5rem + env(safe-area-inset-bottom, 0px));
}
body:has(> main.wrap) > main.wrap {
  margin-block: 2.5rem 1rem;
}
body:has(> main.wrap) > .dtFooter {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 5;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
@media (max-width: 650px) {
  body:has(> main.wrap) {
    padding-bottom: calc(6rem + env(safe-area-inset-bottom, 0px));
  }
  body:has(> main.wrap) > main.wrap {
    margin-block: 1rem;
  }
}

/* The button a settings card collapses into, and the card once it is open
   over the page. Wider than an ordinary dialog because these hold forms with
   several columns, and scrollable because some of them are long. */
.dtSettingsTrigger {
  margin-bottom: 0.75rem;
}
/* In the heading row the button needs no margin of its own, and the row must
   push it to the far end -- the top corner is where somebody looks for
   settings, not the middle of the title. */
.dtSettingsActions {
  margin: 0;
  margin-inline-start: auto;
}
.dtSettingsActions .dtSettingsTrigger {
  margin-bottom: 0;
}
.dtSettingsCard {
  width: min(920px, 100%);
}
.dtSettingsCard > .card,
.dtSettingsCard > .panel {
  border: 0 !important;
  box-shadow: none !important;
  padding: 0 !important;
  margin: 0 !important;
  background: transparent !important;
}
dialog.uiDialog {
  width: min(36rem, calc(100% - 1.5rem));
  max-height: 90vh;
  overflow: auto;
  padding: 0;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
  color: var(--ui-text);
  box-shadow: 0 25px 70px var(--ui-shadow);
}

dialog.uiDialog::backdrop {
  background: color-mix(in srgb, var(--ui-bg) 78%, transparent);
  backdrop-filter: blur(0.25rem);
}

/* Buying and renewing a plan: one path in three steps, and where the
   institution is on it (owner, 1405-07-13). */
.renewSteps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 0.75rem;
  padding: 0;
  list-style: none;
  counter-reset: none;
}

.renewSteps li {
  display: flex;
  flex: 1 1 8rem;
  align-items: center;
  gap: 0.45rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  color: var(--ui-muted);
  font-size: 0.9rem;
}

.renewSteps li span {
  display: inline-grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: var(--ui-surface-2);
  font-weight: 700;
}

.renewSteps li.done {
  color: var(--ui-text);
}

.renewSteps li.done span {
  background: color-mix(in srgb, var(--ui-brand) 22%, transparent);
}

.renewSteps li.now {
  border-color: var(--ui-brand);
  color: var(--ui-text);
  font-weight: 700;
}

.renewSteps li.now span {
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
}

.renewBox {
  margin-block: 0.75rem;
}

.renewOffer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
}

.renewOffer h3,
.renewOffer p {
  margin: 0.2rem 0;
}

.renewPrice {
  font-size: 1.1rem;
}

.planOff {
  background: color-mix(in srgb, var(--ui-brand) 18%, transparent);
}

.renewNext {
  margin: 0 0 0.75rem;
  color: var(--ui-muted);
}

.payCopy {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
  margin-block: 0.35rem;
}

.payCopy code {
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.payRecommended {
  display: grid;
  gap: 0.35rem;
}

.payRecommended > .badge {
  justify-self: start;
}

.paySkipped {
  margin-top: 0.5rem;
}

/* A managed subdomain: the name typed, the suffix fixed beside it. */
.domainSuffixField {
  display: flex;
  align-items: stretch;
}

.domainSuffixField input {
  flex: 1 1 auto;
  min-width: 0;
}

.domainSuffix {
  display: flex;
  align-items: center;
  padding-inline: 0.6rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
  color: var(--ui-muted);
  white-space: nowrap;
}

.domainLogoRow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.domainLogoRow .brandPreview {
  max-height: 3rem;
  max-width: 10rem;
  object-fit: contain;
}

/* Platform staff: their areas as a row of chips, and who is signed in. */
.staffAreas {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-block: 0.4rem;
}

.staffFacts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 7rem), 1fr));
  gap: 0.5rem;
  margin: 0.75rem 0;
}

.staffFacts dt {
  color: var(--ui-muted);
  font-size: 0.8rem;
}

.staffFacts dd {
  margin: 0.15rem 0 0;
  font-variant-numeric: tabular-nums;
}

.mailLayout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: 1rem;
  align-items: start;
}

.mailRow {
  display: grid;
  gap: 0.15rem;
  width: 100%;
  margin-bottom: 0.4rem;
  text-align: start;
}

.mailRow.unread .mailSubject,
.mailRow.unread .mailFrom {
  font-weight: 700;
}

.mailRow.active {
  border-color: var(--ui-brand);
}

.mailBody {
  margin: 0.75rem 0;
  padding: 0.75rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
  overflow-wrap: anywhere;
}

.agentList {
  display: grid;
  gap: 0.75rem;
}

.agentReasons {
  margin: 0.5rem 0;
  padding-inline-start: 1.2rem;
}

.agentReasons li {
  margin-block: 0.15rem;
}

.agentTabs .active {
  border-color: var(--ui-brand);
  color: var(--ui-brand);
}

.brandLogoBlock {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--ui-line);
}

.brandInline {
  display: grid;
  gap: 0.75rem;
}

.contractBody {
  max-height: 50vh;
  overflow-y: auto;
  margin: 0.75rem 0;
  padding: 0.85rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
  line-height: 1.9;
}

/* Shown once its fonts and layout have settled (whoami.js), not built in
   front of the user. */
html.dtBooting body {
  opacity: 0;
}

body {
  transition: opacity 0.16s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  body {
    transition: none;
  }
}

/* «بازاریابی و فروش»: the steps of the work as tabs (marketing-hub.js). */
.mhTabs {
  display: flex;
  gap: 0.25rem;
  margin-block: 0.75rem 0.25rem;
  overflow-x: auto;
  border-bottom: 1px solid var(--ui-line);
  scrollbar-width: thin;
}

/* The tabs carry `.secondary` so the global button fill stays off them; the
   current one is marked by an underline drawn as a shadow, because `.secondary`
   pins every border colour. */
.mhTabs .secondary[role="tab"] {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.4rem;
  border-width: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  white-space: nowrap;
}

.mhTabs .secondary[role="tab"]:hover {
  background: var(--ui-surface-2);
}

.mhTabs .secondary[role="tab"].active {
  box-shadow: inset 0 -2px 0 var(--ui-brand);
  font-weight: 700;
}

.mhStep {
  display: inline-grid;
  place-items: center;
  min-width: 1.4rem;
  height: 1.4rem;
  border: 1px solid var(--ui-line);
  border-radius: 999px;
  font-size: 0.78em;
  font-variant-numeric: tabular-nums;
}

.mhTabs .active .mhStep {
  border-color: var(--ui-brand);
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
}

.mhHint {
  margin-block: 0.25rem 0.75rem;
}

/* One column that may not grow past the screen: a wide table scrolls inside
   its own wrapper instead of widening every card beside it. */
.mhPane,
.mhPart {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  min-width: 0;
}

.mhPane.hidden {
  display: none;
}

.mkLadder {
  display: grid;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.mkStep {
  display: grid;
  gap: 0.4rem;
  padding: 0.85rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
}

.mkStepHead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

.mkStepHead > div {
  flex: 1 1 14rem;
  min-width: 0;
}

.mkStepHead h4,
.mkStepHead p {
  margin: 0;
}

.staffRoleTag {
  margin: 0.25rem 0 0.5rem;
  font-size: 0.85rem;
}

dialog.payDialog {
  width: min(52rem, calc(100% - 1.5rem));
}

.payDialogHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.payDialogHead h3 {
  margin: 0;
}

dialog.uiDialog > .card {
  margin: 0;
  border: 0;
  box-shadow: none;
}

/* Multi-choice answers on the daily report sit as a wrapping row of checkbox
   labels rather than one per line, which is unreadable past three options. */
.drMulti {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
}

/* The ticket whose thread is open, so the list still says where you are once
   the conversation has pushed it out of view. */
.rowActive > td {
  background: var(--ui-surface-2);
  font-weight: 600;
}

/* ---------------------------------------------------------------------------
   Elevation, and tiles that are colour-coded by position

   The page is lavender and cards are white, so a card is already separated from
   the page by being lighter. The border on top of that made every card read as
   a boxed-off region of equal weight -- thirty outlines down a long screen. A
   soft shadow and a hairline does the same job and lets the eye find the top of
   a card without being told.

   The tiles are the one place colour carries meaning rather than decoration: a
   row of identical numbers is a row nobody reads, and giving each its own hue
   makes "the third one" a thing you can point at across a conversation. The
   hues are the palette's own -- brand, info, warn, danger -- so they follow the
   theme, and a tile that already means something (a warning, a failure) keeps
   its own colour instead of taking one from its position.
   --------------------------------------------------------------------------- */
.card,
.panel,
.hero,
.kpi,
.tableWrap {
  box-shadow:
    0 1px 2px var(--ui-shadow),
    0 8px 24px var(--ui-shadow);
}

.metricGrid > .metricCard:not(.warning):not(.danger) {
  --tile: var(--ui-brand);
  background: color-mix(in srgb, var(--tile) 8%, var(--ui-surface));
  border-color: color-mix(in srgb, var(--tile) 24%, var(--ui-line));
  color: var(--ui-text);
}
.metricGrid > .metricCard:not(.warning):not(.danger):nth-child(4n + 2) {
  --tile: var(--ui-info);
}
.metricGrid > .metricCard:not(.warning):not(.danger):nth-child(4n + 3) {
  --tile: var(--ui-warn);
}
.metricGrid > .metricCard:not(.warning):not(.danger):nth-child(4n + 4) {
  --tile: var(--ui-danger);
}
.metricGrid > .metricCard:not(.warning):not(.danger) b {
  color: var(--tile);
}
.metricGrid > .metricCard {
  box-shadow: none;
}

/* A pill, because the reference calls for one and because a segmented control
   reads as "one of these" where a row of buttons reads as "do one of these". */
.tabs,
.tabs button {
  border-radius: var(--ui-radius-pill);
}

/* ---------------------------------------------------------------------------
   The institution's credit panel, at the top of the overview

   A bar rather than only a number, because "thirty days" reads as comfortable
   on an annual plan and alarming on a monthly one, and only the proportion
   says which. The track is the plan's whole window and the fill is what has
   been used, so it grows toward the end rather than shrinking away from it.

   The page is RTL, so the fill starts at the right on its own: inline-size on a
   block child fills from the inline start. Nothing here names a side.

   Tones are the palette's four, picked by meaning and not by position -- the
   opposite of the overview tiles below, where position is all there is to go
   on.
   --------------------------------------------------------------------------- */
.dtCredit {
  margin-bottom: var(--ui-gap);
}
.dtCreditTimeline {
  margin: 1rem 0 1.25rem;
}
.dtCreditTrack {
  --tone: var(--ui-brand);
  block-size: 0.65rem;
  border-radius: var(--ui-radius-pill);
  background: color-mix(in srgb, var(--ui-line) 70%, var(--ui-surface-2));
  overflow: hidden;
}
.dtCreditTrack[data-tone="warn"] {
  --tone: var(--ui-warn);
}
.dtCreditTrack[data-tone="danger"] {
  --tone: var(--ui-danger);
}
.dtCreditTrack > i {
  display: block;
  block-size: 100%;
  border-radius: inherit;
  background: linear-gradient(
    to left,
    color-mix(in srgb, var(--tone) 70%, var(--ui-surface)),
    var(--tone)
  );
}
.dtCreditDates {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.45rem;
  color: var(--ui-muted);
}
.dtCreditDates > small:nth-child(2) {
  text-align: center;
}
.dtCreditDates > small:last-child {
  text-align: end;
}

.dtCreditTiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--ui-gap);
}
.dtCreditTile {
  --tone: var(--ui-brand);
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: var(--ui-pad-card);
  border: 1px solid color-mix(in srgb, var(--tone) 26%, var(--ui-line));
  border-radius: var(--ui-radius-lg);
  background: color-mix(in srgb, var(--tone) 8%, var(--ui-surface));
}
.dtCreditTile[data-tone="info"] {
  --tone: var(--ui-info);
}
.dtCreditTile[data-tone="warn"] {
  --tone: var(--ui-warn);
}
.dtCreditTile[data-tone="danger"] {
  --tone: var(--ui-danger);
}
.dtCreditTile > span {
  color: var(--ui-muted);
}
.dtCreditTile > b {
  font-size: 1.55em;
  line-height: 1.35;
  color: var(--tone);
}
.dtCreditTile > small {
  color: var(--ui-muted);
  line-height: 1.8;
}
/* The action sits at the bottom of every tile whatever the text above it does,
   so the row of buttons lines up instead of stepping. */
.dtCreditTile > .actions {
  margin-top: auto;
  padding-top: 0.6rem;
}
@media (max-width: 560px) {
  .dtCreditDates {
    flex-direction: column;
    gap: 0.15rem;
  }
  .dtCreditDates > small:nth-child(2),
  .dtCreditDates > small:last-child {
    text-align: start;
  }
}

/* Notices come in three weights and only ever had one appearance, so a warning
   and a receipt looked alike. The classes were already being written in the
   pages -- `notice warning`, `notice error` -- with nothing on the other end. */
.notice.warning {
  background: color-mix(in srgb, var(--ui-warn) 12%, var(--ui-surface));
  border: 1px solid color-mix(in srgb, var(--ui-warn) 34%, var(--ui-line));
  color: var(--ui-text);
}
.notice.error {
  background: color-mix(in srgb, var(--ui-danger) 12%, var(--ui-surface));
  border: 1px solid color-mix(in srgb, var(--ui-danger) 34%, var(--ui-line));
  color: var(--ui-text);
}
.notice.success {
  background: color-mix(in srgb, var(--ui-brand) 12%, var(--ui-surface));
  border: 1px solid color-mix(in srgb, var(--ui-brand) 34%, var(--ui-line));
  color: var(--ui-text);
}

/* ── What the glass pass left behind ─────────────────────────────────────────
   The shared dashboard style was written and never looked at, and three faults
   came out of that. Each is fixed at its cause rather than by nudging values. */

/* 1. The decorative wash covered the wrong box.
   .content is max-width:1250px and centred, so painting the gradients on it
   left the rest of the grid column showing the body colour -- a seam beside
   the sidebar on any window wider than the content. The shell is the element
   that actually spans the column, so it carries the background and .content
   goes transparent over it. */
.dtDashboardShell {
  background:
    radial-gradient(
      circle at 8% 0%,
      color-mix(in srgb, var(--ui-brand) 14%, transparent),
      transparent 34%
    ),
    radial-gradient(
      circle at 92% 18%,
      color-mix(in srgb, var(--ui-info) 10%, transparent),
      transparent 30%
    ),
    var(--ui-bg);
}
.dtDashboardShell .content {
  background: transparent;
}

/* 2. Nothing separated one block from the next.
   The glass pass set gap inside the grids but never between the cards stacked
   down a section, so every panel met the one below it with no air. */
.dtDashboardShell .content .section > article,
.dtDashboardShell .content .section > .card,
.dtDashboardShell .content .section > .notice,
.dtDashboardShell .content .section > .tableWrap {
  margin-block-end: clamp(0.9rem, 1.6vw, 1.4rem);
}
.dtDashboardShell .content .section > :last-child {
  margin-block-end: 0;
}

/* 3. The date picker opened underneath the next card.
   backdrop-filter makes every card its own stacking context, so a calendar
   positioned inside one cannot paint outside it however high its z-index goes.
   The card is what has to be lifted, and only while a calendar is open in it.
   Not scoped to the dashboard: any card holding a picker needs this. */
article:has(> .jalaliWrap .jalaliCalendar),
article:has(.jalaliCalendar),
.card:has(.jalaliCalendar),
.section:has(.jalaliCalendar) {
  position: relative;
  z-index: 60;
}

/* 4. A grid inside a fieldset overflowed the page.
   fieldset defaults to min-inline-size:min-content, so it refuses to shrink
   below its widest child and the grid pushes the whole page sideways -- which
   is what put a horizontal scrollbar under the registration grade list. */
fieldset {
  min-inline-size: 0;
}

/* ── The dashboard, closer to a console than a form ──────────────────────────
   Brought toward a reference the owner picked: a quieter ground, charts that
   read as trends rather than sawtooths, and blocks with room between them.
   Every colour is a token, so both themes follow without a second palette. */

/* A trend needs an area under it to have weight. Two stops of the series'
   own colour, faded to nothing, so the fill never competes with the line and
   two overlapping series stay legible. */
.chartArea {
  stroke: none;
}
.chartFillTop {
  stop-color: currentColor;
  stop-opacity: 0.28;
}
.chartFillBottom {
  stop-color: currentColor;
  stop-opacity: 0;
}
/* The gradient stops inherit the series colour through `color`, which stroke
   alone would not give them. */
.chartSeriesA {
  color: var(--ui-brand);
}
.chartSeriesB {
  color: var(--ui-info);
}
circle.chartSeriesB {
  fill: var(--ui-info);
  stroke: none;
}
/* Where the line ends is the number people look for, so it is marked and the
   rest of the points are not. */
/* `circle.chartSeriesA` sets stroke:none and is more specific than a bare
   class, so this has to match on the element too or the ring never appears.
   More specific, not louder -- DESIGN.md rule 3. */
circle.chartEnd {
  fill: currentColor;
  stroke: var(--ui-surface);
  stroke-width: 2;
}
/* An invisible, finger-sized target for the tooltip: a 3px dot is not
   something anybody can hit, and the title only appears on hover. */
.chartHit {
  fill: transparent;
  stroke: none;
  cursor: pointer;
}
.chartGrid line {
  stroke: color-mix(in srgb, var(--ui-line) 55%, transparent);
  stroke-dasharray: 3 5;
}
.chartLine {
  stroke-width: 2;
}

/* Space between blocks, and inside them. The glass pass set a radius and a
   shadow and left the rhythm alone, so panels sat shoulder to shoulder. */
.dtDashboardShell .content .card,
.dtDashboardShell .content > .section > article {
  padding: clamp(1rem, 1.8vw, 1.5rem);
}
.dtDashboardShell .content .sectionHead {
  margin-block-end: clamp(0.75rem, 1.4vw, 1.15rem);
}
.dtDashboardShell .content .sectionHead h2 {
  /* normal, not tighter. This carried -0.02em, which is the Latin display
     convention and is wrong here: Persian letters connect, and negative
     tracking drags the joins apart until the word reads as a rendering
     fault. The tighten belongs on digits and nowhere else. */
  letter-spacing: var(--ui-track);
}
.dtDashboardShell .content .sectionHead .muted {
  margin-block-start: 0.25rem;
}

/* Numbers are the point of a metric tile, so they get the size and the tabular
   figures that keep a row of them aligned. */
.dtDashboardShell .content .metricCard b,
.dtDashboardShell .content .kpi b {
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.35rem, 2.4vw, 1.9rem);
  line-height: 1.15;
}
.dtDashboardShell .content .metricCard span,
.dtDashboardShell .content .kpi span {
  color: var(--ui-muted);
  font-size: 0.86em;
}

/* The sidebar reads as one long list of buttons. A little weight on the
   current one, and less on the rest, gives it a shape. */
.dtDashboardShell .side nav button {
  border-radius: var(--ui-radius);
  color: var(--ui-muted);
}
.dtDashboardShell .side nav button.active {
  color: var(--ui-text);
  background: color-mix(in srgb, var(--ui-brand) 16%, transparent);
}
.dtDashboardShell .side nav button:hover {
  color: var(--ui-text);
  background: color-mix(in srgb, var(--ui-text) 6%, transparent);
}

/* The grade under its own bar. Centred on the bar and small enough that a
   two-line year-and-stream pair does not crowd the one beside it. */
.chartAxisLabel {
  fill: var(--ui-muted);
  font-size: 10.5px;
  text-anchor: middle;
}

/* ── Platform announcements, at the top of a dashboard ───────────────────────
   The platform speaking, not part of whichever screen is open -- so it sits
   above the page's own content and carries its own weight without shouting. */
.dtAnnouncements {
  display: grid;
  gap: 0.75rem;
  margin-block-end: clamp(0.9rem, 1.6vw, 1.4rem);
}
/* One tone token per card, and everything else reads from it.
   The four tones differed only in the colour of a 4px rule, which is not
   enough to tell an outage from a greeting at a glance -- the card, the title
   and the mark were identical. Each tone now sets `--tone` once and the tint,
   the rule, the title and the mark all derive from it, so adding a fifth tone
   is one declaration rather than five. */
.dtAnnounce {
  --tone: var(--ui-info);
  --tone-mark: "i";
  display: grid;
  /* Two columns: the text and the close control. The image column is added
     only when there is an image -- declaring it unconditionally put the close
     button in the flexible track on every announcement without one. */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 1rem;
  padding: clamp(0.9rem, 1.6vw, 1.25rem);
  border: 1px solid color-mix(in srgb, var(--tone) 28%, var(--ui-line));
  border-inline-start: 4px solid var(--tone);
  border-radius: var(--ui-radius-lg);
  /* A tint, not a fill: the card still has to sit on the page as a card, and
     the text on it still has to meet contrast against a surface. */
  background: color-mix(in srgb, var(--tone) 7%, var(--ui-surface));
}
.dtAnnounce.hasImage {
  grid-template-columns: auto minmax(0, 1fr) auto;
}
.dtAnnounce.tone-success {
  --tone: var(--ui-brand);
  --tone-mark: "✓";
}
.dtAnnounce.tone-warn {
  --tone: var(--ui-warn);
  --tone-mark: "!";
}
.dtAnnounce.tone-danger {
  --tone: var(--ui-danger);
  --tone-mark: "!";
}
/* The mark carries the tone at a glance, before the title is read. */
.dtAnnounceMark {
  display: inline-grid;
  place-items: center;
  inline-size: 1.35em;
  block-size: 1.35em;
  margin-inline-end: 0.5em;
  border-radius: 50%;
  background: var(--tone);
  color: var(--ui-surface);
  font-size: 0.82em;
  font-weight: 700;
  line-height: 1;
  vertical-align: -0.15em;
}
.dtAnnounceMark::before {
  content: var(--tone-mark);
}
.dtAnnounceImage {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: var(--ui-radius);
}
.dtAnnounceBody h3 {
  margin: 0 0 0.35rem;
  font-size: 1.02rem;
}
.dtAnnounceText {
  color: var(--ui-muted);
  line-height: 1.9;
}
.dtAnnounceText :is(p, ul, ol) {
  margin: 0 0 0.5rem;
}
.dtAnnounceText img {
  max-width: 100%;
  border-radius: var(--ui-radius);
}
/* A close control, not a call to action.
   It inherited the shared button styling and rendered as a filled brand-
   coloured bar wider than the message it was closing -- the loudest thing on a
   card whose whole job is the sentence in the middle. `justify-self: end` is
   the belt to the grid's braces: even if a track is flexible, this never
   stretches to fill it. */
.dtAnnounceClose {
  align-self: start;
  justify-self: end;
  display: inline-grid;
  place-items: center;
  inline-size: 32px;
  min-inline-size: 32px;
  block-size: 32px;
  min-block-size: 32px !important;
  padding: 0 !important;
  border: 1px solid transparent !important;
  border-radius: 50% !important;
  background: transparent !important;
  color: var(--ui-muted) !important;
  font-size: 1.15rem;
  line-height: 1;
  box-shadow: none !important;
}
.dtAnnounceClose:hover,
.dtAnnounceClose:focus-visible {
  border-color: color-mix(in srgb, var(--tone) 40%, transparent) !important;
  background: color-mix(in srgb, var(--tone) 12%, transparent) !important;
  color: var(--ui-text) !important;
}
/* ui.js reads a button's label and adds an icon; this one already shows its
   own glyph, so a second one beside it would be two close marks. */
.dtAnnounceClose::after {
  content: none !important;
}
.dtAnnounceBody h3 {
  color: color-mix(in srgb, var(--tone) 55%, var(--ui-text));
}
@media (max-width: 560px) {
  .dtAnnounce,
  .dtAnnounce.hasImage {
    /* The text and the close control stay side by side even here: stacking
       them puts a lone round button on a row of its own under the message. */
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .dtAnnounce.hasImage .dtAnnounceImage {
    grid-column: 1 / -1;
    width: 100%;
    height: 140px;
  }
}

/* The announcement editor. contenteditable with a toolbar, so a platform
   message can carry a heading and a link without anybody writing HTML. */
.richEditor {
  min-height: 160px;
  padding: 0.85rem 1rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-field);
  line-height: 1.9;
}
.richEditor:focus-visible {
  outline: 2px solid var(--ui-brand);
  outline-offset: 1px;
}
.editorBar {
  margin-block-end: 0.5rem;
}
/* Visible to a screen reader, not on screen: the tick boxes in the event table
   are labelled by their column header for sighted readers. */
.srOnly {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* One accordion, for settings that sit above a list.
   Two screens asked for the same shape in the same week -- exam settings and
   class settings, each a form that is filled occasionally above a table that is
   read constantly -- and the alternative was two of these that drift apart.
   Built on <details> so it opens without script and keyboards already know it. */
.uiAccordion {
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  margin-bottom: 16px;
  overflow: hidden;
}
.uiAccordion > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  cursor: pointer;
  font-weight: 600;
  list-style: none;
  color: var(--ui-text);
}
.uiAccordion > summary::-webkit-details-marker {
  display: none;
}
/* The marker is drawn here rather than left to the browser, which puts its own
   triangle on the wrong side in a right-to-left page. */
.uiAccordion > summary::after {
  content: "";
  width: 8px;
  height: 8px;
  border-inline-start: 2px solid var(--ui-muted);
  border-block-end: 2px solid var(--ui-muted);
  transform: rotate(-45deg);
  transition: transform 0.2s;
  flex: none;
}
.uiAccordion[open] > summary::after {
  transform: rotate(135deg);
}
.uiAccordion > summary:hover {
  background: var(--ui-surface-2);
}
.uiAccordion > summary:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--ui-brand) 40%, transparent);
  outline-offset: -3px;
}
.uiAccordion > .uiAccordionBody {
  padding: 0 18px 18px;
  border-block-start: 1px solid var(--ui-line);
}
.uiAccordion > summary .hint {
  font-weight: 400;
  margin: 0;
}
@media (prefers-reduced-motion: reduce) {
  .uiAccordion > summary::after {
    transition: none;
  }
}

/* The status chip's tones, in one place.
   `published` and `archived` are the two most-used tones in the app -- active
   institutions, running plans, live classes, submitted exams, everything
   archived -- and neither was defined anywhere. Every one of those chips
   rendered as the plain grey base, so tables meant to be scanned by colour
   were grey rows of Persian words. `in_review`, `submitted` and `closed`
   existed only inside dashboard.html's own <style>, which is why the platform
   panel's chips were grey too.

   The extra names are not speculation: several chips are written
   `statusChip ${row.status}`, so the class is whatever the column holds, and
   the columns hold exactly these -- pending/active/cancelled from the
   subscription and session constraints, draft/in_review/published/archived
   from the question and exam ones.
   Tokens only, so both themes follow. */
.statusChip.published,
.statusChip.active,
.statusChip.submitted {
  background: color-mix(in srgb, var(--ui-brand) 14%, var(--ui-surface));
  color: var(--ui-brand);
  border-color: color-mix(in srgb, var(--ui-brand) 30%, var(--ui-line));
}
.statusChip.in_review,
.statusChip.pending {
  background: color-mix(in srgb, var(--ui-warn) 14%, var(--ui-surface));
  color: var(--ui-warn);
  border-color: color-mix(in srgb, var(--ui-warn) 30%, var(--ui-line));
}
.statusChip.closed,
.statusChip.cancelled {
  background: color-mix(in srgb, var(--ui-danger) 12%, var(--ui-surface));
  color: var(--ui-danger);
  border-color: color-mix(in srgb, var(--ui-danger) 28%, var(--ui-line));
}
/* The two the exam list asks for that nothing painted. A student's exam is
   `active`, `in_progress`, `completed`, `upcoming` or `expired`, and the
   middle two fell through to the grey base -- so the column that says whether
   a paper is still open or already sat was the one column you could not scan.
   In flight reads as warning, finished reads as information: `active` is
   already the brand tone and two greens in one column distinguish nothing. */
.statusChip.in_progress {
  background: color-mix(in srgb, var(--ui-warn) 14%, var(--ui-surface));
  color: var(--ui-warn);
  border-color: color-mix(in srgb, var(--ui-warn) 30%, var(--ui-line));
}
.statusChip.completed {
  background: color-mix(in srgb, var(--ui-info) 14%, var(--ui-surface));
  color: var(--ui-info);
  border-color: color-mix(in srgb, var(--ui-info) 30%, var(--ui-line));
}
.statusChip.archived,
.statusChip.draft {
  background: color-mix(in srgb, var(--ui-muted) 13%, var(--ui-surface-2));
  color: var(--ui-muted);
  border-color: color-mix(in srgb, var(--ui-muted) 26%, var(--ui-line));
}

/* Charts. The SVG scales to its container and the key sits above or below it,
   so a narrow screen gets a shorter chart rather than a sideways scroll. */
/* The donut is square, so it needs a tighter ceiling than a wide line chart or
   it becomes the tallest thing on the screen. */
.chartDonut .chartSvg {
  /* min-width as well as max-width. The shared .chartSvg rule carries
     min-width:460px for the wide line charts, and a min-width always wins over
     a max-width -- which is why the donut rendered nearly five hundred pixels
     across and became the tallest thing on the page. */
  min-width: 0;
  max-width: 220px;
}
.chartDonut {
  display: grid;
  grid-template-columns: minmax(min(100%, 180px), 220px) minmax(0, 1fr);
  align-items: center;
  gap: 18px;
}
.chartDonut > .chartKeys {
  flex-direction: column;
  align-items: flex-start;
  margin: 0;
}
@media (max-width: 560px) {
  .chartDonut {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A KPI card that leads somewhere. It is a real button, so it needs the
   surrounding cards' geometry back -- a button brings its own padding, border
   and centred text from the base rule. */
button.kpi {
  display: grid;
  gap: 6px;
  text-align: right;
  width: 100%;
  cursor: pointer;
  font: inherit;
}
button.kpi:hover {
  border-color: var(--ui-brand);
}
/* Waiting on somebody. Warm rather than red: an order in the queue is work to
   do, not a failure, and the console reserves red for what has broken. */
.kpi.kpiWaiting {
  background: color-mix(in srgb, var(--ui-warn) 12%, var(--ui-surface));
  border-color: color-mix(in srgb, var(--ui-warn) 34%, var(--ui-line));
}
.kpi.kpiWaiting > b,
.kpi.kpiWaiting > small {
  color: var(--ui-warn);
}

/* A subheading inside a card, and a plain list.
   .cardSubhead is written in ten files and was defined in none of them, so
   every one of those headings sat hard against the control above it with a
   browser default margin and no rule of its own. That is a real part of
   "everything is stuck together": the separator these sections were written
   around did not exist. */
.cardSubhead {
  margin: 22px 0 10px;
  padding-block-start: 16px;
  border-block-start: 1px solid var(--ui-line);
  font-size: 1em;
  color: var(--ui-text);
}
/* The first one in a card has nothing above it to be separated from. */
.card > .cardSubhead:first-child,
.uiAccordionBody > .cardSubhead:first-child {
  margin-block-start: 0;
  padding-block-start: 0;
  border-block-start: 0;
}
.plainList {
  margin: 8px 0;
  padding-inline-start: 20px;
  color: var(--ui-text);
  line-height: 2;
}
.plainList > li + li {
  border-block-start: 1px solid color-mix(in srgb, var(--ui-line) 60%, transparent);
}

/* Two columns for a form grid, everywhere.
   dashboard.html has declared this in its own <style> since the beginning;
   integrations.html never did, so every `.formGrid` on the platform console
   was a plain block and each field -- the institution search, the onboarding
   form, the bot-account filters -- stretched the full width of the screen on
   its own row. The rule belongs here, where both pages and the next one get
   it, rather than in one page's private stylesheet. */
.formGrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: end;
}
/* One column when two would be narrower than a field can usefully be. */
@media (max-width: 700px) {
  .formGrid {
    grid-template-columns: minmax(0, 1fr);
  }
}
/* A field that genuinely wants the whole row says so. */
.formGrid > .spanAll {
  grid-column: 1 / -1;
}

/* Affiliate sales has dense financial settings and Jalali date-time controls.
   A dedicated layout keeps those controls readable without inheriting the
   overly wide generic form grid or wrapping the fourth KPI onto another row. */
.affiliateSalesSection {
  container-type: inline-size;
}
.affiliateSalesSection #affiliateSalesPanel {
  display: grid;
  gap: 1rem;
}
.affiliateKpis {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--ui-gap);
}
.affiliateKpi {
  display: grid;
  align-content: center;
  min-width: 0;
  min-height: 6rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: linear-gradient(145deg, color-mix(in srgb, var(--ui-brand) 8%, var(--ui-surface)), var(--ui-surface));
  box-shadow: 0 0.55rem 1.5rem var(--ui-shadow);
}
.affiliateKpi span {
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
}
.affiliateKpi b {
  margin-block-start: 0.3rem;
  color: var(--ui-text);
  font-size: var(--ui-step-2);
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.affiliateCard {
  margin: 0;
  border: 1px solid var(--ui-line);
  box-shadow: 0 0.55rem 1.5rem var(--ui-shadow);
}
.affiliateCardHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-block-end: 0.8rem;
}
.affiliateCardHead h3,
.affiliateCardHead p {
  margin-block: 0;
}
.affiliateSwitch {
  flex: 0 0 auto;
  min-height: var(--ui-control-h);
  margin: 0;
  padding: 0.45rem 0.75rem;
  border: 1px solid color-mix(in srgb, var(--ui-brand) 34%, var(--ui-line));
  border-radius: var(--ui-radius-pill);
  background: color-mix(in srgb, var(--ui-brand) 8%, var(--ui-surface));
}
.affiliateFieldset {
  margin-block-start: 0.85rem;
  padding-block-start: 0.8rem;
  border-block-start: 1px solid color-mix(in srgb, var(--ui-line) 72%, transparent);
}
.affiliateFieldset h4 {
  margin: 0 0 0.35rem;
  color: var(--ui-text);
  font-size: var(--ui-step-0);
}
.affiliateFormGrid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: end;
  gap: 0.65rem var(--ui-gap);
}
.affiliateField {
  min-width: 0;
}
.affiliateField > label {
  display: block;
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
}
.affiliateField > :is(input, select, textarea),
.affiliateField .jalaliDateTime {
  width: 100%;
  max-width: none;
}
.affiliateField--compact {
  max-width: 18rem;
}
.affiliateField--date {
  min-width: 0;
}
.affiliateField--wide,
.affiliateBankFields {
  grid-column: 1 / -1;
}
.affiliateBankFields {
  padding: 0.75rem;
  border: 1px dashed color-mix(in srgb, var(--ui-brand) 38%, var(--ui-line));
  border-radius: var(--ui-radius);
  background: color-mix(in srgb, var(--ui-brand) 5%, var(--ui-surface-2));
}
.affiliateBankFields[hidden] {
  display: none;
}
.affiliateActions {
  display: flex;
  justify-content: flex-start;
  padding-block-start: 0.25rem;
}
.affiliateActions .primary {
  min-width: 11rem;
}
.affiliateTable {
  max-height: 24rem;
  overflow: auto;
}
.affiliateTable thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  background: var(--ui-surface-2);
}

@container (max-width: 1050px) {
  .affiliateFormGrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@container (max-width: 720px) {
  .affiliateKpis {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .affiliateCardHead {
    align-items: stretch;
    flex-direction: column;
  }
  .affiliateSwitch {
    align-self: flex-start;
  }
  .affiliateFormGrid {
    grid-template-columns: minmax(0, 1fr);
  }
  .affiliateField--compact {
    max-width: none;
  }
  .affiliateField--wide,
  .affiliateBankFields {
    grid-column: auto;
  }
  .affiliateResponsiveTable {
    position: relative;
    max-height: none;
    overflow: visible;
    border: 0;
    background: transparent;
  }
  .affiliateResponsiveTable table,
  .affiliateResponsiveTable tbody,
  .affiliateResponsiveTable tr,
  .affiliateResponsiveTable td {
    display: block;
    width: 100%;
    min-width: 0;
  }
  .affiliateResponsiveTable thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .affiliateResponsiveTable tbody {
    display: grid;
    gap: 0.75rem;
  }
  .affiliateResponsiveTable tr {
    overflow: hidden;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius-lg);
    background: var(--ui-surface-2);
  }
  .affiliateResponsiveTable td {
    display: grid;
    grid-template-columns: minmax(0, 38%) minmax(0, 1fr);
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem;
    border: 0;
    border-block-end: 1px solid var(--ui-line);
    text-align: start;
    overflow-wrap: anywhere;
  }
  .affiliateResponsiveTable td:last-child {
    border-block-end: 0;
  }
  .affiliateResponsiveTable td::before {
    content: attr(data-label);
    color: var(--ui-muted);
    font-size: var(--ui-step--1);
    font-weight: 700;
  }
  .affiliateResponsiveTable td[dir="ltr"] {
    direction: rtl;
  }
  .affiliateResponsiveTable td > button {
    width: 100%;
    min-width: 0;
    direction: ltr;
  }
  .affiliateResponsiveTable .affiliateEmptyRow td {
    display: block;
    text-align: center;
  }
  .affiliateResponsiveTable .affiliateEmptyRow td::before {
    content: none;
  }
}
@container (max-width: 430px) {
  .affiliateKpis {
    grid-template-columns: minmax(0, 1fr);
  }
  .affiliateKpi {
    min-height: 5.25rem;
  }
  .affiliateActions .primary {
    width: 100%;
  }
  .affiliateCard {
    padding: 1rem;
  }
  .affiliateResponsiveTable td {
    grid-template-columns: 1fr;
    gap: 0.25rem;
  }
}

/* What happens to the days you have already paid for, and why a plan is not
   available. Neither class had a rule: the upgrade note inherited the plain
   `.notice` and the refusal was grey body text under the price, which is the
   least likely place anyone reads before clicking buy. These are the two
   sentences that decide the purchase, so they are given the weight of one. */
.planNote,
.planBlocked {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 8px;
  margin: 12px 0 0;
  padding: 11px 13px;
  border-radius: var(--ui-radius);
  border: 1px solid;
  font-size: 0.9em;
  line-height: 1.85;
}
.planNote::before,
.planBlocked::before {
  font-size: 1.05em;
  line-height: 1.6;
}
/* The good news: the remaining days are carried over, not lost. */
.planNote {
  background: color-mix(in srgb, var(--ui-brand) 10%, var(--ui-surface));
  border-color: color-mix(in srgb, var(--ui-brand) 32%, var(--ui-line));
  color: var(--ui-text);
}
.planNote::before {
  content: "✔";
  color: var(--ui-brand);
}
/* The refusal, in the warning tone the console uses for "not now" rather than
   the danger tone it keeps for "something broke". */
.planBlocked {
  background: color-mix(in srgb, var(--ui-warn) 11%, var(--ui-surface));
  border-color: color-mix(in srgb, var(--ui-warn) 32%, var(--ui-line));
  color: var(--ui-text);
}
.planBlocked::before {
  content: "!";
  font-weight: 700;
  color: var(--ui-warn);
}

/* The struck-through list price beside what a plan actually sells for. */
.planWasCell {
  color: var(--ui-muted);
  text-decoration: line-through;
}

/* The platform's testing panel: the stages of a seeding run, listed as they
   finish. A plain list rather than a progress bar, because what somebody
   watching wants is which stage is slow, not a percentage that means nothing
   until it is done. */
.ptSteps {
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 6px;
}
.ptSteps li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
}

/* A sent message, shown as it went. `pre` because the line breaks in an SMS
   are part of it, wrapped because a pre that scrolls sideways is the one thing
   a person reading a message does not want. */
.mkSentText {
  margin: 0 0 10px;
  padding: 14px 16px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
  color: var(--ui-text);
  font: inherit;
  line-height: 2;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ------------------------------------------------------------------------
   One frame for every chart, and it is the server monitor's.

   `.smCard` is the reference and is listed here with the rest rather than
   being copied: four charts drawn in four containers is how a dashboard ends
   up looking assembled instead of designed. Anything that draws a chart gets
   this, and a fifth chart gets it by naming the class.
   ------------------------------------------------------------------------ */
.dtChartFrame,
.chartCard,
.chartDonut,
.trendChartWrap,
.smCard {
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
}
.dtChartFrame,
.chartCard,
.chartDonut,
.trendChartWrap {
  padding: 16px;
}
/* The head of a chart reads like the monitor's: the title quiet and small, the
   figure or the chip loud. A chart whose title shouts competes with its own
   drawing. */
.dtChartFrame > h3,
.chartCard > h3,
.trendChartWrap > h3 {
  margin: 0 0 12px;
  font-size: 0.95em;
  font-weight: 700;
  color: var(--ui-muted);
}
/* Strokes, the same everywhere. The monitor's rings are 11 against a radius of
   42 and end in a round cap; lines and bars follow that weight so a dashboard
   of charts looks drawn by one hand. */
.chartSvg polyline,
.chartSvg path,
.trendChart polyline,
.trendChart path {
  stroke-linecap: round;
  stroke-linejoin: round;
}
.chartTrack {
  stroke: var(--ui-line);
}
.chartBar,
.smGauge circle {
  transition:
    fill 0.15s ease,
    stroke 0.15s ease;
}
/* A chart inside a card should not draw a second card around itself. The
   dashboard puts these in `.card` already, and two borders one pixel apart is
   the thing that made the overview look busy. */
.card > .chartDonut,
.card > .trendChartWrap,
.card > .chartCard {
  border: 0;
  padding: 0;
  background: transparent;
}

/* ------------------------------------------------------------------------
   One rhythm down a dashboard section.

   Every card in a section carried its own `margin-top`, several of them
   inline, so the distance between two blocks depended on which two they were:
   18px here, nothing there, and a clamp somewhere else. Cards are laid out by
   the section now, with one gap, and the margins they brought are dropped --
   `gap` cannot be collapsed or doubled the way adjacent margins can.
   ------------------------------------------------------------------------ */
.dtDashboardShell .content > .section {
  display: grid;
  align-content: start;
  gap: clamp(1rem, 1.6vw, 1.5rem);
}
.dtDashboardShell .content > .section > * {
  margin-block: 0;
  min-width: 0;
}
/* The head is part of the rhythm rather than a block with a margin of its own,
   so the distance from a title to its content is the same distance as between
   two cards -- which is what makes a column of sections look like one page. */
.dtDashboardShell .content > .section > .sectionHead {
  margin-block-end: 0;
}

/* A list of short labels -- the school grades, two or three words each -- is
   not the same layout as a list of modules with a paragraph under each. In the
   wide grid the tick box sat at one end of a sixteen-rem cell and its label at
   the other, three lines tall, and the columns read as a wall rather than as a
   set of choices.

   Chips: each cell as wide as it needs to be, the box beside its own words. */
.choiceGridCompact {
  /* Not a grid at all. Equal columns mean the widest label sets every column,
     or -- narrow enough to fit many -- «دهم علوم تجربی» breaks over three
     lines in every cell. These are chips: each one as wide as its own words,
     wrapping when the row is full, which is what the other tick-box groups on
     these screens already look like. */
  display: flex;
  flex-wrap: wrap;
}
.choiceGridCompact .choiceOption {
  /* Only the sizing. The display comes from `.choiceOption` itself, in one
     place, so there is no second copy of this decision to drift. */
  flex: 0 0 auto;
  gap: 0.5rem;
  align-items: center;
  padding: 0.5rem 0.75rem;
  min-height: 44px;
}
.choiceGridCompact .choiceOption span {
  flex: 0 0 auto;
  line-height: 1.6;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Dashboard chassis.

   A pass over the shell every panel is drawn inside: what a heading weighs,
   how far apart two sections sit, where depth is allowed, and how long a state
   change takes. It spends the tokens declared at the top of this file and adds
   no colour of its own -- every value below resolves to one of the palette
   tokens or to a transparent mix of one, so both themes follow automatically.
   --------------------------------------------------------------------------- */

/* Type. Sizes and weights, with tracking pinned to normal: the joins in
   Persian letterforms do not survive a tighten. */
h1,
h2,
h3,
h4 {
  letter-spacing: var(--ui-track);
  line-height: var(--ui-lead-display);
  text-wrap: balance;
  margin-block: 0 var(--ui-space-2);
}
h1,
h2 {
  font-weight: 700;
}
h3,
h4,
legend {
  font-weight: 600;
}
/* h4 and legend had no size anywhere; the three above are set by the scoped
   rules further up, which are the scale. */
h4,
legend {
  font-size: var(--ui-step-1);
}
p,
li,
td,
label {
  line-height: var(--ui-lead-body);
}
small,
.hint,
.fieldHelp,
.muted small {
  font-size: var(--ui-step--1);
  line-height: 1.7;
}

/* Figures that sit in a column line up, and this is the one place the tighten
   is welcome -- digits, not script. */
td,
th,
.metricCard b,
.statusChip,
output {
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ui-track-tabular);
}

/* Rhythm. Sections breathe at one interval instead of at whatever margin each
   rule happened to set. */
.section {
  margin-block-end: var(--ui-space-6);
}
.section > h2:first-child,
.sectionHead {
  margin-block-end: var(--ui-space-4);
}
.sectionHead p {
  margin-block: var(--ui-space-1) 0;
}
.card + .card,
.card + .tableWrap,
.tableWrap + .card {
  margin-block-start: var(--ui-space-4);
}

/* Depth. A card sits on a page that is already a different colour, so the
   hairline is the whole of its edge and nothing heavier is needed. */
.card,
.hero,
.metricCard,
.emptyState {
  box-shadow: none;
  transition:
    border-color var(--ui-quick) var(--ui-ease),
    background var(--ui-quick) var(--ui-ease);
}
.metricCard {
  gap: var(--ui-space-1);
}
.metricCard b {
  font-size: var(--ui-step-3);
  line-height: 1.2;
}

/* Chrome. The two fixed surfaces become material: translucent, blurred, with a
   hairline on the edge the content passes. Nothing else on the page floats. */
/* The sidebar stays on screen, and on desktop it already did.

   Above 901px the shell is an app shell: `html` and `body` are
   `overflow: hidden`, the shell is `height: 100dvh`, the sidebar is
   `height: 100%` and `.content` scrolls on its own. So the sidebar never
   moves -- not because it is sticky, but because nothing under it scrolls.
   Measured: the content pane scrolled 500px and the sidebar's top stayed at
   0. That is a better answer than sticky and it is why the earlier
   `position: sticky` written here did nothing: a rule four thousand lines up
   holds it `static !important` precisely because it does not need to be.

   What follows is the case that rule does not cover -- a shell that never
   became an app shell, because dashboard-navigation.js did not run or
   `:has()` did not match. There the page itself scrolls and the sidebar goes
   with it. Sticky costs nothing where the app shell engages (the element
   never scrolls anyway) and is the whole answer where it does not. */
@media (min-width: 901px) {
  .shell:not(.dtDashboardShell) > .side,
  .shell:not(.dtDashboardShell) > .sidebar {
    position: sticky;
    top: 0;
    align-self: start;
    max-height: 100dvh;
    overflow-y: auto;
  }
}

/* The sidebar is not pinned here either, and not because nobody tried.

   A rule from before this pass makes it static on purpose:

       @media (min-width: 901px) { .dtDashboardSidebar { position: static !important } }

   and dashboard-navigation.js puts `dtDashboardSidebar` on every shell it
   finds, on both pages. So a `.shell > .side { position: sticky }` written
   here is not overridden loudly -- it simply never applies anywhere, on any
   screen, while reading as though the sidebar were pinned. It was in this
   file for an afternoon doing exactly that.

   If the sidebar should pin on desktop, the rule above is the one to change,
   and it should be changed deliberately rather than shadowed from further
   down the file. */
/* The page header is not pinned, and the reason is worth writing down.

   It was, for one afternoon, and it could not be made to work. `.content` is
   the scroll container on these pages -- `body` and `.shell` are
   `overflow: hidden` and `.content` is `overflow: auto` -- so it clips
   everything its descendants paint. The shell puts a 24px column gap between
   that scrollport and the sidebar, and a pinned bar's background can never
   cross into it: not with a negative margin, not with an overhanging pseudo
   element, not with any distance, because the clip is at the scrollport's
   edge and the bar lives inside it. Three attempts, each one measured, each
   one stopping at exactly the same pixel.

   The seam that produced -- a band of chrome ending 24px short of the sidebar
   with the page visible in the strip -- only existed because something was
   painting a background there. Unpinned there is no band and no seam, and the
   gutter is what it always was: empty column gap that nothing scrolls through,
   since the scrollport clips at its own edge.

   The sidebar is still pinned. That is the half worth having -- it is outside
   `.content`, so it is not clipped, and navigation is what a reader actually
   wants to keep. */
.content > .top {
  padding-block-end: var(--ui-space-3);
  margin-block-end: var(--ui-space-4);
  border-block-end: 1px solid color-mix(in srgb, var(--ui-line) 60%, transparent);
}

/* The badge above the page title is a label, so it takes the caption step
   rather than whatever a span inherits. The title itself stays on the scale:
   it was shrunk while the header was pinned -- a title that is permanently on
   screen is a label -- and the header is not pinned any more, so it is a
   headline again. */
.content > .top .badge {
  font-size: var(--ui-step--1);
}

/* The content column's padding comes from the token the sticky bar bleeds by,
   so the bar always covers exactly the column and never a pixel more. The
   pages set this inline as a raw 30px; this is that number, named. */
.shell > .content {
  padding-inline: var(--ui-content-pad);
}
@media (max-width: 900px) {
  :root {
    --ui-content-pad: 1.125rem;
  }
}

/* The one piece of state a dashboard shows at all times, drawn like it. */
.side nav button {
  border-radius: var(--ui-radius-pill);
  padding-inline: var(--ui-space-3);
  transition:
    background var(--ui-quick) var(--ui-ease),
    color var(--ui-quick) var(--ui-ease);
}
.side nav button:hover {
  background: color-mix(in srgb, var(--ui-brand) 8%, transparent);
  color: var(--ui-text);
}
.side nav button.active {
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
  font-weight: 600;
}

/* The primary action is a capsule and everything else is a rectangle, so the
   one thing a panel most wants you to do is shaped differently from the six
   things beside it rather than merely coloured differently. */
.primary {
  border-radius: var(--ui-radius-pill);
  padding-inline: var(--ui-space-4);
}
button {
  transition:
    background var(--ui-quick) var(--ui-ease),
    border-color var(--ui-quick) var(--ui-ease),
    transform var(--ui-quick) var(--ui-ease);
}
button:active:not(:disabled) {
  transform: scale(0.97);
}

/* A table is read by scanning down a column, so the head has to stay put while
   that happens. The wrapper already scrolls; this makes its head the fixed
   part of that scroll. */
.tableWrap {
  box-shadow: var(--ui-elev-hairline);
  border-color: transparent;
}
.tableWrap th {
  position: sticky;
  top: 0;
  z-index: 1;
}

/* The two things that genuinely float. */
.uiModalCard,
#toast {
  box-shadow: var(--ui-elev-raised);
}

/* Motion is a courtesy, not a requirement; the file already turns it all off
   for anybody who asked, and these inherit that. */
@media (prefers-reduced-motion: reduce) {
  button:active:not(:disabled) {
    transform: none;
  }
}

/* Below the tablet breakpoint the sidebar stops being a column and the sticky
   chrome would eat the screen, so both go back to flowing with the page. */
@media (max-width: 900px) {
  .side nav {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 8.5rem), 1fr));
    gap: var(--ui-space-1);
  }
  .side nav button {
    margin: 0;
    text-align: center;
  }
  .content > .top {
    border-block-end: 0;
  }
  .section {
    margin-block-end: var(--ui-space-5);
  }
}

/* The plan list.

   It was a nine-column table whose last column held four buttons, so it ran
   about 1500px wide and the screen scrolled sideways to reach «حذف». A table
   suits many rows of a few facts; this is eight rows of a dozen facts, most of
   which need their label to mean anything -- «۱۰۰» is not a number anybody can
   read without «دانش‌آموز» next to it, which is why three columns repeated
   their unit in every single cell.

   The grid reflows instead of scrolling: as many cards as fit, one per column
   on a phone, and no minimum width for the page to exceed. */
.planGrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr));
  gap: var(--ui-space-3);
  margin-block-start: var(--ui-space-4);
}
.planCard {
  /* A column rather than a grid, so the actions can be pushed to the bottom.
     Cards in one row are the same height -- the tallest sets it -- and with
     the rows packed to the top the buttons landed wherever each card's
     content happened to end, so no two rows of buttons lined up. */
  display: flex;
  flex-direction: column;
  gap: var(--ui-space-3);
  padding: var(--ui-pad-card);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
}
/* A plan that is not on sale is still a plan, so it is dimmed rather than
   hidden -- and dimmed on the card's own ground, not by opacity, which would
   have taken its text below the contrast the palette guarantees. */
.planCard.isHidden {
  background: var(--ui-surface-2);
  border-style: dashed;
}
.planCardHead {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: var(--ui-space-2);
}
.planCardHead h3 {
  margin: 0;
}
.planCardHead small {
  display: block;
}
/* The price leads: it is what this list is opened to check. */
.planCardPrice {
  display: grid;
  gap: var(--ui-space-1);
  padding-block: var(--ui-space-2);
  border-block: 1px solid var(--ui-line);
  font-size: var(--ui-step-1);
}
.planFacts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 7rem), 1fr));
  gap: var(--ui-space-2);
  margin: 0;
}
.planFacts > div {
  display: grid;
  gap: 0.1rem;
}
.planFacts dt {
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
}
.planFacts dd {
  margin: 0;
  font-weight: 600;
}
/* A share of a whole, drawn as a share of a whole. It was a coloured dot and a
   percentage, which said «this one is greenish» and nothing about how the
   plans compare. */
.planCoverage {
  display: grid;
  gap: var(--ui-space-1);
}
.planCoverageHead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ui-space-2);
}
.planBar {
  block-size: 0.5rem;
  border-radius: var(--ui-radius-pill);
  background: color-mix(in srgb, var(--ui-line) 70%, var(--ui-surface-2));
  overflow: hidden;
}
.planCard > .actions {
  margin-block-start: auto;
  margin-block-end: 0;
}
.planBar i {
  display: block;
  block-size: 100%;
  border-radius: inherit;
  background: var(--ui-brand);
}

/* The video library.

   A card per video with the player on top, because a wall of titles tells a
   student nothing about which lesson they missed -- the thumbnail Aparat draws
   in the frame is the only thing that does. The frame keeps 16:9 whatever the
   column width, so the grid reflows without the players changing shape.

   A draft is dimmed on its own ground rather than by opacity, the same rule
   the plan cards follow: opacity would take the text under the contrast the
   palette guarantees. */
.videoGrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: var(--ui-space-3);
  margin-block: var(--ui-space-2) var(--ui-space-4);
}
.videoCard {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
  overflow: hidden;
}
.videoCard.isDraft {
  background: var(--ui-surface-2);
  border-style: dashed;
}
.videoFrame {
  aspect-ratio: 16 / 9;
  background: var(--ui-surface-2);
}
.videoFrame iframe {
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  display: block;
}
.videoBody {
  display: flex;
  flex-direction: column;
  gap: var(--ui-space-1);
  padding: var(--ui-pad-card);
}
.videoHead {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: var(--ui-space-2);
}
.videoHead h5 {
  margin: 0;
  font-size: var(--ui-step-1);
  font-weight: 600;
}
.videoBody > .actions {
  margin-block-start: auto;
}

/* The live session room.

   A modal rather than a page: a teacher opens a session from the list they
   were already looking at and goes back to it afterwards, and a session that
   owned the whole screen would lose them the roll they were taking. */
.liveCard {
  width: min(1100px, 100%);
}
.liveStage {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--ui-space-2);
  margin-block: var(--ui-space-3);
}
.liveTile {
  position: relative;
  border-radius: var(--ui-radius);
  overflow: hidden;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
}
.liveVideo {
  aspect-ratio: 16 / 9;
  display: grid;
  place-items: center;
}
.liveVideo video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
}
/* The name sits on the picture, so it needs its own ground to stay legible
   against whatever is behind it -- a dark classroom or a white slide. */
.liveName {
  position: absolute;
  inset-block-end: 0.35rem;
  inset-inline-start: 0.35rem;
  padding: 0.15rem 0.5rem;
  border-radius: var(--ui-radius-pill);
  background: color-mix(in srgb, var(--ui-surface) 82%, transparent);
  backdrop-filter: blur(6px);
  color: var(--ui-text);
  font-size: var(--ui-step--1);
}

/* Choosing an audience by group.
   Sits inside the session form, so it must read as a part of that form rather
   than a second card arguing with it: same surface family, no shadow of its
   own, and its buttons on a row of their own so they never touch the selects
   above them. */
.audiencePick{
  margin:var(--ui-space-3) 0;
  background:var(--ui-surface-2);
  box-shadow:none;
  border:1px solid var(--ui-line);
}
.audiencePick > p.muted{margin:0 0 var(--ui-space-2)}
.audiencePick > .actions{margin-top:var(--ui-space-3)}
.audiencePick .hint{align-self:center}

/* The live session, in the page.
   It used to be a modal over the dashboard. A lesson is an hour of somebody's
   day, not an alert, so it is a card at the top of its own section: the stage
   takes the width it needs and one side column carries the people, the queue
   and the conversation -- as tabs, because three panels stacked in one column
   left each of them too short to be read. */
.liveRoom {
  margin-bottom: var(--ui-space-4);
}
.liveLayout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
  gap: var(--ui-space-3);
  align-items: stretch;
}
.liveMain {
  display: flex;
  flex-direction: column;
  gap: var(--ui-space-2);
  min-width: 0;
}

/* The bar under the stage.
   What this person is doing is on one side and what they are doing it to is
   on the other, so «تمام‌صفحه» is not one more identical button in a row of
   five. */
.liveBar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ui-space-2);
  padding: var(--ui-space-2);
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.liveBar > .actions {
  margin-top: 0;
  flex-wrap: wrap;
}

/* A toggle says which way it is set.
   The alternative was two classes swapped by hand at four call sites, which
   is four places for the button and the camera to disagree. */
button[aria-pressed="true"] {
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
  border-color: var(--ui-brand);
}

/* The teacher's transport for a file they are playing.
   Inside the file's own tile, because that tile only exists in the browser
   that has the file -- there is no viewer to hide it from. */
.liveFileBar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.5rem 0.65rem;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.liveFileBar button {
  font-size: var(--ui-step--1);
  padding: 0.3rem 0.55rem;
  min-height: 0;
}
.liveSeek {
  flex: 1 1 8rem;
  min-width: 0;
  /* The browser's own 2px side margin on a range input, which no author rule
     sets and every :not([type=range]) list therefore misses. */
  margin-inline: 0;
}
.liveFileTime {
  flex: 0 0 auto;
  font-size: var(--ui-step--1);
  color: var(--ui-muted);
  font-variant-numeric: tabular-nums;
}

/* `.hidden` lives here, not only in each page's own <style>.
   It was defined four times, once per page, and any component in this file
   that toggles it was hostage to that boilerplate: a new page without the
   line, and every accordion, tab and role-gated menu item in it stops
   closing. `!important` because hiding is the one thing that must win over a
   component's own display, and because the four copies it replaces all had
   it. */
.hidden {
  display: none !important;
}

/* -------------------------------------------------------------- the tiles */
.liveTile {
  position: relative;
}
/* Over the picture, not under it: a caption below each tile made the grid rows
   ragged whenever one name wrapped and another did not. */
.liveTileBar {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.35rem 0.5rem;
  background: linear-gradient(to top, var(--ui-shadow), transparent);
}
.liveTileBar .liveName {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The label is for a screen reader and for the icon rule to read; the tile
   shows the icon alone, because a tile is mostly picture. */
.liveTileExpand {
  flex: 0 0 auto;
  font-size: 0;
  padding: 0.3rem;
  min-height: 0;
}
.liveTile:fullscreen,
.liveStage:fullscreen {
  background: var(--ui-bg);
}
.liveStage:fullscreen {
  padding: var(--ui-space-3);
}

/* --------------------------------------------------------- the side column */
.liveSide {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 24rem;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  overflow: hidden;
}
.liveTabs {
  display: flex;
  flex: 0 0 auto;
  border-bottom: 1px solid var(--ui-line);
}
.liveTab {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-width: 0;
  padding: 0.6rem 0.4rem;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
}
.liveTab.active {
  color: var(--ui-text);
  border-bottom-color: var(--ui-brand);
  background: var(--ui-surface);
}
.liveTab::before {
  content: none !important;
}
.livePanel {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--ui-space-2);
  gap: var(--ui-space-2);
}
/* The one rule that makes a long list usable: the panel is the height the
   column gives it, and the list inside scrolls. Without this a roster of
   forty people pushed the stage off the screen. */
.liveScroller {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ------------------------------------------------------------- the roster */
.liveRosterRow,
.liveQueueRow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--ui-line);
}
.liveRosterRow:last-child,
.liveQueueRow:last-child {
  border-bottom: 0;
}
.liveRosterWho {
  display: grid;
  gap: 0.25rem;
  min-width: 0;
}
.liveRosterName,
.liveQueueName {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.liveRosterState {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}
/* A row's own buttons, smaller than the ones in the bar: they belong to one
   person, not to the session. */
.liveRowActions {
  /* Stated rather than inherited from .actions. The markup always carries both
     classes, but a rule that depends on that is a rule that breaks the day
     somebody writes only one of them. */
  display: flex;
  margin-top: 0;
  flex-wrap: wrap;
}
.liveRowActions button {
  font-size: var(--ui-step--1);
  padding: 0.3rem 0.55rem;
  min-height: 0;
}
.liveScroller h5 {
  margin: var(--ui-space-2) 0 0.25rem;
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
}
.liveScroller h5:first-child {
  margin-top: 0;
}

/* --------------------------------------------------------------- the chat */
.liveChatLog {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.5rem;
  background: var(--ui-field);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
/* A bubble, not a log line. Yours on one side and everyone else's on the
   other is the whole of what makes a transcript read as a conversation. */
.liveBubble {
  display: grid;
  gap: 0.1rem;
  max-width: 85%;
  align-self: flex-start;
  padding: 0.4rem 0.6rem;
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  border-start-start-radius: 0.25rem;
}
.liveBubble.mine {
  align-self: flex-end;
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
  border-color: var(--ui-brand);
  border-start-start-radius: var(--ui-radius);
  border-start-end-radius: 0.25rem;
}
.liveBubble > b {
  font-size: var(--ui-step--1);
  color: var(--ui-muted);
}
.liveBubble.mine > b,
.liveBubble.mine > time {
  color: inherit;
  opacity: 0.8;
}
.liveBubble > time {
  font-size: var(--ui-step--1);
  color: var(--ui-muted);
  justify-self: end;
}
.liveChatForm {
  display: flex;
  flex: 0 0 auto;
  gap: 0.5rem;
}
.liveChatForm > input {
  flex: 1 1 auto;
  min-width: 0;
}
.liveChatAdmin {
  margin-top: 0;
}

@media (max-width: 900px) {
  .liveLayout {
    grid-template-columns: minmax(0, 1fr);
  }
  .liveSide {
    min-height: 20rem;
  }
}

/* Choosing an audience by group.
   Sits inside the session form, so it must read as a part of that form rather
   than a second card arguing with it: same surface family, no shadow of its
   own, and its buttons on a row of their own so they never touch the selects
   above them. */
.audiencePick{
  margin:var(--ui-space-3) 0;
  background:var(--ui-surface-2);
  box-shadow:none;
  border:1px solid var(--ui-line);
}
.audiencePick > p.muted{margin:0 0 var(--ui-space-2)}
.audiencePick > .actions{margin-top:var(--ui-space-3)}
.audiencePick .hint{align-self:center}

/* The live session, in the page.
   It used to be a modal over the dashboard. A lesson is an hour of somebody's
   day, not an alert, so it is a card at the top of its own section: the stage
   takes the width it needs and the roster and the chat share a side column
   that drops underneath when there is not room for both. */
.liveRoom {
  margin-bottom: var(--ui-space-4);
}
.liveLayout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
  gap: var(--ui-space-3);
  align-items: start;
}
.liveMain {
  min-width: 0;
}
.liveRoster h4,
.liveChat h4 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 0 0 var(--ui-space-2);
}
.liveRosterRow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--ui-line);
}
.liveRosterRow:last-child {
  border-bottom: 0;
}
/* A fixed height, deliberately: the chat fills and scrolls inside itself
   rather than growing and pushing the stage off the screen. */
.liveChatLog {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  height: 15rem;
  overflow-y: auto;
  padding: 0.5rem;
  background: var(--ui-field);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.liveChatRow {
  display: grid;
  gap: 0.1rem;
}
.liveChatRow > b {
  font-size: var(--ui-step--1);
  color: var(--ui-muted);
}
.liveChatForm {
  display: flex;
  gap: 0.5rem;
  margin-top: var(--ui-space-2);
}
.liveChatForm > input {
  flex: 1 1 auto;
  min-width: 0;
}
@media (max-width: 900px) {
  .liveLayout {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The generated card, in the calendar.
   Small on purpose: this is the row's thumbnail, not the picture. Clicking
   through to the file is what the media URL is for. */
.postCard {
  display: block;
  inline-size: 72px;
  block-size: 72px;
  object-fit: cover;
  border-radius: var(--ui-radius);
  border: 1px solid var(--ui-line);
  margin-block-end: 0.35rem;
}

/* The uploaded platform logo, wherever the brand is drawn.
   Capped rather than sized: an upload is whatever shape somebody had, and a
   bar that changes height with the picture in it is a bar that jumps. */
.logo .brandLogo {
  block-size: 1.6em;
  inline-size: auto;
  max-inline-size: 9rem;
  object-fit: contain;
  vertical-align: middle;
  margin-inline-end: 0.5rem;
}

/* The logo as it is now, in the appearance panel.
   On a chequered ground, because a transparent logo on a plain card looks
   fine until it is put on a dark bar and the transparency turns out to be
   white. */
.brandNow {
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 6rem;
  margin-block: var(--ui-space-2);
  padding: var(--ui-space-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background-color: var(--ui-field);
  background-image:
    linear-gradient(45deg, var(--ui-line) 25%, transparent 25%),
    linear-gradient(-45deg, var(--ui-line) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--ui-line) 75%),
    linear-gradient(-45deg, transparent 75%, var(--ui-line) 75%);
  background-size: 16px 16px;
  background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}
.brandPreview {
  max-block-size: 5rem;
  max-inline-size: 100%;
  object-fit: contain;
}

/* ---------------------------------------------------------------------------
   The content calendar.

   A list of days, not a table. The table it replaced put the picture, the
   title, the failure, the status, the time and five buttons in six columns,
   and every one of them had to be narrow enough for the others -- so the
   failure that said what to fix was a clipped line of small grey text, which
   is the one thing on the row somebody actually needs to read.

   Grid rather than flex, so the thumbnail column lines up down the whole list
   and the actions stay on the same edge whatever the title does. minmax(0,1fr)
   on the middle column and not 1fr: the failures carry Latin API text with no
   spaces, which is unbreakable, and a bare 1fr takes its floor from the widest
   one and pushes the page sideways.
--------------------------------------------------------------------------- */
.calDay {
  margin-block: 1.1rem 0.45rem;
  font-size: 0.92rem;
  color: var(--ui-muted);
  font-weight: 600;
  padding-block-end: 0.35rem;
  border-block-end: 1px solid var(--ui-line);
}
.calDay:first-child {
  margin-block-start: 0.25rem;
}
.calRow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 0.75rem;
  align-items: start;
  padding: 0.7rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
  margin-block-end: 0.5rem;
}
/* The one row that needs finding in a long list. A border, not a fill: the
   status chip beside it already carries the colour, and two of them shouting
   at once makes the list harder to read rather than easier. */
.calRowFailed {
  border-color: var(--ui-danger);
}
.calBody {
  min-inline-size: 0;
}
.calBody b {
  display: block;
  margin-block-end: 0.2rem;
}
.calFail {
  margin-block: 0.35rem 0;
  color: var(--ui-danger);
  font-size: 0.86rem;
  /* The reason is the point of the row, so it wraps rather than being clipped,
     and it breaks inside a word because the reason is often one long Latin
     sentence from an API with nowhere else to break. */
  overflow-wrap: anywhere;
}
.calSide {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--ui-muted);
}
.calWhen {
  font-variant-numeric: tabular-nums;
  color: var(--ui-text);
}
.calActions {
  grid-column: 1 / -1;
}
.calWhenForm {
  margin-block-start: 0.6rem;
  padding: 0.6rem;
  border: 1px dashed var(--ui-line);
  border-radius: var(--ui-radius);
}
.calFilters button[aria-pressed="true"] {
  border-color: var(--ui-brand);
  color: var(--ui-brand);
}
/* An empty thumbnail keeps the grid's first column the same width on a post
   whose card has not been drawn, so the titles stay in one line down the list. */
.postCard:empty {
  background: var(--ui-surface-2);
}

@media (max-width: 560px) {
  .calRow {
    grid-template-columns: auto minmax(0, 1fr);
  }
  .calSide {
    grid-column: 1 / -1;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 0.6rem;
  }
}

/* ---------------------------------------------------------------------------
   The platform's 24-hour time control, built by ui.js in place of every native
   time field. Laid out as one object so a label sits against it the way it did
   against the input it replaced.
--------------------------------------------------------------------------- */
.dtTime24 {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.dtTime24Part {
  inline-size: auto;
  min-inline-size: 4.4rem;
  font-variant-numeric: tabular-nums;
}
.dtTime24Colon {
  color: var(--ui-muted);
}
/* Inside a date-and-time picker: beside the date when the field is wide
   enough, on its own line under it when not.
   The picker's grid gave the time a 108px column -- the width of the one
   native time field this control replaced. Two selects need about 10rem, so
   the hours were drawn over the date (reported on the CRM's «زمان اقدام
   بعدی», true of every picker placed in half a form row). A wrapping row
   sizes each part to what it holds. */
.jalaliDateTime:has(> .dtTime24) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
.jalaliDateTime:has(> .dtTime24) > .jalaliWrap {
  flex: 1 1 11rem;
  min-width: 0;
}
.jalaliDateTime > .dtTime24 {
  flex: 0 0 auto;
  margin-block-start: 0;
}
.jalaliDateTime:has(> .dtTime24) > .jalaliZone {
  flex: 1 0 100%;
}

/* What will actually be posted, side by side with the card that carries it. */
.previewWrap {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}
.previewCard {
  inline-size: 100%;
  block-size: auto;
  border-radius: var(--ui-radius);
  border: 1px solid var(--ui-line);
}
.previewSide h4 {
  margin-block: 0.9rem 0.3rem;
}
.previewSide h4:first-child {
  margin-block-start: 0;
}
.previewText {
  white-space: pre-wrap;
  /* The caption is the thing being checked, so it breaks rather than scrolls
     out of sight -- and it can carry a long address with nowhere to break. */
  overflow-wrap: anywhere;
  margin: 0;
  padding: 0.6rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius);
  font-family: inherit;
  font-size: 0.9rem;
  max-block-size: 16rem;
  overflow-y: auto;
}

@media (max-width: 560px) {
  .previewWrap {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The contract an institution administrator signs (contract-gate.js). The
   text scrolls inside the card so the accept box and the button stay in view,
   and it is set for reading: a legal text read in a cramped box is a text
   nobody reads. */
.dtContractCard {
  inline-size: min(860px, 100%);
}
.dtContractText {
  max-block-size: 52vh;
  overflow-y: auto;
  margin-block: 12px;
  padding: 14px 18px;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  line-height: 2;
  overflow-wrap: anywhere;
}
.dtContractText h3 {
  margin: 14px 0 4px;
  font-size: 1rem;
}
.dtContractText p {
  margin: 0 0 6px;
}
.contractEditor {
  min-block-size: 420px;
  line-height: 1.9;
}

/* A multi-choice dropdown: the chosen items in the closed bar, ticks when open.
   (Contract settings: plans exempt from signing.) */
.planPicker {
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-field);
  margin-bottom: 6px;
}
.planPicker > summary {
  cursor: pointer;
  padding: 11px 13px;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: 10px;
}
.planPicker > summary::-webkit-details-marker {
  display: none;
}
.planPicker > summary::after {
  content: '▾';
  color: var(--ui-muted);
}
.planPicker[open] > summary::after {
  content: '▴';
}
.planPickerList {
  display: grid;
  gap: 6px;
  padding: 4px 13px 12px;
  max-height: 260px;
  overflow-y: auto;
  border-top: 1px solid var(--ui-line);
}

/* The lead book (leads.js). */
.ldStages {
  display: flex;
  flex-wrap: wrap;
  margin: 4px 0 14px;
}
.ldLinks {
  display: grid;
  gap: 8px;
  margin: 8px 0 12px;
}
.ldLink {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 8px 12px;
  border: 1px solid var(--ui-line);
  border-radius: 10px;
  background: var(--ui-surface-2);
}
.ldLink > span:first-child {
  min-width: 140px;
  color: var(--ui-muted);
}
.ldLink code {
  overflow-wrap: anywhere;
}
.ldRow {
  cursor: pointer;
}
.ldRow:hover td,
.ldRow:focus-visible td {
  background: var(--ui-surface-2);
}
.ldModal {
  width: min(920px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}
.ldModal h4 {
  margin: 18px 0 8px;
}
.ldTimeline {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0 18px 0 0;
}
.ldTimeline p {
  margin: 4px 0 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.ldSample {
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid var(--ui-line);
  border-radius: 10px;
  background: var(--ui-surface-2);
}
.ldSample p {
  margin: 4px 0 8px;
  white-space: pre-wrap;
}
.ldWriter {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  align-items: end;
  margin: 6px 0;
}
@media (max-width: 560px) {
  .ldWriter {
    grid-template-columns: minmax(0, 1fr);
  }
}
/* The send log: the full text of each message, opened in place. */
.ldMsgCell {
  min-width: 220px;
  max-width: 420px;
}
.ldMsgCell summary {
  cursor: pointer;
  color: var(--ui-muted);
}
.ldMsgCell p {
  margin: 6px 0 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--ui-text);
}

/* In-panel notifications (inbox.js). Named dtNote*, not dtToast*: ui.js's
   message strip owns those, and sharing them squeezed its text into a 34px
   column one word per line.
   Original note:: the bell, its list, the popups and the
   counts on menu entries. Every colour from a token, so both themes hold. */
.dtBell {
  position: fixed;
  top: 14px;
  left: 16px;
  z-index: 9000;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  min-height: 44px !important;
  padding: 0 !important;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-pill);
  background: var(--ui-surface);
  color: var(--ui-text);
  box-shadow: 0 8px 24px var(--ui-shadow);
  cursor: pointer;
}
.dtBell svg {
  width: 21px;
  height: 21px;
  fill: currentColor;
}
.dtBell.dtBellHasNew svg {
  color: var(--ui-brand);
}
.dtBellCount {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: var(--ui-radius-pill);
  background: var(--ui-danger);
  color: var(--ui-surface);
  font-size: 0.72rem;
  font-weight: 800;
  line-height: 20px;
  text-align: center;
}
.dtBellPanel {
  position: fixed;
  top: 66px;
  left: 16px;
  z-index: 9001;
  width: min(380px, calc(100vw - 32px));
  max-height: min(70vh, 560px);
  overflow-y: auto;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
  color: var(--ui-text);
  box-shadow: 0 24px 64px var(--ui-shadow);
}
.dtBellPanel header {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--ui-line);
  background: var(--ui-surface);
}
.dtBellPanel header button {
  min-height: 32px !important;
  padding: 4px 10px !important;
  font-size: 0.8rem;
}
.dtBellPanel ol {
  margin: 0;
  padding: 6px;
  list-style: none;
}
.dtBellItem {
  display: grid;
  gap: 2px;
  margin: 4px 0;
  padding: 10px 12px;
  border-inline-start: 4px solid var(--dt-tone, var(--ui-line));
  border-radius: var(--ui-radius);
  cursor: pointer;
}
.dtBellItem:hover,
.dtBellItem:focus-visible {
  background: var(--ui-surface-2);
}
.dtBellItem.dtUnread {
  background: var(--ui-surface-2);
}
.dtBellItem p,
.dtNoteBody p {
  margin: 0;
  color: var(--ui-muted);
  font-size: 0.86rem;
  white-space: pre-line;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.dtBellItem small,
.dtNoteBody small {
  color: var(--ui-muted);
  font-size: 0.75rem;
}
.dtBellEmpty {
  margin: 0;
  padding: 24px;
  color: var(--ui-muted);
  text-align: center;
}
.dtNoteStack {
  position: fixed;
  top: 70px;
  left: 16px;
  z-index: 9002;
  display: grid;
  gap: 10px;
  width: min(380px, calc(100vw - 32px));
  pointer-events: none;
}
.dtNote {
  --dt-tone: var(--ui-info);
  position: relative;
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) 28px;
  gap: 10px;
  align-items: start;
  padding: 12px 12px 14px;
  overflow: hidden;
  border: 1px solid var(--ui-line);
  border-inline-start: 4px solid var(--dt-tone);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
  color: var(--ui-text);
  box-shadow: 0 18px 48px var(--ui-shadow);
  cursor: pointer;
  pointer-events: auto;
  animation: dtNoteIn 0.24s ease-out;
}
.dtNote--success {
  --dt-tone: var(--ui-brand);
}
.dtNote--warning {
  --dt-tone: var(--ui-warn);
}
.dtNote--danger {
  --dt-tone: var(--ui-danger);
}
.dtBellItem.dtNote--info {
  --dt-tone: var(--ui-info);
}
.dtBellItem.dtNote--success {
  --dt-tone: var(--ui-brand);
}
.dtBellItem.dtNote--warning {
  --dt-tone: var(--ui-warn);
}
.dtBellItem.dtNote--danger {
  --dt-tone: var(--ui-danger);
}
.dtNoteIcon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--ui-radius-pill);
  background: var(--dt-tone);
  color: var(--ui-surface);
  font-weight: 900;
}
.dtNoteBody {
  display: grid;
  gap: 3px;
  min-width: 0;
}
.dtNoteClose {
  width: 28px;
  height: 28px;
  min-height: 28px !important;
  padding: 0 !important;
  border: 0;
  border-radius: var(--ui-radius-pill);
  background: transparent;
  color: var(--ui-muted);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}
.dtNoteClose:hover {
  background: var(--ui-surface-2);
}
.dtNote:focus-visible,
.dtBellItem:focus-visible,
.dtBell:focus-visible {
  outline: 3px solid var(--ui-brand);
  outline-offset: 2px;
}
.dtNoteTimer {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--dt-tone);
  opacity: 0.55;
  transform-origin: right;
  animation: dtNoteTime linear forwards;
}
.dtNoteLeaving {
  opacity: 0;
  transform: translateX(-12px);
  transition: opacity 0.2s, transform 0.2s;
}
/* Never from invisible: a tab that is not painting holds the first frame, and
   a notice stuck at opacity 0 is a notice nobody sees. */
@keyframes dtNoteIn {
  from {
    opacity: 0.7;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes dtNoteTime {
  from {
    transform: scaleX(1);
  }
  to {
    transform: scaleX(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .dtNote,
  .dtNoteTimer {
    animation: none;
  }
}
.dtNavCount {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  margin-inline-start: 6px;
  padding: 0 6px;
  border-radius: var(--ui-radius-pill);
  background: var(--ui-danger);
  color: var(--ui-surface);
  font-size: 0.72rem;
  font-weight: 800;
}
@media (max-width: 560px) {
  .dtBell {
    top: 10px;
    left: 10px;
  }
  .dtNoteStack,
  .dtBellPanel {
    left: 10px;
    right: 10px;
    width: auto;
  }
}

/* «ویدئوهای معرفی» editor (promo-videos.js): one card per video, the player
   beside its fields on a wide screen, above them on a phone. */
.pvList {
  display: grid;
  gap: 14px;
  margin: 14px 0;
}
.pvCard {
  display: grid;
  grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
  gap: 16px;
  padding: 14px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
}
.pvPreview {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
  text-align: center;
  padding: 8px;
}
.pvPreview iframe {
  width: 100%;
  height: 100%;
  border: 0;
}
.pvFields {
  display: grid;
  gap: 6px;
  min-width: 0;
}
@media (max-width: 900px) {
  .pvCard {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The live lesson, drawn like a call app (live-room.js).

   A dark stage whose tiles fill it, one pill of round buttons under it, the
   hang-up in red, your own picture floating in a corner. The microphone and
   the camera say their state three ways at once -- red, a slashed icon, and
   aria-pressed -- because «am I on?» is the question of every lesson.

   Scoped to #liveRoom, and the colour and size of a .liveCtl marked
   !important: `.secondary` and `button` already set background, colour and
   border with !important, and only a louder, more specific rule can take a
   button back from them. Nothing outside the room is touched. */
#liveRoom.liveRoom {
  display: flex;
  flex-direction: column;
  gap: var(--ui-space-2);
  padding: var(--ui-space-3);
  background: var(--ui-stage) !important;
  color: var(--ui-stage-ink);
  border: 0 !important;
  border-radius: var(--ui-radius-lg);
}
/* The anchor for everything absolute inside the room: the floating self
   tile, the badges, and on a phone the side buttons and the sheet. The phone
   rule below makes it fixed, which anchors them just the same. */
#liveRoom {
  position: relative;
}
#liveRoom [hidden] {
  display: none !important;
}
#liveRoom .liveHead {
  display: flex;
  align-items: center;
  gap: var(--ui-space-2);
  min-width: 0;
}
#liveRoom .liveHeadText {
  min-width: 0;
}
#liveRoom .liveHead h3 {
  margin: 0;
  color: var(--ui-stage-ink);
  font-size: var(--ui-step-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#liveRoom .liveHead p {
  margin: 0;
  color: var(--ui-stage-ink);
  opacity: 0.72;
  font-size: var(--ui-step--1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#liveRoom .liveNotice,
#liveRoom #liveNotice {
  margin: 0;
}
#liveRoom .liveLayout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--ui-space-3);
  align-items: stretch;
  height: min(78vh, 52rem);
  min-height: 26rem;
}
#liveRoom.sideOpen .liveLayout {
  grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
}
#liveRoom .liveMain {
  display: flex;
  flex-direction: column;
  gap: var(--ui-space-2);
  min-width: 0;
  min-height: 0;
}

/* ------------------------------------------------------------- the stage */
/* live-room.js places every tile itself -- the big one, the strip, the one
   floating in the corner -- as a size and a translate from the stage's top
   left. `left: 0` is physical on purpose: the script's coordinates are, and a
   logical inset would flip them on a right-to-left page. The transition is
   what makes a new speaker a slide rather than a jump. */
#liveRoom .liveStage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  margin: 0;
  display: block;
  overflow: hidden;
}
#liveRoom .liveTile,
#liveRoom .liveMore {
  position: absolute;
  top: 0;
  left: 0;
  transition:
    transform var(--ui-settle) var(--ui-ease),
    width var(--ui-settle) var(--ui-ease),
    height var(--ui-settle) var(--ui-ease);
}
#liveRoom .liveTile {
  min-width: 0;
  min-height: 0;
  background: var(--ui-stage-tile);
  border: 0;
  border-radius: var(--ui-radius-lg);
  overflow: hidden;
  cursor: pointer;
  animation: liveTileIn var(--ui-settle) var(--ui-ease);
}
@keyframes liveTileIn {
  from {
    opacity: 0;
  }
}
#liveRoom .liveTile--off {
  display: none;
}
#liveRoom .liveTile--main {
  z-index: 1;
}
/* The big tile shows the whole picture: a teacher at a board cropped to the
   stage's shape loses the board. The strip crops, the way thumbnails do. */
#liveRoom .liveTile--main .liveVideo video {
  object-fit: contain;
}
#liveRoom .liveTile--small .liveTileBar {
  padding: 0.25rem 0.4rem;
}
#liveRoom .liveTile--small .liveName {
  font-size: 0.72rem;
}
#liveRoom .liveTile--small .liveTileExpand {
  display: none;
}
#liveRoom .liveTile--small .liveAvatar {
  font-size: 1.1rem;
}
/* «+۵ نفر دیگر» where the strip runs out; it opens the list of people. */
#liveRoom .liveMore {
  display: grid;
  place-items: center;
  margin: 0;
  padding: 0 !important;
  min-height: 0 !important;
  border: 0 !important;
  border-radius: var(--ui-radius-lg);
  background: var(--ui-stage-tile) !important;
  color: var(--ui-stage-ink) !important;
  font-weight: 700;
}
#liveRoom .liveMore::before {
  content: none !important;
}
/* The ring of whoever is talking, drawn over the picture so the video does
   not hide it. */
#liveRoom .liveTile::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 3px transparent;
  transition: box-shadow var(--ui-quick) var(--ui-ease);
  pointer-events: none;
  z-index: 2;
}
#liveRoom .liveTile.isSpeaking::after {
  box-shadow: inset 0 0 0 3px var(--ui-stage-on);
}
#liveRoom .liveVideo {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
}
#liveRoom .liveVideo video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}
#liveRoom .liveTile--screen .liveVideo video {
  object-fit: contain;
}
/* Your own camera as a mirror, which is how everybody expects to see
   themselves; the room receives it the right way round. */
#liveRoom .liveTile--self .liveVideo video {
  transform: scaleX(-1);
}
#liveRoom .liveTile.noCam .liveVideo {
  visibility: hidden;
}
#liveRoom .liveAvatar {
  position: absolute;
  inset: 0;
  margin: auto;
  display: none;
  place-items: center;
  inline-size: clamp(3rem, 24%, 7rem);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
  font-size: clamp(1.25rem, 5vmin, 2.75rem);
  font-weight: 700;
}
#liveRoom .liveTile.noCam .liveAvatar,
#liveRoom .liveVideo:empty + .liveAvatar {
  display: grid;
}
#liveRoom .liveTileBar {
  z-index: 3;
  gap: 0.4rem;
  padding: 0.4rem 0.55rem;
  background: linear-gradient(to top, color-mix(in srgb, var(--ui-stage) 75%, transparent), transparent);
}
#liveRoom .liveName {
  position: static;
  flex: 1 1 auto;
  padding: 0;
  background: none;
  backdrop-filter: none;
  color: var(--ui-stage-ink);
  font-size: var(--ui-step--1);
  text-shadow: 0 1px 2px var(--ui-stage);
}
#liveRoom .liveMuted {
  display: none;
  flex: 0 0 auto;
  place-items: center;
  inline-size: 1.6rem;
  block-size: 1.6rem;
  border-radius: 50%;
  background: var(--ui-stage-off);
  color: var(--ui-stage-ink);
}
#liveRoom .liveTile.isMuted .liveMuted {
  display: grid;
}
#liveRoom .liveMuted svg {
  inline-size: 1rem;
  block-size: 1rem;
}
#liveRoom .liveTileExpand,
#liveRoom .liveTilePin {
  display: grid;
  place-items: center;
  inline-size: 1.9rem !important;
  block-size: 1.9rem !important;
  min-height: 0 !important;
  padding: 0 !important;
  border: 0 !important;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ui-stage) 55%, transparent) !important;
  color: var(--ui-stage-ink) !important;
  opacity: 0;
  transition: opacity var(--ui-quick);
}
#liveRoom .liveTileExpand::before,
#liveRoom .liveTilePin::before {
  content: none !important;
}
/* Only on the tile that is pinned, and always visible there: it is the way
   back to the room choosing for itself. */
#liveRoom .liveTilePin {
  display: none;
}
#liveRoom .liveTile--pinned .liveTilePin {
  display: grid;
  opacity: 1;
}
#liveRoom .liveTilePin svg,
#liveRoom .liveTileExpand svg {
  inline-size: 1rem;
  block-size: 1rem;
}
#liveRoom .liveTile:hover .liveTileExpand,
#liveRoom .liveTileExpand:focus-visible {
  opacity: 1;
}
@media (hover: none) {
  #liveRoom .liveTileExpand {
    opacity: 1;
  }
}
/* You, in the corner, once there is one other person on the stage. */
#liveRoom .liveTile--float {
  z-index: 4;
  box-shadow: 0 0.6rem 1.6rem var(--ui-stage);
  outline: 1px solid color-mix(in srgb, var(--ui-stage-ink) 18%, transparent);
}
#liveRoom .liveTile--float .liveTileExpand {
  display: none;
}

/* --------------------------------------------------------------- the bar */
#liveRoom .liveBar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--ui-space-2);
  padding: 0;
  background: none;
  border: 0;
}
#liveRoom .liveBarInfo {
  justify-self: start;
  color: var(--ui-stage-ink);
  font-variant-numeric: tabular-nums;
}
#liveRoom .liveCtls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.45rem 0.6rem;
  background: var(--ui-stage-tile);
  border-radius: var(--ui-radius-pill);
}
#liveRoom .liveSideBtns {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
#liveRoom .liveCtl {
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  inline-size: 3rem !important;
  block-size: 3rem !important;
  min-width: 0 !important;
  min-height: 0 !important;
  margin: 0;
  padding: 0 !important;
  border: 0 !important;
  border-radius: 50% !important;
  background: var(--ui-stage-ctl) !important;
  color: var(--ui-stage-ink) !important;
  box-shadow: none !important;
  cursor: pointer;
  transition:
    background-color var(--ui-quick) var(--ui-ease),
    transform var(--ui-quick) var(--ui-ease);
}
#liveRoom .liveCtl::before {
  content: none !important;
}
#liveRoom .liveCtl svg {
  inline-size: 1.4rem;
  block-size: 1.4rem;
}
#liveRoom .liveCtl:hover:not(:disabled) {
  filter: brightness(1.18);
}
#liveRoom .liveCtl:active:not(:disabled) {
  transform: scale(0.94);
}
#liveRoom .liveCtl:focus-visible {
  outline: 3px solid var(--ui-stage-on);
  outline-offset: 2px;
}
#liveRoom .liveCtl--media[aria-pressed="false"] {
  background: var(--ui-stage-off) !important;
  color: var(--ui-stage-ink) !important;
}
#liveRoom .liveCtl--toggle[aria-pressed="true"],
#liveRoom .liveSideBtn.isActive {
  background: var(--ui-stage-on) !important;
  color: var(--ui-stage-on-ink) !important;
}
#liveRoom .liveCtl--end {
  inline-size: 4.25rem !important;
  border-radius: var(--ui-radius-pill) !important;
  background: var(--ui-stage-off) !important;
}
#liveRoom .liveCtl--end svg {
  inline-size: 1.65rem;
  block-size: 1.65rem;
}
#liveRoom .liveCtl:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
#liveRoom .liveSideBtns .liveCtl:not(.isActive) {
  background: transparent !important;
}
#liveRoom .liveSideBtns .liveCtl:not(.isActive):hover {
  background: var(--ui-stage-ctl) !important;
}
#liveRoom .liveDot {
  position: absolute;
  inset-block-start: -0.15rem;
  inset-inline-end: -0.15rem;
  min-inline-size: 1.2rem;
  block-size: 1.2rem;
  padding: 0 0.3rem;
  border-radius: var(--ui-radius-pill);
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1.2rem;
  text-align: center;
}

/* ------------------------------------------------------ the side column */
#liveRoom .liveSide {
  display: none;
  min-height: 0;
  color: var(--ui-text);
}
#liveRoom.sideOpen .liveSide {
  display: flex;
}
#liveRoom .liveSideClose {
  flex: 0 0 auto;
  align-self: center;
  inline-size: 2.4rem !important;
  block-size: 2.4rem !important;
  margin-inline: 0.25rem;
  background: transparent !important;
  color: var(--ui-muted) !important;
}
#liveRoom .liveSideClose:hover {
  background: var(--ui-surface) !important;
}
/* The tabs are buttons without `.secondary`, so the global brand fill took
   them: three solid purple blocks where a tab strip should be. */
#liveRoom .liveTab {
  background: transparent !important;
  color: var(--ui-muted) !important;
  border: 0 !important;
  border-bottom: 2px solid transparent !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
#liveRoom .liveTab.active {
  background: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border-bottom-color: var(--ui-brand) !important;
}
#liveRoom .livePanel {
  padding-block-end: max(var(--ui-space-2), env(safe-area-inset-bottom));
}
#liveRoom .liveChatLog {
  height: auto;
}
#liveRoom .liveChatForm {
  margin-top: 0;
}

/* A phone: the whole screen, the way a call app is. The menu and the page
   behind are still there when the lesson ends; the hang-up is the way out. */
@media (max-width: 900px) {
  html.liveOpen,
  html.liveOpen body {
    overflow: hidden;
  }
  #liveRoom.liveRoom {
    position: fixed;
    inset: 0;
    z-index: 12030;
    margin: 0;
    border-radius: 0;
    /* inset:0 alone sizes it to the visible screen. A 100dvh height beside it
       measured 19px taller on a phone and put the hang-up under the edge. */
    padding: max(env(safe-area-inset-top), 0.6rem) 0.6rem max(env(safe-area-inset-bottom), 0.6rem);
  }
  #liveRoom .liveHead {
    min-block-size: 2.75rem;
    padding-inline-end: 8.5rem;
  }
  #liveRoom .liveLayout,
  #liveRoom.sideOpen .liveLayout {
    grid-template-columns: minmax(0, 1fr);
    flex: 1 1 auto;
    block-size: auto;
    min-height: 0;
  }
  #liveRoom .liveBar {
    grid-template-columns: minmax(0, 1fr);
  }
  #liveRoom .liveBarInfo {
    display: none;
  }
  #liveRoom .liveCtls {
    justify-self: center;
    max-inline-size: 100%;
    overflow-x: auto;
    gap: 0.5rem;
  }
  #liveRoom .liveSideBtns {
    position: absolute;
    inset-block-start: max(env(safe-area-inset-top), 0.6rem);
    inset-inline-end: 0.6rem;
  }
  #liveRoom .liveSideBtns .liveCtl {
    inline-size: 2.6rem !important;
    block-size: 2.6rem !important;
  }
  /* People and chat as a sheet from the bottom, over the stage. */
  #liveRoom .liveSide {
    position: absolute;
    z-index: 6;
    inset: auto 0 0 0;
    block-size: min(78%, 36rem);
    min-height: 0;
    border-radius: var(--ui-radius-lg) var(--ui-radius-lg) 0 0;
    border-block-end: 0;
    box-shadow: 0 -1rem 2.5rem var(--ui-stage);
  }
  #liveRoom .liveTile--float .liveName {
    font-size: 0.7rem;
  }
  #liveRoom .liveTileExpand {
    display: none;
  }
}
@media (max-width: 560px) {
  #liveRoom .liveCtl {
    inline-size: 2.9rem !important;
    block-size: 2.9rem !important;
  }
  #liveRoom .liveCtl--end {
    inline-size: 3.75rem !important;
  }
  #liveRoom .liveCtls {
    gap: 0.4rem;
    padding: 0.4rem 0.5rem;
  }
}
@media (prefers-reduced-motion: reduce) {
  #liveRoom .liveCtl,
  #liveRoom .liveTile::after {
    transition: none;
  }
}

/* Full screen is the room (live-room.js expandLiveStage): the browser's own
   full screen where it has one, the whole window where it does not (an
   iPhone). Either way the room carries .liveFull: the stage takes the screen,
   the bar floats over the bottom of the picture, and after a few quiet
   seconds the bar and the title fade until a click, a tap or a move of the
   mouse brings them back. */
#liveRoom.liveFull {
  position: fixed;
  inset: 0;
  z-index: 12030;
  margin: 0;
  padding: max(env(safe-area-inset-top), var(--ui-space-2)) var(--ui-space-2)
    max(env(safe-area-inset-bottom), var(--ui-space-2));
  border-radius: 0;
}
html.liveFullOpen,
html.liveFullOpen body {
  overflow: hidden;
}
#liveRoom.liveFull .liveLayout {
  flex: 1 1 auto;
  block-size: auto;
  min-height: 0;
}
#liveRoom.liveFull .liveBar {
  position: absolute;
  z-index: 8;
  inset-inline: var(--ui-space-3);
  inset-block-end: max(env(safe-area-inset-bottom), var(--ui-space-3));
  transition: opacity var(--ui-settle) var(--ui-ease);
}
#liveRoom.liveFull .liveHead {
  transition: opacity var(--ui-settle) var(--ui-ease);
}
#liveRoom.liveFull.liveCalm .liveBar,
#liveRoom.liveFull.liveCalm .liveHead,
#liveRoom.liveFull.liveCalm .liveSideBtns {
  opacity: 0;
  pointer-events: none;
}
#liveRoom.liveFull.liveCalm {
  cursor: none;
}
@media (prefers-reduced-motion: reduce) {
  #liveRoom .liveTile,
  #liveRoom .liveMore {
    transition: none;
    animation: none;
  }
}

/* CRM جذب و ثبت‌نام (admissions.js).
   A kanban of seven stages that wraps onto a second row rather than scrolling
   sideways (the owner asked for no horizontal scroll: a column off the edge is
   a stage nobody looks at), each column scrolling down inside itself; a modal
   with the lead on one side and what was said on the other. */
.adToolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ui-space-2);
  margin-block: var(--ui-space-3);
}
.adToolbar .actions {
  margin-top: 0;
}
.adFilters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ui-space-2);
  flex: 1 1 32rem;
  justify-content: flex-end;
}
.adFilters input {
  flex: 1 1 14rem;
  min-width: 0;
}
.adFilters select {
  flex: 0 1 11rem;
  min-width: 0;
}
.adBoard {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 12.5rem), 1fr));
  gap: var(--ui-space-2);
  align-items: start;
}
.adCol {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-height: 72vh;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.adCol > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.6rem 0.75rem;
  border-bottom: 2px solid var(--ui-line);
}
.adCol[data-stage="enrolled"] > header {
  border-bottom-color: var(--ui-series-4);
}
.adCol[data-stage="lost"] > header {
  border-bottom-color: var(--ui-danger);
}
.adCards {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 5rem;
  padding: 0.5rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: var(--ui-radius);
}
.adCards.over {
  outline: 2px dashed var(--ui-brand);
  outline-offset: -4px;
}
.adCard {
  display: grid;
  gap: 0.3rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  cursor: grab;
}
.adCard:hover,
.adCard:focus-visible {
  border-color: var(--ui-brand);
  outline: none;
}
.adCard.dragging {
  opacity: 0.45;
}
.adCardHead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
}
.adCardHead b {
  min-width: 0;
  overflow-wrap: anywhere;
}
.adCard small,
.adNext {
  font-size: var(--ui-step--1);
}
.adNext {
  color: var(--ui-text);
}
.adNext.late,
.adLate {
  color: var(--ui-danger);
  font-weight: 700;
}
.adNext.none {
  color: var(--ui-muted);
}
.adEmpty,
.adMore {
  margin: 0;
  padding: 0.25rem 0.75rem 0.6rem;
  font-size: var(--ui-step--1);
}
/* The score in its own colour -- warm, middling, cold. The number is the
   signal; the colour only helps a glance down a column. */
.adScore {
  flex: 0 0 auto;
  display: inline-block;
  min-inline-size: 2rem;
  padding: 0 0.4rem;
  border: 1.5px solid currentColor;
  border-radius: var(--ui-radius-pill);
  font-size: var(--ui-step--1);
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.adScore[data-level="hi"] {
  color: var(--ui-series-4);
}
.adScore[data-level="mid"] {
  color: var(--ui-warn);
}
.adScore[data-level="lo"] {
  color: var(--ui-danger);
}
.adScore.big {
  font-size: var(--ui-step-2);
  padding: 0.1rem 0.7rem;
}
.adRow {
  cursor: pointer;
}
.adPeriod {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ui-space-2);
  margin-block: 0 var(--ui-space-3);
  align-items: center;
}
.adPeriod select {
  inline-size: auto;
}
.adFunnel {
  display: grid;
  gap: 0.45rem;
  margin-block: var(--ui-space-2) var(--ui-space-4);
}
.adFunnelRow {
  display: grid;
  grid-template-columns: minmax(min(100%, 6rem), 10rem) minmax(0, 1fr) 3.5rem 3.5rem;
  align-items: center;
  gap: var(--ui-space-2);
  font-variant-numeric: tabular-nums;
}
.adFunnelTrack {
  block-size: 0.75rem;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-pill);
  overflow: hidden;
}
.adFunnelTrack i {
  display: block;
  block-size: 100%;
  background: var(--ui-brand);
  border-radius: inherit;
}
.adFunnelTrack.lost i {
  background: var(--ui-danger);
}
/* The lead: its form, and beside it the score, the composer and the history. */
.uiModalCard.adModalWide {
  width: min(1120px, 100%);
}
.adLead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
  gap: var(--ui-space-4);
  align-items: start;
}
.adLead.single {
  grid-template-columns: minmax(0, 1fr);
}
.adLeadSide {
  display: grid;
  gap: var(--ui-space-2);
  min-width: 0;
}
.adLeadSide h3 {
  margin: var(--ui-space-2) 0 0;
  font-size: var(--ui-step-0);
}
.adLeadSide .actions {
  margin-top: 0;
}
.adScoreBox {
  padding: var(--ui-space-2) var(--ui-space-3);
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.adScoreBox > div {
  display: flex;
  align-items: center;
  gap: var(--ui-space-2);
}
.adScoreBox ul {
  list-style: none;
  margin: var(--ui-space-2) 0 0;
  padding: 0;
  display: grid;
  gap: 0.25rem;
  font-size: var(--ui-step--1);
}
.adScoreBox li {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
}
.adScoreBox li b {
  color: var(--ui-series-4);
  font-variant-numeric: tabular-nums;
}
.adScoreBox li b.neg {
  color: var(--ui-danger);
}
.adCompose {
  display: grid;
  gap: 0.35rem;
}
/* The Persian date-and-24-hour picker needs about 19rem; half of a 26rem
   column pushed its hour selector out of the card. */
.adCompose .adNextGrid {
  grid-template-columns: minmax(0, 1fr);
}
.adTimeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--ui-space-2);
  max-height: 22rem;
  overflow-y: auto;
}
.adTimeline li {
  display: grid;
  gap: 0.2rem;
  padding-inline-start: var(--ui-space-2);
  border-inline-start: 2px solid var(--ui-line);
}
.adTimeline li[data-kind="stage"],
.adTimeline li[data-kind="converted"] {
  border-inline-start-color: var(--ui-brand);
}
.adTimeline li[data-kind="call"] {
  border-inline-start-color: var(--ui-info);
}
.adTimeline li p {
  margin: 0;
  overflow-wrap: anywhere;
}
.adTimeline li > div {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
@media (max-width: 900px) {
  .adLead {
    grid-template-columns: minmax(0, 1fr);
  }
  .adFilters {
    justify-content: stretch;
  }
  .adFilters select {
    flex: 1 1 9rem;
  }
  .adCol {
    max-height: 60vh;
  }
}
@media (max-width: 560px) {
  .adFunnelRow {
    grid-template-columns: minmax(min(100%, 5rem), 7rem) minmax(0, 1fr) 2.5rem;
  }
  .adFunnelRow small {
    display: none;
  }
}

/* The owner picker: type to narrow the institution's staff (admissions.js). */
.adPick {
  position: relative;
  min-width: 0;
}
.adFilters .adPick {
  flex: 0 1 13rem;
}
.adPickList {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  z-index: 6;
  max-height: 15rem;
  overflow-y: auto;
  margin: 0.25rem 0 0;
  padding: 0.25rem;
  list-style: none;
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-elev-raised);
}
.adPickList li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.6rem;
  border-radius: calc(var(--ui-radius) - 0.25rem);
  cursor: pointer;
}
.adPickList li[aria-selected="true"],
.adPickList li:hover {
  background: var(--ui-surface-2);
}
.adPickList li small {
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
}
.adPickList li.adPickNone {
  color: var(--ui-muted);
  cursor: default;
}

/* «پروندهٔ ۳۶۰» (student-timeline.js): the summary, then the timeline grouped
   by day, a dot per event coloured by what kind of event it is. */
.tlSummary .metricCard small {
  display: block;
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
}
.tlSummary .tlSmall {
  font-size: var(--ui-step-0);
  overflow-wrap: anywhere;
}
.tlSummary .tlDown {
  color: var(--ui-danger);
}
.tlSummary .tlUp {
  color: var(--ui-series-4);
}
.tlCompose {
  margin-block: var(--ui-space-3);
  padding: var(--ui-space-2) var(--ui-space-3);
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.tlCompose > summary {
  cursor: pointer;
  font-weight: 700;
}
.tlFilters {
  display: flex;
  flex-wrap: wrap;
  margin-block: var(--ui-space-3) var(--ui-space-2);
}
.tlList {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}
.tlDay {
  margin-block: var(--ui-space-3) 0.2rem;
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
  font-weight: 700;
}
.tlItem {
  display: grid;
  grid-template-columns: 3.2rem minmax(0, 1fr);
  gap: var(--ui-space-2);
  padding: 0.45rem 0.6rem;
  border-inline-start: 3px solid var(--ui-line);
  background: var(--ui-surface);
  border-radius: 0 var(--ui-radius) var(--ui-radius) 0;
}
.tlItem time {
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
  font-variant-numeric: tabular-nums;
}
.tlItem p {
  margin: 0.15rem 0 0;
  overflow-wrap: anywhere;
}
.tlItem[data-kind="call"],
.tlItem[data-kind="meeting"],
.tlItem[data-kind="followup"] {
  border-inline-start-color: var(--ui-info);
}
.tlItem[data-kind="session"] {
  border-inline-start-color: var(--ui-brand);
}
.tlItem[data-kind="exam"] {
  border-inline-start-color: var(--ui-series-3);
}
.tlItem[data-kind="daily_report"] {
  border-inline-start-color: var(--ui-series-4);
}
.tlItem[data-kind="payment"] {
  border-inline-start-color: var(--ui-series-6);
}
.tlItem[data-kind="ticket"] {
  border-inline-start-color: var(--ui-warn);
}
.tlItem[data-kind="admission"],
.tlItem[data-kind="enrollment"] {
  border-inline-start-color: var(--ui-series-5);
}

/* «موفقیت دانش‌آموز» (success.js): four levels, each with its own colour and
   always its name beside it, so colour is never the only signal. */
.scLevel {
  display: inline-block;
  padding: 0.1rem 0.55rem;
  border: 1.5px solid currentColor;
  border-radius: var(--ui-radius-pill);
  font-size: var(--ui-step--1);
  font-weight: 700;
}
.scLevel[data-level="stable"],
.scLevelCard[data-level="stable"] b {
  color: var(--ui-series-4);
}
.scLevel[data-level="attention"],
.scLevelCard[data-level="attention"] b {
  color: var(--ui-warn);
}
.scLevel[data-level="risk"],
.scLevelCard[data-level="risk"] b {
  color: var(--ui-series-5);
}
.scLevel[data-level="critical"],
.scLevelCard[data-level="critical"] b {
  color: var(--ui-danger);
}
.scLevels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
  gap: var(--ui-space-2);
  margin-block: var(--ui-space-3);
}
.scLevelCard {
  display: grid !important;
  gap: 0.2rem;
  justify-items: start;
  padding: 0.7rem 0.9rem !important;
  min-height: 0 !important;
  background: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
  border-radius: var(--ui-radius) !important;
  text-align: start;
}
.scLevelCard::before {
  content: none !important;
}
.scLevelCard b {
  font-size: var(--ui-step-2);
}
.scLevelCard.on {
  border-color: var(--ui-brand) !important;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-brand) 30%, transparent);
}
.scScore {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.scScore b {
  font-size: var(--ui-step-1);
  font-variant-numeric: tabular-nums;
}
.scScore.big b {
  font-size: var(--ui-step-4);
}
.scUp {
  color: var(--ui-series-4);
}
.scDown {
  color: var(--ui-danger);
}
.scReasons {
  margin: 0;
  padding-inline-start: 1.1rem;
  font-size: var(--ui-step--1);
}
.scHead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ui-space-3);
}
.scSpark {
  inline-size: min(100%, 280px);
  block-size: auto;
}
.scSpark .scLine {
  fill: none;
  stroke: var(--ui-brand);
  stroke-width: 2;
}
.scSpark .scGuide {
  stroke: var(--ui-line);
  stroke-dasharray: 3 3;
}
.scSpark .scDot {
  fill: var(--ui-brand);
}
.scFactors,
.scActions {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}
.scFactors li,
.scActions li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.6rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius);
}
.scFactors li b {
  color: var(--ui-danger);
  font-variant-numeric: tabular-nums;
}
.scActions .actions {
  margin-top: 0;
}
.scActions select {
  inline-size: auto;
}
.scInline {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--ui-space-3);
  padding: var(--ui-space-2) var(--ui-space-3);
  margin-block-end: var(--ui-space-2);
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}

.aiBillingNotice {
  margin-block: var(--ui-space-3);
  border-inline-start: 4px solid var(--ui-brand);
  background: color-mix(in srgb, var(--ui-brand) 8%, var(--ui-surface));
}
.aiBillingNotice p { margin: var(--ui-space-2) 0; }

/* «دستیار هوشمند مشاور» (copilot.js), inside the 360 file. The answer keeps
   the model's own line breaks and bullets; the note under it says it is a
   suggestion, every time. */
.cpBox {
  display: grid;
  gap: var(--ui-space-2);
  margin-block: var(--ui-space-3);
  padding: var(--ui-space-3);
  background: color-mix(in srgb, var(--ui-brand) 6%, var(--ui-surface));
  border: 1px solid color-mix(in srgb, var(--ui-brand) 30%, var(--ui-line));
  border-radius: var(--ui-radius-lg);
}
.cpHead {
  display: grid;
  gap: 0.2rem;
}
.cpTools {
  display: grid;
  gap: var(--ui-space-2);
}
.cpTools .actions {
  margin-top: 0;
}
.cpAsk {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ui-space-2);
}
.cpAsk input {
  flex: 1 1 16rem;
  min-width: 0;
}
.cpAnswer,
.cpDraft,
.cpPast {
  padding: var(--ui-space-2) var(--ui-space-3);
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.cpAnswer header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
}
.cpText {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: var(--ui-lead-body);
}
.cpNote {
  margin: var(--ui-space-2) 0 0;
  color: var(--ui-muted);
  font-size: var(--ui-step--1);
}
.cpActions {
  display: grid;
  gap: 0.4rem;
}
.cpAction {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 7rem) 4rem auto;
  gap: 0.4rem;
  align-items: center;
}
.cpPast + .cpPast {
  margin-top: 0.4rem;
}
.cpPast > summary {
  cursor: pointer;
}
@media (max-width: 560px) {
  .cpAction {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

/* «تحلیل هوشمند آزمون» (exam-intel.js): a bar per topic in its band's colour,
   the band's name beside it; the item table flags suspect questions. */
.eiSubject {
  margin: var(--ui-space-3) 0 0.3rem;
  font-size: var(--ui-step-0);
}
.eiTopics {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}
.eiTopics li {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr) 3.5rem;
  gap: 0.2rem var(--ui-space-2);
  align-items: center;
}
.eiTopics li small {
  grid-column: 1 / -1;
}
.eiBar {
  block-size: 0.7rem;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-pill);
  overflow: hidden;
}
.eiBar i {
  display: block;
  block-size: 100%;
  border-radius: inherit;
  background: var(--ui-muted);
}
.eiBar[data-band="green"] i {
  background: var(--ui-series-4);
}
.eiBar[data-band="yellow"] i {
  background: var(--ui-warn);
}
.eiBar[data-band="red"] i {
  background: var(--ui-danger);
}
.eiPct[data-band="green"] {
  color: var(--ui-series-4);
}
.eiPct[data-band="yellow"] {
  color: var(--ui-warn);
}
.eiPct[data-band="red"] {
  color: var(--ui-danger);
}
.eiOpts,
.eiFlags {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--ui-step--1);
}
.eiOpts .key {
  font-weight: 700;
  color: var(--ui-series-4);
}
.eiFlags li {
  color: var(--ui-danger);
}
.eiSuspect td:first-child {
  box-shadow: inset 3px 0 0 var(--ui-danger);
}
.eiWarn {
  color: var(--ui-danger);
}
.eiTabs {
  margin-block: var(--ui-space-3) var(--ui-space-2);
}
@media (max-width: 560px) {
  .eiTopics li {
    grid-template-columns: minmax(0, 1fr) 3.5rem;
  }
  .eiTopics li .eiBar {
    grid-column: 1 / -1;
    grid-row: 2;
  }
}

/* «برنامه‌ریز هوشمند» (smart-planner.js): seven day inputs in a row that
   wraps, the subjects' weights, and today's adjusted targets with reasons. */
.spDays {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 8rem), 1fr));
  gap: var(--ui-space-2);
}
.spDays label {
  display: grid;
  gap: 0.25rem;
  margin: 0;
}
/* The planner's three steps (owner's review, 1405-07-10): numbered blocks,
   one-click amounts, a minutes box with its unit, and each subject as a row
   of four named shares instead of a 0..3 list. */
.spStep {
  display: grid;
  gap: var(--ui-space-2);
  padding-block: var(--ui-space-2) var(--ui-space-3);
  border-bottom: 1px solid var(--ui-line);
}
.spStep h3 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}
.spNum {
  display: inline-grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: var(--ui-brand);
  color: var(--ui-brand-ink);
  font-size: 0.85em;
}
.spPresets,
.spFields {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
.spMinutes {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.spMinutes input {
  min-width: 0;
  flex: 1 1 auto;
}
.spMinutes small {
  color: var(--ui-muted);
}
.spMinutes input[aria-invalid="true"] {
  border-color: var(--ui-danger);
}
.spError {
  color: var(--ui-danger);
  margin: 0;
}
.spSubjectList {
  display: grid;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.spSubject {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem 1rem;
  padding: 0.5rem 0.75rem;
}
.spSubject + .spSubject {
  border-top: 1px solid var(--ui-line);
}
.spSubjectName {
  display: grid;
  min-width: 0;
}
.spSeg {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}
.spPill {
  position: relative;
  margin: 0;
  cursor: pointer;
}
.spPill input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
.spPill span {
  display: inline-block;
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--ui-line);
  border-radius: 99px;
  background: var(--ui-surface);
  color: var(--ui-text);
  font-size: 0.85em;
  white-space: nowrap;
}
.spPill input:checked + span {
  background: var(--ui-brand);
  border-color: var(--ui-brand);
  color: var(--ui-brand-ink);
}
.spPill input:focus-visible + span {
  outline: 2px solid var(--ui-brand);
  outline-offset: 2px;
}
.spWhy summary {
  cursor: pointer;
  margin-block: var(--ui-space-2);
}
@media (max-width: 900px), (pointer: coarse) {
  .spPill span {
    padding-block: 0.65rem;
  }
}
.spToday {
  margin-block: var(--ui-space-2) var(--ui-space-3);
  padding: var(--ui-space-2) var(--ui-space-3);
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.spToday h3 {
  margin: 0 0 var(--ui-space-2);
  font-size: var(--ui-step-0);
}
.spToday ul {
  list-style: none;
  margin: 0 0 var(--ui-space-2);
  padding: 0;
  display: grid;
  gap: 0.4rem;
}
.spToday li {
  display: grid;
  gap: 0.1rem;
}
.spUp {
  color: var(--ui-info);
}
.spDown {
  color: var(--ui-series-4);
}
.spReason {
  color: var(--ui-muted);
}

/* «گزارش هفتگی والدین» (parent-reports.js): the report as the parent reads it. */
.prText {
  padding: var(--ui-space-2) var(--ui-space-3);
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.prCard + .prCard {
  margin-top: var(--ui-space-3);
}

/* «مرکز ارتباطات» (communications.js): one stream, a stripe per channel. */
.ccStream {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
}
.ccStream li {
  padding: 0.5rem 0.75rem;
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-inline-start: 3px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.ccStream li p {
  margin: 0.25rem 0 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.ccStream li[data-channel="sms"] {
  border-inline-start-color: var(--ui-series-6);
}
.ccStream li[data-channel="panel"] {
  border-inline-start-color: var(--ui-brand);
}
.ccStream li[data-channel="ticket"] {
  border-inline-start-color: var(--ui-warn);
}
.ccStream li[data-channel="call"],
.ccStream li[data-channel="meeting"] {
  border-inline-start-color: var(--ui-info);
}
.ccMeta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
.ccTitle {
  display: block;
  margin-top: 0.2rem;
}
.ccFound,
.ccPicked {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-block: 0.4rem;
}


/* «نگهداشت و تمدید» (retention.js): the risk as a number with a bar beside
   it -- colour follows the success engine's four levels, the number is always
   there, so colour is never the only signal. */
.rtRisk {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 6rem;
}
.rtRisk b {
  min-width: 1.8rem;
  font-variant-numeric: tabular-nums;
}
.rtRisk span {
  position: relative;
  flex: 1;
  height: 0.4rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius-pill);
  overflow: hidden;
}
.rtRisk span::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--w, 0%);
  background: currentColor;
  border-radius: inherit;
}
.rtRisk[data-level="stable"] {
  color: var(--ui-series-4);
}
.rtRisk[data-level="attention"] {
  color: var(--ui-warn);
}
.rtRisk[data-level="risk"] {
  color: var(--ui-series-5);
}
.rtRisk[data-level="critical"] {
  color: var(--ui-danger);
}
.rtInsights {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: var(--ui-space-3);
  margin-top: var(--ui-space-4);
}
.rtBars {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
}
.rtBars li {
  display: grid;
  grid-template-columns: minmax(0, 8rem) minmax(0, 1fr) 2.2rem;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--ui-step--1);
}
.rtBars i {
  position: relative;
  display: block;
  height: 0.55rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius-pill);
  overflow: hidden;
}
.rtBars i::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--w, 0%);
  background: var(--ui-brand);
  border-radius: inherit;
}
.rtBars b {
  text-align: end;
  font-variant-numeric: tabular-nums;
}
.rtCaseHead {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
  gap: var(--ui-space-3);
  margin-block: var(--ui-space-3);
}
.rtActivities {
  list-style: none;
  margin: var(--ui-space-3) 0 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}
.rtActivities li {
  padding: 0.5rem 0.7rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius);
}

/* «اتوماسیون پیگیری» (workflows.js): each rule a card that reads as a
   sentence -- when, then what -- with its switch at the top. */
.wfPresets {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-block: var(--ui-space-3);
}
.wfRules {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: var(--ui-space-3);
  margin-bottom: var(--ui-space-4);
}
.wfRule.off {
  opacity: 0.7;
}
.wfRuleHead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.wfDo {
  margin: 0.3rem 0;
  padding-inline-start: 1.1rem;
  font-size: var(--ui-step--1);
}
.wfAction {
  padding: 0.6rem 0.8rem;
  margin-block: 0.5rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius);
}

/* «آزمون کاغذی» (paper-exams.js): the photo with what was read drawn on it
   beside the grid the teacher confirms; a doubtful row is marked in the warn
   colour and by its title, not by colour alone. */
.pxTabs {
  margin-block: var(--ui-space-3);
}
.pxReview {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: var(--ui-space-4);
  align-items: start;
  margin-top: var(--ui-space-3);
}
.pxPhoto canvas {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.pxGrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
  gap: 0.3rem 0.8rem;
  margin-block: var(--ui-space-3);
}
.pxRow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
  padding: 0.2rem 0.4rem;
  border-radius: var(--ui-radius);
}
.pxRow b {
  min-width: 1.8rem;
  font-variant-numeric: tabular-nums;
}
.pxRow.flagged {
  background: color-mix(in srgb, var(--ui-warn) 18%, transparent);
  outline: 1.5px solid var(--ui-warn);
}
.pxOpt {
  display: inline-flex;
  align-items: center;
  gap: 0.1rem;
  font-size: var(--ui-step--1);
  cursor: pointer;
}
.pxWritten {
  display: grid;
  gap: 0.5rem;
  padding-inline-start: 1.2rem;
}
.pxAdd {
  margin-block: var(--ui-space-3);
}
@media (max-width: 900px) {
  .pxReview {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* «مرکز ممیزی» (audit-center.js): category chips above the stream. */
.auChips {
  margin-block: var(--ui-space-3);
}

/* «وب‌هوک و اتصال» (integration-hub.js): a URL is a machine value and wraps
   anywhere rather than pushing its card wide. */
.ihUrl {
  overflow-wrap: anywhere;
  font-size: var(--ui-step--1);
}
.ihEvents {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: 0.2rem 0.8rem;
  margin-block: 0.4rem;
}
.ihHelp {
  margin-top: var(--ui-space-4);
}

/* «معرفی دوستان» (referrals.js): the code large enough to read aloud. */
.rfShare {
  display: grid;
  gap: 0.5rem;
  margin-bottom: var(--ui-space-3);
}
.rfCode {
  font-size: var(--ui-step-3);
  letter-spacing: 0.2em;
  font-variant-numeric: tabular-nums;
}
.rfLeaders {
  display: grid;
  gap: 0.35rem;
  margin: 0;
  padding-inline-start: 1.2rem;
}
.rfLeaders li {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
}

/* «هوش مالی» (financial-intel.js) reuses the retention bars and risk; weeks
   need a narrower label column. */
.fiWeeks li {
  grid-template-columns: minmax(0, 5rem) minmax(0, 1fr) 3rem;
}

/* «شعبه‌ها» (branches.js): the comparison reads across a row; the best
   branch in a row is bold and tinted, the institution's total set apart. */
.brTable th[scope="row"] {
  text-align: start;
  font-weight: 500;
  white-space: nowrap;
}
.brTable td {
  font-variant-numeric: tabular-nums;
}
.brTable .brBest {
  font-weight: 700;
  background: color-mix(in srgb, var(--ui-brand) 12%, transparent);
}
.brTable .brTotal {
  border-inline-start: 2px solid var(--ui-line);
  color: var(--ui-muted);
}
.brBulk {
  margin-block: var(--ui-space-2);
}

/* «هدایت تحصیلی» (career-guidance.js): one statement per row with its five
   answers beside it, wrapping under it on a phone. */
.cgItems {
  display: grid;
  gap: 0.6rem;
  padding-inline-start: 1.4rem;
}
.cgItems li p {
  margin: 0 0 0.3rem;
}
.cgScale {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.9rem;
}
.cgReport + .cgReport {
  margin-top: var(--ui-space-3);
}
.cgNote {
  white-space: pre-line;
}
.cgOptions {
  display: grid;
  gap: 0.6rem;
  padding-inline-start: 1.3rem;
}
.cgDetail {
  margin-top: var(--ui-space-4);
}

/* «داشبورد مدیریت» (executive.js): what needs a decision today, each line
   with its severity as a coloured edge and a word, never colour alone; the
   health mix as one stacked bar with its numbers under it. */
.exAttention ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}
.exAttention li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.7rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius);
  border-inline-start: 4px solid var(--ui-warn);
}
.exAttention li[data-tone="danger"] {
  border-inline-start-color: var(--ui-danger);
}
.exAttention li button {
  margin-inline-start: auto;
}
.exHealth {
  display: flex;
  height: 0.8rem;
  margin-block: 0.35rem;
  border-radius: var(--ui-radius-pill);
  overflow: hidden;
  background: var(--ui-surface-2);
}
.exHealth span {
  width: var(--w, 0%);
}
.exHealth span[data-level="stable"] {
  background: var(--ui-series-4);
}
.exHealth span[data-level="attention"] {
  background: var(--ui-warn);
}
.exHealth span[data-level="risk"] {
  background: var(--ui-series-5);
}
.exHealth span[data-level="critical"] {
  background: var(--ui-danger);
}
/* A sortable header reads as a header, not a purple button: the same id
   weight as the menu, for the same reason. */
.exSort:not(#dt-none) {
  all: unset;
  background: transparent !important;
  color: inherit !important;
  border: 0 !important;
  box-shadow: none !important;
  min-height: 0 !important;
  padding: 0 !important;
  cursor: pointer;
  font-weight: 600;
}
.exSort:not(#dt-none):hover {
  background: transparent !important;
  color: var(--ui-brand) !important;
}
.exSort:focus-visible {
  outline: 2px solid var(--ui-brand);
}
.exSample {
  white-space: pre-wrap;
  padding: 0.7rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius);
  font-family: inherit;
  font-size: var(--ui-step--1);
}

/* «حضور و غیاب» (attendance.js): the meeting screen is read from the back
   of a classroom -- a large QR and a large code -- beside the roll. */
.atMeeting {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  gap: var(--ui-space-4);
  align-items: start;
}
.atBoard {
  display: grid;
  justify-items: center;
  gap: 0.6rem;
  padding: var(--ui-space-4);
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-lg);
}
.atBoard img {
  width: min(100%, 22rem);
  aspect-ratio: 1;
  /* A QR needs a light ground to scan, whatever the theme. */
  background: white;
  border-radius: var(--ui-radius);
}
.atBoard b {
  font-size: var(--ui-step-4);
  letter-spacing: 0.25em;
  font-variant-numeric: tabular-nums;
}
.atRoll {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}
.atRoll li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  padding: 0.4rem 0.6rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius);
  border-inline-start: 4px solid var(--ui-line);
}
.atRoll li[data-status="present"] {
  border-inline-start-color: var(--ui-series-4);
}
.atRoll li[data-status="late"] {
  border-inline-start-color: var(--ui-warn);
}
.atRoll li[data-status="absent"] {
  border-inline-start-color: var(--ui-danger);
}
.atMarks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}
@media (max-width: 900px) {
  .atMeeting {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* «پروندهٔ اسناد» (student-documents.js) */
.sdUpload {
  margin-block: var(--ui-space-3);
}

/* «موفقیت دانش‌آموز» as cards (success.js): each student a card that wraps
   to the width -- no table wider than the screen -- with a 14-day line; and
   the month as stacked level bands. Colours follow the four levels, always
   with their names beside them. */
.scCards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 21rem), 1fr));
  gap: var(--ui-space-3);
}
.scCard {
  display: grid;
  gap: 0.45rem;
  align-content: start;
  padding: 0.8rem 0.9rem;
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-inline-start: 4px solid var(--ui-line);
  border-radius: var(--ui-radius);
  min-width: 0;
}
.scCard[data-level="stable"] {
  border-inline-start-color: var(--ui-series-4);
}
.scCard[data-level="attention"] {
  border-inline-start-color: var(--ui-warn);
}
.scCard[data-level="risk"] {
  border-inline-start-color: var(--ui-series-5);
}
.scCard[data-level="critical"] {
  border-inline-start-color: var(--ui-danger);
}
.scCardHead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.5rem;
}
.scCardTrend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}
.scMini {
  width: 7.5rem;
  height: 2rem;
}
.scNext {
  margin: 0;
  font-size: var(--ui-step--1);
}
.scTrendCard {
  margin-block: var(--ui-space-3);
}
.scTrend {
  display: block;
  width: 100%;
  height: auto;
  max-height: 14rem;
}
.scBand[data-level="stable"] {
  fill: color-mix(in srgb, var(--ui-series-4) 55%, transparent);
}
.scBand[data-level="attention"] {
  fill: color-mix(in srgb, var(--ui-warn) 55%, transparent);
}
.scBand[data-level="risk"] {
  fill: color-mix(in srgb, var(--ui-series-5) 60%, transparent);
}
.scBand[data-level="critical"] {
  fill: color-mix(in srgb, var(--ui-danger) 65%, transparent);
}
.scAvg {
  fill: none;
  stroke: var(--ui-text);
  stroke-width: 2;
  stroke-dasharray: 5 4;
}
.scAxis {
  fill: var(--ui-muted);
  font-size: 11px;
}
.scLegend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  font-size: var(--ui-step--1);
}
.scLegend span {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.scLegend i {
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 3px;
  display: inline-block;
}
.scLegend [data-level="stable"] i {
  background: var(--ui-series-4);
}
.scLegend [data-level="attention"] i {
  background: var(--ui-warn);
}
.scLegend [data-level="risk"] i {
  background: var(--ui-series-5);
}
.scLegend [data-level="critical"] i {
  background: var(--ui-danger);
}
.scAvgKey i {
  height: 0;
  border-top: 2px dashed var(--ui-text);
  border-radius: 0;
}
.scMini .scLine {
  fill: none;
  stroke: var(--ui-brand);
  stroke-width: 2;
}
.scMini .scDot {
  fill: var(--ui-brand);
}
.scTrend .scGuide {
  stroke: var(--ui-line);
}

/* Automatic reviews (success.js): this week against the last as two bars on
   one scale, and the seven days of daily reports as seven squares. */
.scTabs {
  margin-block: var(--ui-space-2);
}
.rvPair {
  display: grid;
  grid-template-columns: minmax(0, 6rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--ui-step--1);
}
.rvBars {
  display: grid;
  gap: 2px;
}
.rvBars i {
  display: block;
  height: 0.45rem;
  width: var(--w, 0%);
  border-radius: var(--ui-radius-pill);
}
.rvBars .rvNow {
  background: var(--ui-brand);
}
.rvBars .rvBefore {
  background: color-mix(in srgb, var(--ui-muted) 55%, transparent);
}
.rvDays {
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: var(--ui-step--1);
}
.rvDays i {
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 3px;
  background: var(--ui-surface-2);
  border: 1px solid var(--ui-line);
}
.rvDays i.on {
  background: var(--ui-series-4);
  border-color: var(--ui-series-4);
}
.rvDays small {
  margin-inline-start: 0.4rem;
}
.rvCard label {
  display: grid;
  gap: 0.25rem;
}
.rvCard textarea {
  width: 100%;
  box-sizing: border-box;
  max-width: 100%;
}
.cpPick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-block: 0.3rem;
}
.cpOwn {
  margin-block: 0.4rem;
}
.scLevelLine {
  fill: none;
  stroke-width: 2.5;
}
.scLevelLine[data-level="stable"] {
  stroke: var(--ui-series-4);
}
.scLevelLine[data-level="attention"] {
  stroke: var(--ui-warn);
}
.scLevelLine[data-level="risk"] {
  stroke: var(--ui-series-5);
}
.scLevelLine[data-level="critical"] {
  stroke: var(--ui-danger);
}

/* «تخصیص هوشمند» (smart-assignment.js): each person's load as a bar against
   their ceiling -- the state (near full, full, not accepting) in colour and in
   words beside it. */
.saLoad {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
}
.saLoad li {
  display: grid;
  grid-template-columns: minmax(0, 9rem) minmax(0, 1fr) auto;
  grid-template-areas: "name bar count" "note note act";
  align-items: center;
  gap: 0.2rem 0.6rem;
}
.saLoad .saName {
  grid-area: name;
  overflow-wrap: anywhere;
}
.saLoad i {
  grid-area: bar;
  position: relative;
  display: block;
  height: 0.6rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius-pill);
  overflow: hidden;
}
.saLoad i::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--w, 0%);
  background: var(--ui-series-4);
  border-radius: inherit;
}
.saLoad li[data-state="near"] i::after {
  background: var(--ui-warn);
}
.saLoad li[data-state="full"] i::after {
  background: var(--ui-danger);
}
.saLoad li[data-state="off"] i::after {
  background: var(--ui-muted);
}
.saLoad b {
  grid-area: count;
  font-variant-numeric: tabular-nums;
}
.saLoad small {
  grid-area: note;
}
.saLoad button {
  grid-area: act;
}
.saSuggest {
  display: grid;
  gap: 0.4rem;
  margin: 0;
  padding-inline-start: 1.2rem;
}

/* «منابع و بودجه‌بندی» (study-resources.js): progress bars with a tick where
   the student should be today, the budget per subject as bars, students by
   coverage as columns, and the 30-day coverage line. */
.srExams,
.srHoldings {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: var(--ui-gap);
}
.srExams > .card {
  display: grid;
  gap: 0.4rem;
  align-content: start;
}
.srChips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}
.srCountdown {
  display: grid;
  justify-items: center;
  min-width: 4rem;
}
.srCountdown b {
  font-size: var(--ui-step-3);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.srExam {
  display: grid;
  gap: 0.75rem;
}
.srCoverage,
.srBars li,
.srRoster li {
  display: grid;
  grid-template-columns: minmax(0, 11rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.25rem 0.6rem;
}
.srBars,
.srRoster,
.srSuggest {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}
.srBars li > small {
  grid-column: 1 / -1;
}
.srCoverage i,
.srBars i,
.srRoster i,
.srProgress {
  position: relative;
  display: block;
  height: 0.6rem;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius-pill);
  overflow: hidden;
}
.srCoverage i::after,
.srBars i::after,
.srRoster i::after,
.srProgress i {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--w, 0%);
  background: var(--ui-series-2);
  border-radius: inherit;
}
.srCoverage i {
  height: 0.9rem;
}
.srProgress {
  height: 0.75rem;
  overflow: visible;
}
.srProgress .srExpected {
  position: absolute;
  inset-block: -0.2rem;
  inset-inline-start: var(--w, 0%);
  width: 2px;
  background: var(--ui-text);
}
.srHolding {
  display: grid;
  gap: 0.5rem;
}
.srHolding[data-state="good"] .srProgress i {
  background: var(--ui-series-4);
}
.srHolding[data-state="warn"] .srProgress i {
  background: var(--ui-warn);
}
.srHolding[data-state="bad"] .srProgress i {
  background: var(--ui-danger);
}
.srHolding input[type="number"] {
  width: 6rem;
}
.srPace {
  margin: 0;
}
.srHisto {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  align-items: end;
  gap: 0.5rem;
  min-height: 9rem;
}
.srHisto > div {
  display: grid;
  justify-items: center;
  gap: 0.25rem;
  text-align: center;
}
.srHisto i {
  display: block;
  width: 70%;
  height: calc(6rem * var(--h, 0));
  min-height: 2px;
  background: var(--ui-series-2);
  border-radius: var(--ui-radius) var(--ui-radius) 0 0;
}
.srHisto [data-bin="none"] i {
  background: var(--ui-danger);
}
.srHisto [data-bin="q1"] i {
  background: var(--ui-warn);
}
.srHisto [data-bin="q4"] i {
  background: var(--ui-series-4);
}
.srHisto small {
  font-size: var(--ui-step--1);
}
.srChart {
  margin: 0;
}
.srChart svg {
  width: 100%;
  height: auto;
}
.srLine {
  fill: none;
  stroke: var(--ui-series-2);
  stroke-width: 2.5;
}
.srDot {
  fill: var(--ui-series-2);
}
.srPickWrap {
  position: relative;
}

/* «اپ و اعلان» (app-brand.js): the app as it sits on a home screen, and the
   thirty days of people opening it as columns. */
.abPreview {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ui-gap);
  align-items: flex-start;
}
.abTile {
  display: grid;
  justify-items: center;
  gap: 0.35rem;
  width: 5.5rem;
  text-align: center;
}
.abTile img {
  width: 4rem;
  height: 4rem;
  border-radius: 22%;
  box-shadow: var(--ui-elev-raised);
}
.abTile span {
  font-size: var(--ui-step--1);
  overflow-wrap: anywhere;
}
.abHow ul {
  margin: 0.4rem 0 0;
  padding-inline-start: 1.1rem;
}
.abBars {
  display: grid;
  grid-template-columns: repeat(30, minmax(0, 1fr));
  align-items: end;
  gap: 2px;
  height: 7rem;
  border-block-end: 1px solid var(--ui-line);
}
.abBars i {
  display: block;
  height: calc(100% * var(--h, 0));
  min-height: 1px;
  background: var(--ui-series-2);
  border-radius: 2px 2px 0 0;
}
.abAxis {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  color: var(--ui-muted);
}
#abLink {
  flex: 1 1 16rem;
  min-width: 0;
}

/* «دوره‌ها» (lms.js): the outline beside the lesson on a wide screen, above
   it on a phone; a video at 16:9; a PDF tall enough to read. */
.lmCourse {
  display: grid;
  gap: 0.4rem;
  align-content: start;
}
.lmSection {
  display: grid;
  gap: 0.5rem;
}
.lmLessons {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}
.lmLessons li {
  display: grid;
  grid-template-columns: 1.6rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.5rem;
  padding-block: 0.3rem;
  border-block-end: 1px solid var(--ui-line);
}
.lmKind {
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
}
.lmStudy {
  display: grid;
  grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
  gap: var(--ui-gap);
  align-items: start;
}
@media (max-width: 900px) {
  .lmStudy {
    grid-template-columns: minmax(0, 1fr);
  }
}
.lmOutline ol {
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
  display: grid;
  gap: 0.3rem;
}
.lmOutline button {
  width: 100%;
  justify-content: flex-start;
  text-align: start;
}
.lmOutline li[data-state="done"] button {
  color: var(--ui-muted);
}
.lmOutline button[aria-current="true"] {
  border-color: var(--ui-brand);
}
.lmVideo {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--ui-surface-2);
  border-radius: var(--ui-radius);
  overflow: hidden;
}
.lmVideo iframe,
.lmVideo video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.lmPdf {
  width: 100%;
  height: 75vh;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.lmText,
.lmAnswerText {
  line-height: 1.9;
  overflow-wrap: anywhere;
}
.lmAnswer {
  display: grid;
  gap: 0.5rem;
  margin-block-end: var(--ui-gap);
}

/* «گزارش‌ساز» (report-builder.js): one line per category in five series
   colours, a two-way table shaded by value, the ready-made reports as a row. */
.rbPresets {
  margin-block-end: var(--ui-gap);
}
.rbForm {
  display: grid;
  gap: 0.75rem;
}
.rbMeasures {
  border: 0;
  margin: 0;
  padding: 0;
}
.rbLine {
  fill: none;
  stroke-width: 2.5;
  stroke: var(--ui-series-1);
}
.rbLine[data-s="2"] {
  stroke: var(--ui-series-2);
}
.rbLine[data-s="3"] {
  stroke: var(--ui-series-3);
}
.rbLine[data-s="4"] {
  stroke: var(--ui-series-4);
}
.rbLine[data-s="5"] {
  stroke: var(--ui-series-5);
}
.rbKey::before {
  content: "";
  display: inline-block;
  width: 0.8rem;
  height: 0.25rem;
  margin-inline-end: 0.35rem;
  vertical-align: middle;
  border-radius: 2px;
  background: var(--ui-series-1);
}
.rbKey[data-s="2"]::before {
  background: var(--ui-series-2);
}
.rbKey[data-s="3"]::before {
  background: var(--ui-series-3);
}
.rbKey[data-s="4"]::before {
  background: var(--ui-series-4);
}
.rbKey[data-s="5"]::before {
  background: var(--ui-series-5);
}
.rbHeat td {
  background: color-mix(in srgb, var(--ui-series-2) calc(var(--a, 0) * 70%), transparent);
  font-variant-numeric: tabular-nums;
}
.rbSavedList {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}
.rbSavedList li {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
  align-items: center;
  padding-block: 0.4rem;
  border-block-end: 1px solid var(--ui-line);
}

/* The report builder's line chart is drawn at the width it is shown at
   (report-builder.js), so it is not stretched: labels stay at their size. */
.rbChart svg {
  max-width: 100%;
  height: auto;
}
.rbDot {
  fill: var(--ui-surface);
  stroke: var(--ui-series-1);
  stroke-width: 2;
}
.rbValue {
  fill: var(--ui-text);
  font-size: 11px;
  font-weight: 600;
}

/* «حضور من» / «حضور کارکنان» (staff-attendance.js): each day a square in its
   status colour, the thirty days as stacked columns, the status in a chip. */
.saClock {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ui-gap);
  margin-block-end: var(--ui-gap);
}
.saClock > div:not(.actions) {
  display: grid;
  gap: 0.2rem;
  min-width: 6rem;
}
.saClock b {
  font-size: var(--ui-step-2);
  font-variant-numeric: tabular-nums;
}
.saCal {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 2.2rem), 1fr));
  gap: 0.3rem;
  margin-block: 0.5rem;
}
.saCal span {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
  font-size: var(--ui-step--1);
}
.saCal span[data-status="present"],
.saKey[data-status="present"]::before,
.saStack i[data-status="present"] {
  background: var(--ui-series-4);
}
.saCal span[data-status="late"],
.saKey[data-status="late"]::before,
.saStack i[data-status="late"] {
  background: var(--ui-warn);
}
.saCal span[data-status="absent"],
.saKey[data-status="absent"]::before,
.saStack i[data-status="absent"] {
  background: var(--ui-danger);
}
.saCal span[data-status="leave"],
.saKey[data-status="leave"]::before,
.saStack i[data-status="leave"] {
  background: var(--ui-info);
}
.saCal span[data-status="extra"],
.saKey[data-status="extra"]::before {
  background: var(--ui-series-2);
}
.saCal span[data-status="present"],
.saCal span[data-status="late"],
.saCal span[data-status="absent"],
.saCal span[data-status="leave"],
.saCal span[data-status="extra"] {
  color: var(--ui-brand-ink);
}
.saKey {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
}
.saKey::before {
  content: "";
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  background: var(--ui-line);
}
.saCounts {
  margin-block: 0.75rem;
}
.saStack {
  display: grid;
  grid-template-columns: repeat(30, minmax(0, 1fr));
  align-items: end;
  gap: 2px;
  height: 9rem;
  border-block-end: 1px solid var(--ui-line);
}
.saStack > div {
  display: flex;
  flex-direction: column-reverse;
  height: 100%;
}
.saStack i {
  display: block;
  height: calc(100% * var(--h, 0));
}
.saOffice {
  display: grid;
  justify-items: center;
  gap: 0.6rem;
  text-align: center;
}
.saOffice img {
  width: min(22rem, 80vw);
  aspect-ratio: 1;
  /* A QR must be dark on light to scan, in either theme -- as .atBoard img. */
  background: white;
  border-radius: var(--ui-radius);
}
.saOffice b {
  font-size: var(--ui-step-4);
  letter-spacing: 0.2em;
}

/* «CRM بازاریابی» (platform-crm.js): new leads per week stacked by source in
   the report builder's series colours, and a lead's events as a timeline. */
.crmWeeks {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: end;
  gap: 3px;
  height: 9rem;
  border-block-end: 1px solid var(--ui-line);
}
.crmWeeks > div {
  display: flex;
  flex-direction: column-reverse;
  height: 100%;
}
.crmWeeks i {
  display: block;
  height: calc(100% * var(--h, 0));
  background: var(--ui-series-1);
}
.crmWeeks i[data-s="2"] {
  background: var(--ui-series-2);
}
.crmWeeks i[data-s="3"] {
  background: var(--ui-series-3);
}
.crmWeeks i[data-s="4"] {
  background: var(--ui-series-4);
}
.crmWeeks i[data-s="5"] {
  background: var(--ui-series-5);
}
.crmTimeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
  border-inline-start: 2px solid var(--ui-line);
  padding-inline-start: 0.8rem;
}

/* The success trend is drawn at its shown width (success.js); it only shrinks. */
svg.scTrend {
  max-width: 100%;
  height: auto;
}
.scLegend {
  row-gap: 0.4rem;
}

/* A level chip on a Student Success card stays on one line: «نیازمند مداخلهٔ
   فوری» broke in two beside the score. The name column gives way instead. */
.scCardHead > div:first-child {
  min-width: 0;
}
.scScore {
  flex-shrink: 0;
}
.scScore .statusChip {
  white-space: nowrap;
}

/* Touch targets: at least 44px on a phone or any touch screen (Codex's live
   audit: menu items 29px, compact buttons 38px -- an institution's 13px base
   font shrinks every rem-sized control). An id's weight because the sizes
   this overrides carry !important. The desktop keeps its density. Calendar
   days are left alone: seven to a row has to fit. */
@media (max-width: 900px), (pointer: coarse) {
  button:not(.jalaliDay):not(#dt-none),
  .btn:not(#dt-none),
  select:not(#dt-none),
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]):not(#dt-none),
  .compact:not(.jalaliDay):not(#dt-none),
  .dtNavGroup button:not(#dt-none) {
    min-height: 44px !important;
  }
  .dtToastClose:not(#dt-none) {
    min-width: 44px !important;
  }
  input[type="checkbox"]:not(#dt-none),
  input[type="radio"]:not(#dt-none) {
    width: 22px;
    height: 22px;
  }
  .check:not(#dt-none) {
    min-height: 44px;
  }
}

/* A long table scrolls inside its own box, so the sticky head above
   (`.tableWrap th`) has something to stick to -- in a wrapper that only
   scrolled sideways it never did (Codex's live audit, P2). ui.js marks a
   table long past fifteen rows; a short one keeps flowing with the page. */
.tableWrap.dtTall {
  max-height: min(72vh, 760px);
  overflow: auto;
}

/* The side a table continues on fades out, so a cut-off column reads as
   "more this way" rather than as the edge (ui.js sets the classes from the
   scroll position; in RTL the end is the left). A mask, not a shadow: it
   shows over cells that paint a background, and only the opacity of the
   token in it counts. */
.tableWrap.dtMoreEnd {
  -webkit-mask-image: linear-gradient(to right, transparent 0, var(--ui-text) 36px);
  mask-image: linear-gradient(to right, transparent 0, var(--ui-text) 36px);
}
.tableWrap.dtMoreStart {
  -webkit-mask-image: linear-gradient(to left, transparent 0, var(--ui-text) 36px);
  mask-image: linear-gradient(to left, transparent 0, var(--ui-text) 36px);
}
.tableWrap.dtMoreStart.dtMoreEnd {
  -webkit-mask-image: linear-gradient(to right, transparent 0, var(--ui-text) 36px, var(--ui-text) calc(100% - 36px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, var(--ui-text) 36px, var(--ui-text) calc(100% - 36px), transparent 100%);
}

/* The menu's search, recent sections and folding groups
   (dashboard-navigation.js). The group titles come back on the desktop as the
   control that folds a group -- they had been hidden to fit thirteen items in
   the window, and fifty-seven never fit. An id's weight where a nav rule with
   !important has to be beaten. */
.dtNavTools {
  flex: none;
  display: grid;
  gap: 0.4rem;
  margin: 0 0 0.45rem;
}
.dtNavTools input {
  width: 100%;
}
.dtNavRecent {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
}
.dtNavRecent[hidden] {
  display: none;
}
.dtNavRecentTitle {
  font-size: 0.75em;
  color: var(--ui-muted);
}
.dtNavRecentChip:not(#dt-none) {
  min-height: 0 !important;
  padding: 0.15rem 0.55rem !important;
  font-size: 0.78em !important;
  font-weight: 600 !important;
  border-radius: 99px !important;
  background: var(--ui-surface-2) !important;
  color: var(--ui-text) !important;
  border: 1px solid var(--ui-line) !important;
  box-shadow: none !important;
}
.dtNavRecentChip:not(#dt-none):hover {
  border-color: var(--ui-brand) !important;
}
.dtNavGroupTitle:not(#dt-none) {
  display: flex !important;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  user-select: none;
}
.dtNavGroupTitle::after {
  content: "‹";
  font-size: 1.2em;
  line-height: 1;
  transform: rotate(-90deg);
  transition: transform 0.15s ease;
}
.dtNavGroupTitle[aria-expanded="false"]::after {
  transform: rotate(0deg);
}
.dtNavGroupTitle:focus-visible {
  outline: 2px solid var(--ui-brand);
  outline-offset: 1px;
}
.dtGroupedNav:not(.dtNavSearching) .dtNavGroup.dtNavCollapsed > button:not(.active):not(#dt-none),
.dtGroupedNav.dtNavSearching .dtNavGroup > button.dtNavMiss:not(#dt-none),
.dtNavGroup.dtNavEmpty:not(#dt-none) {
  display: none !important;
}
/* On a touch screen the 44px floor above wins, so the chip widens to match
   rather than turning into a circle. */
@media (max-width: 900px), (pointer: coarse) {
  .dtNavRecentChip:not(#dt-none) {
    padding-inline: 0.9rem !important;
  }
}
.platformLiveCard{display:grid;gap:var(--ui-space-3)}
.platformLiveCard .sectionHead{display:flex;margin:0}
.livePulse{display:inline-block;width:.7rem;height:.7rem;margin-inline-end:var(--ui-space-1);border-radius:var(--ui-radius-pill);background:var(--ui-info);box-shadow:0 0 0 .22rem color-mix(in srgb,var(--ui-info) 18%,transparent)}
.liveMetricGrid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--ui-gap)}
.liveMetric{display:grid;gap:var(--ui-space-1);min-height:6.5rem;padding:var(--ui-pad-card);border:1px solid var(--ui-line);border-radius:var(--ui-radius);background:var(--ui-surface-2)}
.liveMetric span,.liveMetric small{color:var(--ui-muted)}
.liveMetric b{font-size:var(--ui-step-3);color:var(--ui-text)}
.liveDetails{border:1px solid var(--ui-line);border-radius:var(--ui-radius);background:var(--ui-surface-2);overflow:hidden}
.liveDetails summary{min-height:var(--ui-control-h);padding:var(--ui-pad-control);display:flex;align-items:center;font-weight:700;cursor:pointer;color:var(--ui-text)}
.liveDetails[open] summary{border-bottom:1px solid var(--ui-line)}
@media(max-width:900px){.liveMetricGrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:560px){.liveMetricGrid{grid-template-columns:repeat(2,minmax(0,1fr))}.liveMetric{min-height:5.5rem}.platformLiveCard .sectionHead{align-items:flex-start}}
.liveMonitorActions{display:flex;align-items:center;margin:0}

/* Student PWA containment.  A dashboard installed as an app has no browser
   chrome to hint that content continues off-screen, so the page itself must
   never become wider than the viewport. Wide data keeps its own table/chart
   scroller; cards, forms and controls shrink to the mobile content column. */
@media (max-width: 560px) {
  .dtDashboardShell,
  .dtDashboardShell > .content,
  .dtDashboardShell .section,
  .dtDashboardShell .section > *,
  .dtDashboardShell :is(.card, .hero, .notice, fieldset) {
    min-inline-size: 0;
    max-inline-size: 100%;
    inline-size: 100%;
  }
  .dtDashboardShell,
  .dtDashboardShell > .content,
  .dtDashboardShell .section {
    overflow-x: clip;
  }
  .dtDashboardShell :is(.grid, .formGrid, .metricGrid, .kpis, .stack, .coreColumns) {
    grid-template-columns: minmax(0, 1fr);
    min-inline-size: 0;
    inline-size: 100%;
  }
  .dtDashboardShell :is(input, select, textarea, button) {
    max-inline-size: 100%;
  }
  .dtDashboardShell :is(h1, h2, h3, h4, p, small, label, .muted, .notice) {
    overflow-wrap: anywhere;
  }
  .dtDashboardShell :is(.tableWrap, .chartWrap) {
    inline-size: 100%;
    max-inline-size: 100%;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
  }
}

/* The AI post requests in «تولید محتوای جدید»: a long focus sentence wraps in
   its own column instead of pushing the table into a sideways scroll (the
   platform page sets nowrap on every cell). */
.genJobsTable {
  width: 100%;
  min-width: 0;
}
.genJobsTable td:nth-child(3) {
  white-space: normal;
  overflow-wrap: anywhere;
  min-width: 12rem;
  line-height: 1.8;
}
.genJobsTable td:nth-child(4) .fieldError {
  display: block;
  white-space: normal;
}

/* The plan open in the platform's plan form (integrations.html markPlanEditing):
   the form framed in the brand colour, a banner that stays at its top while
   the modules below are reviewed, and the plan's own card marked. */
.card.planEditing {
  border: 2px solid var(--ui-brand);
}
.planEditBar {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin: 0 0 var(--ui-space-3);
  padding: 0.6rem 0.9rem;
  border-radius: var(--ui-radius);
  background: color-mix(in srgb, var(--ui-brand) 12%, var(--ui-surface));
  box-shadow: 0 6px 18px var(--ui-shadow);
}
.planEditBarActions {
  display: flex;
  gap: 0.4rem;
  margin-inline-start: auto;
}
.planCard.isEditing {
  outline: 2px solid var(--ui-brand);
  outline-offset: 2px;
}

/* A module that stops when the AI provider does (integrations.html aiDep):
   a small sparkle chip beside its name; «جزئی» when only one part waits on it. */
.aiDep {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  margin-inline-start: 0.4rem;
  padding: 0.05rem 0.45rem;
  border-radius: 99px;
  font-size: 0.72em;
  font-weight: 700;
  vertical-align: middle;
  color: var(--ui-brand);
  background: color-mix(in srgb, var(--ui-brand) 12%, var(--ui-surface));
  border: 1px solid color-mix(in srgb, var(--ui-brand) 35%, var(--ui-line));
}
.aiDep svg {
  width: 0.9em;
  height: 0.9em;
  fill: currentColor;
}
.aiDep.isPartial {
  color: var(--ui-muted);
  background: transparent;
  border-style: dashed;
}

/* Linking a messenger bot (dashboard.html linkBot): the open button with its
   icon, the command in its own box beside a copy button, and the QR always in
   view rather than behind a disclosure. */
.botLink {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ui-space-3);
  margin-top: var(--ui-space-2);
  padding: var(--ui-space-3);
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
}
.botLinkMain {
  flex: 1 1 18rem;
  display: grid;
  gap: var(--ui-space-2);
  min-width: 0;
}
.botOpen:not(#dt-none) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  justify-self: start;
  text-decoration: none;
}
.botOpen svg {
  width: 1.2em;
  height: 1.2em;
  fill: currentColor;
}
.botCodeBox {
  display: grid;
  gap: 0.35rem;
}
.botCodeRow {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.botCodeRow code {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.5rem 0.8rem;
  border: 1px dashed var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
  font-size: 1.05em;
  letter-spacing: 0.04em;
  overflow-wrap: anywhere;
  user-select: all;
}
.botQrBox {
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  gap: 0.35rem;
  margin: 0;
}
.botQrBox .botQr {
  width: 168px;
  height: 168px;
  padding: 8px;
  border-radius: 12px;
  background: #fff;
}
.botQrBox .botQr svg {
  width: 100%;
  height: 100%;
}
.botQrBox figcaption {
  font-size: 0.8em;
  color: var(--ui-muted);
}

/* Fingerprint / face sign-in (passkeys.js): the button on the login page, the
   one-time offer on the dashboard, and the device list in «امنیت حساب». */
.passkeyLogin:not(#dt-none) {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 18px;
  background: var(--ui-surface) !important;
  color: var(--ui-text) !important;
  border: 1.5px solid var(--ui-brand) !important;
}
.passkeyLogin svg {
  width: 1.4em;
  height: 1.4em;
  fill: var(--ui-brand);
}
.passkeyOffer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  margin: 0 0 var(--ui-space-3);
  padding: 0.8rem 1rem;
  border: 1px solid color-mix(in srgb, var(--ui-brand) 35%, var(--ui-line));
  border-radius: var(--ui-radius);
  background: color-mix(in srgb, var(--ui-brand) 8%, var(--ui-surface));
}
.passkeyOffer > div:not(.actions) {
  display: grid;
  gap: 2px;
  flex: 1 1 14rem;
}
.passkeyOffer small {
  color: var(--ui-muted);
}
.passkeyOffer .actions {
  margin: 0;
}
.passkeyOfferIcon {
  font-size: 1.6em;
  color: var(--ui-brand);
}
.passkeyList {
  list-style: none;
  margin: 0 0 var(--ui-space-2);
  padding: 0;
  display: grid;
  gap: 0.5rem;
}
.passkeyList li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
}
.passkeyList li > span {
  display: grid;
  gap: 2px;
  min-width: 0;
}

/* Paper exams, the ways a sheet arrives (paper-exams.js scanTab): three
   buttons, a drop zone for a scanner's files, and the live camera. */
.pxWays {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-block: var(--ui-space-2);
}
.pxPick:not(#dt-none) {
  cursor: pointer;
}
.pxDrop {
  display: grid;
  gap: 0.3rem;
  place-items: center;
  text-align: center;
  padding: var(--ui-space-3);
  margin-bottom: var(--ui-space-2);
  border: 2px dashed var(--ui-line);
  border-radius: var(--ui-radius);
  color: var(--ui-muted);
}
.pxDrop small {
  font-size: 0.8em;
}
.pxDrop.isOver {
  border-color: var(--ui-brand);
  background: color-mix(in srgb, var(--ui-brand) 8%, var(--ui-surface));
}
.pxLiveBox {
  display: grid;
  gap: 0.5rem;
  justify-items: center;
  margin-bottom: var(--ui-space-2);
}
.pxLiveBox.hidden {
  display: none;
}
.pxLiveBox video {
  width: min(100%, 560px);
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--ui-radius);
  background: var(--ui-surface-2);
}
@media (hover: none) {
  .pxDrop {
    display: none;
  }
}
