/* ====================================================================
   MedDesk — one stylesheet, four front ends.

   The design is NextUp's, carried across whole rather than borrowed
   from: white ground, soft grey hairlines, near-black actions. The
   working accent is not a colour at all — it is ink. Colour is
   RESERVED, and that reservation is the single rule that makes the rest
   of it hold together:

     green   WhatsApp, and work that is finished
     amber   waiting on somebody
     crimson the mark, the flag band, and destruction

   Nothing decorative is red. So when red appears — an allergy, a missed
   appointment, a delete button — it means read this, and it is believed.

   The clinic app, the public booking page, the patient portal and the
   intake form share these tokens and these primitives, then add their
   own layout. A patient who books online and then signs into the portal
   should feel they are in the same building.
   ==================================================================== */

:root {
  /* MedDesk is a light interface and declares it outright. A browser only
     auto-inverts pages that never say what they support, and Samsung
     Internet's force-dark turns a clinic's logo and a patient's photographs
     into negatives. Said here, and in a <meta> tag on every page. */
  color-scheme: light;

  /* --- the working accent: near-black --- */
  --accent:      #101012;
  --accent-deep: #101012;
  --accent-dark: #000000;
  --accent-tint: #F2F2F4;
  --accent-line: #D8D8DE;
  --accent-soft: #F3F3F5;

  /* --- reserved: the Trinidad crimson --- */
  --red:      #CE1126;
  --red-deep: #A50E1E;
  --red-dark: #7E0A17;
  --red-tint: #FDF0F1;
  --red-line: #F2CDD2;

  /* --- neutral ground --- */
  --ink:       #101012;
  /* THE BUTTON BLACK.

     --ink is the text black: #101012, a hair off true black, which is
     right for a paragraph because pure black type on a warm white page
     glares. It is wrong for a large filled rectangle — at that size the
     two per cent of grey in it reads as a wash rather than as softness,
     and a screen of them looks faded. Buttons get their own token and it
     is nearly true black. */
  --btn:       #050506;
  --btn-hover: #1C1C21;
  --ink-2:     #36363C;
  --ink-3:     #6E6E76;
  --ink-4:     #9C9CA3;

  --cream:     #FBFBFC;
  --cream-2:   #F3F3F5;
  --card:      #FFFFFF;
  --line:      #E8E8EB;
  --line-soft: #F1F1F3;

  --green:      #16785A;
  --green-deep: #12634A;
  --green-tint: #E6F4EF;
  --green-line: #BFE3D6;
  --amber:      #A96A12;
  --amber-tint: #FBF0DC;
  --amber-line: #EFD9AE;
  --blue:       #1D4ED8;
  --blue-tint:  #EEF2FF;
  --wa:         #1FA855;
  --wa-deep:    #1B9349;

  /* --- type --- */
  --display: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --body: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* Body text sits at 600, not 400. A regular weight washes out on a
     phone held up in a bright waiting room, and it washes out again on a
     clinic monitor at arm's length across a desk — which is where most
     of this is actually read. Names and labels go to 700 so they can be
     scanned rather than read. The step up was asked for and taken once,
     here, so nothing downstream has to be re-tuned by hand. */
  --w-read: 600;
  --w-name: 700;
  /* The quiet weight, for the sentence under a heading that explains it
     and for anything deliberately receding. Still readable — it is one
     step below the body, not two. */
  --w-soft: 500;

  /* --- shape --- */
  --r-sm: 8px;
  --r:    12px;
  --r-lg: 18px;
  --r-xl: 24px;

  /* Neutral shadows — no warm cast bleeding into the ground. */
  --shadow-sm: 0 1px 2px rgba(16,16,18,.05), 0 1px 3px rgba(16,16,18,.04);
  --shadow:    0 2px 4px rgba(16,16,18,.04), 0 8px 20px -6px rgba(16,16,18,.10);
  --shadow-lg: 0 4px 8px rgba(16,16,18,.05), 0 24px 48px -12px rgba(16,16,18,.18);
  --shadow-red: 0 6px 18px -6px rgba(206,17,38,.40);

  /* --- motion: one language, used everywhere --- */
  --ease:      cubic-bezier(.22, 1, .36, 1);
  --ease-in:   cubic-bezier(.55, 0, 1, .45);
  --ease-soft: cubic-bezier(.4, 0, .2, 1);
  --t-fast: .16s;
  --t:      .3s;
  --t-slow: .6s;

  --sidebar-w: 236px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; overflow-x: clip; }

body {
  font-family: var(--body);
  font-weight: var(--w-read);
  background: var(--cream);
  color: var(--ink);
  line-height: 1.55;
  font-size: 15px;
  min-height: 100vh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3 { font-family: var(--display); font-weight: 700; letter-spacing: -.018em; color: var(--ink); }
h1 { font-size: 26px; line-height: 1.2; }
h2 { font-size: 18px; margin-bottom: 14px; }
h3 { font-size: 15.5px; }
p { margin: 0 0 12px; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent-deep); text-decoration: none; transition: color var(--t-fast) var(--ease); }
a:hover { color: var(--accent); text-decoration: underline; }

:focus-visible { outline: 2.5px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
::selection { background: rgba(16,16,18,.12); }

/* ---------------------------------------------------------- utilities */

.muted { color: var(--ink-3); font-size: 13.5px; }
.small { font-size: 13.5px; }
.tiny  { font-size: 12px; }
.center { text-align: center; }
.right  { text-align: right; }
.nowrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.strong { font-weight: var(--w-name); }
.grow { flex: 1; min-width: 0; }
.hidden, .hide { display: none !important; }
.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .88em;
}
.num { font-family: var(--display); font-weight: 700; font-variant-numeric: tabular-nums; }
.mt { margin-top: 12px; } .mt-2 { margin-top: 22px; }
.mb { margin-bottom: 12px; } .mb-2 { margin-bottom: 22px; }
.row { display: flex; align-items: center; gap: 10px; }
.row.wrap-row { flex-wrap: wrap; }
.between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.stack > * + * { margin-top: 12px; }
.divider { height: 1px; background: var(--line); margin: 16px 0; }
.scroll-x { overflow-x: auto; }

/* The eyebrow. 10.5–12px, uppercase, tracked out, --ink-4. It is the
   most repeated move in the whole design and it is what stops a column
   of headings from all shouting at the same volume. */
.eyebrow, .part-label {
  display: block;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .09em;
  font-weight: 700; color: var(--ink-4); margin: 20px 0 8px;
}
.part-label:first-child { margin-top: 0; }
/* A paragraph's own bottom margin does not stack with the label's top one
   inside a flex or grid parent, and the two end up touching. Said
   explicitly rather than relied upon. */
p + .part-label, p + .eyebrow { margin-top: 22px; }

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

.grid { display: grid; gap: 16px; }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 860px) { .grid.cols-3, .grid.cols-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .grid.cols-2 { grid-template-columns: 1fr; } }
@media (max-width: 420px) { .grid.cols-3, .grid.cols-4 { grid-template-columns: 1fr; } }

.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  margin-bottom: 22px; padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
.page-head .sub { color: var(--ink-3); font-size: 14px; margin-top: 2px; }

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

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 20px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--t) var(--ease), border-color var(--t) var(--ease);
  animation: rise .5s var(--ease) both;
}
.card:hover { box-shadow: var(--shadow); }
.card.flat { animation: none; }
.card.flush { padding: 0; overflow: hidden; }

.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px;
}
.card-head h2, .card-head h3 { margin-bottom: 0; }
.card-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-left: auto; }
.card-tools select { width: auto; min-width: 150px; margin-top: 0; padding: 7px 10px; font-size: 13px; }
@media (max-width: 640px) {
  .card-tools { margin-left: 0; width: 100%; }
  .card-tools select { flex: 1 1 100%; min-width: 0; }
}

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

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

   One button, one colour: black fill, white text, everywhere.

   The request was literal and the result is better than what it
   replaced. MedDesk had four button treatments — filled, outlined,
   tinted green, tinted red — and on a screen with five of them the
   filled one stopped meaning "this is the action" and started meaning
   nothing at all. With every button the same weight, what tells them
   apart is the word on the face of it, which is the thing people
   actually read.

   The two exceptions are hover states, not resting states. A delete
   still turns red under the cursor and a completion still turns green,
   because a destructive press should say so at the moment somebody is
   about to make it — and neither costs the flat black anything when
   nobody is pointing at it. */
.btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 10px 17px;
  font-family: inherit; font-size: 14.5px; font-weight: 700; line-height: 1.2;
  color: #FFFFFF;
  background: var(--btn);
  border: 1.5px solid var(--btn);
  border-radius: var(--r);
  cursor: pointer; white-space: nowrap; text-decoration: none;
  /* Black at 600 weight on a screen reads thinner than the same black at
     700 — the strokes let the fill through. Every button here is the
     same fill, so weight is doing real work and it goes up. */
  -webkit-font-smoothing: antialiased;
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease);
}
/* The lift only where a cursor exists. A phone has no hover, and the
   button otherwise sticks in its raised form after a tap. */
@media (hover: hover) {
  .btn:hover { transform: translateY(-1px); box-shadow: var(--shadow); background: var(--btn-hover);
               border-color: var(--btn-hover); color: #fff; text-decoration: none; }
}
.btn:active { transform: translateY(0) scale(.975); }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none; box-shadow: none; }

.btn.primary { background: var(--btn); border-color: var(--btn); color: #fff;
               box-shadow: 0 6px 16px -8px rgba(0,0,0,.55); }
.btn.primary:hover { background: var(--btn-hover); border-color: var(--btn-hover); color: #fff; }
.btn.dark { background: var(--btn); border-color: var(--btn); color: #fff; }
.btn.dark:hover { background: var(--btn-hover); border-color: var(--btn-hover); color: #fff; }
/* WhatsApp keeps its own green. It is not one of MedDesk's buttons — it
   is somebody else's brand, and a black one reads as "send" rather than
   as "send by WhatsApp", which is a different promise. */
.btn.wa { background: var(--wa); border-color: var(--wa); color: #fff; }
.btn.wa:hover { background: var(--wa-deep); border-color: var(--wa-deep); color: #fff; }
.btn.danger { color: #fff; border-color: var(--btn); background: var(--btn); }
.btn.danger:hover { background: var(--red); border-color: var(--red); color: #fff; }
.btn.good { color: #fff; border-color: var(--btn); background: var(--btn); }
.btn.good:hover { background: var(--green); border-color: var(--green); color: #fff; }
/* Ghost WAS the same black at 82% opacity, which is exactly the mistake
   that made the whole set look washed out: opacity does not make a black
   button quieter, it makes it a muddy grey, and next to a real black one
   both of them look wrong. It is the same solid black now. What makes it
   quiet is that it is smaller and sits second. */
.btn.ghost { background: var(--btn); border-color: var(--btn); color: #fff; }
.btn.ghost:hover { background: var(--btn-hover); border-color: var(--btn-hover); color: #fff; }

.btn.small { padding: 6px 11px; font-size: 13px; border-radius: var(--r-sm); }
/* A full-width button is always the one decision on its screen. The
   capsule is what makes it read as *the* action. */
.btn.wide { width: 100%; border-radius: 999px; padding: 12px 20px; }
.btn.block { width: 100%; }
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
@media (max-width: 860px) {
  .btn { padding: 11px 16px; }
  .btn.small { padding: 8px 12px; font-size: 13.5px; }
}

/* The everyday completion — the other capsule, and the only other one. */
.btn.done-btn {
  background: var(--btn); color: #fff; border-color: var(--btn);
  padding: 10px 20px; border-radius: 999px;
}
.btn.done-btn:hover { background: var(--green-deep); border-color: var(--green-deep); color: #fff; }
.tick-i {
  display: inline-block; width: 6px; height: 11px; margin: -2px 3px 0 0;
  border: solid #fff; border-width: 0 2.5px 2.5px 0; transform: rotate(45deg);
}

/* ------------------------------------------------------------- forms */

label { display: block; margin-bottom: 14px; font-size: 13px; font-weight: var(--w-name); color: var(--ink-2); }
label .hint { font-weight: var(--w-soft); color: var(--ink-3); }

input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]):not([type=submit]):not([type=button]),
select, textarea {
  width: 100%; margin-top: 6px; padding: 10px 12px;
  font-family: inherit; font-size: 15px; font-weight: var(--w-read); color: var(--ink);
  background: var(--card);
  border: 1.5px solid var(--line);
  border-radius: var(--r);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease);
  -webkit-appearance: none; appearance: none;
}
select {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%236E6E76' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 12px;
  padding-right: 34px;
}
textarea { min-height: 84px; resize: vertical; line-height: 1.5; }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(16,16,18,.12);
}
input::placeholder, textarea::placeholder { color: var(--ink-4); }
input[type="range"] { width: 100%; margin-top: 8px; accent-color: var(--accent); }
input[type="file"] { font-size: 13.5px; margin-top: 6px; }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.form-grid .full { grid-column: 1 / -1; }

/* 16px is mandatory on a phone. It is the whole of what stops iOS
   zooming the page the moment a field takes focus. */
@media (max-width: 860px) {
  body { font-size: 15.5px; }
  h1 { font-size: 23px; }
  input:not([type=checkbox]):not([type=radio]):not([type=file]),
  select, textarea { font-size: 16px; padding: 12px 13px; }
  .form-grid { grid-template-columns: 1fr; }
}

/* No custom switch anywhere. Every boolean is a native checkbox tinted
   with accent-color, in a row you can tap the whole of. */
.check-row {
  display: flex; align-items: flex-start; gap: 9px;
  font-weight: 500; color: var(--ink-2); cursor: pointer; margin: 2px 0 16px;
  font-size: 14.5px;
}
.check-row input[type="checkbox"] {
  width: 18px; height: 18px; margin: 2px 0 0; flex: 0 0 18px;
  accent-color: var(--accent); -webkit-appearance: checkbox; appearance: checkbox;
}
.check-row b { color: var(--ink); font-weight: 600; }
.check-row em { display: block; font-style: normal; font-size: 12.5px;
                line-height: 1.45; color: var(--ink-3); margin-top: 1px; }
.check-box {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 13px; margin-bottom: 6px;
  background: var(--cream-2); border: 1px solid var(--line-soft); border-radius: var(--r-sm);
  cursor: pointer;
}
.check-box input[type="checkbox"] { margin-top: 3px; flex: 0 0 auto; accent-color: var(--accent); }
.check-box b { display: block; font-size: 14.5px; font-weight: 600; }
.check-box em { display: block; margin-top: 2px; font-style: normal;
                font-size: 12.5px; line-height: 1.45; color: var(--ink-3); }

/* The password strength meter. Four bars, and the colour is earned rather
   than decorative: nothing until it is usable, amber at fair, green when
   it is genuinely strong. A meter that goes green at "Passw0rd!" has
   taught somebody the wrong thing about their own safety. */
.pw-meter { margin: -8px 0 16px; }
/* pw-bars, not bars. The reports page already owns .bars and gives it a
   140px height for a column chart — reusing the name here made the
   strength meter a hundred and sixty pixels tall. */
.pw-meter .pw-bars { display: flex; gap: 4px; margin-bottom: 6px; }
.pw-meter .pw-bars i {
  height: 4px; flex: 1; border-radius: 2px; background: var(--line);
  transition: background var(--t-fast) var(--ease);
}
.pw-meter .pw-bars i.on { background: var(--ink-4); }
.pw-meter .pw-bars i.on.s1 { background: var(--red); }
.pw-meter .pw-bars i.on.s2 { background: var(--amber); }
.pw-meter .pw-bars i.on.s3 { background: var(--green); }
.pw-meter .pw-bars i.on.s4 { background: var(--green-deep); }
.pw-meter .pw-say { line-height: 1.45; display: block; }
.pw-meter .pw-say.good { color: var(--green-deep); }
.pw-meter .pw-say.warn { color: var(--amber); }

/* A password or a link, read aloud: monospace on a dashed ground, so it
   is plainly a value to be copied rather than prose to be read. */
.pw-box {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px; font-weight: 600; letter-spacing: .01em;
  background: var(--cream-2); border: 1.5px dashed var(--accent-line);
  border-radius: var(--r); padding: 12px 14px; color: var(--ink);
  word-break: break-all; white-space: pre-wrap; line-height: 1.6;
}

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

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 100px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .01em;
  border: 1px solid transparent; white-space: nowrap;
}
.badge::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }

.badge.booked, .badge.confirmed { background: var(--green-tint); color: var(--green); border-color: var(--green-line); }
.badge.pending, .badge.requested { background: var(--amber-tint); color: var(--amber); border-color: var(--amber-line); }
.badge.pending::before, .badge.requested::before { animation: pulseRing 2s var(--ease-soft) infinite; }
.badge.info { background: var(--blue-tint); color: var(--blue); border-color: #C7D2FE; }
.badge.grey, .badge.cancelled { background: var(--cream-2); color: var(--ink-3); border-color: var(--line); }
.badge.bad, .badge.no_show { background: #F6E9E9; color: #8C3B3B; border-color: #E8CACA; }
.badge.ink { background: var(--accent-tint); color: var(--accent-dark); border-color: var(--accent-line); }

/* Done is settled, not something needing attention — no fill, just a tick. */
.badge.completed {
  background: transparent; color: var(--ink-2); border-color: transparent;
  padding-left: 0; gap: 6px;
}
.badge.completed::before {
  content: ''; width: 5px; height: 9px;
  border: solid var(--green); border-width: 0 2px 2px 0; border-radius: 0;
  background: none; transform: rotate(45deg); margin: -2px 1px 1px 1px;
}

.chip-n {
  display: inline-block; margin-left: 8px; padding: 1px 8px;
  border-radius: 100px; background: var(--cream-2);
  font-size: 11.5px; font-weight: 600; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* Toggleable filter chips. The tick is inserted, not implied by colour
   alone — a chip that only changes shade is a chip half the room cannot
   read. */
.chip-row { display: flex; flex-wrap: wrap; gap: 7px; }
.chip {
  padding: 6px 12px; border-radius: 100px;
  border: 1.5px solid var(--line); background: var(--card); color: var(--ink-3);
  font-family: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  transition: all var(--t-fast) var(--ease);
}
.chip:hover { border-color: var(--ink-4); color: var(--ink); }
.chip.on { background: var(--accent-tint); border-color: var(--accent-line); color: var(--accent-dark); }
.chip.on::before { content: '✓ '; }

/* Scrollable pill tabs, and the iOS-style segmented control. */
.tabs {
  display: flex; gap: 6px; margin-bottom: 16px;
  overflow-x: auto; padding-bottom: 4px;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  flex: 0 0 auto; padding: 9px 16px; border-radius: 100px;
  border: 1.5px solid var(--line); background: var(--card); color: var(--ink-3);
  font-family: inherit; font-size: 13.5px; font-weight: 600;
  white-space: nowrap; cursor: pointer;
  transition: all var(--t-fast) var(--ease);
}
.tabs button:hover { border-color: var(--ink-4); color: var(--ink); }
.tabs button.on { background: var(--accent-tint); border-color: var(--accent-line); color: var(--accent-dark); }
.tabs.small button { padding: 6px 13px; font-size: 12.5px; }

.seg {
  display: inline-flex; padding: 3px; gap: 3px;
  background: var(--cream-2); border: 1px solid var(--line); border-radius: 100px;
}
.seg button {
  padding: 7px 18px; border: none; border-radius: 100px; background: transparent;
  color: var(--ink-3); font-family: inherit; font-size: 13.5px; font-weight: 600;
  cursor: pointer; transition: all var(--t-fast) var(--ease);
}
.seg button:hover { color: var(--ink); }
.seg button.on { background: var(--card); color: var(--accent-dark); box-shadow: var(--shadow-sm); }
@media (max-width: 520px) { .seg { display: flex; width: 100%; } .seg button { flex: 1; } }

/* ------------------------------------------------------------- discs

   Rounded-square plates for organisations, circles for people. A clinic
   has a mark; a practitioner has a face. */

.av {
  flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--cream-2); color: var(--ink-3);
  font-family: var(--display); font-size: 11px; font-weight: 600; letter-spacing: .02em;
  line-height: 1; background-size: cover; background-position: center;
  box-shadow: inset 0 0 0 1px rgba(16,16,18,.06);
}
.av.sm { width: 22px; height: 22px; font-size: 9.5px; }
.av.lg { width: 40px; height: 40px; font-size: 14px; }
.av.xl { width: 72px; height: 72px; font-size: 24px; }
.av.plate { border-radius: 11px; }
.av.lg.plate { border-radius: 12px; }
.av.xl.plate { border-radius: 18px; }
.av.ink { background: var(--accent-tint); color: var(--accent-dark); }

/* The universal name tag: disc and name as one inline object, so a name
   never wraps away from the face beside it. */
.tag { display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%; vertical-align: middle; }
.tag .nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--w-name); }
td .tag { max-width: 24ch; }

/* -------------------------------------------------------- the brand */

.brand {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--display); font-weight: 700; font-size: 21px;
  letter-spacing: -.02em; color: var(--ink); min-width: 0;
}
.brand-mark {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 9px;
  background: var(--card) center/cover no-repeat url('/icons/icon-192.png');
  box-shadow: var(--shadow-red);
  transition: transform var(--t) var(--ease); font-size: 0;
}
.brand:hover .brand-mark { transform: rotate(-6deg) scale(1.05); }
.brand-mark.lg { width: 56px; height: 56px; flex-basis: 56px; border-radius: 14px; }
/* A clinic's own logo loses the crimson glow and gains a hairline. That
   shadow belongs to MedDesk and would tint somebody else's brand. */
.brand-mark.own { box-shadow: none; border: 1px solid var(--line); }
.brand-names { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.brand-names > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brand-sub {
  font-family: var(--body); font-weight: 500; font-size: 12px;
  color: var(--ink-3); letter-spacing: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

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

table { width: 100%; border-collapse: collapse; font-size: 14.5px; }
th {
  text-align: left; padding: 9px 12px;
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .07em;
  font-weight: 600; color: var(--ink-4);
  border-bottom: 1.5px solid var(--line);
}
td { padding: 12px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
tr:last-child td { border-bottom: none; }
tbody tr { animation: riseSm .4s var(--ease) both; }
tbody tr:nth-child(1) { animation-delay: .01s; }
tbody tr:nth-child(2) { animation-delay: .04s; }
tbody tr:nth-child(3) { animation-delay: .07s; }
tbody tr:nth-child(4) { animation-delay: .10s; }
tbody tr:nth-child(5) { animation-delay: .13s; }
tbody tr:nth-child(n+6) { animation-delay: .16s; }
tr.clickable { cursor: pointer; transition: background var(--t-fast) var(--ease); }
tr.clickable:hover td { background: var(--cream-2); }
tr.is-you td { background: var(--accent-tint); }

/* A five-column table cannot be read at 360px. Below 640 every row
   becomes a card and every cell carries its own heading, copied into
   data-label by the app. */
@media (max-width: 860px) {
  table { font-size: 13.5px; table-layout: auto; }
  td, th { padding: 9px 7px; word-break: break-word; }
}
@media (max-width: 640px) {
  table.stacked { display: block; font-size: 14px; }
  table.stacked thead { display: none; }
  table.stacked tbody, table.stacked tr, table.stacked td { display: block; width: 100%; }
  table.stacked tr {
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
    padding: 4px 13px 10px; margin-bottom: 9px; box-shadow: var(--shadow-sm);
  }
  table.stacked tr:last-child { margin-bottom: 0; }
  table.stacked td {
    display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
    gap: 2px 14px; padding: 7px 0;
    border: none; border-bottom: 1px solid var(--line-soft);
    text-align: left; word-break: break-word;
  }
  table.stacked td:last-child { border-bottom: none; }
  table.stacked td::before {
    content: attr(data-label); flex: 0 0 auto;
    color: var(--ink-3); font-size: 12.5px; font-weight: 600; white-space: nowrap;
  }
  table.stacked td.cell-actions { display: flex; justify-content: flex-start; gap: 7px;
                                  flex-wrap: wrap; padding-top: 11px; }
  table.stacked td.cell-actions::before { display: none; }
  table.stacked td.cell-actions .btn { flex: 1 1 auto; min-width: 92px; justify-content: center; }
  table.stacked tr.clickable:hover td { background: transparent; }
  table.stacked tr.clickable:active { border-color: var(--accent-line); background: var(--accent-tint); }
}

/* ------------------------------------------------------------- rows */

/* The generic clickable list row, shared by the admin register, the
   practitioner list, and the recall book. */
.row-item {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 4px;
  border-bottom: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  animation: riseSm .42s var(--ease) both;
  transition: background var(--t-fast) var(--ease);
}
.row-item:last-child { border-bottom: none; }
.row-item:hover { background: var(--cream-2); }
.row-item.off { opacity: .58; }
/* The pieces of a row — who it is on the left, the figures on the right —
   are shared with the summary line of an expandable group, because a
   clinic with four practitioners and a clinic with one are the same row
   wearing a different hat. Scoped to one and not the other, a group
   header loses every rule and the numbers run into each other with no
   spacing at all, which is exactly what it did. */
.row-item .who, details.group > summary .who { flex: 1; min-width: 0; }
.row-item .who b, details.group > summary .who b { font-size: 15px; font-weight: 600; }
.row-item .who .sub, details.group > summary .who .sub {
  display: block; color: var(--ink-3); font-size: 12.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row-item .figs, details.group > summary .figs { display: flex; gap: 20px; flex-wrap: wrap; }
.row-item .fig, details.group > summary .fig { text-align: right; min-width: 62px; }
.row-item .fig .n, details.group > summary .fig .n {
  display: block; font-family: var(--display); font-weight: 700; font-size: 17px;
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.row-item .fig .k, details.group > summary .fig .k {
  display: block; font-size: 10px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--ink-4); font-weight: 600;
}
.row-item .acts, details.group > summary .acts {
  display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end;
  flex: 0 0 auto; min-width: 150px;
}
@media (max-width: 640px) {
  .row-item, details.group > summary { flex-wrap: wrap; }
  .row-item .who, details.group > summary .who { flex: 1 1 100%; order: 1; }
  .row-item > .av, details.group > summary > .av { order: 0; }
  .row-item .figs, details.group > summary .figs {
    flex: 1 1 100%; order: 2; justify-content: flex-start; gap: 16px;
  }
  .row-item .fig, details.group > summary .fig { text-align: left; min-width: 0; }
  .row-item .acts { flex: 1 1 100%; order: 3; justify-content: flex-start; }
}

/* --------------------------------------------------------- the diary

   The left status spine: a coloured 3–4px border encoding state, instead
   of a coloured background. A tinted row shouts; a spine tells you at a
   glance and then gets out of the way. */

.appt {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 13px 15px;
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: var(--r);
  cursor: pointer;
  animation: riseSm .4s var(--ease) both;
  transition: box-shadow var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
@media (hover: hover) { .appt:hover { box-shadow: 0 4px 16px rgba(20,24,28,.07); } }
.appt:active { transform: scale(.995); }
.appt.completed { border-left-color: var(--green); opacity: .72; }
/* A request is not an alarm. It was a dashed amber edge, which on a
   Monday morning gave the page thirty flickering brown outlines and made
   the diary underneath them look like the quiet part. One solid black
   line along the edge, the same as everything else, and the amber is left
   to the badge — where one small dot of it still means "waiting on
   somebody" and is actually believed. */
.appt.requested { border-left-color: var(--accent); border-style: solid; }
.appt.arrived, .appt.in_progress { border-left-color: var(--green); background: var(--green-tint); }
/* Called, and walking down the corridor. Amber, because amber in MedDesk
   means waiting on somebody — here, on the patient to reach the door. */
.appt.called { border-left-color: var(--amber); background: var(--amber-tint); }
.appt.no_show { border-left-color: #B4534F; background: var(--red-tint); }
.appt.cancelled { border-left-color: var(--ink-4); opacity: .55; }

.appt .t { flex: 0 0 58px; display: flex; flex-direction: column; align-items: flex-start; line-height: 1; padding-top: 1px; }
.appt .t .hr { font-family: var(--display); font-weight: 700; font-size: 21px; color: var(--ink);
               font-variant-numeric: tabular-nums; }
.appt .t .ap { font-size: 11px; font-weight: 700; letter-spacing: .05em; color: var(--ink-3); margin-top: 3px; }
.appt .body { flex: 1; min-width: 0; }
.appt .body .top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* In a diary column there are barely 200px to play with, and a name cut to
   "Ani…" is a card nobody can read at a glance — which is the only thing a
   day column is for. Names wrap to a second line here and the status badge
   drops beneath them; the left spine has already said the status in colour
   before anybody reads the word. */
.diary .appt .body .top { flex-wrap: wrap; row-gap: 5px; }
.diary .appt .body .top .tag .nm { white-space: normal; overflow: visible; line-height: 1.3; }
.appt .body .svc { color: var(--ink-3); font-size: 13px; margin-top: 2px; }
.appt .body .warn { color: var(--red-deep); font-size: 12.5px; font-weight: 600; margin-top: 2px; }
.appt .body .acts { margin-top: 11px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
@media (max-width: 560px) {
  .appt .t { flex-basis: 50px; }
  .appt .t .hr { font-size: 19px; }
  .appt .body .acts .btn.done-btn { flex: 1 1 100%; justify-content: center; order: -1; }
}

/* The gap between two appointments, named. A front desk fitting somebody
   in is looking for the holes, not the rows. */
.gap {
  font-size: 11.5px; color: var(--ink-4); text-align: center; padding: 3px 0;
  border-top: 1px dashed var(--line); border-bottom: 1px dashed var(--line);
  font-weight: 600; letter-spacing: .02em;
}

/* The hero. One appointment, pulled out of the day. */
.next-card {
  display: flex; align-items: center; gap: 16px;
  padding: 15px 18px;
  background: var(--card);
  border: 1.5px solid var(--accent-line);
  border-left: 4px solid var(--accent);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  animation: rise .45s var(--ease) both;
  margin-bottom: 14px;
}
.next-card .when { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; flex: 0 0 auto; }
.next-card .lbl { font-size: 10.5px; text-transform: uppercase; letter-spacing: .09em;
                  font-weight: 700; color: var(--accent-deep); }
.next-card .tm { font-family: var(--display); font-weight: 700; font-size: 24px;
                 color: var(--ink); font-variant-numeric: tabular-nums; line-height: 1.15; }
.next-card .who { flex: 1; min-width: 0; }
.next-card .who .nm { display: block; font-size: 15.5px; font-weight: 600; }
/* .tag is inline-flex, so a bare sibling span sits on its line and the
   service name runs straight into the patient's surname. Block, and the
   two lines the design assumed actually appear. */
.next-card .who .svc { display: block; color: var(--ink-3); font-size: 13px; margin-top: 2px; }
/* Equal-width action columns — three words of different lengths
   otherwise give three buttons of three widths and ragged gaps. */
.next-card .acts {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px;
  flex: 0 0 auto; min-width: 300px;
}
.next-card .acts .btn { justify-content: center; width: 100%; }
@media (max-width: 860px) {
  .next-card { flex-wrap: wrap; gap: 10px; }
  .next-card .acts { width: 100%; min-width: 0; }
}

/* ------------------------------------------------------- alert lines */

.alert-line {
  display: block;                    /* not flex — it is a sentence and must wrap like one */
  padding: 11px 15px; margin-bottom: 14px;
  background: var(--accent-tint); border: 1px solid var(--accent-line);
  border-radius: var(--r); color: var(--accent-dark);
  font-size: 14px; font-weight: 600; line-height: 1.5; text-decoration: none;
  cursor: pointer; width: 100%; text-align: left; font-family: inherit;
  animation: riseSm .4s var(--ease) both;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.alert-line b { font-size: 16px; }
.alert-line .cta { display: block; margin-top: 3px; color: var(--accent-deep); font-weight: 600; }
.alert-line:hover { background: #E7E7EB; text-decoration: none; transform: translateY(-1px); }
.alert-line.quiet { background: var(--card); border-color: var(--line); color: var(--ink-2); font-weight: 500; }
.alert-line.quiet b { font-size: 15px; color: var(--ink); }
.alert-line.quiet .cta { color: var(--ink-3); }
.alert-line.quiet:hover { background: var(--cream-2); }
.alert-line.warn { background: var(--red-tint); border-color: var(--red-line); color: var(--red-dark); }
.alert-line.warn:hover { background: #FBE3E5; }

/* An allergy or a standing alert. Loud on purpose — this is the one
   thing on a patient's card that must survive being glanced at. */
.alert-strip {
  display: flex; gap: 8px; align-items: center;
  background: var(--red-tint); border: 1px solid var(--red-line); color: var(--red-dark);
  border-radius: var(--r); padding: 9px 13px; font-size: 14px; font-weight: 600;
  margin-bottom: 12px;
}
.alert-strip.amber { background: var(--amber-tint); border-color: var(--amber-line); color: var(--amber); }
.error {
  padding: 10px 13px; background: var(--red-tint); border: 1px solid var(--red-line);
  border-radius: var(--r); color: var(--red-dark);
  font-size: 13.5px; font-weight: 500; margin-bottom: 14px;
  animation: riseSm var(--t) var(--ease) both;
}
.ok-note {
  padding: 10px 13px; background: var(--green-tint); border: 1px solid var(--green-line);
  border-radius: var(--r); color: var(--green-deep); font-size: 13.5px; font-weight: 500;
  margin-bottom: 14px;
}
.note {
  padding: 11px 14px; background: var(--accent-tint); border: 1px solid var(--accent-line);
  border-radius: var(--r); color: var(--accent-dark); font-size: 13.5px; line-height: 1.55;
}

/* --------------------------------------------------------- stat tiers */

.stat-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; margin-bottom: 14px;
}
.mini-stat {
  display: flex; flex-direction: column; gap: 1px;
  padding: 10px 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow-sm); text-decoration: none; min-width: 0;
  animation: riseSm .4s var(--ease) both;
  font-family: inherit; text-align: left; cursor: default;
}
button.mini-stat, a.mini-stat { cursor: pointer; }
button.mini-stat:hover, a.mini-stat:hover { border-color: var(--ink-4); text-decoration: none; }
.mini-stat .k {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em;
  font-weight: 600; color: var(--ink-4);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mini-stat .v {
  font-family: var(--display); font-weight: 700; font-size: 19px; color: var(--ink);
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.mini-stat .v .of { color: var(--ink-4); font-weight: 600; }
.mini-stat .v.bad { color: var(--red-deep); }
.mini-stat .v.good { color: var(--green); }
@media (max-width: 860px) {
  .stat-strip { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .mini-stat { padding: 9px 12px; }
  .mini-stat .v { font-size: 17px; }
}

.stat .label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .09em;
               font-weight: 600; color: var(--ink-4); }
.stat .value { font-family: var(--display); font-size: 30px; font-weight: 700; line-height: 1.15;
               margin-top: 6px; color: var(--ink); font-variant-numeric: tabular-nums; }
.stat .value.accent { color: var(--accent-deep); }
.stat .value.bad { color: var(--red-deep); }
.stat .note-under { margin-top: 5px; font-size: 11.5px; font-weight: 600; color: var(--ink-4); }
@media (max-width: 860px) { .stat .value { font-size: 25px; } }

/* Four figures, dividers only, no boxes. */
.glance {
  display: grid; grid-template-columns: repeat(4, 1fr);
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden; margin-bottom: 14px;
}
.glance > div { padding: 13px 8px; text-align: center; border-right: 1px solid var(--line-soft); }
.glance > div:last-child { border-right: none; }
.glance small { display: block; color: var(--ink-3); font-size: 11.5px; }
.glance b { display: block; margin-top: 3px; font-family: var(--display);
            font-size: 19px; font-weight: 600; font-variant-numeric: tabular-nums; }
.glance b.bad { color: var(--red-deep); }
@media (max-width: 560px) { .glance { grid-template-columns: repeat(2, 1fr); } }

/* ------------------------------------------------------------ charts */

.bars { display: flex; align-items: flex-end; gap: 3px; height: 140px; padding-top: 6px;
        border-bottom: 1px solid var(--line); }
.bars .bar { flex: 1 1 0; min-width: 2px; min-height: 2px; border-radius: 3px 3px 0 0;
             background: var(--accent); opacity: .82; transition: opacity var(--t-fast) var(--ease); }
.bars .bar:hover { opacity: 1; }
.bar-labs { display: flex; gap: 3px; margin-top: 6px; }
.bar-labs span { flex: 1 1 0; min-width: 0; text-align: center;
                 font-size: 10.5px; font-weight: 600; color: var(--ink-4); }

.hbars { display: grid; gap: 9px; }
.hb { display: grid; grid-template-columns: minmax(70px, 150px) 1fr auto auto;
      align-items: center; gap: 10px; padding: 3px 6px; border-radius: var(--r-sm); }
.hb.you { background: var(--accent-tint); }
.hb .nm { font-size: 13.5px; font-weight: var(--w-name); color: var(--ink-2);
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hb .track { height: 12px; border-radius: 100px; background: var(--cream-2); overflow: hidden; }
.hb .fill { display: block; height: 100%; border-radius: 100px; background: var(--accent); opacity: .85; }
.hb .val { font-size: 13.5px; font-weight: 600; color: var(--ink);
           font-variant-numeric: tabular-nums; white-space: nowrap; }
.hb .pct { min-width: 38px; text-align: right; font-size: 12.5px; font-weight: 600;
           color: var(--ink-4); font-variant-numeric: tabular-nums; }
@media (max-width: 560px) {
  .hb { grid-template-columns: 1fr auto auto; row-gap: 4px; }
  .hb .nm { grid-column: 1 / -1; }
  .hb .track { grid-column: 1; }
}

/* The money block. The heavier rule is what marks the total. */
.ms-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; padding: 9px 0; border-bottom: 1px solid var(--line-soft); font-size: 14.5px;
}
.ms-row:last-of-type { border-bottom: none; }
.ms-row .k { color: var(--ink-3); }
.ms-row b { font-variant-numeric: tabular-nums; font-weight: 600; text-align: right; }
.ms-row.total { border-top: 1.5px solid var(--line); border-bottom: none;
                margin-top: 4px; padding-top: 12px; font-size: 16px; }
.ms-row.total b { font-family: var(--display); font-weight: 700; font-size: 21px; color: var(--accent-deep); }

/* ------------------------------------------------------------ modals */

.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(28,26,23,.32);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  display: grid; place-items: center; padding: 20px; z-index: 100;
  animation: fade var(--t) var(--ease) both;
  overscroll-behavior: contain;
}
.modal {
  width: 100%; max-width: 480px; max-height: 88vh; overflow-y: auto;
  background: var(--card); border-radius: var(--r-xl); padding: 26px;
  box-shadow: var(--shadow-lg); animation: popIn .36s var(--ease) both; position: relative;
}
.modal.wide { max-width: 620px; }
.modal.xwide { max-width: 820px; }
.modal h2 { margin-bottom: 18px; }
.modal-actions { display: flex; gap: 9px; justify-content: flex-end; margin-top: 20px; flex-wrap: wrap; }
.modal-x {
  position: absolute; top: 18px; right: 18px;
  background: none; border: none; font-size: 22px; line-height: 1;
  color: var(--ink-4); cursor: pointer; padding: 2px 6px; border-radius: 8px;
}
.modal-x:hover { background: var(--cream-2); color: var(--ink); }
@media (max-width: 860px) { .modal { max-height: 92vh; padding: 22px 18px; } }

/* The asked-a-question modal, as used on the public booking page.

   A question needs more air than a form does, because there is almost
   nothing in it: a line, a fact, a sentence, two buttons. Packed to the
   same density as a screen with eleven fields it reads as cramped — the
   buttons end up against the text and the thing being confirmed is
   indistinguishable from the explanation of it. So: more padding, the
   fact set apart in its own box, and the buttons given a rule and real
   space above them. */
.modal.ask { max-width: 440px; padding: 30px 30px 26px; }
.modal.ask h2 { font-size: 20px; margin-bottom: 12px; line-height: 1.28; }
.modal.ask .ask-detail {
  background: var(--cream-2); border: 1px solid var(--line-soft);
  border-radius: var(--r); padding: 12px 14px; margin-bottom: 14px;
  font-weight: var(--w-name); color: var(--ink);
}
.modal.ask p.muted { line-height: 1.6; margin-bottom: 0; }
.modal.ask .modal-actions {
  margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--line-soft);
}
.modal.ask .modal-actions .btn { min-width: 112px; justify-content: center; }
@media (max-width: 560px) {
  .modal.ask { padding: 24px 20px 20px; }
  .modal.ask .modal-actions { flex-direction: column-reverse; }
  .modal.ask .modal-actions .btn { width: 100%; }
}

/* The grouped choice sheet: options grouped by consequence, each with a
   sentence saying what it does, danger set apart at the bottom. It
   replaced a row of five identical grey rectangles with no sense of
   which were routine and which were serious. */
.sheet-head {
  display: flex; align-items: center; gap: 14px;
  padding-bottom: 16px; margin-bottom: 6px; border-bottom: 1px solid var(--line);
}
.sheet-head h2 { margin: 0; font-size: 20px; }
.sheet-head .muted { margin-top: 2px; }
.sheet-head .badge { margin-left: auto; align-self: flex-start; }
.sheet-group { margin-top: 18px; }
.sheet-title {
  font-family: var(--display); font-size: 11px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 8px;
}
.sheet-group.danger .sheet-title { color: var(--red-deep); }
.sheet-item {
  display: flex; align-items: center; gap: 13px; width: 100%;
  padding: 13px 14px; margin-bottom: 8px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  text-align: left; cursor: pointer; font-family: inherit; color: inherit;
  transition: border-color var(--t-fast) var(--ease-soft),
              background var(--t-fast) var(--ease-soft), transform var(--t-fast) var(--ease);
}
.sheet-item:last-child { margin-bottom: 0; }
.sheet-item:active { transform: scale(.99); }
@media (hover: hover) { .sheet-item:hover { border-color: var(--accent-line); background: var(--accent-tint); } }
.sheet-item .txt { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.sheet-item .txt b { font-size: 15px; font-weight: var(--w-name); }
.sheet-item .txt small { color: var(--ink-3); font-size: 12.5px; margin-top: 1px; }
.sheet-item .caret { flex: none; color: var(--ink-4); font-size: 18px; }
.sheet-item .ic {
  flex: none; width: 36px; height: 36px; border-radius: 10px;
  display: grid; place-items: center; font-size: 16px;
  background: var(--accent-tint); color: var(--accent-deep);
}
.sheet-item.bad { border-color: var(--red-line); }
.sheet-item.bad .txt b { color: var(--red-dark); }
.sheet-item.bad .ic { background: var(--red-tint); color: var(--red-deep); }
@media (hover: hover) { .sheet-item.bad:hover { border-color: var(--red-deep); background: var(--red-tint); } }

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

.toast {
  position: fixed; bottom: 22px; right: 22px; z-index: 200;
  display: flex; align-items: center; gap: 9px;
  padding: 12px 17px;
  background: var(--ink); color: #fff;
  font-size: 14px; font-weight: 500;
  border-radius: var(--r); box-shadow: var(--shadow-lg);
  animation: slideInR .34s var(--ease) both;
  max-width: min(360px, calc(100vw - 44px));
}
.toast::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: #E8E8EB; flex: 0 0 7px; }
.toast.err { background: var(--red-dark); }
.toast.err::before { background: #FFC2C9; }
@media (max-width: 860px) {
  .toast { left: 16px; right: 16px; bottom: calc(20px + env(safe-area-inset-bottom)); max-width: none; }
}

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

.empty { display: grid; place-items: center; gap: 10px; padding: 44px 20px; text-align: center;
         animation: rise var(--t-slow) var(--ease) both; }
.empty .mark {
  width: 52px; height: 52px; border-radius: 14px;
  background: var(--card) center/cover no-repeat url('/icons/icon-192.png');
  opacity: .22; filter: grayscale(.3);
}
.empty h3 { font-size: 16px; }
.empty p { color: var(--ink-3); font-size: 14px; max-width: 36ch; margin: 0; }

/* ------------------------------------------------------ loading bits */

.skeleton {
  border-radius: var(--r-sm); min-height: 15px;
  background: linear-gradient(100deg, var(--cream-2) 30%, #FAFAFC 50%, var(--cream-2) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.4s var(--ease-soft) infinite;
}
.spinner {
  width: 17px; height: 17px; border: 2.5px solid rgba(255,255,255,.35);
  border-top-color: #fff; border-radius: 50%; animation: spin .7s linear infinite;
}
.loading-wrap { display: grid; place-items: center; gap: 14px; padding: 54px 0;
                animation: fade var(--t) var(--ease) both; }

/* The pulse loader. NextUp's barber pole names its trade; this one is a
   heartbeat, drawn in the same two colours and nothing else. */
.pulse-bar {
  width: 168px; height: 5px; border-radius: 100px; overflow: hidden;
  background-image: repeating-linear-gradient(
    115deg, var(--red) 0 14px, #fff 14px 22px, var(--ink) 22px 36px, #fff 36px 44px);
  background-size: 58px 100%;
  animation: poleRun 1.1s linear infinite;
}
@keyframes poleRun { to { background-position: 58px 0; } }

/* ---------------------------------------------------------- the flag */

.auth-wrap { min-height: 100vh; min-height: 100dvh; display: grid; place-items: center; padding: 24px; }
.auth-card {
  width: 100%; max-width: 400px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-xl);
  padding: 34px 30px 28px; box-shadow: var(--shadow-lg);
  animation: popIn var(--t-slow) var(--ease) both;
  position: relative; overflow: hidden;
}
.auth-card.wide { max-width: 560px; }
.auth-card::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 5px;
  background: linear-gradient(90deg, var(--red) 0 42%, var(--ink) 42% 58%, var(--red) 58% 100%);
  /* Clipped to the card's own corners. Without this the band runs square
     across a rounded top and hangs off both ends like a sticker. */
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.auth-card { overflow: hidden; }
.auth-card .brand { justify-content: center; font-size: 26px; margin-bottom: 6px; }
.auth-card form { margin-top: 20px; }

/* -------------------------------------------------------- slot grids */

.slot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(98px, 1fr)); gap: 8px; }
.slot {
  border: 1.5px solid var(--line); background: var(--card); border-radius: var(--r);
  padding: 10px 4px; font: inherit; font-size: 14px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  cursor: pointer; text-align: center; color: var(--ink);
  transition: all var(--t-fast) var(--ease);
}
.slot:hover { border-color: var(--ink-4); transform: translateY(-1px); }
.slot:active { transform: scale(.97); }
.slot.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.slot .who { display: block; font-size: 10.5px; font-weight: 500; color: var(--ink-3); margin-top: 2px; }
.slot.on .who { color: rgba(255,255,255,.75); }

.date-strip { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 4px;
              scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.date-strip::-webkit-scrollbar { display: none; }
.date-chip {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 12px 7px; min-width: 66px;
  border: 1px solid transparent; border-radius: 12px; background: var(--cream-2);
  cursor: pointer; font-family: inherit;
  transition: transform var(--t-fast) var(--ease), background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.date-chip:hover { transform: translateY(-1px); background: var(--accent-tint); }
.date-chip .d { display: block; font-size: 10.5px; font-weight: 700; text-transform: uppercase;
                letter-spacing: .04em; color: var(--ink-3); }
.date-chip .n { display: block; font-family: var(--display); font-weight: 700; font-size: 17px;
                color: var(--ink); line-height: 1; }
.date-chip .m { display: block; font-size: 10.5px; color: var(--ink-3); font-weight: 600; }
.date-chip.on { background: var(--accent); border-color: var(--accent); }
.date-chip.on .d, .date-chip.on .m { color: rgba(255,255,255,.85); }
.date-chip.on .n { color: #fff; }
.date-chip.on:hover { background: var(--accent); }

/* --------------------------------------------------------- choices */

.choice {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  padding: 12px 14px;
  background: var(--card); border: 1.5px solid var(--line); border-radius: var(--r);
  cursor: pointer; font-family: inherit; color: var(--ink);
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.choice:hover { border-color: var(--ink-4); }
.choice:active { transform: scale(.99); }
.choice.on { border-color: var(--accent); background: var(--accent-tint); }
.choice .t { display: block; font-size: 14.5px; font-weight: 600; }
.choice .s { display: block; font-size: 12.5px; color: var(--ink-3); margin-top: 1px; }
.choice .p { font-weight: 600; font-size: 14px; color: var(--ink-2);
             font-variant-numeric: tabular-nums; white-space: nowrap; }
.choice.on .t, .choice.on .p { color: var(--accent-dark); }
.choice-list { display: flex; flex-direction: column; gap: 7px; }

/* ---------------------------------------------- collapsible groupings */

details.group { border-bottom: 1px solid var(--line-soft); }
details.group:last-child { border-bottom: none; }
details.group.off { opacity: .58; }
details.group > summary {
  display: flex; align-items: center; gap: 14px;
  padding: 13px 4px; cursor: pointer; list-style: none;
  border-radius: var(--r-sm); transition: background var(--t-fast) var(--ease);
}
details.group > summary::-webkit-details-marker { display: none; }
details.group > summary:hover { background: var(--cream-2); }
.caret {
  flex: 0 0 auto; display: grid; place-items: center; color: var(--ink-4);
  width: 14px; font-size: 15px; line-height: 1;
  transition: transform var(--t-fast) var(--ease);
}
details.group[open] > summary .caret { transform: rotate(90deg); }
.group-body { padding-left: 26px; border-left: 2px solid var(--line-soft);
              margin-left: 22px; margin-bottom: 6px; }
.group-body .row-item .av { border-radius: 50%; }
/* Stands where the buttons stand on child rows, so a parent's totals
   line up over its children's. */
.summary-spacer { flex: 0 0 auto; width: 150px; }
@media (max-width: 640px) {
  .summary-spacer { display: none; }
  details.group > summary { flex-wrap: wrap; }
  .group-body { padding-left: 12px; margin-left: 10px; }
}

/* Collapsible day bands. */
details.band { border-bottom: 1px solid var(--line-soft); }
details.band:last-child { border-bottom: none; }
details.band > summary {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 4px; cursor: pointer; list-style: none;
  font-size: 12px; text-transform: uppercase; letter-spacing: .08em;
  font-weight: 700; color: var(--ink-3); user-select: none;
}
details.band > summary::-webkit-details-marker { display: none; }
details.band > summary::before {
  content: ''; width: 7px; height: 7px; flex: 0 0 7px;
  border-right: 2px solid var(--ink-4); border-bottom: 2px solid var(--ink-4);
  transform: rotate(-45deg); transition: transform var(--t-fast) var(--ease);
}
details.band[open] > summary::before { transform: rotate(45deg); }
details.band .band-n {
  margin-left: auto; min-width: 22px; padding: 1px 7px;
  border-radius: 100px; background: var(--cream-2); border: 1px solid var(--line);
  text-align: center; font-size: 11.5px; color: var(--ink-2); letter-spacing: 0;
}
details.band > div { display: flex; flex-direction: column; gap: 8px; padding-bottom: 12px; }

/* ------------------------------------------------------- key / value */

.kv { display: grid; grid-template-columns: minmax(120px, 36%) 1fr; gap: .4rem .9rem; font-size: 14px; }
.kv dt { color: var(--ink-3); }
.kv dd { margin: 0; font-weight: var(--w-name); word-break: break-word; }

/* --------------------------------------------------------- the bars

   Impersonation and demonstration. Fixed, black, and impossible to
   ignore — which is the point. Support work is visible while it is
   happening. */
.top-bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  padding: 9px 16px;
  background: var(--ink); color: #fff;
  font-size: 13.5px; font-weight: 500;
  box-shadow: 0 2px 12px -2px rgba(28,26,23,.4);
  animation: riseSm var(--t) var(--ease) both;
}
.top-bar b { font-weight: 700; }
.top-bar .btn {
  padding: 5px 11px; font-size: 12.5px;
  background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.28); color: #fff;
}
.top-bar .btn:hover { background: #fff; color: var(--ink); border-color: #fff; }
.top-bar .dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--red); flex: none;
  animation: pulseRingRed 2s var(--ease-soft) infinite;
}
@media (max-width: 560px) { .top-bar { font-size: 12.5px; gap: 8px; padding: 8px 10px; } }

/* -------------------------------------------------- reduced motion */

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

/* -------------------------------------------------------- keyframes */

@keyframes rise     { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes riseSm   { from { opacity: 0; transform: translateY(7px); }  to { opacity: 1; transform: none; } }
@keyframes fade     { from { opacity: 0; } to { opacity: 1; } }
@keyframes popIn    { 0% { opacity: 0; transform: translateY(18px) scale(.97); } 100% { opacity: 1; transform: none; } }
@keyframes slideInR { from { opacity: 0; transform: translateX(26px); } to { opacity: 1; transform: none; } }
@keyframes shimmer  { to { background-position: 220% 0; } }
@keyframes spin     { to { transform: rotate(360deg); } }
@keyframes growBar  { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes pulseRing {
  0%   { box-shadow: 0 0 0 0 rgba(16,16,18,.26); }
  70%  { box-shadow: 0 0 0 12px rgba(16,16,18,0); }
  100% { box-shadow: 0 0 0 0 rgba(16,16,18,0); }
}
@keyframes pulseRingRed {
  0%   { box-shadow: 0 0 0 0 rgba(206,17,38,.34); }
  70%  { box-shadow: 0 0 0 12px rgba(206,17,38,0); }
  100% { box-shadow: 0 0 0 0 rgba(206,17,38,0); }
}

/* ==================================================================
   The bell, and the panel under it.

   Placed inside the brand row so it belongs to the practice's furniture
   rather than floating over whatever screen is open. The dot is the only
   thing on this rail allowed to appear without being asked for, so it is
   small, it counts, and it goes away when the work is done.
   ================================================================== */

/* The corner the bell sits in on a wide screen.

   Fixed, and a direct child of #app rather than of the rail — the rail's
   backdrop-filter creates a stacking context, and no z-index inside one
   can lift a panel above the page's own cards. This is why the panel used
   to render underneath the text. */
.bell-corner {
  position: fixed; top: 16px; right: 20px; z-index: 95;
}
@media (max-width: 860px) { .bell-corner { display: none; } }

.bell-host { margin-left: auto; position: relative; flex: 0 0 auto; }

.bell {
  position: relative; width: 38px; height: 38px;
  display: grid; place-items: center;
  /* Over the page rather than on the rail now, so it carries its own
     ground — a transparent button floating over a table of patient names
     is a button nobody finds. */
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  color: var(--ink-3); cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.bell:hover { background: var(--cream-2); color: var(--ink); border-color: var(--line); }
.bell[aria-expanded="true"] { background: var(--cream-2); color: var(--ink); border-color: var(--line); }

.bell .dot {
  position: absolute; top: -2px; right: -2px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 9px; background: var(--accent); color: #fff;
  border: 2px solid var(--card);
  font-size: 10.5px; font-weight: 700; font-style: normal;
  display: grid; place-items: center; font-variant-numeric: tabular-nums;
  animation: pulseRing 2.4s var(--ease-soft) infinite;
}
.bell .dot.hidden { display: none; }

.bell-pop {
  /* Hung off the bell's right edge, which is where there is room now that
     the bell is in the top-right corner rather than in a 236px rail. */
  position: absolute; top: calc(100% + 8px); right: 0; left: auto; z-index: 95;
  width: min(340px, calc(100vw - 32px));
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: 0 24px 60px -22px rgba(28,26,23,.42), 0 2px 8px -4px rgba(28,26,23,.18);
  overflow: hidden;
  animation: rise .2s var(--ease) both;
}
/* On a phone the bell sits in the top bar, and a panel anchored to it
   would hang off the right edge of the screen. */
/* On a phone the bell is in the mobile bar, already at the top right,
   and the panel spans the width of the bar rather than hanging off it. */
@media (max-width: 860px) {
  .bell-pop {
    position: fixed; top: calc(62px + env(safe-area-inset-top));
    right: 12px; left: 12px; width: auto; z-index: 95;
  }
}

.bell-body { max-height: min(70vh, 520px); overflow-y: auto; }
.bell-head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 14px 10px; border-bottom: 1px solid var(--line-soft);
}
.bell-head b { font-size: 14px; flex: 1; }
.bell-label {
  padding: 12px 14px 4px; font-size: 10.5px; font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-4);
}
.bell-work { padding: 6px; display: flex; flex-direction: column; gap: 2px; }

.bell-job {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px; border: none; background: var(--accent-tint); border-radius: var(--r);
  font: inherit; text-align: left; color: var(--ink); cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.bell-job:hover { background: var(--accent-tint-2, var(--cream-2)); }
.bell-job .ico { color: var(--accent); width: 20px; height: 20px; display: grid; place-items: center; }
.bell-job .ico svg { width: 20px; height: 20px; }
.bell-job span { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.bell-job b { font-size: 13.5px; font-weight: 600; }
.bell-job em { font-style: normal; font-size: 12px; color: var(--ink-4); }
.bell-job .go { flex: 0 0 auto; color: var(--ink-4); font-size: 15px; display: block; }

.bell-list { padding: 2px 6px 8px; display: flex; flex-direction: column; }
.bell-row {
  display: flex; align-items: flex-start; gap: 12px; width: 100%; padding: 9px 8px;
  border: none; background: none; border-radius: var(--r-sm);
  font: inherit; text-align: left; cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.bell-row:hover { background: var(--cream-2); }
/* The narrative takes the room. `min-width: 0` is what lets it, inside a
   flex row — without it a long title refuses to wrap and pushes the time
   out of the panel, which is what this row used to do. */
.bell-row .t { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.bell-row .t b {
  font-size: 13px; font-weight: 500; color: var(--ink-2); line-height: 1.35;
  overflow-wrap: anywhere;
}
.bell-row .t em {
  font-style: normal; font-size: 12px; color: var(--ink-4); line-height: 1.4;
  overflow-wrap: anywhere;
  /* Two lines of the body and then an ellipsis. The whole message is on
     the screen the row opens; the panel is an index, not an inbox. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.bell-row.fresh .t b { font-weight: 700; color: var(--ink); }
/* And the time, right, on the title's own line — never wrapping, never
   taking a column away from the words. */
.bell-row .w {
  flex: 0 0 auto; margin-left: auto; padding-top: 2px;
  font-size: 11.5px; color: var(--ink-4); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* An unread one is marked on the left edge rather than by colour alone. */
.bell-row.fresh { position: relative; }
.bell-row.fresh::before {
  content: ''; position: absolute; left: 0; top: 12px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--ink);
}
.bell-row.fresh .t { padding-left: 10px; }

.bell-empty { padding: 26px 18px 30px; text-align: center; }
.bell-empty b { display: block; font-size: 14px; margin-bottom: 3px; }
.bell-empty span { font-size: 12.5px; color: var(--ink-4); }

/* The WhatsApp mark sits on the button's own baseline, not above it. */
.wa-i { vertical-align: -3px; margin-right: 7px; flex: 0 0 auto; }
.btn.wa .wa-i { color: #fff; }

/* ================================================== template editors */

.tpl-row {
  display: flex; align-items: flex-start; gap: 12px; width: 100%;
  padding: 12px 14px; border: none; background: none;
  border-bottom: 1px solid var(--line-soft);
  font: inherit; text-align: left; cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.tpl-row:last-child { border-bottom: none; }
.tpl-row:hover { background: var(--cream-2); }
.tpl-row .tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.tpl-row .tx b { font-size: 14px; }
.tpl-row .tx em { font-style: normal; font-size: 12.5px; color: var(--ink-4); }
/* The preview is the point of the row: two lines of the sentence this
   actually sends, so nobody has to open six of them to find one. */
.tpl-row .pv {
  margin-top: 5px; font-size: 12.5px; color: var(--ink-3); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tpl-row .caret { color: var(--ink-4); font-size: 17px; align-self: center; }

.tpl-inner { margin-top: 10px; padding-left: 4px; }
.muted-card .tpl-inner { display: none; }
.muted-card { opacity: .55; }

.tpl-field {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 7px 0; border-top: 1px solid var(--line-soft);
}
.tpl-field input[type="text"], .tpl-field input:not([type]), .tpl-field select {
  margin: 0; flex: 1 1 160px; min-width: 0;
}
.tpl-field input[type="checkbox"] { flex: 0 0 auto; margin: 0; }
.tpl-field label.tiny {
  display: flex; align-items: center; gap: 5px; margin: 0; flex: 0 0 auto;
  font-size: 12px; color: var(--ink-4);
}
.tpl-field .hidden { display: none; }

/* ================================================ the reports page */

.rpt-filters .chip-row { margin-bottom: 0; }
.rpt-filters .form-grid label { margin-bottom: 0; }

.rpt-stat .value { line-height: 1.05; }
.rpt-stat .delta {
  margin-top: 6px; font-size: 12.5px; font-weight: 600;
  color: var(--ink-4); display: flex; align-items: baseline; gap: 5px;
  font-variant-numeric: tabular-nums;
}
.rpt-stat .delta .arrow { font-size: 13px; }
.rpt-stat .delta.up { color: var(--green); }
.rpt-stat .delta.down { color: var(--red-deep); }

/* A chart is drawn at a fixed viewBox and scaled, so the same code makes
   a readable picture on a phone and on a front desk's 27-inch screen.
   Below 640px it scrolls rather than squashing: forty bars in 320px is a
   texture, not a chart. */
.chart-wrap { width: 100%; overflow-x: auto; }
.chart { width: 100%; height: 200px; display: block; min-width: 480px; }
.chart rect { transition: opacity var(--t-fast) var(--ease); }
.chart g:hover rect { opacity: 1; }

.legend { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink-4); }
.legend .k { width: 9px; height: 9px; border-radius: 2px; display: inline-block; margin-left: 8px; }
.legend .k.accent { background: var(--accent); }
.legend .k.line { background: var(--line); }

.mom-strip {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
  margin-top: 14px; background: var(--line-soft);
  border: 1px solid var(--line-soft); border-radius: var(--r); overflow: hidden;
}
.mom-strip > div { background: var(--card); padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; }
.mom-strip .k { font-size: 11px; color: var(--ink-4); }
.mom-strip b { font-size: 15px; font-variant-numeric: tabular-nums; }
.mom-strip b.good { color: var(--green); }
.mom-strip b.bad { color: var(--red-deep); }
.mom-strip b.muted { color: var(--ink-4); font-weight: 500; font-size: 13px; }
@media (max-width: 560px) { .mom-strip { grid-template-columns: repeat(2, 1fr); } }

/* The busy-hours grid. Opacity on one colour rather than a rainbow: a
   heat map with five hues in it needs a key, and a key is a thing
   nobody reads. */
.heat { display: grid; gap: 2px; min-width: 420px; align-items: center; }
.heat .hh { font-size: 9.5px; color: var(--ink-4); text-align: center; }
.heat .hd { font-size: 11px; color: var(--ink-3); font-weight: 600; }
.heat .hc {
  height: 20px; border-radius: 3px; background: var(--accent);
  transition: transform var(--t-fast) var(--ease);
}
.heat .hc:hover { transform: scale(1.14); }

/* An invoice line, being corrected. Description takes what is left after
   the two number boxes, which are sized to what actually goes in them. */
.inv-line {
  display: flex; gap: 7px; align-items: center;
  padding: 6px 0; border-top: 1px solid var(--line-soft);
}
.inv-line input { margin: 0; }
.inv-line .il-d { flex: 1 1 auto; min-width: 0; }
.inv-line .il-q { flex: 0 0 54px; text-align: right; }
.inv-line .il-u { flex: 0 0 92px; text-align: right; font-variant-numeric: tabular-nums; }

/* The end-of-consultation list. Three rows, each one press, and the ones
   already done step back rather than disappearing — a list that shrinks
   as you work it makes people wonder what they missed. */
.wrap-list { display: flex; flex-direction: column; gap: 8px; margin: 16px 0 4px; }
.wrap-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border: 1.5px solid var(--line); border-radius: var(--r-lg);
  background: var(--card);
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.wrap-row:hover { border-color: var(--ink-4); }
.wrap-row.done { border-color: var(--line-soft); background: var(--cream); }
.wrap-row.done .ic { color: var(--green); }
.wrap-row .ic { color: var(--accent); display: grid; place-items: center; flex: 0 0 auto; }
.wrap-row .ic svg { width: 20px; height: 20px; }
.wrap-row .tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.wrap-row .tx b { font-size: 14px; }
.wrap-row .tx em { font-style: normal; font-size: 12.5px; color: var(--ink-4); }
@media (max-width: 480px) {
  .wrap-row { flex-wrap: wrap; }
  .wrap-row .btn { width: 100%; }
}

/* The portal's "come through" banner. The only thing in this whole
   application that is allowed to move on its own. */
.called-banner {
  display: flex; align-items: center; gap: 14px;
  padding: 16px 18px; margin-bottom: 18px;
  border-radius: var(--r-lg);
  background: var(--green-tint); border: 1.5px solid var(--green-line);
  animation: rise .4s var(--ease) both;
}
.called-banner .pulse {
  flex: 0 0 12px; width: 12px; height: 12px; border-radius: 50%;
  background: var(--green); animation: pulseRing 1.6s var(--ease-soft) infinite;
}
.called-banner b { display: block; font-size: 16px; color: var(--green-deep, var(--ink)); }
.called-banner span { font-size: 13.5px; color: var(--ink-2); }

/* ==================================================================
   The links card in Settings.

   A practice hands out several addresses — the booking page, its own
   patient portal, one per practitioner who takes bookings by name. They
   are read down a telephone and pasted into WhatsApp, so each is shown
   in full and in a monospaced face, where a lowercase l and a 1 are
   different shapes.
   ================================================================== */

.link-list { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }

.link-row {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  padding: 13px 0; border-bottom: 1px solid var(--line-soft);
}
.link-row:last-child { border-bottom: none; padding-bottom: 0; }

.link-txt { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.link-txt b { font-size: 14.5px; font-weight: var(--w-name); }
.link-txt .mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px; font-weight: 500; color: var(--ink-2);
  overflow-wrap: anywhere;
}
.link-txt em { font-style: normal; font-size: 12.5px; color: var(--ink-3); line-height: 1.45; }

.link-acts { flex: 0 0 auto; display: flex; gap: 7px; }

.link-sub {
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-4);
  padding: 16px 0 2px; border-top: 1px solid var(--line-soft); margin-top: 6px;
}
.link-row + .link-sub { margin-top: 0; }

/* A ghost button whose consequence is destruction. The crimson is in the
   word, not in a filled rectangle: the row it sits on is somebody's
   medical file and a red block on every line of it would make the file
   look like a list of problems. */
.btn.ghost.danger-text { color: var(--red-deep); }
.btn.ghost.danger-text:hover { color: var(--red-dark); border-color: var(--red-line); background: var(--red-tint); }

/* A single line that leads somewhere: "for the visit on Tuesday", the
   appointment behind an invoice, the invoice behind an appointment. It is
   a button, so it looks pressable, and it is one line, so it does not
   compete with the document it sits inside. */
.link-line {
  display: flex; gap: 12px; align-items: center; width: 100%; text-align: left;
  background: var(--cream-2); border: 1px solid var(--line); border-radius: var(--r);
  padding: 11px 14px; font: inherit; cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.link-line:hover { border-color: var(--accent-line); background: var(--accent-tint); }
.link-line .grow { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.link-line b { font-size: 14px; font-weight: var(--w-name); }
.link-line span:not(.go):not(.grow) { font-size: 12.5px; color: var(--ink-3); }
.link-line .go { flex: 0 0 auto; color: var(--ink-4); font-size: 18px; }
.mt-1 { margin-top: 6px; }

/* ==================================================================
   What came out of a visit.

   A row of small chips under the appointment they belong to: the note,
   the prescription, the photograph, the invoice. Small on purpose — the
   appointment is the thing being read and these are its contents, not
   four more rows competing with it.
   ================================================================== */

.from-visit { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.from-visit:empty { display: none; }

.visit-chip {
  display: inline-flex; align-items: center; gap: 5px;
  max-width: 100%; padding: 4px 9px 4px 6px;
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  font: inherit; font-size: 12px; font-weight: var(--w-name); color: var(--ink-2);
  cursor: pointer; text-decoration: none;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.visit-chip:hover { border-color: var(--accent-line); color: var(--ink); text-decoration: none; }
.visit-chip .ico { width: 14px; height: 14px; flex: 0 0 auto; color: var(--ink-4); }
.visit-chip .ico svg { width: 14px; height: 14px; }
.visit-chip span {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 210px;
}
.visit-chip.add { border-style: dashed; color: var(--ink-3); padding-left: 9px; }

/* One dot, and it means the patient can see this. Green, because in
   MedDesk green is work that is finished — this one has reached them. */
.visit-chip .dot-shared {
  width: 6px; height: 6px; border-radius: 50%; background: var(--green);
  flex: 0 0 auto; margin-left: 1px;
}

/* ==================================================================
   Finding one person inside a filter bar.

   A practice of any age has thousands of patients, so this is a search
   box with the answers under it — not a select. The panel is absolutely
   positioned so opening it does not push the rest of the filter bar
   down, which would move the thing somebody is about to click.
   ================================================================== */

.find { position: relative; }
.find input { width: 100%; }

.find-clear {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  background: none; border: none; font-size: 19px; line-height: 1;
  color: var(--ink-4); cursor: pointer; padding: 2px 6px; border-radius: 6px;
}
.find-clear:hover { background: var(--cream-2); color: var(--ink); }

.find-pop {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0; right: 0;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow); overflow: hidden; max-height: 260px; overflow-y: auto;
}
.find-pop.hidden { display: none; }

.find-hit {
  display: flex; align-items: baseline; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; font: inherit; cursor: pointer;
  padding: 9px 12px; border-bottom: 1px solid var(--line-soft);
}
.find-hit:last-child { border-bottom: none; }
.find-hit:hover { background: var(--cream-2); }
.find-hit b { font-size: 14px; font-weight: var(--w-name); }
.find-hit em { font-style: normal; font-size: 12px; color: var(--ink-4); }
.find-none { padding: 11px 12px; font-size: 13px; color: var(--ink-3); }

/* The dot that ties a row in a table to its line on the chart above it. */
.dot-line {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  margin-right: 7px; vertical-align: -1px;
}

/* ==================================================================
   A sheet of paper on the screen, and on the printer.

   Used by the intake form's print preview. Two jobs: look like the page
   it will be on screen, and — under @media print — be the only thing on
   the page. The second is why this is a class on a container rather than
   a new window: a popup blocker eats a new window, and a print
   stylesheet is what was actually missing.
   ================================================================== */

.print-sheet {
  background: #fff; color: #101012;
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 32px 34px; margin-top: 14px;
  font-size: 13px; line-height: 1.5;
}

.ps-head {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 20px;
  padding-bottom: 12px; border-bottom: 2px solid #101012; margin-bottom: 18px;
}
.ps-head b { display: block; font-family: var(--display); font-size: 17px; font-weight: 700; }
.ps-head span { display: block; font-size: 11.5px; color: #55555c; }
.ps-title { font-family: var(--display); font-size: 15px; font-weight: 700; white-space: nowrap; }

.print-sheet .ps-intro { font-size: 12.5px; color: #45454c; margin-bottom: 16px; }
.ps-sec { margin-bottom: 18px; break-inside: avoid; }
.ps-sec h3 {
  font-family: var(--display); font-size: 12px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: #101012;
  border-bottom: 1px solid #C9C9D0; padding-bottom: 4px; margin-bottom: 10px;
}
.print-sheet .ps-note { font-size: 11.5px; color: #55555c; margin-bottom: 8px; }

.ps-field { margin-bottom: 11px; break-inside: avoid; }
.ps-field label {
  display: block; font-size: 11.5px; font-weight: 700; color: #101012; margin-bottom: 5px;
}
/* A line to write on, not a box to write across the edges of. */
.ps-rule { display: block; border-bottom: 1px solid #9A9AA3; height: 19px; }
.ps-rule + .ps-rule { margin-top: 2px; }

.ps-sign { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-top: 26px; }
.ps-sign h3 { grid-column: 1 / -1; }
.print-sheet .ps-consent {
  font-size: 11px; color: #45454c; border: 1px solid #C9C9D0;
  border-radius: 6px; padding: 9px 11px; margin-top: 18px;
}
.print-sheet .ps-foot { font-size: 10.5px; color: #6E6E76; margin-top: 14px; }

@media print {
  /* Everything on the page goes except the sheet.

     Done with `visibility` and not `display`, on purpose. The sheet
     appears in two quite different places — inside a modal in the clinic
     app, and as a plain block on the patient's own intake page — and an
     ancestor set to display:none cannot be overridden from inside it, so
     any rule that hides containers has to know which containers. This one
     does not: hide everything, show the sheet and its contents, and let
     it print wherever it happens to live.

     `.no-print` is the escape hatch for a control that sits outside the
     sheet, like the Print button itself. */
  body:has(.print-sheet) { visibility: hidden; }
  body:has(.print-sheet) .print-sheet,
  body:has(.print-sheet) .print-sheet * { visibility: visible; }
  body:has(.print-sheet) .print-sheet {
    position: absolute; left: 0; top: 0; width: 100%;
    border: none !important; border-radius: 0 !important;
    padding: 0 !important; margin: 0 !important; box-shadow: none !important;
  }
  .no-print { display: none !important; }
  @page { size: A4; margin: 16mm; }
}

/* A disc wearing a photograph rather than initials.

   Background rather than <img> so it fills at all four sizes with one
   rule, and the initials stay underneath — a file that 404s from a
   half-restored volume then shows a name rather than a broken-image
   glyph. `color: transparent` hides them only when the image loads. */
.av.has-face {
  background-size: cover; background-position: center;
  color: transparent; text-shadow: none;
}

/* The portal's own view of it: the "My details" card, and the small
   round face beside the practice name in the header. */
.photo-card { display: flex; gap: 16px; align-items: flex-start; }
.photo-card .grow { flex: 1; min-width: 0; }
.photo-card .grow b { display: block; font-size: 15px; font-weight: var(--w-name); }
.photo-card .grow p { margin: 3px 0 10px; }
.photo-now { flex: 0 0 auto; }
.photo-now img {
  width: 72px; height: 72px; border-radius: 50%;
  object-fit: cover; border: 1px solid var(--line); display: block;
}
.me-face {
  width: 30px; height: 30px; border-radius: 50%; object-fit: cover;
  border: 1px solid var(--line); flex: 0 0 auto;
}

/* The page, held still behind a dialog.

   `overflow: hidden` on both the element and the body, because Safari
   honours it on one and Chrome on the other, and a phone that keeps
   scrolling the record away behind a half-filled form is the version of
   this bug people actually notice. `overscroll-behavior` stops the
   rubber-band at the end of a dialog's own scroll from being handed on
   to the page underneath. The padding that replaces the scrollbar is set
   in JavaScript, where its width is known.

   Position is deliberately NOT switched to fixed: it would hold the page
   just as still and lose the scroll offset on the way back. */
body.no-scroll { overflow: hidden; }
html:has(body.no-scroll) { overflow: hidden; }
.modal-backdrop, .bell-body, .modal { overscroll-behavior: contain; }

/* A slot inside a choice sheet that is waiting on a request. Holds the
   height so the rows below it do not jump when the answer lands. */
.sheet-slot { height: 46px; border-radius: var(--r); background: var(--cream-2); }

/* A print button, and anything else that must not appear on the paper it
   prints. See the @media print block above. */
.no-print { }

/* The clinic is waiting on a form. Sits above every tab of the portal,
   like the called-in banner, because somebody asked to fill in a form is
   not necessarily on the screen where it lives. Accent rather than
   amber: this is a job, not a warning. */
.intake-banner {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  background: var(--accent-tint); border: 1px solid var(--accent-line);
  border-radius: var(--r-lg); padding: 14px 16px; margin-bottom: 14px;
}
.intake-banner .grow { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.intake-banner b { font-size: 15px; font-weight: var(--w-name); color: var(--accent-dark); }
.intake-banner span { font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.intake-banner .acts { display: flex; gap: 8px; flex: 0 0 auto; }

/* ---------------------------------------------------------- calendar

   The week across the top, one day open below it. Built to be read at
   arm's length by somebody holding a telephone: the counts are the point
   of the strip, so they carry weight, and an empty day is drawn as empty
   rather than as a nought — a row of noughts reads as broken. */
.cal-who { margin-bottom: 12px; }
.cal-head { padding: 12px 12px 8px; margin-bottom: 12px; }
.cal-week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-day {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 2px 7px;
  background: transparent; border: 1.5px solid transparent; border-radius: var(--r);
  font-family: inherit; cursor: pointer;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.cal-day:hover { background: var(--cream-2); }
.cal-day .cd-dow { font-size: 11px; font-weight: 600; color: var(--ink-4); letter-spacing: .04em; text-transform: uppercase; }
.cal-day .cd-num { font-family: var(--display); font-size: 19px; font-weight: 600; color: var(--ink); line-height: 1; }
.cal-day .cd-dot {
  min-width: 20px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11.5px; font-weight: 700; color: #fff; background: var(--ink);
  border-radius: 999px;
}
/* Nothing booked is a small grey pip, not a nought. */
.cal-day .cd-dot.empty { min-width: 5px; width: 5px; height: 5px; padding: 0; background: var(--line); margin: 6px 0 7px; }
.cal-day.today .cd-num { text-decoration: underline; text-underline-offset: 3px; }
.cal-day.sel { background: var(--cream-2); border-color: var(--ink); }

.cal-bar { display: flex; align-items: center; gap: 8px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line-soft); }
.cal-bar .cal-nav { padding: 4px 12px; font-size: 17px; line-height: 1; }
.cal-bar-mid { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; }
.cal-bar .cal-date { font-family: var(--display); font-size: 15.5px; font-weight: 600; color: var(--ink); white-space: nowrap;
                     overflow: hidden; text-overflow: ellipsis; }
.cal-bar input[type=date] { margin-top: 0; max-width: 155px; padding: 6px 10px; font-size: 13.5px; }

.cal-summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 14px; }
.cal-summary .cs-cell {
  background: var(--card); border: 1.5px solid var(--line); border-radius: var(--r);
  padding: 10px 12px; display: flex; flex-direction: column; gap: 2px;
}
.cal-summary .cs-k { font-size: 11.5px; font-weight: 600; color: var(--ink-4); text-transform: uppercase; letter-spacing: .04em; }
.cal-summary .cs-v { font-family: var(--display); font-size: 21px; font-weight: 600; color: var(--ink); line-height: 1.1; }
.cal-summary .cs-v.bad { color: var(--red-deep); }

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

@media (max-width: 620px) {
  .cal-bar { flex-wrap: wrap; }
  .cal-bar-mid { order: -1; width: 100%; }
  .cal-bar input[type=date] { max-width: 140px; }
  .cal-day .cd-num { font-size: 17px; }
}

/* The running total on an invoice being built. It moves as lines are
   added, because a figure that only appears after saving is a figure
   somebody finds out about from a patient. */
.wi-total {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 14px;
  background: var(--cream-2); border: 1.5px solid var(--line); border-radius: var(--r);
}
.wi-total .k { font-size: 12.5px; font-weight: 600; color: var(--ink-3); text-transform: uppercase; letter-spacing: .04em; }
.wi-total .v { font-family: var(--display); font-size: 20px; font-weight: 600; color: var(--ink); margin-left: auto; }

/* The desk's own notes on a person. Deliberately plain — it is prose
   somebody typed, not a field, and dressing it up as a field invites
   short answers where the useful ones are sentences. */
.note-card .note-body {
  margin: 0 0 12px; font-size: 15px; line-height: 1.55; color: var(--ink-2);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.note-card textarea { margin-bottom: 10px; }
/* A continuation line under a table row — the note under a dose. */
.stacked tr.sub-row td { padding-top: 0; border-top: 0; }

/* A labelled bar with its figure beside it. Used on the sales screen to
   show what the money was for. The bar is a comparison, not a
   measurement — the number to the right of it is the measurement — so it
   is deliberately plain and never carries a scale. */
.bar-row { display: grid; grid-template-columns: minmax(120px, 1.1fr) 2fr minmax(110px, auto);
           align-items: center; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--line-soft); }
.bar-row:last-child { border-bottom: 0; }
.bar-row .bl { font-size: 14px; font-weight: 600; color: var(--ink); }
.bar-row .bt { height: 9px; background: var(--cream-2); border-radius: 999px; overflow: hidden; }
.bar-row .bf { display: block; height: 100%; background: var(--ink); border-radius: 999px; }
.bar-row .bv { font-family: var(--display); font-size: 15px; font-weight: 600; color: var(--ink); text-align: right; }
@media (max-width: 620px) {
  .bar-row { grid-template-columns: 1fr auto; }
  .bar-row .bt { grid-column: 1 / -1; }
}

/* Three facts across the top of a dialog — the number, the month, the
   amount. A row rather than a definition list because they are read
   together, at a glance, before anybody reads anything else. */
.kv-strip { display: flex; gap: 18px; flex-wrap: wrap; padding: 0 0 14px; margin-bottom: 14px;
            border-bottom: 1px solid var(--line); }
.kv-strip .k { display: block; font-size: 11.5px; font-weight: 600; color: var(--ink-4);
               text-transform: uppercase; letter-spacing: .04em; }
.kv-strip .v { display: block; font-family: var(--display); font-size: 17px; font-weight: 600; color: var(--ink); }

/* A medication chip on a visit — the same shape as a document chip, in a
   different weight, so the two read as different kinds of thing without
   needing a legend. */
.visit-chip.given { background: var(--cream-2); border-style: dashed; cursor: default; }
