/* DentWe Clinic
 *
 * Same shape as before -- rail, search, options, content -- but built as a
 * proper surface: one purple accent carried from the mobile app, a single
 * spacing rhythm, and depth from layered shadow rather than from borders.
 */

:root {
  color-scheme: light;

  --purple: #7367f0;
  --purple-600: #5e50ee;
  --purple-100: #ece9fe;
  --purple-glow: rgba(115, 103, 240, 0.28);

  --ink: #2f3349;
  --ink-soft: #4b5068;
  --muted: #8b90a5;
  --faint: #aeb3c4;

  --bg: #f5f6fa;
  --surface: #ffffff;
  --surface-2: #fbfbfe;
  --line: #e9ebf2;
  --line-soft: #f1f2f7;

  --green: #22c55e;
  --green-bg: #dcfce7;
  --amber: #f59e0b;
  --amber-bg: #fef3c7;
  --red: #ef4444;
  --red-bg: #fee2e2;
  --blue: #3b82f6;
  --blue-bg: #dbeafe;

  --rail-top: #322d55;
  --rail-bottom: #221f3d;

  --r-sm: 9px;
  --r-md: 13px;
  --r-lg: 18px;
  --r-pill: 999px;

  --sp: 8px;

  --shadow-1: 0 1px 2px rgba(37, 40, 66, 0.05), 0 1px 3px rgba(37, 40, 66, 0.04);
  --shadow-2: 0 2px 6px rgba(37, 40, 66, 0.06), 0 8px 24px rgba(37, 40, 66, 0.06);
  --shadow-3: 0 12px 40px rgba(37, 40, 66, 0.14);

  /* Enough for the longest label under its mark, and no more: a tile is
   * centred, so width past what the word needs is a margin on both sides of
   * every section. */
  --rail-w: 158px;
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --ink: #e6e8f0;
    --ink-soft: #c3c7d6;
    --muted: #9199ad;
    --faint: #6f7689;
    --bg: #15161d;
    --surface: #1d1f28;
    --surface-2: #22252f;
    --line: #2c3040;
    --line-soft: #262936;
    --purple-100: #2a2750;
    --green-bg: #14331f;
    --amber-bg: #3a2b0c;
    --red-bg: #3a1c1c;
    --blue-bg: #172a45;
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-2: 0 2px 6px rgba(0, 0, 0, 0.35), 0 8px 24px rgba(0, 0, 0, 0.3);
    --shadow-3: 0 12px 40px rgba(0, 0, 0, 0.55);
  }
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font: 400 14px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", "Noto Kufi Arabic", Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

.hidden { display: none !important; }

/* The attribute, not only the class. A rule that sets display -- .summary is
 * a grid -- beats the browser's own [hidden] { display: none }, so an element
 * hidden by the property went on being drawn. */
[hidden] { display: none !important; }

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

::selection { background: var(--purple-glow); }

/* Focus that is visible without being loud, and never on a mouse click. */
:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ==================================================================== login */

.login {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 24px;
  background:
    radial-gradient(900px 500px at 15% -5%, rgba(115, 103, 240, 0.18), transparent 60%),
    radial-gradient(700px 500px at 100% 100%, rgba(59, 130, 246, 0.12), transparent 55%),
    var(--bg);
}

.login-card {
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  padding: 36px 32px 32px;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);
  border: 1px solid var(--line);
  animation: rise 0.4s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
}

.login-mark {
  width: 46px;
  height: 46px;
  border-radius: 14px;
  display: grid;
  place-items: center;
  font-size: 24px;
  margin-bottom: 18px;
  background: linear-gradient(135deg, var(--purple), #9b8bff);
  box-shadow: 0 6px 18px var(--purple-glow);
}

.login-card h1 { margin: 0 0 4px; font-size: 21px; letter-spacing: -0.01em; }
.login-card > p { margin: 0; color: var(--muted); font-size: 13.5px; }
.login-card label { display: block; margin-top: 18px; font-weight: 600; font-size: 12.5px; color: var(--ink-soft); }
.login-card input { width: 100%; margin-top: 6px; }

/* ====================================================================== app */

.app { display: flex; min-height: 100vh; }

/* ----------------------------------------------------------------- the rail
 *
 * Dark, because it is the frame and not the page: everything to the right of
 * it is white paper and the eye should know which is which without being
 * told. One accent -- the brand purple on whatever section you are in -- and
 * nothing else in the rail is coloured, so the lit row is the only thing
 * competing for attention and it wins by a mile.
 */

/*
 * The teeth behind it.
 *
 * A molar, a premolar, a canine and a central incisor, drawn from the same
 * measurements the chart draws from rather than as a cartoon tooth or as the
 * emoji -- which is a different picture on every machine and in colours
 * nobody chose. Scattered over a tile that repeats, at an opacity where it
 * is a texture you notice on the second look and never a picture you have to
 * read past. Under the gradient, so the top of the rail keeps its glow.
 */
.rail {
  width: var(--rail-w);
  flex: 0 0 var(--rail-w);
  background:
    radial-gradient(120% 60% at 50% 0%, rgba(115, 103, 240, 0.22), transparent 70%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='210' height='300' viewBox='0 0 210 300'%3E%3Cg transform='translate(38 30) rotate(-13) scale(0.4) translate(-50 -78)'%3E%3Cpath d='M12,211 C0,227.5 0,238 3,279 L5.5,275 Q18.7,291.3 31.8,270.3 Q34.3,266.3 36.8,270.3 Q50,291.3 63.2,270.3 Q65.7,266.3 68.2,270.3 Q81.3,291.3 94.5,275 L97,279 C100,238 100,227.5 88,211 Q50,204.3 12,211 Z M88,211 C88.7,158.5 93.8,138.5 88.1,96 Q85,86 81.9,96 C76.2,138.5 64.3,158.5 65,173.5 Q63,189.3 60,173.5 C60,163.8 58,145.8 52.8,107.5 Q50,98.5 47.2,107.5 C42,145.8 40,163.8 40,173.5 Q37,189.3 35,173.5 C34.3,158.5 23.8,138.5 18.1,96 Q15,86 11.9,96 C6.2,138.5 12.7,158.5 12,211 Z' fill='none' stroke='%23fff' stroke-opacity='0.082' stroke-width='3.75' stroke-linejoin='round'/%3E%3C/g%3E%3Cg transform='translate(150 66) rotate(17) scale(0.32) translate(-37.5 -23)'%3E%3Cpath d='M13.5,186 C0,208 0,238 3.8,252 Q15.9,267.3 35.5,285 Q37.5,288 39.5,285 Q59.1,267.3 71.3,252 C75,238 75,208 61.5,186 Q37.5,177 13.5,186 Z M61.5,186 C61.7,120.9 59.6,96.1 46.7,43.4 Q39.8,31 32.8,43.4 C20,96.1 13.3,120.9 13.5,186 Z' fill='none' stroke='%23fff' stroke-opacity='0.045' stroke-width='4.69' stroke-linejoin='round'/%3E%3C/g%3E%3Cg transform='translate(74 138) rotate(-6) scale(0.27) translate(-42.5 -43)'%3E%3Cpath d='M17.9,181 C0,204.1 0,246.1 2.6,282 Q2.6,286 12.9,286 L72.1,286 Q82.4,286 82.4,282 C85,246.1 85,204.1 67.2,181 Q42.5,171.6 17.9,181 Z M67.2,181 C67.3,126.4 66,105.6 51.8,61.4 Q44.2,51 36.6,61.4 C22.4,105.6 17.7,126.4 17.9,181 Z' fill='none' stroke='%23fff' stroke-opacity='0.050' stroke-width='5.56' stroke-linejoin='round'/%3E%3C/g%3E%3Cg transform='translate(166 196) rotate(-9) scale(0.35) translate(-35 -53)'%3E%3Cpath d='M11.9,201 C0,219.7 0,236.7 2.8,278 L5.4,273.5 Q18.9,292 32.4,268.2 Q35,263.7 37.6,268.2 Q51.1,292 64.6,273.5 L67.2,278 C70,236.7 70,219.7 58.1,201 Q35,193.4 11.9,201 Z M58.1,201 C58.4,142.2 57.7,119.8 52.9,72.2 Q50.4,61 47.9,72.2 C43.1,119.8 36.8,142.2 37.1,142.2 Q35,166.9 32.9,142.2 C32.6,142.2 26.9,119.8 22.1,72.2 Q19.6,61 17.1,72.2 C12.3,119.8 12.2,142.2 11.9,201 Z' fill='none' stroke='%23fff' stroke-opacity='0.055' stroke-width='4.29' stroke-linejoin='round'/%3E%3C/g%3E%3Cg transform='translate(30 216) rotate(23) scale(0.28) translate(-37.5 -23)'%3E%3Cpath d='M13.5,186 C0,208 0,238 3.8,252 Q15.9,267.3 35.5,285 Q37.5,288 39.5,285 Q59.1,267.3 71.3,252 C75,238 75,208 61.5,186 Q37.5,177 13.5,186 Z M61.5,186 C61.7,120.9 59.6,96.1 46.7,43.4 Q39.8,31 32.8,43.4 C20,96.1 13.3,120.9 13.5,186 Z' fill='none' stroke='%23fff' stroke-opacity='0.055' stroke-width='5.36' stroke-linejoin='round'/%3E%3C/g%3E%3Cg transform='translate(108 262) rotate(9) scale(0.23) translate(-50 -78)'%3E%3Cpath d='M12,211 C0,227.5 0,238 3,279 L5.5,275 Q18.7,291.3 31.8,270.3 Q34.3,266.3 36.8,270.3 Q50,291.3 63.2,270.3 Q65.7,266.3 68.2,270.3 Q81.3,291.3 94.5,275 L97,279 C100,238 100,227.5 88,211 Q50,204.3 12,211 Z M88,211 C88.7,158.5 93.8,138.5 88.1,96 Q85,86 81.9,96 C76.2,138.5 64.3,158.5 65,173.5 Q63,189.3 60,173.5 C60,163.8 58,145.8 52.8,107.5 Q50,98.5 47.2,107.5 C42,145.8 40,163.8 40,173.5 Q37,189.3 35,173.5 C34.3,158.5 23.8,138.5 18.1,96 Q15,86 11.9,96 C6.2,138.5 12.7,158.5 12,211 Z' fill='none' stroke='%23fff' stroke-opacity='0.045' stroke-width='6.52' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E"),
    linear-gradient(180deg, var(--rail-top), var(--rail-bottom));
  background-repeat: no-repeat, repeat, no-repeat;
  color: #fff;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  /* A hairline rather than a shadow: a shadow over white paper reads as the
   * rail floating above the page, and it is beside it. */
  border-inline-end: 1px solid rgba(0, 0, 0, 0.2);
}

/* The icons. One family, one weight, the colour of whatever is around them. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.rail-icon {
  width: 20px;
  height: 20px;
  flex: 0 0 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --------------------------------------------------------- which practice */

.rail-practice {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 13px 12px 11px;
}

.rail-logo,
.rail-mark {
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  font-size: 18px;
  background: rgba(255, 255, 255, 0.1);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}

.rail-logo { object-fit: contain; background: #fff; padding: 3px; }

/* The mark is the stand-in for a practice that has not uploaded one, so it
 * goes when the real thing arrives. */
.rail-logo:not(.hidden) + .rail-mark { display: none; }

.rail-clinic {
  min-width: 0;
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* ------------------------------------------------------------------- nav */

.rail-nav {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 4px 10px 10px;
  /* The bar itself only when there is something to scroll, and thin: a light
   * scrollbar down a dark rail is a white stripe nobody asked for. */
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
}

.rail-nav::-webkit-scrollbar { width: 6px; }
.rail-nav::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.16);
  border-radius: var(--r-pill);
}

.rail-group + .rail-group { margin-top: 10px; }

/* A group with nothing in it has no name either. */
.rail-group.hidden { display: none; }

.rail-caption {
  margin: 0 0 3px;
  padding-inline: 10px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.34);
}

/* A tile: the mark over the word, both centred. */
.rail-item {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 7px 6px;
  margin-bottom: 1px;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: rgba(255, 255, 255, 0.7);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.rail-glyph {
  font-size: 20px;
  line-height: 1.1;
  /* Held back a little while the section is not the one you are in, so a
   * column of full-strength colour does not outshout the one that is. */
  filter: grayscale(0.25);
  opacity: 0.9;
  transition: filter 0.15s ease, opacity 0.15s ease;
}

.rail-label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail-item:hover { background: rgba(255, 255, 255, 0.07); color: #fff; }

.rail-item:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.5);
  outline-offset: -2px;
}

/* The section you are in, as the one lit thing in the rail. Solid brand
 * purple rather than a tint of white: from across a surgery the question is
 * "which one is on", and a slightly paler grey row does not answer it. */
.rail-item.active {
  background: linear-gradient(135deg, var(--purple), var(--purple-600));
  color: #fff;
  box-shadow: 0 4px 14px var(--purple-glow);
}

.rail-item:hover .rail-glyph,
.rail-item.active .rail-glyph { filter: none; opacity: 1; }

.rail-item.hidden { display: none !important; }

/* ------------------------------------------------------------ who you are */

.rail-foot {
  padding: 8px 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
  background: rgba(0, 0, 0, 0.16);
}

.rail-user {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 2px 4px 8px;
  min-width: 0;
}

.rail-avatar {
  width: 32px;
  height: 32px;
  flex: 0 0 32px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #fff;
  background: linear-gradient(135deg, var(--purple), #a794ff);
}

.rail-who { min-width: 0; display: flex; flex-direction: column; }

.rail-name {
  font-size: 12.5px;
  font-weight: 600;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail-role {
  font-size: 10.5px;
  color: rgba(255, 255, 255, 0.45);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail-role:empty { display: none; }

/* The four things that are about you rather than about the practice. Icons
 * on one line, because a row of full-width buttons above the nav competed
 * with the nav and won -- and none of them is a section. */
.rail-actions { display: flex; gap: 4px; }

.rail-action {
  position: relative;
  flex: 1;
  height: 34px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.07);
  color: rgba(255, 255, 255, 0.8);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
}

.rail-action:hover { background: rgba(255, 255, 255, 0.17); color: #fff; }
.rail-action:active { transform: scale(0.94); }
.rail-action.danger:hover { background: rgba(239, 68, 68, 0.85); color: #fff; }

.rail-action:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.5);
  outline-offset: 1px;
}

/* Which language the button switches to, on the button. An icon alone says
 * "language" and not "Arabic", and that is the half that matters. */
.rail-action-tag {
  position: absolute;
  right: 3px;
  bottom: 1px;
  font-size: 8.5px;
  font-weight: 700;
  line-height: 1;
  padding: 1px 2px;
  border-radius: 3px;
  background: var(--purple);
  color: #fff;
}

[dir="rtl"] .rail-action-tag { right: auto; left: 3px; }

/* ------------------------------------------------------------------ the main */

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* A block of fields that wraps -- the clinic's own details on the settings
 * page, where they read as one thing rather than a column of separate rows. */
.field-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
}

/* -------------------------------------------------------------- the page bar */

/* What you are looking at, a box to search it, and what it filters by -- one
 * line, sticky, above the list it belongs to. This replaced a full-width
 * search bar pinned across every screen with a second strip under it holding
 * a "Search options" toggle: two bands of chrome above every page, one of
 * them a closed drawer. */
.page-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px 22px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}

.page-title {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-inline-end: auto;
}

.bar-search {
  flex: 1 1 260px;
  max-width: 380px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
  padding: 9px 13px;
  font-size: 13.5px;
  color: var(--ink);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}

.bar-search::placeholder { color: var(--faint); }

.bar-search:focus {
  background: var(--surface);
  border-color: var(--purple);
  box-shadow: 0 0 0 3px var(--purple-glow);
}

.bar-filters {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 8px;
}

/* The filter controls lose their stacked label: on one line there is no room
 * for a caption above each, and a date field and a status list say what they
 * are by what they hold. */
.bar-filters .field { flex-direction: row; align-items: center; gap: 6px; min-width: 0; }
.bar-filters .field > span { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; }
.bar-filters .field.grow { flex: 0 1 auto; min-width: 0; }
.bar-filters input, .bar-filters select { padding: 7px 10px; font-size: 13px; }
.bar-filters .bar-button { padding: 7px 13px; font-size: 13px; }

/* -------------------------------------------------------------- the content */

.content { padding: 6px 22px 110px; }

/*
 * The figures, behind a line you can fold away.
 *
 * Six tiles across the top of every screen is a hundred and twenty pixels of
 * the same six numbers over the patient list, the diary, the books and the
 * team -- read once in the morning and scrolled past for the rest of the day.
 * Shut by default, and the line above it carries the one number the strip was
 * being kept open for.
 */
.summary-fold { padding: 10px 22px 0; }

.summary-toggle {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 7px 2px;
  border: 0;
  background: none;
  font: inherit;
  color: var(--muted);
  cursor: pointer;
  text-align: start;
}

.summary-toggle:hover { color: var(--ink); }

.summary-title {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* What is inside, without opening it. */
.summary-peek {
  margin-inline-start: auto;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-soft);
}

.summary-fold.open .summary-peek { opacity: 0; }

/* Drawn rather than a glyph, so it points the same way in both languages and
 * turns rather than swapping for a different character. */
.summary-chevron {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-inline-end: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transform-origin: 60% 60%;
  transition: transform 0.18s ease;
}

[dir="rtl"] .summary-chevron { transform: rotate(-135deg); }

.summary-fold.open .summary-chevron,
[dir="rtl"] .summary-fold.open .summary-chevron { transform: rotate(45deg); }

.summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 12px;
  padding: 6px 0 4px;
}

.stat {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  padding: 14px 16px;
  overflow: hidden;
  transition: transform 0.16s ease, box-shadow 0.16s ease;
}

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

/* A thin coloured edge tells the tiles apart without a legend. */
.stat::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--purple);
  opacity: 0.8;
}

.stat:nth-child(2)::before { background: var(--blue); }
.stat:nth-child(3)::before { background: var(--amber); }
.stat:nth-child(4)::before { background: var(--green); }
.stat:nth-child(5)::before { background: var(--red); }
.stat:nth-child(6)::before { background: #8b5cf6; }

.stat-label {
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.stat-value {
  font-size: 21px;
  font-weight: 700;
  margin-top: 3px;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

/* Whose visit is being written up, over the form that writes it. */
.writeup-who {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

.writeup-who strong { font-size: 15px; }
.writeup-who .muted { font-size: 12.5px; }

/* The second half of a tile that carries two figures: the same shape, half a
 * step quieter, under a rule that says they are one thought and not two. */
.stat-half {
  margin-top: 9px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

.stat-half .stat-label,
.stat-half .report-stat-label { font-size: 10.5px; }
.stat-half .stat-value,
.stat-half .report-stat-value { font-size: 17px; color: #15803d; }

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

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  padding: 15px 17px;
  margin-bottom: 12px;
  transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
  animation: fade 0.28s ease both;
}

@keyframes fade {
  from { opacity: 0; transform: translateY(6px); }
}

.card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
  border-color: #dcd9f7;
}

.card-row { display: flex; gap: 15px; align-items: flex-start; }

/* ------------------------------------------- a dentist with two practices */

/* Which of them this panel is showing. Only ever on screen for somebody who
   has more than one, so it never takes space from the clinic name. */
.rail-switch { width: 100%; margin-top: 8px; }

.clinic-switch {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

/* ------------------------------------- the clinic's own list of work */

/* Grouped as the treatment form groups them: two lists of the same thing in
   two different orders is two things to learn. */
.group-row td { padding-top: 16px; border-bottom: 0; }

.group-name {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Something the practice has stopped doing. On screen only when asked for,
   and legible rather than crossed out -- the reason to look at one is to put
   it back. */
.retired td { opacity: 0.55; }

.show-retired {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-inline-end: auto;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
}

/* ------------------------------------------------- who runs a clinic */

.who-runs { margin-bottom: 4px; }

.who-found { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }

.who-card {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.14s ease, background 0.14s ease;
}

.who-card:hover { border-color: var(--purple); background: var(--surface-2); }

/* The one they settled on: not a result any more, an answer. */
.who-card.on {
  position: relative;
  margin-top: 8px;
  border-color: var(--purple);
  background: var(--purple-100);
  cursor: default;
  padding-inline-end: 96px;
}

.who-name { font-weight: 700; }
.who-under { color: var(--muted); font-size: 12px; }

/* Where they already work. Said rather than refused -- a chain is a real
   thing -- but said, because handing somebody a second practice should be a
   decision rather than an accident. */
.who-warn { margin-top: 3px; color: var(--amber); font-size: 12px; font-weight: 600; }

.who-change {
  position: absolute;
  inset-inline-end: 10px;
  top: 50%;
  transform: translateY(-50%);
  padding: 5px 12px;
  font-size: 13px;
}

/* A file with nothing on it for money to be against.
   Plain rather than a warning: nobody has done anything wrong, they pressed
   the money button on a file that has not been written up yet. A yellow panel
   would be telling them off for arriving in the right order. */
.no-work { text-align: center; }

.no-work-mark {
  margin: 6px 0 10px;
  font-size: 40px;
  line-height: 1;
  opacity: 0.55;
}

.no-work-say { margin: 0 0 6px; font-size: 18px; }

.no-work-why {
  max-width: 34em;
  margin: 0 auto;
  line-height: 1.55;
}

.no-work .modal-actions { margin-top: 22px; justify-content: center; }

/* ------------------------------------------- what a payment is for */

/* Tiles, not rows.
   The same four facts stacked instead of strung out -- the name first, what
   it is on second, the amount at the foot where the eye lands last -- and
   laid out as a grid they are a handful of things to look at rather than a
   list to read down. */
.pay-for {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 10px;
  padding: 2px;
}

/* A scroll of its own only when the alternative is worse: the dialog already
   scrolls, and a box that scrolls inside a box that scrolls is two things to
   find instead of one. */
.pay-for.long {
  max-height: 360px;
  overflow-y: auto;
}

.pay-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 132px;
  padding: 14px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.14s ease, background 0.14s ease, box-shadow 0.14s ease,
              transform 0.14s ease;
}

.pay-card:hover {
  border-color: var(--purple);
  box-shadow: var(--shadow-1);
  transform: translateY(-1px);
}

.pay-card.on {
  border-color: var(--purple);
  background: var(--purple-100);
  box-shadow: 0 0 0 3px var(--purple-glow);
}

/* The radio does the work and none of the showing: it keeps the arrow keys,
   the label click and the screen reader, and the tick is what is looked at. */
.pay-card input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.pay-card input:focus-visible ~ .pay-name {
  outline: 2px solid var(--purple);
  outline-offset: 3px;
  border-radius: 3px;
}

/* Out of the corner rather than in the flow, so choosing one does not move
   the words on it. */
.pay-tick {
  position: absolute;
  top: 10px;
  inset-inline-end: 10px;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--purple);
  color: #fff;
  font-size: 12px;
  line-height: 1;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 0.14s ease, transform 0.14s ease;
}

.pay-card.on .pay-tick { opacity: 1; transform: scale(1); }

.pay-name {
  font-weight: 700;
  line-height: 1.25;
  /* Two lines and then an ellipsis: a tile whose height depends on how long
     somebody's treatment name is makes a ragged grid. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Room for the tick, on the line it shares with it. */
  padding-inline-end: 22px;
}

.pay-under {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* At the foot, pushed there, so every tile's figure sits on the same line as
   its neighbours' and they can be compared across the grid. */
.pay-due { margin-top: auto; padding-top: 10px; }

.pay-amount {
  display: block;
  font-size: 17px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.pay-word {
  display: block;
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.pay-card.on .pay-amount { color: var(--purple-600); }

/* The square a practice puts in its window. */
.share { margin-top: 12px; }

.share-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 18px;
}

.share-square {
  display: grid;
  place-items: center;
  min-width: 168px;
  min-height: 168px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: #fff;
}

.share-img { width: 160px; height: 160px; image-rendering: pixelated; }

.share-said { flex: 1 1 240px; min-width: 0; }

/* An address, so it wraps anywhere rather than pushing the card wide. */
.share-link {
  margin: 4px 0 12px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: var(--bg);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  word-break: break-all;
}

.share-buttons { display: flex; flex-wrap: wrap; gap: 8px; }

/* The code a dentist shows a clinic that wants to hire them. */
.my-code-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 18px;
  margin-top: 12px;
}

.my-code-square {
  display: grid;
  place-items: center;
  min-width: 148px;
  min-height: 148px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: #fff;
}

.my-code-img { width: 140px; height: 140px; image-rendering: pixelated; }

.my-code-said { flex: 1 1 220px; min-width: 0; }

/* Eight characters somebody reads out, so they are big enough to read out. */
.my-code-text {
  margin: 4px 0 10px;
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.my-code-where { margin-bottom: 10px; }

.code-box {
  font-size: 18px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.code-scan { margin: 6px 0 12px; }

.code-scan .scan-view {
  display: block;
  width: 100%;
  max-width: 320px;
  border-radius: var(--r-md);
  background: #000;
  margin-bottom: 8px;
}

/* The hours a dentist is at their other practice: said, and set apart from
   this clinic's own bookings, because they are not this clinic's to open. */
.busy-away { margin-top: 4px; color: var(--faint); }

/* A card that is the thing it opens.
   Its own buttons keep their pointer and their own click; everything else on
   it belongs to the card. */
.card.openable { cursor: pointer; }
.card.openable button,
.card.openable a { cursor: pointer; }

.card.openable:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
}

/* ------------------------------------------------------------- case card */

/* Everything on one line at desk width, wrapping in sensible pieces as the
 * window narrows. Each block owns its width, so a short treatment name no
 * longer leaves a hole across the middle of the card. */
.case-head {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}

.case-identity { flex: 1 1 180px; min-width: 0; }

.case-treatment {
  flex: 1 1 190px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.case-teeth { flex: 0 0 auto; display: flex; flex-direction: column; gap: 3px; align-items: center; }

/* ------------------------------------------ the four quadrants, as a cross */

/* What a paper chart draws: the midline down, the occlusal plane across, and
 * a tooth's quadrant said by which corner its number sits in. Upper right is
 * top left because you are looking at the patient -- the one thing about a
 * dental chart everybody who reads one already knows.
 *
 * The lines are cell borders rather than a drawn cross, so the arms grow with
 * whatever is written in the quadrants and the centre stays the centre. */
.palmer {
  display: grid;
  grid-template-columns: 1fr 1fr;
  font-size: 12.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  line-height: 1.35;
  color: var(--ink);
}

.palmer-cell {
  padding: 1px 7px;
  min-width: 18px;
  white-space: nowrap;
}

/* Each quadrant carries the two sides of the cross it touches, so the four
 * together draw it once. */
.palmer-ur { text-align: right; border-inline-end: 1.5px solid var(--ink-soft); border-block-end: 1.5px solid var(--ink-soft); }
.palmer-ul { text-align: left; border-block-end: 1.5px solid var(--ink-soft); }
.palmer-lr { text-align: right; border-inline-end: 1.5px solid var(--ink-soft); }
.palmer-ll { text-align: left; }

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


.case-status {
  flex: 0 0 auto;
  margin-inline-start: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 7px;
}

.case-chips { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }

/* A caption over the value, so a number in a row of text has a name. */
.case-caption {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--faint);
}

.case-treatment-name {
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The actions sit on their own line under a hairline, so the card reads as
 * information first and controls second. */
.case-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--line);
}

/* Initials on a colour derived from the name: a wall of identical grey
 * silhouettes is harder to scan than a wall of distinct ones. */
.avatar {
  width: 52px;
  height: 52px;
  flex: 0 0 52px;
  border-radius: 15px;
  display: grid;
  place-items: center;
  font-size: 17px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.02em;
  box-shadow: var(--shadow-1);
}

.card-main { flex: 1; min-width: 0; }

.card-name {
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-phone { color: var(--muted); font-size: 12.5px; direction: ltr; font-variant-numeric: tabular-nums; }

/* The tooth number, boxed the way a casesheet boxes it. */
.teeth {
  display: inline-grid;
  place-items: center;
  min-width: 54px;
  min-height: 34px;
  padding: 4px 11px;
  font-weight: 700;
  font-size: 14.5px;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--ink-soft);
  border-radius: var(--r-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Holding a cross, the box drops its own chrome. The cross is already four
 * boxes with a rule between them, and a border round that is a box in a box;
 * a paper chart draws the lines and nothing else. */
/* Holding a cross, the box drops its own chrome. The cross is already four
 * cells with a rule between them, and a border round that is a box in a box;
 * a paper chart draws the lines and nothing else. A file with nothing charted
 * keeps the box, because a dash needs something to sit in. */
/* Nothing left to do. Quieter than the cross and without its caption: the
 * box is answering a different question, and dressing the answer up as a
 * chart with no teeth on it would be a chart that says nothing. */
.teeth-done {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  background: var(--surface-2);
  border-color: var(--line);
  border-bottom-color: var(--line);
}

.teeth-cross {
  min-width: 0;
  padding: 0;
  background: none;
  border: 0;
}

.card-treatment { font-weight: 600; }
.card-date { color: var(--muted); font-size: 12px; }

.card-actions { display: flex; gap: 7px; flex-wrap: wrap; }

.icon-btn {
  width: 36px;
  height: 36px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
  cursor: pointer;
  font-size: 15px;
  display: grid;
  place-items: center;
  text-decoration: none;
  transition: transform 0.14s ease, box-shadow 0.14s ease, filter 0.14s ease;
}

.icon-btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); filter: brightness(1.03); }
.icon-btn:active { transform: translateY(0) scale(0.96); }

.icon-btn.pink { background: #ffe4ef; border-color: #ffcfe2; }
.icon-btn.green { background: #dcfce7; border-color: #bbf7d0; }
.icon-btn.amber { background: #fef3c7; border-color: #fde68a; }

.card-side {
  text-align: end;
  display: flex;
  flex-direction: column;
  gap: 7px;
  align-items: flex-end;
}

/* The case's own state, as a pill with a dot rather than shouted in capitals
 * inside brackets. */
.state-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 12px;
  border-radius: var(--r-pill);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  background: var(--line-soft);
  color: var(--muted);
  white-space: nowrap;
}

.state-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }

/* Agreed but not begun. Amber rather than green: nothing is happening to
 * this tooth yet, and it should not read like work in progress. */
.state-pill.waiting { background: var(--amber-bg); color: #b45309; }

.state-pill.ongoing { background: var(--green-bg); color: #15803d; }
.state-pill.ongoing .state-dot { animation: pulse 1.9s ease-in-out infinite; }
.state-pill.completed { background: var(--purple-100); color: var(--purple-600); }
.state-pill.cancelled { background: var(--red-bg); color: #b91c1c; }

@keyframes pulse {
  50% { opacity: 0.35; transform: scale(0.8); }
}

.chip {
  display: inline-block;
  padding: 3px 11px;
  border-radius: var(--r-pill);
  font-size: 11.5px;
  font-weight: 600;
  background: var(--purple-100);
  color: var(--purple-600);
  white-space: nowrap;
}

.chip.green { background: var(--green-bg); color: #15803d; }
.chip.amber { background: var(--amber-bg); color: #b45309; }
.chip.red { background: var(--red-bg); color: #b91c1c; }
.chip.grey { background: var(--line-soft); color: var(--muted); }

.edited { color: var(--faint); font-size: 11.5px; }

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

.table-wrap {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  overflow: hidden;
}

.table { width: 100%; border-collapse: collapse; }

.table th, .table td {
  padding: 13px 16px;
  text-align: start;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}

.table th {
  background: var(--surface-2);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--muted);
  letter-spacing: 0.06em;
  position: sticky;
  top: 0;
  z-index: 1;
}

.table tbody tr { transition: background 0.13s ease; }
.table tbody tr:hover { background: var(--surface-2); }
.table tr:last-child td { border-bottom: 0; }

.table td.num, .table th.num { text-align: end; font-variant-numeric: tabular-nums; }
.table td.num .btn { margin-inline-start: 6px; }

/* ------------------------------------------------------------ empty states */

.empty { text-align: center; padding: 72px 20px; color: var(--muted); }
.empty-icon { font-size: 40px; opacity: 0.55; margin-bottom: 10px; }
.empty-title { font-size: 17px; font-weight: 700; color: var(--ink); margin-bottom: 5px; }

/* A shape while it loads, rather than the word "Loading" -- the page then
 * settles into content instead of jumping from text to a list. */
.skeleton { padding: 4px 0; }

.skeleton-row {
  height: 84px;
  border-radius: var(--r-md);
  margin-bottom: 12px;
  border: 1px solid var(--line);
  background: linear-gradient(90deg, var(--surface) 25%, var(--surface-2) 37%, var(--surface) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}

@keyframes shimmer {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* ------------------------------------------------------------------ inputs */

input, select, textarea {
  font: inherit;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  transition: border-color 0.14s ease, box-shadow 0.14s ease;
  max-width: 100%;
}

input:hover, select:hover, textarea:hover { border-color: #d6d9e6; }

/* A field that is switched off looks switched off. The console greys the two
 * dates until an answer asks for them, and a box that still looked ready to
 * type in would be a box somebody types in. */
input:disabled, select:disabled, textarea:disabled {
  background: var(--bg);
  color: var(--muted);
  cursor: not-allowed;
}

input:disabled:hover, select:disabled:hover, textarea:disabled:hover {
  border-color: var(--line);
}

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--purple);
  box-shadow: 0 0 0 3px var(--purple-glow);
}

textarea { resize: vertical; min-height: 72px; }

.field { display: flex; flex-direction: column; gap: 5px; font-size: 13px; }
.field > span { color: var(--ink-soft); font-weight: 600; font-size: 12px; }
.field.grow { flex: 1; min-width: 190px; }

.btn {
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  padding: 9px 16px;
  border-radius: var(--r-sm);
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  font-size: 13px;
  transition: background 0.14s ease, border-color 0.14s ease, transform 0.14s ease, box-shadow 0.14s ease;
  white-space: nowrap;
}

.btn:hover { background: var(--surface-2); border-color: #d6d9e6; }
.btn:active { transform: translateY(1px); }

.btn.primary {
  background: linear-gradient(135deg, var(--purple), #8a7bff);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 4px 12px var(--purple-glow);
}

.btn.primary:hover { box-shadow: 0 6px 18px var(--purple-glow); filter: brightness(1.03); }

.btn.danger { color: var(--red); border-color: #f6cccc; background: var(--surface); }
.btn.danger:hover { background: var(--red-bg); }

.btn.block { width: 100%; margin-top: 22px; padding: 11px; }
.btn:disabled { opacity: 0.55; cursor: not-allowed; transform: none; box-shadow: none; }

.error { color: var(--red); margin-top: 12px; min-height: 20px; font-size: 13px; }

/* --------------------------------------------------------------------- fab */

.fab {
  position: fixed;
  inset-inline-end: 26px;
  bottom: 26px;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  border: 0;
  background: linear-gradient(135deg, var(--purple), #8a7bff);
  color: #fff;
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 10px 28px var(--purple-glow), 0 2px 6px rgba(0, 0, 0, 0.18);
  transition: transform 0.16s cubic-bezier(0.2, 0.8, 0.3, 1), box-shadow 0.16s ease;
  z-index: 30;
}

.fab:hover { transform: translateY(-3px) rotate(90deg); box-shadow: 0 14px 34px var(--purple-glow); }
.fab:active { transform: translateY(0) scale(0.94); }

/* ------------------------------------------------------------------- modal */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 22, 34, 0.5);
  backdrop-filter: blur(3px);
  display: grid;
  place-items: center;
  padding: 20px;
  z-index: 40;
  animation: fadein 0.16s ease both;
}

@keyframes fadein { from { opacity: 0; } }

.modal {
  background: var(--surface);
  border-radius: var(--r-lg);
  width: 100%;
  max-width: 580px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-3);
  border: 1px solid var(--line);
  animation: pop 0.22s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

@keyframes pop {
  from { opacity: 0; transform: translateY(14px) scale(0.98); }
}

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line);
}

.modal-head h2 { margin: 0; font-size: 16.5px; letter-spacing: -0.01em; }

.modal-close {
  border: 0;
  background: transparent;
  font-size: 24px;
  cursor: pointer;
  color: var(--muted);
  line-height: 1;
  width: 32px;
  height: 32px;
  border-radius: var(--r-sm);
  transition: background 0.14s ease, color 0.14s ease;
}

.modal-close:hover { background: var(--line-soft); color: var(--ink); }

.modal-body { padding: 22px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; }

/*
 * Nothing in a dialog is squeezed; the dialog scrolls instead.
 *
 * This is a scrolling flex column, and its children shrink by default -- so
 * a casesheet taller than the dialog had its tab body compressed to fit
 * while the notes inside it, which clip nothing, went on painting over the
 * buttons underneath. The content was all there and none of it was where it
 * said it was.
 */
.modal-body > * { flex-shrink: 0; }

.modal-body form { display: flex; flex-direction: column; gap: 14px; }
.modal-body h3 { margin: 6px 0 0; font-size: 14px; color: var(--ink-soft); }
.modal-body .summary { padding: 0; }

.modal-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 4px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

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

.toast {
  position: fixed;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  bottom: 30px;
  background: var(--ink);
  color: var(--bg);
  padding: 12px 22px;
  border-radius: var(--r-pill);
  z-index: 60;
  font-weight: 600;
  font-size: 13.5px;
  box-shadow: var(--shadow-3);
  animation: toastin 0.25s cubic-bezier(0.2, 0.8, 0.3, 1) both;

  /* A refusal worth reading is a sentence, not a word. Without a ceiling the
   * pill grew to the width of the message and ran off both sides of the
   * screen, which is where "your plan covers three dentists" went. */
  max-width: min(520px, calc(100vw - 40px));
  text-align: center;
  line-height: 1.5;
}

@keyframes toastin {
  from { opacity: 0; transform: translateX(-50%) translateY(10px); }
}

.toast.error { background: var(--red); color: #fff; }

/* --------------------------------------------------------------------- rtl */

html[dir="rtl"] .toast { transform: translateX(50%); }
html[dir="rtl"] .card-side { align-items: flex-start; }

@keyframes toastin-rtl {
  from { opacity: 0; transform: translateX(50%) translateY(10px); }
}

html[dir="rtl"] .toast { animation-name: toastin-rtl; }

/* ------------------------------------------------------------------ narrow */

/*
 * Narrow: the rail becomes its icons.
 *
 * Not the same rail squeezed -- the words go entirely and everything centres,
 * so what is left is a column of marks at a comfortable size rather than a
 * column of clipped labels. Each keeps its title, so the name is a hover
 * away and a screen reader still reads it.
 */
@media (max-width: 1040px) {
  :root { --rail-w: 68px; }

  /* Sixty-eight pixels of a two-hundred-and-ten-pixel tile is its left-hand
   * edge, which is whichever teeth happen to be cut by it. Slid across so
   * the middle column shows instead: whole teeth, and the same two every
   * time rather than fragments. */
  .rail { background-position: 0 0, -71px 0, 0 0; }

  .rail-practice { justify-content: center; padding: 14px 0 12px; }
  .rail-clinic { display: none; }

  .rail-nav { padding-inline: 10px; }

  /* A caption at this width is two stacked words of eight-point type, which
   * costs more room than the group it names and reads as noise. A rule says
   * the same thing: these belong together and those do not. */
  .rail-caption {
    height: 0;
    margin: 0 0 10px;
    overflow: hidden;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
  }

  .rail-group:first-child .rail-caption { border-top: 0; margin-bottom: 0; }
  .rail-group + .rail-group { margin-top: 10px; }

  .rail-item { gap: 0; padding: 11px 0; }
  .rail-label { display: none; }

  .rail-foot { padding: 8px 10px; }
  .rail-user { justify-content: center; padding-inline: 0; }
  .rail-who { display: none; }

  /* Two by two rather than a column of four. A grid rather than wrapped flex
   * because at this width the pair fits by a pixel or misses by one, and
   * which of those you get is a rounding decision. */
  .rail-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
  .rail-action { height: 30px; }

  .content, .summary { padding-inline: 14px; }
}

@media (max-width: 620px) {
  .card-row { flex-wrap: wrap; }
  .card-side { align-items: flex-start; text-align: start; width: 100%; flex-direction: row; flex-wrap: wrap; }
  /* On a phone the status belongs under the name, not squeezed beside it. */
  .case-status { margin-inline-start: 0; align-items: flex-start; width: 100%; }
  .case-chips { justify-content: flex-start; }
  .case-teeth { align-items: flex-start; }
  .table th, .table td { padding: 10px 11px; }
  /* A wide table on a phone scrolls inside its own box rather than pushing
   * the whole page sideways. */
  .table-wrap { overflow-x: auto; }
  .table { min-width: 640px; }
}

/* ================================================================== chart */

.modal.modal-wide { max-width: 1080px; }

/*
 * A dialog the size of what is in it.
 *
 * Booking an hour is four short answers -- who, when, how long, with whom --
 * and in the standard 580px they were a column of wide boxes with half the
 * dialog empty beside them. Narrower, with the two short ones on one line,
 * and the whole form is one glance.
 */
.modal.modal-tight { max-width: 408px; }
.modal.modal-tight .modal-head { padding: 14px 16px 12px; }
.modal.modal-tight .modal-head h2 { font-size: 15px; }
.modal.modal-tight .modal-body { padding: 16px; gap: 11px; }
.modal.modal-tight .modal-body form { gap: 11px; }
.modal.modal-tight .field-grid { gap: 10px; }
/* Two to a line at this width. The modal's own pairing rule asks for 200px
 * a side, which in 376 of room is a wrap rather than a pair. */
.modal.modal-tight .modal-body .field-grid > .field { flex: 1 1 132px; }
.modal.modal-tight .field.grow { min-width: 132px; }
.modal.modal-tight .modal-actions { margin-top: 1px; padding-top: 11px; }

/* A box with the book behind it. The arrow a <select> has would be a lie --
 * this one takes names that are not on the list. */
.combo { display: flex; min-width: 0; }
.combo > input { flex: 1; min-width: 0; }

/*
 * A time, on a twenty-four hour clock.
 *
 * Built rather than <input type="time">, which the browser draws in the
 * browser's own locale: on an American one that is "03:15 PM" in a diary that
 * says 17:00 everywhere else, and no attribute changes it. So this is made to
 * look and behave like the field it replaces -- one box, two segments you
 * type straight into -- and to read the same clock as the rest of the panel.
 *
 * Left to right in both languages: a clock is written hour then minute
 * whichever way the page runs, which is the same reason every other time on
 * the screen is wrapped in an isolate.
 */
.clock {
  display: flex;
  align-items: center;
  gap: 0;
  min-width: 0;
  direction: ltr;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  transition: border-color 0.14s ease, box-shadow 0.14s ease;
}

.clock:hover { border-color: #d6d9e6; }

/* The whole box lights up, because the whole box is the field. */
.clock:focus-within {
  border-color: var(--purple);
  box-shadow: 0 0 0 3px var(--purple-100);
}

.clock-seg {
  width: 2ch;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.clock-seg:hover { border-color: transparent; }
.clock-seg:focus { outline: none; box-shadow: none; }

/* The segment you are in, marked the way the native field marks it. */
.clock-seg::selection { background: var(--purple); color: #fff; }

.clock-colon { flex: 0 0 auto; color: var(--ink); }

.clock.off { opacity: 0.55; }

/* The way through to the dial, small enough to ignore. */
.clock-dial {
  flex: 0 0 auto;
  margin-inline-start: auto;
  padding: 0 0 0 8px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.14s ease, transform 0.14s ease;
}

.clock-dial:hover { color: var(--purple-600); transform: scale(1.1); }
.clock-dial:focus-visible { outline: 2px solid var(--purple); outline-offset: 2px; }

/* --------------------------------------------------------------- the dial
 *
 * The hour as the shape it actually has. Twenty-four of them on a round
 * clock means two rings -- the morning outside, the afternoon in -- so three
 * in the afternoon is the inner ring where a 3 would be.
 */
.dial {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  /* A clock runs the same way round in both languages. */
  direction: ltr;
}

.dial-read {
  display: flex;
  align-items: center;
  gap: 2px;
  font-size: 34px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.dial-part {
  padding: 2px 8px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease;
}

/* The half being picked, which is the only thing the numbers up here have
 * to say -- the dial below shows the rest. */
.dial-part.on { background: var(--purple-100); color: var(--purple-600); }
.dial-part:hover:not(.on) { color: var(--ink); }

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

.dial-face {
  position: relative;
  width: 248px;
  height: 248px;
  border-radius: 50%;
  background: var(--bg);
  touch-action: none;
  user-select: none;
  cursor: pointer;
}

/* The pin the hand turns on. */
.dial-face::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: var(--purple);
}

.dial-number {
  position: absolute;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  pointer-events: none;
}

/* Marked by the hand rather than by a second colour: the knob lands on it,
 * and two things saying the same thing is one of them to keep in step. */
.dial-number.on { color: #fff; font-weight: 700; }

/* The hours nobody works: there, because a clock with holes in it is not a
   clock, but plainly not on offer. */
.dial-number.shut { opacity: .22; }

/* Morning or afternoon, which a twelve-hour face cannot say for itself. */
.dial-halves {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 14px;
}

.dial-half {
  min-width: 62px;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}

.dial-half:hover:not(.on):not(:disabled) { border-color: var(--purple); color: var(--ink); }

.dial-half.on {
  background: var(--purple);
  border-color: var(--purple);
  color: #fff;
}

/* A half of the day the practice never works. */
.dial-half:disabled { opacity: .35; cursor: default; }

.dial-open {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--muted);
}

.dial-bands {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--ink);
}

.dial-hand {
  position: absolute;
  left: 50%;
  bottom: 50%;
  width: 2px;
  margin-left: -1px;
  transform-origin: bottom center;
  background: var(--purple);
  pointer-events: none;
}

.dial-knob {
  position: absolute;
  left: 50%;
  top: 0;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  background: var(--purple);
}

.dial .modal-actions { align-self: stretch; }

.chart { display: flex; flex-direction: column; gap: 16px; }

/* The stage is dark, like the chart a dentist is used to seeing on a
 * screen in the surgery, and it makes the teeth read as objects on a light
 * background rather than as shapes on paper. */
.chart-stage {
  position: relative;
  background:
    linear-gradient(180deg, #2b3444, #232b39);
  border-radius: var(--r-md);
  /* Deep enough for a label hanging off the outside of either arch, which no
   * longer takes room inside the row it belongs to. */
  padding: 40px 22px;
  overflow-x: auto;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);

  /* The root, the crown, and the line round both. A tooth is two colours in
   * life and was one on this chart, which is why its cervical line had
   * nothing to divide. */
  --tooth: #f2e9d5;
  --tooth-crown: #ffffff;
  --tooth-line: #23303f;
}

.chart-arches {
  position: relative;
  min-width: 780px;
  display: flex;
  flex-direction: column;
  gap: 26px;
  padding: 4px 26px;
}

/* The horizontal rule between the arches, which is the occlusal plane. */
.chart-arches::before {
  content: "";
  position: absolute;
  left: 26px;
  right: 26px;
  top: 50%;
  height: 2px;
  background: rgba(255, 255, 255, 0.85);
  transform: translateY(-1px);
}

.arch { display: flex; align-items: flex-end; justify-content: center; gap: 2px; }
.arch-upper { align-items: flex-end; }
.arch-lower { align-items: flex-start; }

/* The midline. Without it left and right cannot be told apart at a glance. */
.midline {
  width: 2px;
  align-self: stretch;
  background: rgba(255, 255, 255, 0.85);
  margin: -14px 8px;
  border-radius: 2px;
}

/* The four corners of the mouth, named where each one is.
 *
 * They replace the bare R and L a printed chart carries. R and L said which
 * side and left the jaw to the row you were reading; UR said at the upper
 * right corner says both, and it is the half of a tooth's name the tooth
 * itself no longer carries -- the numbers under the teeth count outwards
 * from the midline, so the corner is what tells you which run they belong
 * to.
 *
 * Logical insets, not left and right: the arches are flex rows, so the whole
 * mouth mirrors under Arabic and the marks mirror with the teeth they name.
 */
.quad-mark {
  position: absolute;
  color: rgba(255, 255, 255, 0.6);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  pointer-events: none;
  /* Always read left to right. "UR" is an abbreviation, not a word, and it
   * is the same two letters in either language. */
  direction: ltr;
  unicode-bidi: isolate;
}

.quad-ur { top: 12px; inset-inline-start: 6px; }
.quad-ul { top: 12px; inset-inline-end: 6px; }
.quad-lr { bottom: 12px; inset-inline-start: 6px; }
.quad-ll { bottom: 12px; inset-inline-end: 6px; }

/* ------------------------------------------------------------- one tooth */

.tooth {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  border: 0;
  background: transparent;
  padding: 0 1px;
  cursor: pointer;
  font: inherit;
  border-radius: 8px;
  transition: transform 0.14s ease, filter 0.14s ease;
}

.tooth.upper { flex-direction: column; }
.tooth.lower { flex-direction: column; }

/* Which way up a tooth is drawn is now part of the drawing: a lower molar
 * has two roots and an upper has three, so the arches are not each other
 * turned over and cannot be made from one shape and a transform. */

.tooth:hover { transform: translateY(-3px); filter: brightness(1.08); }
.tooth.upper:hover { transform: translateY(3px); }
.tooth:active { transform: scale(0.97); }

.tooth-svg { display: block; overflow: visible; }

.tooth-number {
  font-size: 11px;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.5);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

.tooth.marked .tooth-number { color: rgba(255, 255, 255, 0.95); }

/* The label and its leader line, as a paper chart annotates a tooth.
 *
 * Out of the layout on purpose. In flow, a "Filling" pill is twice the width
 * of the tooth it names, so a marked tooth's cell stretched and the arch grew
 * with it -- which left the two arches different widths, each centred on its
 * own, and the midline sitting anywhere but the middle. A tooth's cell is now
 * the width of the tooth, marked or not, so both arches measure the same and
 * the midline is where it says it is. */
.tooth-label {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;

  font-size: 10.5px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);

  /* Its own keyframes, not the shared `fade`: that one animates transform,
   * which would replace the translateX(-50%) doing the centring and leave
   * every label sitting half a pill to the right of its tooth. */
  animation: label-in 0.2s ease both;
}

@keyframes label-in {
  from { opacity: 0; transform: translateX(-50%) translateY(6px); }
}

.tooth.upper .tooth-label { bottom: 100%; }
.tooth.lower .tooth-label { top: 100%; }

/* A run of marked teeth alternates between two heights, so neighbouring
 * labels sit past each other rather than on top of each other. */
.tooth[data-tier="1"] .tooth-label { --tier: 19px; }
.tooth.upper[data-tier="1"] .tooth-label { bottom: calc(100% + var(--tier)); }
.tooth.lower[data-tier="1"] .tooth-label { top: calc(100% + var(--tier)); }

.tooth-stem {
  width: 1px;
  height: 14px;
  background: rgba(255, 255, 255, 0.45);
}

/* The leader line grows to reach a label on the far tier. */
.tooth[data-tier="1"] .tooth-stem { height: 33px; }

.tooth:not(.marked):not(.planned) .tooth-stem { visibility: hidden; }

/* Work that is coming, not work that is done.
 *
 * Outlined rather than filled, and dashed rather than solid, because the
 * tooth has not been touched yet -- a planned extraction that looked like an
 * extraction would make the chart lie about the mouth. The colour is still
 * the condition's, so it reads as the same vocabulary. */
.tooth-label.planned {
  background: var(--surface);
  border: 1.5px dashed currentColor;
  box-shadow: none;
  font-weight: 600;
}

.tooth.planned .tooth-number { color: rgba(255, 255, 255, 0.75); }
.tooth.planned .tooth-stem { background: rgba(255, 255, 255, 0.3); }

/* The hovered tooth's label reads over its neighbours' rather than under. */
.tooth:hover .tooth-label { z-index: 3; }

/* The badge and the way to clear it, together. An appointment nobody has been
 * put on is the one row in the table with something to do about it, and the
 * doing belongs beside the thing that says so. */
.needs-dentist {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* Coming up, at the top of a tooth's card, marked off from what is already
 * history: one is a record, the other is something somebody still has to do. */
.past-row.planned .past-line { font-style: italic; }

.past-row.planned {
  border-left: 2px dashed var(--purple);
  padding-left: 8px;
  margin-left: -10px;
}

/* ---------------------------------------------------------- the palette */

.chart-legend { display: flex; flex-wrap: wrap; gap: 8px; }

.swatch {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--r-pill);
  padding: 6px 14px 6px 8px;
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  transition: border-color 0.14s ease, box-shadow 0.14s ease, transform 0.14s ease;
}

.swatch:hover { transform: translateY(-1px); }

.swatch-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--swatch, var(--muted));
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}

.swatch.erase .swatch-dot {
  background: repeating-linear-gradient(45deg, #fff, #fff 3px, #e5e7eb 3px, #e5e7eb 6px);
}

/* The armed colour is the one every click will apply, so it is unmistakable. */
.swatch.armed {
  border-color: var(--swatch, var(--purple));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--swatch, var(--purple)) 26%, transparent);
}

.chart-toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.chart-count { margin-inline-start: auto; color: var(--muted); font-size: 12.5px; font-weight: 600; }

/* -------------------------------------------------- the field on the form */

.teeth-field {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  padding: 8px 8px 8px 12px;
}

.teeth-summary { flex: 1; min-width: 0; font-size: 13px; color: var(--ink); }
.teeth-summary.empty-value { color: var(--faint); }

@media (max-width: 620px) {
  .chart-stage { padding: 18px 10px; }
  .chart-count { margin-inline-start: 0; }
}

/* A layer opened from inside a dialog sits above it. */
.modal-overlay { z-index: 50; }

/* The chart as it appears on an open casesheet: the picture, then what it
 * says in words, then the way in to change it. */
.case-chart { display: flex; flex-direction: column; gap: 10px; }

.case-chart-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.case-chart-foot .muted { font-size: 13px; }

/* Read-only: it is a picture of the mouth, so nothing invites a click. */
.chart-static .tooth { cursor: default; }
.chart-static .tooth:hover { transform: none; filter: none; }
.chart-static .chart-arches { min-width: 620px; }

/* ================================================= the casesheet dialog */

/* A record rather than a scroll: what identifies the case stays at the top,
 * what it collects is behind tabs, and the actions stay in reach. */
.sheet { display: flex; flex-direction: column; gap: 14px; min-height: 0; }

/* The same rule as the dialog's, for the same reason: the page scrolls, the
 * file does not compress. */
.sheet > *, .tabbed > * { flex-shrink: 0; }

/*
 * Who this file is about, as one block.
 *
 * It was four flex columns -- an avatar, a name, a captioned date, a pill --
 * which rearranged themselves at every width, so the steadiest thing on the
 * screen was the part that moved most. One tinted band: the name, everything
 * known about them as chips under it, and the state at the far end.
 */
.sheet-band {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 13px 15px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: linear-gradient(180deg, var(--purple-100), var(--surface-2));
}

.sheet-who { flex: 1 1 220px; min-width: 0; }

.sheet-name {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.01em;
  unicode-bidi: plaintext;
  text-align: start;
}

.sheet-facts { display: flex; flex-wrap: wrap; gap: 5px 6px; margin-top: 7px; }

/* One fact each, so a missing age or gender leaves no orphaned separator --
 * the middle-dot list read "9647700000002 ·  · male" for a patient whose age
 * nobody had written down. */
.sheet-fact {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-soft);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 2px 9px;
  white-space: nowrap;
}

.sheet-fact.muted { color: var(--faint); }

.sheet-side { margin-inline-start: auto; display: flex; align-items: center; gap: 8px; }

.sheet-money { display: flex; gap: 10px; flex-wrap: wrap; }

/* Out of the dialog and into a page of its own. Sits at the end of the header
 * row, where it reads as a property of the record rather than of the dialog. */
.sheet-expand { margin-inline-start: 4px; }

/* The same body in the whole width of the screen. Bounded, because a chart
 * stretched across a wide monitor is as hard to read as one squeezed into a
 * dialog. */
.case-page {
  max-width: 1080px;
  margin: 0 auto;
  padding: 18px 0 40px;
}

/* One course of work in the file's list. */
.treatment-row { gap: 12px; }
.treatment-row .row-amount { font-weight: 600; }
.row-money .row-amount { margin-inline-start: 0; }

/* What the course of work costs, and what is still owed on it -- stacked, so
 * the agreed figure and the outstanding one are not read as one number. */
.row-money {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  margin-inline-start: auto;
  white-space: nowrap;
}

.row-due { font-size: 11.5px; font-weight: 600; }
.row-due.amber { color: #b45309; }
.row-due.green { color: #15803d; }

/* What the clinic has asked for and not yet been paid.
 *
 * Above the receipts, because an ask is the open question and a receipt is a
 * closed one -- and tinted, so the two are not read as one list. */
.asks {
  border: 1px solid var(--purple);
  background: var(--purple-100);
  border-radius: var(--r-md);
  padding: 10px 12px;
  margin-bottom: 12px;
}

.asks .case-caption { display: block; margin-bottom: 4px; }
.ask-row { border-bottom-color: rgba(115, 103, 240, 0.25); }
.ask-row:last-child { border-bottom: 0; }
.ask-amount { color: var(--purple-600); }

/* What has happened to one tooth.
 *
 * A chart says what a mouth looks like now. A dentist about to touch tooth 16
 * wants to know when it was last touched, what was done, and under which
 * course of work -- which is the thing a diagram cannot say.
 *
 * Fixed to the viewport and placed by script: the chart scrolls sideways, and
 * a box that scrolls on one axis clips the other, so a card living inside a
 * tooth was cut off top and bottom. */
.tooth.has-past { cursor: help; }

.tooth-past {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 400;

  min-width: 200px;
  max-width: 280px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-2);
  text-align: start;

  /* Never under the pointer: a card that could be hovered would flicker as
   * the pointer crossed from the tooth onto it. */
  pointer-events: none;

  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
}

.tooth-past.showing { opacity: 1; visibility: visible; }

.past-head {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 6px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
}

.past-row { display: flex; gap: 9px; padding: 3px 0; }

.past-when {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.past-what { display: flex; flex-direction: column; min-width: 0; }
.past-line { font-size: 12px; font-weight: 600; color: var(--ink); }
.past-who { font-size: 11px; color: var(--muted); }

/* Everything done to this mouth, in date order, on the casesheet itself.
 *
 * Hovering answers "what happened to this tooth", one tooth at a time. The
 * question you have with a casesheet open is "what has been done here, and
 * when", and nobody finds that by pointing at thirty-two teeth in turn. */
.tooth-log {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.tooth-log .case-caption { display: block; margin-bottom: 8px; }

.log-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line-soft);
}

.log-row:last-child { border-bottom: 0; }

.log-when {
  flex: 0 0 92px;
  font-size: 11.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The colour the tooth was marked in, so the list reads against the chart
 * above it without having to name the condition twice. */
.log-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--line);
  transform: translateY(-1px);
}

.log-what { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; gap: 4px 8px; }
.log-teeth { font-size: 12.5px; font-weight: 700; color: var(--ink); white-space: nowrap; }
.log-change { font-size: 12.5px; color: var(--ink-soft); }

.log-who {
  flex: 0 1 auto;
  font-size: 11.5px;
  color: var(--muted);
  text-align: end;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tooth-log.empty .muted { font-size: 12.5px; }

@media (max-width: 620px) {
  .log-row { flex-wrap: wrap; }
  .log-who { flex-basis: 100%; text-align: start; padding-inline-start: 102px; }
}

/* The status, as something you can change. Shaped like the pill it replaces,
 * because it is a status first and a control second -- one that looked like a
 * form field would pull the eye off the treatment's name. */
.state-select {
  -webkit-appearance: none;
  appearance: none;
  border: 0;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  padding-inline: 12px 24px;
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='9' height='6'><path d='M1 1l3.5 3.5L8 1' fill='none' stroke='%23888' stroke-width='1.6' stroke-linecap='round'/></svg>");
}

[dir="rtl"] .state-select { background-position: left 8px center; padding-inline: 12px 24px; }

.state-select:focus { outline: 2px solid var(--purple); outline-offset: 1px; }

.row-tools {
  display: flex;
  gap: 4px;
  margin-inline-start: 4px;
}

.row-tools .icon-btn { width: 30px; height: 30px; font-size: 13px; }

.icon-btn.red { background: var(--red-bg); border-color: #fecaca; color: #b91c1c; }

.figure {
  flex: 1 1 120px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-inline-start: 3px solid var(--purple);
  border-radius: var(--r-sm);
  padding: 9px 12px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.figure.green { border-inline-start-color: var(--green); }
.figure.amber { border-inline-start-color: var(--amber); }
/* What the practice spent, not what the patient owes. Toned apart from
 * the three beside it because it is the clinic's money, not theirs. */
.figure.red { border-inline-start-color: var(--red); }

.figure-value { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.015em; }

.sheet-complaint {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--surface-2);
  border-radius: var(--r-sm);
  padding: 9px 12px;
  font-size: 13.5px;
}

.sheet-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  flex-wrap: wrap;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

/* Stuck to the foot of the dialog. A file with four treatments and a dozen
 * notes is a long scroll, and Close was at the bottom of it -- so the way out
 * of the dialog was the one thing you had to scroll to reach. The negative
 * offsets cancel the dialog's own padding so it sits flush on the edge. */
.modal-body .sheet-actions {
  position: sticky;
  bottom: -22px;
  z-index: 2;
  background: var(--surface);
  padding-bottom: 22px;
  margin-bottom: -22px;
}

/* ------------------------------------------------------------------ tabs */

.tabbed { display: flex; flex-direction: column; min-height: 0; }

.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
}

.tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-weight: 600;
  font-size: 13px;
  padding: 9px 14px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
  transition: color 0.14s ease, border-color 0.14s ease;
}

.tab:hover { color: var(--ink); }
.tab.active { color: var(--purple); border-bottom-color: var(--purple); }

.tab-badge {
  background: var(--line-soft);
  color: var(--muted);
  border-radius: var(--r-pill);
  padding: 1px 7px;
  font-size: 11px;
}

.tab.active .tab-badge { background: var(--purple-100); color: var(--purple-600); }

.tab-body { padding-top: 14px; min-height: 0; }

.panel { display: flex; flex-direction: column; gap: 12px; }

.panel-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

/*
 * A form that sits above the list it adds to.
 *
 * Written as form.inline-form rather than .inline-form: a dialog styles every
 * form inside it as a column, that selector is the more specific of the two,
 * and this one lost -- so in a dialog the fields stacked and align-items:
 * flex-end, which bottom-aligns a row, right-aligned a column instead. The
 * whole form sat in a narrow strip down the right-hand edge.
 */
form.inline-form {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 10px;
  align-items: flex-end;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 12px;
}

.rows { display: flex; flex-direction: column; }

.row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 2px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13.5px;
}

.row:last-child { border-bottom: 0; }
.row-date { flex: 0 0 92px; color: var(--muted); font-variant-numeric: tabular-nums; font-size: 12.5px; }
.row-main { flex: 1; min-width: 0; }
.row-amount { margin-inline-start: auto; font-variant-numeric: tabular-nums; }

.rows .empty { padding: 40px 20px; }

/* ------------------------------------------------------------- the notes */

/* The note across the width, its attachments under it. A two-line box
 * squeezed between two file buttons is a box people write one line in. */
.note-write { flex: 1 1 100%; min-width: 0; }
.note-write .field { min-width: 0; }
.note-write textarea { min-height: 62px; }

.note-bits {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
}

.note-bits .field { flex: 0 1 200px; }
.note-add { margin-inline-start: auto; }

/*
 * A file input, wearing this application's clothes.
 *
 * A bare one is a grey "Choose File" next to the words "No file chosen", both
 * written by the browser in whatever language it was installed in -- two
 * English words on an Arabic screen that nobody here put there and nobody can
 * restyle. The real input is inside the label and hidden; this is what you
 * see, and the filename is read back beside it.
 */
.picker { display: flex; flex-direction: column; gap: 5px; min-width: 0; }

.picker-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  font-weight: 600;
}

.picker-glyph { font-size: 14px; line-height: 1; }

.picker-name {
  font-size: 11.5px;
  max-width: 190px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding-inline-start: 2px;
}

.notes { display: flex; flex-direction: column; gap: 10px; }

.note {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 10px 12px;
  background: var(--surface-2);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* A chip is as wide as its word. In a flex column it stretches to the card
 * unless it is told not to, and "Filed against tooth 16" was a grey band
 * across the whole note. */
.note > .chip { align-self: flex-start; }

/* An x-ray is looked at, not glanced at, so it gets room -- but never more
 * than the dialog it sits in. */
.note-media {
  display: block;
  width: 100%;
  max-width: 100%;
  max-height: 320px;
  object-fit: contain;
  border-radius: var(--r-sm);
  background: #0b0b10;
}

.note-body { white-space: pre-wrap; font-size: 13.5px; line-height: 1.5; }

.note-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 11.5px;
  padding-top: 2px;
  border-top: 1px solid var(--line-soft);
}

/* Quiet until it is wanted. Removing a note is a real thing to be able to do
 * and not something the eye should land on once a card. */
.note-foot .btn.danger {
  padding: 3px 9px;
  font-size: 11.5px;
  background: none;
  border-color: transparent;
}

.note-foot .btn.danger:hover { background: var(--red-bg); border-color: #fecaca; }

/* ========================================================== prescription */

/* The page and the things you reach for while writing on it, side by side.
 * Everything that is not the page is out of the page's way. */
.rx { display: flex; gap: 16px; align-items: flex-start; }

.rx-stage {
  flex: 1;
  min-width: 0;
  background: var(--line-soft);
  padding: 16px;
  border-radius: var(--r-md);
  display: flex;
  justify-content: center;
  max-height: 68vh;
  overflow: auto;
}

/* Paper. White whatever the viewer's theme is, at the proportions it prints
 * at, so what is on screen is what comes out. */
.rx-sheet {
  background: #ffffff;
  color: #111111;
  width: 100%;
  display: flex;
  flex-direction: column;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.18);
}

.rx-a5 { max-width: 460px; min-height: 650px; }
.rx-a4 { max-width: 560px; min-height: 792px; }

/* The clinic's own artwork, edge to edge as it was uploaded. */
.rx-band { display: block; width: 100%; height: auto; }

.rx-band-empty {
  background: #23282e;
  color: #ffffff;
  padding: 20px 16px;
  text-align: center;
  font-size: 18px;
  font-weight: 700;
}

.rx-page { flex: 1; display: flex; flex-direction: column; padding: 18px 22px 12px; gap: 14px; }

.rx-meta { display: flex; flex-wrap: wrap; gap: 8px 14px; }

.rx-detail { flex: 1 1 180px; display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.rx-detail-date { flex: 0 0 auto; }
.rx-detail-small { flex: 0 1 110px; }

.rx-detail-label { font-size: 11px; color: #777777; white-space: nowrap; }

/* Underlined rather than boxed: a prescription is a written form, and boxes
 * around filled-in values make it look like a screen instead. */
.rx-detail-value {
  flex: 1;
  min-width: 0;
  border-bottom: 1px solid #cccccc;
  padding: 2px 2px 3px;
  font-size: 13.5px;
  min-height: 20px;
}

.rx-main { flex: 1; display: flex; gap: 12px; align-items: stretch; }

.rx-symbol {
  font-size: 34px;
  font-weight: 400;
  line-height: 1;
  color: #222222;
  font-family: Georgia, "Times New Roman", serif;
}

/* Typed straight onto the page, so what is written is what prints. */
.rx-body {
  flex: 1;
  min-height: 300px;
  font-size: 14.5px;
  line-height: 2.05;
  white-space: pre-wrap;
  outline: none;
  /* Ruled, like a pad. The lines are drawn rather than bordered so nothing
   * boxes the text in. */
  background-image: repeating-linear-gradient(
    transparent, transparent 28.7px, #ececec 28.7px, #ececec 29.7px
  );
  padding: 2px 4px;
}

.rx-body:empty::before {
  content: attr(data-placeholder);
  color: #b6b6b6;
}

.rx-sign { display: flex; flex-direction: column; gap: 3px; align-items: flex-end; }
.rx-rule { width: 190px; border-bottom: 1px solid #999999; }
.rx-sign-label { display: flex; gap: 8px; font-size: 11.5px; color: #777777; }
.rx-dentist { color: #333333; font-weight: 600; }
.rx-note { font-size: 11px; color: #888888; text-align: center; }

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

.rx-side {
  flex: 0 0 268px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-height: 68vh;
}

.rx-side-block { display: flex; flex-direction: column; gap: 8px; min-height: 0; flex: 1; }
.rx-side-block input { width: 100%; }

.rx-medicines {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-inline-end: 2px;
}

.rx-medicine {
  text-align: start;
  border: 1px solid var(--line);
  background: var(--surface);
  border-radius: var(--r-sm);
  padding: 8px 11px;
  cursor: pointer;
  font: inherit;
  display: flex;
  flex-direction: column;
  gap: 2px;
  transition: border-color 0.14s ease, transform 0.14s ease, box-shadow 0.14s ease;
}

.rx-medicine:hover {
  border-color: var(--purple);
  transform: translateY(-1px);
  box-shadow: var(--shadow-1);
}

.rx-medicine-name { font-weight: 600; font-size: 13px; }
.rx-medicine-directions { font-size: 11.5px; color: var(--muted); }
.rx-hint { font-size: 12.5px; padding: 10px 2px; }

.rx-side-foot {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.rx-side-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

.segmented { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; }

.segmented button {
  border: 0;
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-weight: 600;
  font-size: 12.5px;
  padding: 6px 16px;
  cursor: pointer;
}

.segmented button.active { background: var(--purple); color: #ffffff; }

/* The print button is the sidebar's purpose, so it takes the full width. */
.rx-print { width: 100%; padding: 11px; }

/* ------------------------------------------- the letterhead, in settings */

.card h3 { margin: 0 0 6px; font-size: 15px; }

.letterhead { display: flex; flex-wrap: wrap; gap: 14px; margin: 12px 0; }
.letterhead-slot { flex: 1 1 260px; display: flex; flex-direction: column; gap: 6px; }

/* The mark is square and shown small, so it does not need a band's width. */
.letterhead-logo { flex: 0 0 160px; }
.letterhead-logo .letterhead-preview { aspect-ratio: 1; object-fit: contain; padding: 6px; }

.letterhead-preview {
  width: 100%;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: #ffffff;
}

.letterhead-empty {
  padding: 22px;
  text-align: center;
  color: var(--faint);
  border: 1px dashed var(--line);
  border-radius: var(--r-sm);
}

/* --------------------------------------------------------------- narrow */

@media (max-width: 820px) {
  .rx { flex-direction: column; }
  .rx-side { flex: 1 1 auto; width: 100%; max-height: none; }
  .rx-medicines { max-height: 220px; }
  .rx-stage { max-height: none; }
}

/* ---------------------------------------------------------------- print */

/* Nothing but the page. It prints from the same markup that was on screen,
 * so what comes out cannot differ from what the dentist approved. */

/* ----------------------------------------------------------- the report */

/* One rhythm down the page. Every block is a card of the same weight with the
 * same gap under it, so the eye reads a column of findings rather than a
 * jumble of boxes at different insets. Nothing here re-pads horizontally --
 * .content already provides the page's side margin, and the first version
 * borrowed the dashboard's .summary, which is a sibling of the content area
 * and carries that margin itself. Inside it, everything was inset twice. */
.report {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 4px;
}

.report-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.report-head strong { font-size: 15px; }
.report-head .btn { margin-inline-start: auto; }

/* The four figures, denser than the dashboard's tiles: this page is read as a
 * whole, and a row of tall cards pushes the substance below the fold. */
.report-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
}

.report-stat {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  padding: 13px 15px 13px 18px;
  overflow: hidden;
}

/* A coloured edge tells them apart without a legend. Named rather than keyed
 * to position: a report's tiles are not always in the same order. */
.report-stat::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--purple);
}

.report-stat.green::before { background: var(--green); }
.report-stat.amber::before { background: var(--amber); }
.report-stat.red::before { background: var(--red); }

.report-stat-label {
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.report-stat-value {
  font-size: 22px;
  font-weight: 700;
  margin-top: 2px;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

/* What the figure means, under it. A number on its own invites the reader to
 * invent a reason for it. */
.report-stat-note {
  margin-top: 3px;
  font-size: 11.5px;
  color: var(--muted);
}

/* Two abreast on a desk, one under the other on anything narrower. A report
 * read on a phone is still a report. */
/* Panels fill their row rather than hanging from the top of it: a short card
 * beside a tall one leaves a hole between cards, which reads as something
 * missing. Stretched, the slack sits inside the card where it belongs. */
.report-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
  gap: 14px;
  align-items: stretch;
}

/* Its own card. .panel is a bare flex column -- the pages that use it put the
 * card edges on the rows inside -- so a report built out of them was a stack
 * of unboxed text. */
.report-panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  padding: 16px 18px 18px;
}

.report-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.report-title h2 {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.report-note { margin: 10px 0 0; font-size: 12px; }

/* The card's own padding is the row's left margin, so the rows sit flush
 * inside it rather than being inset again. */
.report-panel .row { padding: 9px 0; }
.report-panel .empty { padding: 18px 0; }

/* Income against expense, month by month. Divs rather than a charting
 * library: two dozen numbers do not justify a dependency, and a bar whose
 * height is a percentage is a bar that cannot be wrong. */
/* Its own name, not .chart: that already belongs to the dental chart in
 * chart.js, and borrowing it left the tooth diagram inheriting a bar chart's
 * height and the bar chart inheriting the diagram's column direction. */
.report-chart {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  gap: 12px;
  height: 168px;
  overflow-x: auto;
  padding-bottom: 2px;
}

.report-month {
  flex: 1 0 46px;
  display: flex;
  flex-direction: column;
  height: 100%;
}

.report-bars {
  flex: 1;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 4px;
}

.report-bar {
  width: 16px;
  min-height: 3px;
  border-radius: 3px 3px 0 0;
  transition: height 0.25s ease;
}

.report-bar.income { background: var(--green); }
.report-bar.expense { background: var(--amber); }

.report-bar-label {
  margin-top: 8px;
  text-align: center;
  font-size: 10.5px;
  color: var(--muted);
  white-space: nowrap;
}

.report-key {
  display: flex;
  gap: 16px;
  margin-top: 12px;
  font-size: 12px;
  color: var(--muted);
}

.report-key-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  margin-inline-end: 6px;
}

.report-key-dot.income { background: var(--green); }
.report-key-dot.expense { background: var(--amber); }

/* A share of a total, as a bar somebody can read at a glance rather than a
 * percentage they have to compare in their head. */
.meter-row { padding: 9px 0; border-bottom: 1px solid var(--line); }
.meter-row:last-child { border-bottom: 0; padding-bottom: 0; }
.meter-row:first-child { padding-top: 0; }

.meter-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.meter-head strong { font-variant-numeric: tabular-nums; }

.meter {
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--line);
  overflow: hidden;
}

.meter-fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--purple);
  transition: width 0.25s ease;
}

.meter-row .muted { font-size: 11.5px; margin-top: 4px; }

/* Two columns, fixed. Auto-fitting them left the fourth count stranded on a
 * row of its own whenever the panel happened to fit three. */
.mini-stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

.mini-stat {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

.mini-value {
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}

.row-amount.amber { color: #b45309; }

/* A narrow window has no room for two columns, and the chart's bars would be
 * thinner than their own gaps. */
@media (max-width: 720px) {
  .report-grid { grid-template-columns: 1fr; }
  .report-panel { padding: 14px 14px 16px; }
  .report-chart { gap: 8px; }
  .report-month { flex: 1 0 38px; }
}

/* A word in a table that opens something. The rail cannot be reached from
 * inside a cell, and a row you have to read sideways is a row nobody follows. */
.link {
  color: var(--purple);
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}

.link:hover { text-decoration: underline; }

/* A gateway reference is read character by character against a bank
 * statement, which proportional digits make needlessly hard. */
.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  word-break: break-all;
}

/* --------------------------------------------------- the week's notice */

.notice-bar { padding: 12px 22px 0; }

.notice {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 14px;
  border-radius: var(--r-md);
  font-size: 13px;
}

.notice.amber {
  background: var(--amber-bg);
  border: 1px solid var(--amber);
  color: #7c4a03;
}

.notice strong { text-transform: uppercase; font-size: 11px; letter-spacing: 0.05em; }

/* The clinic has not paid, so there is nothing to show but why. One page,
 * centred, saying the date and that nothing has been deleted -- which is the
 * first thing anybody locked out of a patient list wants to know. */
.locked {
  max-width: 520px;
  margin: 12vh auto;
  text-align: center;
}

.locked-mark { font-size: 40px; margin-bottom: 12px; }
.locked h2 { margin: 0 0 10px; font-size: 20px; }
.locked p { margin: 0 0 10px; line-height: 1.55; }

@media print {
  body { background: #ffffff; }

  .app, .rail, .page-bar, .summary, .content,
  .fab, .toast, .rx-side, .modal-head { display: none !important; }

  /* Except the report, which is the one page in here meant to leave the
   * screen on paper. Everything around it still goes: a printed report with
   * a navigation rail down the side of it is a screenshot, not a document. */
  body.view-reports .app { display: block !important; }
  body.view-reports .main { display: block !important; }
  body.view-reports .content { display: block !important; padding: 0 !important; }
  body.view-reports .report { display: flex !important; }
  body.view-reports .report-head button,
  body.view-reports .rail,
  body.view-reports .page-bar,
  body.view-reports .fab { display: none !important; }

  /* Flat on paper, and never split across a page. Shadows print as grey
   * smudges, and a panel broken over a fold is a figure nobody can read. */
  body.view-reports .report-panel,
  body.view-reports .report-stat {
    box-shadow: none;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .modal-backdrop, .modal-overlay {
    position: static;
    display: block;
    background: none;
    backdrop-filter: none;
    padding: 0;
  }

  .modal {
    box-shadow: none;
    border: 0;
    max-width: none;
    max-height: none;
    border-radius: 0;
  }

  .modal-body { padding: 0; overflow: visible; }

  .rx { display: block; }

  .rx-stage {
    background: none;
    padding: 0;
    overflow: visible;
    max-height: none;
    display: block;
  }

  .rx-sheet { box-shadow: none; max-width: none; min-height: 0; width: 100%; }

  /* The ruling is a writing aid on screen; on paper it is somebody else's
   * lines printed under the dentist's words. */
  .rx-body { background-image: none; }
  .rx-body:empty::before { content: none; }

  @page { margin: 0; }
}

/* A table cell that is the way into something, rather than a value to read.
 * The patient's name opens their file; it should look like it does. */
.link-cell {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--purple);
  cursor: pointer;
  text-align: start;
}

.link-cell:hover { text-decoration: underline; }


/* Adding somebody to the clinic: the two ways in, and the fields for each.
 * The panel is swapped rather than the modal reopened, so switching does not
 * lose what has already been typed into the fields the two have in common. */
.staff-modes { align-self: flex-start; }
.staff-fields { display: flex; flex-direction: column; gap: 14px; }
.form-row { display: flex; flex-wrap: wrap; gap: 12px; }
.form-note { line-height: 1.5; }

/* What the receiving dentist already has on, under the time being picked --
 * so a clash is seen while it is being made rather than after it is refused. */
.busy-note { font-size: 12.5px; line-height: 1.5; }

/* ------------------------------------------------------------ the plan
 *
 * What the patient takes home: everything still to do, what each piece
 * costs, and a line for both signatures. Built to print -- the bar above it
 * is the only part that is not the document, and it goes when the document
 * does not.
 */
.plan { display: flex; flex-direction: column; gap: 14px; }

.plan-bar { display: flex; justify-content: flex-end; }

.plan-sheet {
  background: #ffffff;
  color: #14121c;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 28px 30px 34px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.plan-head {
  display: flex;
  align-items: center;
  gap: 16px;
  border-bottom: 2px solid #14121c;
  padding-bottom: 14px;
}

.plan-logo { width: 54px; height: 54px; object-fit: contain; }
.plan-clinic { margin: 0; font-size: 19px; }
.plan-kind { font-size: 13px; letter-spacing: .08em; text-transform: uppercase; opacity: .65; }

/* Pushed to the far edge whichever way the page runs, so the date sits
 * opposite the clinic's name in Arabic as well as in English. */
.plan-when { margin-inline-start: auto; text-align: end; font-size: 13px; }

.plan-who {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 34px;
}

.plan-who > div { display: flex; flex-direction: column; gap: 2px; font-size: 14px; }
.plan-who .muted { font-size: 12px; }

.plan-table { width: 100%; border-collapse: collapse; font-size: 14px; }

.plan-table th,
.plan-table td {
  text-align: start;
  padding: 9px 8px;
  border-bottom: 1px solid #e2dff0;
  vertical-align: top;
}

.plan-table thead th {
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  opacity: .6;
}

.plan-table .num { text-align: end; white-space: nowrap; }
.plan-table tfoot th { border-bottom: 0; border-top: 1px solid #e2dff0; font-size: 15px; }

/* The heavy rule belongs under the last treatment, not under every total. */
.plan-table tfoot tr:first-child th { border-top: 2px solid #14121c; }
.plan-table .muted { font-size: 12.5px; }

.plan-note { font-size: 12.5px; line-height: 1.6; opacity: .75; margin: 0; }

.plan-signatures { display: flex; gap: 40px; margin-top: 26px; }

/* The rule is the line they sign on, so it belongs above the caption rather
 * than under it. */
.plan-sign {
  flex: 1;
  border-top: 1px solid #14121c;
  padding-top: 6px;
  font-size: 12.5px;
  opacity: .7;
}

/* The four spreadsheets, as choices rather than a dropdown: four buttons is
 * one click, and a select is two plus a decision about which is the default. */
.export-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.export-choice { justify-content: flex-start; text-align: start; }

/* A recall whose date has gone by. Tinted rather than badged alone, because
 * the point of the list is to see at a glance how much of it is late. */
.row-due > td { background: color-mix(in srgb, var(--red) 8%, transparent); }

/* A sentence under a field explaining what it is for, where the label alone
 * cannot carry it. */
.field-note { font-size: 12.5px; line-height: 1.5; margin: -6px 0 0; }

@media print {
  /* The plan prints like the prescription does: the sheet, and nothing that
   * is not the sheet. */
  .plan-bar { display: none !important; }

  .plan-sheet {
    border: 0;
    border-radius: 0;
    padding: 0;
    break-inside: auto;
  }

  .plan-table tr { break-inside: avoid; page-break-inside: avoid; }
}

/* The record the clinic already has, shown when somebody types a number that
 * is already on the roll. */
.dupe-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-2, transparent);
}

/* A tooth with a radiograph filed against it. The mark is small and in the
 * corner: which teeth have pictures is a thing to notice while reading the
 * chart, not a thing the chart is about. */
.tooth.has-shots { cursor: pointer; }

.tooth-shots {
  position: absolute;
  inset-inline-end: 0;
  top: 0;
  font-size: 9px;
  line-height: 1;
  opacity: .8;
  pointer-events: none;
}

/* Everything filed against one tooth, opened from the chart. */
.tooth-shots-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 16px;
  margin-top: 12px;
}

.tooth-shot { margin: 0; display: flex; flex-direction: column; gap: 6px; }

.tooth-shot img,
.tooth-shot video {
  width: 100%;
  border-radius: 10px;
  border: 1px solid var(--line);
  /* Radiographs are dark and wildly different shapes; a fixed box that
   * cropped them would cut off the apex, which is the part being looked at. */
  object-fit: contain;
  background: #000000;
}

.tooth-shot figcaption { font-size: 12.5px; line-height: 1.5; }

/* Whose hand is on a treatment row. Quieter than the rest of the line: it is
 * there to be found when somebody asks, not read on every pass. */
.row-hand { font-size: 11.5px; margin-top: 2px; opacity: .8; }

/* ------------------------------------------------------- reading a code
 *
 * The scanner, and what comes back from it. A camera view with a window cut
 * out of it, because a rectangle to aim at is the difference between a
 * scanner people can use and one they hold at arm's length hoping.
 */
.scan { display: flex; flex-direction: column; gap: 12px; }

.scan-frame {
  position: relative;
  border-radius: var(--r-md);
  overflow: hidden;
  background: #10151d;
  aspect-ratio: 4 / 3;
  max-height: 46vh;
}

.scan-view { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The square to aim at. Drawn over the picture rather than around it, so it
 * sits where the camera is actually looking. */
.scan-window {
  position: absolute;
  inset: 50% auto auto 50%;
  width: min(58%, 240px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border: 3px solid rgba(255, 255, 255, 0.9);
  border-radius: 14px;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.38);
}

.scan-nocam {
  display: grid;
  place-items: center;
  height: 100%;
  padding: 24px;
  text-align: center;
  line-height: 1.6;
}

/* The code, typed. Eight characters in three groups, so it is set as a code
 * rather than as a sentence: monospaced, spaced out, and always left to right
 * because it is the same eight characters in either language. */
.scan-typed {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 2px;
  text-align: center;
  text-transform: uppercase;
  direction: ltr;
}

.scan-label { display: block; font-size: 12px; margin-bottom: 4px; }

/* A patient's own code, wherever the panel shows it. Set as a code rather
 * than as text -- monospaced, spaced out, and always left to right, because
 * it is the same eight characters in either language. */
/* One patient's card, opened from the roll. */
.patient-card { display: flex; flex-direction: column; gap: 10px; align-items: center; text-align: center; }
.patient-card > .card-name { font-size: 16px; font-weight: 700; }
.patient-card > p { max-width: 340px; }

.card-square {
  display: grid;
  place-items: center;
  width: 230px;
  height: 230px;
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 10px;
}

/* Never smoothed. A QR is squares, and a browser's idea of a nicer scaling is
 * a blur the camera has to work through. */
.card-qr { width: 100%; height: 100%; image-rendering: pixelated; }

.card-code-big {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 3px;
  direction: ltr;
  unicode-bidi: isolate;
}

/* The quadrants beside the name of the work, on an appointment. Smaller than
 * the one on a casesheet row: there it is the row's subject, here it is one
 * fact on a card that already has a patient and a dentist on it. */
.cal-slot-cross .palmer { font-size: 11px; letter-spacing: 0.04em; }
.cal-slot-cross .palmer-cell { padding: 0 5px; min-width: 14px; }

/* A patient on the roll with nothing written down about them yet. Grey rather
 * than a colour: it is not a state the file is in, it is the absence of one. */
.state-pill.unfiled { background: var(--surface-2); color: var(--muted); }
.state-pill.unfiled .state-dot { background: var(--muted); }

/* ------------------------------------------ what a dentist keeps, in reports */

/* The two colours named once, above the rows, so no row has to carry a
 * caption of its own. */
.split-key { display: flex; gap: 16px; margin-bottom: 10px; font-size: 12px; color: var(--muted); }
.split-key-item { display: inline-flex; align-items: center; gap: 6px; }
.split-dot { width: 9px; height: 9px; border-radius: 50%; }
.split-dot.dentist { background: var(--purple); }
.split-dot.clinic { background: var(--green); }

/* One bar in two parts, so the split is the shape of the row rather than a
 * sum the reader does in their head. Each part is measured against the
 * biggest earner's total, so the rows compare with one another. */
.meter.split { display: flex; overflow: hidden; }
.meter-fill.dentist { background: var(--purple); }
.meter-fill.clinic { background: var(--green); }

.split-who { font-weight: 600; }

.split-line {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 4px;
  font-size: 12.5px;
}

.split-cut { display: inline-flex; align-items: baseline; gap: 3px; }
.split-cut.dentist, .split-cut.dentist strong { color: var(--purple-600); }
.split-cut.clinic, .split-cut.clinic strong { color: #15803d; }

/* The column added up, because the row somebody wants is often the last. */
/* Shaped like the rows above it rather than four figures on one line, which
 * wrapped into nonsense as soon as the card was in a narrow column. */
.split-total {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  font-size: 13px;
}

/* A row of the split that can be pressed for the payments behind it. A button
 * rather than a div, so it is reachable by keyboard and announced as doing
 * something -- reset to look like the row it replaced. */
.meter-row.pressable {
  display: block;
  text-align: inherit;
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 9px 8px;
  margin: 0 -8px;
  width: calc(100% + 16px);
  cursor: pointer;
  transition: background 0.14s ease;
}

.meter-row.pressable:hover,
.meter-row.pressable:focus-visible { background: var(--surface-2, rgba(99, 102, 241, 0.06)); }

/* The second half of a merged card: a heading inside it rather than a card of
 * its own, because it is a second fact about the same money. */
.report-subhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.report-subhead h3 {
  margin: 0;
  font-size: 13.5px;
  font-weight: 700;
}

.report-subhead + .rows { margin-top: 8px; }

/* The payments behind a figure somebody pressed. */
.drill .report-stats { margin-bottom: 14px; }
.drill .table { font-size: 12.5px; }

.card-code {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1px;
  white-space: nowrap;
  direction: ltr;
  unicode-bidi: isolate;
}

.scanned { display: flex; flex-direction: column; gap: 16px; }

.scanned-who { display: flex; align-items: center; gap: 12px; }
.scanned-who .chip { margin-inline-start: auto; }

/* The way in from the sign-in screen. Bottom corner: it is the second thing
 * that screen is for, and it should not compete with signing in. */
.login-scan {
  position: absolute;
  inset-block-end: 22px;
  inset-inline-end: 22px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-1);
}

.login-scan:hover { border-color: var(--purple); color: var(--purple); }
.login-scan-glyph { font-size: 16px; line-height: 1; }

.login { position: relative; }

@media (max-width: 640px) {
  .login-scan { inset-inline: 22px; inset-block-end: 18px; justify-content: center; }
}

/* ------------------------------------------------------ the waiting room
 *
 * The one screen a practice leaves open all day, so it is built to be read
 * from across a room: three columns, big names, and the wait in minutes
 * loud enough to notice from the other side of a desk.
 */
.ward { display: flex; flex-direction: column; gap: 18px; }

/* Who can take the next one, along the top. The half of the board that turns
 * it from a list into a decision. */
.chairs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 12px;
}

.chair {
  padding: 12px 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.chair-who { display: flex; align-items: center; gap: 8px; }

.chair-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--grey, #9ca3af);
  flex: none;
}

.chair.free { border-color: color-mix(in srgb, var(--green) 55%, var(--line)); }
.chair.free .chair-dot { background: var(--green); box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 22%, transparent); }
.chair.free .chair-what { color: var(--green); font-weight: 600; }

.chair.busy .chair-dot { background: var(--amber); }
.chair-what { font-size: 13px; }
.chair-count { font-size: 12px; }

.ward-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: 14px;
  align-items: start;
}

.ward-column {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ward-head { display: flex; align-items: center; gap: 10px; }
.ward-head h2 { margin: 0; font-size: 15px; }

.ward-count {
  margin-inline-start: auto;
  min-width: 26px;
  text-align: center;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--surface-2, rgba(0, 0, 0, 0.05));
  font-size: 12.5px;
  font-weight: 700;
}

/* The column somebody is meant to act on, said with colour rather than with
 * a heading nobody reads twice. */
.ward-waiting { border-color: color-mix(in srgb, var(--amber) 45%, var(--line)); }
.ward-chair { border-color: color-mix(in srgb, var(--green) 40%, var(--line)); }

.ward-list { display: flex; flex-direction: column; gap: 8px; }

.ward-card {
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 10px);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: var(--bg);
}

/* Somebody who has been sitting there too long. The one thing this screen
 * exists to make impossible to miss. */
.ward-card.long { border-color: var(--red); background: color-mix(in srgb, var(--red) 6%, var(--bg)); }

.ward-when { display: flex; align-items: baseline; gap: 8px; font-variant-numeric: tabular-nums; }

.ward-waited {
  font-size: 12.5px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--amber) 18%, transparent);
  color: var(--amber-ink, #7a4a00);
}

.ward-waited.long { background: color-mix(in srgb, var(--red) 16%, transparent); color: var(--red); }
.ward-waited.late { background: color-mix(in srgb, var(--red) 12%, transparent); color: var(--red); }

.ward-tools { display: flex; flex-wrap: wrap; gap: 6px; }
.ward-tools .btn { padding: 5px 10px; font-size: 12.5px; }

/* WhatsApp, in its own colour. It is the button somebody reaches for without
 * reading, and everything this system tells a patient otherwise goes out as a
 * push that most of a clinic's roll has no account to receive. */
.btn.wa { border-color: #25d366; color: #128c4a; }
.btn.wa:hover { background: color-mix(in srgb, #25d366 12%, transparent); }

/* -------------------------------------------------- what must be known
 *
 * An allergy, a blood thinner. Red, wide, and above everything else on
 * whatever screen it appears on: a warning nobody can see at a glance is a
 * warning that gets scrolled past.
 */
.alert-band,
.ward-alert {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: var(--r-sm, 10px);
  background: color-mix(in srgb, var(--red) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--red) 45%, transparent);
  color: var(--red);
  font-weight: 650;
  font-size: 13px;
  line-height: 1.45;
}

.ward-alert { padding: 5px 9px; font-size: 12px; }
.alert-mark { font-size: 15px; line-height: 1; }

/* ---------------------------------------------------- when the clinic opens */

/*
 * Closed, right now.
 *
 * Red rather than amber, and beside the subscription warning rather than
 * below it. A practice is warned about its subscription for a week; this is
 * true at this minute and stops being true at nine o'clock, which is a
 * different kind of thing to say.
 */
.notice.closed {
  background: var(--red-bg);
  border: 1px solid var(--red);
  color: #b91c1c;
}

/* The working week, seven rows of it. */
.hours { display: flex; flex-direction: column; gap: 4px; margin: 10px 0 4px; }

.hours-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 7px 10px;
  border-radius: var(--r-sm);
}

.hours-row:nth-child(odd) { background: var(--surface-2, rgba(127, 127, 127, 0.05)); }

.hours-day {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 140px;
  font-weight: 600;
}

.hours-day input { width: auto; margin: 0; }

.hours-times { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.hours-times input { width: auto; min-width: 0; }

/* A day that is off keeps its times, greyed. Emptying them would mean
 * retyping the whole day to turn it back on for one week's holiday cover. */
.hours-times.is-off { opacity: 0.4; }

.hours-dash { color: var(--muted); }
.hours-break-label { color: var(--muted); font-size: 12px; margin-inline-start: 10px; }

.check { display: flex; align-items: center; gap: 8px; margin: 8px 0; }
.check input { width: auto; margin: 0; }

@media (max-width: 640px) {
  .hours-row { align-items: flex-start; flex-direction: column; gap: 6px; }
  .hours-break-label { margin-inline-start: 0; }
}

/*
 * "Give them a login", inside a table cell.
 *
 * A plain button would draw as much attention as the status control beside
 * it, which it should not: this is a thing worth doing when somebody is
 * standing there, not a thing the row is about.
 */
.link-btn {
  padding: 0;
  border: 0;
  background: none;
  color: var(--purple);
  font-size: 12.5px;
  font-weight: 600;
  text-align: start;
  cursor: pointer;
}

.link-btn:hover { text-decoration: underline; }

/* ------------------------------------------------- the board on the wall */

/*
 * A waiting room television.
 *
 * Sized for four metres rather than forty centimetres, which changes almost
 * every decision: type in the tens of pixels, two columns instead of four, no
 * controls, and a dark ground because a bright rectangle on a wall for eight
 * hours is glare rather than information.
 *
 * Everything here is in viewport units, so the same board fills a 24-inch
 * monitor in a small practice and a 65-inch screen in a large one without
 * anybody choosing a size.
 */
body.tv-open { overflow: hidden; }

.tv {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  flex-direction: column;
  padding: 2.2vh 2.4vw;
  gap: 2vh;
  background:
    radial-gradient(120% 90% at 82% 0%, #2c2a63 0%, transparent 55%),
    radial-gradient(110% 80% at 8% 100%, #123a52 0%, transparent 60%),
    #0d1020;
  color: #f2f4ff;
  font-feature-settings: 'tnum' 1;
}

/* --- the band across the top: who this is, and what time it is */

.tv-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2vw;
  padding-bottom: 1.6vh;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}

.tv-brand { display: flex; align-items: center; gap: 1.1vw; min-width: 0; }
.tv-mark { font-size: 3.4vh; line-height: 1; }

.tv-clinic {
  font-size: 3vh;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Clear of the close button in the corner, so the clock never sits under it. */
.tv-when { text-align: end; padding-inline-end: 5vh; }

.tv-clock {
  font-size: 5.4vh;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  /* Latin digits in a right-to-left page still read left to right. */
  direction: ltr;
  unicode-bidi: isolate;
}

.tv-date { font-size: 1.9vh; color: #98a0c8; margin-top: 0.4vh; direction: ltr; unicode-bidi: isolate; }

/* Small, dim, and in the corner. Present so somebody can get out, absent
 * enough that it is not the first thing a patient looks at. */
.tv-close {
  position: absolute;
  top: 1.4vh;
  inset-inline-end: 1.2vw;
  width: 4vh;
  height: 4vh;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.35);
  font-size: 2.4vh;
  line-height: 1;
  cursor: pointer;
}

.tv-close:hover { background: rgba(255, 255, 255, 0.16); color: #fff; }

/* --- the two columns */

.tv-body {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2.4vw;
  min-height: 0;
}

.tv-col {
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 1.8vh 1.6vw;
  border-radius: 2.2vh;
  background: rgba(255, 255, 255, 0.045);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.tv-col-title {
  margin: 0 0 1.4vh;
  font-size: 2.1vh;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8f9ac9;
}

.tv-list { flex: 1; overflow: hidden; display: flex; flex-direction: column; gap: 1.2vh; }

/* One person in a chair should not sit in the top corner of a tall empty
 * box. The chairs centre; the queue stays at the top, because a queue reads
 * downwards from the front. */
.tv-chair-col .tv-list { justify-content: center; }

.tv-empty {
  margin: auto;
  font-size: 2.4vh;
  color: rgba(255, 255, 255, 0.28);
}

/* --- somebody in a chair: the biggest thing on the screen */

.tv-chair {
  padding: 2.2vh 1.8vw;
  border-radius: 1.8vh;
  background: linear-gradient(135deg, rgba(115, 103, 240, 0.42), rgba(65, 198, 232, 0.28));
  border: 1px solid rgba(255, 255, 255, 0.18);
}

.tv-chair .tv-name { font-size: 5vh; font-weight: 800; line-height: 1.1; }
.tv-chair .tv-with { font-size: 2.4vh; color: rgba(255, 255, 255, 0.82); margin-top: 0.6vh; }

/* --- the queue, with the number that answers the only real question */

.tv-queue-row {
  display: flex;
  align-items: center;
  gap: 1.4vw;
  padding: 1.4vh 1.4vw;
  border-radius: 1.4vh;
  background: rgba(255, 255, 255, 0.05);
}

.tv-place {
  flex: none;
  width: 6vh;
  height: 6vh;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  font-size: 2.8vh;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* Whoever is next. The room reads this line first. */
.tv-queue-row:first-child { background: rgba(255, 255, 255, 0.11); }
.tv-queue-row:first-child .tv-place { background: #41c6e8; color: #06202a; }

.tv-queue-who { flex: 1; min-width: 0; }
.tv-queue-row .tv-name { font-size: 3.2vh; font-weight: 700; line-height: 1.15; }
.tv-queue-row .tv-with { font-size: 1.9vh; color: #98a0c8; }

.tv-at {
  flex: none;
  font-size: 2.6vh;
  font-weight: 700;
  color: #98a0c8;
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
}

/* --- who is free, along the bottom */

.tv-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 1.6vw;
  padding-top: 1.4vh;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.tv-dentist { display: flex; align-items: center; gap: 0.7vw; font-size: 2.1vh; color: #98a0c8; }
.tv-dentist.free { color: #7ee6a8; }

.tv-dot {
  width: 1.2vh;
  height: 1.2vh;
  border-radius: 50%;
  background: #6b7399;
}

.tv-dentist.free .tv-dot { background: #45d483; box-shadow: 0 0 0 0.5vh rgba(69, 212, 131, 0.18); }

.tv-loading { margin: auto; font-size: 3vh; color: rgba(255, 255, 255, 0.4); }

/* --- being called: the whole wall, for twenty seconds */

.tv-call {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.6vh;
  text-align: center;
  padding: 4vh;
  background:
    radial-gradient(80% 70% at 50% 40%, rgba(115, 103, 240, 0.55) 0%, transparent 70%),
    rgba(9, 12, 26, 0.97);
  animation: tv-arrive 420ms ease-out;
}

.tv-call-label {
  font-size: 3vh;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #9fb0ff;
}

/* Deliberately the largest thing this application ever draws. Somebody who
 * looked up late still has to be able to read it from the far wall. */
.tv-call-name {
  font-size: 12vh;
  font-weight: 900;
  line-height: 1.02;
  letter-spacing: -0.02em;
  animation: tv-pulse 2.4s ease-in-out infinite;
}

.tv-call-with { font-size: 3.4vh; color: rgba(255, 255, 255, 0.86); }

@keyframes tv-arrive {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}

@keyframes tv-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.72; }
}

/* A screen somebody has told their browser to keep still. */
@media (prefers-reduced-motion: reduce) {
  .tv-call { animation: none; }
  .tv-call-name { animation: none; }
}

/* Portrait screens, and the small monitor somebody has hung sideways. */
@media (max-aspect-ratio: 1/1) {
  .tv-body { grid-template-columns: 1fr; }
  .tv-chair .tv-name { font-size: 4vh; }
  .tv-call-name { font-size: 8vh; }
}

/* The row above the board that offers the wall screen. */
.ward-tv-bar {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.tv-btn { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; }
.tv-btn-glyph { font-size: 15px; line-height: 1; }
.ward-tv-bar .tv-names { margin: 0; font-size: 13px; color: var(--muted); }

/* Two fields to a row inside a form. The grid is used for the clinic's own
 * details elsewhere; here it keeps a form covering a practice, a person and a
 * payment short enough to see the button at the end of it. */
.modal-body .field-grid > .field { flex: 1 1 200px; min-width: 0; }


/* ================================================================ the diary
 *
 * The appointments page as a week on a clock.
 *
 * A table answers "what is on today". A month answers the shape of a
 * fortnight but cannot say what a Tuesday morning looks like -- and a morning
 * is the thing a practice books into. So: seven columns against an hour
 * scale, where an appointment is a block as long as it lasts. The gaps are
 * drawn to scale, which makes "when am I free" a thing you look at.
 */

.cal { min-width: 0; }

/*
 * The week bar stays put.
 *
 * It scrolled away with the page, so the moment somebody scrolled down to
 * look at the afternoon, the arrows and the zoom went off the top of the
 * screen -- which is why the zoom looked as though it did not exist. Stuck
 * to the top of the content, above the day heads that are stuck under it.
 */
.cal-head {
  position: sticky;
  top: 0;
  z-index: 8;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 0 12px;
  margin-bottom: 0;
  background: var(--bg);
}

.cal-arrow {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  line-height: 1;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.cal-arrow:hover { background: var(--purple-100); color: var(--purple-600); border-color: var(--purple); }

.cal-title {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

/* Pushed to the far end, away from the two arrows it is not one of. */
.cal-today { margin-inline-start: auto; }

/* How much of the day is on the screen: out, in, and the whole of it. */
.cal-zoom {
  display: flex;
  align-items: stretch;
  gap: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.cal-zoom-btn {
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 0 11px;
  min-width: 34px;
  height: 34px;
  cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease;
}

.cal-zoom-btn + .cal-zoom-btn { border-inline-start: 1px solid var(--line); }
.cal-zoom-btn:hover:not(:disabled) { background: var(--purple-100); color: var(--purple-600); }
.cal-zoom-btn:disabled { opacity: 0.4; cursor: default; }

/*
 * The whole window, for the one screen people sit in front of all morning.
 *
 * Named as well as drawn, and the same height as everything else on the bar
 * rather than a 20px glyph in a corner: a control that has to be hunted for
 * is a control most people never find, and this is the one they want most.
 */
.cal-full {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 34px;
  padding: 0 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--ink-soft);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}

.cal-full-mark { font-size: 19px; line-height: 1; }

.cal-full:hover { background: var(--purple-100); color: var(--purple-600); border-color: var(--purple); }

/* Filled in while it is on, so the way back out is the brightest thing on
 * the bar -- which it should be, since the rail it replaced is not there. */
.cal-full.on {
  background: var(--purple);
  border-color: var(--purple);
  color: #fff;
}

.cal-full.on:hover { background: var(--purple-600); color: #fff; }

/*
 * The diary fills the window.
 *
 * The grid was capped at a guess -- the viewport less two hundred and fifty
 * pixels of chrome -- which is right on exactly one window and leaves a strip
 * of nothing under the last hour on every other. Driven by the layout
 * instead: the page bar, the notice and the figures take what they take, and
 * the week has the rest.
 */
/* Which the rail has to live with, on a short screen. */
body.view-diary .rail { overflow-y: auto; }

/*
 * And everything above it gives up what it can.
 *
 * The grid is worth more per pixel than anything around it: a row of chrome
 * is read once in the morning, and an hour of the day is read all day. The
 * page bar, the figures line, the calendar's own bar and the seven day names
 * are all still there and still legible -- each is simply as short as it can
 * be, which is about fifty pixels of diary between them.
 */
body.view-diary .page-bar { padding-top: 9px; padding-bottom: 9px; }
body.view-diary .summary-fold { padding-top: 4px; }
body.view-diary .summary-toggle { padding-top: 3px; padding-bottom: 3px; }
body.view-diary .cal-head { padding: 5px 0 8px; }
body.view-diary .week-head { padding-top: 6px; padding-bottom: 6px; }

body.view-diary .content { padding-top: 0; padding-bottom: 8px; }

/*
 * How tall the week is, is measured rather than declared.
 *
 * The cap used to be a guess at the chrome above it, which is right on one
 * window and leaves a strip of nothing on every other. Calendar.frame() sets
 * it from what is actually left, with a floor -- because "what is left" stops
 * being an answer the moment something above grows. Open the figures out and
 * the old sum gave the week four hours; now the page scrolls and the week
 * keeps its height. The diary is the screen; the strips above it are not.
 */
body.view-diary .week-body { flex: 0 0 auto; max-height: none; }

/* Full screen: the three strips above the diary, which have already been
 * read, give up their room to the grid. */
body.view-diary.cal-full-on .rail,
body.view-diary.cal-full-on .page-bar,
body.view-diary.cal-full-on .notice-bar,
body.view-diary.cal-full-on .summary-fold { display: none; }

body.view-diary.cal-full-on .content { padding: 0 14px 12px; }

/* The one anybody actually wants, said in words rather than as a third
 * symbol nobody would recognise. */
.cal-zoom-btn.fit { font-size: 12.5px; white-space: nowrap; }
.cal-zoom-btn.fit.on { background: var(--purple); color: #fff; }

/* ---------------------------------------------------------------- the week */

.week {
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
  overflow: hidden;
  min-width: 0;
}

/* Seven columns and a gutter for the hours, set once here so the heads and
 * the grid below them cannot drift apart by a pixel. */
.week-sheet {
  --gutter: 74px;
  --line-1: var(--line);
  --line-2: var(--line-soft);
  min-width: 0;
}

/* The seven names, stuck to the top of the scroller they belong to. */
.week-heads {
  position: sticky;
  top: 0;
  z-index: 6;
  /* Under the week bar rather than beside it: they are in different
   * scrollers, so this only has to beat the blocks. */
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}

.week-corner {
  position: sticky;
  top: 0;
  z-index: 7;
  background: var(--surface);
  border-inline-end: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.week-head {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 7px;
  padding: 9px 6px;
  text-align: center;
  border-inline-end: 1px solid var(--line-soft);
}

.week-head:last-child { border-inline-end: 0; }

.week-head-name {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.week-head-date {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

/* How many are booked, as a count rather than a third line: the strip was
 * eighty pixels of chrome over the thing anybody came to look at. */
.week-head-count {
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1;
  padding: 3px 6px;
  border-radius: var(--r-pill);
  background: var(--purple-100);
  color: var(--purple-600);
}

.week-head-count.muted { background: transparent; color: var(--faint); font-weight: 600; }

/* Today, in the circle every calendar puts it in. */
.week-head.today .week-head-date {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-pill);
  background: var(--purple);
  color: #fff;
  font-size: 14px;
}

.week-head.today { background: var(--purple-100); }
.week-head.today .week-head-name { color: var(--purple-600); }

.week-head.shut .week-head-date { color: var(--faint); }

/* ------------------------------------------------------------- the hours */

/*
 * The grid scrolls under the heads, which do not move.
 *
 * A practice looks at its own working hours all day and at seven in the
 * morning about twice a year, so the scale runs from the hour it opens --
 * but anything booked outside those hours stretches it rather than being
 * drawn off the end, and then the scroll is how you reach it.
 */
.week-body {
  display: grid;
  grid-template-columns: var(--gutter) minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-content: start;
  max-height: calc(100vh - 250px);
  min-height: 380px;
  overflow: auto;
  overscroll-behavior: contain;
}

.week-hours {
  position: relative;
  border-inline-end: 1px solid var(--line);
  /* Its own hover, so the buttons appear when the pointer is anywhere down
   * the side of the week rather than only on the nineteen pixels of one. */
  z-index: 2;
}

.week-hour {
  position: absolute;
  inset-inline: 6px 6px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 5px;
  white-space: nowrap;
}

.week-hour-at {
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* The hour the doors open, and the hour they shut. */
.week-hour.door .week-hour-at { color: var(--purple-600); font-weight: 700; }

/*
 * Booking an hour from the hour itself.
 *
 * Clicking an empty stretch of a day has always booked it, which is quick
 * once you know and invisible until somebody tells you. This says so, and
 * says it whether or not anything is under the pointer -- a control that
 * has to be found before it can be seen is a control most people never
 * find.
 *
 * In the colour it does things in, all the time. Grey until pointed at said
 * "not yet" about a button that is perfectly ready, and the one colour this
 * panel uses for something you can press is the purple.
 */
.week-add {
  flex: 0 0 auto;
  width: 19px;
  height: 19px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: var(--purple-100);
  color: var(--purple-600);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease, transform 0.14s ease;
}

/* Filled in under the pointer, which is the only thing left for hover to
 * say now that the resting state says the rest. */
.week-add:hover { background: var(--purple); color: #fff; }

/* An hour the practice does not work keeps the space so the times stay in
 * line, and offers nothing, because there is nothing to offer. */
.week-add.shut { background: transparent; cursor: default; }
.week-add:active { transform: scale(0.9); }

.week-add:focus-visible { outline: 2px solid var(--purple); outline-offset: 1px; }

.week-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
}

/*
 * One line per hour, placed rather than repeated.
 *
 * They were a repeating gradient, which is two rules for however many lines
 * a day needs and exactly right while every hour is the same height. The
 * ruler stretches now wherever a slot needed more room than its own minutes
 * were worth, and a repeat cannot follow that.
 */
.week-line {
  position: absolute;
  inset-inline: 0;
  height: 1px;
  background: var(--line-1);
  pointer-events: none;
}

.week-line.soft { background: var(--line-2); }

.week-col {
  position: relative;
  z-index: 1;
  min-width: 0;
  border-inline-end: 1px solid var(--line-soft);
  cursor: copy;
}

.week-col:last-child { border-inline-end: 0; }
.week-col.today { background: rgba(115, 103, 240, 0.05); }

/*
 * The clinic does not open. Not an error and not empty -- there was never
 * going to be anything here.
 *
 * Flat rather than hatched. Two columns of diagonal stripes is the loudest
 * thing on a screen whose whole job is to show a handful of small coloured
 * blocks, and the day head already says "Closed" in words.
 */
.week-col.shut { cursor: default; }

/*
 * The hours nobody works.
 *
 * The ruler runs midnight to midnight, so a grid that showed four in the
 * morning exactly as it shows four in the afternoon would be saying the
 * practice might see somebody at either. Grey, and no more than grey: these
 * are most of the day on the screen, and anything stronger would drown the
 * hours that matter.
 */
.week-shut {
  position: absolute;
  inset-inline: 0;
  /* A wash of the ink rather than a grey of its own, so it darkens the
   * column in light and in dark without two sets of colours. */
  background: rgba(47, 51, 73, 0.05);
  pointer-events: none;
}

/* The break in the middle of a working day, which is not the same thing as
 * the middle of the night: lighter, because the practice is there. */
.week-shut.rest { background: rgba(47, 51, 73, 0.032); }

/* And a day with nobody in at all, which is the whole column and should not
 * need looking twice at. */
.week-shut.all { background: rgba(47, 51, 73, 0.085); }

/*
 * Where the doors open and shut, as a line across the day.
 *
 * The shading already says which hours are which, but a wash has no edge
 * you can point at -- and "we open at nine" is a line, not a shade. Drawn on
 * the band rather than as something of its own, so it is by construction the
 * same pixel the shading ends on.
 */
.week-shut.opens { border-bottom: 3px solid var(--purple-600); }
.week-shut.closes { border-top: 3px solid var(--purple-600); }

/*
 * The hour named on the line it is drawn at.
 *
 * A rule says where the working day starts and stops; this says what the
 * rule is. In the gutter, beside the hour it names, rather than repeated
 * down every column of the week: seven copies of the same sentence said
 * nothing the first one had not.
 */
.week-door {
  position: absolute;
  inset-inline-end: 0;
  max-width: 100%;
  padding: 1px 5px;
  border-radius: var(--r-pill);
  background: var(--purple-600);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}

/* Above the hour for the one the day starts at, below it for the one it
 * ends at: the same side of the line as the hours it speaks for. */
.week-door.opens { bottom: calc(100% + 2px); }
.week-door.closes { top: calc(100% + 2px); }

/* Arabic joins its letters; spacing them out pulls the word apart. */
[dir="rtl"] .week-door { letter-spacing: 0; }

/* The break is bounded by both, and is the practice stepping out rather
 * than the day beginning: the same line, drawn lighter. */
.week-shut.rest.opens { border-bottom-width: 2px; border-bottom-color: rgba(115, 103, 240, 0.4); }
.week-shut.rest.closes { border-top-width: 2px; border-top-color: rgba(115, 103, 240, 0.4); }

@media (prefers-color-scheme: dark) {
  .week-shut { background: rgba(0, 0, 0, 0.22); }
  .week-shut.rest { background: rgba(0, 0, 0, 0.14); }
  .week-shut.all { background: rgba(0, 0, 0, 0.32); }
}

/* ------------------------------------------------------ one appointment */

.week-ev {
  position: absolute;
  /* The whole width of the day. Clashes stack down it rather than dividing
   * it, so there is nothing to leave room for beside them. */
  inset-inline: 3px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  overflow: hidden;
  padding: 5px 9px 5px 10px;
  border-radius: 8px;
  border-inline-start: 4px solid var(--tone);
  background: var(--tint);
  color: var(--ink);
  cursor: pointer;
  transition: box-shadow 0.15s, transform 0.1s;
}

/*
 * One of several at the same time.
 *
 * Outlined rather than tinted differently: the colour already says what the
 * appointment's status is, and a second meaning on the same colour would be
 * two facts fighting over one signal. A hairline and a hair's gap is enough
 * to read them as a stack.
 */
.week-ev.stacked {
  box-shadow: inset 0 0 0 1px var(--surface);
  margin-bottom: 1px;
}

.week-ev:hover {
  box-shadow: var(--shadow-2);
  transform: translateY(-1px);
  z-index: 4;
}

.week-ev-who {
  font-size: 13px;
  font-weight: 700;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  unicode-bidi: plaintext;
  text-align: start;
}

.week-ev-sub {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
  line-height: 1.3;
  color: var(--ink-soft);
  min-width: 0;
}

.week-ev-at { flex: 0 0 auto; font-weight: 600; font-variant-numeric: tabular-nums; }

.week-ev-doc {
  flex: 0 1 auto;
  text-align: end;
  min-width: 0;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  unicode-bidi: plaintext;
  text-align: start;
}

/* Nobody down for it yet. Never clipped: a dentist's name cut to
 * 'Muhammad S…' is still a name, and 'Needs a…' is not a sentence. */
.week-ev-doc.none { color: var(--amber); flex: 0 0 auto; }

/* The five states, in the colours the rest of the diary uses for them. */
.week-ev.set   { --tone: var(--purple); --tint: var(--purple-100); }
.week-ev.asked { --tone: var(--amber);  --tint: var(--amber-bg); }
.week-ev.here  { --tone: var(--blue);   --tint: var(--blue-bg); }
.week-ev.done  { --tone: var(--green);  --tint: var(--green-bg); }
.week-ev.off   { --tone: var(--faint);  --tint: var(--line-soft); }

.week-ev.off .week-ev-who { text-decoration: line-through; color: var(--muted); }

/* Where the day is up to. Only ever on one column, and only while the hour
 * it points at is on the scale. */
.week-now {
  position: absolute;
  inset-inline: 0;
  height: 2px;
  background: var(--purple);
  z-index: 5;
  pointer-events: none;
}

.week-now-dot {
  position: absolute;
  inset-inline-start: -4px;
  top: -3px;
  width: 8px;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--purple);
}

/* ------------------------------------------------- one, in a dialog */

.cal-slots { display: flex; flex-direction: column; gap: 8px; }

/* Opened out of the grid there is only ever one, and it has the width of the
 * dialog rather than of a side panel. */
.cal-slots.one .cal-slot { background: none; border-color: transparent; padding-inline: 0; }


/*
 * One appointment, as small as it can be and still be read across a desk.
 *
 * Three things at full size -- the time, the patient, the dentist -- because
 * those are what somebody reads off a day. The phone number and the note are
 * what they read after they have found the row, so they are one small line,
 * and no line at all when there is nothing in them.
 */
.cal-slot {
  display: flex;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  border-inline-start: 4px solid var(--purple);
  background: var(--surface-2);
  min-width: 0;
}

.cal-slot.set   { border-inline-start-color: var(--purple); }
.cal-slot.asked { border-inline-start-color: var(--amber); }
.cal-slot.here  { border-inline-start-color: var(--blue); }
.cal-slot.done  { border-inline-start-color: var(--green); }
.cal-slot.off   { border-inline-start-color: var(--faint); opacity: 0.7; }

.cal-slot-when {
  flex: 0 0 48px;
  text-align: center;
  padding-top: 1px;
}

.cal-slot-at {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

.cal-slot.off .cal-slot-at { text-decoration: line-through; }

.cal-slot-body { flex: 1 1 auto; min-width: 0; }

/* The two names. Colour separates them at a glance; the label under the
 * patient's name says which of the two is which, because colour on its own
 * left a card carrying "Sarmad Aziz" over "Mohammed Salah" with nothing to
 * say who was seeing whom. */
.cal-slot-name,
.cal-slot-doc-line {
  display: flex;
  align-items: baseline;
  gap: 5px;
  min-width: 0;
}

.cal-slot-cap {
  flex: 0 0 auto;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}

.cal-slot-who,
.cal-slot-doc {
  display: block;
  font-weight: 700;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  max-width: 100%;
  unicode-bidi: plaintext;
  text-align: start;
}

.cal-slot-who { font-size: 15px; color: var(--ink); flex: 0 1 auto; }
.cal-slot-doc { font-size: 13.5px; color: var(--purple-600); flex: 0 1 auto; }

/* Nobody is down to see them, and this line is how you fix it. */
button.cal-slot-doc {
  padding: 0;
  border: 0;
  background: none;
  color: var(--amber);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.cal-slot-small {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--muted);
  margin-top: 1px;
  min-width: 0;
}

/* Shrinks to its text rather than filling the line, so it starts where the
 * names above it start; the link is pushed to the far end instead. */
.cal-slot-detail {
  flex: 0 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  unicode-bidi: plaintext;
  text-align: start;
}

.cal-slot-link {
  flex: 0 0 auto;
  margin-inline-start: auto;
  padding-inline-start: 7px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--purple-600);
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* One row, always. Four controls in about two hundred and fifty pixels, so
 * they are sized for it -- a Delete that wrapped onto a line of its own was
 * costing every card in the day the height of a button. */
.cal-slot-tools {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: nowrap;
  margin-top: 6px;
}

.cal-slot-tools .btn {
  padding: 3px 7px;
  font-size: 11.5px;
  line-height: 1.4;
  white-space: nowrap;
  flex: 0 0 auto;
}

/* The status is the one control that can give ground: its longest word is
 * "Requested" and it can lose the tail of it before a button loses a letter. */
.cal-slot-tools select {
  font-size: 11.5px;
  padding: 3px 6px;
  padding-inline-end: 18px;
  background-position: right 5px center;
  flex: 0 1 auto;
  /* A floor, not a width. Without one the select is the thing that gives when
   * the row runs out of space, and it gives all of it -- on a narrow panel it
   * shrank to an empty box with an arrow in it. */
  min-width: 86px;
}

[dir="rtl"] .cal-slot-tools select { background-position: left 5px center; }

/* ------------------------------------------------------------- narrow */

/*
 * Seven columns of a working day need room. Under about a thousand pixels the
 * week slides sideways inside its own box rather than squeezing seven columns
 * into a phone -- which would give each of them the width of a word.
 */
@media (max-width: 1000px) {
  .week { overflow-x: auto; overscroll-behavior-x: contain; }
  .week-sheet { min-width: 940px; }
  /* The body scrolls the hours; the box around it scrolls the days. Two
   * scrollers on one axis fight, so the inner one gives that axis up. */
  .week-body { overflow-x: visible; }
}

@media (max-width: 720px) {
  .week-sheet { --gutter: 46px; }
  /* Nothing wider than the hour itself fits in a gutter this narrow. The
   * purple figure still says which hour it is. */
  .week-door { display: none; }
  .week-head { padding: 7px 4px 6px; }
  .week-head-date { font-size: 18px; }
  .week-head.today .week-head-date { width: 28px; height: 28px; }
  .week-body { max-height: calc(100vh - 210px); }

  .cal-slot-when { flex-basis: 44px; }
  .cal-slot-at { font-size: 16px; }

  /* Four controls do not fit across a phone. Two lines here rather than a
   * squeezed row. */
  .cal-slot-tools { flex-wrap: wrap; }
}

/* ------------------------------------------- managing one practice */

/*
 * The platform's side of a clinic, on four tabs: its details, its term,
 * whether it is switched off, and deleting it. Together because they are one
 * conversation -- somebody rings about a practice and what happens next is one
 * of these.
 */
.manage-form { display: flex; flex-direction: column; gap: 12px; }

.manage-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 14px;
}

.manage-term-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.manage-term-head .chip { margin-inline-start: auto; }

.manage-off .notice, .manage-off .form-note, .manage-gone .form-note { margin-bottom: 12px; }

/* The two that cannot be undone by clicking again are the two set apart. */
.manage-gone .field { margin-top: 6px; }

/* The reason a practice was switched off, in the words of whoever did it. */
.locked-why {
  display: inline-block;
  margin: 4px auto 10px;
  padding: 8px 14px;
  border-radius: var(--r-md);
  background: var(--amber-bg);
  border: 1px solid var(--amber);
  color: #7c4a03;
  font-size: 13.5px;
}

/* The reason a practice was switched off, under the sign-in box. */
.login-why {
  margin-top: 8px;
  padding: 8px 12px;
  border-radius: var(--r-sm);
  background: var(--red-bg);
  border: 1px solid var(--red);
  font-size: 13px;
  text-align: start;
}

/* The visit a course of treatment starts with, asked for on the same form.
 * Set apart, because it is the one thing here that is not about the work
 * itself -- it makes a row in the diary. */
.first-visit {
  border: 1px solid var(--purple);
  background: var(--purple-100);
  border-radius: var(--r-md);
  padding: 11px 12px;
}

.first-visit .field-grid { margin-bottom: 6px; }
.first-visit .field-note { margin: 0; }
.first-visit .field > span { color: var(--purple-600); }

/* What the visit is for, on the appointment's own card.
 *
 * A file is not an answer: one with a filling on 21 and an implant on 24 is
 * two different afternoons, and a dentist reading their day should not have to
 * open the record to find out which of them this is. */
.cal-slot-work {
  display: flex;
  /* Centred, not on a baseline: the cross is a block with two rows in it and
   * has no baseline to share with the words beside it. */
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 3px;
  min-width: 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink-soft);
}

.cal-slot-tooth { flex: 0 0 auto; font-size: 11px; opacity: 0.75; }

.cal-slot-for {
  flex: 0 1 auto;
  min-width: 0;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  unicode-bidi: plaintext;
  text-align: start;
}

/* What a course of work cost the practice, under what it was sold for. */
.row-cost { font-size: 11.5px; color: var(--muted); }

/*
 * An empty list with something to do about it.
 *
 * Not the big centred .empty of a whole screen: this one sits inside a
 * settings card between the heading and the buttons, so it stays the size of
 * the two lines it holds and lines up with everything else in the card.
 */
.empty-note {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px 0 4px;
}

.empty-doing { margin-top: 6px; }

/*
 * A value printed as it came: a status, a column name, a path.
 *
 * Left to right whichever way the page is facing, because none of it is
 * prose -- a refusal reordered by the paragraph is a refusal nobody can pass
 * on to the person who can fix it.
 */
.server-value {
  direction: ltr;
  unicode-bidi: isolate;
  font-weight: 600;
  font-size: 12.5px;
}

.server-value.bad { color: var(--red); }

/*
 * The piles a practice sorts its treatments into.
 *
 * One line each: the name in a box, and the two things you can do to it. A
 * table would be three columns of which two are buttons, which is furniture
 * around a list that is rarely longer than four rows.
 */
.group-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
}

.group-line {
  display: flex;
  align-items: center;
  gap: 8px;
}

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

/* The one that adds rather than edits, set apart by a line above it so the
 * empty box does not read as another pile called nothing. */
.group-new {
  margin-top: 6px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.btn.small { padding: 7px 11px; font-size: 12.5px; }

/* Two buttons where the card foot expects one. */
.foot-buttons {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

@media (max-width: 560px) {
  .group-line { flex-wrap: wrap; }
  .group-line input { flex: 1 0 100%; }
}

/*
 * The owner's one tick, under the word "owner" on the team page.
 *
 * Stacked rather than strung along the row: the cell is already narrow, and
 * the line that says what it does is worth more than the width it costs -- a
 * checkbox reading "show as a dentist" says what it is and not what happens.
 */
.sees-patients {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: 12.5px;
  color: var(--ink);
  cursor: pointer;
}

.sees-patients input { width: 15px; height: 15px; cursor: pointer; margin: 0; }

.sees-label { font-weight: 600; }

/* What ticking it does, under the tick rather than beside it. */
.sees-note {
  flex: 1 0 100%;
  color: var(--muted);
  font-size: 11.5px;
}
