/* The application shell: a sticky menu beside a scrolling content column.
   Loaded last, so it wins over the page-frame rules in base.css.

   The menu is on the right unless the person moved it (<html data-nav>,
   set before the first paint in index.html). nav.js also moves the
   <nav> in the DOM, so the column order below always matches it. */

.app {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--sidebar-width);
  min-height: 100vh;
}

[data-nav="left"] .app {
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Sidebar ------------------------------------------------------------
   Told apart from the content by its lighter ground alone. */

.sidebar {
  display: flex;
  flex-direction: column;
  background: var(--group);
  /* 底部不留內距：黏底的 .sidebar-footer 自己帶底距，否則捲動中的選單項目
     會從它下面那條內距裡露出來。 */
  padding: var(--sp-6) var(--sp-3) 0;
  /* Stays put while a long table scrolls beside it. */
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  overflow-y: auto;
  /* 細而淡的捲軸：旁邊已經有頁面本身的捲軸，兩條同樣粗會搶眼。 */
  scrollbar-width: thin;
  scrollbar-color: var(--hairline-strong) transparent;
}

.brand {
  padding: 0 var(--sp-4) var(--sp-7);
}

.brand h1 {
  font-family: var(--font-display);
  font-size: var(--fs-title2);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.12em;
  line-height: 1.2;
  color: var(--ink);
  margin: 0;
}

/* The wordmark's full stop: the one dot that is always there. */
.brand h1 i,
.auth-wordmark i {
  display: inline-block;
  width: var(--indicator-dot);
  height: var(--indicator-dot);
  margin-left: 2px;
  border-radius: 50%;
  background: var(--signature);
  vertical-align: baseline;
}

.brand-clinic {
  margin: var(--sp-1) 0 0;
  /* 診所全名較長時兩行等長，不留一個孤字在第二行。 */
  text-wrap: balance;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  line-height: var(--lh-ui);
}

/* 還沒填診所名稱時，管理者看到的「前往設定」：不在詞中間斷行。 */
.brand-clinic-link {
  white-space: nowrap;
}

.nav-group-title {
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  color: var(--ink-3);
  margin: var(--sp-6) 0 var(--sp-1);
  padding: 0 var(--sp-4);
}

.nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.nav-link {
  /* relative：目前頁的圓點（::after）絕對定位在右側內距裡，不佔文字的寬度。 */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 52px;
  /* 右側內距＝--sp-3 加上目前頁圓點的寬：圓點放在這塊內距裡，字再長也碰不到它；
     「每月例行申報」「匯入收費 Excel」在 220px 側欄（含細捲軸）裡仍放得下一行。原本
     --sp-4 內距再加圓點佔的 flex 空隙會折成兩行（彩排 2 側欄 #2）。 */
  padding: 0 calc(var(--sp-3) + var(--indicator-dot)) 0 calc(var(--sp-4) + 22px + var(--sp-3));
  border-radius: 12px;
  color: var(--ink-2);
  text-decoration: none;
  font-size: var(--fs-body);
  transition: background-color var(--dur-micro) var(--ease-micro);
}

.nav-link.has-icon {
  padding-left: var(--sp-4);
}

/* 次要項目（申報、報表、設定等，nav.js PRIMARY 以外）矮一點，讓整份選單在
   900px 高的螢幕上不必捲就看得到底部的深淺色與左右切換（設計審查第 1 輪 #3）。 */
.nav-link:not(.has-icon),
.nav-link.is-minor {
  min-height: 40px;
}

/* 1280×800 這類矮螢幕：有圖示的主要項目也矮一點、群組間距縮小，讓「申報作業」
   （健保申報、每月例行申報）不必捲就看得到，不會落在黏底的連線／提醒那一塊後面
   （彩排 #2）。仍維持 38px 以上，滑鼠與觸控都好按。 */
@media (min-width: 900px) and (max-height: 860px) {
  .nav-link,
  .nav-link:not(.has-icon),
  .nav-link.is-minor {
    min-height: 38px;
  }

  .nav-group-title {
    margin-top: var(--sp-3);
  }
}

.nav-icon {
  width: 22px;
  height: 22px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.nav-link:hover {
  background: var(--nav-hover);
  color: var(--ink);
}

/* Where you are: a fill, the weight, and the dot. No stripe. */
.nav-link.is-current {
  background: var(--fill);
  color: var(--ink);
  font-weight: var(--fw-semibold);
}

.nav-link.is-current .nav-icon {
  stroke-width: 1.75;
}

.nav-link.is-current::after {
  content: '';
  position: absolute;
  top: 50%;
  right: 6px;
  width: var(--indicator-dot);
  height: var(--indicator-dot);
  margin-top: calc(var(--indicator-dot) / -2);
  border-radius: 50%;
  background: var(--signature);
}

.nav-link:focus-visible {
  outline-offset: -2px;
}

/* --- Content ------------------------------------------------------------ */

.content {
  /* The cap is a variable rather than a fixed value, and it lives on main
   * rather than on .view deliberately. Custom properties inherit downward,
   * so a view cannot widen its own ancestor; the hook has to be above main.
   * The router writes the active view's id onto <body>, which gives a
   * screen a supported way to opt out of the cap:
   *
   *     body[data-view="appointments"] { --content-max: none; } */
  width: 100%;
  max-width: var(--content-max);
  /* 頂部只留 24px：登入者與登出已併進頁標題那一行（見 .topbar），不再需要
     為獨立一行頂列讓出 56px（使用者回報「留白太多」，2026-10-23）。 */
  padding: var(--sp-6) var(--sp-9) var(--sp-9);
}

/* Every view is a section and they are all siblings in <main>, so the
   page-frame rule spacing one section from the next would push every view
   but the first further down. */
.content > section.view {
  margin-top: 0;
}

/* 浮在標題右邊的 .topbar（登入者、登出）在 900～1279px 可能折成兩行、比標題高；
   標題後面的東西一律排到它下面，不會被擠窄在它左邊。沒折行時不多佔空間。 */
.content > section.view > .section-title ~ * {
  clear: right;
}

/* A card after a container of cards (a list the script fills) keeps the
   same distance as one card after another. */
.view > * + .card {
  margin-top: var(--sp-6);
}

/* A second, quieter piece beside a page title: today's date, say. */
.title-aside {
  margin-left: var(--sp-4);
  font-size: var(--fs-title1);
  font-weight: var(--fw-regular);
  color: var(--ink-3);
}

.title-aside:empty {
  display: none;
}

/* 標題列上的主要動作（報到頁的「讀健保卡」）：字型回到按鈕本來的大小，不跟著標題放大。 */
.title-action {
  margin-left: var(--sp-4);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  letter-spacing: normal;
  line-height: 1.2;
  vertical-align: middle;
}

/* --- Appointments ------------------------------------------------------- */

/* The timeline wants the whole window; see the --content-max note above. */
body[data-view="appointments"] {
  --content-max: none;
}

/* 約診打開右邊的詳情欄時，兩位醫師的欄被擠窄、姓名被切掉（DEMO 彩排 3，1440 寬）。
   這頁本來就是全寬，兩側 72px 的留白換成 32px（跟 1279px 以下一樣），多出來的寬度
   給時間軸。只管桌機：手機、平板的留白另有規則（含瀏海安全區），不能被這條蓋掉。 */
@media (min-width: 900px) {
  body[data-view="appointments"] .content {
    padding-left: var(--sp-7);
    padding-right: var(--sp-7);
  }
}

.day-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-bottom: var(--sp-6);
}

/* The day itself, in the heading face. 今天看板的日期（A23）同一個樣子，兩頁的換日看起來一樣。 */
#appt-date,
.po-today-date {
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums;
  font-size: var(--fs-title1);
  font-weight: var(--fw-semibold);
  padding: 0 var(--sp-1);
}

/* Previous and next day: round. 今天看板的 ‹ ›（.day-step，A23）同一個樣子。 */
#appt-prev,
#appt-next,
.day-step {
  width: 40px;
  min-height: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
  font-size: var(--fs-title3);
}

/* The date field beside the day's heading: jump to any day. */
.day-pick {
  width: auto;
}

/* The Google Calendar day sits above the board, which is not a card. */
#appt-gcal {
  margin-bottom: var(--sp-6);
}

.toolbar-spacer {
  flex: 1;
}

.appt-layout.has-panel {
  display: grid;
  /* 詳情欄 320px 放得下「報到」整排與改約／取消／刪除三顆，再寬就是從時間軸拿走。 */
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--sp-6);
  align-items: start;
}

.side-panel {
  position: sticky;
  top: var(--sp-6);
}

.side-panel.card {
  /* 所有寬度都歸零：.card + .card 的上距會把側欄推得比當天的卡片低一截。 */
  margin: 0;
  padding: var(--sp-6);
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
}

.panel-header :is(h2, h3, strong) {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: var(--fw-semibold);
}

.detail-line {
  display: flex;
  gap: var(--sp-4);
  padding: var(--sp-1) 0;
}

.detail-line > :first-child {
  /* 標籤欄固定 4 個字寬：「時間」「狀態」和「北昕說明」的值對齊同一條線，
     四個字的也不折行（QA B8 第 3、4 輪：768 寬折行、值沒對齊）。 */
  width: 4em;
  flex-shrink: 0;
  white-space: nowrap;
  color: var(--ink-3);
}

.detail-line > :last-child {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* 約診詳情的醫師那一行（北昕第一批 B1）：現在的醫師、其他醫師的「換給」排在同一行，放不下就折行。 */
.appt-doctor-value {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

.appt-doctor-choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.appt-doctor-choices .btn-secondary {
  min-height: var(--h-control-sm);
}

.appt-doctor-why {
  flex-basis: 100%;
  margin: 0;
}

.panel-label {
  display: block;
  margin-top: var(--sp-4);
  color: var(--ink-2);
  font-size: var(--fs-footnote);
}

.side-panel textarea {
  width: 100%;
  margin: var(--sp-1) 0;
  font: inherit;
}

.panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
}

.panel-actions .btn-primary {
  flex-basis: 100%;
  min-height: 52px;
}

/* Timeline: absolutely positioned against a minutes-to-pixels scale. */
.timeline {
  position: relative;
  margin-left: 3.5em;
}

.timeline-line {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px solid var(--hairline);
}

.timeline-line.is-hour {
  border-top-color: var(--hairline-strong);
}

.timeline-label {
  position: absolute;
  left: -3.5em;
  top: -0.7em;
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.timeline-closed {
  position: absolute;
  left: 0;
  right: 0;
  background: repeating-linear-gradient(45deg, transparent 0 6px, var(--fill) 6px 12px);
}

.timeline-block,
.timeline-gap {
  position: absolute;
  left: var(--sp-2);
  right: var(--sp-2);
  min-height: 0;
  text-align: left;
  border-radius: 10px;
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  line-height: 1.3;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* An appointment on the day: state is the fill alone, no side stripe.
   名字與「時間 · 項目 · 狀態」是兩個 flex 項目：放得下同一行，欄窄（多位醫師、
   右側面板打開、取消窄道）時細節折到第二行，各自在行尾截斷，不再整行只剩名字和
   半個時間（彩排 2 約診 #5）。safe center：放不下時從頂端排起，切掉的是尾巴，
   名字永遠看得到。line-height 與上下內距收小，30 分鐘（43px）的格子放得下兩行。 */
.timeline-block {
  border: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: safe center;
  column-gap: 0.25em;
  padding-top: 2px;
  padding-bottom: 2px;
  line-height: 1.2;
}

.timeline-block > * {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.timeline-block:active:not(:disabled),
.timeline-gap:active:not(:disabled) {
  transform: none;
}

.timeline-block.status-scheduled { background: var(--fill-strong); color: var(--ink); }
.timeline-block.status-arrived { background: var(--ok-wash); color: var(--ok-ink); }
.timeline-block.status-arrived::before {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--ok);
  vertical-align: middle;
}
.timeline-block.status-cancelled {
  background: transparent;
  outline: 1px dashed var(--hairline-strong);
  outline-offset: -1px;
  color: var(--ink-3);
  text-decoration: line-through;
}
.timeline-block.status-no_show { background: var(--danger-wash); color: var(--danger); }

.timeline.has-side-lane .timeline-block,
.timeline.has-side-lane .timeline-gap {
  right: calc(30% + var(--sp-2));
}

.timeline.has-side-lane .timeline-block.is-side {
  left: 70%;
  right: var(--sp-2);
}

.timeline-block.is-selected {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  z-index: 1;
}

/* Free time: a dashed outline on the card ground. */
.timeline-gap {
  border: 1.5px dashed var(--hairline-strong);
  background: var(--group);
  color: var(--ink-2);
  display: flex;
  align-items: center;
  /* safe：放得下才置中；放不下時靠左，切掉的是尾巴（時間），不是開頭的醫師名字
     ——置中時文字兩邊一起溢出，欄窄時只看得到「師 空檔 30 分鐘（14:30–15:」。 */
  justify-content: safe center;
}

.timeline-gap:hover {
  background: var(--nav-hover);
}

/* Now: a fine line with the dot at its left end. */
.timeline-now {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 2;
  border-top: 1px solid var(--ink-3);
  pointer-events: none;
}

.timeline-now::before {
  content: '';
  position: absolute;
  left: -4px;
  top: -4.5px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--signature);
}

.panel-field {
  display: block;
  margin-top: var(--sp-3);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.panel-field select,
.panel-field input,
.panel-field textarea {
  display: block;
  width: 100%;
  margin-top: var(--sp-1);
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
}

.slot-list {
  margin-top: var(--sp-4);
  max-height: 22rem;
  overflow-y: auto;
}

/* 約診面板：兩個欄位排一行（窄的時候自己折回一欄），面板矮一截（QA r2 A15 C7）。 */
.booking-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  column-gap: var(--sp-3);
}

.booking-manual {
  margin-top: var(--sp-3);
}

.booking-manual > summary {
  min-height: var(--h-control-sm);
  display: flex;
  align-items: center;
  cursor: pointer;
  color: var(--ink-2);
}

.booking-manual-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
}

.booking-manual-row input {
  flex: 1 1 9rem;
  min-width: 0;
}

.booking-foot {
  margin-top: var(--sp-3);
}

.booking-foot .notice {
  margin: 0 0 var(--sp-2);
}

.slot-day {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-footnote);
  margin: var(--sp-3) 0 var(--sp-1);
}

/* 一天一行：每段空檔的第一個開始時間＋「其他時間」（booking.js dayStarts，smooth-ops A5）。 */
.slot-day-starts {
  align-items: center;
  margin-top: 0;
}

.slot-day-more {
  min-height: var(--h-control-sm);
  padding: 0 var(--sp-2);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

/* 展開後的一段空檔：時段在前、開始時間跟在後面，同一行放不下才折。 */
.slot-block {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}

.slot-block > .chip-row {
  margin-top: 0;
}

.slot-block-head {
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  min-width: 7.5em;
}

.patient-picker input {
  width: 100%;
}

/* .panel-field input 的 display:block 會蓋掉 hidden：病患選好後搜尋框要真的收起來。 */
.patient-picker input[hidden] {
  display: none;
}

.picker-result {
  white-space: normal;
  display: block;
  width: 100%;
  min-height: var(--h-control);
  text-align: left;
  background: none;
  border: 0;
  border-radius: 0;
  font-weight: var(--fw-regular);
  box-shadow: inset 0 -1px 0 var(--hairline);
  padding: var(--sp-2);
}

.picker-result:hover {
  background: var(--nav-hover);
}

.picker-result:active:not(:disabled) {
  transform: none;
}

.picker-chosen {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}

.side-panel details {
  margin-top: var(--sp-4);
}

.side-panel .btn-primary {
  width: 100%;
  min-height: 52px;
  margin-top: var(--sp-4);
}

/* List: the phone version of the same day. */
.day-list-title {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  margin: var(--sp-4) 0 var(--sp-2);
}

.day-list-row {
  white-space: normal;
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  width: 100%;
  min-height: var(--h-row);
  text-align: left;
  background: none;
  border: 0;
  border-radius: 0;
  font-weight: var(--fw-regular);
  padding: var(--sp-2) 0;
  color: var(--ink);
}

/* The list sits in a card, whose padding already insets it. */
.day-list-row + .day-list-row::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-top: 1px solid var(--hairline);
}

.day-list-row:hover {
  background: var(--nav-hover);
}

.day-list-row:active:not(:disabled) {
  transform: none;
}

.day-list-time {
  width: 6.5rem;
  flex-shrink: 0;
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-body);
  color: var(--ink-2);
}

.day-list-name {
  flex: 1;
  min-width: 0;
}

.day-list-row.is-gap {
  color: var(--ink-2);
  border: 1.5px dashed var(--hairline-strong);
  border-radius: 12px;
  background: var(--group);
  margin: var(--sp-2) 0;
  padding: var(--sp-2) var(--sp-4);
}

.day-list-row.is-gap::before,
.day-list-row.is-gap + .day-list-row::before {
  display: none;
}

.day-list-row.status-cancelled .day-list-name {
  text-decoration: line-through;
  color: var(--ink-3);
}

/* --- Check-in ------------------------------------------------------------ */

/* Fixed column widths. With automatic layout the browser sizes each column
   to its content, so "已報到" being wider than "已約" moved every column to
   its left when a patient arrived. */
.checkin-table {
  table-layout: fixed;
  width: 100%;
  min-width: 40rem; /* narrower than this the card scrolls sideways */
}

.checkin-table th:nth-child(1) { width: 7.5em; }
.checkin-table th:nth-child(4) { width: 7em; }
.checkin-table th:nth-child(5) { width: 8.5em; }
/* The rest goes to the patient, whose cell also holds the cloud query. */
.checkin-table th:nth-child(3) { width: 7em; }
.checkin-table th:nth-child(6),
.checkin-table th:nth-child(7) { width: 7.5em; }
/* 平板與窄的電腦視窗（1280px 以下）：固定寬的欄位與格子留白縮小，病患欄才不會
   被擠到一個字一行（QA 2026-10-23：768、1024 寬只看得到不到 5 位病患）。 */
@media (max-width: 1279px) {
  .checkin-table th:nth-child(1) { width: 6.5em; }
  .checkin-table th:nth-child(3) { width: 5.5em; }
  .checkin-table th:nth-child(4) { width: 4.5em; }
  .checkin-table th:nth-child(5) { width: 4em; }
  .checkin-table th:nth-child(6),
  .checkin-table th:nth-child(7) { width: 5.5em; }
  .checkin-table th,
  .checkin-table td { padding-left: var(--sp-2); padding-right: var(--sp-2); }
  .checkin-table .checkin-action { min-width: 0; padding: 0 var(--sp-3); }
}

/* 病患清單：姓名是要找的東西，比其他欄重一級。 */
#patients-table td.cell-name,
#patients-today-table td.cell-name,
#patients-booked-table td.cell-name {
  font-weight: 600;
}

/* 手機上欄位不換行（電話號碼被拆成三行時讀不出來），整張表在卡片裡橫向捲。 */
@media (max-width: 899px) {
  #patients-table th,
  #patients-table td,
  #patients-today-table th,
  #patients-today-table td,
  #patients-booked-table th,
  #patients-booked-table td {
    white-space: nowrap;
  }
}

/* The time: a figure, like every time in a list. */
.checkin-table td:first-child {
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-body);
  color: var(--ink-2);
}

/* The cloud query under the patient's name (checkin.js patientCell). */
.checkin-cloud {
  margin-top: var(--sp-1);
}

/* Same geometry whichever role the button has, so a row keeps its height
   when 報到 turns into 取消報到. */
.checkin-action {
  min-width: 6em;
  min-height: var(--h-control-sm);
  padding: 0 var(--sp-4);
  font-size: var(--fs-footnote);
  text-align: center;
}

/* --- Appointment settings: fixed column widths --------------------------
   Same reason as .checkin-table: "使用中" and "停用" are different widths,
   so with automatic layout toggling a row moved the columns beside it. */

.types-table,
.chairs-table {
  table-layout: fixed;
  width: 100%;
  min-width: 32rem; /* narrower than this the card scrolls sideways */
}

.types-table th:nth-child(2) { width: 15em; } /* the round stepper */
.types-table th:nth-child(3) { width: 6.5em; }
.types-table th:nth-child(4) { width: 11em; }
.chairs-table th:nth-child(2) { width: 6.5em; }
.chairs-table th:nth-child(3) { width: 11em; }

/* --- Appointment settings: opening hours -------------------------------- */

.hours-row {
  display: grid;
  grid-template-columns: 4.5em minmax(0, 1fr) auto;
  gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--hairline);
}

.hours-sessions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
}

.hours-session {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

/* --- Waiting-room board (叫號) ------------------------------------------
   The desk's main screen (B3a-desk-1440). Chairs across the top; below
   them the three lists side by side as a board. Whether a chair is taken
   is the tile's darker ground and its dot -- nothing else, and readable in
   no time at all. Rows are flex lines rather than a table: on the doctor's
   phone each one wraps, with its buttons on a line of their own. */

.queue-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
  flex-wrap: wrap;
  /* 頁標題下方 20px，這行往上收 8px：緊貼標題、讀成標題的副標。 */
  margin: calc(-1 * var(--sp-2)) 0 var(--sp-5);
}

.queue-summary {
  margin: 0;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.queue-live,
.sidebar-live {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.queue-live::before,
.sidebar-live::before {
  content: '';
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--danger);
}

.queue-live.is-live::before,
.sidebar-live:not(.is-down)::before {
  background: var(--ok);
}

/* 「看板是舊的」：連線還活著（所以不是斷線那種紅點），但畫面上這一份不是現在的——
   仍然是錯誤，所以用錯誤色寫出字，不只變一顆點。 */
.queue-live.is-stale {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.queue-live.is-stale::before {
  background: var(--danger);
}

.queue-tv-link,
.topbar-notice {
  margin-left: auto;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

.queue-tv-link:hover,
.topbar-notice:hover {
  color: var(--ink);
}

.queue-error,
.queue-note {
  margin: 0 0 var(--sp-6);
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--danger-wash);
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

/* 被拒的原因：寫在按下的那一列按鈕底下，跟按鈕對齊（queue.js noteFor）。 */
.queue-note {
  margin: 0;
  font-size: var(--fs-footnote);
}

.queue-list .queue-note {
  flex-basis: 100%;
  margin-left: calc(60px + var(--sp-4));
}

.queue-chair .queue-note {
  grid-column: 1 / -1;
  margin-top: var(--sp-3);
}

/* 取代 alert() 的行內錯誤（dom.js showInlineError），外觀同叫號看板的錯誤列。 */
.inline-error {
  margin: var(--sp-2) 0;
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--danger-wash);
  color: var(--danger);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
  white-space: normal;
}

/* form 是兩欄 grid：錯誤橫跨整列，不擠在某一欄裡。 */
form > .inline-error {
  grid-column: 1 / -1;
}

.inline-error.is-page {
  margin: 0 0 var(--sp-6);
}

.inline-error[hidden] {
  display: none;
}

/* 另一台工作站同時動了同一筆錢（payments.js、charge-conflict-view.js）：外觀同行內錯誤——
   它就是錯誤，不另立一種顏色（一色一義）。差別是它帶按鈕：講完現在是什麼樣子，還要給得出下一步。 */
.charge-conflict {
  grid-column: 1 / -1;
  margin: var(--sp-2) 0;
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--danger-wash);
  color: var(--danger);
  font-size: var(--fs-footnote);
}

.charge-conflict[hidden] {
  display: none;
}

.charge-conflict strong {
  display: block;
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
}

.charge-conflict p {
  margin: var(--sp-1) 0 0;
}

.charge-conflict .charge-conflict-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}

.queue-chairs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: var(--sp-6);
  margin-bottom: var(--sp-9);
}

/* An empty chair: the card ground with a fine edge. */
.queue-chair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  /* 名稱、姓名、處置三列固定，第四列吃掉剩餘高度：同一排的椅子不論有沒有
     處置標籤、空不空，按鈕都落在卡片底部對齊（設計審查第 1 輪 #5）。 */
  grid-template-rows: auto auto auto 1fr;
  column-gap: var(--sp-5);
  min-height: 248px;
  padding: var(--sp-7);
  background: var(--group);
  border-radius: var(--r-tile);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
}

/* A taken chair: the tile, one step darker, and the dot by its name. */
.queue-chair.is-busy {
  background: var(--tile);
  box-shadow: none;
}

.queue-chair > * {
  grid-column: 1;
}

.queue-chair-name {
  display: flex;
  align-items: center;
  white-space: nowrap;
  gap: var(--sp-2) var(--sp-3);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.queue-chair.is-busy .queue-chair-name > span:first-child {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.queue-chair.is-busy .queue-chair-name > span:first-child::before {
  content: '';
  width: var(--indicator-dot-lg);
  height: var(--indicator-dot-lg);
  border-radius: 50%;
  background: var(--signature);
}

.queue-chair-name .queue-ticket {
  font-family: var(--font-ui);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  color: var(--ink-2);
}

.queue-chair-who,
.queue-chair-empty {
  margin-top: var(--sp-2);
  font-family: var(--font-display);
  font-size: var(--fs-large);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.01em;
  line-height: 1.2;
}

.queue-chair-empty {
  color: var(--ink-3);
}

.queue-chair-label {
  margin-top: var(--sp-1);
  font-size: var(--fs-title3);
  color: var(--ink-2);
}

/* Minutes in the chair, on the right of the name. Over time turns amber,
   never red: it is a nudge, not an error. */
.queue-chair-time {
  grid-column: 2;
  grid-row: 1 / span 3;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
}

.queue-chair-minutes {
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-numeral-lg);
  font-weight: 500;
  line-height: 1;
  color: var(--ink);
}

/* 「分」接在大數字後面，用介面字、小一號，不跟數字搶。 */
.queue-chair-unit {
  margin-left: var(--sp-1);
  font-family: var(--font-ui);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  color: var(--ink-2);
}

.queue-chair-expected {
  margin-top: var(--sp-1);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.queue-chair-time.is-over .queue-chair-minutes,
.queue-chair-time.is-over .queue-chair-unit,
.queue-chair-time.is-over .queue-chair-expected {
  color: var(--warn);
}

.queue-chair .queue-actions {
  grid-column: 1 / -1;
  grid-row: 4;
  align-self: end;
  padding-top: var(--sp-6);
}

/* On the tile the usual fill would vanish; the card ground stands out. */
.queue-chair.is-busy .btn-secondary {
  background: var(--group);
}

.queue-chair.is-busy .btn-secondary:hover {
  background: var(--canvas);
}

/* One chair: one large card (spec 2026-10-21-queue-toggle-design.md §6). */
.queue-chairs.is-single .queue-chair {
  padding: var(--sp-8);
}

.queue-chairs.is-single .queue-chair-who {
  font-size: var(--text-display);
}

/* The three lists as a board; a hidden one leaves its room to the rest. */
.queue-lists {
  /* 一欄最窄到這裡就不再並排，改成換行（見下面 .queue-column）。這個數字是量出來的，不是估的：
     一列上面那行的固定開銷＝清單卡內距 48（≤1279 時 .card 的 var(--sp-6) 左右各 24）
     ＋列內距 32＋四個間隙 32＋號碼軌 3.2em≈54＋三顆星 72＋狀態標最寬 108（「候診 12 分」）
     ＝約 347px，姓名要再留 4em≈68 才看得到字 → 415。取 416。
     少算了清單卡那 48px 的話，1024 三欄時姓名只剩 23px（實測）。 */
  --queue-col-min: 416px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
  align-items: flex-start;
}

.queue-column {
  /* 寧可換行讓欄變少、每一欄夠寬，也不要多欄一起擠窄：三欄並排在 1280 時每欄只有 244px，
     姓名被壓成 0px、整列還頂出欄外蓋到隔壁欄（fix/queue-row-1280 實測）。
     min-width 用 min(…, 100%)：容器本身比下限還窄時（手機）不要反而溢出。 */
  flex: 1 1 var(--queue-col-min);
  min-width: min(var(--queue-col-min), 100%);
}

/* The waiting list has the most to do, so it gets the most room. */
.queue-column:first-child {
  flex-grow: 1.5;
}

.queue-column[hidden] {
  display: none;
}

.queue-list-title {
  margin: 0 0 var(--sp-3);
  padding: 0 var(--sp-1);
  font-family: var(--font-display);
  font-size: var(--fs-title2);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.01em;
}

.queue-count {
  margin-left: var(--sp-1);
  font-family: var(--font-ui);
  font-size: var(--fs-title3);
  font-weight: var(--fw-regular);
  color: var(--ink-3);
}

.queue-list {
  list-style: none;
  margin: 0;
  padding: var(--sp-1) 0;
}

/* --- List rows: the board's, the day list's, the notifications' --------
   A hairline between rows, inset from both edges rather than running full
   width. */
.queue-row {
  position: relative;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  min-height: var(--h-row);
  padding: var(--sp-2) var(--sp-5);
}

/* 一列底下開著的小塊（換醫師要換給誰，A27）：整列寬、不影響上面那一行的欄寬。 */
.queue-row-ask {
  flex: 1 0 100%;
  min-width: 0;
}

.queue-row + .queue-row::before {
  content: '';
  position: absolute;
  top: 0;
  left: var(--sp-5);
  right: var(--sp-5);
  border-top: 1px solid var(--hairline);
}

.queue-ticket {
  min-width: 60px;
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-numeral);
  font-weight: var(--fw-regular);
  color: var(--ink-2);
  white-space: nowrap;
}

.queue-who {
  display: flex;
  flex-direction: column;
  flex: 1 1 8rem;
  min-width: 0;
}

.queue-who strong {
  font-weight: var(--fw-semibold);
}

.queue-visits-link {
  align-self: flex-start;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.queue-who .muted {
  font-size: var(--fs-footnote);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.queue-eta {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  white-space: nowrap;
}

/* In the lists the buttons take a line of their own under the name, the
   same in every row, rather than wrapping wherever they run out of room. */
.queue-list .queue-actions {
  flex-basis: 100%;
  padding-left: calc(60px + var(--sp-4));
}

.queue-list .queue-actions:empty {
  display: none;
}

.queue-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.queue-actions button {
  white-space: nowrap;
}

.queue-list .queue-actions button {
  min-height: var(--h-control-sm);
  padding: 0 var(--sp-4);
  font-size: var(--fs-footnote);
}

/* A chair that is taken: still pressable, so the server can say by whom.
   Quieter words, not a fade. */
.queue-actions .is-taken {
  color: var(--ink-3);
}

.queue-empty {
  padding: var(--sp-6) var(--sp-5);
  text-align: center;
  font-size: var(--fs-footnote);
}

/* --- Waiting-room TV (候診區畫面) -----------------------------------------
   The whole window, no sidebar: it is a screen on a wall, not a page
   anyone navigates. */

/* 電視畫面不捲動，不必替捲軸留那條空白（base.css html 的 scrollbar-gutter）。 */
html:has(body[data-view="queue-display"]) {
  scrollbar-gutter: auto;
}

body[data-view="queue-display"] {
  --content-max: none;
}

body[data-view="queue-display"] .app {
  grid-template-columns: minmax(0, 1fr);
}

body[data-view="queue-display"] .sidebar,
body[data-view="queue-display"] .tabbar,
body[data-view="queue-display"] .topbar {
  display: none;
}

body[data-view="queue-display"] .content {
  padding: var(--sp-7);
}

/* The board switched off: one line, nothing about anyone. */
.tv-off {
  margin: 0;
  padding: var(--sp-8);
  background: var(--group);
  border-radius: var(--r-tile);
  font-size: var(--text-display);
  color: var(--ink-3);
  text-align: center;
}

/* 伺服器組不出看板時電視上那一句話。用錯誤色（一色一義：--danger 是過敏、欠款、錯誤）：
   候診室的人離螢幕遠，所以字跟著電視的尺度走。 */
.tv-stale {
  margin: 0 0 var(--sp-6);
  padding: var(--sp-5) var(--sp-7);
  background: var(--danger-wash);
  border-radius: var(--r-tile);
  font-size: var(--text-display);
  color: var(--danger);
  text-align: center;
}

.tv-stale[hidden] {
  display: none;
}

.tv {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--sp-7);
}

/* 窄螢幕（用手機或平板直放預覽候診畫面）：診療椅與候診上下排，
   候診欄才不會窄到姓名放不下、蓋到診療椅上。 */
@media (max-width: 899px) {
  .tv {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* display: grid above beats the browser's own [hidden] rule. */
.tv[hidden] {
  display: none;
}

/* 最新叫號的那一句：橫跨整個看板寬度，放在診療椅與候診上面。 */
.tv-latest {
  grid-column: 1 / -1;
  margin: 0;
  padding: var(--sp-5) var(--sp-7);
  background: var(--tile);
  border-radius: var(--r-tile);
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-size: var(--text-display-lg);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tv-latest[hidden] {
  display: none;
}

.tv-chairs {
  display: grid;
  gap: var(--sp-6);
  align-content: start;
}

.tv-chair,
.tv-side {
  padding: var(--sp-7);
  background: var(--group);
  border-radius: var(--r-tile);
}

/* Taken: the tile and a large dot by the chair's name. No outline. */
.tv-chair.is-busy {
  background: var(--tile);
}

.tv-chair-name,
.tv-heading {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-3);
  font-size: var(--text-display);
  font-weight: var(--fw-semibold);
  color: var(--ink-2);
}

.tv-chair.is-busy .tv-chair-name::before {
  content: '';
  width: 12px;
  height: 12px;
  flex: none;
  border-radius: 50%;
  background: var(--signature);
}

/* 遮名後的姓名可能很長（例如英文名「D○○○○○吉」）：一律單行，放不下就截尾，
   不讓號碼與姓名斷成兩行（QA D11、D13）。 */
.tv-chair-who {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-size: var(--text-display-lg);
  font-weight: var(--fw-semibold);
}

.tv-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.tv-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-5);
  padding: var(--sp-3) 0;
  border-top: 1px solid var(--hairline);
  font-size: var(--text-display);
}

.tv-row:first-child {
  border-top: none;
}

.tv-ticket {
  flex: none;
  white-space: nowrap;
  min-width: 4ch;
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--ink-2);
}

/* 姓名至少留 3 個字寬；整排放不下時，換到下一行的是「約 30 分」，不是姓名。 */
.tv-name {
  flex: 1 1 3em;
  min-width: 3em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tv-eta {
  flex: none;
  margin-left: auto;
  white-space: nowrap;
  color: var(--ink-3);
}

.tv-skipped {
  margin: var(--sp-6) 0 0;
  font-size: var(--text-display);
  color: var(--ink-3);
}

/* The call, filling the screen. It sets display, so [hidden] needs saying
   again (see the sign-in note below). */
.tv-call {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* Deep ink, not orange: orange is never a large area, and the dark
     ground reads further across the room. */
  background: var(--ink);
  color: var(--canvas);
  animation: tv-call-in 0.3s ease-out;
}

.tv-call[hidden] {
  display: none;
}

.tv-call-who {
  max-width: calc(100vw - 2 * var(--sp-7));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-size: var(--text-display-xl);
  font-weight: var(--fw-semibold);
}

.tv-call-who::before {
  content: '';
  /* 深墨底上 --signature 的對比不足，改用較亮的同色系。 */
  display: inline-block;
  width: 24px;
  height: 24px;
  margin-right: var(--sp-5);
  border-radius: 50%;
  background: #d98a78;
  vertical-align: middle;
}

.tv-call-where {
  margin: var(--sp-4) 0 0;
  font-size: var(--text-display-lg);
}

@keyframes tv-call-in {
  from { opacity: 0; transform: scale(0.96); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .tv-call {
    animation: none;
  }
}

/* 收在畫面底部那條 --sp-7 的留白裡：之前 44px 高、離底 24px，疊在候診卡的右下角
   上（彩排 2 其他 #15）。這顆只在開機時按一次，矮一點不影響；也不另外撐高頁面，
   電視畫面才不會多出捲軸。 */
.tv-sound {
  position: fixed;
  right: var(--sp-7);
  bottom: 2px;
  min-height: 28px;
  padding: 0 var(--sp-3);
  z-index: 41;
  background: var(--fill);
  color: var(--ink-2);
  font-size: var(--fs-footnote);
}

/* --- Phone: a tab bar, and the menu as a sheet -------------------------
   Kept beside the shell rules it overrides rather than in base.css, so the
   shell is described in one place. nav.js adds the bar. */

.tabbar {
  display: none;
}

/* --- Sign-in --------------------------------------------------------------
   .app and .auth-screen both set display, and an author display rule beats
   the browser's own [hidden] { display: none }. Without these two lines the
   app would show through while signed out. */
.app[hidden],
.auth-screen[hidden] {
  display: none;
}

.auth-screen {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--sp-6) var(--sp-4);
  background: var(--canvas);
}

/* Not a card: the form sits straight on the page (B3a-login-1440). */
.auth-card {
  width: min(100%, 380px);
}

.auth-wordmark {
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.12em;
  line-height: 1.2;
  color: var(--ink);
}

.auth-brand {
  margin: var(--sp-1) 0 var(--sp-7);
  text-align: center;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.auth-card h1 {
  font-size: var(--fs-title2);
  margin: 0;
}

.auth-card h1:empty {
  display: none;
}

.auth-form {
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--sp-5);
}

.auth-form input {
  min-height: 56px;
  border-radius: var(--r-input);
}

.auth-form .btn-primary {
  width: 100%;
  min-height: 52px;
  margin-top: var(--sp-2);
}

.auth-message {
  color: var(--danger);
  min-height: 1.5em;
  margin: var(--sp-3) 0 0;
  text-align: center;
}

.auth-link {
  display: block;
  min-height: var(--h-control);
  margin: var(--sp-3) auto 0;
  padding: 0;
  background: none;
  color: var(--ink-2);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  text-decoration: underline;
  text-decoration-color: var(--hairline-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

.auth-link:hover,
.auth-link:active:not(:disabled) {
  background: none;
  color: var(--ink);
  text-decoration-color: currentColor;
}

.auth-link[hidden] {
  display: none;
}

.sidebar-footer {
  /* 選單比視窗長時（管理者 28 項），底部的狀態列黏在側欄底，不會被捲出畫面。 */
  position: sticky;
  bottom: 0;
  /*
    選單比視窗短時（醫師只有 9 項）**不**把它推到底邊。
    使用者 2026-10-26 看著醫師的側欄問「這空一大格式怎樣」：量出來 1536×864 下
    最後一項到狀態列之間空了 467px（側欄高 864，等於一半以上是空的），
    就是 `margin-top: auto` 推出來的。改成照一般流排在選單後面：
    剩下的空白落在整欄的末端（看起來是「這一欄到這裡結束」），
    而不是夾在兩塊東西中間（看起來是「這裡少了什麼」）。

    `position: sticky` 在內容沒有溢出時本來就不生效，所以長選單那一邊
    （管理者）完全不變：照舊黏在側欄底，上面的項目在側欄內自己捲。
  */
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
  /* 一條細線把黏底的區塊和捲到它下面的選單項目分開，字不會看起來被切掉一半。 */
  border-top: 1px solid var(--hairline);
  background: var(--group);
  font-size: var(--fs-footnote);
  /* DOM 是「開關在前、內容在後」（鍵盤打開提醒後 Tab 直接進內容），畫面上要
     狀態列在最底、提醒在它上面往上長，所以用 order 排。 */
  display: flex;
  flex-direction: column;
}

.sidebar-account { order: 0; }
.sidebar-alerts { order: 1; }
.sidebar-status-line { order: 2; }

.sidebar-live {
  margin: 0;
}

/* 連線狀態與「提醒 N 項」同一行；提醒區平常收起（nav.js collapseSidebarAlerts）。 */
.sidebar-status-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-3);
  margin: 0;
}

.sidebar-alerts-toggle {
  min-height: 36px;
  padding: 0 var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--fill);
  color: var(--ink);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
}

.sidebar-alerts-toggle:hover {
  background: var(--fill-strong);
}

/* 有逾期、警告、或醫師卡在等 PIN：字改紅，收起來也看得出要處理。 */
.sidebar-alerts-toggle.is-warning {
  background: var(--danger-wash);
  color: var(--danger);
}

.sidebar-alerts-toggle::after {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: var(--sp-2);
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
}

.sidebar-alerts-toggle[aria-expanded="true"]::after {
  transform: translateY(1px) rotate(-135deg);
}

.sidebar-alerts-toggle[hidden] {
  display: none;
}

/* 展開後高度有上限、在框內捲動：再多提醒也只佔側欄下方一塊，選單永遠看得到。 */
.sidebar-alerts {
  max-height: min(32vh, 280px);
  overflow-y: auto;
  margin: 0 calc(-1 * var(--sp-2)) var(--sp-2);
  padding: var(--sp-2);
  border-radius: 12px;
  background: var(--canvas);
  scrollbar-width: thin;
}

.sidebar-alerts > :last-child,
.sidebar-alerts > div > :last-child {
  margin-bottom: 0;
}

/* 誰登入與登出：只在手機的「更多」選單出現（頂列在手機收起）。 */
.sidebar-account {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--sp-4);
  margin: 0 0 var(--sp-2);
  color: var(--ink-3);
}

.sidebar-account .btn-plain {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

/* 我的設定「顯示」卡：一行一個偏好，名稱一欄、選項一欄，兩組選項左緣對齊（account.js）。 */
.pref-row {
  display: grid;
  grid-template-columns: 6em auto;
  justify-content: start;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-2) 0;
}

/* 我的設定：卡片兩欄並排（≥1100px）。原本五張卡一欄到底，1536×864 要捲才看得到
   深色模式（量測：13 個控制項有 5 個在摺線下，使用者 10-26 因此找不到）。
   §10.4：看不到就等於不知道有這個東西。 */
@media (min-width: 1100px) {
  /*
    :not([hidden]) 不是裝飾——少了它這一頁就永遠在畫面上。
    每個 view 平常是靠 hidden 屬性收起來的（瀏覽器內建樣式表的 display: none），
    而這裡寫 display: grid 的優先度比內建樣式高，於是不管在哪一頁，
    我的設定那一整頁都會疊在底下：實測內容欄從 1150px 長到 1966px、
    櫃台 1536×864 的頁面捲動從 286px 變成 1102px，而且沒有任何測試會紅。
  */
  #view-account:not([hidden]) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
    gap: var(--sp-5);
  }

  /* 標題自己一整行。 */
  #view-account > .section-title {
    grid-column: 1 / -1;
  }

  /* 顯示（深色模式、選單位置、快捷鍵）是他最常來改的，放左上第一格——
     §10.1：拿到空間的理由是「一次看診會碰幾次」。 */
  #view-account > #display-prefs {
    grid-column: 1;
  }
}

/* --- 快捷鍵（A22，js/shortcuts.js）------------------------------------------
   一個動作一列：動作、在哪一頁、按鍵、改回預設。畫面上沒有說明文字（§10.3），
   怎麼改收在標題的 ⓘ，固定不給改的理由收在那一列的 ⓘ。

   兩欄（可以改的／固定的）：一欄排到底時 13 列加上「全部回到預設」在 1536×864
   要捲才看得到（量到 4 個控制項在摺線下）。寬度本來就有剩，拿來省掉捲動。 */
@media (min-width: 1100px) {
  /* :not([hidden]) 不可省：display 會蓋掉 hidden 屬性的 display:none，
     少了它這一頁會疊在每一頁底下（view-hidden-wins.test.mjs 守這條）。 */
  #view-shortcuts:not([hidden]) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
    gap: var(--sp-5);
  }

  #view-shortcuts > .section-title {
    grid-column: 1 / -1;
  }
}

/* 小標題（「可以改的」「固定的」）：這是欄位標籤，不是教人怎麼操作的說明文字。 */
.shortcut-head {
  margin: 0 0 var(--sp-3);
}

/* 標題與「全部回到預設」同一行：按鈕不隨名單變長被推到摺線下。 */
.shortcut-head-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-3);
}

.shortcut-head-row .shortcut-head {
  margin: 0;
}

/* 欄寬讓內容決定（不用 table-layout: fixed）：固定寬度的欄位加起來超過卡片寬時，
   瀏覽器仍照寫死的寬度排，動作那一欄被壓成一個字一行——1280×720 實測整張表爛掉。
   只給按鍵與操作欄一個下限，動作欄吃剩下的全部。 */
.shortcut-table th:last-child,
.shortcut-table td:last-child {
  width: 1%;
  white-space: nowrap;
}

.shortcut-table tbody th {
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  color: var(--ink);
  text-align: left;
  padding: var(--sp-1) var(--sp-3);
  border-bottom: 1px solid var(--hairline);
  white-space: normal;
}

.shortcut-table td {
  padding: var(--sp-1) var(--sp-3);
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
}

/* 動作名稱，底下一行小字寫「在哪一頁」（同一格兩行，不是兩欄）。 */
.shortcut-label,
.shortcut-where {
  display: block;
}

.shortcut-where {
  color: var(--ink-3);
  font-size: var(--fs-footnote);
  line-height: var(--lh-ui);
}

/* 按鍵那一格：可以改的是一顆按鈕，固定的是灰字＋ⓘ，一眼分得出哪些能動。 */
.shortcut-key {
  min-width: 7em;
  font-variant-numeric: tabular-nums;
}

/* 正在等新鍵：加深底色、字變粗。
   不用橘色框——tokens.css 寫明橘色只有主要按鈕填色與「你在這裡」的小圓點兩處，
   不能當外框或選中狀態（B3a 凍結，§10.7）。按鈕這時本來就有焦點，焦點環已經在指它。 */
.shortcut-key.is-listening {
  background: var(--fill-strong);
  font-weight: var(--fw-semibold);
}

.shortcut-fixed {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* 自己改過的那幾列：左緣一條細線（同理不用橘色；底色留給「選中」）。
   那一列本來就多一顆「回到預設」，這條線只是讓一眼掃得出來改過幾個。 */
.shortcut-table tr.is-changed > th {
  box-shadow: inset 2px 0 0 var(--hairline-strong);
}

.shortcut-actions {
  text-align: right;
  white-space: nowrap;
}

/* 觸控（平板）：按鍵那一顆要按得到（戴手套也要）。 */
@media (pointer: coarse) {
  .shortcut-key,
  .shortcut-actions .btn-plain {
    min-height: var(--h-control);
  }
}

/* The backup line stays quiet while all is well and turns red when it is
   not -- it is on every screen precisely so nobody has to go looking. */
.backup-status {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.backup-status.is-warning {
  color: var(--danger);
  font-weight: 600;
}

/* 資料鑰匙鎖住（每個人）與管理者的精簡備份狀態：釘在「已連線」上方、不收進提醒，
   不展開、不捲動就看得到（合併後反方 PM C3、W2）。字太長時換行，不撐出橫向捲軸。 */
.sidebar-lock,
.sidebar-backup {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  overflow-wrap: anywhere;
}

.sidebar-backup a {
  color: var(--ink-2);
}

.sidebar-lock,
.sidebar-lock a,
.sidebar-backup.is-warning a {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

/* 平板直放（899px 以下）側欄收在「更多」裡：鎖住時底部導覽列上緣轉紅、「更多」上
   標「鑰匙鎖住」，不用打開「更多」就知道（合併後 3 視口複驗 C3）。 */
body.keyring-locked .tabbar {
  border-top: 3px solid var(--danger);
}

body.keyring-locked .tabbar .tab[aria-controls="app-sidebar"] {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

body.keyring-locked .tabbar .tab[aria-controls="app-sidebar"]::after {
  content: '鑰匙鎖住';
  font-size: var(--fs-footnote);
}

/* 錯誤訊息旁的「前往解鎖」（js/unlock-link.js）。 */
.lock-unlock {
  display: inline-flex;
  align-items: center;
  margin-left: var(--sp-2);
  white-space: nowrap;
}

/* 平板（1024 橫放、768 直放）與觸控螢幕：這幾個連結與按鈕要點得到（至少 44×44）。
   不只看 pointer: coarse——有些平板瀏覽器與桌機的觸控模擬回報的是 fine，所以寬度
   1366 以下一律放大（合併後 3 視口複驗 N2）。 */
@media (pointer: coarse), (max-width: 1366px) {
  .sidebar-lock a,
  .sidebar-backup a,
  #recovery-unprinted a,
  .lock-unlock,
  #view-data-protection a[href="#/accounts"] {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    min-width: 44px;
  }

  .sidebar-backup a {
    width: 100%;
  }

  .sidebar-alerts-toggle {
    min-height: 44px;
  }
}

/* 資料保護頁就地改密碼的小表單：寬的螢幕兩欄並排，平板直放一欄，不出現橫向捲軸。 */
.dp-inline-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: var(--sp-2) var(--sp-4);
  align-items: end;
  margin-top: var(--sp-2);
}

.dp-inline-form .form-actions {
  grid-column: 1 / -1;
}

/* A release waiting, or an update that did not take: a line under the
   backup's, there only when there is something to say. */
.update-status {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-footnote);
}

.update-status a {
  color: var(--ink-2);
  font-weight: var(--fw-semibold);
}

.update-status.is-warning a {
  color: var(--danger);
}

/* 更新後第一位登入的人看到的那一行：一行字＋×，不搶注意力（使用者介面極簡）。 */
.update-notice {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-tag);
  background: var(--ok-wash);
  color: var(--ok-ink);
  font-weight: var(--fw-semibold);
}

.update-notice[hidden] {
  display: none;
}

.update-notice > span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.update-notice-close {
  border: 0;
  background: none;
  color: inherit;
  font-size: 1.25rem;
  line-height: 1;
  min-width: 32px;
  min-height: 32px;
  cursor: pointer;
}

/* 自動更新時段：開始、到、結束排一列。 */
.update-window {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

.update-window [hidden] {
  display: none;
}

/* The doctor's card: like the backup line, quiet while signing keeps up and
   red when a record is near its 24 hours or waits for someone. */
.esign-status {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.esign-status.is-warning {
  color: var(--danger);
  font-weight: 600;
}

/* 側欄只有 220px：PIN 欄位與按鈕並排時欄位會被擠到只剩一個字寬，改成上下兩行。 */
.esign-unlock {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-3);
}

/* It sets display (and so does every form), so [hidden] needs saying:
   otherwise the field stays after the card is ready. */
.esign-unlock[hidden] {
  display: none;
}

.esign-unlock input {
  flex: 1 1 100%;
  min-width: 0;
}

/* 讀卡機有鍵盤時按鈕上的字（「在讀卡機上輸入 PIN」）比「開始簽章」長一倍，而
   1024×768 的側欄只有 200px：讓它自己佔一整行並允許折行，不然字會被側欄切掉。 */
.esign-unlock input[hidden] ~ button {
  flex: 1 1 100%;
  min-width: 0;
  white-space: normal;
  line-height: 1.3;
}

/* 看診紀錄按「簽章」帶人過來時亮一下（esign-status.js startSigning），眼睛才跟得過來。 */
.esign-unlock.is-calling input {
  animation: esign-calling 1.6s var(--ease-out) 2;
}

/* 讀卡機自己有數字鍵盤時 PIN 欄被收起來（esign-line.js layOutPINEntry），
   亮的那一下就改亮按鈕——不然眼睛被帶到一格看不見的東西上。 */
.esign-unlock.is-calling input[hidden] ~ button {
  animation: esign-calling 1.6s var(--ease-out) 2;
}

@keyframes esign-calling {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  40% { box-shadow: 0 0 0 4px var(--accent); }
}

@media (prefers-reduced-motion: reduce) {
  .esign-unlock.is-calling input,
  .esign-unlock.is-calling input[hidden] ~ button {
    animation: none;
    box-shadow: 0 0 0 3px var(--accent);
  }
}

.update-line {
  margin: 0 0 var(--sp-3);
  font-weight: 600;
}

.update-line.is-error {
  color: var(--danger);
}

.update-line {
  font-size: var(--fs-body);
}

.update-notes {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-radius: 12px;
  background: var(--fill);
  white-space: pre-wrap;
}

/* --- Connection banner ----------------------------------------------------
   Fixed to the top over everything, including the sign-in screen, because
   when the host is unreachable nothing else on the page is true any more. */
.connection-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  padding: var(--sp-3) var(--sp-6);
  background: var(--danger-wash);
  color: var(--danger);
  border-bottom: 1px solid var(--hairline);
  text-align: center;
  font-weight: var(--fw-semibold);
}

.connection-banner.is-ok {
  background: var(--ok-wash);
  color: var(--ok-ink);
}

/* --- Notifications ------------------------------------------------------
   One card per message, the same on a desk and on a phone: the front desk
   may send from either (spec 2026-10-02 notifications §6). */
.notify-intro {
  margin: calc(-1 * var(--sp-2)) 0 var(--sp-5);
}

/* One message per row, the same on a desk and on a phone: the front desk
   may send from either (spec 2026-10-02 notifications §6). A list row, not
   a card in a card. */
.notify-list {
  display: grid;
}

.notify-card {
  position: relative;
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-4) 0;
}

.notify-card + .notify-card::before,
.handled-row + .handled-row::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-top: 1px solid var(--hairline);
}

.notify-who,
.notify-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
}

.notify-who strong {
  font-weight: var(--fw-semibold);
}

.notify-meta {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.notify-message {
  margin: 0;
  padding: var(--sp-4) var(--sp-5);
  background: var(--fill);
  border-radius: 12px;
  white-space: pre-wrap;
  user-select: text;
}

.notify-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.notify-hint {
  margin: 0;
  color: var(--danger);
  font-size: var(--fs-footnote);
}

.notify-hint:empty {
  display: none;
}

.handled summary {
  cursor: pointer;
  font-weight: var(--fw-semibold);
}

.handled-row {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  min-height: var(--h-row);
  padding: var(--sp-2) 0;
}

.handled-row:first-child {
  margin-top: var(--sp-3);
}

.span-all {
  grid-column: 1 / -1;
}

.template-preview {
  margin: calc(-1 * var(--sp-2)) 0 0;
  color: var(--ink-3);
  font-size: var(--fs-footnote);
}

.panel-check {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-2) 0;
}

/* The hidden box clipboard.js copies from. Off screen rather than
   display:none, which some browsers refuse to select text in; 16px so iOS
   does not zoom the page when it is focused. */
.copy-buffer {
  position: fixed;
  top: 0;
  left: -9999px;
  font-size: 16px;
}

/* --- LINE assistant: the binding code on the patient page ---------------- */

/* 病患要照著在 LINE 打出來的號碼：用 B3a 的襯線數字（--font-display），
   跟叫號、時間這類「這一行的重點就是數字」的寫法一致。 */
.line-code {
  margin: 0;
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-size: var(--fs-title1);
  letter-spacing: 0.2em;
}

/* --- iPhone home-screen app ---------------------------------------------
   viewport-fit=cover (index.html) lets the page reach the screen's edges:
   the home bar at the bottom always, and in landscape the notch and the
   rounded corners at the sides. env(safe-area-inset-*) is how much of each
   edge they cover; it is 0 everywhere else, so these rules change nothing
   in a normal browser. In portrait the status bar is not see-through
   (status-bar-style: default), so the top inset is 0 there; the top rules
   are for devices and modes where it is not. */
.connection-banner {
  padding-top: calc(var(--sp-3) + env(safe-area-inset-top));
}

/* An iPhone on its side wider than 899px gets the desktop layout, with
   the notch over the sidebar on one side. */
.app {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

/* Tablet: a narrower menu and gutters; two lists to a row.
   直立的平板（768～834px）走下面 899px 的手機版面：側欄收成底部「更多」。
   之前斷點在 767px，768 寬的平板仍有 200px 側欄，看診紀錄的表格被擠到要
   橫向捲動、右半被切掉（DEMO 彩排 R32）。 */
@media (max-width: 1279px) {
  :root {
    --sidebar-width: 200px;
  }

  .content {
    padding-left: var(--sp-7);
    padding-right: var(--sp-7);
  }

  .card {
    padding: var(--sp-6);
  }

  /* 換行本身已經是基本規則（.queue-lists），這裡只調每一欄想要多寬：
     候診自己一整列，過號與待收費並排。兩者都還是會被 --queue-col-min 擋住——
     算出來不到下限就不並排，改成各自一整列。 */
  .queue-column {
    flex-basis: calc(50% - var(--sp-6) / 2);
  }

  .queue-column:first-child {
    flex-basis: 100%;
  }

  /* Half a row is too narrow to indent the buttons under the name. */
  .queue-column + .queue-column .queue-actions {
    padding-left: 0;
  }

  .queue-column + .queue-column .queue-note {
    margin-left: 0;
  }
}

@media (max-width: 899px) {
  /* 底部分頁列的高度：.tab 的 min-height 就用它，要停在它上面的東西也用這個變數（改一處兩邊一起變）。 */
  :root {
    --tabbar-h: 64px;
  }

  .app,
  [data-nav="left"] .app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 1fr auto;
    /* The phone layout pads its own bars and content instead. */
    padding-left: 0;
    padding-right: 0;
  }

  .tabbar {
    position: sticky;
    bottom: 0;
    z-index: 20;
    display: grid;
    grid-template-columns: repeat(var(--tab-count, 4), 1fr);
    background: color-mix(in srgb, var(--canvas) 85%, transparent);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  }

  .tab {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: var(--tabbar-h);
    padding: 0;
    border-radius: 0;
    background: none;
    color: var(--ink-3);
    font-size: var(--fs-caption);
    font-weight: var(--fw-regular);
    text-decoration: none;
  }

  /* 焦點框往內收、帶圓角：分頁貼著螢幕底邊與彼此，外擴的方框會被切掉（按 Esc 關「更多」後最明顯）。 */
  .tab:focus-visible {
    outline-offset: -4px;
    border-radius: 12px;
  }

  /* 手機的選單是底部面板，沒有左右可換。 */
  .nav-side-toggle {
    display: none;
  }

  .tab:hover,
  .tab:active:not(:disabled) {
    background: none;
    color: var(--ink);
  }

  .tab .nav-icon {
    width: 24px;
    height: 24px;
  }

  /* The tab you are on: ink, weight, and the dot above -- the icon stays
     ink, never orange. */
  .tab.is-current {
    color: var(--ink);
    font-weight: var(--fw-semibold);
  }

  .tab.is-current .nav-icon {
    stroke-width: 1.75;
  }

  .tab.is-current::before {
    content: '';
    position: absolute;
    top: 6px;
    left: 50%;
    width: var(--indicator-dot);
    height: var(--indicator-dot);
    margin-left: calc(var(--indicator-dot) / -2);
    border-radius: 50%;
    background: var(--signature);
  }

  /* 更多: the whole menu slides up from the bottom. Opening is the one
     movement on the phone that animates -- a few times a day, and it says
     where the menu came from. A transition, not keyframes, so closing half
     way reverses from where it is. */
  .sidebar {
    position: fixed;
    inset: auto 0 0 0;
    width: auto;
    height: auto;
    max-height: 85vh;
    z-index: 30;
    border-radius: var(--r-tile) var(--r-tile) 0 0;
    /* 底距由黏底的 .sidebar-footer 自己帶，這裡只補 iPhone 的 home bar。 */
    padding: var(--sp-6) var(--sp-3) env(safe-area-inset-bottom);
    transform: translateY(100%);
    opacity: 0;
    visibility: hidden;
    transition:
      transform var(--dur-micro) var(--ease-out),
      opacity var(--dur-micro) var(--ease-out),
      visibility 0s linear var(--dur-micro);
  }

  body.nav-open .sidebar {
    transform: none;
    opacity: 1;
    visibility: visible;
    transition:
      transform var(--dur-sheet) var(--ease-out),
      opacity var(--dur-sheet) var(--ease-out),
      visibility 0s;
  }

  /* Dims the page behind the open sheet and catches the click that closes
     it, so that click cannot land on whatever is underneath. */
  body::after {
    content: '';
    position: fixed;
    inset: 0;
    z-index: 29;
    background: var(--scrim);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-micro) var(--ease-micro);
  }

  body.nav-open::after {
    opacity: 1;
    pointer-events: auto;
  }

  .brand {
    padding-bottom: var(--sp-4);
  }

  .content {
    padding: calc(var(--sp-6) + env(safe-area-inset-top))
      calc(var(--sp-5) + env(safe-area-inset-right))
      var(--sp-7)
      calc(var(--sp-5) + env(safe-area-inset-left));
  }

  section > h2.section-title {
    font-size: var(--fs-title1);
    margin-bottom: var(--sp-5);
  }

  .title-aside {
    font-size: var(--fs-title3);
    margin-left: var(--sp-2);
  }

  form {
    grid-template-columns: minmax(0, 1fr);
  }

  .card {
    padding: var(--sp-5);
    overflow-x: auto;
  }

  .queue-list.card {
    padding: var(--sp-1) 0;
  }

  /* 手機上頂列整條收起：兩行小字擠掉標題上方的空間，而登入者與登出已移到
     「更多」選單底部的 .sidebar-account（設計審查第 1 輪 #12）。
     Two classes: the topbar's own rule comes later in this file. */
  .content .topbar {
    display: none;
  }

  .sidebar-account {
    display: flex;
  }

  /* 約診的日期列：‹ 日期 › 三個綁在第一行，日期選擇器自己一行，其餘按鈕在下。 */
  #view-appointments .day-toolbar > #appt-prev { order: 1; }
  #view-appointments .day-toolbar > #appt-date { order: 2; }
  #view-appointments .day-toolbar > #appt-next { order: 3; }

  #view-appointments .day-toolbar > .day-pick {
    order: 4;
    flex: 1 1 100%;
  }

  #view-appointments .day-toolbar > :nth-child(n + 5) {
    order: 5;
  }

  .queue-chairs {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-4);
    margin-bottom: var(--sp-7);
  }

  .queue-chair {
    min-height: 0;
    padding: var(--sp-6);
  }

  .queue-column {
    flex-basis: 100%;
  }

  .queue-list .queue-actions {
    padding-left: 0;
  }

  .queue-list .queue-note {
    margin-left: 0;
  }

  /* The board on the doctor's phone: buttons on their own line, thumb-sized. */
  .queue-row .queue-actions {
    width: 100%;
  }

  .queue-actions button,
  .queue-list .queue-actions button {
    flex: 1 1 0;
    min-width: 0;
    min-height: var(--h-control);
    padding: 0 var(--sp-2);
  }

  /* Thumb-sized: three buttons sharing the width of a phone. */
  .notify-actions button {
    flex: 1;
    min-height: var(--h-control);
  }

  .appt-layout.has-panel {
    display: block;
  }

  /* On a phone the panel is the whole screen, not a column beside the day.
     底部分頁列（.tabbar，高 --tabbar-h）留在外面看得到、按得到：換頁不必先按 ✕ 關面板（smooth-ops A13）。 */
  .side-panel {
    position: fixed;
    inset: 0 0 calc(var(--tabbar-h) + env(safe-area-inset-bottom));
    z-index: 25;
    overflow-y: auto;
    border-radius: 0;
    padding-top: calc(var(--sp-7) + env(safe-area-inset-top));
    padding-bottom: var(--sp-4);
  }

  /* It follows the day's card in the markup, so .card + .card would push
     it down and let the page show through above it. Two classes, to
     outrank that rule. */
  .side-panel.card {
    margin: 0;
  }

  /* Finding a slot can list two weeks of times. The button that books the
     chosen one stays at the bottom of the screen instead of at the end of
     that list, clear of the iPhone's home bar. */
  .booking-foot {
    position: sticky;
    bottom: 0;
    background: var(--group);
    box-shadow: 0 -1px 0 var(--hairline);
  }

  .booking-confirm {
    width: 100%;
    min-height: var(--h-control);
  }
}

/* --- 每月例行申報 -------------------------------------------------------- */

/* Filing lines in the sidebar: there only while something is due. */
.filing-reminder {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-footnote);
}

.filing-reminder a {
  color: var(--ink-2);
  font-weight: var(--fw-semibold);
}

.filing-reminder.is-warning a {
  color: var(--danger);
}

.filing-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-3);
}

.filing-head h2 {
  margin: 0;
}

.filing-split {
  margin-left: auto;
}

/* A period's state is its pill (filings.js); the card itself stays plain
   -- no side stripe. */

.filing-part {
  font-size: var(--fs-body);
  margin: var(--sp-4) 0 var(--sp-2);
}

.filing-steps {
  margin: 0;
  padding-left: var(--sp-6);
}

.filing-steps li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-2) 0;
}

.filing-steps li.is-done .filing-step-label {
  color: var(--ok-ink);
  font-weight: 600;
}

/* 下一步目前的狀態（「還沒申報」）：一般文字加淡底，不是按鈕的樣子。 */
.filing-step-state {
  padding: 2px var(--sp-2);
  border-radius: var(--r-tag);
  background: var(--warn-wash);
  color: var(--warn);
  font-size: var(--fs-footnote);
}

.filing-steps li.is-later {
  color: var(--ink-3);
}

.filing-done {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  overflow-wrap: anywhere;
}

.filing-next {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}

.filing-undo {
  padding: var(--sp-1) var(--sp-3);
}

.filing-zero {
  font-size: var(--fs-title2);
}

.filing-message {
  margin: var(--sp-3) 0 0;
  color: var(--danger);
  font-weight: 600;
}

.filing-settings {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* --- Who is signed in ------------------------------------------------------
   Top right of every screen: the name, and switching or signing out. The
   shared-account line sits to its left only while the shared account is
   the one in use.
   跟頁標題同一行：原本自己佔一整行，加上上方留白，內容被往下推了一大截
   （使用者回報「留白太多」，2026-10-23）。用 float 而不是改每一頁的 HTML：
   .topbar 在 DOM 裡排在所有頁面之前，浮到右邊後，各頁的 h2 自然在它左邊排；
   標題太長放不下時 h2 會自動掉到下一行，不會壓在一起。DOM 順序沒變，Tab
   的順序仍是選單 → 登入者／登出 → 頁面內容。
   margin-top 把 44px 高的文字按鈕跟 34px 襯線標題（行高 1.15）的中線對齊。 */
.topbar {
  float: right;
  max-width: 100%;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0 var(--sp-4);
  margin: calc((var(--fs-large) * 1.15 - var(--h-control)) / 2) 0 0 var(--sp-6);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.topbar .btn-plain {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.topbar-notice {
  margin-right: auto;
  margin-left: 0;
}

.auth-message.is-ok {
  color: var(--ok-ink);
}

/* The names on a clinic computer's sign-in screen. */
.auth-switch {
  margin-top: var(--sp-5);
  padding-bottom: var(--sp-5);
  border-bottom: 1px solid var(--hairline);
}

.auth-switch-names {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* --- 操作紀錄 ------------------------------------------------------------ */
.audit-table td:first-child,
.audit-table td:last-child {
  white-space: nowrap;
}

.chip-clear {
  background: none;
  border: 0;
  padding: 0 0 0 var(--sp-1);
  color: inherit;
}

details.card > summary {
  cursor: pointer;
  font-weight: 600;
}

/* --- Calling hardware (叫號設備) ----------------------------------------
   One form across several cards, so a save or a test call takes every
   device's settings at once. */

/* 卡片跟其他設定頁一樣佔滿內容寬：表單預設的 720px 上限讓這頁的卡片只有半邊寬
   （同健保讀卡，彩排 2 其他 #13）。欄位格線維持原本的寬度，不被拉長。 */
.call-outputs-form {
  max-width: none;
}

.call-outputs-form .card {
  margin: 0;
}

.call-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 330px));
  gap: var(--sp-4) 1.25rem;
}

.call-grid input[name$='template'] {
  font-family: var(--font-mono, monospace);
}

.call-outputs-result:empty {
  display: none;
}

.call-outputs-result.is-error,
.call-clips .is-error,
.call-outputs-status .is-error {
  color: var(--danger);
}

.call-outputs-status {
  margin: 0;
  padding-left: 1.25rem;
}

/* The board's line saying a display did not get the last call: a notice,
   not an error -- the call itself went through. */
.queue-devices {
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-footnote);
  color: var(--danger);
}

.queue-voice {
  font-size: var(--fs-footnote);
}

@media (max-width: 640px) {
  .call-grid {
    grid-template-columns: 1fr;
  }
}

/* 健保抽審（spec 2026-10-22-nhi-sample-review-design）。名單表格欄位多，
   在窄螢幕上橫向捲動（.sr-scroll），不擠壓輸入框。頁寬跟其他頁一樣受 --content-max
   限制：原本放寬成 none，寬螢幕上比別頁寬 21px，切頁時標題與卡片右緣會跳（設計審查 D11）。 */

.sr-head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

.sr-paste {
  width: 100%;
  margin: var(--sp-2) 0;
  font: inherit;
}

/* 三張表都包在 .sr-scroll 裡：放不下時在卡片內橫向捲動，而不是把中文欄位
   擠成一欄一個字（同 .items-scroll 的做法）。 */
.sr-scroll {
  overflow-x: auto;
}

/* 抽審清單：每格都短，一律不換行；手機上整張表在卡片內捲動。 */
.sr-list-table td {
  white-space: nowrap;
}

/* 一批的明細：八欄，1280 寬的桌機要整列放得下。格子左右內距收窄一級，
   列尾的按鈕排成一行、各縮一級內距；只有「病患」一欄可以換行。 */
.sr-rows-table th,
.sr-rows-table td {
  padding-left: var(--sp-3);
  padding-right: var(--sp-3);
}

.sr-rows-table td > .action-group {
  flex-wrap: nowrap;
  justify-content: flex-end;
}

.sr-rows-table td .btn-secondary,
.sr-rows-table td .btn-danger,
.sr-rows-table td .btn-primary,
.sr-rows-table td .btn-link {
  padding: 0 var(--sp-3);
  white-space: nowrap;
}

/* 「印這位」是連結做的按鈕，components.css 的列內小一號規則只管 button。 */
.sr-rows-table td .btn-link {
  min-height: var(--h-control-sm);
  font-size: var(--fs-footnote);
}

/* 病患欄是唯一會換行的欄位；給它一個下限，手機上不會被擠成一欄一個字，
   表格改為在卡片內捲動。 */
.sr-who {
  min-width: 12em;
}

/* 候選病患的按鈕字很長（「是 陳範例（N000003，79/11/20）」），在這一欄裡
   可以折成兩行；找病患的輸入框跟著欄寬縮。 */
.sr-rows-table .sr-who .btn-secondary {
  white-space: normal;
  text-align: left;
}

.sr-who .sr-assign input {
  flex: 1 1 8em;
  min-width: 0;
  width: 8em;
}

.sr-matched {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
}

/* 病患名字佔滿剩下的寬度，「取消對應」因此每列都對齊在欄位右緣，不隨名字長短
   左右錯開（彩排 2 其他 #12）。 */
.sr-matched > span {
  flex: 1 1 auto;
}

/* 沒對上的原因：接在那一列底下、橫跨整張表的第二行，與上一行之間不畫線。
   td:first-child 預設不換行（base.css），這裡要讓整句正常折行。 */
.sr-rows-table tr.has-note td {
  border-bottom: none;
}

.sr-rows-table tr.sr-note td {
  white-space: normal;
  height: auto;
  padding-top: 0;
}

/* 手機上明細表在卡片內橫向捲動，這一行卻橫跨整張表（八百多像素），原因會
   有一半在畫面外。字黏在捲動框左緣、寬度限在看得到的範圍（內容區與卡片
   各留兩側 --sp-5，見下方 767px 區塊），捲到哪都讀得完整句。 */
@media (max-width: 899px) {
  .sr-rows-table tr.sr-note td > div {
    position: sticky;
    left: 0;
    max-width: calc(100vw - 4 * var(--sp-5) - 2 * var(--sp-3));
  }
}

/* 確認名單的疑點與原文欄給下限，手機上捲動而不擠字。疑點欄沒有疑點時是空的，
   不給下限；姓名、流水號輸入框收窄：之前八欄加起來比 1536 寬的桌機還寬，
  「讀到的原文」被切在捲動框外（彩排 2 其他 #12）。 */
.sr-draft-table td.is-error,
.sr-draft-table td.sr-raw {
  min-width: 10em;
}

.sr-input {
  width: 9em;
}

.sr-input.sr-name,
.sr-input.sr-serial_no {
  width: 6em;
}

.sr-input.sr-case_type {
  width: 4em;
}

.sr-nowrap {
  white-space: nowrap;
}

.sr-raw {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

#view-sample-review td.is-error,
.sr-problems {
  color: var(--danger);
  font-size: var(--fs-footnote);
}

.sr-skipped {
  margin: var(--sp-2) 0;
  font-size: var(--fs-footnote);
}

.sr-edit,
.sr-assign {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--sp-2);
}

.sr-assign {
  margin-top: var(--sp-1);
}

.call-rooms input {
  width: 100%;
  font-family: var(--font-mono, monospace);
}

.call-clip-actions {
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

/* --- 遠端協助 ------------------------------------------------------------
   第一張卡以說明段落開頭：段落預設的上緣會讓這張卡比下面以標題開頭的卡
   多出約 17px 的上空白，收掉讓三張卡頂端一致。 */
.remote-support-intro > p:first-child {
  margin-top: 0;
}

.remote-reason {
  max-width: 28rem;
}

/* 手機（窄於 640px）：紀錄四欄塞在 335px 的卡裡，「誰」只剩 49px、帳號名
   一字一行（一列 300px 高），裝置欄被裁掉。改成一筆一張卡：時間當標題，
   誰、事由、裝置各一行，欄名從 td 的 data-label 來。 */
@media (max-width: 640px) {
  .remote-log-table thead {
    display: none;
  }

  .remote-log-table,
  .remote-log-table tbody {
    display: block;
  }

  .remote-log-table tr {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    padding: var(--sp-3);
    border-bottom: 1px solid var(--hairline);
  }

  .remote-log-table tbody tr:last-child {
    border-bottom: none;
  }

  /* base.css 的 td 固定 56px 高；卡片裡每一行要跟內容長。 */
  .remote-log-table td {
    display: flex;
    gap: var(--sp-3);
    height: auto;
    padding: 0;
    border: 0;
    white-space: normal;
  }

  .remote-log-table td:first-child {
    font-weight: var(--fw-semibold);
  }

  .remote-log-table td:not(:first-child)[data-label]::before {
    content: attr(data-label);
    flex: 0 0 3rem;
    color: var(--ink-3);
  }

  .remote-log-table tr.empty-row td,
  .remote-log-table tr.skeleton-row td {
    display: block;
  }
}

/* "Nothing to pay this month": a quiet second choice under the steps, and
   once ticked a single line in their place. Indented to line up with the
   step list (.filing-steps). */
.filing-nothing-button {
  margin: var(--sp-3) 0 0 var(--sp-6);
}

.filing-nothing {
  margin: 0;
  padding-left: var(--sp-6);
}

.filing-nothing .filing-step-label {
  color: var(--ok-ink);
  font-weight: var(--fw-semibold);
}

/* --- 回診追蹤 ------------------------------------------------------------
   一張卡：頂端一條工具列（分段切換＋下拉，標籤在下拉左邊）、一行數字與期間＋
   匯出列印，接著就是名單（原本篩選卡、期間、數字各佔一段，名單從 y≈567 才開始）。
   沒有側邊色條；狀態只用既有的膠囊。間距與色彩只用 tokens.css 的 B3a token。 */

.recall-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}

/* 標籤放在下拉左邊，整條工具列只有一個控制項高。 */
.recall-toolbar .field {
  flex-direction: row;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: 0;
  white-space: nowrap;
}

.recall-custom {
  display: flex;
  gap: var(--sp-3);
}

.recall-custom[hidden] {
  display: none;
}

.recall-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
  margin: var(--sp-3) 0 var(--sp-2);
}

.recall-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
}

.recall-stat {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-1);
  font-size: var(--fs-footnote);
}

.recall-stat strong {
  font-size: var(--fs-title3);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.recall-range {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

/* 期間在畫面上是 ⓘ 提示（smooth-ops A14），只有列印時印出來。 */
@media screen {
  .recall-range {
    display: none;
  }
}

.recall-tools {
  margin-left: auto;
}

/* 每一位一行、列高 ≤ 56px：姓名＋生日、原因＋說明、日期＋醫師、膠囊＋日期都在同一行。 */
.recall-table td,
.recall-table th {
  /* 8px（原 12px）：1536×864 內容區 1072px 時七欄比卡片寬 12px，冒出橫捲軸（smooth-ops A12 稽核）。 */
  padding-inline: var(--sp-2);
}

.recall-table td {
  padding-block: var(--sp-2);
  white-space: nowrap;
}

.recall-inline {
  margin-left: var(--sp-2);
}

/* 原因說明最長（「全口牙周治療，還有 2 步」），放不下時以「…」收尾，全文在 title。 */
.recall-oneline {
  max-width: 10rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recall-note-text {
  display: inline-block;
  max-width: 8rem;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* 約診、記聯絡結果並排的小按鈕（原本上下疊，列高 113px）。 */
.recall-table td .action-group {
  flex-wrap: nowrap;
}

.recall-table td button {
  min-height: 36px;
  padding: 0 var(--sp-2);
  font-size: var(--fs-footnote);
}

.recall-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}

.recall-note input {
  flex: 1 1 16rem;
  min-width: 0;
}

.recall-note .action-group {
  flex-wrap: wrap;
}

/* 平板與小筆電（視窗窄於 1500px，內容區不到 1072px）：一行放不下七欄，每格改成上下兩小行（姓名／生日、
   原因／說明、日期／醫師、膠囊／日期），行距收緊，列高仍在 56px 內；按鈕照樣並排。
   上次就診（含醫師）跟手機一樣不顯示，才不用左右捲找按鈕。 */
@media (max-width: 1499px) and (min-width: 641px) {
  .recall-table td,
  .recall-table th {
    padding-inline: var(--sp-2);
  }

  .recall-table td {
    padding-block: var(--sp-1);
    line-height: 1.3;
    font-size: var(--fs-footnote);
  }

  .recall-table .recall-inline {
    display: block;
    margin-left: 0;
  }

  .recall-oneline {
    max-width: 7rem;
  }

  .recall-table td button {
    padding: 0 6px;
  }

  .recall-oneline > span {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .recall-table th:nth-child(4),
  .recall-table td:nth-child(4) {
    display: none;
  }
}

/* 手機（窄於 640px）：表格橫捲在手機上找不到右邊的按鈕，改成一位一張卡：
   病患、原因、應回診、狀態、電話、操作。上次就診（含醫師）在手機上不顯示。 */
@media (max-width: 640px) {
  /* 兩個日期並排會撐破 350px 的篩選卡（卡片自己冒出橫捲軸），手機上改直排、各佔滿一行。 */
  .recall-custom {
    flex-direction: column;
    width: 100%;
  }

  .recall-custom .field,
  .recall-custom input {
    width: 100%;
    min-width: 0;
  }

  .recall-table thead {
    display: none;
  }

  .recall-table,
  .recall-table tbody {
    display: block;
  }

  .recall-table tr {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-3) 0;
    border-bottom: 1px solid var(--hairline);
  }

  /* base.css 的 td 固定 56px 高（--h-row）；變成卡片的一行後要跟內容長，
     不然膠囊被拉成 56px 高、展開的備註列按鈕溢到下一張卡。 */
  .recall-table td {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    height: auto;
    padding: 0;
    border: 0;
    white-space: normal;
  }

  /* 病患那格自己就是標題，不必再寫「病患」：選擇器排除第一格，
     不靠後面再蓋掉（權重比不過這條）。 */
  .recall-table td:not(:first-child)[data-label]:not([data-label=""])::before {
    content: attr(data-label);
    flex: 0 0 4.5rem;
    color: var(--ink-3);
  }

  .recall-table td:nth-child(1) { order: 1; font-weight: var(--fw-semibold); }
  .recall-table td:nth-child(3) { order: 2; }
  .recall-table td:nth-child(2) { order: 3; }
  .recall-table td:nth-child(6) { order: 4; }
  .recall-table td:nth-child(5) { order: 5; }
  .recall-table td:nth-child(7) { order: 6; }

  .recall-table td:nth-child(4) {
    display: none;
  }

  /* 狀態的備註留在膠囊右邊、長了在那一欄裡折行，不掉回卡片最左邊。 */
  .recall-table td:nth-child(6) {
    flex-wrap: nowrap;
    min-width: 0;
  }

  .recall-table td:nth-child(6) > div {
    flex: 1 1 auto;
    min-width: 0;
  }

  .recall-table tr.empty-row td,
  .recall-table tr.recall-note-row td {
    display: block;
  }
}

/* --- 健保取號（報到頁，nhi-card.js）與健保讀卡設定 ---------------------
   一位報到的病患一列，沿用叫號清單的列（.queue-row）；按鈕的結果（成功的
   提醒、失敗的說明、PIN、異常就醫序號）收在那一列底下的 .nhi-panel，不跳視窗。 */

.nhi-status {
  margin: 0 0 var(--sp-3);
}

/* 第二行是給櫃台讀的說明（上次為什麼沒取到），不截斷。 */
.nhi-row .queue-who .muted {
  white-space: normal;
}

.nhi-sim-note {
  margin-bottom: var(--sp-4);
}

.nhi-panel,
.nhi-row > .nhi-problem,
.nhi-row > .nhi-attention {
  flex-basis: 100%;
  margin-left: calc(60px + var(--sp-4));
}

/* 已取號、還不能上傳的提醒：還沒到期是淡色說明，快到期或逾期再加上 .nhi-problem 變紅。 */
.nhi-attention {
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--warn-wash);
  color: var(--warn);
}

.nhi-attention.nhi-problem {
  background: var(--danger-wash);
  color: var(--danger);
}

.nhi-attention p {
  margin: 0;
}

.nhi-attention p + p {
  margin-top: var(--sp-1);
}

/* 局部錯誤（B3a 三態）：第一句發生什麼事，第二句怎麼辦。 */
.nhi-problem {
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--danger-wash);
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.nhi-problem p,
.nhi-ok p {
  margin: 0;
}

.nhi-problem p + p,
.nhi-ok p + p {
  margin-top: var(--sp-1);
}

.nhi-problem .nhi-code {
  font-weight: var(--fw-regular);
  font-size: var(--fs-footnote);
}

.nhi-ok {
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--ok-wash);
  color: var(--ok-ink);
  font-weight: var(--fw-semibold);
}

.nhi-ok .nhi-note {
  font-weight: var(--fw-regular);
}

/* 待報備的異常就醫（spec 2026-10-23-nhi-gateway-design §10.3）：一筆一行，按鈕靠右。 */
.nhi-reports {
  margin-top: var(--sp-4);
}

.nhi-report-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.nhi-report {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
}

/* 定稿時健保卡沒寫成的說明，在看診表格上方。 */
.visits-nhi-write {
  margin-bottom: var(--sp-3);
}

.visits-nhi-write button {
  margin-top: var(--sp-2);
}

.nhi-pin,
.nhi-abnormal {
  margin-top: var(--sp-3);
}

.nhi-pin > p,
.nhi-abnormal > p {
  grid-column: 1 / -1;
  margin: 0;
}

.nhi-panel-actions {
  margin-top: var(--sp-3);
}

/* 卡片跟同頁的「讀卡機狀態」一樣寬（表單預設 720px 上限會讓這張卡只佔半邊，
   右邊空一大塊，彩排 2 其他 #13）；欄位本身維持原本的寬度，不被拉長。 */
.nhigw-form {
  max-width: none;
  grid-template-columns: repeat(2, minmax(0, 348px));
}

.nhigw-wide,
.nhigw-form > h2 {
  grid-column: 1 / -1;
}

/* 選項文字很長（代碼的官方原文）：欄位跟著格線縮，不把卡片撐破。 */
.nhigw-form label,
.nhigw-form select {
  min-width: 0;
  max-width: 100%;
}

p.nhigw-wide {
  margin: 0;
}

/* 模擬器的欄位跟上面的設定排在同一個兩欄格線裡。 */
.nhigw-sim {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 348px));
  gap: var(--sp-5) var(--sp-6);
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.nhigw-sim[hidden] {
  display: none;
}

.nhigw-sim legend {
  grid-column: 1 / -1;
  margin-bottom: var(--sp-3);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.nhigw-sim > p {
  grid-column: 1 / -1;
  margin: 0;
}

.nhigw-force {
  display: contents;
}

.nhigw-force[hidden] {
  display: none;
}

@media (max-width: 899px) {
  .nhi-panel,
  .nhi-row > .nhi-problem,
  .nhi-row > .nhi-attention,
  .queue-list.nhi-list .queue-actions {
    margin-left: 0;
    padding-left: 0;
  }

  .nhi-pin,
  .nhi-abnormal,
  .nhigw-form,
  .nhigw-sim {
    grid-template-columns: 1fr;
  }
}

/* --- 寬表格：在卡片內左右捲動（收費、收費統計每日、健保申報當月案件） ------
   這幾張表原本沒有包捲動框，1280 寬時跑到側欄底下、整頁可以左右捲；平板上中文欄
   被壓成一欄一個字（QA D40、D43、D46）。照 .items-scroll／.sr-scroll 的做法：
   表頭、數字、日期不換行，唯一允許換行的文字欄給最小寬度，放不下就在卡片內捲。 */
.wide-table th,
.wide-table td.numeric,
.wide-table td:first-child {
  white-space: nowrap;
}

.charges-table td:nth-child(2) {
  min-width: 12em;
}

/* 收費清單那一格動作（修改／列印／刪除）黏在表格的右邊（QA 768×1024：那一格整個落在畫面外，
   只能把表格往旁邊捲才按得到；1024×768 也要捲）。錢的那幾欄照舊可以左右捲——它們是用看的，
   要按的那幾顆則一直在原地：不必捲，也不必為了塞進去而多一層選單（那會讓最常按的「修改」多一下）。
   標題那一格一起黏，不然捲動時欄名與內容對不起來。背景要跟卡片一樣不透明，才蓋得住捲過去的數字。 */
.charges-table thead th:last-child,
.charges-table td.charge-actions {
  position: sticky;
  right: 0;
  z-index: 1;
  background: var(--group);
}

/* 捲到一半時，黏住的那一格左邊要有一條線，看得出它是浮在上面的。 */
.charges-table td.charge-actions::before,
.charges-table thead th:last-child::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: var(--hairline);
}

.daily-table td,
.claims-table td:nth-child(7),
.claims-table td:nth-child(8) {
  white-space: nowrap;
}

/* 加成醫令的說明可以斷行，不把診療點數那一欄撐寬。數字格是 nowrap（上面
   .wide-table td.numeric），不在這裡放開的話這行會被繼承成不換行，整欄撐到 250px，
   1440 寬時狀態與「修改」被擠到卡片外（DEMO 彩排 3）。 */
.claims-table .claim-surcharge {
  min-width: 7em;
  font-size: var(--fs-footnote);
  text-align: left;
  white-space: normal;
}

/* 收費統計每日表 13 欄都是短數字：1440 寬時比卡片寬約 165px，最後的健保點數、
   印花要捲才看得到。格子左右內距收到 8px（欄與欄之間仍有 16px）就放得下。 */
.daily-table th,
.daily-table td {
  padding-left: var(--sp-2);
  padding-right: var(--sp-2);
}

/* 十欄在 1440 寬（內容約 1010px）要放得下：格子左右內距收一級。 */
.claims-table th,
.claims-table td {
  padding-left: var(--sp-3);
  padding-right: var(--sp-3);
}

.claims-table td:nth-child(2) {
  min-width: 7em;
}


/* 遠端協助模擬（-remote-sim，展示用）：代碼要大到隔著櫃台念得清楚；同意框與
   連線中橫幅用既有的提醒與成功色，一眼分得出「等你決定」與「有人連著」。 */
.remote-sim-code {
  margin: var(--sp-2) 0;
  font-size: var(--fs-numeral-lg);
  font-feature-settings: var(--numerals);
  letter-spacing: 0.08em;
}

.remote-sim-ask {
  padding: var(--sp-4);
  border-radius: var(--r-input);
  background: var(--warn-wash);
}

.remote-sim-ask-title {
  margin-top: 0;
  font-size: var(--fs-title3);
  font-weight: var(--fw-semibold);
}

.remote-sim-banner {
  position: sticky;
  top: 0;
  z-index: 5;
  margin: 0 0 var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-input);
  background: var(--ok-wash);
  color: var(--ok-ink);
  font-size: var(--fs-title3);
  font-weight: var(--fw-semibold);
}

/* .form-actions 自己設了 display，會蓋過 [hidden]：沒有連線時「結束連線」也冒出來。 */
.remote-sim [hidden],
.remote-sim-banner[hidden] {
  display: none;
}

/* 資料保護：復原碼列印頁。復原碼要一字不差抄回去，用等寬字、可斷行；
   O 與 0、l 與 1 在等寬字裡比較分得清楚。 */
.recovery-sheet {
  padding: var(--sp-6) 0;
  container-type: inline-size;
}

/* 復原碼永遠一整行、不在中間斷開（合併後 3 視口複驗 N5）：斷在哪裡都可能被抄成兩段、
   或在 age 用的 key.txt 裡多出換行。字級跟著螢幕寬度縮（74 個等寬字在 768 直放的平板上
   也放得下）；真的放不下時橫向捲動這一行，而不是換行。選取複製得到的就是這一行原樣。
   字級用列印頁本身的寬度（cqi）算：74 個等寬字約 45em 寬，2.1cqi 剛好一行放得下。 */
.recovery-code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: min(1.25rem, 2.1cqi);
  letter-spacing: 0;
  white-space: nowrap;
  overflow-x: auto;
  margin: var(--sp-5) 0;
}

/* 資料保護頁：來歷鏈碰到診所共用帳號的管理者標紅，請人確認是認得的人。 */
#dp-admins .is-error {
  color: var(--danger);
}

/* 健保上傳（spec 2026-10-23-nhi-gateway-design §11）：快到期、逾期、重試用完的列標紅。 */
.nhi-upload-table tr.is-warning td {
  color: var(--danger);
}

.nhi-upload-table tr.is-warning {
  background: var(--danger-wash);
}

.nhi-upload-check {
  margin-top: var(--sp-3);
}

/* 切換檢查清單（cutover.js）：每一項一張卡；勾選列不在 <form> 裡，
   所以不吃 form label.check-field 的樣式，這裡自己給夠大的點擊範圍。 */
.cutover-item h2 .pill {
  margin-left: 8px;
  vertical-align: middle;
}
.cutover-ask {
  color: var(--warn);
}
.cutover-mark {
  margin-top: 10px;
}
.cutover-mark label {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
}
.cutover-mark input[type="checkbox"] {
  width: 20px;
  height: 20px;
}
.cutover-by {
  margin: 0 0 0 28px;
}
.cutover-doc-text {
  white-space: pre-wrap;
  line-height: 1.7;
}

/* --- 說明、導覽、練習模式（help.js、tour.js、practice-mode.js） ---------- */

/* 頂列的「說明」：每一頁都在同一個位置，一按就到這一頁的手冊章節。 */
.topbar .topbar-help {
  color: var(--ink);
  font-weight: var(--fw-semibold);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* 說明頁上方一列：練習模式的狀態與按鈕、重看導覽。1536×864 不用捲就看得到，
   手冊放在下面填滿剩下的高度。 */
.help-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.help-actions { display: inline-flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.help-practice-state { font-weight: var(--fw-semibold); }
.help-message { color: var(--danger); }
.help-tour { margin-left: auto; }

.doc-frame {
  display: block;
  width: 100%;
  border: 1px solid var(--hairline);
  border-radius: var(--r-group);
  background: var(--canvas);
}
/* 說明頁：左邊主題清單與搜尋，右邊手冊。1536×864 不用捲就看得到整份主題清單的
   前十幾項；清單自己在框內捲。 */
.help-layout {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: var(--sp-4);
  height: calc(100vh - 170px);
  min-height: 420px;
}
.help-toc {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-height: 0;
}
.help-search { width: 100%; box-sizing: border-box; }
.help-topics { overflow-y: auto; min-height: 0; flex: 1; }
.help-topic-group {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  margin: var(--sp-3) 0 var(--sp-1);
}
.help-topic {
  display: block;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-tag);
  color: var(--ink);
  text-decoration: none;
}
.help-topic:hover, .help-topic:focus-visible { background: var(--fill); }
.help-topic-sub { display: block; font-size: var(--fs-footnote); color: var(--ink-3); }
.help-frame { height: 100%; }
@media (max-width: 899px) {
  .help-layout { grid-template-columns: 1fr; grid-template-rows: auto 1fr; height: auto; }
  .help-topics { max-height: 30vh; }
  .help-frame { height: 70vh; }
}
.cutover-doc-text .doc-frame { height: 70vh; margin-top: var(--sp-3); }

/* 導覽卡：浮在選單旁邊的小卡，不蓋住整個畫面、不搶游標。 */
.tour-card {
  position: fixed;
  z-index: 90;
  width: min(300px, calc(100vw - 16px));
  padding: var(--sp-4);
  background: var(--group);
  color: var(--ink);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-group);
  box-shadow: 0 8px 24px var(--scrim);
}
.tour-card.is-floating { right: var(--sp-6); bottom: var(--sp-7); }
/* 平板、手機：浮在底部列上面，不被它蓋住。 */
@media (max-width: 899px) {
  .tour-card.is-floating { right: var(--sp-2); bottom: var(--sp-9); }
}
.tour-text { margin: 0 0 var(--sp-3); font-size: var(--fs-body); }
.tour-actions { display: flex; align-items: center; gap: var(--sp-3); justify-content: flex-end; }
.tour-count { margin-right: auto; color: var(--ink-3); font-size: var(--fs-footnote); }
.tour-target {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--r-tag);
}

/* 練習模式：頂端一條不會捲走的警示，整個畫面換成暖黃底，一眼分得出不是正式系統。 */
.practice-banner {
  position: sticky;
  top: 0;
  z-index: 95;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-1) var(--sp-6);
  background: var(--warn);
  color: var(--group);
  font-size: var(--fs-body);
}
/* display:flex 會蓋過 hidden 屬性：正式系統裡一定要真的藏起來。 */
.practice-banner[hidden] { display: none; }
.practice-banner a { color: var(--group); font-weight: var(--fw-semibold); }
body.is-practice { --canvas: var(--warn-wash); }

/* ux-D: 設定頁精簡（UX 稽核 A11）——收費設定的搜尋與矮列、約診設定分頁、
   匯入規則收合、切換檢查清單已完成收成一行。只作用在這幾頁自己的 id／class。 */

/* 收費設定：表格標題列左邊標題、右邊搜尋框；窄螢幕搜尋框掉到下一行佔滿。 */
.billing-table-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-3);
}

.billing-table-head h2 {
  margin: 0;
}

.billing-filter {
  width: min(18rem, 100%);
}

/* 品項三十幾列、診療項目五十幾列：列高壓到 44px 以內（按鈕縮成 32px、上下留白縮小），
   一個畫面看得到的列數多一倍；三顆按鈕不換行，免得窄螢幕把一列撐成兩倍高。 */
.billing-table td {
  height: auto;
  padding-top: var(--sp-1);
  padding-bottom: var(--sp-1);
}

.billing-table td button {
  min-height: 32px;
  padding: 0 var(--sp-3);
}

.billing-table td .action-group {
  flex-wrap: nowrap;
  justify-content: flex-end;
  gap: var(--sp-1);
}

@media (max-width: 1100px) {
  .billing-table th,
  .billing-table td {
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
  }
}

/* 約診設定分頁列：一列放得下五個分頁，放不下時換行而不是撐出水平捲動。 */
.appt-tabs {
  display: flex;
  flex-wrap: wrap;
  width: fit-content;
  max-width: 100%;
  margin-bottom: var(--sp-5);
}

.appt-tabs button {
  min-height: 40px;
}

/* 分頁切換是藏卡片：.card + .card 的上間距要算在看得到的第一張上面也不會多出空白。 */
#view-appointment-settings [data-appt-tab][hidden] {
  display: none;
}

/* 匯入頁「匯入的規則」整張卡收成一行可展開的標題。 */
details.rules-card > ul {
  margin: var(--sp-3) 0 0;
}

/* 切換檢查清單：卡片留白縮小、項目之間貼近；已完成的一項只佔一行。
   勾選與日期排成一列，放不下才換行。 */
#cutover-items > .cutover-item {
  padding: var(--sp-4) var(--sp-5);
}

#cutover-items > .cutover-item > h2 {
  margin-bottom: var(--sp-2);
}

#cutover-items > .cutover-item.is-done {
  padding-top: var(--sp-1);
  padding-bottom: var(--sp-1);
}

.cutover-marks {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0 var(--sp-6);
}

.cutover-marks > .cutover-mark {
  margin-top: var(--sp-1);
}

#cutover-items > .cutover-item + .cutover-item {
  margin-top: var(--sp-3);
}

.cutover-item.is-done > summary .pill {
  margin-left: 8px;
}

.cutover-next {
  margin: 0 0 var(--sp-2);
}

/* ux-A: 側欄群組收合與精簡高度、頁首名字進我的帳號、帳號 PIN 標籤、操作紀錄列高 --------
   （2026-10-23 非看診流程 UX 修正 A1–A3、A11 帳號／操作紀錄） */

/* 群組標題是一顆按鈕（nav.js），點了收合；比原本的標題（24px 字列＋上 24px 間距）矮。 */
.nav-group-toggle {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 30px;
  margin: var(--sp-2) 0 2px;
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink-3);
  font: inherit;
  font-size: var(--fs-footnote);
  text-align: left;
  cursor: pointer;
}

.nav-group-toggle:hover {
  background: var(--nav-hover);
  color: var(--ink);
}

.nav-group-toggle:focus-visible {
  outline-offset: -2px;
}

/* 箭頭：展開朝上、收起朝下，跟「提醒 N 項」的開關同一種畫法。 */
.nav-group-toggle::after {
  content: '';
  width: 7px;
  height: 7px;
  margin-left: auto;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
}

.nav-group-toggle[aria-expanded="true"]::after {
  transform: translateY(1px) rotate(-135deg);
}

/* 目前頁在收起來的群組裡：標題帶同一顆圓點，看得出人在這一群。 */
.nav-group-toggle.has-current[aria-expanded="false"] {
  color: var(--ink);
  font-weight: var(--fw-semibold);
}

.nav-group-toggle.has-current[aria-expanded="false"]::before {
  content: '';
  position: absolute;
  top: 50%;
  right: calc(var(--sp-4) + 14px);
  width: var(--indicator-dot);
  height: var(--indicator-dot);
  margin-top: calc(var(--indicator-dot) / -2);
  border-radius: 50%;
  background: var(--signature);
}

/* .nav-list 是 grid，會蓋過瀏覽器預設的 [hidden]，要明寫。 */
.nav-list[hidden] {
  display: none;
}

/* 只有 1 項、不顯示標題的群組（櫃台的訂購與廠商、回診追蹤、自費價目）：上面一條細線，
   跟上一群分開。原本只隔 4px，看起來像上一群（申報作業）的子項（設計審查 D10）。 */
.nav-list.is-headless {
  margin-top: var(--sp-3);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--hairline);
}

/* 側欄每一項同一個高度（設計審查 D12）：原本日常項目 38～52px、其他 32～40px，同一份清單
   兩種節奏。字一律一行：200px 側欄裡「Google 行事曆」「匯入收費 Excel」原本折成兩行。 */
.nav-link,
.nav-link.is-minor,
.nav-link:not(.has-icon) {
  min-height: 40px;
  white-space: nowrap;
}

/* 黏底的連線狀態與「提醒 N 項」：原本在 220px 側欄裡折成兩行（底部佔 97px），
   內距與間距縮小後同一行放得下。放不下時（200px 側欄、提醒字較長）讓提醒換到下一行，
   「已連線」本身不斷字：原本被擠成一字一行、高 71px（設計審查 D2）。 */
.sidebar-footer {
  padding: var(--sp-1) var(--sp-2) var(--sp-2);
}

.sidebar-status-line {
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
}

.sidebar-live {
  flex: none;
  white-space: nowrap;
}

/* 1100px 以下寬的側欄（200px）：連線狀態只留圓點，字（已連線／連線中斷）仍在 DOM 裡給螢幕閱讀器、
   滑過看 title（nav.js followConnection），「提醒 N 項」才跟圓點同一行（設計審查第 2 輪 R1）。 */
@media (min-width: 900px) and (max-width: 1100px) {
  .sidebar-live {
    font-size: 0;
  }
}

.sidebar-alerts-toggle {
  flex: none;
  padding: 0 var(--sp-3);
  white-space: nowrap;
}

/* 管理者的備份狀態（資料保護的 #sidebar-backup-line）：只改呈現。固定一行、放不下以「…」收尾，
   完整原因在連結的 title（backup-status.js 設的）；仍是紅字、仍釘在已連線上方，不收進提醒。
   原本折成兩行（47px），合併 master 後管理者側欄又要捲（設計審查第 2 輪 R1）。
   平板寬度那段把連結放大成 44px 的 inline-flex，flex 的匿名文字項目吃不到 ellipsis，改 block＋行高。 */
.sidebar-backup {
  margin-bottom: var(--sp-1);
  overflow-wrap: normal;
}

.sidebar-backup a {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (pointer: coarse), (max-width: 1366px) {
  .sidebar-backup a {
    display: block;
    line-height: 44px;
  }
}

/* 200px 側欄（1280px 以下寬的電腦、平板橫放）：圖示與字的間距、左內距各縮 4px，
   長項目名稱才放得進一行。 */
@media (min-width: 900px) and (max-width: 1279px) {
  .nav-link,
  .nav-link.has-icon {
    gap: var(--sp-2);
    padding-left: var(--sp-3);
  }
}

/* 1536×864（1920×1080 縮放 125%）以下高度的電腦：櫃台不捲側欄就要看得到全部項目、
   管理者看得到設定以外的全部（驗收 A1）。每項 34px，滑鼠仍好點；
   觸控為主的平板直放（899px 以下寬）是底部面板，不受這段影響。 */
@media (min-width: 900px) and (max-height: 960px) {
  .sidebar {
    padding-top: var(--sp-3);
  }

  .brand {
    padding-bottom: var(--sp-3);
  }

  .nav-link,
  .nav-link.is-minor,
  .nav-link:not(.has-icon) {
    min-height: 34px;
  }

  /* 群組標題 26px（原 28）：管理者多了一行備份狀態後，1536×864 還差 3px（第 2 輪 R1）。 */
  .nav-group-toggle {
    min-height: 26px;
    margin-top: var(--sp-1);
  }

  .nav-list.is-headless {
    margin-top: var(--sp-2);
    padding-top: var(--sp-1);
  }
}

/* 平板橫放 1024×768、1280×800 這類更矮的畫面：設定收起時管理者的整份選單（約 16 項、
   4 個群組標題）不必捲（設計審查 D3：原本多出 109px，報表被切到首屏外）。 */
@media (min-width: 900px) and (max-height: 800px) {
  .sidebar {
    padding-top: var(--sp-3);
  }

  .brand {
    padding-bottom: var(--sp-1);
  }

  .nav-link,
  .nav-link.is-minor,
  .nav-link:not(.has-icon) {
    min-height: 30px;
  }

  .nav-group-toggle {
    min-height: 22px;
    margin-top: 2px;
  }

  .nav-list.is-headless {
    margin-top: var(--sp-1);
  }

  .sidebar-footer {
    padding-bottom: var(--sp-1);
  }

  /* 項目之間不留縫：30px 一列已經分得開，16 項省下約 30px。 */
  .nav-list {
    gap: 0;
  }
}

/* 底部「更多」面板（平板直放、手機）：觸控為主，每項 44px。 */
@media (max-width: 899px) {
  .nav-link,
  .nav-link.is-minor,
  .nav-link:not(.has-icon) {
    min-height: 44px;
  }
}

/* 帳號表（設計審查 D5）：操作欄兩顆按鈕並排不折行；1100px 以下寬，「帳號」欄收進名字那一格
   （accounts.js nameCell 的小字），角色與對應醫師的下拉縮成內容寬，整張表放得進卡片、
   列高 56px 以內。原本 1024／768 表寬 755 大於卡片 745／713，「重設密碼」被切掉、列高 113px。 */
.accounts-table .action-group {
  flex-wrap: nowrap;
}

/* 每列都有 44px 的下拉與按鈕，上下內距各 4px，列高約 53px。 */
.accounts-table td {
  padding-top: var(--sp-1);
  padding-bottom: var(--sp-1);
}

.accounts-table .action-group button {
  white-space: nowrap;
}

.account-login-inline {
  display: none;
  margin-left: var(--sp-1);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

@media (max-width: 1100px) {
  .accounts-table th:nth-child(2),
  .accounts-table td:nth-child(2) {
    display: none;
  }

  .account-login-inline {
    display: inline;
  }

  .accounts-table td {
    white-space: nowrap;
  }

  .accounts-table th,
  .accounts-table td {
    padding-left: var(--sp-3);
    padding-right: var(--sp-3);
  }

  .accounts-table select {
    width: auto;
    min-width: 88px;
    padding: 0 var(--sp-2);
  }
}

/* 頁首自己的名字＝我的帳號的入口：跟旁邊「登出」一樣小字，加底線看得出能點。
   「名字・角色」一行（S7）：不准斷成兩行；名字長到放不下才用 …，全名在滑過的提示裡（main.js 設 title）。 */
.topbar-who,
.sidebar-who {
  display: inline-block;
  vertical-align: middle;
  max-width: 20em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-3);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--hairline-strong);
  text-underline-offset: 4px;
}

.topbar-who:hover,
.sidebar-who:hover {
  color: var(--ink);
  text-decoration-color: currentColor;
}

/* 操作紀錄：一列一行，列高壓在 56px 以內（裝置欄重複的已留白，內容欄有空間）。 */
.audit-table td {
  height: 44px;
  padding-top: var(--sp-2);
  padding-bottom: var(--sp-2);
}

/* ux-B: 健保申報、健保上傳、每月例行申報、健保抽審（2026-10-23 非看診流程 UX 修正） ----
   健保申報：費用年月跟統計數字同一行；同類檢核一行、展開才列哪幾件；案件表每列一顆
   問題數紅標，點了才展開；表格放得進卡片（三視口不左右捲）。 */
.claim-top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-4) var(--sp-8);
  margin-bottom: var(--sp-4);
}

.claim-top .field {
  margin: 0;
}

.claim-top .claim-summary {
  margin-bottom: 0;
}

#claim-download-tip[hidden] {
  display: none;
}

.claim-group-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--sp-2);
}

.claim-group-head .info-tip {
  margin-inline-start: 0;
}

.claim-group-count {
  font-size: var(--fs-footnote);
}

.claim-problem-detail > summary {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  cursor: pointer;
}

.claim-case-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
  margin: var(--sp-1) 0 var(--sp-2);
  padding: 0;
  list-style: none;
  color: var(--ink);
}

/* 一件的訊息或略過表單跟在連結後面時，整行自己一行。 */
.claim-case-links > li:has(> span, > form) {
  flex-basis: 100%;
}

.claim-case-link {
  min-height: 32px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-pill);
  background: var(--group);
  color: var(--ink);
  font-size: var(--fs-footnote);
  cursor: pointer;
}

.claim-case-link:hover {
  background: var(--fill-strong);
}

/* 案件表：問題數紅標是一顆可以按的圓角數字。 */
.claim-badge {
  min-width: 32px;
  min-height: 28px;
  padding: 0 var(--sp-2);
  border: 0;
  border-radius: var(--r-pill);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

.claim-badge.is-error {
  background: var(--danger-wash);
  color: var(--danger);
}

.claim-badge.is-warning {
  background: var(--warn-wash);
  color: var(--warn);
}

.claim-badge[aria-expanded='true'] {
  outline: 2px solid currentColor;
  outline-offset: 1px;
}

/* 列高收到 56px 內：格子上下內距 4px（按鈕 40 高、窄畫面姓名下一行病歷號兩行字都放得下；一列 16 件要一眼掃完）。 */
.claims-table > tbody > tr > td {
  padding-top: var(--sp-1);
  padding-bottom: var(--sp-1);
}

.claims-table td.claim-who {
  min-width: 0;
  white-space: nowrap;
}

/* 「開病歷」「修改」一列並排、不折字（設計審查 r1 #2：768 寬時折成三行、1536 寬時上下疊，列高 108–135px）；
   按鈕用小一級的高度，列高收在 56px。診察費「00121C（230）」也不折。 */
.claims-table .action-group {
  flex-wrap: nowrap;
}

.claims-table .action-group > * {
  min-height: var(--h-control-sm);
  height: var(--h-control-sm);
  white-space: nowrap;
}

.claims-table td:nth-child(6) {
  white-space: nowrap;
}

.claims-table .claim-sub {
  margin-inline-start: var(--sp-2);
  font-size: var(--fs-footnote);
}

.claims-table .claim-sub-inline {
  font-size: var(--fs-footnote);
}

.claims-table td.numeric .info-tip {
  margin-inline: 0 var(--sp-1);
}

/* 展開的明細、修改表單那一列是第一格（.wide-table td:first-child 不換行），要放開，
   不然一句長的檢核訊息就把整張表撐寬（修正前表寬 1447px 的主因）。 */
.claims-table td.claim-wrap-cell {
  white-space: normal;
}

.claims-table tr.is-flash > td {
  animation: claim-flash 1.6s ease-out;
}

@keyframes claim-flash {
  from { background: var(--warn-wash); }
  to { background: transparent; }
}

/* 卡片窄（平板直放、橫放約 670–700px）時：格子內距收一級；診察費、案件分類先藏
   （按「修改」看得到），其餘欄位放得下、不用左右捲。 */
.claims-scroll {
  container-type: inline-size;
}

@container (max-width: 860px) {
  .claims-table th,
  .claims-table td {
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
  }

  /* 窄的時候病歷號排到姓名下一行，姓名欄不撐寬。 */
  .claims-table .claim-sub {
    display: block;
    margin-inline-start: 0;
    line-height: 1.2;
  }

  /* 就醫序號也藏（窄的時候多半是「—」，按「修改」看得到），動作那兩顆才放得下一列。 */
  .claims-table th:nth-child(6),
  .claims-table th:nth-child(7),
  .claims-table th:nth-child(8),
  .claims-table td:nth-child(6):not([colspan]),
  .claims-table td:nth-child(7):not([colspan]),
  .claims-table td:nth-child(8):not([colspan]) {
    display: none;
  }
}

/* ux-B 每月例行申報：已完成的收成最下面一組，每張一行、點了才展開。 */
.filing-done-group > summary,
.filing-done-item > summary {
  cursor: pointer;
  list-style-position: outside;
}

.filing-done-summary {
  font-weight: var(--fw-semibold);
}

.filing-done-item {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}

/* summary 裡的標題列：跟三角形同一行，不留卡片標題的下距。 */
.filing-done-item > summary .filing-head {
  display: inline-flex;
  margin-bottom: 0;
  vertical-align: middle;
}

.filing-done-item > summary .filing-head h2 {
  font-size: var(--fs-body);
}

.filing-done-item[open] > summary {
  margin-bottom: var(--sp-3);
}

/* ux-B D8 待辦卡精簡：一張卡兩行（標題＋期限／狀態＋主要動作＋之後要做的），
   7 張待辦約一屏放得下（原本每張約 210–300px，頁高 2504）。 */
#filing-cards > .filing-card {
  padding: var(--sp-4) var(--sp-5);
}

#filing-cards > .filing-card .filing-head {
  margin-bottom: var(--sp-2);
}

#filing-cards > .filing-card .filing-steps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-4);
  padding-left: 0;
  list-style: none;
}

#filing-cards > .filing-card .filing-steps li {
  padding: 0;
}

#filing-cards > .filing-card .filing-steps li.is-later {
  font-size: var(--fs-footnote);
}

#filing-cards > .filing-card .filing-nothing-button {
  margin: 0;
}

#filing-cards > .filing-card .filing-nothing {
  padding-left: 0;
}

#filing-cards > .filing-card .filing-zero {
  font-size: var(--fs-body);
}

#filing-cards > .filing-card .claim-summary {
  margin-bottom: var(--sp-2);
}

/* 設定的七個勾選框排成兩三行（原本一個一行，設定卡約 290px）。 */
.filing-settings {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
}

/* 登錄者與時間在提示裡（title）：游標提示這一行還有東西可看。 */
.filing-steps li.is-done,
.filing-nothing {
  cursor: help;
}

/* ux-B 健保上傳：「立即上傳」在卡片標題同一行右側。 */
.uxb-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-4);
}

.uxb-card-head h2 {
  margin: 0;
}

.uxb-card-head .uxb-card-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin-left: auto;
}

.uxb-card-head .uxb-card-actions[hidden] {
  display: none;
}

/* ux-B 健保抽審：「附病歷：往前 [6] 個月」，數字欄窄、單位跟在後面。 */
.sr-months {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.sr-months input {
  width: 5em;
}

/* ux-B 健保申報「和北昕比對」：.action-group 預設 stretch，按鈕會被旁邊「標籤＋日期框」
   撐成 88px 高的藥丸；改成底部對齊，按鈕回到一般控制項高度、跟日期框同一條底線。 */
#claim-compare-card .action-group {
  align-items: flex-end;
}

#claim-compare-card .action-group .field {
  margin-bottom: 0;
}


/* ux-C: 通知、LINE 回覆、回診追蹤、收費統計、零錢櫃、訂購與廠商的精簡（2026-10-23 UX 稽核）
   操作效率鐵則：空的區塊只剩一行標題、一則一行、日期欄跟統計同一行、
   表格列高 ≤ 56px（修改／刪除並排的小按鈕）。回診追蹤的規則在它自己那一節。 */

/* 空的區塊（看診後問候（0）、待回覆（0））：只剩一行標題，卡片也跟著變矮。 */
.notify-section.is-empty {
  padding-block: var(--sp-4);
}

.notify-section.is-empty h2 {
  margin: 0;
  color: var(--ink-3);
}

.notify-section.is-empty > :not(h2) {
  display: none;
}

/* 通知一則一行：姓名（病歷號、電話）／時間與項目／按鈕；窄的時候按鈕折到下一行。
   訊息全文收在這一行底下，按「訊息」才展開。 */
.notify-row {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  padding: var(--sp-2) 0;
}

.notify-row + .notify-row::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-top: 1px solid var(--hairline);
}

.notify-row .notify-who {
  flex: 0 1 auto;
}

.notify-row .notify-meta {
  flex: 1 1 auto;
}

.notify-row .notify-actions {
  flex-wrap: nowrap;
  margin-left: auto;
}

.notify-row .notify-actions button {
  min-height: 36px;
  padding: 0 var(--sp-4);
}

/* 展開的訊息與複製失敗的提示佔滿整行，接在那一則底下。 */
.notify-row .notify-message,
.notify-row .notify-hint {
  flex: 1 1 100%;
}

.notify-row .notify-message {
  padding: var(--sp-3) var(--sp-4);
}

.notify-row .notify-message[hidden] {
  display: none;
}

.notify-actions button.is-current {
  background: var(--fill-strong);
}

/* 「今天處理過的」平常收著，只是一行標題，不用一般卡片的大內距。 */
#view-notifications .handled {
  padding-block: var(--sp-4);
}

/* 月份／日期欄在左、統計數字在右，同一行。 */
.stat-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-4) var(--sp-8);
}

.stat-row .field,
.stat-row .claim-summary {
  margin-bottom: 0;
}

.stat-row + .items-scroll {
  margin-top: var(--sp-5);
}

/* 卡片標題右邊放這張卡的主要動作（「＋新增訂購」）。 */
.card-title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}

.card-title-row h2 {
  margin: 0;
}

/* 表格列高 ≤ 56px：格子上下內距縮小，列裡的按鈕 36px、並排不折行。 */
.compact-rows td {
  padding-block: var(--sp-2);
}

.compact-rows td .action-group {
  flex-wrap: nowrap;
}

.compact-rows td button {
  min-height: 36px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-footnote);
}

/* 「20 盒」不折成兩行；廠商、品項、說明一行放不下以「…」收尾（全文在 title）。 */
.purchase-quantity {
  min-width: 4em;
  white-space: nowrap;
}

.cell-clip {
  max-width: 10em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.purchases-table td,
.vendors-table td {
  white-space: nowrap;
}

/* 平板放不下整張表（訂購、廠商、零錢櫃）時在卡片裡左右捲，最右邊的操作欄
   （修改／刪除）黏在右緣，不用捲到最右邊才找得到按鈕。 */
@media (max-width: 1499px) {
  .compact-rows td,
  .compact-rows th {
    padding-inline: var(--sp-2);
  }

  .cell-clip {
    max-width: 7em;
  }

  .compact-rows tr:not(.empty-row) td:last-child {
    position: sticky;
    right: 0;
    background: var(--group);
    box-shadow: inset 1px 0 0 var(--hairline);
  }
}

/* ux-C D6：平板寬度（≤1100px，卡片約 670–700px）訂購表與廠商表要整張放進卡片，
   黏在右緣的修改／刪除才不會蓋住到貨、付款。藏單價（金額還在，按「修改」看得到）、
   日期只留月日（全文在 title）、「今天到貨」縮成「到貨」、廠商／品項／名稱再窄一點。 */
@media (max-width: 1100px) {
  .purchases-table th:nth-child(5),
  .purchases-table td:nth-child(5):not([colspan]) {
    display: none;
  }

  .purchases-table .date-year,
  .purchases-table .mark-today {
    display: none;
  }

  .purchases-table .cell-clip {
    max-width: 5em;
  }

  .vendors-table td.vendor-name {
    max-width: 8em;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .compact-rows td button {
    padding: 0 var(--sp-2);
  }
}

/* ux-C D7：數字欄的表頭跟數字一樣靠右（收費統計、零錢櫃、訂購、健保申報案件表、收費設定），
   表頭和數值才對得齊（第 2 輪 R2 補上後兩張）。 */
.billing-table th.numeric,
.wide-table th.numeric,
#view-reports th.numeric,
#view-cash-drawer th.numeric,
.purchases-table th.numeric {
  text-align: right;
}

/* ux-C A9：收費統計的月份和 8 個數字在 1536 寬同一行（原本 164＋885＋間距超過卡片
   1072，月份自己一行）。數字之間的間距收一級；數字區佔剩下的寬度、窄的時候在自己
   裡面換行，月份不會被擠到上面。平板（≤1100px）放不下一行，月份自己一行、數字在下面。 */
#view-reports .stat-row {
  flex-wrap: nowrap;
  gap: var(--sp-4) var(--sp-6);
}

#view-reports .stat-row .field {
  flex: 0 0 auto;
}

#view-reports .stat-row .claim-summary {
  flex: 1 1 0;
  min-width: 0;
  gap: var(--sp-3) var(--sp-5);
}

@media (max-width: 1100px) {
  #view-reports .stat-row {
    flex-wrap: wrap;
  }

  #view-reports .stat-row .claim-summary {
    flex-basis: 100%;
  }
}

/* ux-C D4：1024 寬時通知一則也維持一行（原本四顆按鈕掉到第二行，列高約 85px）。
   病歷號藏起來（姓名上的 title 看得到）、按鈕內距收一級、項目放不下以「…」收尾
   （全文在 title）。直放平板（≤800px）放不下，照舊讓按鈕折到下一行。 */
@media (min-width: 801px) and (max-width: 1100px) {
  .notify-row {
    flex-wrap: nowrap;
    column-gap: var(--sp-3);
  }

  .notify-row .notify-who {
    flex: 0 0 auto;
    flex-wrap: nowrap;
    white-space: nowrap;
    column-gap: var(--sp-2);
  }

  .notify-row .notify-chart {
    display: none;
  }

  .notify-row .notify-meta {
    flex: 1 1 0;
    min-width: 0;
    flex-wrap: nowrap;
    overflow: hidden;
  }

  .notify-row .notify-meta-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .notify-row .notify-actions button {
    padding: 0 var(--sp-2);
  }

  /* 空的提示行在不換行的列裡也會分走寬度，沒字時不排版。 */
  .notify-row .notify-hint:empty {
    display: none;
  }

  /* 訊息展開或有提示時，這一則改回可以換行，訊息與提示照舊佔滿底下一整行。 */
  .notify-row:has(> .notify-message:not([hidden])),
  .notify-row:has(> .notify-hint:not(:empty)) {
    flex-wrap: wrap;
  }
}

/* 資料匯出的密碼表單（data-export.js）：一欄，密碼框不必拉滿整張卡；結果那一行跨整欄。 */
.export-form {
  grid-template-columns: minmax(0, 22rem);
  gap: var(--sp-3);
}

.export-form .gcal-status:empty {
  display: none;
}

/* 病患頁「匯出這位病人的資料」展開的表單：接在工具列下面，跟下面的資料隔開一點。 */
.export-panel {
  margin: 0 0 var(--sp-5);
  padding: var(--sp-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-group);
}

.export-panel[hidden] {
  display: none;
}

/* 頁面裡通往相關頁的連結（切換檢查清單 → 匯出全部資料）：一行小字，不搶清單的版面。 */
.page-shortcuts {
  margin: var(--sp-3) 0;
  font-size: var(--fs-footnote);
}

/* 醫師業績（doctor-report.js）：表格容器窄於整列時藏次要欄（健保／自費／其他人次、申請點數），
   1024、768 寬不必橫捲就看得到自費收入與總收入。用 container query 量的是表格自己的寬度，
   不是視窗：側欄收合與否都對。1060px＝1536 寬時整列剛好放得下的寬度再留一點餘裕。 */
.dr-scroll {
  container-type: inline-size;
}

@container (max-width: 1060px) {
  .dr-secondary {
    display: none;
  }
}
/* === 病患看診畫面 po- ===================================================
   病患清單與看診工作畫面（docs/superpowers/specs/2026-10-23-patient-visit-screen-design.md；
   設計規格 chixu/design/patient-overview-spec.md）。全部用 B3a token，不新增色碼。
   放在 views.css 尾端：另一個 session（UX 整理）把共用元件加在 components.css 尾端。 */

/* 病患頁要整個內容寬（1536×864 時 1172）：列表多放幾欄、看診畫面放兩欄。 */
body[data-view="patients"] {
  --content-max: none;
}

/* 標題只給螢幕閱讀器，這一行由工具列／頁首佔，跟浮在右邊的 .topbar 並排；
   不要被「標題之後一律 clear」推到 topbar 底下（spec §0）。 */
#view-patients > .section-title ~ * {
  clear: none;
}

/* --- 清單：工具列（就是頁標題那一行） --- */
.po-list-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  min-height: var(--h-control);
  margin-bottom: var(--sp-2);
}

.po-list-bar input[type="search"] {
  flex: 1 1 320px;
  width: auto;
  max-width: 480px;
  min-height: var(--h-control-sm);
  border-radius: var(--r-pill);
  padding-left: calc(var(--sp-4) + 20px + var(--sp-2));
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23605750' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='6.5'/%3E%3Cpath d='M15.5 15.5L20 20'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 20px 20px;
  background-position: var(--sp-4) center;
}

[data-theme="dark"] .po-list-bar input[type="search"] {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23bcb1a4' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='6.5'/%3E%3Cpath d='M15.5 15.5L20 20'/%3E%3C/svg%3E");
}

.po-list-bar select,
.po-list-bar > button,
.po-list-bar .menu > summary.btn-secondary {
  width: auto;
  min-height: var(--h-control-sm);
}

.po-count {
  margin-left: auto;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  font-feature-settings: var(--numerals);
  white-space: nowrap;
}

.po-count.is-error {
  color: var(--danger);
}

/* 「還沒寫 N」按鈕（北昕第一批 B4）：開第一筆還沒寫的。跟人數同一行，字一樣大；要處理的事所以是按鈕不是字。 */
.po-count .po-day-unwritten {
  min-height: var(--h-control-sm);
  margin-left: var(--sp-1);
  padding: 0 var(--sp-3);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
}

.po-list-card {
  clear: both;
  padding: var(--sp-2) var(--sp-5) var(--sp-4);
}

/* --- A30：病患頁預設是「今天要看誰」。換日那一排與今天看板（.po-today-day）同一個樣子。 --- */
.po-day-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  min-height: 44px;
  margin-bottom: var(--sp-2);
  /* 日期選擇器（.po-today-pick）貼著日期叫出來，所以這一排要是它的定位參考。 */
  position: relative;
}

/* display: flex 會蓋過瀏覽器自己的 [hidden] { display: none }：搜尋時這一排要真的收起來。 */
.po-day-bar[hidden] {
  display: none;
}

.po-day-bar > button {
  width: auto;
}

/* 清單頁本身貼齊內容區左右（使用者 2026-10-26：「搜尋頁面一樣放大成滿版 方便操作」）：
   .content 的 72px 外緣留白在 1536 上等於吃掉 144px、一整欄的資訊。只有清單那一頁縮成 20px，
   看診畫面（兩欄、要留白才不擠）維持原本的留白——所以用 :has 綁在清單頁露出來的時候。 */
body[data-view="patients"]:has(#patient-list-page:not([hidden])) .content {
  padding-left: var(--sp-5);
  padding-right: var(--sp-5);
}

/* --- 滿版的清單（使用者 2026-10-26：「搜尋頁面一樣放大成滿版 方便操作」）---------------
   病患頁本來就吃整個內容寬（body[data-view="patients"] --content-max: none），但「短欄只佔內容寬、
   姓名欄吃掉剩下的寬度」那套會讓中間空出一大塊死白：欄位全擠在兩邊，眼睛要跨過 700px 才對得到狀態。
   這兩張表改成固定版面＋百分比欄寬，把寬度分給真的有東西的欄（時間、姓名、到哪一步、醫師、病歷、
   下一步），列高不變，所以一眼看得到的列數只增不減。 */
.po-wide-table {
  table-layout: fixed;
}

.po-wide-table th,
.po-wide-table td {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 名字前的三顆星（A17）在這兩張清單裡的格子（fix/restore-name-marks）。
   刻意「不」加一欄：這兩張表是 table-layout: fixed ＋百分比欄寬，多一欄要把所有百分比重分配，
   而且會讓照位置寫的舊規則整批指到別欄去（知識卡 reordering-table-columns-silently-retargets-every-nth-child-rule）。
   星放進原本的姓名格裡（不動 td 的 display：把 td 改成 flex 會讓它脫離表格、fixed 的欄寬就失效），
   包一層 .po-name-line 當那一格的版面：星固定三顆寬、永不縮，姓名是唯一縮得下去的那一段，
   過敏小紅標跟著姓名後面但同樣不縮——放不下時截的是姓名，不是星也不是過敏（病患視覺規格 §3.2）。
   寬度固定三顆星——還沒報到的人 marksCell 回一個空格子，有星沒星的列名字都從同一個 x 開始
   （A17「星號上下都要對齊」）；醫師只看到兩顆星也一樣對齊。
   星盒高 28px < 列高 34px，所以列高由列自己決定，看得到的列數不被星吃掉。 */
/* min-height 不是 height：名字換行時這一格要長得高，寫死 34 會讓第二行被切掉
   （而「被切掉的名字」正是這次要根除的東西）。 */
.po-name-line {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 34px;
  line-height: var(--lh-ui);
}

#patients-today-table .po-name-line > .vm-stars-cell,
#patients-booked-table .po-name-line > .vm-stars-cell {
  display: flex;
  flex: none;
  width: calc(var(--star-box-w) * 3);
  margin-inline-end: var(--sp-1);
}

/* 姓名**永遠不准被截**。
   櫃台認人靠姓名＋生日（規格 §4），而這一頁存在的理由就是「我現在要看誰」——
   把「蔡明哲」畫成「蔡..」是臨床風險，不是版面取捨。A30 之前這兩張表跑的是
   table-layout: auto，欄寬由最長的名字決定，所以名字從來沒被截過；改成 fixed ＋
   百分比之後如果用 text-overflow: ellipsis 收尾，就是我們自己弄丟了那個保證。
   所以這裡刻意**不用** ellipsis：放不下就換行，寧可那一列高一點。
   這條讓「名字不會被截」變成結構上成立，不依賴下面的百分比算得剛剛好。 */
.po-name-line > .po-row-link,
.po-name-line > .muted {
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* 過敏永不截：它是臨床警示，寧可擠掉名字的字也要留著（規格 §10 第 8 條）。
   margin-left 歸零：這一行已經有 flex 的 gap，再加它自己那 8px 等於間距兩份，
   而那 8px 是從名字身上拿的（窄視窗每一個字都算）。 */
.po-name-line > .po-allergy-badge {
  flex: none;
  margin-left: 0;
}

@media (pointer: coarse) {
  .po-name-line { min-height: 44px; }
}

/* 放不下時名字改佔一整行，三顆星與過敏小紅標退到第二行（水平預算不再三個人分）。
   這樣名字在窄螢幕上既不會被截、也不會折成兩三行，而且每一列的名字左緣都在同一條線上，
   掃名字比原本更快。只有這兩張表的這一格變兩行，列高跟著變 auto；
   問題是水平的，付的是垂直的——768×1024 原本就有約 200px 空白沒用到。

   斷點為什麼長這樣（量出來的，不是挑的）：這一格放得下一行的條件是表格寬 ≥ 約 710。
   而表格寬並不隨視窗單調變化——側欄在 ≤899 會自己收起來，表格反而變寬：
     視窗 1024→745、1011→732、900→621（側欄還在）
     視窗 899→820、821→742、768→689（側欄收起來）
   所以「放不下」的區間是兩段：900–1010 與 ≤820。1024 與 821–899 都放得下一行，
   維持單行才守得住 1024×768 ≥14 列（兩行會掉到 10 列）。 */
@media (max-width: 820px), (min-width: 900px) and (max-width: 1010px) {
  /* 不另外給上下內距：兩行之間的 row-gap 就夠了，多加 4+4 等於每一列多 8px，
     768×1024 會因此少看一列。 */
  .po-name-line {
    flex-wrap: wrap;
    min-height: 0;
    row-gap: 2px;
  }
  /* order: -1 把名字提到第一行；星與過敏照 DOM 順序落到第二行。 */
  .po-name-line > .po-row-link,
  .po-name-line > .muted {
    order: -1;
    flex-basis: 100%;
  }
  /* 上下內距只由 .po-name-line 那一層給，這裡不要再加一次——加兩次每一列多 8px，
     768×1024 會因此少看一列。 */
  #patients-today-table td,
  #patients-booked-table td {
    height: auto;
    line-height: var(--lh-ui);
  }
}

/* 寬螢幕的欄寬用**固定 px**，不用百分比。三顆星不另立一欄，算在病患那一欄裡。

   為什麼不是百分比：百分比會讓每一欄跟著視窗一起長大，但欄位裡的字不會跟著變長。
   病患欄先前寫 38%，在 1536 上是 446px，而最長的「巴奈・庫穗・瑪莎蓮娜」連星帶內距只用 254
   ——**每一列的名字右邊都空著 192px**，使用者看到的就是「這裡也空一大格」。
   資料欄要的是「放得下就好」，那是一個固定的 px 數，不是視窗的一個比例。
   （CLAUDE.md §10.1：大氣不等於空；不准常用的地方小、不常用的地方大。）

   數字是量出來的：把表格暫時改回 auto＋max-content，量每一欄「一個字都不截」要多少寬
   （含左右內距 24），**用櫃台登入量**——它的「加處置／病碼」144 是全站最長的動作鈕，
   只照醫師量（「寫病歷」96）會少算 48。
   今天清單（櫃台）：時間 62｜病患 278｜到哪一步 174｜醫師 72｜病歷 92｜下一步 144｜› 7 ＝ 828
   病患 278 ＝ 星 72 ＋ 間距 8 ＋ 最長姓名 170 ＋ 內距 24。
   量到的表格寬：1536→1237、1280→981、1101→822、1024→745、899→820、768→689。
   828 只有 ≥1280 放得下（981），1101 的 822 差 6px——醫師欄因此提前到 1279 以下就收起來。

   分配的規則：**每一欄＝自己要的寬 ＋ 一份一樣大的溝槽**，不是照比例放大。
   照比例放大會讓本來就寬的欄分到最多多餘的寬（病患欄要的 278 是七欄裡最大的，
   一放大就變成最大的洞）；一樣大的溝槽則讓多出來的寬度平均變成欄與欄之間的間隔。
   1536（表格 1237）實測每一欄右邊剩 49～81px，而不是病患欄獨得 192px。
   百分比是用這一段**最窄**的那一刻（1280，表格 981）反推的，所以 1280 一欄都不會擠。 */
#patients-today-table .col-time { width: 9%; }
#patients-today-table .col-name { width: 29%; }
#patients-today-table .col-step { width: 19%; }
#patients-today-table .col-doctor { width: 11%; }
#patients-today-table .col-rec { width: 13%; }
#patients-today-table .col-next { width: 17%; }
#patients-today-table .po-row-chevron { width: 2%; }

/* 預約清單（櫃台）：日期 111｜時間 62｜病患 278｜醫師 72｜項目 88｜狀態 92｜下一步 112｜› 7 ＝ 822。 */
#patients-booked-table .col-date { width: 13%; }
#patients-booked-table .col-time { width: 9%; }
#patients-booked-table .col-name { width: 29%; }
#patients-booked-table .col-doctor { width: 9%; }
#patients-booked-table .col-step { width: 12%; }
#patients-booked-table .col-rec { width: 12%; }
#patients-booked-table .col-next { width: 14%; }
#patients-booked-table .po-row-chevron { width: 2%; }

/* 保險絲：萬一哪個寬度還是放不下（例如之後有人把某句話改長，或 900 這種側欄還在、
   醫師欄已收起來的中間寬度），標與按鈕自己收成 …，而不是被 td 的 overflow: hidden
   從中間切掉半個字。規格 §10 第 8 條要的是「每一列最後一個元素都在自己那一欄的內容框內」，
   這條讓它在任何寬度都成立。正常寬度下用不到：上面的百分比已經留了餘裕。 */
#patients-today-table .po-st,
#patients-booked-table .po-st,
#patients-today-table .pill,
#patients-booked-table .pill,
td .btn-secondary.po-day-go {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 1279 以下收起「醫師」欄。為什麼是 1279 不是 1100：量到 1101 的表格只有 822，
   而櫃台的今天清單一個字都不截要 828——差 6px，那 6px 只能從名字身上拿，不行。
   1280 的 981 才真的放得下八欄。醫師在看診畫面與叫號都看得到，名字沒有別的地方可以看。
   這一段的百分比以這一段最窄的單行情形（表格 732，視窗 1011）反推，1024 的 745 有餘裕。
   格子的左右內距也從 12 收到 8：六欄共省 48px，這 48 全部回到名字身上。 */
@media (max-width: 1279px) {
  .po-wide-table .col-doctor {
    display: none;
  }
  #patients-today-table td,
  #patients-today-table th,
  #patients-booked-table td,
  #patients-booked-table th {
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
  }
  /* 標與按鈕的左右內距也收一級（10→6、12→8）：省下來的 24px 不是拿去擠名字，
     而是讓「加處置／病碼」「◐ 看診中・2 號椅」在 1024 上仍然寫得完整。
     只套這兩張表，叫號與今天看板的標不受影響。 */
  #patients-today-table .po-st,
  #patients-booked-table .po-st,
  #patients-today-table .pill,
  #patients-booked-table .pill {
    padding-left: 6px;
    padding-right: 6px;
  }
  #patients-today-table td .btn-secondary.po-day-go,
  #patients-booked-table td .btn-secondary.po-day-go {
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
  }
  /* 1024（表格 745）實測要的格內寬：到哪一步 141、下一步 112、病歷 60；
     名字那一格要 星 72 ＋ 間距 8 ＋ 姓名 170 ＝ 250。以此反推，不是估的。 */
  #patients-today-table .col-time { width: 8%; }
  #patients-today-table .col-name { width: 36%; }
  #patients-today-table .col-step { width: 23%; }
  #patients-today-table .col-rec { width: 12%; }
  #patients-today-table .col-next { width: 19%; }
  #patients-today-table .po-row-chevron { width: 2%; }

  #patients-booked-table .col-date { width: 15%; }
  #patients-booked-table .col-time { width: 8%; }
  #patients-booked-table .col-name { width: 37%; }
  #patients-booked-table .col-step { width: 11%; }
  #patients-booked-table .col-rec { width: 12%; }
  #patients-booked-table .col-next { width: 15%; }
  #patients-booked-table .po-row-chevron { width: 2%; }
}

/* 900–1010：側欄還在、醫師欄已收起來，表格只有 621–731，是所有寬度裡最窄的一段
   （768 的 689 反而比它寬，因為側欄已經收掉）。名字在這一段是獨立一行（見上面的兩行版面），
   所以這一欄只要放得下姓名本身 170＋內距，省下來的寬度全部給到哪一步與下一步。 */
@media (min-width: 900px) and (max-width: 1010px) {
  /* 名字這一欄給得比「一行放得下全名」再少一點：這一段放不下的話名字會折成兩行
     （仍然看得到全名，只是多一行），而標與按鈕被收成 … 是真的看不到字，兩害相權取前者。 */
  #patients-today-table .col-time { width: 9%; }
  #patients-today-table .col-name { width: 27%; }
  #patients-today-table .col-step { width: 27%; }
  #patients-today-table .col-rec { width: 14%; }
  #patients-today-table .col-next { width: 22%; }
  #patients-today-table .po-row-chevron { width: 1%; }

  #patients-booked-table .col-date { width: 16%; }
  #patients-booked-table .col-time { width: 9%; }
  #patients-booked-table .col-name { width: 30%; }
  #patients-booked-table .col-step { width: 10%; }
  #patients-booked-table .col-rec { width: 14%; }
  #patients-booked-table .col-next { width: 20%; }
  #patients-booked-table .po-row-chevron { width: 1%; }
}

/* 窄螢幕（768 直放）放不下時先丟最不重要的欄，不是把列壓薄——手指要按得到（A18）。
   預約那張表多兩欄（日期、項目），丟掉醫師還差一點，照既有的順序「預約再丟項目」。
   ≤899 側欄自己收起來，表格反而變寬（899→820、768→689）。

   **必須排在 ≤1279 那一段之後**：兩段的選擇器權重相同，誰在後面誰贏；寫在前面的話
   768 會套到上一段的百分比（實測過一次，狀態標與「加處置／病碼」被收成 …）。 */
@media (max-width: 899px) {
  .po-wide-table .col-doctor,
  #patients-booked-table .col-step {
    display: none;
  }
  #patients-today-table .col-time { width: 9%; }
  #patients-today-table .col-name { width: 33%; }
  #patients-today-table .col-step { width: 24%; }
  #patients-today-table .col-rec { width: 12%; }
  #patients-today-table .col-next { width: 20%; }
  #patients-today-table .po-row-chevron { width: 2%; }

  #patients-booked-table .col-date { width: 15%; }
  #patients-booked-table .col-time { width: 9%; }
  #patients-booked-table .col-name { width: 42%; }
  #patients-booked-table .col-rec { width: 14%; }
  #patients-booked-table .col-next { width: 18%; }
  #patients-booked-table .po-row-chevron { width: 2%; }
}

/* 觸控：整列是點擊目標，列高拉到 44px（平板上手指按得準）。 */
@media (pointer: coarse) {
  .po-wide-table td {
    height: 44px;
  }
}

/* 「下一步」那一顆：跟整列是同一件事，所以是次要樣式、矮一點，不讓列變高吃掉看得到的列數。
   選擇器必須壓過 components.css 的 `td .btn-secondary { min-height: var(--h-control-sm) }`（40px）：
   那條是 (0,1,1)，單寫 .po-day-go 是 (0,1,0) 會輸，所以 A30 以來這顆實際畫出來一直是 40 高、
   每一列被它撐成 41——1280×720 因此少看 3 列。這裡用同樣的 `td .btn-secondary` 前綴把權重補齊，
   不用 !important（下一個人要覆寫時還有路可走）。 */
td .btn-secondary.po-day-go {
  min-height: 26px;
  height: 26px;
  padding: 0 var(--sp-3);
  width: auto;
  font-size: var(--fs-footnote);
  pointer-events: none; /* 點到它＝點到整列（同一個動作），不另外攔截 */
}

/* 已經做完的那幾列（看病歷）：同一顆按鈕、淡一級，眼睛才會先落在還要處理的那幾列上。 */
.po-day-go.is-quiet {
  background: none;
  border-color: transparent;
  color: var(--ink-3);
  font-weight: var(--fw-regular);
}

@media (pointer: coarse) {
  td .btn-secondary.po-day-go {
    min-height: 34px;
    height: 34px;
  }
}

/* 「下一步」靠右：每一列的那一顆對齊同一條直線（今天看板設計審查 D2），姓名與動作之間只剩
   一道留白，不是欄內的一大塊死白。 */
#patients-today-table .col-next,
#patients-booked-table .col-next {
  text-align: right;
}

/* 到哪一步：文字就是狀態（不只靠顏色）；醫師名接在後面、淡一級。 */
.po-day-doctor {
  margin-left: var(--sp-2);
  color: var(--ink-3);
}

/* 「病歷寫了沒」與「約診狀態」不在這裡另畫一份：
   - 看過診了還沒定稿 → components.css 的 .pill .pill-draft（--warn-wash/--warn），看診紀錄頁同一顆。
   - 約診四態 → .pill-scheduled / arrived / cancelled / no_show，class 名就是 API 的 status 值。
   原本的 .po-day-rec / .po-day-rec.is-due 是 .pill-draft 的第二份抄本（同樣的 --warn-wash、--warn、
   --r-pill、semibold，只少了 .pill 的 26 高與 nowrap），而且被預約那張表借去畫狀態，
   把四種約診狀態收成兩種一模一樣的灰。兩個毛病同一個來源，所以整組刪掉改用既有的 pill。 */

/* --- 清單：表格。列高 32、字 16，1536×864 可見 ≥20 列（A22）。 --- */
.po-table-scroll {
  overflow-x: auto;
  margin: 0 calc(-1 * var(--sp-2));
}

#patients-table,
#patients-today-table,
#patients-booked-table {
  width: 100%;
}

/* 全部病患那一份是舊清單本人：短欄只佔內容寬、姓名欄吃掉剩下的，所以仍然是 auto。 */
#patients-table {
  table-layout: auto;
}

/* 今天與預約是固定版面＋百分比欄寬。這條必須是 ID 權重：.po-wide-table 的 `table-layout: fixed`
   (0,1,0) 原本輸給同檔後面那條 `#patients-today-table { table-layout: auto }` (1,0,0)，
   所以 A30 寫的百分比欄寬從來沒生效、欄寬其實是「內容說了算」，1024 時整張表撐出卡片、
   「下一步」與 › 被推到看不見。用同樣的 ID 權重釘回 fixed，每一條軌道才真的縮得下去。 */
#patients-today-table,
#patients-booked-table {
  table-layout: fixed;
}

#patients-table th,
#patients-today-table th,
#patients-booked-table th {
  height: 26px;
  padding: 0 var(--sp-3) var(--sp-1);
  vertical-align: bottom;
  white-space: nowrap;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

#patients-table td,
#patients-today-table td,
#patients-booked-table td {
  height: 32px;
  padding: 0 var(--sp-3);
  white-space: nowrap;
  font-size: var(--fs-footnote);
  line-height: 1.3;
}

/* 今天與預約的列高＝今天看板既有的 34（desk-today §2「一列 34 高」）：這兩份清單的列裡有
   26 高的狀態標與「下一步」那一顆，32 會讓它們貼邊。行高當列高，行內的標與按鈕才置中。
   舊清單（#patients-table，沒有標也沒有列內按鈕）維持 32，不動。 */
#patients-today-table td,
#patients-booked-table td {
  height: 34px;
  line-height: 34px;
}

/* 置中只給「有自己高度的盒子」（狀態標、下一步那一顆）。
   刻意不寫成 `td > *`：vertical-align: middle 套在**純文字的 inline span** 上會把它往下推
   （盒子中線對齊基線＋半個 x 高，而不是對齊行框中線），行框因此比 34 的 strut 還高——實測
   「病歷」那一格的 `<span class="muted">已定稿</span>` 就把整列從 35 撐成 37，1280 少一列。
   .pill 與 .po-allergy-badge 自己已經帶 vertical-align: middle，不必在這裡再寫一次。 */
#patients-today-table .po-st,
#patients-booked-table .po-st,
td .btn-secondary.po-day-go {
  vertical-align: middle;
}

@media (pointer: coarse) {
  #patients-today-table td,
  #patients-booked-table td {
    height: 44px;
    line-height: 44px;
  }
}

/* 短欄位只佔內容寬，姓名欄吃掉剩下的寬度；任何欄位組合都不會擠成一字一行（A23）。 */
#patients-table .po-short,
#patients-today-table .po-short {
  width: 1%;
}

#patients-table td.cell-name,
#patients-today-table td.cell-name,
#patients-booked-table td.cell-name {
  min-width: 9em;
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
}

/* 放不下時表格在卡片裡橫向捲動，姓名欄釘住。 */
#patients-table th:first-child,
#patients-table td:first-child,
#patients-today-table th:first-child,
#patients-today-table td:first-child,
#patients-booked-table th:first-child,
#patients-booked-table td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--group);
}

#patients-table tbody tr:hover td:first-child,
#patients-today-table tbody tr:hover td:first-child,
#patients-booked-table tbody tr:hover td:first-child {
  background: linear-gradient(var(--nav-hover), var(--nav-hover)), var(--group);
}

#patients-table tr.is-clickable,
#patients-today-table tr.is-clickable,
#patients-booked-table tr.is-clickable {
  cursor: pointer;
}

#patients-table tr.is-clickable:focus-visible,
#patients-today-table tr.is-clickable:focus-visible,
#patients-booked-table tr.is-clickable:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
  background: var(--nav-hover);
}

.po-row-link {
  color: inherit;
  text-decoration: none;
}

/* 列尾的 ›：原本只寫給舊清單，今天與預約那兩張表的 › 因此是一個沒有樣式、靠左的 16px 字。
   長相照舊（置中、20px、--ink-3），只是把既有選擇器擴充到兩張新表；寬度不在這裡給——
   fixed 版面的那兩張各自有百分比軌道（上面的欄寬段），舊清單仍是固定 40px。 */
#patients-table .po-row-chevron {
  width: 40px;
}

#patients-table .po-row-chevron,
#patients-today-table .po-row-chevron,
#patients-booked-table .po-row-chevron {
  padding: 0;
  text-align: center;
  font-size: 20px;
  color: var(--ink-3);
}

#patients-table tr.is-clickable:hover .po-row-chevron,
#patients-table tr.is-clickable:focus .po-row-chevron,
#patients-today-table tr.is-clickable:hover .po-row-chevron,
#patients-today-table tr.is-clickable:focus .po-row-chevron,
#patients-booked-table tr.is-clickable:hover .po-row-chevron,
#patients-booked-table tr.is-clickable:focus .po-row-chevron {
  color: var(--ink);
}

/* --- 三態（規格 §5）：讀取中、沒有資料、讀不到。三份清單共用。 --- */

/* 骨架列跟真的列一樣高，讀完換上資料時卡片不跳高度（既有的 .skeleton-row > td 是 56）。 */
#patients-table .skeleton-row > td,
#patients-today-table .skeleton-row > td,
#patients-booked-table .skeleton-row > td {
  height: 34px;
}

/* 沒有資料：至少 120 高，卡片不塌成一條縫。 */
#patients-table .empty-row td,
#patients-today-table .empty-row td,
#patients-booked-table .empty-row td {
  height: 120px;
  line-height: var(--lh-text);
}

/* 讀不到：錯誤留在表格裡（就在人正在看的那一塊），附「重試」，而且不自己消失——
   既有的 .inline-error.is-page 10 秒後自己 remove，留下一張一個字都沒有的空卡片。
   長相對齊看診畫面既有的 .po-error，但不畫整塊 --danger-wash 底：在清單裡一整列紅底太重。 */
.po-list-error {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  min-height: 120px;
  padding: var(--sp-5);
  color: var(--danger);
  font-weight: var(--fw-semibold);
  white-space: normal;
}

.po-list-error .btn-secondary {
  flex: none;
}

/* 錯誤那一列不吃列高、也不吃 hover（它不是一位病患）。 */
#patients-table .error-row > td,
#patients-today-table .error-row > td,
#patients-booked-table .error-row > td {
  height: auto;
  padding: 0;
  line-height: var(--lh-text);
}

tbody tr.error-row:hover {
  background: none;
}

/* 過敏小紅標：姓名後面，完整內容在提示裡（A24）。 */
.po-allergy-badge {
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  margin-left: var(--sp-2);
  padding: 0 6px;
  border-radius: var(--r-tag);
  background: var(--danger-wash);
  color: var(--danger);
  box-shadow: inset 0 0 0 1px currentColor;
  font: var(--fw-semibold) var(--fs-footnote) / 1 var(--font-ui);
  vertical-align: middle;
  cursor: help;
}

.po-allergy-badge.is-unreadable {
  background: var(--warn-wash);
  color: var(--warn);
}

/* 欄名旁的 ▲▼：沒排序時兩個都淡，正在排序只留目前方向。 */
.po-sort {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  margin-left: var(--sp-1);
  font-size: 10px;
  line-height: 1;
  color: var(--ink-3);
  vertical-align: middle;
}

.sort-heading.is-sorted .po-sort {
  color: var(--ink);
}

/* --- 看診畫面：頁首（第 1 行跟浮動的 .topbar 並排） --- */
/* 整個頁首是「一串東西依序換行」，不是「一塊一塊各佔一列」（使用者 2026-10-26：
   「移到名字那一排 你他媽的」「這裡空那麼大是怎樣」）。名字那一排與健保卡那一排都改成
   display: contents，兩邊的東西排在同一個換行流裡：放得下就同一行，放不下才換行，
   行尾的剩餘空間不會變成某一樣東西左邊的空白帶。 */
.po-banner {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  background: var(--canvas);
  padding-bottom: var(--sp-2);
}

/* 這四個只是「分組」用的盒子，不是版面上的一列：交給上面那個換行流自己排，
   才不會因為某一組整組塞不下而多折一整行（量過 1024×768 櫃台：差 2px 就多一列、頁首從 148 變 182）。 */
.po-banner-row,
.po-cardrow,
.po-flags,
.po-banner-right {
  display: contents;
}

/* 自己要獨佔一行的：補卡退還、PIN／就醫序號小表單、X 光按了才出現的那一句。 */
.po-deposit-slot:not([hidden]),
.po-nhi-form:not([hidden]),
.po-xray-note:not([hidden]) {
  flex-basis: 100%;
}

.po-back {
  white-space: nowrap;
}

.po-name {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-title1);
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  white-space: nowrap;
}

.po-name.is-unreadable {
  color: var(--ink-3);
}

/* 年齡・性別・病歷號：名字那一排上唯一可以讓的一段（其餘都是狀態或要按的東西）。
   放不下時截斷，不折行也不把旁邊的狀態標籤擠掉；min-width 是不讓它被壓到一個字都看不見。 */
.po-facts {
  flex: 0 1 auto;
  min-width: 5rem;
  margin: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--fs-body);
  color: var(--ink-2);
  font-feature-settings: var(--numerals);
}

.po-next {
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  white-space: nowrap;
}

.po-next[hidden] {
  display: none;
}

/* 名字那一排最後的幾樣：下次約診、約診、編輯資料、更多。刻意不靠右（沒有 margin-left: auto）：
   量過四種寬度兩種角色都不只一行，靠右的結果就是「孤零零一群、左邊空一大片」——正是
   使用者 2026-10-26 指的「這裡空那麼大是怎樣」。依序往左排，空白一律留在行尾。 */
.po-more-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.po-more-actions .btn-plain {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}

/* 上面的 display 蓋過了瀏覽器的 [hidden]（醫師看不到的「操作紀錄」）。 */
.po-more-actions [hidden] {
  display: none;
}

.po-more-menu .menu-body {
  min-width: 13rem;
}

/* 刪除：不加底色、紅字，排在選單最後（按了先確認）。 */
.menu-item.po-delete {
  width: 100%;
  padding: 0;
  background: none;
  color: var(--danger);
  font-weight: var(--fw-regular);
}

.menu-item.po-delete[hidden] {
  display: none;
}

/* 狀態與警示：過敏、病史、欠款。接在名字與年齡後面，不自成一列（.po-flags 是 display: contents，
   見上面那條）；文字不截斷，放不下就跟著整個頁首換行。 */
.po-flags[hidden] {
  display: none;
}

.po-flag {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 0 var(--sp-3);
  border-radius: var(--r-tag);
  box-shadow: inset 0 0 0 1px currentColor;
  font: var(--fw-semibold) var(--fs-body) / 1.3 var(--font-ui);
}

.po-flag-allergy,
.po-flag-owed {
  background: var(--danger-wash);
  color: var(--danger);
}

.po-flag-history,
.po-flag-allergy.is-unreadable {
  background: var(--warn-wash);
  color: var(--warn);
}

.po-flag-credit {
  background: var(--fill);
  color: var(--ink-2);
}

.po-flag-figure {
  margin: 0 2px;
  font-family: var(--font-display);
  font-size: var(--fs-title3);
  font-feature-settings: var(--numerals);
}

/* --- 健保卡動作列（display: contents，排版交給 .po-banner 那一個換行流） --- */

.po-cardrow.is-off .po-card-actions,
.po-cardrow.is-off .po-nhi {
  display: none;
}

.po-card-actions,
.po-role-actions,
.po-book-slot {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: none;
}

/* 第二排（健保卡＋X 光＋叫下一位／收費）照流程由左往右排，不把後面幾顆推到右邊角落：
   推到右邊會在中間留一條幾百 px 的空白帶（使用者 2026-10-26：「這裡空那麼大是怎樣」），
   而且下一步離上一步整整一個螢幕寬（§10.2 滑鼠移動）。 */
.po-xray-note {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.po-xray-note[hidden] {
  display: none;
}

.po-xray-note-text.is-bad {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.po-card-actions button,
.po-role-actions .btn-primary,
.po-role-actions .btn-secondary,
.po-book-slot .btn-secondary {
  min-height: var(--h-control-sm);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}

.po-role-actions a.btn-secondary {
  display: inline-flex;
  align-items: center;
}

/* 停用用 aria-disabled（提示才看得到）：外觀跟 button:disabled 一樣，不用 opacity。 */
.po-page button[aria-disabled="true"] {
  background: var(--disabled-bg);
  color: var(--disabled-ink);
  cursor: not-allowed;
}

.po-page button[aria-disabled="true"]:active {
  transform: none;
}

/* 破壞性的淡色文字鈕（看診畫面標題列的「刪除草稿」、報到頁的「退掛」）：class 掛在外層或按鈕本身都算。 */
.po-danger-text > button:first-child,
button.po-danger-text {
  color: var(--danger);
}

/* 問著的時候（dom.js confirmDangerButton／confirmDangerPrompt）：問句與兩顆按鈕之間要有間距，放不下
   就換行——問句比原本那一顆長得多，看板一列底下、窄螢幕都要放得下。收起來的時候不改版面（只有 is-asking 才套）。 */
.inline-confirm.is-asking {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

/* confirmDangerButton 的「沒做成」那一句（伺服器擋下的原因）。 */
.inline-confirm-failed {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

/* 讀卡訊息不再是「把後面的按鈕推到右邊」的墊片（flex: 1）：它只佔自己要的寬度，
   X 光與叫下一位就接在插卡那兩顆後面（§10.2 下一步在上一步旁邊）。 */
.po-nhi {
  display: flex;
  align-items: center;
  flex: 0 1 auto;
  flex-wrap: wrap;
  gap: var(--sp-2);
  min-width: 0;
}

.po-nhi-err {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  max-width: 100%;
  overflow: hidden;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-tag);
  background: var(--danger-wash);
  color: var(--danger);
  font: var(--fw-semibold) var(--fs-footnote) / 1.3 var(--font-ui);
}

form.po-inline-form {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  max-width: none;
}

.po-inline-form input {
  width: 9em;
  min-height: var(--h-control-sm);
}

.po-inline-form input.po-inline-note {
  /* 異常就醫序號的補充說明：提示字整句放得下，寬的時候再多佔一點 */
  flex: 1 1 11em;
  width: auto;
  min-width: 11em;
  max-width: 24em;
}

.po-inline-form select {
  width: auto;
  min-height: var(--h-control-sm);
}

.po-error {
  clear: both;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 56px;
  margin-bottom: var(--sp-3);
  padding: 0 var(--sp-4);
  border-radius: 12px;
  background: var(--danger-wash);
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.po-error[hidden] {
  display: none;
}

/* --- 版面：主欄＋右欄。1280 以上兩欄各自捲動，整頁不捲（spec §2.3）。 --- */
.po-work {
  clear: both;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-4);
  margin-top: var(--sp-3);
}

.po-work[hidden] {
  display: none;
}

.po-main {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
}

/* 右欄的卡片。
   align-items: start 是**這一條規則的重點**，不是順手加的（使用者 2026-10-26：「要會自動縮小」，
   附一張「最近治療」的截圖——最後一列貼在卡片頂端，下面空了約 200px 才是卡片的圓角下緣）：

   這是一個兩欄的格線，而格線項目預設 `align-items: stretch`，所以**同一列裡矮的那張卡會被
   拉到跟旁邊高的那張一樣高**。最近治療旁邊是約診，約診高，最近治療就被撐出一片空白。
   `align-content: start` 管的是「整堆列在容器裡怎麼擺」，管不到這件事——兩者差一個字、
   修不到同一個病，所以這一行不能刪。

   這一行放在**基底規則**上，不是放在某個 @media 裡：原本只有 900–1179px 那一段寫了
   align-items: start，於是 900 以下（768×1024 平板直放）與 1180–1279 都還在 stretch。
   「哪些寬度要縮」不是一個斷點問題，而是「格線項目不該被拉高」這個通則——寫在基底，
   新開一個斷點不必記得再補一次（§10.12）。1280 以上這一欄是 flex column，本來就不會拉高。 */
.po-side {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3);
  align-content: start;
  align-items: start;
  min-width: 0;
}

/* 1280 以下整頁捲、頁首黏在上面：「約診紀錄 N 次 ›」跳到北昕約診卡時，標題要停在
   頁首下面而不是被它蓋住（reviewer r7）。--po-top 是 patient-work.js 量的頁首底部。
   1280 以上右欄自己捲，不能加這段（會停在右欄頂往下 164px）。 */
@media (max-width: 1279px) {
  .po-side #patient-cooper-extras-title {
    scroll-margin-top: calc(var(--po-top, 164px) + var(--sp-2));
  }
}

@media (min-width: 1280px) {
  .po-work {
    grid-template-columns: minmax(0, 1fr) 360px;
  }

  /* --po-top：頁首＋卡片列的實際底部，patient-work.js 量好寫進來。 */
  .po-main,
  .po-side {
    max-height: calc(100dvh - var(--po-top, 164px) - var(--sp-4));
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .po-side {
    display: flex;
    flex-direction: column;
  }
}

/* --- 今天的病歷 --- */
.po-record {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
  padding: var(--sp-3) var(--sp-4) 0;
  border-radius: var(--r-group);
  background: var(--group);
}

.po-record.is-readonly {
  padding-bottom: var(--sp-3);
}

.po-rec-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  min-height: 40px;
}

.po-rec-when {
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  font-feature-settings: var(--numerals);
}

.po-rec-doctor {
  width: auto;
  min-height: var(--h-control-sm);
}

.po-rec-text {
  margin: 0;
}

.po-teeth {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-1);
  min-width: 0;
}

.po-teeth > .odonto {
  flex: 1;
  min-width: 0;
}

/* 圖例收進 ⓘ；處置表單裡另一份「這位病患的牙位圖」在這個畫面上重複了，藏起來。 */
.po-teeth .odonto-legend,
.po-record .odonto-details {
  display: none;
}

.po-text {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3);
}

/* 這裡原本寫 `.po-text textarea { min-height: 44px }`，2026-10-26 拿掉：那一條的權重（0,1,1）
   比 .clinical-text（0,1,0）高，會蓋掉共用的「收起來多高」而把帶子釘在 44，
   使得 doctor.css 設的 --ct-collapsed: 30px 看起來沒作用。
   收起來的高度現在只有一個來源：.clinical-text 的 --ct-collapsed（基底 44＝觸控下限，
   滑鼠下的帶子覆寫成 30）。 */

/* 片語排成一排、橫向捲動，不往下長三四行把處置輸入擠出首屏（spec §2.4）。 */
.po-text .phrase-chips {
  flex-wrap: nowrap;
  overflow-x: auto;
  padding-bottom: 2px;
}

.po-text .phrase-chips > * {
  flex: none;
}

.po-code-search {
  width: 100%;
  min-height: var(--h-control);
}

.po-proc .item-form {
  max-width: none;
}

/* 看診紀錄頁的處置面板搬進主欄：拿掉外框底色與內距、格子收窄，七欄在 796 寬裡放得下，
   列尾的修改／刪除不會被切掉（knowledge：寬表格在卡片內捲動）。 */
.po-record .items-panel {
  margin-top: 0;
  padding: 0;
  background: none;
}

.po-record .items-table th,
.po-record .items-table td {
  padding-left: var(--sp-2);
  padding-right: var(--sp-2);
  height: 44px;
}

.po-record .items-table td:first-child {
  min-width: 8em;
}

/* 整頁不捲：主欄、右欄的可用高度算到視窗底，頁面底部只留 16（spec §2.3）。 */
@media (min-width: 1280px) {
  body.po-working .content {
    padding-bottom: var(--sp-4);
  }
}

.po-rec-foot {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 52px;
  margin: 0 calc(-1 * var(--sp-4));
  padding: 0 var(--sp-4);
  border-top: 1px solid var(--hairline);
  border-radius: 0 0 var(--r-group) var(--r-group);
  background: var(--group);
}


.po-saved {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.po-saved.is-error {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.po-finish-slot {
  display: flex;
  align-items: center;
  margin-left: auto;
}

.po-finish-slot .btn-primary {
  min-height: var(--h-control);
}

.po-rec-note {
  flex: 1;
  margin: 0;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.po-rec-note.is-warning {
  color: var(--warn);
}

.po-skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
}

/* --- 右側資訊卡 --- */
.po-card {
  min-width: 0;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-group);
  background: var(--group);
}

.po-card[hidden] {
  display: none;
}

/* 卡片是 <section>：不要吃到頁框「section + section 隔 56px」的規則，間距由外層 gap 管。 */
section.po-card {
  margin-top: 0;
}

/* 卡片下緣還有第二種空白：**最後一個元素的下邊距從卡片裡 collapse 出來**
   （版面閘門 card_slack 量到：雲端查詢卡 8px、櫃台主欄的約診卡 16.5px）。
   這跟上面 align-items: start 修的不是同一件事——那個修「被旁邊的卡拉高」（約 200px），
   這個是「自己的尾巴多一截」（約 8～17px）。

   **試過一條通則把它收掉，結果更糟，已經放棄**：`.po-card` 底下所有 `:last-child` 的
   `margin-block-end` 設成 0 之後，1280×720 管理者已定稿畫面摺線下控制項從 3 顆 6.9px
   變成 4 顆 20.3px——拿掉一個已經 collapse 掉的邊距，會讓相鄰的另一個邊距改以自己的值生效，
   淨結果是變高；而且那條規則會打到每一張卡的每一個子樹，影響面遠大於它修的 8px。
   要修就得逐卡量、逐卡改，不要再用一條通則掃。已記在 backlog.md。 */

/* 只有一句狀態的卡：標題與狀態排同一行，整張卡就一列高。
   給「永遠都在、但這一刻沒有內容」的卡用（雲端藥歷還沒查過時）——卡片要在，才不會被當成
   功能消失（§10.10 第 1 條）；但右欄每一張卡都會把下面的往下推，所以沒有內容時只佔一列。
   量過：收成一行省約 28px，1280×720 已定稿畫面的「雲端查詢」三顆按鈕因此回到首屏。 */
.po-card-oneline {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  /* 上下內距收到 --sp-1：這張卡沒有內容，不必撐出一張「正常卡」的厚度。
     量出來的理由：1280×720 的右欄**原本剛好填滿首屏**（改前 below_fold＝0），所以這張一行卡
     每一 px 都會把下面的東西推到摺線下。12→4 省 16px，醫師與櫃台的「雲端查詢」那幾顆就回到首屏。 */
  padding-block: var(--sp-1);
}

/* 標題不留 28px 的行高下限（.po-card-title 那一條是給「標題列還要擺膠囊、按鈕」的卡用的，
   這張一行卡上沒有那些）。字級與字重不動——B3a 是凍結的視覺語言（§10.7），要省高度不靠改字級；
   實測這一條省 0px（29.4px 是 --fs-title3 的行框本身），留著是為了不讓別的卡的下限滲進來。 */
.po-card-oneline > .po-card-title {
  margin: 0;
  min-height: 0;
}

.po-card-oneline > .po-empty {
  margin: 0;
}

.po-main-cards {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.po-main-cards:empty {
  display: none;
}

.po-card-title {
  display: flex;
  align-items: center;
  min-height: 28px;
  margin: 0 0 var(--sp-1);
  font-family: var(--font-display);
  font-size: var(--fs-title3);
  font-weight: var(--fw-semibold);
}

.po-line {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 36px;
  font-size: var(--fs-body);
  color: var(--ink);
  text-decoration: none;
}

.po-line + .po-line {
  border-top: 1px solid var(--hairline);
}

.po-line-date {
  flex: none;
  color: var(--ink-2);
}

.po-line-main {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.po-line-aside {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

a.po-line.is-link::after {
  content: '›';
  flex: none;
  font-size: 20px;
  color: var(--ink-3);
}

a.po-line.is-link:hover {
  background: var(--nav-hover);
}

.po-line.is-cancelled .po-line-main {
  text-decoration: line-through;
  color: var(--ink-3);
}

.po-src {
  flex: none;
  padding: 0 6px;
  border-radius: var(--r-tag);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.po-empty {
  margin: 0;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.po-owed {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-body);
}

.po-owed.is-error {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.po-card-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.po-inactive-note {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.po-inactive-note[hidden] {
  display: none;
}

/* --- 七種看診狀態（visit-flow-spec §3；B1 只定樣式，全站套用在 B2） ---
   三層明度：實心深色（候診、超時、待收費）→ 淺色底（還沒到、看診中、未到）→
   沒有底（完成、取消）。深色主題的 token 會自動反轉，候診仍是對比最強的。 */
.po-st {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 26px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  font: var(--fw-semibold) var(--fs-footnote) / 1 var(--font-ui);
  white-space: nowrap;
}

.po-st-scheduled { background: var(--fill);        color: var(--ink-2); }
.po-st-waiting   { background: var(--ink);         color: var(--canvas); }
.po-st-waiting.is-overdue { background: var(--danger); color: var(--canvas); }
.po-st-in_chair  { background: var(--ok-wash);     color: var(--ok-ink); box-shadow: inset 0 0 0 1px currentColor; }
.po-st-to_charge { background: var(--warn);        color: var(--canvas); }
.po-st-done      { background: transparent;        color: var(--ink-3); }
.po-st-no_show   { background: var(--danger-wash); color: var(--danger); }
.po-st-cancelled { background: transparent;        color: var(--ink-3); text-decoration: line-through; }

/* --- 平板與手機（spec §2.10）：單欄、整頁捲動，頁首與卡片列一起黏在上方。 --- */
@media (max-width: 899px) {
  .po-name {
    font-size: var(--fs-title3);
  }

  .po-banner {
    background: color-mix(in srgb, var(--canvas) 85%, transparent);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
  }

  /* 窄畫面放不下就換行（「X 光」不再蓋住「就醫序號」，設計審查 D9），不左右捲。 */

  .po-side {
    grid-template-columns: minmax(0, 1fr);
  }

  /* 完成列黏在底部 tabbar 上方。 */
  .po-rec-foot {
    bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  }

  .po-list-bar input[type="search"] {
    flex-basis: 100%;
    max-width: none;
  }
}

@media (max-width: 640px) {
  .po-text {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* 觸控：可以點的東西至少 44（spec §0）。 */
@media (pointer: coarse) {
  #patients-table td {
    height: 44px;
  }

  .po-list-bar input[type="search"],
  .po-list-bar select,
  .po-list-bar > button,
  .po-list-bar .menu > summary.btn-secondary,
  .po-page button,
  .po-page select,
  .po-page input,
  .po-page .menu > summary,
  .po-page a.btn-secondary,
  .po-page a.btn-plain,
  .po-card-actions button,
  .po-role-actions .btn-primary,
  .po-role-actions .btn-secondary,
  .po-inline-form input,
  .po-inline-form select,
  .po-rec-doctor {
    min-height: 44px;
  }

  .po-line {
    min-height: 44px;
  }

  .po-page .info-tip {
    min-width: 44px;
    min-height: 44px;
  }
}

/* === 病患看診畫面 po-：設計審查第 1 輪修正 ============================== */

/* D1：登入列浮在頁首上面（sticky 的 .po-banner 會蓋住它）。 */
body[data-view="patients"] .topbar {
  position: relative;
  z-index: 4;
}

/* 北昕摘要放進頁首警示那一群：舊病患頁那套上下留白（名字下面自成一段）在這裡不要，跟警示同一行。 */
.po-flags .cooper-peek {
  margin: 0;
}

.po-more-menu .menu-item {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--h-control);
  color: var(--ink);
  text-decoration: none;
}

/* D3：1180 起就兩欄（1229×610 的桌機右欄不掉到下面），右欄 300；1280 起 360。 */
@media (min-width: 1180px) and (max-width: 1279px) {
  .po-work {
    grid-template-columns: minmax(0, 1fr) 300px;
  }

  .po-main,
  .po-side {
    max-height: calc(100dvh - var(--po-top, 164px) - var(--sp-4));
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .po-side {
    display: flex;
    flex-direction: column;
  }

  body.po-working .content {
    padding-bottom: var(--sp-4);
  }
}

/* D4：病歷區壓矮。主訴／所見平常一行高，聚焦時長到 4–6 行；牙齒 48 高。 */
.po-record {
  gap: var(--sp-2);
}

/* === 臨床自由文字欄（dom.js clinicalText）=====================================
   使用者 2026-10-26 原話：「你不覺得點下輸入框後 要會自動放大一個範圍的輸入框比較合理嗎?
   如果她說很多怎麼半?」

   收起來一行、點進去 4 行、內容再多跟著長到 6 行、離開縮回一行。
   **整個系統只有這一份**：主訴、臨床所見、處置說明、補充記錄都掛 .clinical-text
   （以前主訴／所見是 `.po-text textarea` 一份、其他欄位沒有，等於同一個用途三種行為，§10.12）。
   每個使用處只覆寫 --ct-collapsed（收起來多高），長高的規則不複製。

   三個 class 共用同一個長高狀態，三個不同的理由，缺一個就會出事：
     :focus         ——游標在裡面。
     .is-held       ——滑鼠正按在別處（dom.js）：失焦縮回會讓下面整片往上跳，放開時已經不在
                      按下去的那一項上，那一下 click 就落空（打完主訴第一下點代碼永遠沒加入）。
     .is-dictating  ——正在口述（dictation.js）：按「語音」時焦點離開欄位，不撐住的話講出來的
                      一整段就擠在一行的框裡看不到。 */
.clinical-text {
  --ct-collapsed: 44px;
  --ct-open-min: 4lh;
  --ct-open-max: 6lh;
  --ct-pad: var(--sp-2);
  height: var(--ct-collapsed);
  min-height: var(--ct-collapsed);
  /* 自己會長高，就不要再給一個跟它打架的拉把（平板上也按不到）。 */
  resize: none;
}

.clinical-text:focus,
.clinical-text.is-held,
.clinical-text.is-dictating {
  height: calc(var(--ct-open-min) + 2 * var(--ct-pad));
}

/* 支援 field-sizing 的瀏覽器：照內容長高，下限 4 行、上限 6 行。打一行主訴不會先自己撐成 4 行把
   選碼器往下推，1024×768 時 91004C 留在首屏（smooth-ops A7）；講了一大段也長得到 6 行，
   超過 6 行時框自己捲（overflow-y: auto），**不會往外推成橫向捲動**（寬度始終是欄寬）。 */
@supports (field-sizing: content) {
  .clinical-text:focus,
  .clinical-text.is-held,
  .clinical-text.is-dictating {
    field-sizing: content;
    height: auto;
    min-height: calc(var(--ct-open-min) + 2 * var(--ct-pad));
    max-height: calc(var(--ct-open-max) + 2 * var(--ct-pad));
    overflow-y: auto;
  }
}

/* 縮回一行、而且內容被切掉了（dom.js 量 scrollHeight 後掛 .is-clipped）：下緣淡出，
   讓人看得出「還有」。用漸層不用文字——畫面上不放常駐說明文字（§10.3），而且這是狀態不是說明。
   mask 只吃自己的最後幾 px，不影響版面高度、不佔寬度。 */
.clinical-text.is-clipped:not(:focus):not(.is-held):not(.is-dictating) {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 10px), transparent 100%);
}

/* 基底的 44 就是觸控目標的下限（spec §0：可以點的東西至少 44）。要把它壓矮的地方
   （主訴／所見那條帶子壓成 30）自己用 @media (pointer: fine) 包起來——在這裡寫一條
   `@media (pointer: coarse) { .clinical-text { … } }` 是**沒有用的**：那條選擇器的
   權重比 `.po-record .po-text-band .clinical-text` 低，權重贏過順序，壓矮的那一份照樣生效。 */

/* .po-field-head（欄名＋麥克風＋片語排一行）在 2026-10-26 拿掉了：語音與片語搬到整條帶子
   最右邊的 .po-band-tools，欄名自己跟輸入框排一行（doctor.css .po-text-band .phrase-label）。 */

.po-phrase-menu > summary.btn-plain {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  font-size: var(--fs-footnote);
}

/* 選單往左長：這一顆在整條帶子的最右邊，往右長會掉出視窗外。 */
.po-phrase-menu .menu-body {
  left: auto;
  right: 0;
  width: min(28rem, 80vw);
}

.po-phrase-menu .phrase-chips {
  flex-wrap: wrap;
  overflow: visible;
}

/* 片語選單裡不該有麥克風：整條帶子只有一顆語音（.po-band-tools 那一顆）。
   共用的 phraseBar 在「一份片語管多欄」模式下本來就不放麥克風（phrases.js mic 要有固定欄位
   才放），這一條是第二道保險——有人把它放回來的話畫面上不會又變成兩顆。 */
.po-phrase-menu .dictation {
  display: none;
}

.po-deciduous {
  font-size: var(--fs-footnote);
}

.po-deciduous[hidden] {
  display: none;
}

/* 牙位圖：牙 48 高、列標籤 40 寬（上下兩字疊起來）、牙間距 2、中線留白 6，
   平板直放（768）一排 16 顆每顆也有 38 寬（D6）。 */
.po-teeth .tooth-grid {
  gap: var(--sp-2);
  --tooth-w: clamp(30px, (100cqi - 40px - 2 * var(--sp-2) - 6px - 14 * 2px - 2px) / 16, 44px);
}

.po-teeth .tooth-row {
  gap: var(--sp-2);
}

.po-teeth .tooth-row-label {
  width: 40px;
  line-height: 1.2;
}

.po-teeth .tooth-half {
  gap: 2px;
  width: calc(8 * var(--tooth-w) + 7 * 2px);
}

.po-teeth .tooth-half:first-of-type {
  padding-right: 6px;
  width: calc(8 * var(--tooth-w) + 7 * 2px + 6px);
}

.po-teeth .odonto-tooth {
  height: 48px;
  min-height: 48px;
  gap: 0;
}

.po-teeth .odonto-marks {
  min-height: 14px;
}

/* D5：處置面板的緊湊模式（treatment-items.js itemsPanel compact）。沒有處置時只有
   一格代碼搜尋；選了處置才展開牙面、診斷、數量；牙位用上方牙位圖。 */
.items-panel.is-compact .item-form-title,
.items-panel.is-compact [data-field="牙位"] {
  display: none;
}

.items-panel.is-compact .item-form:not(.has-procedure) > :not([data-field="處置"]) {
  display: none;
}

/* 存好一項後表單收起來，但洗牙提醒（例如「可同時報 91014C」＋「加入」）要留在畫面上（第 4 批 A21）。 */
.items-panel.is-compact .item-form:not(.has-procedure) > .scaling-reminders:not([hidden]) {
  display: block;
}

/* Esc 時「這一項還沒存，要離開嗎？」：處置清空了只剩診斷時也要看得到（QA B4 r1）。 */
.items-panel.is-compact .item-form:not(.has-procedure) > .leave-confirm {
  display: flex;
}

.items-panel.is-compact .item-form {
  gap: var(--sp-2);
}

.items-panel.is-compact [data-field="處置"] > .item-field-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.items-panel.is-compact .items-scroll[hidden] {
  display: none;
}

.po-proc .catalog-picker input,
.po-code-search {
  min-height: var(--h-control);
}

/* D9：櫃台主欄的約診、收費兩張卡並排。 */
.po-main-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}

@media (max-width: 899px) {
  /* 平板直放、手機：病歷卡內距收窄，16 顆牙每顆仍有 38 寬。 */
  .po-record {
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
  }

  .po-rec-foot {
    margin: 0 calc(-1 * var(--sp-2));
    padding: 0 var(--sp-2);
  }

  /* D7：清單工具列兩行：搜尋框自己第一行，其餘第二行。 */
  #patient-search {
    order: -1;
    flex-basis: 100%;
  }

  .po-main-cards {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* D6：觸控時這些也要 44。 */
@media (pointer: coarse) {
  .sort-heading {
    min-height: 44px;
  }

  .po-banner .po-next,
  .po-more-menu > summary,
  .po-phrase-menu > summary.btn-plain,
  .po-deciduous {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    min-width: 44px;
  }

  /* 上面的 inline-flex 跟 .po-next[hidden] 同權重、又寫在後面，會把藏起來的「下次」
     又顯示出來（觸控裝置換病患時露出上一位的下次約診，QA r5）。這裡再藏一次。 */
  .po-banner .po-next[hidden] {
    display: none;
  }
}

/* D2（續）原本讓卡片列自己橫向捲（卡片列不折行）。2026-10-26 頁首改成一個換行流之後
   （.po-cardrow 是 display: contents，自己不再是一個盒子）橫捲規則不成立也不需要：
   放不下就跟著整個頁首換行，不會有「捲了才看得到的按鈕」（§10.4）。
   結果小標（就醫序號、模擬）仍然整組不折。 */
.po-nhi {
  flex-wrap: nowrap;
}

.po-nhi > * {
  flex: none;
}

.po-nhi > .po-nhi-err {
  flex: 0 1 auto;
  min-width: 12em;
}

/* 兩排牙之間收成 4（1536×730 時「加處置」底邊壓在 480 以內）。 */
.po-teeth .tooth-grid {
  gap: var(--sp-1);
}

/* D5（續）：緊湊模式的新增表單不再是一張內縮的卡，代碼搜尋跟草稿建立前那格對齊；
   處置列的「修改／刪除」排成一行，列不被撐成兩倍高。 */
.items-panel.is-compact .item-form {
  margin-top: var(--sp-2);
  padding: 0;
  background: none;
  border-radius: 0;
}

.po-record .items-table .action-group {
  flex-wrap: nowrap;
}

/* 沒有看診醫師、草稿建不起來：醫師欄標紅（不搶焦點）。 */
.po-rec-doctor.is-missing {
  border-color: var(--danger);
  box-shadow: inset 0 0 0 1px var(--danger);
}

/* === 病患看診畫面 po-：QA 與設計審查第 2 輪 ================================ */

/* QA A43：PIN、異常就醫序號的小表單自己一行，在卡片列下面，不疊到約診、收費。 */
.po-nhi-form {
  display: flex;
  align-items: center;
  min-height: var(--h-control);
  margin-top: var(--sp-1);
}

.po-nhi-form[hidden] {
  display: none;
}

/* 設計 D8′：停用的卡片按鈕跟 .btn-primary:disabled 同一組 token。 */
.po-cardrow .btn-secondary[aria-disabled="true"],
.po-cardrow .btn-primary[aria-disabled="true"] {
  background: var(--disabled-bg);
  color: var(--disabled-ink);
}

/* 設計第 2 輪：「草稿」標一開始就佔位置（看不見），建草稿時不跳。 */
.pill.is-placeholder {
  visibility: hidden;
}

.po-adopt-note {
  margin: 0;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.po-adopt-note.is-warning {
  color: var(--warn);
  font-weight: var(--fw-semibold);
}

/* A6：最近治療的主訴淡色接在處置後面。 */
.po-line-sub {
  color: var(--ink-3);
  font-size: var(--fs-footnote);
}

/* QA 7：代碼搜尋釘在處置清單上面——處置一多，搜尋框照樣在原地，不用往下捲。 */
.items-panel.is-compact {
  display: flex;
  flex-direction: column;
}

.items-panel.is-compact > .item-form {
  order: -1;
  margin-top: 0;
  margin-bottom: var(--sp-2);
}

/* QA r2 A15 D2：選了處置後的欄位標籤放在左邊同一行（不另佔一行），「新增項目」排在數量
   那一行；1536×864 不捲動就按得到。 */
.items-panel.is-compact .item-form > .item-field:not([data-field="處置"]) {
  flex-direction: row;
  align-items: center;
  gap: var(--sp-3);
}

.items-panel.is-compact .item-form > .item-field:not([data-field="處置"]) > .item-field-label {
  flex: none;
  width: 4.5em;
}

.items-panel.is-compact .item-form > .item-field > :not(.item-field-label):not(.form-actions):not(.item-quantity) {
  flex: 1 1 auto;
  min-width: 0;
}

.items-panel.is-compact .item-form > .item-field > .item-quantity {
  flex: none;
}

.items-panel.is-compact [data-field="數量"] > .form-actions {
  margin: 0 0 0 auto;
}

/* QA 8：代碼搜尋的清單蓋在黏底的完成列上面，第 5 個選項不被擋住。 */
.po-rec-foot {
  z-index: 1;
}

.po-record .catalog-results {
  z-index: 2;
}

/* QA 6：平板觸控時牙齒的可點範圍 44 寬：列標籤放到那一排上面（不佔左邊 40），牙齒之間
   不留縫，視覺上照樣 40 寬（左右各 2 透明邊）；16×44＋中線 2＝706，768 直放放得下。 */
@media (pointer: coarse) {
  .po-teeth .tooth-grid {
    --tooth-w: 44px;
  }

  .po-teeth .tooth-row {
    flex-wrap: wrap;
    gap: 0;
  }

  .po-teeth .tooth-row-label {
    flex-basis: 100%;
    width: auto;
    line-height: 1.4;
  }

  .po-teeth .tooth-half {
    gap: 0;
    width: calc(8 * 44px);
  }

  .po-teeth .tooth-half:first-of-type {
    padding-right: 2px;
    width: calc(8 * 44px + 2px);
  }

  .po-teeth .odonto-tooth {
    border-left: 2px solid transparent;
    border-right: 2px solid transparent;
    background-clip: padding-box;
  }

  /* QA 6：病歷與處置列上的按鈕也要 44。 */
  .po-record button,
  .po-record select,
  .po-record input,
  .po-record .catalog-option {
    min-height: 44px;
  }
}

/* === 病患看診畫面：就地約診／改約對話框（QA r2 A15 C2、C7） ====================
   上面的欄位固定、空檔清單吃掉剩下的高度並自己捲、確認鈕黏在底部：1536×864、
   1024×768、768×1024 都不必捲整個對話框就按得到「手動指定時間」與確認。 */
dialog.po-booking {
  width: min(36rem, calc(100vw - 2 * var(--sp-4)));
  max-height: calc(100dvh - 2 * var(--sp-4));
  margin: auto;
  padding: var(--sp-4) var(--sp-5);
  border: 0;
  border-radius: var(--r-group);
  background: var(--group);
  color: var(--ink);
  box-shadow: 0 12px 40px rgb(0 0 0 / 0.25);
  overflow: hidden;
}

dialog.po-booking[open] {
  display: flex;
  flex-direction: column;
}

dialog.po-booking::backdrop {
  background: rgb(0 0 0 / 0.35);
}

.po-booking .booking {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.po-booking .booking > * {
  flex: none;
}

.po-booking .panel-field {
  margin-top: var(--sp-2);
}

/* 共用的 .panel-label 自帶上方 sp-4，放在 .panel-field 裡等於每一列多 16px；五列下來
   1024×768 的「約診管道／備註」就被黏底確認鈕蓋住（QA r3 A16）。欄位間距交給
   .panel-field 的 margin 就好。 */
.po-booking .panel-label {
  margin-top: 0;
}

.po-booking .booking > p.muted {
  margin: var(--sp-1) 0 0;
}

.po-booking .slot-list {
  flex: 1 1 auto;
  min-height: 3rem;
  max-height: none;
  margin-top: var(--sp-2);
}

.po-booking .booking-foot {
  position: sticky;
  bottom: 0;
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  background: var(--group);
}

/* 改約面板很窄：時長「－ 30 分鐘 ＋」不換行、不貼卡片邊（數字用小一級的字、不留 4em 底寬，設計審查 r1 #1）。 */
.po-booking .stepper,
.side-panel .booking-length {
  flex-wrap: nowrap;
  max-width: 100%;
  padding-inline-end: var(--sp-2);
}

.po-booking .stepper-value,
.side-panel .booking-length .stepper-value {
  min-width: 0;
  padding: 0;
  font-size: var(--fs-title3);
  white-space: nowrap;
}

.po-booking .booking-confirm {
  width: 100%;
  min-height: var(--h-control);
}

/* 平板與觸控：時長 −／+、只看的時段 chip、「手動指定時間」原本 40px，照本檔平板觸控
   規則（同一個斷點：pointer: coarse 或 1366 以下）放大到 44（QA r3 A16）。 */
@media (pointer: coarse), (max-width: 1366px) {
  .po-booking .stepper button {
    width: 44px;
    height: 44px;
    min-height: 44px;
  }

  .po-booking .chip,
  .po-booking .booking-manual > summary {
    min-height: 44px;
  }
}

/* 備份與還原（spec 2026-10-23-data-protection-phase1 §7）。本地 QA D4、D5：
   - 最上面一行狀態＋動作列（立即驗證全部、印復原碼），接著還原（時間點選單與按鈕同一行），
     1536×864 第一個畫面就做得到這三件事；
   - 每一處的表格固定欄寬（table-layout: fixed），任何一格都可以在任何位置斷行：base.css 讓
     第一欄不換行，網路路徑一長就把整張表撐寬，平板橫放出現左右捲動、狀態欄一個字一行；
   - 路徑另起一行小字、最多兩行；1100px 以下（平板橫放、直放）每一處改成一張卡片：
     那時內容區不到 750px，五欄的表格按鈕會擠出欄外。按鈕至少 44px 高，手指點得到。 */
.bk-top .gcal-status { margin-top: 0; }
.bk-bar { margin-top: var(--sp-3, 12px); }
.bk-pick {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-3, 12px);
}
.bk-pick-point { flex: 1 1 22rem; min-width: 0; }
.bk-pick #bk-preview { flex-basis: 100%; margin: 0; }
.bk-table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
}
.bk-col-place { width: 24%; }
.bk-col-state { width: 34%; }
.bk-col-time { width: 13%; }
.bk-col-act { width: 16%; }
.bk-table th { white-space: normal; }
.bk-table td,
.bk-table td:first-child {
  white-space: normal;
  overflow-wrap: anywhere;
  vertical-align: top;
}
/* 路徑最多兩行，多的用 …，完整的在 title（滑過看得到）。 */
.bk-dir {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  font-size: var(--fs-footnote);
  color: var(--ink-3, #667085);
}
.bk-table td.is-error { color: var(--danger, #b42318); font-weight: 600; }
.bk-table td.bk-actions button { margin: 0 0.4em 0.4em 0; max-width: 100%; }
#view-backups button,
#view-backups .btn-secondary,
#view-backups select,
#view-backups input[type="password"] { min-height: 44px; }
#view-backups input[type="checkbox"] { width: 22px; height: 22px; }
#view-backups select { width: 100%; max-width: 100%; }
.test-mode-banner {
  margin: 0;
  padding: var(--sp-2, 8px) var(--sp-4, 16px);
  background: var(--danger, #b42318);
  color: #fff;
  font-weight: 700;
  text-align: center;
  margin-bottom: var(--sp-3, 12px);
}
@media (max-width: 1100px) {
  .bk-table colgroup, .bk-table thead { display: none; }
  .bk-table, .bk-table tbody, .bk-table tr, .bk-table td { display: block; width: 100%; }
  .bk-table tr {
    border: 1px solid var(--line, #d0d5dd);
    border-radius: 12px;
    padding: var(--sp-2, 8px) var(--sp-3, 12px);
    margin-bottom: var(--sp-2, 8px);
  }
  .bk-table td { padding: 2px 0; border: 0; height: auto; }
  .bk-table td[data-label]:not([data-label=""])::before {
    content: attr(data-label) "：";
    color: var(--ink-3, #667085);
  }
  .bk-table td.bk-place[data-label]::before { content: none; }
  .bk-table td.bk-place { font-weight: 600; }
  .bk-table td.bk-place .bk-dir { font-weight: 400; }
}

/* 設定 → 文件範本（print-templates.js）：1536×864 一打開就看得到分頁、標題、
   儲存與整個內文框，不用捲；按鈕跟標題同一列。 */
body[data-view="print-templates"] {
  --content-max: 1100px;
}

.print-template-tabs {
  flex-wrap: wrap;
  margin-bottom: var(--sp-3);
}

/* 改了還沒存的分頁：標一個點。 */
.print-template-tabs button.is-dirty::after {
  content: " ●";
  color: var(--warn);
}

#print-template-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  max-width: none;
}

.print-template-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-3) var(--sp-4);
}

.print-template-head label {
  flex: 0 1 18em;
}

.print-template-head .form-actions {
  margin-top: 0;
}

.print-template-legal {
  margin: 0;
  color: var(--warn);
  font-size: var(--fs-footnote);
}

.print-template-fields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
}

.print-template-body {
  width: 100%;
  min-height: 18em;
  font-family: var(--font-ui);
  line-height: 1.6;
  resize: vertical;
}

/* --- 斷網／主機當機第一階段（spec 2026-10-23-offline-checkin-design §9 階段 1） ---------- */

/* 狀態列三燈（links.js）：主機／外網／健保，各一個圓點加兩個字，滑過看狀態。
   有三燈時原本單一的「已連線」收起來（它只說主機），兩者不同時出現。
   用 display 而不是 hidden：.sidebar-live 有 display:inline-flex，hidden 蓋不過它。 */
.sidebar-status-line.has-links .sidebar-live {
  display: none;
}

.sidebar-links {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  margin: 0;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.link-light {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  white-space: nowrap;
}

.link-light::before {
  content: '';
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 50%;
  /* 還不知道：空心灰圈，不像綠燈也不像紅燈。 */
  border: 1.5px solid var(--ink-3);
}

.link-light.is-ok::before {
  border-color: var(--ok);
  background: var(--ok);
}

.link-light.is-down {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.link-light.is-down::before {
  border-color: var(--danger);
  background: var(--danger);
}

/* 外網或健保 VPN 斷了的那一句白話（links.js）：每一頁頂端，平板、手機也看得到
   （側欄在 900px 以下收進「更多」）。 */
.links-alert {
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--warn-wash);
  color: var(--warn);
  font-weight: var(--fw-semibold);
}

.links-alert[hidden] {
  display: none;
}

.links-alert p {
  margin: 0;
}

.links-alert p + p {
  margin-top: var(--sp-1);
}

/* 現場掛號（walk-in.js）：一張卡，病患搜尋框在最上面，醫師／診療椅只在有得選時出現。 */
.walkin-panel {
  margin-top: var(--sp-4);
}

/* 今天看板上的現場掛號：跟下面的清單隔開、上下留 20（設計審查 D7）。 */
#view-checkin .walkin-panel {
  margin-top: 0;
  margin-bottom: var(--sp-3);
  padding-block: var(--sp-5);
}

.walkin-panel[hidden] {
  display: none;
}

.walkin-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

.walkin-head h3 {
  margin: 0;
}

.walkin-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin-top: var(--sp-2);
}

.walkin-field {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.walkin-field[hidden] {
  display: none;
}

.walkin-status {
  margin: var(--sp-2) 0 0;
}

.walkin-status:empty {
  display: none;
}

.walkin-done {
  margin: 0;
  color: var(--ok-ink);
  font-weight: var(--fw-semibold);
}

/* 叫號頁的「下一號 N」與管理者的起跳號（queue-floor.js）。 */
.queue-next {
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  white-space: nowrap;
}

.queue-floor-open[hidden],
.queue-floor-form[hidden] {
  display: none;
}

.queue-floor-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  margin: 0 0 var(--sp-4);
}

.queue-floor-label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.queue-floor-label input {
  width: 6em;
}

.queue-floor-form .queue-note:empty {
  display: none;
}

/* 起跳號表單的按鈕：不用 .queue-actions（平板上它是一列三等分的格子，給候診列用）。 */
.queue-floor-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}

/* 報到頁「列印備援單」：這一段診前還沒印時亮起來（fallback-reminder.js），印了就恢復。 */
#fallback-print.is-due {
  background: var(--warn-wash);
  color: var(--warn);
  box-shadow: inset 0 0 0 2px var(--warn);
}

/* 主機連不到的橫幅是 position: fixed，會蓋住頁面最上面那一列（報到頁的「讀健保卡」
   「現場掛號」「列印備援單」）。橫幅出現時把整個畫面往下推，按鈕照樣按得到（B10 QA）。 */
body:has(> #connection-banner:not([hidden]):not(.is-ok)) > .app {
  margin-top: 3.5rem;
}

@media (max-width: 899px) {
  body:has(> #connection-banner:not([hidden]):not(.is-ok)) > .app {
    margin-top: 5rem;
  }
}

/* --- 洗牙卡 .po-scaling（第 4 批，設計 §2.8）：每列三行——代碼＋名稱＋狀態、下次／上次、規則白話。 --- */
.po-scale-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-2) 0;
}

.po-scale-row + .po-scale-row {
  border-top: 1px solid var(--hairline);
}

/* 這位病患該報的代碼（suggested）：左邊一條強調色線，其他列不變淡（設計退修 D1）。 */
.po-scale-row.is-suggested {
  box-shadow: inset 3px 0 0 var(--accent);
  padding-left: 12px;
  border-radius: 0;
}

/* 列可以 Tab 到、點一下看提示（D2）。外框往外 3px：往內會壓到字（設計退修 R2-1）。 */
.po-scale-row:focus-visible {
  outline: 2px solid var(--ink-2);
  outline-offset: 3px;
  border-radius: 8px;
}

/* 提示開著的那一列（tipOn 在列上設 aria-describedby）：平板點一下沒有 hover，要有回饋（R2-2）。 */
.po-scale-row[aria-describedby="info-tip-pop"] {
  background: var(--nav-hover);
  border-radius: 8px;
}

/* 平板橫放（右欄兩欄格線）：洗牙卡較高，跨兩列，最近治療與約診疊在旁邊，不撐出空白（D4）。
   `align-items: start` 原本寫在這裡，2026-10-26 搬到基底的 .po-side 去了：只在這一段寫，
   900 以下與 1180–1279 的卡片照樣被拉高（使用者就是在那種寬度上看到最近治療空 200px）。 */
@media (min-width: 900px) and (max-width: 1179px) {
  /* 跨兩列的卡比旁邊兩張高時，多出來的高度全給第 2 列（1fr），最近治療與約診才會緊貼著疊。 */
  .po-side:has(> #po-scaling-card:not([hidden])) {
    grid-template-rows: min-content 1fr;
  }

  #po-scaling-card {
    grid-row: span 2;
  }
}

.po-scale-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  min-height: 28px;
  font-size: var(--fs-body);
}

.po-scale-code {
  font-weight: var(--fw-semibold);
}

.po-scale-name {
  /* 名稱不擠成直排：放不下就整行換到下一行（設計審查 D8）。 */
  flex: 1 0 auto;
  white-space: nowrap;
}

.po-scale-dates {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  font-size: 16px;
  color: var(--ink-2);
}

/* 只剩「上次」（不該報的列不寫下次）時仍靠右，跟其他列對齊。 */
.po-scale-last {
  margin-left: auto;
  color: var(--ink-3);
}

/* 改報／併同的列：這位病患不該報，第 2 行整行淡色（R2-3）。 */
.po-scale-dates.is-muted {
  color: var(--ink-3);
}

.po-scale-rule {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.po-scale-hint {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-footnote);
  color: var(--warn);
}

.po-scaling .po-card-title {
  gap: var(--sp-2);
}

/* --- 櫃台「今天」看板（visit-flow-spec §2，看診流程第 2 批；js/checkin.js） ---
   一份清單分五區，每區一張淺色卡片（--group）；每列一顆「下一步」。狀態標用 .po-st-*（七態，全站同一組）。 */
.po-today-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  min-height: 44px;
  margin-bottom: var(--sp-3);
}

.po-today-head .section-title {
  margin: 0;
}

/* 標題旁的換日（A23）：‹ 日期 › 回今天一組，不折行；日期本身是按鈕（點了開日期選擇），樣子同約診頁的日期。 */
.po-today-day {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
  position: relative;
}

button.po-today-date {
  min-height: 40px;
  background: none;
  border: 0;
  border-radius: 10px;
  color: var(--ink);
  white-space: nowrap;
}

button.po-today-date:hover {
  background: var(--nav-hover);
}

/* 看以前的某一天：日期換成淡底的膠囊，一眼看得出「這不是今天」（不寫字說明）。 */
button.po-today-date.is-past {
  background: var(--warn-wash);
  color: var(--ink);
}

/* 日期選擇本身不佔版面：點日期時叫出它（showPicker），位置貼著日期。 */
.po-today-pick {
  position: absolute;
  left: 48px;
  bottom: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
  border: 0;
  padding: 0;
}

/* 只屬於今天的按鈕與區塊（讀健保卡、現場掛號、備援單、讀卡機狀態、插卡通知、健保取號明細）：看以前的某一天時收起來。 */
#view-checkin.is-past .today-only {
  display: none;
}

.po-today-reader {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: auto;
}

.po-today-reader[hidden] {
  display: none;
}

/* 讀卡自動補資料的那一行（card-fix.js）。 */
/* 一條一行（設計審查 B2 r2 R2-3）：已補上的與每一條不一樣的各自一行。 */
.card-fix,
.po-today-panel.card-fix {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 4px;
}

.card-fix .card-fix-accept {
  min-height: 28px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-footnote);
}

.card-fix-line {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
}

.po-card-text {
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}

.po-card-text.is-ok { color: var(--ok-ink); }
.po-card-text.is-muted { color: var(--ink-3); font-weight: var(--fw-regular); }
.po-card-text.is-warn { color: var(--warn); }
.po-card-text.is-danger { color: var(--danger); }

/* 插卡的通知：一行（設計審查 D5），說明收進提示，✕ 跟在後面。 */
.po-card-found {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  min-height: 44px;
  margin-bottom: var(--sp-3);
  padding: 0 var(--sp-3);
  border-radius: var(--r-input);
  background: var(--fill);
  font: var(--fw-semibold) var(--fs-body) / var(--lh-ui) var(--font-ui);
}

.po-card-found-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.po-card-found[hidden] {
  display: none;
}

.po-card-found.is-danger .po-card-found-text {
  color: var(--danger);
}

.po-card-found .btn-plain {
  min-height: 44px;
}

.card-result {
  position: relative;
}

.card-result-close {
  position: absolute;
  top: var(--sp-1);
  right: var(--sp-2);
}

.po-today {
  container-type: inline-size;
}

/* 整個看板一張格線，每一區、每一列用 subgrid 對齊同一組欄：各列的欄寬一樣，眼睛直著掃得下去。
   欄：時間｜姓名（標記）｜醫師・椅｜處置｜卡況｜狀態｜缺什麼｜下一步｜次要動作。下一步單獨一欄、靠右對齊：
   每一列的主要按鈕在同一條直線上（設計審查 B2 r1 D2）。 */
.po-today-grid {
  display: grid;
  /* 姓名欄有上限、醫師・椅 7–12em（設計審查 B2 r2 R2-1：max-content 的姓名欄被標記撐寬，整張表超出畫面要橫捲）。 */
  grid-template-columns: 56px fit-content(20em) minmax(7em, 12em) minmax(0, 1fr) max-content max-content max-content max-content max-content;
  column-gap: var(--sp-3);
}

.po-today-grid > .po-sec,
.po-today-grid .po-sec-rows,
.po-today-grid .po-today-row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

.po-sec-head,
.po-sec-empty {
  grid-column: 1 / -1;
}

.po-sec + .po-sec {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 2px solid var(--hairline-strong);
}

.po-sec-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 32px;
  margin: 0;
  padding: 0 var(--sp-3);
  border-radius: 8px 8px 0 0;
  background: var(--fill);
  color: var(--ink);
  font: var(--fw-semibold) var(--fs-body) / var(--lh-ui) var(--font-ui);
}

/* 「看完了」整條標題都是開合鈕（設計審查 B2 r1）。 */
.po-sec-head.is-toggle {
  cursor: pointer;
}

.po-sec-count {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: var(--numerals);
}

.po-sec-toggle {
  flex: 1;
  min-height: 32px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.po-sec-toggle::after {
  content: " ▾";
}

.po-sec-toggle[aria-expanded="true"]::after {
  content: " ▴";
}

.po-sec-rows {
  border-radius: 0 0 8px 8px;
  background: var(--group);
}

.po-sec-empty {
  margin: 0;
  padding: var(--sp-1) var(--sp-3);
  color: var(--ink-3);
}

.po-today-row {
  align-items: center;
  min-height: 34px;
  padding: 2px var(--sp-3);
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-footnote);
  cursor: pointer;
}

.po-sec-rows > .po-today-row:first-child {
  border-top: 0;
}

.po-today-row:hover {
  background: color-mix(in srgb, var(--fill) 45%, transparent);
}

.po-today-row:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
}

.po-today-row.is-highlight {
  background: var(--warn-wash);
}

.po-today-time {
  font-size: var(--fs-body);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: var(--numerals);
}

/* 姓名與標記一行，不換行（設計審查 D3：換行會把整列撐成兩倍高）。 */
.po-today-who {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--sp-1);
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

/* 標記放不下時截掉的是標記不是姓名（完整內容在提示與 title 裡；設計審查 B2 r2 R2-2）。 */
.po-today-who > .po-today-name {
  flex: none;
}

/* 先截身障的長標記，過敏、押金、自費這些短而要緊的不截。 */
.po-today-who > :not(.po-today-name) {
  flex: none;
}

.po-today-who > .po-care-chip {
  flex: 0 1 auto;
  min-width: 6em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 身障證明到期、押金：一律在第二行（設計審查 B2 r3 R3-2）。寬的時候是姓名底下自己一格；中等寬度以下卡況已經在第二行，
   改跟在卡況後面（checkin.js 兩處都畫、CSS 擇一顯示）。 */
.po-today-card .po-who-extra {
  display: none;
}

.po-today-row > .po-today-extra {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
  grid-row: 2;
  grid-column: 2 / 5;
  padding-bottom: 2px;
}

/* 自費等標記離醫師欄遠一點。 */
.po-today-who {
  padding-inline-end: var(--sp-2);
}

.po-today-name {
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

a.po-today-name {
  text-decoration: none;
}

a.po-today-name:hover {
  text-decoration: underline;
}

.po-care-chip {
  padding: 0 6px;
  border-radius: var(--r-tag);
  font-weight: var(--fw-semibold);
  line-height: 22px;
}

.po-care-chip.is-warn { background: var(--warn-wash); color: var(--warn); }
.po-care-chip.is-danger { background: var(--danger-wash); color: var(--danger); }

/* 忘帶卡押金還沒退（A69）。 */
.po-deposit {
  padding: 0 6px;
  border-radius: var(--r-tag);
  background: var(--warn-wash);
  color: var(--warn);
  font-weight: var(--fw-semibold);
  line-height: 22px;
}

.po-deposit.is-late {
  background: transparent;
  color: var(--ink-3);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
}

/* 補卡退還那一塊（late-card.js）。 */
.late-card {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}

.late-card-text {
  font-weight: var(--fw-semibold);
}

.late-card-text.is-muted {
  color: var(--ink-3);
  font-weight: var(--fw-regular);
}

.late-card-text.is-done {
  color: var(--ok-ink);
}

.po-today-doc,
.po-today-what {
  overflow: hidden;
  color: var(--ink-3);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.po-today-progress {
  display: flex;
  gap: var(--sp-2);
  white-space: nowrap;
}

.po-miss {
  color: var(--warn);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}

.po-miss.is-done {
  color: var(--ink-3);
  font-weight: var(--fw-regular);
}

.po-today-next-cell {
  justify-self: end;
}

/* 同一位已經在看板上：報到鈕的位置寫他的號碼（不是按鈕、不搶眼，合併審查）。 */
.po-today-onboard {
  color: var(--ink-2);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}

.po-today-more {
  display: flex;
  align-items: center;
  gap: var(--sp-1) var(--sp-3);
  white-space: nowrap;
}

.po-today-next {
  min-height: 28px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
}

.po-today-more .btn-plain {
  min-height: 28px;
  font-size: var(--fs-footnote);
}

/* 醫師名字本身就是「換醫師」（北昕第一批 B1）：跟同一列的次要字鈕同一個高度與底線，字跟原本那一格一樣大，
   名字太長照舊省略（按鈕本身跟著那一格縮）。 */
.po-today-doc-btn {
  max-width: 100%;
  min-height: 28px;
  overflow: hidden;
  font-size: inherit;
  text-overflow: ellipsis;
  vertical-align: middle;
  white-space: nowrap;
}

/* 「⋯ 更多動作」（action-menu.js）：28px 見方。 */
.action-menu-button {
  min-width: 28px;
  padding: 0;
  border-radius: var(--r-tag);
  font-size: var(--fs-body);
  line-height: 1;
}

/* 停上去跟其他次要按鈕一樣有底色（設計審查 B2 r3）。 */
.action-menu-button:hover,
.action-menu-button[aria-expanded="true"] {
  background: var(--fill);
}

/* 看完了：整列字淡（不用 opacity，visit-flow-spec §3）。 */
.po-today-row.is-done,
.po-today-row.is-cancelled,
.po-today-row.is-done .po-today-name,
.po-today-row.is-cancelled .po-today-name {
  color: var(--ink-3);
}

/* 一列底下的小表單、錯誤、補資料：橫跨整列，自己的寬度不影響欄寬（設計審查 D1：展開一列時別的欄被撐寬）。 */
.po-today-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  grid-column: 1 / -1;
  min-width: 0;
  contain: inline-size;
  padding: var(--sp-1) 0 var(--sp-2) 68px;
}

/* 就地確認的問句靠右（北昕第一批 B5）：「確定」落在剛按的那一顆底下，不必橫跨整列。 */
.po-today-panel.is-near-actions,
.po-today-panel.is-near-actions .inline-confirm.is-asking {
  justify-content: flex-end;
}

.po-today-panel p {
  margin: 0;
}

.po-today-note {
  color: var(--ink-3);
}

.po-card-fix {
  color: var(--ok-ink);
  font-weight: var(--fw-semibold);
}

.po-card-conflict {
  color: var(--warn);
  font-weight: var(--fw-semibold);
}

/* 中等寬度（平板橫放、平板直放約 728、側欄開著）：仍是一行——藏醫師、處置兩欄，卡況改寫在姓名下面一行，
   缺什麼只寫缺的（設計審查 D4）。 */
@container (max-width: 1000px) {
  .po-today-grid {
    grid-template-columns: 44px minmax(6em, 1fr) max-content max-content max-content max-content;
  }

  .po-today-row > * {
    grid-row: 1;
  }

  .po-today-row > .po-today-card {
    grid-row: 2;
    grid-column: 2;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1) var(--sp-2);
    min-width: 0;
  }

  .po-today-row > .po-today-extra {
    display: none;
  }

  .po-today-card .po-who-extra {
    display: inline;
  }

  .po-today-row > .po-today-panel {
    grid-row: 3;
  }

  .po-today-doc,
  .po-today-what {
    display: none;
  }

  .po-today-who {
    overflow: hidden;
  }

  .po-today-more {
    flex-wrap: wrap;
  }

  .po-miss.is-done {
    display: none;
  }
}

/* 很窄（手機、≤680）：每列兩行——第一行時間、姓名、狀態；第二行卡況、缺什麼、下一步（visit-flow-spec §2.5）。 */
@container (max-width: 680px) {
  .po-today-grid,
  .po-today-grid > .po-sec,
  .po-today-grid .po-sec-rows {
    display: block;
  }

  .po-today-grid .po-today-row {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) max-content max-content;
    grid-template-areas: "time who who state" ". card progress next" ". more more more";
    column-gap: var(--sp-3);
    row-gap: var(--sp-1);
    padding-block: var(--sp-1);
  }

  .po-today-row > * {
    grid-row: auto;
  }

  .po-today-time { grid-area: time; }
  .po-today-who { grid-area: who; }
  .po-today-state { grid-area: state; justify-self: end; }
  .po-today-row > .po-today-card { grid-area: card; }
  .po-today-progress { grid-area: progress; }
  .po-today-next-cell { grid-area: next; }
  .po-today-more { grid-area: more; justify-content: flex-end; }

  .po-today-row > .po-today-panel {
    grid-row: auto;
    grid-column: 1 / -1;
    padding-left: 56px;
  }
}

/* 觸控：列 48 高、按鈕 44（操作效率鐵則：平板要點得到）。 */
@media (pointer: coarse) {
  .po-today-row {
    min-height: 48px;
  }

  .po-today-next,
  .po-today-more .btn-plain,
  .po-sec-toggle,
  .po-today-filter button {
    min-height: 44px;
  }

  .action-menu-button {
    min-width: 44px;
  }

  .card-fix .card-fix-accept {
    min-height: 44px;
  }
}

/* 結帳頁：印收據的切換、收完款之後那一塊（A29、A30）。
   收費那一條（S8）：應收在左，收現金＋找零緊接著「收費」在右——眼睛看應收、手打現金、按收費，三步同一處。 */
.charge-submit-row {
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
}

.charge-submit-row > .charge-live {
  margin: 0;
}

.charge-submit-row > .charge-tender {
  margin-left: auto;
}

form label.chip-toggle,
.chip-toggle {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: var(--sp-1);
  min-height: var(--h-control);
  padding: 0 var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--fill);
  font-size: var(--fs-footnote);
  cursor: pointer;
}

.chip-toggle input {
  width: 18px;
  height: 18px;
}

.charge-after {
  display: grid;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  border-radius: var(--r-input);
  background: var(--ok-wash);
}

.charge-after[hidden] {
  display: none;
}

.charge-after-paid,
.charge-after-next,
.charge-after-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}

.charge-after-total {
  color: var(--ok-ink);
  font: var(--fw-semibold) var(--fs-title2) / 1 var(--font-ui);
  font-variant-numeric: tabular-nums lining-nums;
}

.charge-after-owed {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.charge-after-label,
.charge-after-booked {
  font-weight: var(--fw-semibold);
}

/* 一鍵約那幾天都沒有空檔（A27 批次修正）：整行放在那一排 chip 的上面，不要把 chip 擠掉。
   不是錯誤（只是那幾天滿了），所以不用紅色。 */
.charge-after-noslot {
  flex: 1 0 100%;
  color: var(--ink-2);
  font-feature-settings: var(--numerals);
}

.charge-after .chip.is-suggested {
  background: var(--group);
  box-shadow: inset 0 0 0 1px var(--ink-3);
}

/* 約診格與當天清單：今天看完了（完成、取消）的區塊淡掉（visit-flow-spec §3：完成的區塊底色 --group、字 --ink-3）。 */
.timeline-block.is-done,
.timeline-block.is-cancelled,
.day-list-row.is-done,
.day-list-row.is-cancelled {
  background: var(--group);
  color: var(--ink-3);
}

.timeline-block .po-st {
  min-height: 20px;
  padding: 0 6px;
  vertical-align: middle;
}

/* 現場掛號的新病患（A61）：一行四格＋建立並掛號，窄的時候換行。 */
.walkin-new {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-2) var(--sp-3);
  margin-top: var(--sp-2);
}

form.walkin-new label.walkin-field {
  flex-direction: row;
}

.walkin-new input {
  width: 9em;
}

/* 收費設定：收據的院所章（A62）。 */
.stamp-setting {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin-top: var(--sp-4);
}

.stamp-preview {
  width: 90px;
  height: 90px;
  object-fit: contain;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-tag);
  background: #fff;
}

.stamp-preview[hidden],
.stamp-setting .btn-plain[hidden],
#charge-receipt-note[hidden],
#charge-identity-note[hidden],
.charge-draft-note[hidden] {
  display: none;
}

/* 上傳鈕是膠囊按鈕（label 包住看不見但能 focus 的檔案欄；設計審查 B2 r2 R2-8）。 */
.stamp-upload {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--sp-5);
  border-radius: 999px;
  background: var(--fill);
  color: var(--ink);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

.stamp-upload:hover {
  background: var(--fill-hover, var(--fill));
}

.stamp-upload:focus-within {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* 叫號看板的空椅：一行（設計審查 D8：原本一張 290px 高的空卡）。 */
.queue-chair.is-empty {
  align-self: start;
  grid-template-rows: auto;
  align-content: center;
  min-height: 72px;
  padding-block: var(--sp-4);
}

.queue-chair.is-empty .queue-chair-name {
  font-size: var(--fs-body);
  color: var(--ink-3);
}

/* 平板橫放（1024×768）：椅子三張並排、卡片壓矮，兩三張椅子有人時候診清單的「叫到 N 號椅」仍在首屏
   （smooth-ops 反方 PM #4：原本兩欄兩排、候診第一列在 y≈869 要捲）。 */
@media (min-width: 900px) and (max-width: 1279px) {
  .queue-chairs {
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: var(--sp-4);
    margin-bottom: var(--sp-6);
  }

  .queue-chair {
    min-height: 0;
    padding: var(--sp-5);
  }

  .queue-chair .queue-actions {
    padding-top: var(--sp-3);
  }
}

/* 單椅與空椅並排時的那一排不必撐到 248。 */
.queue-chairs:has(.queue-chair.is-empty):not(:has(.is-busy)) {
  align-items: start;
}

/* 約診面板的時長：身障病患預設較長時旁邊一個小標（A73）。 */
.booking-length {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

.booking-care-note[hidden] {
  display: none;
}

/* 看診畫面頁首：忘帶卡押金（A69）。 */
.po-flag-deposit {
  color: var(--warn);
}

.po-flag-deposit.is-late {
  color: var(--ink-3);
}

.po-deposit-slot {
  padding: var(--sp-1) 0;
}

.po-deposit-slot[hidden] {
  display: none;
}

/* 結帳頁的病患身分（A63）。 */
form label.charge-identity {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: var(--sp-2);
}

form label.charge-identity[hidden] {
  display: none;
}

/* 依病患找約診（UX 稽核）：一筆一顆，至少看得到 5 筆。 */
.appt-patient-list {
  display: grid;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}

.appt-patient-item {
  justify-content: flex-start;
}

/* 一筆約診一行：日期那顆佔滿，「改約」跟在右邊（smooth-ops A5）。用格線而不是 flex：日期很長時
   日期那格縮（字換行），「改約」不被擠出面板右緣（設計審查 r1 #1）。日期那顆是「打開這筆」的連結，
   不做成跟「改約」一樣的實心按鈕：卡片底色、細框，一眼分得出哪顆是動作。 */
.appt-patient-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-2);
}

.appt-patient-line > .appt-patient-item {
  min-width: 0;
  /* 面板窄時日期、時間、醫師換行，不截掉時間。 */
  white-space: normal;
  text-align: start;
  background: var(--group);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
}

.appt-patient-line > .appt-patient-item:hover {
  background: var(--canvas);
}

/* 剛約好／改好的那一格：捲到畫面中間時閃一下，看得出是哪一格。 */
:is(.timeline-block, .day-list-row).is-just-booked {
  outline: 3px solid var(--focus-ring);
  outline-offset: 1px;
}

.visits-flags[hidden] {
  display: none;
}

/* 約診格：午休壓成一條細線（UX 稽核），中間寫「午休 12:00–14:00」。 */
.timeline-closed.is-folded {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--fill);
}

.timeline-fold-label {
  color: var(--ink-3);
  font-size: var(--fs-footnote);
  line-height: 1;
}

/* 叫號看板的看診中卡片：姓名用大字，計時一行小字（UX 稽核）。 */
.queue-chair-who {
  font-family: var(--font-display);
  font-size: var(--fs-title1);
}

/* 姓名下一行的小字計時：窄的時候「看診 3 分」「預計 30 分」分兩行，不把椅子名稱擠成兩行（設計審查 B2 r2 R2-5）。 */
.queue-chair-time.is-small {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  column-gap: var(--sp-1);
  font: var(--fw-regular) var(--fs-footnote) / var(--lh-ui) var(--font-ui);
  color: var(--ink-3);
}

.queue-chair-time.is-small.is-over {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

/* 收費表單：寬螢幕四欄（UX 稽核：只用左半邊、表單 900px 高）。 */
@media (min-width: 1200px) {
  #charge-form {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    max-width: none;
  }
}

/* 收費設定的病患身分表：欄寬照內容，不把兩個輸入框拉開（設計審查 B2 r2）。 */
#identity-table {
  width: auto;
}

#identity-table input {
  width: 9em;
}

#identity-table td,
#identity-table th {
  padding-inline-end: var(--sp-4);
}

/* 看診紀錄頁沒選病患時：今天的看診（設計審查 B2 r2 R2-4）。 */
.visits-today-caption {
  margin: 0 0 var(--sp-2);
  color: var(--ink-2);
  font-weight: var(--fw-semibold);
}

/* 從今天看板「收費」來：合計與「記下這筆」在病患那一行底下靠右（smooth-ops A3，payments.js placeSubmitRow）。
   合計靠左、記下這筆與印收據靠右——就在剛按的「收費」下方。在表單頂端，不需要黏在畫面下緣。 */
#charge-form > .charge-submit-row.is-top {
  position: static;
  margin-top: 0;
  padding: var(--sp-2) 0;
  justify-content: flex-end;
}

.charge-submit-row.is-top > .charge-live {
  margin-right: auto;
}

/* 櫃台的病患畫面（約診、收費兩張卡在主欄時）、1180 以下整頁一欄：「最近治療」排在今天那張卡的正下方，
   不再壓在約診、收費之後（smooth-ops A6：找病患看上次做了什麼，1024×768、768×1024 都不用捲）。
   只拆外框（display: contents），卡片本身不動；醫師與管理者的版面不受影響（主欄沒有那兩張卡）。 */
@media (max-width: 1179px) {
  .po-work:has(> .po-main > .po-main-cards:not(:empty)) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
    gap: var(--sp-3);
  }

  .po-work:has(> .po-main > .po-main-cards:not(:empty)) > :is(.po-main, .po-side),
  .po-work:has(> .po-main > .po-main-cards:not(:empty)) .po-main-cards {
    display: contents;
  }

  .po-work:has(> .po-main > .po-main-cards:not(:empty)) .po-record {
    grid-column: 1 / -1;
  }

  .po-work:has(> .po-main > .po-main-cards:not(:empty)) > .po-side > * {
    order: 4;
  }

  .po-work:has(> .po-main > .po-main-cards:not(:empty)) #po-recent-card {
    order: 1;
  }

  .po-work:has(> .po-main > .po-main-cards:not(:empty)) #po-appts-card {
    order: 2;
  }

  .po-work:has(> .po-main > .po-main-cards:not(:empty)) #patient-charges-card {
    order: 3;
  }
}

@media (max-width: 899px) {
  .po-work:has(> .po-main > .po-main-cards:not(:empty)) {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* 約診頁的約診／改約面板（≥900 寬，右欄）：整個面板放進一個畫面高，空檔清單吃掉剩下的高度、自己捲，
   確認鈕固定在面板底（smooth-ops A5：以前空檔清單是 22rem 的小框、面板又比畫面高，改到隔天要捲好幾次）。
   appointments.js startBooking 打開時把面板捲進畫面。 */
@media (min-width: 900px) {
  /* 約診時面板寬一點（320 → 400）：約診項目／時長、醫師／診療椅才排得成兩兩一行，面板上半部矮一半，
     空檔清單才有高度。約完就回 320。 */
  .appt-layout.has-panel:has(> .side-panel > .booking) {
    grid-template-columns: minmax(0, 1fr) 400px;
  }

  .side-panel.card:has(> .booking) {
    display: flex;
    flex-direction: column;
    /* 固定一個畫面高（不是 max-height）：空檔還沒讀回來時面板就已經是最後的高度，打開時捲進畫面才準。 */
    height: calc(100dvh - 2 * var(--sp-6));
    /* 矮螢幕上欄位加清單最小高度放不下時整個面板捲（保底，不切掉確認鈕；審查 r1 #9）。 */
    overflow-y: auto;
    padding: var(--sp-4) var(--sp-5);
  }

  .side-panel > .booking {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }

  .side-panel > .booking > * {
    flex: 0 0 auto;
  }

  .side-panel > .booking > .slot-list {
    flex: 1 1 auto;
    min-height: 6rem;
    max-height: none;
    margin-top: var(--sp-2);
  }

  /* 欄位間距收緊（同看診畫面的約診對話框 .po-booking）：.panel-label 自帶的上距在欄位裡是多的。 */
  .side-panel > .booking .panel-field {
    margin-top: var(--sp-2);
  }

  .side-panel > .booking .panel-label {
    margin-top: 0;
  }

  .side-panel > .booking > p.muted {
    margin: var(--sp-1) 0 0;
  }

  .side-panel > .booking .slot-day:first-child {
    margin-top: 0;
  }
}

/* smooth-ops 管理線（2026-10-24 驗收 A12、A13）---------------------------------------- */

/* 「其他設定」子群組：掛在「設定」清單底下，標題縮排一格，看得出是設定裡的一小群。 */
.nav-sub {
  display: grid;
  gap: 2px;
}

.nav-sub > .nav-group-toggle {
  padding-left: var(--sp-6);
}

/* 底部「更多」面板（平板直放、手機）：拉到全高，品牌縮成一行，底部登入者、連線與提醒併成
   一行（原本品牌約 90px、底部約 155px，選單只看得到 12 項，管理者要捲才到回診追蹤）。 */
@media (max-width: 899px) {
  .sidebar {
    max-height: 100vh;
    max-height: 100dvh;
    padding-top: var(--sp-3);
  }

  .sidebar .brand {
    display: flex;
    align-items: baseline;
    gap: var(--sp-3);
    padding-bottom: var(--sp-2);
  }

  .sidebar .brand .brand-clinic {
    margin: 0;
  }

  /* 底部列上已經有的頁不在面板裡重列（nav.js onTabbar）。 */
  .nav-list > li.in-tabbar {
    display: none;
  }

  .sidebar-footer {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1) var(--sp-4);
    padding-top: var(--sp-2);
    padding-bottom: var(--sp-2);
  }

  /* 警示（鑰匙鎖住、備份有問題）與展開的提醒仍各佔一整行，排在登入者那一行上面。 */
  .sidebar-footer > .sidebar-lock,
  .sidebar-footer > .sidebar-backup,
  .sidebar-footer > .sidebar-alerts {
    order: -1;
    flex: 1 1 100%;
  }

  .sidebar-footer > .sidebar-account {
    order: 0;
    flex: 1 1 auto;
    margin: 0;
  }

  .sidebar-footer > .sidebar-status-line {
    order: 1;
    flex: 0 1 auto;
  }
}

/* 收費設定的單價、進價直接在表格裡改（billing-settings.js inlineNumber，smooth-ops A12）：
   平常看起來像一般數字，滑過或聚焦才露出框；改了才出現「儲存」。 */
.inline-edit {
  white-space: nowrap;
}

.inline-edit input {
  width: 6.5em;
  min-height: 36px;
  padding: 0 var(--sp-2);
  border: 1px solid transparent;
  background: transparent;
  text-align: right;
  font: inherit;
}

.inline-edit input:hover,
.inline-edit input:focus {
  border-color: var(--hairline-strong);
  background: var(--canvas);
}

.inline-edit button {
  margin-left: var(--sp-1);
  min-height: 36px;
  padding: 0 var(--sp-2);
}

.inline-edit button[hidden] {
  display: none;
}

/* 打錯字的格子（重畫後也留著）：紅框＋錯誤字，看得出不是「清空」。 */
.inline-edit input.is-invalid {
  border-color: var(--danger);
  background: var(--danger-wash);
}

.inline-edit-error {
  display: block;
  color: var(--danger);
  font-size: var(--fs-caption);
  white-space: normal;
}

.inline-edit-error[hidden] {
  display: none;
}

/* 存檔鈕黏在畫面底（目前用在約診設定的營業時段，smooth-ops A12）：清單長過一個畫面時
   不必捲到底才按得到。平板直放、手機底部列也黏在底，存檔鈕排在它上面。
   （帳號的新增表單沒有用它：改成表格上方「＋ 新增帳號」打開，表單本來就在首屏。） */
.sticky-actions {
  position: sticky;
  bottom: 0;
  z-index: 5;
  margin-inline: calc(var(--sp-2) * -1);
  padding: var(--sp-2);
  background: var(--group);
  border-top: 1px solid var(--hairline);
}

@media (max-width: 899px) {
  .sticky-actions {
    bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  }

  /* 899px 以下的卡片是 overflow:auto（寬表格在卡片裡橫捲），黏底會黏在卡片自己的捲動框上、
     等於沒黏：有黏底存檔鈕的卡片不當捲動框。 */
  .card:has(> .sticky-actions),
  .card:has(.visit-row-pinned) {
    overflow: visible;
  }
}

/* 帳號頁「＋ 新增帳號」打開的表單：在帳號表上方，跟表格隔一點。 */
.user-add {
  margin-bottom: var(--sp-5);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--hairline);
}

.user-add[hidden] {
  display: none;
}

/* 1536×864 這類矮的電腦畫面：管理者收著「設定」時，「設定」標題本身要不捲就看得到（smooth-ops A12：
   原本側欄 922px 比畫面高 58px，標題被黏底區蓋住）。品牌與診所名同一行（名字長就自己換行），
   每項 32px（原 34；1024×768 那段已是 30px）。 */
@media (min-width: 900px) and (max-height: 960px) {
  .brand {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--sp-2);
  }

  .brand .brand-clinic {
    margin: 0;
  }
}

@media (min-width: 900px) and (max-height: 960px) and (min-height: 801px) {
  .nav-link,
  .nav-link.is-minor,
  .nav-link:not(.has-icon) {
    min-height: 32px;
  }
}

/* 從健保申報「開病歷」打開的那一次看診（visits.js）：那一列黏在畫面頂，往下看處置與牙位時
   列上的「‹ 回健保申報」不會被捲走（smooth-ops A10）。 */
.visit-row-pinned > td {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--group);
}

/* 練習模式／演練的頂端橫幅也黏在頂（約 36px）：黏頂的列排在它下面，不被蓋住。 */
body:has(.practice-banner:not([hidden])) .visit-row-pinned > td {
  top: 40px;
}

/* 我的帳號 → 我的常用診斷（smooth-ops A16）：一列一個代碼，↑ ↓ 移除排在右邊。 */
.account-fav-list {
  display: grid;
  gap: var(--sp-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.account-fav-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  border-bottom: 1px solid var(--hairline);
}

.account-fav-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 設定 → 常用診斷（smooth-ops A16）：上面一排選醫師、搜尋加入；下面是清單（同一個樣式，↑ ↓ 移除在右）。 */
.fav-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}

.fav-add {
  position: relative;
  flex: 1 1 18rem;
}

.fav-add input {
  width: 100%;
}

.fav-results {
  position: absolute;
  z-index: 5;
  inset-inline: 0;
  top: 100%;
  max-height: 22rem;
  margin: var(--sp-1) 0 0;
  padding: var(--sp-1);
  overflow-y: auto;
  list-style: none;
  background: var(--group);
  border-radius: 10px;
  box-shadow: 0 6px 20px rgb(0 0 0 / 0.15);
}

.fav-results[hidden] {
  display: none;
}

.fav-result {
  min-height: 40px;
  padding: var(--sp-2) var(--sp-3);
  border-radius: 8px;
  cursor: pointer;
}

.fav-result.is-active,
.fav-result:hover {
  background: var(--fill);
}

/* --- 收費紀錄的範圍、篩選、日期分組（smooth-ops A19，payments.js renderDay） --- */
.charge-list-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-3);
}

/* --- 自動訊息與傳送紀錄（js/auto-messages.js、auto-message-log.js；spec 2026-10-24-auto-messages §8–9）---
   1536×864：九種開關一次看完不捲動，編輯與預覽在右欄；窄於 1100px 改成上下排。 */
.auto-msg-general {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}

.charge-list-head > h2 {
  margin: 0;
}

.charge-filter {
  flex: 1 1 12rem;
  max-width: 20rem;
}

.charge-range-total {
  margin-left: auto;
  color: var(--ink-2);
  white-space: nowrap;
}

/* 日期分組那一列：跟資料列分得開，但不搶合計的眼睛。 */
.charges-table tr.charge-day-row th {
  text-align: left;
  background: var(--fill);
  font-weight: var(--fw-semibold);
  padding-top: var(--sp-2);
  padding-bottom: var(--sp-2);
}

.charge-day-row th > span + span {
  margin-left: var(--sp-3);
}

@media (max-width: 899px) {
  .charge-filter {
    flex-basis: 100%;
    max-width: none;
    order: 3;
  }
}

/* 還不能寫的「寫入卡片」（smooth-ops A24）：淡色文字鈕，不像排在主要動作裡的一顆正常按鈕；提示寫要先按哪一顆。 */
.po-card-actions .po-write-later {
  color: var(--ink-3);
  font-weight: var(--fw-regular);
}

/* 櫃台、今天還沒有病歷（A24）：一句狀態＋「加處置／病碼」主按鈕，同一行。 */
.po-desk-entry {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}

.po-desk-entry .po-empty {
  margin: 0;
}

/* 900–1279 寬「編輯資料」收進「更多」選單（patient-work.js placeEdit），整排才塞得下。
   下界 900（與下面牙位圖那條同一個界）是因為 768×1024 直放時這些 nowrap 會把警示壓成 60–110px 寬的直條
   （「病史：高血／壓、糖／尿病」），而且頁首反而從 142 變成 170 高（審查 r1 嚴重 3）：警示、錯誤、過敏是
   產品規則裡唯一允許常駐在畫面上的字，上面 .po-flags 的決定是「文字不截斷，放不下就折行」，768 要照那個走。 */
@media (min-width: 900px) and (max-width: 1279px) {
  .po-banner {
    column-gap: var(--sp-2);
  }

  .po-facts {
    font-size: var(--fs-footnote);
  }

  /* 標籤左右收一格：櫃台在 1024 寬同時有「看診中／無過敏紀錄／無欠款」三顆，
     量下來就差這 24px，差這一點整排就要多折一行（182px → 144px）。字一個都沒少。 */
  .po-flags .po-flag,
  .po-flags .pill {
    padding-inline: var(--sp-2);
  }
}

/* 量到的寬度（2026-10-26，fix/patient-header-one-row）：名字那一排扣掉左邊選單與浮在右上的
   登入列之後，1536 只剩 961（醫師）／977（櫃台），1280 只剩 705／721，1024 只剩 549／565，
   768 直放 713。同一排要放的東西最少要 969（醫師）／1077（櫃台）。四種寬度兩種角色都裝不下，
   所以這一排一定會折第二行——這是量出來的，不是沒調。能讓的只有年齡那一段（.po-facts 會截斷），
   狀態、警示與四顆動作都不准縮也不准拿掉。 */

/* 1024×768 這種矮的橫放畫面（滑鼠）：牙位圖每顆 40 高（原 48），名字前多了看診星號後頁首高了一點，選碼器第一排
   仍在首屏（smooth-ops A7 不退步）。觸控照舊 44 以上。 */
@media (min-width: 900px) and (max-width: 1279px) and (max-height: 820px) and (pointer: fine) {
  .po-teeth .odonto-tooth {
    height: 40px;
    min-height: 40px;
  }
}

/* 定稿後「修改」被擋（已申報／已上傳要補報，A27）：紅字一句＋「回去修改」。 */
.po-amend-blocked:not(:empty),
.visit-revise {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

.po-amend-blocked .po-check {
  margin: 0;
}

/* 退回草稿的那一筆：常駐一行紅字（沒重新定稿就不會進申報，審查 r2 一般 5）。 */
.po-rec-reopened[hidden] {
  display: none;
}

.po-rec-reopened .po-check {
  margin: 0 0 var(--sp-2);
}

.visit-revise-blocked {
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

/* 自己不能按「修改」時那一格只寫「已定稿」（要找誰在提示框裡）。 */
.visit-revise-note {
  color: var(--muted);
}

/* 看診畫面標題列的「刪除草稿」：問的時候一句＋回去修改（主）＋刪除。 */
.po-draft-delete {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
/* 今天看板的標題列（A23 加了 ‹ › 之後）：1536 寬時日期、只看、讀健保卡、現場掛號、備援單仍排一行——
   間距與按鈕左右留白各收一點（按鈕高度不變，觸控照樣 44）。更窄時工具往第二行折，日期那一組本身不拆開。 */
.po-today-head {
  gap: 6px;
}

.po-today-day {
  gap: var(--sp-1);
}

.po-today-day > h2 > .po-today-date {
  padding: 0;
}

/* 以前的某一天：淡底膠囊要有左右留白（今天不需要，省下的寬度讓 1536 一行放得下）。 */
.po-today-day > h2 > .po-today-date.is-past {
  padding: 0 var(--sp-2);
}

.po-today-head > .btn-secondary {
  padding: 0 var(--sp-4);
}

.po-today-filter button {
  padding: 0 10px;
}

/* 收費頁日期欄不是今天（改別天的收費、從今天看板看以前的某一天補收費）：同看板別天日期的淡底，看得出這一筆記在別天（A23 審查）。 */
#charge-date.is-not-today {
  background: var(--warn-wash);
}

/* --- 叫號頁緊湊版（smooth-ops A23，使用者「排版太分散了」「不用放那麼大」「下面候診也要顯示出來」）----------
   有人的椅子：矮卡並排（放得下就三張一排），第一行 椅名・號碼・星・姓名・看診 N 分，第二行一顆主要「看完了」＋小鈕＋「⋯」。
   空椅：一行小標。候診：一位一行，欄寬固定（號碼｜星｜姓名｜小字｜狀態｜動作），跟今天看板的列一樣上下對齊。
   顏色只有兩種：建議叫進去的那一顆主色，其他都是同一種次要樣式（不出現第三種、像漸層的色階）。 */
.queue-chairs,
.queue-chairs.is-single {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-5);
}

.queue-chairs:empty {
  display: none;
}

/* 有人的椅子一排並列；椅子多（十張）時在這一排裡左右捲，不往下推候診清單。 */
.queue-chair-cards {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: 2px;
}

.queue-chair-cards > .queue-chair.is-busy {
  flex: 1 0 14rem;
  max-width: 26rem;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-height: 0;
  padding: var(--sp-2) var(--sp-3);
}

/* 卡裡的號碼跟著列的字級（不是候診清單那種大數字）。 */
.queue-chair-line > .queue-ticket {
  min-width: 0;
  font-family: var(--font-ui);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.queue-chair-line {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  white-space: nowrap;
}

.queue-chair-line > .queue-chair-name {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  flex: none;
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
}

.queue-chair-line > .queue-chair-name::before {
  content: '';
  width: var(--indicator-dot);
  height: var(--indicator-dot);
  border-radius: 50%;
  background: var(--signature);
}

.queue-chair-line > .queue-ticket {
  flex: none;
}

.queue-chair-line > .queue-chair-who,
.queue-chairs.is-single .queue-chair-line > .queue-chair-who {
  display: inline-flex;
  align-items: center;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  line-height: var(--lh-ui);
  letter-spacing: 0;
}

.queue-chair-who > strong {
  overflow: hidden;
  text-overflow: ellipsis;
}

.queue-chair-line > .queue-chair-time {
  display: inline;
  flex: none;
  margin-left: auto;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.queue-chair-line > .queue-chair-time.is-over {
  color: var(--warn);
  font-weight: var(--fw-semibold);
}

/* 預排的下一位：卡上一行小字。 */
.queue-chair-next {
  overflow: hidden;
  color: var(--ink-2);
  font-size: var(--fs-footnote);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 已經預排的那一列：「預排 N 號椅」小標（不是按鈕，外框、不填色）。 */
.queue-reserved-tag {
  padding: 2px var(--sp-2);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-pill);
  color: var(--ink-2);
  font-size: var(--fs-footnote);
  white-space: nowrap;
}

.queue-chairs .queue-chair .queue-actions {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  align-self: flex-start;
  gap: var(--sp-2);
  width: auto;
  padding-top: 0;
}

.queue-chairs .queue-chair .queue-actions > * {
  flex: none;
  min-height: 28px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-footnote);
}

.queue-chairs .queue-chair .queue-actions > .action-menu-button {
  min-width: 32px;
  padding: 0;
}

.queue-empty-chairs {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.queue-empty-chair {
  padding: 2px var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--fill);
  color: var(--ink-3);
  font-size: var(--fs-footnote);
  white-space: nowrap;
}

/* 候診、過號、待收費：一位一列——上面一行是號碼｜星｜姓名｜小字｜狀態，按鈕在同一列的
   下一行（見下面 .queue-actions 的 grid-column: 1 / -1）。
   按鈕為什麼不跟姓名同一行：這一行的固定開銷實測約 299px（號碼 54＋三顆星 72＋狀態標 108＋
   間隙內距 64；≤1279 時清單卡還多 48px 內距，見上面 --queue-col-min），而候診列的按鈕
   光三張椅子就要 346px；候診欄最寬也只有 667px（1536 兩欄），一行放不下。
   以前這一軌寫 max-content，軌道縮不下去就整列頂出欄外去蓋到隔壁欄的姓名
   （1280 兩欄實測溢出 198px、三欄 359px，1920 也還溢出 259px）。
   每一軌都寫 minmax(0, …)：軌道縮不到比內容窄的話，列會頂出欄外，而不是把自己的字縮短
   （知識卡 css-grid-1fr-track-cannot-shrink-below-nowrap-content）。 */
.queue-lists .queue-row {
  display: grid;
  grid-template-columns:
    minmax(0, 3.2em) minmax(0, calc(var(--star-box-w) * 3))
    minmax(0, 7em) minmax(0, 1fr) minmax(0, max-content);
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
  min-height: 44px;
  padding: var(--sp-1) var(--sp-4);
}

.queue-lists .queue-row + .queue-row::before {
  left: var(--sp-4);
  right: var(--sp-4);
}

.queue-lists .queue-ticket {
  min-width: 0;
  font-family: var(--font-ui);
  font-size: var(--fs-body);
}

.queue-lists .queue-who {
  flex-direction: row;
  min-width: 0;
}

.queue-lists .queue-who strong,
.queue-lists .queue-who a {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.queue-detail {
  overflow: hidden;
  font-size: var(--fs-footnote);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 醫師名字＝「換醫師」（北昕第一批 B1）：動作那一行的最左邊（姓名正下方），叫號鈕照舊靠右。
   選擇器要壓過下面 .queue-actions button 的通用尺寸，所以帶 .queue-list。 */
.queue-lists .queue-list .queue-actions .queue-doc-btn {
  margin-right: auto;
  padding: 0 var(--sp-1);
  font-size: var(--fs-footnote);
}

.queue-lists .queue-row > .queue-note {
  grid-column: 3 / -1;
  margin-left: 0;
}

/* 按鈕自己一行、靠右：椅子幾張照設定（1 張、3 張、10 張都一樣算，不寫死），放不下就在這一行內
   折行，永遠不會擠出欄外。主要的「叫到 N 號椅」是 DOM 的第一顆，所以一定留在第一行、
   永遠直接點得到，不會被折到看不見、也不會被收進選單（每多一下點擊都是診所的時間）。 */
.queue-lists .queue-list .queue-actions {
  grid-column: 1 / -1;
  flex-wrap: wrap;
  justify-content: flex-end;
  width: auto;
  min-width: 0;
  padding-left: 0;
}

/* 一列底下開著的小塊（換醫師要換給誰、取消報到問一次；A27）：整列寬。
   這一列是 grid，.queue-row-ask 的 flex-basis: 100% 在 grid 裡沒有作用，少了這一條就會被
   自動塞進第一軌（3.2em）再從欄裡溢出去。 */
.queue-lists .queue-row > .queue-row-ask {
  grid-column: 1 / -1;
}

/* 換醫師的名單靠左（北昕第一批 B1）：醫師名字那一顆在動作那一行最左邊，名單接在它正下方。 */
.queue-lists .queue-list .queue-row-ask .queue-actions.is-doctor {
  justify-content: flex-start;
}

.queue-lists .queue-list .queue-actions button {
  flex: none;
  min-width: 0;
  min-height: 32px;
  padding: 0 var(--sp-3);
}

.queue-lists .queue-list .queue-actions .queue-call-chip {
  padding: 0 var(--sp-2);
}

.queue-lists .queue-list .queue-actions .action-menu-button {
  min-width: 32px;
  padding: 0;
}

/* 半寬的欄（過號、待收費並排時）：號碼、星、姓名、狀態；小字讓位（第四軌 0）。 */
.queue-column + .queue-column .queue-row {
  grid-template-columns:
    minmax(0, 3.2em) minmax(0, calc(var(--star-box-w) * 3))
    minmax(0, 1fr) 0 minmax(0, max-content);
}

/* 觸控：按鈕 40 高仍點得到，列不超過 56。 */
@media (pointer: coarse) {
  .queue-lists .queue-list .queue-actions button {
    min-height: 40px;
  }

  .queue-chairs .queue-chair .queue-actions > * {
    min-height: 36px;
  }
}

/* 網址打不開的那一塊（nav.js showRouteProblem）：一句話、一兩顆按鈕，不要排版成一個大空畫面。
   四種狀況（功能沒開／還沒做／角色不能開／網址打錯）共用這一塊。 */
.route-problem {
  max-width: 34rem;
  padding-top: var(--sp-7);
}

.route-problem-detail {
  margin: var(--sp-2) 0 var(--sp-5);
  color: var(--ink-2);
}
.auto-msg-general label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.auto-msg-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
}
@media (min-width: 1100px) {
  .auto-msg-layout:has(.auto-msg-editor:not([hidden])) {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  }
}
.auto-msg-table {
  width: 100%;
  table-layout: fixed;
}
.auto-msg-table th:first-child { width: 3.5rem; }
.auto-msg-table th:nth-child(2) { width: 15rem; }
.auto-msg-table th:last-child { width: 6rem; }
.auto-msg-table td {
  padding-top: var(--sp-2);
  padding-bottom: var(--sp-2);
}
.auto-msg-table input[type="checkbox"] {
  width: 22px;
  height: 22px;
}
.auto-msg-table tr.is-selected td {
  background: var(--fill);
}
.auto-msg-name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}
.auto-msg-snippet {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--ink-3);
}
.auto-msg-editor textarea {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
}
.auto-msg-vars {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: var(--sp-2) 0;
}
/* 預覽排成手機聊天室：一個對話泡泡，下面一排快速回覆按鈕（同 LINE quick reply）。 */
.auto-msg-phone {
  max-width: 360px;
  padding: var(--sp-3);
  border-radius: var(--r-card, 12px);
  background: var(--fill);
}
.auto-msg-phone:empty {
  display: none;
}
.auto-msg-bubble {
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  border-radius: 14px;
  background: var(--canvas);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.auto-msg-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}
.auto-msg-quick {
  padding: 2px var(--sp-3);
  border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
  background: var(--canvas);
  font-size: var(--fs-footnote);
}
.auto-log-toolbar {
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.auto-log-presets {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.auto-log-table {
  width: 100%;
}
.table-scroll {
  overflow-x: auto;
}

/* === 看診畫面：頁首動作群搬家（使用者 2026-10-27 口述排法） ==============================
   原話：「我告訴你排版怎麼排 全部移到[右欄底部那個位置] [右欄] 這個整個移上去 [主區] 這個也移上去
   這樣一次就能顯示很多[處置表的列] 這個了」「[編輯資料 操作紀錄 更多] 移到[頁頂右側那排] 這排 排版整齊 通順」
   「排列要整齊 這樣亂糟糟的像甚麼樣子」
   白話：頁首只留名字那一行（加上過敏、病史、欠款這些警示）；編輯資料／操作紀錄／更多到右上角帳號那一排；
   其餘（中性小標、約診、健保卡、X 光、叫下一位／收費）在兩欄寬度搬進右欄雲端查詢下面那張動作卡。
   誰在哪裡的邏輯在 patient-work.js placeMore／placeActions。 */

/* 帳號列裡的病患動作：同一款淡色文字鈕（.topbar .btn-plain），右邊一道細線跟帳號項目分開，
   讀起來是「這位病患的事｜我的帳號」。離開看診畫面就不出現（帳號列每一頁共用）。 */
.topbar > .po-more-actions {
  gap: var(--sp-4);
  padding-inline-end: var(--sp-4);
  border-inline-end: 1px solid var(--hairline-strong);
}

body:not(.po-working) .topbar > .po-more-actions {
  display: none;
}

/* 動作卡：貼在右欄底部（sticky），右欄內容再多、捲到哪裡，這幾顆都看得到（§10.4）。 */
.po-actions-dock {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  box-shadow: 0 0 0 1px var(--hairline);
}

.po-actions-dock[hidden] {
  display: none;
}

/* 分組用的盒子在卡裡全部攤平，每一顆直接排進卡片那一個網格；
   .is-off（讀卡關閉）時健保卡那兩組照舊整組不出現，所以要排除它。 */
.po-actions-dock .po-banner-right,
.po-actions-dock .po-quiet-slot,
.po-actions-dock .po-book-slot,
.po-actions-dock .po-cardrow,
.po-actions-dock .po-cardrow:not(.is-off) > .po-card-actions,
.po-actions-dock .po-cardrow:not(.is-off) > .po-nhi,
.po-actions-dock .po-role-actions {
  display: contents;
}

/* 第一列只放狀態（無過敏紀錄、無欠款、下次約診、模擬、就醫序號），同一款膠囊、同高；
   ::before 是一條看不見的換行，把狀態列和下面的動作鈕分成兩段。 */
.po-actions-dock .po-quiet-slot > *,
.po-actions-dock .po-next,
.po-actions-dock .po-nhi > * {
  order: -2;
  min-height: 28px;
}

.po-actions-dock .po-nhi > .po-nhi-err,
.po-actions-dock .po-deposit-slot {
  order: -2;
  flex-basis: 100%;
}

.po-actions-dock::before {
  content: '';
  order: -1;
  flex-basis: 100%;
  height: 0;
}

/* 動作鈕：一律同一款、同高、等寬三欄，左緣對齊（最後一列不滿也不拉寬）。 */
.po-actions-dock .po-book-slot > *,
.po-actions-dock .po-card-actions > *,
.po-actions-dock .po-role-actions > * {
  flex: 0 0 calc((100% - 2 * var(--sp-2)) / 3);
  min-width: 0;
}

.po-actions-dock .po-book-slot button,
.po-actions-dock .po-card-actions button,
.po-actions-dock .po-role-actions button,
.po-actions-dock .po-role-actions a.btn-secondary {
  width: 100%;
  min-height: var(--h-control-sm);
  justify-content: center;
  padding-inline: var(--sp-2);
  white-space: nowrap;
}

/* 淡色文字鈕（還不能寫卡時的「寫入卡片」）在卡裡跟其他鈕同一款外形，停用的樣子交給 aria-disabled。 */
.po-actions-dock .po-card-actions .btn-plain {
  background: var(--fill);
  text-decoration: none;
}

.po-actions-dock .po-xray-note,
.po-actions-dock .po-nhi-form {
  order: 1;
}

/* 「退掛」按下去變成問句（確定／取消）時佔整列，問句與兩顆鈕不擠在三分之一寬裡。 */
.po-actions-dock .inline-confirm.is-asking {
  flex-basis: 100%;
}

.po-actions-dock .inline-confirm.is-asking button {
  width: auto;
}

/* 單欄寬度（<1180，動作群留在頁首）：頁首改成行內排版，不是 flex。
   flex 容器自成 BFC，排在浮動的帳號列（.topbar，約 44 高）旁邊時**整個盒子全高**都被縮窄，第二、三排也少掉
   帳號列那一截寬，結果是三短排、右邊一大片空白（使用者 10-27：「空了那麼一大塊是怎樣?」）。
   行內排版的每一行（line box）只有跟浮動同高的那一行讓開，下面幾行用滿全寬。
   分組盒子照舊 display: contents，裡面每一樣變成行內的一格；要獨佔一行的（補卡退還、PIN 小表單、X 光那一句）照舊整行。 */
@media (max-width: 1179px) {
  .po-banner {
    display: block;
    line-height: 1;
  }

  .po-banner-row > :not([hidden]),
  .po-flags > :not([hidden]),
  .po-banner-right > :not([hidden]),
  .po-cardrow > :not([hidden]) {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    margin: var(--sp-1) var(--sp-3) var(--sp-1) 0;
  }

  .po-banner-row > .po-name,
  .po-banner-row > .po-facts {
    display: inline-block;
    max-width: 100%;
  }

  .po-banner .po-quiet-slot {
    gap: var(--sp-2);
  }

  .po-banner .po-deposit-slot:not([hidden]),
  .po-banner .po-nhi-form:not([hidden]),
  .po-banner .po-xray-note:not([hidden]) {
    display: flex;
  }
}

/* 每一頁共用的「‹ 返回」（back-nav.js）：主內容左上角，浮在左邊，各頁的標題自然排在它右邊（跟浮在右邊的帳號列同一個做法），
   不另外佔一整行高度。外觀同病患表單的「‹ 返回」（btn-secondary）。 */
.nav-back {
  float: left;
  min-height: var(--h-control-sm);
  margin: calc((var(--fs-large) * 1.15 - var(--h-control-sm)) / 2) var(--sp-4) var(--sp-2) 0;
}

.nav-back[hidden],
body.has-own-back .nav-back {
  display: none;
}
