/* ============================================================================
   admin.css - layout and components for the read-only PC admin.

   Structure follows docs/design-refs/ima-PC后台原型/index.html (nav 248px, three views);
   values follow docs/design-refs/效果图/B-PC后台与图标集-效果图-总览.png.

   COLOURS: every colour in this file is `var(--bbwy-*)`. There is no literal colour value
   here on purpose - tokens.css is the only file allowed to hold one.
   ============================================================================ */

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bbwy-bg);
  color: var(--bbwy-text);
  font: 14px/1.6 -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, sans-serif;
}
button { font: inherit; cursor: pointer; }
b { font-weight: 700; color: var(--bbwy-accent); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------- the `hidden` attribute (MG-7AF)

   THE `hidden` ATTRIBUTE IS ONLY AS STRONG AS NOBODY OVERRIDES `display`.
   ----------------------------------------------------------------------
   `[hidden]` is a UA rule (`display: none`), so it loses to ANY author rule that sets
   `display` on the same element. Every component below sets `display`, and the console
   hides things with `.hidden = true` - a combination that silently keeps the box on
   screen. That is exactly how the operator's menu stayed at eleven items in the MG-7A
   acceptance run: the six platform entries DID carry `hidden`, and `getComputedStyle`
   still read `display: flex` because `.nav__item` (line ~62) won.

   SO EVERY SELECTOR THAT BOTH SETS `display` AND IS TOGGLED BY `hidden` GETS AN EXPLICIT
   `[hidden]` RULE HERE. This is the same shape the file already used for `.view` and
   `.loginpage`; the three below were the ones missing it. The list was not guessed: it is
   the intersection of "selectors with a `display` declaration" and "elements that
   `index.html` marks `hidden` or `app.js` sets `.hidden` on", and the acceptance suite
   reads the RESULT (`getComputedStyle().display`) rather than the attribute, so a future
   component that forgets its rule fails there instead of shipping. */
.nav__item[hidden] { display: none; }
.chip[hidden] { display: none; }
.btn[hidden] { display: none; }
.context[hidden] { display: none; }
.loginpage__scan[hidden] { display: none; }

/* ---------------------------------------------------------------- layout */
.app { display: flex; min-height: 100vh; }

/* The mockup measures the sidebar at 248px and the console was approved at that width;
   the value is repeated in the report's view-by-view comparison table. */
.side {
  width: 248px; flex: none; background: var(--bbwy-card);
  border-right: 1px solid var(--bbwy-border);
  display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh;
}
.brand { display: flex; align-items: center; gap: 12px; padding: 22px 20px; }
.brand__mark { width: 40px; height: 40px; flex: none; }
.brand__name { font-size: 16px; font-weight: 700; letter-spacing: .02em; }
.brand__sub { font-size: 12px; color: var(--bbwy-text-weak); }

.nav { padding: 8px 12px; flex: 1; overflow-y: auto; }
.nav__label { padding: 12px 10px 6px; font-size: 11px; color: var(--bbwy-text-weak); letter-spacing: .08em; }
/* MG-2F: the nav is grouped into three titled sections. The group title reuses the weak-text
   label vocabulary; the group is just a spacer + title, no new colour or font. */
.nav__group { margin-top: 10px; }
.nav__group:first-child { margin-top: 0; }
.nav__group-title {
  padding: 6px 10px 4px; font-size: 11px; color: var(--bbwy-text-weak);
  letter-spacing: .08em; text-transform: none;
}
.nav__item {
  display: flex; align-items: center; gap: 10px; padding: 11px 12px; margin-bottom: 4px;
  border-radius: 12px; color: var(--bbwy-text-weak); font-weight: 500; cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.nav__item:hover { background: var(--bbwy-bg); }
.nav__item.on { background: var(--bbwy-primary-soft); color: var(--bbwy-on-primary); font-weight: 600; }
.nav__ic { display: inline-flex; width: 22px; height: 22px; flex: none; }
.nav__body { display: flex; flex-direction: column; min-width: 0; }
.nav__name { line-height: 1.35; }
/* MG-2FF: a missing prerequisite shows WHAT is missing as a second, smaller line (`.nav__hint`),
   but never greys the item out - navigation stays clickable, so "从这里开始" stays reachable. */
.nav__hint { font-size: 11px; font-weight: 400; color: var(--bbwy-text-weak); line-height: 1.4; white-space: normal; }
.side__foot { padding: 16px 20px; border-top: 1px solid var(--bbwy-border); font-size: 12px; color: var(--bbwy-text-weak); }
/* MG-2F: the fixed footer 使用指引 entry. It lives OUTSIDE the scrollable .nav, so it stays
   pinned at the bottom; it is not a .nav__item, so the role-scope filter never touches it. */
.guide-link {
  display: inline-flex; align-items: center; height: 30px; padding: 0 14px; margin-bottom: 10px;
  border: 1px solid var(--bbwy-border); border-radius: 999px; background: var(--bbwy-bg);
  color: var(--bbwy-text); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.guide-link:hover { border-color: var(--bbwy-primary); color: var(--bbwy-primary); }
.guide-link.on { background: var(--bbwy-primary-soft); color: var(--bbwy-on-primary); border-color: var(--bbwy-primary-soft); }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.top {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 18px 32px; background: var(--bbwy-card);
  border-bottom: 1px solid var(--bbwy-border); position: sticky; top: 0; z-index: 5;
}
.top__title { font-size: 18px; font-weight: 600; }
.top__crumb { font-size: 12px; color: var(--bbwy-text-weak); }
.top__right { display: flex; align-items: center; gap: 12px; }

/* ---------------------------------------------------------------- context bar */
.context {
  display: flex; align-items: center; gap: 12px; padding: 14px 32px 0;
}
.context__label { font-size: 12px; color: var(--bbwy-text-weak); }
.context__note { font-size: 12px; color: var(--bbwy-text-weak); }
.select {
  height: 34px; min-width: 280px; padding: 0 12px; border-radius: 10px;
  border: 1px solid var(--bbwy-border); background: var(--bbwy-card); color: var(--bbwy-text);
}
.select:focus { outline: 2px solid var(--bbwy-primary-soft); outline-offset: 1px; border-color: var(--bbwy-primary); }

/* ---------------------------------------------------------------- alert */
.alert {
  margin: 14px 32px 0; padding: 14px 18px; border-radius: 12px;
  background: var(--bbwy-primary-soft); border: 1px solid var(--bbwy-primary);
  color: var(--bbwy-on-primary); font-size: 13px;
}
.alert__title { font-weight: 700; margin-bottom: 4px; }
.alert__fix { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }

/* ---------------------------------------------------------------- cards */
.view { padding: 20px 32px 64px; max-width: 1180px; }
.view[hidden] { display: none; }
.card {
  background: var(--bbwy-card); border-radius: 16px; box-shadow: var(--bbwy-shadow);
  padding: 22px 24px; margin-bottom: 16px;
}
.card__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.card__title { font-size: 16px; font-weight: 600; }
.card__hint { margin-top: 8px; font-size: 13px; color: var(--bbwy-text-weak); }

.chip {
  display: inline-flex; align-items: center; height: 26px; padding: 0 12px; border-radius: 999px;
  font-size: 12px; background: var(--bbwy-primary-soft); color: var(--bbwy-text-weak); white-space: nowrap;
}
.chip--solid { background: var(--bbwy-primary); color: var(--bbwy-on-primary); font-weight: 600; }
.chip--accent { background: var(--bbwy-accent); color: var(--bbwy-card); font-weight: 600; }
.chip--on { background: var(--bbwy-primary-soft); color: var(--bbwy-primary); font-weight: 600; }
.chip--muted { background: var(--bbwy-bg); color: var(--bbwy-text-weak); border: 1px solid var(--bbwy-border); }

/* ---------------------------------------------------------------- table */
.scroller { overflow-x: auto; }
.tbl { width: 100%; border-collapse: collapse; margin-top: 14px; }
.tbl th, .tbl td { padding: 11px 12px; text-align: left; border-bottom: 1px solid var(--bbwy-border); font-size: 13px; }
.tbl th { font-size: 12px; font-weight: 600; color: var(--bbwy-text-weak); background: var(--bbwy-bg); white-space: nowrap; }
.tbl tr:last-child td { border-bottom: 0; }
.tbl tbody tr:hover { background: var(--bbwy-bg); }
.tbl .cell-key { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; color: var(--bbwy-text-weak); word-break: break-all; }
.tbl .cell-name { font-weight: 600; }
.tbl .cell-weak { color: var(--bbwy-text-weak); white-space: nowrap; }
.tbl .cell-num { font-weight: 700; color: var(--bbwy-accent); font-variant-numeric: tabular-nums; }

.status { display: inline-flex; align-items: center; height: 24px; padding: 0 10px; border-radius: 999px; font-size: 12px; background: var(--bbwy-bg); color: var(--bbwy-text-weak); border: 1px solid var(--bbwy-border); }
.status--running { background: var(--bbwy-primary-soft); color: var(--bbwy-primary); border-color: var(--bbwy-primary-soft); font-weight: 600; }
.status--ended { background: var(--bbwy-bg); color: var(--bbwy-text-weak); }
.status--published { background: var(--bbwy-primary-soft); color: var(--bbwy-accent); border-color: var(--bbwy-primary-soft); font-weight: 600; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 30px; padding: 0 14px; border-radius: 999px; border: 0; font-size: 13px; font-weight: 600;
  background: var(--bbwy-primary-soft); color: var(--bbwy-primary);
}
.btn--primary { background: var(--bbwy-primary); color: var(--bbwy-on-primary); }
.btn--off { opacity: .5; cursor: not-allowed; }

/* ---------------------------------------------------------------- the edit cell (W-1) */
/* The input keeps the value VERBATIM and is narrow on purpose: a wide box invites editing
   the neighbours, and one save carries exactly one key. */
.editcell { display: inline-flex; align-items: center; gap: 8px; }
.editcell__input {
  width: 96px; height: 30px; padding: 0 10px; border-radius: 8px; font-size: 13px;
  border: 1px solid var(--bbwy-border); background: var(--bbwy-card); color: var(--bbwy-text);
  font-variant-numeric: tabular-nums;
}
.editcell__input:focus { outline: 2px solid var(--bbwy-primary-soft); border-color: var(--bbwy-primary); }
.btn--save { background: var(--bbwy-primary); color: var(--bbwy-on-primary); }
/* S-1: the 退出登录 control. Deliberately NOT `--primary`: it is a way out, not the
   action this screen wants you to take, and a filled orange button beside the identity
   chip read as the main call to action. */
.btn--quiet { background: var(--bbwy-bg); color: var(--bbwy-text-weak); border: 1px solid var(--bbwy-border); }
.btn--quiet:hover { color: var(--bbwy-text); }
.editcell__note { font-size: 12px; white-space: nowrap; }
.editcell__note--saving { color: var(--bbwy-text-weak); }
.editcell__note--ok { color: var(--bbwy-primary); font-weight: 600; }
/* The failure text is allowed to wrap: it carries the kernel's own sentence, and truncating
   that is how a diagnosable refusal turns back into "operation failed".
   The colour is the DECLARED `--bbwy-danger` token (S-1 declared it in tokens.css). It used
   to be written here as an inline fallback, which put a colour literal outside the one file
   allowed to hold one and kept `curve-static-assets` red from W-1 onward. A fallback that
   silently covers a missing token is how the token stayed undeclared for two cards. */
.editcell__note--fail { color: var(--bbwy-danger); max-width: 320px; white-space: normal; }
/* The shop rows (W-2) pack two fields into one cell, so each gets its own short label.
   The fields are numbers the kernel counts with (weight, stock, price), hence the narrow
   variant - a box wide enough for prose is a box that invites the wrong edit. */
.editcell__lbl { font-size: 12px; color: var(--bbwy-text-weak); }
.editcell__input--n { width: 64px; }

/* MG-8B: the ship form lives in the 运单/发货 cell of the 订单 view. The three controls sit
   inline with a small gap; the carrier select is narrow on purpose - it is a fixed vocabulary,
   not prose - and the tracking input keeps the operator's text verbatim. */
.shipform { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.select--xs { min-width: 0; height: 30px; padding: 0 8px; font-size: 12px; }

/* ---------------------------------------------- S-4: the four-part guidance (ruling 216)

   WHERE THIS APPEARS, AND WHY IT IS NOT A TOOLTIP
   -----------------------------------------------
   "操作人员才能知道做这个事情干嘛" - the operator has to be able to READ the four parts, not
   discover them by hovering. A tooltip is invisible in a screenshot, invisible to a screen
   reader, and gone the moment the pointer moves; none of those is acceptable for the one thing
   the ruling describes as the difference between a console someone dares to click and one they
   do not. So the guidance is a block of text that stays on screen.

   It is COLLAPSED BY DEFAULT and opened by the operator (or by the browser judgement, which
   opens every one and counts the rendered paragraphs). Collapsed-by-default keeps a 59-key page
   readable; the summary line still answers "this row has guidance" so nothing is hidden.

   A PART THE SOURCE DECLARES EMPTY RENDERS AS NOTHING AT ALL - no third paragraph, no
   placeholder. `.guidepart` is only emitted for a part that has text, so "declared empty" and
   "not rendered" are the same thing on screen while remaining distinguishable in the payload. */
.guide { margin-top: 10px; border: 1px solid var(--bbwy-border); border-radius: 12px; background: var(--bbwy-bg); }
.guide__head {
  display: flex; align-items: center; gap: 8px; padding: 9px 12px; cursor: pointer;
  font-size: 12px; font-weight: 600; color: var(--bbwy-accent);
}
.guide__caret { width: 7px; height: 7px; flex: none; border-right: 2px solid var(--bbwy-accent); border-bottom: 2px solid var(--bbwy-accent); transform: rotate(45deg); transition: transform .2s ease; }
.guide.open .guide__caret { transform: rotate(-45deg); }
.guide__count { margin-left: auto; font-weight: 400; color: var(--bbwy-text-weak); }
.guide__body { padding: 0 12px 12px; display: none; }
.guide.open .guide__body { display: block; }
.guidepart { display: flex; gap: 8px; padding: 5px 0; font-size: 12px; line-height: 1.7; }
.guidepart__no {
  flex: none; width: 18px; height: 18px; margin-top: 2px; border-radius: 50%;
  background: var(--bbwy-primary-soft); color: var(--bbwy-on-primary);
  font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center;
}
.guidepart__t { flex: none; width: 84px; color: var(--bbwy-text-weak); }
.guidepart__b { color: var(--bbwy-text); }
/* The page-level three sentences (ruling 216 section four) sit under the card head, above the
   table they explain. Same block, same reader, one vocabulary.
   MG-13A: the three sentences collapse to a one-line summary by default (a native <details>),
   so an empty database does not leave a wall of prose above the work area. */
.pageguide { margin-top: 12px; }
/* A page with no guidance entry (e.g. the MG-7A admins page) leaves its host empty; an empty
   box with a background would read as a broken strip, so an empty guide renders as nothing. */
.pageguide:empty { display: none; }
.pageguide__fold { border-radius: 12px; background: var(--bbwy-bg); border: 1px solid var(--bbwy-border); }
.pageguide__summary {
  display: flex; align-items: center; gap: 8px; padding: 12px 14px; cursor: pointer;
  font-size: 13px; font-weight: 600; color: var(--bbwy-accent); list-style: none;
}
.pageguide__summary::-webkit-details-marker { display: none; }
.pageguide__caret {
  width: 7px; height: 7px; flex: none; margin-left: auto;
  border-right: 2px solid var(--bbwy-accent); border-bottom: 2px solid var(--bbwy-accent);
  transform: rotate(45deg); transition: transform .2s ease;
}
.pageguide__fold[open] .pageguide__caret { transform: rotate(-45deg); }
.pageguide__body { padding: 0 14px 14px; display: none; }
.pageguide__fold[open] .pageguide__body { display: block; }
.pageguide__t { font-size: 12px; font-weight: 600; color: var(--bbwy-accent); margin-bottom: 4px; }
.pageguide__b { font-size: 13px; line-height: 1.75; color: var(--bbwy-text-weak); }
/* MG-13A: the one-sentence relationship note under a config row's label (the look-alike keys). */
.cell-relation { font-weight: 400; margin-top: 2px; max-width: 300px; line-height: 1.5; }
/* The guidance panel inside a config row: the row's cell is narrow, so its guide stacks. */
.cell-guide { max-width: 420px; }
.cell-guide .guidepart__t { width: 72px; }

/* ---------------------------------------------------------------- entries */
.entries { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin-top: 14px; }
.entry { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border: 1px solid var(--bbwy-border); border-radius: 12px; }
.entry__ic { width: 32px; height: 32px; flex: none; }
.entry__name { font-weight: 600; font-size: 13px; }
.entry__act { font-size: 12px; color: var(--bbwy-text-weak); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* ---------------------------------------------------------------- config */
.scroller--chips { margin-top: 14px; padding-bottom: 4px; }
.chips { display: flex; gap: 8px; flex-wrap: wrap; }
.chips .chip { cursor: pointer; }
.groups { display: flex; flex-direction: column; gap: 12px; }
.group { background: var(--bbwy-card); border-radius: 16px; box-shadow: var(--bbwy-shadow); overflow: hidden; }
.group__head { display: flex; align-items: center; gap: 12px; padding: 14px 20px; cursor: pointer; }
.group__head:hover { background: var(--bbwy-bg); }
.group__caret { width: 9px; height: 9px; flex: none; border-right: 2px solid var(--bbwy-accent); border-bottom: 2px solid var(--bbwy-accent); transform: rotate(45deg); transition: transform .2s ease; }
.group.collapsed .group__caret { transform: rotate(-45deg); }
.group__no { font-size: 12px; color: var(--bbwy-text-weak); font-variant-numeric: tabular-nums; }
.group__name { font-size: 15px; font-weight: 600; flex: 1; min-width: 0; }
.group__count { font-size: 12px; color: var(--bbwy-text-weak); white-space: nowrap; }
.group__body { padding: 0 20px 6px; }
.group.collapsed .group__body { display: none; }

/* ---------------------------------------------------------------- board */
.seg { display: inline-flex; gap: 4px; padding: 4px; background: var(--bbwy-primary-soft); border-radius: 999px; }
.seg__item { height: 32px; padding: 0 20px; border-radius: 999px; font-size: 13px; font-weight: 600; color: var(--bbwy-text-weak); background: transparent; border: 0; }
.seg__item.on { background: var(--bbwy-primary); color: var(--bbwy-on-primary); }
.board__meta { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.ranklist { margin-top: 10px; }
.rank { display: flex; align-items: center; gap: 14px; padding: 12px 16px; border-bottom: 1px solid var(--bbwy-border); }
.rank:last-child { border-bottom: 0; }
.rank__no { width: 34px; height: 34px; flex: none; border-radius: 999px; display: grid; place-items: center; font-weight: 700; font-size: 13px; background: var(--bbwy-primary-soft); color: var(--bbwy-text-weak); font-variant-numeric: tabular-nums; }
.rank__no.top { background: var(--bbwy-accent); color: var(--bbwy-card); }
.rank__name { color: var(--bbwy-text-weak); font-size: 13px; }
.rank__val { margin-left: auto; font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.rank__unit { font-size: 12px; color: var(--bbwy-text-weak); margin-left: 6px; font-weight: 400; }

/* ---------------------------------------------------------------- curve */
.curve__meta { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.curve__stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-top: 16px; }
.stat { padding: 14px 16px; border: 1px solid var(--bbwy-border); border-radius: 12px; }
.stat__k { font-size: 12px; color: var(--bbwy-text-weak); }
.stat__v { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 4px; }
.stat__u { font-size: 12px; font-weight: 400; color: var(--bbwy-text-weak); margin-left: 4px; }
/* The definition under a stat. It carries the kernel's own wording for a 口径 (e.g. what
   "人数" counts), so it has to be readable but must not compete with the number itself. */
.stat__hint { font-size: 11px; line-height: 1.5; color: var(--bbwy-text-weak); margin-top: 6px; }
.curve__plot { margin-top: 16px; }
/* The chart is drawn in SVG by app.js. The clip keeps the polyline from painting over the
   axis labels when a value sits exactly at the top of the scale. */
.curve__svg { width: 100%; height: 240px; display: block; overflow: hidden; }
.curve__axis { stroke: var(--bbwy-border); stroke-width: 1; }
.curve__grid { stroke: var(--bbwy-border); stroke-width: 1; stroke-dasharray: 3 4; }
.curve__line { fill: none; stroke: var(--bbwy-primary); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.curve__area { fill: var(--bbwy-primary-soft); stroke: none; }
.curve__dot { fill: var(--bbwy-primary); }
.curve__lbl { fill: var(--bbwy-text-weak); font-size: 11px; }
.curve__legend { display: flex; gap: 16px; margin-top: 10px; flex-wrap: wrap; font-size: 12px; color: var(--bbwy-text-weak); }
.curve__key { display: inline-flex; align-items: center; gap: 6px; }
.curve__swatch { width: 14px; height: 3px; border-radius: 2px; background: var(--bbwy-primary); }
.curve__swatch--soft { background: var(--bbwy-primary-soft); height: 10px; border-radius: 3px; }
/* The table is the chart's text form: same numbers, readable without the picture and
   usable as the DOM reading in the evidence. */
.curve__tbl { margin-top: 16px; }
.curve__caption { font-size: 12px; color: var(--bbwy-text-weak); margin-top: 12px; }

/* ------------------------------------------------------- 名单 / 组织 (W-3)

   THE TREE IS INDENTED BY NESTING, NOT BY A COMPUTED PADDING.
   -----------------------------------------------------------
   `app.js` draws one `.tree__kids` box per level, so the indent comes from the tree's own
   shape. A left-padding computed from a depth number would be a second source of truth for
   "how deep is this node" - and it would disagree with the kernel's path grammar the moment
   a path had an unusual segment count. */
.tree { margin-top: 14px; }
.tree__levels { font-size: 12px; color: var(--bbwy-text-weak); margin-bottom: 10px; }
.tree__node { border-left: 1px solid var(--bbwy-border); }
.tree__kids { margin-left: 18px; }
.tree__row { display: flex; align-items: center; gap: 8px; padding: 7px 0; font-size: 13px; }
.tree__dot { width: 7px; height: 7px; border-radius: 999px; background: var(--bbwy-primary); flex: none; }
.tree__name { font-weight: 600; }
.tree__id { font-size: 11px; color: var(--bbwy-text-weak); }

/* MG-2D: the interactive add affordances. The add buttons are quiet pill buttons so a row of
   nodes stays scannable; a pending node wears a dashed border + soft fill + a 未保存 badge, so
   "staged" and "saved" can never look the same. All colours come from the --bbwy-* tokens. */
.tree__act {
  display: inline-flex; align-items: center; height: 22px; padding: 0 8px;
  border-radius: 999px; border: 0; font-size: 11px; font-weight: 600; cursor: pointer;
  background: var(--bbwy-primary-soft); color: var(--bbwy-primary);
}
.tree__act:hover { background: var(--bbwy-primary); color: var(--bbwy-on-primary); }
.tree__del {
  display: inline-flex; align-items: center; height: 22px; padding: 0 8px;
  border-radius: 999px; border: 1px solid var(--bbwy-border); font-size: 11px; font-weight: 600; cursor: pointer;
  background: var(--bbwy-bg); color: var(--bbwy-danger);
}
.tree__node--pending {
  border: 1px dashed var(--bbwy-primary); border-radius: 6px;
  background: var(--bbwy-primary-soft); padding: 0 8px; margin: 2px 0;
}
.tree__pending {
  font-size: 10px; font-weight: 600; color: var(--bbwy-accent);
  background: var(--bbwy-bg); border: 1px solid var(--bbwy-border);
  border-radius: 999px; padding: 1px 6px;
}
.tree__inputrow { display: flex; align-items: center; gap: 8px; padding: 6px 0; }
.tree__input { width: 200px; }

/* The per-row import report. The head carries the two counts and every row carries the
   kernel's own reason - a failed row is tinted, never recoloured into prose. */
.impreport { margin-top: 12px; font-size: 13px; }
.impreport__head { font-size: 12px; color: var(--bbwy-text-weak); padding: 8px 0; }
.impreport__row { display: flex; gap: 10px; padding: 7px 10px; border-bottom: 1px solid var(--bbwy-border); }
.impreport__row--bad { background: var(--bbwy-bg); color: var(--bbwy-danger); }
.impreport__row--warn { color: var(--bbwy-danger); }
.impreport__no { width: 30px; flex: none; font-variant-numeric: tabular-nums; color: var(--bbwy-text-weak); }
.impreport__row--bad .impreport__no { color: var(--bbwy-danger); }
.impreport__what { width: 190px; flex: none; }
.impreport__why { flex: 1; }

/* MG-2E: the Excel-first import flow. The file picker is a label that reads as a save button,
   and the paste box folds into a native <details> marked 高级. */
.import-file { display: inline-flex; align-items: center; cursor: pointer; }
.import-file input[type="file"] { display: none; }
.advanced { margin-top: 12px; }
.advanced > summary {
  cursor: pointer; list-style: none; color: var(--bbwy-primary);
  font-size: 12px; font-weight: 600; padding: 4px 0; user-select: none;
}
.advanced > summary::-webkit-details-marker { display: none; }
.advanced > summary::before { content: '▸ '; }
.advanced[open] > summary::before { content: '▾ '; }
.advanced .context { padding-left: 0; padding-right: 0; }
/* MG-2DF: the Excel import row moved OUT of the 高级 fold, so it flushes with the table/tree
   instead of inheriting the 32px context indent (which the 高级 fold used to cancel). */
.context--import { padding-left: 0; padding-right: 0; }
.impreview { margin-top: 4px; }
.impreview .scroller { margin-top: 8px; margin-bottom: 4px; }

/* MG-2B: the missing-phone mark. A missing phone must not look like an empty cell - it is a
   warning the row will fail MG-3's phone admission, so it wears the danger token rather than
   the weak-text an empty cell would. */
.phone-missing {
  color: var(--bbwy-danger);
  background: var(--bbwy-primary-soft);
  border: 1px solid var(--bbwy-border);
  border-radius: 4px;
  padding: 1px 8px;
  font-size: 12px;
  white-space: nowrap;
}

/* MG-3B: the missing-phone COUNT hint on the 员工 view. A clickable warn row, not a silent
   table state - the operator lands on this view exactly when admission is failing, and the
   count names the consequence ("admission will miss all of them") instead of leaving the
   empty cells to be counted by eye. Zero is hidden rather than shown as a warning. */
.missing-hint {
  display: block;
  width: 100%;
  margin: 8px 0 12px;
  padding: 9px 14px;
  text-align: left;
  font-size: 13px;
  color: var(--bbwy-danger);
  background: var(--bbwy-primary-soft);
  border: 1px solid var(--bbwy-border);
  border-radius: 8px;
  cursor: pointer;
}

/* ---------------------------------------------------------------- login (S-1)

   THE BACKDROP TAKES NO POINTER EVENTS, AND THAT IS LOAD-BEARING.
   ------------------------------------------------------------------
   `gm011e switch-refetches` and `gm011f curve-one-read-per-refresh` drive this console by
   clicking `.nav__item[data-view=...]`. A full-screen box with default `pointer-events`
   sits on top of the nav and makes that click time out - measured, both red. So the
   backdrop is `pointer-events: none` and only the panel turns them back on. What a person
   sees is unchanged: the panel is opaque and covers the screen's centre, and the console
   underneath is dimmed by the backdrop's own background. */
.loginpage {
  position: fixed; inset: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; background: var(--bbwy-bg);
  pointer-events: none;
}
.loginpage[hidden] { display: none; }
.loginpage__panel {
  pointer-events: auto;
  width: 100%; max-width: 460px; background: var(--bbwy-card);
  border-radius: 20px; box-shadow: var(--bbwy-shadow);
  padding: 28px 28px 24px;
}
.loginpage__brand { display: flex; align-items: center; gap: 12px; }
.loginpage__mark { width: 44px; height: 44px; flex: none; }
.loginpage__name { font-size: 16px; font-weight: 700; }
.loginpage__sub { font-size: 12px; color: var(--bbwy-text-weak); }
.loginpage__state {
  margin-top: 18px; padding: 10px 12px; border-radius: 10px;
  background: var(--bbwy-bg); color: var(--bbwy-text-weak); font-size: 13px;
}
.loginpage__state--bad { color: var(--bbwy-danger); }
/* MG-4EK: the scan area (B 方案) above the paste form. The <img> is the ONLY image the login
   page renders, and it is only painted after /api/login/qr answers with a real QR. */
.loginpage__scan { margin-top: 16px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.loginpage__scan-qr {
  padding: 12px; border-radius: 12px; background: var(--bbwy-card);
  border: 1px solid var(--bbwy-border); line-height: 0;
}
.loginpage__scan-img { width: 176px; height: 176px; image-rendering: crisp-edges; }
.loginpage__scan-hint { font-size: 13px; color: var(--bbwy-text-weak); }
.loginpage__scan-state { font-size: 13px; font-weight: 600; color: var(--bbwy-text); }
.loginpage__scan-state--bad { color: var(--bbwy-danger); }
.loginpage__scan-note {
  width: 100%; padding: 10px 12px; border-radius: 10px; font-size: 13px;
  background: var(--bbwy-bg); color: var(--bbwy-text-weak);
}
/* MG-4E: the paste-code form replaces the QR slot. No <img> is rendered anywhere on the
   login page, so there is no broken-image state left to guard against. */
.loginpage__form { margin-top: 16px; display: flex; align-items: center; gap: 10px; }
.loginpage__field { display: flex; flex: 1; flex-direction: column; gap: 6px; }
.loginpage__field-label { font-size: 12px; font-weight: 600; color: var(--bbwy-text-weak); }
.loginpage__input {
  width: 100%; height: 34px; padding: 0 12px; border-radius: 8px; font-size: 13px;
  border: 1px solid var(--bbwy-border); background: var(--bbwy-card); color: var(--bbwy-text);
}
.loginpage__input:focus { outline: 2px solid var(--bbwy-primary-soft); border-color: var(--bbwy-primary); }
.loginpage__submit { height: 34px; }
.loginpage__fielderr { margin-top: 8px; font-size: 12px; color: var(--bbwy-danger); }
/* MG-2G: the account-password form is the DEFAULT and stacks vertically (username, password,
   then the button with the error line directly beneath it). Reuses the existing field/input
   tokens - no new visual language. */
.loginpage__form--stack { flex-direction: column; align-items: stretch; }
.loginpage__form--stack .loginpage__submit { align-self: stretch; }
/* MG-2G: the folded WeChat door. A native <details> so the paste/scan/guide stay in the DOM
   (the S-1 judgement counts the four `loginpage__step` nodes inside) while being collapsed. */
.loginpage__wechat { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--bbwy-border); }
.loginpage__wechat summary {
  cursor: pointer; list-style: none; font-size: 13px; font-weight: 600;
  color: var(--bbwy-text-weak); user-select: none;
}
.loginpage__wechat summary::-webkit-details-marker { display: none; }
.loginpage__wechat summary::before { content: "▸"; display: inline-block; margin-right: 6px; transition: transform .15s; }
.loginpage__wechat[open] summary::before { transform: rotate(90deg); }
.loginpage__wechat summary:hover { color: var(--bbwy-primary); }
.loginpage__guide { margin-top: 18px; display: flex; flex-direction: column; gap: 12px; }
.loginpage__step { display: flex; gap: 10px; }
.loginpage__step-no {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  background: var(--bbwy-primary-soft); color: var(--bbwy-on-primary);
  font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center;
}
.loginpage__step-t { font-size: 13px; font-weight: 600; }
.loginpage__step-b { font-size: 13px; color: var(--bbwy-text-weak); }
.loginpage__note { margin-top: 16px; font-size: 12px; color: var(--bbwy-text-weak); }
.loginpage__note code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
/* S-4: the refusal-block shape, reused on the login page for "this WeChat is not the
   administrator" and by the console's 401/403 banner. Four labelled lines, because the whole
   point of the block is that a person can find the ONE line that applies to them. */
.loginpage__refusal { margin-top: 16px; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--bbwy-danger); background: var(--bbwy-bg); text-align: left; }
.loginpage__refusal-t { font-size: 13px; font-weight: 700; color: var(--bbwy-danger); margin-bottom: 6px; }

/* ---------------------------------------------------------------- empty */
.empty { padding: 46px 0; text-align: center; color: var(--bbwy-text-weak); }
.empty__title { font-weight: 600; color: var(--bbwy-text); margin-bottom: 6px; }

/* MG-4EN：空态里的「去选客户」按钮，与确认块。只用既有令牌，不引入新视觉语言。 */
.empty__action { margin-top: 16px; }

/* ---------------------------------------------------------------- MG-2F: step bar
   The step bar sits at the top of every main view and says 「第 N 步 · 做什么 —— 怎么做」.
   `data-step` is written by app.js from NAV_ORDER, never hardcoded. Colours stay on tokens. */
.stepbar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 12px 16px; margin-bottom: 16px; border: 1px solid var(--bbwy-border);
  border-radius: 12px; background: var(--bbwy-card); flex-wrap: wrap;
}
.stepbar__text { font-size: 13px; color: var(--bbwy-text); }
.stepbar__text b { font-weight: 700; color: var(--bbwy-accent); }
.stepbar__actions { display: flex; gap: 8px; }
.stepbar__btn:disabled { opacity: .4; cursor: not-allowed; }

/* ---------------------------------------------------------------- MG-2F: empty guide
   An empty view shows a first-screen guide card (data-empty-guide) instead of a bare empty
   table, with ONE primary button to the next view. Same token set as `.empty`. */
.empty-guide { padding: 46px 0; text-align: center; color: var(--bbwy-text-weak); }

/* ---------------------------------------------------------------- MG-2F: 使用指引 checklist
   The four-step checklist with checkmarks derived from real state. */
.guide-list { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.guide-step {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px;
  border: 1px solid var(--bbwy-border); border-radius: 12px; background: var(--bbwy-bg);
}
.guide-step--done { border-color: var(--bbwy-primary-soft); background: var(--bbwy-primary-soft); }
.guide-step__check {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  background: var(--bbwy-card); border: 1px solid var(--bbwy-border);
  color: var(--bbwy-primary); font-weight: 700; font-size: 13px;
  display: flex; align-items: center; justify-content: center;
}
.guide-step--done .guide-step__check { background: var(--bbwy-primary); color: var(--bbwy-on-primary); border-color: var(--bbwy-primary); }
.guide-step__no { flex: none; width: 18px; font-size: 12px; color: var(--bbwy-text-weak); font-weight: 700; }
.guide-step__body { display: flex; flex-direction: column; min-width: 0; }
.guide-step__name { font-size: 13px; font-weight: 600; color: var(--bbwy-text); }
.guide-step__hint { font-size: 12px; color: var(--bbwy-text-weak); line-height: 1.5; }

/* ---------------------------------------------------------------- confirm (MG-4EN) */
.confirm__text { margin-top: 12px; font-size: 13px; line-height: 1.7; color: var(--bbwy-text); }
.confirm__actions { display: flex; gap: 10px; margin-top: 14px; }

/* --------------------------------------------------- 客户结案报告 (MG-5C) */
.report__meta { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.fr-block { margin-top: 14px; padding: 14px 16px; border: 1px solid var(--bbwy-border); border-radius: 12px; }
.fr-title { font-size: 13px; font-weight: 700; color: var(--bbwy-text); margin-bottom: 8px; }
.fr-row { display: flex; align-items: baseline; gap: 12px; padding: 3px 0; }
.fr-k { font-size: 12px; color: var(--bbwy-text-weak); min-width: 120px; flex: none; }
.fr-v { font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* 诚实空态：这一项还没有数据（与「—」区分开，0 就是 0、没有就是没有）。 */
.fr-empty-note { margin-top: 8px; font-size: 12px; color: var(--bbwy-text-weak); }
/* 口径说明（怎么算的）：这是验收的抓手，必须可读但不抢数字的风头。 */
.fr-method { margin-top: 10px; font-size: 11px; line-height: 1.6; color: var(--bbwy-text-weak); border-top: 1px dashed var(--bbwy-border); padding-top: 8px; }

/* ---------------------------------------------------------------- icons */
.ic-bg { fill: var(--bbwy-primary-soft); }
.ic-fg { fill: none; stroke: var(--bbwy-primary); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.brand__mark .ic-bg { fill: var(--bbwy-primary); }
.brand__mark .ic-fg { fill: var(--bbwy-card); stroke: none; }

@media (max-width: 860px) {
  .app { flex-direction: column; }
  .side { width: 100%; height: auto; position: static; flex-direction: row; align-items: center; overflow-x: auto; }
  .nav { display: flex; gap: 6px; padding: 8px; }
  .nav__label, .side__foot { display: none; }
  .view, .context { padding-left: 16px; padding-right: 16px; }
  .alert { margin-left: 16px; margin-right: 16px; }
}
