:root{ /* courtyard tokens for the portal screens (design names; distinct from the portal's own --cream/--ink) */
  --cream-50:#FBF7EE; --cream-100:#F5EEDF; --cream-200:#ECE2CC; --cream-300:#DCCBA8;
  --clay-200:#C9B393; --clay-400:#A0825F; --clay-500:#8B6B47; --clay-600:#6B4A30; --clay-700:#4E3422;
  --ink-300:#9C8676; --ink-500:#6B5346; --ink-700:#4A3326; --ink-800:#34221A; --ink-900:#261810;
  --shadow-soft:0 18px 60px -20px rgba(38,24,16,0.18); --shadow-deep:0 40px 120px -30px rgba(38,24,16,0.35);
}
/* HansMed Patient Portal — portal-specific styles
   Reuses tokens from styles.css (loaded first). */

body { background: var(--cream-50); }

#app {
  min-height: 100vh;
  position: relative;
  padding-bottom: 24px;
}

/* ─── Portal top bar (logo · centered nav · right cluster) ─── */
.p-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 32px;
  padding: 16px 32px;
  background: rgba(251, 247, 238, 0.92);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid rgba(78, 52, 34, 0.06);
}
.p-bar-right {
  display: flex;
  align-items: center;
  gap: 14px;
  justify-self: end;
}

/* Centered nav inside the header */
.p-nav {
  display: flex;
  align-items: stretch;
  gap: 2px;
  background: var(--cream-100);
  border: 1px solid rgba(78, 52, 34, 0.08);
  border-radius: 999px;
  padding: 4px;
  justify-self: center;
}
.p-nav-btn {
  position: relative;
  background: transparent;
  border: none;
  color: var(--ink-700);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.005em;
  transition: color 0.2s ease, background 0.25s ease;
  white-space: nowrap;
}
.p-nav-btn:hover { color: var(--ink-900); background: rgba(78, 52, 34, 0.05); }
.p-nav-btn.active {
  background: var(--ink-900);
  color: var(--cream-50);
}
.p-nav-icon {
  width: 16px; height: 16px;
  display: inline-flex; align-items: center; justify-content: center;
}
.p-nav-icon svg { width: 100%; height: 100%; }
.p-nav-badge {
  background: #c97157;
  color: var(--cream-50);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  border-radius: 999px;
  padding: 1px 6px;
  line-height: 1.4;
}

.p-avatar-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px 6px 6px;
  background: var(--cream-100);
  border: 1px solid rgba(78, 52, 34, 0.08);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s ease;
}
.p-avatar-pill:hover { background: var(--cream-200); }
.p-avatar-mini {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: linear-gradient(160deg, var(--clay-200), var(--clay-500));
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 600;
  font-size: 14px;
  color: var(--cream-50);
}
.p-avatar-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-900);
}
.p-avatar-pill .chev { font-size: 10px; color: var(--clay-500); margin-left: 2px; }

.p-lang {
  display: flex;
  background: var(--cream-200);
  border-radius: 999px;
  padding: 3px;
  font-size: 13px;
}
.p-lang button {
  border: none; background: transparent;
  padding: 5px 12px; border-radius: 999px;
  font-family: var(--font-ui); font-weight: 500;
  color: var(--ink-700); cursor: pointer;
}
.p-lang button.active { background: var(--ink-900); color: var(--cream-50); }

/* ─── Screen scaffold ─── */
.p-screen {
  max-width: 720px;
  margin: 0 auto;
  padding: 40px 28px 60px;
}
.p-screen-wide {
  max-width: 920px;
}

.p-greet {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 32px;
}
.p-greet-eyebrow {
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--clay-500);
  font-weight: 600;
}
.p-greet-title {
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 500;
  font-size: 38px;
  line-height: 1.1;
  color: var(--ink-900);
  margin: 0;
  letter-spacing: -0.005em;
}

.p-section-title {
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--clay-500);
  font-weight: 600;
  margin: 36px 0 14px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.p-section-title::before, .p-section-title::after {
  content: "";
  height: 1px;
  background: rgba(78, 52, 34, 0.18);
}
.p-section-title::after { flex: 1; }

/* ─── Cards ─── */
.p-card {
  background: var(--cream-100);
  border: 1px solid rgba(78, 52, 34, 0.06);
  border-radius: 16px;
  padding: 22px 24px;
  margin-bottom: 12px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.p-card.clickable {
  cursor: pointer;
}
.p-card.clickable:hover {
  transform: translateY(-1px);
  box-shadow: 0 14px 30px -16px rgba(38, 24, 16, 0.18);
}
.p-card.dark {
  background: var(--ink-900);
  color: var(--cream-50);
  border-color: transparent;
}
.p-card.urgent {
  background: var(--cream-100);
  border-color: rgba(180, 100, 70, 0.25);
  border-left: 3px solid #b06846;
}

/* ─── Hero "next appointment" ─── */
.p-hero {
  background: var(--ink-900);
  color: var(--cream-50);
  border-radius: 22px;
  padding: 36px 32px;
  position: relative;
  overflow: hidden;
  margin-bottom: 24px;
}
.p-hero::before {
  content: "";
  position: absolute;
  top: -30%; right: -10%;
  width: 60%; height: 200%;
  background: radial-gradient(circle, rgba(220, 180, 130, 0.25), transparent 60%);
  pointer-events: none;
}
.p-hero-content { position: relative; z-index: 1; }
.p-hero-tag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(245, 238, 223, 0.7);
  font-weight: 600;
  margin-bottom: 14px;
}
.p-hero-tag .live-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #d4a86a;
  box-shadow: 0 0 0 4px rgba(212, 168, 106, 0.2);
  animation: livePulse 2s ease-in-out infinite;
}
@keyframes livePulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(212, 168, 106, 0.2); }
  50% { box-shadow: 0 0 0 8px rgba(212, 168, 106, 0.05); }
}
.p-hero-when {
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 500;
  font-size: clamp(32px, 4vw, 44px);
  line-height: 1.05;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
  color: #FCF6EA;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
}
.p-hero-when.zh {
  font-family: "Noto Serif SC", "Source Han Serif SC", serif;
  font-style: normal;
  font-weight: 500;
  font-size: clamp(26px, 3.2vw, 34px);
  letter-spacing: 0.04em;
}
.p-hero-countdown {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.15em;
  color: rgba(245, 238, 223, 0.65);
  margin-bottom: 22px;
}
.p-hero-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px 24px;
  padding-top: 22px;
  border-top: 1px solid rgba(245, 238, 223, 0.15);
  margin-bottom: 22px;
}
.p-hero-meta-row { display: flex; flex-direction: column; gap: 2px; }
.p-hero-meta-row .k {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(245, 238, 223, 0.55);
  font-weight: 600;
}
.p-hero-meta-row .v {
  font-size: 15px;
  color: var(--cream-50);
}
.p-hero-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
.p-btn {
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 500;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  padding: 12px 22px;
  letter-spacing: 0.005em;
  transition: transform 0.2s ease;
}
.p-btn:hover { transform: translateY(-1px); }
.p-btn-primary { background: var(--cream-50); color: var(--ink-900); }
.p-btn-on-dark-outline {
  background: transparent;
  border: 1px solid rgba(245, 238, 223, 0.35);
  color: var(--cream-50);
}
.p-btn-on-dark-outline:hover { background: rgba(245, 238, 223, 0.08); }

.p-btn-outline {
  background: transparent;
  border: 1px solid rgba(38, 24, 16, 0.22);
  color: var(--ink-900);
  padding: 11px 20px;
}
.p-btn-outline:hover { background: rgba(38, 24, 16, 0.04); }
.p-btn-ink {
  background: var(--ink-900); color: var(--cream-50);
}
.p-btn-danger {
  background: transparent;
  border: 1px solid rgba(180, 80, 60, 0.35);
  color: #a04a32;
  padding: 11px 20px;
}
.p-btn-danger:hover { background: rgba(180, 80, 60, 0.08); }

/* ─── Stack rows (outstanding payment, assessment, etc.) ─── */
.stack-row {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 18px 0;
  border-bottom: 1px solid rgba(78, 52, 34, 0.08);
}
.stack-row:last-child { border-bottom: none; }
.stack-row-icon {
  width: 42px; height: 42px;
  border-radius: 50%;
  background: var(--cream-200);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-style: normal;
  color: var(--ink-900);
  font-weight: 600;
  flex-shrink: 0;
  font-size: 18px;
}
.stack-row-icon.urgent { background: rgba(180, 100, 70, 0.18); color: #a04a32; }
.stack-row-icon.calm   { background: var(--cream-200); color: var(--clay-600); }
.stack-row-body { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.stack-row-title {
  font-family: var(--font-display);
  font-style: normal;
  font-size: 20px;
  color: var(--ink-900);
  line-height: 1.2;
}
.stack-row-meta {
  font-size: 13px;
  color: var(--ink-500);
}
.stack-row-amt {
  font-family: var(--font-display);
  font-style: normal;
  font-size: 24px;
  color: var(--ink-900);
}
.stack-row-arrow {
  font-size: 18px;
  color: var(--clay-500);
  opacity: 0.7;
}

/* Mobile: stack the action row so the title gets full width and the
   amount + button drop to a second line (fixes crushed/overlapping
   'needs attention' rows on phones, esp. with the wider Fraunces face). */
@media (max-width: 600px) {
  .stack-row { flex-wrap: wrap; align-items: flex-start; column-gap: 12px; row-gap: 10px; padding: 16px 0; }
  .stack-row-body { flex: 1 1 calc(100% - 54px); }
  .stack-row-title { font-size: 16.5px; line-height: 1.25; }
  .stack-row-amt { font-size: 19px; margin-left: 54px; align-self: center; }
  .stack-row .p-btn { margin-left: auto; align-self: center; flex-shrink: 0; }
  /* Booking stepper: compact; show only the active step label. */
  .steps { gap: 4px; flex-wrap: wrap; }
  .step-separator { width: 14px; }
  .step { font-size: 11px; }
  .step:not(.is-active) .step-label { display: none; }
  .step.is-active .step-label { font-size: 12px; }
  /* Settings saved-address card: stack details + actions so the bilingual buttons wrap. */
  .addr-row { flex-direction: column !important; align-items: stretch !important; }
  .addr-row > div:last-child { width: 100% !important; flex-shrink: 1 !important; }
}

/* ─── Tab bar (within a screen — appointments past/upcoming) ─── */
.p-tabs {
  display: flex;
  gap: 6px;
  background: var(--cream-100);
  padding: 4px;
  border-radius: 999px;
  margin-bottom: 24px;
  width: fit-content;
}
.p-tab {
  border: none;
  background: transparent;
  padding: 9px 18px;
  border-radius: 999px;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-700);
  cursor: pointer;
  letter-spacing: 0.01em;
}
.p-tab.active { background: var(--ink-900); color: var(--cream-50); }
.p-tab .count {
  font-family: var(--font-mono);
  font-size: 10px;
  margin-left: 6px;
  opacity: 0.7;
}

/* ─── Appointment cards ─── */
.appt-card {
  display: grid;
  grid-template-columns: 88px 1fr auto;
  gap: 20px;
  align-items: center;
  padding: 18px 22px;
  background: var(--cream-100);
  border: 1px solid rgba(78, 52, 34, 0.06);
  border-radius: 14px;
  margin-bottom: 10px;
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.appt-card:hover { transform: translateY(-1px); box-shadow: 0 14px 30px -16px rgba(38, 24, 16, 0.18); }
.appt-card.past { opacity: 0.85; }

.appt-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 6px 0;
  border-right: 1px solid rgba(78, 52, 34, 0.12);
  padding-right: 16px;
  margin-right: 0;
}
.appt-date .mon {
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--clay-600);
  font-weight: 600;
}
.appt-date .day {
  font-family: var(--font-display);
  font-style: normal;
  font-size: 26px;
  line-height: 1;
  color: var(--ink-900);
  font-weight: 500;
  margin-top: 2px;
}
.appt-date.zh .mon {
  font-family: "Noto Serif SC", serif;
  font-size: 11px;
  letter-spacing: 0.08em;
  font-weight: 500;
}
.appt-date.zh .day {
  font-family: "Noto Serif SC", serif;
  font-style: normal;
  font-size: 22px;
  font-weight: 500;
  letter-spacing: 0.02em;
}
.appt-date.zh .time {
  font-family: var(--font-mono);
  font-size: 10px;
}
.appt-date .time {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-500);
  margin-top: 6px;
}

.appt-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.appt-doctor {
  font-family: var(--font-display);
  font-style: normal;
  font-size: 22px;
  color: var(--ink-900);
  line-height: 1.15;
}
.appt-type-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.appt-type {
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
}
.appt-type.online { background: rgba(60, 100, 80, 0.12); color: #3a6a52; }
.appt-type.physical { background: var(--cream-200); color: var(--clay-700); }
.appt-type.soon { background: var(--cream-200); color: var(--clay-600); }
.appt-meta { font-size: 13px; color: var(--ink-500); }
/* The dot between status and notes is its own span (so the language switcher
   can split the status on its own); pull it in so the row's 8px gap on each
   side reads like the single space it replaced. */
.appt-meta-sep { margin: 0 -4px; }
/* HM.format.clauses() separator: the gap is a margin, not whitespace, because
   the language switcher's re-wrap of the neighbouring text node drops spaces. */
.hm-clause-sep { margin: 0 0.35em; }

.appt-status {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  font-size: 12px;
  color: var(--ink-500);
}
.appt-manage { display: flex; align-items: center; gap: 6px; }
.appt-status .arrow { font-size: 16px; color: var(--clay-500); }
/* The invoice download, stacked under Manage. Its own scale: the card's
   right column is 12px type, so a hero-sized .p-btn-outline here out-weighed
   the practitioner's name. Sage is the card's ONLINE chip colour, solid. */
.appt-invoice {
  padding: 7px 12px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  white-space: nowrap;
  border-radius: 999px;
  background: #3a6a52;
  color: #fff;
}
.appt-invoice:hover { background: #2f5743; }
/* The arrow is its own span so it survives the language switch; the space
   after it does not, so the gap is a margin. */
.appt-invoice > span { margin-right: 4px; }

/* Empty state */
.empty {
  text-align: center;
  padding: 40px 24px;
  color: var(--ink-500);
}
.empty-title {
  font-family: var(--font-display);
  font-style: normal;
  font-size: 22px;
  color: var(--ink-900);
  margin-bottom: 6px;
}

/* ─── Bills & receipts ───
   One compact row per receipt under month headers, with year/month chips and
   a search box above. The old .invoice-row card (~150px on a phone, every
   consultation reading the same) and the parcel tracker that sat above the
   receipts are gone; parcels live on Medicine. Every row is a native <button>
   so the whole 56px row is the target. */
.rc-owe {
  display: flex; align-items: center; gap: 12px;
  background: rgba(180, 100, 70, 0.06);
  border-left: 3px solid #b06846;
  border-radius: 12px;
  padding: 12px 14px;
  margin-bottom: 8px;
}
.rc-owe-b { flex: 1; min-width: 0; }
.rc-owe-k { font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: #b06846; font-weight: 700; margin-bottom: 2px; }
.rc-owe-t { font-size: 13.5px; font-weight: 600; color: var(--ink-900); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-owe-m { font-size: 11.5px; color: var(--ink-500); margin-top: 1px; }
.rc-owe-amt { font-family: var(--font-display); font-style: normal; font-size: 17px; color: var(--ink-900); white-space: nowrap; }
.rc-pay {
  background: var(--ink-900); color: var(--cream-50);
  border: none; border-radius: 999px;
  padding: 10px 14px; min-height: 40px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
  cursor: pointer;
}

.rc-tools { margin: 18px 0 4px; }
.rc-search {
  display: flex; align-items: center; gap: 8px;
  background: #fff;
  border: 1px solid var(--cream-300);
  border-radius: 12px;
  padding: 0 12px;
  min-height: 44px;
}
.rc-search:focus-within { border-color: var(--clay-400); }
.rc-search-in {
  flex: 1; min-width: 0;
  border: none; background: transparent; outline: none;
  font: inherit; font-size: 14px; color: var(--ink-900);
  padding: 10px 0;
}
.rc-search-in::placeholder { color: var(--ink-300); }
.rc-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.rc-chip {
  font: inherit; font-size: 12.5px; font-weight: 600; line-height: 1.2;
  padding: 8px 12px; min-height: 36px;
  border-radius: 999px;
  border: 1px solid var(--clay-200);
  color: var(--clay-700);
  background: transparent;
  cursor: pointer;
}
.rc-chip:hover { border-color: var(--clay-400); }
.rc-chip.on { background: var(--ink-900); color: var(--cream-50); border-color: var(--ink-900); }

.rc-month {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--clay-500); font-weight: 700;
  margin: 22px 2px 6px;
}
.rc-month small {
  font-family: var(--font-mono); font-size: 11px; font-weight: 500;
  letter-spacing: 0.04em; text-transform: none;
  color: var(--ink-300);
  white-space: nowrap;
}
.rc-list { background: var(--cream-100); border-radius: 12px; overflow: hidden; }
.rc {
  display: flex; align-items: center; gap: 12px;
  width: 100%; min-height: 56px; box-sizing: border-box;
  padding: 12px 14px;
  border: none; background: transparent;
  font: inherit; color: inherit; text-align: left;
  cursor: pointer;
}
.rc + .rc { border-top: 1px solid rgba(78, 52, 34, 0.08); }
.rc:hover { background: var(--cream-200); }
.rc:focus-visible { outline: 2px solid var(--court-clay-500, #b8975a); outline-offset: -2px; }
.rc-b { flex: 1; min-width: 0; }
.rc-t { display: block; font-size: 14px; font-weight: 600; color: var(--ink-900); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rc-m { display: block; font-size: 11.5px; color: var(--ink-500); margin-top: 2px; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rc-amt { flex: none; font-family: var(--font-display); font-style: normal; font-size: 16px; color: var(--ink-900); font-variant-numeric: tabular-nums; white-space: nowrap; }
.rc-chev { flex: none; color: var(--clay-400); font-size: 16px; margin-left: -4px; }
.rc-empty { padding: 18px 4px; font-size: 13px; color: var(--ink-500); font-family: var(--font-ui); }
.rc-foot { margin: 28px 2px 0; font-size: 12.5px; }
.rc-foot a { color: var(--clay-600); text-decoration: underline; text-underline-offset: 3px; }

/* ─── Messages ─── */
.msg-layout {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 0;
  background: var(--cream-100);
  border-radius: 16px;
  overflow: hidden;
  min-height: 560px;
  border: 1px solid rgba(78, 52, 34, 0.06);
}
.msg-list {
  border-right: 1px solid rgba(78, 52, 34, 0.08);
  background: var(--cream-50);
  display: flex;
  flex-direction: column;
}
.msg-list-head {
  padding: 18px 18px 14px;
  border-bottom: 1px solid rgba(78, 52, 34, 0.06);
}
.msg-list-head h4 {
  font-family: var(--font-display);
  font-style: normal;
  font-size: 24px;
  color: var(--ink-900);
  margin: 0;
}
.msg-list-item {
  padding: 14px 18px;
  border-bottom: 1px solid rgba(78, 52, 34, 0.04);
  cursor: pointer;
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.msg-list-item:hover { background: var(--cream-100); }
.msg-list-item.active { background: var(--cream-200); }
.msg-list-item .ma {
  width: 36px; height: 36px;
  border-radius: 50%;
  background: linear-gradient(160deg, var(--clay-200), var(--clay-500));
  flex-shrink: 0;
}
.msg-list-body { flex: 1; min-width: 0; }
.msg-list-name { font-size: 14px; font-weight: 600; color: var(--ink-900); }
.msg-list-preview {
  font-size: 12px; color: var(--ink-500);
  white-space: nowrap; text-overflow: ellipsis; overflow: hidden;
  margin-top: 2px;
}
.msg-list-meta {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--clay-500);
  margin-top: 4px;
}
.msg-list-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--ink-900);
  margin-left: auto;
  margin-top: 6px;
}

.msg-thread {
  display: flex; flex-direction: column;
  background: var(--cream-100);
}
.msg-thread-head {
  padding: 18px 24px;
  border-bottom: 1px solid rgba(78, 52, 34, 0.08);
  display: flex;
  align-items: center;
  gap: 14px;
}
.msg-thread-head .ma { width: 42px; height: 42px; border-radius: 50%; background: linear-gradient(160deg, var(--clay-200), var(--clay-500)); }
.msg-thread-head h5 { font-family: var(--font-display); font-style: normal; font-size: 22px; color: var(--ink-900); margin: 0; }
.msg-thread-head .sub { font-size: 12px; color: var(--clay-600); }

.msg-thread-body {
  flex: 1;
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  max-height: 460px;
}
.msg-bubble {
  max-width: 76%;
  padding: 11px 16px;
  border-radius: 14px;
  font-size: 14px;
  line-height: 1.5;
}
.msg-bubble.you {
  background: var(--ink-900);
  color: var(--cream-50);
  align-self: flex-end;
  border-bottom-right-radius: 4px;
}
.msg-bubble.them {
  background: var(--cream-50);
  color: var(--ink-800);
  align-self: flex-start;
  border-bottom-left-radius: 4px;
  border: 1px solid rgba(78, 52, 34, 0.06);
}
.msg-day {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  color: var(--clay-500);
  margin: 12px 0 6px;
}

.msg-input-row {
  display: flex;
  gap: 10px;
  padding: 14px 20px;
  border-top: 1px solid rgba(78, 52, 34, 0.08);
  background: var(--cream-50);
}
.msg-input-row input {
  flex: 1;
  border: 1px solid rgba(78, 52, 34, 0.15);
  border-radius: 999px;
  padding: 10px 16px;
  font-family: var(--font-ui);
  font-size: 14px;
  background: var(--cream-50);
  color: var(--ink-900);
}
.msg-input-row input:focus { outline: none; border-color: var(--ink-900); }
.msg-input-row .send {
  background: var(--ink-900);
  color: var(--cream-50);
  border: none;
  border-radius: 50%;
  width: 40px; height: 40px;
  cursor: pointer;
  font-size: 16px;
}

/* ─── Settings ─── */
.settings-group {
  background: var(--cream-100);
  border-radius: 16px;
  margin-bottom: 16px;
  overflow: hidden;
  border: 1px solid rgba(78, 52, 34, 0.06);
}
.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 22px;
  border-bottom: 1px solid rgba(78, 52, 34, 0.06);
  gap: 18px;
}
.settings-row:last-child { border-bottom: none; }
.settings-row .lbl {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.settings-row .lbl .k {
  font-size: 14px; font-weight: 500; color: var(--ink-900);
}
.settings-row .lbl .d {
  font-size: 12px; color: var(--ink-500);
}
.settings-row .v {
  font-size: 13px;
  color: var(--ink-700);
  display: flex; align-items: center; gap: 10px;
}

.toggle-switch {
  position: relative;
  width: 42px; height: 24px;
  background: rgba(78, 52, 34, 0.18);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s ease;
}
.toggle-switch::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--cream-50);
  transition: transform 0.25s ease;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.toggle-switch.on { background: var(--ink-900); }
.toggle-switch.on::after { transform: translateX(18px); }

.danger-zone {
  background: rgba(180, 80, 60, 0.04);
  border: 1px solid rgba(180, 80, 60, 0.18);
  border-radius: 16px;
  padding: 22px 24px;
  margin-top: 32px;
}
.danger-zone-title {
  font-family: var(--font-display);
  font-style: normal;
  font-size: 22px;
  color: #a04a32;
  margin: 0 0 6px;
}
.danger-zone-desc { font-size: 13px; color: var(--ink-500); margin-bottom: 16px; }

/* ─── (Floating dock removed — nav lives in the top bar) ─── */

/* ─── Quick links / shortcuts ─── */
.quick-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-top: 12px;
}
.quick {
  background: var(--cream-100);
  border: 1px solid rgba(78, 52, 34, 0.06);
  border-radius: 12px;
  padding: 16px;
  cursor: pointer;
  display: flex; flex-direction: column; gap: 6px;
  transition: background 0.2s ease;
}
.quick:hover { background: var(--cream-200); }
.quick-eyebrow {
  font-size: 10px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--clay-600);
  font-weight: 600;
}
.quick-title {
  font-family: var(--font-display); font-style: normal;
  font-size: 18px; color: var(--ink-900); line-height: 1.2;
}
.quick-arrow { font-size: 14px; color: var(--clay-500); margin-top: auto; }

/* ─── Detail / modal ─── */
.sheet-backdrop {
  position: fixed; inset: 0;
  background: rgba(38, 24, 16, 0.4);
  backdrop-filter: blur(8px);
  z-index: 80;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  animation: sheetIn 0.3s ease;
}
@keyframes sheetIn { from { background: rgba(38, 24, 16, 0); } }
.sheet {
  background: var(--cream-50);
  border-radius: 22px 22px 0 0;
  width: 100%;
  max-width: 540px;
  padding: 28px;
  max-height: 80vh;
  overflow-y: auto;
  animation: sheetUp 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes sheetUp { from { transform: translateY(40px); } }

/* ─── Mobile ─── */
@media (max-width: 900px) {
  .p-bar {
    grid-template-columns: auto auto;
    gap: 10px;
    padding: 12px 18px;
  }
  .p-nav {
    grid-column: 1 / -1;
    grid-row: 2;
    justify-self: stretch;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .p-nav::-webkit-scrollbar { display: none; }
  .p-nav-btn { padding: 7px 12px; font-size: 12px; }
  /* Labels stay ON at every width. They used to be hidden here for all but the
     active tab, from when .p-nav was a scrolling pill INSIDE the header and had
     to fit beside the brand. It is the bottom dock now, five items across the
     full width, and the space is there. Icon-only, it read as a 44px strip of
     unlabelled glyphs in near-page-colour cream - present, but not legible as
     navigation, and the only text down there was an unread badge. A patient
     should not have to decode a pictogram to find their prescriptions. */
  .p-nav-icon { width: 18px; height: 18px; }
  .p-screen { padding: 24px 18px 60px; }
  .p-greet-title { font-size: 32px; }
  .p-hero { padding: 28px 22px; border-radius: 18px; }
  .p-hero-meta { grid-template-columns: 1fr; gap: 14px; }
  .appt-card { grid-template-columns: 72px 1fr; row-gap: 8px; padding: 16px; }
  /* Under the body now, so Manage and the invoice share one row, ends apart. */
  .appt-status { grid-column: 2; padding-top: 4px; flex-direction: row; justify-content: space-between; align-items: center; }
  .msg-layout { grid-template-columns: 1fr; min-height: auto; }
  .msg-list { border-right: none; border-bottom: 1px solid rgba(78, 52, 34, 0.08); max-height: 240px; overflow-y: auto; }
  .quick-grid { grid-template-columns: 1fr; }
  .p-avatar-name { display: none; }
}

/* Light cards (e.g. appointment detail) reuse the hero text classes, which
   are cream-coloured for the DARK dashboard hero -> invisible on cream.
   Force dark ink; scoped to :not(.dark) so the dark hero is untouched. */
.p-card:not(.dark) .p-hero-when { color: var(--ink-900); text-shadow: none; }
.p-card:not(.dark) .p-hero-countdown { color: var(--ink-500); }
.p-card:not(.dark) .p-hero-meta { border-top-color: rgba(38, 24, 16, 0.12); }
.p-card:not(.dark) .p-hero-meta-row .k { color: var(--ink-500); }
.p-card:not(.dark) .p-hero-meta-row .v { color: var(--ink-900); }

/* ── Today: the practitioner's own advice ──
   lifestyle_picks rendered only inside the plan document until now. */
.p-advice {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--cream-100, #F5EEDF);
  border-left: 3px solid var(--clay-400, #A0825F);
  border-radius: 0 12px 12px 0;
  padding: 12px 15px; margin-bottom: 8px;
}
.p-advice-ico { font-size: 17px; line-height: 1.25; flex-shrink: 0; }
.p-advice-text { font-size: 13px; line-height: 1.5; color: var(--ink-700, #4A3326); }
.p-advice-zh { color: var(--ink-500, #6B5346); font-family: "Noto Serif SC", serif; }
.p-btn-quiet {
  background: transparent; color: var(--ink-500, #6B5346);
  border: 1px solid var(--cream-300, #DCCBA8);
  margin-top: 4px;
}
.p-btn-quiet:hover { background: var(--cream-100, #F5EEDF); color: var(--ink-800, #34221A); }

/* ══════════════════════════════════════════════════════════════════
   TODAY SCREEN — visual pass to the approved mockup
   (_internal/mockups/patient-portal-merged-2026-09.html)

   The redesign shipped its STRUCTURE first: the dock, the Inbox, the
   block order on Today. The look stayed the old portal's, which is why
   the live screen did not match the mockup it was built from. This is
   that gap.

   The headline change is the care card. It was a solid dark slab; the
   mockup makes it a WHITE card with a dark status strip, so the visit
   reads as a document rather than a banner. Everything below follows
   from that inversion — including the buttons, which were cream-on-dark
   and would have been invisible on white.

   All four families restyled here are patient-only (grepped: 0 uses in
   admin, doctor and pharmacy panels), so none of this needs scoping to
   html[data-role="patient"].
   ══════════════════════════════════════════════════════════════════ */

/* ── The care card: white document, dark strip ── */
.p-hero {
  background: #fff;
  color: var(--ink-800);
  border: 1px solid var(--clay-400);
  border-radius: 16px;
  padding: 0;                       /* the strip and body own their padding */
  overflow: hidden;
  box-shadow: 0 8px 22px -12px rgba(38, 24, 16, 0.22);
}
.p-hero-content { padding: 0; }
/* The old slab carried a warm radial glow to lift its dark corner.
   On white it reads as a muddy tan smear across the meta rows, which is
   what it was doing on the live screen. */
.p-hero::before { content: none; }
.p-hero-tag {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--clay-700);
  color: var(--cream-100);
  padding: 9px 16px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.p-hero-when,
.p-hero-countdown,
.p-hero-meta,
.p-hero-actions { margin-left: 18px; margin-right: 18px; }
.p-hero-when {
  margin-top: 15px;
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 600;
  color: var(--ink-900);
}
.p-hero-countdown { color: var(--ink-500); font-size: 13px; }
.p-hero-meta { border-top: 1px solid var(--cream-200); }
.p-hero-meta-row .k { color: var(--ink-500); }
.p-hero-meta-row .v { color: var(--ink-800); }
.p-hero-actions { padding-bottom: 18px; }

/* ── Care card: the countdown pill, and what replaced the meta table ──
   The four-row Practitioner / Status / Assessment / Fee table put chrome
   between the patient and the one thing they came to do. Three of those rows
   are said better on the new card - practitioner on the sub-line, status by
   the strip and the buttons, assessment by its own note - and ALL of them are
   still on the appointment detail screen, which is exactly where this card's
   button goes (appointments.js still renders .p-hero-meta, so those rules
   stay; they are that screen's, not dead code). */
.p-hero-pill {
  margin-left: auto;
  letter-spacing: 0.06em;
  color: var(--clay-200);
  white-space: nowrap;
}
.p-hero-title {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink-900);
  margin: 14px 18px 2px;
}
.p-hero-sub {
  font-size: 12.5px;
  color: var(--ink-500);
  margin: 0 18px 12px;
}

/* The one line that survived the table, shown only when something is owed:
   an unpaid booking, or an incomplete assessment at the moment it can be
   acted on. Silent on a normal visit, which is most of them. */
.p-hero-flag {
  margin: 0 18px 12px;
  padding: 8px 11px;
  border-radius: 9px;
  font-size: 12px;
  line-height: 1.45;
}
.p-hero-flag.warn { background: #FBEEE9; color: #A8503C; }
.p-hero-flag.due  { background: var(--cream-100); color: var(--ink-800); }
.p-hero-flag b    { font-weight: 700; }
/* The reassurance line, between the meta rows and the buttons. It is on the
   white card, so it is NOT the mockup's amber warning strip - amber here
   would read as an alert, and the whole point of the sentence is that there
   is nothing wrong. Quiet cream, same inset as its siblings. */
.p-hero-note {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 14px 18px 0;
  padding: 10px 12px;
  background: var(--cream-100);
  border-radius: 10px;
  font-size: 12px; line-height: 1.5;
  color: var(--ink-700);
}
.p-hero-note-ico { flex-shrink: 0; line-height: 1.4; }
.p-hero-note-zh { color: var(--ink-500); font-family: "Noto Serif SC", serif; }

/* Byline under the practitioner's advice. Deliberately small and quiet: it
   is attribution, not another thing to read. */
.p-advice-by {
  margin: 8px 0 2px 15px;
  font-size: 11.5px;
  color: var(--ink-500);
}

/* ── Buttons ──
   p-btn-primary was cream-on-dark, sized for the old slab. On a white
   card it has to carry itself. */
.p-btn-primary {
  background: var(--clay-600);
  color: var(--cream-50);
  border-radius: 12px;
  padding: 13px 20px;
  font-weight: 600;
}
.p-btn-primary:hover { background: var(--clay-700); }
/* Replaces p-btn-on-dark-outline at the call sites: an "on dark" name on a
   white card would be a lie, and those names get trusted later. */
.p-btn-outline {
  background: #fff;
  border: 1px solid var(--clay-200);
  color: var(--ink-700);
  border-radius: 12px;
  padding: 13px 20px;
  font-weight: 600;
}
.p-btn-outline:hover { background: var(--cream-100); }

/* ── Section headings: smaller, with the mockup's hairline ── */
.p-section-title {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.13em;
  font-weight: 600;
  margin: 22px 0 9px;
}
.p-section-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--cream-200);
}
/* A "see all" link in a section heading. order:1 puts it AFTER the ::after
   hairline (a pseudo-element is always the last flex item at order 0), so it
   sits at the right edge instead of between the words and the line. */
.p-section-link {
  order: 1;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--clay-600);
  text-decoration: none;
  white-space: nowrap;
  padding: 4px 0;
}
.p-section-link:hover { text-decoration: underline; }

/* ── Rows become cards, not ruled lines ── */
.stack-row {
  gap: 12px;
  padding: 12px 14px;
  background: #fff;
  border: 1px solid var(--cream-300);
  border-bottom: 1px solid var(--cream-300);
  border-radius: 13px;
  margin-bottom: 8px;
}
.stack-row-icon {
  width: 34px; height: 34px;
  border-radius: 10px;             /* a tile, per the mockup, not a circle */
  font-size: 14px;
}
.stack-row-title { font-size: 13px; font-weight: 600; color: var(--ink-800); }
.stack-row-meta  { font-size: 11px; color: var(--ink-500); }
/* ── CJK must not break mid-word ──
   CSS breaks CJK between ANY two characters, because unlike English it has
   no spaces to break at - so a phrase splits into fragments the eye has to
   reassemble. Measured in headless Chrome against the real Today markup,
   before this rule:

     320px  两 | 周后            (.stack-row-title)
            8月28日 | 方案       (.p-advice-by)
     360px  就诊前 | 无需准备。   (.p-hero-note-zh)
            留意疲 | 劳复发       (.stack-row-meta)
     390px  诊前无需准 | 备。     (.p-hero-note-zh)
     740px (desktop, 2-col meta)  none

   After: zero at every width, and nothing overflows.

   Scoped to the portal's own short EN · ZH labels rather than added to
   cjk-type.css, which loads site-wide: the same targeted approach the merged
   mockup took, not a global typography change.

   overflow-wrap is NOT optional here. keep-all leaves a long unpunctuated CJK
   run with no break opportunity at all, and practitioner free text is exactly
   that - followup_watch is a VARCHAR nobody validates for punctuation. With
   keep-all alone, a 40-character warning measured scrollWidth 374px inside a
   92px box: silently unreadable. With overflow-wrap it wraps instead.

   'anywhere' rather than 'break-word' because only 'anywhere' also reduces
   the element's MIN-CONTENT size, so an unbreakable run can never force a
   grid or flex track wider than its share. Both behaved identically on
   today's content; 'anywhere' is the one that stays correct if a track ever
   gets tighter. */
.p-hero-tag,
.p-hero-meta-row .k,
.p-hero-meta-row .v,
.p-hero-note,
.p-hero-note-zh,
.p-section-title,
.stack-row-title,
.stack-row-meta,
.p-advice-text,
.p-advice-zh,
.p-advice-by,
.med-pill,
.med-empty,
.med-note,
.med-prod-name,
.med-prod-zh,
.med-prod-out,
.med-note-zh,
.p-hero-title,
.p-hero-sub,
.p-hero-flag,
.p-btn {
  word-break: keep-all;
  overflow-wrap: anywhere;
}

/* ── Medicine screen ──
   The status pill. The portal's existing .badge lives in the legacy sheet
   with the rest of the card/btn language this screen was moved off, so it
   gets a small one in the portal's own tokens rather than dragging that
   stylesheet back in for one element. */
.med-pill {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 600;
  vertical-align: middle;
}
.med-pill--due  { background: #FBEEE9; color: #A8503C; }
.med-pill--done { background: #E3EDE4; color: #3F5A4C; }
.med-pill--live { background: var(--cream-200); color: var(--clay-700); }
.med-pill--off  { background: var(--cream-200); color: var(--ink-500); }

/* Gate 4's disclaimer. Same quiet panel as the empty state - it must read as
   standing guidance, not an alarm, but it must not read as decoration. */
.med-note {
  background: var(--cream-100);
  border-radius: 10px;
  padding: 11px 13px;
  margin-bottom: 10px;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--ink-700);
}
.med-note b { color: var(--ink-900); }

.med-empty {
  background: var(--cream-100);
  border-radius: 12px;
  padding: 16px 18px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-500);
}

/* A whole row is the tap target here, so it needs the affordances a button
   gets for free. */
.stack-row[role="link"] { cursor: pointer; }
.stack-row[role="link"]:hover { border-color: var(--clay-200); }
.stack-row[role="link"]:focus-visible { outline: 2px solid var(--clay-500); outline-offset: 2px; }

/* Browse — a two-up shortcut into the shop, not a replacement for it. */
.med-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; }
.med-prod {
  background: #fff;
  border: 1px solid var(--cream-300);
  border-radius: 13px;
  padding: 11px;
  cursor: pointer;
}
.med-prod:hover { border-color: var(--clay-200); }
.med-prod-img {
  font-size: 30px; text-align: center;
  background: var(--cream-100); border-radius: 9px;
  padding: 12px 0; margin-bottom: 9px;
}
.med-prod-name { font-size: 13px; font-weight: 600; color: var(--ink-900); line-height: 1.3; }
.med-prod-zh { font-size: 11.5px; color: var(--ink-500); font-family: "Noto Serif SC", serif; }
.med-prod-foot { margin-top: 9px; }
.med-prod-price { font-size: 13.5px; font-weight: 700; color: var(--ink-900); }
.med-prod-out { display: block; font-size: 10.5px; color: var(--ink-500); margin-top: 6px; }
.med-note-zh { font-family: "Noto Serif SC", serif; color: var(--ink-500); }
.med-prod .p-btn { display: block; width: 100%; padding: 6px 8px; font-size: 11.5px; margin-top: 6px; }
