/* ============================================================================
   agentcss / 03-patterns / form-validation / form-validation.css
   表单校验

   依赖：01-tokens/tokens.css  +  02-primitives/input/input.css
   版本：A ✅ / B ✅ / C ✅

   🔴 本组件只做"编排"，不重定义输入框 —— 那是 input 的事。
      这里负责三件 input 管不了的事：
        1. 校验时机（什么时候才报错了）
        2. 错误汇总（提交失败时怎么让人一次看到全部问题）
        3. 焦点管理（提交失败后焦点去哪）
   ========================================================================= */

.form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.form__row {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.form__actions {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  flex-wrap: wrap;
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border-decor);
}

/* ============================================================================
   1. 错误汇总 —— 提交失败时必做
   =========================================================================
   🔴 这是 WCAG 3.3.1（错误识别）和 3.3.3（错误建议）的落点。

   为什么必须有：
   一个 12 字段的表单，3 个字段错了。错误提示分散在各字段下方时，
   用户要上下扫、自己数"还错几个"、猜"上面那个是不是也错了"。
   汇总把三件事一次说清：有几处错了 / 分别是哪里 / 怎么改。
   ========================================================================= */
.form__summary {
  /* 🔴 改成**一行且宽度按内容**（要求：错误多的时候提示太大，
     应该收窄，然后放到一行去就可以了）。
     原来是 column 竖排：标题一行 + <ul> 里每项一行 ⇒ 错 5 处就占 7 行。
     现在 inline-flex：整条提示只占一行，宽度由内容决定。
     —— 信息量没变（几处 / 哪几处 / 可点击跳转），但**占用的竖向空间从 O(n) 变成 O(1)**。 */
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  max-width: 100%;
  padding: var(--sp-3) var(--sp-4);
  background: var(--danger-bg);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

/* 🔴 去掉 `border: 1px solid danger` + `border-left: 4px solid`。
   原来那两条的理由写得很清楚：「左边框加粗，色弱用户也能靠"这里有个条"发现它」。
   **这个理由本身是对的，但手段选错了** ——
     4px 竖条在页面上拉出一条与内容无关的硬边，视觉噪声大于信息量。
   改用**图标**承担同一份职责：同样让色弱用户一眼看到"这里出错了"，
   但它跟着文字走，不会形成横贯页面的竖线。 */
.form__summary::before {
  content: "!";
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  margin-inline-end: var(--sp-2);
  border-radius: 50%;
  background: var(--danger);
  /* 🔴 同上：暗色下 danger 变亮 ⇒ 白叹号看不清。 */
  color: var(--text-on-accent);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}

.form__summary-title {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--danger);   /* 6.26 AA on danger-bg */
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* 汇总里每条都是可点击的链接，点了跳到对应字段并聚焦它。
   <a href="#field-id"> 天然支持键盘和"复制链接"，比 <div onclick> 好。 */
  /* 🔴 汇总条目不再包 <ul>，直接是并列的 <a>。
     <ul> 的竖排是"提示占满竖向空间"的元凶。 */
  .form__summary-link + .form__summary-link { margin-inline-start: var(--sp-1); }

.form__summary-link {
  display: inline-block;
  color: var(--danger);
  font-size: var(--fs-sm);
  text-decoration: underline;
  text-underline-offset: 2px;   /* 下划线离字远一点，中文小字更易读 */
  text-decoration-thickness: 1px;
}
@media (hover: hover) and (pointer: fine) {
.form__summary-link:hover { color: var(--text-primary); }
}
.form__summary-link:focus-visible {
  outline: 2px solid var(--danger);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ============================================================================
   2. 校验时机 —— 本组件最关键的判断
   =========================================================================
   🔴 不要在 oninput 时报错。

   用户刚开始打字，字段就变红 —— 打到一半全是红字，用户根本不知道自己
   哪里错了，也不敢继续打。这个反馈是纯噪音，甚至会让人放弃填表。

   正确时机（三层）：
     a) onblur        —— 字段失焦时校验一次（用户已经输完一段了）
     b) 之后才实时    —— 字段"脏"过之后（用户回来修改）才实时报错
     c) onsubmit     —— 提交时全量校验，并弹出错误汇总

   "脏"用 `data-dirty` 标记，由 JS 加。CSS 只负责让脏字段的错误显示出来。

   🔴 这里刻意**不用 `:has()`**（写第一版时用了，是错的）：
      一是把"何时校验"这个逻辑硬塞进样式层，行为不可测；
      二是 `:has()` 属 CSS Level 4，兼容性不稳 —— 某项目 上的 WebView
      版本不确定，精简档 不能冒这个险。
      控制权交给 JS：字段校验后由 JS 加 `data-dirty`，CSS 只认这个属性。

   ⚠️ 为什么是 `data-dirty` 而不是 `data-state="dirty"`：
      `data-state` 是"这个组件当前处于哪个枚举状态"（open/closed/…），
      "脏"是**正交标记**（一个字段可以同时是 dirty 且 state=invalid）。
      塞进同一个属性 ⇒ 两个维度互斥，表达不了组合。
   ------------------------------------------------------------------------ */
/* 🔴 这里必须是 `.field .field__error`（两段），不能只写 `.field__error`。
   原因：`02-primitives/input/input.css` 给 `.field__error` 定了**外观**
   （`display:flex` + 图标 + 危险色），本文件给它定**可见性**（默认藏起来）。
   两条同特异性（0,1,0）⇒ 谁后加载谁赢。实测：

     input.css 先、本文件后 → display:none  ✅ 校验前不报红
     本文件先、input.css 后 → display:flex  🔴 首屏把全部错误都挂出来

   也就是说"错误提示该不该显示"取决于打包器的 CSS 顺序 —— 用户永远不会想到
   去查这个。加一层 `.field` 把特异性抬到 (0,2,0)，顺序就不再是变量。
   代价：错误元素必须在 `.field` 内（本库的结构契约就是这么定的）。 */
.field .field__error { display: none; }
.field[data-dirty] .field__error { display: flex; }

/* 错误汇总展开时，全部错误强制显示，不管字段脏不脏 —— 那是用户主动要看清单。
   ⚠️ 原来这里是 `.form--has-summary .field__error`（不带 :not(:empty)），
   它会把**空元素也显示出来** —— 于是没填错的字段下方挂着空红点。
   实测反馈"下方的红点一直存在"，根因就在这里。 */
.form--has-summary .field__error:not(:empty) { display: flex; }

/* 🔴 兜底（必须放在上面两条**之后**）：**没有文字的错误提示一律不显示**。
   `[data-dirty]` 只表示"这个字段被校验过"，**不表示"它现在有错"**。
   以前没内容时看不出问题（空元素不渲染任何东西），
   加了错误图标之后，空元素会露出一个圆点。

   🔴 教训：我第一次把这条写在**上面**，被后面同优先级的
   `.form--has-summary .field__error` 覆盖掉了，等于没写。
   **兜底规则必须放在它要约束的所有规则之后**，否则会被吃掉。 */
.field__error:empty { display: none; }

/* 错误图标 —— 兑现 README「三条线索」里的第 2 条（红边框 + 叹号 + 错误文字）。
   🔴 补的：README 一直这么写，但实现里一直缺这一条
   （审核时才发现"文档承诺 ≠ 实现存在"）。

   为什么不用 Unicode 符号（❌ ⚠ 等）：
   缺字会渲染成**空心方框（tofu）**，而且小尺寸下与实心符号肉眼难分。
   `!` 是 ASCII，任何字体都有，**不可能缺字**。

   为什么画成圆底而不直接放一个叹号：
   直接放字符在小字号下会像"多了一个字"，不像"这是个状态标记"。 */
.field__error::before {
  content: "!";
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 14px;
  height: 14px;
  margin-inline-end: var(--sp-1);
  border-radius: 50%;
  background: var(--danger);
  /* 🔴 暗色下底色变亮，写死的白字会看不清。 */
  color: var(--text-on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
}

/* ============================================================================
   3. 提交成功后的焦点去向
   =========================================================================
   提交成功后焦点必须**移走**。留在提交按钮上，用户按 Enter 会重复提交。
   移到成功提示上，让屏幕阅读器读出来。
   ------------------------------------------------------------------------ */
.form__notice {
  padding: var(--sp-4) var(--sp-5);
  background: var(--success-bg);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  color: var(--text-primary);
}

/* ============================================================================
   4. 校验中的状态（异步校验，比如"检查用户名是否可用"）
   =========================================================================
   🔴 别让输入框变灰或禁用 —— 用户会以为不能打字了。
   正确做法：保持可输入，在旁边显示一个小小的 pending 指示。
   ------------------------------------------------------------------------ */
.field--checking .field__input::after { content: ""; }   /* 占位，禁止改用 padding 挤压 */
.field__checking-hint {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}
.field__checking-hint::before {
  content: "";
  width: 12px;
  height: 12px;
  border: 2px solid var(--border-control);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: form-spin 700ms linear infinite;
}
@keyframes form-spin { to { transform: rotate(360deg); } }

/* ============================================================================
   reduced-motion
   =========================================================================
   与库内其他规则一致：原地旋转的 spinner 保留但放慢（停了以为卡死）。
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  .field__checking-hint::before { animation-duration: 1.2s !important; }
}

/* ============================================================================
   反模式
   =========================================================================
   1. oninput 时实时报错
      用户刚开始打字就一片红，噪音，且会让人放弃填表。

   2. 没有错误汇总
      12 个字段错 3 个，用户要自己数、自己找。WCAG 3.3.1 要求错误可识别。

   3. 汇总里的条目用 <div onclick> 跳转
      键盘不可达。用 <a href="#id">。

   4. 提交失败后焦点不动
      焦点还留在提交按钮，用户按 Enter 又提交一次。必须移到汇总上。

   5. 提交成功后焦点不动
      同上，会重复提交。移到成功提示上。

   6. 异步校验时禁用输入框
      用户以为不能打字了。保持可输入，旁边给小提示。

   7. 错误文案只说"格式错误"
      3.3.3 要求给修正建议。要写"最多两位小数，例如 12.34"，
      不要写"格式错误"或"非法输入"。
   ========================================================================= */
