/* 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;

  /* 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;
}
/* 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;
}
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;
}
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 {
  letter-spacing: -0.02em;
}
.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) {
  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. */
.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: flex-start;
  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;
}
.choiceOption {
  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. */
#templatePanel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
  gap: 18px;
  align-items: start;
}
.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 */
.aiTutorShell {
  display: grid;
  grid-template-columns: minmax(min(100%, 230px), 300px) minmax(0, 1fr);
  min-height: 650px;
  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: 14px;
  padding: 18px;
  border-inline-end: 1px solid var(--ui-line);
  background: var(--ui-surface-2);
}
.aiSideHead,
.aiComposeFoot,
.aiChatHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.aiSideHead > div,
.aiChatHead > div {
  display: grid;
  gap: 4px;
}
.aiSideHead small,
.aiChatHead small {
  color: var(--ui-muted);
}
.aiSessionList {
  display: grid;
  gap: 8px;
  align-content: start;
  overflow: auto;
  min-height: 180px;
  max-height: 470px;
}
.aiSessionItem {
  display: grid;
  gap: 5px;
  text-align: right;
  min-height: 64px;
  padding: 10px 12px;
  border: 1px solid var(--ui-line);
  border-radius: 14px;
  background: var(--ui-surface);
  color: var(--ui-text);
}
.aiSessionItem small {
  color: var(--ui-muted);
}
.aiSessionItem.active {
  border-color: var(--ui-brand);
  box-shadow: inset 3px 0 var(--ui-brand);
}
.aiNewForm {
  display: grid;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--ui-line);
  border-radius: 14px;
  background: var(--ui-surface);
}
.aiChat {
  display: grid;
  grid-template-rows: auto minmax(280px, 1fr) auto;
  min-width: 0;
}
.aiChatHead {
  padding: 18px 20px;
  border-bottom: 1px solid var(--ui-line);
}
.aiMessages {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 22px;
  overflow: auto;
  max-height: 520px;
  background: var(--ui-bg);
}
.aiBubble {
  max-width: min(82%, 720px);
  padding: 14px 16px;
  border: 1px solid var(--ui-line);
  border-radius: 18px;
  background: var(--ui-surface);
  color: var(--ui-text);
  line-height: 1.9;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.aiBubble.student {
  align-self: flex-start;
  background: color-mix(in srgb, var(--ui-brand) 12%, var(--ui-surface));
  border-bottom-right-radius: 5px;
}
.aiBubble.assistant {
  align-self: flex-end;
  border-bottom-left-radius: 5px;
}
.aiBubble footer {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 9px;
  font-size: 0.78em;
  color: var(--ui-muted);
}
.aiRedacted {
  color: var(--ui-danger);
  font-weight: 700;
}
.aiComposer {
  display: grid;
  gap: 8px;
  padding: 16px 20px;
  border-top: 1px solid var(--ui-line);
  background: var(--ui-surface);
}
.aiComposer textarea {
  min-height: 112px;
  resize: vertical;
}
.aiComposeFoot button {
  min-width: 130px;
}
.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);
  }
}
.aiConsent {
  max-width: 760px;
  margin: auto;
  display: grid;
  gap: 14px;
  padding: 28px;
}
.aiConsent h3,
.aiConsent p,
.aiConsent ul {
  margin: 0;
}
.aiConsent ul {
  display: grid;
  gap: 8px;
  padding-inline-start: 22px;
  color: var(--ui-muted);
}
.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;
}
.aiConsentCheck {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--ui-line);
  border-radius: 12px;
  background: var(--ui-surface-2);
}
.aiConsentCheck input {
  margin-top: 5px;
  flex: 0 0 auto;
}
.dangerOutline {
  margin-top: auto;
  color: var(--ui-danger) !important;
  border-color: var(--ui-danger) !important;
  background: transparent !important;
}
.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: #061222aa;
  backdrop-filter: blur(3px);
}
@media (max-width: 850px) {
  .aiTutorShell {
    grid-template-columns: 1fr;
  }
  .aiSessions {
    border-inline-end: 0;
    border-bottom: 1px solid var(--ui-line);
  }
  .aiSessionList {
    max-height: 220px;
  }
  .aiMessages {
    max-height: 55vh;
  }
  .aiBubble {
    max-width: 94%;
  }
  .aiChatHead,
  .aiComposer,
  .aiMessages {
    padding-inline: 14px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .uiSpinner {
    animation-duration: 1.8s;
  }
}
.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;
  }
  .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) {
  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;
  }
}
.dtNavGroup button {
  background: transparent !important;
  color: var(--ui-muted) !important;
  border-color: transparent !important;
}
.dtNavGroup button:hover {
  background: color-mix(in srgb, var(--ui-brand) 10%, var(--ui-surface-2)) !important;
  color: var(--ui-text) !important;
}
.dtNavGroup 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;
}
.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);
}

.content h1 {
  font-size: 1.55rem;
}
.content h2,
.sectionHead h2 {
  font-size: 1.3rem;
}
.content h3,
.sectionHead h3 {
  font-size: 1.05rem;
}
.dtOverviewHead h2 {
  font-size: 1.35rem;
}

.metricCard {
  padding: var(--ui-pad-card);
  border-radius: var(--ui-radius);
  gap: 0.35rem;
}
.metricCard b {
  font-size: 1.5em;
}
.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 {
  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="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;
}

/* 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);
}

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 {
  letter-spacing: -0.02em;
}
.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));
}
.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;
}

/* 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;
}
