@import "tailwindcss";

/* ─────────────────────────────────────────────────────────
 * PAC Design Tokens(design-system.md §2-§4)
 *
 * 沿用 shadcn 的 HSL var 模式(方便组件级覆盖),但:
 *   - primary 改成 brand-600(PAC 品牌 = PANTONE 286 C / #0032A0)
 *   - border 加深(default shadcn 91.4% 视觉太弱)
 *   - 阴影整体放轻(Claude / OpenAI 现代风:border 主导,shadow 仅用于"浮起"场景)
 *   - 字体优先 PingFang(医疗 SaaS 中文场景)
 *
 * 加新色?加到 :root 再到 @theme inline 暴露 utility(bg-X / text-X)。
 * ───────────────────────────────────────────────────────── */

@layer base {
  :root {
    /* === 中性灰阶(shadcn slate)=== */
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;

    /* === PAC 品牌色 === */
    --primary: 221.25 100% 31.4%;           /* brand-600 = #0032A0(PANTONE 286 C) */
    --primary-foreground: 0 0% 100%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --destructive: 347 77% 50%;             /* rose-600 */
    --destructive-foreground: 0 0% 100%;

    /* === 边框 — 加深(91.4% → 87%,1px 视觉更扎实)=== */
    --border: 214 24% 87%;
    --input: 214 28% 82%;                   /* 输入框边再深一档,跟 hover 状态拉开 */
    --ring: 221.25 100% 31.4%;              /* focus ring 与 primary 同色 */

    --radius: 0.5rem;
  }
}

@theme inline {
  /* ⭐ === PAC 品牌色阶 brand-50…900 ===
   *
   * 主色 = **PANTONE 286 C / #0032A0**(RGB 0/50/160),坐在 600 档 —— 因为
   * 全站 `bg-brand-600` 是主按钮、`--primary` 也指这一档,主色必须落在这里才算"改了主色"。
   * 其余档由同色相(H=221.25)调明度/饱和推出:浅档做底纹(brand-50 是最常用的选中/命中底),
   * 深档做正文链接(brand-700 用得最多)。
   *
   * ⚠️ 类名从 `teal-*` 整体改名成 `brand-*`(263 处)—— 不是把 Tailwind 的 teal 调色板
   * 偷偷改成蓝的:那样类名写着 teal 渲染出蓝,下一个人读代码会以为自己看错了。
   * 品牌色就该叫 brand,换色只改这一段。
   *
   * 换色只需改下面 10 行;别在组件里写死十六进制(宠物 SVG 是唯一例外,见 pet-body.tsx)。
   */
  --color-brand-50:  #F0F4FF;
  --color-brand-100: #DCE7FE;
  --color-brand-200: #BACFFD;
  --color-brand-300: #85A9FA;
  --color-brand-400: #3B76F7;
  --color-brand-500: #0043D6;
  --color-brand-600: #0032A0;  /* ← 主色 PANTONE 286 C */
  --color-brand-700: #002985;
  --color-brand-800: #01226A;
  --color-brand-900: #031C53;

  /* === 颜色 token → 自动生成 bg-{name} text-{name} border-{name} === */
  --color-background: hsl(var(--background));
  --color-foreground: hsl(var(--foreground));
  --color-card: hsl(var(--card));
  --color-card-foreground: hsl(var(--card-foreground));
  --color-popover: hsl(var(--popover));
  --color-popover-foreground: hsl(var(--popover-foreground));
  --color-primary: hsl(var(--primary));
  --color-primary-foreground: hsl(var(--primary-foreground));
  --color-secondary: hsl(var(--secondary));
  --color-secondary-foreground: hsl(var(--secondary-foreground));
  --color-muted: hsl(var(--muted));
  --color-muted-foreground: hsl(var(--muted-foreground));
  --color-accent: hsl(var(--accent));
  --color-accent-foreground: hsl(var(--accent-foreground));
  --color-destructive: hsl(var(--destructive));
  --color-destructive-foreground: hsl(var(--destructive-foreground));
  --color-border: hsl(var(--border));
  --color-input: hsl(var(--input));
  --color-ring: hsl(var(--ring));

  /* === radius === */
  --radius-lg: var(--radius);
  --radius-md: calc(var(--radius) - 2px);
  --radius-sm: calc(var(--radius) - 4px);

  /* === 阴影 — 整体放轻(Claude/Anthropic/OpenAI 风:几乎只见 border)===
   * 对照 shadcn 默认 / Tailwind v4 默认:都偏 0.05-0.10 透明度;
   * 我们改 0.03-0.06,叠加更克制,只用于"浮起"场景(popover/dialog)。
   */
  --shadow-2xs: 0 1px 1px 0 rgb(15 23 42 / 0.03);
  --shadow-xs:  0 1px 2px 0 rgb(15 23 42 / 0.04);
  --shadow-sm:  0 1px 2px 0 rgb(15 23 42 / 0.04);
  --shadow:     0 1px 3px 0 rgb(15 23 42 / 0.06);
  --shadow-md:  0 2px 8px -2px rgb(15 23 42 / 0.06), 0 1px 3px -1px rgb(15 23 42 / 0.04);
  --shadow-lg:  0 6px 16px -4px rgb(15 23 42 / 0.08), 0 2px 6px -2px rgb(15 23 42 / 0.04);
  --shadow-xl:  0 12px 24px -6px rgb(15 23 42 / 0.10), 0 4px 8px -4px rgb(15 23 42 / 0.05);

  /* === 字体 — 优先 PingFang(中文)+ 系统栈兜底 === */
  --font-sans: "PingFang SC", "Noto Sans CJK SC", ui-sans-serif, system-ui,
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Microsoft YaHei", sans-serif;
}

/* 全局 border 颜色 — 这样写不用每处都标 border-border
 *
 * ⚠️ **必须待在 `@layer base` 里**(2026-08-11 修)。原来它是**无层级**的裸 `*` 规则,
 *   而无层级样式整体胜过 `@layer utilities`(与优先级无关)—— 于是它压过了 Tailwind
 *   **全部** `border-*` 颜色工具类:组件里写 `border-l-brand-400` / `border-rose-300`
 *   不报错、也不生效,算出来永远是这个灰。实测全站 258 处彩色 border 类无一生效,
 *   连 `border-t-transparent`(转圈 loading 的缺口)和 `border-current` 都被吃掉。
 *   放进 base 层后 utilities 重新压得过它,工具类恢复正常。
 *
 * ⛔ 别再把它挪出 `@layer`,也别加 `!important` —— 那等于把这个坑原样刨回来。
 * 备注:Tailwind v4 没有 `--default-border-color` 这个主题变量(v3 的
 *   `borderColor.DEFAULT` 在 v4 已移除,preflight 改成 `border: 0 solid` = currentColor),
 *   所以「分层的 `*` 规则」就是 v4 官方给的写法,不存在更声明式的替代。 */
@layer base {
  * {
    border-color: hsl(var(--border));
  }
}

body {
  background: hsl(var(--background));
  color: hsl(var(--foreground));
  font-family: var(--font-sans);
}

/* ─────────────────────────────────────────────────────────
 * 自定义 utilities(Tailwind v4 @utility 推荐写法)
 *
 * 用途:跨组件复用的视觉模式,不到组件抽象的程度(轻量布局/状态)。
 * v3 用 @apply,v4 推荐 @utility(更精准,不污染 base layer,可被 variant 修饰)。
 * ───────────────────────────────────────────────────────── */

/** 次级信息行 — 淡 bg,放在 Card 内做"二级数据条"(push secret / endpoint / event row) */
@utility row-quiet {
  background: hsl(var(--muted) / 0.5);
  border-radius: var(--radius-sm);
}

/** 卡片内的小型分组容器(凭据 / push secret 列表的外框) */
@utility surface-inset {
  background: hsl(var(--card));
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius-md);
}

/** 等宽数字 — 金额/时间/计数(design-system.md §3) */
@utility nums {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}


/* fadeIn — EmrSoapView carousel 切换淡入 */
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(2px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* shimmerText — AI 流式生成时标题文字扫光闪动(GPT / Claude Code 风格) */
@keyframes shimmerText {
  0%   { background-position: 150% 0; }
  100% { background-position: -150% 0; }
}
.shimmer-text {
  background: linear-gradient(
    100deg,
    rgb(30 41 59) 35%,
    rgb(148 163 184) 50%,
    rgb(30 41 59) 65%
  );
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: shimmerText 1.6s linear infinite;
}

/* 实时教练 — 语音波形脉冲 */
@keyframes audioWave {
  0%, 100% { height: 4px; opacity: 0.45; }
  50%      { height: 28px; opacity: 1; }
}
.animate-audioWave { animation: audioWave 0.7s ease-in-out infinite; }

/* 实时教练 — 录音中麦克风按钮呼吸光圈 */
@keyframes coachPulse {
  0%   { box-shadow: 0 0 0 0 rgba(244,63,94,0.45); }
  70%  { box-shadow: 0 0 0 12px rgba(244,63,94,0); }
  100% { box-shadow: 0 0 0 0 rgba(244,63,94,0); }
}
.animate-coachPulse { animation: coachPulse 1.5s ease-out infinite; }

/* 实时教练 — 面板进场 */
@keyframes slideInUp {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}
.animate-slideInUp { animation: slideInUp 0.2s ease-out; }

/* 实时教练 — 字幕当前句滑入吸睛(新句从下方淡入+轻微放大) */
@keyframes lyricIn {
  from { opacity: 0; transform: translateY(10px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.animate-lyricIn { animation: lyricIn 0.32s cubic-bezier(0.22, 1, 0.36, 1); }

/* ── 统一滚动条 —— Windows 默认滚动条过粗突兀;细轨 + slate 拇指,贴 PAC 中性色。
   现代 Chrome/Edge/Firefox 走标准属性(scrollbar-width/color);
   Safari 及旧 Chromium 走 ::-webkit-scrollbar(标准属性生效时自动忽略 webkit 版)。 */
* {
  scrollbar-width: thin;
  scrollbar-color: rgb(203 213 225) transparent; /* slate-300 / 轨道透明 */
}
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: rgb(203 213 225);
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
  background: rgb(148 163 184); /* slate-400 */
}
::-webkit-scrollbar-corner {
  background: transparent;
}

/* ── Radix 浮层的出现/消失 ────────────────────────────────────────
   ⚠️ shadcn 官方那套 `animate-in fade-in-0 zoom-in-95` 来自 tailwindcss-animate 插件,
   **本仓没装**(内网拉包本身是风险)—— 那些 class 写了也不生效,
   popover.tsx 里原先就挂着一串,实际一帧动画都没有。这里用原生 keyframes 补上。

   ⚠️⚠️ 必须经 `@theme` 注册成 `--animate-*`,**不能只写一条 `.animate-overlayIn` 类**:
   浮层要靠 `data-[state=open]:animate-overlayIn` 触发,而 Tailwind 的 variant
   只作用在**它自己生成的 utility** 上 —— 手写的同名 class 拿不到 variant,
   实测算出来 animation-name: none(class 在、样式在,就是没被 variant 选中)。 */
@keyframes overlayIn {
  from { opacity: 0; transform: scale(0.96) translateY(-2px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes overlayOut {
  from { opacity: 1; transform: scale(1) translateY(0); }
  to   { opacity: 0; transform: scale(0.96) translateY(-2px); }
}
@theme {
  --animate-overlayIn: overlayIn 0.14s cubic-bezier(0.22, 1, 0.36, 1);
  --animate-overlayOut: overlayOut 0.1s ease-in;
}

/* ── 人群移交:数据流粒子 ──────────────────────────────────────────
   点中矩阵某一格 → 一串小方块沿一条弧线飞向助手,像数据被"抽走"。
   走 CSS Motion Path(offset-path):曲线一次算好塞进 --pac-stream-path,
   每颗粒子只改 offset-distance —— 位置由合成器算,不占主线程,
   与本仓「不引动画库」的既有取舍一致(宠物那 31 个 keyframes 也是这么来的)。
   ⚠️ 粒子是 position:fixed + left/top:0,所以 path 里的坐标**就是视口坐标**,
     调用方直接把 getBoundingClientRect() 的值塞进去即可,不用换算。 */
@keyframes pacStreamFly {
  0%   { offset-distance: 0%;   opacity: 0; scale: 0.5; }
  15%  { opacity: 1; scale: 1; }
  80%  { opacity: 1; scale: 1; }
  100% { offset-distance: 100%; opacity: 0; scale: 0.4; }
}
.pac-stream-dot {
  position: fixed;
  left: 0;
  top: 0;
  width: 7px;
  height: 7px;
  border-radius: 2px;
  pointer-events: none;
  z-index: 62; /* 压过助手窗(60)与 FAB(61):粒子要能飞到脸上 */
  offset-path: var(--pac-stream-path);
  offset-rotate: 0deg; /* 保持正方向,不让方块跟着切线转 —— 转起来像碎屑不像数据 */
  animation: pacStreamFly var(--pac-stream-dur, 620ms) cubic-bezier(0.4, 0, 0.35, 1) forwards;
}
@media (prefers-reduced-motion: reduce) {
  /* 无障碍:不飞,直接不出现(移交本身仍照常发生,只是没有这段演出) */
  .pac-stream-dot { display: none; }
}

/* ── 初选矩阵:悬停 = **瞄准**(点下去才是发射,接上面那串粒子)────────────
   ⚠️ 这一整段只在 `pool-matrix` 用。它做的是**一件事的两半**:
     · 准星(这里)—— 你正指着**哪一格**;
     · 十字(组件里的行/列高亮)—— 那一格**是谁 × 什么时候**。
   8 行 × 6 列的表,格子离行头和列头都很远,光把格子框起来答不了后一半。

   🔴 用**角括号**而不是整圈实线框:
     实线框留给「**已经选中**」(组件里 `selected` 那一支)。两个状态长得一样的话,
     "我正指着" 和 "我已经选了" 就分不出来 —— 而这一点下去人就发出去了。
   ⚠️ 画在**伪元素**上,⛔ 不画在按钮自己的 background 上:
     按钮的底色要留给十字那层薄纱,两者叠在一起时得各归各的图层。
   ⚠️ 这个类由组件**按 state 挂**(只挂在瞄中的那一格),所以下面 `::after` ⛔ 不加 `:hover` ——
     0 人的格子是 disabled,而 disabled 的 hover 跨浏览器不一致,靠 CSS 拦不住。 */
.pac-reticle { position: relative; }
.pac-reticle::after {
  content: '';
  position: absolute;
  inset: 2px;
  pointer-events: none;
  --pac-ret-c: var(--color-brand-600);
  --pac-ret-b: 1.5px;  /* 线宽 */
  --pac-ret-l: 7px;    /* 每个角的臂长 */
  /* 四个角 = 8 条渐变(每角一横一竖)。⚠️ 这样零 DOM、零 SVG,
     而且随格子尺寸自适应(角永远贴在四角,中间留空)。 */
  background-image:
    linear-gradient(var(--pac-ret-c), var(--pac-ret-c)),
    linear-gradient(var(--pac-ret-c), var(--pac-ret-c)),
    linear-gradient(var(--pac-ret-c), var(--pac-ret-c)),
    linear-gradient(var(--pac-ret-c), var(--pac-ret-c)),
    linear-gradient(var(--pac-ret-c), var(--pac-ret-c)),
    linear-gradient(var(--pac-ret-c), var(--pac-ret-c)),
    linear-gradient(var(--pac-ret-c), var(--pac-ret-c)),
    linear-gradient(var(--pac-ret-c), var(--pac-ret-c));
  background-repeat: no-repeat;
  background-size:
    var(--pac-ret-l) var(--pac-ret-b), var(--pac-ret-b) var(--pac-ret-l),
    var(--pac-ret-l) var(--pac-ret-b), var(--pac-ret-b) var(--pac-ret-l),
    var(--pac-ret-l) var(--pac-ret-b), var(--pac-ret-b) var(--pac-ret-l),
    var(--pac-ret-l) var(--pac-ret-b), var(--pac-ret-b) var(--pac-ret-l);
  background-position:
    0 0, 0 0,
    100% 0, 100% 0,
    0 100%, 0 100%,
    100% 100%, 100% 100%;
  /* ⭐ 两段:先**咬合**(从格子外一口收进来),再**呼吸**(产品要的"忽大忽小")。
     ⚠️ 呼吸幅度只有 1.5px、周期 2.4s —— 再大就从"锁定中"变成"这里出错了"。 */
  animation:
    pacReticleLock 0.18s cubic-bezier(0.2, 1.3, 0.4, 1),
    pacReticleBreathe 2.4s ease-in-out 0.18s infinite;
}
/* 扣扳机的那一下:准星收紧,紧接着交给粒子(pacStreamFly) */
.pac-reticle:active::after { inset: 3.5px; animation: none; transition: inset 0.08s ease-out; }

/* ⚠️ from 从 `-2px` 改成 `0`(2026-08-20):负 inset = 画到格子**外面**,
   而渐变面带 `overflow-hidden` —— 最边上那一格的这一帧会被裁掉半个角。
   0 → 2px 一样读得出"从边缘一口咬进来",且全程在盒子内。 */
@keyframes pacReticleLock {
  from { inset: 0;   opacity: 0; }
  to   { inset: 2px; opacity: 1; }
}
@keyframes pacReticleBreathe {
  0%, 100% { inset: 2px;   opacity: 1; }
  50%      { inset: 3.5px; opacity: 0.7; }
}

@media (prefers-reduced-motion: reduce) {
  /* 无障碍:准星照旧画出来(它是**信息**:你指着哪一格),只是不动 */
  .pac-reticle::after { animation: none; }
}

/* ── 「分一批新的 · 里世界」传送门 —— **2026-08-11 删** ──────────────
   这里原来是那版全屏「打地鼠」的动效：一个 200×200 的小圆从按钮位置
   `transform: scale()` 放大盖满全屏（`.pac-portal-disc`），内容随后 `opacity` 淡入
   （`.pac-portal-body`），外加敲击形变 `whack` 与冲击环 `ringOut`。
   产品定下只留矩阵那一版、并且常驻在页面上，整套演出连同 `ArcadeMatrix` 一起删。

   ⚠️ 万一哪天要复活，去 git 里翻（`new-batch.tsx` 的 `ArcadeMatrix`），
     ⛔ 别凭印象重写 —— 那里踩过两条不重写一遍就还会再踩的坑：
     ① 幕布**别用 `clip-path: circle()`**：`at <x> <y>` 按元素的 reference box 算，
        `fixed` 的包含块被祖先 transform/filter/contain 劫走后圆心整体偏，且不报错。
     ② 幕布**别做成全屏再 scale**：纹理按未缩放尺寸申请（2700² ≈ 28MB）。 */
