/* mylab/member — shared theme and components.
   §5 house theme (core + derived tokens) from mynote/mylab_project_devguide.md,
   copied verbatim; everything after it is app-specific and built only from
   those variables (no hardcoded hex in anything that renders content). This
   file is the visual reference named in §5. Kept in one file rather than
   inlined per page because three pages share it. */

:root {
  color-scheme: light;
  --bg: #f8fafc; --surface: #ffffff; --text: #1e293b; --text-muted: #64748b;
  --accent: #0f766e; --accent-hover: #115e59; --border: #e2e8f0;
  --shadow: 0 1px 3px rgba(0,0,0,0.05), 0 1px 2px rgba(0,0,0,0.1);
}
[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0f172a; --surface: #1e293b; --text: #f1f5f9; --text-muted: #94a3b8;
  --border: #334155; --shadow: 0 4px 6px -1px rgba(0,0,0,0.2);
}

/* Derived tokens. Defined per theme so nothing downstream needs a literal. */
:root {
  --accent-soft: #ccfbf1;
  /* Foreground for text sitting ON --accent-soft. Kept separate from --accent
     because the accent is tuned for contrast against --surface, and in dark
     mode teal-on-dark-teal is unreadable. */
  --accent-on-soft: #0f766e;
  /* Row hover. Must differ from BOTH --surface (the card behind the table) and
     --bg (the stripe), or hovering a striped row shows no change at all. */
  --row-hover: #e6f5f2;
  --danger: #b91c1c;
  --danger-soft: #fee2e2;
  --radius: 14px;
  --radius-sm: 10px;
  --shadow-md: 0 4px 6px -1px rgba(15, 23, 42, 0.06), 0 10px 24px -8px rgba(15, 118, 110, 0.12);
  --shadow-lg: 0 12px 40px -12px rgba(15, 23, 42, 0.18), 0 4px 12px -4px rgba(15, 118, 110, 0.1);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}
[data-theme="dark"] {
  --accent-soft: #134e4a;
  --accent-on-soft: #5eead4;
  --row-hover: #17323a;
  --danger: #fca5a5;
  --danger-soft: #450a0a;
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.35), 0 10px 28px -8px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 16px 48px -12px rgba(0, 0, 0, 0.55);
}

/* Bootstrap's display utilities (.d-flex, .d-block, …) are `!important`, so they
   beat the `hidden` attribute and an element marked hidden keeps rendering. This
   bit the staff banner — a guest saw an empty green bar. Restore the attribute's
   meaning once, globally, rather than swapping every toggle to .d-none. */
[hidden] { display: none !important; }

/* Sticky footer: the page column fills the viewport and the main region takes
   the slack, so the debug footer sits at the bottom of the first screen without
   anyone hard-coding its height. */
body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: var(--bg);
  color: var(--text);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  /* Soft teal wash — atmosphere without leaving the house palette. */
  background-image:
    radial-gradient(ellipse 80% 55% at 12% -10%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 55%),
    radial-gradient(ellipse 70% 50% at 92% 8%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 50%),
    radial-gradient(ellipse 60% 40% at 50% 110%, color-mix(in srgb, var(--accent) 6%, transparent), transparent 55%);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
body > .wrap { flex: 1 0 auto; width: 100%; }

a { color: var(--accent); transition: color .15s var(--ease); }
a:hover { color: var(--accent-hover); }

.wrap { max-width: 1180px; margin: 0 auto; padding: 0 16px; }

/* House button per §5. Bootstrap's .btn sizing, our accent. */
.btn-teal {
  background: var(--accent); border-color: var(--accent); color: #fff;
  font-weight: 600; letter-spacing: .01em;
  box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 28%, transparent);
  transition: background .15s var(--ease), border-color .15s var(--ease),
              box-shadow .2s var(--ease), transform .15s var(--ease);
}
.btn-teal:hover, .btn-teal:focus {
  background: var(--accent-hover); border-color: var(--accent-hover); color: #fff;
  box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 32%, transparent);
  transform: translateY(-1px);
}
.btn-teal:active { transform: translateY(0); }
.btn-outline-teal {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  color: var(--accent); background: transparent;
  font-weight: 500;
  transition: background .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease);
}
.btn-outline-teal:hover { background: var(--accent); border-color: var(--accent); color: #fff; }

/* Bootstrap surfaces that do not follow data-bs-theme on their own. */
.card, .modal-content, .dropdown-menu {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
}
.card {
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
}
.modal-header, .modal-footer { border-color: var(--border); }
.form-control, .form-select {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
  border-radius: var(--radius-sm);
  padding: .6rem .85rem;
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.form-control:focus, .form-select:focus {
  background: var(--surface);
  color: var(--text);
  border-color: var(--accent);
  box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--accent) 25%, transparent);
}
.form-control::placeholder { color: var(--text-muted); }
.input-group-text { background: var(--bg); color: var(--text-muted); border-color: var(--border); }

label:not(.form-check-label) {
  display: block;
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--text-muted);
  margin-bottom: .35rem;
}
.form-check-label {
  color: var(--text);
  font-size: .9rem;
  font-weight: 400;
  letter-spacing: 0;
}

.sub {
  color: var(--text-muted);
  font-size: .95rem;
  line-height: 1.5;
  margin: 0 0 1.35rem;
}

/* §9.1 — Bootstrap paints striped/hover cells with an opaque inset box-shadow,
   above any background we set on th/td, and reads --bs-table-* which follow
   data-bs-theme. Driving those variables is the only thing that works; th/td
   rules alone silently miss striped and hover rows. */
.table {
  --bs-table-bg: transparent;
  --bs-table-color: var(--text);
  --bs-table-border-color: var(--border);
  --bs-table-striped-bg: var(--bg);
  --bs-table-striped-color: var(--text);
  --bs-table-hover-bg: var(--bg);
  --bs-table-hover-color: var(--text);
  color: var(--text);
  background: transparent;
  margin-bottom: 0;
}
.table th {
  background: color-mix(in srgb, var(--bg) 70%, var(--surface));
  color: var(--text-muted);
  border-bottom-color: var(--border);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: .75rem 1rem;
}
.table td {
  color: var(--text);
  border-bottom-color: var(--border);
  padding: .7rem 1rem;
  vertical-align: middle;
}

/* Whole-row hover shade.
   Bootstrap paints the cell from `--bs-table-bg-type` (striped) OR
   `--bs-table-bg-state` (hover) — and which of the two wins is a detail of the
   version's fallback chain. Setting both to the same value makes the hover shade
   correct whichever one it consults, so a striped row highlights like any other.
   Applied per cell, which is what makes the shade span the full row. */
.table-hover > tbody > tr:hover > * {
  --bs-table-bg-type: var(--row-hover);
  --bs-table-bg-state: var(--row-hover);
  --bs-table-hover-bg: var(--row-hover);
  color: var(--text);
}

.admin-main .card {
  overflow: hidden;
  border: 1px solid var(--border);
}

/* ---------------------------------------------------------------- nav */

.member-nav {
  position: sticky;
  top: 0;
  z-index: 1040;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--surface) 40%, transparent);
}
.member-nav-inner {
  max-width: 1180px; margin: 0 auto; padding: 0 16px;
  height: 60px; display: flex; align-items: center; gap: 16px;
}
.member-nav-brand {
  font-weight: 700; color: var(--text);
  display: inline-flex; align-items: center; gap: 10px;
  letter-spacing: -.02em; font-size: 1.05rem;
  transition: opacity .15s var(--ease);
}
.member-nav-brand:hover { color: var(--text); text-decoration: none; opacity: .85; }
.member-nav-mark {
  width: 28px; height: 28px; border-radius: 9px;
  background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 88%, #fff), var(--accent-hover));
  box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 35%, transparent);
  position: relative; flex: 0 0 auto;
}
.member-nav-mark::before,
.member-nav-mark::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  background: color-mix(in srgb, #fff 92%, transparent);
}
.member-nav-mark::before {
  width: 8px; height: 8px; top: 7px; left: 6px;
  box-shadow: 9px 0 0 color-mix(in srgb, #fff 92%, transparent);
}
.member-nav-mark::after {
  width: 14px; height: 7px; bottom: 6px; left: 7px;
  border-radius: 0 0 8px 8px;
  background: transparent;
  border: 1.5px solid color-mix(in srgb, #fff 92%, transparent);
  border-top: 0;
}
/* Keep the old class name working if anything still emits it. */
.member-nav-dot {
  width: 28px; height: 28px; border-radius: 9px;
  background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 88%, #fff), var(--accent-hover));
  box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 35%, transparent);
}
.member-nav-links { display: flex; gap: 4px; margin-left: auto; }
.member-nav-link {
  padding: 7px 13px; border-radius: 8px;
  color: var(--text-muted); font-size: .92rem; font-weight: 500; white-space: nowrap;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.member-nav-link:hover { background: var(--bg); color: var(--text); text-decoration: none; }
.member-nav-link.active { background: var(--accent-soft); color: var(--accent-on-soft); }

.member-nav-lang {
  display: flex; border: 1px solid var(--border); border-radius: 8px;
  overflow: hidden; flex: 0 0 auto;
}
.member-nav-lang button {
  border: 0; background: transparent; color: var(--text-muted);
  padding: 5px 9px; font: inherit; font-size: .8rem; font-weight: 500; cursor: pointer;
  /* Fixed min-width, never a flex stretch: the EN/中文 pair kept blowing out
     the mobile nav when the two labels differ in width. */
  min-width: 42px;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.member-nav-lang button:hover { color: var(--text); }
.member-nav-lang button.active { background: var(--accent); color: #fff; }

#themeBtn {
  border: 1px solid var(--border); background: var(--surface);
  border-radius: 8px; padding: 4px 9px; cursor: pointer; font-size: .95rem; line-height: 1.4;
  flex: 0 0 auto;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
#themeBtn:hover { background: var(--bg); }

/* -------------------------------------------------------------- admin */

.admin-layout { display: flex; flex: 1 0 auto; min-height: 0; }
.admin-side {
  width: 220px; flex: 0 0 220px;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  border-right: 1px solid var(--border);
  padding: 18px 12px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.admin-side-store {
  font-size: .72rem; color: var(--text-muted);
  padding: 0 10px 14px; border-bottom: 1px solid var(--border); margin-bottom: 12px;
  word-break: break-all;
  letter-spacing: .02em;
  text-transform: uppercase;
  font-weight: 600;
}
.admin-nav-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 9px 12px; margin-bottom: 3px;
  border: 0; border-radius: 9px; background: transparent;
  color: var(--text-muted); font: inherit; font-size: .92rem; font-weight: 500;
  text-align: left; cursor: pointer;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.admin-nav-item:hover { background: var(--bg); color: var(--text); text-decoration: none; }
.admin-nav-item.active {
  background: var(--accent-soft); color: var(--accent-on-soft);
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--accent);
}
.admin-nav-item--danger { color: var(--danger); }
.admin-nav-item--danger:hover { background: var(--danger-soft); }
.admin-main { flex: 1; padding: 28px 24px; min-width: 0; }

.panel-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
.panel-head h1 {
  margin: 0; font-size: 1.45rem; font-weight: 700;
  letter-spacing: -.025em; line-height: 1.2;
}
.panel-head .spacer { margin-left: auto; }
.filter-bar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.filter-bar .form-control, .filter-bar .form-select { width: auto; min-width: 150px; }

/* The search box carries a placeholder that names what it accepts — name,
   pinyin, either Chinese script — so it needs room to show the whole sentence.
   It grows into the spare space while the dropdowns beside it stay compact. */
#dirSearch, #memberSearch { flex: 1 1 22rem; min-width: 16rem; max-width: 30rem; }

@media (max-width: 480px) {
  /* At phone width the sentence is a few pixels wider than the viewport allows.
     Shrinking the placeholder alone keeps the whole hint readable without making
     what the user types any smaller. */
  #dirSearch::placeholder, #memberSearch::placeholder { font-size: .78rem; }
}

.row-actions { display: flex; gap: 6px; }

/* The Chinese name sits in its own column so each member is one row.
   Both name cells refuse to wrap: splitting "Yufeng Chen" across two lines undoes
   the very thing the extra column was for. The table already scrolls sideways
   inside .table-responsive, so width is the cheaper thing to spend here. */
.table td.name-zh { white-space: nowrap; }
.table td.name-en { white-space: nowrap; }
.table td.name-en .hint { white-space: nowrap; }

.pager {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  padding: 16px 8px 4px;
}
.pager-status { font-size: .86rem; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* The whole member row opens the edit form; the Edit button stays as the
   keyboard-reachable path, so this is an affordance, not the only way in. */
.row-clickable { cursor: pointer; }
.msg-empty { color: var(--text-muted); text-align: center; padding: 48px 16px; }
.hint { font-size: .82rem; color: var(--text-muted); }

/* Hand-rolled dialog, deliberately NOT Bootstrap's `.modal`: that class is
   display:none until Bootstrap's JS shows it, and this page toggles [hidden]
   itself rather than pulling in the bundle. The inner panel still uses `.card`
   so it follows the theme. */
.member-modal-backdrop {
  position: fixed; inset: 0; z-index: 1055;
  background: color-mix(in srgb, #0f172a 45%, transparent);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 28px 14px; overflow-y: auto;
  animation: fade-in .18s var(--ease);
}
.member-modal-backdrop[hidden] { display: none; }
.member-modal {
  width: 100%; max-width: 680px; padding: 26px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  animation: rise-in .28s var(--ease);
}
.member-modal h2 {
  margin: 0 0 18px; font-size: 1.2rem; font-weight: 700; letter-spacing: -.02em;
}
.modal-actions {
  display: flex; gap: 8px; justify-content: flex-end;
  margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border);
}

/* --------------------------------------------------- public directory */

.auth-shell {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 40px 0 32px;
  min-height: calc(100vh - 140px);
}

.pin-box {
  max-width: 420px;
  width: 100%;
  margin: 0 auto;
  padding: 2rem 1.75rem 1.75rem !important;
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  animation: rise-in .4s var(--ease);
}
.pin-box h1 {
  font-size: 1.55rem;
  font-weight: 700;
  letter-spacing: -.03em;
  margin: 0 0 .4rem;
  line-height: 1.2;
}
.auth-mark {
  width: 44px; height: 44px; border-radius: 13px;
  margin-bottom: 1.15rem;
  background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 85%, #fff), var(--accent-hover));
  box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 28%, transparent);
  position: relative;
}
.auth-mark::before,
.auth-mark::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  background: color-mix(in srgb, #fff 92%, transparent);
}
.auth-mark::before {
  width: 11px; height: 11px; top: 11px; left: 9px;
  box-shadow: 13px 0 0 color-mix(in srgb, #fff 92%, transparent);
}
.auth-mark::after {
  width: 20px; height: 10px; bottom: 9px; left: 11px;
  border-radius: 0 0 11px 11px;
  background: transparent;
  border: 2px solid color-mix(in srgb, #fff 92%, transparent);
  border-top: 0;
}

#pinInput {
  font-size: 1.35rem;
  letter-spacing: .35em;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.household-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px 14px 18px;
  margin-bottom: 0;
  box-shadow: var(--shadow);
  position: relative;
  overflow: hidden;
  transition: transform .2s var(--ease), box-shadow .2s var(--ease), border-color .2s var(--ease);
}
.household-card::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 40%, transparent));
  border-radius: 3px 0 0 3px;
  opacity: .85;
}
.household-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: color-mix(in srgb, var(--accent) 28%, var(--border));
}
.household-head {
  display: flex; align-items: baseline; gap: 10px;
  margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--border);
}
.household-head h3 {
  margin: 0; font-size: .98rem; font-weight: 600; letter-spacing: -.01em;
}
.household-card.solo .household-head { display: none; }
.household-card.solo::before { opacity: .45; }

/* Number in its own column so a wrapped second line indents under the name it
   belongs to instead of running into the next person. */
.person {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 4px 10px; align-items: baseline; padding: 8px 0;
}
.person + .person { border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); }
.person-body { min-width: 0; }
.person-line { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.person-line + .person-line { margin-top: 3px; }
.person-name { font-weight: 600; letter-spacing: -.01em; }
.person-formal, .person-meta { color: var(--text-muted); font-size: .86rem; }

.badge {
  font-weight: 600;
  font-size: .72rem;
  letter-spacing: .02em;
  padding: .28em .55em;
  border-radius: 6px;
  border: 0;
}
.badge-no {
  background: color-mix(in srgb, var(--bg) 80%, var(--border));
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.badge-group { background: var(--accent-soft); color: var(--accent-on-soft); }
.badge-inactive { background: var(--danger-soft); color: var(--danger); }
.badge-muted {
  background: color-mix(in srgb, var(--bg) 80%, var(--border));
  color: var(--text-muted);
}

/* One labelled block per group. A PIN shared by several groups shows each
   roster under its own heading rather than merging them into one list. */
.group-section + .group-section { margin-top: 28px; }
.group-section-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 14px; }
.group-section-head h2 {
  margin: 0; font-size: 1.1rem; font-weight: 700; letter-spacing: -.02em;
}

.directory-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 14px;
  animation: fade-in .35s var(--ease);
}

#resultBox { animation: fade-in .3s var(--ease); }

#staffBar {
  border: 1px solid color-mix(in srgb, var(--accent) 25%, var(--border));
  background: var(--accent-soft);
  color: var(--accent-on-soft);
  border-radius: var(--radius-sm);
}

/* Shared mylab footer. store_code always; PB/myapi URLs only when not https,
   filled in by mylab-debug-bar.js — the fastest way to catch a page talking to
   the wrong PocketBase. */
.mylab-footer {
  flex: 0 0 auto;
  padding: 20px 16px 28px;
  text-align: center;
  font-size: .78rem;
  color: var(--text-muted);
}
.mylab-footer #mylab-pb-debug { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

@keyframes rise-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .household-card:hover { transform: none; }
  .btn-teal:hover, .btn-teal:focus { transform: none; }
}

/* Below ~420px the nav runs out of room: brand + two links + EN/中文 + the
   theme button measured 398px in a 375px viewport. The brand label is the one
   element carrying no information the rest of the bar lacks, so it goes first
   and the coloured mark stays as the home affordance. */
@media (max-width: 420px) {
  .member-nav-brand-label { display: none; }
  .member-nav-inner { gap: 6px; padding: 0 10px; }
  .member-nav-link { padding: 6px 6px; font-size: .84rem; }
  .member-nav-lang button { min-width: 36px; padding: 5px 6px; }
  .auth-shell { padding: 24px 0 20px; min-height: 0; }
  .pin-box { padding: 1.5rem 1.15rem 1.25rem !important; }
}

@media (max-width: 720px) {
  .admin-layout { flex-direction: column; }
  .admin-side {
    width: auto; flex: none; border-right: 0; border-bottom: 1px solid var(--border);
    display: flex; gap: 6px; overflow-x: auto; padding: 8px;
    backdrop-filter: none;
  }
  .admin-side-store { display: none; }
  .admin-nav-item { width: auto; margin: 0; white-space: nowrap; }
  .admin-nav-item.active { box-shadow: none; }
  .admin-main { padding: 16px 14px; }
  .member-nav-inner { gap: 8px; }
  .member-nav-link { padding: 6px 8px; font-size: .87rem; }
}
