/* Horizon interactive — additions on top of style-a.css */

/* The whole product is a warm-paper light theme — declare that to the UA so
   form controls, scrollbars and the iOS keyboard render light-themed even
   when the system is dark. No prefers-color-scheme: dark overrides — an
   earlier auto tone-down made the sidebar look near-black on dark-mode
   Macs and was reverted (commit 9289893). */
html { color-scheme: light; }

html, body { margin: 0; height: 100%; background: #EEE6D4; }
body { font-family: 'Inter Tight', 'Noto Sans TC', sans-serif; }

/* root shell fills viewport */
#root, .hz-app { height: 100vh; width: 100vw; }

/* Override the .A scale-in-artboard behavior for full-viewport app.
   .hz-app is a flex column so the optional <HzDemoBanner/> takes its
   natural height and .A claims whatever vertical space remains.
   Without this, .A/.layout/.main were all height:100% (= 100vh) and
   the banner pushed the whole layout 36pt below the viewport, making
   the document scroll and dragging the sidebar with it. */
.hz-app { display: flex; flex-direction: column; }
/* hz-app contains TWO .A children: the main app (wraps .crm-frame) and
   the modal/sheet hosts (empty most of the time). Without :has() guard,
   `.hz-app > .A { flex: 1 }` matched both and split the viewport in
   half, leaving an empty cream band below the app. Pin flex grow to
   the main .A; let the modals wrapper collapse via display:contents
   (modals are position:fixed so they don't need a layout box). */
.hz-app > .A:has(.crm-frame) { flex: 1; min-height: 0; }
.hz-app > .A:not(:has(.crm-frame)) { display: contents; }
.hz-app .A { width: 100%; background: var(--ivory); }
.hz-app .A .crm-frame { height: 100%; width: 100%; }
.hz-app .A .layout { height: 100%; }
.hz-app .A .main { height: 100%; overflow: auto; }
.hz-main { scroll-behavior: smooth; }

/* Make nav-items / rows etc. actually feel interactive */
.A .nav-item, .A .tab, .A .btn, .A .stage-chip, .A .chip, .A .pipe-row, .A .stat, .A .card button {
  user-select: none;
}

.A .nav-item { transition: background .12s, color .12s, padding-left .15s; }
.A .nav-item:active { transform: translateY(0.5px); }
.A .table tbody tr { transition: background .12s; }
.A .table tbody tr.sel { background: rgba(156,61,31,0.04) !important; }
.A .table tbody tr.sel td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

.A .kbd-hint { font-family: 'JetBrains Mono', monospace; font-size: 10px; padding: 1px 5px; border: 0.5px solid var(--line); border-radius: 3px; color: var(--ink-4); background: var(--paper); }

/* ── Toast ── */
.hz-toast {
  position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%);
  background: var(--ink); color: var(--paper);
  padding: 10px 18px; border-radius: 999px; font-size: 13px;
  box-shadow: 0 12px 40px rgba(32,26,18,0.25), 0 2px 8px rgba(32,26,18,0.15);
  display: flex; align-items: center; gap: 10px;
  z-index: 60;
  animation: hz-toast-in .25s cubic-bezier(0.16, 1, 0.3, 1);
}
.hz-toast.bye { animation: hz-toast-out .2s cubic-bezier(0.4, 0, 1, 1) forwards; }
.hz-toast .mark { width: 16px; height: 16px; border-radius: 50%; background: var(--paper); color: var(--ink); display: inline-flex; align-items: center; justify-content: center; }
.hz-toast.err { background: #6e2a1c; }
.hz-toast.err .mark { background: rgba(255,255,255,0.92); color: #6e2a1c; }
@keyframes hz-toast-in { from { opacity: 0; transform: translate(-50%, 10px); } }
@keyframes hz-toast-out { to { opacity: 0; transform: translate(-50%, 10px); } }

/* ── Modal ── */
.hz-modal-bg {
  position: fixed; inset: 0; background: rgba(32,26,18,0.55);
  backdrop-filter: blur(8px); z-index: 50;
  display: flex; align-items: center; justify-content: center;
  animation: fadein .18s ease-out;
}
.hz-modal {
  background: var(--paper) !important; border-radius: 12px;
  border: 0.5px solid var(--line);
  width: 520px; max-width: calc(100vw - 40px);
  box-shadow: 0 40px 100px rgba(32,26,18,0.4), 0 0 0 1px rgba(32,26,18,0.05);
  animation: hz-modal-in .25s cubic-bezier(0.16, 1, 0.3, 1);
  overflow: hidden;
}
.hz-modal-hd, .hz-modal-body, .hz-modal-foot { background: var(--paper); }
.hz-modal .card { background: var(--paper); border: 0.5px solid var(--line); border-radius: 8px; padding: 20px; }
.hz-modal-hd { padding: 22px 26px 14px; border-bottom: 0.5px solid var(--line); }
.hz-modal-title { font-family: 'Instrument Serif', 'Noto Serif TC', serif; font-style: italic; font-size: 26px; color: var(--ink); }
.hz-modal-sub { font-size: 12.5px; color: var(--ink-3); margin-top: 4px; }
.hz-modal-body { padding: 20px 26px; }
.hz-modal-foot { padding: 14px 26px; border-top: 0.5px solid var(--line); display: flex; justify-content: flex-end; gap: 8px; background: var(--ivory); }
@keyframes hz-modal-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
@keyframes fadein { from { opacity: 0; } }

/* ── Side sheet (stage selector, quick preview) ── */
.hz-sheet-bg {
  position: fixed; inset: 0; background: rgba(32,26,18,0.32);
  backdrop-filter: blur(3px);
  z-index: 45; animation: fadein .18s ease-out;
}
.hz-sheet {
  position: fixed; top: 0; right: 0; bottom: 0; width: 460px;
  background: var(--paper); border-left: 0.5px solid var(--line);
  z-index: 46; box-shadow: -24px 0 72px rgba(32,26,18,0.28);
  animation: hz-sheet-in .28s cubic-bezier(0.16, 1, 0.3, 1);
  display: flex; flex-direction: column;
  overflow: hidden;
}
@keyframes hz-sheet-in { from { transform: translateX(100%); } }
.hz-sheet-hd { padding: 20px 26px; border-bottom: 0.5px solid var(--line); display: flex; justify-content: space-between; align-items: center; background: var(--paper); }
.hz-sheet-body { padding: 20px 26px; overflow: auto; flex: 1; background: var(--paper); }
/* Sheet renders outside .A so .A .card styles don't apply — re-declare here */
.hz-sheet .card { background: var(--paper); border: 0.5px solid var(--line); border-radius: 8px; padding: 20px; }
.hz-sheet .stage-chip, .hz-sheet .chip { display: inline-flex; align-items: center; }

/* Generic dropdown menu item hover */
.hz-menu-item { transition: background .12s; }
.hz-menu-item:hover { background: var(--ivory); }

/* Stage picker within sheet */
.hz-stage-pick { display: flex; flex-direction: column; gap: 8px; }
.hz-stage-pick-item {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; border: 0.5px solid var(--line);
  border-radius: 8px; background: var(--paper); cursor: pointer;
  transition: all .15s;
}
.hz-stage-pick-item:hover { border-color: var(--ink-3); transform: translateY(-1px); box-shadow: 0 6px 20px rgba(32,26,18,0.06); }
.hz-stage-pick-item.active { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.hz-stage-pick-item.active .sub { color: rgba(251,248,242,0.7); }

/* Draggable pipeline lanes */
.hz-pipe-board { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; }
.hz-pipe-lane { background: var(--paper); border: 0.5px solid var(--line); border-radius: 8px; display: flex; flex-direction: column; min-height: 360px; transition: background .15s, border-color .15s; }
.hz-pipe-lane.over { background: rgba(156,61,31,0.03); border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.hz-pipe-lane-hd { padding: 12px 14px 8px; border-bottom: 0.5px solid var(--line); }
.hz-pipe-lane-body { padding: 10px; display: flex; flex-direction: column; gap: 8px; overflow: auto; flex: 1; }
.hz-pipe-card {
  padding: 10px 12px; background: var(--ivory); border: 0.5px solid var(--line);
  border-radius: 6px; cursor: grab; font-size: 12px;
  transition: all .12s;
}
.hz-pipe-card:hover { border-color: var(--ink-3); box-shadow: 0 4px 12px rgba(32,26,18,0.06); }
.hz-pipe-card:active { cursor: grabbing; }
.hz-pipe-card.drag { opacity: 0.4; }
.hz-pipe-card .n { font-family: 'Noto Serif TC', serif; font-weight: 500; font-size: 13px; }
.hz-pipe-card .a { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-3); margin-top: 4px; }

/* Checkbox */
.hz-check { width: 14px; height: 14px; border: 1px solid var(--ink-3); border-radius: 3px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; background: transparent; transition: all .12s; }
.hz-check:hover { border-color: var(--ink); }
.hz-check.on { background: var(--ink); border-color: var(--ink); }

/* Star button */
.hz-star { width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; color: var(--ink-4); cursor: pointer; transition: color .12s, transform .12s; }
.hz-star:hover { color: var(--gold); }
.hz-star.on { color: var(--gold); }
.hz-star:active { transform: scale(0.9); }

/* Bulk action bar */
.hz-bulk {
  position: sticky; bottom: 20px;
  margin: 20px auto 0;
  background: var(--ink); color: var(--paper);
  padding: 10px 14px 10px 18px; border-radius: 999px;
  display: flex; align-items: center; gap: 14px; font-size: 12.5px;
  box-shadow: 0 16px 40px rgba(32,26,18,0.25);
  width: fit-content;
  animation: hz-toast-in .2s ease-out;
}
.hz-bulk button { background: transparent; border: 0.5px solid rgba(251,248,242,0.3); color: var(--paper); padding: 4px 10px; border-radius: 999px; font: inherit; cursor: pointer; }
.hz-bulk button:hover { background: rgba(251,248,242,0.1); }

/* Command palette */
.hz-cmd-bg { position: fixed; inset: 0; background: rgba(32,26,18,0.18); backdrop-filter: blur(3px); z-index: 70; display: flex; align-items: flex-start; justify-content: center; padding-top: 14vh; animation: fadein .15s; }
.hz-cmd { width: 600px; max-width: calc(100vw - 40px); background: var(--paper); border-radius: 12px; border: 0.5px solid var(--line); box-shadow: 0 30px 80px rgba(32,26,18,0.3); overflow: hidden; animation: hz-modal-in .2s cubic-bezier(0.16, 1, 0.3, 1); }
/* Make sure text inside the cmd palette is dark/legible (cmdk renders outside .A) */
.hz-cmd, .hz-cmd input, .hz-cmd .item, .hz-cmd * { color: var(--ink); }
.hz-cmd .hint, .hz-cmd .sub { color: var(--ink-3); }
.hz-cmd-input { padding: 18px 22px; border-bottom: 0.5px solid var(--line); display: flex; align-items: center; gap: 12px; }
.hz-cmd-input input { flex: 1; background: transparent; border: 0; outline: 0; font-family: 'Instrument Serif', 'Noto Serif TC', serif; font-style: italic; font-size: 22px; color: var(--ink); }
.hz-cmd-list { max-height: 48vh; overflow: auto; padding: 8px; }
.hz-cmd-item { padding: 10px 14px; border-radius: 6px; display: flex; align-items: center; gap: 12px; cursor: pointer; font-size: 13px; }
.hz-cmd-item.on { background: var(--ink); color: var(--paper); }
.hz-cmd-item .hint { margin-left: auto; font-size: 11px; color: var(--ink-4); }
.hz-cmd-item.on .hint { color: rgba(251,248,242,0.7); }
.hz-cmd-group { padding: 10px 14px 4px; font-size: 10px; letter-spacing: 0.12em; color: var(--ink-4); text-transform: uppercase; }

/* Row affordance — make clickable feel clear */
.A .table tbody tr:hover td:last-child .goto { opacity: 1; transform: translateX(0); }
.A .table .goto { opacity: 0; transform: translateX(-4px); transition: all .15s; color: var(--ink-3); }

/* page transitions */
.hz-page { animation: hz-page-in .25s ease-out; }
@keyframes hz-page-in { from { opacity: 0; transform: translateY(4px); } }

/* Login specific entry */
.hz-login { background: var(--ivory); width:100%; height:100%; }

/* Tiny spinner for button loading */
.hz-spin { width: 12px; height: 12px; border: 1.5px solid rgba(251,248,242,0.3); border-top-color: var(--paper); border-radius: 50%; display: inline-block; animation: hz-spin 0.7s linear infinite; }
@keyframes hz-spin { to { transform: rotate(360deg); } }

/* Segmented control */
.hz-seg { display: inline-flex; background: var(--paper); border: 0.5px solid var(--line); border-radius: 6px; padding: 2px; gap: 2px; }
.hz-seg button { border: 0; background: transparent; padding: 4px 10px; font: inherit; font-size: 12px; border-radius: 4px; color: var(--ink-3); cursor: pointer; }
.hz-seg button.on { background: var(--ink); color: var(--paper); }

/* Mark cell when typing */
.hz-search-highlight { background: rgba(184,146,79,0.25); }

/* ── Dashboard empty-state card ─────────────────────────────────────────
   Brand-new accounts open the dashboard and find every list / chart
   empty (no tasks, no hot leads, no funnel data, no historical bars).
   The page used to silently shrink to a band of unrelated whitespace
   underneath the KPI row, leaving the user with nothing to act on. We
   render an .hz-empty-state inside the affected card with friendly copy
   plus a CTA button (or two text links). Centred, low-key colour, no
   border so it visually inherits the parent .card. ─────────────────── */
.hz-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 32px 20px;
  color: var(--ink-3);
  text-align: center;
}
.hz-empty-state .hz-empty-text {
  font-size: 12.5px;
  color: var(--ink-3);
  line-height: 1.5;
}
.hz-empty-state .hz-empty-link {
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color .12s, text-decoration-color .12s;
}
.hz-empty-state .hz-empty-link:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}
.hz-empty-state .btn {
  font-size: 12px;
  padding: 6px 12px;
}

/* ── Dashboard stat cards: keep min-width:0 so a long stat-value can
   ellipsis instead of pushing the 4th card past the page right edge.
   Combined with the JSX-side `minmax(0, 1fr)` grid track, this stops
   the 平均成交週期 card from clipping when "NT$ -99,999" or similar
   wide values appear. ────────────────────────────────────────────── */
.A .stat { min-width: 0; }
.A .stat .stat-value {
  /* white-space:nowrap is already set in the mobile rule but only
     under @media (max-width: 767px). Apply it on desktop too so the
     wide value never wraps to two lines and overflows the card. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* ── Dashboard funnel card: defensive against vertical clipping ─────────
   The 5-stage funnel (初次接觸 → 需求確認 → 方案報價 → 合約洽談 →
   成交結案) lives inside a .card that inherits whatever vertical
   constraint its grid row gives it. When `.main` was accidentally
   half-tall (pre-f2f5070) the last row "成交結案" would visually clip
   off the bottom of the card. Guard against any future re-occurrence
   by letting the card size to its natural content height (no fixed
   height ever) and refusing to clip overflow. The funnel rows are
   short and the total summary band is tiny, so unbounded growth here
   is bounded by the 5 rows + 14px summary line. ───────────────────── */
.A .page > div .card:has(> .pipe-row) {
  overflow: visible;
  min-height: 0;
}

/* ── Mobile hamburger button (hidden on desktop) ── */
.hz-hamburger { display: none; }

/* ── Mobile sidebar backdrop ── */
.hz-sidebar-backdrop { display: none; }

/* ── Body lock when modal is open (mobile + desktop both benefit) ──
   Stops the page behind a bottom-sheet from rubber-band scrolling on iOS
   and also locks desktop background scroll while a centered modal is up. */
body.hz-modal-open { overflow: hidden; touch-action: none; }
body.hz-modal-open .hz-app .main { overflow: hidden; }

/* ── Mobile responsive: < 768px ── */
@media (max-width: 767px) {
  /* iOS Safari zooms the page when focusing any form control whose computed
     font-size is < 16px. The .A scale is 13px so .form-input inherits 13px
     and triggers zoom on every focus. Force ≥ 16px on every input control
     (and the sort select on the customers page) only on phones — desktop
     stays at the designed 13px because no zoom heuristic applies there. */
  .A input, .A textarea, .A select,
  .A .form-input,
  .A .topbar select,
  .A .search input,
  .A select.btn { font-size: 16px; }

  /* Layout becomes single column — main fills viewport, sidebar overlays */
  .A .layout { grid-template-columns: 1fr; }

  /* Sidebar becomes a fixed off-canvas panel */
  .A .side {
    position: fixed; top: 0; left: 0; bottom: 0;
    width: 260px; max-width: 80vw;
    transform: translateX(-100%);
    transition: transform .25s cubic-bezier(0.16, 1, 0.3, 1);
    z-index: 48;
    box-shadow: 0 0 60px rgba(32,26,18,0.18);
    overflow-y: auto;
  }
  body.hz-sidebar-open .A .side { transform: translateX(0); }

  /* Main is full width — and on phones we let the document itself be
     the vertical scroll container so position:sticky on .topbar /
     page-head binds to window scroll (overflow:auto on .main earlier
     in this file would otherwise create a separate scroll port and
     break sticky on iOS Safari).  But we must clip horizontally —
     a flex-column .main with visible overflow expands to fit a wide
     child like the horizontally-scrolled customers <table>, which in
     turn pushes body wider than the viewport (583pt vs 390pt) and
     breaks position:fixed inside.  overflow-x:clip + max-width:100vw
     keeps .main exactly viewport-wide while letting vertical content
     spill into the document's natural scroll. The earlier
     `.hz-app .A .main` rule (height:100%; overflow:auto) is more
     specific than `.A .main`, hence the namespacing here. */
  .hz-app .A .main {
    width: 100%; max-width: 100vw;
    overflow-x: clip; overflow-y: visible;
    height: auto; min-width: 0;
  }

  /* Topbar: tighter padding so hamburger + actions fit */
  .A .topbar { padding: 12px 16px; gap: 8px; flex-wrap: wrap; }
  .A .topbar .btn { white-space: nowrap; }
  .A .search { flex: 1 1 auto; min-width: 0; }
  .A .search > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .A .search .kbd { display: none; }

  /* Page padding tighter */
  .A .page { padding: 20px 16px 32px; }
  .A .page-head { flex-wrap: wrap; gap: 12px; }
  .A .page-title { font-size: 28px; }

  /* Hamburger visible — sized for thumb (44×44) with explicit press feedback
     so users see they hit it, since :hover doesn't fire on touch. */
  .hz-hamburger {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; border-radius: 8px;
    border: 0.5px solid var(--line); background: var(--paper);
    color: var(--ink); cursor: pointer; padding: 0;
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
    transition: transform .12s, background .15s, box-shadow .15s;
  }
  .hz-hamburger:active {
    background: var(--ink);
    color: var(--paper);
    transform: scale(0.92);
    box-shadow: 0 0 0 4px rgba(32, 26, 18, 0.08);
  }

  /* Topbar respects iOS safe-area-inset-top so it isn't tucked under a
     notch / Dynamic Island, AND sticks to the top of the viewport so
     the hamburger / search / bell stay reachable while the user
     scrolls a long list. */
  .A .topbar {
    padding-top: calc(12px + env(safe-area-inset-top));
    position: sticky;
    top: 0;
    z-index: 30;
  }

  /* Backdrop overlay */
  body.hz-sidebar-open .hz-sidebar-backdrop {
    display: block; position: fixed; inset: 0;
    background: rgba(32,26,18,0.4); backdrop-filter: blur(2px);
    z-index: 47; animation: fadein .2s ease-out;
  }

  /* Login: collapse two-column shell to single column,
     hide decorative left panel, form takes full width. */
  .A .login-shell { grid-template-columns: 1fr; }
  .A .login-art { display: none; }
  .A .login-form {
    padding: 40px 24px;
    min-height: 100vh;
    justify-content: flex-start;
    padding-top: 14vh;
  }
  .A .login-form > div { max-width: 360px; width: 100%; }

  /* Customers table (9 cols): hide 下一步(5)/負責人(6)/更新(7) on phone.
     Keep checkbox(1) / 客戶名(2) / 階段(3) / 金額(4) / star(8) / goto(9). */
  .A .page .table thead th:nth-child(5),
  .A .page .table thead th:nth-child(6),
  .A .page .table thead th:nth-child(7),
  .A .page .table tbody td:nth-child(5),
  .A .page .table tbody td:nth-child(6),
  .A .page .table tbody td:nth-child(7) { display: none; }

  /* The ⋯ row-action popover (.hz-row-menu) was positioned with
     absolute right:8 inside the last <td>. Once we wrap the table in
     a horizontal scroller, that <td> is offscreen most of the time, so
     the menu rendered at left:~400, right:~560 — invisible.
     On phones, lift it out of its scroller and pin it to the bottom-
     right of the viewport so it's always reachable regardless of how
     far the table has scrolled. Acts as a mini action sheet anchored
     to the right thumb. */
  .hz-row-menu {
    position: fixed !important;
    top: auto !important;
    right: 12px !important;
    bottom: calc(16px + env(safe-area-inset-bottom)) !important;
    left: auto !important;
    min-width: 220px !important;
    max-width: calc(100vw - 24px) !important;
    z-index: 60 !important;
  }

  /* Even with hidden columns the remaining 6 still total ~530px (bigger
     touch targets push width up), wider than a 390px viewport. Wrap the
     table in a horizontal scroller so the star and "⋯" actions on the
     right edge stay reachable by swiping. The table itself is rendered
     bare (no wrapper element) so we use `display:block; overflow:auto`
     directly on .table — auto layout still works inside a block element. */
  .A .page .table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    width: 100%;
  }
  .A .page .table > thead, .A .page .table > tbody {
    display: table; width: max-content; min-width: 100%;
  }
  /* Visual cue that more content sits to the right. */
  .A .page .table::after {
    content: ''; display: block; height: 4px;
  }

  /* ── Touch targets ≥ 44pt — Apple HIG / Material both ask for this.
     Use min-* to grow the hit area without breaking typography of small
     visual chips (the chip itself stays 23px high; the touch box grows). ── */

  /* All buttons have a comfortable height. */
  .A .btn { min-height: 44px; padding-top: 10px; padding-bottom: 10px; }
  /* Topbar bell / hamburger / icon-only ghost buttons use square hit area.
     ".btn.ghost" with no text + only an SVG ends up 40×30; force square. */
  .A .topbar .btn.ghost { min-width: 44px; min-height: 44px; }

  /* The row "⋯" menu trigger on the customers list — it visually stays a
     single dot stack but the tappable area expands to a 40×40 box. */
  .A .table .btn.ghost { min-width: 40px; min-height: 40px;
    display: inline-flex; align-items: center; justify-content: center; }

  /* Star button — keep the icon at 22px but expand its tappable padding. */
  .A .hz-star { width: 40px; height: 40px; }

  /* Checkbox — keep the visual 16px box but pad an outer hit area.
     Achieved by making the surrounding <td> larger; we just give the chip
     itself comfortable touch padding via box-shadow inset hit area. */
  .A .table td .hz-check { width: 22px; height: 22px; }
  .A .table td:first-child { padding: 14px 8px; }

  /* Stage chip in the table — make the cell row tall so the tap target
     for the chip (which opens stage picker) is forgiving. */
  .A .table td { padding-top: 12px; padding-bottom: 12px; }

  /* Custom select used as sort control on customers page — add some
     vertical breathing room (height inherits from .btn now ≥ 44). */
  .A select.btn { min-height: 44px; }

  /* ── Modal becomes a bottom sheet on phones ──
     Full-width, slides up from the bottom (where the thumb lives), top
     corners rounded, max-height 90vh so the body always scrolls inside the
     sheet rather than the page. Footer respects the iOS home-indicator
     safe area so primary actions ("儲存變更" / "寄出") never sit under it. */
  .hz-modal-bg {
    align-items: flex-end;
    backdrop-filter: blur(6px);
  }
  .hz-modal {
    width: 100% !important;
    max-width: 100% !important;
    max-height: 90vh;
    border-radius: 16px 16px 0 0;
    display: flex;
    flex-direction: column;
    animation: hz-modal-up .28s cubic-bezier(0.16, 1, 0.3, 1);
  }
  /* Body should always be the scroll container, not the modal itself. */
  .hz-modal-body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    flex: 1 1 auto;
    min-height: 0;
  }
  /* Footer sticks above the home indicator. */
  .hz-modal-foot {
    padding-bottom: calc(14px + env(safe-area-inset-bottom));
  }
  /* Inside-modal "form-row" is too cramped at 2-col — drop to single col on
     phones; the modal sheet is full-width so labels read properly. */
  .hz-modal .form-row,
  .hz-modal-body .form-row { grid-template-columns: 1fr; gap: 0; }

  /* On a bottom sheet, modal-body internal max-height set inline by
     ModalEditCustomer (60vh) becomes redundant; let the body take whatever
     the sheet gives it. */
  .hz-modal-body[style*="max-height"] { max-height: none !important; }

  /* .form-row is a class (grid 1fr 1fr defined in style-a.css) so the
     generic [style*="grid-template-columns"] selector above doesn't
     catch it. Collapse it to a single column EVERYWHERE on phones —
     used in Settings → 個人檔案, in 新增客戶 page, and in modal bodies.
     This stops the right-hand input (英文名 / 統一編號 / 預估金額 …)
     from spilling past the viewport edge. */
  .A .form-row { grid-template-columns: minmax(0, 1fr); gap: 14px; }

  /* Settings page: SettingRow uses an inline `gridTemplateColumns:'1fr auto'`
     with gap:40 — at 358pt the gap eats the 'auto' track and the toggle
     button gets cramped against the description.  Reduce the gap and
     allow the auto track to use the full needed width. */
  .A .page > div > div[style*="grid-template-columns:'1fr auto'"],
  .A .page > div > div[style*="grid-template-columns: 1fr auto"] {
    gap: 16px !important;
  }

  /* Dashboard stat cards in 2-col layout (≈170pt wide) sometimes hold
     long figures like "$ -99,999" or "$ 1.28M" that, at 30px font,
     wrap between "$" and the number.  Allow horizontal overflow inside
     the card (no scroll, just no wrap) AND step font down a touch so it
     fits in normal cases.  Numbers that still don't fit (extreme ones)
     simply clip — better than awkward two-line money. */
  .A .stat-value {
    white-space: nowrap;
    font-size: clamp(22px, 7vw, 30px);
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* When a bottom-sheet modal or the off-canvas sidebar is open, the
     toast bubble (bottom: 28px) overlapped either the modal footer
     (取消 / 建立合約) or the sidebar's bottom 登出 row. Float it to the
     TOP of the viewport instead so it stops sitting on top of the
     interactive bottom-sheet content too. The default bottom anchor
     stays for the everyday case. */
  body.hz-modal-open .hz-toast,
  body.hz-sidebar-open .hz-toast {
    bottom: auto;
    top: calc(20px + env(safe-area-inset-top));
  }
  body.hz-modal-open .hz-toast,
  body.hz-modal-open .hz-toast.bye,
  body.hz-sidebar-open .hz-toast,
  body.hz-sidebar-open .hz-toast.bye {
    /* Override the default translateY-based intro animation that
       presumed bottom-anchored origin. */
    animation-name: hz-toast-in-top;
  }
  @keyframes hz-toast-in-top {
    from { opacity: 0; transform: translate(-50%, -10px); }
  }

  /* Dashboard funnel (.pipe-row) is 80px / 1fr / 80px / 100px on desktop.
     At 358pt the third column ("6 筆") is wider than its 80px slot once
     it's centred and the fourth column's "NT\$ -99999" or "NT\$ 9.17M"
     overflows the 100px slot, so each row clipped past the card's right
     edge. Tighten the chip slot to fit the longest stage (合約洽談) and
     widen the amount slot to match real money widths. */
  .A .pipe-row {
    grid-template-columns: minmax(70px, auto) minmax(0, 1fr) auto auto;
    column-gap: 10px;
  }
  .A .pipe-row .stage-chip {
    font-size: 11px;
    padding: 2px 6px;
    white-space: nowrap;
  }
  .A .pipe-row .pipe-count { font-size: 11px; }
  .A .pipe-row .pipe-amt { font-size: 12.5px; white-space: nowrap; }

  @keyframes hz-modal-up {
    from { transform: translateY(100%); opacity: 0.3; }
    to   { transform: translateY(0);    opacity: 1;   }
  }

  /* ── Hardcoded grid containers in Dashboard / Reports / Customer detail
        are inline-styled in JSX (gridTemplateColumns: '1.3fr 1fr' etc.).
        Mobile needs a single column or 2-col fallback, so we override the
        inline style by targeting common shapes inside .A .page > div. ── */

  /* Generic: any grid inside a page that uses repeat(N) or M-col template
     becomes a single column. Use minmax(0, 1fr) instead of plain 1fr so a
     long min-content (e.g. "8 筆成交商機") can't push the track wider than
     the available viewport. !important is required to beat inline style. */
  .A .page > div[style*="grid-template-columns"],
  .A .page > div > div[style*="grid-template-columns"] {
    grid-template-columns: minmax(0, 1fr) !important;
  }

  /* Dashboard's 4 stat cards: prefer a 2-col mobile layout (better density
     than a stack of 4 single rows). The browser normalises the inline
     gridTemplateColumns attribute to "repeat(4, 1fr)" with a space after
     the comma; the JSX may also write the safer minmax(0, 1fr) form
     after the desktop overflow fix — match every shape defensively. */
  .A .page > div[style*="repeat(4, 1fr)"],
  .A .page > div[style*="repeat(4,1fr)"],
  .A .page > div[style*="repeat(4, minmax"],
  .A .page > div[style*="repeat(4,minmax"] {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }

  /* Reports team-row inner grid: '44px 1fr 70px 110px' → on phones we want
     avatar / name / amount on a single row that fits the viewport. The
     percentage column (3rd grid item) is hidden because the bar inside
     the name cell already encodes percentage visually. min-width:0 on
     the 1fr cell stops long CJK names from forcing the row to overflow. */
  .A .page .card div[style*="44px 1fr 70px 110px"] {
    grid-template-columns: 44px minmax(0, 1fr) auto !important;
    column-gap: 12px !important;
  }
  .A .page .card div[style*="44px 1fr 70px 110px"] > :nth-child(2) {
    min-width: 0;
  }
  .A .page .card div[style*="44px 1fr 70px 110px"] > :nth-child(3) {
    display: none;
  }
  /* Allow the team card itself to stay within the page padding. */
  .A .page .card { max-width: 100%; box-sizing: border-box; }

  /* ── Sticky page-head on detail pages so the customer name + action bar
        stays visible while the timeline scrolls. The .page-head is the
        first child block of .page; we make it sticky to the document
        scroll port (mobile uses window scroll). 70px clears the
        now-sticky topbar above (measured 69pt on iPhone 14). The
        customers-list page-head also becomes sticky so filter chips
        stay reachable. ── */
  .A .main .page > .page-head {
    position: sticky;
    top: 70px;
    z-index: 5;
    background: var(--ivory);
    /* re-add a subtle bottom hairline so it visually separates when sticky */
    padding-top: 8px;
    padding-bottom: 12px;
    margin-top: -8px;
    box-shadow: 0 6px 12px -10px rgba(32, 26, 18, 0.18);
  }

  /* Customer detail's action bar (紀錄 / 寄信 / 標記成交) sits inside
     page-head — wrap it nicely so buttons don't shrink below tap size and
     keep them on a second line if needed. */
  .A .page-head > div { min-width: 0; }
  .A .page-head .btn { flex-shrink: 0; }

  /* ── Customer-detail amount block (.page-date "預計成交金額" + the big
        money figure + 負責人/機率 row + action buttons) is the second
        page-head child, set to text-align:right inline. On phones the
        flex-wrap drops it below the title block but the right-alignment
        leaves the left half visually empty. Stretch it full-width and
        switch to left-align so amount + label sit at the leading edge,
        matching the rest of the column. The action buttons row (the
        last inline-flex inside this block) gets justify-content:flex-start
        too. Desktop is untouched because of the @media wrap. */
  .A .page-head > div:nth-child(2) {
    width: 100%;
    text-align: left !important;
  }
  /* .page-date has its own text-align:right in style-a.css; flip on phone. */
  .A .page-head > div:nth-child(2) .page-date { text-align: left; }
  /* The action button group inside is inline-styled with
     justifyContent:flex-end — override on phones so it lines up left. */
  .A .page-head > div:nth-child(2) > div[style*="justify-content"] {
    justify-content: flex-start !important;
  }
  /* Pull the big amount display down a notch so label/figure look like
     a tidy stack rather than crowding the title. */
  .A .page-head > div:nth-child(2) > .display {
    font-size: 30px !important;
    margin-top: 2px !important;
  }

  /* ── Customer-detail stage stepper (5 stages: 初次接觸 → 成交結案):
        each column was flex:1 with no min-width, so on a 390pt screen
        the inner label div ended up ~33px wide and the 4-char CJK label
        wrapped per-character vertically (初/次/接/觸 etc.). Force
        white-space:nowrap on every label, give each step column a
        comfortable min-width (4em = ~52px at 12px), shrink connectors,
        and let the whole row scroll horizontally if it overflows. */
  .A .hz-stage-stepper-card {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .A .hz-stage-stepper {
    /* min-content keeps the row from being squeezed below its natural
       width; combined with the parent's overflow-x:auto this enables
       horizontal scroll only when the labels really don't fit. */
    min-width: min-content;
  }
  /* Each step column = circle + label. Both children of .hz-stage-stepper
     except the connectors. Connector divs have inline width:auto on the
     outer + width:40px on the inner. Step columns are the ones with
     flex-direction:column. */
  .A .hz-stage-stepper > div[style*="flex-direction:column"],
  .A .hz-stage-stepper > div[style*="flex-direction: column"] {
    flex: 0 0 auto !important;
    min-width: 4em;
  }
  /* The label text div is the second child of each step column. Keep it
     on a single line and slightly smaller so 4 CJK chars fit at ~46px. */
  .A .hz-stage-stepper > div[style*="flex-direction:column"] > div:nth-child(2),
  .A .hz-stage-stepper > div[style*="flex-direction: column"] > div:nth-child(2) {
    white-space: nowrap;
    font-size: 11.5px !important;
    text-align: center;
    line-height: 1.3;
  }
  /* Connectors between steps — shrink the 40px line to 16px so all 5
     stages can sit on one row at 390pt without forcing a scroll in the
     common case (5×52 + 4×16 = 324px, fits inside the card minus padding). */
  .A .hz-stage-stepper > div[style*="height:1px"] > div,
  .A .hz-stage-stepper > div[style*="height: 1px"] > div {
    width: 16px !important;
  }

  /* ── Reports' "bars" chart (.bars inside .card) was being squashed into
        342px which made labels overlap and reduced bar height resolution.
        Make the .card container scroll horizontally so the chart can keep
        each bar at a sensible width (52px) and the user swipes left/right
        to see all 6 months. ── */
  .A .card > .bars {
    min-width: 360px;
    padding-bottom: 6px;
  }
  .A .card:has(> .bars) {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .A .card > .bars > .bar-col {
    min-width: 48px;
  }

  /* ── Customer-list filter tabs (全部/我的客戶/熱門商機/待跟進/已逾期/草稿)
        — six chips at 12.5px + padding don't fit a 358px row, so flex's
        default behaviour was to shrink each cell, forcing the CJK label
        to wrap per character ("我的 / 客戶"). Make the tabs row scroll
        horizontally and force every label onto a single line. ── */
  .A .tabs {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    flex-wrap: nowrap;
    /* Hide the scrollbar — content is short and the visible chevron of
       the next chip already telegraphs scroll-ability. */
    scrollbar-width: none;
  }
  .A .tabs::-webkit-scrollbar { display: none; }
  .A .tab {
    white-space: nowrap;
    flex: 0 0 auto;
  }
}

/* ── Tablet responsive (768–1023): hide secondary table columns,
      ensure tables don't overflow. ── */
@media (min-width: 768px) and (max-width: 1023px) {
  /* Customers (9 cols): hide 下一步(5) / 更新(7) */
  .A .page .table thead th:nth-child(5),
  .A .page .table thead th:nth-child(7),
  .A .page .table tbody td:nth-child(5),
  .A .page .table tbody td:nth-child(7) { display: none; }
  .A .topbar { padding: 14px 24px; }
  .A .page { padding: 24px 24px 32px; }
}

/* ── Generic table overflow safety net: at any width, allow horizontal
      scroll inside a wrapper if columns still overflow. The .table itself
      keeps its existing width:100% from style-a. ── */
.A .table { table-layout: auto; }
@media (max-width: 1023px) {
  /* Allow text-heavy cells to wrap rather than blow out the row,
     but keep numeric / chip / monospace cells intact. */
  .A .table td, .A .table th { word-break: keep-all; overflow-wrap: break-word; }
  .A .table td.amount, .A .table .amount { white-space: nowrap; }
  .A .table td .stage-chip, .A .table td .chip { white-space: nowrap; }
  .A .table td .cust-id, .A .table td .mono { white-space: nowrap; }
}

/* ── Dark-mode "tone-down" — DISABLED 2026-05-09 ──
   Tried "auto-tone-down on prefers-color-scheme: dark", which on this
   warm-paper UI ended up making the sidebar look almost black and felt
   uglier than the original light theme. `html { color-scheme: light }`
   above already keeps form controls / scrollbars light-themed so the
   eye-stab worry is mostly handled. Leaving the rule block out keeps the
   identical light look in dark-mode systems too.

   If we ship a real dark theme later (full token swap), restore here. */


/* ── Landscape phone (short viewport) ──
   At iPhone landscape (e.g. 844 × 390) the app width crosses 768px so the
   normal desktop layout returns: sidebar visible, no hamburger, no bottom
   sheet. That part is fine. What breaks is the centered .hz-modal (e.g.
   新增合約): its natural height (~430px) exceeds the 390px viewport, so the
   header gets clipped at the top AND the footer (取消 / 建立合約) hides below
   the fold — the user can't reach the primary action.
   Fix: when viewport is short, cap the modal at viewport height, make the
   body the scroll container, and keep the footer always visible. We key on
   max-height so this triggers on real short-screen contexts (landscape phone,
   split-view iPad) without affecting normal desktop windows. */
@media (max-height: 500px) {
  .hz-modal-bg { align-items: center; padding: 8px 0; }
  .hz-modal {
    max-height: calc(100vh - 16px);
    display: flex;
    flex-direction: column;
  }
  .hz-modal-body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    flex: 1 1 auto;
    min-height: 0;
  }
  .hz-modal-hd { padding-top: 14px; padding-bottom: 10px; }
  .hz-modal-foot { padding-top: 10px; padding-bottom: 10px; }
  /* Inline max-height set by ModalEditCustomer (60vh) is fine here, but
     anything that hard-codes a too-tall body should defer to the flex cap. */
  .hz-modal-body[style*="max-height"] { max-height: none !important; }
}

/* ─── UX Phase 1 ───────────────────────────────────────────── */
/* Append-only block. Toast type variants, Confirm dialog, Loading spinner. */

/* Toast types — success/error/warn/info */
/* success = jade (uses --green from style-a) */
.hz-toast.toast-success,
.hz-toast.ok {
  background: var(--green, #5A6E3C);
  color: var(--paper, #fff);
}
.hz-toast.toast-success .mark,
.hz-toast.ok .mark {
  background: var(--paper, #fff);
  color: var(--green, #5A6E3C);
}
/* error = 朱 (accent / deep rust) */
.hz-toast.toast-error,
.hz-toast.err {
  background: #6e2a1c;
  color: var(--paper, #fff);
}
.hz-toast.toast-error .mark,
.hz-toast.err .mark {
  background: rgba(255,255,255,0.92);
  color: #6e2a1c;
}
/* warn = 金 (gold) */
.hz-toast.toast-warn,
.hz-toast.warn {
  background: var(--gold, #B8924F);
  color: var(--paper, #fff);
}
.hz-toast.toast-warn .mark,
.hz-toast.warn .mark {
  background: var(--paper, #fff);
  color: var(--gold, #B8924F);
}
/* info = ink (default look — already styled by .hz-toast) */
.hz-toast.toast-info { /* identity — same as base */ }

/* ─── Confirm dialog ─── */
/* Stack above other modals so HzConfirm wins focus. */
.hz-confirm-bg { z-index: 55; }
.hz-confirm {
  width: min(440px, calc(100vw - 32px));
  max-width: 440px;
  background: var(--paper, #fff) !important;
  border-radius: 12px;
}
.hz-confirm .hz-modal-hd {
  padding: 22px 26px 12px;
  border-bottom: 0.5px solid var(--line, #E4DACB);
}
.hz-confirm .hz-confirm-msg {
  font-size: 14px;
  color: var(--ink-2, #564A3A);
  line-height: 1.55;
  white-space: pre-wrap;
}
.hz-confirm .hz-modal-foot {
  padding: 14px 22px;
}
/* Confirm primary button colour: jade by default, 朱 when danger=true */
.hz-confirm .btn.primary.ok {
  background: var(--green, #5A6E3C);
  border-color: var(--green, #5A6E3C);
  color: var(--paper, #fff);
}
.hz-confirm .btn.primary.ok:hover {
  filter: brightness(0.93);
}
.hz-confirm .btn.primary.danger {
  background: var(--accent, #9C3D1F);
  border-color: var(--accent, #9C3D1F);
  color: var(--paper, #fff);
}
.hz-confirm .btn.primary.danger:hover {
  filter: brightness(0.93);
}

body.hz-confirm-open { overflow: hidden; touch-action: none; }

/* ─── Loading spinner ─── */
.hz-loading {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink-3, #8A7A63);
  font-size: 13px;
}
.hz-loading.block {
  display: flex;
  width: 100%;
  justify-content: center;
  padding: 40px 0;
}
.hz-spinner {
  display: inline-block;
  width: 18px;
  height: 18px;
  border: 2px solid var(--line, #E4DACB);
  border-top-color: var(--accent, #9C3D1F);
  border-radius: 50%;
  animation: hz-spin 0.8s linear infinite;
  box-sizing: border-box;
}
.hz-loading-msg { color: var(--ink-3, #8A7A63); }
@keyframes hz-spin {
  to { transform: rotate(360deg); }
}
