/* Tipsy AB-Config Console — minimal, dependency-free styling. */

:root {
  --fg: #1f2937;
  --muted: #6b7280;
  --bg: #ffffff;
  --card: #ffffff;
  --border: #e5e7eb;
  --primary: #2563eb;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--fg);
  background: var(--bg);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Geometry mirrors the React ConsoleChrome header (minHeight 68px, px 24px,
   toolbar gap 16px, nav item px 12px / py 6px, gap 4px) so the pre-mount
   fallback paint does not visibly re-flow when React replaces it. Colors
   mirror the light antd-style header: white surface, #f0f0f0 bottom border. */
.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 68px;
  padding: 0 24px;
  background: #ffffff;
  color: rgba(0,0,0,.88);
  border-bottom: 1px solid #f0f0f0;
  position: sticky;
  top: 0;
  z-index: 10;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 14px;
  color: inherit;
  text-decoration: none;
}

.brand::before { content: ""; width: 10px; height: 10px; border-radius: 50%; background: #1677ff; flex-shrink: 0; }

.nav-main { display: flex; gap: 4px; flex: 1; }

.nav-item {
  text-decoration: none;
  color: rgba(0,0,0,.65);
  font-weight: 500;
  /* Nav pills: fixed 6-character width (Chinese "灰度白名单" = 6 chars is the
     widest label; English long labels extend past it rather than clipping),
     centered text, smaller than the page title. */
  font-size: 14px;
  min-width: 6em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 12px;
  /* Match the React chrome's antd default borderRadius (6px) so the server
     fallback pill does not flash a different shape before React replaces it
     on tab navigation. */
  border-radius: 6px;
}

.nav-item:hover { background: rgba(0,0,0,.04); color: rgba(0,0,0,.88); }

.nav-item.is-active { background: #e6f4ff; color: #1677ff; font-weight: 600; }

.user-box { display: flex; gap: 12px; align-items: center; }

/* Fallback user-box controls (pre-mount paint): mirror the React chrome's
   LanguageToggle (two-segment pill), #user-sub chip slot, and logout button so
   the no-JS / pre-mount paint has the same silhouette as the mounted chrome —
   no shape or color jump on MPA navigation. The user id is unknown
   server-side, so #user-sub is a transparent width placeholder; the lang
   segment highlight is set by the translate script from localStorage. */
.lang-toggle-skeleton {
  display: flex;
  height: 38px;
  width: 112px;
  flex-shrink: 0;
  border: 1px solid #d9d9d9;
  border-radius: 6px;
  overflow: hidden;
}
.lang-seg {
  flex: 1 1 50%;
  min-width: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: rgba(0,0,0,.88);
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  padding: 0 10px;
  white-space: nowrap;
  cursor: pointer;
}
.lang-seg.is-on { color: #fff; background: #1677ff; }
.lang-seg.is-on:hover { background: #4096ff; }

.user-sub {
  /* Reserve the width of a 19-digit uid (matches the React Chip's min/max) so
     the user box does not reflow — but keep it an invisible slot (transparent
     color, no pill background) so it never flashes as a stray "—" or pill
     before React replaces it with the real id chip. */
  display: inline-block;
  min-width: 144px;
  max-width: 180px;
  height: 24px;
  color: transparent;
  overflow: hidden;
  white-space: nowrap;
}

.logout-skeleton {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  background: transparent;
  color: rgba(0,0,0,.65);
  font: inherit;
  font-size: 0.9rem;
  padding: 4px 8px;
  cursor: pointer;
}
.logout-skeleton:hover { color: rgba(0,0,0,.88); }

/* Fallback nav pill (server-rendered) keeps the exact radius of the React
   chrome's antd button — see the radius comment next to .nav-item above. */


.language-toggle { font-weight: 600; min-width: 3.5em; }

/* Full-bleed content: only the 24px side padding separates the page from the
   viewport edges (no centered max-width column). */
.content { padding: 32px 24px 48px; }

.react-page-active .content { padding-top: 32px; }

.react-page-root { min-height: 420px; }

/* Modal 一级内容区块间距统一 32px；不要命中表单内部的嵌套 Flex，组件
   自己声明的 12/16px 字段间距必须保留。 */
.ant-modal-container .ant-modal-body > .ant-flex-vertical { gap: 32px !important; }

/* 所有 Modal 的标题与内容保持清晰分区，避免首个表单项紧贴标题。 */
.ant-modal-container .ant-modal-header { margin-bottom: 32px; }

/* 所有 Modal 弹窗（用户约定 2026-08-23）：垂直居中；整个 Dialog 限高
   80vh（上下各留约 10%）；内容过高时 body 出纵向滚动条。 */
/* 中心化走 ConfigProvider modal.centered（main.jsx）；此处只做限高 +
   内部纵向滚动——上下各留约 10%，过高时弹窗 body 出滚动条。 */
.ant-modal-container {
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.ant-modal-container .ant-modal-header,
.ant-modal-container .ant-modal-footer {
  flex: 0 0 auto;
}

.ant-modal-container .ant-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* AntD leaves 24px at the container edge. Extend the body (and with it the
     scrollbar) 16px into that gutter so the scrollbar sits 8px from the Dialog
     edge (pinned by list-search-layout.spec.js). Content fills the body box
     all the way to its right edge (pinned by dialog-consistency.spec.js), so
     no compensating padding-right/scrollbar-gutter here: reserving them would
     shrink every dialog form by a phantom 31px even when nothing scrolls.
     When content does overflow, the docking scrollbar takes 15px from the
     content box and the content right edge lands back near the original 24px
     alignment. */
  margin-right: -16px;
}

/* Dialog sections use dividers for hierarchy instead of nested outlined cards.
   Keep this scoped to modal content so standalone pages can still use cards as
   visual containers where that context is useful. */
.ant-modal-container .ant-modal-body .ant-card {
  border: 0 !important;
  box-shadow: none !important;
  background: transparent !important;
}
.ant-modal-container .ant-modal-body .ant-card + .ant-card {
  border-top: 1px solid #f0f0f0 !important;
  padding-top: 24px;
}

/* A few legacy detail previews still carry an inline outline.  They are
   sections inside a Dialog, so treat them the same as the borderless cards. */
.ant-modal-container .ant-modal-body #domain-parent-preview {
  border: 0 !important;
  border-radius: 0 !important;
  padding-inline: 0 !important;
}

/* Keep the section rhythm visible after removing card outlines. */
.ant-modal-container .ant-modal-body .dialog-section-divider {
  margin: 0;
}

/* Detail metadata uses the same horizontal KV rhythm as Dialog forms instead
   of a paragraph of bold labels followed by values. */
.info-fields-card .ant-card-body { padding: 0 0 0 28px; }
.info-field-row {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 16px;
  align-items: baseline;
}
.info-field-label {
  text-align: left;
  white-space: nowrap;
}
.info-field-value {
  min-width: 0;
  overflow-wrap: anywhere;
}
.ant-modal-container .ant-modal-body .table-slot-card .ant-card-body {
  padding: 0;
}

/* Dialog 表单统一左右 KV：标签列固定，控件列吃满剩余宽度。复杂表格/分组
   编辑器仍保留自身行结构，只给明确标记的字段行套用。
   实验独立页（/experiments/new、/experiments/:id，F3 后不再是列表弹窗）沿用
   同一套 KV 排版（D35）：页面根带 .exp-standalone-page（ExperimentNewPage /
   ExperimentDetailPage），故这里与 .ant-modal-body 并列。只加这一个页面作用域，
   不放开成全局 .dialog-form-row——其它独立页（/configs/new、combo 页等）依赖
   该类在页面上是普通块级（ComboCreateForm 的 inline-flex 列等）。 */
.ant-modal-body .dialog-form-row,
.exp-standalone-page .dialog-form-row {
  display: grid !important;
  grid-template-columns: 120px minmax(0, 1fr);
  align-items: start !important;
  gap: 16px !important;
  width: 100%;
}
.ant-modal-body .dialog-form-label,
.exp-standalone-page .dialog-form-label {
  padding-top: 8px;
  text-align: right;
}
.ant-modal-body .dialog-form-control,
.exp-standalone-page .dialog-form-control { min-width: 0; }

/* 管理员创建弹层字段较少：使用紧凑标签列，控件向右吃满 Dialog 内容区。 */
#admin-create-form .dialog-form-row {
  grid-template-columns: 32px minmax(0, 1fr);
  gap: 16px !important;
}
#admin-create-form .dialog-form-label {
  align-self: center;
  padding-top: 0;
  justify-self: end;
  white-space: nowrap;
}
#admin-create-form .dialog-form-control { grid-column: 2; }
#admin-create-form .admin-create-input {
  width: 100%;
  height: 32px;
  box-sizing: border-box;
}

/* 白名单创建表单使用短标签，避免继承 120px 标签列后整体右移；同时恢复
   组件声明的表单行/键列表间距，避免 Modal 的 32px 区块间距误伤嵌套 Flex。 */
#wl-create-form > .ant-flex-vertical { gap: 16px !important; }
#wl-create-form .dialog-form-row {
  grid-template-columns: 40px minmax(0, 1fr);
}
#wl-create-form .dialog-form-label {
  justify-self: end;
  white-space: nowrap;
}
#wl-create-form .dialog-form-control.ant-flex-vertical { gap: 12px !important; }

/* 实验创建表单（F3 起在独立页 /experiments/new，KV 排版沿用原 Dialog，D35）：
   表单居中，使用能容纳中英文标签的紧凑 KV 列；字段间距走组件声明的 16px，
   不被 Modal 一级区块间距放大。 */
#exp-create-form {
  width: 100%;
  margin-inline: auto;
}
#exp-create-form > .ant-flex-vertical { gap: 16px !important; }
#exp-create-form > .ant-flex-vertical > .dialog-form-row {
  grid-template-columns: 48px minmax(0, 1fr);
}
#exp-create-form > .ant-flex-vertical > .dialog-form-row > .dialog-form-label {
  justify-self: end;
  white-space: nowrap;
}
#exp-create-form .experiment-section-row > .dialog-form-label { padding-top: 8px; }

/* 资源创建弹层不再套一层窄的内部 max-width；标签列按各表单实际文案
   收紧，让控件区在弹层左右获得对称、紧凑的留白。 */
#namespace-create-form,
#config-create-form,
#domain-create-form,
#layer-create-form {
  width: 100%;
  margin-inline: auto;
}

#namespace-create-form .dialog-form-row,
#layer-create-form .dialog-form-row {
  grid-template-columns: 56px minmax(0, 1fr);
}

#domain-create-form .dialog-form-row {
  grid-template-columns: 72px minmax(0, 1fr);
  padding-right: 16px;
}

#namespace-create-form .dialog-form-label,
#domain-create-form .dialog-form-label,
#layer-create-form .dialog-form-label {
  justify-self: end;
  white-space: nowrap;
}
#domain-create-form .dialog-form-control { grid-column: 2; }

/* ConfigCreateForm 为兼容独立 /configs/new 页面仍保留 120px inline 标签列；
   仅在 Modal 内覆盖为紧凑列，并统一使用 antd secondary 文本色。 */
.ant-modal-body #config-create-form .config-form-label { width: 56px !important; }
.ant-modal-body #config-create-form .config-form-control { min-width: 0; }

#domain-create-form > .ant-flex-vertical,
#layer-create-form > .ant-flex-vertical,
#namespace-create-form > .ant-flex-vertical { gap: 16px !important; }

#domain-create-form #parent-form-select .ant-radio-group {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
  padding-top: 6px;
}

#domain-create-form .domain-slot-fields {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Combo 流量占比的重复项统一成逐行 KV。避免字段标题浮在输入框上方，
   并让重复项、新增按钮和 simple 余量使用同一套 antd 尺寸。 */
.combo-share-groups {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.combo-create-section { padding: 24px 40px 24px 24px; }
.combo-create-section + .combo-create-section { border-top: 1px solid #f0f0f0; }
.combo-modal-form .dialog-form-row { grid-template-columns: 88px minmax(0, 1fr); }
.combo-share-item {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 0 0 24px;
  background: transparent;
}
.combo-share-item + .combo-share-item {
  padding-top: 24px;
  border-top: 1px solid #f0f0f0;
}
.combo-share-item-header { min-height: 32px; }
.combo-share-item .combo-share-field,
.combo-simple-pct {
  display: grid !important;
  grid-template-columns: 88px minmax(0, 1fr);
  align-items: center !important;
  gap: 16px !important;
  width: 100%;
}
.combo-share-item .combo-share-field .dialog-form-label,
.combo-simple-pct .dialog-form-label {
  justify-self: end;
  padding-top: 0;
  white-space: nowrap;
}
.combo-share-item .combo-share-field .dialog-form-control,
.combo-simple-pct .dialog-form-control { min-width: 0; }
.combo-share-add-actions { padding-left: 104px; }
.combo-simple-pct .ant-flex > .ant-input { flex: 1; }

.react-page-root { min-width: 0; }

/* 列表页固定视口布局（模板约定 2026-08-23）：带 .console-list-page 的列表
   页里 body/整个页面不滚，表格内容在 .console-table-host 内部滚动，分页器
   （ListTable 内 sticky bottom）始终钉在页面底部可见。非列表页（详情/表单）
   不受影响：仍旧整页滚动。 */
body:has(.console-list-page) {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}
/* React 挂载点 #root 是 body 的直接子级，必须进 flex 链，否则 .content 的
   flex:1 无从继承（实测表格区只撑到内容高度、页面底部留白）。 */
#console-root:has(.console-list-page) {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
body:has(.console-list-page) .content { padding-bottom: 24px; }
.content:has(.console-list-page) {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
#console-page-root:has(.console-list-page) {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}
.react-page-root.console-list-page {
  flex: 1;
  min-height: 0;
  /* 区块间距（用户约定 2026-08-23）：头部行/表格/分页之间的留白加大，
     antd Flex 的 gap 以 inline style 注入，此处用 !important 强制。 */
  gap: 32px !important;
}
.react-page-root.console-list-page > * { flex-shrink: 0; }
.react-page-root.console-list-page > .console-table-host {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.react-page-root.console-list-page .console-list-table {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.react-page-root.console-list-page .console-list-table-scroll {
  flex: 1;
  min-height: 0;
}
/* 边框已移到表格滚动区（ListTable 内），外层 host 不再画边框，
   分页器就不会贴着带边框盒子的底边。 */
.console-list-page .console-table-host { border: none !important; }

@media (max-width: 900px) {
  .content { padding: 24px 16px 36px; }
  .topbar { min-height: 60px; padding: 0 16px; }
  .nav-main { overflow-x: auto; padding-bottom: 2px; }
  .nav-item { white-space: nowrap; }
  .user-box { gap: 8px; }
}

@media (max-width: 640px) {
  .content { padding: 20px 12px 28px; }
  .user-sub { max-width: 132px; overflow: hidden; text-overflow: ellipsis; }
}

.btn {
  display: inline-block;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--fg);
  padding: 6px 12px;
  border-radius: 9px;
  cursor: pointer;
  text-decoration: none;
  font-size: 0.95em;
  transition: background .16s ease, border-color .16s ease, transform .16s ease;
}

.btn:hover { background: #f3f4f6; transform: translateY(-1px); }

.btn-link { border: none; background: transparent; color: var(--primary); padding: 4px 8px; }

.btn-link:hover { text-decoration: underline; background: transparent; }

/* List identifiers remain clickable, but read as links rather than boxed
   controls. The detail action remains a regular button in the Actions column. */
.resource-id-link.ant-btn {
  height: auto;
  padding: 0;
  font-weight: 500;
}

.group-row.ant-card {
  margin-bottom: 0;
  padding: 0;
  border-color: var(--border);
  background: #fafafa;
}
.group-row.ant-card .ant-card-body { padding: 20px; }
.g-no-preview { display: block; font-size: 16px; }
#groups-container .group-kv-row {
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr);
  align-items: start;
  gap: 16px;
}
#groups-container .group-kv-label {
  padding-top: 5px;
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
}
#groups-container .group-kv-control { min-width: 0; }
#groups-container .group-id-control .ant-input { flex: 1 1 auto; min-width: 0; }

code { background: #f3f4f6; padding: 1px 6px; border-radius: 3px; font-family: ui-monospace, monospace; font-size: 0.9em; }

/* config_version key/version selector rows in the experiment group editor:
   each key + version antd Select plus its Remove button sit on ONE line
   (create page and detail edit mode render identically). Scoped strictly to
   .kv-* so other pages' selects are untouched. */

.kv-selector { display: flex; flex-direction: column; gap: 6px; min-width: 240px; }

#groups-container .kv-row {
  display: grid !important;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: 12px !important;
  align-items: center;
}

.kv-row .ant-select { flex: 1 1 auto; min-width: 0; }
#groups-container .kv-row .kv-version { width: 100%; }
#groups-container .kv-row-actions {
  min-width: 168px;
  white-space: nowrap;
}
#groups-container .kv-selector > .kv-add { align-self: flex-end; }

/* One key-select + version-select + remove button per row (like .kv-row). */

.wl-key-row { display: flex; gap: 8px; align-items: center; }

/* key/version 选择器全部定宽（用户约定 2026-08-23：点「添加」后下拉宽度
   必须稳定——包括首次加行时 Remove 按钮出现引起的重排）。 */
.wl-key-row .ant-select { min-width: 0; }
.wl-key-row .wl-version { flex: 0 0 220px; }

/* Stacked (key → version) lines inside the list table's Keys cell. */

.wl-key-line { white-space: nowrap; line-height: 1.6; }

/* The newly created whitelist row the locate flow scrolled to (F10); the class
   is removed again after 3 s. Painted on the cells: antd colours the <td>s. */

#wl-table tr.wl-row-highlight > td { background: #fff7e6 !important; transition: background 0.3s; }

/* Version status-label badges (configs detail page). kind from
   /api/v1/configs/key-status drives the class: full/whitelist/ab/ab-paused/
   ab-draft. (The blue "published" fall-through badge was removed 2026-08-26 —
   it carried no signal and misread as "live".) */
.status {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 0.85em;
  background: #e5e7eb;
  color: var(--fg);
}
.status-full { background: #dcfce7; color: #166534; }
.status-whitelist { background: #ffedd5; color: #9a3412; }
.status-ab { background: #ede9fe; color: #5b21b6; }
.status-ab-paused { background: #fef9c3; color: #854d0e; }
.status-ab-draft { background: #e5e7eb; color: #4b5563; }

/* Per-version value cell in the Versions table: monospace, capped width,
   wrap long/multi-line values without blowing out the table layout. The
   wrapper stacks the value over the copy action; the <pre> fills the column
   width (min 120px) so short values like "true" render on one line instead
   of collapsing to one char per line when the column is narrow. */
.value-cell-wrap {
  max-width: 320px;
}
.value-cell {
  margin: 0;
  width: 100%;
  min-width: 120px;
  max-width: 320px;
  max-height: 6em;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85em;
}

/* Compact inline row (experiments/new #slot-range, G5): controls sit on one
   row at content width; wraps on narrow viewports. */
.form.form-inline {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
}

/* ================= AB Dashboard (experiment dashboard page) =================
   Card-shell styles ported from ab_design_new/dashboard_universal.html.
   Light-only (no dark scheme by design); fixed colors approximate the
   prototype's CSS variables; MUI covers the sidebar toggle/alerts. */

.dash-layout { display: flex; align-items: flex-start; gap: 0; width: 100%; min-width: 0; }
.dash-side {
  width: 320px; flex: 0 0 320px; background: #fbfcfd;
  border: 1px solid #e8eaed; border-radius: 12px; padding: 20px 18px;
  position: sticky; top: 76px; max-height: calc(100vh - 96px); overflow-y: auto;
}
.dash-main { flex: 1 1 auto; min-width: 0; max-width: 100%; padding: 0 0 60px 28px; }

.dash-side .dash-side-toolbar {
  display: flex;
  justify-content: flex-end;
  margin: -8px -8px 10px 0;
}
.dash-side.dash-side-collapsed {
  width: 52px;
  flex-basis: 52px;
  padding: 10px 8px;
  overflow: hidden;
  display: flex;
  justify-content: center;
}
.dash-side-collapsed #dashboard-sidebar-expand {
  width: 34px;
  height: 34px;
}

.dash-side .side-h {
  font-size: 12px; font-weight: 700; letter-spacing: .6px; color: #9ca3af;
  text-transform: uppercase; margin: 0 0 6px;
}
.dash-side .side-sub { font-size: 11.5px; color: #9ca3af; margin: 0 0 18px; }
.dash-side .dim { margin-bottom: 20px; }
.dash-side .dim-title {
  font-size: 13.5px; font-weight: 700; color: #111827; margin: 0 0 9px;
  display: flex; align-items: center; gap: 6px;
}
.dash-side .dim-title .ic { width: 4px; height: 13px; border-radius: 2px; background: #2f6fed; opacity: .85; }
.dash-side .opts { display: flex; flex-wrap: wrap; gap: 5px; }
.dash-side .opt {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 11px; border-radius: 8px; font-size: 13px; color: #374151;
  cursor: pointer; border: 1px solid transparent; transition: .12s; user-select: none;
}
.dash-side .opt:hover { background: #eef1f5; }
.dash-side .opt.on { background: #2f6fed; color: #fff; font-weight: 600; }
.dash-side .side-foot {
  margin-top: 8px; padding-top: 14px; border-top: 1px dashed #e8eaed;
  font-size: 11px; color: #9ca3af; line-height: 1.5;
}

.dash-side .daterange { display: flex; flex-direction: column; gap: 8px; margin: 2px 0 16px; }
.dash-side .dr-quick { display: flex; gap: 6px; margin-top: 2px; }
.dash-side .dr-quick .qk {
  flex: 1; text-align: center; font-size: 11px; color: #374151;
  border: 1px solid #e8eaed; border-radius: 7px; padding: 4px 0; cursor: pointer; transition: .12s;
}
.dash-side .dr-quick .qk:hover { border-color: #2f6fed; color: #2f6fed; }
.dash-side .dr-quick .qk.on { background: #2f6fed; color: #fff; border-color: #2f6fed; }
.dash-side .dr-err { font-size: 11px; color: #e5484d; min-height: 14px; }

.dash-side .csvbar { margin-bottom: 18px; }
.dash-side .csv-btn {
  width: 100%; padding: 8px 0; border: 1px solid #2f6fed; border-radius: 9px;
  background: #2f6fed; color: #fff; font-size: 13px; font-weight: 600; cursor: pointer;
  font-family: inherit; transition: .12s;
}
.dash-side .csv-btn:hover { opacity: .88; }
.dash-side .csv-hint { font-size: 10.5px; color: #9ca3af; margin-top: 5px; text-align: center; }

.dash-main .pagehead {
  display: flex; align-items: baseline; justify-content: space-between;
  margin: 2px 2px 18px; flex-wrap: wrap; gap: 8px;
}
.dash-main .pagehead > * { min-width: 0; }
.dash-main .pagehead h1 { font-size: 19px; margin: 0; font-weight: 700; overflow-wrap: anywhere; }
.dash-main .pagehead .sub { font-size: 12.5px; color: #6b7280; overflow-wrap: anywhere; }
.dash-main .legend { display: flex; gap: 16px; font-size: 12.5px; align-items: center; flex-wrap: wrap; }
.dash-main .legend i {
  display: inline-block; width: 11px; height: 11px; border-radius: 3px;
  margin: 0 5px 0 4px; vertical-align: -1px;
}
.dash-main .legend-item { display: inline-flex; align-items: center; cursor: pointer; user-select: none; }
.dash-main .legend-item input.ctrl-checkbox { cursor: pointer; margin: 0; }
.dash-main .legend-hint { font-size: 11px; color: #9ca3af; }

.dash-main .warnbar {
  border-radius: 9px; padding: 8px 13px; font-size: 12px; margin: 0 0 16px; font-weight: 600;
}
.dash-main .warnbar.monitor { background: #fff8e6; border: 1px solid #f0c869; color: #8a6d1a; }

/* 图卡 ⓘ 口径按钮 + 定义弹窗 (MetricDefinitionModal, 仿兄弟工程 DefinitionPopover) */
.mcard-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.mcard-info.ant-btn { color: #9ca3af; }
.mcard-info.ant-btn:hover { color: var(--grp-c, #2f6fed); }
.defmodal-body { padding-top: 2px; }
.defmodal-row {
  display: grid; grid-template-columns: 76px 1fr; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid #eef0f2;
  font-size: 13px; line-height: 1.7;
}
.defmodal-row:last-child { border-bottom: none; }
.defmodal-row__label { color: #8a8a93; font-weight: 600; }
.defmodal-row__value { color: #374151; word-break: break-word; overflow-wrap: anywhere; }
.defmodal-notes { margin: 6px 0 0; padding-left: 18px; }
.defmodal-notes li { margin: 2px 0; }
.defmodal-src { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.defmodal-src-hint { margin-left: 8px; font-size: 11.5px; color: #9ca3af; }

.mgroup-card { margin-top: 34px; min-width: 0; }
.mgroup-card:first-child { margin-top: 6px; }
.mgroup-h {
  display: flex; align-items: center; gap: 11px; margin-bottom: 14px;
  padding: 9px 14px; border-radius: 10px;
  background: linear-gradient(90deg, var(--grp-tint), transparent 70%);
  border-left: 5px solid var(--grp-c, #2f6fed);
  min-width: 0;
}
.mgroup-h .bar { width: 8px; height: 22px; border-radius: 3px; background: var(--grp-c, #2f6fed); }
.mgroup-h .name { font-size: 19px; font-weight: 900; letter-spacing: .3px; color: #111827; min-width: 0; overflow-wrap: anywhere; }
.mgroup-h .desc { font-size: 11.5px; color: #6b7280; font-weight: 500; min-width: 0; overflow-wrap: anywhere; }
/* 分区标题即折叠开关: 整条可点, 复位 button 默认样式 */
button.mgroup-h {
  width: 100%; text-align: left; font: inherit; color: inherit; cursor: pointer;
  border-top: 0; border-right: 0; border-bottom: 0;
}
button.mgroup-h:hover { background: linear-gradient(90deg, var(--grp-tint), var(--grp-tint) 20%, transparent 85%); }
button.mgroup-h:focus-visible { outline: 2px solid var(--grp-c, #2f6fed); outline-offset: 2px; }
.mgroup-h .mgroup-count { margin-left: auto; font-size: 11.5px; color: #9ca3af; font-weight: 500; white-space: nowrap; }
.mgroup-h .mgroup-caret { font-size: 12px; color: #9ca3af; transition: transform .15s; }
.mgroup-card[data-collapsed] .mgroup-h { margin-bottom: 0; }
.mcard-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; min-width: 0; }
.mcard { border: 1px solid #e8eaed; border-radius: 11px; padding: 13px 15px 15px; background: #fff; min-width: 0; }
.mcard-h {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 6px; flex-wrap: wrap;
  min-width: 0;
}
.mcard-t { font-size: 13.5px; font-weight: 700; color: #111827; min-width: 0; overflow-wrap: anywhere; }
.mcard-caveat {
  display: flex; align-items: center; gap: 4px;
  font-size: 11px; color: #b45309;
  background: #fff7ed; border: 1px solid #fed7aa; border-radius: 6px;
  padding: 3px 8px; margin: 0 0 8px; line-height: 1.3;
}
.fam {
  display: inline-block; font-size: 9px; font-weight: 700; padding: 1px 6px;
  border-radius: 8px; margin-left: 6px; vertical-align: middle; letter-spacing: .2px;
}
.fam.sameday { background: #f3effe; color: #8b5cf6; border: 1px solid #e3d9fb; }
.fam.rolling { background: #e9f9f0; color: #128a5a; border: 1px solid #c8ecd7; }
.daybtns { display: flex; gap: 4px; }
.db {
  font-size: 10.5px; color: #374151; border: 1px solid #e8eaed; border-radius: 6px;
  padding: 2px 8px; cursor: pointer; transition: .12s; user-select: none;
}
.db:hover { border-color: #128a5a; color: #128a5a; }
.db.on { background: #128a5a; color: #fff; border-color: #128a5a; }
.db.dis { opacity: .35; cursor: not-allowed; pointer-events: none; }
.mcard .chart { width: 100%; position: relative; margin-top: 4px; }
.mcard .chart-with-data { height: 230px; }
.mcard .chart svg text { font-size: 11px; fill: #9ca3af; }
.mcard .chart { cursor: crosshair; }
.mcard .dashboard-tooltip {
  position: absolute; top: 6px; transform: translateX(-50%); pointer-events: none;
  background: #fff; border: 1px solid #e8eaed; border-radius: 9px; padding: 7px 10px;
  box-shadow: 0 4px 16px rgba(16,24,40,.13); font-size: 12px; min-width: 130px;
  transition: opacity .1s; z-index: 5; display: none;
}
.mcard .dashboard-tooltip .tip-x { font-weight: 700; color: #111827; margin-bottom: 4px; font-size: 11.5px; }
.mcard .dashboard-tooltip .tip-r { display: flex; justify-content: space-between; gap: 14px; margin: 2px 0; }
.mcard .dashboard-tooltip .tip-r span { font-size: 11.5px; }
.mcard .dashboard-tooltip .tip-r b { font-variant-numeric: tabular-nums; font-size: 11.5px; }
.mcard .dashboard-tooltip .tip-r b em {
  font-style: normal; color: #6b7280; font-weight: 600; margin-left: 3px; font-size: 10.5px;
}
.cmp-wrap { margin-top: 9px; padding-top: 9px; border-top: 1px dashed #f0f1f3; }
.cmp { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; }
/* flex-wrap: chip 带上显著性徽标/近似/CI 后可能比半宽卡片还长, 按子元素整体换行而不是在文字中间断开。 */
.cmp-i { font-size: 11.5px; display: inline-flex; flex-wrap: wrap; align-items: center; gap: 2px 5px; min-width: 0; overflow-wrap: anywhere; }
.cmp-i b { font-weight: 600; }
.cmp-i .p { color: #1fa971; font-weight: 700; font-variant-numeric: tabular-nums; }
.cmp-i .n { color: #e5484d; font-weight: 700; font-variant-numeric: tabular-nums; }
.cmp-i .z { color: #6b7280; font-weight: 700; font-variant-numeric: tabular-nums; }
.cmp-i .dz { color: #9ca3af; font-style: italic; }
.cmp-na { font-size: 11.5px; color: #9ca3af; font-style: italic; }
/* 显著性标注（组间差脚注）: 显著=绿高亮 / 不显著=灰 / 未计算原因=浅灰斜体 */
.cmp-sig { font-size: 11px; color: #1fa971; font-weight: 700; background: #e7f7ef; border-radius: 4px; padding: 1px 5px; font-variant-numeric: tabular-nums; }
/* 显著且 delta<0: 红色药丸，与 .n 同色（颜色表方向，不表好坏）。用独立修饰类而非 .n:
   .cmp-i .n 的 (0,2,0) 会改字色却留下 .cmp-sig 的绿底。 */
.cmp-sig.cmp-sig-neg { color: #e5484d; background: #fdecec; }
.cmp-nsig { font-size: 11px; color: #9ca3af; font-weight: 600; }
.cmp-sig-na { font-size: 11px; color: #b0b4bb; font-style: italic; }
/* 近似: 每次进组跨多日的 p/CI 偏乐观，小号描边标签提示，不抢徽标的视觉。 */
.cmp-approx { font-size: 10px; color: #8a8f98; border: 1px solid #e5e7eb; border-radius: 3px; padding: 0 3px; line-height: 14px; }
.cmp-ci { font-size: 10.5px; color: #9ca3af; font-variant-numeric: tabular-nums; }
.cmp-i[title] { cursor: help; }
.axis-note { font-size: 11px; color: #9ca3af; margin-top: 8px; }
.dash-note { font-size: 11px; color: #9ca3af; margin-top: 24px; text-align: center; }
.dash-note-sig { margin-top: 4px; }

@media (max-width: 1100px) { .mcard-grid { grid-template-columns: 1fr; } }
@media (max-width: 900px) {
  .dash-layout { flex-direction: column; }
  .dash-side { position: static; width: 100%; flex: none; max-height: none; margin-bottom: 16px; }
  .dash-side.dash-side-collapsed {
    width: 100%;
    justify-content: flex-end;
  }
  .dash-main { padding-left: 0; }
}

.namespace-detail-modal .dialog-form-row {
  grid-template-columns: 56px minmax(0, 1fr);
}
.namespace-detail-modal .dialog-form-label {
  justify-self: end;
  white-space: nowrap;
}
.namespace-detail-modal .desc-input {
  flex: 1;
  min-width: 0;
}

/* Refetch feedback: the page keeps the previous payload visible but dims the
   charts area while the new slice is in flight (hook sets data-loading). The
   sidebar stays interactive — filter changes abort the in-flight request. */
.dash-layout[data-loading] .dash-main { opacity: .55; pointer-events: none; transition: opacity .15s; }

/* 全项目 textarea 固定尺寸: 禁止拖拽缩放; 高度由 rows 决定; 文案超出走内部滚动条. */
textarea,
textarea.ant-input {
  resize: none;
  overflow-y: auto;
}
