feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)

Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
This commit is contained in:
Z User committed 2026-10-27 22:50:50 +00:00
1 parent 7acc0c5451
commit e45b68bb8b
1089 files changed
+260614

No files matched your search

+293
View File
@@ -0,0 +1,293 @@
---
name: ascii-cosmos
description: Generate a single-file product landing page with a layered ASCII cosmos hero (rotating ASCII planet, drifting irregular asteroids, comet trails, nebula gas clouds — each in its own harmonious color) plus full marketing sections (about / features grid / showcase / spec / pricing / CTA / footer). Takes a natural-language description of one product (any kind — a note app, a coffee subscription, a CLI tool, an indie hardware, a hosting service…) and outputs ONE working .html file with deep-space dark or warm-letterpress theme. Runs in BOTH Claude Code (writes to ~/Desktop) and GLM / online chat (outputs HTML inline). Trigger phrases: "做个宇宙风产品落地页", "生成 ASCII 产品官网", "ascii-cosmos-gen ...", "做个独立软件 / 独立硬件落地页", or when user drags this skill folder into chat asking for an atmospheric product landing page.
visibility: public
mode: template
carrier: web-page
scenario: marketing-landing
pattern_source: reference.html
source_priority: skill-first
triggers:
- "做个宇宙风产品落地页"
- "生成 ASCII 产品官网"
- "做个独立软件落地页"
- "做个独立硬件落地页"
- "ascii 落地页"
- "ascii hero"
legacy_gen: true
---
# ascii-cosmos — ASCII 宇宙调性产品落地页生成器
## 作品长什么样
单文件 HTML 产品官网,hero 是动态分层 ASCII 宇宙:
- **星云气体云**(紫,柔和散点)
- **闪烁星点**(冷白,带 ★ ✦ 装饰)
- **旋转 ASCII 行星**(青,带 3 圈卫星轨道)
- **漂浮不规则小行星**(琥珀金,6 种带陨石坑的多行 ASCII 形状,自旋 + 缓慢漂移)
- **彗星拖尾**(粉,从屏幕掠过)
下面是常规 marketing 区块:关于 / 核心功能 6 卡片网格 / 界面预览 2 块 / 技术规格 / 订阅方案 3 档 / CTA / 页脚。所有强调字段都有"hover 时打乱-解码"的字符滚动动效(`.pt` 类)。
适合:独立软件、笔记/写作工具、本地优先 AI、CLI、独立硬件、订阅服务、SaaS、电子书、播客、任何"安静 / 克制 / 有手作味"的产品。
## 何时触发
用户说:
- "做个宇宙风产品落地页"
- "做个 ASCII 风格的产品官网"
- "生成一个像墨思那样的落地页 — 我的产品是 ..."
- "ascii-cosmos-gen ..."
- 把本 skill 文件夹拖进对话框并描述一个产品
提供:
- **产品名字 + 一句话定位**(必需)
- **核心卖点 / 功能清单**(推荐 — 给 6 张卡片用)
- **定价档位**(可选 — 没说就编 3 档:免费 / 专业 / 团队)
- **风格**(可选 — 深空青 / 暖色羊皮纸 / 赛博紫 / 终端绿 …,照抄 `styles.md` 预设名或自由描述。不写就用默认深空青)
如果用户**只给产品名一句话**:自己围绕这个产品把 6 个功能、3 档价、技术规格全部脑补出来。语气要克制、有手作味,不要 marketing 鸡血。
## 两种运行模式
### 模式 A · Claude Code / 本地(有文件系统)
- 输出位置:`~/Desktop/{slug}.html`(**单文件**,所有图片走 unsplash URL 或用户给的链接)
- slug 用产品英文名小写连字符
- 一句话报告:路径 + 用了什么风格 + 多少卡片
### 模式 B · GLM / 在线对话(无文件系统)
- 直接把完整 HTML 包在 ` ```html ` 代码块里输出
- 图片全部用 `https://images.unsplash.com/photo-XXX?...` 或 `https://picsum.photos/seed/{slug}-NN/W/H` 占位
- 不要尝试访问本地文件、不要建议命令行操作
- 末尾一行说明:保存为 .html 双击打开即可
判断:如果你**无法 Read 看到本地图片**或工作目录不是用户家目录 → 默认走 B。
## 工作步骤
### 1. 读模板
读取本 skill 同目录下的 `template.html`。这是完整骨架,包含:
- 全部 CSS(深空主题色 + 5 个 ASCII 层 + 卡片 / 价格 / 规格 / hero 网点字 …)
- 全部 JS(5 层 ASCII 渲染、信息区轨道、字符滚动、标签筛选、平滑滚动、实时时钟)
- 12 处 `<!-- SWAP-N -->` 注释标记需要替换的位置
**JS / CSS 不要改**(除了主题色变量与字体可以按 styles 改)。
### 2. 读风格预设
看 `styles.md`:5 种预设。如果用户的风格描述匹配某个名字,照那份的 CSS 变量替换 `:root`。否则按用户描述自己设计 6 个变量(`--bg / --fg / --dim / --accent / --accent2 / --accent3 / --line / --paper`)+ 5 个 ASCII 层颜色(`#ascii-nebula / -stars / -planet / -asteroids / -comets`)。
> 配色铁律:5 个 ASCII 层颜色必须**互相和谐 + 都能在背景上读出来**。深色背景 → 高饱和发光色;浅色背景 → 中低明度有重量的色。
### 3. 解析用户输入抽信息
抽:
- **PRODUCT_EN** — 英文 logo 名,全大写 ASCII 字(用于 hero 大字 ASCII title)
- **PRODUCT_CN** — 1-2 个中文字(hero 副标题大字,会渲染为网点字)
- **TAGLINE** — 一句话副标语(30-50 字)
- **VERSION / TYPE / PLATFORM** — 3 个 meta 标签
- **ABOUT_PARAGRAPH** — 80-150 字关于段落,3-5 个 `<span class="pt">` 强调短语
- **3 HIGHLIGHT** — 3 个 cell(标题 + 一句话描述)
- **6 FEATURES** — 6 张卡片:图片 URL + ASCII overlay(5-6 行小图) + 标题 + 副标题 + 1-2 个标签 + 分类(ai/write/graph/sync 任选或自定义)
- **2 SHOWCASE** — 2 段界面介绍(截图 URL + mini-ascii 装饰 + 标题带 `<em>` 强调 + 2 段 60-100 字描述)
- **10 SPEC ROWS** — 10 行键值对(版本 / 平台 / 引擎 / 数据格式 / 同步协议 …)
- **3 PRICING** — 3 档(名字 / 价格数字 / 价格单位 / 描述 / 5-6 条特性 / CTA 按钮文案)。**中间一档加 `featured` 类**
- **CTA_BIG** — 大字号 CTA 文案(带 `<em>` 强调)+ 副文案 + 3 个按钮 + 6-7 个平台标签
### 4. 替换 12 处 SWAP
| SWAP | 位置 | 内容 |
|---|---|---|
| 1 | `<title>` | `{PRODUCT_CN}{PRODUCT_EN} — {TAGLINE}` |
| 2 | header logo + 5 项 nav | logo 文字 + 导航锚点(默认:功能 / 界面 / 规格 / 订阅 / 关于) |
| 3 | hero ASCII title | `PRODUCT_EN` 的 ANSI Shadow 风 ASCII(用 https://patorjk.com/software/taag 风格,5 行高) |
| 4 | hero 中文大字 + tagline + 3 meta | `PRODUCT_CN` + `TAGLINE` + 3 个 meta span |
| 5 | intro 段落 + 3 cells | `ABOUT_PARAGRAPH`(含 `<span class="pt">` 强调)+ 3 个 `<div class="cell">` |
| 6 | 6 张 features 卡片 | 每张:`data-cat` + `<img src>` + ASCII overlay + 标题 + small + tags |
| 7 | 4 个 filter tag | 分类 chip(all + 你定的 3-4 个分类,匹配卡片的 data-cat) |
| 8 | 2 块 showcase | 每块:截图 + mini-ascii + h3(含 em) + 2 段 p(含 pt 强调) |
| 9 | 10 行 spec table | 每行:`<b>` 字段名 + `<span class="pt">` 值 |
| 10 | 3 档 pricing | name / num / desc / ul li / btn |
| 11 | info 区 CTA | info-big 大字(含 em)+ hero-sub 副文案 + 3 个按钮 + platforms |
| 12 | footer | 版权 + 一句署名 |
**强制保留**(不能动):
- 全部 `<script>` 块(5 层 ASCII 渲染 / 信息区轨道 / scramble / 标签筛选 / 平滑滚动 / 时钟)
- `.pt` 类的字符滚动机制(要在用户文案里手动撒 `<span class="pt" data-text="...">...</span>`)
- 5 个 ascii-layer 的 `<pre id="ascii-...">` 节点
- `<pre id="info-ascii">` 节点
- 6 张卡片的 data-cat 和筛选逻辑
- featured 价格档的样式
### 5. 写 ASCII 大字标题(SWAP-3)
用 ANSI Shadow / ANSI Regular 字体的 5 行 ASCII 文字。每个字母用 ╗╔═║║║╝╚═╝ 这种线条字符。例如 INKWEAVE 是:
```
██╗███╗ ██╗██╗ ██╗██╗ ██╗███████╗ █████╗ ██╗ ██╗███████╗
██║████╗ ██║██║ ██╔╝██║ ██║██╔════╝██╔══██╗██║ ██║██╔════╝
██║██╔██╗ ██║█████╔╝ ██║ █╗ ██║█████╗ ███████║██║ ██║█████╗
██║██║╚██╗██║██╔═██╗ ██║███╗██║██╔══╝ ██╔══██║╚██╗ ██╔╝██╔══╝
██║██║ ╚██╗██║██║ ██╗╚███╔███╔╝███████╗██║ ██║ ╚████╔╝ ███████╗
╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝
```
**如果产品英文名超过 8 字母** → 缩到 8 字母内的简称(如 `INTERSTELLAR` → `STELLAR`),或者用更窄的 ANSI 字体(更细的 `▌▐█`)。
**如果你不确定字模长什么样** → 至少给个看得过去的 5 行 ASCII,不要留空。
### 6. 写卡片 ASCII overlay(SWAP-6 内嵌)
每张卡片右下盖一段 4-5 行的小 ASCII 装饰,对应这张卡片的功能。例如:
- "智能续写" → `╭─────╮ │ ... │ ╰──→──╯`
- "知识图谱" → `●─●─● │\\ │/│ ●─●`
- "搜索" → `┌─────┐ │ ⌕ 找到 │ │ 3 处 │ └─────┘`
- "同步" → `▣ ⇄ ▢ ╲ ╱ ⇅ ╱ ╲ ▤ ⇄ ▥`
- "时间线" → `←─◆─◆─◆─→ ░░▒▒▓▓██`
不要超过 5 行,不要超过 14 字符宽。
### 7. 撒 `<span class="pt" data-text="...">` 强调
模板里所有需要"hover 时字符乱码再解码"的强调短语都用:
```html
<span class="pt" data-text="原文">原文</span>
```
- intro 段落里撒 3-5 处
- showcase p 里每段撒 1-2 处
- spec 每行的值都包一层 `<span class="pt">`
- pricing btn 文字也加 `pt`
- 标题里的色字用 `<span class="em">...</span>`(不是 `pt`)
### 8. 风格变体(可选)
如果用户指定了风格,照 `styles.md` 改:
- `:root` 8 个变量
- `.hero` background 渐变
- 5 个 `#ascii-{layer}` 颜色
- 图片滤镜(深色用 `hue-rotate(200deg) brightness(.7)`,暖色用 `sepia(.3) saturate(.85)`)
> **没有指定风格时**:根据产品的"调性词"自动选色 — 见下面「调性 → 配色映射」。
### 8.5 替换 ASCII 场景 motif(推荐!)
模板的 hero 默认是宇宙(行星 + 小行星 + 彗星 + 星云)。**这个 motif 应该跟着产品调性换** — 一个咖啡订阅不该飘小行星,一个森林漫步 app 不该有彗星。
改法**只改一个对象**:找到 `<script>` 里的 `// SWAP-13 · ASCII 场景资产` 标记,整体替换里面的 `SCENE = { ... }`。其它 JS 不要动。
`SCENE` 11 个键各自的角色:
| 键 | 默认(宇宙) | 角色 / 视觉职责 |
|---|---|---|
| `floaters` | 6 个不规则小行星 ASCII 形状(多行数组) | 屏幕里漂浮 + 自旋的"主物体" |
| `centerShade` | `" .,:;-~+*=%#@█"` | 中央旋转球体的明暗梯度(暗→亮 14 字符) |
| `diffuseChars` | `"·∙∶⋮⋯░▒"` | 弥漫底层的"气体云"密度梯度(淡→浓) |
| `sparkRare` | `"★"` | 稀有大亮点 (4% 概率) |
| `sparkBright` | `"✦"` | 中频亮点 (15%) |
| `sparkDim` | `".∙"` | 普通暗点 |
| `streakHead` | `"☄"` | 飞掠物的"头"(最亮的一格) |
| `streakTailNear/Mid/Far/End` | `"*","∙","·","."` | 拖尾从近到远 4 段渐隐 |
| `centerSats` | `"◉◎○"` | 围绕中央球体的 3 颗"卫星",由内向外 |
| `orbitDots` | `".∙•"` | 3 圈轨道线的点字符,由内向外 |
#### Motif 库(按产品类型选)
| 产品类型 | floaters 主体 | centerShade | diffuseChars | sparks | streakHead/tail | centerSats | 视觉故事 |
|---|---|---|---|---|---|---|---|
| **咖啡 / 茶 / 饮品** | 咖啡豆 / 茶叶 形状 | `" .'-=+oO0@#"` | `"·~,'\""` (蒸汽) | `★ ✧ . ∙` | `"☕"` 头 + `"*∙·."` 拖尾 | `"●◐○"` (三种烘焙) | 一杯咖啡 + 飘豆 + 蒸汽 + 偶尔一颗豆掉过 |
| **森林 / 自然 / 户外** | 树叶 / 松果 形状 | `" .,;:!|tT#"` (年轮) | `"~^,'\""` (雾) | `"⁂"` 萤火 + `"·"` 露珠 | `"𓅯"` 鸟 + `"~~~~"` 飞行轨迹 | `"❀✿✾"` (花) | 一棵树 + 落叶 + 雾 + 萤火虫 + 偶尔飞鸟 |
| **音乐 / 播客 / 音频** | 音符 / 唱片碎片 | `" .─=≡▪▫■□"` | `"·∶⋮⁞"` (噪点) | `"♪♫"` + `"·"` | `"♬"` 头 + `"~∼≈"` 波 | `"◉◎○"` (黑胶圈) | 一张唱片 + 飘音符 + 频谱噪点 + 节拍 |
| **代码 / CLI / 开发者** | `{}` `[]` `()` `<>` 包裹的小 ASCII 块 | `" 01[](){}<>#"` | `"·.,:|"` | `"★ ✦ . _"` | `"_"` 光标 + `" "` (闪现型) | `"◉⊞⊟"` 模块 | 一个调用图 + 飞过的 commit + 闪烁光标 + 矩阵雨 |
| **手作 / 工艺 / 极物** | 锤 / 刨花 / 钉 / 锈点 | `" .·°*+x#X@"` | `"·:.,"` (尘) | `"✦ ▪ ▫"` | `"→"` 头 + `"-—"` 切线 | `"◉○⊙"` (齿轮) | 一个工作台 + 飘刨花 + 木屑 + 偶尔一记 |
| **冥想 / 健康 / 慢生活** | 涟漪 / 圆 / 波浪 形状 | `" ··∘○●"` | `"~∼≈"` (呼吸波) | `". ∙ °"` | `"◌"` 头 + `"·· ··"` 散点 | `"◯◌○"` (空圆) | 一个呼吸圆 + 涟漪 + 暖色尘埃 + 缓慢 |
| **极客硬件 / 电子** | 芯片 / 电路块 (`├┤├ ┐└┘`) | `" .·•◊◆■"` | `"⋅¨"` (电气噪) | `"⚡"` 偶现 + `"·"` | `"►"` 头 + `"==="` 数据流 | `"⊙⊚⊛"` (LED) | PCB + 漂浮电容 + 数据流飞过 |
| **写作 / 笔记 / 思考** | `"…"` `"——"` `"§"` 标点形状 | `" .,;:!?¶§"` | `"··¨"` | `". ∶ ⋮"` | `"✒"` 头 + `"~~~"` 笔迹 | `"◉◯●"` (墨点) | 一支笔 + 飘标点 + 笔迹划过 + 思绪点 |
| **意式 / 复古 / 80s** | 像素方块 / 撞色几何 | `" .░▒▓█"` | `"·:|"` | `"◆ ◇ ▰"` | `"◉"` 头 + `"━━"` 实线 | `"■□▣"` | 一个 CRT + 像素粒子 + 扫描线 |
| **海洋 / 旅行 / 远方** | 鱼 / 帆 / 船 / 浪 | `" .~≈≋"` 水波 | `"~∼≈≋"` (波纹) | `"·°"` (气泡) | `"⛵"` 头 + `"~~~"` 尾流 | `"◐◑◯"` (月相) | 一艘船 + 浪 + 气泡 + 海鸟划过 |
#### 怎么写 floaters 形状
每个形状是 3-4 行的多行 ASCII,宽 8-12 字符。一定要:
- 形状轮廓闭合(比如树叶 `,/^\,` `( )` `'\_/'` )
- 多个变体不要长得一模一样,至少 3-6 个不同的
- 单字符宽度,不要用全角
- 留 1 格 padding 让旋转抖动好看
**树叶示例**:
```js
floaters: [
[" ,-^-, ",
"| |",
" '---' "],
[" /\\\\ ",
" / \\\\ ",
" \\__/ "],
[" *_* ",
" /...\\\\ ",
" '___' "]
]
```
**音符示例**:
```js
floaters: [
[" ♪ ",
" /| ",
"♪ |____♪"],
[" ♫ ",
" /\\\\ ",
"♪__/ \\\\"],
[" ♩ ",
" | ",
" o "]
]
```
**写完检查**:把 SCENE 直接贴进控制台,跑一下 `JSON.stringify(SCENE)` 不报错就 OK。
#### 自由发挥的判定
如果用户的产品**不在上表 10 类**里 → 自己设计 motif,遵守原则:
1. **floaters** 选 3-6 个跟产品有具象关联的小图形("它会让人想到这个产品")
2. **centerShade** 14 字符明暗梯度,暗的字符(` .,`)开头,亮的(`@#█`)结尾,中间渐变
3. **diffuseChars** 5-8 个"半透明"字符(点 / 顿点 / 小方块 / 波浪),表现氛围底层
4. **streak** 整套要"前重后轻"渐隐
5. **sat / orbitDots** 各 3 个,表现轻重三级
> 一句话原则:**让人不戴眼镜、3 米外瞄一眼,能猜出这是个什么产品的网站。**
### 9. 调性 → 配色映射(用户没指定风格时按这个选)
| 用户描述里的关键词 | 推荐预设 | 主色 |
|---|---|---|
| 安静 / 克制 / 思考 / 写作 / 笔记 / 本地优先 | **深空青**(默认) | `#8be9ff` 青 |
| 温暖 / 手作 / 中式 / 文艺 / 慢 / 茶 / 复古 | **暖色羊皮纸** | `#660125` 酒红 |
| 高能 / 未来 / AI / Web3 / 游戏 / 极客 | **赛博紫** | `#ff00aa` 霓虹品红 |
| CLI / 终端 / 安全 / 复古 80s / phosphor | **终端绿** | `#00ff66` |
| 自然 / 健康 / 冥想 / 旅行 / 北欧 | **极光** | `#7fe3c4` 极光绿 |
| 咖啡 / 烘焙 / 工艺 / 食品 / 沙漠 / 大地 | **沙漠日落** | `#ff8b3d` 烤橙 |
| 海洋 / 远方 / 蓝色 / 冷静 / 数据 | 自由设计 | `#4fc3f7` 海洋蓝 |
| 黑金 / 奢侈 / 精确 / 钟表 / 高端硬件 | 自由设计 | `#d4af37` 香槟金 + 黑底 |
判定流程:
1. **用户明确说了风格名** → 照 `styles.md` 改
2. **用户没说,但产品有明显调性** → 按上表选最贴的预设
3. **完全开放** → 默认深空青
### 10. 报告
**模式 A**:一句话。
> 已生成 ~/Desktop/{slug}.html — {产品名}产品落地页,6 个功能卡片 + 3 档价格,深空青风格。双击打开。
**模式 B**:完整 HTML 代码块 + 一行说明。
> 保存为 `{slug}.html` 双击打开。要换图就把图片 URL 换成你自己的。要改 ASCII 主标题去顶部的 `<pre class="ascii-title lg">` 替换字模。
## 易错速查
- **5 个 ASCII 层颜色互相打架**:测试一下深色背景下 5 色都要清晰可读。粉太亮、紫太暗都会塌
- **ASCII 大字模板对不上**:5 行 ANSI Shadow 字符高度必须严格 5-6 行,不要 3 行
- **`<pre>` 里换行用真换行**,不要 `\n` 字符串字面量
- **卡片 data-cat 和 filter 不匹配** → 筛选失效。一致性检查
- **pricing 中间档忘加 `featured`** → 视觉重心丢
- **JSON / 对象里中文引号**:ASCII overlay 里如果用引号一律用全角 `""`
- **删了 `<pre id="ascii-comets">` 之类的层** → JS 报错。5 层节点必须全部保留
- **没给图片 URL** → 用 unsplash 的横图(`?auto=format&fit=crop&w=900&q=70`)或 picsum
+958
View File
@@ -0,0 +1,958 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>墨思 — 智能思维编织工作台</title>
<style>
:root{
--bg:#05060f;
--fg:#e6e8ff;
--dim:#7a7fa8;
--accent:#8be9ff;
--accent2:#ff6ad5;
--accent3:#c792ea;
--line:#1a1d3a;
--hl:#0d1024;
--paper:#0a0c1f;
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{background:var(--bg);color:var(--fg);font-family:"Songti SC","STSong","Source Han Serif SC","Noto Serif CJK SC","SimSun",serif;font-size:14px;line-height:1.8;-webkit-font-smoothing:antialiased}
/* 中文段落均使用宋体 */
:lang(zh),.intro-text,.showcase-text p,.showcase-text h3,.info-big,.hero-title,.card-title,.price .desc,.cell{font-family:"Songti SC","STSong","Source Han Serif SC","Noto Serif CJK SC","SimSun",serif}
/* 数字 / 拉丁字母仍保持等宽,但中文遇到等宽会 fallback 回宋体 */
.header-logo,.nav a,.hero-meta,.hero-sub,.sec-head,.scroll-cue,.tag,.card-num,.card-tags,.spec-row b,.price h4,.price .num,.platforms,.footer,.here{font-family:"SF Mono","Menlo","Consolas","Songti SC","STSong","SimSun",serif}
a{color:inherit;text-decoration:none;cursor:pointer}
::selection{background:var(--accent);color:var(--bg)}
/* HEADER */
.header{position:fixed;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:18px 28px;z-index:100;color:var(--fg);background:rgba(5,6,15,.85);backdrop-filter:blur(8px);border-bottom:1px solid var(--line)}
.header-logo{font-weight:600;letter-spacing:0.18em;font-size:13px}
.header-logo span{color:var(--accent)}
.nav ul{display:flex;gap:26px;list-style:none}
.nav a{font-size:12px;letter-spacing:0.2em;position:relative;padding:4px 0;transition:color .3s}
.nav a:hover{color:var(--accent)}
.nav a::after{content:"";position:absolute;left:0;bottom:0;height:1px;width:0;background:var(--accent);transition:width .3s}
.nav a:hover::after{width:100%}
/* HEY */
.here{position:fixed;right:32px;bottom:28px;font-size:12px;letter-spacing:0.2em;z-index:90;text-align:right;color:var(--dim)}
.here .blink{display:inline-block;animation:blink 1.2s infinite}
@keyframes blink{0%,49%{opacity:1}50%,100%{opacity:0}}
/* HERO ASCII */
.hero{height:100vh;width:100%;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;border-bottom:1px solid var(--line);background:radial-gradient(ellipse at 30% 20%, #1a0a2e 0%, #05060f 55%, #000 100%)}
.ascii-layer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:12px;letter-spacing:0;white-space:pre;font-family:"SF Mono",monospace;pointer-events:none;font-weight:600;margin:0}
#ascii-nebula{color:#a87bd6;opacity:.55;text-shadow:0 0 8px rgba(168,123,214,.45);mix-blend-mode:screen}
#ascii-stars{color:#cfe6ff;opacity:.85;text-shadow:0 0 4px rgba(207,230,255,.5)}
#ascii-planet{color:var(--accent);opacity:.95;text-shadow:0 0 6px rgba(139,233,255,.45)}
#ascii-asteroids{color:#f5b971;opacity:.92;text-shadow:0 0 6px rgba(245,185,113,.45)}
#ascii-comets{color:#ff8fd2;opacity:.95;text-shadow:0 0 8px rgba(255,143,210,.55)}
.hero-overlay{position:relative;z-index:2;text-align:center;padding:0 24px;color:var(--fg)}
.hero-title{font-size:clamp(40px,7vw,98px);font-weight:300;letter-spacing:0.04em;line-height:1;margin-bottom:18px}
.hero-title .em{font-style:italic;color:var(--accent)}
.hero-sub{font-size:14px;letter-spacing:0.3em;color:var(--dim);margin-bottom:36px}
/* ASCII 大标题 */
.ascii-title{font-family:"SF Mono","Menlo","Consolas",monospace;color:var(--accent);white-space:pre;display:inline-block;text-align:left;line-height:1;font-weight:700;margin:0 auto;letter-spacing:0;text-shadow:0 0 12px rgba(139,233,255,.5),0 0 2px rgba(199,146,234,.4)}
.ascii-title.lg{font-size:clamp(8px,1.25vw,14px);line-height:1.05}
.ascii-title.md{font-size:clamp(6px,.85vw,10px);color:var(--fg)}
.ascii-title.sm{font-size:clamp(5px,.55vw,7px);color:var(--accent);opacity:.85}
.brand-wrap-outer{text-align:center}
.brand-cn-sub{font-family:"Songti SC","STSong","SimSun",serif;font-size:clamp(28px,3.6vw,46px);font-weight:400;letter-spacing:0.5em;color:var(--fg);margin:24px 0 0;padding-left:0.5em}
.ascii-cn{display:none}
.sec-ascii{display:block;color:var(--accent);white-space:pre;font-family:"SF Mono",monospace;font-size:14px;line-height:1;margin-bottom:10px;opacity:.85;letter-spacing:0.4em}
/* 中文真字宋体大标题 + ASCII 网点背景 */
.brand-block{text-align:center;line-height:1;display:inline-block;padding:36px 64px;position:relative}
.brand-block::before{
content:"";position:absolute;inset:0;
background-image:radial-gradient(circle, rgba(139,233,255,.22) 1px, transparent 1.4px);
background-size:7px 7px;
pointer-events:none;
opacity:.85;
z-index:0;
/* 中间淡四角浓的渐隐遮罩,像深空尘埃 */
-webkit-mask-image:radial-gradient(ellipse at center, transparent 30%, #000 90%);
mask-image:radial-gradient(ellipse at center, transparent 30%, #000 90%);
}
.brand-deco-top,.brand-deco-bot{font-family:"SF Mono","Menlo",monospace;color:var(--accent);font-size:clamp(12px,1.4vw,18px);letter-spacing:0.6em;opacity:.85;margin:0 0 18px;position:relative;z-index:1}
.brand-deco-bot{margin:18px 0 0}
.brand-title-cn{
font-family:"Songti SC","STSong","Source Han Serif SC","SimSun",serif;
font-weight:700;
font-size:clamp(140px,20vw,240px);
line-height:1;
letter-spacing:0.32em;
display:inline-block;
position:relative;
z-index:1;
/* 字内填充:星云网点,模拟字符画/星尘 */
background-image:
radial-gradient(circle, var(--accent) 0.95px, transparent 1.3px),
radial-gradient(circle, var(--accent2) 0.45px, transparent 0.85px);
background-size: 5px 5px, 3px 3px;
background-position: 0 0, 1.5px 1.5px;
-webkit-background-clip:text;
background-clip:text;
color:transparent;
-webkit-text-fill-color:transparent;
/* 极细描边 */
-webkit-text-stroke:0.4px rgba(139,233,255,.55);
text-shadow:0 0 8px rgba(139,233,255,.3);
}
.brand-title-cn .gap{display:inline-block;width:.2em}
.brand-wrap-outer{text-align:center}
.sec-head h2{font-size:18px;font-weight:400;letter-spacing:0.4em;color:var(--fg);display:flex;flex-direction:column;gap:6px;font-family:"Songti SC","STSong","SimSun",serif}
.hero-meta{display:flex;gap:32px;justify-content:center;font-size:11px;letter-spacing:0.16em;color:var(--dim);flex-wrap:wrap}
.hero-meta span b{color:var(--fg);font-weight:400}
.scroll-cue{position:absolute;left:50%;bottom:32px;transform:translateX(-50%);font-size:11px;letter-spacing:0.3em;color:var(--dim);z-index:3;animation:bob 2.4s ease-in-out infinite}
@keyframes bob{0%,100%{transform:translate(-50%,0)}50%{transform:translate(-50%,8px)}}
/* SECTION */
.sec{padding:96px 48px;border-bottom:1px solid var(--line);position:relative}
.sec-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:48px;font-size:11px;letter-spacing:0.24em;color:var(--dim);border-bottom:1px solid var(--line);padding-bottom:18px}
.sec-head .idx{font-family:monospace}
/* INTRO */
.intro{max-width:980px;margin:0 auto}
.intro-text{font-size:clamp(20px,2.4vw,30px);line-height:1.55;font-weight:300;letter-spacing:0.02em}
.intro-text .pt{display:inline;cursor:default;transition:color .2s}
.intro-text .pt:hover{color:var(--accent)}
.intro-text .pt.scrambling{color:var(--accent)}
.intro-text .em{color:var(--accent);font-style:italic}
.intro-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:32px;margin-top:64px;padding-top:48px;border-top:1px dashed var(--line)}
.intro-grid .cell{font-size:12px;color:var(--dim);line-height:1.7}
.intro-grid .cell b{display:block;color:var(--fg);font-weight:400;margin-bottom:8px;letter-spacing:0.16em;font-size:11px}
/* TAGS */
.filter{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:36px}
.tag{padding:6px 14px;border:1px solid var(--line);font-size:11px;letter-spacing:0.16em;color:var(--dim);transition:all .25s;cursor:pointer;user-select:none}
.tag:hover{color:var(--fg);border-color:var(--dim)}
.tag.active{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.tag .dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:currentColor;margin-right:8px;vertical-align:middle;opacity:.6}
/* GRID */
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.card{border:1px solid var(--line);background:var(--paper);transition:all .3s;position:relative;overflow:hidden;display:flex;flex-direction:column}
.card:hover{border-color:var(--accent);transform:translateY(-4px);box-shadow:0 12px 32px -16px rgba(139,233,255,.5)}
.card-img{aspect-ratio:4/3;background:#0d1024;position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center}
.card-img img{width:100%;height:100%;object-fit:cover;transition:transform .8s ease,filter .4s;filter:hue-rotate(200deg) saturate(.9) contrast(1.1) brightness(.6)}
.card:hover .card-img img{transform:scale(1.06);filter:hue-rotate(220deg) saturate(1.1) contrast(1.05) brightness(.75)}
.card-img .ascii-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:9px;line-height:9px;color:var(--accent);white-space:pre;opacity:.75;pointer-events:none;mix-blend-mode:screen;text-shadow:0 0 4px rgba(139,233,255,.6)}
.card-meta{padding:18px 20px;display:flex;justify-content:space-between;align-items:flex-start;gap:14px}
.card-title{font-size:14px;letter-spacing:0.04em;font-weight:400}
.card-title .pt{cursor:default}
.card-title small{display:block;color:var(--dim);font-size:11px;margin-top:6px;letter-spacing:0.12em}
.card-num{color:var(--dim);font-size:10px;font-family:monospace;letter-spacing:0.1em}
.card-tags{display:flex;gap:6px;padding:0 20px 18px;flex-wrap:wrap}
.card-tags span{font-size:10px;letter-spacing:0.16em;color:var(--dim);padding:2px 8px;border:1px solid var(--line);text-transform:uppercase}
.card.hidden{display:none}
/* SHOWCASE / UI 截图 */
.showcase{display:grid;grid-template-columns:1.2fr 1fr;gap:48px;align-items:center;margin-top:24px}
.showcase-shot{border:1px solid var(--line);background:var(--paper);padding:14px;position:relative;box-shadow:0 8px 28px -16px rgba(139,233,255,.3)}
.showcase-shot .bar{display:flex;gap:6px;margin-bottom:10px}
.showcase-shot .bar i{display:block;width:10px;height:10px;border-radius:50%;background:#3a3f6a}
.showcase-shot img{width:100%;height:auto;display:block;filter:hue-rotate(200deg) saturate(.85) contrast(1.05) brightness(.7);border:1px solid var(--line)}
.showcase-text h3{font-size:clamp(24px,3vw,36px);font-weight:300;line-height:1.3;margin-bottom:18px;letter-spacing:0.02em;color:var(--fg)}
.showcase-text h3 .em{color:var(--accent);font-style:italic}
.showcase-text .mini-ascii{font-family:"SF Mono",monospace;color:var(--accent);font-size:10px;line-height:1;white-space:pre;display:block;margin-bottom:18px;opacity:.85}
.showcase-text p{color:var(--dim);font-size:13px;line-height:1.8;margin-bottom:14px}
.showcase-text p .pt:hover{color:var(--accent)}
/* SPEC */
.spec-wrap{display:grid;grid-template-columns:1.3fr 1fr;gap:64px;align-items:start}
.spec-visual{position:relative;aspect-ratio:4/5;background:var(--paper);border:1px solid var(--line);overflow:hidden}
.spec-visual img{width:100%;height:100%;object-fit:cover;filter:hue-rotate(220deg) saturate(.9) contrast(1.05) brightness(.7)}
.spec-visual::before{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(5,6,15,0) 50%,rgba(5,6,15,.85) 100%);z-index:2}
.spec-visual .label{position:absolute;left:20px;bottom:18px;z-index:3;font-size:11px;letter-spacing:0.2em;color:var(--accent)}
.spec-table{width:100%}
.spec-row{display:grid;grid-template-columns:140px 1fr;padding:14px 0;border-bottom:1px dashed var(--line);font-size:12px;align-items:baseline}
.spec-row b{color:var(--dim);font-weight:400;letter-spacing:0.16em;font-size:11px}
.spec-row span{color:var(--fg)}
.spec-row .pt{cursor:default}
/* PRICING */
.price-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
.price{border:1px solid var(--line);padding:32px 26px;background:var(--paper);transition:.3s;display:flex;flex-direction:column}
.price:hover{border-color:var(--accent);box-shadow:0 12px 32px -16px rgba(139,233,255,.4)}
.price.featured{border-color:var(--accent);background:#0f1233;border-width:1px;box-shadow:0 0 0 1px var(--accent) inset, 0 0 32px -8px rgba(139,233,255,.3)}
.price h4{font-size:13px;letter-spacing:0.2em;color:var(--dim);margin-bottom:18px;font-weight:400}
.price .num{font-size:42px;font-weight:300;letter-spacing:0.02em;margin-bottom:6px;color:var(--fg)}
.price .num small{font-size:13px;color:var(--dim);letter-spacing:0.1em;margin-left:6px}
.price .desc{color:var(--dim);font-size:12px;margin-bottom:24px;line-height:1.6}
.price ul{list-style:none;font-size:12px;color:var(--fg);line-height:2;margin-bottom:28px;flex:1}
.price ul li::before{content:"+ ";color:var(--accent);margin-right:4px}
.price .btn{display:block;text-align:center;padding:12px;border:1px solid var(--fg);font-size:11px;letter-spacing:0.2em;transition:.25s;color:var(--fg)}
.price .btn:hover{background:var(--fg);color:var(--bg);border-color:var(--fg)}
.price.featured .btn{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.price.featured .btn:hover{background:var(--accent2);border-color:var(--accent2);color:var(--bg)}
/* INFO */
.info{text-align:center;padding:120px 24px}
.info-big{font-size:clamp(36px,6vw,76px);font-weight:300;line-height:1.1;letter-spacing:0.02em;margin-bottom:36px}
.info-big .em{font-style:italic;color:var(--accent)}
.info-links{display:flex;gap:28px;justify-content:center;flex-wrap:wrap;margin-top:28px;font-size:12px;letter-spacing:0.2em}
.info-links a{padding:14px 28px;border:1px solid var(--fg);transition:all .25s;color:var(--fg)}
.info-links a:hover{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.info-orbit{margin:0 auto 48px;width:280px;height:280px;position:relative;color:var(--dim);font-size:9px;line-height:9px;white-space:pre;font-family:monospace;display:flex;align-items:center;justify-content:center}
#info-ascii{color:var(--accent);opacity:1;font-weight:700}
/* PLATFORMS */
.platforms{display:flex;flex-wrap:wrap;gap:14px;justify-content:center;margin-top:24px;font-size:11px;letter-spacing:0.18em;color:var(--dim)}
.platforms span{padding:6px 14px;border:1px solid var(--line)}
/* FOOTER */
.footer{padding:24px 32px;display:flex;justify-content:space-between;font-size:10px;letter-spacing:0.18em;color:var(--dim);border-top:1px solid var(--line);flex-wrap:wrap;gap:12px;background:var(--paper)}
/* HERO 后景柔光遮罩,仅在中心给标题留出可读区,四周让 ASCII 充分显现 */
.hero::after{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:min(82vw,1100px);height:min(58vh,520px);background:radial-gradient(ellipse at center,rgba(5,6,15,.85) 0%,rgba(5,6,15,.6) 40%,rgba(5,6,15,0) 80%);pointer-events:none;z-index:1;border-radius:50%}
@media(max-width:900px){
.grid{grid-template-columns:1fr 1fr}
.intro-grid{grid-template-columns:1fr}
.spec-wrap,.showcase{grid-template-columns:1fr;gap:32px}
.price-grid{grid-template-columns:1fr}
.sec{padding:64px 24px}
.header{padding:14px 18px}
}
@media(max-width:560px){
.grid{grid-template-columns:1fr}
#ascii-stage{font-size:7px;line-height:7px}
}
</style>
</head>
<body>
<!-- HEADER -->
<div class="header">
<div class="header-logo"><a href="#top">墨思</a></div>
<nav class="nav">
<ul>
<li><a href="#features">功能</a></li>
<li><a href="#showcase">界面</a></li>
<li><a href="#spec">规格</a></li>
<li><a href="#price">订阅</a></li>
<li><a href="#info">关于</a></li>
</ul>
</nav>
</div>
<!-- HEY -->
<div class="here">你好<br><span class="blink">▮</span> 现在是 凌晨 03:14</div>
<!-- HERO -->
<section class="hero" id="top">
<pre id="ascii-nebula" class="ascii-layer"></pre>
<pre id="ascii-stars" class="ascii-layer"></pre>
<pre id="ascii-planet" class="ascii-layer"></pre>
<pre id="ascii-asteroids" class="ascii-layer"></pre>
<pre id="ascii-comets" class="ascii-layer"></pre>
<div class="hero-overlay">
<div class="brand-wrap-outer">
<pre class="ascii-title lg">
██╗███╗ ██╗██╗ ██╗██╗ ██╗███████╗ █████╗ ██╗ ██╗███████╗
██║████╗ ██║██║ ██╔╝██║ ██║██╔════╝██╔══██╗██║ ██║██╔════╝
██║██╔██╗ ██║█████╔╝ ██║ █╗ ██║█████╗ ███████║██║ ██║█████╗
██║██║╚██╗██║██╔═██╗ ██║███╗██║██╔══╝ ██╔══██║╚██╗ ██╔╝██╔══╝
██║██║ ╚██╗██║██║ ██╗╚███╔███╔╝███████╗██║ ██║ ╚████╔╝ ███████╗
╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝
</pre>
<div class="brand-cn-sub">墨 思</div>
</div>
<div class="hero-sub" style="margin-top:28px">让 散 落 的 想 法,自 己 长 出 经 纬</div>
<div class="hero-meta">
<span>版本 <b>v3.2 / 2026.05</b></span>
<span>类型 <b>本地智能笔记 / 知识星图</b></span>
<span>平台 <b>桌面 / 移动 / 网页</b></span>
</div>
</div>
<div class="scroll-cue">↓ 向 下 滚 动 探 索 ↓</div>
</section>
<!-- INTRO -->
<section class="sec">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ❖ ═══</pre>关于</h2><div class="idx">[ 01 / 05 ]</div></div>
<div class="intro">
<p class="intro-text">
<span class="pt" data-text="墨思">墨思</span> 是一款 <span class="em">本地优先</span> 的智能思维工作台。
它会在你写字时,悄悄读懂上下文,
把你过去 <span class="pt" data-text="三年">三年</span> 写过的 <span class="pt" data-text="所有笔记、网页摘录、文献批注">所有笔记、网页摘录、文献批注</span>
自动织成一张 <span class="em">双向链接图谱</span>。
所有推理 <span class="pt" data-text="完全在你的电脑上运行">完全在你的电脑上运行</span>,
你的想法 <span class="em">永远不离开本机</span>。
</p>
<div class="intro-grid">
<div class="cell"><b>✦ 本地优先</b>内置量化大模型,主流芯片可全程离线运行,不依赖任何云端接口。</div>
<div class="cell"><b>✦ 双链图谱</b>每一个想法都是一颗节点,智能模型自动发现它们之间的潜在引用,让旧笔记重新被点亮。</div>
<div class="cell"><b>✦ 端到端加密</b>多端同步走点对点链路,密钥只在你的设备里,连我们也读不到。</div>
</div>
</div>
</section>
<!-- FEATURES GRID -->
<section class="sec" id="features">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ❍ ═══</pre>核心功能</h2><div class="idx">[ 02 / 05 ]</div></div>
<div class="filter">
<a class="tag active" data-cat="all"><span class="dot"></span>全部</a>
<a class="tag" data-cat="ai"><span class="dot"></span>智能协作</a>
<a class="tag" data-cat="write"><span class="dot"></span>写作</a>
<a class="tag" data-cat="graph"><span class="dot"></span>图谱</a>
<a class="tag" data-cat="sync"><span class="dot"></span>同步</a>
</div>
<div class="grid" id="grid">
<div class="card" data-cat="ai write">
<div class="card-img">
<img src="../assets/ai-01-search.jpg" alt="AI 续写">
<pre class="ascii-overlay">▌ AI ▍▎▏
╭─────╮
│ ... │
╰──→──╯</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="智能续写">智能续写</span><small>本机模型</small></div>
<div class="card-num">/01</div>
</div>
<div class="card-tags"><span>智能</span><span>写作</span></div>
</div>
<div class="card" data-cat="graph">
<div class="card-img">
<img src="../assets/ai-02-document.jpg" alt="知识图谱">
<pre class="ascii-overlay"> ●─●─●
│\ │ /│
● ●─● ●
│/ │ \│
●─●─●</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="思维双链图谱">思维双链图谱</span><small>反向链接</small></div>
<div class="card-num">/02</div>
</div>
<div class="card-tags"><span>图谱</span></div>
</div>
<div class="card" data-cat="ai">
<div class="card-img">
<img src="../assets/ai-03-video.jpg" alt="语义搜索">
<pre class="ascii-overlay">┌─────────┐
│ ⌕ 找到 │
│ 3 处 │
│ 相似 │
└─────────┘</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="语义搜索">语义搜索</span><small>向量检索</small></div>
<div class="card-num">/03</div>
</div>
<div class="card-tags"><span>智能</span><span>搜索</span></div>
</div>
<div class="card" data-cat="write">
<div class="card-img">
<img src="../assets/ai-04-orchestration.jpg" alt="Markdown 编辑">
<pre class="ascii-overlay"># Heading
- list
- item
> quote
**bold**</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="纯文本写作">纯文本写作</span><small>原生标记</small></div>
<div class="card-num">/04</div>
</div>
<div class="card-tags"><span>写作</span></div>
</div>
<div class="card" data-cat="sync">
<div class="card-img">
<img src="../assets/ai-05-voice.jpg" alt="多端同步">
<pre class="ascii-overlay"> ▣ ⇄ ▢
╲ ╱
⇅
╱ ╲
▤ ⇄ ▥</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="端到端同步">端到端同步</span><small>全程加密</small></div>
<div class="card-num">/05</div>
</div>
<div class="card-tags"><span>同步</span><span>加密</span></div>
</div>
<div class="card" data-cat="ai graph">
<div class="card-img">
<img src="../assets/ai-06-integration.jpg" alt="时光回溯">
<pre class="ascii-overlay">←─◆─◆─◆─◆─→
2023 24 25 26
░░▒▒▓▓██</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="时间线回溯">时间线回溯</span><small>变迁追踪</small></div>
<div class="card-num">/06</div>
</div>
<div class="card-tags"><span>图谱</span><span>智能</span></div>
</div>
</div>
</section>
<!-- SHOWCASE -->
<section class="sec" id="showcase">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ◈ ═══</pre>界面预览</h2><div class="idx">[ 03 / 05 ]</div></div>
<div class="showcase">
<div class="showcase-shot">
<div class="bar"><i></i><i></i><i></i></div>
<img src="../assets/ai-07-hero.jpg" alt="编辑器界面">
</div>
<div class="showcase-text">
<pre class="mini-ascii">┌──────────────────────┐
│ ▓▓░░░░░░░░░░░░░░░░░ │
└──────────────────────┘</pre>
<h3>一个 <span class="em">极克制</span> 的写作环境,<br>但每一行都被静静听见。</h3>
<p><span class="pt" data-text="左侧">左侧</span> 是无限层级的笔记本,
<span class="pt" data-text="中间">中间</span> 是纯净的写作区域,
<span class="pt" data-text="右侧">右侧</span> 折叠面板里,正在为你浮现三条相关旧笔记。</p>
<p>没有花里胡哨的图标,没有打扰你的弹窗。所有的智能,<span class="pt" data-text="都安静地待在角落">都安静地待在角落</span>,等你召唤。</p>
</div>
</div>
<div class="showcase" style="margin-top:64px">
<div class="showcase-text">
<pre class="mini-ascii"> ●─●─●─●
│╲│╱│╲│
●─●─●─●
│╱│╲│╱│
●─●─●─●</pre>
<h3>用 <span class="em">图谱视图</span>,<br>看见你思考的<span class="em">形状</span>。</h3>
<p>每一个 <span class="pt" data-text="双向链接">双向链接</span> 都会变成一条引力线。后台持续计算笔记之间的语义距离,让相关的想法在画布上相互靠近。</p>
<p>支持 <span class="pt" data-text="立体力导向布局、自由排布、时间线视图">立体力导向布局、自由排布、时间线视图</span>,鼠标滚轮缩放穿越十万个节点也不卡顿。</p>
</div>
<div class="showcase-shot">
<div class="bar"><i></i><i></i><i></i></div>
<img src="../assets/ai-08-architecture.jpg" alt="图谱视图">
</div>
</div>
</section>
<!-- SPEC -->
<section class="sec" id="spec">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ✦ ═══</pre>技术规格</h2><div class="idx">[ 04 / 05 ]</div></div>
<div class="spec-wrap">
<div class="spec-visual">
<img src="../assets/ai-01-search.jpg" alt="技术架构">
<div class="label">✦ 本地推理 / 跨平台 / 开源核心</div>
</div>
<div class="spec-table">
<div class="spec-row"><b>软件版本</b><span class="pt" data-text="墨思 v3.2.1,构建 26513">墨思 v3.2.1,构建 26513</span></div>
<div class="spec-row"><b>支持平台</b><span class="pt" data-text="桌面三端 / 手机平板 / 网页">桌面三端 / 手机平板 / 网页</span></div>
<div class="spec-row"><b>本机模型</b><span class="pt" data-text="千问 7B / 羊驼 8B 量化">千问 7B / 羊驼 8B 量化</span></div>
<div class="spec-row"><b>云端模型</b><span class="pt" data-text="可选接入主流大模型,或自托管">可选接入主流大模型,或自托管</span></div>
<div class="spec-row"><b>数据格式</b><span class="pt" data-text="纯文本标记 + 本地索引数据库">纯文本标记 + 本地索引数据库</span></div>
<div class="spec-row"><b>同步协议</b><span class="pt" data-text="冲突自由 / 点对点 / 端到端加密">冲突自由 / 点对点 / 端到端加密</span></div>
<div class="spec-row"><b>插件系统</b><span class="pt" data-text="脚本 + 沙盒,已有 280 余款插件">脚本 + 沙盒,已有 280 余款插件</span></div>
<div class="spec-row"><b>导入导出</b><span class="pt" data-text="兼容主流笔记软件迁移">兼容主流笔记软件迁移</span></div>
<div class="spec-row"><b>离线能力</b><span class="pt" data-text="完全可用,同步可选,无需账号">完全可用,同步可选,无需账号</span></div>
<div class="spec-row"><b>开源情况</b><span class="pt" data-text="核心引擎开源,星标超 25k">核心引擎开源,星标超 25k</span></div>
</div>
</div>
</section>
<!-- PRICING -->
<section class="sec" id="price">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ❑ ═══</pre>订阅方案</h2><div class="idx">[ 05 / 05 ]</div></div>
<div class="price-grid">
<div class="price">
<h4>个人版</h4>
<div class="num">¥0<small>/ 永久免费</small></div>
<p class="desc">适合个人写作者、学生、研究者</p>
<ul>
<li>所有本地功能 不限笔记数</li>
<li>本机智能模型</li>
<li>两台设备局域网同步</li>
<li>社区插件 主题</li>
<li>核心代码开源 可自托管</li>
</ul>
<a class="btn pt" data-text="立即下载 ↓">立即下载 ↓</a>
</div>
<div class="price featured">
<h4>★ 专业版</h4>
<div class="num">¥38<small>/ 月,年付 ¥328</small></div>
<p class="desc">为创作者提供云同步与高级智能</p>
<ul>
<li>包含个人版全部功能</li>
<li>无限设备 端到端加密同步</li>
<li>云端大模型接入</li>
<li>语义搜索 自动双链建议</li>
<li>高级图谱视图 主题市场</li>
<li>优先邮件支持</li>
</ul>
<a class="btn pt" data-text="开始 14 天试用 →">开始 14 天试用 →</a>
</div>
<div class="price">
<h4>团队版</h4>
<div class="num">¥98<small>/ 人 / 月</small></div>
<p class="desc">适合 5–50 人的研究 创作团队</p>
<ul>
<li>包含专业版全部功能</li>
<li>共享工作区 多人协作编辑</li>
<li>权限分级 审计日志</li>
<li>统一身份认证</li>
<li>私有部署 内网托管</li>
<li>专属客户成功经理</li>
</ul>
<a class="btn pt" data-text="联系销售 ✉">联系销售 ✉</a>
</div>
</div>
</section>
<!-- INFO / CTA -->
<section class="sec info" id="info">
<div class="info-orbit"><pre id="info-ascii"></pre></div>
<div class="info-big">让你的 <span class="em">想法</span>,<br>开始彼此对话。</div>
<div class="hero-sub">v3.2 已上线 / 桌面、移动、网页全平台</div>
<div class="info-links">
<a class="pt" data-text="下载墨思 ↓">下载墨思 ↓</a>
<a class="pt" data-text="网页版打开 →">网页版打开 →</a>
<a class="pt" data-text="开发者文档 ✦">开发者文档 ✦</a>
</div>
<div class="platforms">
<span>★ 苹果电脑</span><span>★ 视窗系统</span><span>★ 开源系统</span><span>★ 苹果手机</span><span>★ 平板</span><span>★ 安卓</span><span>★ 网页</span>
</div>
</section>
<div class="footer">
<span>© 2026 墨思工作室,版权所有</span>
<span>以 ASCII 与 茶 写就于 凌晨三点</span>
</div>
<script>
/* ============================================================
1. HERO ASCII :旋转球体 + 行星轨道 + 星点场
============================================================ */
(function(){
const elNeb = document.getElementById('ascii-nebula');
const elStar = document.getElementById('ascii-stars');
const elPlanet = document.getElementById('ascii-planet');
const elAst = document.getElementById('ascii-asteroids');
const elComet = document.getElementById('ascii-comets');
if(!elPlanet) return;
let cols, rows, t = 0;
let stars = [];
let asteroids = [];
let comets = [];
let nebulae = [];
// 不规则小行星形状(多种轮廓 + 表面陨石坑)
const ASTEROID_SHAPES = [
[
" .-~~-. ",
" ( o..o )",
" `-~~-' "
],
[
" _.--._ ",
" / .o \\ ",
" \\ o. / ",
" `~--~' "
],
[
" .-=-. ",
" /,o o.\\ ",
" \\ `-' / ",
" `~~-' "
],
[
" _.+~+._ ",
"/ . o \\",
"\\ o . o/",
" `~--~~' "
],
[
" .o0o. ",
" 0o..oO ",
" `OoO' "
],
[
" ,-~^~-. ",
"( :*. * )",
" `-,_,-' "
]
];
function reseed(){
cols = Math.floor(window.innerWidth / 7);
rows = Math.floor(window.innerHeight / 11);
stars = [];
const n = Math.floor(cols*rows*0.05);
for(let i=0;i<n;i++){
stars.push({
x: Math.random()*cols,
y: Math.random()*rows,
z: Math.random()*0.9+0.1,
c: Math.random()<.04?'★':(Math.random()<.15?'✦':(Math.random()<.5?'.':'∙'))
});
}
asteroids = [];
const aN = Math.max(4, Math.floor(Math.min(cols,rows)/14));
for(let i=0;i<aN;i++){
asteroids.push({
x: Math.random()*cols,
y: Math.random()*rows,
vx: (Math.random()-0.5)*0.08,
vy: (Math.random()-0.5)*0.04,
shape: ASTEROID_SHAPES[Math.floor(Math.random()*ASTEROID_SHAPES.length)],
rot: Math.random()*Math.PI*2,
rs: (Math.random()-0.5)*0.01
});
}
comets = [];
for(let i=0;i<2;i++){
comets.push({
x: Math.random()*cols,
y: Math.random()*rows*0.6,
vx: 0.35 + Math.random()*0.25,
vy: 0.12 + Math.random()*0.1,
life: Math.random()*200
});
}
nebulae = [];
for(let i=0;i<3;i++){
nebulae.push({
x: Math.random()*cols,
y: Math.random()*rows,
r: 9 + Math.random()*15,
seed: Math.random()*100
});
}
}
reseed();
window.addEventListener('resize', reseed);
function newBuf(){
const b = new Array(rows);
for(let y=0;y<rows;y++) b[y] = new Array(cols).fill(' ');
return b;
}
function flush(buf){ return buf.map(r=>r.join('')).join('\n'); }
function drawAsteroid(buf, ax, ay, shape){
for(let i=0;i<shape.length;i++){
const line = shape[i];
const yy = Math.floor(ay) + i - Math.floor(shape.length/2);
if(yy<0||yy>=rows) continue;
for(let j=0;j<line.length;j++){
const ch = line[j];
if(ch===' ') continue;
const xx = Math.floor(ax) + j - Math.floor(line.length/2);
if(xx<0||xx>=cols) continue;
buf[yy][xx] = ch;
}
}
}
const chars = " .,:;-~+*=%#@█";
const nebChars = "·∙∶⋮⋯░▒";
function render(){
t += 0.012;
const bufNeb = newBuf();
const bufStar = newBuf();
const bufPlanet = newBuf();
const bufAst = newBuf();
const bufComet = newBuf();
// 星云气体
for(const n of nebulae){
const cx = n.x + Math.sin(t*0.2 + n.seed)*3;
const cy = n.y + Math.cos(t*0.15 + n.seed)*2;
for(let dy=-n.r; dy<=n.r; dy++){
for(let dx=-n.r*1.6; dx<=n.r*1.6; dx++){
const xx = Math.floor(cx+dx);
const yy = Math.floor(cy+dy);
if(xx<0||xx>=cols||yy<0||yy>=rows) continue;
const d = Math.sqrt((dx/1.6)*(dx/1.6) + dy*dy);
if(d > n.r) continue;
const noise = Math.sin(xx*0.3 + n.seed)*Math.cos(yy*0.4 - n.seed*0.7) + Math.sin(t*0.5+n.seed);
const intensity = (1 - d/n.r) * (0.5 + 0.5*noise);
if(intensity > 0.55){
const ci = Math.min(nebChars.length-1, Math.floor(intensity * nebChars.length));
bufNeb[yy][xx] = nebChars[ci];
}
}
}
}
// 闪烁星点
for(const s of stars){
const xi = Math.floor((s.x + Math.sin(t*0.3 + s.y)*0.3 + cols)%cols);
const yi = Math.floor(s.y);
if(yi>=0 && yi<rows){
const tw = (Math.sin(t*2 + s.x*0.7 + s.y*0.3)+1)*0.5;
if(tw>0.55 || s.c==='★' || s.c==='✦') bufStar[yi][xi] = s.c;
}
}
// 旋转行星
const cx = cols/2, cy = rows/2;
const R = Math.min(cols,rows*2)*0.18;
const aspect = 0.5;
for(let phi=0; phi<Math.PI; phi+=0.06){
for(let theta=0; theta<2*Math.PI; theta+=0.04){
const x3 = R*Math.sin(phi)*Math.cos(theta + t);
const y3 = R*Math.cos(phi);
const z3 = R*Math.sin(phi)*Math.sin(theta + t);
const lx = Math.cos(t*0.6), ly = -0.3, lz = Math.sin(t*0.6);
const nx = Math.sin(phi)*Math.cos(theta + t);
const ny = Math.cos(phi);
const nz = Math.sin(phi)*Math.sin(theta + t);
const lum = nx*lx + ny*ly + nz*lz;
if(z3 < 0) continue;
const sx = Math.floor(cx + x3/aspect);
const sy = Math.floor(cy + y3);
if(sx<0||sx>=cols||sy<0||sy>=rows) continue;
const idx = Math.max(0, Math.min(chars.length-1, Math.floor((lum+0.2)*chars.length*0.9)));
bufPlanet[sy][sx] = chars[idx];
}
}
// 行星轨道与卫星
for(let i=0; i<3; i++){
const orbR = R*(1.6 + i*0.5);
const tilt = 0.35 + i*0.1;
for(let a=0; a<2*Math.PI; a+=0.04){
const ox = orbR*Math.cos(a + t*(0.4-i*0.1));
const oy = orbR*Math.sin(a + t*(0.4-i*0.1))*tilt;
const sx = Math.floor(cx + ox/aspect);
const sy = Math.floor(cy + oy);
if(sx<0||sx>=cols||sy<0||sy>=rows) continue;
if(bufPlanet[sy][sx]===' ') bufPlanet[sy][sx] = (i===0?'.':(i===1?'∙':'•'));
}
const a = t*(0.7-i*0.18);
const ox = orbR*Math.cos(a);
const oy = orbR*Math.sin(a)*tilt;
const sx = Math.floor(cx + ox/aspect);
const sy = Math.floor(cy + oy);
if(sx>=0&&sx<cols&&sy>=0&&sy<rows) bufPlanet[sy][sx] = (i===0?'◉':(i===1?'◎':'○'));
}
// 彗星拖尾
for(const c of comets){
c.x += c.vx; c.y += c.vy; c.life++;
if(c.x>cols+10 || c.y>rows+5){
c.x = -10; c.y = Math.random()*rows*0.5;
c.life = 0;
}
const tail = 16;
for(let k=0;k<tail;k++){
const tx = Math.floor(c.x - k*c.vx*1.5);
const ty = Math.floor(c.y - k*c.vy*1.5);
if(tx<0||tx>=cols||ty<0||ty>=rows) continue;
const tch = k===0?'☄':(k<3?'*':(k<7?'∙':(k<12?'·':'.')));
bufComet[ty][tx] = tch;
}
}
// 漂浮的小行星
for(const a of asteroids){
a.x += a.vx; a.y += a.vy; a.rot += a.rs;
if(a.x<-6) a.x = cols+6;
if(a.x>cols+6) a.x = -6;
if(a.y<-4) a.y = rows+4;
if(a.y>rows+4) a.y = -4;
const wob = Math.sin(t*0.8 + a.rot)*0.6;
drawAsteroid(bufAst, a.x + wob, a.y + Math.cos(t*0.6+a.rot)*0.4, a.shape);
}
elNeb.textContent = flush(bufNeb);
elStar.textContent = flush(bufStar);
elPlanet.textContent = flush(bufPlanet);
elAst.textContent = flush(bufAst);
elComet.textContent = flush(bufComet);
requestAnimationFrame(render);
}
render();
})();
/* ============================================================
2. INFO 区小型轨道
============================================================ */
(function(){
const el = document.getElementById('info-ascii');
if(!el) return;
const W=44, H=22;
let t=0;
// 小行星带的随机位置
const belt = [];
for(let i=0;i<14;i++){
belt.push({
a: Math.random()*Math.PI*2,
r: 8.5 + Math.random()*1.6,
sp: 0.5 + Math.random()*0.4,
ch: '✦◆◈*·•'[Math.floor(Math.random()*6)]
});
}
function tick(){
t+=0.04;
const buf = Array.from({length:H},()=>new Array(W).fill(' '));
const cx=W/2, cy=H/2;
// 散落的星点背景
for(let i=0;i<24;i++){
const x = Math.floor((Math.sin(i*7.3)+1)*W/2);
const y = Math.floor((Math.cos(i*5.1)+1)*H/2);
const tw = (Math.sin(t*1.5 + i)+1)*0.5;
if(x>=0&&x<W&&y>=0&&y<H && tw>0.6) buf[y][x]='.';
}
// 行星轨道
for(let i=0;i<4;i++){
const r = 3+i*2.6;
for(let a=0;a<Math.PI*2;a+=0.18){
const x = Math.floor(cx + r*Math.cos(a)*1.0);
const y = Math.floor(cy + r*Math.sin(a)*0.5);
if(x>=0&&x<W&&y>=0&&y<H && buf[y][x]===' ') buf[y][x]='.';
}
const a = t*(1-i*0.18);
const x = Math.floor(cx + r*Math.cos(a));
const y = Math.floor(cy + r*Math.sin(a)*0.5);
if(x>=0&&x<W&&y>=0&&y<H) buf[y][x]='★';
}
// 外侧小行星带
for(const b of belt){
const ang = b.a + t*b.sp*0.3;
const x = Math.floor(cx + b.r*Math.cos(ang));
const y = Math.floor(cy + b.r*Math.sin(ang)*0.5);
if(x>=0&&x<W&&y>=0&&y<H) buf[y][x]=b.ch;
}
// 中心恒星与日冕
if(buf[Math.floor(cy)]) buf[Math.floor(cy)][Math.floor(cx)]='◉';
const halo = [[-1,0,'-'],[1,0,'-'],[0,-1,'|'],[0,1,'|']];
for(const [dx,dy,ch] of halo){
const x = Math.floor(cx)+dx, y = Math.floor(cy)+dy;
if(x>=0&&x<W&&y>=0&&y<H && buf[y][x]===' ') buf[y][x]=ch;
}
el.textContent = buf.map(r=>r.join('')).join('\n');
requestAnimationFrame(tick);
}
tick();
})();
/* ============================================================
3. PROTEXT :文本乱码-解码效果
悬停或进入视口时,文字先打乱再逐字"解密"显现
============================================================ */
(function(){
const glyphs = "★☆✶✦✧◉◎○●∙•~≈≋▁▂▃▄▅▆▇█ABCDEFGHJKLMNPQRSTUVWXYZ0123456789@#%&*";
const RAND = ()=> glyphs[Math.floor(Math.random()*glyphs.length)];
function scramble(el){
if(el.dataset.scrLock==='1') return;
el.dataset.scrLock='1';
el.classList.add('scrambling');
const target = el.dataset.text || el.textContent;
const chars = [...target];
const len = chars.length;
let frame = 0;
const total = 16;
const step = ()=>{
frame++;
const out = chars.map((ch,i)=>{
if(ch===' '||ch==='\n') return ch;
const reveal = Math.floor((frame/total)*len*1.2);
if(i < reveal) return ch;
return RAND();
}).join('');
el.textContent = out;
if(frame < total){
setTimeout(step, 40);
} else {
el.textContent = target;
el.classList.remove('scrambling');
el.dataset.scrLock='0';
}
};
step();
}
document.querySelectorAll('.pt').forEach(el=>{
if(!el.dataset.text) el.dataset.text = el.textContent.trim();
el.addEventListener('mouseenter', ()=>scramble(el));
});
const io = new IntersectionObserver((entries)=>{
entries.forEach(e=>{
if(e.isIntersecting){
scramble(e.target);
io.unobserve(e.target);
}
});
},{threshold:0.3});
document.querySelectorAll('.intro-text .pt, .spec-row .pt, .showcase-text .pt').forEach(el=>io.observe(el));
})();
/* ============================================================
4. 标签筛选
============================================================ */
(function(){
const tags = document.querySelectorAll('.tag');
const cards = document.querySelectorAll('#grid .card');
tags.forEach(tag=>{
tag.addEventListener('click', ()=>{
tags.forEach(t=>t.classList.remove('active'));
tag.classList.add('active');
const cat = tag.dataset.cat;
cards.forEach(c=>{
if(cat==='all' || c.dataset.cat.split(' ').includes(cat)){
c.classList.remove('hidden');
} else {
c.classList.add('hidden');
}
});
});
});
})();
/* ============================================================
5. 平滑滚动
============================================================ */
document.querySelectorAll('a[href^="#"]').forEach(a=>{
a.addEventListener('click', e=>{
const id = a.getAttribute('href');
if(id.length>1){
const tgt = document.querySelector(id);
if(tgt){ e.preventDefault(); tgt.scrollIntoView({behavior:'smooth',block:'start'}); }
}
});
});
/* ============================================================
6. 实时时钟
============================================================ */
(function(){
const el = document.querySelector('.here');
if(!el) return;
function fmt(n){return n<10?'0'+n:n}
function tick(){
const d = new Date();
el.innerHTML = '你好<br><span class="blink">▮</span> 现在是 ' + fmt(d.getHours())+':'+fmt(d.getMinutes())+':'+fmt(d.getSeconds());
setTimeout(tick, 1000);
}
tick();
})();
</script>
</body>
</html>
+188
View File
@@ -0,0 +1,188 @@
# 风格预设库
每个预设给 8 个 `:root` CSS 变量 + 5 个 ASCII 层颜色 + hero 背景渐变。改其他地方需要相应连带改(图片滤镜、scrambler 字符颜色等)。
> **配色原则**:5 个 ASCII 层在背景上必须**同时可读 + 互相和谐**。同一冷暖、轻微变奏,不要把"对比 = 好看"。
---
## 1. 深空青(默认 · cosmic-cyan)
最克制的太空感,宝石蓝紫为底,青/粉/琥珀的发光层。适合本地优先 AI、笔记/写作、独立软件、CLI 工具。
```css
:root{
--bg:#05060f;
--fg:#e6e8ff;
--dim:#7a7fa8;
--accent:#8be9ff; /* 青 */
--accent2:#ff6ad5; /* 粉 */
--accent3:#c792ea; /* 紫 */
--line:#1a1d3a;
--hl:#0d1024;
--paper:#0a0c1f;
}
.hero{ background: radial-gradient(ellipse at 30% 20%, #1a0a2e 0%, #05060f 55%, #000 100%); }
#ascii-nebula { color:#a87bd6; } /* 星云 */
#ascii-stars { color:#cfe6ff; } /* 星点 */
#ascii-planet { color:#8be9ff; } /* 行星 */
#ascii-asteroids{ color:#f5b971; } /* 小行星 */
#ascii-comets { color:#ff8fd2; } /* 彗星 */
```
图片滤镜:`hue-rotate(200deg) saturate(.9) contrast(1.1) brightness(.6)`
---
## 2. 暖色羊皮纸(letterpress-warm · 原墨思配色)
宋体凸印 + 米色背景,复古旧报纸感。适合中文写作工具、独立出版、设计工作室、小众杂志。
```css
:root{
--bg:#F3E4D4;
--fg:#1f1510;
--dim:#8a7560;
--accent:#660125; /* 酒红 */
--accent2:#a83344;
--accent3:#7a4a1f;
--line:#d8c4af;
--hl:#ecd8ca;
--paper:#ecd8ca;
}
.hero{ background: linear-gradient(180deg, #F3E4D4 0%, #e9d2b8 100%); }
#ascii-nebula { color:#b88a6a; opacity:.5; mix-blend-mode:multiply; }
#ascii-stars { color:#5a3a1e; opacity:.7; }
#ascii-planet { color:#660125; }
#ascii-asteroids{ color:#7a4a1f; }
#ascii-comets { color:#a83344; }
```
图片滤镜:`sepia(.3) contrast(1.05) saturate(.85) brightness(.95)`
注:暖色背景下要把 `mix-blend-mode: screen` 改成 `multiply`,让 ASCII 是"压在纸上"而不是"发光"。
---
## 3. 赛博紫(cyber-violet)
霓虹紫 + 电子粉,赛博朋克招牌色。适合 AI / Web3 / 游戏外设 / 电竞订阅。
```css
:root{
--bg:#0a0118;
--fg:#f0e6ff;
--dim:#7e5cab;
--accent:#ff00aa; /* 霓虹品红 */
--accent2:#00ffe0; /* 青电流 */
--accent3:#b14aff; /* 紫 */
--line:#2a0d4a;
--hl:#150327;
--paper:#10021f;
}
.hero{ background: radial-gradient(ellipse at 70% 30%, #4a0e6e 0%, #0a0118 60%, #000 100%); }
#ascii-nebula { color:#b14aff; opacity:.55; }
#ascii-stars { color:#e0d4ff; }
#ascii-planet { color:#00ffe0; }
#ascii-asteroids{ color:#ff00aa; }
#ascii-comets { color:#ffd400; } /* 黄电流彗星 */
```
图片滤镜:`hue-rotate(280deg) saturate(1.4) contrast(1.1) brightness(.65)`
---
## 4. 终端绿(terminal-green)
黑底 phosphor 绿,复古 CRT 终端味。适合 CLI 工具、开发者订阅、安全产品、复古游戏。
```css
:root{
--bg:#000800;
--fg:#a8ffb0;
--dim:#5fa05f;
--accent:#00ff66; /* phosphor 绿 */
--accent2:#80ff80;
--accent3:#ffaa44;
--line:#0a3010;
--hl:#001500;
--paper:#001a05;
}
.hero{ background: radial-gradient(ellipse at center, #052010 0%, #000800 70%, #000 100%); }
#ascii-nebula { color:#2a8a44; opacity:.45; }
#ascii-stars { color:#a8ffb0; }
#ascii-planet { color:#00ff66; }
#ascii-asteroids{ color:#ffaa44; }
#ascii-comets { color:#80ffd4; }
```
图片滤镜:`grayscale(1) brightness(.6) sepia(1) hue-rotate(70deg) saturate(2)`
另外可在 `.hero::after` 加扫描线:`background: repeating-linear-gradient(0deg, transparent 0, transparent 2px, rgba(0,255,102,.05) 3px, rgba(0,255,102,.05) 4px);`
---
## 5. 极光(aurora · 默认风格的冷淡变奏)
灰青冰蓝主色,加一抹粉绿,像北极极光。适合冥想 / 健康 / 旅行 / 北欧风产品。
```css
:root{
--bg:#0c1420;
--fg:#dceaf0;
--dim:#6b8499;
--accent:#7fe3c4; /* 极光绿 */
--accent2:#b8a4ff; /* 淡紫 */
--accent3:#ffb3c1; /* 极光粉 */
--line:#1a2638;
--hl:#101a28;
--paper:#0e1622;
}
.hero{ background: linear-gradient(160deg, #1a3548 0%, #0c1420 50%, #050810 100%); }
#ascii-nebula { color:#b8a4ff; opacity:.5; }
#ascii-stars { color:#dceaf0; }
#ascii-planet { color:#7fe3c4; }
#ascii-asteroids{ color:#ffb3c1; }
#ascii-comets { color:#88ddff; }
```
图片滤镜:`hue-rotate(180deg) saturate(.7) contrast(1.05) brightness(.7)`
---
## 6. 沙漠日落(desert-sunset · 暖色 ASCII 系)
橙红土 + 焦糖 + 杏色,沙丘黄昏感。适合咖啡 / 独立硬件 / 工艺品 / 食品订阅。
```css
:root{
--bg:#1a0a05;
--fg:#f5e6d0;
--dim:#a8765a;
--accent:#ff8b3d; /* 烤橙 */
--accent2:#d94545; /* 焦红 */
--accent3:#ffd47a; /* 杏黄 */
--line:#3a1a10;
--hl:#1f0d06;
--paper:#15080a;
}
.hero{ background: radial-gradient(ellipse at 50% 80%, #5a1f10 0%, #1a0a05 60%, #000 100%); }
#ascii-nebula { color:#a8765a; opacity:.5; }
#ascii-stars { color:#ffd47a; }
#ascii-planet { color:#ff8b3d; }
#ascii-asteroids{ color:#d94545; }
#ascii-comets { color:#ffe9c2; }
```
图片滤镜:`sepia(.5) saturate(1.3) contrast(1.1) brightness(.7) hue-rotate(-10deg)`
---
## 怎么自由设计
如果用户给的风格描述不在以上 6 种里,按这个流程:
1. **定主色调温度**:冷(蓝/紫/青)/ 暖(橙/红/黄)/ 中性(绿/灰)
2. **`--bg` 选最深值**(如 `#05060f`),`--fg` 选最浅值(如 `#e6e8ff`)
3. **`--accent` / `--accent2` / `--accent3`** 三色:拉开**色相**而非饱和度,它们在小区域出现,要"互相点亮"
4. **5 个 ASCII 层颜色** 排序原则:
- `nebula` → 半透紫/灰,做底
- `stars` → 高明度白/暖白,最亮
- `planet` → 主 `--accent`
- `asteroids` → 暖色对比(即使主调冷也加暖一抹)
- `comets` → 第二高饱和,做"流过去"的视觉锚
5. **图片滤镜** 必须连带调,否则 unsplash 图会"颜色不进环境"
> 测试方法:把 5 个层颜色填进调色板生成器(Coolors / Realtime Colors),确认它们之间最低对比度 ≥ 2.0,最高色相差 ≥ 90°。
+967
View File
@@ -0,0 +1,967 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><!-- SWAP-1 -->{{PRODUCT_CN}} — {{TAGLINE_SHORT}}</title>
<style>
:root{
--bg:#05060f;
--fg:#e6e8ff;
--dim:#7a7fa8;
--accent:#8be9ff;
--accent2:#ff6ad5;
--accent3:#c792ea;
--line:#1a1d3a;
--hl:#0d1024;
--paper:#0a0c1f;
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{background:var(--bg);color:var(--fg);font-family:"Songti SC","STSong","Source Han Serif SC","Noto Serif CJK SC","SimSun",serif;font-size:14px;line-height:1.8;-webkit-font-smoothing:antialiased}
/* 中文段落均使用宋体 */
:lang(zh),.intro-text,.showcase-text p,.showcase-text h3,.info-big,.hero-title,.card-title,.price .desc,.cell{font-family:"Songti SC","STSong","Source Han Serif SC","Noto Serif CJK SC","SimSun",serif}
/* 数字 / 拉丁字母仍保持等宽,但中文遇到等宽会 fallback 回宋体 */
.header-logo,.nav a,.hero-meta,.hero-sub,.sec-head,.scroll-cue,.tag,.card-num,.card-tags,.spec-row b,.price h4,.price .num,.platforms,.footer,.here{font-family:"SF Mono","Menlo","Consolas","Songti SC","STSong","SimSun",serif}
a{color:inherit;text-decoration:none;cursor:pointer}
::selection{background:var(--accent);color:var(--bg)}
/* HEADER */
.header{position:fixed;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:18px 28px;z-index:100;color:var(--fg);background:rgba(5,6,15,.85);backdrop-filter:blur(8px);border-bottom:1px solid var(--line)}
.header-logo{font-weight:600;letter-spacing:0.18em;font-size:13px}
.header-logo span{color:var(--accent)}
.nav ul{display:flex;gap:26px;list-style:none}
.nav a{font-size:12px;letter-spacing:0.2em;position:relative;padding:4px 0;transition:color .3s}
.nav a:hover{color:var(--accent)}
.nav a::after{content:"";position:absolute;left:0;bottom:0;height:1px;width:0;background:var(--accent);transition:width .3s}
.nav a:hover::after{width:100%}
/* HEY */
.here{position:fixed;right:32px;bottom:28px;font-size:12px;letter-spacing:0.2em;z-index:90;text-align:right;color:var(--dim)}
.here .blink{display:inline-block;animation:blink 1.2s infinite}
@keyframes blink{0%,49%{opacity:1}50%,100%{opacity:0}}
/* HERO ASCII */
.hero{height:100vh;width:100%;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;border-bottom:1px solid var(--line);background:radial-gradient(ellipse at 30% 20%, #1a0a2e 0%, #05060f 55%, #000 100%)}
.ascii-layer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:12px;letter-spacing:0;white-space:pre;font-family:"SF Mono",monospace;pointer-events:none;font-weight:600;margin:0}
#ascii-nebula{color:#a87bd6;opacity:.55;text-shadow:0 0 8px rgba(168,123,214,.45);mix-blend-mode:screen}
#ascii-stars{color:#cfe6ff;opacity:.85;text-shadow:0 0 4px rgba(207,230,255,.5)}
#ascii-planet{color:var(--accent);opacity:.95;text-shadow:0 0 6px rgba(139,233,255,.45)}
#ascii-asteroids{color:#f5b971;opacity:.92;text-shadow:0 0 6px rgba(245,185,113,.45)}
#ascii-comets{color:#ff8fd2;opacity:.95;text-shadow:0 0 8px rgba(255,143,210,.55)}
.hero-overlay{position:relative;z-index:2;text-align:center;padding:0 24px;color:var(--fg)}
.hero-title{font-size:clamp(40px,7vw,98px);font-weight:300;letter-spacing:0.04em;line-height:1;margin-bottom:18px}
.hero-title .em{font-style:italic;color:var(--accent)}
.hero-sub{font-size:14px;letter-spacing:0.3em;color:var(--dim);margin-bottom:36px}
/* ASCII 大标题 */
.ascii-title{font-family:"SF Mono","Menlo","Consolas",monospace;color:var(--accent);white-space:pre;display:inline-block;text-align:left;line-height:1;font-weight:700;margin:0 auto;letter-spacing:0;text-shadow:0 0 12px rgba(139,233,255,.5),0 0 2px rgba(199,146,234,.4)}
.ascii-title.lg{font-size:clamp(8px,1.25vw,14px);line-height:1.05}
.ascii-title.md{font-size:clamp(6px,.85vw,10px);color:var(--fg)}
.ascii-title.sm{font-size:clamp(5px,.55vw,7px);color:var(--accent);opacity:.85}
.brand-wrap-outer{text-align:center}
.brand-cn-sub{font-family:"Songti SC","STSong","SimSun",serif;font-size:clamp(28px,3.6vw,46px);font-weight:400;letter-spacing:0.5em;color:var(--fg);margin:24px 0 0;padding-left:0.5em}
.ascii-cn{display:none}
.sec-ascii{display:block;color:var(--accent);white-space:pre;font-family:"SF Mono",monospace;font-size:14px;line-height:1;margin-bottom:10px;opacity:.85;letter-spacing:0.4em}
/* 中文真字宋体大标题 + ASCII 网点背景 */
.brand-block{text-align:center;line-height:1;display:inline-block;padding:36px 64px;position:relative}
.brand-block::before{
content:"";position:absolute;inset:0;
background-image:radial-gradient(circle, rgba(139,233,255,.22) 1px, transparent 1.4px);
background-size:7px 7px;
pointer-events:none;
opacity:.85;
z-index:0;
/* 中间淡四角浓的渐隐遮罩,像深空尘埃 */
-webkit-mask-image:radial-gradient(ellipse at center, transparent 30%, #000 90%);
mask-image:radial-gradient(ellipse at center, transparent 30%, #000 90%);
}
.brand-deco-top,.brand-deco-bot{font-family:"SF Mono","Menlo",monospace;color:var(--accent);font-size:clamp(12px,1.4vw,18px);letter-spacing:0.6em;opacity:.85;margin:0 0 18px;position:relative;z-index:1}
.brand-deco-bot{margin:18px 0 0}
.brand-title-cn{
font-family:"Songti SC","STSong","Source Han Serif SC","SimSun",serif;
font-weight:700;
font-size:clamp(140px,20vw,240px);
line-height:1;
letter-spacing:0.32em;
display:inline-block;
position:relative;
z-index:1;
/* 字内填充:星云网点,模拟字符画/星尘 */
background-image:
radial-gradient(circle, var(--accent) 0.95px, transparent 1.3px),
radial-gradient(circle, var(--accent2) 0.45px, transparent 0.85px);
background-size: 5px 5px, 3px 3px;
background-position: 0 0, 1.5px 1.5px;
-webkit-background-clip:text;
background-clip:text;
color:transparent;
-webkit-text-fill-color:transparent;
/* 极细描边 */
-webkit-text-stroke:0.4px rgba(139,233,255,.55);
text-shadow:0 0 8px rgba(139,233,255,.3);
}
.brand-title-cn .gap{display:inline-block;width:.2em}
.brand-wrap-outer{text-align:center}
.sec-head h2{font-size:18px;font-weight:400;letter-spacing:0.4em;color:var(--fg);display:flex;flex-direction:column;gap:6px;font-family:"Songti SC","STSong","SimSun",serif}
.hero-meta{display:flex;gap:32px;justify-content:center;font-size:11px;letter-spacing:0.16em;color:var(--dim);flex-wrap:wrap}
.hero-meta span b{color:var(--fg);font-weight:400}
.scroll-cue{position:absolute;left:50%;bottom:32px;transform:translateX(-50%);font-size:11px;letter-spacing:0.3em;color:var(--dim);z-index:3;animation:bob 2.4s ease-in-out infinite}
@keyframes bob{0%,100%{transform:translate(-50%,0)}50%{transform:translate(-50%,8px)}}
/* SECTION */
.sec{padding:96px 48px;border-bottom:1px solid var(--line);position:relative}
.sec-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:48px;font-size:11px;letter-spacing:0.24em;color:var(--dim);border-bottom:1px solid var(--line);padding-bottom:18px}
.sec-head .idx{font-family:monospace}
/* INTRO */
.intro{max-width:980px;margin:0 auto}
.intro-text{font-size:clamp(20px,2.4vw,30px);line-height:1.55;font-weight:300;letter-spacing:0.02em}
.intro-text .pt{display:inline;cursor:default;transition:color .2s}
.intro-text .pt:hover{color:var(--accent)}
.intro-text .pt.scrambling{color:var(--accent)}
.intro-text .em{color:var(--accent);font-style:italic}
.intro-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:32px;margin-top:64px;padding-top:48px;border-top:1px dashed var(--line)}
.intro-grid .cell{font-size:12px;color:var(--dim);line-height:1.7}
.intro-grid .cell b{display:block;color:var(--fg);font-weight:400;margin-bottom:8px;letter-spacing:0.16em;font-size:11px}
/* TAGS */
.filter{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:36px}
.tag{padding:6px 14px;border:1px solid var(--line);font-size:11px;letter-spacing:0.16em;color:var(--dim);transition:all .25s;cursor:pointer;user-select:none}
.tag:hover{color:var(--fg);border-color:var(--dim)}
.tag.active{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.tag .dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:currentColor;margin-right:8px;vertical-align:middle;opacity:.6}
/* GRID */
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.card{border:1px solid var(--line);background:var(--paper);transition:all .3s;position:relative;overflow:hidden;display:flex;flex-direction:column}
.card:hover{border-color:var(--accent);transform:translateY(-4px);box-shadow:0 12px 32px -16px rgba(139,233,255,.5)}
.card-img{aspect-ratio:4/3;background:#0d1024;position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center}
.card-img img{width:100%;height:100%;object-fit:cover;transition:transform .8s ease,filter .4s;filter:hue-rotate(200deg) saturate(.9) contrast(1.1) brightness(.6)}
.card:hover .card-img img{transform:scale(1.06);filter:hue-rotate(220deg) saturate(1.1) contrast(1.05) brightness(.75)}
.card-img .ascii-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:9px;line-height:9px;color:var(--accent);white-space:pre;opacity:.75;pointer-events:none;mix-blend-mode:screen;text-shadow:0 0 4px rgba(139,233,255,.6)}
.card-meta{padding:18px 20px;display:flex;justify-content:space-between;align-items:flex-start;gap:14px}
.card-title{font-size:14px;letter-spacing:0.04em;font-weight:400}
.card-title .pt{cursor:default}
.card-title small{display:block;color:var(--dim);font-size:11px;margin-top:6px;letter-spacing:0.12em}
.card-num{color:var(--dim);font-size:10px;font-family:monospace;letter-spacing:0.1em}
.card-tags{display:flex;gap:6px;padding:0 20px 18px;flex-wrap:wrap}
.card-tags span{font-size:10px;letter-spacing:0.16em;color:var(--dim);padding:2px 8px;border:1px solid var(--line);text-transform:uppercase}
.card.hidden{display:none}
/* SHOWCASE / UI 截图 */
.showcase{display:grid;grid-template-columns:1.2fr 1fr;gap:48px;align-items:center;margin-top:24px}
.showcase-shot{border:1px solid var(--line);background:var(--paper);padding:14px;position:relative;box-shadow:0 8px 28px -16px rgba(139,233,255,.3)}
.showcase-shot .bar{display:flex;gap:6px;margin-bottom:10px}
.showcase-shot .bar i{display:block;width:10px;height:10px;border-radius:50%;background:#3a3f6a}
.showcase-shot img{width:100%;height:auto;display:block;filter:hue-rotate(200deg) saturate(.85) contrast(1.05) brightness(.7);border:1px solid var(--line)}
.showcase-text h3{font-size:clamp(24px,3vw,36px);font-weight:300;line-height:1.3;margin-bottom:18px;letter-spacing:0.02em;color:var(--fg)}
.showcase-text h3 .em{color:var(--accent);font-style:italic}
.showcase-text .mini-ascii{font-family:"SF Mono",monospace;color:var(--accent);font-size:10px;line-height:1;white-space:pre;display:block;margin-bottom:18px;opacity:.85}
.showcase-text p{color:var(--dim);font-size:13px;line-height:1.8;margin-bottom:14px}
.showcase-text p .pt:hover{color:var(--accent)}
/* SPEC */
.spec-wrap{display:grid;grid-template-columns:1.3fr 1fr;gap:64px;align-items:start}
.spec-visual{position:relative;aspect-ratio:4/5;background:var(--paper);border:1px solid var(--line);overflow:hidden}
.spec-visual img{width:100%;height:100%;object-fit:cover;filter:hue-rotate(220deg) saturate(.9) contrast(1.05) brightness(.7)}
.spec-visual::before{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(5,6,15,0) 50%,rgba(5,6,15,.85) 100%);z-index:2}
.spec-visual .label{position:absolute;left:20px;bottom:18px;z-index:3;font-size:11px;letter-spacing:0.2em;color:var(--accent)}
.spec-table{width:100%}
.spec-row{display:grid;grid-template-columns:140px 1fr;padding:14px 0;border-bottom:1px dashed var(--line);font-size:12px;align-items:baseline}
.spec-row b{color:var(--dim);font-weight:400;letter-spacing:0.16em;font-size:11px}
.spec-row span{color:var(--fg)}
.spec-row .pt{cursor:default}
/* PRICING */
.price-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
.price{border:1px solid var(--line);padding:32px 26px;background:var(--paper);transition:.3s;display:flex;flex-direction:column}
.price:hover{border-color:var(--accent);box-shadow:0 12px 32px -16px rgba(139,233,255,.4)}
.price.featured{border-color:var(--accent);background:#0f1233;border-width:1px;box-shadow:0 0 0 1px var(--accent) inset, 0 0 32px -8px rgba(139,233,255,.3)}
.price h4{font-size:13px;letter-spacing:0.2em;color:var(--dim);margin-bottom:18px;font-weight:400}
.price .num{font-size:42px;font-weight:300;letter-spacing:0.02em;margin-bottom:6px;color:var(--fg)}
.price .num small{font-size:13px;color:var(--dim);letter-spacing:0.1em;margin-left:6px}
.price .desc{color:var(--dim);font-size:12px;margin-bottom:24px;line-height:1.6}
.price ul{list-style:none;font-size:12px;color:var(--fg);line-height:2;margin-bottom:28px;flex:1}
.price ul li::before{content:"+ ";color:var(--accent);margin-right:4px}
.price .btn{display:block;text-align:center;padding:12px;border:1px solid var(--fg);font-size:11px;letter-spacing:0.2em;transition:.25s;color:var(--fg)}
.price .btn:hover{background:var(--fg);color:var(--bg);border-color:var(--fg)}
.price.featured .btn{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.price.featured .btn:hover{background:var(--accent2);border-color:var(--accent2);color:var(--bg)}
/* INFO */
.info{text-align:center;padding:120px 24px}
.info-big{font-size:clamp(36px,6vw,76px);font-weight:300;line-height:1.1;letter-spacing:0.02em;margin-bottom:36px}
.info-big .em{font-style:italic;color:var(--accent)}
.info-links{display:flex;gap:28px;justify-content:center;flex-wrap:wrap;margin-top:28px;font-size:12px;letter-spacing:0.2em}
.info-links a{padding:14px 28px;border:1px solid var(--fg);transition:all .25s;color:var(--fg)}
.info-links a:hover{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.info-orbit{margin:0 auto 48px;width:280px;height:280px;position:relative;color:var(--dim);font-size:9px;line-height:9px;white-space:pre;font-family:monospace;display:flex;align-items:center;justify-content:center}
#info-ascii{color:var(--accent);opacity:1;font-weight:700}
/* PLATFORMS */
.platforms{display:flex;flex-wrap:wrap;gap:14px;justify-content:center;margin-top:24px;font-size:11px;letter-spacing:0.18em;color:var(--dim)}
.platforms span{padding:6px 14px;border:1px solid var(--line)}
/* FOOTER */
.footer{padding:24px 32px;display:flex;justify-content:space-between;font-size:10px;letter-spacing:0.18em;color:var(--dim);border-top:1px solid var(--line);flex-wrap:wrap;gap:12px;background:var(--paper)}
/* HERO 后景柔光遮罩,仅在中心给标题留出可读区,四周让 ASCII 充分显现 */
.hero::after{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:min(82vw,1100px);height:min(58vh,520px);background:radial-gradient(ellipse at center,rgba(5,6,15,.85) 0%,rgba(5,6,15,.6) 40%,rgba(5,6,15,0) 80%);pointer-events:none;z-index:1;border-radius:50%}
@media(max-width:900px){
.grid{grid-template-columns:1fr 1fr}
.intro-grid{grid-template-columns:1fr}
.spec-wrap,.showcase{grid-template-columns:1fr;gap:32px}
.price-grid{grid-template-columns:1fr}
.sec{padding:64px 24px}
.header{padding:14px 18px}
}
@media(max-width:560px){
.grid{grid-template-columns:1fr}
#ascii-stage{font-size:7px;line-height:7px}
}
</style>
</head>
<body>
<!-- HEADER · SWAP-2: logo + 5 项导航 -->
<div class="header">
<div class="header-logo"><a href="#top">{{LOGO_TEXT}}</a></div>
<nav class="nav">
<ul>
<li><a href="#features">{{NAV_1}}</a></li>
<li><a href="#showcase">{{NAV_2}}</a></li>
<li><a href="#spec">{{NAV_3}}</a></li>
<li><a href="#price">{{NAV_4}}</a></li>
<li><a href="#info">{{NAV_5}}</a></li>
</ul>
</nav>
</div>
<!-- HEY · 角落问候,可改成产品 slogan -->
<div class="here">{{GREETING}}<br><span class="blink">▮</span> 现在是 凌晨 03:14</div>
<!-- HERO -->
<section class="hero" id="top">
<pre id="ascii-nebula" class="ascii-layer"></pre>
<pre id="ascii-stars" class="ascii-layer"></pre>
<pre id="ascii-planet" class="ascii-layer"></pre>
<pre id="ascii-asteroids" class="ascii-layer"></pre>
<pre id="ascii-comets" class="ascii-layer"></pre>
<div class="hero-overlay">
<div class="brand-wrap-outer">
<!-- SWAP-3: 产品英文名的 5-6 行 ANSI Shadow ASCII 字模。
字模生成参考 patorjk.com/software/taag (字体 ANSI Shadow)。
最长支持 ~10 字母;超长名缩写或换更窄的字体。 -->
<pre class="ascii-title lg">
{{ASCII_TITLE_LINE_1}}
{{ASCII_TITLE_LINE_2}}
{{ASCII_TITLE_LINE_3}}
{{ASCII_TITLE_LINE_4}}
{{ASCII_TITLE_LINE_5}}
{{ASCII_TITLE_LINE_6}}
</pre>
<!-- SWAP-4a: 中文 1-2 字大字(会渲染为彩色网点字) -->
<div class="brand-cn-sub">{{PRODUCT_CN_BIG}}</div>
</div>
<!-- SWAP-4b: hero 副标语,30-50 字,每字之间空格让节奏更慢 -->
<div class="hero-sub" style="margin-top:28px">{{HERO_TAGLINE}}</div>
<!-- SWAP-4c: 3 个 meta — 版本 / 类型 / 平台 -->
<div class="hero-meta">
<span>{{META_LABEL_1}} <b>{{META_VALUE_1}}</b></span>
<span>{{META_LABEL_2}} <b>{{META_VALUE_2}}</b></span>
<span>{{META_LABEL_3}} <b>{{META_VALUE_3}}</b></span>
</div>
</div>
<div class="scroll-cue">↓ 向 下 滚 动 探 索 ↓</div>
</section>
<!-- INTRO · SWAP-5: 关于段落 + 3 个亮点格 -->
<section class="sec">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ❖ ═══</pre>关于</h2><div class="idx">[ 01 / 05 ]</div></div>
<div class="intro">
<p class="intro-text">
{{ABOUT_PARAGRAPH_HTML}}
<!-- 段落里要撒 3-5 个 <span class="pt" data-text="原文">原文</span>
做 hover 字符乱码动效,2-3 个 <span class="em">关键词</span> 做色字强调 -->
</p>
<div class="intro-grid">
<div class="cell"><b>✦ {{HIGHLIGHT_1_TITLE}}</b>{{HIGHLIGHT_1_DESC}}</div>
<div class="cell"><b>✦ {{HIGHLIGHT_2_TITLE}}</b>{{HIGHLIGHT_2_DESC}}</div>
<div class="cell"><b>✦ {{HIGHLIGHT_3_TITLE}}</b>{{HIGHLIGHT_3_DESC}}</div>
</div>
</div>
</section>
<!-- FEATURES GRID · SWAP-6: 6 张功能卡片 + SWAP-7: 4-5 个分类筛选 -->
<section class="sec" id="features">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ❍ ═══</pre>{{FEATURES_TITLE}}</h2><div class="idx">[ 02 / 05 ]</div></div>
<div class="filter">
<a class="tag active" data-cat="all"><span class="dot"></span>全部</a>
<a class="tag" data-cat="{{CAT_1_KEY}}"><span class="dot"></span>{{CAT_1_LABEL}}</a>
<a class="tag" data-cat="{{CAT_2_KEY}}"><span class="dot"></span>{{CAT_2_LABEL}}</a>
<a class="tag" data-cat="{{CAT_3_KEY}}"><span class="dot"></span>{{CAT_3_LABEL}}</a>
<a class="tag" data-cat="{{CAT_4_KEY}}"><span class="dot"></span>{{CAT_4_LABEL}}</a>
</div>
<div class="grid" id="grid">
<!-- 卡片模板:data-cat 必须用上面 4 个 key 之一(可空格分隔多个) -->
<div class="card" data-cat="{{CARD_1_CATS}}">
<div class="card-img">
<img src="{{CARD_1_IMG_URL}}" alt="{{CARD_1_TITLE}}">
<pre class="ascii-overlay">{{CARD_1_ASCII}}</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="{{CARD_1_TITLE}}">{{CARD_1_TITLE}}</span><small>{{CARD_1_SUB}}</small></div>
<div class="card-num">/01</div>
</div>
<div class="card-tags"><span>{{CARD_1_TAG_1}}</span><span>{{CARD_1_TAG_2}}</span></div>
</div>
<div class="card" data-cat="{{CARD_2_CATS}}">
<div class="card-img">
<img src="{{CARD_2_IMG_URL}}" alt="{{CARD_2_TITLE}}">
<pre class="ascii-overlay">{{CARD_2_ASCII}}</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="{{CARD_2_TITLE}}">{{CARD_2_TITLE}}</span><small>{{CARD_2_SUB}}</small></div>
<div class="card-num">/02</div>
</div>
<div class="card-tags"><span>{{CARD_2_TAG_1}}</span><span>{{CARD_2_TAG_2}}</span></div>
</div>
<div class="card" data-cat="{{CARD_3_CATS}}">
<div class="card-img">
<img src="{{CARD_3_IMG_URL}}" alt="{{CARD_3_TITLE}}">
<pre class="ascii-overlay">{{CARD_3_ASCII}}</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="{{CARD_3_TITLE}}">{{CARD_3_TITLE}}</span><small>{{CARD_3_SUB}}</small></div>
<div class="card-num">/03</div>
</div>
<div class="card-tags"><span>{{CARD_3_TAG_1}}</span><span>{{CARD_3_TAG_2}}</span></div>
</div>
<div class="card" data-cat="{{CARD_4_CATS}}">
<div class="card-img">
<img src="{{CARD_4_IMG_URL}}" alt="{{CARD_4_TITLE}}">
<pre class="ascii-overlay">{{CARD_4_ASCII}}</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="{{CARD_4_TITLE}}">{{CARD_4_TITLE}}</span><small>{{CARD_4_SUB}}</small></div>
<div class="card-num">/04</div>
</div>
<div class="card-tags"><span>{{CARD_4_TAG_1}}</span></div>
</div>
<div class="card" data-cat="{{CARD_5_CATS}}">
<div class="card-img">
<img src="{{CARD_5_IMG_URL}}" alt="{{CARD_5_TITLE}}">
<pre class="ascii-overlay">{{CARD_5_ASCII}}</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="{{CARD_5_TITLE}}">{{CARD_5_TITLE}}</span><small>{{CARD_5_SUB}}</small></div>
<div class="card-num">/05</div>
</div>
<div class="card-tags"><span>{{CARD_5_TAG_1}}</span><span>{{CARD_5_TAG_2}}</span></div>
</div>
<div class="card" data-cat="{{CARD_6_CATS}}">
<div class="card-img">
<img src="{{CARD_6_IMG_URL}}" alt="{{CARD_6_TITLE}}">
<pre class="ascii-overlay">{{CARD_6_ASCII}}</pre>
</div>
<div class="card-meta">
<div class="card-title"><span class="pt" data-text="{{CARD_6_TITLE}}">{{CARD_6_TITLE}}</span><small>{{CARD_6_SUB}}</small></div>
<div class="card-num">/06</div>
</div>
<div class="card-tags"><span>{{CARD_6_TAG_1}}</span><span>{{CARD_6_TAG_2}}</span></div>
</div>
</div>
</section>
<!-- SHOWCASE · SWAP-8: 2 块界面截图 + 文案 -->
<section class="sec" id="showcase">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ◈ ═══</pre>{{SHOWCASE_TITLE}}</h2><div class="idx">[ 03 / 05 ]</div></div>
<div class="showcase">
<div class="showcase-shot">
<div class="bar"><i></i><i></i><i></i></div>
<img src="{{SHOWCASE_1_IMG}}" alt="{{SHOWCASE_1_ALT}}">
</div>
<div class="showcase-text">
<pre class="mini-ascii">{{SHOWCASE_1_MINI_ASCII}}</pre>
<h3>{{SHOWCASE_1_HEADLINE_HTML}}</h3>
<!-- headline 可用 <span class="em">关键词</span> 强调 -->
<p>{{SHOWCASE_1_PARA_1_HTML}}</p>
<p>{{SHOWCASE_1_PARA_2_HTML}}</p>
<!-- 段落里撒 1-2 个 <span class="pt" data-text="...">...</span> -->
</div>
</div>
<div class="showcase" style="margin-top:64px">
<div class="showcase-text">
<pre class="mini-ascii">{{SHOWCASE_2_MINI_ASCII}}</pre>
<h3>{{SHOWCASE_2_HEADLINE_HTML}}</h3>
<p>{{SHOWCASE_2_PARA_1_HTML}}</p>
<p>{{SHOWCASE_2_PARA_2_HTML}}</p>
</div>
<div class="showcase-shot">
<div class="bar"><i></i><i></i><i></i></div>
<img src="{{SHOWCASE_2_IMG}}" alt="{{SHOWCASE_2_ALT}}">
</div>
</div>
</section>
<!-- SPEC · SWAP-9: 大图 + 10 行规格表 -->
<section class="sec" id="spec">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ✦ ═══</pre>{{SPEC_TITLE}}</h2><div class="idx">[ 04 / 05 ]</div></div>
<div class="spec-wrap">
<div class="spec-visual">
<img src="{{SPEC_IMG}}" alt="{{SPEC_ALT}}">
<div class="label">✦ {{SPEC_LABEL}}</div>
</div>
<div class="spec-table">
<div class="spec-row"><b>{{SPEC_1_KEY}}</b><span class="pt" data-text="{{SPEC_1_VAL}}">{{SPEC_1_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_2_KEY}}</b><span class="pt" data-text="{{SPEC_2_VAL}}">{{SPEC_2_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_3_KEY}}</b><span class="pt" data-text="{{SPEC_3_VAL}}">{{SPEC_3_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_4_KEY}}</b><span class="pt" data-text="{{SPEC_4_VAL}}">{{SPEC_4_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_5_KEY}}</b><span class="pt" data-text="{{SPEC_5_VAL}}">{{SPEC_5_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_6_KEY}}</b><span class="pt" data-text="{{SPEC_6_VAL}}">{{SPEC_6_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_7_KEY}}</b><span class="pt" data-text="{{SPEC_7_VAL}}">{{SPEC_7_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_8_KEY}}</b><span class="pt" data-text="{{SPEC_8_VAL}}">{{SPEC_8_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_9_KEY}}</b><span class="pt" data-text="{{SPEC_9_VAL}}">{{SPEC_9_VAL}}</span></div>
<div class="spec-row"><b>{{SPEC_10_KEY}}</b><span class="pt" data-text="{{SPEC_10_VAL}}">{{SPEC_10_VAL}}</span></div>
</div>
</div>
</section>
<!-- PRICING · SWAP-10: 3 档订阅,中间档加 .featured -->
<section class="sec" id="price">
<div class="sec-head"><h2><pre class="sec-ascii">═══ ❑ ═══</pre>{{PRICING_TITLE}}</h2><div class="idx">[ 05 / 05 ]</div></div>
<div class="price-grid">
<div class="price">
<h4>{{PRICE_1_NAME}}</h4>
<div class="num">{{PRICE_1_NUM}}<small>{{PRICE_1_UNIT}}</small></div>
<p class="desc">{{PRICE_1_DESC}}</p>
<ul>
<li>{{PRICE_1_FEAT_1}}</li>
<li>{{PRICE_1_FEAT_2}}</li>
<li>{{PRICE_1_FEAT_3}}</li>
<li>{{PRICE_1_FEAT_4}}</li>
<li>{{PRICE_1_FEAT_5}}</li>
</ul>
<a class="btn pt" data-text="{{PRICE_1_BTN}}">{{PRICE_1_BTN}}</a>
</div>
<div class="price featured">
<h4>★ {{PRICE_2_NAME}}</h4>
<div class="num">{{PRICE_2_NUM}}<small>{{PRICE_2_UNIT}}</small></div>
<p class="desc">{{PRICE_2_DESC}}</p>
<ul>
<li>{{PRICE_2_FEAT_1}}</li>
<li>{{PRICE_2_FEAT_2}}</li>
<li>{{PRICE_2_FEAT_3}}</li>
<li>{{PRICE_2_FEAT_4}}</li>
<li>{{PRICE_2_FEAT_5}}</li>
<li>{{PRICE_2_FEAT_6}}</li>
</ul>
<a class="btn pt" data-text="{{PRICE_2_BTN}}">{{PRICE_2_BTN}}</a>
</div>
<div class="price">
<h4>{{PRICE_3_NAME}}</h4>
<div class="num">{{PRICE_3_NUM}}<small>{{PRICE_3_UNIT}}</small></div>
<p class="desc">{{PRICE_3_DESC}}</p>
<ul>
<li>{{PRICE_3_FEAT_1}}</li>
<li>{{PRICE_3_FEAT_2}}</li>
<li>{{PRICE_3_FEAT_3}}</li>
<li>{{PRICE_3_FEAT_4}}</li>
<li>{{PRICE_3_FEAT_5}}</li>
<li>{{PRICE_3_FEAT_6}}</li>
</ul>
<a class="btn pt" data-text="{{PRICE_3_BTN}}">{{PRICE_3_BTN}}</a>
</div>
</div>
</section>
<!-- INFO / CTA · SWAP-11: 大字 CTA + 3 按钮 + 平台标签 -->
<section class="sec info" id="info">
<div class="info-orbit"><pre id="info-ascii"></pre></div>
<div class="info-big">{{CTA_BIG_HTML}}</div>
<!-- CTA_BIG 可用 <em>...</em> 强调,<br> 换行 -->
<div class="hero-sub">{{CTA_SUB}}</div>
<div class="info-links">
<a class="pt" data-text="{{CTA_BTN_1}}">{{CTA_BTN_1}}</a>
<a class="pt" data-text="{{CTA_BTN_2}}">{{CTA_BTN_2}}</a>
<a class="pt" data-text="{{CTA_BTN_3}}">{{CTA_BTN_3}}</a>
</div>
<div class="platforms">
<span>★ {{PLATFORM_1}}</span><span>★ {{PLATFORM_2}}</span><span>★ {{PLATFORM_3}}</span><span>★ {{PLATFORM_4}}</span><span>★ {{PLATFORM_5}}</span><span>★ {{PLATFORM_6}}</span><span>★ {{PLATFORM_7}}</span>
</div>
</section>
<!-- FOOTER · SWAP-12 -->
<div class="footer">
<span>{{FOOTER_LEFT}}</span>
<span>{{FOOTER_RIGHT}}</span>
</div>
<script>
/* ============================================================
1. HERO ASCII :旋转球体 + 行星轨道 + 星点场
============================================================ */
(function(){
const elNeb = document.getElementById('ascii-nebula');
const elStar = document.getElementById('ascii-stars');
const elPlanet = document.getElementById('ascii-planet');
const elAst = document.getElementById('ascii-asteroids');
const elComet = document.getElementById('ascii-comets');
if(!elPlanet) return;
let cols, rows, t = 0;
let stars = [];
let asteroids = [];
let comets = [];
let nebulae = [];
// ============================================================
// SWAP-13 · ASCII 场景资产(motif 可替换处)
// 想换主题(咖啡/森林/音乐/代码/齿轮/海洋…),只改这个 SCENE 对象。
// 渲染逻辑不要动 — 只要每个键的"形状个数 / 字符个数"保持合理即可。
// 详见 SKILL.md「ASCII 场景 motif 库」。
// ============================================================
const SCENE = {
// 漂浮的不规则物体(默认:小行星)。每个元素是多行 string 数组
floaters: [
[
" .-~~-. ",
" ( o..o )",
" `-~~-' "
],
[
" _.--._ ",
" / .o \\ ",
" \\ o. / ",
" `~--~' "
],
[
" .-=-. ",
" /,o o.\\ ",
" \\ `-' / ",
" `~~-' "
],
[
" _.+~+._ ",
"/ . o \\",
"\\ o . o/",
" `~--~~' "
],
[
" .o0o. ",
" 0o..oO ",
" `OoO' "
],
[
" ,-~^~-. ",
"( :*. * )",
" `-,_,-' "
]
],
// 中央旋转球体的明暗梯度字符(暗→亮)。默认:太空岩石球
centerShade: " .,:;-~+*=%#@█",
// 弥漫底层的"气体云"字符梯度(淡→浓)。默认:星云尘埃
diffuseChars: "·∙∶⋮⋯░▒",
// 闪烁的散点:稀有 / 中频 / 普通频。默认:星
sparkRare: "★", // ~4% 概率出现
sparkBright: "✦", // ~15%
sparkDim: ".∙", // 其余对半分
// 飞掠物:头 + 拖尾从近到远 5 段。默认:彗星
streakHead: "☄",
streakTailNear:"*",
streakTailMid: "∙",
streakTailFar: "·",
streakTailEnd: ".",
// 中央球体周围的 3 颗"卫星" + 轨道线点
centerSats: "◉◎○", // 3 颗,由内向外
orbitDots: ".∙•" // 3 圈轨道,由内向外
};
// ============================================================
// END SWAP-13
// ============================================================
function reseed(){
cols = Math.floor(window.innerWidth / 7);
rows = Math.floor(window.innerHeight / 11);
stars = [];
const n = Math.floor(cols*rows*0.05);
for(let i=0;i<n;i++){
const dimChars = SCENE.sparkDim;
stars.push({
x: Math.random()*cols,
y: Math.random()*rows,
z: Math.random()*0.9+0.1,
c: Math.random()<.04?SCENE.sparkRare:(Math.random()<.15?SCENE.sparkBright:dimChars[Math.floor(Math.random()*dimChars.length)])
});
}
asteroids = [];
const aN = Math.max(4, Math.floor(Math.min(cols,rows)/14));
for(let i=0;i<aN;i++){
asteroids.push({
x: Math.random()*cols,
y: Math.random()*rows,
vx: (Math.random()-0.5)*0.08,
vy: (Math.random()-0.5)*0.04,
shape: SCENE.floaters[Math.floor(Math.random()*SCENE.floaters.length)],
rot: Math.random()*Math.PI*2,
rs: (Math.random()-0.5)*0.01
});
}
comets = [];
for(let i=0;i<2;i++){
comets.push({
x: Math.random()*cols,
y: Math.random()*rows*0.6,
vx: 0.35 + Math.random()*0.25,
vy: 0.12 + Math.random()*0.1,
life: Math.random()*200
});
}
nebulae = [];
for(let i=0;i<3;i++){
nebulae.push({
x: Math.random()*cols,
y: Math.random()*rows,
r: 9 + Math.random()*15,
seed: Math.random()*100
});
}
}
reseed();
window.addEventListener('resize', reseed);
function newBuf(){
const b = new Array(rows);
for(let y=0;y<rows;y++) b[y] = new Array(cols).fill(' ');
return b;
}
function flush(buf){ return buf.map(r=>r.join('')).join('\n'); }
function drawAsteroid(buf, ax, ay, shape){
for(let i=0;i<shape.length;i++){
const line = shape[i];
const yy = Math.floor(ay) + i - Math.floor(shape.length/2);
if(yy<0||yy>=rows) continue;
for(let j=0;j<line.length;j++){
const ch = line[j];
if(ch===' ') continue;
const xx = Math.floor(ax) + j - Math.floor(line.length/2);
if(xx<0||xx>=cols) continue;
buf[yy][xx] = ch;
}
}
}
const chars = SCENE.centerShade;
const nebChars = SCENE.diffuseChars;
function render(){
t += 0.012;
const bufNeb = newBuf();
const bufStar = newBuf();
const bufPlanet = newBuf();
const bufAst = newBuf();
const bufComet = newBuf();
// 星云气体
for(const n of nebulae){
const cx = n.x + Math.sin(t*0.2 + n.seed)*3;
const cy = n.y + Math.cos(t*0.15 + n.seed)*2;
for(let dy=-n.r; dy<=n.r; dy++){
for(let dx=-n.r*1.6; dx<=n.r*1.6; dx++){
const xx = Math.floor(cx+dx);
const yy = Math.floor(cy+dy);
if(xx<0||xx>=cols||yy<0||yy>=rows) continue;
const d = Math.sqrt((dx/1.6)*(dx/1.6) + dy*dy);
if(d > n.r) continue;
const noise = Math.sin(xx*0.3 + n.seed)*Math.cos(yy*0.4 - n.seed*0.7) + Math.sin(t*0.5+n.seed);
const intensity = (1 - d/n.r) * (0.5 + 0.5*noise);
if(intensity > 0.55){
const ci = Math.min(nebChars.length-1, Math.floor(intensity * nebChars.length));
bufNeb[yy][xx] = nebChars[ci];
}
}
}
}
// 闪烁星点
for(const s of stars){
const xi = Math.floor((s.x + Math.sin(t*0.3 + s.y)*0.3 + cols)%cols);
const yi = Math.floor(s.y);
if(yi>=0 && yi<rows){
const tw = (Math.sin(t*2 + s.x*0.7 + s.y*0.3)+1)*0.5;
if(tw>0.55 || s.c===SCENE.sparkRare || s.c===SCENE.sparkBright) bufStar[yi][xi] = s.c;
}
}
// 旋转行星
const cx = cols/2, cy = rows/2;
const R = Math.min(cols,rows*2)*0.18;
const aspect = 0.5;
for(let phi=0; phi<Math.PI; phi+=0.06){
for(let theta=0; theta<2*Math.PI; theta+=0.04){
const x3 = R*Math.sin(phi)*Math.cos(theta + t);
const y3 = R*Math.cos(phi);
const z3 = R*Math.sin(phi)*Math.sin(theta + t);
const lx = Math.cos(t*0.6), ly = -0.3, lz = Math.sin(t*0.6);
const nx = Math.sin(phi)*Math.cos(theta + t);
const ny = Math.cos(phi);
const nz = Math.sin(phi)*Math.sin(theta + t);
const lum = nx*lx + ny*ly + nz*lz;
if(z3 < 0) continue;
const sx = Math.floor(cx + x3/aspect);
const sy = Math.floor(cy + y3);
if(sx<0||sx>=cols||sy<0||sy>=rows) continue;
const idx = Math.max(0, Math.min(chars.length-1, Math.floor((lum+0.2)*chars.length*0.9)));
bufPlanet[sy][sx] = chars[idx];
}
}
// 行星轨道与卫星
for(let i=0; i<3; i++){
const orbR = R*(1.6 + i*0.5);
const tilt = 0.35 + i*0.1;
for(let a=0; a<2*Math.PI; a+=0.04){
const ox = orbR*Math.cos(a + t*(0.4-i*0.1));
const oy = orbR*Math.sin(a + t*(0.4-i*0.1))*tilt;
const sx = Math.floor(cx + ox/aspect);
const sy = Math.floor(cy + oy);
if(sx<0||sx>=cols||sy<0||sy>=rows) continue;
if(bufPlanet[sy][sx]===' ') bufPlanet[sy][sx] = SCENE.orbitDots[i] || SCENE.orbitDots[SCENE.orbitDots.length-1];
}
const a = t*(0.7-i*0.18);
const ox = orbR*Math.cos(a);
const oy = orbR*Math.sin(a)*tilt;
const sx = Math.floor(cx + ox/aspect);
const sy = Math.floor(cy + oy);
if(sx>=0&&sx<cols&&sy>=0&&sy<rows) bufPlanet[sy][sx] = SCENE.centerSats[i] || SCENE.centerSats[SCENE.centerSats.length-1];
}
// 彗星拖尾
for(const c of comets){
c.x += c.vx; c.y += c.vy; c.life++;
if(c.x>cols+10 || c.y>rows+5){
c.x = -10; c.y = Math.random()*rows*0.5;
c.life = 0;
}
const tail = 16;
for(let k=0;k<tail;k++){
const tx = Math.floor(c.x - k*c.vx*1.5);
const ty = Math.floor(c.y - k*c.vy*1.5);
if(tx<0||tx>=cols||ty<0||ty>=rows) continue;
const tch = k===0?SCENE.streakHead:(k<3?SCENE.streakTailNear:(k<7?SCENE.streakTailMid:(k<12?SCENE.streakTailFar:SCENE.streakTailEnd)));
bufComet[ty][tx] = tch;
}
}
// 漂浮的小行星
for(const a of asteroids){
a.x += a.vx; a.y += a.vy; a.rot += a.rs;
if(a.x<-6) a.x = cols+6;
if(a.x>cols+6) a.x = -6;
if(a.y<-4) a.y = rows+4;
if(a.y>rows+4) a.y = -4;
const wob = Math.sin(t*0.8 + a.rot)*0.6;
drawAsteroid(bufAst, a.x + wob, a.y + Math.cos(t*0.6+a.rot)*0.4, a.shape);
}
elNeb.textContent = flush(bufNeb);
elStar.textContent = flush(bufStar);
elPlanet.textContent = flush(bufPlanet);
elAst.textContent = flush(bufAst);
elComet.textContent = flush(bufComet);
requestAnimationFrame(render);
}
render();
})();
/* ============================================================
2. INFO 区小型轨道
============================================================ */
(function(){
const el = document.getElementById('info-ascii');
if(!el) return;
const W=44, H=22;
let t=0;
// 小行星带的随机位置
const belt = [];
for(let i=0;i<14;i++){
belt.push({
a: Math.random()*Math.PI*2,
r: 8.5 + Math.random()*1.6,
sp: 0.5 + Math.random()*0.4,
ch: '✦◆◈*·•'[Math.floor(Math.random()*6)]
});
}
function tick(){
t+=0.04;
const buf = Array.from({length:H},()=>new Array(W).fill(' '));
const cx=W/2, cy=H/2;
// 散落的星点背景
for(let i=0;i<24;i++){
const x = Math.floor((Math.sin(i*7.3)+1)*W/2);
const y = Math.floor((Math.cos(i*5.1)+1)*H/2);
const tw = (Math.sin(t*1.5 + i)+1)*0.5;
if(x>=0&&x<W&&y>=0&&y<H && tw>0.6) buf[y][x]='.';
}
// 行星轨道
for(let i=0;i<4;i++){
const r = 3+i*2.6;
for(let a=0;a<Math.PI*2;a+=0.18){
const x = Math.floor(cx + r*Math.cos(a)*1.0);
const y = Math.floor(cy + r*Math.sin(a)*0.5);
if(x>=0&&x<W&&y>=0&&y<H && buf[y][x]===' ') buf[y][x]='.';
}
const a = t*(1-i*0.18);
const x = Math.floor(cx + r*Math.cos(a));
const y = Math.floor(cy + r*Math.sin(a)*0.5);
if(x>=0&&x<W&&y>=0&&y<H) buf[y][x]='★';
}
// 外侧小行星带
for(const b of belt){
const ang = b.a + t*b.sp*0.3;
const x = Math.floor(cx + b.r*Math.cos(ang));
const y = Math.floor(cy + b.r*Math.sin(ang)*0.5);
if(x>=0&&x<W&&y>=0&&y<H) buf[y][x]=b.ch;
}
// 中心恒星与日冕
if(buf[Math.floor(cy)]) buf[Math.floor(cy)][Math.floor(cx)]='◉';
const halo = [[-1,0,'-'],[1,0,'-'],[0,-1,'|'],[0,1,'|']];
for(const [dx,dy,ch] of halo){
const x = Math.floor(cx)+dx, y = Math.floor(cy)+dy;
if(x>=0&&x<W&&y>=0&&y<H && buf[y][x]===' ') buf[y][x]=ch;
}
el.textContent = buf.map(r=>r.join('')).join('\n');
requestAnimationFrame(tick);
}
tick();
})();
/* ============================================================
3. PROTEXT :文本乱码-解码效果
悬停或进入视口时,文字先打乱再逐字"解密"显现
============================================================ */
(function(){
const glyphs = "★☆✶✦✧◉◎○●∙•~≈≋▁▂▃▄▅▆▇█ABCDEFGHJKLMNPQRSTUVWXYZ0123456789@#%&*";
const RAND = ()=> glyphs[Math.floor(Math.random()*glyphs.length)];
function scramble(el){
if(el.dataset.scrLock==='1') return;
el.dataset.scrLock='1';
el.classList.add('scrambling');
const target = el.dataset.text || el.textContent;
const chars = [...target];
const len = chars.length;
let frame = 0;
const total = 16;
const step = ()=>{
frame++;
const out = chars.map((ch,i)=>{
if(ch===' '||ch==='\n') return ch;
const reveal = Math.floor((frame/total)*len*1.2);
if(i < reveal) return ch;
return RAND();
}).join('');
el.textContent = out;
if(frame < total){
setTimeout(step, 40);
} else {
el.textContent = target;
el.classList.remove('scrambling');
el.dataset.scrLock='0';
}
};
step();
}
document.querySelectorAll('.pt').forEach(el=>{
if(!el.dataset.text) el.dataset.text = el.textContent.trim();
el.addEventListener('mouseenter', ()=>scramble(el));
});
const io = new IntersectionObserver((entries)=>{
entries.forEach(e=>{
if(e.isIntersecting){
scramble(e.target);
io.unobserve(e.target);
}
});
},{threshold:0.3});
document.querySelectorAll('.intro-text .pt, .spec-row .pt, .showcase-text .pt').forEach(el=>io.observe(el));
})();
/* ============================================================
4. 标签筛选
============================================================ */
(function(){
const tags = document.querySelectorAll('.tag');
const cards = document.querySelectorAll('#grid .card');
tags.forEach(tag=>{
tag.addEventListener('click', ()=>{
tags.forEach(t=>t.classList.remove('active'));
tag.classList.add('active');
const cat = tag.dataset.cat;
cards.forEach(c=>{
if(cat==='all' || c.dataset.cat.split(' ').includes(cat)){
c.classList.remove('hidden');
} else {
c.classList.add('hidden');
}
});
});
});
})();
/* ============================================================
5. 平滑滚动
============================================================ */
document.querySelectorAll('a[href^="#"]').forEach(a=>{
a.addEventListener('click', e=>{
const id = a.getAttribute('href');
if(id.length>1){
const tgt = document.querySelector(id);
if(tgt){ e.preventDefault(); tgt.scrollIntoView({behavior:'smooth',block:'start'}); }
}
});
});
/* ============================================================
6. 实时时钟
============================================================ */
(function(){
const el = document.querySelector('.here');
if(!el) return;
function fmt(n){return n<10?'0'+n:n}
function tick(){
const d = new Date();
el.innerHTML = '{{GREETING}}<br><span class="blink">▮</span> 现在是 ' + fmt(d.getHours())+':'+fmt(d.getMinutes())+':'+fmt(d.getSeconds());
setTimeout(tick, 1000);
}
tick();
})();
</script>
</body>
</html>