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

html,
body {
  margin: 0;
}

body {
  font-family: var(--font-sans);
  color: var(--text);
  background: var(--app-bg);
  -webkit-font-smoothing: antialiased;
}

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

input:focus,
textarea:focus {
  outline: none;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-thumb {
  background: #cdd9d9;
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

/* ---- animations (names kept from the design reference) ---- */
@keyframes acFade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes acRise {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes acPop {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

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

/**
 * Print isolation: only the loyalty card reaches the paper.
 *
 * This is laid out in normal flow rather than by `position: fixed`. Taking the
 * card out of flow made it shrink-to-fit -- 408px against 484px on screen --
 * and once `aspect-ratio` derived a height from that narrower box, the content
 * no longer fitted and `overflow: hidden` cut through the QR, taking the tier
 * pill and SCAN AT COUNTER with it.
 *
 * `height: auto` with a `min-height` is the belt to that braces: the card keeps
 * its credit-card proportions whenever the content fits, and grows instead of
 * clipping when it does not. A cut-off card is worse than a slightly tall one.
 *
 * 484px is 128mm at 96dpi, so scaling by 0.669 prints it at 85.6mm -- the real
 * width of a credit card, so it can be cut out and kept in a wallet. Scaling
 * the whole element keeps every padding and type size in proportion, which
 * setting a millimetre width would not.
 */
@media print {
  @page {
    margin: 14mm;
  }

  html,
  body {
    background: #fff !important;
  }

  /* The app itself, and the modal's own chrome, are not part of the artefact.
     #modal-root is a sibling of the shell, not part of it -- excluding it here
     is what leaves the card on the page at all. */
  .app__app > *:not(#modal-root),
  .lc__header,
  .lc__footer {
    display: none !important;
  }

  /* Strip the overlay and dialog back to nothing so the card lays out in flow. */
  .modal__overlay {
    position: static !important;
    display: block !important;
    background: none !important;
    backdrop-filter: none !important;
    animation: none !important;
  }

  .modal__dialog {
    width: auto !important;
    max-width: none !important;
    max-height: none !important;
    overflow: visible !important;
    background: none !important;
    box-shadow: none !important;
    padding: 0 !important;
    border-radius: 0 !important;
    animation: none !important;
  }

  #acPrintCard {
    width: 484px !important;
    height: auto !important;
    min-height: 305px !important;
    aspect-ratio: auto !important;
    margin: 0 auto !important;
    transform: scale(0.669) !important;
    transform-origin: top center !important;
    box-shadow: none !important;
    /* Without this the gradient and the QR come out as blank paper. */
    print-color-adjust: exact !important;
    -webkit-print-color-adjust: exact !important;
  }

  /**
   * The points balance is deliberately not printed. A card is a physical object
   * that outlives the number on it: it would be wrong by the patient's next
   * visit, and a member holding a card that disagrees with the counter is worse
   * than one that never claimed to know.
   */
  #acPrintCard [data-card-points] {
    display: none !important;
  }
}

/**
 * Visually hidden, still announced. Hoisted here from the sidebar and the
 * WhatsApp modal, which defined it identically.
 */
.u-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;
}

/**
 * htmx only swaps an out-of-band target that exists, so the refills badge is
 * always rendered and hidden at zero rather than omitted. Its own
 * `display: inline-flex` would otherwise beat the UA rule for [hidden].
 */
[hidden] {
  display: none !important;
}

/* Toasts dismiss themselves without JavaScript. 2800ms is the prototype's. */
@keyframes acToastOut {
  to {
    opacity: 0;
    visibility: hidden;
  }
}

/* Icons are <use> references into one inline sprite; colour and weight are set
   by the referencing element so a single symbol serves every call site. */
.icon {
  flex: none;
}

/* Initials avatar. Size and colour are per-instance and therefore inline. */
.avatar {
  flex: none;
  border-radius: 50%;
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  line-height: 1;
}

/**
 * The toast stack. The prototype put `position:fixed; left:50%` on the toast
 * itself, which stacks two messages on top of each other; the positioning
 * belongs to the container. A single toast renders identically.
 */
.toasts {
  position: fixed;
  bottom: 28px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 80;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  pointer-events: none;
}

/* Paging footer. The design has no reference for it -- six seeded patients
   never paginate -- so it is built from the same tokens as the table it ends. */
.pagination {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 22px;
  border-top: 1px solid var(--border-faint);
  font-size: 12.5px;
}

.pagination__link {
  color: var(--teal);
  font-weight: 700;
  text-decoration: none;
}

.pagination__status {
  margin-left: auto;
  color: var(--text-muted-2);
}

/**
 * Several controls the prototype drew as <button> are links here, because they
 * navigate -- and a link that navigates must stay a link to work with the
 * keyboard, the middle mouse button and JavaScript disabled. The ported rules
 * set their own colour and background, so only the anchor defaults need
 * clearing.
 */
a.sb__nav-item,
a.tb__add-btn,
a.db__view-all,
a.db__remind-btn,
a.pt__patient,
a.pt__icon-btn,
a.rx__patient-cell,
a.rx__new,
a.rx__dispense,
a.pp__rx-add,
a.pp__rx-dispense,
a.rx__remind,
a.pp__back,
a.pp__action,
a.wa__close,
a.lc__close-button,
a.lc__close-action {
  text-decoration: none;
  color: inherit;
}

/* A button centres its own label; an anchor is inline and does not, so the
   controls the design drew as buttons need the box treatment restoring. */
a.lc__close-action {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
}

/* These carry their own foreground from the ported rules; `color: inherit`
   above would otherwise override it through the cascade order. */
a.tb__add-btn { color: #fff; }
a.db__view-all { color: var(--teal); }
a.db__remind-btn { color: var(--green-soft-fg); }
a.rx__remind { color: var(--green-soft-fg); }
a.rx__new,
a.pp__rx-add { color: #fff; }
a.rx__dispense,
a.pp__rx-dispense { color: var(--teal); }
a.pp__back { color: var(--text-muted); }
