/* ==========================================================================
   endfield-ui 组件样式（纯 CSS 落地版）
   --------------------------------------------------------------------------
   依据：icaruszezen/endfield-ui（MIT，非官方爱好者项目）
     - 令牌   packages/ui/src/styles/theme.css
     - 工具类 packages/ui/src/styles/utilities.css
     - 组件   packages/ui/src/components/*（Tailwind 类名逐个翻译成这里的类）
   文档：https://icaruszezen.github.io/endfield-ui/

   约定：
     - 只有本文件里的 .ef-* 类定义样式；页面自己只写布局与少量特例
     - 颜色一律走 --ef-* 语义变量，不写十六进制（页面特例除外）
     - 焦点环：outline 2px + offset 2px，颜色 var(--ef-focus)
     - 直角为主；切角只给「行动」，胶囊只放短状态
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. 令牌（亮色主题；与 theme.css 的 :root 一一对应）
   -------------------------------------------------------------------------- */
:root {
  color-scheme: light;

  /* 表面 */
  --ef-surface: #ffffff;
  --ef-surface-raised: #fafafa;
  --ef-surface-sunken: #f2f2f2;
  --ef-surface-muted: #e6e6e6;
  --ef-surface-inverse: #191919;

  /* 文字 */
  --ef-ink: #191919;
  --ef-ink-secondary: #626262;
  --ef-ink-tertiary: #8a8a8a;
  --ef-ink-disabled: #b3b3b3;
  --ef-ink-inverse: #ffffff;

  /* 线 */
  --ef-line: #d9d9d9;
  --ef-line-strong: #b3b3b3;

  /* 行动与选中 */
  --ef-action: #fffa00;
  --ef-action-pressed: #eeea00;
  --ef-on-action: #191919;
  --ef-accent-ink: #6b5d00;

  /* 中性控件 */
  --ef-control: #383838;
  --ef-control-hover: #484848;
  --ef-control-pressed: #282828;
  --ef-on-control: #eeeeee;
  --ef-button: #383838;
  --ef-button-hover: #484848;
  --ef-button-pressed: #282828;
  --ef-on-button: #eeeeee;
  --ef-disabled: #b3b3b3;
  --ef-on-disabled: #626262;

  /* 焦点与遮罩 */
  --ef-focus: #191919;
  --ef-scrim: rgb(0 0 0 / 0.5);

  /* 状态（文字级，亮底上 ≥ 4.5:1） */
  --ef-danger: #de0909;
  --ef-success: #007f43;
  --ef-warning: #b74e00;
  --ef-info: #25759f;

  /* 原始色（角色色、稀有度、注册色条） */
  --color-neutral-0: #ffffff;
  --color-neutral-50: #fafafa;
  --color-neutral-100: #f2f2f2;
  --color-neutral-200: #e6e6e6;
  --color-neutral-300: #d9d9d9;
  --color-neutral-400: #b3b3b3;
  --color-neutral-500: #8a8a8a;
  --color-neutral-600: #626262;
  --color-neutral-700: #484848;
  --color-neutral-800: #383838;
  --color-neutral-850: #282828;
  --color-neutral-900: #191919;
  --color-neutral-950: #141414;
  --color-signal: #fffa00;
  --color-signal-pressed: #eeea00;
  --color-signal-deep: #e6de01;
  --color-signal-ink: #6b5d00;
  --color-notice: #fc6c00;
  --color-gain: #bce41d;
  --color-data: #2a84b4;
  --color-region: #06a2a2;
  --color-alert: #f62a2a;
  --color-alert-fill: #de0909;
  --color-alert-fill-hover: #c40808;
  --color-alert-fill-pressed: #a80707;
  --color-confirm: #00d872;
  --color-special: #904ef0;

  /* 字体 */
  --font-sans: "HarmonyOS Sans SC", "Noto Sans SC", "Source Han Sans SC",
    "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
  --font-display: "Archivo", "Montserrat", "HarmonyOS Sans SC", sans-serif;
  --font-latin: "Outfit", "Urbanist", "HarmonyOS Sans SC", sans-serif;
  --font-tech: "Space Grotesk", "HarmonyOS Sans SC", sans-serif;
  --font-mono: "JetBrains Mono", "Cascadia Code", ui-monospace, "SF Mono",
    Consolas, monospace;

  /* 字阶（16px 基准） */
  --text-micro: 0.625rem;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.25rem;
  --text-xl: 1.5rem;
  --text-2xl: 2rem;
  --text-3xl: 2.5rem;
  --text-4xl: 3.25rem;
  --text-5xl: 4.5rem;
  --text-6xl: 6rem;
  --text-ghost: clamp(5rem, 10.4vw, 12.5rem);
  --tracking-ghost: -0.04em;
  --tracking-label: 0.05em;

  /* 圆角：默认直角，另有两档微圆角 */
  --radius-xs: 2px;
  --radius-sm: 4px;
  --radius-md: 6px;

  /* 阴影：零偏移的环境阴影 */
  --shadow-xs: 0 0 4px rgb(0 0 0 / 0.25);
  --shadow-sm: 0 0 8px rgb(0 0 0 / 0.25);
  --shadow-md: 0 0 12px rgb(0 0 0 / 0.3);
  --shadow-lg: 0 0 24px rgb(0 0 0 / 0.3);
  --shadow-rail: 0 0 12px rgb(0 0 0 / 0.1);

  /* 缓动与时长 */
  --ease-standard: ease;
  --ease-emphasis: ease-in-out;
  --ease-exit: ease-out;
  --duration-instant: 50ms;
  --duration-fast: 200ms;
  --duration-base: 300ms;
  --duration-slow: 400ms;
  --duration-slower: 600ms;
  --motion-shift: 4px;
  --motion-shift-lg: 8px;

  /* 母题尺寸 */
  --cut-sm: 6px;
  --cut-md: 10px;
  --cut-lg: 16px;
  --hatch-size: 6px;

  /* 层叠 */
  --z-float: 100;
  --z-nav: 200;
  --z-overlay: 300;
  --z-toast: 400;
  --z-loader: 1000;
}

/* --------------------------------------------------------------------------
   2. 基础重置与正文
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--ef-surface);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ef-surface);
  color: var(--ef-ink);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, p, dl, dd, figure, blockquote {
  margin: 0;
}

ul, ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

img, svg, video {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

[hidden] {
  display: none !important;
}

::selection {
  background: var(--ef-action);
  color: var(--ef-on-action);
}

/* 焦点环：全站一套 */
:focus-visible {
  outline: 2px solid var(--ef-focus);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* 底纹层：一个视口选一种 */
.ef-dot-grid {
  background-image: radial-gradient(
    circle,
    var(--dot-color, color-mix(in srgb, var(--ef-ink) 10%, transparent))
      var(--dot-size, 1px),
    transparent calc(var(--dot-size, 1px) + 0.5px)
  );
  background-size: var(--dot-gap, 16px) var(--dot-gap, 16px);
}
.ef-blueprint-grid {
  --_g: var(--grid-color, color-mix(in srgb, var(--ef-ink) 6%, transparent));
  background-image:
    linear-gradient(var(--_g) 1px, transparent 1px),
    linear-gradient(90deg, var(--_g) 1px, transparent 1px);
  background-size: var(--grid-gap, 72px) var(--grid-gap, 72px);
}

/* --------------------------------------------------------------------------
   4. 动画关键帧（theme.css 同名）
   -------------------------------------------------------------------------- */
@keyframes ef-flicker {
  0% { opacity: 0; }
  10% { opacity: 0.5; }
  11% { opacity: 0; }
  20% { opacity: 0.5; }
  21% { opacity: 0; }
  40% { opacity: 0.5; }
  41% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes ef-fill {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}
@keyframes ef-scroll-hint {
  0% { transform: translateY(0); opacity: 0; }
  60% { opacity: 1; }
  80% { transform: translateY(1.5rem); opacity: 0; }
  100% { transform: translateY(0); opacity: 0; }
}
@keyframes ef-breathe {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}
@keyframes ef-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@keyframes ef-spin {
  to { transform: rotate(360deg); }
}
@keyframes ef-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}
@keyframes ef-slide-in {
  from { transform: translateX(var(--motion-shift)); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
@keyframes ef-turn-in {
  from { transform: rotate(-90deg); opacity: 0; }
  to { transform: rotate(0); opacity: 1; }
}
@keyframes ef-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes ef-indeterminate {
  from { transform: translateX(0); }
  to { transform: translateX(200%); }
}
@keyframes ef-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}
@keyframes ef-bracket-in {
  from { transform: scale(1.15); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}
@keyframes ef-shift-in {
  from { transform: translateY(var(--motion-shift)); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
@keyframes ef-wipe-in {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 0 0 0); }
}
/* ==========================================================================
   01 · 文字 / 按钮 / 标记
   --------------------------------------------------------------------------
   依据（唯一事实来源）：
     /tmp/endfield-ui/packages/ui/src/components/button/Button.tsx
     /tmp/endfield-ui/packages/ui/src/components/button/ButtonGroup.tsx
     /tmp/endfield-ui/packages/ui/src/components/tag/Tag.tsx
     /tmp/endfield-ui/packages/ui/src/components/tag/TagPair.tsx
     /tmp/endfield-ui/packages/ui/src/components/badge/Badge.tsx
     /tmp/endfield-ui/packages/ui/src/components/kbd/Kbd.tsx
     /tmp/endfield-ui/packages/ui/src/components/bracket-title/BracketTitle.tsx
     /tmp/endfield-ui/packages/ui/src/components/section-title/SectionTitle.tsx
     /tmp/endfield-ui/packages/ui/src/components/ghost-text/GhostText.tsx
     /tmp/endfield-ui/packages/ui/src/components/link-element/LinkElement.tsx
     /tmp/endfield-ui/packages/ui/src/lib/focus-ring.ts

   约定：颜色只用 var(--ef-*) / var(--color-*)；切角用 .ef-cut-*，斜纹用
   .ef-hatch（--mid/--fine），点阵用 .ef-dot-grid，镂空字用 .ef-ghost-hatch /
   .ef-ghost-outline——本文件不再另造一套母题类。

   注意加载顺序：本文件必须排在 endfield.css 之后（组件规则要能盖住工具类）。
   ========================================================================== */

/* --------------------------------------------------------------------------
   Button —— components/button/Button.tsx
   禁忌：一屏只放一个 action（黄），danger 只给真正不可逆的操作；
        不给按钮切角、胶囊、渐变，也不拿 text 变体当页面的主要行动。
   -------------------------------------------------------------------------- */

/* 基础 = control 变体：深灰底浅字、4px 微圆角、悬停只换底色 */
.ef-btn {
  /* 尺寸变量（默认 md 档） */
  --ef-btn-height: 2.5rem; /* h-10 */
  --ef-btn-gap: 0.5rem; /* gap-2 */
  --ef-btn-font: var(--text-base); /* text-base */
  --ef-btn-icon: 1rem; /* inlineIconSize md = 16 */
  --ef-btn-icon-only: 1.25rem; /* 图标按钮 md = 20（button.md 图标按钮表） */
  --ef-btn-divider-h: 0.875rem; /* h-3.5 */
  --ef-btn-pad-x: 1.5rem; /* px-6 */
  --ef-btn-pad-l: 1.5rem; /* pl-6 */
  --ef-btn-pad-r: 2.75rem; /* pr-11 */
  --ef-btn-pad-back: 1.25rem; /* px-5 */
  --ef-btn-pad-start: var(--ef-btn-pad-x); /* 图标槽离左缘的距离 */
  --ef-btn-min-w: 6rem; /* min-w-24 */
  --ef-btn-adiv-r: 1.125rem; /* right-4.5 */
  --ef-btn-adiv-w: 3px; /* w-[3px] */
  --ef-btn-spin-l: 0.5rem; /* left-2 */
  --ef-btn-spin-s: 0.5rem; /* size-2 */
  --ef-btn-spin-r: 4px; /* text 变体：(16 - 8) / 2 */
  --ef-btn-aspi-r: 5px; /* right-[5px] */
  --ef-btn-aspi-s: 0.5rem; /* size-2 */
  /* 颜色变量（默认 control 档） */
  --ef-btn-bg: var(--ef-button);
  --ef-btn-fg: var(--ef-on-button);
  --ef-btn-bg-hover: var(--ef-button-hover); /* hover:bg-button-hover */
  --ef-btn-fg-hover: var(--color-neutral-0); /* hover:text-white */
  --ef-btn-bg-active: var(--ef-button-pressed); /* active:bg-button-pressed */
  --ef-btn-fg-active: var(--color-neutral-0); /* active:text-white */
  --ef-btn-shadow: var(--shadow-xs); /* shadow-xs */
  position: relative; /* relative */
  display: inline-flex; /* inline-flex */
  flex-shrink: 0; /* shrink-0 */
  align-items: center; /* items-center */
  justify-content: center; /* justify-center */
  height: var(--ef-btn-height);
  min-width: var(--ef-btn-min-w);
  padding-inline: var(--ef-btn-pad-x);
  border-radius: var(--radius-sm); /* rounded-sm */
  background-color: var(--ef-btn-bg);
  box-shadow: var(--ef-btn-shadow);
  color: var(--ef-btn-fg);
  font-size: var(--ef-btn-font);
  font-weight: 500; /* font-medium */
  line-height: 1; /* leading-none */
  white-space: nowrap; /* whitespace-nowrap */
  vertical-align: middle;
  cursor: pointer;
  user-select: none; /* select-none */
  transition: color var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    text-decoration-color var(--duration-fast) var(--ease-standard);
}

/* sm：32px / 14px / 16px 横排留白 */
.ef-btn--sm {
  --ef-btn-height: 2rem; /* h-8 */
  --ef-btn-gap: 0.375rem; /* gap-1.5 */
  --ef-btn-font: var(--text-sm); /* text-sm */
  --ef-btn-icon: 14px; /* inlineIconSize sm */
  --ef-btn-icon-only: 1rem; /* 图标按钮 sm = 16 */
  --ef-btn-divider-h: 0.75rem; /* h-3 */
  --ef-btn-pad-x: 1rem; /* px-4 */
  --ef-btn-pad-l: 1rem; /* pl-4 */
  --ef-btn-pad-r: 2rem; /* pr-8 */
  --ef-btn-pad-back: 0.875rem; /* px-3.5 */
  --ef-btn-min-w: 4rem; /* min-w-16 */
  --ef-btn-adiv-r: 0.875rem; /* right-3.5 */
  --ef-btn-adiv-w: 2px; /* w-0.5 */
  --ef-btn-spin-l: 5px; /* left-[5px] */
  --ef-btn-spin-s: 0.375rem; /* size-1.5 */
  --ef-btn-spin-r: 3px; /* (14 - 8) / 2 */
  --ef-btn-aspi-r: 3px; /* right-[3px] */
  --ef-btn-aspi-s: 0.5rem; /* size-2 */
}

/* 只有图标的方形按钮：与同档文字按钮等高，左右不留白 */
.ef-btn--icon {
  --ef-btn-icon: var(--ef-btn-icon-only);
  width: var(--ef-btn-height);
  min-width: var(--ef-btn-height);
  padding-inline: 0;
}

/* control：与基类同值，显式写出来和其它变体对照 */
.ef-btn--control {
  background-color: var(--ef-button); /* bg-button */
  color: var(--ef-on-button); /* text-on-button */
  box-shadow: var(--shadow-xs); /* shadow-xs */
}

/* action：信号黄底墨字，右侧一条分隔线；悬停只加深分隔线 */
.ef-btn--action {
  --ef-btn-bg: var(--ef-action); /* bg-action */
  --ef-btn-fg: var(--ef-on-action); /* text-on-action */
  --ef-btn-bg-hover: var(--ef-action); /* 悬停底色不变 */
  --ef-btn-fg-hover: var(--ef-on-action);
  --ef-btn-bg-active: var(--ef-action-pressed); /* active:bg-action-pressed */
  --ef-btn-fg-active: var(--ef-on-action);
  --ef-btn-shadow: var(--shadow-md); /* shadow-md */
  --ef-btn-adiv: color-mix(in srgb, var(--ef-on-action) 10%, transparent);
  --ef-btn-adiv-hover: color-mix(in srgb, var(--ef-on-action) 15%, transparent);
  --ef-btn-adiv-active: color-mix(in srgb, var(--ef-on-action) 20%, transparent);
  --ef-btn-pad-start: var(--ef-btn-pad-l);
  padding-inline: var(--ef-btn-pad-l) var(--ef-btn-pad-r); /* pl-* pr-* */
}

/* light：浅灰底 + 一层点阵（点阵由 .ef-dot-grid 画，变量给在这里） */
.ef-btn--light {
  --ef-btn-bg: var(--color-neutral-100); /* bg-neutral-100 */
  --ef-btn-fg: var(--color-neutral-900); /* text-neutral-900 */
  --ef-btn-bg-hover: var(--color-neutral-50); /* hover:bg-neutral-50 */
  --ef-btn-fg-hover: var(--color-neutral-900);
  --ef-btn-bg-active: var(--color-neutral-200); /* active:bg-neutral-200 */
  --ef-btn-fg-active: var(--color-neutral-900);
  --ef-btn-shadow: var(--shadow-xs); /* shadow-xs */
  --dot-color: color-mix(in srgb, var(--color-neutral-900) 8%, transparent); /* [--dot-color:…] */
  --dot-gap: 4px; /* [--dot-gap:4px] */
  --dot-size: 0.5px; /* [--dot-size:0.5px] */
}

/* text：无底、下划线、末尾斜箭头；高度由内容撑开 */
.ef-btn--text {
  --ef-btn-fg: var(--ef-ink); /* text-ink */
  --ef-btn-bg-hover: transparent;
  --ef-btn-fg-hover: var(--ef-ink);
  --ef-btn-bg-active: transparent;
  --ef-btn-fg-active: var(--ef-ink-secondary); /* active:text-ink-secondary */
  height: auto; /* h-auto */
  min-width: 0;
  padding: 0.25rem 0; /* py-1 */
  border-bottom: 1px solid currentColor; /* border-b border-current */
  border-radius: 0;
  background-color: transparent;
  box-shadow: none;
}

/* 撑满容器宽度 */
.ef-btn--block {
  display: flex; /* flex */
  width: 100%; /* w-full */
}

/* loadingText：两段文字叠在同一格里，按较宽的那段定宽，宽度不跳 */
.ef-btn [data-label-stack] {
  display: grid; /* grid */
  justify-items: center; /* justify-items-center */
}

.ef-btn [data-label-stack] > [data-label] {
  grid-area: 1 / 1; /* col-start-1 row-start-1 */
}

.ef-btn [data-label][aria-hidden="true"] {
  visibility: hidden; /* invisible（不显示，但仍占宽） */
}

/* 图标槽：只有图标的槽（--start/--end）自带那条 1px 细竖线 */
.ef-btn__icon {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  gap: var(--ef-btn-gap); /* 线的两边各一份 gap */
  height: var(--ef-btn-icon);
}

.ef-btn__icon > svg {
  width: var(--ef-btn-icon); /* inlineIconSize */
  height: var(--ef-btn-icon);
}

.ef-btn__icon--start::after {
  content: ""; /* 组件源码里的 [data-button-divider] */
  flex-shrink: 0; /* shrink-0 */
  width: 1px; /* w-px */
  height: var(--ef-btn-divider-h); /* h-3 / h-3.5 / h-4 */
  background-color: currentColor; /* bg-current */
  opacity: 0.3; /* opacity-30 */
}

/* 标签在 React 里是独立子元素，button 的 gap 会落在线与文字之间；
   纯 HTML 里标签是裸文本节点，flex 的 gap 不作用在匿名盒上，所以把这份间距补在槽上，
   和源码的 [start][divider][label] 三段间隔一致 */
.ef-btn__icon--start {
  margin-inline-end: var(--ef-btn-gap);
}

.ef-btn__icon--end {
  margin-inline-start: var(--ef-btn-gap);
}

/* text 变体靠下划线成形，不加竖线 */
.ef-btn--text .ef-btn__icon--start::after {
  content: none;
}

.ef-btn__icon--end {
  transition: translate var(--duration-base) var(--ease-standard); /* transition-[translate] */
}

/* action 右侧的分隔线：写在伪元素上，不占流内位置 */
.ef-btn--action::after {
  content: "";
  position: absolute; /* absolute */
  top: 17.5%; /* top-[17.5%] */
  right: var(--ef-btn-adiv-r);
  width: var(--ef-btn-adiv-w);
  height: 65%; /* h-[65%] */
  background-color: var(--ef-btn-adiv);
  pointer-events: none;
  transition: background-color var(--duration-fast) var(--ease-standard);
}

/* 悬停：只在真能悬停的设备上换色（Tailwind v4 的 hover: 默认如此） */
@media (hover: hover) {
  .ef-btn:hover:not([disabled]):not([aria-disabled="true"]) {
    background-color: var(--ef-btn-bg-hover);
    color: var(--ef-btn-fg-hover);
  }

  .ef-btn--action:hover:not([disabled]):not([aria-disabled="true"])::after {
    background-color: var(--ef-btn-adiv-hover); /* group-hover:bg-on-action/15 */
  }

  .ef-btn--text:hover:not([disabled]):not([aria-disabled="true"]) .ef-btn__icon--end {
    translate: 0.125rem 0.125rem; /* group-hover:translate-x-0.5 translate-y-0.5 */
  }
}

/* 按下 */
.ef-btn:active:not([disabled]):not([aria-disabled="true"]) {
  background-color: var(--ef-btn-bg-active);
  color: var(--ef-btn-fg-active);
}

.ef-btn--action:active:not([disabled]):not([aria-disabled="true"])::after {
  background-color: var(--ef-btn-adiv-active); /* group-active:bg-on-action/20 */
}

/* 键盘聚焦：2px 环、间隔 2px（focus-ring.ts） */
.ef-btn:focus-visible {
  outline: 2px solid var(--ef-focus); /* focus-visible:outline-2 */
  outline-offset: 2px; /* focus-visible:outline-offset-2 */
}

/* 禁用：带底色的变体统一换成灰底灰字，不保留阴影 */
.ef-btn[disabled]:not(.ef-btn--text),
.ef-btn[aria-disabled="true"]:not([aria-busy="true"]):not(.ef-btn--text) {
  background-color: var(--ef-disabled); /* bg-disabled */
  color: var(--ef-on-disabled); /* text-on-disabled */
  box-shadow: none;
}

.ef-btn[disabled],
.ef-btn[aria-disabled="true"]:not([aria-busy="true"]) {
  cursor: not-allowed; /* cursor-not-allowed */
}

/* text 的禁用只换字色，下划线跟着 currentColor 一起变 */
.ef-btn--text[disabled],
.ef-btn--text[aria-disabled="true"]:not([aria-busy="true"]) {
  background-color: transparent;
  box-shadow: none;
  color: var(--ef-ink-disabled); /* text-ink-disabled */
}

.ef-btn--action[disabled]::after,
.ef-btn--action[aria-disabled="true"]:not([aria-busy="true"])::after {
  background-color: color-mix(in srgb, var(--ef-on-disabled) 20%, transparent); /* bg-on-disabled/20 */
}

/* 加载中：不是禁用，颜色照旧；只是不再响应点击 */
.ef-btn[aria-busy="true"] {
  pointer-events: none; /* pointer-events-none */
}

/* 旋转的小方块：占一个已经有的位置，宽度不变 */
.ef-btn[aria-busy="true"]::before {
  content: ""; /* Spinner（components/spinner/Spinner.tsx） */
  position: absolute;
  top: 50%;
  left: var(--ef-btn-spin-l);
  width: var(--ef-btn-spin-s);
  height: var(--ef-btn-spin-s);
  translate: 0 -50%; /* -translate-y-1/2 */
  background-color: currentColor; /* bg-current */
  animation: ef-spin 1s linear infinite; /* animate-spin */
}

/* 有起始图标时，方块换掉图标、留在图标槽里（action 除外：它在分隔线右边） */
.ef-btn:not(.ef-btn--action)[aria-busy="true"]:has(> .ef-btn__icon--start)::before {
  left: calc(var(--ef-btn-pad-start) + (var(--ef-btn-icon) - 0.5rem) / 2);
  width: 0.5rem; /* Spinner className="size-2" */
  height: 0.5rem;
}

.ef-btn:not(.ef-btn--action)[aria-busy="true"]:has(> .ef-btn__icon--start) > .ef-btn__icon--start > svg {
  visibility: hidden;
}

/* action：方块在分隔线右侧的窄条里，图标照旧 */
.ef-btn--action[aria-busy="true"]::before {
  left: auto;
  right: var(--ef-btn-aspi-r);
  width: var(--ef-btn-aspi-s);
  height: var(--ef-btn-aspi-s);
}

/* text：方块换掉末尾的斜箭头 */
.ef-btn--text[aria-busy="true"]::before {
  left: auto;
  right: var(--ef-btn-spin-r);
  width: 0.5rem;
  height: 0.5rem;
}

.ef-btn--text[aria-busy="true"] > .ef-btn__icon--end {
  visibility: hidden;
}

/* 图标按钮：方块居中，图标隐去 */
.ef-btn--icon[aria-busy="true"]::before {
  left: 50%;
  margin-left: -0.25rem;
}

.ef-btn--icon[aria-busy="true"] > * {
  visibility: hidden;
}

/* 减少动态效果：停成一个 45° 的菱形（motion-reduce:rotate-45） */
@media (prefers-reduced-motion: reduce) {
  .ef-btn[aria-busy="true"]::before {
    animation: none;
    rotate: 45deg;
  }
}

/* 高对比模式：底色被系统换掉，补一圈线，记号改用系统字色 */
@media (forced-colors: active) {
  .ef-btn:not(.ef-btn--text) {
    border: 1px solid ButtonText;
  }

  .ef-btn__icon--start::after {
    background-color: ButtonText;
    opacity: 1; /* forced-colors:opacity-100 */
  }

  .ef-btn--action::after {
    background-color: ButtonText;
  }

  .ef-btn[disabled]:not(.ef-btn--text) {
    border-color: GrayText;
    color: GrayText;
  }
}

/* --------------------------------------------------------------------------
   Tag —— components/tag/Tag.tsx
   禁忌：直角标签只装"归档性的、不变的信息"；不给标签加圆角、渐变或图标。
   -------------------------------------------------------------------------- */

.ef-tag {
  display: inline-flex; /* inline-flex */
  flex-shrink: 0; /* shrink-0 */
  align-items: center; /* items-center */
  height: 1.75rem; /* h-7 */
  padding-inline: 0.625rem; /* px-2.5 */
  border-radius: var(--radius-xs); /* rounded-xs（solid 才有） */
  background-color: var(--ef-control); /* bg-control */
  color: var(--ef-on-control); /* text-on-control */
  font-size: var(--text-sm); /* text-sm */
  font-weight: 500; /* font-medium */
  line-height: 1; /* leading-none */
  white-space: nowrap; /* whitespace-nowrap */
}

.ef-tag[data-size="sm"] {
  height: 1.25rem; /* h-5 */
  padding-inline: 0.375rem; /* px-1.5 */
  font-size: var(--text-xs); /* text-xs */
}

/* inverse：墨底白字（日期块、名值对里的"名"） */
.ef-tag--inverse {
  border-radius: 0;
  background-color: var(--ef-surface-inverse); /* bg-surface-inverse */
  color: var(--ef-ink-inverse); /* text-ink-inverse */
}

/* outline：白底细边，直角（活动类型、分类） */
.ef-tag[data-variant="outline"] {
  border: 1px solid var(--ef-line-strong); /* border border-line-strong */
  border-radius: 0;
  background-color: var(--ef-surface); /* bg-surface */
  color: var(--ef-ink); /* text-ink */
}

/* muted：浅灰底墨字（名值对里的"值"） */
.ef-tag[data-variant="muted"] {
  border-radius: 0;
  background-color: var(--ef-surface-sunken); /* bg-surface-sunken */
  color: var(--ef-ink); /* text-ink */
}

/* gain：荧光黄绿底墨字，两个主题下相同 */
.ef-tag[data-variant="gain"] {
  border-radius: 0;
  background-color: var(--color-gain); /* bg-gain */
  color: var(--color-neutral-900); /* text-neutral-900 */
}

/* 左上角的小红角：6px 三角，标出一组日期块里要留意的那一个 */
.ef-tag[data-marked] {
  position: relative; /* relative */
}

.ef-tag[data-marked]::before {
  content: ""; /* before:content-[''] */
  position: absolute; /* before:absolute */
  top: 0; /* before:top-0 */
  left: 0; /* before:left-0 */
  width: 0.375rem; /* before:size-1.5 */
  height: 0.375rem;
  background-color: var(--color-alert); /* before:bg-alert */
  clip-path: polygon(0 0, 100% 0, 0 100%); /* before:[clip-path:…] */
}

/* --------------------------------------------------------------------------
   TagPair —— components/tag/TagPair.tsx
   禁忌：两半必须紧贴（中间没有缝也没有分隔线）；多组之间由使用方留 8px。
   -------------------------------------------------------------------------- */

.ef-tag-pair {
  display: inline-flex; /* inline-flex */
  flex-shrink: 0; /* shrink-0 */
}

/* --------------------------------------------------------------------------
   Micro —— 技术微标签（ScrollHint / Schedule 里的 font-tech text-micro 行）
   禁忌：只写真实的车次、栏目名、版本号；不要编造伪十六进制或假坐标。
   -------------------------------------------------------------------------- */

/* 字重不写死：ItemSlot 用 font-bold，ScrollHint 用正文字重，交给使用方 */
.ef-micro {
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-micro); /* text-micro */
  letter-spacing: var(--tracking-label); /* tracking-label */
  line-height: 1; /* --text-micro--line-height */
  text-transform: uppercase; /* uppercase */
  font-variant-numeric: tabular-nums; /* tabular-nums（Schedule 的微标） */
  color: var(--ef-ink-tertiary); /* text-ink-tertiary */
}

/* --------------------------------------------------------------------------
   BracketTitle —— components/bracket-title/BracketTitle.tsx
   禁忌：只给人物、地点、物品这类"被归档的条目"用；普通分节标题用
        .ef-section-title，按钮文字和正文强调都不加方括号。
   -------------------------------------------------------------------------- */

.ef-bracket-title {
  margin: 0;
  color: var(--ef-ink); /* text-ink */
  font-size: var(--text-4xl); /* text-4xl */
  font-weight: 700; /* font-bold */
  line-height: 1; /* --text-4xl--line-height */
  overflow-wrap: anywhere; /* wrap-anywhere */
}

/* 括号与名称排在同一行内：名称换行时右括号跟着最后一个字走 */
.ef-bracket-title::before {
  content: "[";
  margin-right: 0.4em; /* mr-[0.4em] */
  color: var(--ef-ink-tertiary); /* text-ink-tertiary */
  font-weight: 500; /* font-medium */
}

.ef-bracket-title::after {
  content: "]";
  margin-left: 0.4em; /* ml-[0.4em] */
  color: var(--ef-ink-tertiary); /* text-ink-tertiary */
  font-weight: 500; /* font-medium */
}

/* 入场（opt-in）：括号先淡入，名称晚 100ms；只淡入、不位移 */
.ef-bracket-title--in::before,
.ef-bracket-title--in::after {
  animation: ef-fade-in var(--duration-base) var(--ease-emphasis) both; /* animate-fade-in */
}

.ef-bracket-title--in {
  animation: ef-bracket-name-in var(--duration-base) var(--ease-emphasis) 100ms both; /* [animation-delay:100ms] */
}

@keyframes ef-bracket-name-in {
  from { color: transparent; }
  to { color: var(--ef-ink); }
}

/* --------------------------------------------------------------------------
   SectionTitle —— components/section-title/SectionTitle.tsx
   禁忌：一个版块一个分节标题，一页里别每个标题都带滑块和入场动画；
        色带（band）一页最多一到两条，且上面只放墨色文字和线稿。
   -------------------------------------------------------------------------- */

.ef-section-title {
  display: block;
}

.ef-section-title [data-meta] {
  margin: 0 0 0.5rem; /* mb-2 */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-xs); /* text-xs */
  line-height: 1.25; /* --text-xs--line-height */
}

.ef-section-title [data-line] {
  display: flex; /* flex */
  flex-wrap: wrap; /* flex-wrap */
  align-items: center; /* items-center */
  column-gap: 0.5rem; /* gap-x-2 */
}

/* 灰色滑块：6.5 × 2rem，箭头贴右端 */
.ef-section-title [data-slider] {
  position: relative; /* relative */
  display: block; /* block */
  flex-shrink: 0; /* shrink-0 */
  width: 6.5rem; /* w-26 */
  height: 2rem; /* h-8 */
  overflow: hidden; /* overflow-hidden */
}

.ef-section-title [data-slider] > span {
  position: absolute; /* absolute */
  inset: 0; /* inset-0 */
  display: flex;
  align-items: center; /* items-center */
  justify-content: flex-end; /* justify-end */
  padding-right: 0.375rem; /* pr-1.5 */
  background-color: var(--color-neutral-300); /* bg-neutral-300 */
  color: var(--color-neutral-900); /* text-neutral-900 */
}

.ef-section-title [data-slider] svg {
  width: 20px; /* ArrowCorner size={20} */
  height: 20px;
}

.ef-section-title [data-latin] {
  max-width: 100%; /* max-w-full */
  margin: 0;
  color: var(--ef-ink); /* text-ink */
  font-family: var(--font-latin); /* font-latin */
  font-size: var(--text-xl); /* text-xl */
  font-weight: 500; /* font-medium */
  line-height: 2rem; /* leading-8 */
  overflow-wrap: anywhere; /* wrap-anywhere */
  text-transform: uppercase; /* uppercase */
}

.ef-section-title [data-title] {
  margin: 0.25rem 0 0; /* mt-1 */
  color: var(--ef-ink); /* text-ink */
  font-size: var(--text-2xl); /* text-2xl */
  font-weight: 700; /* font-bold */
  line-height: 1.1; /* --text-2xl--line-height */
  overflow-wrap: anywhere; /* wrap-anywhere */
}

/* 入场（opt-in）：滑块 → 箭头 → 文字，每步错开 100ms */
.ef-section-title[data-in] [data-slider] > span {
  animation: ef-section-slide-in var(--duration-base) var(--ease-exit) both; /* animate-slide-in */
}

.ef-section-title[data-in] [data-slider] svg {
  animation: ef-turn-in var(--duration-base) var(--ease-exit) 100ms both; /* animate-turn-in */
}

.ef-section-title[data-in] [data-meta],
.ef-section-title[data-in] [data-latin],
.ef-section-title[data-in] [data-title] {
  animation: ef-fade-in var(--duration-base) var(--ease-exit) 200ms both; /* animate-fade-in */
}

@keyframes ef-section-slide-in {
  from { translate: -100% 0; }
  to { translate: 0 0; }
}

/* plain：只留标题和左侧一条短竖条（文档、设置这类工具页） */
.ef-section-title[data-variant="plain"] {
  display: flex; /* flex */
  align-items: center; /* items-center */
  gap: 0.75rem; /* gap-3 */
}

.ef-section-title[data-variant="plain"]::before {
  content: ""; /* 源码里的 h-6 w-1 bg-ink 竖条 */
  flex-shrink: 0; /* shrink-0 */
  width: 0.25rem; /* w-1 */
  height: 1.5rem; /* h-6 */
  background-color: var(--ef-ink); /* bg-ink */
}

.ef-section-title[data-variant="plain"] [data-title] {
  margin: 0;
  font-size: var(--text-2xl); /* text-2xl */
  font-weight: 700;
  line-height: 1.1;
}

/* band：通栏信号黄；字号与留白跟着色带自身宽度分档（容器查询） */
.ef-section-title[data-variant="band"] {
  container-type: inline-size; /* @container */
  background-color: var(--ef-action); /* bg-action */
  color: var(--ef-on-action); /* text-on-action */
}

.ef-section-title[data-variant="band"] > div {
  display: flex;
  align-items: center; /* items-center */
  gap: 1rem; /* gap-4 */
  min-height: 4rem; /* min-h-16 */
  padding: 1rem; /* px-4 py-4 */
}

.ef-section-title[data-variant="band"] [data-subtitle] {
  margin: 0;
  font-family: var(--font-latin); /* font-latin */
  font-size: var(--text-base); /* text-base */
  font-weight: 300; /* font-light */
  line-height: 1.25; /* leading-tight */
  overflow-wrap: anywhere; /* wrap-anywhere */
  text-transform: uppercase;
}

.ef-section-title[data-variant="band"] [data-title] {
  margin: 0;
  font-family: var(--font-latin); /* font-latin */
  font-size: var(--text-xl); /* text-xl */
  font-weight: 500; /* font-medium */
  line-height: 1.1;
  overflow-wrap: anywhere;
  text-transform: uppercase;
}

.ef-section-title[data-variant="band"] [data-illustration] {
  display: none; /* hidden */
  flex-shrink: 0; /* shrink-0 */
}

@container (min-width: 20rem) {
  .ef-section-title[data-variant="band"] [data-title] {
    font-size: var(--text-2xl); /* @xs:text-2xl */
  }
}

@container (min-width: 24rem) {
  .ef-section-title[data-variant="band"] [data-illustration] {
    display: block; /* @sm:block */
  }
}

@container (min-width: 28rem) {
  .ef-section-title[data-variant="band"] > div {
    min-height: 6rem; /* @md:min-h-24 */
    gap: 1.5rem; /* @md:gap-6 */
    padding-inline: 2.5rem; /* @md:px-10 */
  }

  .ef-section-title[data-variant="band"] [data-subtitle] {
    font-size: var(--text-lg); /* @md:text-lg */
  }

  .ef-section-title[data-variant="band"] [data-title] {
    font-size: var(--text-3xl); /* @md:text-3xl */
  }
}

/* side：竖排侧签；窄屏下回到横排 */
.ef-section-title[data-variant="side"] {
  display: flex; /* flex */
  flex-wrap: wrap; /* flex-wrap */
  align-items: center; /* items-center */
  gap: 0.75rem; /* gap-3 */
  padding: 0.75rem 1rem; /* px-4 py-3 */
  background-color: var(--ef-action); /* tone=accent：bg-action */
  color: var(--ef-on-action); /* text-on-action */
}

.ef-section-title[data-variant="side"][data-tone="muted"] {
  background-color: var(--ef-surface-muted); /* bg-surface-muted */
  color: var(--ef-ink); /* text-ink */
}

.ef-section-title[data-variant="side"] [data-title] {
  margin: 0;
  font-size: var(--text-sm); /* text-sm */
  font-weight: 500; /* font-medium */
  line-height: 1.4;
}

.ef-section-title[data-variant="side"] [data-latin] {
  max-width: 100%; /* max-w-full */
  margin: 0;
  font-family: var(--font-latin); /* font-latin */
  font-size: var(--text-2xl); /* text-2xl */
  font-weight: 500;
  line-height: 1.1;
  overflow-wrap: anywhere;
  text-transform: uppercase;
}

@media (min-width: 48rem) {
  .ef-section-title[data-variant="side"] {
    align-self: stretch; /* md:self-stretch */
    padding: 1.5rem 0.75rem; /* md:px-3 md:py-6 */
    writing-mode: vertical-rl; /* md:[writing-mode:vertical-rl] */
  }
}
/* ==========================================================================
   02 —— 面、表格、导航
   --------------------------------------------------------------------------
   依据：icaruszezen/endfield-ui（MIT）
     packages/ui/src/components/{panel,media-card,table,list,stat,tabs,
       page-header,breadcrumb,pagination,side-rail,top-bar,empty-state}
     packages/ui/src/lib/focus-ring.ts
   约定：颜色只走 var(--ef-*) / var(--color-*)；尺寸取自源码 Tailwind 类，
   行尾注释标来源。亮色主题一套；未做 data-theme 局部反转，反转块直接取
   --ef-surface-inverse / --ef-ink-inverse 这对令牌。
   ========================================================================== */

/* --------------------------------------------------------------------------
   Panel —— 面
   来源：components/panel/Panel.tsx
   禁忌：围住一组信息的容器，不要面板套面板（Panel.tsx 顶部注释）。
   -------------------------------------------------------------------------- */

.ef-panel {
  background: var(--ef-surface-raised); /* bg-surface-raised */
  color: var(--ef-ink); /* text-ink */
  border: 1px solid var(--ef-line); /* border border-line */
}

/* 标题带（默认 band）：反转的一块，左端一条竖条 */
.ef-panel__head {
  position: relative; /* relative */
  display: flex; /* flex */
  align-items: center; /* items-center */
  justify-content: space-between; /* justify-between */
  gap: 0.75rem; /* gap-3 */
  padding-inline: 1rem; /* px-4 */
  min-height: 2rem; /* min-h-8 */
  font-size: var(--text-sm); /* text-sm */
  font-weight: 500; /* font-medium */
  background: var(--ef-surface-inverse); /* bg-surface @ data-theme="inverse" */
  color: var(--ef-ink-inverse); /* text-ink @ data-theme="inverse" */
}

.ef-panel__head::before {
  content: ""; /* 组件里是一个 aria-hidden <span> */
  position: absolute; /* absolute */
  top: 50%; /* top-1/2 */
  left: 0; /* left-0 */
  width: 0.25rem; /* w-1 */
  height: 1rem; /* h-4 */
  transform: translateY(-50%); /* -translate-y-1/2 */
  background: var(--ef-action); /* bg-accent-ink @ inverse = accent-ink-inverse */
}

.ef-panel__head > :first-child {
  min-width: 0; /* min-w-0 */
  overflow: hidden; /* truncate */
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ef-panel__extra {
  display: flex; /* flex */
  flex-shrink: 0; /* shrink-0 */
  align-items: center; /* items-center */
  gap: 0.5rem; /* gap-2 */
}

.ef-panel__body {
  padding: 1rem; /* p-4 */
}

/* PanelRows：属性行的容器，里面只放 .ef-panel__row */
.ef-panel__rows {
  padding-inline: 1rem; /* px-4 */
}

.ef-panel__row {
  display: flex; /* flex */
  align-items: baseline; /* items-baseline */
  justify-content: space-between; /* justify-between */
  gap: 1rem; /* gap-4 */
  padding-block: 0.625rem; /* py-2.5 */
  border-bottom: 1px solid var(--ef-line); /* border-b border-line */
}

.ef-panel__row:last-child {
  border-bottom: 0; /* last:border-b-0 */
}

.ef-panel__row dt {
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
}

.ef-panel__row dd {
  margin: 0; /* dd 默认缩进 */
  text-align: right; /* text-right */
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-sm); /* text-sm */
  font-weight: 700; /* font-bold */
  color: var(--ef-ink); /* text-ink */
  font-variant-numeric: tabular-nums; /* tabular-nums */
}

/* --------------------------------------------------------------------------
   Table —— 表格
   来源：components/table/Table.tsx
   禁忌：整行不可点：要进详情把名称写成链接，要勾选在第一列放复选框
   （Table.tsx 文档注释）。单元格默认不折行，窄屏靠 .ef-table--scroll 滚。
   -------------------------------------------------------------------------- */

/* 横向/纵向滚动的那一层。真的溢出时才由 JS 补 role="region" tabindex="0"，
   静态页里直接给 tabindex="0" 也无妨 */
.ef-table--scroll {
  overflow: auto; /* overflow-auto */
}

.ef-table--scroll:focus-visible {
  outline: 2px solid var(--ef-focus); /* focusRingInset */
  outline-offset: -2px;
}

.ef-table--scroll > .ef-table {
  min-width: 100%; /* w-full */
}

.ef-table {
  width: 100%; /* w-full */
  border-collapse: separate; /* border-separate */
  border-spacing: 0; /* border-spacing-0 */
  text-align: left; /* text-left */
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-ink); /* text-ink */
}

/* 表头里排序三角的强调色：band 是反转底，用行动色；muted 用强调墨色 */
.ef-table thead {
  --ef-table-accent: var(--ef-action); /* text-accent-ink @ inverse */
}

.ef-table--muted thead {
  --ef-table-accent: var(--ef-accent-ink); /* text-accent-ink */
}

.ef-table th {
  height: 2rem; /* h-8 */
  font-weight: 500; /* font-medium */
  white-space: nowrap; /* whitespace-nowrap */
}

.ef-table thead th {
  background: var(--ef-surface-inverse); /* bg-surface @ inverse（band，默认） */
  color: var(--ef-ink-inverse); /* text-ink @ inverse */
}

.ef-table--muted thead th {
  background: var(--ef-surface-muted); /* bg-surface-muted */
  color: var(--ef-ink); /* text-ink */
}

.ef-table th,
.ef-table td {
  padding-inline: 0.75rem; /* px-3 */
}

.ef-table th:first-child,
.ef-table td:first-child {
  padding-left: 1rem; /* first:pl-4 */
}

.ef-table th:last-child,
.ef-table td:last-child {
  padding-right: 1rem; /* last:pr-4 */
}

.ef-table td,
.ef-table tbody th {
  height: 2.75rem; /* h-11（md，默认） */
  padding-block: 0.25rem; /* py-1 */
  border-bottom: 1px solid var(--ef-line); /* border-b border-line */
  white-space: nowrap; /* 默认不折行，放不下就横向滚 */
}

.ef-table[data-size="sm"] td,
.ef-table[data-size="sm"] tbody th {
  height: 2.25rem; /* h-9 */
}

.ef-table tbody tr:last-child > * {
  border-bottom: 0; /* [tr:last-child>&]:border-b-0 */
}

.ef-table tbody tr {
  transition: background-color var(--duration-fast) var(--ease-standard); /* transition-colors */
}

.ef-table tbody tr:hover {
  background: color-mix(in srgb, var(--ef-ink) 5%, transparent); /* hover:bg-ink/5 */
}

/* 选中行：浅灰底 + 左缘 4px 行动色条（画在第一格的内阴影上） */
.ef-table tbody tr[aria-selected="true"] {
  background: var(--ef-surface-muted); /* bg-surface-muted */
}

.ef-table tbody tr[aria-selected="true"] > :first-child {
  box-shadow: inset 4px 0 0 var(--ef-action); /* [&>*:first-child]:shadow-[inset_4px_0_0_var(--ef-action)] */
}

/* 数字列：右对齐、等宽 */
.ef-table th[data-numeric],
.ef-table td[data-numeric] {
  text-align: right; /* text-right */
  font-family: var(--font-tech); /* font-tech */
  font-variant-numeric: tabular-nums; /* tabular-nums */
}

/* 窄屏：长文字列允许折行 + 截断（源码里由使用方加 whitespace-normal） */
.ef-table td[data-wrap],
.ef-table tbody th[data-wrap] {
  min-width: 8rem; /* 给折行列一个地板，免得挤成一条 */
  white-space: normal; /* whitespace-normal */
  overflow-wrap: anywhere; /* wrap-anywhere */
}

.ef-table td[data-truncate],
.ef-table tbody th[data-truncate] {
  max-width: 16rem; /* 截断宽度的上限（自定，源码由使用方给列宽） */
  overflow: hidden; /* truncate */
  text-overflow: ellipsis;
}

/* --------------------------------------------------------------------------
   List —— 行列表
   来源：components/list/List.tsx（+ band-style.ts 的画布色）
   禁忌：整行可点时只有一个可点击元素，不要在里面再嵌按钮；左缘黄条只表示
   “选中 / 当前”，不要拿它表示类目（List.tsx 文档注释）。
   -------------------------------------------------------------------------- */

.ef-list {
  color: var(--ef-ink); /* text-ink */
}

.ef-list > li + li {
  border-top: 1px solid var(--ef-line); /* divide-y divide-line */
}

/* 一行：编号 / 标题 / 说明 / 尾部 / 箭头 */
.ef-list__item {
  position: relative; /* relative */
  display: flex; /* flex */
  width: 100%; /* w-full */
  align-items: center; /* items-center */
  gap: 0.75rem; /* gap-3 */
  padding-inline: 1rem; /* px-4 */
  min-height: 3rem; /* min-h-12（md，默认） */
  padding-block: 0.625rem; /* py-2.5 */
  text-align: left; /* text-left */
  color: inherit;
  transition: background-color var(--duration-fast) var(--ease-standard); /* transition-colors */
}

.ef-list__item[data-size="sm"] {
  min-height: 2.5rem; /* min-h-10 */
  padding-block: 0.5rem; /* py-2 */
}

/* 左缘 4px 黄条：入口行悬停时从左边亮出来，选中/当前时常亮 */
.ef-list__item::before {
  content: "";
  position: absolute; /* absolute */
  inset-block: 0; /* inset-y-0 */
  right: auto;
  left: 0; /* left-0 */
  width: 0.25rem; /* w-1 = 4px */
  background: var(--ef-action); /* bg-action */
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.ef-list__item:hover {
  background: color-mix(in srgb, var(--ef-ink) 5%, transparent); /* hover:bg-ink/5 */
}

.ef-list__item:active {
  background: color-mix(in srgb, var(--ef-ink) 10%, transparent); /* active:bg-ink/10 */
}

/* 黄条只表示“选中 / 当前”（card.md：黄色不做类目，也不做悬停提示）：
   悬停与聚焦只换底色，亮黄条留给 [aria-current="page"] / [aria-pressed="true"] */

/* 选中 / 当前行：浅灰底 + 黄条 + 标题加粗 */
.ef-list__item[aria-current="page"],
.ef-list__item[aria-pressed="true"] {
  background: var(--ef-surface-muted); /* bg-surface-muted */
}

.ef-list__item[aria-current="page"]::before,
.ef-list__item[aria-pressed="true"]::before {
  opacity: 1;
}

.ef-list__item[aria-current="page"] .ef-list__title,
.ef-list__item[aria-pressed="true"] .ef-list__title {
  font-weight: 700; /* selected: font-bold */
}

.ef-list__item:focus-visible {
  outline: 2px solid var(--ef-focus); /* focusRingInset */
  outline-offset: -2px;
}

.ef-list__item[aria-disabled="true"],
.ef-list__item:disabled {
  cursor: not-allowed; /* cursor-not-allowed */
  color: var(--ef-ink-disabled); /* text-ink-disabled */
}

/* 编号格：等宽、次要色，宽 2rem 让各行对齐 */
.ef-list__index {
  flex-shrink: 0; /* shrink-0 */
  width: 2rem; /* w-8 */
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-ink-tertiary); /* text-ink-tertiary */
  font-variant-numeric: tabular-nums; /* tabular-nums */
}

.ef-list__text {
  min-width: 0; /* min-w-0 */
  flex: 1 1 auto; /* flex-1 */
}

.ef-list__title {
  display: block; /* block */
  overflow: hidden; /* truncate */
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ef-list__desc {
  display: block; /* block */
  overflow: hidden; /* truncate */
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-xs); /* text-xs */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
}

.ef-list__item[aria-disabled="true"] .ef-list__desc,
.ef-list__item:disabled .ef-list__desc {
  color: var(--ef-ink-disabled); /* disabled: text-ink-disabled */
}

/* 尾部标记 / 数值：右对齐、等宽，不被标题挤压 */
.ef-list__end {
  display: flex; /* flex */
  flex-shrink: 0; /* shrink-0 */
  align-items: center; /* items-center */
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
  font-variant-numeric: tabular-nums; /* tabular-nums */
}

.ef-list__item[aria-current="page"] .ef-list__end,
.ef-list__item[aria-pressed="true"] .ef-list__end {
  color: var(--ef-ink); /* selected: text-ink */
}

/* 箭头：悬停时向右让一步（和 text 按钮的斜箭头同一种“记号让一步”） */
.ef-list__arrow {
  flex-shrink: 0;
  width: 0;
  height: 0;
  border-left: 0.375rem solid currentColor; /* 实心三角的宽：源码里是 8px 的图标 */
  border-block: 0.25rem solid transparent; /* 三角的高 */
  color: var(--ef-ink-tertiary);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.ef-list__item:hover .ef-list__arrow {
  transform: translateX(var(--motion-shift)); /* 4px */
}

/* --------------------------------------------------------------------------
   Stat —— 统计块
   来源：components/stat/Stat.tsx
   禁忌：不装进卡片，只用左缘一条墨色竖线；并排几个时给最重要的那个 lg
   （Stat.tsx 文档注释）。
   -------------------------------------------------------------------------- */

.ef-stat {
  min-width: 0; /* min-w-0 */
  border-left: 0.25rem solid var(--ef-ink); /* border-l-4 border-ink */
  padding-left: 1rem; /* pl-4 */
  color: var(--ef-ink); /* text-ink */
}

.ef-stat__label {
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-xs); /* text-xs */
  letter-spacing: var(--tracking-label); /* tracking-label */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
}

.ef-stat__label::before {
  content: "// "; /* 组件加的 {"// "}，对读屏隐藏 */
}

.ef-stat__row {
  display: flex; /* flex */
  flex-wrap: wrap; /* flex-wrap */
  align-items: baseline; /* items-baseline */
  column-gap: 0.5rem; /* gap-x-2 */
  row-gap: 0.25rem; /* gap-y-1 */
  margin-top: 0.5rem; /* mt-2 */
}

.ef-stat__value {
  min-width: 0; /* min-w-0 */
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-4xl); /* text-4xl（md，默认） */
  line-height: 1; /* leading-none */
  font-weight: 500; /* font-medium */
  font-variant-numeric: tabular-nums; /* tabular-nums */
  overflow-wrap: anywhere; /* wrap-anywhere */
}

.ef-stat[data-size="lg"] .ef-stat__value {
  font-size: var(--text-5xl); /* text-5xl */
}

.ef-stat__unit {
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
}

/* 按自身宽度响应（容器查询，不看视口）：容器宽到 28rem 时标题升一档、返回格里长高 */
@container (min-width: 28rem) {
  .ef-page-header__back {
    height: 3rem; /* @md:h-12 */
  }

  .ef-page-header__title {
    font-size: var(--text-3xl); /* @md:text-3xl */
    line-height: 3rem; /* @md:leading-12 */
  }

  .ef-page-header__actions {
    min-height: 3rem; /* @md:min-h-12 */
  }
}

/* 入场：微文字行 → 标题 → 说明，各自从左 8px 淡入归位，依次晚 100ms —— 挂上就播 */
@keyframes ef-page-header-in {
  from {
    transform: translateX(calc(var(--motion-shift-lg) * -1));
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* --------------------------------------------------------------------------
   Breadcrumb —— 面包屑
   来源：components/breadcrumb/Breadcrumb.tsx
   禁忌：斜杠全是装饰（对读屏隐藏），读屏听到的是一个名为“面包屑”的导航地标
   （navigation.md#面包屑）。
   -------------------------------------------------------------------------- */

.ef-breadcrumb {
  display: flex; /* flex */
  align-items: baseline; /* items-baseline */
  gap: 0.5rem; /* gap-2 */
  font-size: var(--text-sm); /* text-sm */
}

/* 双斜杠起头：纯装饰层，画在这里省一个 aria-hidden 的空 span */
.ef-breadcrumb::before {
  content: "//"; /* 组件里的 {"//"} 装饰（对读屏隐藏） */
  flex-shrink: 0; /* shrink-0 */
  color: var(--ef-ink-tertiary); /* text-ink-tertiary */
}

.ef-breadcrumb__list {
  display: flex; /* flex */
  min-width: 0; /* min-w-0 */
  flex-wrap: wrap; /* flex-wrap（容器太窄时整条换行） */
  align-items: baseline; /* items-baseline */
  column-gap: 0.5rem; /* gap-x-2 */
  row-gap: 0.25rem; /* gap-y-1 */
}

.ef-breadcrumb__list > li {
  display: flex; /* flex */
  min-width: 0; /* min-w-0 */
  align-items: baseline; /* items-baseline */
  gap: 0.5rem; /* gap-2 */
}

.ef-breadcrumb__list > li:first-child .ef-breadcrumb__sep {
  display: none; /* [li:first-child>&]:hidden */
}

.ef-breadcrumb a {
  color: var(--ef-ink-secondary); /* text-ink-secondary */
  text-underline-offset: 4px; /* underline-offset-4 */
  transition: color var(--duration-fast) var(--ease-standard);
}

.ef-breadcrumb a:hover {
  color: var(--ef-ink); /* hover:text-ink */
  text-decoration: underline; /* hover:underline */
}

.ef-breadcrumb [aria-current="page"] {
  font-weight: 700; /* font-bold */
  color: var(--ef-ink); /* text-ink */
}

/* 折叠中间项的那个 … 按钮 */
.ef-breadcrumb button {
  padding-inline: 0.25rem; /* px-1 */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
  transition: color var(--duration-fast) var(--ease-standard);
}

.ef-breadcrumb button:hover {
  color: var(--ef-ink); /* hover:text-ink */
}

/* --------------------------------------------------------------------------
   Topbar —— 顶栏（细横条）
   来源：components/top-bar/TopBar.tsx
   禁忌：它只管排版，栏目本身不排在这一条里，收进全屏菜单
   （navigation.md#顶栏）。
   -------------------------------------------------------------------------- */

/* 一条细横条：左侧微标签、右侧状态、底部一条 1px 线 */
.ef-topbar {
  position: sticky; /* sticky */
  top: 0; /* top-0 */
  z-index: var(--z-nav); /* z-(--z-nav) */
  display: flex; /* flex */
  align-items: center; /* items-center */
  gap: 0.75rem; /* gap-3 */
  height: 2.25rem; /* 细横条：h-9（源码 TopBar 是 h-14，这里只取细条） */
  padding-inline: 1rem; /* px-4 */
  border-bottom: 1px solid var(--ef-line); /* border-b border-line */
  background: var(--ef-surface); /* bg-surface */
  color: var(--ef-ink); /* text-ink */
}

/* 左侧微标签：大写字距的技术字 */
.ef-topbar__label {
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-micro); /* text-micro */
  letter-spacing: var(--tracking-label); /* tracking-label */
  text-transform: uppercase;
  color: var(--ef-ink-secondary); /* text-ink-secondary */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 右侧状态：一个小方点 + 一句技术字 */
.ef-topbar__status {
  display: flex; /* flex */
  align-items: center; /* items-center */
  gap: 0.375rem; /* gap-1.5 */
  margin-left: auto; /* 推到右端 */
  flex-shrink: 0;
  font-family: var(--font-tech); /* font-tech */
  font-size: var(--text-xs); /* text-xs */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
  white-space: nowrap;
}

.ef-topbar__status::before {
  content: "";
  width: 0.375rem; /* 6px 方点 */
  height: 0.375rem;
  background: var(--ef-success); /* 在线 = success */
}

.ef-topbar__status[data-state="idle"]::before {
  background: var(--ef-line-strong); /* 空闲 */
}

.ef-topbar__status[data-state="warn"]::before {
  background: var(--ef-warning); /* 告警 */
}

.ef-topbar__status[data-state="down"]::before {
  background: var(--ef-danger); /* 中断 */
}

.ef-empty__desc {
  margin-top: 0.25rem; /* mt-1 */
  max-width: 40ch; /* max-w-[40ch] */
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
}
/* ==========================================================================
   03 · 表单
   Field / Input / Textarea / Select / Checkbox / Radio / Switch / SegmentedControl
   --------------------------------------------------------------------------
   依据：icaruszezen/endfield-ui（MIT，非官方爱好者项目）
     packages/ui/src/components/field/Field.tsx
     packages/ui/src/components/input/Input.tsx
     packages/ui/src/components/input/control-box.ts
     packages/ui/src/components/textarea/Textarea.tsx
     packages/ui/src/components/select/Select.tsx
     packages/ui/src/components/checkbox/{Checkbox.tsx,choice-style.ts}
     packages/ui/src/components/radio/Radio.tsx
     packages/ui/src/components/switch/Switch.tsx
     packages/ui/src/components/segmented-control/SegmentedControl.tsx
     packages/ui/src/lib/focus-ring.ts
   行尾注释 = 源码里的那个 Tailwind 类；1 单位 = var(--spacing) = 0.25rem。
   尺寸档用 data-size="sm|md|lg"，画法用 data-variant="sunken|outline"（同源码）。
   状态钩子：.ef-input--invalid、[data-invalid]、[aria-invalid="true"]、
   [data-readonly]、[data-disabled]、[data-required]，原生 :checked / :disabled
   等照常可用。
   ========================================================================== */

/* --------------------------------------------------------------------------
   Field —— 字段：标签在上、控件在中、帮助与错误在下
   来源：packages/ui/src/components/field/Field.tsx
   禁忌：不要用占位文字代替标签；每个控件都要有一个真的 <label> 指过去。
   -------------------------------------------------------------------------- */
.ef-field {
  display: block;
  min-width: 0; /* min-w-0 */
  margin: 0; /* <fieldset> 重置 */
  padding: 0; /* <fieldset> 重置 */
  border: 0; /* <fieldset> 重置 */
}

.ef-field__label {
  display: block; /* block */
  margin-bottom: 0.375rem; /* mb-1.5 */
  padding: 0; /* <legend> 重置 */
  font-size: var(--text-sm); /* text-sm */
  font-weight: 500; /* font-medium */
  color: var(--ef-ink); /* text-ink */
}

/* 必填记号：标签后一个小菱形，随控件的 required / aria-required 自动出现 */
.ef-field:has([required]) .ef-field__label::after,
.ef-field:has([aria-required="true"]) .ef-field__label::after,
.ef-field__label[data-required]::after {
  content: "";
  display: inline-block; /* inline-block */
  width: 0.375rem; /* size-1.5 */
  height: 0.375rem; /* size-1.5 */
  margin-left: 0.375rem; /* ml-1.5 */
  vertical-align: middle; /* align-middle */
  background: var(--ef-accent-ink); /* bg-accent-ink */
  transform: rotate(45deg); /* rotate-45 */
}

/* 禁用：标签、必填菱形与帮助文字一起变浅。
   只认「字段自己禁用了」：显式的 data-disabled / 禁用的 <fieldset>，或者字段唯一的
   那个控件（.ef-input / .ef-select / .ef-textarea 的直接子控件）被禁用。
   组里某一个选项被禁用不会把整段的标签拖暗 */
.ef-field[data-disabled] .ef-field__label,
.ef-field[data-disabled] .ef-field__hint,
.ef-field[data-group]:disabled .ef-field__label,
.ef-field[data-group]:disabled .ef-field__hint,
.ef-field:has(> .ef-input > input:disabled) .ef-field__label,
.ef-field:has(> .ef-input > input:disabled) .ef-field__hint,
.ef-field:has(> .ef-select > select:disabled) .ef-field__label,
.ef-field:has(> .ef-select > select:disabled) .ef-field__hint,
.ef-field:has(> .ef-textarea > textarea:disabled) .ef-field__label,
.ef-field:has(> .ef-textarea > textarea:disabled) .ef-field__hint {
  color: var(--ef-ink-disabled); /* text-ink-disabled */
}

.ef-field[data-disabled] .ef-field__label::after,
.ef-field[data-group]:disabled .ef-field__label::after,
.ef-field:has(> .ef-input > input:disabled) .ef-field__label::after,
.ef-field:has(> .ef-select > select:disabled) .ef-field__label::after,
.ef-field:has(> .ef-textarea > textarea:disabled) .ef-field__label::after {
  background: var(--ef-ink-disabled); /* bg-ink-disabled */
}

.ef-field__hint {
  color: var(--ef-ink-disabled); /* text-ink-disabled */
}

.ef-field[data-disabled] .ef-field__label::after,
.ef-field:has(input:disabled) .ef-field__label::after,
.ef-field:has(select:disabled) .ef-field__label::after,
.ef-field:has(textarea:disabled) .ef-field__label::after {
  background: var(--ef-ink-disabled); /* bg-ink-disabled */
}

.ef-field__hint {
  margin-top: 0.375rem; /* mt-1.5 */
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
}

/* 错误说明：红色一行 + 菱形符号；出现在帮助文字之前 */
.ef-field__error {
  display: flex; /* flex */
  align-items: flex-start; /* items-start */
  gap: 0.375rem; /* gap-1.5 */
  margin-top: 0.375rem; /* pt-1.5 */
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-danger); /* text-danger */
}

.ef-field__error::before {
  content: "";
  flex: none; /* shrink-0 */
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.3125rem; /* mt-0.5 + 视觉居中 */
  border: 2px solid currentColor; /* StatusDanger size=16：空心菱形 */
  transform: rotate(45deg);
}

.ef-field__error > span {
  min-width: 0; /* min-w-0 */
  overflow-wrap: anywhere; /* wrap-anywhere */
}

/* 组（几个复选、一个单选组）：<fieldset> + <legend>，内部竖排 */
.ef-field[data-group] > div {
  display: flex; /* flex */
  flex-direction: column; /* flex-col */
  align-items: flex-start; /* items-start */
}

/* 单选组：语义靠 role="radiogroup"，不加新类；横排用 data-orientation */
.ef-field [role="radiogroup"]:not(.ef-segmented) {
  display: flex; /* flex */
  flex-direction: column; /* flex-col */
  min-width: 0; /* min-w-0 */
}

.ef-field [role="radiogroup"][data-orientation="horizontal"] {
  flex-direction: row; /* flex */
  flex-wrap: wrap; /* flex-wrap */
  column-gap: 1.5rem; /* gap-x-6 */
}

/* --------------------------------------------------------------------------
   输入类控件的共同外框（Input / Textarea / Select / SegmentedControl 共用）
   来源：packages/ui/src/components/input/control-box.ts —— controlBox()
   `sunken` = 凹陷底 + 2px 底边线；`outline` = 1px 四边描边 + 页面底色。
   禁忌：不要给输入框切角——它需要完整的边界；不要做会发光的聚焦态。
   -------------------------------------------------------------------------- */
.ef-input,
.ef-textarea,
.ef-select {
  position: relative; /* relative */
  display: flex; /* flex */
  width: 100%; /* w-full */
  min-width: 0; /* min-w-0 */
  color: var(--ef-ink); /* text-ink */
  border-style: solid; /* border-b-2 */
  border-width: 0 0 2px; /* border-b-2 */
  border-color: var(--ef-line-strong); /* border-line-strong */
  background: var(--ef-surface-sunken); /* bg-surface-sunken */
  transition: border-color var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard); /* transition-colors duration-(--duration-fast) ease-standard */
}

/* 聚焦的墨线：另画一条压在边线上，从左往右擦出 */
.ef-input::before,
.ef-textarea::before,
.ef-select::before,
.ef-segmented::before {
  content: ""; /* before:content-[''] */
  position: absolute; /* before:absolute */
  pointer-events: none; /* before:pointer-events-none */
  inset-inline: 0; /* before:inset-x-0 */
  bottom: -2px; /* before:-bottom-[2px] */
  height: 2px; /* before:h-[2px] */
  background: var(--ef-ink); /* before:bg-ink */
  transform: scaleX(0); /* before:scale-x-0 */
  transform-origin: left; /* before:origin-left */
  transition: transform var(--duration-fast) var(--ease-standard),
    clip-path var(--duration-fast) var(--ease-standard); /* before:transition-[scale,clip-path] */
}

.ef-input:focus-within::before,
.ef-textarea:focus-within::before,
.ef-select:focus-within::before,
.ef-segmented:focus-within::before {
  transform: scaleX(1); /* focus-within:before:scale-x-100 */
  transition-duration: var(--duration-base); /* focus-within:before:duration-(--duration-base) */
  transition-timing-function: var(--ease-exit); /* focus-within:before:ease-exit */
}

/* `outline` 变体的墨线是整圈从左往右露出来（用裁切，不用缩放） */
.ef-input[data-variant="outline"]::before,
.ef-textarea[data-variant="outline"]::before,
.ef-select[data-variant="outline"]::before,
.ef-segmented[data-variant="outline"]::before {
  inset: -1px; /* before:-inset-px */
  height: auto;
  background: none;
  border: 1px solid var(--ef-ink); /* before:border before:border-ink */
  transform: none;
  clip-path: inset(0 100% 0 0); /* before:[clip-path:inset(0_100%_0_0)] */
}

.ef-input[data-variant="outline"]:focus-within::before,
.ef-textarea[data-variant="outline"]:focus-within::before,
.ef-select[data-variant="outline"]:focus-within::before,
.ef-segmented[data-variant="outline"]:focus-within::before {
  clip-path: inset(0); /* focus-within:before:[clip-path:inset(0)] */
}

/* 悬停：边线深一档，但只在没聚焦时（聚焦中的框不因鼠标经过变浅）。
   用 :where() 把 :not() 那几层的权重清零，好让下面的只读 / 错误 / 禁用按顺序压过它 */
.ef-input:hover:where(:not(:focus-within, :has(:disabled), :has([readonly]))),
.ef-textarea:hover:where(:not(:focus-within, :has(:disabled), :has([readonly]))),
.ef-select:hover:where(:not(:focus-within, :has(> select:disabled), :has([readonly]))),
.ef-segmented:hover:where(:not(:focus-within, [data-disabled])) {
  border-color: var(--ef-ink-secondary); /* hover:not-focus-within:border-ink-secondary */
}

/* 只读：没有边线、底变透明；边线留透明的，尺寸不跳（排在错误之前：只读又报错时留着红线） */
.ef-input[data-readonly],
.ef-textarea[data-readonly],
.ef-select[data-readonly],
.ef-input:has([readonly]),
.ef-textarea:has([readonly]),
.ef-select:has([readonly]) {
  border-color: transparent; /* border-transparent */
  background: transparent; /* bg-transparent */
}

/* 错误：边线变红。三个钩子都要认：类、外框上的 data-invalid、控件上的 aria-invalid */
.ef-input.ef-input--invalid,
.ef-input[data-invalid],
.ef-textarea[data-invalid],
.ef-select[data-invalid],
.ef-segmented[data-invalid],
.ef-input:has([aria-invalid="true"]),
.ef-textarea:has([aria-invalid="true"]),
.ef-select:has([aria-invalid="true"]),
.ef-segmented:has([aria-invalid="true"]) {
  border-color: var(--ef-danger); /* border-danger */
}

/* 错误、只读时不再画聚焦墨线：会把红线盖掉 / 本来就不是"可以填" */
.ef-input--invalid::before,
.ef-input[data-invalid]::before,
.ef-textarea[data-invalid]::before,
.ef-select[data-invalid]::before,
.ef-segmented[data-invalid]::before,
.ef-input:has([aria-invalid="true"])::before,
.ef-textarea:has([aria-invalid="true"])::before,
.ef-select:has([aria-invalid="true"])::before,
.ef-segmented:has([aria-invalid="true"])::before,
.ef-input[data-readonly]::before,
.ef-textarea[data-readonly]::before,
.ef-select[data-readonly]::before,
.ef-input:has([readonly])::before,
.ef-textarea:has([readonly])::before,
.ef-select:has([readonly])::before {
  display: none; /* controlLine 只在 !disabled && !invalid && !readOnly 时挂上 */
}

/* 禁用：边线降到 line，字变浅（排在最后，压过只读与错误）。
   看控件自己，不看 <select> 里被禁用的某个 <option> */
.ef-input:has(:disabled),
.ef-textarea:has(:disabled),
.ef-select:has(> select:disabled),
.ef-segmented[data-disabled],
.ef-segmented:not(:has(> .ef-segmented__item > input:enabled)) {
  border-color: var(--ef-line); /* border-line */
  color: var(--ef-ink-disabled); /* text-ink-disabled */
  cursor: not-allowed; /* cursor-not-allowed */
}

/* --------------------------------------------------------------------------
   Input —— 单行输入框
   来源：packages/ui/src/components/input/Input.tsx + input/control-box.ts
   禁忌：不要用占位文字代替标签；不要把单位、图标塞进 <input> 的 value 里。
   结构：.ef-input 是外框，<input> 在里面；两侧可选 [data-slot="start|end"]。
   -------------------------------------------------------------------------- */
.ef-input {
  height: 2.5rem; /* h-10 */
  align-items: center; /* items-center */
  font-size: var(--text-base); /* text-base */
}

.ef-input[data-size="sm"] {
  height: 2rem; /* h-8 */
  font-size: var(--text-sm); /* text-sm */
}

.ef-input[data-size="lg"] {
  height: 3.5rem; /* h-14 */
  font-size: var(--text-lg); /* text-lg */
}

.ef-input[data-variant="outline"] {
  border-width: 1px; /* border */
  background: var(--ef-surface); /* bg-surface */
}

/* 等宽字版：编号、口令、代号这类逐位读的值 */
.ef-input--mono {
  font-family: var(--font-mono); /* font-mono */
  font-variant-numeric: tabular-nums; /* tabular-nums */
}

/* 框里真正的 <input>：没有自己的底和边，焦点环画在外框上 */
.ef-input > input {
  flex: 1; /* flex-1 */
  min-width: 0; /* min-w-0 */
  height: 100%; /* h-full */
  margin: 0;
  padding-block: 0;
  padding-inline: 0.75rem; /* pl-3 pr-3 */
  border: 0;
  background: transparent; /* bg-transparent */
  color: inherit;
  outline: none; /* outline-none */
}

.ef-input > input::placeholder {
  color: var(--ef-ink-tertiary); /* placeholder:text-ink-tertiary */
}

.ef-input > input:disabled {
  cursor: not-allowed; /* disabled:cursor-not-allowed */
}

.ef-input > input:disabled::placeholder {
  color: var(--ef-ink-disabled); /* disabled:placeholder:text-ink-disabled */
}

/* 框内左端：图标 */
.ef-input > [data-slot="start"] {
  display: flex; /* flex */
  flex: none; /* shrink-0 */
  align-items: center; /* items-center */
  padding-left: 0.75rem; /* pl-3 */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
}

/* 框内右端：单位、后缀 */
.ef-input > [data-slot="end"] {
  display: flex; /* flex */
  flex: none; /* shrink-0 */
  align-items: center; /* items-center */
  padding-right: 0.75rem; /* pr-3 */
  font-size: var(--text-sm); /* text-sm */
  color: var(--ef-ink-secondary); /* text-ink-secondary */
}

/* 有前缀时框内左内边距收窄（start ? pl-2 : pl-3） */
.ef-input > [data-slot="start"] + input {
  padding-left: 0.5rem; /* pl-2 */
}

/* 有后缀时右内边距收窄（end ? pr-2 : pr-3） */
.ef-input:has(> [data-slot="end"]) > input {
  padding-right: 0.5rem; /* pr-2 */
}

/* 错误态右侧的菱形图标（StatusDanger）。放在 ::after 上，作为外框的最后一个弹性项 */
.ef-input--invalid::after,
.ef-input[data-invalid]::after,
.ef-input:has([aria-invalid="true"])::after {
  content: "";
  flex: none; /* shrink-0 */
  width: 0.5rem;
  height: 0.5rem;
  margin-right: 0.75rem; /* mr-3 */
  border: 2px solid var(--ef-danger); /* text-danger */
  transform: rotate(45deg);
}

/* --------------------------------------------------------------------------
   Select —— 下拉选择（原生 <select>，无 JS）
   来源：packages/ui/src/components/select/Select.tsx
   禁忌：选项只有两三个、又都该一眼看到时，用单选或分段选择，不要用下拉。
   说明：触发器右侧的实心三角用 ::after 画成 CSS 三角（不用图、不用 JS）。
   -------------------------------------------------------------------------- */
.ef-select {
  height: 2.5rem; /* controlSize md: h-10 */
  align-items: center; /* items-center */
  font-size: var(--text-base); /* text-base */
}

.ef-select[data-size="sm"] {
  height: 2rem; /* h-8 */
  font-size: var(--text-sm); /* text-sm */
}

.ef-select[data-size="lg"] {
  height: 3.5rem; /* h-14 */
  font-size: var(--text-lg); /* text-lg */
}

.ef-select[data-variant="outline"] {
  border-width: 1px; /* border */
  background: var(--ef-surface); /* bg-surface */
}

.ef-select > select {
  flex: 1; /* flex-1 */
  min-width: 0; /* min-w-0 */
  height: 100%; /* h-full */
  margin: 0;
  padding-block: 0;
  padding-inline: 0.75rem 2rem; /* pl-3 + 给三角留位 */
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: inherit;
  outline: none; /* outline-none */
  appearance: none; /* appearance-none */
  cursor: pointer; /* 可点的提示（源码为 cursor-default） */
}

/* 没选时显示占位文字：占位 option 写成 value="" */
.ef-select > select:has(option[value=""]:checked) {
  color: var(--ef-ink-tertiary); /* data-placeholder:text-ink-tertiary */
}

.ef-select > select:disabled {
  cursor: not-allowed; /* disabled:cursor-not-allowed */
  color: var(--ef-ink-disabled); /* text-ink-disabled */
}

/* 右侧实心三角：TriangleRight 转 90° 朝下（iconSize - 4 = 12px） */
.ef-select::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0.75rem; /* pr-3 一侧 */
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 7px solid var(--ef-ink-secondary); /* text-ink-secondary */
  transform: translateY(-50%);
  pointer-events: none; /* 点击穿透到 <select> */
}

.ef-select:has(> select:disabled)::after {
  border-top-color: var(--ef-ink-disabled); /* disabled ? text-ink-disabled */
}

/* --------------------------------------------------------------------------
   Checkbox —— 复选框（原生 <input> + 兄弟 <span> 画记号，无 JS）
   来源：packages/ui/src/components/checkbox/{Checkbox.tsx,choice-style.ts}
   禁忌：不要改用整块黄底——已选是墨底 + 黄色记号，黄色只点在记号上。
   结构：<label class="ef-checkbox"><span><input><span aria-hidden></span></span>文字</label>
   -------------------------------------------------------------------------- */
.ef-checkbox,
.ef-radio {
  display: inline-flex; /* inline-flex */
  align-items: flex-start; /* items-start */
  gap: 0.5rem; /* gap-2 */
  min-height: 2.5rem; /* min-h-10 */
  padding-block: 0.5rem; /* py-2 */
  font-size: var(--text-base); /* text-base */
  color: var(--ef-ink); /* text-ink */
  cursor: pointer;
}

/* 控件的定位盒：高度等于一行文字，标签换行时控件仍对齐第一行 */
.ef-checkbox > span:first-child,
.ef-radio > span:first-child {
  position: relative; /* relative */
  display: flex; /* inline-flex */
  flex: none; /* shrink-0 */
  align-items: center; /* items-center */
  height: 1lh; /* h-[1lh] */
}

/* 方格 / 圆就是 <input> 自己 */
.ef-checkbox input,
.ef-radio input {
  appearance: none; /* appearance-none */
  flex: none; /* shrink-0 */
  width: 1.25rem; /* size-5 */
  height: 1.25rem; /* size-5 */
  margin: 0;
  border: 2px solid var(--ef-line-strong); /* border-2 border-line-strong */
  background: var(--ef-surface-sunken); /* bg-surface-sunken */
  transition: border-color var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard); /* transition-colors duration-(--duration-fast) ease-standard */
  cursor: pointer;
}

/* 悬停：未选的描边加深（鼠标在文字标签上同样触发） */
.ef-checkbox:hover input,
.ef-radio:hover input {
  border-color: var(--ef-ink-secondary); /* group-hover:border-ink-secondary */
}

/* 已选：反转块。半选（:indeterminate）同样铺墨底 */
.ef-checkbox input:checked,
.ef-checkbox input:indeterminate,
.ef-radio input:checked {
  border-color: var(--ef-surface-inverse); /* checked:border-surface-inverse */
  background: var(--ef-surface-inverse); /* checked:bg-surface-inverse */
}

/* 禁用压过已选：描边降到 ink-disabled（同源码的层叠顺序） */
.ef-checkbox input:disabled,
.ef-radio input:disabled {
  border-color: var(--ef-ink-disabled); /* disabled:border-ink-disabled */
  background: var(--ef-surface-sunken); /* disabled:bg-surface-sunken */
  cursor: not-allowed; /* disabled:cursor-not-allowed */
}

/* 压在方格正中的记号层：<input> 的兄弟 <span>，20×20 与方格重合 */
.ef-checkbox input + span,
.ef-radio input + span {
  position: absolute;
  top: 50%;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem; /* size-5 */
  height: 1.25rem; /* size-5 */
  transform: translateY(-50%);
  color: var(--ef-accent-ink-inverse, var(--ef-action)); /* text-accent-ink-inverse */
  pointer-events: none; /* pointer-events-none */
}

/* 对勾：Check size=14 stroke=3，用两条 3px 的边拼出来 */
.ef-checkbox input + span::before {
  content: "";
  width: 0.3125rem;
  height: 0.5625rem;
  margin-bottom: 0.0625rem;
  border-right: 3px solid currentColor;
  border-bottom: 3px solid currentColor;
  transform: rotate(45deg);
  opacity: 0; /* opacity-0 */
  transition: opacity var(--duration-fast) var(--ease-standard); /* transition-opacity duration-(--duration-fast) ease-standard */
}

.ef-checkbox input:checked + span::before {
  opacity: 1; /* peer-checked:opacity-100 */
}

/* 半选：短横（Minus size=14 stroke=3），与对勾交叉淡化 */
.ef-checkbox input + span::after {
  content: "";
  position: absolute;
  width: 0.875rem;
  height: 3px;
  background: currentColor;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.ef-checkbox input:indeterminate + span::before {
  opacity: 0; /* peer-indeterminate:opacity-0 */
}

.ef-checkbox input:indeterminate + span::after {
  opacity: 1; /* peer-indeterminate:opacity-100 */
}

/* 禁用的记号降到 ink-disabled */
.ef-checkbox input:disabled + span,
.ef-radio input:disabled + span {
  color: var(--ef-ink-disabled); /* peer-disabled:text-ink-disabled */
}

/* 一整行禁用：文字标签同步变浅 */
.ef-checkbox:has(:disabled),
.ef-radio:has(:disabled) {
  color: var(--ef-ink-disabled); /* has-disabled:text-ink-disabled */
  cursor: not-allowed; /* has-disabled:cursor-not-allowed */
}

/* 焦点环与输入框同一条：画在外框上，不画在里面的控件上 */
.ef-input:has(:focus-visible),
.ef-textarea:has(:focus-visible),
.ef-select:has(:focus-visible) {
  outline: 2px solid var(--ef-focus); /* has-focus-visible:outline-2 outline-focus */
  outline-offset: 2px; /* has-focus-visible:outline-offset-2 */
}

.ef-input > input:focus-visible,
.ef-textarea > textarea:focus-visible,
.ef-select > select:focus-visible {
  outline: none; /* 外框自己画环，里面的控件不再画一遍 */
}
/* ==========================================================================
   04 —— 浮层与反馈（Dialog / Drawer / Alert / Progress / Spinner / Tooltip）
   --------------------------------------------------------------------------
   源：packages/ui/src/components/{dialog,drawer,alert,progress,spinner,tooltip}
       packages/ui/src/lib/focus-ring.ts
   约定：颜色只用 var(--ef-*) / var(--color-*)，数值来自源码里的 Tailwind 类，
         行尾注释标来源类名。空状态（Empty）归 02 组，这里不重复定义。
   ========================================================================== */

/* --------------------------------------------------------------------------
   Dialog 弹窗
   源：components/dialog/Dialog.tsx、components/dialog/overlay-style.ts
   禁忌：不要弹窗套弹窗；破坏性确认用 role="alertdialog"，点遮罩不关。
   直接用在原生 <dialog> 上：.ef-dialog 就是那个 <dialog>，::backdrop 是遮罩，
   打开态用 [open]（showModal() 之后浏览器自己加）。
   -------------------------------------------------------------------------- */
.ef-dialog {
  /* 三档宽度是上限，窄屏上通宽：sm 480 / md 640 / lg 1040 */
  --ef-dialog-width: 40rem; /* max-w-[40rem]（md 档） */
  position: fixed;
  inset: 0; /* 视口居中：inset 0 + margin auto */
  z-index: var(--z-overlay); /* z-(--z-overlay) */
  margin: auto;
  flex-direction: column;
  width: min(100%, var(--ef-dialog-width));
  max-width: calc(100% - 2rem); /* 四周留 16px：视口容器的 p-4 */
  max-height: calc(100dvh - 2rem); /* 弹窗整体不超出视口，同上 p-4 */
  overflow: hidden;
  border: 0;
  border-radius: 0; /* 浮层没有圆角 */
  padding: 0;
  background: var(--ef-surface-raised); /* bg-surface-raised */
  color: var(--ef-ink); /* text-ink */
  box-shadow: var(--shadow-lg); /* shadow-lg */
}

/* 关着的时候必须保持浏览器默认的 display:none，所以 display 只在 [open] 上给 */
.ef-dialog[open] {
  display: flex;
  /* 进场：淡入 200ms，同时上移 8px 用 300ms；退场只淡出 */
  transition:
    opacity var(--duration-fast) var(--ease-standard), /* duration-[var(--duration-fast)] */
    translate var(--duration-base) var(--ease-exit); /* duration-[var(--duration-base)] */
}

/* 起点：淡 + 从下方 8px 升上来（不放大） */
@starting-style {
  .ef-dialog[open] {
    opacity: 0; /* data-starting-style:opacity-0 */
    translate: 0 var(--motion-shift-lg); /* data-starting-style:translate-y-(--motion-shift-lg) */
  }
}

/* 退场：页面先加 data-closing 再 close()，只淡出，不位移 */
.ef-dialog[data-closing] {
  opacity: 0; /* data-ending-style:opacity-0 */
}

.ef-dialog[data-size="sm"] {
  --ef-dialog-width: 30rem; /* max-w-[30rem] */
}
.ef-dialog[data-size="md"] {
  --ef-dialog-width: 40rem; /* max-w-[40rem] */
}
.ef-dialog[data-size="lg"] {
  --ef-dialog-width: 65rem; /* max-w-[65rem] */
}

/* 标题带：固定的深色斜纹块，亮暗主题下都是反色的 */
.ef-dialog__band {
  position: relative;
  display: flex;
  flex: none; /* shrink-0 */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.375rem; /* gap-1.5 */
  min-height: 4rem; /* min-h-16 */
  padding: 0.75rem 3rem; /* py-3 px-12 */
  border-bottom: 1px solid var(--color-neutral-800); /* border-b border-line（深色主题下的线） */
  background-color: var(--ef-surface-inverse); /* data-theme="dark" 下的 surface-raised */
  background-image: repeating-linear-gradient(
    -45deg,
    color-mix(in srgb, var(--ef-ink-inverse) 5%, transparent) 0 1.9px,
    transparent 0 calc(var(--hatch-size) * 0.7071)
  ); /* hatch：6px 方格、-45°、5% 的反色斜带 */
  color: var(--ef-ink-inverse); /* 深底浅字：带内的 text-ink */
}

/* 标题：常规字重居中，长标题换行 */
.ef-dialog__band > :is(h2, h3) {
  margin: 0;
  font-size: var(--text-xl); /* text-xl */
  line-height: 1.1; /* text-xl--line-height */
  font-weight: 400; /* font-normal：弹窗标题是例外 */
  text-align: center; /* text-center */
  overflow-wrap: anywhere; /* wrap-anywhere */
}

/* 标题下方六个 4px 小方点（纯装饰） */
.ef-dialog__band [data-ornament] {
  display: flex;
  gap: 0.25rem; /* gap-1 */
}
.ef-dialog__band [data-ornament] > span {
  width: 0.25rem; /* size-1 */
  height: 0.25rem; /* size-1 */
  background: color-mix(in srgb, var(--ef-ink-inverse) 50%, transparent); /* bg-ink/50 */
}

/* 关闭钮：24px 的叉、40px 的点击区，悬停转 90° */
.ef-dialog__close {
  position: absolute;
  top: 50%; /* top-1/2 */
  right: 0.25rem; /* right-1 */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem; /* size-10 */
  height: 2.5rem; /* size-10 */
  translate: 0 -50%; /* -translate-y-1/2 */
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit; /* 带内即反色的 text-ink */
  cursor: pointer;
}
.ef-dialog__close svg {
  transition: rotate var(--duration-base) var(--ease-standard); /* [&_svg]:duration-(--duration-base) */
}
.ef-dialog__close:hover svg {
  rotate: 90deg; /* hover:[&_svg]:rotate-90 */
}
.ef-dialog__close:active {
  color: var(--color-neutral-300); /* active:text-neutral-300 */
}
.ef-dialog__close:focus-visible {
  outline: 2px solid var(--ef-focus); /* focus-visible:outline-2 outline-focus */
  outline-offset: -2px; /* focusRingInset：画在内侧，不被滚动区裁掉 */
}

/* 内容区：铺点阵，正文垫实色底，四面露出一圈点阵；只有这一块滚 */
.ef-dialog__body {
  position: relative;
  flex: 1 1 auto; /* flex-auto */
  min-height: 0; /* min-h-0 */
  overflow-y: auto; /* overflow-y-auto */
  overscroll-behavior: contain; /* overscroll-contain */
  padding: 1rem; /* p-4 */
  background-image: radial-gradient(
    circle,
    color-mix(in srgb, var(--ef-ink) 10%, transparent) var(--dot-size, 1px),
    transparent calc(var(--dot-size, 1px) + 0.5px)
  ); /* dot-grid */
  background-size: var(--dot-gap, 16px) var(--dot-gap, 16px); /* dot-grid */
}
.ef-dialog__body > * {
  padding: 1.25rem; /* p-5 */
  background: var(--ef-surface-raised); /* 正文那块实色底 */
  overflow-wrap: anywhere; /* wrap-anywhere */
}

/* 行动区：按钮靠右、主要行动在最右；与内容区之间一条线 */
.ef-dialog__actions {
  display: flex;
  flex: none; /* shrink-0 */
  flex-wrap: wrap; /* flex-wrap */
  justify-content: flex-end; /* justify-end */
  gap: 0.75rem; /* gap-3 */
  border-top: 1px solid var(--ef-line); /* border-t border-line */
  background: var(--ef-surface-raised); /* bg-surface-raised */
  padding: 1rem 1.25rem; /* px-5 py-4 */
}

/* 起点：整块移到屏幕外（位移只写在起点上，退场不位移） */
@starting-style {
  .ef-drawer[open][data-side="right"] {
    translate: 100% 0; /* data-starting-style:translate-x-full */
  }
  .ef-drawer[open][data-side="left"] {
    translate: -100% 0; /* data-starting-style:-translate-x-full */
  }
  .ef-drawer[open][data-side="bottom"] {
    translate: 0 100%; /* data-starting-style:translate-y-full */
  }
}

.ef-drawer::backdrop,
.ef-dialog::backdrop {
  background: var(--ef-scrim, rgb(0 0 0 / 0.5)); /* bg-scrim */
}

/* 标题带：比弹窗更矮（48px），标题左对齐、小一档 */
.ef-drawer .ef-dialog__band {
  align-items: flex-start;
  min-height: 3rem; /* min-h-12 */
  padding: 0.625rem 3rem 0.625rem 1rem; /* pl-4 pr-12 */
}
.ef-drawer .ef-dialog__band > :is(h2, h3) {
  font-size: var(--text-lg); /* text-lg */
  line-height: 1.2; /* text-lg--line-height */
  text-align: left;
}

/* 内容区：抽屉不铺点阵，正文也不再垫实色块 */
.ef-drawer .ef-dialog__body {
  padding: 1rem; /* p-4 */
  background-image: none;
}
.ef-drawer .ef-dialog__body > * {
  padding: 0;
  background: none;
  overflow-wrap: anywhere; /* wrap-anywhere */
}

/* 行动区比弹窗紧一档 */
.ef-drawer .ef-dialog__actions {
  padding: 0.75rem 1rem; /* px-4 py-3 */
}

/* --------------------------------------------------------------------------
   减少动态效果：全局只把时长压到 1ms，这里给两处"静态替代形"
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* 停成 45° 的菱形，和列表里的菱形记号同一个样子 */
  .ef-spinner::before {
    animation: none; /* motion-reduce:animate-none */
    rotate: 45deg; /* motion-reduce:rotate-45 */
  }
  /* 停在轨道正中，不会被看成"进度 40%" */
  .ef-progress__bar[data-indeterminate] > span {
    animation: none;
    width: 40%;
    translate: 75% 0; /* motion-reduce:translate-x-3/4 */
  }
}
/* ==========================================================================
   endfield-ui 页面骨架（三个站共用）
   --------------------------------------------------------------------------
   组件样式在 endfield.css；这里只放「页面级」的东西：外壳、栅格、分节节奏、
   页脚，以及少量跟着内容走的文字工具类（mono / muted / kv / hint / pill）。
   这些名字同时也被 huanxx-admin 的 app.js 和内联脚本生成的 HTML 使用，
   改名前先 grep 一遍脚本。
   ========================================================================== */

/* --------------------------------------------------------------------------
   外壳：左侧竖轨 + 主列
   -------------------------------------------------------------------------- */
.ef-shell {
  display: flex;
  min-height: 100svh;
}

.ef-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* 主列宽：文档示例页用的 max-w-5xl（64rem），留出竖轨后视觉重心略偏右 */
.ef-wrap {
  width: 100%;
  max-width: 64rem;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 3rem);
}

/* 版块之间的节奏：紧的地方很紧，松的地方很松 */
.ef-page {
  display: flex;
  flex-direction: column;
  gap: clamp(3rem, 7vw, 5.5rem);
  padding-block: clamp(1.5rem, 3vw, 2.5rem) clamp(4rem, 8vw, 6rem);
}

.ef-section {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

/* --------------------------------------------------------------------------
   页脚：备案、署名、微标签
   -------------------------------------------------------------------------- */
.ef-footer {
  margin-top: auto;
  border-top: 1px solid var(--ef-line);
  padding-block: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.5rem;
  font-size: var(--text-xs);
  color: var(--ef-ink-secondary);
}

.ef-footer .ef-spacer {
  flex: 1 1 auto;
}

.ef-footer a {
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--duration-fast) var(--ease-standard);
}

.ef-footer a:hover {
  border-bottom-color: currentColor;
}

.ef-beian {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}

/* --------------------------------------------------------------------------
   文字工具类（脚本会用到，勿改名）
   -------------------------------------------------------------------------- */
.ef-mono {
  font-family: var(--font-tech);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* 名 / 值 列表：标签名窄、值宽，行间一条发丝线 */
.ef-kv {
  display: grid;
  grid-template-columns: minmax(7rem, max-content) minmax(0, 1fr);
  gap: 0;
  margin: 0;
}

.ef-kv dt,
.ef-kv dd {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--ef-line);
  font-size: var(--text-sm);
  line-height: 1.5;
  min-width: 0;
}

.ef-kv dt {
  color: var(--ef-ink-secondary);
  padding-right: 1rem;
}

.ef-kv dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* 短状态：胶囊只放「在线 / 离线」这种两三个字 */
.ef-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.125rem 0.5rem;
  border: 1px solid var(--ef-line-strong);
  border-radius: 999px;
  font-family: var(--font-tech);
  font-size: var(--text-micro);
  line-height: 1.6;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ef-ink-secondary);
}

.ef-pill::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  background: currentColor;
  border-radius: 999px;
}

.ef-pill.on,
.ef-pill.pub {
  color: var(--ef-success);
  border-color: color-mix(in srgb, var(--ef-success) 45%, transparent);
}

.ef-pill.off,
.ef-pill.loop {
  color: var(--ef-ink-secondary);
}

.ef-pill.warn {
  color: var(--ef-warning);
  border-color: color-mix(in srgb, var(--ef-warning) 45%, transparent);
}

/* 代码块 */
.ef-code {
  margin: 0;
  padding: 1rem;
  max-height: 22rem;
  overflow: auto;
  background: var(--ef-surface-sunken);
  border-left: 2px solid var(--ef-line-strong);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.55;
  white-space: pre;
  tab-size: 2;
}

.ef-code.tall {
  max-height: 32rem;
}

/* 窄屏：外壳改成上边栏 */
@media (max-width: 48rem) {
  .ef-shell {
    display: block;
  }
}
/* ==========================================================================
   页面入场动效
   --------------------------------------------------------------------------
   依据 docs/design/foundations/motion.md：

     · 短（200 / 300 / 400ms）、硬（位移、擦入、明暗），只动 transform /
       opacity / clip-path，不进布局计算；
     · 进场 300ms、退出 200ms；方向位移只写在进场的起点上（4px / 8px）；
     · 一组逐项入场每项错开 50–100ms，最多前六项，整组不超过 600ms；
     · 列表、时间线、步骤条不做默认的逐项入场；
     · prefers-reduced-motion 下全部关掉，内容直接到位。

   两种用法：
     .ef-up / .ef-left / .ef-fade / .ef-wipe        挂上就播（页头这类一进来就在眼前的东西）
     .ef-reveal（容器）                            进视口才播，里面的直接子项错开上浮

   用 .ef-reveal 时要在 <head> 里先给 <html> 加 .ef-js（见各页的模板），
   这样脚本没跑起来时内容仍然可见。
   ========================================================================== */

@keyframes ef-in-up {
  from {
    opacity: 0;
    transform: translateY(var(--motion-shift-lg));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes ef-in-left {
  from {
    opacity: 0;
    transform: translateX(calc(-1 * var(--motion-shift-lg)));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes ef-in-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes ef-in-wipe {
  from {
    clip-path: inset(0 100% 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

/* 挂上就播：300ms，ease-out，停在终态 */
.ef-up,
.ef-left,
.ef-fade {
  animation-duration: var(--duration-base);
  animation-timing-function: var(--ease-exit);
  animation-fill-mode: both;
}

.ef-up { animation-name: ef-in-up; }
.ef-left { animation-name: ef-in-left; }
.ef-fade { animation-name: ef-in-fade; }

/* 错开：各自晚 100ms，只在前四项上用 */
.ef-d1 { animation-delay: 100ms; }
.ef-d2 { animation-delay: 200ms; }

/* 进视口才播：容器加 .ef-reveal，脚本进视口时补上 .in */
html.ef-js .ef-reveal:not(.in) > * {
  opacity: 0;
}

html.ef-js .ef-reveal.in > * {
  animation: ef-in-up var(--duration-base) var(--ease-exit) both;
}

html.ef-js .ef-reveal.in > :nth-child(1) { animation-delay: 0ms; }
html.ef-js .ef-reveal.in > :nth-child(2) { animation-delay: 60ms; }
html.ef-js .ef-reveal.in > :nth-child(3) { animation-delay: 120ms; }
html.ef-js .ef-reveal.in > :nth-child(4) { animation-delay: 180ms; }
html.ef-js .ef-reveal.in > :nth-child(5) { animation-delay: 240ms; }
html.ef-js .ef-reveal.in > :nth-child(6) { animation-delay: 300ms; }
/* 第七项起一起到：整组停在 600ms 以内 */
html.ef-js .ef-reveal.in > :nth-child(n + 7) { animation-delay: 300ms; }

/* 减少动态效果：不播，也不留等待态 */
@media (prefers-reduced-motion: reduce) {
  html.ef-js .ef-reveal:not(.in) > * {
    opacity: 1;
  }

  .ef-up,
  .ef-left,
  .ef-fade,
  .ef-wipe,
  html.ef-js .ef-reveal.in > *,
  .ef-live {
    animation: none;
  }
}
