/* Hulul staff dashboard — Sneat design system, Arabic / RTL.
 *
 * Values are Sneat's own: the violet #696cff family, the #f5f5f9 page tint,
 * shadow-based card separation instead of borders, 6px card radius, and the
 * dark palette's desaturated indigo (#232333 / #2b2c40) rather than grey.
 *
 * Written entirely in logical properties (inline-start / inline-end), so the
 * RTL layout is correct by construction rather than by mirrored overrides.
 * No build step, no framework, no network fetches.
 */

/* ------------------------------------------------------------------ fonts */

@font-face {
  font-family: "Cairo";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("assets/cairo-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1,
    U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF,
    U+FE70-FE74, U+FE76-FEFC;
}

@font-face {
  font-family: "Cairo";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("assets/cairo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}

/* The four alternatives an employee can pick in "حسابي" — self-hosted for the
 * same reason Cairo is: the page's security policy allows no outside font. */

@font-face {
  font-family: "Amiri";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/amiri-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "Amiri";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/amiri-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Amiri";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/amiri-arabic-700.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "Amiri";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/amiri-latin-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "El Messiri";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("assets/elmessiri-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "El Messiri";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("assets/elmessiri-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Scheherazade New";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/scheherazade-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "Scheherazade New";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/scheherazade-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Scheherazade New";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/scheherazade-arabic-700.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "Scheherazade New";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/scheherazade-latin-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Tajawal";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/tajawal-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "Tajawal";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/tajawal-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Tajawal";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/tajawal-arabic-700.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "Tajawal";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/tajawal-latin-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ----------------------------------------------------------------- tokens */

:root {
  /* The reading font. Overridden on <html> by whichever face the person
   * picked — see fontCard() in app.js. */
  --font: "Cairo";

  /* Sneat brand + semantic palette — identical in both themes */
  --primary: #696cff;
  --primary-hover: #5f61e6;
  --secondary: #5b6b78;
  --success: #71dd37;
  --info: #03c3ec;
  --warning: #ffab00;
  --danger: #ff3e1d;

  --primary-soft: rgba(105, 108, 255, 0.16);
  --success-soft: rgba(113, 221, 55, 0.16);
  --info-soft: rgba(3, 195, 236, 0.16);
  --warning-soft: rgba(255, 171, 0, 0.16);
  --danger-soft: rgba(255, 62, 29, 0.16);
  --secondary-soft: rgba(133, 146, 163, 0.16);

  /* Light surfaces */
  --bg: #f5f5f9;
  --card: #ffffff;
  --text: #566a7f;
  --heading: #384551;
  --muted: #5b6b78;
  --border: #e4e6e8;
  --hover: #f7f7f9;

  --shadow-sm: 0 0.125rem 0.375rem 0 rgba(161, 172, 184, 0.3);
  --shadow: 0 0.1875rem 0.5rem 0 rgba(161, 172, 184, 0.4);
  --shadow-lg: 0 0.25rem 0.75rem 0 rgba(161, 172, 184, 0.45);
  --shadow-primary: 0 0.125rem 0.25rem 0 rgba(105, 108, 255, 0.4);

  /* Shape */
  --r-sm: 0.25rem;
  --r: 0.375rem;
  --r-lg: 0.5rem;
  --r-pill: 50rem;

  --sidebar-w: 210px;
  --sidebar-w-collapsed: 80px;
  --topbar-h: 62px;
}

.dark {
  --bg: #232333;
  --card: #2b2c40;
  --text: #b2b2c4;
  --heading: #e8e8ef;
  --muted: #a0a0b8;
  --secondary: #a0a0b8;
  --border: #4e4f6c;
  --hover: #35364e;

  --shadow-sm: 0 0.125rem 0.375rem 0 rgba(20, 20, 29, 0.2);
  --shadow: 0 0.1875rem 0.5rem 0 rgba(20, 20, 29, 0.28);
  --shadow-lg: 0 0.25rem 0.75rem 0 rgba(20, 20, 29, 0.36);
}

/* ------------------------------------------------------------------- base */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* The `hidden` property must always win.
 *
 * The browser hides [hidden] with `display: none`, but that is the weakest
 * possible rule, so ANY rule here that sets a display beats it and the
 * element stays on screen. That bit four separate elements before it was
 * traced to the cause, each fixed on its own (.picker-panel, .attachment,
 * .bell-count, then a button); the working-hours row was silently broken the
 * same way and nobody had noticed. `!important` is deliberate and is the one
 * place in this file that earns it: "hidden means hidden" should not be
 * re-litigated by whatever display a component happens to need. */
[hidden] {
  display: none !important;
}

html {
  color-scheme: light;
  /* The gutter is reserved on every screen, scrolling or not. Without it a
   * short page (the inbox) and a long one (the tickets table) disagree about
   * whether a scrollbar exists, and the whole layout jumps sideways each time
   * staff switch tabs. */
  scrollbar-gutter: stable;
}

.dark {
  color-scheme: dark;
}

body {
  margin: 0;
  font-family: var(--font), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.2s ease, color 0.2s ease;
}

h1,
h2,
h3,
h4,
h5 {
  color: var(--heading);
  font-weight: 600;
  margin: 0;
}

h1 {
  font-size: 1.375rem;
  line-height: 1.4;
}

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}

h2 {
  font-size: 1.0625rem;
  line-height: 1.4;
}

a {
  color: var(--primary);
  text-decoration: none;
}

a:hover {
  color: var(--primary-hover);
  text-decoration: underline;
}

a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* Read aloud, never drawn: a heading or column name a sighted person
 * already gets from the layout. */
.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.muted {
  color: var(--muted);
}

.spacer {
  flex: 1;
}

.bx {
  font-size: 1.25rem;
  vertical-align: middle;
}

/* -------------------------------------------------------- form primitives */

input,
select,
textarea,
button {
  font-family: inherit;
  font-size: 0.9375rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="date"],
select,
textarea {
  width: 100%;
  padding: 0.4219rem 0.875rem;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

textarea {
  resize: vertical;
  line-height: 1.55;
}

input::placeholder,
textarea::placeholder {
  color: var(--muted);
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0.25rem 0.05rem rgba(105, 108, 255, 0.1);
}

select {
  appearance: none;
  padding-inline-end: 2rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23a1acb8'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: left 0.7rem center;
  background-size: 14px;
  cursor: pointer;
}

label {
  display: block;
  margin-block-end: 0.3rem;
  font-size: 0.8125rem;
  color: var(--heading);
}

input[type="checkbox"] {
  inline-size: 1.1rem;
  block-size: 1.1rem;
  accent-color: var(--primary);
  margin: 0;
  cursor: pointer;
}

/* Sneat's pill switch. The checkbox stays in the page — invisible but focusable
   and still the thing that holds the state — so keyboard and screen readers get
   a real checkbox rather than a div pretending to be one. */
/* Settings screen: a hierarchical grid rather than one column of full-width
   forms — the biggest card gets its own row, a medium pair shares the next,
   and the one-field cards (price, dark mode, account, audit, deleted chats)
   group three to a row instead of each claiming a full-width row of blank
   space around a single input. */
.settings-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  align-items: start;
}

.settings-grid .span-2 {
  grid-column: span 2;
}

.settings-grid .span-3 {
  grid-column: 1 / -1;
}

/* Price and dark mode are both short and the same height — pairing them
   here keeps that row from stretching to match a taller three-up sibling. */
.settings-grid .subgrid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}

/* Groups a few short fields (a name, a tagline, a color) on one line instead
   of each stretching the full card width for a handful of characters. */
.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-block-end: 1rem;
}

.field-row > div {
  flex: 1 1 12rem;
  min-inline-size: 0;
}

.field-row > .field-narrow {
  flex: 0 0 auto;
}

.swatch-value {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.swatch-value code {
  font-size: 0.8125rem;
  color: var(--muted);
  background: var(--bg);
  padding: 0.3rem 0.55rem;
  border-radius: var(--r-sm);
}

.switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  margin: 0;
  cursor: pointer;
}

.switch-title {
  display: block;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--heading);
}

.switch-desc {
  display: block;
  margin-block-start: 0.25rem;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--muted);
}

/* Label and value on one line, divided by a hairline — three short facts read
   better as a short list than as a grid of near-empty columns. */
.info-row {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding-block: 0.6rem;
  border-block-end: 1px solid var(--border);
}

.info-row:last-of-type {
  border-block-end: none;
  padding-block-end: 0;
}

/* A fixed label column keeps the value beside its own label instead of
   stranding the two at opposite edges of a wide card. */
.info-row dt {
  flex: none;
  inline-size: 7.5rem;
  font-size: 0.8125rem;
  color: var(--muted);
}

.info-row dd {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--heading);
}

.switch {
  position: relative;
  flex: none;
  inline-size: 2.25rem;
  block-size: 1.375rem;
  border-radius: var(--r-pill);
  background: var(--border);
  transition: background-color 0.2s ease;
}

.switch input[type="checkbox"] {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

/* Moved with a logical offset rather than a transform: right-to-left then
   flips the travel direction on its own. */
.switch .knob {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 0.1875rem;
  inline-size: 1rem;
  block-size: 1rem;
  margin-block-start: -0.5rem;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0.125rem 0.25rem 0 rgba(20, 20, 29, 0.2);
  transition: inset-inline-start 0.2s ease;
  pointer-events: none;
}

.switch:has(input:checked) {
  background: var(--primary);
  box-shadow: var(--shadow-primary);
}

.switch input:checked ~ .knob {
  inset-inline-start: calc(100% - 1.1875rem);
}

.switch input:focus-visible ~ .knob {
  box-shadow: 0 0 0 0.2rem rgba(105, 108, 255, 0.3);
}

.switch input:disabled {
  cursor: wait;
}

/* A second, glossier switch — embossed ON/OFF lettering, a raised knob, a
   recessed track — for a screen where the toggle itself is the focal point
   rather than a quiet row control. Built the same way as .switch: a real
   checkbox stays under the surface for focus and screen readers, everything
   else is decoration keyed off :checked. */
.switch-glossy {
  position: relative;
  display: inline-block;
  flex: none;
  inline-size: 3.375rem;
  block-size: 1.75rem;
  border-radius: var(--r-pill);
  cursor: pointer;
}

.switch-glossy input[type="checkbox"] {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.switch-glossy .track {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, var(--border), color-mix(in srgb, var(--border) 80%, black));
  box-shadow:
    inset 0 0.1875rem 0.375rem rgba(0, 0, 0, 0.28),
    inset 0 -0.0625rem 0.125rem rgba(255, 255, 255, 0.5);
  transition:
    background 0.3s ease,
    box-shadow 0.3s ease;
}

.switch-glossy:has(input:checked) .track {
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--success) 85%, white),
    color-mix(in srgb, var(--success) 80%, black)
  );
  box-shadow:
    inset 0 0.1875rem 0.375rem color-mix(in srgb, var(--success) 55%, black),
    inset 0 -0.125rem 0.1875rem rgba(255, 255, 255, 0.3);
}

/* Full block height + flex centering, not a 50%/translateY pair — a bold
   Cairo "ON"/"OFF" has more descent than ascent, so centering the line box
   itself (rather than centering on the text's own uneven metrics) is what
   actually lands the letters in the middle instead of reading high. */
.switch-glossy .word {
  position: absolute;
  inset-block: 0;
  display: flex;
  align-items: center;
  padding-block-start: 0.0625rem;
  font-size: 0.5625rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.switch-glossy .word.on {
  inset-inline-start: 0.375rem;
  inset-inline-end: 1.75rem;
  justify-content: center;
  color: color-mix(in srgb, var(--success) 60%, black);
  opacity: 0;
}

.switch-glossy .word.off {
  inset-inline-start: 1.75rem;
  inset-inline-end: 0.375rem;
  justify-content: center;
  color: rgba(0, 0, 0, 0.35);
  opacity: 1;
}

.switch-glossy:has(input:checked) .word.on {
  opacity: 1;
}

.switch-glossy:has(input:checked) .word.off {
  opacity: 0;
}

/* Moved with a logical offset rather than a transform, same as .switch's
   knob — right-to-left then flips the travel direction on its own. */
.switch-glossy .knob {
  position: absolute;
  inset-block-start: 0.1875rem;
  inset-inline-start: 0.1875rem;
  inline-size: 1.375rem;
  block-size: 1.375rem;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, #fff, #f4f4f4 55%, #e3e3e4 100%);
  box-shadow:
    0 0.25rem 0.5rem rgba(0, 0, 0, 0.28),
    0 0.0625rem 0.125rem rgba(0, 0, 0, 0.22),
    inset 0 -0.1875rem 0.25rem rgba(0, 0, 0, 0.1),
    inset 0 0.125rem 0.125rem rgba(255, 255, 255, 0.95);
  transition: inset-inline-start 0.3s cubic-bezier(0.34, 1.4, 0.4, 1);
  pointer-events: none;
}

.switch-glossy:has(input:checked) .knob {
  inset-inline-start: calc(100% - 1.5625rem);
}

.switch-glossy input:focus-visible ~ .knob {
  box-shadow:
    0 0.25rem 0.5rem rgba(0, 0, 0, 0.28),
    0 0 0 0.2rem color-mix(in srgb, var(--primary) 45%, transparent);
}

.switch-glossy input:disabled {
  cursor: wait;
}

/* Locked off by the workspace master switch, not mid-save (that's the rule
   above) — dim the whole control so it reads as "can't", not "loading". */
.switch-glossy:has(input:disabled) {
  opacity: 0.5;
  cursor: not-allowed;
}

/* "Last changed by X" under a setting, and the same muted note elsewhere on
   the settings screen. Collapses when there is nothing to say. */
.setting-stamp {
  margin: 1.25rem 0 0;
  font-size: 0.8125rem;
  color: var(--muted);
}

.setting-stamp:empty {
  display: none;
}

/* ---------------------------------------------------------------- buttons */

button {
  cursor: pointer;
  /* Buttons sit next to each other inline. Baseline alignment would offset a
   * button carrying an icon against a plain-text one beside it; middles line
   * them up whatever is inside them. */
  vertical-align: middle;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  padding: 0.4219rem 1.25rem;
  font-weight: 600;
  font-size: 0.8125rem;
  line-height: 1.5;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  transition: background-color 0.15s ease, box-shadow 0.15s ease, color 0.15s ease,
    border-color 0.15s ease, transform 0.1s ease;
}

button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Sneat tints a button's shadow toward its own hue rather than neutral grey. */
button.primary {
  background: var(--primary);
  color: #fff;
  box-shadow: var(--shadow-primary);
}

button.primary:hover:not(:disabled) {
  background: var(--primary-hover);
  box-shadow: 0 0.125rem 0.375rem 0 rgba(105, 108, 255, 0.5);
}

/* A solid destructive button — the confirm side of a delete dialog. The sheet
 * only had a ghost variant (button.ghost.danger) before this. */
button.danger {
  background: var(--danger);
  color: #fff;
  border-color: var(--danger);
  box-shadow: 0 0.125rem 0.25rem 0 rgba(255, 62, 29, 0.4);
}

button.danger:hover:not(:disabled) {
  background: #e8351a;
  border-color: #e8351a;
}

button.ghost {
  background: transparent;
  color: var(--primary);
  border-color: rgba(105, 108, 255, 0.5);
}

button.ghost:hover:not(:disabled) {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
  box-shadow: var(--shadow-primary);
}

button.ghost.danger {
  color: var(--danger);
  border-color: rgba(255, 62, 29, 0.5);
}

button.ghost.danger:hover:not(:disabled) {
  background: var(--danger);
  color: #fff;
  border-color: var(--danger);
  box-shadow: 0 0.125rem 0.25rem 0 rgba(255, 62, 29, 0.4);
}

button.link,
button.icon-btn {
  background: transparent;
  border: none;
  color: var(--text);
  padding: 0.4rem;
  border-radius: var(--r-pill);
}

button.link:hover,
button.icon-btn:hover {
  background: var(--primary-soft);
  color: var(--primary);
}

button:active:not(:disabled) {
  transform: translateY(1px);
}

/* ------------------------------------------------------------------ cards */

.card {
  background: var(--card);
  border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  padding: 1.5rem;
}

/* A label that follows a switch row was sitting on the row's description. */
.card > .switch-row + label {
  margin-block-start: 1rem;
}

.card > h2 {
  margin-block-end: 1rem;
}

/* The home page's three setup cards sit in one short row with nothing below
 * them — same-height shadows lining up like that read as a stray line under
 * the row rather than a per-card shadow, so these skip it. */
.setup-card {
  box-shadow: none;
  border: 1px solid var(--border);
}

/* Breakdown cards: a handful of short rows each, so they get tighter padding
 * and sit side by side instead of one full-width card per table. */
.report-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
  gap: 1.25rem;
  align-items: start;
}

.card.compact {
  padding: 1.1rem 1.25rem;
}

.card.compact > h2 {
  margin-block-end: 0.5rem;
  font-size: 0.9375rem;
}

.card.compact tbody td {
  padding: 0.4rem 0;
  font-size: 0.8125rem;
}

.card.compact tbody td:first-child {
  padding-inline-end: 0.75rem;
  white-space: nowrap;
}

.card.compact tbody td:last-child {
  text-align: end;
}

.card.compact .bar {
  min-inline-size: 40px;
}

.card.compact .bar-row {
  gap: 0.5rem;
}

.card.compact .bar-row .pct {
  min-inline-size: 1.5rem;
}

/* ---------------------------------------------------------------- sidebar */

#sidebar {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: var(--sidebar-w);
  background: var(--card);
  border-inline-end: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  z-index: 30;
  transition: transform 0.25s ease, inline-size var(--t) var(--ease);
}

.brand-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem 1.5rem 1rem;
}

.logo-icon {
  inline-size: 100%;
  max-inline-size: 160px;
  block-size: auto;
  object-fit: contain;
}

.logo-text {
  display: block;
  font-size: 1.375rem;
  font-weight: 700;
  color: var(--heading);
  line-height: 1.1;
}

.logo-sub {
  display: block;
  font-size: 0.75rem;
  color: var(--muted);
}

.sidebar-menu {
  list-style: none;
  margin: 0;
  padding: 0.5rem 1rem 1.5rem;
  overflow-y: auto;
  flex: 1;
}

/* Grounds the rail below a short menu instead of leaving it a dead gap —
 * sidebar-menu above takes flex:1, so this sits flush at the bottom. */
.sidebar-footer {
  padding: 1rem 1.5rem;
  border-block-start: 1px solid var(--border);
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--muted);
}

.sidebar-footer div:first-child {
  color: var(--heading);
  font-weight: 600;
  font-size: 0.8125rem;
}

/* Chat-only sliders: relevant while reading a conversation, noise everywhere
 * else — .chat-scale-visible is toggled by route() alongside the nav-link
 * .active state, so both follow the same "is inbox open" check. */
.chat-scale {
  display: none;
  padding: 0.75rem 1.5rem 0;
  border-block-start: 1px solid var(--border);
}

.chat-scale-visible {
  display: block;
}

.chat-scale label,
.chat-scale-card label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: 0.75rem;
  color: var(--muted);
  margin-block-end: 0.4rem;
}

.chat-scale input[type="range"],
.chat-scale-card input[type="range"] {
  inline-size: 100%;
  margin: 0 0 0.6rem;
}

.sidebar-header {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--muted);
  padding: 1rem 0.5rem 0.5rem;
  letter-spacing: 0.4px;
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  margin-block-end: 0.25rem;
  border-radius: var(--r-pill);
  color: var(--text);
  font-size: 0.9375rem;
  font-weight: 600;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.nav-link:hover {
  background: var(--hover);
  color: var(--heading);
  text-decoration: none;
}

/* Sneat's selection state: a solid, rounded violet pill — the one piece of
 * high contrast in an otherwise low-contrast menu. */
.nav-link.active,
.nav-link.active:hover {
  background: linear-gradient(270deg, rgba(105, 108, 255, 0.7), var(--primary));
  color: #fff;
  box-shadow: var(--shadow-primary);
}

.nav-count {
  margin-inline-start: auto;
  font-size: 0.75rem;
  font-weight: 600;
  background: var(--danger);
  color: #fff;
  border-radius: var(--r-pill);
  padding: 0.05rem 0.45rem;
}

.nav-link.active .nav-count {
  background: rgba(255, 255, 255, 0.25);
}

/* ----------------------------------------------------------------- topbar */

.topbar {
  position: fixed;
  inset-block-start: 0;
  inset-inline-end: 0;
  inset-inline-start: var(--sidebar-w);
  block-size: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-inline: 1.25rem;
  background: var(--card);
  border-block-end: 3px solid var(--primary);
  z-index: 20;
  transition: inset-inline-start 0.25s ease;
}

.topbar-search {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--muted);
  flex: 1;
  min-inline-size: 0;
}

/* Only ever shown by body.inbox-active at the 576px breakpoint below;
 * otherwise this slot stays empty space in the topbar's middle. */
.inbox-search-slot {
  display: none;
}

.topbar-search .bx {
  font-size: 1.375rem;
}

.topbar-search input {
  border: none;
  background: transparent;
  padding: 0;
  flex: 1;
  min-inline-size: 0;
}

.topbar-search input:focus {
  box-shadow: none;
}

.menu-toggle {
  background: transparent;
  border: none;
  color: var(--text);
  padding: 0.35rem;
  display: inline-flex;
}

/* -- desktop collapse ---------------------------------------------------
 * The same toggle button and body.sidebar-toggled class that turn the
 * sidebar into a mobile overlay (see the 992px block below) shrink it to an
 * icon rail on a wide screen instead — same switch, different shape of
 * collapse per breakpoint. #sidebar, .topbar and .view already read
 * --sidebar-w and already transition on it, so overriding the variable here
 * is all three following it in sync needs. */
@media (min-width: 993px) {
  body.sidebar-toggled {
    --sidebar-w: var(--sidebar-w-collapsed);
  }

  body.sidebar-toggled .nav-link span,
  body.sidebar-toggled .sidebar-footer,
  body.sidebar-toggled .logo-text,
  body.sidebar-toggled .logo-sub {
    display: none;
  }

  body.sidebar-toggled .nav-link {
    justify-content: center;
  }

  body.sidebar-toggled .brand-logo {
    padding-inline: 0.5rem;
  }
}

/* Holds the open conversation's AI switch, filled by renderThread() and
   cleared by route() on every navigation. `display: contents` so an empty
   slot (no conversation open) takes up no space and claims none of the
   topbar's flex `gap` either — only a filled slot becomes a real flex item. */
#topbar-ai-slot {
  display: contents;
}

.who {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-inline-start: 0.5rem;
}

.who-name {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--heading);
  line-height: 1.2;
}

.who-role {
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.2;
}

/* ---------------------------------------------------------------- avatars */

.avatar {
  inline-size: 38px;
  block-size: 38px;
  border-radius: 50%;
  background: var(--primary-soft);
  color: var(--primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8125rem;
  font-weight: 600;
  flex-shrink: 0;
  position: relative;
}

.avatar.sm {
  inline-size: 30px;
  block-size: 30px;
  font-size: 0.6875rem;
}

.avatar .bx {
  font-size: 1.25rem;
}

/* Avatar tiles still vary down a list, so two rows in the inbox are not
   mistaken for each other — but they vary inside the company's own colour
   instead of across a rainbow of unrelated hues. Three looks (soft, deeper,
   solid) carried by the six classes the initials hash into: every one is
   derived from --primary, so setting the brand colour in settings repaints
   all of them, and each stays legible in both themes. */
.avatar.c1,
.avatar.c4 {
  background: color-mix(in srgb, var(--primary) 14%, transparent);
  color: var(--primary);
}

.avatar.c2,
.avatar.c5 {
  background: color-mix(in srgb, var(--primary) 28%, transparent);
  color: var(--primary);
}

.avatar.c3,
.avatar.c6 {
  background: var(--primary);
  color: #fff;
}

.avatar.online::after,
.avatar.busy::after {
  content: "";
  position: absolute;
  inset-block-end: 0;
  inset-inline-end: 0;
  inline-size: 10px;
  block-size: 10px;
  border-radius: 50%;
  border: 2px solid var(--card);
}

.avatar.online::after {
  background: var(--success);
}

.avatar.busy::after {
  background: var(--secondary);
}

/* Sneat's signature tinted icon tile — a rounded square holding a line icon. */
.icon-tile {
  inline-size: 42px;
  block-size: 42px;
  border-radius: var(--r);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.icon-tile .bx {
  font-size: 1.5rem;
}

.icon-tile.primary { background: var(--primary-soft); color: var(--primary); }
.icon-tile.success { background: var(--success-soft); color: #4fa720; }
.icon-tile.info { background: var(--info-soft); color: #039fc0; }
.icon-tile.warning { background: var(--warning-soft); color: #cc8900; }
.icon-tile.danger { background: var(--danger-soft); color: var(--danger); }
.icon-tile.secondary { background: var(--secondary-soft); color: var(--secondary); }

/* ----------------------------------------------------------------- badges */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.181rem 0.5rem;
  border-radius: var(--r-sm);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.3;
  background: var(--secondary-soft);
  color: var(--secondary);
  white-space: nowrap;
}

.badge.on,
.badge.success { background: var(--success-soft); color: #4fa720; }
.badge.off { background: var(--secondary-soft); color: var(--secondary); }
.badge.urgent { background: var(--danger-soft); color: var(--danger); }
.badge.high { background: var(--warning-soft); color: #cc8900; }
.badge.normal { background: var(--info-soft); color: #039fc0; }
.badge.low { background: var(--secondary-soft); color: var(--secondary); }
.badge.primary { background: var(--primary-soft); color: var(--primary); }

/* ------------------------------------------------------------ page layout */

.view {
  margin-inline-start: var(--sidebar-w);
  padding: calc(var(--topbar-h) + 1.5rem) 1.5rem 1.5rem;
  min-block-size: 100dvh;
  transition: margin-inline-start 0.25s ease;
}

/* The inbox is a full-bleed two-pane workspace: it runs flush to the topbar
 * and the window edges, unlike the padded card pages (dashboard, settings). */
.view:has(> .split) {
  padding: var(--topbar-h) 0 0;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.page-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.page-head p {
  margin: 0.15rem 0 0;
  font-size: 0.8125rem;
  color: var(--muted);
}

.two-col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 1.5rem;
  align-items: start;
}

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}

.filters select,
.filters input[type="search"],
.filters input[type="date"] {
  inline-size: auto;
  min-inline-size: 170px;
}

.filters > label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  font-size: 0.875rem;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}

/* The inbox status/assignment filters, as two small icon groups instead of
 * four standalone checkboxes in a line. */
.filter-row {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  flex-wrap: wrap;
}

.filter-group {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.filter-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.4rem;
  border: none;
  background: transparent;
  border-radius: var(--r-sm);
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 0.15s ease, background 0.15s ease;
}

.filter-toggle:hover {
  opacity: 0.8;
  background: var(--hover);
}

.filter-toggle.on {
  opacity: 1;
  background: var(--primary-soft);
}

/* "Export all" sits apart from the toggles above — it isn't a filter, it's a
 * one-off download, so it gets its own spot rather than joining their row. */
.filter-export {
  align-self: flex-start;
}

.filter-card {
  background: var(--card);
  border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  padding: 1.25rem 1.5rem;
}

.filter-card h2 {
  margin-block-end: 1rem;
}

/* Tickets page: the KPI strip and the filter bar used to be two separate
 * full-width rows. Minimizing the KPI cards lets both share one row instead. */
.ticket-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: stretch;
}

.ticket-toolbar .stats {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: nowrap;
  gap: 0.5rem;
}

.ticket-toolbar .stat {
  padding: 0.5rem 0.7rem;
  gap: 0.5rem;
}

.ticket-toolbar .stat .value {
  font-size: 1.0625rem;
}

.ticket-toolbar .stat .name {
  font-size: 0.6875rem;
  white-space: nowrap;
}

.ticket-toolbar .icon-tile {
  inline-size: 28px;
  block-size: 28px;
}

.ticket-toolbar .icon-tile .bx {
  font-size: 1rem;
}

.ticket-toolbar .filter-card {
  flex: 1 1 220px;
  display: flex;
  align-items: center;
  padding: 0.6rem 0.85rem;
}

.ticket-toolbar .filter-card h2 {
  display: none;
}

.ticket-toolbar .filter-card .filters {
  flex: 1;
  flex-wrap: nowrap;
}

.ticket-toolbar .filter-card .filters select,
.ticket-toolbar .filter-card .filters input[type="search"] {
  min-inline-size: 0;
}

.ticket-toolbar .filter-card .filters input[type="search"] {
  flex: 1 1 120px;
}

/* Customers page: title/actions row, then a pill rail (segments + counts)
 * beside one compound search/activity bar, all in a single card. */
.customers-toolbar {
  background: var(--card);
  border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  padding: 1.25rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.customers-toolbar .row-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
}

.customers-toolbar .row-top-actions {
  display: flex;
  gap: 0.625rem;
}

.customers-toolbar .row-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.875rem;
  flex-wrap: wrap;
}

.customers-toolbar .pill-group {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.customers-toolbar .pill-group .chips {
  display: contents;
}

.chip-static {
  cursor: default;
}

.search-compound {
  display: flex;
  align-items: center;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
}

.search-compound > *:not(.divider) {
  padding: 0.625rem 0.875rem;
  font-size: 0.8125rem;
  white-space: nowrap;
}

.search-compound .divider {
  align-self: stretch;
  flex: 0 0 1px;
  width: 1px;
  background: var(--border);
}

.search-compound input[type="search"] {
  border: none;
  background: transparent;
  min-inline-size: 220px;
  box-shadow: none;
}

.search-compound input[type="search"]:focus {
  outline: none;
  box-shadow: none;
}

.search-compound select {
  border: none;
  background: transparent;
  color: var(--text);
  inline-size: auto;
  -webkit-appearance: none;
  appearance: none;
}

.search-compound select:focus {
  outline: none;
  box-shadow: none;
}

.search-compound .new-list-action {
  border: none;
  background: transparent;
  color: var(--primary);
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.empty {
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--muted);
  font-size: 0.9375rem;
}

.empty .illus {
  display: block;
  inline-size: min(440px, 85%);
  margin-block-end: 1rem;
  margin-inline: auto;
}

.empty .bx {
  display: block;
  font-size: 3.5rem;
  margin-block-end: 0.75rem;
  color: var(--primary);
  opacity: 0.45;
}

/* ----------------------------------------------------------------- tables */

.table-wrap {
  overflow-x: auto;
  border-radius: var(--r);
}

table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

thead th {
  text-align: start;
  padding: 0.75rem 1.25rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.4px;
  color: var(--muted);
  background: var(--hover);
  border-block-end: 1px solid var(--border);
  white-space: nowrap;
}

tbody td {
  padding: 0.8rem 1.25rem;
  border-block-end: 1px solid var(--border);
  color: var(--text);
  vertical-align: middle;
}

tbody tr:last-child td {
  border-block-end: none;
}

table.clickable tbody tr {
  cursor: pointer;
  transition: background-color 0.15s ease;
}

table.clickable tbody tr:hover {
  background: var(--hover);
}

.truncate {
  max-inline-size: 300px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.overdue {
  color: var(--danger);
  font-weight: 600;
}

.cell-main {
  display: flex;
  align-items: center;
  gap: 0.65rem;
}

.cell-title {
  font-weight: 600;
  color: var(--heading);
  line-height: 1.3;
}

.cell-sub {
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.3;
}

/* ------------------------------------------------------- stats / KPI rows */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: 0.75rem;
}

.stat {
  background: var(--card);
  border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  padding: 0.75rem 1rem;
  display: flex;
  align-items: center;
  gap: 0.65rem;
}

.stat .icon-tile {
  inline-size: 32px;
  block-size: 32px;
}

.stat .icon-tile .bx {
  font-size: 1.125rem;
}

.stat .value {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--heading);
  line-height: 1.25;
}

.stat .name {
  font-size: 0.75rem;
  color: var(--muted);
}

.stat-body {
  min-inline-size: 0;
}

/* Corner layout: icon top-end, number top-start, label along the bottom. */
.stats-corner .stat {
  position: relative;
  display: block;
  padding-inline-end: 3.25rem;
}

.stats-corner .stat .icon-tile {
  position: absolute;
  top: 0.75rem;
  inset-inline-end: 1rem;
}

.stats-corner .stat-body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

/* ------------------------------------------------- progress bars & gauges */

.bar {
  block-size: 6px;
  border-radius: var(--r-pill);
  background: var(--secondary-soft);
  overflow: hidden;
  min-inline-size: 90px;
}

.bar > span {
  display: block;
  block-size: 100%;
  border-radius: var(--r-pill);
  background: var(--primary);
}

.bar-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.bar-row .bar {
  flex: 1;
}

.bar-row .pct {
  font-size: 0.8125rem;
  color: var(--muted);
  min-inline-size: 3.2rem;
  text-align: end;
}

.gauge-row {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
}

/* Radial gauge, the way Sneat shows a single headline percentage. */
.gauge {
  inline-size: 110px;
  block-size: 110px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  background: conic-gradient(var(--primary) var(--pct, 0%), var(--secondary-soft) 0);
}

.gauge::before {
  content: "";
  grid-area: 1 / 1;
  inline-size: 84px;
  block-size: 84px;
  border-radius: 50%;
  background: var(--card);
}

.gauge-label {
  grid-area: 1 / 1;
  text-align: center;
  z-index: 1;
}

.gauge-label .value {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--heading);
  line-height: 1.2;
}

.gauge-label .name {
  font-size: 0.75rem;
  color: var(--muted);
}

/* ------------------------------------------------------- inbox: two panes */

.split {
  display: grid;
  grid-template-columns: 340px 1fr;
  grid-template-rows: 100%;
  block-size: calc(100dvh - var(--topbar-h));
}

.pane {
  background: var(--card);
  display: flex;
  flex-direction: column;
  min-block-size: 0;
  overflow: hidden;
}

/* Flush panes need a seam: the gap used to separate them, a border does now. */
.split > .pane + .pane {
  border-inline-start: 1px solid var(--border);
}

.list-pane {
  padding: 0;
}

.list-pane .filters {
  padding: 1rem 1.25rem;
  border-block-end: 1px solid var(--border);
  gap: 0.5rem;
}

.list-pane .filters input[type="search"] {
  flex: 1 0 100%;
  min-inline-size: 0;
}

.list-pane .filters select {
  flex: 1;
  min-inline-size: 0;
}

.list-pane .filters > label {
  font-size: 0.8125rem;
}

.list-pane .empty {
  padding: 2rem 1rem;
}

.list-pane button.ghost {
  margin: 0.75rem 1.25rem 1.25rem;
  justify-content: center;
}

/* ------------------------------------- inbox: the customer panel (3rd pane) */
/* Docked, not floating: staff read it while they type, so it must not cover
   the thread. It is always in the grid and hidden by a class, which is why
   opening it costs no rebuild of the two panes beside it. */

.split.with-info {
  grid-template-columns: 340px 1fr 340px;
}

.split:not(.with-info) > .info-pane {
  display: none;
}

.info-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.85rem 1.25rem;
  border-block-end: 1px solid var(--border);
}

.info-head h2 {
  margin: 0;
  font-size: 1rem;
}

.info-head button.ghost {
  padding: 0.3rem 0.5rem;
}

.info-body {
  flex: 1;
  min-block-size: 0;
  overflow-y: auto;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.info-who {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.info-who-text {
  min-inline-size: 0;
}

/* The panel is narrower than any page, so the shared 300px cap would spill. */
.info-pane .truncate {
  max-inline-size: 100%;
}

/* Four tiles, two across. The page version is one per row at this width and
   would push the ticket history two screens down. */
.info-pane .stats {
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
}

.info-pane .stat {
  padding: 0.7rem;
  gap: 0.5rem;
  box-shadow: none;
  border: 1px solid var(--border);
}

.info-pane .stat .value {
  font-size: 1.125rem;
}

.info-pane .icon-tile {
  inline-size: 32px;
  block-size: 32px;
}

.info-pane .icon-tile .bx {
  font-size: 1.125rem;
}

.info-section {
  border-block-start: 1px solid var(--border);
  padding-block-start: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.info-section h3 {
  margin: 0 0 0.3rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: none;
}

.info-section > p {
  margin: 0.4rem 0 0;
}

.info-section .primary {
  inline-size: 100%;
  justify-content: center;
}

/* One past problem, as a tappable block rather than a table row: a table at
   340px would put the ticket number and its date on separate lines anyway. */
.info-ticket {
  display: block;
  text-decoration: none;
  color: inherit;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 0.6rem 0.7rem;
}

.info-ticket:hover {
  border-color: var(--primary);
  background: var(--primary-soft);
}

.info-ticket-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-block-end: 0.25rem;
}

.info-stamp {
  margin: 0.3rem 0 0;
  font-size: 0.75rem;
  color: var(--muted);
}

/* Lit while the panel is open, so the header says which state it is in. */
.thread-head .info-toggle.on {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}

.rows {
  overflow-y: auto;
  flex: 1;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.rows::-webkit-scrollbar {
  inline-size: 6px;
}

.rows::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 999px;
}

/* Flush, edge-to-edge rows — WhatsApp's own list has no card padding or
 * per-row rounding, just a flat tint on hover/selection and a divider inset
 * to start after the avatar (below, on .row-main). */
.row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.6rem 1rem;
  color: var(--text);
  transition: background-color 0.1s ease;
}

.row:hover {
  background: var(--hover);
  text-decoration: none;
}

.row.active,
.row.active:hover {
  background: var(--primary-soft);
}

.row .avatar {
  inline-size: 49px;
  block-size: 49px;
  font-size: 1rem;
}

.row-main {
  min-inline-size: 0;
  flex: 1;
  padding-block-end: 0.6rem;
  border-block-end: 1px solid var(--border);
}

.row:last-child .row-main {
  border-block-end: none;
}

.row-top {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.row-name {
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--heading);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-time {
  margin-inline-start: auto;
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
  flex-shrink: 0;
}

.row-preview-line {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.row-preview {
  flex: 1;
  min-inline-size: 0;
  font-size: 0.875rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Unread messages from this customer since the team last opened the thread
 * (shared read state, not per-agent — see 0026). Same pill as .nav-badge. */
.row-unread-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-inline-size: 1.5625rem;
  block-size: 1.5625rem;
  padding: 0 0.44rem;
  border-radius: 0.94rem;
  background: var(--danger);
  color: #fff;
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1;
  text-align: center;
}

.row-tags {
  display: flex;
  gap: 0.3rem;
  flex-wrap: wrap;
  margin-block-start: 0.3rem;
}

.row-tags:empty {
  display: none;
}

/* Compact icon-only status flags, replacing what used to be text badges
 * ("مغلقة", "المساعد متوقف"...) in the tight space of a conversation row. */
.row-flag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 22px;
  block-size: 22px;
  border-radius: 50%;
  background: var(--primary-soft);
  color: var(--primary);
  cursor: default;
}

.row-flag .bx {
  font-size: 0.875rem;
}

/* ------------------------------------------------------ inbox: the thread */

.thread-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.25rem;
  border-block-end: 1px solid var(--border);
  flex-wrap: wrap;
}

.thread-head select {
  inline-size: auto;
  min-inline-size: 150px;
}

.thread-who {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-inline-size: 0;
}

.thread-who .cell-title {
  font-size: 1rem;
}

.thread-who .cell-sub {
  font-size: 0.85rem;
}

/* Floats at the end of the row, beside the action icons, instead of stacked
 * under the name. */
.thread-status-badge {
  flex: none;
}

/* One tinted rounded box per group of related actions, instead of every
 * icon button sitting loose in the header at equal weight. */
.action-group {
  display: flex;
  align-items: center;
  gap: 0.1rem;
  padding: 0.2rem;
  flex: none;
}

.action-group .bx {
  font-size: calc(1.5625rem + var(--chat-icon-step, 0) * 1pt);
}

/* Every other chat-screen icon follows the same slider — header badges, the
 * composer toolbar, the send button — except the delivery tick, which has
 * its own always-on-top-of-text size further down and stays out of this. */
.thread-pane .bx:not(.msg-tick) {
  font-size: calc(1.25rem + var(--chat-icon-step, 0) * 1pt);
}

.thread-pane .png-icon {
  inline-size: calc(1.375rem + var(--chat-icon-step, 0) * 1pt);
  block-size: calc(1.375rem + var(--chat-icon-step, 0) * 1pt);
}

.action-group .icon-action:hover:not(:disabled) {
  background: var(--hover);
}

/* Delete is the one action in this header nothing else here can undo, so it
 * keeps its own colour rather than blending into the group around it. */
.icon-action-danger {
  color: var(--danger);
}

.icon-action-danger:hover:not(:disabled) {
  background: var(--danger-soft) !important;
}

/* The tag box, now a header item beside the identity stack instead of its
 * own full-width row underneath. */
.thread-head .label-bar {
  flex: 1;
  min-inline-size: 0;
  padding: 0;
  border-block-end: none;
  justify-content: flex-start;
}

/* The AI on/off control, living in the top bar (#topbar-ai-slot) rather than
   here — a label and .switch-glossy. */
.ai-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}

.ai-toggle-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--muted);
}

/* The customer's name opens their file. Nothing else in this header is a
   link, so it has to look like one or nobody finds the page. */
.thread-who a.cell-title {
  color: var(--primary);
}

.thread-who a.cell-title:hover {
  text-decoration: underline;
}

.messages {
  flex: 1;
  overflow-y: auto;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--bg);
}

.msg {
  max-inline-size: 68%;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* Inbound hugs the end edge, outbound the start — which under dir="rtl" puts
 * the customer on the left and staff/AI on the right. */
.msg.in {
  align-self: flex-end;
  align-items: flex-end;
}

.msg.out {
  align-self: flex-start;
  align-items: flex-start;
}

.msg-meta {
  font-size: calc(0.8125rem + var(--chat-text-step, 0) * 1pt);
  color: var(--muted);
  padding-inline: 0.25rem;
}

/* The time (+ AI icon, + delivery tick) sits inside the bubble, on its own
 * line under the text — end-aligned toward whichever physical side the
 * bubble itself hugs (right for staff/AI, left for the customer), always
 * the same side as the bubble's own tail, regardless of text direction. */
.msg-time {
  display: block;
  margin-block-start: 0.3rem;
  font-size: calc(0.6875rem + var(--chat-text-step, 0) * 1pt);
  white-space: nowrap;
  opacity: 0.7;
}

.msg.out .msg-time {
  text-align: right;
  color: rgba(255, 255, 255, 0.85);
}

.msg.in .msg-time {
  text-align: left;
}

.msg-day {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  align-self: center;
  display: flex;
  justify-content: center;
}

.msg-day span {
  background: var(--card);
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.3rem 0.9rem;
  border-radius: 999px;
  box-shadow: var(--shadow-sm);
}

.msg-body {
  position: relative;
  display: table;
  padding: 0.6rem 0.9rem;
  border-radius: var(--r-lg);
  font-size: calc(0.875rem + var(--chat-text-step, 0) * 1pt);
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  box-shadow: var(--shadow-sm);
}

.msg.in .msg-body {
  background: var(--card);
  color: var(--text);
  border-start-end-radius: 0;
}

.msg.out .msg-body {
  background: var(--primary);
  color: #fff;
  border-start-start-radius: 0;
  box-shadow: var(--shadow-primary);
}

.msg-link {
  color: #2563eb;
  text-decoration: underline;
  word-break: break-all;
}

.msg.out .msg-link {
  color: #bfdbfe;
}

/* The little pointed tail WhatsApp draws off the squared corner, on the
 * outer edge of the bubble (away from the other side's messages) — a solid
 * triangle in the bubble's own colour, sat right where the radius was
 * zeroed out above, so it reads as part of the bubble rather than a
 * separate shape. */
.msg.in .msg-body::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: -0.5rem;
  inline-size: 0;
  block-size: 0;
  border-block-start: 0.5rem solid var(--card);
  border-inline-end: 0.5rem solid transparent;
}

.msg.out .msg-body::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: -0.5rem;
  inline-size: 0;
  block-size: 0;
  border-block-start: 0.5rem solid var(--primary);
  border-inline-start: 0.5rem solid transparent;
}

.msg-ai-icon {
  inline-size: 0.8125rem;
  block-size: 0.8125rem;
  vertical-align: middle;
  margin-inline-end: 0.2rem;
}

.msg.system {
  align-self: center;
  max-inline-size: 80%;
  align-items: center;
}

.msg.system .msg-body {
  background: var(--secondary-soft);
  color: var(--secondary);
  font-size: 0.75rem;
  border-radius: var(--r-pill);
  box-shadow: none;
  text-align: center;
}

.msg-media {
  font-size: 0.8125rem;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding-inline: 0.25rem;
}

.msg.out .msg-media {
  color: #fff;
  text-decoration: underline;
}

/* A generic file (pdf, csv, doc, ...) drawn as WhatsApp's document card:
   coloured type icon, filename in full, extension underneath, save icon. */
.msg-doc {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  inline-size: min(280px, 100%);
  margin: 0 0 0.35rem;
  padding: 0.5rem 0.65rem;
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--shadow-sm);
  color: inherit;
  text-decoration: none;
}

.msg-doc:hover {
  text-decoration: none;
}

.msg.out .msg-doc {
  background: rgba(255, 255, 255, 0.94);
}

.msg-doc-icon {
  flex: none;
}

.msg-doc-body {
  flex: 1;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.msg-doc-name {
  font-size: 0.8125rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  unicode-bidi: plaintext;
}

.msg.out .msg-doc-name {
  color: var(--text);
}

.msg-doc-ext {
  font-size: 0.6875rem;
  color: var(--muted);
}

.msg.out .msg-doc-ext {
  color: var(--muted);
}

.msg-doc-dl {
  flex: none;
  display: grid;
  place-items: center;
  font-size: 1.0625rem;
  color: var(--muted);
}

.msg.out .msg-doc-dl {
  color: var(--muted);
}

/* An attachment shown as itself: a photo you can see, a voice note you can
   play. Capped rather than full width — a portrait photo from a phone would
   otherwise be taller than the thread it is in. */
.msg-file {
  margin: 0 0 0.35rem;
  inline-size: min(320px, 100%);
  background: var(--card);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.msg-image {
  display: block;
  line-height: 0;
}

.msg-image img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  max-block-size: 340px;
  object-fit: cover;
}

.msg-video {
  display: block;
  inline-size: 100%;
  max-block-size: 340px;
  background: #14141d;
}

/* A voice note in WhatsApp's own layout — avatar with a mic badge, round play
   button, waveform, running time — drawn in this dashboard's colours rather
   than WhatsApp's green. The browser's grey audio player is still underneath
   doing the playing; only its face is replaced. */
.voice {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  inline-size: min(300px, 100%);
  margin-block-end: 0.35rem;
  padding: 0.4rem 0.55rem;
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--shadow-sm);
}

.msg.out .voice {
  background: var(--primary);
  box-shadow: var(--shadow-primary);
}

.voice-avatar {
  position: relative;
  flex: none;
  inline-size: 38px;
  block-size: 38px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 1.25rem;
  background: var(--primary-soft);
  color: var(--primary);
}

/* The little microphone WhatsApp tucks against the avatar — the one mark that
   says "this is a recording" before anything is played. */
.voice-mic {
  position: absolute;
  inset-block-end: -1px;
  inset-inline-end: -1px;
  padding: 1px;
  border-radius: 50%;
  font-size: 0.75rem;
  background: var(--card);
  color: var(--primary);
}

.voice-play {
  flex: none;
  inline-size: 32px;
  block-size: 32px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  font-size: 1.125rem;
  cursor: pointer;
  background: var(--primary-soft);
  color: var(--primary);
}

.voice-body {
  flex: 1;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.voice-wave {
  display: flex;
  align-items: center;
  gap: 2px;
  block-size: 24px;
  cursor: pointer;
}

.voice-wave span {
  flex: 1;
  min-inline-size: 2px;
  border-radius: var(--r-pill);
  background: var(--border);
}

.voice-wave span.played {
  background: var(--primary);
}

.voice-time {
  align-self: flex-start;
  font-size: 0.6875rem;
  color: var(--muted);
}

/* On the staff side the bubble is already violet, so the same parts have to
   read against it in white instead. */
.msg.out .voice-avatar,
.msg.out .voice-play {
  background: rgba(255, 255, 255, 0.22);
  color: #fff;
}

.msg.out .voice-mic {
  background: var(--primary);
  color: #fff;
}

.msg.out .voice-wave span {
  background: rgba(255, 255, 255, 0.42);
}

.msg.out .voice-wave span.played {
  background: #fff;
}

.msg.out .voice-time {
  color: rgba(255, 255, 255, 0.85);
}

/* What the file is, what it is called, and a way out to it. */
.msg-file-foot {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.6rem;
  font-size: 0.75rem;
  color: var(--muted);
  border-block-start: 1px solid var(--border);
}

.msg-file-foot .bx {
  font-size: 0.9375rem;
  flex-shrink: 0;
}

/* plaintext, not a fixed direction: this line holds either an Arabic label
   ("رسالة صوتية") or a latin filename, and each needs its own. */
.msg-file-name {
  flex: 1;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  unicode-bidi: plaintext;
}

.msg-file-save {
  display: inline-flex;
  color: var(--muted);
  flex-shrink: 0;
}

.msg-file-save:hover {
  color: var(--primary);
}

/* A media attachment opens over the thread instead of a new browser tab —
 * same backdrop/card recipe as .modal-backdrop, sized for a photo or video
 * rather than a form. */
.lightbox-backdrop {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  background: rgba(15, 15, 20, 0.82);
  overflow-y: auto;
}

.lightbox-card {
  inline-size: min(880px, 100%);
  max-block-size: min(90vh, 92dvh);
  margin: auto;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
}

.lightbox-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.75rem 1rem;
  border-block-end: 1px solid var(--border);
}

.lightbox-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  unicode-bidi: plaintext;
}

.lightbox-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  inline-size: 2.1rem;
  block-size: 2.1rem;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  text-decoration: none;
}

.lightbox-icon-btn:hover {
  background: var(--hover);
  color: var(--primary);
}

.lightbox-body {
  flex: 1;
  display: grid;
  place-items: center;
  background: #0b0b0f;
  min-block-size: 40vh;
  overflow: auto;
}

.lightbox-body img,
.lightbox-body video {
  max-inline-size: 100%;
  max-block-size: 80vh;
  display: block;
}

.lightbox-body iframe {
  inline-size: 100%;
  block-size: 80vh;
  border: 0;
  background: #fff;
}

.lightbox-doc-fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  padding: 3rem 1.5rem;
  color: #cfcfd6;
  text-align: center;
}

.lightbox-doc-fallback .bx {
  font-size: 3rem;
}

.lightbox-download {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1.25rem;
  border-radius: var(--r-sm);
  text-decoration: none;
  font-weight: 600;
}

/* ------------------------------------------------------ inbox: the composer */

.composer {
  padding: 1rem 1.25rem;
  border-block-start: 1px solid var(--border);
}

.composer-box {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 0.5rem 0.6rem 0.5rem 0.75rem;
  background: var(--card);
  transition: border-color 0.15s ease;
}

.composer-box:focus-within {
  border-color: var(--primary);
}

.composer-box textarea {
  border: none;
  padding: 0.25rem 0;
  max-block-size: 140px;
}

.composer-box textarea:focus {
  box-shadow: none;
}

/* The reply-window / note hint, sharing the rail's row alongside the icons,
 * trailing behind the send button — all one line. */
.composer-box .muted {
  font-size: 0.75rem;
  line-height: 1.4;
  flex: 1;
  min-inline-size: 0;
}

/* The bottom rail inside the composer frame: send button on the leading
 * edge (far right under RTL), hint text and tool icons trailing. */
.composer-rail {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.5rem;
}

.composer-rail .primary.icon-btn {
  inline-size: 3.375rem;
  block-size: 3.375rem;
  padding: 0;
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.composer-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-block-start: 0.6rem;
}

.composer-actions .muted {
  font-size: 0.75rem;
  flex: 1;
}

/* ------------------------------------------------------------ ticket detail */

.detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: 1.1rem;
  margin-block-end: 1.5rem;
}

.field-label {
  font-size: 0.75rem;
  color: var(--muted);
  margin-block-end: 0.2rem;
}

.detail-grid > div > div:last-child {
  font-size: 0.875rem;
  color: var(--heading);
  font-weight: 600;
}

/* Timeline with Sneat's coloured dot markers running down the start edge. */
.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  padding-inline-start: 1.35rem;
  border-inline-start: 1px solid var(--border);
}

.timeline li {
  position: relative;
  padding-block-end: 1.35rem;
}

.timeline li:last-child {
  padding-block-end: 0;
}

.timeline li::before {
  content: "";
  position: absolute;
  inset-inline-start: calc(-1.35rem - 5px);
  inset-block-start: 0.45rem;
  inline-size: 9px;
  block-size: 9px;
  border-radius: 50%;
  background: var(--primary);
  box-shadow: 0 0 0 3px rgba(105, 108, 255, 0.16);
}

.timeline strong {
  color: var(--heading);
  font-weight: 600;
  font-size: 0.875rem;
}

.timeline .when {
  font-size: 0.75rem;
  color: var(--muted);
  margin-block-start: 0.15rem;
}

.form-grid {
  display: grid;
  gap: 1rem;
}

/* ---------------------------------------------------------- knowledge base */

.kb-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
  gap: 1.5rem;
  align-items: start;
}

.kb-item {
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  transition: box-shadow 0.15s ease;
}

.kb-item:hover {
  box-shadow: var(--shadow-lg);
}

.kb-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.kb-head strong {
  color: var(--heading);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.4;
}

.kb-head button {
  padding: 0.3rem 0.6rem;
  font-size: 0.75rem;
}

/* Collapsed by default; the body is the click target that opens it. */
.kb-body {
  font-size: 0.875rem;
  line-height: 1.7;
  color: var(--text);
  white-space: pre-wrap;
  cursor: pointer;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.kb-item.open .kb-body {
  -webkit-line-clamp: unset;
  line-clamp: unset;
  overflow: visible;
}

.campaign-preview {
  font-size: 0.875rem;
  line-height: 1.7;
  color: var(--text);
  white-space: pre-wrap;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin-block-start: auto;
}

/* ------------------------------------------------------------------ login */

/* The illustration lives inside the card, full width and fully visible, with
 * the fields stacked below it — one self-contained panel rather than a
 * backdrop behind floating fields. */
.login-wrap {
  position: relative;
  min-block-size: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2.5rem 1.5rem;
  background: var(--bg);
  overflow: hidden;
}

.login-backdrop {
  inline-size: calc(100% + 4rem);
  margin-inline: -2rem;
  block-size: auto;
  max-block-size: 220px;
  object-fit: cover;
  display: block;
  margin-block-end: 0.5rem;
  pointer-events: none;
  user-select: none;
}

.login {
  position: relative;
  z-index: 1;
  inline-size: min(100%, 440px);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.35rem;
  text-align: center;
  padding: 2rem 2rem 2.5rem;
  border-radius: 1.25rem;
  background: var(--card);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.1);
}

.login-head {
  margin-block-end: 2rem;
}

.login-mark {
  inline-size: 40px;
  block-size: 40px;
  object-fit: contain;
  margin-block-end: 0.75rem;
}

.login-head h1 {
  font-size: 1.75rem;
  font-weight: 800;
  color: var(--heading);
}

.login-head p {
  margin: 0.35rem 0 0;
  font-size: 0.9375rem;
}

.login label {
  margin-block-start: 1rem;
  font-size: 0.8125rem;
  text-align: start;
}

/* The field's visible surface moves to this wrapper — a pill holding a muted
 * leading icon and a borderless input — so the input itself needs no border
 * of its own to read clearly against the page. */
.login .field-box {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-inline: 1rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-sm);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.login .field-box:focus-within {
  border-color: var(--primary);
  box-shadow: 0 0 0 0.2rem var(--primary-soft);
}

.login .field-box .bx {
  flex: none;
  font-size: 1.125rem;
  color: var(--muted);
}

.login .field-box input {
  flex: 1;
  min-inline-size: 0;
  border: none;
  background: transparent;
  padding: 0.75rem 0;
}

.login .field-box input:focus {
  box-shadow: none;
}

.toggle-visibility {
  flex: none;
  display: flex;
  padding: 0;
  background: none;
  border: none;
  color: var(--muted);
}

.toggle-visibility:hover {
  color: var(--heading);
}

.login button.primary {
  margin-block-start: 1.5rem;
  justify-content: center;
  padding-block: 0.75rem;
  font-size: 0.9375rem;
  border-radius: var(--r-pill);
}

.error {
  color: var(--danger);
  font-size: 0.8125rem;
  margin: 0.5rem 0 0;
  min-block-size: 1.2rem;
}

/* ------------------------------------------------------------------ toast */

.toast {
  position: fixed;
  inset-block-end: 1.5rem;
  inset-inline-start: 1.5rem;
  background: var(--card);
  color: var(--text);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  padding: 0.85rem 1.15rem;
  font-size: 0.875rem;
  z-index: 100;
  max-inline-size: 360px;
  border-inline-start: 3px solid var(--danger);
  animation: toast-in 0.2s ease;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

button.icon-btn.danger {
  color: var(--danger);
}

button.icon-btn.danger:hover {
  background: var(--danger-soft);
  color: var(--danger);
}

/* In dark mode the card colour sits too close to the chat background for a
 * received bubble to read, and the shadow that separates them in light mode
 * is invisible. Lift the bubble instead of tinting the whole pane. */
.dark .msg.in .msg-body {
  background: var(--hover);
}

.dark .msg.in .msg-body::before {
  border-block-start-color: var(--hover);
}

/* --- The account link in the top bar -------------------------------------
 * The name was already the obvious place to click for your own account; this
 * only makes it behave that way. Inherits its colours so nothing about the
 * top bar changes visually until it is hovered. */
.who-link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: inherit;
  text-decoration: none;
  border-radius: var(--r-lg);
  padding: 0.25rem 0.4rem;
  transition: background 0.15s ease;
}
.who-link:hover {
  background: var(--hover);
  text-decoration: none;
}

/* A link that needs to read as a button — used where a card offers a way out
 * to another screen rather than an action of its own. */
.button-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  text-decoration: none;
}

/* --- Saved phrases and templates above the reply box ---------------------
 * Sits over the composer, opens on demand and takes no room until it does:
 * most replies are typed, and a permanently open list would push the
 * conversation off the screen for the sake of a control used now and then. */
.picker {
  margin-block-end: 0.6rem;
}
.picker-panel {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-block-start: 0.6rem;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--bg);
  max-block-size: 40vh;
  overflow-y: auto;
}
.picker-link {
  margin-inline-start: auto;
  font-size: 0.75rem;
}
.picker-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  inline-size: 100%;
}
/* The template's own wording, shown before it is sent. Placeholders are left
 * as {{1}} on purpose so it is obvious which parts the boxes below fill in. */
.picker-preview {
  flex-basis: 100%;
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.7;
  white-space: pre-wrap;
}

/* ------------------------------------------------------------ ticket board */
/* One column per ticket status, scrolling sideways when they do not all fit.
 * Cards are dragged between columns — see ticketBoard() in app.js. */
.board {
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  align-items: flex-start;
  padding-block-end: 0.5rem;
}

.board-col {
  flex: 0 0 16rem;
  background: var(--hover);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 0.75rem;
}

/* The column the card would land in, while a drag is over it. */
.board-col.drop-target {
  border-color: var(--primary);
  background: var(--primary-soft);
}

.board-col-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-block-end: 0.75rem;
  font-weight: 600;
  color: var(--heading);
}

.board-count {
  background: var(--card);
  border-radius: var(--r-pill);
  padding: 0.05rem 0.5rem;
  font-size: 0.8125rem;
  color: var(--muted);
}

/* Keeps a height when empty, so an empty column is still something you can
 * drop onto rather than a one-line strip. */
.board-cards {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-block-size: 4rem;
}

.board-card {
  background: var(--card);
  border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  padding: 0.625rem 0.75rem;
  cursor: grab;
  /* Up and down stays the browser's, so a finger can still scroll the page
   * from anywhere on the board. Sideways comes to us, which is the direction
   * a card travels — the columns sit side by side. */
  touch-action: pan-y;
  /* A mouse drag would otherwise select the card's text as it goes. Safari
   * still needs the prefixed property for this even where it accepts the
   * standard one. */
  -webkit-user-select: none;
  user-select: none;
}

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

/* Keyboard focus. There is no keyboard way to move a card; this is so the
 * ticket can still be opened, and its status changed on its own screen. */
.board-card:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* While it follows the pointer: lifted above the other cards, and transparent
 * to the pointer so the column underneath is what gets found. */
.board-card.dragging {
  cursor: grabbing;
  position: relative;
  z-index: 5;
  pointer-events: none;
  box-shadow: var(--shadow-lg);
}

.board-card-top {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-block-end: 0.375rem;
}

/* Collapsed by default (see ticketCard in app.js) so a full column reads as
 * a stack of titles, not a stack of paragraphs. */
.board-card-toggle {
  background: transparent;
  border: none;
  padding: 0.2rem;
  border-radius: var(--r-pill);
  color: var(--muted);
  cursor: pointer;
}

.board-card-toggle:hover {
  background: var(--primary-soft);
  color: var(--primary);
}

.board-card-toggle i {
  display: inline-block;
  transition: transform 0.15s ease;
}

.board-card-toggle.open i {
  transform: rotate(180deg);
}

.board-card-body {
  margin-block-start: 0.375rem;
}

.board-card-foot {
  margin-block-start: 0.375rem;
  font-size: 0.75rem;
  color: var(--muted);
}

.board-empty {
  margin: 0;
  padding-block: 0.75rem;
  text-align: center;
  font-size: 0.8125rem;
  color: var(--muted);
}

/* ------------------------------------------------------- customer segments */
/* A rail of named lists above the customer table. Each is a rule evaluated
 * when it is opened, so the count on a chip is what is true right now. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--body);
  font-size: 0.8125rem;
  cursor: pointer;
}

.chip:hover {
  border-color: var(--primary);
  color: var(--primary);
}

.chip.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

.chip-count {
  font-size: 0.6875rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--primary-soft);
  color: var(--primary);
}

.chip.active .chip-count {
  background: rgba(255, 255, 255, 0.24);
  color: #fff;
}

.chip-icon {
  display: inline-flex;
  opacity: 0.75;
}

.chip-icon:hover {
  opacity: 1;
}

.chip-new {
  border-style: dashed;
  color: var(--muted);
}

.chip-new:hover {
  color: var(--primary);
}

/* ------------------------------------------------------------ file library */
.media-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr));
  gap: 1rem;
}

.media-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--card);
  text-align: start;
  font: inherit;
  color: inherit;
}

button.media-tile {
  cursor: pointer;
}

button.media-tile:hover {
  border-color: var(--primary);
}

.media-thumb {
  inline-size: 100%;
  block-size: 96px;
  object-fit: cover;
  border-radius: var(--r-lg);
  background: var(--bg);
}

/* Non-images have no thumbnail to show, so the type glyph fills the same box
 * — a grid where some tiles are short and some are tall reads as broken. */
.media-thumb-icon {
  display: grid;
  place-items: center;
  font-size: 2rem;
  color: var(--muted);
}

.media-name {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--heading);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.media-meta {
  font-size: 0.75rem;
  color: var(--muted);
}

.media-delete {
  position: absolute;
  inset-block-start: 0.4rem;
  inset-inline-end: 0.4rem;
  background: var(--card);
}

/* The logo shown under the URL box on the settings screen. */
.logo-preview {
  max-block-size: 64px;
  max-inline-size: 220px;
  object-fit: contain;
  margin-block: 0.5rem;
}

.logo-row {
  display: flex;
  gap: 0.6rem;
  align-items: center;
}

.logo-row input {
  flex: 1;
}

/* The file chosen for a reply, between the tools and the text box. */
.attachment {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-block-end: 0.6rem;
  padding: 0.45rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--bg);
  font-size: 0.8125rem;
}

.composer-tools {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  flex-wrap: wrap;
}

.composer-tools .picker {
  margin-block-end: 0;
}

/* ------------------------------------------------------------------ modals */
/* Used by the file picker and the segment editor. Both are short, occasional
 * dialogs — a whole route for either would put a browser Back button in the
 * middle of choosing a photo. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  background: rgba(0, 0, 0, 0.45);
  overflow-y: auto;
}

.modal-card {
  inline-size: min(680px, 100%);
  max-block-size: min(85vh, 90dvh);
  overflow-y: auto;
  margin: auto;
}

/* ------------------------------------------------- inbox: emoji, drag, record */

.emoji-panel {
  gap: 0.15rem;
  max-block-size: 12rem;
}

.emoji-panel .emoji {
  border: 0;
  background: none;
  cursor: pointer;
  font-size: 1.125rem;
  line-height: 1;
  padding: 0.25rem;
  border-radius: var(--r-sm);
}

.emoji-panel .emoji:hover {
  background: var(--secondary-soft);
}

/* The whole reply area is the drop target, so there is no small rectangle to
   aim at with a file already in hand. */
.composer.dropping {
  outline: 2px dashed var(--primary);
  outline-offset: -4px;
  background: var(--primary-soft);
}

.composer-tools .recording {
  color: var(--danger, #d9534f);
  background: color-mix(in srgb, var(--danger, #d9534f) 14%, transparent);
}

/* ------------------------------------------------------------------- bell */
/* The alert count in the top bar, and the panel behind it. Reuses
 * .picker-panel for the box itself — same border, radius and scroll cap as
 * the saved-phrase list — and only adds what a dropdown needs on top. */
.bell-wrap {
  position: relative;
  display: flex;
}

.bell-count {
  position: absolute;
  inset-block-start: -0.15rem;
  inset-inline-end: -0.15rem;
  min-inline-size: 1.05rem;
  padding-inline: 0.25rem;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 0.6875rem;
  line-height: 1.05rem;
  text-align: center;
  font-weight: 600;
}

.bell-panel {
  position: absolute;
  inset-block-start: calc(100% + 0.5rem);
  inset-inline-end: 0;
  inline-size: min(22rem, calc(100vw - 3rem));
  flex-direction: column;
  align-items: stretch;
  background: var(--card);
  box-shadow: var(--shadow);
  overflow-x: hidden;
  z-index: 30;
}

.bell-list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  inline-size: 100%;
}

.bell-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem;
  border-radius: var(--r);
  color: inherit;
  text-decoration: none;
  inline-size: 100%;
}

.bell-item > div {
  min-inline-size: 0;
}

.bell-item .cell-title {
  overflow-wrap: break-word;
}

.bell-item:hover {
  background: var(--hover);
}

/* The inbox rows, wrapped so the live poll can repaint them without touching
 * the filter bar above — see renderConversationList(). */
.rows-body {
  display: contents;
}

/* ------------------------------------------------- delivery ticks / failed */
/* The same three states WhatsApp shows, in the same order and with the same
 * meaning. A refused message is deliberately NOT a fourth, quieter tick — it
 * gets the strip below, because the failure this closes is staff believing a
 * customer was answered when they were not. */
.msg-tick {
  font-size: 1rem;
  vertical-align: middle;
  margin-inline-start: 0.25rem;
  opacity: 0.75;
}

.msg-tick.read {
  color: var(--info);
  opacity: 1;
}

.msg.failed .msg-body {
  opacity: 0.6;
}

.msg-failed {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-wrap: wrap;
  margin-block-start: 0.4rem;
  padding: 0.4rem 0.6rem;
  border-radius: var(--r);
  background: var(--danger-soft);
  color: var(--danger);
  font-size: 0.8125rem;
}

.msg-failed button.ghost {
  margin-inline-start: auto;
  padding: 0.2rem 0.55rem;
  font-size: 0.75rem;
}

/* --------------------------------------------------------------- team page */
/* A switched-off account stays in the table — its history is the team's
 * record — but should not read like a colleague you can hand work to. */
tr.row-off {
  opacity: 0.55;
}

.row-actions {
  display: flex;
  gap: 0.25rem;
  justify-content: flex-end;
  white-space: nowrap;
}

/* ------------------------------------------- private notes, labels, closing */
/* A note is an aside between colleagues. It is drawn unlike anything that was
 * sent, because the one mistake this screen must never invite is reading a
 * private note as a message the customer received. */
.msg.note {
  align-self: center;
  inline-size: min(100%, 34rem);
  background: var(--warning-soft);
  border: 1px dashed var(--warning);
  color: inherit;
}

.msg.note .msg-meta {
  color: #cc8900;
}

/* Two icon toggles over one reply box: write to the customer, or write for
 * the team. Icon-only, sitting in the composer's tool rail — .icon-action
 * supplies the shape and hover, this just adds the active/current state. */
.composer-tabs {
  display: flex;
  gap: 0.15rem;
}

/* Only when the tabs stand alone above the box — no reply window open, so
 * there is no tool rail to sit inside — do they need their own spacing. */
.composer > .composer-tabs {
  margin-block-end: 0.5rem;
}

.composer-tab.active {
  background: var(--primary-soft);
  color: var(--primary);
}

/* The whole box takes the note colour while a note is being written, so the
 * state is obvious from across the room rather than only from the tab. */
.composer.noting .composer-box textarea {
  background: var(--warning-soft);
}

.label-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 1.25rem;
  border-block-end: 1px solid var(--border);
  color: var(--muted);
}

.label-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

.label-remove {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: inherit;
  display: inline-flex;
  font-size: 0.875rem;
}

.label-input {
  flex: 0 1 10rem;
  min-inline-size: 6rem;
  padding: 0.2rem 0.5rem;
  font-size: 0.8125rem;
}

.close-panel {
  margin: 0.75rem 1.25rem;
}
.close-panel select {
  flex: 0 1 12rem;
  min-inline-size: 0;
}

/* -------------------------------------------------- knowledge base drafts */
/* Entries read out of a document, before any of them exist. Nothing on this
 * screen is saved until Save is pressed, and it should not look like it is. */
.draft {
  border-inline-start: 3px solid var(--info);
}

.draft.dropped {
  opacity: 0.45;
}

.draft-keep {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8125rem;
  margin: 0;
}

/* The customer checklist on the campaign screen. Scrolls inside its own box:
   two hundred names must not push the send button off the page. */
.pick-list {
  gap: 0.5rem;
  max-height: 18rem;
  overflow-y: auto;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--r);
}

.pick-all {
  position: sticky;
  top: -0.75rem;
  margin: -0.75rem -0.75rem 0;
  padding: 0.75rem;
  background: var(--card);
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}

/* Campaign form fields sit two to a row: a name box and a dropdown do not
 * need half a monitor each. Collapses back to one column when the column is
 * too narrow to split. */
.form-grid.cols-2 {
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
}

/* --------------------------------------------------------------- motion */
/* One easing curve for everything that moves, and one duration scale. Values
 * live as tokens so a single edit changes the whole dashboard's feel rather
 * than 20 scattered transition lines. */

:root {
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --t-fast: 0.14s;
  --t: 0.22s;
}

/* Momentum scrolling inside the panes, and no scroll-chaining out of a list
 * into the page behind it. */
html {
  scroll-behavior: smooth;
}

.rows,
.messages,
.view,
.table-wrap {
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* Cards and clickable table rows get the same language, quieter. */
.card {
  transition: box-shadow var(--t) var(--ease), transform var(--t) var(--ease);
}

button.media-tile:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}

table.clickable tbody tr {
  transition: background-color var(--t-fast) var(--ease);
}

/* -- sidebar ---------------------------------------------------------- */
/* The active link's violet fill used to appear instantly. Sliding the whole
 * link a couple of pixels toward the content on hover reads as the row
 * leaning toward where it will take you. */

.nav-link {
  transition: background-color var(--t) var(--ease), color var(--t) var(--ease),
    transform var(--t) var(--ease), box-shadow var(--t) var(--ease);
}

.nav-link:hover:not(.active) {
  transform: translateX(-3px);
}

.nav-link .bx {
  transition: transform var(--t) var(--ease);
}

.nav-link:hover .bx {
  transform: scale(1.12);
}

.icon-btn .bx,
.avatar {
  transition: transform var(--t) var(--ease);
}

.icon-btn:hover .bx {
  transform: scale(1.12);
}

/* -- page transitions ------------------------------------------------- */
/* Every route paints into #view. Re-triggering this animation on each
 * navigation (route() removes and re-adds the class) makes the swap read as
 * a move rather than a flicker. The distance is deliberately small — 6px —
 * because a big slide costs more time than it buys in polish. */

@keyframes view-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

.view.entering > * {
  animation: view-in 0.28s var(--ease) both;
}

/* Cards inside a stack come in one after another, 40ms apart. Five steps is
 * enough for every page here; anything past the fifth child just uses the
 * last delay rather than trailing in late. */
.view.entering > .stack > *:nth-child(1) { animation-delay: 0s; }
.view.entering > .stack > *:nth-child(2) { animation-delay: 0.04s; }
.view.entering > .stack > *:nth-child(3) { animation-delay: 0.08s; }
.view.entering > .stack > *:nth-child(4) { animation-delay: 0.12s; }
.view.entering > .stack > *:nth-child(n + 5) { animation-delay: 0.16s; }

.view.entering > .stack > * {
  animation: view-in 0.28s var(--ease) both;
}

/* -- top progress bar -------------------------------------------------- */
/* A route that waits on the network gets a 2px bar under the topbar. It
 * creeps to 80% while waiting and snaps to 100% on arrival, so a slow page
 * never looks frozen and a fast one barely shows it. */

#progress {
  position: fixed;
  inset-block-start: var(--topbar-h);
  inset-inline-start: var(--sidebar-w);
  inset-inline-end: 0;
  block-size: 2px;
  z-index: 90;
  background: var(--primary);
  transform-origin: inline-start;
  transform: scaleX(0);
  opacity: 0;
  pointer-events: none;
  box-shadow: 0 0 8px var(--primary);
}

#progress.on {
  opacity: 1;
  transform: scaleX(0.8);
  transition: transform 1.6s cubic-bezier(0.1, 0.8, 0.2, 1);
}

#progress.done {
  opacity: 0;
  transform: scaleX(1);
  transition: transform 0.18s ease, opacity 0.3s ease 0.12s;
}

/* -- skeletons --------------------------------------------------------- */
/* Painted the instant a route changes, replaced when the data lands. The
 * shimmer is one background-position animation on a gradient — no extra
 * elements, no JS timer. */

@keyframes shimmer {
  to {
    background-position-x: -200%;
  }
}

.sk {
  border-radius: var(--r-sm);
  background: linear-gradient(
      90deg,
      var(--hover) 25%,
      color-mix(in srgb, var(--hover) 55%, var(--border)) 37%,
      var(--hover) 63%
    )
    0 0 / 200% 100%;
  animation: shimmer 1.4s linear infinite;
  block-size: 0.75rem;
  margin-block-end: 0.6rem;
}

.sk:last-child {
  margin-block-end: 0;
}

.sk-title {
  block-size: 1.5rem;
  inline-size: 220px;
  border-radius: var(--r);
  /* The real page heads sit a little below the topbar; without this the
   * placeholder title rides up against it and looks clipped. */
  margin-block-end: 0.25rem;
}

.sk-card {
  background: var(--card);
  border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  padding: 1.5rem;
}

.sk-row {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  padding: 0.7rem 0.85rem;
  border-block-end: 1px solid var(--border);
}

.sk-avatar {
  inline-size: 38px;
  block-size: 38px;
  border-radius: var(--r-pill);
  flex: none;
  margin: 0;
}

.sk-lines {
  flex: 1;
  min-inline-size: 0;
}

/* The open thread while its one request is in flight. Laid out like the real
 * thread underneath it — header pinned, bubbles filling the rest — so the
 * arriving conversation does not shift the pane when it replaces this. */
.sk-thread {
  display: flex;
  flex-direction: column;
  block-size: 100%;
  min-block-size: 0;
}

.sk-thread-head {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  padding: 0.9rem 1rem;
  border-block-end: 1px solid var(--border);
  flex: none;
}

.sk-thread-body {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: 1.1rem 1rem;
  overflow: hidden;
}

.sk-bubble {
  block-size: 2.4rem;
  border-radius: var(--r);
  margin-block-end: 0;
}

/* The replies. Mirrored rather than floated so this follows the page's RTL
 * direction the same way the real outgoing bubbles do. */
.sk-bubble-out {
  align-self: flex-end;
}

/* -- reduced motion ---------------------------------------------------- */
/* Everything above is decoration. Someone who has asked their system for
 * less of it gets the same dashboard with none of the movement — the
 * skeleton shimmer included, which is the one that actually causes trouble. */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  .nav-link:hover:not(.active),
  button.media-tile:hover {
    transform: none;
  }

  .sk {
    animation: none;
    background: var(--hover);
  }
}

/* The way back from a thread to the list. Only ever on screen when the two
 * are not side by side — see the 700 block below. */
.thread-back {
  display: none;
}

/* ------------------------------------------------------------ small screens */
/* Four steps down from a monitor to a phone:
 *   1200  the inbox list narrows so the thread keeps a usable width
 *    992  the side menu becomes an overlay — a tablet has no room for both
 *    700  every two-pane and side-by-side layout becomes one column, and the
 *         inbox shows the list OR the thread, never a stub of each
 *    576  padding, type and controls tighten for a phone
 * Everything below 700 also assumes a finger, so see the coarse-pointer block
 * at the end for hit sizes. */

@media (max-width: 1200px) {
  .split {
    grid-template-columns: 300px 1fr;
  }

  .split.with-info {
    grid-template-columns: 300px 1fr 300px;
  }
}

@media (max-width: 992px) {
  #sidebar {
    transform: translateX(100%);
  }

  /* Under dir="rtl" the sidebar sits at the right, so it hides to the right;
   * translateX(100%) is the correct direction in both, because the transform
   * axis flips with the writing direction. */
  body.sidebar-toggled #sidebar {
    transform: none;
  }

  /* The menu floats over the page while it is open, so the page behind it
   * must not scroll under it — and its scrollbar must not sit alongside the
   * open menu. The gutter stays reserved (see html), so nothing shifts. */
  body.sidebar-toggled {
    overflow: hidden;
  }

  /* The open menu is the only thing on screen; tapping the page behind it
   * should close it rather than reach a row underneath. */
  body.sidebar-toggled::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 29;
    background: rgba(0, 0, 0, 0.35);
  }

  .topbar {
    inset-inline-start: 0;
  }

  .view {
    margin-inline-start: 0;
  }

  /* The menu is an overlay now, so the whole width is the inbox's: the list
   * can afford to stay beside the thread down to the next step. */
  .split {
    grid-template-columns: 280px 1fr;
  }

  /* Three columns no longer fit. The conversation list is what goes, not the
     thread: the panel is only useful read beside the conversation it is
     about, and the list is one tap away on the back arrow. */
  .split.with-info {
    grid-template-columns: 1fr 300px;
  }

  .split.with-info > .list-pane {
    display: none;
  }

  /* At 280px the status dropdown was sharing a row with two checkboxes and
   * came out one letter wide. It takes the row instead. */
  .list-pane .filters select {
    flex: 1 0 100%;
  }
}

/* ------------------------------------------------------------- one column */
/* 700, not 768: an iPad in portrait is exactly 768 and has the width for the
 * list beside the thread and four KPI tiles across. Below 700 is a phone
 * held upright, and nothing here fits twice. */

@media (max-width: 700px) {
  /* No mouse here, so the reserved scrollbar gutter is 15px of nothing — and
   * 15px is a real share of a 375px screen. The jump it prevents between
   * pages only happens with a scrollbar that takes space. */
  html {
    scrollbar-gutter: auto;
  }

  /* One pane at a time. Which one is decided by viewInbox() in app.js, which
   * puts .thread-open on the body whenever a conversation is open. */
  .split {
    grid-template-columns: 1fr;
  }

  .split > .pane + .pane {
    border-inline-start: none;
  }

  body.thread-open .list-pane,
  body:not(.thread-open) .thread-pane {
    display: none;
  }

  /* A phone shows one of the three. The panel takes the whole width when it
     is open, and its close button is the way back to the conversation. */
  .split.with-info {
    grid-template-columns: 1fr;
  }

  body.info-open .thread-pane {
    display: none;
  }

  /* The way back to the list, since the list is no longer beside the thread. */
  .thread-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.25rem;
    block-size: 2.25rem;
    margin-inline-start: -0.5rem;
    flex-shrink: 0;
    border-radius: var(--r-pill);
    color: var(--heading);
  }

  .thread-back:hover {
    background: var(--hover);
    text-decoration: none;
  }

  /* The header's controls are a second row of their own down here: the name
   * and status keep the first, the dropdown and buttons wrap under it. */
  .thread-head {
    padding: 0.65rem 0.85rem;
    gap: 0.45rem;
  }

  .thread-head select {
    min-inline-size: 0;
    flex: 1 1 8rem;
  }

  .thread-head .spacer {
    flex-basis: 100%;
    block-size: 0;
  }

  /* Side-by-side pairs that no longer have the width to be a pair.
   * minmax(0, …) rather than a plain 1fr: a 1fr track refuses to be narrower
   * than the widest thing in it, so one wide table dragged whole pages
   * sideways instead of scrolling inside its own box. */
  .two-col,
  .form-grid,
  .form-grid.cols-2,
  .report-grid,
  .kb-list {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Below tablet width, three columns (or two, for the price/dark-mode
   * pair) is too narrow to hold a real field — drop to one column and let
   * every card, regardless of its span on the wide layout, take the row. */
  .settings-grid,
  .settings-grid .subgrid-2 {
    grid-template-columns: 1fr;
  }

  .settings-grid .span-2,
  .settings-grid .span-3 {
    grid-column: 1 / -1;
  }

  /* KPI tiles are four short numbers. One per row turns the top of every
   * page into 450px of scrolling before the actual content starts, so they
   * pair up — the icon tucks above the number rather than beside it. */
  .stats {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0.75rem;
  }

  .stat {
    padding: 0.9rem;
    flex-direction: column;
    gap: 0.5rem;
  }

  .stat .value {
    font-size: 1.25rem;
  }

  /* Filter bars stacked one control per row eat half a phone screen before
   * the results start. Two to a row instead, dropdowns sharing evenly. */
  .filters select,
  .filters input[type="search"],
  .filters input[type="date"] {
    min-inline-size: 0;
    flex: 1 1 8.5rem;
  }

  .list-pane .filters {
    padding: 0.75rem 0.85rem;
  }

  .messages {
    padding: 1rem;
  }

  .composer {
    padding: 0.6rem 0.75rem;
  }

  .composer-tools {
    gap: 0.4rem;
  }

  /* The toolbar's five buttons wrap onto two rows on a phone. Trimming the
   * desktop side padding is what gets them back to one. */
  .composer-tools button,
  .thread-head button {
    padding-inline: 0.65rem;
  }

  .label-bar {
    padding: 0.4rem 0.85rem;
  }

  /* A finger drags the board sideways, so a column that fills the screen
   * hides the fact that there are others. Leave the next one peeking. */
  .board-col {
    flex: 0 0 min(15rem, 80%);
  }

  .modal-backdrop {
    padding: 0.75rem;
  }

  /* A table is the one shape a phone cannot fold, so it keeps its sideways
   * scroll — but scrolled halfway across, an unlabelled row is unreadable.
   * Pinning the first cell keeps the ticket number or the customer's name on
   * screen the whole way. inset-inline-start is the right edge under RTL,
   * which is where the first column already is. */
  .table-wrap thead th:first-child,
  .table-wrap tbody td:first-child {
    position: sticky;
    inset-inline-start: 0;
    z-index: 1;
  }

  /* inherit, not a fixed colour: the row already changes background when it
   * is the active or hovered one, and a pinned cell that ignored that would
   * read as a hole in the row. */
  .table-wrap tbody tr {
    background: var(--card);
  }

  .table-wrap tbody td:first-child {
    background: inherit;
  }

  .table-wrap thead th:first-child {
    background: var(--hover);
    z-index: 2;
  }

  /* The seam that says the column is pinned rather than simply first. */
  .table-wrap thead th:first-child::after,
  .table-wrap tbody td:first-child::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-end: 0;
    inline-size: 1px;
    background: var(--border);
  }
}

@media (max-width: 576px) {
  .view {
    padding-inline: 1rem;
  }

  .topbar {
    inset-inline: 0;
    padding-inline: 0.75rem;
    gap: 0.25rem;
  }

  /* On the inbox, the navbar's own ticket/KB search is dropped in favor of
   * the inbox's name/number search (swapped in by route() as .inbox-search),
   * and the topbar wraps so the search takes its own full-width row above
   * the green line, with menu/bell/account dropping to a row below it. */
  body.inbox-active .topbar {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }

  body.inbox-active .topbar-search {
    display: none;
  }

  body.inbox-active .topbar .inbox-search-slot {
    order: 1;
    flex-basis: 100%;
    display: block;
  }

  body.inbox-active .topbar .inbox-search-slot input {
    inline-size: 100%;
  }

  body.inbox-active .topbar > :not(.inbox-search-slot) {
    order: 2;
  }

  /* The filters row's own copy of this box is redundant once the topbar is
   * showing one above the green line. */
  body.inbox-active .filters > input[type="search"] {
    display: none;
  }

  .msg {
    max-inline-size: 88%;
  }

  .who-name,
  .who-role {
    display: none;
  }

  .stack {
    gap: 1rem;
  }

  .card,
  .filter-card {
    padding: 1.15rem 1rem;
  }

  h1 {
    font-size: 1.25rem;
  }

  /* Tables keep their sideways scroll — they are the one shape a phone
   * genuinely cannot fold — but the fat desktop cell padding was costing a
   * whole column's worth of width on every row. */
  thead th,
  tbody td {
    padding-inline: 0.75rem;
  }

  .truncate {
    max-inline-size: 11rem;
  }

  /* Wrapped, a date turns one row into three lines and the table into a
   * cliff. The table scrolls sideways anyway, so width is the cheap axis
   * here and height is the expensive one. Long text is already capped by
   * .truncate, so nothing runs away. */
  tbody td {
    white-space: nowrap;
    max-inline-size: 15rem;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .login {
    padding: 1.5rem 1.25rem 2rem;
  }

  .login-head {
    margin-block-end: 1.25rem;
  }

  .gauge {
    inline-size: 92px;
    block-size: 92px;
  }

  .page-head h1 + .spacer {
    flex-basis: 100%;
  }
}

/* ---------------------------------------------------------- touch screens */
/* Not a width — a finger. A laptop with a touchscreen gets these too, and a
 * narrow desktop window rightly does not. */

@media (pointer: coarse) {
  /* Under 16px iOS zooms the whole page in the moment a field is focused,
   * and never zooms back out. This is the only reliable way to stop it. */
  input,
  select,
  textarea {
    font-size: 1rem;
  }

  /* 24px of icon is a 24px target for a mouse and a miss for a thumb. */
  button.icon-btn,
  .menu-toggle,
  .toggle-visibility {
    min-inline-size: 2.5rem;
    min-block-size: 2.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  button,
  .button-link,
  .composer-tab,
  .topbar-search input {
    min-block-size: 2.5rem;
  }

  .row {
    padding-block: 0.85rem;
  }

  /* Hover lifts and tints have no meaning without a pointer, and on touch
   * they stick after a tap — the row stays "hovered" until something else
   * is tapped. */
  .board-card:hover,
  .kb-item:hover {
    transform: none;
    box-shadow: var(--shadow-sm);
  }
}

/* ----------------------------------------------------------- short screens */
/* A phone turned sideways is 390px tall, and the chat header and the composer
 * between them were taking 388 of it — the conversation itself got 48px and
 * was unreadable. Height is the scarce axis here, not width, so every piece
 * of furniture gives way and the messages keep what is left. */

@media (max-height: 500px) and (max-width: 992px) {
  /* One row that drags sideways rather than two stacked ones. Nothing here
   * opens a panel underneath it — the close-conversation box is a sibling —
   * so a scroller traps nothing. */
  .thread-head {
    padding-block: 0.35rem;
    flex-wrap: nowrap;
    overflow-x: auto;
  }

  .thread-head > * {
    flex-shrink: 0;
  }

  .thread-head .spacer {
    flex: 1 0 0;
  }

  /* Tagging a conversation is not something anyone does with the phone
   * turned sideways, and its row costs a fifth of the messages. It is back
   * the moment the phone is upright. */
  .label-bar {
    display: none;
  }

  .composer {
    padding-block: 0.4rem;
  }

  .composer-tabs {
    margin-block-end: 0.3rem;
  }

  /* Icon-only toolbar. font-size:0 hides the label to the eye but leaves it
   * in the accessibility tree, so each button keeps its spoken name. */
  .composer-tools button {
    font-size: 0;
    gap: 0;
    padding-inline: 0.55rem;
  }

  .composer-tools button .bx {
    font-size: 1.25rem;
  }

  .composer-box {
    padding-block: 0.3rem;
  }

  .composer-box textarea {
    max-block-size: 2.4rem;
  }

  .composer-actions {
    margin-block-start: 0.35rem;
  }

  .composer-actions .muted {
    font-size: 0.6875rem;
  }

  .messages {
    padding: 0.6rem 1rem;
    gap: 0.6rem;
  }

  .msg {
    max-inline-size: 70%;
  }
}

/* ------------------------------------------- inbox: the Test me screen only */
/* Sits above the filters in the list pane rather than in a strip of its own:
   the two-pane grid is sized to the window, and a fourth box outside it would
   have to be measured into that. */
.test-panel {
  padding: 1rem 1.25rem;
  border-block-end: 1px solid var(--border);
  display: grid;
  gap: 0.75rem;
}

.test-note {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--muted);
}

.test-actions {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.5rem;
}

.test-actions button {
  flex: 1 1 0;
  min-width: 0;
}

/* Overrides the "load more" spacing further down the same pane, which would
   otherwise push these two apart and indent them. */
.list-pane .test-panel button.ghost {
  margin: 0;
}

.test-meta {
  font-size: 0.75rem;
  color: var(--muted);
}

/* ---------------------------------------- picture icons and icon-only actions
 * The coloured icon set in assets/icons, used where a control drops its text
 * label. Stored at 2x and sized down here so they stay sharp on retina. */
.png-icon {
  inline-size: 1.375rem;
  block-size: 1.375rem;
  object-fit: contain;
  display: block;
  flex: none;
}

/* A button that is nothing but its icon: no border, no fill, no wording. The
 * label lives in `title`/`aria-label`, so the tooltip is the only hint and a
 * screen reader still hears the action. */
.icon-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.45rem;
  border: none;
  background: transparent;
  border-radius: var(--r);
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
  text-decoration: none;
  flex: none;
}

.icon-action:hover:not(:disabled) {
  background: var(--primary-soft);
  transform: translateY(-1px);
}

.icon-action:active:not(:disabled) {
  transform: none;
}

/* The sign-out button carries both classes: this one for the picture icon,
 * icon-btn for the pill shape and the phone-sized target it already had. */
.icon-action.icon-btn {
  border-radius: var(--r-pill);
}

/* The avatar next to the name opens the same customer panel as the
 * .info-toggle button — just a click target around the picture, no chrome
 * of its own. */
.thread-avatar-toggle {
  display: inline-flex;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
}

/* The customer button sits in the same row as the icon actions, so it drops
 * its border and box too — otherwise it is the only framed thing left. */
.thread-head .info-toggle {
  padding: 0.2rem;
  border: none;
  background: transparent;
  box-shadow: none;
}

.thread-head .info-toggle:hover:not(:disabled) {
  background: var(--primary-soft);
  box-shadow: none;
}

/* Re-stated after the borderless rules above, which would otherwise flatten
 * the "panel is open" state defined earlier in this sheet. */
.thread-head .info-toggle.on,
.thread-head .info-toggle.on:hover:not(:disabled) {
  background: var(--primary);
  color: #fff;
}

/* ------------------------------------------------------- confirm dialog
 * Replaces the browser's own confirm() box for actions that change or destroy
 * something. Above the toast (z-index 100) so an error raised behind it does
 * not cover the question being asked. */
.confirm-backdrop {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(0, 0, 0, 0.45);
  animation: confirm-fade 0.15s ease;
}

.confirm-box {
  background: var(--card);
  color: var(--text);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  padding: 1.5rem;
  inline-size: min(26rem, 100%);
  text-align: center;
  animation: confirm-pop 0.18s ease;
}

.confirm-icon {
  display: flex;
  justify-content: center;
  margin-block-end: 0.85rem;
}

.confirm-icon .png-icon {
  inline-size: 3rem;
  block-size: 3rem;
}

.confirm-box h3 {
  margin: 0 0 0.5rem;
  font-size: 1.0625rem;
}

.confirm-box p {
  margin: 0;
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.7;
}

.confirm-actions {
  display: flex;
  gap: 0.6rem;
  justify-content: center;
  margin-block-start: 1.35rem;
}

.confirm-actions button {
  min-inline-size: 6rem;
}

@keyframes confirm-fade {
  from { opacity: 0; }
}

@keyframes confirm-pop {
  from { opacity: 0; transform: translateY(0.5rem) scale(0.97); }
}

@media (prefers-reduced-motion: reduce) {
  .confirm-backdrop,
  .confirm-box { animation: none; }
  .icon-action { transition: none; }
  .icon-action:hover:not(:disabled) { transform: none; }
}

/* ------------------------------------------- campaign recipients table

   Six columns of times and counts, read across a row and down a column both.
   Everything here exists to keep those two readings possible: the header is
   grouped so four time columns announce themselves as one journey, the digits
   are tabular so they line up down the column, and the rows are striped
   because tracking one person across six narrow columns without stripes is
   how a supervisor reads the wrong row. */

.recipients-card {
  padding-inline: 0;
  padding-block-end: 0;
}

.recipients-card > .kb-head {
  padding-inline: 1.25rem;
}

/* The grouped header row: a label spanning the four time columns. */
.rcpt thead th.group {
  text-align: center;
  border-inline-start: 1px solid var(--border);
  padding-block: 0.5rem;
}

.rcpt thead tr:first-child th {
  border-block-end: none;
}

/* Times and counts: centred under their heading, and tabular so a column of
   them lines up digit over digit instead of drifting. */
.rcpt td.t,
.rcpt th.t {
  text-align: center;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  inline-size: 1%;
  padding-inline: 1rem;
}

.rcpt td.t {
  color: var(--muted);
  font-weight: 600;
}

/* A stage that was actually reached is stated in full colour; one that was
   not stays a quiet dash, so a row's progress is visible without reading it. */
.rcpt td.t.on {
  color: var(--heading);
}

.rcpt td.sep,
.rcpt th.sep {
  border-inline-start: 1px solid var(--border);
}

/* The customer is the subject of the row and takes the slack; every other
   column is sized to its content rather than sharing it out. */
.rcpt th:first-child {
  inline-size: 28%;
}

.rcpt .note {
  inline-size: 22%;
}

.rcpt .note .cell-sub {
  max-inline-size: 210px;
}

.rcpt tbody tr:nth-child(even) {
  background: color-mix(in srgb, var(--hover) 55%, transparent);
}

.rcpt tbody tr:hover {
  background: var(--hover);
}

/* The row's action is always there, but quiet until the row is pointed at:
   hiding it entirely made every row look like it had an empty last column,
   and a control nobody can see is a control nobody uses. Opacity rather than
   display, so the column keeps its width and the rows do not shift on hover. */
.rcpt .row-act {
  opacity: 0.45;
  transition: opacity 0.15s ease;
}

.rcpt tr:hover .row-act,
.rcpt tr:focus-within .row-act {
  opacity: 1;
}

/* The totals row reads as a footer, not as one more customer. */
.rcpt tfoot td {
  padding: 0.85rem 1.25rem;
  font-weight: 600;
  color: var(--heading);
  background: var(--hover);
  border-block-start: 2px solid var(--border);
  font-variant-numeric: tabular-nums;
}

.tbl-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1.25rem;
  border-block-start: 1px solid var(--border);
}

/* The chosen filter, in the brand colour the rest of the app uses for state. */
.chip.on {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}

/* Seven funnel tiles on one line rather than a ragged wrap that leaves a gap
   where the eye expects the next stage. */
.funnel {
  grid-template-columns: repeat(7, 1fr);
}

.stat .value small {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted);
  line-height: 1.2;
}

@media (max-width: 1100px) {
  .funnel {
    grid-template-columns: repeat(4, 1fr);
  }
}

@media (max-width: 640px) {
  .funnel {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ------------------------------------------- عملاء الحملات: campaign leads */

/* The sidebar count. Empty until somebody is actually waiting — a permanent
 * "0" beside a menu item is noise that teaches people to stop looking. */
.nav-badge {
  display: none;
  align-items: center;
  justify-content: center;
  min-inline-size: 1.5625rem;
  block-size: 1.5625rem;
  margin-inline-start: auto;
  padding: 0 0.44rem;
  border-radius: 0.94rem;
  background: var(--danger);
  color: #fff;
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1;
  text-align: center;
}

.nav-badge.on {
  display: inline-flex;
}

/* The counters above the list: waiting / agreed / declined / total. */
.lead-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem;
  padding: 0.75rem 0.75rem 0;
}

.lead-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  padding: 0.5rem 0.25rem;
  border-radius: var(--radius, 0.5rem);
  background: var(--secondary-soft);
}

.lead-stat.warning { background: var(--warning-soft); }
.lead-stat.success { background: var(--success-soft); }
.lead-stat.primary { background: var(--primary-soft); }

.lead-stat-value {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--heading);
}

.lead-stat-label {
  font-size: 0.6875rem;
  color: var(--muted);
  text-align: center;
}

.lead-campaign {
  inline-size: 100%;
}

/* Search and campaign picker share one line. The inbox puts its search on a
 * line of its own (flex: 1 0 100% above) because four icon toggles sit under
 * it; this screen has a single dropdown, so the row has room for both. */
.list-pane .filters.leads input[type="search"] {
  flex: 1 1 0;
}

/* The search gets the larger share: a name or number is what staff reach for
 * most, and the picker only has to show a campaign name. */
.filters.leads .filter-row {
  flex: 0 1 40%;
  min-inline-size: 0;
}

.filters.leads .lead-campaign {
  min-inline-size: 0;
}

/* The campaign's name on a row. A chip, not a circle: unlike every other flag
 * on a row this one is a name, and a name cannot be drawn as an icon. */
.row-chip {
  max-inline-size: 11rem;
  padding: 0.05rem 0.45rem;
  border-radius: 0.75rem;
  background: var(--secondary-soft);
  color: var(--muted);
  font-size: 0.6875rem;
  line-height: 1.25rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A wait long enough to be worth a supervisor's eye. */
.row-time.overdue {
  color: var(--danger);
  font-weight: 600;
}

/* تم الاتفاق / لم يتم الاتفاق, across the top of a lead's thread. */
.lead-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.6rem 0.9rem;
  border-block-end: 1px solid var(--border);
  background: var(--warning-soft);
}

.lead-bar-text {
  font-size: 0.8125rem;
  color: var(--heading);
  font-weight: 500;
}

.lead-bar-actions {
  display: flex;
  gap: 0.5rem;
}

.lead-bar-actions button {
  padding: 0.35rem 0.9rem;
  border: 0;
  border-radius: var(--radius, 0.5rem);
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
}

.lead-agree {
  background: var(--success);
  color: #fff;
}

.lead-decline {
  background: var(--card);
  color: var(--danger);
  box-shadow: inset 0 0 0 1px var(--danger);
}

.lead-bar-actions button:hover {
  filter: brightness(0.95);
}

@media (max-width: 640px) {
  .lead-stats {
    grid-template-columns: repeat(2, 1fr);
  }

  .lead-bar {
    flex-direction: column;
    align-items: stretch;
  }

  .lead-bar-actions button {
    flex: 1;
  }
}
