# Skill

> ----------------------------------------------------------

- Skill: `guokaigdg/skill` (Agent Skill)
- Install (CLI): `npx skillmds@latest add guokaigdg/skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/guokaigdg/skill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: guokaigdg (https://skillmd.com/u/guokaigdg)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/guokaigdg/skill

---

----------------------------------------------------------

# Animal Island Vue 设计风格指南

> **三文档分工**（生成代码 / 调样式时按需查阅，避免互相翻查）：
>
> - `AI_USAGE.md` — API 手册：每个组件的 props、类型、默认值、合法取值、禁用用法。**写代码优先查这里**。
>
> - `skill/SKILL.md`（本文档）— 像素级样式：设计 token、每组件精确 CSS（hex/px/keyframe）、Demo 布局、新组件开发模板。**要自己实现/扩展样式时查这里**。
>
> - `DESIGN_PROMPT.md` — 给外部工具（v0 / Figma AI / Midjourney / DALL-E）的提示词包，含 clip-path、色板速查、禁用清单。**只在喂别的 AI 时用**。

## 概述

animal-island-vue 是一套自然可爱小岛风格的 Vue 3 + TypeScript UI 组件库。
设计语言核心：**温暖大地色系 + 大圆角 pill 形 + 游戏按键立体感 + 柔和动效 + 几何 / 有机形状并存**（几何代表：Title 飘带的 swallowtail clip-path；有机代表：Modal 的 SVG blob）。

- 源码：`src/components/<ComponentName>/`（每组件包含 `*.vue` + `index.ts` + 可选 `types.ts`）

- Demo 站：`demo/pages/<ComponentName>Demo.vue`

- 构建：Vite (library mode) + `vite.config.ts`（库）/ `vite.config.docs.ts`（Demo）

- 样式系统：**scoped** **`<style lang="less" scoped>`** **+ BEM** + `src/styles/variables.less` 设计 token（**不使用 CSS Modules**）

### 全量导出清单（40 个 named exports — 含子组件、命令式 API 与伴生导出）

从 `src/index.ts` 导出：

| 组件           | 职责                                                                                                              | 交互   | 装饰 / 纯展示 |
| -------------- | ----------------------------------------------------------------------------------------------------------------- | ------ | ------------- |
| `BackTop`      | 返回顶部按钮，钱袋图标浮窗，支持自定义滚动容器、动画时长和可见高度                                                | ✓      | <br />        |
| `Button`       | 按钮，5 种类型 × 3 种尺寸                                                                                         | ✓      | <br />        |
| `Input`        | 输入框，3 种尺寸 + clear/prefix/suffix                                                                            | ✓      | <br />        |
| `Switch`       | 开关，默认/小号                                                                                                   | ✓      | <br />        |
| `Modal`        | SVG blob 裁切弹窗                                                                                                 | ✓      | <br />        |
| `Card`         | 容器，`default`/`dashed`，13 种调色板实色 + 13 种 `pattern` 波点墙纸（CSS radial-gradient，非图片）               | <br /> | ✓             |
| `Title`        | 章节标题，飘带横幅（swallowtail clip-path 燕尾 + 折角阴影 + 微透视正面），13 种配色（替代旧 `Card type="title"`） | <br /> | ✓             |
| `Collapse`     | 手风琴（动画用 CSS Grid 0fr↔1fr 实现，无 JS 动画）                                                                | ✓      | <br />        |
| `Select`       | 下拉选择器（受控）                                                                                                | ✓      | <br />        |
| `Skeleton`     | 加载占位骨架屏（SkeletonButton、SkeletonInput、SkeletonAvatar）                                                   | <br /> | ✓             |
| `Checkbox`     | 多选框组，水平/垂直，3 种尺寸                                                                                     | ✓      | <br />        |
| `Radio`        | 单选框组，3 种尺寸，键盘 roving tabindex                                                                          | ✓      | <br />        |
| `Tooltip`      | 12 种 placement，`hover`/`focus`/`click` 触发，`default`/`island` 形态                                            | ✓      | <br />        |
| `Time`         | HUD 实时时钟                                                                                                      | <br /> | ✓             |
| `Footer`       | 底部装饰（14 个 🎄 居中一行，`seamless` 铺满整行）                                                                | <br /> | ✓             |
| `Divider`      | 装饰分割线，5 种风格                                                                                              | <br /> | ✓             |
| `Cursor`       | 自定义光标包裹器，`default` 手指箭头 / `raindrop` 蓝色雨滴（内联 SVG，零图片资源）                                | <br /> | ✓             |
| `Background`   | 装饰背景壁纸，`dots` 波点 / `sprinkles` 圆柱形彩色针糖（纯 CSS + 内联 SVG，零图片资源）                           | <br /> | ✓             |
| `Typewriter`   | 打字机效果，保留 VNode 结构                                                                                       | <br /> | ✓             |
| `Tabs`         | 标签页切换，叶子摆动动画可选                                                                                      | ✓      | <br />        |
| `CodeBlock`    | JSX/TS 语法高亮代码块                                                                                             | <br /> | ✓             |
| `Loading`      | 全屏遮罩 + SVG spinner（mint `#19c8b9`，`stroke-dasharray` 动画）                                                 | <br /> | ✓             |
| `Table`        | 数据表格，固定列、空状态、loading                                                                                 | ✓      | <br />        |
| `Pagination`   | 分页器，条数切换 / 快速跳转 / 总数展示，可内置于 Table                                                            | ✓      | <br />        |
| `Carousel`     | 轮播图，自动播放 / 箭头 / 圆点 / 键盘导航                                                                         | ✓      | <br />        |
| `Countdown`    | 倒计时，里程表式单向滚动数字                                                                                      | <br /> | ✓             |
| `Form`         | 表单系统（Form、FormItem、FormProvider、useForm）                                                                 | ✓      | <br />        |
| `Image`        | 相框图片，14 种底色、懒加载、点击预览                                                                             | ✓      | <br />        |
| `DatePicker`   | 日期选择器，日历网格 / 范围选择                                                                                   | ✓      | <br />        |
| `TimePicker`   | 时间选择器，时/分/秒滚动列                                                                                        | ✓      | <br />        |
| `Notification` | 命令式通知（含 NotificationContainer）                                                                            | ✓      | <br />        |
| `Tag`          | 标签，3 变体 × 13 色                                                                                              | <br /> | ✓             |
| `Progress`     | 进度条，斜纹动画填充                                                                                              | <br /> | ✓             |
| `Drawer`       | 抽屉，四方向弹出 + 焦点陷阱                                                                                       | ✓      | <br />        |

类型导出：`BackTopProps`、`ButtonProps/ButtonType/ButtonSize/ButtonHTMLType`、`InputProps/InputSize`、`SwitchProps/SwitchSize`、`ModalProps`、`CardProps/CardType/CardColor`、`TitleProps/TitleSize/TitleColor`、`CollapseProps`、`CursorProps/CursorType`、`BackgroundProps/BackgroundType`、`DividerProps/DividerType`、`TypewriterProps`、`SelectProps/SelectOption`、`SkeletonProps/SkeletonVariant`、`TabsProps/TabItem`、`CheckboxProps/CheckboxOption/CheckboxSize/CheckboxValue`、`RadioProps/RadioOption/RadioSize/RadioValue`、`TooltipProps/TooltipPlacement/TooltipTrigger/TooltipVariant`、`CodeBlockProps`、`TableProps/TableColumn/TableRecord`、`PaginationProps/PaginationVariant`、`CarouselProps`、`CountdownProps/CountdownSize/CountdownVariant`、`FormProps` 系列、`ImageProps/ImageColor`、`DatePickerProps/DatePickerSize/DatePickerStatus/DatePickerValue`、`TimePickerProps/TimePickerSize/TimePickerStatus/TimePart`、`NotificationConfig` 系列、`TagProps/TagSize/TagVariant/TagColor`、`ProgressProps/ProgressSize/ProgressInfoPosition`、`DrawerProps/DrawerPlacement`。

> Vue 端约定：
>
> - 受控值统一通过 `v-model` / `v-model:open` / `v-model:expanded`（即 `modelValue` + `update:modelValue` 等事件）
>
> - React 中的 `ReactNode` props 在 Vue 端改为**命名插槽**（`#icon`、`#prefix`、`#suffix`、`#footer`、`#checked`、`#unchecked`、`#question`、`#empty`、Table 的 `#cell-{dataIndex}` / `#header-{dataIndex}`、Tabs 按 `item.key` 命名的动态插槽等）；其余可结构化的内容统一通过**默认插槽**承载（如 Card、Collapse 答案区、Modal 主体、Typewriter 等）

---

## 1. Design Tokens

### 色彩系统

```less
// 主色（薄荷青绿）
@primary-color: #19c8b9;
@primary-color-hover: #3dd4c6;
@primary-color-active: #11a89b;
@primary-color-bg: #e6f9f6;

// 文字（温暖棕色系）
@text-color: #794f27; // 主文字（header/sidebar）
@text-color-body: #725d42; // 正文（组件内文字）
@text-color-secondary: #9f927d; // 次级文字
@text-color-muted: #8a7b66; // 浅棕（modal body）
@text-color-disabled: #c4b89e; // 禁用

// 边框
@border-color: #9f927d;
@border-color-light: #c4b89e; // 输入框边框
@border-color-hover: #a89878; // 输入框 hover

// 背景（奶油米白）
@bg-color: #f8f8f0; // 主背景
@bg-color-content: rgb(247, 243, 223); // 内容区（Modal、Card）
@bg-color-secondary: #f0e8d8;
@bg-color-disabled: #f0ece2;
@bg-color-input: rgb(247, 243, 223); // 输入框背景
@bg-color-input-dis: #ece8dc; // 输入框禁用

// 状态色
@success-color: #6fba2c;
@success-color-active: #5a9e1e;
@warning-color: #f5c31c;
@warning-color-active: #dba90e;
@error-color: #e05a5a;
@error-color-active: #c94444;

// 游戏特殊色
@focus-yellow: #ffcc00; // 焦点高亮（非蓝色）
@focus-yellow-dark: #e0b800; // 焦点阴影
@sidebar-active-bg: #b7c6e5; // 侧边栏选中背景
@sidebar-hover-bg: #d6dff0; // 侧边栏 hover 背景

// 3D 阴影色
@shadow-btn: #bdaea0; // 按钮 3D 阴影
@shadow-input: #d4c9b4; // 输入框 3D 阴影
@shadow-switch-on: #5a9e1e; // Switch 开启 3D 阴影
```

**应用调色板**（Card `color` prop 可选值）：

| color 值        | 背景色               | 文字色    |
| --------------- | -------------------- | --------- |
| default         | `rgb(247, 243, 223)` | `#725d42` |
| app-pink        | `#f8a6b2`            | `#fff`    |
| purple          | `#b77dee`            | `#fff`    |
| app-blue        | `#889df0`            | `#fff`    |
| app-yellow      | `#f7cd67`            | `#725d42` |
| app-orange      | `#e59266`            | `#fff`    |
| app-teal        | `#82d5bb`            | `#fff`    |
| app-green       | `#8ac68a`            | `#fff`    |
| app-red         | `#fc736d`            | `#fff`    |
| lime-green      | `#d1da49`            | `#3d5a1a` |
| yellow-green    | `#ecdf52`            | `#725d42` |
| brown           | `#9a835a`            | `#fff`    |
| warm-peach-pink | `#e18c6f`            | `#fff`    |

---

### 字体

项目使用两款 Google Fonts 圆体字，**必须**按以下方式引入，本地未安装时通过在线地址加载：

```html
<!-- 在 index.html <head> 中引入 -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
    href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900&family=Noto+Sans+SC:wght@400;500;700&display=swap"
    rel="stylesheet"
/>
```

或在 CSS / Less 入口文件顶部：

```css
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900&family=Noto+Sans+SC:wght@400;500;700&display=swap');
```

```css
font-family:
    Nunito,
    'Noto Sans SC',
    -apple-system,
    'PingFang SC',
    'Hiragino Sans GB',
    'Microsoft YaHei',
    sans-serif;
```

| 字体             | 用途               | Google Fonts key      |
| ---------------- | ------------------ | --------------------- |
| **Nunito**       | 主字体，拉丁字符   | `family=Nunito`       |
| **Noto Sans SC** | 中文字体，简体覆盖 | `family=Noto+Sans+SC` |

> Vue 版本同时通过 `@fontsource/*` 在 `src/index.ts` 直接 import 字体子集（`nunito` / `noto-sans-sc` / `zen-maru-gothic`），库消费者无需手动 `<link>`；如果你脱离组件库自实现，按上文 `<link>` 引入即可。如需扩展日文字符，自行 `@import` `Zen Maru Gothic` 或类似字体并追加到 `font-family` 末尾。

字重分级：

- 正文内容：**500**

- 按钮文字、标题、菜单项：**600–700**

- 数字强调（时间数字、时钟）：**900**

- placeholder / 说明文字：**400**

字间距：`letter-spacing: 0.01em`（正文）/ `0.02em`（按钮/标题）/ `1.5px`（星期大写）

禁止使用细体（weight < 400）或等宽字体。

---

### 间距 / 圆角 / 边框

```
间距：xs=4px  sm=8px  md=12px  lg=16px  xl=24px
圆角：sm=12px  base=18px  lg=24px  pill=50px（按钮/输入框）
边框：默认 2px solid，输入框 2.5px，大尺寸输入框 3px
```

---

### 阴影

```css
/* 卡片/容器阴影（暖色调，非冷黑）*/
box-shadow: 0 3px 10px 0 rgba(61, 52, 40, 0.1); /* 基础 */
box-shadow: 0 8px 24px 0 rgba(61, 52, 40, 0.14); /* 较大 */
/* Card 默认无 box-shadow（依赖 border / pattern 营造层次，不靠悬浮阴影）*/

/* 默认/虚线/文字/链接按钮阴影（柔和 elevation —— 非 3D 厚阴影）*/
box-shadow: 0 2px 4px 0 rgba(61, 52, 40, 0.06); /* btn-default 静止：--animal-shadow-sm */
box-shadow: 0 3px 10px 0 rgba(61, 52, 40, 0.1); /* btn-default hover：--animal-shadow-base */
/* active 回落到 --animal-shadow-sm，translateY(0) */

/* 游戏按键 3D 立体阴影（仅 primary / danger-primary 按钮；Input 仅 shadow={true} 时启用；Switch 仅 track inset 阴影，handle 无 box-shadow）*/
box-shadow: 0 5px 0 0 #bdaea0; /* primary 按钮默认 */
box-shadow: 0 6px 0 0 #bdaea0; /* primary 按钮 hover */
box-shadow: 0 1px 0 0 #bdaea0; /* primary 按钮 active */
box-shadow: 0 5px 0 0 #c94444; /* danger-primary 按钮默认（hover 6 / active 1） */
box-shadow: 0 3px 0 0 #d4c9b4; /* 输入框 shadow={true} 中号 */
box-shadow: 0 2px 0 0 #d4c9b4; /* 输入框 shadow={true} 小号 */
box-shadow: 0 4px 0 0 #d4c9b4; /* 输入框 shadow={true} 大号 */
/* Switch 仅 track 有 inset 阴影：inset 0 2px 4px rgba(114,93,66,0.15) (OFF) / inset 0 2px 4px rgba(90,158,30,0.20) (ON)；handle 无 outer box-shadow */
```

> **重要**：只有 primary 风格按钮（含 danger primary）才使用 `0 5px 0 0` 这种像素级 3D 厚阴影；`default` / `dashed` / `text` / `link` 用上面的柔和 elevation 阴影。把 3D 阴影套到所有按钮上会让界面变得过重过游戏化。

---

### 动效

```css
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); /* 通用 */
transition: all 0.15s; /* 快速（clear 按钮等）*/
transition: all 0.3s ease; /* 卡片 */
transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* 手风琴 */

/* Hover：上浮 */
transform: translateY(-1px); /* 按钮 / 输入框 */
transform: translateY(-2px); /* 卡片 */
/* Switch handle: 始终 translateY(-50%) 垂直居中，无 hover 上浮 */

/* Active：下压（游戏按键反馈）*/
transform: translateY(2px); /* 按钮 active */

/* 出现动画 */
@keyframes animal-zoom-in {
    from {
        opacity: 0;
        transform: scale(0.92);
    }
    to {
        opacity: 1;
        transform: scale(1);
    }
}
@keyframes animal-fade-in {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}
@keyframes ac-fade-up {
    from {
        opacity: 0;
        transform: translateY(8px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}
```

---

## 2. 组件精确样式规范

### BackTop

返回顶部按钮，钱袋图标浮窗，固定在右下角（`position: fixed`），滚动超过 `visibilityHeight` px 后出现。

| 属性               | 说明               | 默认值         |
| ------------------ | ------------------ | -------------- |
| `visibilityHeight` | 滚动多少 px 后显示 | `400`          |
| `duration`         | 滚动动画时长(ms)   | `300`          |
| `target`           | 滚动容器函数       | `() => window` |
| `onClick`          | 点击回调           | `-`            |
| `className`        | 自定义类名         | `-`            |
| `style`            | 自定义样式         | `-`            |

```vue
<BackTop :visibilityHeight="400" />
<BackTop :duration="800" />
<BackTop :target="() => containerRef" :visibilityHeight="200" />
```

---

### Button

| 属性          | small    | middle   | large    |
| ------------- | -------- | -------- | -------- |
| height        | 32px     | **45px** | 48px     |
| padding       | `0 16px` | `0 20px` | `0 32px` |
| font-size     | 12px     | 14px     | 16px     |
| border-radius | 12px     | **50px** | 24px     |
| border-width  | 2px      | 2px      | 2px      |

**primary 按钮精确值（仅 primary / danger-primary 用 3D 厚阴影**）：

```css
color: #794f27;
background: #f8f8f0;
border-color: #f8f8f0;
font-weight: 600;
letter-spacing: 0.02em;
line-height: 1;
box-shadow: 0 5px 0 0 #bdaea0;

/* hover */
transform: translateY(-1px);
box-shadow: 0 6px 0 0 #bdaea0;

/* active */
transform: translateY(2px);
box-shadow: 0 1px 0 0 #bdaea0;

/* focus-visible */
outline: 2px solid #19c8b9;
outline-offset: 2px;

/* disabled */
opacity: 0.5;
```

**default / dashed / text / link 按钮（柔和 elevation）：**

```css
/* 静止 */
box-shadow: var(--animal-shadow-sm); /* 0 2px 4px 0 rgba(61,52,40,0.06) */

/* hover */
color: #19c8b9;
border-color: #19c8b9;
box-shadow: var(--animal-shadow-base); /* 0 3px 10px 0 rgba(61,52,40,0.10) */
transform: translateY(-1px);

/* active */
color: #11a89b;
border-color: #11a89b;
transform: translateY(0);
box-shadow: var(--animal-shadow-sm); /* 回落到静止态 */
```

> 不要把 primary 那套 `0 5px / 6px / 1px #bdaea0` 套到 default / dashed 上 —— 整体会显得过重过 cartoon。

**loading 斜纹动画（精确值）：**

```css
background: #0ec4b6;
border: 4px solid #4de2da;
color: #fff;
background-image: repeating-linear-gradient(-45deg, #0ec4b6, #0ec4b6 10px, #01b0a7 10px, #01b0a7 20px);
background-size: 28.28px 28.28px;
animation: animal-btn-loading 1s linear infinite;

@keyframes animal-btn-loading {
    0% {
        background-position: 0 0;
    }
    100% {
        background-position: -28.28px 0;
    }
}
```

**danger primary 按钮：**

```css
color: #fff;
box-shadow: 0 5px 0 0 #c94444; /* error-active */
```

---

### Input

> ⚠️ **`shadow`** **prop 默认** **`false`**：默认无阴影，下表的 `box-shadow` 仅在 `<Input shadow />` 显式开启时生效。status (error/warning) 阴影与 focus 黄色光晕不受此 prop 控制。

| 属性                             | small               | middle              | large               |
| -------------------------------- | ------------------- | ------------------- | ------------------- |
| height                           | 32px                | 40px                | 48px                |
| padding                          | `0 14px`            | `0 18px`            | `0 22px`            |
| font-size                        | 12px                | 14px                | 16px                |
| border-radius                    | 40px                | 50px                | 50px                |
| border-width                     | 2.5px               | 2.5px               | **3px**             |
| box-shadow（仅 `shadow={true}`） | `0 2px 0 0 #d4c9b4` | `0 3px 0 0 #d4c9b4` | `0 4px 0 0 #d4c9b4` |

**精确颜色值：**

```css
background: rgb(247, 243, 223);
border: 2.5px solid #c4b89e;
/* 默认无 box-shadow；shadow={true} 时按上表中号取 0 3px 0 0 #d4c9b4 */

/* 文字 */
color: #725d42;
font-weight: 500;
letter-spacing: 0.01em;

/* placeholder */
color: #c4b89e;
font-weight: 400;

/* prefix/suffix */
color: #a0936e;

/* prefix margin-right */
margin-right: 6px;

/* suffix margin-left */
margin-left: 6px;

/* hover */
border-color: #a89878;
box-shadow: 0 3px 0 0 #c4b89e;

/* focus */
border-color: #ffcc00;
box-shadow:
    0 3px 0 0 #e0b800,
    0 0 0 3px rgba(255, 204, 0, 0.15);

/* disabled */
background: #ece8dc;
border-color: #d4c9b4;
box-shadow: none;
opacity: 0.6;
color: #c4b89e;

/* error */
box-shadow: 0 3px 0 0 #c94444;

/* warning */
box-shadow: 0 3px 0 0 #dba90e;
```

**clear 按钮：**

```css
width: 20px;
height: 20px;
margin-left: 4px;
color: #c4b89e;
font-size: 13px;
font-weight: 700;
border-radius: 50%;
transition: all 0.15s;
/* hover */
color: #725d42;
background: rgba(114, 93, 66, 0.1);
```

---

### Switch

**默认尺寸：**

```css
min-width: 52px;
height: 28px;
border: 2.5px solid #c4b89e;
border-radius: 50px;
background: #d4c9b4;
box-shadow: inset 0 2px 4px rgba(114, 93, 66, 0.15);

/* handle */
width: 21px;
height: 21px;
top: 50%;
left: 2px;
transform: translateY(-50%); /* 垂直居中 */
background: rgb(247, 243, 223);
border: 2.5px solid #bdaea0;
border-radius: 50%;
/* handle 无 outer box-shadow，仅靠 border 与 track inset 阴影分层 */

/* 开启态 */
background: #86d67a;
border-color: #6fba2c;
box-shadow: inset 0 2px 4px rgba(90, 158, 30, 0.2);
/* handle 开启后 left */
left: calc(100% - 24px);
border-color: #5a9e1e;

/* focus-visible */
outline: 2px solid #ffcc00;
outline-offset: 2px;

/* disabled */
opacity: 0.5;
```

**small 尺寸：**

```css
min-width: 38px;
height: 20px;
border-width: 2px;
/* handle */
width: 14px;
height: 14px;
top: 1px;
left: 1px;
box-shadow: 0 2px 0 0 #bdaea0;
/* 开启 handle left */
left: calc(100% - 16px);
box-shadow: 0 2px 0 0 #5a9e1e;
```

**inner 文字（#checked / #unchecked 插槽）：**

```css
font-size: 11px;
font-weight: 700;
color: #fff;
line-height: 1;
letter-spacing: 0.02em;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
padding: 0 8px 0 28px; /* 未开启 */
padding: 0 28px 0 8px; /* 开启 */
/* small 版 */
padding: 0 6px 0 20px;
font-size: 9px;
```

**loading spinner：**

```css
width: 11px;
height: 11px;
border: 2px solid #6fba2c;
border-right-color: transparent;
border-radius: 50%;
animation: animal-spin 0.6s linear infinite;
/* 关闭态 */
border-color: #a89878;
@keyframes animal-spin {
    to {
        transform: rotate(360deg);
    }
}
```

---

### Card

```css
/* 默认 */
border-radius: 20px;
background: rgb(247, 243, 223);
padding: 16px 24px;
color: #725d42;
font-weight: 500;
/* 默认 NO box-shadow（依赖 border / pattern 分层，不靠悬浮阴影）*/
transition: all 0.3s ease;
/* hover */
transform: translateY(-2px);

/* dashed 类型 */
border: 2px dashed #e8dcc8;
background: rgb(250, 248, 242);
box-shadow: none;

/* pattern 叠加（pattern !== 'none' 时，纯 CSS 实现，**无 png/svg**） */
/* 双层 radial-gradient 点阵 + 同色调 1.5px solid 边框 + pastel 浅底，
   13 种命名（default / app-pink / purple / app-blue / app-yellow / app-orange /
   app-teal / app-green / app-red / lime-green / yellow-green / brown / warm-peach-pink）
   与 Card.color 同名，但呈现为浅底波点"墙纸"而非实色块。 */
/* 例：pattern="app-pink" */
background:
    radial-gradient(circle, rgba(248, 166, 178, 0.18) 1.5px, transparent 1.5px) 0 0/28px 28px,
    radial-gradient(circle, rgba(255, 200, 210, 0.12) 1px, transparent 1px) 7px 7px/14px 14px,
    #fde4e8;
border: 1.5px solid #f8a6b2;
color: #a85565;
/* 当 color 与 pattern 同时设置时，pattern 视觉上覆盖 color */
```

> 旧版 `Card type="title"` 在 v0.9.x 移除，章节标题请使用独立的 `<Title>` 组件（见下文）。

---

### Title（飘带 Ribbon 章节标题）

替代旧 `Card type="title"`，渲染游戏风飘带横幅：燕尾两端 + 折角阴影 + 微透视正面主体。
源码：`src/components/Title/Title.vue`（scoped Less，BEM 类名 `animal-title__*`）。

```css
/* 默认（绿色配色，可被 .animal-title--color-* 覆盖） */
--rf: #27d039; /* front 正面 */
--rb: #20992a; /* back  燕尾 */
--rk: #115017; /* fold  折角阴影 */
--rt: #fff; /* text  文字色 */

font-family: Nunito, 'Noto Sans SC', sans-serif;
font-weight: 800; /* 外层 wrapper */
/* .animal-title__text 内层文字 font-weight 900；padding-top 0.11em CJK 光学居中 */

/* 飘带主体 */
display: inline-flex;
height: 2em;
padding: 0 1.6em;
letter-spacing: 0.04em;
filter: drop-shadow(0 0.08em 0.12em rgba(0, 0, 0, 0.05));

/* 燕尾（左/右）—— clip-path 鱼尾形 */
.animal-title__back--left {
    clip-path: polygon(100% 0%, 100% 100%, 0% 100%, 30% 50%, 0% 0%);
}
.animal-title__back--right {
    clip-path: polygon(0% 0%, 100% 0%, 70% 50%, 100% 100%, 0% 100%);
}
width: 1.7em;
height: 1.7em;
bottom: -0.4em;

/* 折角阴影 —— CSS border 三角 */
.animal-title__fold--left {
    border-width: 0 0.95em 0.45em 0;
    border-color: transparent var(--rk) transparent transparent;
}
.animal-title__fold--right {
    border-width: 0 0 0.45em 0.95em;
    border-color: transparent transparent transparent var(--rk);
}

/* 正面主体 */
.animal-title__front {
    inset: 0 0.1em;
    border-radius: 0.2em;
    transform: perspective(11.5em) rotateX(3deg);
}
```

尺寸（`SIZE_MAP` 通过 inline `font-size` 注入；所有内部 `em` 自动缩放）：

| size   | font-size |
| ------ | --------- |
| small  | 14px      |
| middle | 20px      |
| large  | 28px      |

13 种颜色覆盖：在 wrapper 上叠加 `.animal-title--color-app-pink` / `.animal-title--color-purple` / `.animal-title--color-app-blue` / `.animal-title--color-app-yellow` / `.animal-title--color-app-orange` / `.animal-title--color-app-teal` / `.animal-title--color-app-green` / `.animal-title--color-app-red` / `.animal-title--color-lime-green` / `.animal-title--color-yellow-green` / `.animal-title--color-brown` / `.animal-title--color-warm-peach-pink` 之一；每个类同时覆盖 `--rf / --rb / --rk / --rt` 四个变量。详见 `Title.vue` `<style scoped>` 末尾的 13 行 `.animal-title--color-*` 定义。

例：

```less
.animal-title--color-app-yellow {
    --rf: #f7cd67;
    --rb: #d4a030;
    --rk: #8a6010;
    --rt: #725d42;
}
.animal-title--color-purple {
    --rf: #b77dee;
    --rb: #9050d0;
    --rk: #5a1a9a;
    --rt: #fff;
}
```

---

### Collapse

```css
/* 外层卡片 */
border-radius: 18px;
border: 2px solid #9f927d;
margin-bottom: 12px;
/* disabled */ opacity: 0.6;

/* 问题栏 */
padding: 16px 24px;
gap: 12px;

/* 图标圆圈 */
width: 28px; height: 28px;
background: #19c8b9;
color: #fff;
border-radius: 50%;
font-size: 18px; font-weight: 700;
box-shadow: 0 2px 4px rgba(25, 200, 185, 0.3);
/* 展开时 */ transform: rotate(180deg);

/* 叶子装饰 */
opacity: 0.5;
/* 展开时 */ opacity: 1; transform: rotate(45deg);

/* 问题文字 */
font-size: 16px; font-weight: 600; line-height: 1.4;

/* 答案展开（CSS Grid trick，无 JS）*/
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* 展开 */ grid-template-rows: 1fr;
/* 内层 */ overflow: hidden;

/* 答案文字 */
padding: 0 24px;
font-size: 14px; line-height: 1.7;
/* 展开后 padding-bottom */ 24px;
```

---

### Tabs

scoped Less + BEM；类名根 `.animal-tabs`，内部使用 `__list` / `__item` / `__icon` / `__leaf` / `__content` 等子元素。

```css
/* 外层容器 */
.animal-tabs {
    background: rgb(247, 243, 223);
    border-radius: 20px;
    border: 2px solid #9f927d;
    overflow: hidden;
}

/* 标签列表 */
.animal-tabs__list {
    display: flex;
    gap: 4px;
    padding: 12px;
    background: rgba(255, 255, 255, 0.6);
    border-bottom: 2px solid #c4b89e;
}

/* 标签项 */
.animal-tabs__item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    background: transparent;
    border: none;
    border-radius: 50px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    color: #8a7b66;
    transition: all 0.2s ease;
}
/* hover */
.animal-tabs__item:hover {
    background: rgba(25, 200, 185, 0.1);
    color: #725d42;
}
/* 激活状态 — 实心 teal 胶囊 + 奶油色字 */
.animal-tabs__item.animal-tabs__item--active {
    background: #0cc0b5;
    color: #fff9e3;
    font-weight: 600;
}
.animal-tabs__item--active.animal-tabs__item--shadow {
    box-shadow: 0 3px 0 0 #d4c9b4; /* 仅 shadow opt-in 时启用 */
}

/* 标签图标 */
.animal-tabs__icon {
    font-size: 10px;
}
/* 激活时图标放大 */
.animal-tabs__item--active .animal-tabs__icon {
    transform: scale(1.2);
}

/* 叶子装饰动画 */
.animal-tabs__leaf {
    position: absolute;
    right: -6px;
    top: -3px;
    font-size: 12px;
    animation: leafWiggle 2s ease-in-out infinite;
}
/* leafAnimation={false} 时追加 .animal-tabs__leaf--static 修饰符去除 animation */

@keyframes leafWiggle {
    0%,
    100% {
        transform: rotate(0deg);
    }
    25% {
        transform: rotate(-10deg);
    }
    75% {
        transform: rotate(10deg);
    }
}

/* 内容区 */
.animal-tabs__content {
    padding: 24px;
    animation: fadeIn 0.25s ease;
}

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(4px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}
```

---

### Modal

**SVG clip-path 完整 path d 值（精确还原 blob 轮廓）：**

```vue
<template>
    <svg style="position: absolute; width: 0; height: 0" aria-hidden="true">
        <defs>
            <clipPath id="animal-modal-clip" clipPathUnits="objectBoundingBox">
                <path
                    d="M0.501,0.005 L0.501,0.005 L0.523,0.005 L0.549,0.006
          C0.704,0.01,0.796,0.017,0.825,0.027
          L0.827,0.028
          C0.872,0.045,0.939,0.044,0.978,0.17
          C1,0.254,1,0.365,0.99,0.505
          L0.988,0.513
          C0.979,0.558,0.971,0.598,0.965,0.633
          C0.956,0.689,0.979,0.77,0.964,0.865
          C0.953,0.928,0.921,0.966,0.869,0.979
          C0.821,0.986,0.773,0.992,0.726,0.995
          L0.712,0.996 L0.694,0.997
          C0.648,1,0.586,1,0.507,1
          L0.501,1 L0.464,1
          C0.385,1,0.325,0.998,0.283,0.995
          C0.234,0.992,0.184,0.987,0.133,0.979
          C0.081,0.966,0.05,0.928,0.039,0.865
          C0.023,0.77,0.047,0.689,0.037,0.633
          C0.031,0.595,0.023,0.552,0.013,0.505
          C-0.006,0.365,-0.002,0.254,0.024,0.17
          C0.064,0.045,0.13,0.045,0.174,0.028
          L0.175,0.028
          C0.204,0.017,0.303,0.009,0.474,0.005
          L0.501,0.005"
                />
            </clipPath>
        </defs>
    </svg>
</template>
```

**Modal 精确样式：**

```css
/* 遮罩 */
background: rgba(0, 0, 0, 0.35);
animation: animal-fade-in 0.25s ease;
z-index: 1000;

/* 弹窗容器 */
max-width: calc(100vw - 32px);
max-height: calc(100vh - 64px);
animation: animal-zoom-in 0.3s ease;

/* 裁切内容区 */
clip-path: url(#animal-modal-clip);
background: rgb(247, 243, 223);
color: rgb(128, 115, 89);
padding: 48px 48px 32px 48px;

/* 标题 */
font-size: 28px;
font-weight: 700;
color: rgba(114, 93, 66, 1);
padding-bottom: 15px;

/* 关闭按钮 */
width: 32px;
height: 32px;
font-size: 22px;
color: rgba(114, 93, 66, 0.6);
border-radius: 50%;
transition: all 0.2s;
/* hover */
background: rgba(114, 93, 66, 0.1);
color: rgba(114, 93, 66, 1);

/* body */
font-size: 20px;
font-weight: 600;
line-height: 1.6;
color: #8a7b66;
padding-bottom: 20px;

/* footer */
gap: 12px;

/* 普通按钮 */
height: 40px;
padding: 0 24px;
font-size: 18px;
border: 2px solid rgba(114, 93, 66, 0.3);
border-radius: 39.81px;
transition: all 0.2s;
line-height: 1;
/* hover */
border-color: rgba(114, 93, 66, 0.6);
background: rgba(114, 93, 66, 0.08);

/* 主按钮（确认）*/
color: rgba(114, 93, 66, 1);
background: rgba(255, 204, 0, 1); /* 游戏黄色！*/
border-color: rgba(255, 204, 0, 1);
/* hover */
background: rgba(255, 204, 0, 0.85);
border-color: rgba(255, 204, 0, 0.85);
```

---

---

### Footer

```vue
<template>
    <Footer />
    <!-- 14 个 🎄 居中一行（高 80px） -->
    <Footer seamless />
    <!-- 🎄 铺满整行（space-between） -->
</template>
```

```less
.animal-footer {
    // 14 个 🎄 emoji 居中一行（flex + letter-spacing），非图片
    width: 100%;
    height: 80px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 28px;
    letter-spacing: 12px;
}
.animal-footer--seamless {
    justify-content: space-between;
    padding: 0 8px;
    letter-spacing: 0;
}
```

- 无 `type` prop（原 `sea` 海浪类型与森林像素图均已移除），仅保留 `seamless` prop。

---

### Divider

```vue
<template>
    <Divider type="line-brown" />
    <!-- 默认 -->
    <Divider type="line-teal" />
    <Divider type="line-white" />
    <Divider type="line-yellow" />
    <Divider type="wave-yellow" />
</template>
```

```less
.animal-divider {
    width: 100%;
    height: 12px;
    background: url('./img/divider-line-brown.svg') center/contain no-repeat;
}
.animal-divider--line-teal {
    background-image: url('./img/divider-line-teal.svg');
}
.animal-divider--line-white {
    background-image: url('./img/divider-line-white.svg');
}
.animal-divider--line-yellow {
    background-image: url('./img/divider-line-yellow.svg');
}
.animal-divider--wave-yellow {
    background-image: url('./img/wave-yellow.svg');
}
```

默认 SVG 色值参考：`#D8D0C3`（米褐），`viewBox="0 0 297 14"`。

---

### Cursor

```vue
<template>
    <Cursor>
        <App />
        <!-- 此范围内所有元素变为光标 -->
    </Cursor>

    <!-- type="raindrop"：蓝色雨滴光标 -->
    <Cursor type="raindrop">
        <MapCanvas />
    </Cursor>

    <!-- forceAll=false：保留交互语义（a/button 仍是 pointer，input 仍是 text） -->
    <Cursor :force-all="false">
        <FormPage />
    </Cursor>
</template>
```

Props：`type`（`'default' | 'raindrop'`，默认 `'default'`）、`forceAll`（boolean，默认 `true`）。

样式文件为 **普通全局 CSS**（非 scoped；类名固定为 `animal-cursor`，挂在根 `<div>` 上）：

```css
/* 内联 SVG data-URI（无外部图片资源）：28×28 几何箭头，hotspot 6 4 */
.animal-cursor--force,
.animal-cursor--force * {
    cursor: url("data:image/svg+xml,...") 6 4, default !important;
}

/* type="raindrop"：32×32 蓝色水滴，hotspot 16 6 */
.animal-cursor--force.animal-cursor--raindrop,
.animal-cursor--force.animal-cursor--raindrop * {
    cursor: url("data:image/svg+xml,...") 16 6, default !important;
}

/* forceAll=false（scoped 模式）：容器自身用自定义光标，后代恢复 auto，
   交互元素（a/button/select 等）恢复 pointer、文本输入恢复 text、禁用态 not-allowed */
.animal-cursor.animal-cursor--scoped { cursor: url("data:image/svg+xml,...") 6 4, default !important; }
.animal-cursor--scoped * { cursor: auto !important; }
```

- 使用 `!important` 覆盖默认光标

- ⚠️ 此组件**不能用** **`scoped`**：scoped 选择器无法穿透 slot 内容；必须以全局 CSS 形式注册（`<style>` 不带 `scoped`，或全局样式入口引入）

---

### Background

装饰背景壁纸容器（纯 CSS + 内联 SVG，零图片资源），子内容渲染在图案之上。

```vue
<template>
    <!-- dots 波点壁纸（默认）：两层错位圆点（28px 大点 + 14px 小点，绿色系 #bfe3bf 底） -->
    <Background style="height: 200px" />

    <!-- sprinkles 圆柱形彩色针糖壁纸：三层互质 tile（190×170 / 230×195 / 255×215），
         内联 SVG 胶囊（圆角矩形 + 竖向高光渐变模拟圆柱受光），重复周期约 220000×280000px，
         视觉上随机散落；#fdf3e3 奶油底 -->
    <Background type="sprinkles" :style="{ minHeight: '200px', padding: '24px' }">
        <p>内容渲染在图案背景之上</p>
    </Background>
</template>
```

Props：`type`（`'dots' | 'sprinkles'`，默认 `'dots'`）。容器为 `position: relative; width: 100%; min-height: 100%`，需通过 `style` 给显式高度。类名 `animal-background`（基础）+ `animal-background--sprinkles`（修饰类在根元素上，scoped 样式中必须放顶层）。

---

### Typewriter

```vue
<template>
    <Typewriter :speed="90" :trigger="openCount" auto-play @done="handleDone">
        <p>第一行 <strong>加粗</strong></p>
        <p>第二行</p>
    </Typewriter>
</template>
```

Props：

| name       | type          | default | 说明                                                 |
| ---------- | ------------- | ------- | ---------------------------------------------------- |
| 默认插槽   | `Slot`        | —       | 要逐字打出的内容，**保留原有元素结构 / 换行 / 样式** |
| `speed`    | `number (ms)` | `90`    | 每字间隔                                             |
| `trigger`  | `unknown`     | —       | 值变化即重新播放（通常传递弹窗 open 次数或递增 key） |
| `autoPlay` | `boolean`     | `true`  | `false` 直接全量显示                                 |
| `@done`    | `() => void`  | —       | 播放完成事件                                         |

**实现要点：**

- `countText(vnode)`：递归统计 VNode 树（含 children / `el?.textContent` / 字符串子节点）的纯文本长度

- `renderTruncated(vnode, state)`：按剩余字符数递归裁剪，使用 `cloneVNode` 保留原节点与样式

- `watch([() => total.value, () => props.speed, () => props.trigger, () => props.autoPlay])`，内部 `setInterval` 按步递增 `count`

- **无样式文件**，不包裹任何额外 DOM（默认插槽直接 render），对布局零影响

---

### Checkbox

Props：

| name                 | type                             | default        | 说明                                         |
| -------------------- | -------------------------------- | -------------- | -------------------------------------------- |
| `options`            | `CheckboxOption[]`               | —              | **必填**；每项 `{ label, value, disabled? }` |
| `modelValue`         | `Array<string \| number>`        | —              | 受控选中值（配合 `v-model`）                 |
| `defaultValue`       | `Array<string \| number>`        | `[]`           | 非受控默认值                                 |
| `size`               | `'small' \| 'middle' \| 'large'` | `'middle'`     | 尺寸                                         |
| `disabled`           | `boolean`                        | `false`        | 禁用全部项                                   |
| `direction`          | `'horizontal' \| 'vertical'`     | `'horizontal'` | 排列方向                                     |
| `@update:modelValue` | `(values) => void`               | —              | 选中值变化（`v-model`）                      |
| `@change`            | `(values) => void`               | —              | 同上，业务回调                               |

**尺寸表（box 方框）：**

| 属性           | small   | middle      | large   |
| -------------- | ------- | ----------- | ------- |
| 宽高           | 18×18px | **22×22px** | 28×28px |
| border-width   | 2px     | 2.5px       | 3px     |
| 标签 font-size | 12px    | 14px        | 16px    |
| 对勾 font-size | 11px    | 13px        | 16px    |

**精确样式：**

```css
/* group */
display: flex; flex-wrap: wrap;
gap: 12px;                                 /* horizontal */
/* vertical */ flex-direction: column; gap: 8px;

/* item */
display: inline-flex; align-items: center;
gap: 8px;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.4,0,0.2,1);

/* box（未选）*/
background: rgb(247, 243, 223);
border: 2.5px solid #c4b89e;
border-radius: 8px;
display: inline-flex; align-items: center; justify-content: center;

/* box hover */
border-color: #19c8b9;
transform: translateY(-1px);

/* box focus-visible */
outline: 2px solid #ffcc00; outline-offset: 2px;

/* 选中 */
background: #19c8b9;
border-color: #11a89b;
/* 选中 hover */ background: #3dd4c6; border-color: #19c8b9;

/* 对勾 ✓ */
color: #fff; font-weight: 700; line-height: 1;
animation: animal-checkbox-pop 0.15s cubic-bezier(0.4,0,0.2,1);

@keyframes animal-checkbox-pop {
  0%   { transform: scale(0.4); opacity: 0; }
  60%  { transform: scale(1.2); }
  100% { transform: scale(1);   opacity: 1; }
}

/* label */
color: #725d42; font-weight: 500;
letter-spacing: 0.01em;
/* item hover */ label color: #794f27;

/* 禁用（单项或整组）*/
cursor: not-allowed;
opacity: 0.55;
/* box */ background: #f0ece2; border-color: #d4c9b4; transform: none !important;
/* label */ color: #c4b89e;
```

---

### CodeBlock

Props：

| name       | type      | default | 说明                                                 |
| ---------- | --------- | ------- | ---------------------------------------------------- |
| `code`     | `string`  | —       | **必填**；原始源码字符串，内部自动按 JSX/TS 分词高亮 |
| `copyable` | `boolean` | `true`  | 是否显示右上角复制按钮                               |

Emits：`copy(code: string)` — 复制成功后触发。

`class` 与非布局类 `:style` 键落在 `<pre>` 上；`width` / `min-width` / `max-width` / `margin*` 键落在外层 wrapper（`inheritAttrs: false` 手动分派）。显示复制按钮且未自定义 `padding` / `padding-right` 时，`<pre>` 自动加 `padding-right: 96px` 给按钮留位。

结构（BEM，scoped Less）：

```html
<div class="animal-code-block">          <!-- wrapper: relative, min-width 0, margin 1em 0 -->
    <pre class="animal-code-block__code"><!-- 高亮内容 -->
    <button class="animal-code-block__copy-btn">复制</button>
</div>
```

复制逻辑与 React 版一致：优先 `navigator.clipboard.writeText`，不可用时降级临时 `textarea` + `document.execCommand('copy')`（finally 中清理临时节点）；按钮三态文案 复制 / 已复制 / 复制失败，2s 后自动复位（卸载时清定时器）；成功时 emit `copy`。

```css
/* 复制按钮 */
.animal-code-block__copy-btn {
    position: absolute;
    top: 12px;
    right: 12px;
    min-width: 62px;
    height: 32px;
    padding: 0 12px;
    border: 1px solid rgba(232, 213, 188, 0.3);
    border-radius: 50px;
    background: rgba(61, 48, 40, 0.94);
    color: #e8d5bc;
    font-size: 12px;
    font-weight: 700;
}
.animal-code-block__copy-btn:hover {
    background: #4b3b31;
    transform: translateY(-1px);
}
.animal-code-block__copy-btn:focus-visible {
    outline: 2px solid var(--animal-primary-color, #19c8b9);
    outline-offset: 2px;
}
```

**pre 默认主题：**

```css
box-sizing: border-box;
width: 100%;
margin: 0;
padding: 20px 24px;
background: #2b2118;
border: 1px solid #3d3028;
border-radius: 20px;
font-size: 14px;
line-height: 1.7;
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
font-weight: 600;
color: #e8d5bc;
white-space: pre;
overflow: auto;
tab-size: 4;
```

**Token 调色板（`COLORS`** **常量）：**

| token     | 颜色      | 覆盖                                                                                                     | <br />     |
| --------- | --------- | -------------------------------------------------------------------------------------------------------- | :--------- |
| comment   | `#6b5e50` | `/* */`、`//`                                                                                            | <br />     |
| string    | `#a8d4a0` | 反引号 / 单双引号、数字                                                                                  | <br />     |
| keyword   | `#d4a0e0` | `import/export/const/return/async/...`、`true/false/null/undefined`                                      | <br />     |
| react     | `#e06c75` | `Vue/ref/computed/onMounted/defineProps/defineEmits/PropType/...`（保留 React 名字以兼容跨框架代码片段） | <br />     |
| component | `#80c0e0` | 大写驼峰标识符（组件名、类型名）                                                                         | <br />     |
| func      | `#61afef` | 小写标识符后跟 `(`                                                                                       | <br />     |
| prop      | `#e8c87a` | 标识符后跟 `=`（template props / 赋值）                                                                  | <br />     |
| jsx       | `#f0a870` | `<Tag`、`</Tag`、`/>`                                                                                    | <br />     |
| operator  | `#d4b896` | `{}[]();,` 和 \`+-\*/=<>&                                                                                | ^\~?:\` 等 |
| default   | `#e8d5bc` | 其余文本                                                                                                 | <br />     |

> 不支持 `language` prop；非 JS/TS 代码（Python/Shell/SQL）会按通用规则着色，显示可能不准确。不带行号或折行；复制按钮默认显示（`copyable: false` 关闭）。

---

### Radio

源码：`src/components/Radio/Radio.vue`（scoped Less + BEM 类名 `animal-radio__*`）。

| 属性            | small   | middle  | large                        |
| --------------- | ------- | ------- | ---------------------------- |
| 外盒尺寸        | 18×18px | 22×22px | 28×28px      

…(truncated)
