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:
1 parent
7acc0c5451
commit
e45b68bb8b
1089 files changed
+260614
No files matched your search
+248
@@ -0,0 +1,248 @@
|
||||
---
|
||||
name: portfolio-detail
|
||||
description: Generate a single-file Chinese atelier-style portfolio website (horizontal-row list + hover-reveal thumb interaction, art lishu typography "Alimama DaoLiTi" via CDN, light/dark theme, hash-routed detail pages). Takes a natural-language description of the studio (name, director, discipline, works) and outputs ONE working .html file. Runs in BOTH Claude Code (writes to ~/Desktop) and GLM / online chat (outputs HTML inline). Trigger phrases include "做个中式工作室作品集", "做个隶书作品集", "lishu-portfolio-gen ...", "做个白屋风格的作品集", or when user drags this skill folder into chat asking for an atelier-style site.
|
||||
visibility: public
|
||||
mode: template
|
||||
carrier: web-page
|
||||
scenario: portfolio
|
||||
pattern_source: reference.html
|
||||
source_priority: skill-first
|
||||
triggers:
|
||||
- "做个中式工作室作品集"
|
||||
- "做个隶书作品集"
|
||||
- "做个白屋风格的作品集"
|
||||
- "横排作品集"
|
||||
- "刀隶体作品集"
|
||||
related_patterns: vr-canvas, digital-eguide
|
||||
legacy_gen: true
|
||||
---
|
||||
|
||||
# portfolio-detail — 中式工作室 / 隶书作品集(带二级详情页)
|
||||
|
||||
## 一句话特征
|
||||
横排文字行 + hover 浮现右侧大图 + 阿里妈妈刀隶体艺术大字 + 黑白纯色 + light/dark + hash 路由详情页 + 列表/画册双视图。
|
||||
|
||||
## 与其他作品集 skill 的差别
|
||||
|
||||
| skill | 核心模式 | 适合谁 |
|
||||
|---|---|---|
|
||||
| `portfolio-gen` | 无限拖拽画布 + tile 平铺 | 摄影师 / 平面设计师挂样品 |
|
||||
| `portfolio-museum-gen` | 英雄轮播 + 大卡 + 瀑布流 + 详情页(影像策展感) | 视频/影像/装置艺术家 |
|
||||
| `industrial-archive-gen` | 黑框翻页封面 + 侧栏档案 + 北欧工业 | 实物产品 / 器物 / 硬件 |
|
||||
| **`lishu-portfolio-gen`** | **横排文字行 + hover 显示右侧缩略图 + 隶书大字 + 中式克制** | **装帧 / 字体 / 工艺 / 中式工作室** |
|
||||
|
||||
如果用户的项目是「书 / 字体 / 展览 / 静物 / 中草药 / 茶 / 陶 / 木 / 纸 / 香」这一类需要克制典雅气质的——用这一份。
|
||||
|
||||
## 何时触发
|
||||
|
||||
用户说:
|
||||
- "做个中式 / 东方 / 古典风格的作品集"
|
||||
- "做个隶书风格的网站 / 工作室主页"
|
||||
- "做个白屋风格的作品集" / "做个像 Gurgel D'Alfonso 那样的横排列表网站"
|
||||
- "lishu-portfolio-gen ..."
|
||||
|
||||
并提供:
|
||||
- **工作室描述**(必需):名字、主理人、城市、学科、几件作品的标题与故事
|
||||
- **作品图片**(可选):URL / 上传 / 路径。没有也能跑(用 picsum 占位)
|
||||
|
||||
如果用户**完全没给作品标题** → 必须问一次至少有哪几件作品要展示。
|
||||
如果用户**只给了标题**没给详情 → 自己写:每件作品都生成 1 句 lead + 3 段 prose + 6-8 行 specs + 3-6 张 picsum 图 + 几条 credits。语气与作品类型匹配(书 → 装帧 / 字 → 字体 / 物 → 静物 / 展 → 展览视觉)。
|
||||
|
||||
## 两种运行模式
|
||||
|
||||
### 模式 A · Claude Code / 本地(有文件系统)
|
||||
- 用 `Bash` 列文件夹、`sips` 探每张图宽高
|
||||
- 输出位置:`~/Desktop/{slug}-portfolio.html`(**单文件**,所有图片走 URL)
|
||||
- 如果用户给了本地图片:拷到 `~/Desktop/{slug}-portfolio-images/` 并改 src 为相对路径
|
||||
|
||||
### 模式 B · GLM / 在线对话(无文件系统)
|
||||
- 用户拖入 zip 解压后能看到 4 份文件
|
||||
- **只输出一份完整 HTML 字符串**,包在 ` ```html ` 代码块里
|
||||
- 用户上传图片但没给 URL → 全用 `https://picsum.photos/id/{id}/{w}/{h}` 占位(用已知存在的 ID)
|
||||
- 不要尝试访问本地文件、不要建议命令行操作
|
||||
|
||||
判断:如果你**无法 Read 看到本地图片** → 默认走 B。
|
||||
|
||||
## 文件分工
|
||||
|
||||
| 文件 | 角色 | 怎么用 |
|
||||
|---|---|---|
|
||||
| `reference.html` | **完整成品参考** — 白屋 BAIWU 6 件作品 | 看:list 项的措辞、prose 语气、specs 词汇、JSON 字段密度 |
|
||||
| `template.html` | **空骨架** — 同样的 CSS/JS/路由系统,2 件示例 + 20 个 SWAP 标记 | 在它的基础上改,输出最终 HTML |
|
||||
| `README.md` | 用户视角的使用说明 | 模型不用看 |
|
||||
| `SKILL.md` | 给模型看的工作流(本文件) | **必读** |
|
||||
|
||||
**核心规则**:所有改动基于 `template.html`;从 `reference.html` 学**怎么写**(语气、字数、tag 用词),但不要复制具体内容。
|
||||
|
||||
## 工作步骤
|
||||
|
||||
### 1. 读两份 HTML
|
||||
- 先读 `reference.html` 体会"完整后长什么样"(特别是 `works-data` JSON 的字段密度和 prose 语气)
|
||||
- 再读 `template.html` 找 20 个 SWAP 位置
|
||||
|
||||
**不要改 JS / 不要改 CSS / 不要改详情页路由系统 / 不要改字体引入**。
|
||||
SWAP 都在 HTML 文本和 JSON 数据里。
|
||||
|
||||
### 2. 解析用户输入抽信息
|
||||
|
||||
抽取:
|
||||
- **ATELIER_NAME_ZH** — 工作室中文名(2-4 字最佳,例如「白屋」「素园」「墨记」「青庐」「拙轩」)
|
||||
- **PINYIN** — 拼音(5-6 个英文字母,全大写:BAIWU / SUYUAN / MOJI)
|
||||
- **CHAR** — Logo 用的单字(通常是工作室名首字:白 / 素 / 墨 / 青 / 拙)
|
||||
- **CITY** — 城市
|
||||
- **DIRECTOR_ZH / DIRECTOR_EN** — 主理人姓名 + 拼音
|
||||
- **DISCIPLINE** — 3 个学科词("装帧 · 字体 · 静物")
|
||||
- **TAGLINE** — 一句话定位(20-40 字,楷体气质)
|
||||
- **YEAR_FOUNDED** — 成立年份(可用干支:乙未冬 / 甲辰春)
|
||||
|
||||
每件作品抽:
|
||||
- **slug** — URL ID,小写英文连字符(`kong-shan-ji` / `wu-ming-shu`)
|
||||
- **title_zh** — 中文标题(2-5 字最佳,越精炼越有调性)
|
||||
- **title_en** — 英文副题(书名号风格:`An Anthology of Empty Mountains`)
|
||||
- **year** — 年份或干支年(`甲辰 / 2024` / `2025` / `乙巳 / 2025`)
|
||||
- **tag** — 顶部分类,方括号包,如 `[ 诗集 · 装帧设计 ]` / `[ 字体设计 · 中草药图谱 ]`
|
||||
- **cover** — 详情页大图 URL
|
||||
- **list 行 location** — `城市,年份 · 工艺/规格`(hover 时浮现)
|
||||
- **meta** — 4 个键值对(详情页 4 列摘要)
|
||||
- **lead** — 30-70 字开篇大字,可用 `<em>关键词</em>` 强调一处
|
||||
- **prose** — 3-4 段散文,每段 80-150 字
|
||||
- **specs** — 6-10 行规格表(开本/纸张/装订/字体/印量/印刷/工艺/定价等)
|
||||
- **gallery** — 3-6 张图,layout 三选一:`g-wide` / `g-half` / `g-third`
|
||||
- **credits** — 4-7 对职位/名字
|
||||
|
||||
如果用户只给一句话/没给故事 → **自己编**:根据作品标题和类型,写出符合作品调性的 lead 和 prose。语气参考 reference.html — 不要 marketing 语言,要有「博物馆 wall-text + 工作室札记」的感觉(克制、具体、缓慢、有手工痕迹)。
|
||||
|
||||
### 3. 列作品 + 探尺寸(仅模式 A)
|
||||
|
||||
```bash
|
||||
ls -la "{works_path}"
|
||||
sips -g pixelWidth -g pixelHeight "{works_path}/{file}" 2>/dev/null
|
||||
```
|
||||
|
||||
得到每张图的 (filename, w, h, aspect)。优先选竖图作 list 缩略图(4:3 容器,竖图更上相)。
|
||||
|
||||
### 4. 替换 20 个 SWAP
|
||||
|
||||
| SWAP | 位置 | 内容 |
|
||||
|---|---|---|
|
||||
| 1 | `<title>` | `{ATELIER_NAME} {PINYIN} — {DIRECTOR}作品集 / Studio` |
|
||||
| 2 | meta description | 一句网站简介 |
|
||||
| 3 | curtain 单字 | 工作室名首字(如「白」) |
|
||||
| 4 | logo SVG 内单字 | 同 SWAP-3 |
|
||||
| 5 | logo SVG 内拼音 | `{PINYIN}` 大写 |
|
||||
| 6 | 主导航 li | 通常 3 项:作品 / 关于 / 联系 |
|
||||
| 7 | brand-block 中文名 | `{ATELIER_NAME}` 拆字加空格(如 `白 屋`) |
|
||||
| 8 | brand-block 英文 | `{PINYIN} Studio · {CITY}` |
|
||||
| 9 | tagline | 楷体一句话定位,20-40 字,可用 `<br>` 换行 |
|
||||
| 10 | **list 项 N 件** | 每件 `<a class="item" href="#work/{slug}">` 包含 num/project-name/en-name/location/photo-thumb |
|
||||
| 11 | **gallery 项 N 件** | 同样的 N 件作品,更紧凑的网格 — slug 必须和 list 一致 |
|
||||
| 12 | 关于标题 | `关 于 {name}<span class="en">About</span>` |
|
||||
| 13 | 关于段落 | 1-3 段楷体(成立背景 / 主理人 / 设计观),每段 60-120 字 |
|
||||
| 14 | 关于事实表 | 4-6 项 li (Founded/Location/Discipline/Director/Hours) |
|
||||
| 15 | 联系标题 | `来 信<span class="en">Get in Touch</span>` |
|
||||
| 16 | 联系段落 | 1-2 段邀请来信 + 回信周期承诺 |
|
||||
| 17 | 联系事实表 | 4-6 项 (E-mail/Phone/Address/Instagram/公众号) |
|
||||
| 18 | rotating credit | `Site by {WHO}`(通常是工作室名) |
|
||||
| 19 | footer 三块 | © / ICP / 社交链接 |
|
||||
| 20 | **works-data JSON** | **N 件作品的完整详情数据(关键!)** |
|
||||
|
||||
### 5. 写 works-data JSON(最关键的一步)
|
||||
|
||||
每件作品都要在 JSON 数组里有一条,**slug 必须和 list/gallery 卡片 href 里的 `#work/SLUG` 完全一致**。
|
||||
|
||||
JSON 字段:
|
||||
```json
|
||||
{
|
||||
"slug": "kong-shan-ji",
|
||||
"title_zh": "空山集",
|
||||
"title_en": "An Anthology of Empty Mountains",
|
||||
"year": "甲辰 / 2024",
|
||||
"tag": "[ 诗集 · 装帧设计 ]",
|
||||
"cover": "https://picsum.photos/id/143/1800/1100",
|
||||
"meta": [
|
||||
{"label": "Type / 类型", "value": "诗集装帧"},
|
||||
{"label": "Year / 年份", "value": "2024"},
|
||||
{"label": "Edition / 印量", "value": "首印 320 本"},
|
||||
{"label": "Format / 开本", "value": "130 × 195 毫米"}
|
||||
],
|
||||
"lead": "三十六首关于山的诗。我们想做一本——读者翻开后,先听见<em>风声</em>,再读见字的书。",
|
||||
"prose": [
|
||||
"段落一:诗稿来源 / 设计语境。80—150 字。",
|
||||
"段落二:制作中的关键决定 — 字体、纸张、装订选择。",
|
||||
"段落三:最后呈现 / 留给读者的感受。"
|
||||
],
|
||||
"specs": [
|
||||
["开本 / Format", "130 × 195 毫米"],
|
||||
["页数 / Pages", "144 页"],
|
||||
["纸张 / Paper", "本色棉纸 90gsm"],
|
||||
["装订 / Binding", "线装四目"],
|
||||
["字体 / Typeface", "老宋(标题)· 楷体(正文)"],
|
||||
["印刷 / Printing", "单色凸印"],
|
||||
["印量 / Edition", "首印 320 本"],
|
||||
["定价 / Price", "¥ 388"]
|
||||
],
|
||||
"gallery": [
|
||||
{"src": "...", "layout": "g-wide", "caption": "封面"},
|
||||
{"src": "...", "layout": "g-half"},
|
||||
{"src": "...", "layout": "g-half"},
|
||||
{"src": "...", "layout": "g-third"},
|
||||
{"src": "...", "layout": "g-third"},
|
||||
{"src": "...", "layout": "g-third"}
|
||||
],
|
||||
"credits": [
|
||||
["设计 / Design", "周墨白"],
|
||||
["手工装订 / Binding", "陈漱石(杭州·订书坊)"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**JSON 易错 ⚠️**:
|
||||
- 中文 prose 里要用引号 → 必须用全角的 `「...」` 或 `"..."`(U+201C / U+201D),**不能**用 ASCII `"`(会破 JSON)
|
||||
- 不要在 JSON 字符串里写真换行;要换段落就在 prose 数组里多写一项
|
||||
- `<em>词</em>` 在 lead 里**直接写 HTML 标签**——渲染时按 innerHTML 出
|
||||
- gallery `layout` 必须三选一:`g-wide`(占满)/ `g-half`(占一半)/ `g-third`(占 1/3)
|
||||
|
||||
### 6. 图片源选择
|
||||
- **picsum 占位**:用 `id/X` 形式而非 `seed/X`(更稳定)。已知好用的 ID:0, 20, 40, 51, 64, 76, 88, 91, 96, 119, 121, 130, 143, 145, 158, 164, 165, 175, 177, 180, 187, 195, 200
|
||||
- 同一作品的 cover / gallery 用相邻的几个 ID,能制造一致的色调
|
||||
- 不要用 `?grayscale` 参数(用户希望图片是彩色的)
|
||||
|
||||
### 7. 中文工作室命名灵感(如果用户没给)
|
||||
若用户让你"自己起一个" → 选 2 字 + 古朴:
|
||||
- 物名类:白屋 / 素园 / 墨记 / 青庐 / 拙轩 / 听雨 / 一勺 / 半亩
|
||||
- 字行类:素白 / 子衿 / 砚池 / 半山 / 寻常 / 浮云
|
||||
- 避免 "工作室 / 设计 / Studio" 这种功能词,让名字本身就是诗
|
||||
|
||||
### 8. 视频支持
|
||||
如果作品 cover 是 .mp4 / .webm / .mov,详情页会自动用 `<video autoplay muted loop>` 替代 `<img>`。模型只要在 cover URL 里给视频路径就行。
|
||||
|
||||
### 9. 报告
|
||||
|
||||
**模式 A**:一句话报告。
|
||||
> 已生成 ~/Desktop/baiwu-portfolio.html — 6 件作品,hover 行查看缩略图,点击进入详情页。Light/Dark 在左下,列表/画册在顶部居中。
|
||||
|
||||
**模式 B**:直接给完整 HTML 代码块 + 一行说明。
|
||||
> 把上面这段保存成 `index.html` 双击打开。鼠标移到任意作品名上看右侧浮现的缩略图,点击跳详情页。要换图把 picsum URL 换成你的图片地址。
|
||||
|
||||
## 强制保留(不能改)
|
||||
|
||||
- **字体引入**:`@font-face` 加载阿里妈妈刀隶体的 woff2 url(不要换字体源)
|
||||
- **CSS 变量系统**:light/dark 双主题
|
||||
- **全部 JS**:theme toggle、view mode toggle、hover-row 交互、rotating credit、详情页 hash 路由 `applyRoute()`、`renderDetail()`
|
||||
- **`<script id="works-data">`** 节点必须保留
|
||||
- **`<main id="detail-root">`** 必须保留(详情页挂载点)
|
||||
- **`body[data-view="home/detail"]` 和 `body[data-mode="list/gallery"]`** 状态系统
|
||||
- **#logo / #main-menu / .theme-toggle / #project-view / #svp-credit** 五个固定定位元素
|
||||
|
||||
## 易错速查
|
||||
|
||||
- **slug 不匹配**:list 项 `#work/kong-shan-ji` ↔ gallery 项 `#work/kong-shan-ji` ↔ JSON `"slug": "kong-shan-ji"` —— 三处必须完全一致
|
||||
- **JSON 引号**:中文内容里的引号一定用全角「」或 `"..."`,不是 ASCII `"`
|
||||
- **gallery 太多**:每件作品最多 6 张图,超了排版会乱
|
||||
- **prose 过长**:每段 80-150 字,超过 200 字阅读体验下降
|
||||
- **list / gallery 不同步**:必须包含同样的作品集(一一对应)
|
||||
- **picsum seed**:用 `id/X` 不要用 `seed/X`,避免随机 ID 命中已删除的图
|
||||
- **作品 < 3 件**:可以,最少 2 件就能跑
|
||||
- **作品 > 8 件**:建议分两批,太多 list 行会让页面变长
|
||||
- **首字下沉**:详情页的 `.detail__prose p:first-of-type::first-letter` 用刀隶体 — 第一段必须是中文开头(不要以 `<em>` 或英文开头)
|
||||
- **删了 picsum 的彩色**:图片应保持彩色,不要在 CSS 加 `filter: grayscale()`
|
||||
+1097
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,717 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, viewport-fit=cover">
|
||||
<!-- SWAP-1-TITLE: 替换为 "{ATELIER_NAME} {PINYIN} — {DIRECTOR} 作品集 / Studio" -->
|
||||
<title>SWAP-1-TITLE — 作品集 / Studio</title>
|
||||
<!-- SWAP-2-DESC: 一句话网站简介 -->
|
||||
<meta name="description" content="SWAP-2-DESC 作品集">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=EB+Garamond:ital,wght@0,400;1,400&family=Cormorant+Garamond:ital,wght@0,300;1,300&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* ========= 阿里妈妈刀隶体(艺术感强的隶书) ========= */
|
||||
@font-face {
|
||||
font-family: 'Alimama DaoLiTi';
|
||||
src: url('https://cdn.jsdelivr.net/npm/@fontpkg/alimama-dao-li-ti@1.0.5/AlimamaDaoLiTi.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root,
|
||||
:root[data-theme="light"]{
|
||||
--c-bg:#ffffff;
|
||||
--c-fg:#000000;
|
||||
--c-mute:#787878;
|
||||
--c-line:#000000;
|
||||
--c-line-soft:#e0e0e0;
|
||||
--c-card:#000000;
|
||||
--c-card-fg:#ffffff;
|
||||
--c-card-mute:#9a9a9a;
|
||||
--c-card-line:rgba(255,255,255,.14);
|
||||
--header-h:60px;
|
||||
--pad:clamp(16px,2vw,28px);
|
||||
--grid-gap:20px;
|
||||
--ease:cubic-bezier(.2,.7,.2,1);
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--c-bg:#000000;
|
||||
--c-fg:#ffffff;
|
||||
--c-mute:#7a7a7a;
|
||||
--c-line:#ffffff;
|
||||
--c-line-soft:#222222;
|
||||
--c-card:#ffffff;
|
||||
--c-card-fg:#000000;
|
||||
--c-card-mute:#5a5a5a;
|
||||
--c-card-line:rgba(0,0,0,.14);
|
||||
}
|
||||
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
html,body{background:var(--c-bg);color:var(--c-fg);min-height:100%}
|
||||
html{scroll-behavior:auto}
|
||||
body{
|
||||
font-family:"DM Mono","Menlo","Monaco","Roboto Mono",monospace;
|
||||
font-weight:400;font-size:13px;line-height:1.4;
|
||||
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
|
||||
overflow-x:hidden;cursor:default;
|
||||
transition:background .9s var(--ease),color .9s var(--ease);
|
||||
}
|
||||
img,video{display:block;max-width:100%}
|
||||
a{color:inherit;text-decoration:none}
|
||||
button{font:inherit;background:none;border:0;color:inherit;cursor:pointer}
|
||||
ul,ol{list-style:none}
|
||||
|
||||
.kai{font-family:"STKaiti","Kaiti SC","KaiTi","楷体","Songti SC",serif;font-weight:400;letter-spacing:.05em;text-shadow:0 0 .25px currentColor}
|
||||
.serif{font-family:"Cormorant Garamond","EB Garamond",serif;font-style:italic;font-weight:300}
|
||||
.mono{font-family:"DM Mono",monospace;font-weight:400}
|
||||
.li{font-family:"Alimama DaoLiTi","STLibian","SimLi","隶书",serif;font-weight:400;letter-spacing:.06em}
|
||||
|
||||
.grid{padding:0 10px;width:100%;display:flex;flex-wrap:nowrap;justify-content:center;min-height:calc(100vh - 100px)}
|
||||
.col-1{width:calc(((100vw - 20px) / 10) * 1);padding:0 10px;flex-shrink:0}
|
||||
.col-2{width:calc(((100vw - 20px) / 10) * 2);padding:0 10px;flex-shrink:0}
|
||||
.col-3{width:calc(((100vw - 20px) / 10) * 3);padding:0 10px;flex-shrink:0}
|
||||
.col-4{width:calc(((100vw - 20px) / 10) * 4);padding:0 10px;flex-shrink:0}
|
||||
.col-5{width:calc(((100vw - 20px) / 10) * 5);padding:0 10px;flex-shrink:0}
|
||||
.col-6{width:calc(((100vw - 20px) / 10) * 6);padding:0 10px;flex-shrink:0}
|
||||
.col-7{width:calc(((100vw - 20px) / 10) * 7);padding:0 10px;flex-shrink:0}
|
||||
.col-8{width:calc(((100vw - 20px) / 10) * 8);padding:0 10px;flex-shrink:0}
|
||||
.col-9{width:calc(((100vw - 20px) / 10) * 9);padding:0 10px;flex-shrink:0}
|
||||
.col-10{width:calc(((100vw - 20px) / 10) * 10);padding:0 10px;flex-shrink:0}
|
||||
|
||||
.curtain{position:fixed;inset:0;z-index:9999;background:var(--c-bg);display:flex;align-items:center;justify-content:center;pointer-events:auto;transition:transform 1.2s var(--ease),opacity .6s ease .9s}
|
||||
.curtain.is-out{transform:translateY(-101%);pointer-events:none}
|
||||
.curtain__mark{font-family:"Alimama DaoLiTi","STLibian",serif;font-size:48px;letter-spacing:.2em;color:var(--c-fg);opacity:0;animation:fadeIn 1s ease forwards}
|
||||
@keyframes fadeIn{to{opacity:1}}
|
||||
|
||||
.paper{position:fixed;inset:0;z-index:1;pointer-events:none;mix-blend-mode:multiply;opacity:.06;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='320' height='320'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.92' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 .1 0 0 0 0 .09 0 0 0 0 .07 0 0 0 .55 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>")}
|
||||
:root[data-theme="dark"] .paper{mix-blend-mode:screen;opacity:.04}
|
||||
|
||||
#logo{position:fixed;top:20px;left:20px;z-index:100;color:var(--c-fg);transition:opacity .4s var(--ease)}
|
||||
#logo:hover{opacity:.6}
|
||||
#logo svg{display:block;width:48px;height:auto}
|
||||
#logo .stroke{stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:square}
|
||||
#logo .fill{fill:currentColor}
|
||||
#logo .label{font-family:"Alimama DaoLiTi","STLibian",serif;font-size:9px;letter-spacing:.5em;fill:currentColor}
|
||||
|
||||
#main-menu{position:fixed;z-index:100;right:20px;top:20px;text-align:right}
|
||||
#main-menu li{position:relative;margin-bottom:8px;display:block;transition:opacity .3s var(--ease)}
|
||||
#main-menu li:hover{opacity:.55}
|
||||
#main-menu li.current a::before{content:"";width:6px;height:6px;position:absolute;border-radius:50%;background:var(--c-fg);left:-12px;top:6px}
|
||||
#main-menu a{font-family:"DM Mono",monospace;font-size:13px;line-height:1;text-transform:lowercase;letter-spacing:.04em;display:inline-block;color:var(--c-fg)}
|
||||
|
||||
.theme-toggle{position:fixed;left:20px;bottom:20px;z-index:100;display:flex;gap:0;font-family:"DM Mono",monospace;font-size:11px;letter-spacing:.18em;text-transform:uppercase}
|
||||
.theme-toggle button{padding:6px 10px;color:var(--c-mute);border:1px solid var(--c-line-soft);border-right:0;transition:color .4s var(--ease),background .4s var(--ease),border-color .4s var(--ease)}
|
||||
.theme-toggle button:last-child{border-right:1px solid var(--c-line-soft)}
|
||||
.theme-toggle button[data-active="1"]{color:var(--c-bg);background:var(--c-fg);border-color:var(--c-fg)}
|
||||
|
||||
#project-view{position:fixed;left:50%;top:60px;transform:translateX(-50%);text-align:center;z-index:100}
|
||||
#project-view ul{display:flex;align-items:center;gap:14px}
|
||||
#project-view li{display:flex;align-items:center}
|
||||
#project-view a{font-family:"DM Mono",monospace;font-size:13px;line-height:1;text-transform:lowercase;letter-spacing:.04em;color:var(--c-fg);display:inline-flex;align-items:center;gap:8px}
|
||||
#project-view .icon{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid var(--c-fg);width:22px;height:22px;flex-shrink:0}
|
||||
#project-view .icon svg{display:block}
|
||||
#project-view .icon svg rect{fill:var(--c-fg)}
|
||||
#project-view span{display:inline-block;padding-bottom:1px;border-bottom:1px solid transparent;transition:border-color .3s var(--ease)}
|
||||
#project-view a:hover span,#project-view .current span{border-bottom-color:var(--c-fg)}
|
||||
|
||||
body[data-view="detail"] .view-home,
|
||||
body[data-view="detail"] #project-view{display:none}
|
||||
body[data-view="home"] .view-detail{display:none}
|
||||
|
||||
.view-home{padding-top:0;padding-bottom:120px}
|
||||
|
||||
.brand-block{padding:140px 20px 80px;text-align:center}
|
||||
.brand-block .zh{font-family:"Alimama DaoLiTi","STLibian",serif;font-size:clamp(56px,9vw,140px);line-height:1;letter-spacing:.18em;text-indent:.18em;color:var(--c-fg);display:block}
|
||||
.brand-block .en{font-family:"Cormorant Garamond","EB Garamond",serif;font-style:italic;font-size:clamp(18px,1.6vw,24px);letter-spacing:.18em;color:var(--c-mute);margin-top:18px;display:block;text-transform:uppercase}
|
||||
.brand-block .tagline{font-family:"STKaiti","KaiTi","楷体",serif;font-size:14px;letter-spacing:.08em;color:var(--c-fg);margin-top:32px;line-height:1.9;max-width:520px;margin-left:auto;margin-right:auto;text-shadow:0 0 .25px currentColor}
|
||||
|
||||
#projects-list{padding-top:60px;margin-bottom:120px;width:100%}
|
||||
#projects-list a.item{width:100%;text-align:left;display:block;cursor:pointer;margin-bottom:32px;position:relative;color:var(--c-fg)}
|
||||
#projects-list a.item:last-child{margin-bottom:0}
|
||||
#projects-list .num{font-family:"DM Mono",monospace;font-size:11px;letter-spacing:.18em;display:inline-block;vertical-align:middle;color:var(--c-mute);margin-right:24px}
|
||||
#projects-list .project-name{font-family:"Alimama DaoLiTi","STLibian","SimLi","隶书",serif;font-size:clamp(36px,5vw,72px);line-height:1;letter-spacing:.04em;display:inline-block;vertical-align:middle;color:var(--c-fg)}
|
||||
#projects-list .location{font-family:"DM Mono",monospace;font-size:13px;line-height:1;letter-spacing:.04em;opacity:0;transition:opacity .4s var(--ease);display:inline-block;vertical-align:middle;margin-left:30px;color:var(--c-mute)}
|
||||
#projects-list .photo-thumb{position:absolute;right:0;top:50%;transform:translateY(-50%);opacity:0;transition:opacity .4s var(--ease);width:calc(((100vw - 20px) / 10) * 3);pointer-events:none}
|
||||
#projects-list .photo-grid{position:relative;display:block;width:100%;padding-bottom:calc((100% / 3) * 4);overflow:hidden;background:var(--c-line-soft)}
|
||||
#projects-list .photo-thumb img{width:100%;height:100%;object-fit:cover;display:block;position:absolute;top:0;left:0}
|
||||
#projects-list .item.active .location,
|
||||
#projects-list .item.active .photo-thumb{opacity:1}
|
||||
#projects-list .item .en-name{font-family:"Cormorant Garamond",serif;font-style:italic;font-size:14px;letter-spacing:.04em;color:var(--c-mute);display:inline-block;vertical-align:baseline;margin-left:14px;opacity:0;transition:opacity .4s var(--ease)}
|
||||
#projects-list .item.active .en-name{opacity:.65}
|
||||
|
||||
@media (max-width:780px){
|
||||
#projects-list .photo-thumb{position:relative;right:auto;top:auto;transform:none;width:100%;margin-top:14px;opacity:1}
|
||||
#projects-list .item.active .photo-thumb{opacity:1}
|
||||
#projects-list .location{display:block;margin-left:0;margin-top:10px;opacity:1}
|
||||
#projects-list .en-name{display:block;margin-left:0;margin-top:6px;opacity:.55}
|
||||
}
|
||||
|
||||
#projects-gallery{padding-top:60px;margin-bottom:120px;text-align:center;display:none}
|
||||
body[data-mode="gallery"] #projects-gallery{display:block}
|
||||
body[data-mode="gallery"] #projects-list{display:none}
|
||||
#projects-gallery ul{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;width:100%}
|
||||
#projects-gallery li{margin:0;display:block}
|
||||
#projects-gallery a{display:block;position:relative;overflow:hidden;background:var(--c-line-soft)}
|
||||
#projects-gallery .image{display:block;width:100%;padding-bottom:calc((100% / 3) * 4);position:relative;overflow:hidden;transition:opacity .3s var(--ease)}
|
||||
#projects-gallery img{width:100%;height:100%;object-fit:cover;display:block;position:absolute;top:0;left:0;transition:transform 1.4s var(--ease)}
|
||||
#projects-gallery a:hover img{transform:scale(1.04)}
|
||||
#projects-gallery a:hover .image{opacity:.85}
|
||||
#projects-gallery .ga-title{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-family:"Alimama DaoLiTi","STLibian",serif;font-size:32px;letter-spacing:.08em;color:#fff;text-shadow:0 0 24px #000;opacity:0;transition:opacity .4s var(--ease);z-index:2;text-align:center}
|
||||
#projects-gallery a:hover .ga-title{opacity:1}
|
||||
@media (max-width:780px){#projects-gallery ul{grid-template-columns:repeat(2,1fr)}}
|
||||
|
||||
.about-strip{padding:120px 20px 80px;display:flex;justify-content:center;width:100%}
|
||||
.about-strip .inner{width:calc(((100vw - 20px) / 10) * 6);text-align:left}
|
||||
.about-strip h3{font-family:"Alimama DaoLiTi","STLibian",serif;font-size:clamp(32px,4vw,56px);letter-spacing:.1em;color:var(--c-fg);margin-bottom:24px}
|
||||
.about-strip h3 .en{font-family:"Cormorant Garamond",serif;font-style:italic;font-size:.5em;color:var(--c-mute);margin-left:18px;letter-spacing:.04em}
|
||||
.about-strip p{font-family:"STKaiti","KaiTi","楷体",serif;font-size:15px;line-height:2;letter-spacing:.06em;color:var(--c-fg);margin-bottom:18px;text-shadow:0 0 .25px currentColor}
|
||||
.about-strip .meta-list{margin-top:36px;border-top:1px solid var(--c-line-soft);padding-top:20px}
|
||||
.about-strip .meta-list li{display:flex;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--c-line-soft);font-family:"DM Mono",monospace;font-size:12px;letter-spacing:.06em}
|
||||
.about-strip .meta-list .k{color:var(--c-mute);text-transform:uppercase;letter-spacing:.16em}
|
||||
.about-strip .meta-list .v{color:var(--c-fg)}
|
||||
|
||||
#svp-credit{font-family:"DM Mono",monospace;font-size:11px;line-height:1.3;color:var(--c-fg);text-align:center;position:fixed;right:14px;bottom:36px;z-index:50;transform:rotate(-45deg);text-decoration:none;opacity:0;transition:opacity .5s ease-in;pointer-events:none}
|
||||
#svp-credit span{display:block;font-style:italic}
|
||||
#svp-credit.visible{opacity:.55;pointer-events:auto}
|
||||
|
||||
footer{padding:60px 20px 30px;text-align:center;border-top:1px solid var(--c-line-soft);margin:0 20px;font-family:"DM Mono",monospace;font-size:11px;letter-spacing:.16em;color:var(--c-mute);text-transform:uppercase;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}
|
||||
footer .links a{margin-left:14px}
|
||||
|
||||
.view-detail{padding-top:90px;padding-bottom:60px;animation:fadeInUp .6s var(--ease);max-width:100%}
|
||||
@keyframes fadeInUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
|
||||
|
||||
.detail__back-row{padding:20px var(--pad) 0}
|
||||
.detail__back{display:inline-flex;align-items:center;gap:10px;font-family:"DM Mono",monospace;font-size:12px;letter-spacing:.1em;text-transform:lowercase;padding:6px 0;border-bottom:1px solid var(--c-fg);color:var(--c-fg)}
|
||||
.detail__back .arrow{display:inline-block;transition:transform .4s var(--ease)}
|
||||
.detail__back:hover .arrow{transform:translateX(-6px)}
|
||||
|
||||
.detail__head{padding:80px var(--pad) 60px;display:grid;grid-template-columns:repeat(12,1fr);gap:var(--grid-gap);align-items:end}
|
||||
.detail__tag{grid-column:1 / span 12;color:var(--c-mute);margin-bottom:20px;font-family:"Cormorant Garamond",serif;font-style:italic;font-size:14px;letter-spacing:.18em;text-transform:uppercase}
|
||||
.detail__title{grid-column:1 / span 9;display:flex;flex-direction:column;gap:8px}
|
||||
.detail__title .zh{font-family:"Alimama DaoLiTi","STLibian","SimLi","隶书",serif;font-size:clamp(56px,8vw,120px);line-height:1;letter-spacing:.08em;color:var(--c-fg)}
|
||||
.detail__title .en{font-family:"Cormorant Garamond","EB Garamond",serif;font-style:italic;font-weight:300;font-size:clamp(18px,2vw,28px);letter-spacing:.04em;color:var(--c-mute)}
|
||||
.detail__year{grid-column:10 / span 3;justify-self:end;align-self:end;font-family:"DM Mono",monospace;font-size:12px;letter-spacing:.18em;color:var(--c-mute);text-align:right}
|
||||
@media (max-width:780px){.detail__title{grid-column:1 / -1}.detail__year{grid-column:1 / -1;justify-self:start;margin-top:12px;text-align:left}}
|
||||
|
||||
.detail__hero{margin:0 var(--pad);height:78vh;min-height:480px;max-height:840px;overflow:hidden;position:relative;background:var(--c-line-soft)}
|
||||
.detail__hero img,.detail__hero video{width:100%;height:100%;object-fit:cover}
|
||||
|
||||
.detail__meta{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--grid-gap);padding:40px var(--pad);border-bottom:1px solid var(--c-line-soft);margin:0 var(--pad)}
|
||||
.detail__meta dl{grid-column:span 3;display:flex;flex-direction:column;gap:8px}
|
||||
.detail__meta dt{font-family:"Cormorant Garamond",serif;font-style:italic;font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--c-mute)}
|
||||
.detail__meta dd{font-family:"STKaiti","KaiTi",serif;font-size:14px;letter-spacing:.04em;color:var(--c-fg);text-shadow:0 0 .25px currentColor}
|
||||
@media (max-width:780px){.detail__meta dl{grid-column:span 6}}
|
||||
|
||||
.detail__body{padding:100px var(--pad) 40px;display:grid;grid-template-columns:repeat(12,1fr);gap:var(--grid-gap)}
|
||||
.detail__lead{grid-column:2 / span 9;font-family:"Cormorant Garamond","Noto Serif SC",serif;font-weight:300;font-size:clamp(22px,2.4vw,36px);line-height:1.45;letter-spacing:.012em;margin-bottom:60px;color:var(--c-fg)}
|
||||
.detail__lead em{font-style:italic;border-bottom:1px solid currentColor;padding-bottom:2px}
|
||||
.detail__prose{grid-column:3 / span 7;font-family:"STKaiti","KaiTi","楷体",serif;font-size:15px;line-height:2;letter-spacing:.06em;color:var(--c-fg);text-shadow:0 0 .25px currentColor}
|
||||
.detail__prose p{margin-bottom:22px}
|
||||
.detail__prose p:first-of-type::first-letter{font-family:"Alimama DaoLiTi","STLibian",serif;font-size:3em;font-weight:400;float:left;line-height:.85;padding:8px 14px 0 0;color:var(--c-fg)}
|
||||
@media (max-width:780px){.detail__lead,.detail__prose{grid-column:1 / -1}}
|
||||
|
||||
.detail__specs{padding:40px var(--pad) 60px;display:grid;grid-template-columns:repeat(12,1fr);gap:var(--grid-gap);border-top:1px solid var(--c-line-soft);margin:0 var(--pad)}
|
||||
.detail__specs h4{grid-column:1 / span 3;align-self:start;padding-top:14px;font-family:"Cormorant Garamond",serif;font-style:italic;font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--c-mute)}
|
||||
.detail__specs h4 .zh{display:block;font-family:"Alimama DaoLiTi","STLibian",serif;font-style:normal;font-size:22px;letter-spacing:.16em;color:var(--c-fg);margin-bottom:6px}
|
||||
.detail__specs dl{grid-column:5 / span 7;display:grid;grid-template-columns:160px 1fr;gap:0 24px}
|
||||
.detail__specs dt,.detail__specs dd{padding:14px 0;border-top:1px solid var(--c-line-soft)}
|
||||
.detail__specs dt{font-family:"DM Mono",monospace;font-size:12px;letter-spacing:.06em;color:var(--c-mute)}
|
||||
.detail__specs dd{font-family:"STKaiti","KaiTi",serif;font-size:14px;letter-spacing:.05em;text-shadow:0 0 .25px currentColor}
|
||||
@media (max-width:780px){.detail__specs h4,.detail__specs dl{grid-column:1 / -1}.detail__specs dl{grid-template-columns:120px 1fr}}
|
||||
|
||||
.detail__gallery{padding:40px 0 60px;display:grid;grid-template-columns:repeat(12,1fr);gap:8px;margin:0 var(--pad)}
|
||||
.detail__gallery figure{position:relative;overflow:hidden;background:var(--c-line-soft)}
|
||||
.detail__gallery .g-wide{grid-column:span 12}
|
||||
.detail__gallery .g-half{grid-column:span 6}
|
||||
.detail__gallery .g-third{grid-column:span 4}
|
||||
.detail__gallery img{width:100%;height:100%;object-fit:cover;display:block;transition:transform 1.4s var(--ease)}
|
||||
.detail__gallery figure:hover img{transform:scale(1.03)}
|
||||
.detail__gallery .g-half img,.detail__gallery .g-third img{aspect-ratio:4/5}
|
||||
.detail__gallery .g-wide img{aspect-ratio:16/8}
|
||||
.detail__gallery figcaption{position:absolute;left:14px;bottom:12px;color:#fff;font-family:"DM Mono",monospace;font-size:11px;letter-spacing:.16em;text-shadow:0 0 6px rgba(0,0,0,.6)}
|
||||
@media (max-width:780px){.detail__gallery .g-half,.detail__gallery .g-third{grid-column:span 12}}
|
||||
|
||||
.detail__credits{padding:50px var(--pad) 60px;border-top:1px solid var(--c-line-soft);display:grid;grid-template-columns:repeat(12,1fr);gap:var(--grid-gap);margin:0 var(--pad)}
|
||||
.detail__credits h4{grid-column:1 / span 3;align-self:start;padding-top:14px;font-family:"Cormorant Garamond",serif;font-style:italic;font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--c-mute)}
|
||||
.detail__credits h4 .zh{display:block;font-family:"Alimama DaoLiTi","STLibian",serif;font-style:normal;font-size:22px;letter-spacing:.16em;color:var(--c-fg);margin-bottom:6px}
|
||||
.detail__credits dl{grid-column:5 / span 7;display:grid;grid-template-columns:200px 1fr;gap:0 24px}
|
||||
.detail__credits dt,.detail__credits dd{padding:14px 0;border-top:1px solid var(--c-line-soft)}
|
||||
.detail__credits dt{font-family:"Cormorant Garamond",serif;font-style:italic;font-size:12px;letter-spacing:.14em;color:var(--c-mute)}
|
||||
.detail__credits dd{font-family:"STKaiti","KaiTi",serif;font-size:14px;letter-spacing:.04em;text-shadow:0 0 .25px currentColor}
|
||||
@media (max-width:780px){.detail__credits h4,.detail__credits dl{grid-column:1 / -1}.detail__credits dl{grid-template-columns:1fr;gap:0}.detail__credits dt{padding-bottom:0}.detail__credits dd{border-top:0;padding-top:4px}}
|
||||
|
||||
.detail__nav{display:grid;grid-template-columns:1fr auto 1fr;gap:24px;padding:50px var(--pad) 60px;align-items:center;border-top:1px solid var(--c-line-soft);margin:0 var(--pad)}
|
||||
.detail__nav a{display:block}
|
||||
.detail__nav .prev{justify-self:start;text-align:left}
|
||||
.detail__nav .index{justify-self:center;font-family:"DM Mono",monospace;font-size:12px;letter-spacing:.14em;text-transform:lowercase;border-bottom:1px solid currentColor;padding-bottom:4px}
|
||||
.detail__nav .next{justify-self:end;text-align:right}
|
||||
.detail__nav .label{display:block;font-family:"Cormorant Garamond",serif;font-style:italic;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--c-mute);margin-bottom:8px}
|
||||
.detail__nav .name{font-family:"Alimama DaoLiTi","STLibian",serif;font-size:clamp(20px,2.2vw,32px);letter-spacing:.06em;line-height:1.2;color:var(--c-fg);transition:opacity .3s var(--ease)}
|
||||
.detail__nav a:hover .name{opacity:.55}
|
||||
@media (max-width:780px){.detail__nav{grid-template-columns:1fr;gap:24px}.detail__nav .prev,.detail__nav .next,.detail__nav .index{justify-self:start;text-align:left}}
|
||||
</style>
|
||||
</head>
|
||||
<body data-view="home" data-mode="list">
|
||||
|
||||
<!-- 帘幕 -->
|
||||
<div class="curtain" id="curtain">
|
||||
<!-- SWAP-3-CURTAIN-CHAR: 一个汉字(工作室名首字,例如「白」「墨」「素」「青」) -->
|
||||
<div class="curtain__mark">SWAP-3</div>
|
||||
</div>
|
||||
|
||||
<div class="paper" aria-hidden="true"></div>
|
||||
|
||||
<!-- Logo(左上)-->
|
||||
<a id="logo" href="#top" aria-label="返回首页">
|
||||
<svg viewBox="0 0 60 80" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect class="stroke" x="4" y="4" width="52" height="60"/>
|
||||
<!-- SWAP-4-LOGO-CHAR: 一个汉字(与 SWAP-3 同字)-->
|
||||
<text class="fill" x="30" y="44" text-anchor="middle" font-family="Alimama DaoLiTi, STLibian, serif" font-size="32">字</text>
|
||||
<!-- SWAP-5-LOGO-PINYIN: 拼音(5-6 个英文字母,全大写,例如 BAIWU / MOJI / SUYUAN)-->
|
||||
<text class="label" x="30" y="76" text-anchor="middle">PINYIN</text>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- 主导航(右上)-->
|
||||
<nav id="main-menu" aria-label="主导航">
|
||||
<ul>
|
||||
<!-- SWAP-6-NAV: 通常 3 项,第一项是当前页 -->
|
||||
<li class="current"><a href="#top">作品</a></li>
|
||||
<li><a href="#about">关于</a></li>
|
||||
<li><a href="#contact">联系</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 主题切换 -->
|
||||
<div class="theme-toggle" role="group" aria-label="主题切换">
|
||||
<button data-theme-set="light" data-active="1">Light</button>
|
||||
<button data-theme-set="dark">Dark</button>
|
||||
</div>
|
||||
|
||||
<!-- 列表 / 画册 切换 -->
|
||||
<nav id="project-view" aria-label="视图切换">
|
||||
<ul>
|
||||
<li class="lista current" data-mode-set="list">
|
||||
<a href="#top">
|
||||
<span class="icon"><svg width="10" height="10" viewBox="0 0 10 10"><rect width="10" height="2"/><rect y="4" width="10" height="2"/><rect y="8" width="10" height="2"/></svg></span>
|
||||
<span>列表</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="galeria" data-mode-set="gallery">
|
||||
<a href="#top">
|
||||
<span class="icon"><svg width="10" height="10" viewBox="0 0 10 10"><rect width="4" height="4"/><rect x="6" width="4" height="4"/><rect y="6" width="4" height="4"/><rect x="6" y="6" width="4" height="4"/></svg></span>
|
||||
<span>画册</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- ========= HOME VIEW ========= -->
|
||||
<main class="view-home" id="top">
|
||||
|
||||
<!-- 品牌区 -->
|
||||
<section class="brand-block">
|
||||
<!-- SWAP-7-BRAND-ZH: 工作室中文名(2-4 字最佳,例如「白屋」「素园」「墨记」)-->
|
||||
<span class="zh">字 名</span>
|
||||
<!-- SWAP-8-BRAND-EN: 拼音 · 城市,例如 "Baiwu Studio · Hangzhou" -->
|
||||
<span class="en">SWAP-8 Studio · City</span>
|
||||
<!-- SWAP-9-TAGLINE: 一句话定位,可换行(用 <br>),20-40 字,楷体气质 -->
|
||||
<p class="tagline">学科一、学科二、学科三 ——<br>一句关于工作方式的低调描述</p>
|
||||
</section>
|
||||
|
||||
<!-- 列表视图(默认)· hover 行 → 右侧大图浮现 -->
|
||||
<div class="grid">
|
||||
<section id="projects-list" class="col-10">
|
||||
<div class="itens">
|
||||
<!-- SWAP-10-LIST-ITEMS: N 件作品的横排卡。每件 <a class="item"> 包含:
|
||||
num - 罗马数字 i. ii. iii. ...
|
||||
project-name - 中文作品名(隶书大字)
|
||||
en-name - 英文副题(hover 时浮现)
|
||||
location - 「地点,年份 · 工艺/规格」(hover 时浮现)
|
||||
photo-thumb img - 4:3 缩略图(hover 时浮现在右侧)
|
||||
href 必须是 #work/{slug},slug 与 JSON 数据匹配 -->
|
||||
|
||||
<a href="#work/example-one" class="item active">
|
||||
<span class="num">i.</span>
|
||||
<h2 class="project-name">作品一</h2>
|
||||
<span class="en-name">First Work</span>
|
||||
<span class="location">城市,年份 · 工艺标签</span>
|
||||
<div class="photo-thumb">
|
||||
<div class="photo-grid"><img alt="" src="https://picsum.photos/id/143/900/1200"></div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="#work/example-two" class="item">
|
||||
<span class="num">ii.</span>
|
||||
<h2 class="project-name">作品二</h2>
|
||||
<span class="en-name">Second Work</span>
|
||||
<span class="location">城市,年份 · 工艺标签</span>
|
||||
<div class="photo-thumb">
|
||||
<div class="photo-grid"><img alt="" src="https://picsum.photos/id/165/900/1200"></div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 画册视图(与列表同步,slug 必须一致)-->
|
||||
<div class="grid">
|
||||
<section id="projects-gallery" class="col-10">
|
||||
<ul>
|
||||
<!-- SWAP-11-GALLERY-ITEMS: 与 list 同样的 N 件作品,更紧凑的网格视图 -->
|
||||
<li><a href="#work/example-one">
|
||||
<div class="image"><img alt="" src="https://picsum.photos/id/143/900/1200"></div>
|
||||
<span class="ga-title">作品一</span>
|
||||
</a></li>
|
||||
<li><a href="#work/example-two">
|
||||
<div class="image"><img alt="" src="https://picsum.photos/id/165/900/1200"></div>
|
||||
<span class="ga-title">作品二</span>
|
||||
</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 关于 -->
|
||||
<section class="about-strip" id="about">
|
||||
<div class="inner">
|
||||
<!-- SWAP-12-ABOUT-TITLE: 中文标题 + 英文副题 -->
|
||||
<h3>关 于 工 作 室<span class="en">About the Studio</span></h3>
|
||||
<!-- SWAP-13-ABOUT-PARAGRAPHS: 1-3 段楷体段落,每段 60-120 字,散文感 -->
|
||||
<p>第一段:成立背景、地点、动机。</p>
|
||||
<p>第二段:主理人简介、师承、设计观。</p>
|
||||
<!-- SWAP-14-ABOUT-META: 4-6 项事实(成立年、城市、学科、主理人、开放时间等) -->
|
||||
<ul class="meta-list">
|
||||
<li><span class="k">Founded</span><span class="v">年份</span></li>
|
||||
<li><span class="k">Location</span><span class="v">城市 · 地址</span></li>
|
||||
<li><span class="k">Discipline</span><span class="v">学科 · 学科 · 学科</span></li>
|
||||
<li><span class="k">Director</span><span class="v">主理人姓名</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 联系 -->
|
||||
<section class="about-strip" id="contact">
|
||||
<div class="inner">
|
||||
<!-- SWAP-15-CONTACT-TITLE -->
|
||||
<h3>来 信<span class="en">Get in Touch</span></h3>
|
||||
<!-- SWAP-16-CONTACT-PARAGRAPHS: 1-2 段,邀请来信的语气 -->
|
||||
<p>一句邀请语 + 一句关于回信周期的承诺。</p>
|
||||
<!-- SWAP-17-CONTACT-META: 4-6 项联系方式 -->
|
||||
<ul class="meta-list">
|
||||
<li><span class="k">E-mail</span><span class="v">hi@example.cn</span></li>
|
||||
<li><span class="k">Phone</span><span class="v">+86 ...</span></li>
|
||||
<li><span class="k">Address</span><span class="v">城市 · 详细地址</span></li>
|
||||
<li><span class="k">Instagram</span><span class="v">@handle</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- DETAIL VIEW(动态渲染 · 不要改)-->
|
||||
<main class="view-detail" id="detail-root" aria-live="polite"></main>
|
||||
|
||||
<!-- 旋转 credit -->
|
||||
<!-- SWAP-18-CREDIT: 通常是 "Site by {WHO}" -->
|
||||
<a id="svp-credit" href="#top">Site by<span>SWAP-18</span></a>
|
||||
|
||||
<footer>
|
||||
<!-- SWAP-19-FOOTER: 3 块 — 版权 / ICP / 社交链接 -->
|
||||
<div>© Anno {Y1} — {Y2} · {NAME} Studio</div>
|
||||
<div>{省 ICP 备 编号}</div>
|
||||
<div class="links">
|
||||
<a href="#">Instagram</a>
|
||||
<a href="#">Are.na</a>
|
||||
<a href="#">公众号</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- ========= 作品数据(核心)=========
|
||||
SWAP-20-WORKS-DATA:每件作品一条;slug 必须和上方两个视图里 #work/{slug} 完全一致。
|
||||
字段说明:
|
||||
slug 小写英文 + 连字符,URL ID
|
||||
title_zh 中文标题(2-5 字最佳)
|
||||
title_en 英文标题(书名号风格的副题)
|
||||
year 年份或干支年(例如 "甲辰 / 2024")
|
||||
tag 顶部分类标签(带方括号)
|
||||
cover 详情页大图 URL(必填)
|
||||
meta 4 项 {label, value}(详情页 4 列摘要)
|
||||
lead 开篇大字段,30-70 字,可用 <em>词</em> 强调
|
||||
prose 3-4 段散文,每段 80-150 字
|
||||
specs 6-10 行规格表 [["Key / 中文键", "值"], ...]
|
||||
gallery 3-6 张图,layout 三选一:g-wide / g-half / g-third
|
||||
credits 4-7 对 [["职位 / Role", "名字"], ...]
|
||||
-->
|
||||
<script type="application/json" id="works-data">
|
||||
[
|
||||
{
|
||||
"slug": "example-one",
|
||||
"title_zh": "作品一",
|
||||
"title_en": "First Work — A Subtitle",
|
||||
"year": "甲辰 / 2024",
|
||||
"tag": "[ 分类 · 子分类 ]",
|
||||
"cover": "https://picsum.photos/id/143/1800/1100",
|
||||
"meta": [
|
||||
{"label": "Type / 类型", "value": "类型描述"},
|
||||
{"label": "Year / 年份", "value": "2024"},
|
||||
{"label": "Edition / 印量", "value": "首印 320 本"},
|
||||
{"label": "Format / 开本", "value": "130 × 195 毫米"}
|
||||
],
|
||||
"lead": "一段开篇陈述:作品的核心动机、形式与你最想留给观众的那一句话。可以在关键词上用 <em>关键词</em> 这种小处理。",
|
||||
"prose": [
|
||||
"第一段:作品的语境——时间、地点、为什么开始。80-150 字。",
|
||||
"第二段:创作过程中的某个决定性的细节——一次相遇、一次失败、一次偶然。",
|
||||
"第三段:最后的呈现形式 + 留给观众的感受。"
|
||||
],
|
||||
"specs": [
|
||||
["开本 / Format", "130 × 195 毫米"],
|
||||
["页数 / Pages", "144 页"],
|
||||
["纸张 / Paper", "本色棉纸 90gsm"],
|
||||
["装订 / Binding", "线装四目 · 棉麻线"],
|
||||
["字体 / Typeface", "字体名称"],
|
||||
["印刷 / Printing", "印刷工艺 · 印厂名"],
|
||||
["印量 / Edition", "首印 320 本"],
|
||||
["定价 / Price", "¥ 388"]
|
||||
],
|
||||
"gallery": [
|
||||
{"src": "https://picsum.photos/id/143/1600/900", "layout": "g-wide", "caption": "封面"},
|
||||
{"src": "https://picsum.photos/id/40/800/1000", "layout": "g-half", "caption": "内文跨页"},
|
||||
{"src": "https://picsum.photos/id/88/800/1000", "layout": "g-half"},
|
||||
{"src": "https://picsum.photos/id/121/600/750", "layout": "g-third"},
|
||||
{"src": "https://picsum.photos/id/175/600/750", "layout": "g-third"},
|
||||
{"src": "https://picsum.photos/id/200/600/750", "layout": "g-third"}
|
||||
],
|
||||
"credits": [
|
||||
["设计 / Design", "主理人姓名"],
|
||||
["排版 / Typesetting", "助理姓名"],
|
||||
["手工装订 / Binding", "合作工坊"],
|
||||
["特别鸣谢 / Acknowledgement", "致谢对象"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "example-two",
|
||||
"title_zh": "作品二",
|
||||
"title_en": "Second Work",
|
||||
"year": "癸卯 / 2023",
|
||||
"tag": "[ 分类 ]",
|
||||
"cover": "https://picsum.photos/id/165/1800/1100",
|
||||
"meta": [
|
||||
{"label": "Type / 类型", "value": "类型描述"},
|
||||
{"label": "Year / 年份", "value": "2023"},
|
||||
{"label": "Glyphs / 字符", "value": "1,072 字"},
|
||||
{"label": "Use / 用途", "value": "用途描述"}
|
||||
],
|
||||
"lead": "一句开篇陈述。可以在关键词上用 <em>关键词</em> 强调。",
|
||||
"prose": [
|
||||
"第一段:起源。80-150 字。",
|
||||
"第二段:方法 / 决定。",
|
||||
"第三段:呈现 / 成果。"
|
||||
],
|
||||
"specs": [
|
||||
["参数一 / Spec One", "值"],
|
||||
["参数二 / Spec Two", "值"],
|
||||
["参数三 / Spec Three", "值"],
|
||||
["参数四 / Spec Four", "值"]
|
||||
],
|
||||
"gallery": [
|
||||
{"src": "https://picsum.photos/id/165/1600/900", "layout": "g-wide"},
|
||||
{"src": "https://picsum.photos/id/0/800/1000", "layout": "g-half"},
|
||||
{"src": "https://picsum.photos/id/20/800/1000", "layout": "g-half"}
|
||||
],
|
||||
"credits": [
|
||||
["主创 / Lead", "姓名"],
|
||||
["合作 / In collaboration with", "合作方"]
|
||||
]
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<script>
|
||||
/* ============ 帘幕收起 ============ */
|
||||
window.addEventListener('load', ()=>{
|
||||
setTimeout(()=>{
|
||||
const c = document.getElementById('curtain');
|
||||
if (c) c.classList.add('is-out');
|
||||
}, 1100);
|
||||
applyRoute();
|
||||
initSvpCredit();
|
||||
});
|
||||
|
||||
/* ============ Theme toggle ============ */
|
||||
(function themeToggle(){
|
||||
const root = document.documentElement;
|
||||
const buttons = document.querySelectorAll('[data-theme-set]');
|
||||
const saved = localStorage.getItem('lishu-theme');
|
||||
if (saved === 'dark' || saved === 'light'){
|
||||
root.setAttribute('data-theme', saved);
|
||||
buttons.forEach(b=> b.dataset.active = (b.dataset.themeSet === saved) ? '1' : '0');
|
||||
}
|
||||
buttons.forEach(btn=>{
|
||||
btn.addEventListener('click', ()=>{
|
||||
const t = btn.dataset.themeSet;
|
||||
root.setAttribute('data-theme', t);
|
||||
localStorage.setItem('lishu-theme', t);
|
||||
buttons.forEach(b=> b.dataset.active = (b === btn) ? '1' : '0');
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
/* ============ List / Gallery 视图切换 ============ */
|
||||
(function viewMode(){
|
||||
const items = document.querySelectorAll('#project-view li[data-mode-set]');
|
||||
function setMode(mode){
|
||||
document.body.dataset.mode = mode;
|
||||
items.forEach(li=>{
|
||||
li.classList.toggle('current', li.dataset.modeSet === mode);
|
||||
});
|
||||
localStorage.setItem('lishu-mode', mode);
|
||||
}
|
||||
const saved = localStorage.getItem('lishu-mode');
|
||||
if (saved === 'gallery' || saved === 'list') setMode(saved);
|
||||
items.forEach(li=>{
|
||||
const link = li.querySelector('a');
|
||||
link.addEventListener('click', (e)=>{
|
||||
e.preventDefault();
|
||||
setMode(li.dataset.modeSet);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
/* ============ Hover-row 显示缩略图 ============ */
|
||||
(function projectListHover(){
|
||||
const list = document.getElementById('projects-list');
|
||||
if (!list) return;
|
||||
const items = list.querySelectorAll('.item');
|
||||
function resetVisibility(){
|
||||
items.forEach((item, i)=>{ item.classList.toggle('active', i === 0); });
|
||||
}
|
||||
items.forEach(item=>{
|
||||
item.addEventListener('mouseenter', ()=>{
|
||||
items.forEach(i => i.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
});
|
||||
});
|
||||
list.addEventListener('mouseleave', resetVisibility);
|
||||
resetVisibility();
|
||||
})();
|
||||
|
||||
/* ============ 旋转 credit ============ */
|
||||
function initSvpCredit(){
|
||||
const credit = document.getElementById('svp-credit');
|
||||
if (!credit) return;
|
||||
const sentinel = document.createElement('div');
|
||||
sentinel.style.height = '1px';
|
||||
document.body.appendChild(sentinel);
|
||||
const observer = new IntersectionObserver(entries=>{
|
||||
entries.forEach(entry=>{
|
||||
if (entry.isIntersecting) credit.classList.add('visible');
|
||||
else credit.classList.remove('visible');
|
||||
});
|
||||
}, { threshold:0, rootMargin:'0px 0px 300px 0px' });
|
||||
observer.observe(sentinel);
|
||||
}
|
||||
|
||||
/* ============ 详情页路由 ============ */
|
||||
const WORKS = (() => {
|
||||
try { return JSON.parse(document.getElementById('works-data').textContent); }
|
||||
catch(e){ console.error('works-data JSON parse failed:', e); return []; }
|
||||
})();
|
||||
const BY_SLUG = Object.fromEntries(WORKS.map(w => [w.slug, w]));
|
||||
|
||||
function escapeHTML(s){ return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
||||
|
||||
function renderDetail(slug){
|
||||
const w = BY_SLUG[slug];
|
||||
const root = document.getElementById('detail-root');
|
||||
if (!w){
|
||||
root.innerHTML = `<div class="detail__back-row"><a class="detail__back" href="#top"><span class="arrow">←</span>返回首页 / back to index</a></div><div style="padding:120px var(--pad);text-align:center"><h2 style="font-family:'Alimama DaoLiTi',serif;font-size:48px;letter-spacing:.1em">作品不存在</h2></div>`;
|
||||
return;
|
||||
}
|
||||
const idx = WORKS.findIndex(x => x.slug === slug);
|
||||
const prev = WORKS[(idx - 1 + WORKS.length) % WORKS.length];
|
||||
const next = WORKS[(idx + 1) % WORKS.length];
|
||||
|
||||
const isVideo = /\.(mp4|webm|mov)(\?|$)/i.test(w.cover);
|
||||
const heroMedia = isVideo
|
||||
? `<video src="${escapeHTML(w.cover)}" autoplay muted loop playsinline></video>`
|
||||
: `<img src="${escapeHTML(w.cover)}" alt="${escapeHTML(w.title_zh||'')}">`;
|
||||
|
||||
const metaHTML = (w.meta||[]).map(m => `<dl><dt>${escapeHTML(m.label)}</dt><dd>${escapeHTML(m.value)}</dd></dl>`).join('');
|
||||
const proseHTML = (w.prose||[]).map(p => `<p>${p}</p>`).join('');
|
||||
const galleryHTML = (w.gallery||[]).map(g => {
|
||||
const layout = g.layout || 'g-half';
|
||||
const cap = g.caption ? `<figcaption>${escapeHTML(g.caption)}</figcaption>` : '';
|
||||
return `<figure class="${layout}"><img src="${escapeHTML(g.src)}" alt="">${cap}</figure>`;
|
||||
}).join('');
|
||||
const specsHTML = (w.specs||[]).map(([k, v]) => `<dt>${k}</dt><dd>${v}</dd>`).join('');
|
||||
const creditsHTML = (w.credits||[]).map(([role, name]) => `<dt>${role}</dt><dd>${name}</dd>`).join('');
|
||||
|
||||
root.innerHTML = `
|
||||
<div class="detail__back-row">
|
||||
<a class="detail__back" href="#top"><span class="arrow">←</span>back to index / 返回索引</a>
|
||||
</div>
|
||||
<header class="detail__head">
|
||||
<p class="detail__tag">${escapeHTML(w.tag||'')}</p>
|
||||
<div class="detail__title">
|
||||
<span class="zh">${escapeHTML(w.title_zh||'')}</span>
|
||||
<span class="en">${escapeHTML(w.title_en||'')}</span>
|
||||
</div>
|
||||
<div class="detail__year">${escapeHTML(w.year||'')}</div>
|
||||
</header>
|
||||
<div class="detail__hero">${heroMedia}</div>
|
||||
${metaHTML ? `<div class="detail__meta">${metaHTML}</div>` : ''}
|
||||
<section class="detail__body">
|
||||
${w.lead ? `<p class="detail__lead">${w.lead}</p>` : ''}
|
||||
${proseHTML ? `<div class="detail__prose">${proseHTML}</div>` : ''}
|
||||
</section>
|
||||
${specsHTML ? `<section class="detail__specs"><h4><span class="zh">规 格</span>Specifications</h4><dl>${specsHTML}</dl></section>` : ''}
|
||||
${galleryHTML ? `<div class="detail__gallery">${galleryHTML}</div>` : ''}
|
||||
${creditsHTML ? `<section class="detail__credits"><h4><span class="zh">致 谢</span>Credits</h4><dl>${creditsHTML}</dl></section>` : ''}
|
||||
<nav class="detail__nav" aria-label="作品导航">
|
||||
<a class="prev" href="#work/${escapeHTML(prev.slug)}">
|
||||
<span class="label">← previous · 前一件</span>
|
||||
<span class="name">${escapeHTML(prev.title_zh||'')}</span>
|
||||
</a>
|
||||
<a class="index" href="#top">all works / 全部作品</a>
|
||||
<a class="next" href="#work/${escapeHTML(next.slug)}">
|
||||
<span class="label">next · 下一件 →</span>
|
||||
<span class="name">${escapeHTML(next.title_zh||'')}</span>
|
||||
</a>
|
||||
</nav>
|
||||
`;
|
||||
}
|
||||
|
||||
function applyRoute(){
|
||||
const h = location.hash || '';
|
||||
if (h.startsWith('#work/')){
|
||||
const slug = h.slice(6);
|
||||
document.body.dataset.view = 'detail';
|
||||
renderDetail(slug);
|
||||
window.scrollTo({top: 0, behavior: 'instant'});
|
||||
} else {
|
||||
document.body.dataset.view = 'home';
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', applyRoute);
|
||||
|
||||
document.querySelectorAll('a[href^="#"]').forEach(a=>{
|
||||
a.addEventListener('click', (e)=>{
|
||||
const id = a.getAttribute('href');
|
||||
if (id.length<2) return;
|
||||
if (id.startsWith('#work/')) return;
|
||||
const el = document.querySelector(id);
|
||||
if (!el) return;
|
||||
if (id === '#top'){ e.preventDefault(); window.scrollTo({top:0, behavior:'smooth'}); return; }
|
||||
e.preventDefault();
|
||||
const top = el.getBoundingClientRect().top + window.scrollY - 40;
|
||||
window.scrollTo({top, behavior:'smooth'});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user