/* Client portal pages. Sits on top of styles.css, which supplies the palette. */
.portal-main { padding: 48px 0 72px; }
.portal-card {
  max-width: 460px; margin: 0 auto; background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.07); border-radius: 14px;
  padding: 32px; box-shadow: 0 2px 14px rgba(0, 0, 0, 0.04);
}
.portal-card h1 { font-size: 24px; margin: 0 0 6px; }
.portal-lede { color: var(--ink-soft); font-size: 15px; margin: 0 0 24px; }
.field { margin: 0 0 16px; }
.field label { display: block; font-size: 13px; font-weight: 600; margin: 0 0 5px; }
/* Selects and textareas were left as browser defaults, which only showed once
   the fields around them started looking deliberate. Same box as an input. */
.field input,
.field select,
.field textarea {
  width: 100%; padding: 10px 12px; font: inherit; font-size: 15px;
  border: 1px solid rgba(0, 0, 0, 0.16); border-radius: 8px; background: #fdfcfc;
  color: inherit;
}
.field input:focus,
.field select:focus,
.field textarea:focus { outline: 2px solid var(--accent-dark); outline-offset: -1px; }

/* A select keeps its own arrow, drawn rather than inherited, so it does not
   sit a few pixels taller than the inputs above and below it. */
.field select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  padding-right: 34px;
  background-image:
    url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23615d57' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}

.field textarea { min-height: 76px; resize: vertical; line-height: 1.5; }
.field textarea::placeholder, .field input::placeholder { color: #9a948c; }
.field-row { display: flex; gap: 12px; }
.field-row .field { flex: 1; }
.field-hint { font-size: 12px; color: var(--ink-soft); margin: 5px 0 0; }
.portal-btn {
  width: 100%; padding: 12px 18px; font: inherit; font-size: 15px; font-weight: 600;
  color: #fff; background: var(--accent-dark); border: 0; border-radius: 8px; cursor: pointer;
}
.portal-btn:hover { filter: brightness(1.08); }
.portal-btn[disabled] { opacity: 0.6; cursor: default; }
.portal-alt { text-align: center; font-size: 14px; margin: 20px 0 0; color: var(--ink-soft); }
.portal-alt a { color: var(--accent-dark); font-weight: 600; text-decoration: none; }
.portal-alt a:hover { text-decoration: underline; }
.portal-status { margin: 16px 0 0; font-size: 14px; min-height: 1.2em; }
.portal-status.bad { color: #a32d2d; }
.portal-status.good { color: #2f6f4f; }
.portal-legend { font-size: 13px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-soft); margin: 26px 0 12px; }

/* Reveal toggle, injected by portal.js into every password field. */
.pw-wrap { position: relative; }
.pw-wrap input { padding-right: 72px; }
.pw-toggle {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; padding: 7px 9px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--accent-dark);
}
.pw-toggle:hover { text-decoration: underline; }
.pw-toggle:focus-visible { outline: 2px solid var(--accent-dark); border-radius: 6px; }

/* --- list-detail layout for the signed-in area --- */
.portal-shell {
  display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 28px;
  max-width: 900px; align-items: start;
  /* 20px of clearance under the header: applied to the whole shell so the nav
     and the detail pane stay level with one another. */
  margin: 20px auto 0;
}
@media (max-width: 720px) { .portal-shell { grid-template-columns: 1fr; gap: 18px; } }

.portal-nav { position: sticky; top: 24px; }
@media (max-width: 720px) { .portal-nav { position: static; } }
.portal-nav ul { list-style: none; margin: 0; padding: 0; }
.portal-nav li + li { margin-top: 2px; }
.portal-nav button {
  display: block; width: 100%; text-align: left; cursor: pointer;
  font: inherit; font-size: 15px; font-weight: 600; color: var(--ink);
  background: none; border: 0; border-radius: 8px; padding: 10px 12px;
}
.portal-nav button:hover { background: rgba(0, 0, 0, 0.04); }
.portal-nav button[aria-current="page"] { background: var(--accent-dark); color: #fff; }
.portal-nav .nav-count { float: right; opacity: 0.6; font-weight: 500; }
.portal-signout {
  margin-top: 18px; padding-top: 14px; border-top: 1px solid rgba(0, 0, 0, 0.08);
  font-size: 14px;
}
.portal-signout a { color: var(--ink-soft); text-decoration: none; }
.portal-signout a:hover { text-decoration: underline; }

.portal-pane { min-width: 0; }
.portal-pane[hidden] { display: none; }

/* `hidden` must actually hide.
   The browser's own [hidden] rule is a UA default, so ANY class that sets
   `display` beats it -- and then setting .hidden from JavaScript does nothing at
   all. The SMS dialog set display:flex and was therefore permanently on screen
   and impossible to close. The line above is the same bug, fixed one class at a
   time in 2026; this stops it recurring for every future one. */
[hidden] { display: none !important; }
.portal-card--wide { max-width: none; }

/* record cards used by Pets and Appointments */
.rec {
  border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 10px;
  padding: 14px 16px; margin: 0 0 10px;
}
.rec h3 { margin: 0 0 4px; font-size: 16px; }
.rec dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 2px 12px; font-size: 14px; }
.rec dt { color: var(--ink-soft); }
.rec dd { margin: 0; }
.rec-when { font-weight: 600; }
.rec-tag {
  display: inline-block; font-size: 12px; font-weight: 600; padding: 2px 8px;
  border-radius: 999px; background: rgba(0, 0, 0, 0.06); color: var(--ink-soft);
  margin-left: 8px; vertical-align: 1px;
}
.rec-group { font-size: 13px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-soft); margin: 22px 0 10px; }
.rec-group:first-child { margin-top: 0; }
.portal-empty { color: var(--ink-soft); font-size: 15px; margin: 0; }

/* Brand on the left, logo on the right of the signed-in title bar. */
.site-nav .nav-inner {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; gap: 16px;
}
.nav-logo { display: inline-flex; align-items: center; line-height: 0; }
.nav-logo img { height: 34px; width: auto; display: block; }
@media (max-width: 520px) { .nav-logo img { height: 28px; } }

/* The spay/neuter condition. Deliberately loud: it is a clinic policy the
   customer is agreeing to, not a formality to click past. */
.warn {
  border: 2px solid #c0392b; background: #fdf3f2; border-radius: 10px;
  padding: 14px 16px; margin: 18px 0;
}
.warn h4 { margin: 0 0 8px; color: #a32d2d; font-size: 15px; }
.warn p { margin: 0 0 10px; font-size: 14px; line-height: 1.5; }
.warn label { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; font-weight: 600; }
.warn input[type="checkbox"] { margin-top: 3px; width: 18px; height: 18px; flex: none; }
.warn .warn-alt { margin: 10px 0 0; font-weight: 400; }

/* --- staff: the day beside the request --- */
.dayview { border: 1px solid rgba(0,0,0,.1); border-radius: 10px; margin: 12px 0 0; overflow: hidden; }
.dayview-head {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  background: rgba(0,0,0,.03); border-bottom: 1px solid rgba(0,0,0,.08);
}
.dayview-head strong { flex: 1; font-size: 14px; }
.dayview-head button {
  font: inherit; font-size: 15px; line-height: 1; cursor: pointer;
  border: 1px solid rgba(0,0,0,.15); background: #fff; border-radius: 6px;
  padding: 4px 10px;
}
.dayview-head button:disabled { opacity: .4; cursor: default; }
.cap { display: flex; gap: 14px; padding: 8px 10px; font-size: 12.5px; flex-wrap: wrap;
       border-bottom: 1px solid rgba(0,0,0,.06); }
.cap span { color: var(--ink-soft); }
.cap b { font-weight: 700; color: var(--ink); }
.cap .full { color: #a32d2d; }
.dayview-cols { display: grid; gap: 0; }
.dvcol { display: grid; grid-template-columns: 130px 1fr; gap: 10px;
         padding: 7px 10px; font-size: 13px; border-top: 1px solid rgba(0,0,0,.05); }
.dvcol:first-child { border-top: 0; }
.dvcol-name { color: var(--ink-soft); font-weight: 600; }
.dvcol.here { background: #f7faf8; }
.dvcol.here .dvcol-name { color: var(--accent-dark); }
.dvslot { display: inline-block; margin: 0 8px 3px 0; white-space: nowrap; }
.dvempty { color: var(--ink-soft); font-style: italic; }
.dvweekend { padding: 10px; font-size: 13px; color: #a32d2d; }

/* --- staff: an actual calendar column, not a list --- */
.grid { display: grid; grid-template-columns: 56px repeat(var(--cols, 2), minmax(0, 1fr));
        gap: 0 10px; padding: 12px 12px 16px; }
.grid-head { font-size: 12px; font-weight: 700; letter-spacing: .04em;
             color: var(--ink-soft); padding: 0 0 8px; text-align: center;
             text-transform: uppercase; }
.grid-head.here { color: #1f4a30; }
.grid-times { position: relative; }
.grid-times span {
  position: absolute; right: 8px; transform: translateY(-50%);
  font-size: 11px; color: var(--ink-soft); white-space: nowrap; font-variant-numeric: tabular-nums;
}
.grid-col {
  position: relative; border: 1px solid rgba(0,0,0,.10); border-radius: 8px;
  background:
    repeating-linear-gradient(to bottom,
      rgba(0,0,0,.05) 0 1px, transparent 1px var(--hour, 60px));
  overflow: hidden;
}
/* The hours the clinic actually works, lit up behind everything else. */
.grid-col .shade {
  position: absolute; left: 0; right: 0; background: rgba(47,111,79,.06);
  border-top: 1px solid rgba(47,111,79,.18); border-bottom: 1px solid rgba(47,111,79,.18);
}
.blk {
  position: absolute; overflow: hidden;
  /* `left` and `width` are set per block: overlapping appointments share the
     column in lanes, side by side. */
  border-radius: 6px; font-size: 11.5px; line-height: 1.3; padding: 3px 6px;
  box-shadow: 0 1px 2px rgba(0,0,0,.08);
}
/* Time and what it is on ONE line: a 10-minute block is barely two lines tall,
   and putting the time on its own row meant a short appointment showed only
   the time -- the least useful half. */
.blk b { font-weight: 700; font-variant-numeric: tabular-nums; margin-right: 5px; }
/* Text wraps inside the block rather than running past its edge; what does
   not fit in the block's height is clipped, never overflowing the next one.
   Alex, 2026-09-08: "if there is a string in the rounded rectangle, do not
   exceed but linebreak it instead." */
.blk { white-space: normal; overflow-wrap: anywhere; line-height: 1.2; overflow: hidden; }
.blk .who { display: block; }
/* The time, type and reason are ONE line that truncates; the client and pets
   wrap below it. The block still clips vertically, so this only changes how the
   first line behaves: it ellipsises instead of wrapping, which is what makes
   room for the reason without pushing the names out of a short block. A long
   appointment-type name now ends in an ellipsis rather than wrapping. */
.blk .line1 { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Why the request card is not offering to text this customer. Said out loud
   rather than a missing button: the desk has to know whether to phone. */
.nosms { align-self: center; font-size: 12.5px; color: #8a6d3b; background: #fcf5e6;
         border: 1px solid #e6d3a8; border-radius: 6px; padding: 4px 9px; }
.blk .rsn { opacity: .8; }

/* The client and pets, after the appointment type. Spaced the way .blk b spaces
   the time -- without it the two run together: "Spay/NeuterRobin Chiang",
   "Surgery AdmitPatty Hummel", "PROPOSEDBoo". Reported by Alex 2026-09-07.
   A margin rather than a text space, so nothing is left behind when the name is
   empty, and because the same span appears on check-in places as well as on
   timeline blocks -- hence a bare .who rather than .blk .who. */
.who { margin-left: 5px; }
.blk .who { opacity: .75; }
/* Colour by what it is, so a day reads at a glance. */
.blk-surgery { background: #f3e0d4; border: 1px solid #d8b295; color: #5a3a22; }
.blk-dental  { background: #d9e8ea; border: 1px solid #a9c9ce; color: #234047; }
.blk-exam    { background: #e2e0ef; border: 1px solid #bab6d8; color: #35315c; }
.blk-arrival { background: #dce9dd; border: 1px solid #aecab3; color: #22452c; }
.blk-other   { background: #e8e6e2; border: 1px solid #cbc6bf; color: #45413b; }
/* Check-in occupancy inside the exam rooms. A BAND, not a block: it is how full
   the room is, not an appointment anybody could open in Shepherd. At one pixel
   a minute a 15-minute slot is 15px tall, so it carries its own compact type
   rather than .blk's padding. */
.ckband {
  position: absolute; left: 4px; right: 4px; z-index: 1;
  border-radius: 4px; font-size: 10px; line-height: 13px; padding: 0 5px;
  white-space: nowrap; overflow: hidden;
  background: repeating-linear-gradient(45deg, #e7f0e9 0 6px, #dbe9de 6px 12px);
  border: 1px dashed #a9c6b0; color: #2b5136;
}
.ckband.full {
  background: #c8ddcc; border-style: solid; border-color: #8fb598; color: #1e4028;
}
/* The arrival this booking would take, outlined the way .blk.ghost outlines an
   exam or a procedure. Transparent on purpose: the occupancy band underneath is
   what says whether the slot has room, and hiding it to show the proposal would
   answer one question by removing another. */
.ckband.ghost {
  background: transparent; border: 2px dashed #2f6f4f; color: #1c4430;
  font-weight: 700; z-index: 4; pointer-events: none;
}
.ckband b { font-weight: 700; font-variant-numeric: tabular-nums; margin-right: 5px; }

/* "Called them", as a checkbox on the card. It sits with the buttons but is
   not one: ticking it records a call, and it comes off again the same way. */
.calledbox {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font-size: 13.5px; color: var(--ink-soft); user-select: none;
  padding: 8px 12px; border: 1px solid rgba(0,0,0,.16); border-radius: 8px;
}
.calledbox input { width: 16px; height: 16px; accent-color: #2f6f4f; cursor: pointer; }
.calledbox.on { background: #eef5f0; border-color: #9dc0a9; color: #1f4a30; font-weight: 600; }
.calledbox[aria-busy="true"] { opacity: .55; cursor: progress; }

/* The heading, with the build identity pushed to the right of it. Deliberately
   tiny: it is for answering "which build am I looking at" when the page and
   production disagree, and for nothing else. */
h1.with-build { display: flex; align-items: baseline; gap: 12px; }
h1.with-build .build {
  margin-left: auto; font-size: 8px; line-height: 1.2; font-weight: 400;
  color: var(--ink-soft); opacity: .7; letter-spacing: .02em;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* What to book: a line per resource, ticked where it applies. */
.lines { margin: 12px 0 4px; }
.line {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 10px; border: 1px solid rgba(0,0,0,.10); border-radius: 8px;
  margin-bottom: 6px; background: #fbfaf8; opacity: .62;
}
.line.on { opacity: 1; background: #f2f7f3; border-color: #bfd8c9; }
.lines .line-tick {
  display: inline-flex; align-items: baseline; gap: 8px; cursor: pointer;
  min-width: 190px; margin: 0; font-weight: 400;
}
.line-tick input { width: 16px; height: 16px; accent-color: #2f6f4f; cursor: pointer; }
.line-tick span:first-of-type { font-weight: 600; font-size: 14px; }
.line-col { font-size: 11.5px; color: var(--ink-soft); }
.line-reason {
  flex: 1; min-width: 220px; font: inherit; font-size: 13.5px; padding: 5px 9px;
  border: 1px solid rgba(0,0,0,.16); border-radius: 6px; background: #fff;
}
.line-mins { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }
.line-at { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }
.line-at input {
  font: inherit; font-size: 13.5px; padding: 5px 7px; margin-left: 5px;
  border: 1px solid rgba(0,0,0,.16); border-radius: 6px; background: #fff;
}
.line-mins input {
  width: 66px; font: inherit; font-size: 13.5px; padding: 5px 7px; margin-right: 5px;
  border: 1px solid rgba(0,0,0,.16); border-radius: 6px; text-align: right;
}

/* Tick what the visit is for. Several are allowed: a spay and a dental are one
   visit, and the old single dropdown could not say that. */
.picks { display: flex; flex-direction: column; gap: 6px; }
.picks .pick {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 9px 12px; border: 1px solid rgba(0,0,0,.14); border-radius: 8px;
  background: #fff; font-size: 15px; margin: 0; font-weight: 400;
}
.picks .pick:hover { background: #f7f6f3; }
.picks .pick input { width: 17px; height: 17px; accent-color: #2f6f4f; cursor: pointer; }
.picks .pick:has(input:checked) { background: #f2f7f3; border-color: #bfd8c9; font-weight: 600; }

/* A day that cannot take what is being booked. Loud on purpose: the proposal
   above it looks perfectly bookable, and that is exactly the mistake. */
.cannot {
  margin: 10px 0; padding: 10px 14px; border-radius: 8px;
  background: #fbeaea; border: 1px solid #d99b9b; border-left: 4px solid #a32d2d;
  color: #7a2020; font-weight: 600; font-size: 14px;
}

/* The day stepper on a request card: arrows, a jump back, and a date picker. */
.dayview-head .spacer { flex: 1; }
.dayview-head input[type="date"] {
  font: inherit; font-size: 13px; padding: 5px 9px;
  border: 1px solid rgba(0,0,0,.16); border-radius: 8px; background: #fff;
  cursor: pointer;
}
.dayview-head [data-jump] { font-size: 13px; }

/* Requests / Schedule. */
/* The tab bar these styled was replaced by the sidebar (.side-list); nothing
   carries class="tab" any more. Removed rather than left to rot -- a dead rule
   is a rule somebody later copies. */

/* One appointment, opened from the schedule. */
.appt-detail {
  border: 1px solid #bfd8c9; background: #f7faf8; border-radius: 10px;
  padding: 14px 16px; margin: 14px 0 4px;
}
.appt-detail h4 { margin: 0 0 10px; font-size: 15.5px; }
.appt-detail dl { margin: 0; }
.appt-detail .appt-when { margin: 0 0 12px; font-size: 14.5px; color: var(--ink); }
.appt-detail a { color: #2f6f4f; }
/* A Shepherd reference code: small, monospaced, obviously a handle. */
.ref {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--ink-soft);
  background: rgba(0,0,0,.05); border-radius: 4px; padding: 1px 5px;
}
.appt-detail .close { float: right; }
/* The one that was clicked, and everything else belonging to that client. */
.blk.pick, .place.pick { outline: 2px solid #2f6f4f; outline-offset: 1px; }
.blk.rel, .place.rel {
  outline: 2px dashed #2f6f4f; outline-offset: 1px;
}
.place.clickable { cursor: pointer; }
.blk.clickable { cursor: pointer; }

/* The schedule's own day stepper, wider than the one inside a request. */
.sched-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 4px 0 12px;
}
.sched-head strong { font-size: 17px; }
.sched-head button, .sched-head input[type="date"] {
  font: inherit; font-size: 14px; padding: 6px 12px; cursor: pointer;
  border: 1px solid rgba(0,0,0,.16); border-radius: 8px; background: #fff;
}
.sched-head .spacer { flex: 1; }

/* Which requests to show. Sits under the ad-hoc button, above the queue. */
.filters { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 16px; }
.chip {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--ink-soft);
  background: #f4f2ee; border: 1px solid rgba(0,0,0,.12); border-radius: 999px;
  padding: 6px 12px;
}
.chip:hover { background: #ece9e3; }
/* Brand colour: which filter is selected is a choice about what you are looking
   at, the same kind of thing as the sidebar. The green in this stylesheet is
   kept for what it means -- capacity, a valid drop target, a good outcome. */
.chip.on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.chip-n {
  font-variant-numeric: tabular-nums; font-size: 12px;
  background: rgba(0,0,0,.08); border-radius: 999px; padding: 0 6px; min-width: 18px;
  text-align: center;
}
.chip.on .chip-n { background: rgba(255,255,255,.22); }

/* Which page of them. Brand colour, for the same reason the chips above it are:
   both are about which requests you are looking at, and neither is a state of
   the clinic. `display` here is safe only because of the global [hidden] rule
   near the top of this file -- the bar is hidden whenever there is one page. */
.pager { display: flex; align-items: center; gap: 12px; margin: 0 0 16px; }
.pager-btn {
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  color: var(--accent-dark); background: #fff;
  border: 1px solid rgba(122,46,46,.32); border-radius: 999px; padding: 6px 13px;
}
.pager-btn:hover:not(:disabled) { background: rgba(122,46,46,.08); }
/* An end of the list is worth showing rather than hiding: a Previous that has
   gone missing reads as a page that failed to draw. */
.pager-btn:disabled {
  color: var(--ink-soft); border-color: rgba(0,0,0,.12); opacity: .5; cursor: default;
}
.pager-at { font-size: 13px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* A settled request: shown for reference, nothing to click. */
.rec-settled { opacity: .82; }
.warn-inline { color: #7a2e2e; font-weight: 600; }
.rec-tag--declined { background: #f0dcdc; border-color: #d9b3b3; color: #7a2e2e; }
.rec-tag--booked   { background: #dce9dd; border-color: #aecab3; color: #22452c; }

/* A move in progress. The bar sits under the day header and stays there while
   the front desk steps between days -- nothing is written until Confirm, so
   the only thing telling them a move is open is this. */
/* Choosing where a reset code is sent. Both are always offered: showing only
   the channels an account has would leak who has one. */
fieldset.via { border: 1px solid #d8d2cc; border-radius: 8px; padding: 10px 12px; margin: 0 0 14px; }
fieldset.via legend { font-size: 12px; color: #6b6b6b; padding: 0 6px; }
label.viaopt { display: flex; gap: 9px; align-items: flex-start; padding: 6px 2px; cursor: pointer; }
label.viaopt input { margin-top: 3px; }
label.viaopt small { color: #6b6b6b; }

/* The booked day on a request card: a way through to the schedule. */
a.gotoday { color: inherit; text-decoration: none; border-bottom: 1px dotted #9fb8a6; }
a.gotoday:hover { border-bottom-style: solid; color: #2f6b46; }

.move-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0 12px 10px; padding: 9px 12px;
  border: 1px dashed #b08968; border-radius: 8px; background: #fdf8f2;
  font-size: 13px;
}
.move-bar .spacer { flex: 1; }
.move-bar .move-from { color: #8a837c; text-decoration: line-through; }
.move-bar .move-to { color: #2f6b46; font-weight: 650; }
.move-bar button[disabled] { opacity: .45; cursor: not-allowed; }
.move-bar .move-acts { display: flex; gap: 8px; flex-basis: 100%; margin-top: 8px; }
.move-bar .move-acts .portal-btn { width: auto; }
.move-bar .move-acts .portal-btn.alt { background: #fff; border: 1px solid #c9c2bb; color: var(--ink); }
.move-bar .move-acts .portal-btn.alt:hover { background: #f6f2ee; }

.move-bar .move-f { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--ink-soft); }
.move-bar .move-f select, .move-bar .move-f input { font: inherit; font-size: 12.5px; padding: 3px 6px;
  border: 1px solid #cfc8c1; border-radius: 5px; background: #fff; color: var(--ink); }
.move-bar .move-f input[type="number"] { width: 68px; }


/* The appointment stays on its own day while the move is unfinished, faded so
   it reads as "leaving" rather than as a second booking. A hole there would
   look like the visit had already gone. */
.blk.moving-from { opacity: .35; filter: grayscale(1); }
.blk.moving-from::after {
  content: "moving"; position: absolute; right: 4px; bottom: 2px;
  font-size: 9px; letter-spacing: .04em; text-transform: uppercase; color: #6b6b6b;
}

/* Modal dialogs on the schedule. `display:flex` here beats the browser's
   [hidden]{display:none}, so the hidden rule has to be said after, and
   specifically -- otherwise every dialog shows on page load. */
.modal { position: fixed; inset: 0; z-index: 80; background: rgba(30,26,22,.38);
         display: flex; align-items: flex-start; justify-content: center; padding: 70px 20px; }
.modal[hidden] { display: none; }
.modal-card { background: #fff; border-radius: 10px; width: 100%; max-width: 460px;
              padding: 20px 22px; box-shadow: 0 12px 40px rgba(0,0,0,.2); }
.modal-card h3 { margin: 0 0 4px; font-size: 16px; }
.modal-what { margin: 0 0 14px; color: var(--ink-soft); font-size: 13px; }
.modal-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.modal-row .portal-btn { width: auto; }
.modal-row.end { justify-content: flex-end; align-items: center; margin-bottom: 0; }
.modal-card textarea { width: 100%; }
.modal-msg { min-height: 16px; margin: 4px 0; color: #9a3412; font-size: 12.5px; }
.portal-btn.danger { background: #fff; border: 1px solid #c8a79f; color: #9a3412; }
.portal-btn.danger:hover { background: #fdf4f1; }

/* What a closed booking panel leaves behind. */
.adhoc-done { margin-left: 10px; font-size: 13px; }
.adhoc-done.good { color: #2f6b46; font-weight: 600; }
.adhoc-done.bad  { color: #9a3412; font-weight: 600; }

/* A customer who has actually been picked. The search box keeps what was typed
   otherwise, which reads as "nothing happened" after a successful click. */
#adSearch.picked { border-color: #2f6b46; background: #f4f9f5; font-weight: 600; }
.picked-who { margin: 0 0 4px; font-size: 13px; }
.picked-who .tick { color: #2f6b46; font-weight: 700; }
.picked-who strong { color: #1c4430; }

.blk.ghost {
  background: repeating-linear-gradient(45deg, #cfe9d8 0 8px, #c2e2cd 8px 16px);
  border: 2px dashed #2f6f4f; color: #1c4430; font-weight: 700; z-index: 3;
  box-shadow: 0 2px 8px rgba(47,111,79,.25);
}

/* Capacity as bars rather than a sentence. */
.cap { display: flex; gap: 16px; padding: 10px 12px 4px; flex-wrap: wrap; }
.cap .meter { min-width: 130px; }
.cap .meter-label { font-size: 11.5px; color: var(--ink-soft); display: flex;
                    justify-content: space-between; gap: 8px; }
.cap .meter-label b { color: var(--ink); font-variant-numeric: tabular-nums; }
.cap .bar { height: 6px; border-radius: 999px; background: rgba(0,0,0,.08); margin-top: 3px;
            overflow: hidden; }
.cap .bar i { display: block; height: 100%; background: #2f6f4f; border-radius: 999px; }
.cap .bar i.warn { background: #c9922b; }
.cap .bar i.full { background: #c0392b; }
.dvweekend { padding: 14px; font-size: 13px; color: #a32d2d; }

/* --- staff: use the screen. A queue is a working tool, not a reading page. --- */
/* The staff page is half again as wide as the public site. Alex, 2026-09-07:
   "the schedule needs to be wider, it is too narrow, please, widen the page to
   150% regarding its content." The site's .container caps at 1040px; the staff
   page and its header get 1560px -- 1040 x 1.5 -- and everything inside, the
   day view's columns above all, spreads into it. Scoped by a class on the staff
   page's own containers so nothing public moves. */
.container--staff { max-width: 1560px; }
.staff-wide { max-width: 1560px; margin: 20px auto 0; }
@media (max-width: 900px) { .staff-wide { max-width: none; } }

/* Check-In is not a timeline: it is four slots with four places each, filled
   left to right. A table says that; overlapping blocks do not. */
.slots { padding: 10px 12px 14px; }
.slotrow { display: grid; grid-template-columns: 62px repeat(4, minmax(90px, 1fr));
           gap: 6px; margin-bottom: 6px; align-items: stretch; }
.slotrow-time { font-size: 12.5px; font-weight: 700; color: var(--ink-soft);
                display: flex; align-items: center; font-variant-numeric: tabular-nums; }
.place {
  border: 1px solid rgba(0,0,0,.10); border-radius: 6px; min-height: 38px;
  padding: 4px 7px; font-size: 12px; line-height: 1.3; background: #fff;
  display: flex; flex-direction: column; justify-content: center;
}
.place.taken { background: #dce9dd; border-color: #aecab3; color: #22452c; }
.place.free { background: repeating-linear-gradient(45deg,
                #fbfbfa 0 6px, #f4f4f2 6px 12px); color: #a8a5a0; }
.place.mine { background: repeating-linear-gradient(45deg, #cfe9d8 0 8px, #c2e2cd 8px 16px);
              border: 2px dashed #2f6f4f; color: #1c4430; font-weight: 700; }
.place b { display: block; font-weight: 700; }
.slotrow.full .slotrow-time { color: #a32d2d; }
/* Someone with an appointment but no arrival booked: they take a place, but we
   cannot say which, so it is drawn faintly rather than as a firm booking. */
.place.inferred { background: #f0f0ee; border-color: rgba(0,0,0,.10); color: #6d6f66;
                  border-style: dashed; }
/* The inferred boxes carry three short lines, so they need a little more room
   than a bare arrival does. */
.place { min-height: 46px; }
.place .who { display: block; font-size: 10.5px; opacity: .7; }

/* Room and duration, adjustable beside the proposal. */
.adjust { display: flex; gap: 16px; align-items: center; flex-wrap: wrap;
          margin: 10px 0 0; font-size: 13px; }
.adjust label { display: flex; gap: 6px; align-items: center; font-weight: 600; }
.adjust select, .adjust input {
  font: inherit; font-size: 13px; padding: 4px 7px;
  border: 1px solid rgba(0,0,0,.16); border-radius: 6px; background: #fdfcfc;
}
.adjust input { width: 70px; }
.adjust-hint { color: var(--ink-soft); font-weight: 400; font-size: 12px; }
/* The proposal can be dragged between interchangeable rooms. */
.blk.draggable { cursor: grab; }
.blk.draggable:active { cursor: grabbing; }
.grid-col.drop-here { outline: 2px dashed #2f6f4f; outline-offset: -2px; background-color: #eef7f0; }

/* Hovering lifts one block clear of the stack so it can be read in full. */
.blk:hover { z-index: 50 !important; left: 4px !important;
             box-shadow: 0 3px 10px rgba(0,0,0,.22); }
/* A request that has been called stays in the queue, visibly settled but still
   actionable -- ringing someone is a step towards booking, not a way to clear
   the list. */
.rec-called { background: #fbfaf8; }
.rec-tag--called { background: #dce9dd; color: #22452c; }

/* Contact consent. The label is the whole row so the words are the click
   target, not just the twelve-pixel box.

   Specificity, because this bit has bitten before: `.field label` is (0,1,1)
   and would make the label a block, and `.field input` is (0,1,1) and would
   stretch the checkbox to the full width. `.field .consent-line` is (0,2,0)
   and `.field .consent-line input` is (0,2,1), so both win on class count
   rather than on being further down the file. */
.field.consent { margin: 0 0 18px; }
.field .consent-line {
  display: flex; align-items: flex-start; gap: 10px;
  font-weight: 600; font-size: 15px; margin: 0; cursor: pointer;
}
.field .consent-line input[type="checkbox"] {
  width: 18px; height: 18px; flex: 0 0 auto; margin: 1px 0 0;
  padding: 0; accent-color: var(--accent-dark); cursor: pointer;
}
.field .consent-line span { line-height: 1.35; }
/* The hint belongs to the question above it, so it lines up with the words
   rather than with the box. On the registration form there is no box to line up
   with -- the question is a legend -- so the indent would be arbitrary. */
.field.consent .field-hint { margin-left: 28px; }
fieldset.field.consent .field-hint { margin-left: 0; }
/* The paragraph that asks for an answer must not sit flush against the first
   question, or it reads as that question's own hint. */
#contactAsk, .consent-intro { margin-bottom: 16px; }
/* A STOP on record outranks the box, so it does not read like a hint. */
.consent-stop { color: var(--accent-dark); font-weight: 600; }

/* Registration asks the same two questions as Yes/No, because a checkbox has no
   way to say "not answered" and that is the one state that must never be
   submitted. `.field` sets a border on fieldsets in some browsers; cleared. */
fieldset.field.consent { border: 0; padding: 0; margin: 0 0 18px; min-width: 0; }
.consent-q { font-size: 15px; font-weight: 600; padding: 0; }
.consent-pick { display: flex; gap: 22px; margin: 10px 0 0; }
.consent-pick label {
  display: flex; align-items: center; gap: 7px;
  font-size: 15px; font-weight: 500; margin: 0; cursor: pointer;
}
.consent-pick input[type="radio"] {
  width: 18px; height: 18px; flex: 0 0 auto; margin: 0; padding: 0;
  accent-color: var(--accent-dark); cursor: pointer;
}

/* ---- Staff: list-detail shell -------------------------------------------
   A row of tabs was fine for two views. A third made the last one read as an
   afterthought, and there will be more, so the views became a list down the
   side with the work beside it. */
.split { display: flex; gap: 26px; align-items: flex-start; }
.split-main { flex: 1 1 auto; min-width: 0; }   /* min-width:0 or the day grid
                                                   refuses to scroll and blows
                                                   the page out sideways */
.side-list {
  flex: 0 0 178px; display: flex; flex-direction: column; gap: 2px;
  border-right: 1px solid rgba(0,0,0,.10); padding-right: 14px;
  /* THE LIST LIVES IN THE WINDOW, it does not grow it. With thirty-five
     requests the sidebar ran past the bottom of the page and took the whole
     document with it, so reading the end of the list meant scrolling the
     request you were looking at off the screen. Alex, 2026-09-15: "it should
     not grow the window to be scrollable based on that list".
     Sticky, capped to the viewport the user sized, and the request list below
     scrolls inside it. */
  position: sticky; top: 16px;
  max-height: calc(100vh - 32px);
  min-height: 0;
}
.side-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 9px 11px; font: inherit; font-size: 15px; text-align: left;
  color: var(--ink-soft); background: none; border: 0; border-radius: 7px;
  cursor: pointer;
  /* Every item was a <button> until the Drug log, which is an <a> because it
     opens its own page rather than switching a pane. An anchor arrives
     underlined and in the link colour, so it stood out as the one odd item in
     the list. Reset here rather than on that one link: the next page-that-is-a
     -link should look like the rest without anyone noticing it needs to. */
  text-decoration: none; box-sizing: border-box;
}
.side-item:hover { background: rgba(0,0,0,.045); color: var(--ink); }
/* Brand colour, not the green used elsewhere in this page. That green is
   semantic -- capacity, a valid drop target, a good outcome -- and navigation is
   not a state, it is the identity of the thing you are looking at. */
.side-item.on { background: rgba(122,46,46,.09); color: var(--accent-dark); font-weight: 700; }
.side-label { flex: 1 1 auto; }
.side-pill {
  min-width: 20px; padding: 1px 7px; border-radius: 999px;
  font-size: 12px; font-weight: 700; color: #fff; background: var(--accent);
  text-align: center;
}

/* The pending requests under the sections, while Apt Requests is open. One
   line each, the one on screen marked in the brand colour like the section
   itself -- a way to jump straight to a request instead of arrowing through
   the queue one step at a time. Alex, 2026-09-08. */
.side-sub {
  display: flex; flex-direction: column; gap: 1px;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(0,0,0,.08);
  /* This is the part that scrolls. `min-height: 0` is not optional: a flex
     child refuses to shrink below its content without it, so the overflow
     never engages and the sidebar grows anyway. `overscroll-behavior` stops
     the page scrolling on once the list hits its end. */
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
}
.side-sub[hidden] { display: none; }
.side-sub-item {
  display: block; width: 100%; padding: 6px 11px 6px 18px;
  font: inherit; font-size: 13px; line-height: 1.25; text-align: left;
  color: var(--ink-soft); background: none; border: 0; border-radius: 7px;
  cursor: pointer;
}
.side-sub-item:hover { background: rgba(0,0,0,.045); color: var(--ink); }
.side-sub-item.on { background: rgba(122,46,46,.09); color: var(--accent-dark); font-weight: 700; }
.side-sub-item small { display: block; font-size: 11.5px; font-weight: 500; opacity: .75; }

@media (max-width: 720px) {
  /* The sidebar becomes a row again rather than eating half a phone screen. */
  .split { flex-direction: column; gap: 14px; }
  /* A phone has the arrows; a column of requests under a row of sections
     would push the request itself off the screen. */
  .side-sub { display: none; }
  .side-list {
    flex-direction: row; flex: 0 0 auto; width: 100%; overflow-x: auto;
    border-right: 0; border-bottom: 1px solid rgba(0,0,0,.10);
    padding: 0 0 8px;
    /* A row of sections across the top does not stick or cap: undo both, or a
       phone gets a 178px-tall strip with its own scrollbar. */
    position: static; max-height: none;
  }
  .side-item { width: auto; white-space: nowrap; }
}

/* ---- Staff: the SMS list ------------------------------------------------ */
.sms-list { display: flex; flex-direction: column; gap: 2px; }
.sms-row {
  display: flex; flex-direction: column; gap: 3px; width: 100%;
  padding: 11px 13px; font: inherit; text-align: left; color: inherit;
  background: none; border: 0; border-radius: 9px; cursor: pointer;
}
.sms-row:hover { background: rgba(0,0,0,.045); }
.sms-row-top { display: flex; justify-content: space-between; gap: 10px; }
.sms-who { font-weight: 700; font-size: 15px; }
.sms-when { font-size: 12px; color: var(--ink-soft); flex: 0 0 auto; }
.sms-row-sub { font-size: 12px; color: var(--ink-soft); }
.sms-last {
  font-size: 14px; color: var(--ink-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sms-mine { color: var(--ink-soft); font-weight: 600; }
/* Unread: a count beside the name, and the last line in full ink. The name is
   already bold on every row, so weight alone could not say it. */
.sms-row.sms-unread { background: #fbf3f1; }
.sms-row.sms-unread .sms-last { color: var(--ink); font-weight: 600; }
.sms-unread-n { flex: 0 0 auto; margin-left: auto; margin-right: 8px; min-width: 20px;
  padding: 1px 7px; border-radius: 999px; background: #a83a26; color: #fff;
  font-size: 12px; font-weight: 700; text-align: center; line-height: 18px; }
.bubble-read { font-size: 11px; color: var(--ink-soft); margin-top: 2px; }
.sms-shared { color: #8a6d1f; font-weight: 600; }
.sms-stopped { color: #a33; font-weight: 700; }

/* ---- Staff: one conversation ------------------------------------------- */
.sms-dialog {
  position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; padding: 20px;
  background: rgba(0,0,0,.42);
}
/* Wide enough to read a conversation in. Alex, 2026-09-09: "can we also make
   the SMS window wider? it is so narrow." 560px was chosen when this held
   nothing but bubbles; it now carries a template row, two language pickers
   and the translated text that will be sent. */
.sms-sheet {
  display: flex; flex-direction: column; width: min(900px, 100%);
  max-height: min(860px, 92vh); background: #fff; border-radius: 14px;
  overflow: hidden; box-shadow: 0 18px 50px rgba(0,0,0,.28);
}
/* Two rows, not one. The controls used to share a row with the customer's
   name, so they moved about depending on how long the name was -- Alex,
   2026-09-09: "the menus move depending on how wide the customer name is."
   Now the name has a row of its own and may grow or be truncated without
   anything else shifting, and the languages sit on a row that is always in
   the same place. */
.sms-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 12px;
  padding: 16px 18px; border-bottom: 1px solid rgba(0,0,0,.10);
}
.sms-head > :first-child { min-width: 0; }
.sms-head h2 {
  margin: 0; font-size: 18px;
  /* A very long name is cut rather than allowed to push the layout around. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sms-detail { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 4px; }
.sms-detail-item { font-size: 13px; color: var(--ink-soft); }
.sms-close {
  font-size: 26px; line-height: 1; padding: 0 6px; color: var(--ink-soft);
  background: none; border: 0; cursor: pointer;
}

/* The thread. Scrolls on its own so the composer stays put. */
.sms-thread {
  flex: 1 1 auto; overflow-y: auto; padding: 16px 18px;
  display: flex; flex-direction: column; gap: 8px; background: #faf9f8;
}
.bubble { max-width: 78%; padding: 8px 12px; border-radius: 15px; }
.bubble-in  { align-self: flex-start; background: #ececec; border-bottom-left-radius: 5px; }
/* The clinic's own messages take the brand colour, the same one the Send
   button uses -- a green bubble beside a maroon button read as two apps. */
.bubble-out { align-self: flex-end; background: var(--accent-dark); color: #fff;
              border-bottom-right-radius: 5px; }
.bubble-body { font-size: 15px; line-height: 1.35; white-space: pre-wrap;
               overflow-wrap: anywhere; }
.bubble-when { margin-top: 3px; font-size: 11px; opacity: .72; }

.sms-stopped-note {
  margin: 0; padding: 10px 18px; font-size: 13px;
  color: #a33; background: #fdf1f1; border-top: 1px solid rgba(0,0,0,.08);
}
.sms-composer {
  display: flex; gap: 9px; align-items: flex-end;
  padding: 12px 14px; border-top: 1px solid rgba(0,0,0,.10);
}
/* (0,2,0) so it beats `.field textarea` at (0,1,1) — that rule stretches a
   textarea to the full width and would push the Send button off the row. */
.sms-composer textarea {
  flex: 1 1 auto; min-height: 40px; max-height: 130px; resize: none;
  padding: 10px 12px; font: inherit; font-size: 15px;
  border: 1px solid rgba(0,0,0,.16); border-radius: 19px; background: #fdfcfc;
}
.sms-composer textarea[disabled] { background: #f2f0ef; color: var(--ink-soft); }
.sms-composer .sms-send { width: auto; flex: 0 0 auto; border-radius: 19px; padding: 10px 18px; }

/* Media in a thread. People photograph wounds, lumps, limps and medication
   labels; 27 of the messages already on the account carry one. */
.bubble-media {
  display: block; width: 100%; max-width: 320px; margin: 0 0 5px;
  border-radius: 10px; overflow: hidden; background: rgba(0,0,0,.06);
}
.bubble-media img, .bubble-media video {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
.bubble-audio { display: block; width: 100%; max-width: 320px; margin: 0 0 5px; }
.bubble-file {
  display: inline-block; margin: 0 0 5px; font-size: 14px;
  text-decoration: underline; color: inherit;
}
/* An image is the whole bubble when there are no words with it, so the bubble
   should not add its own padding around it. */
.bubble:has(> .bubble-media:only-child) { padding: 4px; }

/* Turning a stranger into a customer. Sits under the details in the thread
   header, and only appears when there is genuinely nobody on file. */
.sms-invite {
  margin: 8px 0 0; padding: 6px 12px; font: inherit; font-size: 13px;
  font-weight: 600; color: #fff; background: var(--accent);
  border: 0; border-radius: 999px; cursor: pointer;
}
.sms-invite:hover { filter: brightness(1.08); }
.sms-invite[disabled] { opacity: .6; cursor: default; }

/* Customer or enquiry. 23 of the 56 conversations on the clinic's number are
   from people with no record at all, and those are the ones that need answering
   rather than filing. */
.tag {
  display: inline-block; margin-left: 7px; padding: 1px 7px;
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  border-radius: 999px; vertical-align: 1px;
}
.tag-customer { color: #4a3a1c; background: rgba(0,0,0,.07); }
/* An enquiry is the one that wants doing something about, so it is the one that
   carries the brand colour rather than a grey. */
.tag-enquiry { color: #fff; background: var(--accent); }

/* The invitation row: the button, the language it will be written in, and why
   that language was chosen. */
.invite-row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 8px 0 0; }
.lang-pick { display: inline-flex; gap: 12px; }
.lang-pick label {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; font-weight: 600; margin: 0; cursor: pointer;
}
.lang-pick input[type="radio"] {
  width: 15px; height: 15px; margin: 0; padding: 0;
  accent-color: var(--accent-dark); cursor: pointer;
}
/* Why Spanish is selected. Said out loud, because a radio that moved on its own
   is otherwise just odd. */
.lang-why { font-size: 12px; color: var(--ink-soft); font-style: italic; }

/* The homepage invitation: one button, one field, Send. Used with a customer on
   the telephone, so there is deliberately nothing else to read out loud while
   they wait. */
.sms-cold-btn { width: auto; margin: 0 0 12px; padding: 8px 16px; font-size: 14px; }
.sms-sheet--small { width: min(420px, 100%); }
.sms-cold-body { padding: 16px 18px; }
/* The condition the message goes out under, stated rather than made into a tick
   box: a box ticked on every send is a click people stop reading. */
.sms-cold-note {
  margin: 0 0 12px; padding: 9px 11px; font-size: 12.5px; line-height: 1.4;
  color: #6b5327; background: #fdf7e8; border-radius: 8px;
}
.sms-cold-body input[type="tel"] {
  width: 100%; padding: 10px 12px; font: inherit; font-size: 16px;
  border: 1px solid rgba(0,0,0,.16); border-radius: 8px; background: #fdfcfc;
}
/* Exactly what will be sent. Shown because somebody about to text a stranger
   should be able to see the words before they go. */
.sms-cold-preview {
  margin: 10px 0 0; padding: 9px 11px; font-size: 12.5px; line-height: 1.45;
  color: var(--ink-soft); background: #f4f2ee; border-radius: 8px;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.sms-cold-cancel {
  padding: 10px 16px; font: inherit; font-size: 15px; font-weight: 600;
  color: var(--ink); background: #fff; border: 1px solid #cfcfc6;
  border-radius: 8px; cursor: pointer;
}


/* --- blood work review ---------------------------------------------------
   The review screen. Its one job is to get a vet reading the document rather
   than reading a summary of it, so the document is large and everything else is
   small and out of the way. */
.bw-sheet { width: min(920px, 100%); max-height: 94vh; }

.bw-docs { overflow-y: auto; padding: 0 18px; }
.bw-doc { margin: 0 0 20px; }
.bw-doc figcaption {
  display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline;
  font-weight: 600; font-size: 14px; margin-bottom: 6px;
}
.bw-doc-meta { font-weight: 400; font-size: 13px; color: var(--ink-soft); }
/* Tall on purpose. A lab panel in a 200px box is a document nobody reads. */
.bw-doc-view {
  display: block; width: 100%; height: 60vh; min-height: 380px;
  border: 1px solid #dcd6ce; border-radius: 8px; background: #fff;
}
img.bw-doc-view { height: auto; max-height: 70vh; object-fit: contain; }
.bw-doc-foot { margin: 6px 0 0; font-size: 13px; }

.bw-decide { display: flex; gap: 10px; flex-wrap: wrap; padding: 14px 18px; }
.bw-decide .portal-btn { width: auto; }
.bw-ok { background: #2f6f4f; }
.bw-quiet { background: #fff; color: #a32d2d; border: 2px solid #a32d2d; }
#bw-why { padding: 0 18px 6px; }
.bw-input {
  width: 100%; font: inherit; padding: 10px 12px; border: 1px solid #cfc8bf;
  border-radius: 8px; background: #fff; resize: vertical; line-height: 1.5;
}
.bw-input:focus { outline: 2px solid var(--accent-dark); outline-offset: -1px; }
.bw-radio {
  display: flex; gap: 10px; align-items: flex-start;
  font-size: 14px; line-height: 1.5; margin: 0 0 10px;
}
.bw-radio input { margin-top: 3px; width: 18px; height: 18px; flex: none; }

/* The queue row's state. Words, never a colour alone. */
.bw-tag {
  font-size: 12px; font-weight: 700; border-radius: 999px;
  padding: 2px 9px; white-space: nowrap;
}
.bw-tag-wait { background: #f6ead3; color: #7a5a1e; }
.bw-tag-ok   { background: #dce9dd; color: #22452c; }
.bw-tag-no   { background: #f6dcdc; color: #7a2e2e; }

/* Held on the request card. Reuses .warn so it reads like the capacity warning
   beside it -- same shape of problem: the clinic cannot book this yet. */
.warn-bw { border-color: #c9a227; background: #fdfaf0; }
.warn-bw h4 { color: #7a5a1e; }

/* Who is signed in, in the staff footer. */
#whoami { color: var(--ink-soft); }
.whoami-warn { color: #a32d2d; font-weight: 600; }

/* Language choice on the cold-text composer. Two radios, inline, because it is
   one decision made in passing on the way to the message. */
.sms-lang { display: flex; gap: 18px; margin: 0 0 10px; }
.sms-lang label {
  display: flex; align-items: center; gap: 7px;
  font-size: 14px; font-weight: 600; cursor: pointer;
}
.sms-lang input { width: 17px; height: 17px; flex: none; }
/* The segment count. Red once it would cost more than the composer allows. */
#sms-cold-count.bad { color: #a32d2d; font-weight: 700; }

/* Field labels and the button row inside the small dialogs. Both classes were
   already in the markup and had never been styled -- the labels rendered as
   body text and the buttons stacked. Cheap to fix while adding two more of each. */
.fl {
  display: block; font-size: 13px; font-weight: 700; letter-spacing: .01em;
  color: var(--ink-soft); margin: 12px 0 5px;
}
.dlgbtns { display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px; }
.dlgbtns .portal-btn { width: auto; }

/* The template picker on the SMS composer. */
#sms-cold-template {
  width: 100%; font: inherit; padding: 9px 12px; border: 1px solid #cfc8bf;
  border-radius: 8px; background: #fff;
}
#sms-cold-template:focus { outline: 2px solid var(--accent-dark); outline-offset: -1px; }
#sms-cold-hint { margin: 5px 0 0; }

/* Who is signed in and the way out, in the header rather than at the foot of the
   page. Alex, 2026-09-07: "Right now the current account and log out is at the
   very bottom. Can we have that at the very top, like left from the logo?"
   The brand sits left and the logo right, so this goes between them, pushed up
   against the logo. */
.nav-account {
  display: flex; align-items: center; gap: 14px;
  margin-left: auto; margin-right: 18px;
  font-size: 13px; color: var(--ink-soft);
}
.nav-account a { color: inherit; text-decoration: none; border-bottom: 1px solid currentColor; }
.nav-account a:hover { color: var(--accent-dark); }
#whoami { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 34ch; }
.whoami-warn { color: #a32d2d; font-weight: 600; }

/* On a narrow screen the address is the first thing to go: the two links are
   what somebody is reaching for, and the logo still says whose clinic it is. */
@media (max-width: 700px) {
  .nav-account { gap: 10px; margin-right: 10px; font-size: 12px; }
  #whoami { display: none; }
}
@media (max-width: 460px) {
  .nav-brand { display: none; }
}

/* --- blood work: whose turn ------------------------------------------------ */
/* Two numbers on one pill: vet / desk. */
.bw-two b { font-weight: 700; }
.bw-two i { font-style: normal; opacity: .55; margin: 0 2px; }
.bw-tag-desk { background: #e2e0ef; color: #35315c; }
/* The refusal that is a clinical statement sits apart from the everyday no,
   and reads as the graver of the two. */
.bw-decline { background: #fff; color: #7a2e2e; border: 2px solid #c9a0a0; }
.bw-decline:hover { background: #fdf3f2; }
/* The front desk's turn: what to do, in a band that is not a form. */
.bw-deskturn { padding: 0 18px 8px; }
.bw-deskturn p:first-child {
  font-size: 15px; font-weight: 600; margin: 0 0 6px;
  padding: 12px 14px; border-radius: 8px; background: #fdfaf0; border: 1px solid #e3d5a4;
}
/* What was read off a document, beside its name. A count, never a verdict. */
.bw-doc-read { font-weight: 400; font-size: 13px; color: #35315c; }


/* The blood work line on a customer's request card: what happens next, as a
   sentence, and the vet's words to them underneath. */
.bw-note {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 8px;
  background: #fdfaf0; border: 1px solid #e3d5a4; font-size: 14px; line-height: 1.5;
}
.bw-note-good { background: #f7faf8; border-color: #bfd8c9; }
.bw-note-bad  { background: #fdf3f2; border-color: #e0b4b4; }
.bw-note em { display: block; margin-top: 4px; color: var(--ink-soft); }
/* On the vet's screen a customer's own upload is set apart from Shepherd's files. */
.bw-doc-customer { border-left: 3px solid #c9a227; padding-left: 12px; }


/* A sedating line on a request held for blood work: ticked as the customer
   asked, greyed and inert. The lines that may go ahead -- the draw, an exam --
   stay live beside it. */
.line-held { opacity: .5; pointer-events: none; }
/* The repost: cleared, and the original lines live again. */
.warn-bw-ok { border-color: #2f6f4f; background: #f7faf8; }
.warn-bw-ok h4 { color: #22452c; }

/* A settled blood work record: grey, because nobody owes it a move. */
.bw-tag-muted { background: #ebe8e3; color: #5b5650; }
/* The record: dated steps, who took them, and the animal it is about. */
/* Beside the age on a request card: the blood work decision that answers it. */
.age-bw { display: inline-block; margin-top: 3px; font-size: 13px; color: var(--ink-soft); }
.age-bw-ok { color: #1f6b3a; font-weight: 600; }

/* Where a blood work case stands: the decision, what the customer was told,
   whose move is next. Sits above the timeline, set off so the eye lands on it
   before the history. */
.bw-now { margin: 0 0 12px; padding: 10px 12px; border-radius: 9px; background: rgba(122,46,46,.06); }
.bw-now-line { margin: 0 0 4px; font-size: 13.5px; }
.bw-now-line b { font-weight: 700; margin-right: 4px; }
.bw-now-next { margin-top: 6px; color: var(--accent-dark); }

.bw-timeline { padding: 0 18px 10px; border-bottom: 1px solid rgba(0,0,0,.08); margin-bottom: 10px; }
.bw-step { margin: 0 0 4px; font-size: 13px; line-height: 1.5; }
.bw-step b { font-variant-numeric: tabular-nums; margin-right: 6px; }
.bw-step-who { color: var(--ink-soft); }
.bw-step-pet code { font-size: 12px; background: #f1eee9; padding: 1px 5px; border-radius: 4px; }

/* The page is waiting on the server for something a person just asked for. */
html.busy, html.busy * { cursor: progress !important; }

/* Before the visit: the forms to sign and the deposit, on a booked request in
   the customer's account. */
.before-visit { margin: 12px 0 0; padding: 12px 14px; border: 1px solid rgba(0,0,0,.1); border-radius: 8px; background: #fbfaf7; font-size: 14.5px; }
.before-visit .bv-lede { margin: 6px 0 6px; }
.before-visit .bv-forms { margin: 0 0 6px 1.1em; padding: 0; }
.before-visit .bv-forms li { margin: 0 0 6px; }
.before-visit .bv-sign { font-weight: 600; }
.before-visit .bv-when { color: #6b6b6b; font-size: 13px; }
.before-visit .bv-ok { color: #2f6b45; font-weight: 700; }
.before-visit .bv-deposit { margin: 8px 0 0; }
/* The same two things on the staff card. */
.rec .todo { display: inline-block; margin: 2px 0; }
.rec .todo-ok { color: #2f6b45; font-weight: 600; }
.rec .todo-pending { color: #8a5a12; font-weight: 600; }
/* A booked request still waiting on the customer: in the working queue, and
   not greyed like a finished one. */
.rec-settled.rec-waiting { opacity: 1; border-color: #dcc48c; background: #fffcf3; }
.rec-tag--waiting { background: #f6ecd0; border-color: #e0c98a; color: #6b4d0c; }
.rec-tag--phone   { background: #e8e6f3; border-color: #c6c1e0; color: #3d3570; }
/* The desk's small in-line actions on a card or the schedule popup: "Payment
   link sent", "not sent after all". A link-looking button, so it sits in a
   sentence. */
.linkish { background: none; border: 0; padding: 0; font: inherit; color: var(--accent-dark, #7a2e2e); text-decoration: underline; cursor: pointer; }
.linkish:disabled { opacity: .5; cursor: default; }
.linkish:focus-visible { outline: 2px solid #7a2e2e; outline-offset: 2px; }
/* "Add a form": a select and a button on one line. */
.add-form { display: inline-flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 4px; }
.add-form select { font: inherit; font-size: 14px; padding: 4px 6px; max-width: 320px; }
.portal-btn.small { width: auto; padding: 5px 10px; font-size: 13.5px; }

/* The fasting notice: a warning box with a triangle big enough that nobody
   can miss it. Alex, 2026-09-09: "totally critical for them to see". */
.warn-fast { display: flex; gap: 14px; align-items: center; margin: 14px 0 0; padding: 14px 16px;
  border: 2px solid #c0392b; border-radius: 10px; background: #fff4f2; color: #1c1b19; font-size: 16px; line-height: 1.5; }
.warn-fast strong { color: #a3282b; }
.warn-fast-icon { flex: 0 0 auto; width: 52px; height: 52px; }
@media (max-width: 480px) { .warn-fast-icon { width: 42px; height: 42px; } }

/* The two tabs on the customer's appointments: what is ahead, what is past. */
.acct-tabs { display: flex; gap: 6px; margin: 4px 0 14px; border-bottom: 1px solid rgba(0,0,0,.12); }
.acct-tabs [role="tab"] { font: inherit; font-weight: 600; font-size: 15px; background: none; border: 0;
  border-bottom: 3px solid transparent; padding: 8px 12px 10px; margin-bottom: -1px; color: #6b6b6b; cursor: pointer; }
.acct-tabs [role="tab"][aria-selected="true"] { color: var(--accent-dark); border-bottom-color: var(--accent-dark); }
.acct-tabs [role="tab"]:focus-visible { outline: 2px solid var(--accent-dark); outline-offset: 2px; border-radius: 4px; }
.acct-tabs .tab-n { display: inline-block; min-width: 1.4em; padding: 0 5px; margin-left: 4px; border-radius: 999px;
  background: rgba(0,0,0,.08); font-size: 12.5px; font-weight: 700; color: #1c1b19; text-align: center; }
.acct-tabs .tab-n:empty { display: none; }

/* A link to copy into Messages: the pairing link, or one form for the iPad. */
.link-out { margin: 8px 0 0; padding: 12px 14px; border: 1px solid #c6c1e0; border-radius: 9px; background: #f6f5fb; font-size: 14.5px; }
.link-out p { margin: 0 0 8px; }
.link-out .link-url { width: 100%; box-sizing: border-box; font: inherit; font-size: 13px; padding: 7px 9px;
  border: 1px solid rgba(0,0,0,.15); border-radius: 6px; background: #fff; margin-bottom: 8px; }
.device-row { margin: 0 0 8px; padding: 8px 12px; border: 1px solid rgba(0,0,0,.1); border-radius: 8px; background: #fbfaf7; }
.device-row.retired { opacity: .6; }

/* The condition on a booked visit, and how to cancel it. The condition is
   stated once, firmly, without shouting. */
.before-visit .bv-gate { margin: 10px 0 0; padding: 9px 12px; border-left: 3px solid var(--accent-dark);
  background: rgba(122,46,46,.06); font-weight: 600; }
.before-visit .bv-cancel { margin: 8px 0 0; font-size: 13.5px; color: #6b6b6b; }
.before-visit .bv-cancel--now { color: #7a2e2e; font-weight: 600; }

/* Taking an appointment off the schedule: never one click, never silent. */
.appt-cancel { margin-top: 14px; padding-top: 12px; border-top: 1px solid rgba(0,0,0,.1); }
.appt-cancel textarea { width: 100%; box-sizing: border-box; font: inherit; font-size: 14px;
  padding: 7px 9px; border: 1px solid rgba(0,0,0,.18); border-radius: 6px; margin: 4px 0 6px; }
.appt-cancel .f { display: block; font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-soft); }

/* The template picker above the conversation's composer. Choosing one fills
   the box; sending stays a separate press. */
.sms-tpl { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  padding: 9px 14px 0; font-size: 13.5px; }
.sms-tpl select { font: inherit; font-size: 13.5px; padding: 4px 6px; max-width: 210px; }
.sms-tpl-lang { display: inline-flex; gap: 10px; }
.sms-tpl-lang label { display: inline-flex; gap: 4px; align-items: center; color: var(--ink-soft); }
.sms-tpl .field-hint { flex: 1 1 100%; margin: 0; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* Reading and writing a conversation in another language. The picker at the
   top governs both directions; nothing translates by itself. */
/* The close button keeps the top right corner to itself. */
.sms-head-right { grid-column: 2; grid-row: 1; display: flex; gap: 10px; align-items: flex-start; }
/* And the languages get a row of their own, across the full width, so they
   are in the same place in every conversation. */
.sms-langs {
  grid-column: 1 / -1; grid-row: 2;
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap; font-size: 13px;
}
.sms-langs select { font: inherit; font-size: 13.5px; padding: 4px 6px; }
.sms-langs label { color: var(--ink-soft); font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; font-size: 11px; }
.sms-langs label + select { margin-right: 10px; }
.sms-langs .lang-why { color: var(--ink-soft); font-size: 12px; font-style: italic; }
.bubble-translated { font-style: italic; }
.sms-outgoing { margin: 0; padding: 9px 14px 0; }
.sms-outgoing-label { margin: 0 0 4px; font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); }
.sms-outgoing-text { min-height: 20px; padding: 9px 12px; border-radius: 12px;
  background: #eef2ee; border: 1px dashed #aecab3; font-size: 15px; line-height: 1.45;
  white-space: pre-wrap; overflow-wrap: anywhere; }

/* VIEW CUSTOMER, on a request card.
   Hidden by default and revealed by a class on <body>, set once
   /api/portal/me confirms the signed-in user holds the View customer role.
   A class rather than a flag read while painting, because cards can paint
   before that answer arrives -- a flag leaves the button missing on the first
   paint and present on the next, which reads as the feature being unreliable
   rather than as a permission being checked. */
.cv-open { display: none; }
body.may-view-customer .cv-open {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 8px;
  border: 1px solid #c9b9b7;
  border-radius: 999px;
  font-size: 12px;
  line-height: 1.6;
  white-space: nowrap;
  text-decoration: none;
  color: #6b4a47;
  background: #fbf7f6;
}
body.may-view-customer .cv-open:hover,
body.may-view-customer .cv-open:focus-visible {
  border-color: #a8807c;
  color: #4a2f2d;
  background: #f5ecea;
}

/* ==================== CLINIC REQUESTS ==================== */
/* The clinic's own todo log. A thread is a conversation, so it reads as one:
   indented off the card, quiet, with the handovers in smaller type than what
   people actually said. */
.cr-thread {
  margin: 10px 0 4px;
  padding-left: 12px;
  border-left: 2px solid #e4dbd9;
}
.cr-line { margin: 0 0 10px; font-size: 14px; line-height: 1.5; }
.cr-line:last-child { margin-bottom: 0; }
.cr-reply { margin-top: 10px; }
.cr-reply textarea {
  width: 100%; font: inherit; font-size: 14px; padding: 8px 10px;
  border: 1px solid #cfc8bf; border-radius: 8px; resize: vertical;
}
.cr-reply label { font-size: 13px; color: var(--ink-soft); }
.cr-reply select { font: inherit; font-size: 13px; margin-left: 4px; }

/* A CHIP THAT NEEDS A DECISION WEARS A RED BORDER.
   Alex, 2026-09-21: "there are two types of filters: 1. the ones that need a
   decision 2. the ones that need no immediate attention." The second kind --
   Past/Missed, Open, Engaged, Declined, Cancelled, Booked, All -- are reference
   lists and keep the plain chip. Border only: the fill still carries "which one
   am I looking at", and two things competing for the same signal would leave
   neither readable. */
/* The weight comes from an INSET SHADOW rather than a thicker border: a 2px
   border makes these chips a pixel taller than the plain ones and the row stops
   lining up. This reads as a solid 2px edge and occupies the same box. */
.chip.chip-act {
  border-color: #a32d2d;
  box-shadow: inset 0 0 0 1px #a32d2d;
}
/* The selected one already inverts to the brand fill; the edge stays visible
   against it, so the distinction does not disappear exactly when the chip is
   the one in use. */
.chip.chip-act.on {
  border-color: #6d1f1f;
  box-shadow: inset 0 0 0 1px #6d1f1f;
}

/* ==================== A REQUEST'S THREAD ==================== */
/* Indented off the card and quiet: it is the history, not the work. */
.rt-thread { margin: 4px 0 2px; padding-left: 12px; border-left: 2px solid #e4dbd9; }
.rt-line { margin: 0 0 10px; font-size: 14px; line-height: 1.5; }
.rt-line:last-child { margin-bottom: 0; }
/* What was DONE reads differently from what was SAID. The out-of-portal ones
   are marked, not shamed -- it is what the stats are made of, and the point is
   to show that the portal route costs less, not to tell anybody off. */
.rt-act { font-style: normal; font-weight: 650; color: var(--ink-soft); font-size: 13px; }
.rt-act-out { color: #8a5a2b; }
.rt-reply { margin-top: 10px; }
.rt-reply textarea {
  width: 100%; font: inherit; font-size: 14px; padding: 8px 10px;
  border: 1px solid #cfc8bf; border-radius: 8px; resize: vertical;
}
.rt-reply label { font-size: 13px; color: var(--ink-soft); }
.rt-reply select { font: inherit; font-size: 13px; margin-left: 4px; }

/* The household's list on a request card: the rest of what this customer is
   waiting on, and what they already have booked. */
.hh-title { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-soft); margin: 4px 0 2px; }
.hh-line, .hh-booked { display: block; padding: 2px 0; }
.hh-line { text-decoration: none; color: inherit; }
.hh-line:hover b { text-decoration: underline; }
.hh-stage { font-size: 11px; padding: 1px 6px; border-radius: 999px; background: #eee9e6; color: #5a4a44; margin-left: 4px; }
.hh-when { font-size: 11px; color: var(--ink-soft); }
.hh-unreadable { color: var(--ink-soft); }
.hh-l0 { padding: 2px 0; }
.hh-l1 { padding: 1px 0 1px 16px; }
.hh-l2 { padding: 1px 0 1px 32px; }
.hh-l3 { padding: 1px 0 1px 48px; }
.hh-mine { font-size: 11px; padding: 1px 6px; border-radius: 999px; background: #e3eee7; color: #2f5a3f; margin-left: 4px; }
.warn-list { margin: 0 0 10px 18px; padding: 0; font-size: 14px; line-height: 1.5; }
.warn-list li { margin: 0 0 6px; }
/* A breed the desk is warned about, the moment they look. */
dt.breed-alert { color: #a83a26; font-size: 18px; line-height: 1; }
dd.breed-alert { background: #fbeae6; border: 1px solid #e3b7ad; border-radius: 8px; padding: 6px 10px; color: #6a2418; }
.ad-alert { margin: 8px 0 0; padding: 8px 12px; background: #fbeae6; border: 1px solid #e3b7ad; border-radius: 8px; color: #6a2418; font-weight: 600; }
