/* icookfont is not imported here. Rails 8's stylesheet_link_tag :app includes
   every stylesheet under app/assets/stylesheets, so the vendored files are
   already linked -- and a CSS @import would load them a second time, since
   Propshaft has no bundler and leaves @import to the browser. */
/* Internal back office and the customer-facing help centre. Plain CSS on
   Propshaft: the surface is small and a build step here would earn nothing. */
:root {
  --ink: #2b2b2b;
  --muted: #8a8a8a;
  --line: #e8e6e4;
  --surface: #ffffff;
  --canvas: #faf8f7;
  /* The chrome around the work: the rail and the toolbar. A tone rather than a
     rule, so the edges of the application read as edges without drawing a line
     at every one of them. */
  --shell: #f5f6f7;
  /* A strip that labels what comes under it. A shade off the shell, because it
     sits inside the work rather than around it. */
  --band: #f8f9f9;
  /* What the thread is read on. White: a page of someone else's words wants
     to be the quietest thing on screen, and nothing is quieter than paper. */
  --thread: #ffffff;
  /* Chosen, in the queue: the faintest warm tint the accent can make. Enough
     to find the row you left, not enough to be a second red thing on a column
     that already carries a status chip per row. */
  --selected: #fdf4f2;
  /* Chosen, among the pinned: filled and reversed, the way an active tab has
     always been. These are three or four cards in their own block, so the one
     being read can afford to be unmistakable -- in the queue the same
     treatment on one row of fifty would be a hole punched in the list. */
  --selected-tab: #3a3734;

  /* What lifts a block off the surface it sits on. White on near-white is not
     a difference anybody sees, so the hairline does the separating and the
     drop only says which one is on top. Both are needed: the shadow alone is
     invisible at this weight and the ring alone is just a border. */
  --lift: 0 0 0 1px rgba(42, 40, 38, .05), 0 1px 2px rgba(42, 40, 38, .06);
  --card: 10px;
  --accent: #f0563c;      /* iCook red */
  --accent-soft: #fdeeea;
  --accent-line: #f8d5cc;
  --note: #fffbeb;
  --note-line: #f2d98c;
  --ok: #2f8f4e;
  --warn: #b42318;
  /* The logo's own ink. The help centre lockup is not an accent, it is
     the name of the place, so it does not compete with everything the
     red is actually pointing at. */
  --brand-ink: #564e4a;

  /* One hue per channel, so a source is recognised before it is read. The
     outside ones are the platform's own colour; web and app are ours. Each is
     a shade or two down from the brand swatch: these colour 11px text as well
     as the mark, and Messenger blue or LINE green straight off the brand sheet
     is barely legible at that size on white. */
  --ch-web: #0f766e;
  --ch-app: #d8452c;
  --ch-email: #6b7280;
  --ch-facebook: #0b74d4;
  --ch-instagram: #c1265c;
  --ch-line: #06923f;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 15px/1.6 system-ui, -apple-system, "Noto Sans TC", "PingFang TC", sans-serif;
  color: var(--ink);
  background: var(--canvas);
}
button, select, textarea, input { font: inherit; color: inherit; }
a { color: var(--accent); }

/* The support centre's logo, recoloured through currentColor. Square (64x64),
   and it carries its own padding, so it is sized larger inside the avatar
   than the old wide mark was. */
.brand-avatar,
.hc-top-mark,
.wb-rail-mark { display: inline-flex; align-items: center; justify-content: center; }
.brand-avatar { background: var(--accent); border-radius: 50%; color: #fff; flex: none; }
.brand-avatar-mark { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
.hc-top-mark { color: var(--brand-ink); font-size: 40px; }
.wb-rail-mark { color: var(--brand-ink); font-size: 38px; }
/* The icon font's own rule gives every glyph side margins and a line box of
   its own, neither of which a mark centred in a box wants. */
.brand-avatar-mark i::before,
.hc-top-mark i::before,
.wb-rail-mark i::before { margin: 0; width: auto; line-height: 1; }

/* ── Sign-in and other single-purpose pages ───────────────────────────── */
.auth { max-width: 30rem; margin: 5rem auto; padding: 2.5rem; background: var(--surface); border: 1px solid var(--line); border-radius: 16px; }
.auth h1 { margin: 0 0 1.25rem; font-size: 1.3rem; }
.auth-alert { padding: .75rem 1rem; background: var(--accent-soft); color: var(--warn); border-radius: 8px; }
.auth-hint { color: var(--muted); font-size: .9em; }
.auth form { margin: 0 0 1rem; }
.auth button { padding: .6rem 1.4rem; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); cursor: pointer; }


/* ══ Customer help centre ═════════════════════════════════════════════ */
/* The page fills the window and does not scroll; the two panes inside it do.
   With min-height the shell grew past the viewport, so a long ticket
   list scrolled the document as well as itself -- two scrollbars, doing
   different things, and the composer pushed off the bottom of the screen.
   dvh so a phone's collapsing address bar does not leave a gap. */
.cc { height: 100vh; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
.cc > .hc-top { flex: none; }

/* min-height: 0 on all three, or a flex item refuses to shrink below its
   content and the overflow: auto inside it never has anything to do. */
.cc-body { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(16rem, 22rem) 1fr; gap: 0; max-width: 74rem; width: 100%; margin: 1.5rem auto; padding: 0 1.5rem; }
.cc-list { min-height: 0; background: var(--surface); border: 1px solid var(--line); border-right: 0; border-radius: 14px 0 0 14px; display: flex; flex-direction: column; }
.cc-main { min-height: 0; background: var(--surface); border: 1px solid var(--line); border-radius: 0 14px 14px 0; display: flex; flex-direction: column; }

.cc-list-head { display: flex; align-items: center; gap: .5rem; padding: 1.1rem 1.25rem .75rem; }
.cc-list-head h2 { margin: 0; font-size: 1.05rem; }
.cc-count { padding: .05rem .5rem; border-radius: 999px; background: var(--canvas); color: var(--muted); font-size: .85rem; }
.cc-new { margin: 0 1.25rem 1rem; padding: .7rem; border: 0; border-radius: 10px; background: var(--accent); color: #fff; font-weight: 600; cursor: pointer; }

.cc-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .3rem; padding: 2rem; text-align: center; }
.cc-empty-icon { font-size: 2.6rem; opacity: .3; }
.cc-empty-title { margin: .5rem 0 0; font-weight: 600; font-size: 1.1rem; }
.cc-empty-hint { margin: 0; color: var(--muted); font-size: .9rem; }

.cc-items { list-style: none; margin: 0; padding: 0; flex: 1; overflow-y: auto; border-top: 1px solid var(--line); }
.cc-item { display: flex; flex-direction: column; gap: .3rem; width: 100%; padding: .85rem 1.25rem; border: 0; border-bottom: 1px solid var(--line); background: none; text-align: start; cursor: pointer; }
.cc-item:hover { background: var(--canvas); }
.cc-item.is-active { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.cc-item-top { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.cc-item-title { font-size: .95rem; }
.cc-item-foot { display: flex; justify-content: space-between; gap: .5rem; color: var(--muted); font-size: .82rem; }
.cc-item-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cc-list-foot { margin: 0; padding: .9rem 1.25rem; border-top: 1px solid var(--line); color: var(--muted); font-size: .82rem; text-align: center; }

.cc-badge { padding: .1rem .55rem; border-radius: 999px; font-size: .78rem; white-space: nowrap; }
.cc-badge-waiting { background: var(--accent-soft); color: #c2410c; }
.cc-badge-answered { background: #e8f6ec; color: var(--ok); }
.cc-badge-closed { background: var(--canvas); color: var(--muted); }

.cc-main-head { display: flex; align-items: center; gap: 1rem; padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--line); }
.cc-main-head h2 { margin: 0; font-size: 1.15rem; }
.cc-main-head p { margin: .2rem 0 0; color: var(--muted); font-size: .9rem; }
.cc-thread-sub { display: flex; align-items: center; gap: .5rem; }

.cc-form { padding: 1.5rem; display: flex; flex-direction: column; gap: 1.25rem; }
.cc-field { display: flex; flex-direction: column; gap: .4rem; }
.cc-label { font-weight: 600; }
.cc-label em { margin-left: .3rem; color: var(--accent); font-style: normal; font-size: .85rem; }
.cc-label em.cc-optional { color: var(--muted); }
.cc-field textarea, .cc-field input { padding: .75rem; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); }
.cc-field textarea { resize: vertical; }
.cc-help { color: var(--muted); font-size: .85rem; }
.cc-help-lock { display: flex; gap: .3rem; align-items: center; }
.cc-submit { align-self: flex-start; padding: .7rem 1.6rem; border: 0; border-radius: 10px; background: var(--accent); color: #fff; font-weight: 600; cursor: pointer; }
.cc-submit:disabled { background: var(--line); color: var(--muted); cursor: not-allowed; }
.cc-error { margin: 0; color: var(--warn); }

.cc-thread { flex: 1; overflow-y: auto; padding: 1.25rem 1.5rem; }
.cc-day { display: flex; align-items: center; gap: .75rem; margin: .75rem 0 1rem; color: var(--muted); font-size: .82rem; }
.cc-day::before, .cc-day::after { content: ''; flex: 1; height: 1px; background: var(--line); }
.cc-msg { display: flex; gap: .6rem; margin-bottom: 1rem; }
.cc-msg-you { justify-content: flex-end; }
.cc-msg-col { display: flex; flex-direction: column; max-width: 78%; }
.cc-msg-you .cc-msg-col { align-items: flex-end; }
/* overflow-wrap: anywhere on every body that renders what somebody typed.
   A pasted URL, order id or token is one unbroken word, and without this it
   ran straight out of its bubble -- in the internal note it painted over the
   byline beside it. */
.cc-bubble { padding: .7rem 1rem; border-radius: 14px; white-space: pre-wrap; overflow-wrap: anywhere; }
.cc-msg-support .cc-bubble { background: var(--surface); border: 1px solid var(--line); border-bottom-left-radius: 4px; }
.cc-msg-you .cc-bubble { background: var(--accent-soft); border: 1px solid var(--accent-line); border-bottom-right-radius: 4px; }
.cc-msg-col time { margin-top: .25rem; color: var(--muted); font-size: .78rem; }

/* Chips rather than a dropdown: six options are quicker to read laid out than
   opened, and a dropdown's closed state looks like a required field waiting to
   be filled in, which this one is not. */
.cc-categories { border: 0; margin: 0; padding: 0; }
.cc-categories legend { padding: 0; }
.cc-chips { display: flex; flex-wrap: wrap; gap: .4rem; margin: .4rem 0 .3rem; }
.cc-chip { padding: .3rem .7rem; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); color: var(--ink); font: inherit; font-size: .85rem; cursor: pointer; }
.cc-chip:hover { border-color: var(--accent); color: var(--accent); }
.cc-chip.is-on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.cc-chip:disabled { opacity: .6; cursor: default; }

.cc-closed { margin: 0 1.5rem; padding: .7rem 1rem; background: var(--canvas); border-radius: 10px; color: var(--muted); font-size: .9rem; }
.cc-linklike { border: 0; background: none; color: var(--accent); cursor: pointer; padding: 0; }

/* One row of three controls -- attach, the box, send -- on one height. They
   were 33.4 / 48.4 / 46.4 px sharing a bottom edge, so nothing lined up with
   anything: the attach button sat visibly low and small beside the box.
   The line-height matters here: at 24px the box computed taller than
   --cc-control and won, which is what put the send button 2px below it. */
.cc-composer { --cc-control: 2.9rem; display: flex; gap: .75rem; align-items: flex-end; padding: 1rem 1.5rem 1.25rem; border-top: 1px solid var(--line); }
.cc-composer textarea { flex: 1; padding: .7rem 1rem; border: 1px solid var(--line); border-radius: 999px; resize: none; line-height: 1.45; min-height: var(--cc-control); }
.cc-send { width: var(--cc-control); height: var(--cc-control); flex: none; border: 0; border-radius: 50%; background: var(--accent); color: #fff; cursor: pointer; }
.cc-send:disabled { background: var(--line); color: var(--muted); cursor: not-allowed; }

@media (max-width: 48rem) {
  /* Here the page scrolls, because the panes stack instead of sitting side by
     side and neither would have room in a locked viewport. The 34rem floor
     comes back with it: it is what keeps the thread readable when the page,
     not the pane, is what scrolls. */
  .cc { height: auto; min-height: 100dvh; overflow: visible; }
  .cc-body { grid-template-columns: 1fr; padding: 0; margin: 0; }
  .cc-list { border-radius: 0; border-right: 1px solid var(--line); }
  .cc-main { border-radius: 0; min-height: 34rem; }
}

/* ══ Help centre (public) ═════════════════════════════════════════════ */
.hc-page { background: var(--surface); }
.hc-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

/* One measure for every band, so the header, the hero and the content line up
   down the page rather than each finding its own edge. */
.hc-top-inner, .hc-hero-inner, .hc-body-wrap, .hc-foot-inner, .hc-searchbar > .hc-search,
.hc.hc-with-side { max-width: 68rem; margin: 0 auto; padding: 0 1.25rem; }

.hc-top { border-bottom: 1px solid var(--line); background: var(--surface); }
/* Three parts rather than one row that pushes: brand, tabs, actions. With a
   flex row the tabs sit wherever the things after them leave room, so the chat
   -- which also shows a sign-in state -- put them 136px left of where the help
   centre did. Their own column means nothing beside them can move them. */
.hc-top-inner { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 1rem; height: 3.5rem; }
.hc-top-lead { display: flex; align-items: center; gap: 1rem; min-width: 0; }
.hc-top-actions { display: flex; align-items: center; justify-content: flex-end; gap: 1rem; min-width: 0; }
.hc-top-brand { display: inline-flex; align-items: center; gap: .5rem; color: var(--brand-ink); text-decoration: none; font-weight: 600; }
/* The name of the place does not wrap. On a narrow phone it was breaking into
   愛料理 / 客服中 / 心; the row wraps instead, which is what it is for. */
.hc-top-name { white-space: nowrap; }

/* The two places somebody can be. Underlined rather than filled, so the header
   stays quiet next to the hero below it. */
.hc-tabs { display: flex; gap: 1.25rem; }
.hc-tab { padding: .2rem 0; border-bottom: 2px solid transparent; color: var(--muted); text-decoration: none; font-size: .92rem; }
.hc-tab:hover { color: var(--ink); }
.hc-tab.is-on { border-bottom-color: var(--accent); color: var(--accent); font-weight: 600; }
.hc-top-back { color: var(--muted); text-decoration: none; font-size: .88rem; }
/* Only the chat shows these -- the help centre needs no account to read -- but
   they belong to this header rather than to a second one. */
.hc-top-status { color: var(--muted); font-size: .88rem; }
.hc-top-signin { padding: .3rem 1rem; border: 1px solid var(--accent); border-radius: 8px; color: var(--accent); text-decoration: none; font-size: .88rem; }

.hc-hero { background: var(--accent-soft); border-bottom: 1px solid var(--accent-line); }
.hc-hero-inner { padding-block: 2.75rem 2.25rem; text-align: center; }
.hc-hero-label { margin: 0 0 .4rem; color: var(--muted); font-size: .85rem; }
.hc-hero-title { margin: 0 0 .4rem; font-size: 1.85rem; }
.hc-hero-note { margin: 0 0 1.5rem; color: var(--muted); }

.hc-search { display: flex; gap: .6rem; max-width: 36rem; margin: 0 auto; }
.hc-search-box { flex: 1; min-width: 0; display: flex; align-items: center; gap: .5rem; padding: 0 1rem; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); color: var(--muted); }
.hc-search-box input { flex: 1; min-width: 0; padding: .8rem 0; border: 0; background: none; outline: none; color: var(--ink); }
.hc-search button { padding: .8rem 1.6rem; border: 0; border-radius: 10px; background: var(--accent); color: #fff; font-weight: 600; cursor: pointer; }

/* On an answer page the search box leads to the next question rather than
   being the point of the page, so it gets a strip instead of the hero. */
.hc-searchbar { padding-block: 1rem; background: var(--accent-soft); border-bottom: 1px solid var(--accent-line); }
.hc-search-slim { max-width: 30rem; margin-inline-start: auto; margin-inline-end: 0; }
.hc-search-slim button { padding: .55rem 1.2rem; }
.hc-search-slim .hc-search-box input { padding: .55rem 0; }

.hc-body-wrap { padding-block: 2.5rem 3rem; }
.hc-h2 { margin: 0 0 1rem; font-size: 1.15rem; }
.hc-h2 a { text-decoration: none; }
.hc-body-wrap > section { margin-bottom: 2.75rem; }

/* Cards, because a category is a place to go rather than a heading over a
   list -- six of them read faster than six lists of four. */
/* Three across, because there are six of them: auto-fit gave four and then a
   row of two, which reads as a leftover rather than a grid. */
.hc-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); gap: .75rem; }
@media (min-width: 62rem) { .hc-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.hc-card { display: flex; align-items: flex-start; gap: .85rem; padding: 1.1rem 1.15rem; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); color: var(--ink); text-decoration: none; }
.hc-card:hover { border-color: var(--accent-line); background: var(--accent-soft); }
.hc-card-mark { flex: none; width: 1.9rem; font-size: 1.5rem; line-height: 1.5; text-align: center; }
/* One hue each, so the six are told apart before they are read. */
.hc-card-mark-vip { color: #d99b0a; }
.hc-card-mark-account { color: #2f6fd0; }
.hc-card-mark-recipe { color: #1f8a5c; }
.hc-card-mark-device { color: #7a4fd0; }
.hc-card-mark-market { color: var(--accent); }
.hc-card-mark-announcement { color: #c2410c; }
.hc-card-mark-help { color: var(--muted); }
.hc-card-words { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .15rem; }
.hc-card-name { font-weight: 600; }
/* Two lines rather than one with an ellipsis: these descriptions are real
   sentences somebody wrote, and "針對 VIP 訂閱或 VI…" tells a reader nothing.
   Clamped so a long one cannot make its card taller than its neighbours. */
.hc-card-note { color: var(--muted); font-size: .85rem; line-height: 1.5; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }

.hc-chevron { flex: none; align-self: center; color: var(--line); font-size: .9rem; }
.hc-card:hover .hc-chevron, .hc-row:hover .hc-chevron { color: var(--accent); }

.hc-rows { list-style: none; margin: 0; padding: 0; }
.hc-row { display: flex; align-items: center; gap: .85rem; padding: .95rem .25rem; border-bottom: 1px solid var(--line); color: var(--ink); text-decoration: none; }
.hc-row:hover { background: var(--canvas); }
.hc-row-words { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .2rem; }
.hc-row-title { line-height: 1.5; }
.hc-row-note { color: var(--muted); font-size: .85rem; }
/* Which area it came from, for a list that mixes them. */
.hc-row-aside { flex: none; color: var(--muted); font-size: .82rem; }

.hc-empty { padding: 1.5rem; border: 1px dashed var(--line); border-radius: 12px; background: var(--canvas); color: var(--muted); }

/* The way out of a page that did not answer the question. One band, because it
   is one decision. */
.hc-cta { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; padding: 1.25rem 1.5rem; border: 1px solid var(--accent-line); border-radius: 12px; background: var(--accent-soft); }
.hc-cta-mark { flex: none; font-size: 1.5rem; color: var(--accent); }
.hc-cta-words { flex: 1; min-width: 12rem; }
.hc-cta-words h2 { margin: 0 0 .2rem; font-size: 1.05rem; }
.hc-cta-words p { margin: 0; color: var(--muted); font-size: .9rem; }
.hc-cta-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
.hc-cta-primary { padding: .6rem 1.5rem; border-radius: 8px; background: var(--accent); color: #fff; text-decoration: none; font-weight: 600; }
.hc-cta-secondary { padding: .6rem 1.3rem; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); color: var(--ink); text-decoration: none; }
.hc-cta-mail { color: var(--muted); font-size: .85rem; }

/* The other answers in the same area, which is what somebody who did not find
   theirs needs next. */
.hc.hc-with-side { display: grid; grid-template-columns: 14rem minmax(0, 1fr); gap: 2.5rem; padding-block: 2.5rem 3rem; align-items: start; }
.hc-side { position: sticky; top: 1.5rem; }
.hc-side-title { margin: 0 0 .75rem; font-size: 1.05rem; }
.hc-side-nav { display: flex; flex-direction: column; gap: .1rem; }
.hc-side-link { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: .5rem .75rem; border-radius: 8px; color: var(--ink); text-decoration: none; font-size: .92rem; }
.hc-side-link:hover { background: var(--canvas); }
.hc-side-link.is-on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.hc-side-all { display: inline-block; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line); color: var(--muted); text-decoration: none; font-size: .88rem; }

.hc-main { min-width: 0; }
.hc-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-bottom: 1rem; color: var(--muted); font-size: .85rem; }
.hc-kicker { margin: 0 0 .35rem; color: var(--accent); font-size: .85rem; font-weight: 600; }
.hc-title { margin: 0 0 .5rem; font-size: 1.6rem; line-height: 1.4; }
.hc-lede { margin: 0 0 2rem; color: var(--muted); }
.hc-updated { margin: 0 0 2rem; color: var(--muted); font-size: .85rem; }
.hc-group { margin-bottom: 2.5rem; }
.hc-more { margin-top: 2.5rem; }
.hc-main > .hc-cta { margin-top: 2.5rem; }

/* The article body. These rules style content we did not write, so they cover
   the tags the import actually produced and nothing else. */
.hc-body { line-height: 1.9; }
.hc-body h2 { margin: 2rem 0 .75rem; font-size: 1.2rem; }
.hc-body h3 { margin: 1.5rem 0 .5rem; font-size: 1.05rem; }
.hc-body h4 { margin: 1.25rem 0 .5rem; font-size: .98rem; }
.hc-body p { margin: 0 0 1rem; }
.hc-body ul, .hc-body ol { margin: 0 0 1rem; padding-inline-start: 1.4rem; }
.hc-body li { margin-bottom: .35rem; }
.hc-body img { max-width: 100%; height: auto; border-radius: 8px; }
.hc-body blockquote { margin: 0 0 1rem; padding: .75rem 1rem; border-inline-start: 3px solid var(--accent-line); background: var(--canvas); }
/* Keeps an embed inside the column on a phone, which is where most of these
   are read. */
.hc-video { margin: 0 0 1rem; aspect-ratio: 16 / 9; }
.hc-video iframe { width: 100%; height: 100%; border: 0; border-radius: 8px; }

.hc-foot { border-top: 1px solid var(--line); background: var(--canvas); }
.hc-foot-inner { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; padding-block: 1.5rem; color: var(--muted); font-size: .85rem; }
.hc-foot-brand { color: var(--accent); font-weight: 600; }
.hc-foot-links { display: flex; flex-wrap: wrap; gap: 1rem; margin-inline-start: auto; }
.hc-foot-links a { color: var(--muted); text-decoration: none; }
.hc-foot-links a:hover { color: var(--accent); }

@media (max-width: 48rem) {
  /* Two rows on a phone: identity above, the two places below. */
  .hc-top-inner { display: flex; flex-wrap: wrap; height: auto; padding-block: .6rem; }
  /* min-content, overriding the min-width: 0 above: with the name set not to
     wrap, letting the lead shrink below its content made it run underneath
     the sign-in state instead of pushing it to the next row. */
  .hc-top-lead { flex: 1 1 auto; min-width: min-content; }
  /* Beside the name while it fits, on its own line when it does not. */
  .hc-top-actions { margin-inline-start: auto; }
  .hc-tabs { order: 3; width: 100%; }
  /* Not hidden any more. It used to be safe to drop because the logo also
     went to icook.tw; now the logo goes to the help centre front page and
     this is the only way back to the main site. */
  .hc-top-back { white-space: nowrap; }
  .hc-hero-title { font-size: 1.5rem; }
  .hc-search { flex-direction: column; }
  /* The sidebar becomes what it is on a phone: the list that follows the
     answer rather than a column beside it. */
  .hc.hc-with-side { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
  .hc-side { position: static; order: 2; padding-top: 1.5rem; border-top: 1px solid var(--line); }
  .hc-main { order: 1; }
  .hc-search-slim { margin-inline: auto; }
  .hc-cta-actions { width: 100%; }
  .hc-cta-primary, .hc-cta-secondary { flex: 1; text-align: center; }
}

/* ══ Agent workbench ══════════════════════════════════════════════════ */
/* One screen, and the row is pinned to it: a grid with a fixed height still
   sizes an implicit auto row to its content, so a long list grew the row past
   100vh and the whole page scrolled instead of the column. */
.wb { display: grid; grid-template-columns: 4.5rem 1fr; grid-template-rows: minmax(0, 1fr); height: 100vh; overflow: hidden; }
/* The rail is server-rendered and #inbox-root is React's mount point, so it
   has to disappear as a box or it would sit between the grid and .wb-body. */
#inbox-root { display: contents; }
.wb-body { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
/* Toolbar spans the columns: search and the quick views apply to the list, but
   they are the page's controls, not the list's. */
/* The sides are what the agent dragged, but never more than a share of the
   window: a fixed side column crushing the thread as the window narrows is the
   bug this replaces. The middle takes what is left, and 0 as its minimum is
   what stops it forcing an overflow instead.
.
   The list's share is the larger one. Its rows carry a name, a title that
   wraps to two lines, a message and a strip of chips, and on a wide monitor
   there is room to give it without the thread noticing -- 30vw was capping the
   drag long before the agent let go of it. */
.wb-columns { --gutter: .55rem; --detail-col: min(var(--detail-w, 18rem), 32vw);
  /* Every gutter in here, not just the thread's. The panel is a card with a
     gap above and below it, and that gap was showing the body's warm canvas
     -- a grey strip at the top and bottom of an otherwise white screen. The
     cards are told apart by their own hairline and shadow now, the same way
     the header card is told apart from the thread behind it. */
  background: var(--thread);
  flex: 1; display: grid; min-height: 0;
  grid-template-columns:
    max(11rem, min(var(--list-w, 20rem), 42vw))
    auto
    minmax(0, 1fr)
    auto
    var(--detail-col);
  /* The close is the column shutting, not the panel shrinking: the panel holds
     the width it was given and the track slides over it, so nothing inside it
     reflows on the way out. */
  transition: grid-template-columns .22s ease;
}
.wb-columns.is-detail-off { grid-template-columns:
    max(11rem, min(var(--list-w, 20rem), 42vw)) auto minmax(0, 1fr) auto 0px; }
/* Nothing chosen: the queue takes the window. Two thirds of the screen
   describing a ticket nobody picked is two thirds of the screen saying
   nothing -- and the same transition that closes the panel does this, so the
   columns move rather than jump. */
.wb-columns.is-browsing { grid-template-columns: minmax(0, 1fr) 0 0 0 0; }
.wb-columns.is-browsing .wb-grip { display: none; }
/* A drag is a sequence of tiny width changes; easing each one lags the pointer
   behind the edge being dragged. */
.wb-columns.is-resizing { transition: none; }

/* The slot holds no flow content of its own -- the clip is taken out of flow
   and stretched to it. Left in flow, the panel's full scroll height would size
   the grid's one row and push every column past the bottom of the window.
.
   The clip reaches a few pixels further left than the slot and pads the
   difference back: flush, it cut the panel's own left edge off -- the hairline
   and the shadow that make it a card at all -- so the panel read as a wall
   beside two cards. Nothing is drawn in that strip but the shadow, and nothing
   in it is clickable, or it would swallow the resize handle underneath. */
.wb-panel-slot { position: relative; min-width: 0; }
.wb-panel-clip { position: absolute; top: 0; bottom: 0; right: 0; left: -8px; padding-left: 8px; display: flex; overflow: hidden; pointer-events: none; }
.wb-panel-clip > * { pointer-events: auto; }
.wb-columns.is-resizing { cursor: col-resize; }

/* Below this the sides yield harder than the drag asked for: the thread is
   what an agent is reading, and honouring a 440px list on a 900px window is
   honouring the wrong thing. The stored width comes back untouched as soon as
   there is room for it again. */
@media (max-width: 1200px) {
  .wb-columns {
    --detail-col: min(var(--detail-w, 18rem), 22vw);
    grid-template-columns:
      max(9rem, min(var(--list-w, 20rem), 30vw))
      auto
      minmax(0, 1fr)
      auto
      var(--detail-col);
  }
  .wb-columns.is-detail-off { grid-template-columns:
      max(9rem, min(var(--list-w, 20rem), 30vw)) auto minmax(0, 1fr) auto 0px; }
}

/* Wider than it looks: the visible line is 1px but the grab area is not, or
   catching it becomes a game. */
.wb-grip { width: 5px; margin: 0 -2px; cursor: col-resize; background: none; border: 0; z-index: 5; }
.wb-grip:hover, .wb-columns.is-resizing .wb-grip { background: var(--accent-line); }
body.is-resizing { user-select: none; }

/* macOS hides scrollbars until they are used -- until a mouse is plugged in,
   and then every one of them is on permanently at 15px of solid grey, which
   on this screen is three of the heaviest marks in the interface. Thin and
   faint, and the gutter held open whether or not there is anything to scroll,
   so switching between a long ticket and a short one does not shift the thread
   sideways. (Where the scrollbar is an overlay, reserving the gutter does
   nothing, which is the right answer there too.) */
.wb-items, .wb-thread, .wb-panel {
  scrollbar-width: thin;
  scrollbar-color: rgba(42, 40, 38, .22) transparent;
  scrollbar-gutter: stable;
}

@media (prefers-reduced-motion: reduce) {
  .wb-columns, .wb-composer, .wb-composer-body, .wb-edge, .wb-item-open { transition: none; }
  /* The jump between sections lands rather than travels. */
  .wb-sections { scroll-behavior: auto; }
}

/* Tone and rule both: the grey says this is chrome and the line says where it
   stops. The tone alone left two near-whites meeting with nothing between
   them. */
.wb-toolbar { background: var(--shell); border-bottom: 1px solid var(--line); }
.wb-toolbar-top { display: flex; align-items: center; gap: .75rem; padding: .55rem .85rem .5rem; }
.wb-toolbar-top h1 { margin: 0; font-size: 1rem; white-space: nowrap; }
/* White on the shell's grey. The field used to be an outline on white, which
   on a tinted bar would read as a hole rather than as something to type in. */
.wb-search { flex: 1; max-width: 34rem; display: flex; align-items: center; gap: .5rem; padding: .4rem .8rem; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); color: var(--muted); }
.wb-search input { flex: 1; border: 0; background: none; outline: none; }
.wb-search input:disabled { cursor: not-allowed; }
.wb-filter-button { padding: .4rem 1rem; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); color: var(--muted); cursor: not-allowed; }

.wb-rail { display: flex; flex-direction: column; align-items: center; gap: .8rem; padding: .7rem .35rem; min-height: 0; overflow-y: auto; background: var(--shell); border-right: 1px solid var(--line); }
/* The logo alone. It says 愛料理客服中心 on hover and goes to the inbox; the
   word underneath was a caption for a picture that is already the name. */
.wb-rail-brand { display: flex; align-items: center; justify-content: center; text-decoration: none; }
/* text-decoration: none, which these never had: the base `a` rule underlines
   every link, and on a column of icon-and-label buttons that read as an
   underline under each label. */
.wb-rail-item { display: flex; flex-direction: column; align-items: center; gap: .15rem; width: 2.9rem; padding: .35rem 0; border-radius: 10px; color: var(--muted); text-decoration: none; }
.wb-rail-item i { font-size: 1.1rem; }
.wb-rail-item em { font-style: normal; font-size: .68rem; }
.wb-rail-item.is-active { background: var(--accent-soft); color: var(--accent); }
.wb-rail-me { margin-top: auto; display: flex; flex-direction: column; align-items: center; gap: .4rem; }
.wb-rail-initials { display: flex; align-items: center; justify-content: center; width: 2.1rem; height: 2.1rem; border-radius: 50%; background: var(--surface); border: 1px solid var(--line); font-size: .78rem; font-weight: 600; color: var(--muted); }
.wb-rail-me button { padding: .25rem .45rem; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--muted); font-size: .7rem; cursor: pointer; }

/* Square again. The radius was doing the separating while the shell had no
   border of its own; with the rail and the toolbar ruled, two lines meet at
   this corner and a rounded box inside them leaves a notch of grey in the
   angle rather than a corner. */
.wb-list { background: var(--surface); border-right: 1px solid var(--line); display: flex; flex-direction: column; overflow: hidden; container: wb-list / inline-size; }
.wb-list-head { display: flex; align-items: baseline; gap: .5rem; padding: .3rem .85rem .3rem; }
/* Named for the control that follows it, not visible: the select's own value
   already reads as an order, and a label repeating it would cost a line of a
   column that is short of them. */
.wb-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.wb-list-sort { margin-left: auto; display: flex; align-items: center; gap: .15rem; }
.wb-list-sort select { border: 0; background: none; color: var(--muted); font-size: .78rem; cursor: pointer; }
.wb-list-direction { display: inline-flex; align-items: center; gap: .2rem; padding: .1rem .35rem; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--muted); font-size: .74rem; cursor: pointer; }
.wb-list-direction:hover { border-color: var(--accent-line); color: var(--accent); }
/* Two selects rather than a row of pills: views are mutually exclusive and
   there are eight of them, labels are a separate axis that grows without
   bound, and a flat row could express neither. */
.wb-list-count { margin: 0; color: var(--muted); font-size: .78rem; }
.wb-list-empty { padding: 1rem .85rem; color: var(--muted); font-size: .88rem; }

.wb-items { list-style: none; margin: 0; padding: 0; flex: 1; overflow-y: auto; border-top: 1px solid var(--line); }
/* Warm, because it is a state the agent chose and can leave, not a warning. */
.wb-list-paused { display: flex; align-items: center; gap: .5rem; margin: 0; padding: .35rem .85rem; background: var(--note); border-top: 1px solid var(--note-line); color: #6b4e00; font-size: .76rem; font-weight: 500; }
.wb-list-paused button { margin-left: auto; padding: .1rem .45rem; border: 1px solid var(--note-line); border-radius: 6px; background: var(--surface); color: inherit; font-size: .74rem; cursor: pointer; }
/* The end of the list, which is also what triggers the next page. It keeps a
   height whether or not it says anything, so the observer has something to
   see and the list does not jump as the word appears. */
.wb-list-more { padding: .6rem .85rem 1rem; text-align: center; color: var(--muted); font-size: .78rem; }
/* The card is the row; the link inside it only opens the ticket. The chips are
   buttons of their own and could not live inside it. A link rather than a
   button so ⌘-click, middle-click and the context menu all reach the ticket's
   own address -- and it is styled back to looking like a row, because within
   the page it still behaves like one. */
.wb-row { position: relative; border-bottom: 1px solid var(--line); background: var(--surface); }
/* Over the end of the title rather than in a column of its own: a column would
   be held open on every row for a thing done on one of them, and the disc
   carries its own background so it stays legible over whatever it lands on --
   including the tint of a selected row. */
.wb-item-open {
  position: absolute; top: .4rem; right: .55rem; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  width: 1.4rem; height: 1.4rem; border-radius: 50%;
  background: var(--surface); box-shadow: var(--lift);
  color: var(--muted); font-size: .8rem; line-height: 1; text-decoration: none;
  opacity: 0; transition: opacity .12s ease;
}
.wb-row:hover .wb-item-open, .wb-item-open:focus-visible { opacity: 1; }
.wb-item-open:hover { color: var(--accent); }
.wb-item { display: flex; width: 100%; padding: .65rem .95rem .4rem; border: 0; background: none; text-align: start; cursor: pointer; color: inherit; text-decoration: none; }
/* Warm ground means they are waiting on us -- it replaces the words the count
   already said. Answered rows stay white: greying them put a grey against a
   warm grey, which is harder to tell apart than warm against white. */
.wb-row:hover { background: var(--canvas); }
/* A resolved ticket is still in the list because a filter asked for it, not
   because it wants attention -- so the whole row fades rather than being
   restyled piece by piece. Hover and selection bring it back, since reading a
   closed ticket is the reason it is here at all. */
.wb-row.is-resolved { opacity: .5; }
.wb-row.is-resolved:hover { opacity: .8; }
.wb-row.is-resolved.is-selected { opacity: 1; }
/* No bar down the edge with it: the tone is the whole of what needs saying,
   and the bar was a second mark for the same fact. */
.wb-row.is-selected, .wb-row.is-selected:hover { background: var(--selected); }
/* One letter in a circle, ringed and lettered in the colour of the channel it
   came from, with that channel's mark on its corner. A profile picture is not
   reachable for a Page conversation and Instagram's expire within days, so
   this is as close as we get -- and it answers the question an agent actually
   asks of an avatar, which is which inbox they are standing in.

   --ch is set by the channel class below and read by all three properties, so
   adding a channel is one line rather than four. */
.wb-avatar {
  position: relative; display: flex; align-items: center; justify-content: center; flex: none;
  border-radius: 50%; background: var(--surface);
  border: 1px solid var(--ch, var(--line)); color: var(--ch, var(--muted)); font-weight: 600;
}
.wb-avatar.is-msg { width: 1.9rem; height: 1.9rem; font-size: .8rem; }
.wb-avatar.is-panel { width: 2.2rem; height: 2.2rem; }
/* The channel's colour filled in, with the glyph knocked out of it in white:
   at this size a coloured line on white is a smudge, where a solid disc keeps
   its shape. */
.wb-avatar i {
  position: absolute; right: -3px; bottom: -3px;
  display: flex; align-items: center; justify-content: center;
  width: .9rem; height: .9rem; border-radius: 50%;
  background: var(--ch, var(--muted)); color: var(--surface);
  font-size: .56rem; font-weight: 400;
}
/* The icon font's own rule gives every glyph a 1em box with side margins and
   a line box of its own, none of which a disc this size wants: the margins
   push it off centre horizontally and the line box leaves it riding high.
   Zeroed here so what flex centres is the glyph itself. */
.wb-avatar i::before, .wb-item-open i::before, .wb-view-pin i::before {
  margin: 0; width: auto; line-height: 1;
}
.wb-avatar.is-panel i { width: 1.05rem; height: 1.05rem; font-size: .64rem; }
.wb-avatar.is-web { --ch: var(--ch-web); }
.wb-avatar.is-app { --ch: var(--ch-app); }
.wb-avatar.is-email { --ch: var(--ch-email); }
.wb-avatar.is-facebook { --ch: var(--ch-facebook); }
.wb-avatar.is-instagram { --ch: var(--ch-instagram); }
.wb-avatar.is-line { --ch: var(--ch-line); }

/* One value from a fixed set, so it reads as a state rather than as another
   label. Two characters keeps the column one width; the full word is the title
   attribute, because an abbreviation nobody can expand is a guess. */
.wb-item-status { padding: 0 .25rem; border-radius: 4px; font-size: .68rem; line-height: 1.5; white-space: nowrap; background: var(--canvas); color: var(--muted); }
/* A tint like the rest. Solid accent on white made 新單 the loudest thing on
   the page, on every row that had not been answered yet -- which is most of
   them, most of the time. The unread count is the one strong colour a row
   needs. */
.wb-item-status.is-on_hold { background: var(--note); color: #8a6100; }
.wb-item-status.is-resolved { background: #e8f6ec; color: var(--ok); }

/* Where it came from, beside who it is from. A mark plus a word rather than a
   chip, so it does not read as one of the labels. */
.wb-item-channel { display: inline-flex; align-items: center; gap: .2rem; flex: none; color: var(--muted); font-size: .72rem; font-weight: 500; }
.wb-item-channel i { font-size: .8rem; }
.wb-item-channel em { font-style: normal; }
.wb-item-channel.is-web { color: var(--ch-web); }
.wb-item-channel.is-app { color: var(--ch-app); }
.wb-item-channel.is-email { color: var(--ch-email); }
.wb-item-channel.is-facebook { color: var(--ch-facebook); }
.wb-item-channel.is-instagram { color: var(--ch-instagram); }
.wb-item-channel.is-line { color: var(--ch-line); }
/* Measured against the column, not the window: the column is draggable, so a
   viewport media query would answer the wrong question. */
@container wb-list (max-width: 17rem) {
  .wb-item-channel em { display: none; }
}

/* min-width: 0 is what lets the preview truncate at all. A flex item defaults
   to min-width: auto and so refuses to shrink below its content, which pushes
   the whole row past the edge of the column no matter what the child says
   about ellipsis. Every level of the chain needs it. */
.wb-item-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .1rem; }
/* Name and title on one run of text, allowed to wrap to a second line and
   clamped there. Smaller than it was, because a title that has to say what a
   ticket is about is longer than a name and the point is to read it, not to
   meet its first eight characters. Two lines is the cap: past that the rows
   stop being comparable at a glance. */
.wb-item-head {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow: hidden; min-width: 0; color: var(--ink); font-size: .84rem; line-height: 1.5;
}
/* A rule between them rather than a weight or a colour on one of them: the two
   are equally readable and the divider is the only thing saying where one ends.
   On the title so it cannot wrap to the start of the second line by itself. */
.wb-item-title::before { content: "｜"; color: var(--muted); }
/* No gap: the ・ is the separator, and a gap either side of it reads as a
   gap with a dot in it. */
.wb-item-line { display: flex; align-items: center; min-width: 0; color: var(--muted); font-size: .76rem; }
/* Shrinks rather than grows: the time reads as the end of this line, not as
   something aligned to the far edge of the column. */
.wb-item-preview { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-item-preview + time::before { content: "・"; opacity: .6; }
.wb-item-line time { flex: none; white-space: nowrap; }
.wb-item-line .wb-unread { margin-inline-start: auto; padding-inline: .35rem; }
/* How many messages are waiting, not how many this agent has not read. */
.wb-unread { flex: none; min-width: 1.15rem; padding: 0 .3rem; border-radius: 999px; background: var(--accent); color: #fff; font-size: .7rem; text-align: center; }
/* wrap, and chips that refuse to shrink. Without flex: none a chip gets
   squeezed until its text breaks one character per line, and without wrapping
   the rest run off the edge of the column. */
.wb-item-foot { display: flex; flex-wrap: wrap; align-items: center; gap: .2rem .3rem; padding: 0 .95rem .55rem; }
.wb-chip { flex: none; max-width: 9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: .05rem .45rem; border: 1px solid var(--line); border-radius: 5px; background: none; color: var(--muted); font: inherit; font-size: .74rem; line-height: 1.5; }
button.wb-chip { cursor: pointer; }
button.wb-chip:hover { border-color: var(--accent); color: var(--accent); }
.wb-chip-id { font-weight: 600; }
.wb-chip-channel { display: inline-flex; align-items: center; gap: .2rem; }
.wb-chip-channel i { font-size: .8rem; }
.wb-chip-channel.is-web { color: var(--ch-web); }
.wb-chip-channel.is-app { color: var(--ch-app); }
.wb-chip-channel.is-email { color: var(--ch-email); }
.wb-chip-channel.is-facebook { color: var(--ch-facebook); }
.wb-chip-channel.is-instagram { color: var(--ch-instagram); }
.wb-chip-channel.is-line { color: var(--ch-line); }
/* Same colours the status carried under the avatar, so nothing had to be
   relearned when it moved into the strip. */
/* 待回覆 and 已回覆 are most of the queue, so they are the plain chip: a
   colour that seventy rows share stops being a signal and becomes a texture.
   Only the two that are exceptions are tinted. */
.wb-chip-status.is-on_hold { background: var(--note); color: #8a6100; }
.wb-chip-status.is-resolved { background: #e8f6ec; color: var(--ok); }
.wb-chip-label { border-color: var(--accent-line); background: var(--accent-soft); color: #b3411f; }
/* The number is what agents quote to each other and to customers. */
/* The number, on its own line above the title. Selectable, because an agent
   quoting a ticket to a colleague copies it off this screen. */
.wb-view-id { color: var(--muted); font-weight: 600; }
/* A heading that happens to be a control. It reads as the title until it is
   hovered, which is when the pencil earns its place -- a permanent edit icon on
   every ticket is a button nobody is pressing most of the time. */
.wb-view-title { display: flex; align-items: center; min-width: 0; padding: .1rem .25rem; border: 0; border-radius: 5px; background: none; font: inherit; color: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: text; }
.wb-view-title:hover { background: var(--canvas); }
.wb-view-title i { flex: none; margin-left: .35rem; color: var(--muted); font-size: .8rem; opacity: 0; }
.wb-view-title:hover i { opacity: 1; }
.wb-view-title-edit { flex: 1; min-width: 6rem; padding: .05rem .4rem; border: 1px solid var(--accent-line); border-radius: 5px; font: inherit; background: var(--surface); }
.wb-dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--accent); }

.wb-main { overflow: hidden; display: flex; flex-direction: column; }
.wb-placeholder, .wb-error { padding: 1.1rem; color: var(--muted); }
.wb-error { color: var(--warn); }

.wb-view { display: flex; flex-direction: column; height: 100%; }
/* The three solid blocks float on the thread's own canvas rather than being
   bands welded to its edges. The messages between them keep that canvas, which
   is what the white bubbles are read against -- this is why only these three
   lift and the thread itself does not become a card. */
/* Padded on the right by more than it looks: the panel handle is positioned
   over that strip, and anything in flow would otherwise run underneath it. */
.wb-view-head { position: relative; display: flex; align-items: center; gap: .45rem; margin: var(--gutter) var(--gutter) 0; padding: .6rem 2.2rem .6rem .9rem; border-radius: var(--card); background: var(--surface); box-shadow: var(--lift); }
/* Takes the width, so the title editor can have it. Sized by its content, the
   editor was as wide as the title it replaced -- which for a ticket called
   App 一直閃退 is six characters to write a sentence in. */
.wb-view-who { flex: 1; min-width: 0; }
/* A row, so the editor grows along the line rather than wrapping under it --
   and so a long title is cut with an ellipsis instead of pushing the header
   taller, which would move the thread under it on every other ticket. */
.wb-view-who h2 { display: flex; align-items: center; min-width: 0; margin: 0; font-size: 1rem; }
/* One handle, three places, and always visible. It used to appear on hover,
   on the grounds that a disc parked on every card is clutter for something
   nobody does often -- and the first agents to try it reported both features
   as missing. A control nobody finds is not quieter, it is absent. */
.wb-edge {
  position: absolute; z-index: 6;
  display: flex; align-items: center; justify-content: center;
  width: 1.4rem; height: 1.4rem; border: 0; border-radius: 50%;
  background: none; box-shadow: none; color: var(--muted); cursor: pointer;
  transition: background .15s ease, box-shadow .15s ease, color .15s ease;
}
.wb-edge i { font-size: .68rem; }
/* The disc arrives under the pointer, where it is a target rather than
   decoration. At rest the chevron carries it alone. */
.wb-edge:hover { background: var(--surface); box-shadow: var(--lift); color: var(--accent); }
/* Top right of the panel it closes, in the corner, level with its own first
   heading. */
.wb-edge.is-panel { right: calc(var(--gutter) + .35rem); top: calc(var(--gutter) + .55rem); }
/* And at the far end of the header bar once there is no panel left to sit on,
   which is the only place that can offer it back. */
.wb-edge.is-head { right: .5rem; top: 50%; transform: translateY(-50%); }
/* Inside the card, on the tab strip's line, rather than hung off the corner:
   the corner is where the eye enters the box it is trying to read. */
.wb-edge.is-composer { right: .45rem; top: .4rem; }
/* Indented to match the title's own padding, which is the hover target for
   editing it -- without this the two lines start a few pixels apart. */
.wb-view-who p { margin: 0 0 .1rem; padding-inline-start: .25rem; color: var(--muted); font-size: .76rem; }
.wb-view-notice { margin: var(--gutter) var(--gutter) 0; padding: .5rem .75rem; background: var(--note); border-radius: 8px; }

/* Inset by the gutter, with the padding giving back what the margin took, so
   the messages sit exactly where they did and only the scrollbar moves.
   A scroll container puts its bar at the edge of its own box: left flush, the
   bar stood further right than the header and composer cards it runs between,
   hanging in the gap beside them like a divider somebody forgot to remove. */
.wb-thread {
  list-style: none; flex: 1; overflow-y: auto;
  margin: 0 var(--gutter); padding: .85rem calc(.9rem - var(--gutter));
}
.wb-day { display: flex; align-items: center; gap: .75rem; margin: .35rem 0 .7rem; color: var(--muted); font-size: .8rem; }
.wb-day::before, .wb-day::after { content: ''; flex: 1; height: 1px; background: var(--line); }
.wb-msg { display: flex; gap: .5rem; margin-bottom: .65rem; }
.wb-msg-out { justify-content: flex-end; }
.wb-msg-col { display: flex; flex-direction: column; max-width: 76%; }
.wb-msg-out .wb-msg-col { align-items: flex-end; }
.wb-bubble { padding: .5rem .8rem; border-radius: 12px; white-space: pre-wrap; overflow-wrap: anywhere; }
/* Fill and nothing else. Both of these carried a hairline from when the
   thread was tinted and a white bubble needed an edge to exist at all; on
   paper the fill is the edge, and the line was a second outline drawn just
   inside the first. */
.wb-msg-in .wb-bubble { background: #f3f4f5; }
.wb-msg-out .wb-bubble { background: #eaf2fd; }
.wb-msg-meta { display: flex; gap: .4rem; align-items: center; margin-top: .2rem; color: var(--muted); font-size: .76rem; }
.wb-msg-failed { margin: .3rem 0 0; color: var(--warn); font-size: .82rem; }

/* An internal note must never be mistaken for something the customer saw. */
.wb-note { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; margin-bottom: .65rem; padding: .55rem .8rem; background: var(--note); border: 1px dashed var(--note-line); border-radius: 10px; }
.wb-note-tag { padding: .05rem .45rem; border-radius: 5px; background: var(--note-line); color: #6b4e00; font-size: .74rem; }
.wb-note-body { flex: 1; min-width: 12rem; white-space: pre-wrap; overflow-wrap: anywhere; }
.wb-note-meta { color: var(--muted); font-size: .76rem; }

.wb-composer { position: relative; margin: 0 var(--gutter) var(--gutter); border-radius: var(--card); background: var(--surface); box-shadow: var(--lift); padding: .55rem .9rem .7rem; transition: padding .2s ease; }
/* 0fr to 1fr: a grid row interpolates where `height: auto` does not, so the
   box can collapse to nothing and back without anybody measuring it. */
.wb-composer-body { display: grid; grid-template-rows: 1fr; transition: grid-template-rows .2s ease; }
.wb-composer-body > div { min-height: 0; overflow: hidden; }
.wb-composer.is-folded .wb-composer-body { grid-template-rows: 0fr; }
.wb-composer.is-note { background: var(--note); }
.wb-tabs { display: flex; gap: 1rem; margin-bottom: .4rem; }
.wb-tabs button { padding: .3rem 0; border: 0; border-bottom: 2px solid transparent; background: none; color: var(--muted); cursor: pointer; }
.wb-tabs button.is-active { color: var(--accent); border-bottom-color: var(--accent); }
.wb-composer textarea { width: 100%; padding: .7rem; border: 1px solid var(--line); border-radius: 10px; resize: vertical; background: var(--surface); }
/* One row: what to write with on the left, what sends it on the right. The
   attach button was on a line of its own above this, which made three rows of
   controls under a four-row box. */
.wb-composer-foot { display: flex; align-items: center; gap: .4rem; margin-top: .45rem; }
.wb-composer-foot button[type="submit"] { margin-left: auto; }
/* Where the category came from, said under the select it applies to. */
.wb-panel-note { margin-top: .2rem; color: var(--muted); font-size: .72rem; }
/* Said where the 回覆 tab used to be, so its absence is explained rather than
   looking like something failed to load. */
.wb-composer-closed { margin: 0 0 .5rem; padding: .45rem .6rem; background: var(--canvas); border-radius: 6px; color: var(--muted); font-size: .78rem; }
/* A warning rather than a remark: this one says the send may not work. */
.wb-composer-warn { background: var(--accent-soft); color: var(--warn); }
/* A sentence that contains a thing to do, rather than a button parked under it:
   what to do next is the end of what the sentence was saying. */
.wb-link { padding: 0; border: 0; background: none; font: inherit; color: var(--accent); text-decoration: underline; cursor: pointer; }
.wb-link:disabled { color: var(--muted); cursor: not-allowed; }
/* Folded away, the composer is the tab strip and nothing else, and the thread
   above it gets the height back. */
.wb-composer.is-folded { padding-bottom: .15rem; }
/* The submit button, not every button in the row: this also matched the
   canned-reply toggle beside it and overrode its outlined style, so the
   secondary action was the loudest thing in the composer and the primary one
   next to it looked identical. */
.wb-composer-foot button[type="submit"] { padding: .5rem 1.4rem; border: 0; border-radius: 8px; background: var(--accent); color: #fff; font-weight: 600; cursor: pointer; }
.wb-composer-foot button[type="submit"]:disabled { background: var(--line); color: var(--muted); cursor: not-allowed; }

/* icookfont: the classes come from the vendored stylesheet, these only place
   them. Icons are decorative next to their own labels, so they are hidden from
   assistive technology rather than read out twice. */
[class^="icon-"], [class*=" icon-"] { line-height: 1; }
.cc-send i, .wb-composer-foot button i { font-size: 1rem; }
.cc-empty-icon i { font-size: 2.8rem; }

/* Labels on the open ticket. Adding one is how work is routed to an
   area, which is why it sits above the composer rather than in a menu. */
.wb-labels { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.wb-label { display: inline-flex; align-items: center; gap: .3rem; padding: .12rem .5rem; border: 1px solid var(--accent-line); border-radius: 999px; background: var(--accent-soft); color: #b3411f; font-size: .8rem; }
.wb-label button { border: 0; background: none; color: inherit; cursor: pointer; padding: 0; line-height: 1; opacity: .6; }
.wb-label button:hover { opacity: 1; }
.wb-label-new { padding: .12rem .6rem; border: 1px dashed var(--line); border-radius: 999px; background: none; color: var(--muted); font-size: .8rem; cursor: pointer; }
/* Picking a label. Full width of the panel rather than a pill wedged between
   two buttons: the input is what is being used, and at 15rem there was room
   for about four characters of it. */
.wb-picker { position: relative; width: 100%; }
.wb-picker-input { width: 100%; padding: .25rem .6rem; border: 1px solid var(--accent-line); border-radius: 999px; background: var(--surface); font: inherit; font-size: .8rem; }
.wb-picker-input:focus { outline: 2px solid var(--accent-soft); outline-offset: 1px; border-color: var(--accent); }
/* Ours, not the browser's: a datalist popup is styled by the platform and
   looks like nothing else here. */
.wb-picker-list { position: absolute; z-index: 30; top: calc(100% + .25rem); left: 0; right: 0; list-style: none; margin: 0; padding: .2rem; max-height: 13rem; overflow-y: auto; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 8px 24px rgb(0 0 0 / 12%); }
.wb-picker-option { display: block; width: 100%; padding: .3rem .5rem; border: 0; border-radius: 6px; background: none; color: var(--ink); font: inherit; font-size: .82rem; text-align: start; cursor: pointer; }
.wb-picker-option.is-active { background: var(--accent-soft); color: #b3411f; }
/* Set apart, because it is the one row that changes the taxonomy rather than
   using it. */
.wb-picker-new { margin-top: .2rem; border-top: 1px solid var(--line); border-radius: 0 0 6px 6px; color: var(--muted); }
.wb-picker-new.is-active { color: #b3411f; }

/* One person's whole history, across channels. */
/* Right-hand panel: everything that acts on the ticket, and everything known
   about the person. Beside the ticket rather than above it -- tools
   stacked over the messages interrupt reading them. */
/* No left margin: the gutter beside it is the one the thread's own cards
   already hold open, so the two do not add up to a double-width gap. */
.wb-panel { flex: none; width: calc(var(--detail-col) - var(--gutter)); margin: var(--gutter) var(--gutter) var(--gutter) 0; border-radius: var(--card); background: var(--surface); box-shadow: var(--lift); overflow-y: auto; }
.wb-panel-section { padding: .7rem .8rem; border-bottom: 1px solid var(--line); }
.wb-panel-section h2 { margin: 0 0 .5rem; font-size: .82rem; color: var(--muted); font-weight: 600; letter-spacing: .04em; }
.wb-panel-field { display: flex; flex-direction: column; gap: .2rem; margin-bottom: .55rem; font-size: .78rem; color: var(--muted); }
.wb-panel-field select { padding: .4rem .5rem; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); color: var(--ink); font-size: .9rem; }
/* Status, as three targets rather than a dropdown. An agent sets it dozens of
   times a day and a select costs a click before the options are even visible.
   Vertical because the panel is 15rem wide: three Chinese labels side by side
   read as a toolbar, stacked they read as one choice. */
.wb-seg { display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); overflow: hidden; }
.wb-seg-option { display: flex; align-items: center; gap: .35rem; width: 100%; padding: .34rem .5rem; border: 0; border-bottom: 1px solid var(--line); background: none; color: var(--ink); font: inherit; font-size: .85rem; text-align: start; }
.wb-seg > :last-child { border-bottom: 0; }
.wb-seg-option { cursor: pointer; }
.wb-seg-option:hover:not(:disabled) { background: var(--canvas); }
.wb-seg-option:disabled { cursor: default; }
/* The mark, so the choice is not carried by colour alone. Always laid out, so
   selecting one does not shunt the labels sideways. */
.wb-seg-option::before { content: ""; flex: none; width: .9em; color: currentcolor; }
.wb-seg-option.is-on::before { content: "\2713"; }
.wb-seg-option.is-on { font-weight: 600; }
.wb-seg-option.is-on.is-waiting_us { background: var(--accent-soft); color: #b3411f; }
.wb-seg-option.is-on.is-waiting_them { background: var(--canvas); color: var(--ink); }
.wb-seg-option.is-on.is-on_hold { background: var(--note); color: #8a6100; }
.wb-seg-option.is-on.is-resolved { background: #e8f6ec; color: var(--ok); }
/* 新工單 is a state, not a choice -- replying leaves it on its own -- so it is
   shown as where the ticket is and nothing happens if you press it. */

.wb-panel-who { display: flex; gap: .6rem; align-items: flex-start; margin-bottom: .55rem; }
.wb-panel-badge { display: inline-block; margin-left: .4rem; padding: .05rem .45rem; border-radius: 5px; background: var(--accent-soft); color: #b3411f; font-size: .72rem; }
.wb-panel-badge-guest { background: var(--canvas); color: var(--muted); }
.wb-panel-badge-vip { background: var(--note); color: #8a6100; border: 1px solid var(--note-line); }
.wb-panel-badge-staff { background: #e8f6ec; color: var(--ok); }
/* Quiet, because it qualifies what is above it rather than adding to it. */
.wb-panel-asof { margin: .35rem 0 0; color: var(--muted); font-size: .74rem; }
/* A deleted or locked account explains most of what somebody is writing in
   about, so it is said before anything else in the section rather than as one
   more field among the dates. */
.wb-panel-alarm { margin: 0 0 .5rem; padding: .4rem .6rem; border-radius: 6px; background: var(--accent-soft); border: 1px solid var(--accent-line); color: var(--warn); font-size: .8rem; }
.wb-panel-flags { display: flex; flex-wrap: wrap; gap: .25rem; margin: .1rem 0 .5rem; }
/* Client-supplied strings: a build number or a model wraps rather than
   stretching the column, and it is monospaced so a version is scannable. */
.wb-panel-report-value { text-align: end; word-break: break-word; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .8rem; }
/* Oldest first, so it reads the way they walked it. */
.wb-panel-trail { list-style: none; margin: .1rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: .15rem .3rem; font-size: .78rem; color: var(--ink); }
.wb-panel-trail li + li::before { content: "›"; margin-inline-end: .3rem; color: var(--muted); }
.wb-panel-id { margin: .2rem 0 0; color: var(--muted); font-size: .82rem; word-break: break-all; }
/* On its own line under the address rather than beside it: a Page-scoped id
   runs to sixteen digits and anything sharing its line ends up wrapped into
   the middle of it. */
.wb-panel-list { display: flex; justify-content: space-between; gap: 1rem; margin: 0 0 .4rem; font-size: .85rem; }
.wb-panel-list dt { color: var(--muted); }
.wb-panel-list dd { margin: 0; }
.wb-panel-link { display: inline-block; margin-top: .3rem; padding: 0; border: 0; background: none; color: var(--accent); font-size: .85rem; cursor: pointer; text-decoration: none; }
.wb-panel-pending { margin: .6rem 0 0; padding: .5rem .6rem; background: var(--canvas); border-radius: 8px; color: var(--muted); font-size: .78rem; }
.wb-panel-history { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .1rem; }
.wb-panel-history button { display: flex; flex-direction: column; gap: .1rem; width: 100%; padding: .35rem .4rem; border: 0; border-radius: 6px; background: none; text-align: start; cursor: pointer; }
.wb-panel-history button:hover { background: var(--canvas); }
.wb-panel-history-top { display: flex; align-items: center; gap: .3rem; width: 100%; font-size: .76rem; color: var(--muted); }
.wb-panel-history-id { font-weight: 600; }
.wb-panel-history-top time { margin-left: auto; }
/* What it was about. Truncated rather than wrapped so five of them stay five
   rows -- the section is for recognising a pattern at a glance. */
.wb-panel-history-preview { width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .8rem; }
.wb-panel-history-more { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: .5rem; }
/* A count, quiet, that happens to be the way to the rest of them. In accent
   red it was the loudest thing in the panel while saying the least. */
.wb-panel-history-more .wb-panel-link { margin: 0; padding: .1rem .45rem; border-radius: 999px; background: var(--canvas); color: var(--muted); font-size: .76rem; }
.wb-panel-history-more .wb-panel-link:hover { background: var(--accent-soft); color: var(--accent); }

/* One row per phone. The name leads because that is what somebody on the
   thread says out loud; when it was last used is how an agent tells which of
   two identical models they mean. */
.wb-panel-devices { list-style: none; margin: .4rem 0 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
.wb-panel-devices li { padding: .45rem .55rem; background: var(--canvas); border-radius: 8px; }
.wb-panel-device-name { font-size: .85rem; font-weight: 600; }
.wb-panel-device-seen { color: var(--muted); font-size: .76rem; }
.wb-panel-devices .wb-panel-link:disabled { color: var(--muted); cursor: default; }

/* ══ Settings (server-rendered, under /admin/settings) ════════════════ */
.st { display: grid; grid-template-columns: 14rem minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); min-width: 0; height: 100vh; overflow: hidden; }

.st-rail { padding: 1.25rem 1rem; background: var(--surface); border-right: 1px solid var(--line); overflow-y: auto; }
.st-rail-title { margin: .35rem 0 1rem; font-size: 1.15rem; }
.st-rail-nav { display: flex; flex-direction: column; gap: .15rem; }
.st-rail-link { padding: .5rem .75rem; border-radius: 8px; color: var(--ink); text-decoration: none; font-size: .95rem; }
.st-rail-link:hover { background: var(--canvas); }
.st-rail-link.is-active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.st-rail-divider { margin: .9rem .75rem .15rem; padding-top: .7rem; border-top: 1px solid var(--line); display: block; color: var(--muted); font-size: .74rem; }

.st-main { padding: 2rem 2.5rem 4rem; overflow-y: auto; }
.st-main > * { max-width: 56rem; }
.st-flash { padding: .7rem 1rem; border-radius: 8px; background: #e8f6ec; color: var(--ok); }
.st-flash-alert { background: var(--accent-soft); color: var(--warn); }

.st-head { margin-bottom: 1.75rem; }
.st-head h2 { margin: 0 0 .35rem; font-size: 1.35rem; }
.st-head p { margin: 0 0 1rem; color: var(--muted); }
.st-head code { padding: .1rem .35rem; border-radius: 4px; background: var(--canvas); font-size: .9em; }

.st-form { display: flex; flex-direction: column; gap: 1.25rem; }
.st-field { display: flex; flex-direction: column; gap: .4rem; }
.st-field-inline { flex-direction: row; align-items: center; gap: .5rem; }
.st-label { font-weight: 600; }
.st-input { padding: .6rem .75rem; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }
.st-input-narrow { max-width: 8rem; }
.st-code { padding: .75rem; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; line-height: 1.6; tab-size: 2; }
.st-help { margin: 0; color: var(--muted); font-size: .87rem; }
.st-help code { padding: .05rem .3rem; border-radius: 4px; background: var(--canvas); }
.st-errors { margin: 0; padding: .75rem 1rem .75rem 2rem; border-radius: 8px; background: var(--accent-soft); color: var(--warn); }

.st-actions { display: flex; align-items: center; gap: 1rem; }
.st-submit { padding: .6rem 1.5rem; border: 0; border-radius: 8px; background: var(--accent); color: #fff; font-weight: 600; text-decoration: none; cursor: pointer; }
.st-cancel { color: var(--muted); text-decoration: none; }
/* A second action beside the primary one: same shape, not the same weight. */
.st-submit-plain { background: var(--surface); color: inherit; border: 1px solid var(--line); }
.st-meta { color: var(--muted); font-size: .87rem; }

.st-preview { margin-top: 3rem; }
.st-preview h3 { margin: 0 0 .75rem; font-size: 1.05rem; }
.st-preview-frame { width: 100%; height: 30rem; border: 1px solid var(--line); border-radius: 10px; background: #fff; }

.st-empty { padding: 2rem; border: 1px dashed var(--line); border-radius: 12px; color: var(--muted); text-align: center; }
.st-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); overflow: hidden; }
.st-list-item { display: flex; align-items: center; gap: 1rem; padding: .9rem 1.25rem; border-bottom: 1px solid var(--line); }
.st-list-item:last-child { border-bottom: 0; }
.st-list-item.is-off { background: var(--canvas); }
.st-list-main { flex: 1; min-width: 0; }
.st-list-title { font-weight: 600; }
.st-list-body { margin: .2rem 0 0; color: var(--muted); font-size: .88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.st-list-actions { display: flex; align-items: center; gap: .9rem; font-size: .9rem; }
.st-list-actions form { margin: 0; }
.st-tag { padding: .1rem .5rem; border-radius: 999px; background: var(--canvas); color: var(--muted); font-size: .8rem; }
.st-link-danger { padding: 0; border: 0; background: none; color: var(--warn); font-size: .9rem; cursor: pointer; text-decoration: underline; }

/* Canned replies in the composer */
.wb-canned { position: relative; }
.wb-canned-toggle { padding: .3rem .8rem; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); font-size: .87rem; cursor: pointer; }
.wb-canned-toggle:hover:not(:disabled) { border-color: var(--accent-line); color: var(--accent); }
.wb-canned-menu { position: absolute; bottom: calc(100% + .4rem); left: 0; z-index: 20; width: 24rem; max-height: 20rem; overflow-y: auto; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 8px 24px rgb(0 0 0 / 12%); }
.wb-canned-note { margin: 0; padding: .9rem 1rem; color: var(--muted); font-size: .88rem; }
.wb-canned-item { display: flex; flex-direction: column; gap: .15rem; width: 100%; padding: .65rem 1rem; border: 0; border-bottom: 1px solid var(--line); background: none; text-align: start; cursor: pointer; }
.wb-canned-item:last-child { border-bottom: 0; }
.wb-canned-item:hover { background: var(--accent-soft); }
.wb-canned-title { font-weight: 600; font-size: .9rem; }
.wb-canned-preview { color: var(--muted); font-size: .83rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Members and permissions */
.st-static { margin: 0; color: var(--muted); }
.st-muted { color: var(--muted); }
.st-tag-warn { background: var(--accent-soft); color: #c2410c; }
.st-link-plain { padding: 0; border: 0; background: none; color: var(--accent); font-size: .9rem; cursor: pointer; text-decoration: underline; }

.st-fieldset { margin: 0; padding: 0; border: 0; }
.st-fieldset legend { padding: 0; }
.st-choice { display: flex; align-items: flex-start; gap: .6rem; padding: .7rem .85rem; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); cursor: pointer; }
.st-choice + .st-choice { margin-top: .4rem; }
.st-choice:hover { border-color: var(--accent-line); }
.st-choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.st-choice input { margin-top: .25rem; }
.st-choice-name { display: block; font-weight: 600; }
.st-choice-note { display: block; color: var(--muted); font-size: .87rem; }
/* A one-line choice, for a list of channels rather than a list of roles. */
.st-choice-compact { align-items: center; padding: .45rem .85rem; }
.st-choice-compact input { margin-top: 0; }

.st-note { padding: 1rem 1.25rem; border: 1px solid var(--note-line); border-radius: 10px; background: var(--note); }
.st-note p { margin: 0 0 .5rem; }
.st-note ul { margin: 0; padding-left: 1.25rem; }
.st-note code { font-size: .87rem; }

/* Assignment rules: a page with two things on it, so each says what it is. */
.st-section { margin-bottom: 2.5rem; }
.st-section-title { margin: 0 0 .35rem; font-size: 1.05rem; }
.st-section .st-help { margin-bottom: .9rem; }
.st-inline-form { display: inline-flex; align-items: center; gap: .75rem; margin: 0; }
/* A delete button has to live outside the form it would otherwise submit. */
.st-after-form { margin-top: 1.25rem; }
/* The label picker on a settings form. The chips and the popup are the inbox
   panel's, so only the width differs: a form field, not a narrow column. */
.st-field [data-label-field] { max-width: 28rem; }
.st-input-medium { max-width: 18rem; }
/* Numbered, because the order is the policy: rule 1 beats rule 2. */
.st-list-numbered { counter-reset: st-rule; }
.st-list-numbered .st-list-item::before { counter-increment: st-rule; content: counter(st-rule); flex: none; width: 1.4rem; color: var(--muted); font-size: .85rem; font-variant-numeric: tabular-nums; }

/* ══ Attachments ══════════════════════════════════════════════════════ */
.att-list { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .5rem; }

.att-image { display: block; line-height: 0; border-radius: 10px; overflow: hidden; border: 1px solid var(--line); }
.att-image img { max-width: 220px; max-height: 180px; object-fit: cover; display: block; }

.att-video { max-width: 320px; border-radius: 10px; border: 1px solid var(--line); background: #000; }

.att-card { display: flex; align-items: center; gap: .6rem; max-width: 260px; padding: .6rem .8rem; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); color: var(--ink); text-decoration: none; text-align: start; cursor: pointer; font: inherit; }
.att-card:hover { border-color: var(--accent-line); background: var(--accent-soft); }
.att-card i { font-size: 1.2rem; color: var(--accent); flex: none; }
.att-card-main { min-width: 0; display: flex; flex-direction: column; }
.att-card-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .9rem; }
.att-card-note { color: var(--muted); font-size: .8rem; }
.att-video-load:disabled { opacity: .6; cursor: progress; }

/* The tray of files waiting to be sent */
.up-tray { list-style: none; margin: .5rem 0 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
.up-item { display: flex; align-items: center; gap: .5rem; padding: .35rem .6rem; border: 1px solid var(--line); border-radius: 8px; background: var(--canvas); font-size: .85rem; }
.up-item.is-error { border-color: var(--accent-line); background: var(--accent-soft); }
.up-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-note { color: var(--muted); font-size: .8rem; white-space: nowrap; }
.up-item.is-error .up-note { color: var(--warn); }
.up-item button { border: 0; background: none; color: var(--muted); font-size: 1.1rem; line-height: 1; cursor: pointer; padding: 0 .2rem; }

.up-add { display: inline-flex; align-items: center; gap: .35rem; padding: .3rem .7rem; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); color: var(--muted); font-size: .85rem; cursor: pointer; }
.up-add:hover:not(:disabled) { border-color: var(--accent-line); color: var(--accent); }
.up-add:disabled { opacity: .5; cursor: not-allowed; }

/* A + in a circle, matching the send button across the row. The label was
   13.6px beside a 15px box, which read as an afterthought; the name lives on
   as aria-label and as the title, so it is still announced and still one
   hover away. */
.up-add-compact { width: var(--cc-control, 2.9rem); height: var(--cc-control, 2.9rem); padding: 0; justify-content: center; border-radius: 50%; }
.up-add-compact i { font-size: 1.15rem; }

.cc-uploads { display: flex; flex-direction: column; gap: .4rem; align-items: flex-start; }

.st-tag-ok { background: #e8f6ec; color: var(--ok); }

/* ══ The search box, which is also the filter ═════════════════════════ */
.fb { position: relative; flex: 1; min-width: 0; max-width: 46rem; }

.fb-box { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; padding: .3rem .6rem; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); cursor: text; }
.fb-box:focus-within { border-color: var(--accent); }
.fb-box > i { color: var(--muted); font-size: .95rem; flex: none; }
.fb-box input { flex: 1; min-width: 8rem; padding: .25rem 0; border: 0; background: none; outline: none; font-size: .9rem; }

/* A condition in the box. Labels and people carry the marker everyone already
   reads as tag and person; the rest are distinctive words and need none. */
.fb-token { display: inline-flex; align-items: center; gap: .15rem; padding: .1rem .2rem .1rem .45rem; border-radius: 6px; background: var(--canvas); font-size: .82rem; white-space: nowrap; }
.fb-token button { border: 0; background: none; color: var(--muted); font-size: .95rem; line-height: 1; padding: 0 .2rem; cursor: pointer; }
.fb-token button:hover { color: var(--warn); }
.fb-token-statuses { background: var(--canvas); }
.fb-token-response { background: var(--accent-soft); color: #b3411f; }
.fb-token-labels { background: var(--accent-soft); color: #b3411f; }
.fb-token-assignees { background: #eef2ff; color: #3730a3; }
.fb-token-channels { background: var(--canvas); }

.fb-clear { flex: none; border: 0; background: none; color: var(--muted); font-size: .8rem; cursor: pointer; text-decoration: underline; }
.fb-clear:hover { color: var(--accent); }

.fb-panel { position: absolute; top: calc(100% + .3rem); left: 0; right: 0; z-index: 30; max-height: 24rem; overflow-y: auto; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 10px 30px rgb(0 0 0 / 12%); }

/* A row per dimension: the label, then every option across the line. A single
   vertical list of every option in every dimension reads longer and scans
   worse than the same options grouped on their own row. */
.fb-row { display: flex; align-items: center; gap: 1rem; padding: .5rem .9rem; border-bottom: 1px solid var(--line); }
.fb-row-clear { flex: none; margin-left: auto; width: 1.3rem; height: 1.3rem; border: 0; border-radius: 5px; background: none; color: var(--muted); font-size: 1rem; line-height: 1; cursor: pointer; }
.fb-row-clear:hover { background: var(--accent-soft); color: var(--accent); }
.fb-more { padding: .3rem .6rem; border: 1px dashed var(--line); border-radius: 8px; background: none; color: var(--muted); font-size: .82rem; cursor: pointer; }
.fb-more:hover { border-color: var(--accent-line); color: var(--accent); }
.fb-row:last-of-type { border-bottom: 0; }
.fb-row-title { flex: none; width: 3.5rem; color: var(--muted); font-size: .8rem; }
.fb-option-count { margin-left: .35rem; color: var(--muted); font-style: normal; font-size: .8em; }
.fb-option.is-on .fb-option-count { color: inherit; opacity: .7; }
.fb-row-options { display: flex; flex-wrap: wrap; gap: .2rem; min-width: 0; }

/* The whole option is the target, and the chosen one is a filled pill --
   which is what says "on" from across the room, before anyone reads the tick. */
.fb-option { display: inline-flex; align-items: center; gap: .4rem; padding: .3rem .6rem; border-radius: 8px; font-size: .87rem; cursor: pointer; white-space: nowrap; }
.fb-option:hover { background: var(--canvas); }
.fb-option.is-on { background: var(--accent-soft); color: #b3411f; }

/* The browser's own checkbox cannot be recoloured, so it is replaced. Drawn
   rather than an image: one less asset to load and it inherits the accent. */
.fb-option input { appearance: none; -webkit-appearance: none; position: relative; flex: none; width: 1.05rem; height: 1.05rem; margin: 0; border: 1.5px solid var(--line); border-radius: 5px; background: var(--surface); cursor: pointer; }
.fb-option input:checked { border-color: var(--accent); background: var(--accent); }
.fb-option input:checked::after { content: ''; position: absolute; left: 50%; top: 50%; width: .27rem; height: .5rem; border: solid #fff; border-width: 0 2px 2px 0; transform: translate(-50%, -60%) rotate(45deg); }
.fb-option input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.fb-panel-foot { margin: 0; padding: .5rem .9rem; border-top: 1px solid var(--line); color: var(--muted); font-size: .8rem; }


/* ══ API docs ═════════════════════════════════════════════════════════ */
/* Scalar paints its own full-height page, so this is only the strip above it
   that gets you back and hands you the file. */
.doc-page { display: flex; flex-direction: column; height: 100vh; }
.doc-head { display: flex; align-items: center; gap: 1rem; padding: .5rem .9rem; border-bottom: 1px solid var(--line); background: var(--surface); font-size: .85rem; }
.doc-raw { margin-left: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* ══ Help centre admin ════════════════════════════════════════════════ */
/* Grouped by section, because position is per section: a flat list would make
   the order look arbitrary. */
.hca-group { margin-bottom: 2rem; }
.hca-group-title { margin: 0 0 .5rem; color: var(--muted); font-size: .85rem; font-weight: 600; }

/* Up and down rather than drag-and-drop: it works without JavaScript, it works
   with a keyboard, and eighty articles get reordered a few times a year. */
.hca-move-form { display: inline; }
.hca-move { padding: .1rem .45rem; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--muted); cursor: pointer; }
.hca-move:hover { border-color: var(--accent-line); color: var(--accent); }
.hca-publish { padding: .15rem .6rem; border: 1px solid var(--accent-line); border-radius: 6px; background: var(--accent-soft); color: var(--accent); font-size: .85rem; cursor: pointer; }

.hca-sections { list-style: none; margin: .5rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: .3rem .75rem; font-size: .85rem; }
.hca-sections li { display: inline-flex; align-items: center; gap: .3rem; }
.hca-count { padding: 0 .35rem; border-radius: 999px; background: var(--canvas); color: var(--muted); font-size: .74rem; }

/* Bordered, because it is the article as the public will see it rather than
   part of the form around it. */
.hca-preview { margin-top: .75rem; padding: 1.25rem; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); }

.hca-danger { padding: .5rem 1.2rem; border: 1px solid var(--warn); border-radius: 8px; background: var(--surface); color: var(--warn); cursor: pointer; }
.hca-danger:hover { background: var(--accent-soft); }

/* The queue by status: four lists in the one column, each with a heading that
   says how many there are and a button that asks for more of them. No infinite
   scroll here -- a section that grew as you passed it would push the sections
   below out of reach, which is the one thing the layout exists to prevent. */
/* Positioned, so a band's offsetTop is measured from the column -- which is
   what the jump between sections scrolls by. */
.wb-sections { --band-h: 2.15rem; position: relative; flex: 1; overflow-y: auto; }
/* display: contents, so the bands inside become children of the scrolling
   column itself. A sticky box only sticks while its own containing block is on
   screen: left wrapped in a <section>, each band would let go the moment its
   section scrolled past, which is the one thing the stack is for. */
.wb-section { display: contents; }
/* A gap rather than a rule between sections: a line says "and here is more of
   the same", where a gap says the list has finished and another has begun.
   On the last thing in the section, since the section itself has no box. */
.wb-section > :last-child { margin-bottom: .7rem; }

.wb-section-anchor { height: 0; }
/* All four bands are on screen at every scroll position: --i of them have been
   passed and stack at the top in order, --j have not been reached and stack at
   the bottom. So the shape of the queue is readable from anywhere in it, and
   any section is one press away.
   Both offsets on one box is what does it -- a sticky box with a top and a
   bottom is clamped into the band between them, which is the slot this one
   owns. It needs the four bands to fit the column, which at 2.15rem they do
   several times over.
   Ruled on both edges, which is what makes it a band across the column rather
   than a row that happens to be tinted. */
.wb-section-head {
  position: sticky;
  top: calc(var(--i) * var(--band-h));
  bottom: calc(var(--j) * var(--band-h));
  z-index: 3;
  display: flex; align-items: center; height: var(--band-h);
  margin: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: var(--band); font-size: .78rem; font-weight: 600;
}
/* Two controls, because there are two things to do here. The chevron folds;
   the rest of the band goes there. One target doing both would have to guess
   which was meant. */
.wb-section-fold {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 2rem; align-self: stretch; padding: 0; border: 0; background: none;
  color: var(--muted); font-size: .7rem; cursor: pointer;
}
.wb-section-jump {
  flex: 1; display: flex; align-items: center; gap: .4rem; min-width: 0;
  align-self: stretch; padding: 0 .95rem 0 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: start; cursor: pointer;
}
.wb-section-fold:hover, .wb-section-jump:hover { background: var(--canvas); }
/* Quiet, and at the far end of the band: it undoes everything in the list
   below it, so it should not sit under the thumb on the way to the chevron. */
.wb-section-clear {
  flex: none; align-self: stretch; padding: 0 .95rem; border: 0; background: none;
  color: var(--muted); font: inherit; font-size: .72rem; font-weight: 400; cursor: pointer;
}
.wb-section-clear:hover { background: var(--canvas); color: var(--warn); }
.wb-section-name { color: var(--ink); }
.wb-section-count { color: var(--muted); font-weight: 400; }
.wb-section-empty { margin: 0; padding: .5rem .95rem; color: var(--muted); font-size: .78rem; }
.wb-items.is-section { flex: none; overflow: visible; border-top: 0; }
.wb-section-more {
  display: block; width: 100%; padding: .5rem .95rem; border: 0;
  background: none; color: var(--accent); font: inherit; font-size: .8rem; cursor: pointer;
}
.wb-section-more:hover { background: var(--canvas); }
.wb-section-more:disabled { color: var(--muted); cursor: default; }

/* Found, then confirmed. The target is named on screen before the button
   means anything -- a bare number typed into a box merges without the agent
   ever seeing what they merged into, and a mistyped digit is a real ticket. */
.wb-panel-merge { display: flex; flex-direction: column; gap: .35rem; }
.wb-panel-merge input {
  width: 100%; padding: .25rem .45rem;
  border: 1px solid var(--line); border-radius: 6px; background: var(--surface);
}
.wb-panel-merge .wb-panel-note { margin: 0; }
.wb-panel-merge-picked {
  display: flex; align-items: baseline; gap: .35rem; margin: 0;
  padding: .35rem .5rem; border-radius: 6px; background: var(--canvas); font-size: .8rem;
}
.wb-panel-merge-confirm { display: flex; align-items: center; gap: .8rem; }

/* The actions a section has that are not part of reading it. Folded behind a
   ⋯ because merging is done once in a hundred tickets, and a control for it
   open on every one of them is a question nobody was asking. */
.wb-panel-section h2 { display: flex; align-items: center; }
.wb-panel-menu { position: relative; margin-left: auto; }
.wb-panel-menu-open {
  display: flex; align-items: center; justify-content: center;
  width: 1.4rem; height: 1.4rem; padding: 0; border: 0; border-radius: 5px;
  background: none; color: var(--muted); font-size: .8rem; cursor: pointer;
}
.wb-panel-menu-open:hover, .wb-panel-menu-open[aria-expanded="true"] {
  background: var(--canvas); color: var(--ink);
}
.wb-panel-menu-list {
  position: absolute; right: 0; top: 100%; z-index: 8;
  min-width: 8rem; margin: .2rem 0 0; padding: .2rem; list-style: none;
  border-radius: 8px; background: var(--surface); box-shadow: var(--lift);
}
.wb-panel-menu-list button {
  display: block; width: 100%; padding: .35rem .5rem; border: 0; border-radius: 5px;
  background: none; font: inherit; font-size: .8rem; color: inherit;
  text-align: start; white-space: nowrap; cursor: pointer;
}
.wb-panel-menu-list button:hover { background: var(--canvas); }

/* A real <dialog>: Escape, the focus trap and the backdrop are the browser's,
   which is three things not to get subtly wrong. */
.wb-dialog {
  width: min(26rem, calc(100vw - 2rem)); padding: 0;
  border: 0; border-radius: 12px; background: var(--surface); color: var(--ink);
  box-shadow: 0 10px 40px rgba(42, 40, 38, .18);
}
.wb-dialog::backdrop { background: rgba(42, 40, 38, .3); }
.wb-dialog > header {
  display: flex; align-items: center; gap: .5rem;
  padding: .7rem .9rem; border-bottom: 1px solid var(--line);
}
.wb-dialog > header h2 { margin: 0; font-size: .9rem; }
.wb-dialog > header button {
  margin-left: auto; display: flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; padding: 0; border: 0; border-radius: 6px;
  background: none; color: var(--muted); cursor: pointer;
}
.wb-dialog > header button:hover { background: var(--canvas); color: var(--ink); }
.wb-dialog-body { display: flex; flex-direction: column; gap: .4rem; padding: .8rem .9rem 1rem; }
.wb-dialog-body input {
  width: 100%; padding: .35rem .5rem;
  border: 1px solid var(--line); border-radius: 6px; background: var(--surface);
}
/* The list has room to be a list here, where in the panel it was five rows
   squeezed under everything else. */
.wb-dialog-body .wb-panel-history { max-height: 17rem; overflow-y: auto; }

/* What the agent is holding on to, above the queue and outside its scroller.
   These stay whatever the filter matched, so scrolling past them would be
   scrolling past the things pinned so as not to go looking for them. */
.wb-pinned { display: flex; flex-direction: column; min-height: 0; max-height: 40vh; }
/* Not sticky here: this band is already above everything that scrolls. */
.wb-pinned .wb-section-head { position: static; }
.wb-section-jump.is-static { cursor: default; }
.wb-section-jump.is-static:hover { background: none; }

/* Tabs down the side rather than queue rows. A row describes a ticket -- who,
   what, when, which channel, how many waiting -- which is the right amount to
   read when choosing among four hundred. These are already chosen: a number
   and a name is what you need to come back to one. */
.wb-pins {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  list-style: none; margin: 0; padding: .4rem .55rem .5rem;
  display: flex; flex-direction: column; gap: .3rem;
  border-bottom: 1px solid var(--line);
  scrollbar-width: thin; scrollbar-color: rgba(42, 40, 38, .22) transparent;
}
.wb-pin {
  position: relative; display: flex; align-items: center;
  border: 1px solid var(--line); border-radius: 8px; background: var(--surface);
}
.wb-pin:hover { border-color: var(--accent-line); }
/* The one being read is the tab you are on. */
.wb-pin.is-selected {
  border-color: var(--selected-tab); background: var(--selected-tab); color: var(--surface);
}
.wb-pin.is-selected:hover { border-color: var(--selected-tab); }
.wb-pin.is-selected .wb-pin-id,
.wb-pin.is-selected .wb-pin-off { color: rgba(255, 255, 255, .62); }
.wb-pin.is-selected .wb-pin-off:hover { color: var(--surface); }
.wb-pin-open {
  flex: 1; min-width: 0; display: flex; align-items: baseline; gap: .4rem;
  padding: .4rem .2rem .4rem .6rem; color: inherit; text-decoration: none;
}
.wb-pin-id { flex: none; color: var(--muted); font-size: .76rem; font-weight: 600; }
.wb-pin-title {
  flex: 1; min-width: 0; font-size: .82rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wb-pin .wb-unread { align-self: center; }
.wb-pin-off {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 1.6rem; align-self: stretch; padding: 0; border: 0; border-radius: 0 8px 8px 0;
  background: none; color: var(--muted); font-size: .7rem; cursor: pointer;
}
.wb-pin-off:hover { color: var(--accent); }

/* In the header, one press. Parking the thing you are reading is done often
   enough that two steps would be two steps every time. */
/* Quieter than the pin beside it: leaving a ticket is ordinary, and a second
   boxed control at the head of the card would make the card read as a
   toolbar. */
.wb-view-close {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 1.8rem; height: 1.8rem; padding: 0; border: 0; border-radius: 7px;
  background: none; color: var(--muted); font-size: .85rem; cursor: pointer;
}
.wb-view-close:hover { background: var(--canvas); color: var(--ink); }
.wb-view-close i::before { margin: 0; width: auto; line-height: 1; }

.wb-view-pin {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; padding: 0;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--muted); cursor: pointer;
}
/* The font's pin comes tilted already, which is how a pin pushed into
   something sits -- upright it reads as a chess piece. */
.wb-view-pin { font-size: 1.15rem; }
.wb-view-pin:hover { background: var(--canvas); color: var(--ink); }
.wb-view-pin.is-on {
  border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent);
}

/* The password form on the sign-in page. Below the Google button and under a
   quieter heading, so the two read as the usual way and the exception rather
   than as two equal choices -- but visible, because somebody who was handed
   credentials should not have to look for where to type them. */
.auth-password { margin-top: 1.5rem; border-top: 1px solid var(--line); padding-top: 1rem; }
.auth-password h2 { margin: 0; color: var(--muted); font-size: .9em; font-weight: 600; }
.auth-form { margin-top: 1rem; }
.auth-form .st-field { margin-bottom: .75rem; }
/* Beats `.auth button`, which styles the Google button as the plain one. */
.auth-form .st-submit { border: 0; background: var(--accent); color: #fff; }
