1451 lines
40 KiB
Markdown
1451 lines
40 KiB
Markdown
---
|
||
name: animal-island-ui-style
|
||
description: >
|
||
使用 animal-island-ui 设计风格创建 React UI 界面或组件。当用户需要:
|
||
(1) 用动物森友会风格创建 UI 页面或组件;
|
||
(2) 使用 animal-island-ui 组件库开发界面;
|
||
(3) 构建温馨自然、圆润可爱风格的 React 界面;
|
||
(4) 复现或扩展 animal-island-ui 的视觉语言;
|
||
(5) 提问"动物森友会风格"、"animal island 风格"、"可爱圆润风格"的 UI 时,务必使用此 skill。
|
||
---
|
||
|
||
# Animal Island UI 设计风格指南
|
||
|
||
> **三文档分工**(生成代码 / 调样式时按需查阅,避免互相翻查):
|
||
> - `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-ui 是一套受《集合啦!动物森友会》启发的 React + TypeScript UI 组件库。
|
||
设计语言核心:**温暖大地色系 + 大圆角 pill 形 + 游戏按键立体感 + 柔和动效 + 有机不规则形状**。
|
||
|
||
- 源码:`src/components/<ComponentName>/`
|
||
- Demo 站:`demo/`
|
||
- 构建:Vite (library mode) + `vite.config.ts`(库)/ `vite.config.demo.ts`(Demo)
|
||
- 样式系统:Less Modules + `src/styles/variables.less` 设计 token
|
||
|
||
### 全量组件清单(17 个)
|
||
|
||
从 `src/index.ts` 导出:
|
||
|
||
| 组件 | 职责 | 交互 | 装饰 / 纯展示 |
|
||
|---|---|---|---|
|
||
| `Button` | 按钮,5 种类型 × 3 种尺寸 | ✓ | |
|
||
| `Input` | 输入框,3 种尺寸 + clear/prefix/suffix | ✓ | |
|
||
| `Switch` | 开关,默认/小号 | ✓ | |
|
||
| `Modal` | SVG blob 裁切弹窗 | ✓ | |
|
||
| `Card` | 容器,`default`/`title`,13 种 NookPhone 配色 | | ✓ |
|
||
| `Collapse` | 手风琴(CSS Grid,无 JS 过渡) | ✓ | |
|
||
| `Select` | 下拉选择器(受控) | ✓ | |
|
||
| `Checkbox` | 多选框组,水平/垂直,3 种尺寸 | ✓ | |
|
||
| `Icon` | SVG 图标库(10 个) | | ✓ |
|
||
| `Time` | HUD 实时时钟 | | ✓ |
|
||
| `Phone` | NookPhone 3×3 应用网格 | | ✓ |
|
||
| `Footer` | 底部装饰图(`sea`/`tree`) | | ✓ |
|
||
| `Divider` | 装饰分割线,5 种风格 | | ✓ |
|
||
| `Cursor` | 游戏手指光标包裹器 | | ✓ |
|
||
| `Typewriter` | 打字机效果,保留 ReactNode 结构 | | ✓ |
|
||
| `Tabs` | 标签页切换,叶子摆动动画可选 | ✓ | |
|
||
| `CodeBlock` | JSX/TS 语法高亮代码块 | | ✓ |
|
||
|
||
类型导出:`ButtonProps/ButtonType/ButtonSize`、`InputProps/InputSize`、`SwitchProps/SwitchSize`、`ModalProps`、`CardProps/CardType/CardColor`、`FooterProps/FooterType`、`CollapseProps`、`CursorProps`、`TimeProps`、`PhoneProps`、`DividerProps`、`TypewriterProps`、`SelectProps/SelectOption`、`IconProps/IconName`、`TabsProps/TabItem`、`CheckboxProps/CheckboxOption/CheckboxSize`、`CodeBlockProps`。运行时值:`ICON_LIST`。
|
||
|
||
---
|
||
|
||
## 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 阴影
|
||
```
|
||
|
||
**NookPhone 应用调色板**(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=Zen+Maru+Gothic:wght@400;500;700&family=M+PLUS+Rounded+1c: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&family=Zen+Maru+Gothic:wght@400;500;700&display=swap');
|
||
```
|
||
|
||
```css
|
||
font-family: Nunito, 'Noto Sans SC', 'Zen Maru Gothic',
|
||
-apple-system, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||
```
|
||
|
||
| 字体 | 用途 | Google Fonts key |
|
||
|---|---|---|
|
||
| **Nunito** | 主字体,拉丁字符 | `family=Nunito` |
|
||
| **Noto Sans SC** | 中文字体,简体覆盖 | `family=Noto+Sans+SC` |
|
||
| **Zen Maru Gothic** | 日文字体 | `family=Zen+Maru+Gothic` |
|
||
|
||
字重分级:
|
||
- 正文内容:**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.10); /* 基础 */
|
||
box-shadow: 0 8px 24px 0 rgba(61, 52, 40, 0.14); /* 较大 */
|
||
box-shadow: 0 4px 10px rgba(107, 92, 67, 0.42); /* Card */
|
||
|
||
/* 游戏按键立体阴影(最重要特征)*/
|
||
box-shadow: 0 5px 0 0 #bdaea0; /* 按钮默认 */
|
||
box-shadow: 0 6px 0 0 #bdaea0; /* 按钮 hover */
|
||
box-shadow: 0 1px 0 0 #bdaea0; /* 按钮 active */
|
||
box-shadow: 0 3px 0 0 #d4c9b4; /* 输入框 */
|
||
box-shadow: 0 2px 0 0 #d4c9b4; /* 小号输入框 */
|
||
box-shadow: 0 4px 0 0 #d4c9b4; /* 大号输入框 */
|
||
box-shadow: 0 3px 0 0 #5a9e1e; /* Switch 开启态 */
|
||
box-shadow: 0 2px 0 0 #5a9e1e; /* Switch 小号开启态 */
|
||
```
|
||
|
||
---
|
||
|
||
### 动效
|
||
|
||
```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 */
|
||
transform: translateY(-4px); /* 卡片 */
|
||
|
||
/* 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. 组件精确样式规范
|
||
|
||
### 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 按钮精确值:**
|
||
```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;
|
||
```
|
||
|
||
**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
|
||
|
||
| 属性 | 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 | `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: 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: 2px; left: 2px;
|
||
background: rgb(247, 243, 223);
|
||
border: 2px solid #c4b89e;
|
||
border-radius: 50%;
|
||
box-shadow: 0 3px 0 0 #bdaea0;
|
||
transform: translateY(-2px); /* 悬浮效果 */
|
||
|
||
/* 开启态 */
|
||
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: #6fba2c;
|
||
box-shadow: 0 3px 0 0 #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 文字(checkedChildren/unCheckedChildren):**
|
||
```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;
|
||
box-shadow: 0 4px 10px rgba(107, 92, 67, 0.42);
|
||
transition: all 0.3s ease;
|
||
/* hover */ transform: translateY(-4px);
|
||
|
||
/* title 类型 */
|
||
background: #fdfdf5;
|
||
border-radius: 40px 35px 45px 38px / 38px 45px 35px 40px;
|
||
padding: 12px 32px;
|
||
font-weight: 600;
|
||
```
|
||
|
||
---
|
||
|
||
### 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
|
||
|
||
```css
|
||
/* 外层容器 */
|
||
.tabs {
|
||
background: rgb(247, 243, 223);
|
||
border-radius: 20px;
|
||
border: 2px solid #9f927d;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 标签列表 */
|
||
.tabList {
|
||
display: flex;
|
||
gap: 4px;
|
||
padding: 12px;
|
||
background: rgba(255, 255, 255, 0.6);
|
||
border-bottom: 2px solid #c4b89e;
|
||
}
|
||
|
||
/* 标签项 */
|
||
.tabItem {
|
||
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 */
|
||
.tabItem:hover {
|
||
background: rgba(25, 200, 185, 0.1);
|
||
color: #725d42;
|
||
}
|
||
/* 激活状态 */
|
||
.tabItem.active {
|
||
background: rgba(25, 200, 185, 0.15);
|
||
color: #19c8b9;
|
||
font-weight: 600;
|
||
box-shadow: 0 3px 0 0 #d4c9b4;
|
||
}
|
||
|
||
/* 标签图标 */
|
||
.tabIcon {
|
||
font-size: 10px;
|
||
}
|
||
/* 激活时图标放大 */
|
||
.tabItem.active .tabIcon {
|
||
transform: scale(1.2);
|
||
}
|
||
|
||
/* 叶子装饰动画 */
|
||
.tabLeaf {
|
||
position: absolute;
|
||
right: -6px;
|
||
top: -3px;
|
||
font-size: 12px;
|
||
animation: leafWiggle 2s ease-in-out infinite;
|
||
}
|
||
/* leafAnimation={false} 时追加 tabLeafStatic 类去除 animation */
|
||
|
||
@keyframes leafWiggle {
|
||
0%, 100% { transform: rotate(0deg); }
|
||
25% { transform: rotate(-10deg); }
|
||
75% { transform: rotate(10deg); }
|
||
}
|
||
|
||
/* 内容区 */
|
||
.tabContent {
|
||
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 轮廓):**
|
||
```jsx
|
||
<svg style={{ position: 'absolute', width: 0, height: 0 }} aria-hidden>
|
||
<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>
|
||
```
|
||
|
||
**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);
|
||
```
|
||
|
||
---
|
||
|
||
### Time
|
||
|
||
```css
|
||
/* 容器 */
|
||
display: flex; align-items: center;
|
||
gap: 20px;
|
||
padding: 16px 36px;
|
||
background: linear-gradient(180deg, #fff 0%, #f8f8f0 100%);
|
||
border: 3px solid #d4cfc3;
|
||
border-radius: 18px;
|
||
animation: ac-fade-up 0.5s ease-out;
|
||
|
||
/* 日期块(右侧分隔线)*/
|
||
padding-right: 24px;
|
||
border-right: 3px solid rgba(159, 146, 125, 0.35);
|
||
|
||
/* 星期 */
|
||
color: #6fba2c;
|
||
font-weight: 900; font-size: 14px;
|
||
letter-spacing: 1.5px;
|
||
|
||
/* 月日 */
|
||
color: #8b7355;
|
||
font-weight: 800; font-size: 22px;
|
||
|
||
/* 时间数字 */
|
||
color: #8b7355;
|
||
font-weight: 900; font-size: 48px;
|
||
letter-spacing: 2px;
|
||
|
||
/* 冒号(闪烁)*/
|
||
font-size: 48px; color: #8b7355;
|
||
position: relative; top: -0.08em;
|
||
margin: 0 1px;
|
||
animation: blink 1s step-end infinite;
|
||
|
||
@keyframes blink { 50% { opacity: 0; } }
|
||
|
||
/* 响应式 768px */
|
||
padding: 12px 20px; gap: 12px;
|
||
.acWeekday → font-size: 11px;
|
||
.acMonthday → font-size: 16px;
|
||
.acTime / .acColon → font-size: 32px;
|
||
```
|
||
|
||
---
|
||
|
||
### Phone(NookPhone)
|
||
|
||
**外壳(固定尺寸,不响应式):**
|
||
```css
|
||
.phone {
|
||
width: 527px; height: 788px;
|
||
background: #F8F4E8; /* 奶油米 */
|
||
border-radius: 136px; /* 超大圆角,近似胶囊 */
|
||
overflow: hidden;
|
||
}
|
||
.homeScreen {
|
||
height: 100%;
|
||
padding-top: 40px;
|
||
background: #F8F4E8;
|
||
background-size: 100% 200%;
|
||
animation: grasswave 8s ease-in-out infinite;
|
||
display: flex; flex-direction: column; align-items: center;
|
||
}
|
||
@keyframes grasswave {
|
||
0%, 100% { background-position: 0% 0%; }
|
||
50% { background-position: 0% 100%; }
|
||
}
|
||
```
|
||
|
||
**顶部时间栏:**
|
||
```css
|
||
.dateDisplay { padding: 0 70px 31px 70px; text-align: center; }
|
||
.dateDisplayHeader { display:flex; justify-content:space-between; align-items:center;
|
||
font-size: 32px; font-weight: 800; letter-spacing: 2px; color: #DDDBCC; }
|
||
.blink { font-size: 32px; font-weight: 800; color: #DDDBCC;
|
||
animation: blink 1s steps(1) infinite; vertical-align: text-bottom; }
|
||
@keyframes blink { 0%,50% { opacity: 1; } 51%,100% { opacity: 0; } }
|
||
.dayText { font-size: 48px; font-weight: 800; color: #725C4E;
|
||
letter-spacing: 2px; height: 56px; margin-top: 20px; }
|
||
```
|
||
|
||
**3×3 应用网格:**
|
||
```css
|
||
.appsGrid { display: grid; grid-template-columns: repeat(3, 1fr);
|
||
gap: 32px; padding: 8px; flex: 1;
|
||
align-content: center; justify-content: center; }
|
||
.appItem { width: 123px; height: 123px;
|
||
border-radius: 45px; /* 圆角正方形 */
|
||
position: relative;
|
||
display: flex; justify-content: center; align-items: center; }
|
||
.appItem:hover .appIcon { animation: iconBounce 0.3s ease-in-out forwards; }
|
||
.appIcon { width: 100%; height: 100%;
|
||
background-repeat: no-repeat; background-position: center;
|
||
background-size: 70% auto; }
|
||
.appItemOffset { overflow: hidden; }
|
||
.appIconOffset { transform: translateY(10px); }
|
||
|
||
@keyframes iconBounce {
|
||
0% { transform: scale(1) rotate(0deg); }
|
||
50% { transform: scale(1.2) rotate(-5deg); }
|
||
100% { transform: scale(1.1) rotate(-4deg); }
|
||
}
|
||
```
|
||
|
||
**应用数据结构(`src/components/Phone/Phone.tsx`):**
|
||
|
||
| id | iconClass | 背景色 | offset | hasNewMessage |
|
||
|---|---|---|---|---|
|
||
| camera | iconCamera | `#B77DEE` | | ✓ |
|
||
| app | iconApp | `#889DF0` | ✓ | |
|
||
| critterpedia | iconCritterpedia | `#F7CD67` | | |
|
||
| diy | iconDiy | `#E59266` | | |
|
||
| shopping | iconDesign | `#F8A6B2` | | |
|
||
| variant | iconMap | `#82D5BB` | | ✓ |
|
||
| design | iconVariant | `#8AC68A` | | |
|
||
| map | iconHelicopter | `#FC736D` | | |
|
||
| chat | iconChat | `#D1DA49` | | |
|
||
|
||
每个 iconClass 都绑定一个 `background-image: url('./img/icon-*.svg')`,`iconApp` 特殊使用 `background-size: 100% auto`(其他是 `70% auto`)。可用图标资源:`icon-miles/camera/chat/critterpedia/design/diy/helicopter/map/shopping/variant.svg`,以及状态图标 `wifi.svg` / `location.svg` / `page.svg`。
|
||
|
||
**小红点(新消息):**
|
||
```css
|
||
.badge {
|
||
position: absolute; top: 0; left: 0;
|
||
width: 28px; height: 28px; border-radius: 50%;
|
||
background: #FF544A;
|
||
border: 5px solid #F8F4E8; /* 奶油米描边,形成游戏风徽章 */
|
||
}
|
||
```
|
||
|
||
**底部状态图标:**
|
||
```css
|
||
.iconWifi { width: 79px; height: 29px; background: url('./img/wifi.svg') center/contain no-repeat; }
|
||
.iconLocation { width: 36px; height: 36px; background: url('./img/location.svg') center/contain no-repeat; }
|
||
.iconPage { width: 65px; height: 32px; background: url('./img/page.svg') center/contain no-repeat; }
|
||
.pageIndicator{ display: flex; justify-content: center; align-items: center;
|
||
margin-top: 74px; }
|
||
```
|
||
|
||
**行为:** 内部 `useEffect + setInterval(1000)` 更新时间,`12 小时制 + AM/PM + 零填充分钟`,冒号闪烁 1s 一个周期。组件无业务回调,纯展示。
|
||
|
||
---
|
||
|
||
### Footer
|
||
|
||
```tsx
|
||
<Footer /> // 默认:森林(tree,高 60px)
|
||
<Footer type="sea" /> // 海浪(高 80px)
|
||
```
|
||
|
||
```less
|
||
.footer { width: 100%; height: 80px;
|
||
background: url('./img/footer-sea.svg') center/contain no-repeat; }
|
||
.tree { background-image: url('./img/footer-tree.webp');
|
||
height: 60px;
|
||
background-size: cover;
|
||
background-position: bottom center; }
|
||
```
|
||
|
||
- `sea`:SVG 海浪插画,`viewBox="0 0 1440 186"`,多色(珊瑚 `#EC7175`、海蓝 `#327A93`、浅蓝 `#98D2E3`、深青 `#008077` 等)。
|
||
- `tree`:webp 森林剪影,置于页面最底部。
|
||
|
||
---
|
||
|
||
### Divider
|
||
|
||
```tsx
|
||
<Divider type="line-brown" /> // 默认
|
||
<Divider type="line-teal" />
|
||
<Divider type="line-white" />
|
||
<Divider type="line-yellow" />
|
||
<Divider type="wave-yellow" />
|
||
```
|
||
|
||
```less
|
||
.divider { width: 100%; height: 12px;
|
||
background: url('./img/divider-line-brown.svg') center/contain no-repeat; }
|
||
.line-teal { background-image: url('./img/divider-line-teal.svg'); }
|
||
.line-white { background-image: url('./img/divider-line-white.png'); }
|
||
.line-yellow { background-image: url('./img/divider-line-yellow.svg'); }
|
||
.wave-yellow { background-image: url('./img/wave-yellow.svg'); }
|
||
```
|
||
|
||
默认 SVG 色值参考:`#D8D0C3`(米褐),`viewBox="0 0 297 14"`。
|
||
|
||
---
|
||
|
||
### Cursor
|
||
|
||
```tsx
|
||
<Cursor>
|
||
<App /> {/* 此范围内所有元素变为游戏手指光标 */}
|
||
</Cursor>
|
||
```
|
||
|
||
样式文件为 **普通 CSS**(非 module):
|
||
```css
|
||
.animal-cursor,
|
||
.animal-cursor * {
|
||
cursor: url('./cursor-icon.png') 4 0, auto !important;
|
||
}
|
||
```
|
||
|
||
- `cursor-icon.png` 热点坐标 `(4, 0)`
|
||
- 使用 `!important` 覆盖默认光标;`className` 直接挂在根 `<div>` 上,类名固定为 `animal-cursor`
|
||
|
||
---
|
||
|
||
### Typewriter
|
||
|
||
```tsx
|
||
<Typewriter speed={90} trigger={openCount} autoPlay onDone={() => ...}>
|
||
<p>第一行 <strong>加粗</strong></p>
|
||
<p>第二行</p>
|
||
</Typewriter>
|
||
```
|
||
|
||
Props:
|
||
|
||
| name | type | default | 说明 |
|
||
|---|---|---|---|
|
||
| `children` | `ReactNode` | — | 要逐字打出的内容,**保留原有元素结构 / 换行 / 样式** |
|
||
| `speed` | `number (ms)` | `90` | 每字间隔 |
|
||
| `trigger` | `unknown` | — | 值变化即重新播放(通常传递弹窗 open 次数或递增 key) |
|
||
| `autoPlay` | `boolean` | `true` | `false` 直接全量显示 |
|
||
| `onDone` | `() => void` | — | 播放完成回调 |
|
||
|
||
**实现要点:**
|
||
- `countText(node)`:递归统计 ReactNode 的纯文本长度
|
||
- `renderTruncated(node, state)`:按剩余字符数递归裁剪,`React.cloneElement` 保留原节点与样式
|
||
- `useEffect` 依赖 `[total, speed, trigger, autoPlay]`,内部 `setInterval` 按步递增 `count`
|
||
- **无样式文件**,不包裹任何额外 DOM(返回 `<>...</>`),对布局零影响
|
||
|
||
---
|
||
|
||
### Checkbox
|
||
|
||
Props:
|
||
|
||
| name | type | default | 说明 |
|
||
|---|---|---|---|
|
||
| `options` | `CheckboxOption[]` | — | **必填**;每项 `{ label, value, disabled? }` |
|
||
| `value` | `Array<string \| number>` | — | 受控选中值 |
|
||
| `defaultValue` | `Array<string \| number>` | `[]` | 非受控默认值 |
|
||
| `size` | `'small' \| 'middle' \| 'large'` | `'middle'` | 尺寸 |
|
||
| `disabled` | `boolean` | `false` | 禁用全部项 |
|
||
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | 排列方向 |
|
||
| `onChange` | `(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 分词高亮 |
|
||
| `style` | `CSSProperties` | — | 会合并覆盖默认深色主题 |
|
||
| `className` | `string` | — | 自定义类名 |
|
||
|
||
**默认主题(写死在组件,不走 Less):**
|
||
|
||
```css
|
||
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 | 颜色 | 覆盖 |
|
||
|---|---|---|
|
||
| comment | `#6b5e50` | `/* */`、`//` |
|
||
| string | `#a8d4a0` | 反引号 / 单双引号、数字 |
|
||
| keyword | `#d4a0e0` | `import/export/const/return/async/...`、`true/false/null/undefined` |
|
||
| react | `#e06c75` | `React/useState/useEffect/FC/ReactNode/CSSProperties/...` |
|
||
| component| `#80c0e0` | 大写驼峰标识符(JSX 组件名、类型名) |
|
||
| func | `#61afef` | 小写标识符后跟 `(` |
|
||
| prop | `#e8c87a` | 标识符后跟 `=`(JSX props / 赋值) |
|
||
| jsx | `#f0a870` | `<Tag`、`</Tag`、`/>` |
|
||
| operator | `#d4b896` | `{}[]();,` 和 `+-*/=<>&|^~?:` 等 |
|
||
| default | `#e8d5bc` | 其余文本 |
|
||
|
||
> 不支持 `language` prop;非 JS/TS 代码(Python/Shell/SQL)会按通用规则着色,显示可能不准确。不带 copy 按钮、行号或折行。
|
||
|
||
---
|
||
|
||
## 3. Demo 布局精确规范
|
||
|
||
这是 Demo 站(`demo/App.tsx`)的实际布局数值,用于还原完整页面效果:
|
||
|
||
### 整体布局
|
||
|
||
```css
|
||
/* 页面背景 */
|
||
/* 首页 */ background: url(home_bg.svg) center/cover no-repeat, #7DC395;
|
||
/* 组件页 */ background: url(content_bg_pc.jpg) center fixed;
|
||
|
||
/* Sidebar */
|
||
width: 220px; min-width: 220px;
|
||
background: url(menu_bg.svg) center/cover no-repeat;
|
||
```
|
||
|
||
### Sidebar 精确值
|
||
|
||
```css
|
||
/* 顶部 Logo 区 */
|
||
padding: 20px 16px 12px;
|
||
border-bottom: 1px solid #e8e2d6;
|
||
font-weight: 700; font-size: 15px;
|
||
color: #725d42; letter-spacing: -0.3px;
|
||
|
||
/* Logo 图片 */
|
||
width: 24px; height: 24px; margin-right: 8px;
|
||
|
||
/* 菜单列表 */
|
||
padding: 8px 0;
|
||
|
||
/* 分类标题 */
|
||
padding: 12px 16px 4px;
|
||
font-size: 11px; color: #a0936e;
|
||
font-weight: 600; letter-spacing: 0.5px;
|
||
text-transform: uppercase;
|
||
|
||
/* 菜单项 */
|
||
margin: 1px 5px;
|
||
height: 40px; padding: 0 16px;
|
||
padding-left: 26px;
|
||
font-weight: 600; font-size: 14px;
|
||
border-radius: 12px;
|
||
transition: all 0.15s;
|
||
|
||
/* inactive */ color: #8a7b66; background: transparent;
|
||
/* inactive hover */ background: #d6dff0;
|
||
/* active */ color: #fff; background: #B7C6E5;
|
||
```
|
||
|
||
### 主内容区
|
||
|
||
```css
|
||
/* 桌面 */
|
||
padding: 32px 40px;
|
||
|
||
/* 底部装饰图(桌面端,固定定位)*/
|
||
left: 220px;
|
||
width: calc(100% - 220px);
|
||
z-index: 0; pointer-events: none;
|
||
```
|
||
|
||
### 移动端适配
|
||
|
||
```css
|
||
/* 顶栏 */
|
||
height: 52px; padding: 0 12px;
|
||
background: rgba(255, 252, 244, 0.92);
|
||
backdrop-filter: blur(8px);
|
||
border-bottom: 1px solid #e8e2d6;
|
||
z-index: 50;
|
||
|
||
/* 按钮 */ font-size: 20px; color: #725d42; padding: 4px 8px; border-radius: 8px;
|
||
|
||
/* 主内容区 padding-top */ 68px;
|
||
|
||
/* 抽屉 */
|
||
width: 240px; z-index: 99;
|
||
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
|
||
/* 遮罩 */ background: rgba(0, 0, 0, 0.35); z-index: 98;
|
||
```
|
||
|
||
---
|
||
|
||
## 4. HomePage 精确规范
|
||
|
||
```css
|
||
/* Hero 区域 */
|
||
padding: 60px 40px 40px;
|
||
min-height: 80vh;
|
||
|
||
/* 主标题 */
|
||
font-size: 50px; font-weight: 700;
|
||
color: #FFF9E6;
|
||
text-shadow: 0px 4px 1px rgba(0, 0, 0, 0.4);
|
||
margin: 0 0 12px;
|
||
|
||
/* 版本 Badge */
|
||
font-size: 12px; font-weight: 600;
|
||
padding: 2px 10px; border-radius: 10px;
|
||
background: #e6f9f6; color: #19c8b9;
|
||
margin-left: 8px;
|
||
|
||
/* 副标题 */
|
||
font-size: 17px; color: #7c5734; line-height: 1.7;
|
||
margin: 0 0 28px; max-width: 520px;
|
||
|
||
/* Logo 图片 */
|
||
width: 172px; height: 172px;
|
||
|
||
/* Section */
|
||
padding: 48px 40px; max-width: 960px; margin: 0 auto;
|
||
|
||
/* Section 标题 */
|
||
font-size: 24px; font-weight: 700; color: #725d42; margin: 0 0 8px;
|
||
|
||
/* Section 描述 */
|
||
font-size: 14px; color: #7c5734; margin-bottom: 32px;
|
||
|
||
/* Feature 网格 */
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
|
||
/* Feature Card hover */
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 8px 24px rgba(114, 93, 66, 0.15);
|
||
|
||
/* Feature 图标 hover */
|
||
transform: scale(1.1) rotate(-4deg);
|
||
|
||
/* 代码块 */
|
||
max-width: 600px; margin: 0 auto;
|
||
padding: 20px 28px;
|
||
background: #2b2118;
|
||
border: 1px solid #3d3028;
|
||
border-radius: 20px;
|
||
font-size: 13px; font-weight: 600;
|
||
color: #e8d5bc; line-height: 1.8;
|
||
```
|
||
|
||
**代码高亮配色:**
|
||
|
||
| Token 类型 | 颜色 |
|
||
|---|---|
|
||
| 注释 | `#6b5e50`(italic, weight 400)|
|
||
| 字符串 | `#a8d4a0` |
|
||
| JSX 标签 | `#f0a870` |
|
||
| 关键字 / npm/pnpm | `#f0a870` |
|
||
| 命令动词(install/add)| `#a8d4a0` |
|
||
| 括号 `{}` | `#d4b896` |
|
||
| 箭头 `=>` | `#d4a0e0` |
|
||
| CSS 变量名 | `#e8c87a` |
|
||
| `:root` | `#f0a870` |
|
||
| 十六进制色值 | `#8ab8e0` |
|
||
|
||
---
|
||
|
||
## 5. 自实现 CSS 变量完整模板
|
||
|
||
不依赖组件库时,在 `:root` 中声明以下变量:
|
||
|
||
```css
|
||
:root {
|
||
/* 字体 */
|
||
--animal-font: Nunito, 'Noto Sans SC', 'Zen Maru Gothic',
|
||
-apple-system, 'PingFang SC', 'Hiragino Sans GB', sans-serif;
|
||
|
||
/* 主色 */
|
||
--animal-primary: #19c8b9;
|
||
--animal-primary-hover: #3dd4c6;
|
||
--animal-primary-active: #11a89b;
|
||
--animal-primary-bg: #e6f9f6;
|
||
|
||
/* 文字 */
|
||
--animal-text: #794f27;
|
||
--animal-text-body: #725d42;
|
||
--animal-text-secondary: #9f927d;
|
||
--animal-text-muted: #8a7b66;
|
||
--animal-text-disabled: #c4b89e;
|
||
|
||
/* 背景 */
|
||
--animal-bg: #f8f8f0;
|
||
--animal-bg-content: rgb(247, 243, 223);
|
||
--animal-bg-disabled: #f0ece2;
|
||
|
||
/* 边框 */
|
||
--animal-border: #c4b89e;
|
||
--animal-border-hover: #a89878;
|
||
|
||
/* 圆角 */
|
||
--animal-radius-sm: 12px;
|
||
--animal-radius: 18px;
|
||
--animal-radius-lg: 24px;
|
||
--animal-radius-pill: 50px;
|
||
|
||
/* 3D 阴影 */
|
||
--animal-shadow-btn: #bdaea0;
|
||
--animal-shadow-input: #d4c9b4;
|
||
--animal-shadow-switch: #5a9e1e;
|
||
|
||
/* 游戏特殊色 */
|
||
--animal-focus-yellow: #ffcc00;
|
||
--animal-focus-yellow-d: #e0b800;
|
||
--animal-sidebar-active: #B7C6E5;
|
||
--animal-sidebar-hover: #d6dff0;
|
||
|
||
/* 状态 */
|
||
--animal-success: #6fba2c;
|
||
--animal-warning: #f5c31c;
|
||
--animal-error: #e05a5a;
|
||
|
||
/* 动效 */
|
||
--animal-ease: cubic-bezier(0.4, 0, 0.2, 1);
|
||
--animal-duration-fast: 0.15s;
|
||
--animal-duration: 0.25s;
|
||
--animal-duration-slow: 0.35s;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 7 条设计铁律
|
||
|
||
1. **颜色**:大地棕色系文字 + 薄荷青绿主色 + 奶油米白背景,禁止纯黑 / 冷灰
|
||
2. **圆角**:最小 12px;按钮、输入框必须 50px pill 形
|
||
3. **立体感**:所有可点击元素加底部厚阴影 `0 Npx 0 0 [暗色]`,hover 上浮,active 下压
|
||
4. **字体**:Nunito(Google Fonts)圆体,按钮/标题 weight 600+,从不使用细体
|
||
5. **动效**:过渡 0.15~0.35s,缓动 `cubic-bezier(0.4, 0, 0.2, 1)`,平滑不生硬
|
||
6. **焦点**:输入框用黄色 `#ffcc00`,按钮用青绿 `#19c8b9`,绝不用蓝色
|
||
7. **禁止**:直角矩形交互元素、纯黑文字 `#000`、冷蓝色调、扁平无阴影设计
|
||
|
||
---
|
||
|
||
## 7. 新组件文件结构模板
|
||
|
||
```
|
||
src/components/MyComponent/
|
||
├── MyComponent.tsx # 组件逻辑(必须设置 displayName)
|
||
├── myComponent.module.less # CSS Modules 样式
|
||
└── index.ts # 统一导出
|
||
```
|
||
|
||
`src/index.ts` 追加:
|
||
```ts
|
||
// 方式 A:组件用 default export
|
||
export { default as MyComponent } from './components/MyComponent'
|
||
export type { MyComponentProps } from './components/MyComponent/MyComponent'
|
||
|
||
// 方式 B:组件用 named export(如 Checkbox / CodeBlock / Select / Icon / Tabs 当前采用)
|
||
export { MyComponent } from './components/MyComponent'
|
||
export type { MyComponentProps } from './components/MyComponent'
|
||
```
|
||
> 仓库内两种风格并存;新增组件选一种即可,只要 `src/index.ts` 能成功 re-export。
|
||
|
||
Less 模板(直接使用设计 token):
|
||
|
||
```less
|
||
@import '../../styles/variables.less';
|
||
|
||
.container {
|
||
background: @bg-color-content; // rgb(247,243,223)
|
||
color: @text-color-body; // #725d42
|
||
border: @border-width solid @border-color-light; // 2px solid #c4b89e
|
||
border-radius: @border-radius-base; // 18px
|
||
font-family: @font-family;
|
||
font-weight: 500;
|
||
letter-spacing: 0.01em;
|
||
transition: all @motion-duration-base @motion-ease;
|
||
box-shadow: 0 3px 0 0 @shadow-input; // #d4c9b4
|
||
|
||
&:hover:not(.disabled) {
|
||
border-color: @border-color-hover; // #a89878
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 0 0 @shadow-input;
|
||
}
|
||
|
||
&:focus-within {
|
||
border-color: @focus-yellow; // #ffcc00
|
||
box-shadow: 0 3px 0 0 @focus-yellow-dark,
|
||
0 0 0 3px rgba(255, 204, 0, 0.15);
|
||
}
|
||
|
||
&.disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
background: @bg-color-disabled;
|
||
color: @text-color-disabled;
|
||
border-color: @shadow-input;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
```
|
||
|
||
TSX 模板:
|
||
|
||
```tsx
|
||
import React from 'react'
|
||
import styles from './myComponent.module.less'
|
||
import classNames from 'classnames'
|
||
|
||
export interface MyComponentProps {
|
||
/** 尺寸 */
|
||
size?: 'small' | 'middle' | 'large'
|
||
/** 禁用 */
|
||
disabled?: boolean
|
||
/** 子元素 */
|
||
children?: React.ReactNode
|
||
/** 自定义类名 */
|
||
className?: string
|
||
/** 自定义样式 */
|
||
style?: React.CSSProperties
|
||
}
|
||
|
||
const MyComponent: React.FC<MyComponentProps> = ({
|
||
size = 'middle',
|
||
disabled = false,
|
||
children,
|
||
className,
|
||
style,
|
||
}) => {
|
||
const cls = classNames(
|
||
styles.container,
|
||
styles[size],
|
||
{ [styles.disabled]: disabled },
|
||
className
|
||
)
|
||
|
||
return (
|
||
<div className={cls} style={style}>
|
||
{children}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
MyComponent.displayName = 'MyComponent'
|
||
export default MyComponent
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Demo 页面规范
|
||
|
||
每个组件在 `demo/components/<ComponentName>/index.tsx` 创建演示页:
|
||
|
||
```tsx
|
||
import { CodeBlock, ApiTable } from '../../tools'
|
||
|
||
const props = [
|
||
{ name: 'size', type: "'small' | 'middle' | 'large'", default: "'middle'", description: '尺寸' },
|
||
]
|
||
|
||
export default function MyComponentDemo() {
|
||
return (
|
||
<div>
|
||
<h2>MyComponent</h2>
|
||
<CodeBlock code={`<MyComponent size="large">内容</MyComponent>`} />
|
||
<ApiTable data={props} />
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
并在 `demo/ComponentPage.tsx` 中注册路由,同时把 `title / desc` 写入 `demo/pageInfo.ts`:
|
||
|
||
```ts
|
||
// demo/pageInfo.ts — 供 App 静态导入的轻量元信息
|
||
export const PAGE_INFO: Record<string, { title: string; desc: string }> = {
|
||
button: { title: 'Button 按钮', desc: '...' },
|
||
input: { title: 'Input 输入框', desc: '...' },
|
||
switch: { title: 'Switch 开关', desc: '...' },
|
||
card: { title: 'Card 卡片', desc: '...' },
|
||
collapse: { title: 'Collapse 折叠面板', desc: '...' },
|
||
cursor: { title: 'Cursor 光标', desc: '...' },
|
||
time: { title: 'Time 时间', desc: '...' },
|
||
phone: { title: 'Phone 手机', desc: '...' },
|
||
footer: { title: 'Footer 底部装饰', desc: '...' },
|
||
modal: { title: 'Modal 弹窗', desc: '...' },
|
||
typewriter: { title: 'Typewriter 打字机', desc: '...' },
|
||
'divider-comp': { title: 'Divider 分割线', desc: '...' },
|
||
icon: { title: 'Icon 图标', desc: '...' },
|
||
select: { title: 'Select 选择器', desc: '...' },
|
||
checkbox: { title: 'Checkbox 多选框', desc: '...' },
|
||
codeblock: { title: 'CodeBlock 代码高亮', desc: '...' },
|
||
}
|
||
```
|
||
|
||
新增组件务必追加对应条目,否则 Demo 侧栏不会展示。
|
||
|
||
---
|
||
|
||
## 9. 新增组件 Checklist
|
||
|
||
- [ ] Google Fonts 已在 `index.html` 或样式入口引入(Nunito + Noto Sans SC + Zen Maru Gothic)
|
||
- [ ] Props interface 从组件文件导出
|
||
- [ ] 所有 props 有 JSDoc 注释(中文 OK)
|
||
- [ ] 有状态组件同时支持受控(`value`)和非受控(`defaultValue`)模式
|
||
- [ ] `disabled` 状态:cursor: not-allowed + opacity 0.5~0.6 + 移除阴影
|
||
- [ ] 颜色优先引用 `variables.less` token,避免硬编码 hex
|
||
- [ ] 阴影使用暖色调(`#bdaea0` / `#d4c9b4` / `rgba(61,52,40,...)`),非冷黑
|
||
- [ ] hover 时 `translateY(-1px 或 -4px)` + 阴影加深
|
||
- [ ] active 时 `translateY(2px)` + 阴影减小
|
||
- [ ] 焦点:输入类用 `#ffcc00`,按钮类用 `#19c8b9`
|
||
- [ ] 动画使用 `@motion-duration-*` 和 `@motion-ease` token
|
||
- [ ] 组件从 `src/index.ts` 导出
|
||
- [ ] Demo 页创建于 `demo/components/`
|
||
- [ ] Demo 在 `demo/ComponentPage.tsx` 中注册
|
||
- [ ] `demo/pageInfo.ts` 追加 `{ title, desc }` 元信息
|