/* HansMed Patient Portal — Healing Courtyard shell (Phase 3a).
   Self-contained top-bar shell adapted from the design's portal-styles.css.
   Strangler Fig: the legacy .nav + .sidebar are HIDDEN (not removed) so all
   existing JS still finds its elements; panels render full-width beneath the
   new bar. Loaded LAST in portal.html. */

:root {
  --court-cream-50:  #FBF7EE;
  --court-cream-100: #F5EEDF;
  --court-cream-200: #ECE2CC;
  --court-clay-200:  #C9B393;
  --court-clay-500:  #8B6B47;
  --court-ink-700:   #4A3326;
  --court-ink-900:   #261810;
  --court-font-ui:   "Manrope", "Inter", system-ui, sans-serif;
  --court-font-disp: "Fraunces", Georgia, serif;
}

/* warm courtyard paper backdrop */
body {
  background:
    radial-gradient(ellipse 90% 70% at 50% 22%, rgba(255,250,235,0.85) 0%, rgba(244,234,210,0) 70%),
    linear-gradient(180deg, #F4EAD2 0%, #EBDDBC 100%) fixed !important;
}

/* hide the legacy chrome; run main full-width */
.nav { display: none !important; }
.main { max-width: 1120px; margin: 0 auto; width: 100%; }

/* ── Scoped to pages that actually carry the dock ──
   These two are the Strangler Fig: the patient portal's sidebar was REPLACED by
   .p-nav, so it is collapsed and its grid column removed. Unscoped they also hit
   doctor.html, which loads this stylesheet for the .p-bar but has NO dock - so
   the practitioner console lost its .sidebar at every width with nothing put in
   its place. `.sidebar.is-open`, the mobile drawer, is a transform-only rule and
   cannot undo `display:none`, so the hamburger could not bring it back either.
   Measured display:none at 390x844 and at 1280x900 before this scoping.

   Keyed on the dock's presence rather than on a page class, so a portal that
   adopts the dock is covered without editing this file - the same gate
   portal-menu.js uses to decide whether to inject its hamburger. Where :has() is
   unsupported the sidebar simply also shows next to the dock: extra navigation,
   never none. `.nav` above stays global - doctor.html carries a legacy nav it
   genuinely wants hidden, having .p-bar instead. */
body:has(.p-nav) .portal-layout { grid-template-columns: 1fr !important; }
body:has(.p-nav) .sidebar { display: none !important; }

/* ── New top bar ── */
.p-bar {
  position: sticky; top: 0; z-index: 60;
  display: grid; grid-template-columns: auto 1fr auto;
  align-items: center; gap: 28px;
  padding: 14px 28px;
  background: var(--court-ink-900);
  border-bottom: 1px solid rgba(0,0,0,0.4);
  font-family: var(--court-font-ui);
}
/* dark bar: keep the floating pills light, lift the bare elements to cream */
.p-bell { color: rgba(245,238,223,0.85); }
.p-bar .p-signout { color: var(--court-cream-50) !important; border-color: rgba(245,238,223,0.38) !important; }
.p-bar .p-signout:hover { background: rgba(245,238,223,0.12) !important; }
.p-logo-plate {
  display: inline-flex; align-items: center; gap: 12px;
  background: #F5EBD6; color: var(--court-ink-900);
  padding: 8px 16px 8px 10px; border-radius: 14px;
  text-decoration: none; justify-self: start;
}
.p-logo-plate svg { display: block; width: 30px; height: 30px; color: var(--court-ink-900); }
.p-logo-text { display: flex; flex-direction: column; line-height: 1; }
.p-logo-en { font-family: var(--court-font-ui); font-weight: 700; font-size: 18px; letter-spacing: .01em; }
.p-logo-zh { font-family: "Noto Serif SC","Source Han Serif SC",serif; font-weight: 500; font-size: 11.5px; margin-top: 4px; color: rgba(245,238,223,.8); letter-spacing: .04em; }

/* ── The dock ──
   The nav moved out of the top bar to a dock at the bottom, on EVERY viewport.
   One mental model across devices: the same five items in the same place
   whether the patient is on a phone or a laptop.

   It is a SIBLING of .p-bar, not a child - PR #556 moved it out. This comment
   used to claim the opposite, and the claim was load-bearing: syncNav() scoped
   its query to #court-bar and was never repointed, so it found nothing and no
   tab was ever marked active. portal.html now queries the dock directly. The
   i18n wiring was never at risk - the dock's zh spans are hidden by the global
   `html[lang="en"] [lang="zh"]` rule, which does not care where the dock sits.
   position:fixed is safe here only because .p-bar carries no filter,
   transform or perspective; any of those would make an ancestor the
   containing block and this would position against the BAR, not the viewport.
   Do not add a backdrop-filter to .p-bar without revisiting this. */
.p-nav {
  position: fixed; z-index: 70;
  left: 50%; transform: translateX(-50%); bottom: 18px;
  display: flex; align-items: stretch; gap: 2px;
  background: rgba(251, 247, 238, .97);
  border: 1px solid rgba(78,52,34,.14);
  border-radius: 18px; padding: 7px 8px; max-width: calc(100vw - 24px);
  overflow-x: auto;
  box-shadow: 0 16px 40px -16px rgba(38,24,16,.34);
}
/* Sized to its contents, not full-width: stretched across a desktop the five
   items land ~1200px apart and the row reads as a footer, not a control. */
.p-nav-btn {
  display: inline-flex; flex-direction: column; align-items: center; gap: 3px;
  min-width: 76px; padding: 7px 12px; border-radius: 13px;
  color: var(--court-ink-700); text-decoration: none;
  font-size: 11px; font-weight: 500; white-space: nowrap;
  transition: color .2s, background .25s;
}
.p-nav-btn:hover { color: var(--court-ink-900); background: rgba(78,52,34,.05); }
.p-nav-btn.active { background: var(--court-ink-900); color: var(--court-cream-50); }
.p-nav-btn { position: relative; }
/* The per-tab badges write into a.sidebar-link, which this stylesheet hides,
   and the old #court-notif mirror pointed at an element that never existed -
   so the portal showed NO unread indicator anywhere. This one is on the
   visible button. */
.p-nav-badge {
  position: absolute; top: -4px; right: -6px; min-width: 17px; height: 17px;
  padding: 0 5px; border-radius: 999px; background: #A8503C; color: #fff;
  font-size: 10px; font-weight: 700; line-height: 17px; text-align: center;
}
.p-nav-icon { width: 19px; height: 19px; display: inline-flex; }
.p-nav-icon svg { width: 100%; height: 100%; }
.p-nav-btn .p-nav-label span[lang] { } /* lang spans toggled by existing i18n */

.p-bar-right { display: flex; align-items: center; gap: 12px; justify-self: end; }
.p-bell { position: relative; text-decoration: none; font-size: 18px; line-height: 1; color: var(--court-ink-700); }
.p-bell-badge {
  position: absolute; top: -6px; right: -8px;
  background: #c97157; color: var(--court-cream-50);
  font-size: 9px; font-weight: 700; border-radius: 999px; padding: 1px 5px;
}
.p-lang { display: flex; background: var(--court-cream-200); border-radius: 999px; padding: 3px; }
.p-lang button { border: none; background: transparent; padding: 5px 11px; border-radius: 999px; font-family: var(--court-font-ui); font-weight: 600; font-size: 12px; color: var(--court-ink-700); cursor: pointer; }
.p-lang button.active { background: var(--court-ink-900); color: var(--court-cream-50); }
.p-avatar-pill {
  display: flex; align-items: center; gap: 9px;
  padding: 5px 14px 5px 5px; background: var(--court-cream-100);
  border: 1px solid rgba(78,52,34,.08); border-radius: 999px; text-decoration: none;
}
.p-avatar-pill:hover { background: var(--court-cream-200); }
.p-avatar-mini {
  width: 30px; height: 30px; border-radius: 50%;
  background: linear-gradient(160deg, var(--court-clay-200), var(--court-clay-500));
  display: flex; align-items: center; justify-content: center;
  font-family: var(--court-font-disp); font-style: normal; font-weight: 600; font-size: 14px;
  color: var(--court-cream-50);
}
.p-avatar-name { font-size: 13px; font-weight: 500; color: var(--court-ink-900); }

/* hide the right cluster's text labels are bilingual; lang handled globally */

/* ── Responsive ── */
@media (max-width: 820px) {
  .p-bar { grid-template-columns: 1fr auto; gap: 12px; padding: 10px 14px; }
  .p-nav { grid-column: 1 / -1; grid-row: 2; justify-self: stretch; order: 3; }
  .p-logo-zh { display: none; }
  .p-avatar-name { display: none; }
}

/* ── Tappable list row ("B+", picked 2026-08-02) ──────────────────────
   The whole card opens the record, so the target is the full row rather
   than a 61x32 button. A nested action (e.g. Order) keeps its own one-tap
   job and MUST call stopPropagation() in JS, or tapping it opens the
   record instead. Rolling out per surface: prescriptions first.

   Deliberately NOT using .btn--sm here — responsive.css re-pins that to
   min-height:32px inside a media query, which would undo the fix on the
   exact screens it is meant for. */
.tap-row { cursor: pointer; }
.tap-row:hover { border-color: var(--court-clay-500, #b8975a); }
.tap-row:focus-within { outline: 2px solid var(--court-clay-500, #b8975a); outline-offset: 2px; }

/* Visible size stays 36px so lists do not balloon; the tap area is padded
   out to 48px with a transparent overlay that stays inside its own row. */
.tap-row__hint,
.tap-row__action {
  position: relative;
  min-height: 36px;
  padding: 0 15px;
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
}
.tap-row__hint::before,
.tap-row__action::before {
  content: '';
  position: absolute;
  top: -6px; bottom: -6px; left: -3px; right: -3px;
}
.tap-row__hint { font-weight: 500; }
.tap-row__action { font-weight: 600; }

/* Hit-area expansion only, for buttons that already look right but measure
   under 44px — e.g. the billing row's pill (365x38) and its text-link
   button. Adds no height, so a 2.6-screen list does not get longer. */
.tap-row__hit { position: relative; }
.tap-row__hit::before {
  content: '';
  position: absolute;
  top: -5px; bottom: -5px; left: -3px; right: -3px;
}

/* Action strip. Wraps instead of squeezing: an order row can carry three
   actions (View + Pay + Track), and .flex alone would shrink them below
   44px wide on a phone rather than move one to a second line. */
.tap-row__acts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2, 8px);
}

/* ── Live consultation controls ──────────────────────────────────────
   These sit under an active call and one of them ENDS it. They shipped as
   .btn--sm = 32px — and responsive.css re-pins that class to
   min-height:32px inside a media query, so on a phone, mid-consultation,
   the control that ends the call was a 32px target.

   Given REAL 44px boxes rather than the transparent hit overlay used in
   lists: these two sit side by side and one is destructive, so invisible
   hit areas expanding toward each other is exactly the wrong trade here.
   Real size plus a wider gap (8px -> 14px) keeps them distinct. */
.vc-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
}
.vc-controls .btn { min-height: 44px; }

/* ── Patient-facing status badges ────────────────────────────────────
   .badge ships at 0.65rem = 10.4px. On the wellness assessment that put
   48 badges under the 12px floor being enforced everywhere else in the
   portal — and they are not decoration: "✓ Reviewed · 已审核" and
   "⚠ Needs follow-up · 需跟进" are clinical status a patient has to read.
   So they get the same 13px as every other patient-facing control.

   Scoped to a patient session (data-role is stamped on <html> before
   paint) rather than changed globally: .badge is shared with 20 admin,
   13 doctor and 1 pharmacy panel, which sit in far denser tables and
   have not been reviewed. Those stay exactly as they are.
   Tracking eases 0.08em -> 0.04em so the larger text does not also make
   every badge noticeably wider. */
html[data-role="patient"] .badge {
  font-size: 13px;
  letter-spacing: 0.04em;
  padding: 0.3rem 0.65rem;
}

/* ── Pre-consult vitals: non-blocking sanity hint ────────────────────
   Advisory only. It must never look like an error that stops the patient
   continuing — they can always submit and the practitioner confirms
   vitals verbally during the consult. */
.pa-vital { display: flex; flex-direction: column; gap: 4px; }
.pa-vital-hint {
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--court-ink-700, #4A3326);
  min-height: 0;
}
.pa-vital-hint.is-warn { color: #9a5a1e; }

/* ── Video waiting room live indicator ───────────────────────────────
   The room now polls instead of asking the patient to refresh, so it has
   to LOOK alive — otherwise "you don't need to refresh" reads as a frozen
   page and they tap Refresh anyway. */
.vw-status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--court-ink-700, #4A3326);
}
.vw-status::before {
  content: '';
  width: 9px; height: 9px;
  border-radius: 50%;
  background: #2f8f5b;
  animation: vw-pulse 2s ease-in-out infinite;
}
.vw-status.is-warn::before  { background: #c98a2e; }
.vw-status.is-stale::before { background: #b4441f; animation: none; }
@keyframes vw-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) {
  .vw-status::before { animation: none; }
}

/* Affordance that the row itself is tappable. */
.tap-row__chev {
  margin-left: auto;
  align-self: center;
  font-size: 20px;
  line-height: 1;
  color: var(--court-ink-700, #6b7c82);
}

/* The dock overlays the page, so the column must end above it or the last row
   hides underneath. Scoped for the same reason as the two rules above: on
   doctor.html this reserved 96px for a dock that is not there. */
body:has(.p-nav) .main { padding-bottom: 96px; }

/* The WhatsApp button is fixed bottom-right at z-index 900 - without this it
   sits ON the dock's last item. Lifted clear, again only where the dock is. */
body:has(.p-nav) #hm-whatsapp-fab, body:has(.p-nav) .wa-fab { bottom: 92px !important; }

@media (max-width: 900px) {
  /* Edge-to-edge on a phone: the familiar tab bar and the widest targets.
     Same five items, same order, same place as desktop. */
  .p-nav {
    left: 0; right: 0; bottom: 0; transform: none;
    max-width: none; border-radius: 0;
    border-left: 0; border-right: 0; border-bottom: 0;
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -8px 24px -12px rgba(38,24,16,.28);
  }
  .p-nav-btn { flex: 1; min-width: 0; padding: 6px 4px; }
  body:has(.p-nav) .main { padding-bottom: 104px; }
  body:has(.p-nav) #hm-whatsapp-fab, body:has(.p-nav) .wa-fab { bottom: 100px !important; }
}

/* Never over a live consultation: the room's own controls sit at the bottom and
   one of them ends the call. Same reasoning as PR #491 for the WhatsApp button.
   The flag is set by pages/patient.js on hashchange. */
html[data-consult="1"] .p-nav { display: none !important; }
html[data-consult="1"] .main { padding-bottom: 24px; }

/* ── The sticky join bar ──
   Fixed to the top rather than floated at the bottom: the bottom already
   carries the dock and the WhatsApp FAB, and a third floating thing there is
   how all three become invisible. It also sits ABOVE .p-bar's z-index, and
   pushes the bar and the page down instead of covering them - a patient who
   is late should not lose the nav to find the way in. */
.hm-join-bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 80;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  height: 46px; padding: 0 16px;
  background: #5B7B6A; color: #fff;
  font-family: var(--court-font-ui);
  font-size: 14px; font-weight: 600; text-decoration: none;
  box-shadow: 0 6px 18px -8px rgba(38, 24, 16, .5);
  word-break: keep-all; overflow-wrap: anywhere;
}
.hm-join-bar:hover { background: #52705F; }
.hm-join-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: #fff; box-shadow: 0 0 0 4px rgba(255, 255, 255, .28);
  flex-shrink: 0;
}
.hm-join-go { opacity: .85; }

/* Only while the bar is up. Both offsets are the bar's own height. */
html[data-joinbar="1"] body { padding-top: 46px; }
html[data-joinbar="1"] .p-bar { top: 46px; }

/* Inside the room the bar is removed by paintJoinBar(); this is the belt to
   that braces, so a stale attribute can never leave the page indented. */
html[data-consult="1"] .hm-join-bar { display: none !important; }
html[data-consult="1"] body { padding-top: 0; }
