/* Named, reusable components.

   Where does a rule go? If it styles a bare element (table, input, h1) it
   belongs in base.css. If it needs a class because the same shape recurs
   with a name -- a card, a status pill, an action group -- it belongs here.
   Layout that only exists on one screen belongs in views.css.

   Loaded after base.css, so a component rule wins over the element rule it
   overrides at equal specificity. */

/* --- Card -------------------------------------------------------------- 
   No border, no shadow: a card is told apart from the page only by being
   a step lighter (--group on --canvas). */

.card {
  background: var(--group);
  border-radius: var(--r-group);
  padding: var(--sp-7);
}

.card + .card {
  margin-top: var(--sp-6);
}

.card h2 {
  font-family: var(--font-display);
  font-size: var(--fs-title2);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.01em;
  line-height: 1.25;
  margin: 0 0 var(--sp-4);
  color: var(--ink);
}

/* A table in a card runs closer to the card's edges than the rest of the
   card does, so its rows (and their hover) nearly reach them while the
   text in the cells still lines up with the heading above. */
.card > table {
  width: calc(100% + 2 * var(--sp-3));
  margin-left: calc(-1 * var(--sp-3));
  margin-right: calc(-1 * var(--sp-3));
}

details.card > summary {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  list-style: none;
  cursor: pointer;
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  min-height: var(--h-control);
}

details.card > summary::-webkit-details-marker,
details > summary.is-arrowed::-webkit-details-marker {
  display: none;
}

/* The arrow: a small chevron on the right, turned a quarter when open.
   Not animated (spec: positions change instantly). */
details.card > summary::after {
  content: '';
  width: 8px;
  height: 8px;
  margin-left: auto;
  margin-right: 4px;
  border-right: 1.5px solid var(--ink-3);
  border-bottom: 1.5px solid var(--ink-3);
  transform: rotate(-45deg);
}

details.card[open] > summary::after {
  transform: rotate(45deg);
}

/* --- Status pills ------------------------------------------------------
   Every status in the UI is a wash with an ink from the same signal. A new
   status adds a variant here and, if no pair fits, a wash/ink pair in
   tokens.css -- it should never introduce a new shape. Never orange. */

.pill {
  display: inline-flex;
  align-items: center;
  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;
  vertical-align: middle;
}

/* A clinical warning is not a status: square-ish and outlined, so it reads
   as "careful" at a glance and never as one more state. */
.pill-allergy {
  background: var(--danger-wash);
  color: var(--danger);
  border-radius: var(--r-tag);
  box-shadow: inset 0 0 0 1px currentColor;
}

.pill-active    { background: var(--ok-wash);     color: var(--ok-ink); }
.pill-inactive  { background: transparent;        color: var(--ink-3); box-shadow: inset 0 0 0 1px var(--hairline-strong); }
.pill-draft     { background: var(--warn-wash);   color: var(--warn); }
.pill-finalized { background: var(--fill-strong); color: var(--ink); }

/* A filing period's state, named as the API sends it: finished, waiting
   on someone, too late, nothing to do yet. */
.pill-done     { background: var(--ok-wash);     color: var(--ok-ink); }
.pill-due      { background: var(--warn-wash);   color: var(--warn); }
.pill-overdue  { background: var(--danger-wash); color: var(--danger); }
.pill-upcoming { background: transparent;        color: var(--ink-3); box-shadow: inset 0 0 0 1px var(--hairline-strong); }

/* Appointment statuses. Four of the five variant names are the status
   value from the API (the schema's CHECK on appointments.status), so
   pillCell(label, a.status) works with no mapping. .pill-free has no
   status behind it: a free slot is the absence of an appointment, painted
   by the calendar. Do not go looking for it in the schema. */
.pill-scheduled { background: var(--fill);        color: var(--ink-2); }
.pill-arrived   { background: var(--ok-wash);     color: var(--ok-ink); }
.pill-cancelled { background: transparent;        color: var(--ink-3); text-decoration: line-through; }
.pill-no_show   { background: var(--danger-wash); color: var(--danger); }
.pill-free      { background: var(--group);       color: var(--ink-3); outline: 1px dashed var(--hairline-strong); outline-offset: -1px; }

/* --- Table helpers ------------------------------------------------------ */

.empty-row td {
  white-space: normal;
  text-align: center;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  padding: var(--sp-6) var(--sp-5);
}

tbody tr.empty-row:hover {
  background: none;
}

.muted {
  color: var(--ink-3);
}

/* --- Numbers -------------------------------------------------------------
   Money and counts in a table: tabular digits so a column of amounts lines
   up. Stays in the UI face -- a row cannot grow to fit 24px serif digits.
   Numbers that are the point of a line use .figure instead. */
.numeric {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* 不斷行的格子：電話、日期這類拆成兩行會被讀成兩個值的。 */
.cell-nowrap {
  white-space: nowrap;
}

.figure {
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-weight: var(--fw-regular);
}

/* --- Forms -------------------------------------------------------------- */

form {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-5) var(--sp-6);
  max-width: 720px;
}

form label {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

/* The two rules above set display, which beats the browser's own [hidden]
   rule: a form or a field the script hides (the sign-in screen's user name
   and PIN, say) would still show without these. */
form[hidden],
form label[hidden],
.form-actions[hidden] {
  display: none;
}

/* A standalone labelled control outside a form grid. */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  margin-bottom: var(--sp-4);
  max-width: 320px;
}

.form-actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}

/* --- Buttons ------------------------------------------------------------
   The bare button (base.css) is the secondary look. These are the roles.
   Orange is the primary's fill and nothing else. */

/* 短標籤一律一行（北昕第一批後續 S6、S7；使用者 10-27：「排版 是橫式 變成直式了(錯誤)」「超出範圍」）。
   按鈕、切換、膠囊、狀態標、頁首名字都是幾個字的標籤，被擠窄時以前會一字一行（「今／天」「陳櫃台・櫃」＋「台」）。
   字不換行；空間不夠由版面讓位：分段切換整顆換到下一行（.segmented 的 flex-wrap），外層的列本來就會換行。
   版面閘門 label_wrap 檢查（scripts/layoutgate）量四種寬度×三種角色，抓得到這一種。 */
button,
.btn-primary,
.btn-secondary,
.btn-plain,
.btn-danger,
.btn-link,
.chip,
.pill,
.po-st {
  white-space: nowrap;
}

.btn-primary,
.btn-link {
  background: var(--accent);
  color: var(--on-accent);
}

.btn-primary:hover,
.btn-link:hover {
  background: var(--accent-hover);
  color: var(--on-accent);
}

.btn-primary:active:not(:disabled) {
  background: var(--accent-press);
}

.btn-primary:disabled {
  background: var(--disabled-bg);
  color: var(--disabled-ink);
}

.btn-secondary {
  background: var(--fill);
  color: var(--ink);
}

.btn-danger {
  background: var(--fill);
  color: var(--danger);
}

.btn-danger:hover {
  background: var(--danger-wash);
}

/* Inside a table row a button is a size smaller, so it never sets the
   row's height. */
td .btn-secondary,
td .btn-danger,
td .btn-primary,
td button {
  min-height: var(--h-control-sm);
  padding: 0 var(--sp-4);
  font-size: var(--fs-footnote);
}

/* A button that is only words: 移除, 登出, the sidebar's own two. */
.btn-plain {
  min-height: var(--h-control);
  padding: 0;
  background: none;
  color: var(--ink-2);
  font-weight: var(--fw-regular);
  text-decoration: underline;
  text-decoration-color: var(--hairline-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

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

/* Waiting for the host (aria-busy, set by dom.js lockWhileRunning and the
   sign-in and charge forms): the words go, the width stays, a ring turns.
   Only after 300ms, so a quick answer never flashes a spinner. The button
   keeps its own colours rather than the disabled grey. */
button[aria-busy="true"] {
  position: relative;
  animation: busy-hide-text 0s linear 300ms forwards;
}

.btn-primary[aria-busy="true"]:disabled {
  background: var(--accent);
  color: var(--on-accent);
}

button[aria-busy="true"]:disabled:not(.btn-primary) {
  background: var(--fill);
  color: var(--ink);
}

button[aria-busy="true"]::after {
  content: '';
  position: absolute;
  inset: 0;
  width: 18px;
  height: 18px;
  margin: auto;
  border-radius: 50%;
  border: 2px solid rgb(0 0 0 / 0.12);
  border-top-color: var(--ink);
  opacity: 0;
  animation: busy-show 0s linear 300ms forwards, spin 800ms linear 300ms infinite;
}

.btn-primary[aria-busy="true"]::after {
  border-color: var(--spin-track);
  border-top-color: var(--on-accent);
}

@keyframes busy-hide-text {
  to { color: transparent; }
}

@keyframes busy-show {
  to { opacity: 1; }
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Reduced motion: a still, filled ring instead of a turning one. */
@media (prefers-reduced-motion: reduce) {
  button[aria-busy="true"]::after {
    animation: busy-show 0s linear 300ms forwards;
    border-color: var(--spin-track);
    background: var(--spin-track);
  }
}

.action-group {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

/* --- Amendment list -----------------------------------------------------
   The read-only trail attached under a finalized visit's row. Indented so
   it reads as belonging to the row above rather than as a sibling. */

.amendment-list {
  background: var(--fill);
  border-radius: 12px;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  padding: var(--sp-4) var(--sp-5);
}

.amendment-list div + div {
  margin-top: var(--sp-1);
}

/* --- Signed versions ----------------------------------------------------
   Under the amendment trail, set the same way: it belongs to the row above.
   A version that needs someone (overdue, stood in for) reads in red. */

/* 這幾列是整列一格（colSpan）、又剛好是該列的第一格：base.css 的 td:first-child
   不換行會套上來，長的憑證主旨把表格撐寬，平板上整張表要橫向捲、操作按鈕被
   切掉（DEMO 彩排 R32）。整列的說明文字照常換行。 */
.items-row > td,
td.amendment-list,
td.version-list {
  white-space: normal;
}

.version-list {
  background: var(--fill);
  border-radius: 12px;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  padding: var(--sp-4) var(--sp-5);
}

.version-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

.version-line + .version-line {
  margin-top: var(--sp-1);
}

.version-line.is-warning > span:first-child {
  color: var(--danger);
  font-weight: 600;
}

/* 「簽章」按下後的那句話（visits.js signButton）；要使用者去做事時轉紅。 */
.sign-note.is-warning {
  color: var(--danger);
}

.late-reason-form {
  display: flex;
  gap: var(--sp-2);
}

/* --- Duration stepper --------------------------------------------------- */

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

.stepper button {
  width: 40px;
  min-height: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
  background: var(--fill);
  color: var(--ink);
}

.stepper button:hover {
  background: var(--fill-strong);
}

.stepper button:disabled {
  background: var(--disabled-bg);
  color: var(--disabled-ink);
}

.stepper-value {
  padding: 0 var(--sp-2);
  min-width: 4em;
  text-align: center;
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-numeral);
  color: var(--ink);
}

/* Laid out like a form label, but not one: see the note in index.html. */
.stepper-field {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

/* --- Chips: one choice among a few -------------------------------------- */

.chip {
  min-height: var(--h-control-sm);
  padding: 0 var(--sp-4);
  border-radius: var(--r-pill);
  background: var(--fill);
  color: var(--ink);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
}

/* Chosen: deep ink, never orange. */
.chip.is-on,
.chip.is-on:hover {
  background: var(--ink);
  color: var(--canvas);
  font-weight: var(--fw-semibold);
}

/* --- Notice: something to do before this screen is useful ---------------
   精簡成一行：16px 字（全站下限）、上下 8px、左邊一個圖示，連結跟著句子走。
   原本 17px 字加上下 16px，一則提示就佔 56px，把整頁內容往下推
   （使用者回報「留白太多」，2026-10-23）。
   預設是要先處理的事（--warn 底、驚嘆號）；.is-info 是純告知（--fill 底、
   i 圖示），不搶眼。.is-dismissible 帶 × 收起鈕（dom.js dismissibleNotice），
   只給純告知、會一直掛著的提示用，要櫃台動手的警告與錯誤不給收。 */

.notice {
  position: relative;
  margin-bottom: var(--sp-4);
  padding: var(--sp-2) var(--sp-4) var(--sp-2) calc(var(--sp-4) + 1em + var(--sp-2));
  border-radius: 12px;
  background: var(--warn-wash);
  color: var(--warn);
  font-size: var(--fs-footnote);
  line-height: var(--lh-ui);
}

/* 圖示用 mask 畫，顏色跟字（currentColor），深色主題自動跟著換。
   垂直置中在第一行：上內距 + (行高 - 圖示高) / 2。 */
.notice::before {
  content: '';
  position: absolute;
  left: var(--sp-4);
  top: calc(var(--sp-2) + (var(--lh-ui) * 1em - 1em) / 2);
  width: 1em;
  height: 1em;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM7 3.5v6h2v-6zm0 7.5v2h2v-2z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM7 3.5v6h2v-6zm0 7.5v2h2v-2z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.notice.is-info {
  background: var(--fill);
  color: var(--ink-2);
}

.notice.is-info::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM7 6.5v6h2v-6zm0-3.5v2h2V3z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM7 6.5v6h2v-6zm0-3.5v2h2V3z'/%3E%3C/svg%3E");
}

.notice a {
  color: inherit;
}

.notice.is-dismissible {
  padding-right: calc(var(--sp-2) + var(--lh-ui) * 1em + var(--sp-2) + var(--sp-2));
}

/* 收起鈕：對齊第一行，高度 = 一行字 + 8px，不把提示撐高。 */
.notice-dismiss {
  position: absolute;
  top: var(--sp-1);
  right: var(--sp-2);
  width: calc(var(--lh-ui) * var(--fs-footnote) + var(--sp-2));
  min-height: 0;
  height: calc(var(--lh-ui) * var(--fs-footnote) + var(--sp-2));
  padding: 0;
  border-radius: var(--r-tag);
  background: none;
  color: inherit;
  font-size: var(--fs-title3);
  font-weight: var(--fw-regular);
  line-height: 1;
}

.notice-dismiss:hover,
.notice-dismiss:active:not(:disabled) {
  background: var(--fill-strong);
}

/* --- Treatment items ----------------------------------------------------
   The panel that opens under a visit's row: what was done, and the form
   that adds to it. Tinted like the amendment list, for the same reason --
   it belongs to the row above. */

.items-row > td {
  /* 底色不鋪在整格：病歷編輯區的片語 chip 本身就是 --fill，
     放在 --fill 底上會整排消失（設計審查第 1 輪 #2）。底色只留給 .items-panel。 */
  background: transparent;
  padding: var(--sp-4) 0;
}

/* The panel is a table inside a table row; hovering anywhere in it would
   otherwise light up the whole outer row. */
tbody tr.items-row:hover {
  background: none;
}

/* A table cell grows to fit the widest thing in it, and the tooth chart is
   wide. Zero width with a 100% minimum takes the panel out of that sum: it
   fills whatever the visits table gives the row, and never widens it. */
.items-panel {
  width: 0;
  min-width: 100%;
  /* 面板放在看診列的第一格裡，會繼承 base.css 的 td:first-child 不換行；
     不改回來的話保固提醒等長句在手機上被切掉而不是換行（QA T6）。 */
  white-space: normal;
  margin-top: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  background: var(--fill);
  border-radius: var(--r-group);
}

.items-scroll {
  overflow-x: auto;
}

.items-table {
  background: var(--group);
  border-radius: 12px;
  border-collapse: separate;
  border-spacing: 0;
}

.items-table th {
  padding-top: var(--sp-3);
}

.item-code {
  margin-right: var(--sp-3);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

/* The tooth numbers: the thing a dentist reads first. */
.items-table td:nth-child(2) {
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-numeral);
  line-height: 1.1;
}

/* 處置表在平板的看診卡裡只有 300 多 px 寬：牙位、牙面、數量、點數不換行，處置與
   診斷給最小寬度，放不下就在 .items-scroll 裡左右捲，不會把診斷壓成一欄一個字、
   或被卡片切掉（QA D37）。 */
.items-table td:nth-child(n+2):nth-child(-n+3),
.items-table td:nth-child(n+5):nth-child(-n+6),
.items-table th {
  white-space: nowrap;
}

.items-table td:first-child {
  min-width: 10em;
}

.items-table td:nth-child(4) {
  min-width: 9em;
}

.item-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  /* 通用 form 的 720px 上限會把牙位圖（兩半各八顆）切掉，這張表要用滿面板寬。 */
  max-width: none;
  margin-top: var(--sp-4);
  padding: var(--sp-6);
  background: var(--group);
  border-radius: var(--r-group);
}

.item-form-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-title2);
  font-weight: var(--fw-semibold);
}

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

/* .item-field 設了 display，蓋過瀏覽器的 [hidden]：「部位」一列在不需要時（單顆牙的處置）要真的收起來。 */
.item-field[hidden] {
  display: none;
}

.item-field-label {
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.item-quantity {
  width: 5rem;
}

/* Catalog search: a box, the matches under it, then the chosen entry. */

.catalog-picker input {
  width: 100%;
  box-sizing: border-box;
}

/* position: relative 讓選項的 offsetTop 以清單為準，鍵盤移動時才算得出要捲多少
   （treatment-items.js 的 highlight）；max-height 由 revealResults 視需要放大到 5 項。 */
.catalog-results {
  position: relative;
  display: flex;
  flex-direction: column;
  max-height: 16rem;
  overflow-y: auto;
  background: var(--group);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
  border-radius: 12px;
}

.catalog-results:empty {
  display: none;
}

.catalog-results p {
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.catalog-option {
  white-space: normal;
  min-height: var(--h-control);
  text-align: left;
  font-weight: var(--fw-regular);
  background: none;
  border-radius: 0;
  color: var(--ink);
  padding: var(--sp-2) var(--sp-4);
}

.catalog-option + .catalog-option {
  box-shadow: inset 0 1px 0 var(--hairline);
}

.catalog-option:hover,
.catalog-option:focus-visible,
.catalog-option.is-active {
  background: var(--nav-hover);
}

/* 滑鼠停在某列、再用 ↑↓ 移走時，只留鍵盤停的那一列反白（兩列都亮會分不出 Enter 選哪個）。 */
.catalog-results:has(.is-active) .catalog-option:hover:not(.is-active) {
  background: none;
}

.catalog-option:active:not(:disabled) {
  transform: none;
}

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

.procedure-note {
  margin-top: var(--sp-2);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.procedure-note p {
  margin: var(--sp-2) 0 0;
  line-height: 1.6;
}

/* The tooth chart. Fixed-size squares, so the two halves of each row line
   up into quadrants; on a narrow screen the chart scrolls sideways rather
   than shrinking the buttons below what a finger can hit. */

.tooth-scroll {
  overflow-x: auto;
  padding-bottom: var(--sp-1);
}

.tooth-grid {
  display: inline-flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.tooth-row {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.tooth-row-label {
  width: 3.5rem;
  flex: none;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.tooth-half {
  display: flex;
  gap: var(--sp-1);
  /* Eight teeth wide whether the row has eight teeth or five, so the
     deciduous rows sit against the midline like the permanent ones. */
  width: calc(8 * 44px + 7 * var(--sp-1));
  flex: none;
}

/* The midline between the patient's right and left. */
.tooth-half:first-of-type {
  padding-right: var(--sp-3);
  box-shadow: inset -2px 0 0 var(--hairline-strong);
  width: calc(8 * 44px + 7 * var(--sp-1) + var(--sp-3));
}

.tooth-half:first-of-type {
  justify-content: flex-end;
}

.tooth {
  width: 44px;
  height: 48px;
  min-height: 48px;
  padding: 0;
  flex: none;
  border-radius: var(--r-tooth);
  background: var(--fill);
  color: var(--ink);
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-title3);
  font-weight: var(--fw-regular);
}

.tooth.is-on,
.tooth.is-on:hover {
  background: var(--ink);
  color: var(--canvas);
  font-weight: var(--fw-semibold);
}

/* Disabled is a colour, never a fade (spec: no opacity). */
.tooth:disabled,
.chip:disabled {
  background: var(--disabled-bg);
  color: var(--disabled-ink);
}

.tooth-areas,
.surface-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

.surface-picker {
  margin-top: 0;
}

/* --- 牙位圖（odontogram.js；spec 2026-10-22-tooth-chart §3.4） ------------
   沿用牙位挑選器的格子與排法（.tooth-grid / .tooth-row / .tooth-half），每顆牙
   加高一行放補過的牙面。狀態只靠底色與字色分層，不用橘色：根管是一顆墨色小點，
   牙冠／植牙是較深的 --fill-press（--tile 跟一般牙的 --fill 太接近，看不出差別），
   已拔是空心框加刪除線。 */

.odonto {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-3);
}

.odonto .tooth-half {
  width: calc(8 * var(--tooth-w) + 7 * var(--sp-1));
}

.odonto .tooth-half:first-of-type {
  width: calc(8 * var(--tooth-w) + 7 * var(--sp-1) + var(--sp-3));
}

/* 牙位圖要裝進所在的卡片：看診表單裡的內容寬比病患頁窄，固定 44px 的牙會把
   27/28/37/38 推到卡片外（QA T6）。捲動區撐滿父層並當尺寸容器，牙寬依可用寬度
   在 30–44px 之間縮放（號碼字級跟著縮）；再窄（手機）就停在 30px，
   改為在捲動區內左右捲，不會超出卡片。扣掉的是列標籤、兩個列間距、中線留白、
   14 個牙間距，再多扣 2px 吸收小數像素。 */
.odonto .tooth-scroll {
  align-self: stretch;
  container-type: inline-size;
  scrollbar-width: thin;
}

/* 下限從 36px 降到 30px：平板直放（768）的病患頁要放得下一整排 16 顆，
   27/28、37/38 才不會跑出卡片外（彩排 #2）；號碼字級跟著牙寬縮。 */
.odonto .tooth-grid {
  --tooth-w: clamp(30px, (100cqi - 3.5rem - 2 * var(--sp-4) - var(--sp-3) - 14 * var(--sp-1) - 2px) / 16, 44px);
}

.odonto .odonto-tooth {
  font-size: min(var(--fs-title3), calc(var(--tooth-w) * 0.62));
}

.odonto-tooth {
  width: var(--tooth-w);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  height: 64px;
  min-height: 64px;
}

.odonto-number {
  line-height: 1;
}

/* 牙面與根管點那一行；空的時候也佔高度，整排的號碼才對得齊。 */
.odonto-marks {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  min-height: 18px;
  max-width: calc(var(--tooth-w) - 4px);
  overflow: hidden;
}

.odonto-surfaces {
  font-family: var(--font-ui);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
  line-height: 1;
  color: var(--ink-2);
  white-space: nowrap;
}

.odonto-dot {
  width: var(--indicator-dot);
  height: var(--indicator-dot);
  flex: none;
  border-radius: var(--r-pill);
  background: var(--ink-2);
}

.odonto-tooth.is-crown {
  background: var(--fill-press);
}

.odonto-tooth.is-gone {
  background: transparent;
  box-shadow: inset 0 0 0 2px var(--hairline-strong);
  color: var(--ink-3);
}

.odonto-tooth.is-gone .odonto-number {
  text-decoration: line-through;
}

/* 選中時字和點都翻成底色，跟挑選器的 .tooth.is-on 一樣是深墨，不是橘色。
   底色要在這裡再寫一次：.is-crown／.is-gone 與 .tooth.is-on 權重相同又寫在後面，
   不重寫的話選中的牙冠是淺字配淺底（讀不出來）、選中的已拔牙看不出被選中
   （QA T6；滑鼠停著時 :hover 權重較高才碰巧正常，所以要在沒有 hover 時驗）。 */
.odonto-tooth.is-on,
.odonto-tooth.is-on:hover {
  background: var(--ink);
  color: var(--canvas);
}

.odonto-tooth.is-on .odonto-surfaces {
  color: var(--canvas);
}

.odonto-tooth.is-on.is-manual {
  outline-color: var(--canvas);
}

.odonto-tooth.is-on .odonto-dot {
  background: var(--canvas);
}

.odonto-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
  margin: 0;
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

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

.odonto-swatch {
  display: inline-block;
  width: 18px;
  height: 18px;
  border-radius: var(--r-tag);
  background: var(--fill);
}

.odonto-swatch.is-endo {
  width: var(--indicator-dot);
  height: var(--indicator-dot);
  border-radius: var(--r-pill);
  background: var(--ink-2);
}

.odonto-swatch.is-crown {
  background: var(--fill-press);
}

.odonto-swatch.is-gone {
  background: transparent;
  box-shadow: inset 0 0 0 2px var(--hairline-strong);
}

/* 手動標記（外院／手動）：用虛線框和紀錄推導的分開，不用顏色（橘色留給主要動作）。 */
.odonto-tooth.is-manual {
  outline: 2px dashed var(--ink-3);
  outline-offset: -5px;
}

.odonto-swatch.is-manual {
  background: transparent;
  outline: 2px dashed var(--ink-3);
  outline-offset: -2px;
}

.odonto-manual-tag {
  padding: 0 var(--sp-2);
  border-radius: var(--r-tag);
  background: var(--fill);
  color: var(--ink-2);
  font-size: var(--fs-footnote);
  white-space: nowrap;
}

.odonto-mark-actions {
  display: inline-flex;
  gap: var(--sp-2);
}

.odonto-mark-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}

.odonto-mark-form h4 {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
}

/* 卡片本身是 --group，展開的歷史用頁面底色 --canvas 分出一層。 */
.odonto-detail {
  align-self: stretch;
  padding: var(--sp-4) var(--sp-5);
  background: var(--canvas);
  border-radius: var(--r-group);
}

.odonto-detail-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  font-size: var(--fs-body);
}

.odonto-detail-head .figure {
  font-size: var(--fs-numeral);
}

.odonto-history .overview-line {
  flex-wrap: wrap;
  row-gap: var(--sp-1);
  font-size: var(--fs-body);
}

/* 做了什麼（「拔牙」「OD 面」）是短詞，不在詞中間斷行；長的是後面的註記。 */
.odonto-event-what {
  word-break: keep-all;
}

.odonto-event-notes {
  flex: 1 1 12rem;
  min-width: 0;
}

/* 手機上一筆紀錄直排：日期、做了什麼、誰標的與註記、修改／刪除各占一行，
   不再擠成四欄（QA T6：註記只剩五個字寬、按鈕跑到畫面外）。 */
@media (max-width: 899px) {
  .odonto-history .overview-line {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-1);
    padding: var(--sp-3) 0;
  }

  .odonto-event-notes {
    flex: none;
  }
}

/* 看診的治療項目裡，牙位圖收在可展開的一行，平常不佔版面。 */
.odonto-details > summary {
  min-height: var(--h-control);
  display: flex;
  align-items: center;
  cursor: pointer;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.odonto-details[open] > summary {
  margin-bottom: var(--sp-3);
}

/* summary 設成 flex 之後瀏覽器原本的展開三角形就不見了，整行看起來只是一段標題
   （DEMO 彩排 3：被當成處置表的標題）。補一個箭頭，讓它看得出是可以點開的開關。 */
.odonto-details > summary::before {
  content: "▸";
  margin-right: var(--sp-2);
  color: var(--ink-3);
}

.odonto-details[open] > summary::before {
  content: "▾";
}

.odonto-details > summary::-webkit-details-marker {
  display: none;
}

/* 已定稿的病歷裡，牙位圖開關接在處置表與合計後面，跟表格隔一條線。 */
.items-panel > .odonto-details {
  margin-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}

/* 補牙保固提醒：表單裡的一段字，不跳視窗。待處理的提醒用 warn（不是紅色，
   這不是錯誤，醫師可以照樣存）。 */
.warranty-warnings {
  padding: var(--sp-3) var(--sp-4);
  background: var(--warn-wash);
  color: var(--warn);
  border-radius: 12px;
  font-size: var(--fs-footnote);
}

.warranty-warnings p {
  margin: 0;
}

.warranty-warning {
  font-weight: var(--fw-semibold);
}

.warranty-warning + .warranty-warning {
  margin-top: var(--sp-1);
}

.warranty-warnings .warranty-hint {
  margin-top: var(--sp-2);
}

/* 洗牙提醒（第 4 批）：同保固提醒的框；就地按鈕（換成／加入）接在那一行後面，
   存檔前的「照樣存檔／回去修改」排在最下面。 */
.scaling-reminder {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* 規則檔紅色的太早：定稿一定擋，存檔前也只能回去修改（smooth-ops）。 */
.scaling-reminder.is-red {
  color: var(--danger);
}

.scaling-reminder .scaling-reminder-action {
  min-height: 28px;
  padding: 0 var(--sp-3);
}

.scaling-confirm {
  display: flex;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

@media (pointer: coarse) {
  .scaling-reminder .scaling-reminder-action,
  .scaling-confirm button {
    min-height: 44px;
  }
}

/* --- Monthly claim ------------------------------------------------------ */

.claim-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-8);
  margin-bottom: var(--sp-4);
}

.claim-stat {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.claim-stat strong {
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--fs-large);
  font-weight: 500;
  line-height: 1.1;
  color: var(--ink);
  text-align: left;
}

.claim-download {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-4);
  font-size: var(--fs-footnote);
}

/* A link dressed as the primary button: downloading is a navigation, which
   is what a link is, but it is this card's main action. */
.btn-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--h-control);
  padding: 0 var(--sp-5);
  border-radius: var(--r-pill);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  text-decoration: none;
}

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

/* An anchor dressed as a secondary button (a link in a toolbar). */
a.btn-secondary {
  display: inline-flex;
  align-items: center;
  min-height: var(--h-control);
  padding: 0 var(--sp-5);
  border-radius: var(--r-pill);
  font-weight: var(--fw-semibold);
  text-decoration: none;
}

a.btn-secondary[hidden] {
  display: none;
}

a.btn-secondary:hover {
  background: var(--fill-strong);
}

.claim-problem-title {
  margin: var(--sp-4) 0 var(--sp-2);
  font-size: var(--fs-body);
}

.claim-problem-title:first-child {
  margin-top: 0;
}

.claim-problem-list {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-body);
  line-height: 1.7;
}

/* A problem is its words in the signal's ink after a dot of the same ink:
   no ground, no stripe. */
.claim-problem-list > li {
  position: relative;
  padding-left: calc(6px + var(--sp-3));
}

.claim-problem-list > li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.75em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.claim-problem-error {
  color: var(--danger);
}

.claim-problem-warning {
  color: var(--warn);
}

.claim-form {
  background: var(--canvas);
  border-radius: var(--r-group);
  padding: var(--sp-5);
}

/* --- Patients ------------------------------------------------------------ */

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

.patient-toolbar h2 {
  margin: 0;
  font-size: var(--fs-title1);
}

/* The search: tall, a pill, a magnifier drawn in on the left. */
.patient-toolbar input[type="search"] {
  width: 480px;
  max-width: 100%;
  min-height: var(--h-search);
  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"] .patient-toolbar 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");
}

.patient-toolbar select {
  width: auto;
}

/* Who the patient is, above their form: the name in the heading face. */
.patient-identity {
  margin: var(--sp-2) 0 var(--sp-6);
}

.patient-identity-name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-large);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.01em;
  line-height: 1.2;
}

.patient-identity-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

/* The patient's page: the form card, then the overview's cards. */
#patient-overview {
  margin-top: var(--sp-6);
}

/* Deleting is kept apart from saving, at the far end of the row. */
#patient-form-delete {
  margin-left: auto;
}

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  margin-top: var(--sp-4);
  font-size: var(--fs-footnote);
}

.pager[hidden] {
  display: none;
}

.overview-title {
  margin: var(--sp-5) 0 var(--sp-2);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  color: var(--ink-3);
}

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

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

.overview-line.is-cancelled span:not(.pill) {
  text-decoration: line-through;
  color: var(--ink-3);
}

.cooper-visit {
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--hairline);
}

.cooper-visit:last-child {
  border-bottom: none;
}

.cooper-visit-head,
.cooper-procedure-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-3);
}

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

/* --- 牙位十字（web/js/tooth-cross.js；北昕的畫法，使用者 2026-10-26「十字要能填數字」） ---
   上兩格、下兩格，中間一個十字：左上格＝#1x、右上格＝#2x、左下格＝#4x、右下格＝#3x
   （病患的右邊在畫面左邊）。可打字的（處置表、表單）與唯讀的（已定稿、看診紀錄、收費頁、
   北昕匯入的紀錄）是同一組類名，看起來是同一個東西。十字的兩劃畫在格子之間的縫上，
   格子本身沒有底色：空著就是一個空的十字（使用者：「不用顯示未填 空下來就好」）。 */
.tooth-cross {
  --cross-cell-w: 2.4em;
  --cross-cell-h: 1.4em;
  /* 字級寫死成內文：放在唯讀處置表的牙位格裡時，那一格的 24px 襯線數字（.items-table td:nth-child(2)）
     會把用 em 算的十字撐成兩倍大（量過：一格 77×36，整列 100 高）。 */
  font-family: var(--font-ui);
  font-size: var(--fs-footnote);
  line-height: 1.4;
  --cross-line: var(--ink-3);
  position: relative;
  display: inline-grid;
  grid-template-columns: repeat(2, var(--cross-cell-w));
  grid-auto-rows: var(--cross-cell-h);
  gap: 3px;
  flex: none;
  vertical-align: middle;
  font-feature-settings: var(--numerals);
  font-variant-numeric: lining-nums tabular-nums;
}

.tooth-cross[hidden] {
  display: none;
}

/* 兩劃：直的那一筆分病患左右，橫的那一筆分上下顎。 */
.tooth-cross::before,
.tooth-cross::after {
  content: '';
  position: absolute;
  background: var(--cross-line);
  pointer-events: none;
}

.tooth-cross::before {
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  transform: translateX(-50%);
}

.tooth-cross::after {
  left: 0;
  right: 0;
  top: 50%;
  height: 2px;
  transform: translateY(-50%);
}

.tooth-cross-cell {
  min-width: 0;
  overflow: hidden;
  font-size: var(--fs-footnote);
  line-height: var(--cross-cell-h);
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--ink);
}

/* 左邊兩格靠中線（向右對齊）、右邊兩格也靠中線：數字貼著十字，讀起來是一顆一顆往外數。 */
.tooth-cross-cell:nth-child(odd) {
  text-align: right;
}

/* 唯讀的十字跟著字長寬（至少一格 1.4em）：唯讀處置表的牙位欄很窄，固定寬會蓋到隔壁的牙面欄。 */
/* 兩欄一樣寬（取比較寬的那一格），直的那一筆才會落在兩欄中間。 */
.tooth-cross.is-view {
  --cross-cell-w: minmax(max-content, 1fr);
}

.tooth-cross.is-view .tooth-cross-cell {
  min-width: 1.6em;
  padding: 0 var(--sp-1);
}

/* 可打字的格子：四個看得見的輸入框（框線＋輸入框底色），十字那兩筆在框與框之間的縫裡。
   不畫框的話看起來是一張細線畫、不像能打字的地方（主控 2026-10-26 看截圖退回）。
   z-index：十字的線是疊在上面的定位元素，框要蓋過它，線才只露在縫裡。 */
.tooth-cross.is-entry {
  gap: 4px;
  --cross-line: var(--ink-2);
}

.tooth-cross.is-entry input.tooth-cross-cell {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  min-height: 0;
  padding: 0 var(--sp-1);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-tag);
  background: var(--canvas);
  font-family: inherit;
  text-align: center;
}

.tooth-cross.is-entry input.tooth-cross-cell:hover {
  border-color: var(--ink-3);
}

.tooth-cross.is-entry input.tooth-cross-cell:focus {
  border-color: var(--ink-2);
  box-shadow: inset 0 0 0 1px var(--ink-2);
  outline: none;
}

/* 打錯的那一格：和其他格子同一個標紅（底下那一句由呼叫處寫）。 */
.tooth-cross.is-entry input.tooth-cross-cell.is-bad {
  border-color: var(--danger);
  background: var(--danger-wash);
}

/* 會被讀成兩顆的寫法（左上格打 16＝11、16）：提醒色，不是錯，照樣能存；底下那一行寫會存什麼。 */
.tooth-cross.is-entry input.tooth-cross-cell.is-ambiguous {
  border-color: var(--warn);
  background: var(--warn-wash);
}

.grid-cell-note {
  font-size: var(--fs-footnote);
  line-height: 1.25;
  color: var(--warn);
  white-space: normal;
}

.grid-cell-note[hidden] {
  display: none;
}

/* 已定稿、存檔中：停用的格子照樣讀得清楚（不淡化），只是不能打。 */
.tooth-cross.is-entry input.tooth-cross-cell:disabled {
  color: var(--ink);
  background: transparent;
  border-color: var(--hairline);
  opacity: 1;
}

/* 唯讀處置表的牙位格（td.item-teeth）：十字跟著格子的寬（兩欄平分），一格的字太長就換行往下長，
   不往旁邊頂出格子、也不切字（切掉的牙位比頂出去更糟）。北昕匯入的紀錄等表格外的十字不受影響。 */
td.item-teeth .tooth-cross.is-view {
  --cross-cell-w: minmax(0, 1fr);
  display: grid;
  width: 100%;
}

td.item-teeth .tooth-cross.is-view .tooth-cross-cell {
  min-width: 0;
  padding: 0 2px;
  overflow: visible;
  white-space: normal;
  overflow-wrap: anywhere;
  text-overflow: clip;
}

.tooth-cross-wrap {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

/* 表單裡的牙位欄：十字大一號（表單有空間，手指按得到）。 */
.tooth-cross-field {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-1);
}

.tooth-cross-field .tooth-cross {
  --cross-cell-w: 4em;
  --cross-cell-h: var(--h-control-sm);
  font-size: var(--fs-body);
}

.tooth-cross-field .tooth-cross-text[hidden] {
  display: none;
}

.cooper-detail,
.cooper-note {
  margin: var(--sp-1) 0 0 var(--sp-3);
  color: var(--ink-3);
  font-size: var(--fs-footnote);
}

.cooper-note {
  display: flex;
  gap: var(--sp-3);
}

.cooper-note-label {
  flex: none;
  min-width: 2.2em;
  font-weight: 600;
}

.cooper-note-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.cooper-problem {
  color: var(--danger);
}

.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--sp-2) var(--sp-6);
  margin: 0;
}

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

.facts dd {
  margin: 0;
}

tr.is-clickable {
  cursor: pointer;
}

.field-hint {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  min-height: 1.2em;
}

.field-hint:empty {
  min-height: 0;
}

.field-hint.is-error,
#patient-count.is-error {
  color: var(--danger);
}

form label:has(.field-hint.is-error) input {
  border-color: var(--danger);
}

form label.check-field {
  flex-direction: row;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--h-control);
}

/* 勾選框維持 base.css 的 20px：原本這裡寫 width: auto，特定度高過
   input[type="checkbox"]，整站表單裡的勾選框都縮回瀏覽器預設的 13px。
   flex: none 讓長標籤在窄螢幕上也擠不扁它。 */
form label.check-field input[type="checkbox"],
form label.check-field input[type="radio"] {
  width: 20px;
  flex: none;
}

/* --- Import from 北昕 --------------------------------------------------- */

.cooper-result {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}

.cooper-result h3 {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-footnote);
}

.cooper-list {
  margin-top: var(--sp-2);
}

.cooper-list summary {
  cursor: pointer;
  font-weight: 600;
}

.cooper-list p {
  margin: var(--sp-2) 0 0;
  overflow-wrap: anywhere;
}

/* 「全部 N 筆」：清單裡的次要展開，字重和連結一樣輕，不跟原因標題搶。 */
.cooper-list .cooper-list-all > summary {
  font-weight: var(--fw-regular);
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* A row number, a name and a button each stay on one line; the reason
   beside them takes up the slack. */
.cooper-list td.note-keep,
.cooper-list td.note-keep .btn-secondary {
  white-space: nowrap;
}

.notify-unmatched {
  display: block;
  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);
}

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

/* --- Google Calendar ---------------------------------------------------- */

.gcal-status {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.gcal-status.is-error {
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--danger-wash);
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.gcal-list {
  margin-top: var(--sp-3);
}

.gcal-row {
  display: flex;
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--hairline);
}

.gcal-row:last-child {
  border-bottom: none;
}

.gcal-time {
  flex: none;
  width: 7.5rem;
  color: var(--ink-3);
}

.gcal-what {
  min-width: 0;
}

.gcal-title {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.gcal-read {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.gcal-note {
  font-size: var(--fs-footnote);
}

.gcal-row.is-cancelled .gcal-title {
  text-decoration: line-through;
  color: var(--ink-3);
}

.gcal-entry.is-cancelled strong {
  text-decoration: line-through;
}

/* Each name on its own line once it carries who it is. */
.gcal-read {
  flex-direction: column;
}

.gcal-patient {
  margin-left: var(--sp-3);
}

.gcal-chart {
  color: var(--ink-3);
}

.gcal-unmatched {
  color: var(--danger);
}

.gcal-link {
  min-height: 0;
  margin-left: var(--sp-2);
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  font: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  cursor: pointer;
}

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

.gcal-choose {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  margin: var(--sp-2) 0;
  padding: var(--sp-4) var(--sp-5);
  background: var(--fill);
  border-radius: 12px;
}

.gcal-choose .gcal-link {
  margin-left: 0;
}

.gcal-choose .patient-picker {
  width: 100%;
  max-width: 24rem;
}

.gcal-records {
  margin: var(--sp-2) 0;
  padding: var(--sp-4) var(--sp-5);
  border-radius: 12px;
  background: var(--fill);
}

.gcal-records p {
  margin: var(--sp-2) 0;
}

.gcal-records-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* --- Menus and sortable headings ----------------------------------------- */

.menu {
  position: relative;
}

.menu > summary {
  list-style: none;
  cursor: pointer;
}

.menu > summary::-webkit-details-marker {
  display: none;
}

.menu > summary.btn-secondary {
  display: inline-flex;
  align-items: center;
  min-height: var(--h-control);
  padding: 0 var(--sp-5);
  border-radius: var(--r-pill);
  font-weight: var(--fw-semibold);
}

.menu-body {
  position: absolute;
  right: 0;
  z-index: 5;
  min-width: 11rem;
  margin-top: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  background: var(--group);
  border: 1px solid var(--hairline-strong);
  border-radius: 12px;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--h-control);
  font-size: var(--fs-body);
  white-space: nowrap;
  cursor: pointer;
}

.menu-item input {
  width: auto;
}

.sort-heading {
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  white-space: nowrap;
}

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

.sort-heading.is-sorted {
  color: var(--ink);
  font-weight: var(--fw-semibold);
}

.gcal-row.is-holiday {
  background: var(--group);
  padding-left: var(--sp-3);
  padding-right: var(--sp-3);
}

/* --- The calendar's day as a timeline ---------------------------------- */

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

.segmented {
  display: inline-flex;
  /* 放不下時整顆選項換到下一行，不是把一顆選項的字擠成直排（S6）；整組也不被外層壓扁。 */
  flex-wrap: wrap;
  flex: none;
  max-width: 100%;
  gap: 2px;
  padding: 2px;
  border-radius: 10px;
  background: var(--fill);
}

/* display above would otherwise beat the hidden attribute. */
.segmented[hidden] {
  display: none;
}

.segmented button {
  min-height: 36px;
  border: 0;
  border-radius: 8px;
  background: none;
  padding: 0 var(--sp-4);
  font: inherit;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  cursor: pointer;
}

.segmented button:hover {
  background: var(--fill-strong);
}

.segmented button.is-current {
  background: var(--group);
  color: var(--ink);
  font-weight: var(--fw-semibold);
}

.gcal-which {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-6);
  margin-bottom: var(--sp-3);
  font-size: var(--fs-footnote);
}

.gcal-which label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
}

.gcal-words-title {
  margin: var(--sp-4) 0 var(--sp-2);
  font-size: var(--fs-footnote);
}

.gcal-words {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.gcal-word {
  border-radius: var(--r-pill);
  background: var(--fill);
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-footnote);
}

.gcal-allday {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-3) 3.5em;
}

.gcal-allday-chip {
  min-height: 0;
  border: 0;
  border-radius: var(--r-pill);
  padding: var(--sp-1) var(--sp-3);
  font: inherit;
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  background: var(--fill);
  color: var(--ink-2);
  cursor: pointer;
}

.gcal-allday-chip.is-holiday { background: var(--group); color: var(--ink-3); box-shadow: inset 0 0 0 1px var(--hairline-strong); }
.gcal-allday-chip.is-observance { background: none; color: var(--ink-3); box-shadow: none; outline: 1px dashed var(--hairline-strong); outline-offset: -1px; }
.gcal-allday-chip.is-closure { background: var(--fill); color: var(--ink-2); }

/* A block on the day's timeline (calendar-timeline.js). State is the fill
   alone -- no side stripe. */
.gcal-block {
  position: absolute;
  box-sizing: border-box;
  min-height: 0;
  text-align: left;
  border: 0;
  border-radius: 10px;
  padding: var(--sp-1) var(--sp-3);
  font: inherit;
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  line-height: 1.25;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  cursor: pointer;
}

.gcal-block:active:not(:disabled) {
  transform: none;
}

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

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

.gcal-detail {
  margin-top: var(--sp-4);
  padding: 0 var(--sp-5);
  background: var(--fill);
  border-radius: 12px;
}

#appt-own > summary {
  margin: var(--sp-4) 0 var(--sp-3);
  color: var(--ink-3);
  font-size: var(--fs-footnote);
  cursor: pointer;
}

.gcal-row.is-closure {
  background: var(--fill);
  padding-left: var(--sp-3);
  padding-right: var(--sp-3);
}

/* --- Charges ------------------------------------------------------------ */

.charge-wide {
  grid-column: 1 / -1;
}

.charge-presets {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* 收現金與找零（S8）：收現金那一格旁邊大字寫「找零 1,800」（不足寫「還差 N」，用警示色）；
   「多的存預付」是次要的字鈕，只在多給時出現。 */
.charge-tender {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
}

.charge-tender-field {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  white-space: nowrap;
}

.charge-tender-field input {
  width: 9rem;
  min-height: var(--h-control);
  font-size: var(--fs-title3);
  font-feature-settings: var(--numerals);
  text-align: right;
}

.charge-change {
  font-family: var(--font-display);
  font-size: var(--fs-numeral);
  font-feature-settings: var(--numerals);
  color: var(--ink);
  white-space: nowrap;
}

.charge-change.is-short {
  color: var(--warn);
}

.charge-change:empty {
  display: none;
}

/* 自費金額底下那一行（S9）：是哪幾項自費、各多少（從看診帶來的），改過的標「改過」。 */
.charge-selfpay-items {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

/* 處置表底下的「櫃台補登」（S10）：跟醫師打的處置分開一塊，小字；不在醫師簽章內容裡、不進健保申報。 */
.desk-items {
  margin-top: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-input);
  background: var(--canvas);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

.desk-items-title {
  color: var(--ink-3);
}

.desk-items-list {
  margin: var(--sp-1) 0 0;
  padding-left: var(--sp-4);
}

/* 「醫師已打這一項」（審查 r1 嚴重 1）：就地問，不加是預設。 */
.charge-desk-dup {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

.charge-desk-dup-text {
  color: var(--warn);
  font-weight: var(--fw-semibold);
}

/* 收完款那一塊：收費記好了、後面沒做成的事（補登、存預付；審查 r1 一般 4）。 */
.charge-after-problem {
  margin: var(--sp-2) 0 0;
  color: var(--warn);
  font-weight: var(--fw-semibold);
}

/* 病患頁今天清單頂端：一筆接一筆寫完、但最後一筆還沒簽章（審查 r1 建議）。 */
.po-count .po-warn-text {
  color: var(--warn);
  font-weight: var(--fw-semibold);
}

/* 作廢問句裡那一句「已收的錢請到收費紀錄改金額（退款），作廢只撤銷補登」：自己一行，警示色。 */
.charge-desk-void-note {
  flex-basis: 100%;
  color: var(--warn);
  font-size: var(--fs-footnote);
  white-space: normal;
}

.charge-desk-item {
  flex-wrap: wrap;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}

/* 銷貨（北昕第一批後續 S1–S3）：「銷貨」標籤＋搜尋列一行，底下一列一個品項（品名｜× 數量｜金額｜改過｜刪除）。
   不再是一整片小圓鈕：那一片在 1280 寬就佔四五行、把下面的欄位推到摺線下。 */
.charge-sales {
  /* flex 不用 grid：底下那幾列跨兩欄時會把「銷貨」那一欄撐寬，搜尋列被推到最右邊（1536 實測離標籤 560px）。 */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}

.charge-sales-picker {
  flex: 0 1 24rem;
  min-width: 12rem;
}

.charge-sales .sales-lines {
  flex-basis: 100%;
}

.sales-lines {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.sales-lines:empty {
  display: none;
}

.sales-line {
  display: grid;
  /* 一列最寬 44rem：數量、金額緊跟著品名，不在很寬的螢幕上被拉到另一頭（下一步在上一步旁邊，§10.2）。 */
  max-width: 44rem;
  grid-template-columns: minmax(0, 1fr) auto 5rem 7rem auto auto;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--h-control-sm);
}

.sales-line-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sales-line-qty,
.sales-line-amount {
  min-height: var(--h-control-sm);
  font-feature-settings: var(--numerals);
  text-align: right;
}

.sales-line-x {
  color: var(--ink-3);
}

.sales-line-edited,
.sales-line .btn-plain {
  white-space: nowrap;
}

.sales-line-raw {
  font-size: var(--fs-footnote);
}

.charge-debt {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--danger-wash);
  color: var(--danger);
  font-weight: var(--fw-semibold);
}

.charge-debt:empty {
  display: none;
}

/* 結帳時的健保漏報提醒：一條一行，不把收費表單往下推（spec §17.6）。 */
.charge-nhi {
  list-style: none;
  margin: var(--sp-2) 0 0;
  padding: 0;
  display: grid;
  gap: var(--sp-1);
}

.charge-nhi:empty {
  display: none;
}

.charge-nhi-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-1) var(--sp-3);
  border-radius: 10px;
  font-weight: var(--fw-semibold);
  min-width: 0;
}

.charge-nhi-item > span {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.charge-nhi-item.is-yellow {
  background: var(--warn-wash);
  color: var(--warn);
}

.charge-nhi-item.is-red {
  background: var(--danger-wash);
  color: var(--danger);
}

.charge-nhi-fix {
  flex: none;
}

.charge-items {
  margin-top: var(--sp-2);
}

/* 收費頁的處置表窄版（treatment-items.js checkout）：藏牙面、數量、點數，處置與診斷可以換行，修改／刪除
   不必左右捲就看得到（QA 10-24：1536／1024／768 寬都被捲到右邊）。只挑沒有 colspan 的格，空白列不受影響。 */
.items-panel.is-checkout .items-table :is(th, td):is(:nth-child(3), :nth-child(5), :nth-child(6)):not([colspan]) {
  display: none;
}

.items-panel.is-checkout .items-table td:first-child {
  min-width: 7em;
}

/* 收費表單很長：「記下這筆」黏在畫面下緣，日常流程不必捲到底才按得到（QA 10-24：1536×864 要捲一次）。 */
#charge-form > .form-actions {
  position: sticky;
  bottom: 0;
  z-index: 2;
  padding: var(--sp-3) 0;
  background: var(--group);
}

/* 窄螢幕底下有分頁列（views.css .tabbar，高 --tabbar-h、也黏在下緣）：儲存列停在它上面。 */
@media (max-width: 899px) {
  #charge-form > .form-actions {
    bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  }

  /* 窄螢幕的卡片會自己捲（overflow: auto），黏性的儲存列就只黏在卡片裡、整頁捲時跟著走：收費這張卡片
     不自己捲（表單本身不寬，處置表另有 .items-scroll）。 */
  .card:has(> #charge-form) {
    overflow: visible;
  }
}

/* 定稿按鈕不必整排拉長。 */
.charge-finalize {
  justify-items: start;
}

/* 窄版的牙位、診斷可以換行，窄卡片（平板直式）也不必左右捲。 */
.items-panel.is-checkout .items-table td:nth-child(2) {
  white-space: normal;
}

.items-panel.is-checkout .items-table td:nth-child(4) {
  min-width: 5em;
}

/* 定稿檢核一條一行，整句在滑鼠提示。 */
.charge-finalize-list .charge-nhi-item > span {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 病患資料的病史勾選（history-ticks.js）：一排可換行的勾選。 */
.history-ticks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
}

.charge-drafts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

.charge-drafts:empty,
.charge-finalize:empty {
  display: none;
}

.charge-drafts-label {
  font-weight: var(--fw-semibold);
  color: var(--warn);
}

.charge-finalize {
  margin-top: var(--sp-2);
  display: grid;
  gap: var(--sp-2);
}

.charge-finalize-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-1);
}

.charge-finalize-list label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}

.charge-finalize-list .is-ignored {
  opacity: 0.5;
}

.charge-finalize-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}

.charge-finalize-error {
  margin: 0;
  color: var(--danger);
}

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

.charge-receipt-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-5) var(--sp-6);
  margin-top: var(--sp-3);
}

.charge-receipt-fields label {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

/* The amount worked out as the desk types: the number is the point. */
.charge-live {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--sp-2);
  margin: 0;
}

.charge-live .figure {
  margin-right: var(--sp-4);
  font-size: 24px;
  font-weight: 500;
  line-height: 1.15;
  color: var(--ink-2);
}

/* 一個畫面只有一個大數字：合計。現金、匯款等組成數字退成 24px（設計審查第 1 輪 #9）。 */
.charge-live .figure.is-total,
#charge-day-summary .claim-stat strong.is-total {
  font-size: 40px;
  color: var(--ink);
}

#charge-day-summary .claim-stat strong {
  font-size: 24px;
  color: var(--ink-2);
}

.charge-live-label {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
}

.settings-add {
  margin-top: var(--sp-6);
}

.report-total td {
  font-weight: var(--fw-semibold);
  border-top: 1px solid var(--hairline-strong);
}

/* On a phone the visits table is wider than the screen and its card
   scrolls sideways. The panel should not scroll away with it: it is pinned
   to the left edge and sized to the card's visible width (the screen, less
   the page gutter and the card's own padding on both sides).
   釘住的是整個 .visit-panel（病歷編輯、療程、處置），不只處置區：原本只釘
   .items-panel，上面的主訴／臨床所見輸入框與片語被撐到表格寬度、右半截在
   畫面外。寬度照 B3a 手機版的幾何算：內容區左右各 --sp-5（加 safe-area）、
   卡片左右各 --sp-5、卡片沒有邊框（views.css 899px 區塊）。 */
@media (max-width: 899px) {
  .items-row > td {
    padding: var(--sp-3) 0;
  }

  .visit-panel {
    position: sticky;
    left: 0;
    min-width: 0;
    width: calc(100vw - 4 * var(--sp-5) - env(safe-area-inset-left) - env(safe-area-inset-right));
  }

  .item-form {
    padding: var(--sp-3);
  }

  .items-table {
    min-width: 34rem;
  }
}

@media (max-width: 899px) {
  .charge-receipt-fields {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 899px) {
  .gcal-row {
    flex-direction: column;
    gap: var(--sp-1);
  }

  .gcal-time {
    width: auto;
  }
}

/* More went out than came in. */
td.is-negative {
  color: var(--danger);
}

/* --- Cash drawer ----------------------------------------------------------
   Eight small boxes in a row, largest note first, as one counts them out. */
.drawer-counts {
  display: grid;
  grid-template-columns: repeat(8, minmax(4.5rem, 1fr));
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  overflow-x: auto;
}

.drawer-counts label {
  font-family: var(--font-display);
  font-feature-settings: var(--numerals);
  font-size: var(--fs-body);
  color: var(--ink-2);
}

.drawer-counts input {
  font-family: var(--font-ui);
  min-width: 0;
  text-align: right;
}

/* One charge on a patient's page, or under a 北昕 visit of the same day. */
.charge-line {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-4);
  min-height: var(--h-row);
  padding: var(--sp-2) 0;
  font-size: var(--fs-footnote);
}

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

.cooper-visit .charge-line {
  margin-top: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-radius: 12px;
  background: var(--fill);
}

/* --- Reading a health card at check-in ----------------------------------- */
.card-result {
  margin-top: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-radius: 12px;
  background: var(--fill);
}

.card-result p {
  margin: var(--sp-2) 0;
}

tr.is-highlight td {
  background: var(--fill);
}

/* 標示中的一列底色就是 --fill，跟次要按鈕同色（深色模式下完全看不出按鈕）；
   列內的次要按鈕往上加深一階。 */
tr.is-highlight td .btn-secondary {
  background: var(--fill-strong);
}

tr.is-highlight td .btn-secondary:hover {
  background: var(--fill-press);
}


/* --- Code tables (設定 → 代碼表) ------------------------------------------ */

.code-line {
  margin: 0 0 var(--sp-2);
}

.code-check,
.code-import,
.code-pending .form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}

.code-interval {
  width: 4.5em;
}

.code-pending,
.code-recent {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}

.code-recent h3 {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-body);
}

.code-edition + .code-edition {
  margin-top: var(--sp-3);
}

.code-changes {
  font-size: var(--fs-footnote);
}

.code-changes ul {
  margin: var(--sp-2) 0 0;
  padding-left: var(--sp-6);
}

/* --- Cloud query: the button, the tabs looked at, the alert badges ------- */

.cloud-query {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

/* 「虛擬卡」: an alternative to the button beside it, not a second action. */
.btn-text {
  min-height: var(--h-control);
  background: none;
  border: none;
  padding: 0;
  color: var(--ink-2);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-regular);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  cursor: pointer;
}

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

.btn-text:disabled {
  background: none;
  color: var(--disabled-ink);
  text-decoration: none;
}

.cloud-query-status,
.cloud-tabs {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-footnote);
}

.cloud-query-error {
  color: var(--danger);
  font-size: var(--fs-footnote);
}

.cloud-alerts p {
  margin: 0 0 var(--sp-2);
}

.claim-problem-info {
  color: var(--ink-2);
}

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

.claim-override input {
  min-width: 22rem;
}

/* --- 衛生局通報 ---------------------------------------------------------- */

.emr-scope {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  color: var(--ink-3);
}

/* --- Visit record: phrases, the record's text, courses -------------------
   Spec 2026-10-21-visit-record-plus. The forms here are single columns, so
   they leave the two-column form grid. */

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

.phrase-field textarea {
  width: 100%;
}

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

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

/* 片語全文顯示，太長就在按鈕裡折行：截成「牙冠印模完成，暫時…」醫師看不出
   是哪一句（彩排 2 看診 #9）。 */
.phrase-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  max-width: 100%;
  padding-top: var(--sp-1);
  padding-bottom: var(--sp-1);
  text-align: left;
  white-space: normal;
}

.phrase-chip span:first-child {
  overflow-wrap: anywhere;
}

.phrase-code {
  color: var(--ink-3);
  font-weight: normal;
  white-space: nowrap;
}

/* 語音輸入（spec 2026-10-22-voice-dictation）：片語列最前面的一顆按鈕與一行小字。
   沒開啟時容器是空的，不佔位置。收音中只換底色與一個小圓點，不閃、不彈窗：
   醫師的眼睛在病人身上。樣式比照 B3a 的「有人坐的椅子」（--tile 底＋
   --signature 圓點）：橘色只准用在圓點，不准當字色或外框；B3a 動效只做
   換色與按下回饋，所以圓點是靜止的。 */
.dictation {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.dictation:empty {
  display: none;
}

.dictation-button.is-listening,
.dictation-button.is-listening:hover {
  background: var(--tile);
  color: var(--ink);
}

.dictation-button.is-listening::before {
  content: "";
  display: inline-block;
  width: var(--indicator-dot-lg);
  height: var(--indicator-dot-lg);
  margin-right: var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--signature);
  vertical-align: middle;
}

.dictation-status {
  font-size: var(--fs-footnote);
  color: var(--ink-3);
  /* 不排在片語按鈕之間：之前「聆聽中」「辨識中」一出現，後面的片語整排被往右推、
     換行（彩排 2 看診 #10）。改成浮在語音鍵正上方、文字框的左下角裡，字就是要
     出現在那個框裡，眼睛不必移開；不佔版面，所以什麼都不會跳。 */
  position: absolute;
  left: var(--sp-2);
  bottom: calc(100% + var(--sp-5));
  z-index: 1;
  width: max-content;
  max-width: 28rem;
  padding: 0 var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--group);
  pointer-events: none;
}

.dictation-status:empty {
  display: none;
}

.dictation-status.is-error {
  color: var(--danger);
}

.phrase-manager-box {
  margin-top: var(--sp-3);
  padding: var(--sp-5);
  background: var(--canvas);
  border-radius: var(--r-group);
}

.phrase-manager + .phrase-manager {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}

.phrase-manager-title,
.phrase-group {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-body);
}

.phrase-group {
  margin-top: var(--sp-3);
  color: var(--ink-3);
}

.phrase-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--hairline);
}

.phrase-words {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.phrase-edit,
.record-editor,
.course-form,
.course-add-step {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

.form-actions:has(.form-status) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}

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

.form-status.is-error,
.form-error {
  color: var(--danger);
  font-size: var(--fs-footnote);
}

.form-error {
  margin: var(--sp-2) 0 0;
}

.visit-panel > * + * {
  margin-top: var(--sp-4);
}

.record-text {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--sp-2) var(--sp-4);
  margin: 0;
}

.record-text dt {
  color: var(--ink-3);
  font-size: var(--fs-footnote);
}

.record-text dd {
  margin: 0;
  white-space: pre-wrap;
}

.visit-course {
  font-size: var(--fs-footnote);
}

/* 「新增看診紀錄 ↓」放在選擇病患卡的右上角，與卡片標題同一行，不多佔一行。 */
.visits-pick-card {
  position: relative;
}

.visit-jump-new {
  position: absolute;
  top: var(--sp-6);
  right: var(--sp-7);
}

/* 剛新增的看診草稿（visits.js showJustAdded）：捲到畫面中間時先亮一下再淡掉，
   旁邊那句「已新增」留到下一次重畫。不想要動畫的系統設定就只留靜態底色。 */
#visits-body tr.is-just-added > td {
  animation: visit-just-added 4s var(--ease-out) both;
}

@keyframes visit-just-added {
  0%, 50% { background: var(--ok-wash); }
  100% { background: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  #visits-body tr.is-just-added > td {
    animation: none;
    background: var(--ok-wash);
  }
}

.visit-added-note {
  margin-top: var(--sp-1);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold);
  color: var(--ok-ink);
}

.course + .course {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}

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

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

.course-progress {
  font-weight: 600;
}

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

.course-step {
  padding: var(--sp-1) 0;
}

.course-step > span:first-child {
  margin-right: var(--sp-2);
}

.course-step .action-group {
  display: inline-flex;
  margin-left: var(--sp-2);
  vertical-align: middle;
}

.course-step-inputs {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.course-step-input {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 2fr auto;
  gap: var(--sp-2);
  align-items: start;
}

.course-date {
  width: auto;
}

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

@media (max-width: 899px) {
  .course-step-input {
    grid-template-columns: 1fr;
  }
}

/* --- 讀取中的骨架列（dom.js showSkeleton） ------------------------------
   讀取超過 300ms 才出現。不做閃光動畫：診間螢幕整天開著，會動的東西
   只留給真正要人注意的事。 */
.skeleton-row > td,
li.skeleton-row,
div.skeleton-row {
  height: 56px;
  padding-top: 0;
  padding-bottom: 0;
  vertical-align: middle;
}

li.skeleton-row,
div.skeleton-row {
  display: flex;
  align-items: center;
  list-style: none;
}

.skeleton-bars {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  width: 100%;
}

.skeleton-bar {
  display: block;
  height: 14px;
  border-radius: var(--r-pill);
  background: var(--fill);
}

tbody tr.skeleton-row:hover {
  background: none;
}

/* 「詳細」：技術細節收在這裡，平常只露出一行可以點開的字（系統更新頁）。 */
details.more-detail {
  margin: var(--sp-2) 0;
}

details.more-detail > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 36px;
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-footnote);
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

details.more-detail > summary::-webkit-details-marker {
  display: none;
}

details.more-detail > p {
  margin: var(--sp-1) 0 0;
  overflow-wrap: anywhere;
}

/* 健保申報頁「和北昕的申報檔比對」：讀不懂的檔與需要留意的提示用警示色。 */
#cmp-msg.is-error,
#cmp-notes .is-error {
  color: var(--danger);
}

/* ⓘ 說明提示（dom.js infoTip）：操作說明不常駐在畫面上，滑鼠移過去或鍵盤 focus
   時才在旁邊跳出。純 CSS，靜態 HTML 也能直接寫
   <button type="button" class="info-tip" data-tip="…" aria-label="說明：…">i</button>。 */
.info-tip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  min-height: 0;
  margin-inline-start: var(--sp-2);
  padding: 0;
  vertical-align: middle;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-pill);
  background: var(--group);
  color: var(--ink-3);
  font: italic var(--fw-semibold) 13px/1 Georgia, var(--font-display);
  cursor: help;
}

/* 提示框本身（dom.js 掛在 body、position: fixed，不會被捲動框切掉、不佔版面）。
   底色用 ink、字用 canvas：深色模式兩個 token 會一起反過來，對比照樣夠。 */
#info-tip-pop {
  position: fixed;
  z-index: 1000;
  max-width: min(24em, calc(100vw - 16px));
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-tag);
  background: var(--ink);
  color: var(--canvas);
  font: var(--fw-regular) var(--fs-footnote) / var(--lh-text) var(--font-ui);
  text-align: start;
  white-space: pre-line;
  overflow-wrap: anywhere;
  box-shadow: 0 4px 14px rgb(0 0 0 / 0.18);
  pointer-events: none;
}

.info-tip[hidden],
#info-tip-pop[hidden] {
  display: none;
}

.info-tip:hover {
  color: var(--ink);
  border-color: var(--ink-3);
}

/* 表頭裡的 ⓘ 不要把欄名擠到換行。 */
th .info-tip {
  margin-inline-start: var(--sp-1);
}

/* 收費單的自費項目挑選：打字就跳出清單，一次看得到 8 項不用捲。 */
.price-picker {
  position: relative;
}

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

.price-picker-list {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  min-width: min(420px, 90vw);
  margin: 0;
  padding: var(--sp-1);
  list-style: none;
  background: var(--group);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-input);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
}

.price-picker-list {
  overflow-y: auto;
}

.price-picker-list.opens-up {
  top: auto;
  bottom: calc(100% + 4px);
}

.price-picker-list li {
  display: grid;
  grid-template-columns: 5em 1fr auto;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px; /* 觸控 */
  padding: var(--sp-2) var(--sp-3);
  border-radius: 10px;
  cursor: pointer;
}

.price-picker-list li.is-active,
.price-picker-list li:hover {
  background: var(--fill);
}

.price-picker-list .price-picker-code,
.price-picker-list .price-picker-empty {
  color: var(--ink-3);
}

.price-picker-list li.price-picker-empty {
  display: flex;
  cursor: default;
}

.price-picker-list li.price-picker-empty:hover {
  background: none;
}

/* 清單上方的搜尋框（欠款、自費價目）。 */
.list-filter {
  width: min(320px, 100%);
  margin-bottom: var(--sp-3);
}

/* 自費價目：搜尋與收起來的新增表單在同一列，清單一列一行（平板也看得到五列以上）。 */
.price-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}

.price-tools .list-filter {
  margin-bottom: 0;
}

.price-add > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  min-height: var(--h-control);
  cursor: pointer;
}

.price-add > summary::-webkit-details-marker {
  display: none;
}

.price-add[open] {
  flex-basis: 100%;
}

.price-add-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
}

.price-table td,
.price-table th {
  white-space: nowrap;
}

.price-table td:nth-child(2) {
  max-width: 18em;
  overflow: hidden;
  text-overflow: ellipsis;
}

.price-table .btn-secondary {
  padding-inline: var(--sp-3);
}

/* 收費單的自費項目：平板上不被下方分頁列蓋住。 */
@media (max-width: 899px) {
  .price-picker input {
    scroll-margin-bottom: 96px;
  }
}

.price-table .action-group {
  flex-wrap: nowrap;
}

.price-add > summary {
  padding-inline: var(--sp-4);
  border-radius: var(--r-pill);
}

.price-table .price-on {
  color: var(--ok-ink);
  background: var(--ok-wash);
}

/* 病患頁的北昕約診紀錄：一次看得到六列左右，多的在框內捲（不撐長整頁）。 */
.cooper-appt-history {
  max-height: calc(var(--h-row) * 6.5);
  overflow-y: auto;
}

/* 病患名字下面的北昕摘要：家屬（點了打開那位病患）與跳到約診紀錄，一行放得下。 */
.cooper-peek {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin: calc(var(--sp-4) * -1) 0 var(--sp-6);
}

.cooper-peek[hidden] {
  display: none;
}

/* 從摘要跳過來時，卡片標題不貼齊視窗頂端被切掉。 */
#patient-cooper-extras-title {
  scroll-margin-top: var(--sp-6);
}

.cooper-peek-label {
  font-size: var(--fs-footnote);
  color: var(--ink-2);
}

/* 北昕約診說明：在日期、醫師、狀態後面，長的換行，不撐寬。 */
.cooper-appt-note {
  flex: 1 1 12em;
  min-width: 0;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}

/* 看診紀錄「文件」選單（visit-documents.js）：掛在 body、position: fixed，
   放在表格的捲動框裡也不會被切掉。每一項都是一顆夠大的按鈕（平板手指點）。 */
.doc-menu {
  position: fixed;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  min-width: 11em;
  padding: var(--sp-1);
  border-radius: var(--r-input);
  background: var(--group);
  box-shadow: 0 0 0 1px var(--hairline), 0 6px 20px rgb(0 0 0 / 0.16);
}

.doc-menu button[role="menuitem"] {
  min-height: var(--h-control-sm);
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: var(--r-tag);
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.doc-menu button[role="menuitem"]:hover,
.doc-menu button[role="menuitem"]:focus-visible {
  background: var(--fill);
}

/* 草稿時反灰的那一項與它的 ⓘ 同一列。 */
.doc-menu-row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

.doc-menu-row button:first-child {
  flex: 1;
}

.doc-menu button[role="menuitem"]:disabled {
  color: var(--ink-3);
  cursor: not-allowed;
}

.doc-menu button[role="menuitem"]:disabled:hover {
  background: none;
}

/* 管理者的「文件範本…」捷徑：與文件項目隔一條線，看得出不是要印的文件。 */
.doc-menu .doc-menu-link {
  display: flex;
  align-items: center;
  min-height: var(--h-control-sm);
  margin-top: var(--sp-1);
  padding: 0 var(--sp-4);
  border-top: 1px solid var(--hairline);
  border-radius: 0 0 var(--r-tag) var(--r-tag);
  color: var(--ink-2);
  text-decoration: none;
}

.doc-menu .doc-menu-link:hover,
.doc-menu .doc-menu-link:focus-visible {
  background: var(--fill);
  color: var(--ink);
}
