Files
stack/design-refs/mockup/course-shelf.html
T
cqh963852 714210cc87 design: 课程书架设计稿(5 页横版 PDF + HTML 源 + 可复现构建脚本)
- 设计稿:封面 / 整体布局 / 卡片解剖与悬停态 / 交互与 GIF 资产规范 / 设计 tokens
- visual-judge 视觉验收 5/5 通过(四轮迭代:fixed 布局改走 html2poster 单页渲染)
- build.sh 固化 拆页→渲染→合并 管线,single/ 中间产物已忽略
2026-10-02 07:58:05 +08:00

700 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>课程书架 设计稿 · stack idealjs</title>
<style>
/* ====== 画布与页面基座 ====== */
@page { size: 1280px 800px; margin: 0; }
*, *::before, *::after { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
width: 1280px;
background: #f8fafc;
font-family: "Noto Sans CJK SC", "PingFang SC", "Microsoft YaHei", sans-serif;
color: #0f172a;
-webkit-font-smoothing: antialiased;
}
.page {
width: 1280px;
height: 800px;
position: relative;
background: #f8fafc;
overflow: hidden;
page-break-after: always;
}
.page:last-child { page-break-after: auto; }
/* 浏览器预览自适应缩放(仅 screen 生效,print 不受影响) */
@media screen {
html {
height: auto;
display: flex;
justify-content: center;
background: #f8fafc;
}
body {
transform-origin: top center;
scale: min(1, calc(100vw / 1280px));
margin: 0 auto;
box-shadow: 0 0 48px rgba(15, 23, 42, 0.18);
}
}
/* ====== 通用元素 ====== */
.kicker {
font-size: 15px;
letter-spacing: 4px;
font-weight: 500;
color: #475569;
text-transform: uppercase;
}
.pagenum {
position: absolute;
bottom: 28px;
right: 40px;
font-size: 13px;
color: #64748b;
letter-spacing: 2px;
}
.footline {
position: absolute;
bottom: 28px;
left: 40px;
font-size: 13px;
color: #64748b;
letter-spacing: 1px;
}
.rule { height: 1px; background: #cbd5e1; }
/* 页首条 */
.pagehead {
position: absolute;
top: 44px;
left: 40px;
right: 40px;
display: flex;
align-items: baseline;
justify-content: space-between;
}
.pagehead h2 { margin: 0; font-size: 30px; font-weight: 700; color: #0f172a; }
.pagehead .sub { font-size: 15px; color: #475569; }
/* 标注组件 */
.note {
display: flex;
gap: 10px;
align-items: flex-start;
margin-bottom: 14px;
}
.note .idx {
flex: none;
width: 22px;
height: 22px;
border-radius: 50%;
background: #0f172a;
color: #ffffff;
font-size: 13px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
margin-top: 1px;
}
.note .txt { font-size: 14.5px; line-height: 1.55; color: #334155; }
.note .txt b { color: #0f172a; }
.panel {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 22px 24px;
}
.panel h3 {
margin: 0 0 14px;
font-size: 17px;
font-weight: 700;
color: #0f172a;
padding-bottom: 10px;
border-bottom: 1px solid #e2e8f0;
}
/* ====== P1 封面 ====== */
.cover { background: #f8fafc; }
.cover .band {
position: absolute;
top: 0; left: 0; right: 0;
height: 8px;
background: linear-gradient(90deg, #6366f1, #ec4899, #f59e0b, #10b981, #0ea5e9, #8b5cf6);
}
.cover .kicker-box { position: absolute; top: 72px; left: 80px; }
.cover h1 {
position: absolute;
top: 240px;
left: 80px;
margin: 0;
font-size: 96px;
line-height: 1.12;
font-weight: 900;
color: #0f172a;
letter-spacing: 2px;
}
.cover .subtitle {
position: absolute;
top: 492px;
left: 80px;
font-size: 22px;
color: #475569;
line-height: 1.6;
}
.cover .meta {
position: absolute;
bottom: 72px;
right: 80px;
text-align: right;
font-size: 15px;
color: #64748b;
line-height: 1.9;
}
.cover .minicards {
position: absolute;
bottom: 72px;
left: 80px;
display: flex;
gap: 14px;
}
.minicard {
width: 92px;
height: 62px;
border-radius: 8px;
background: #ffffff;
border: 1px solid #e2e8f0;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
position: relative;
}
.minicard::before {
content: "";
position: absolute;
top: 10px; left: 10px;
width: 26px; height: 6px;
border-radius: 3px;
background: var(--c);
opacity: 0.85;
}
.minicard::after {
content: "";
position: absolute;
top: 28px; left: 10px;
width: 56px; height: 4px;
border-radius: 2px;
background: #e2e8f0;
box-shadow: 0 8px 0 #e2e8f0, 0 16px 0 #e2e8f0;
}
/* ====== P2 整体布局:浏览器框 mockup ====== */
.browser {
position: absolute;
top: 130px;
left: 40px;
width: 760px;
height: 596px;
background: #ffffff;
border: 1px solid #dbe2ea;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.10);
}
.browser .bar {
height: 38px;
border-bottom: 1px solid #e8edf3;
display: flex;
align-items: center;
gap: 8px;
padding: 0 16px;
}
.dot { width: 10px; height: 10px; border-radius: 50%; background: #e2e8f0; }
.browser .url {
margin-left: 12px;
flex: 1;
height: 22px;
border-radius: 11px;
background: #f1f5f9;
color: #64748b;
font-size: 12px;
display: flex;
align-items: center;
padding: 0 12px;
}
/* mock 首页 hero */
.mock-hero {
height: 96px;
background: #2f4bad;
color: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
}
.mock-hero .t { font-size: 20px; font-weight: 700; }
.mock-hero .btns { display: flex; gap: 10px; }
.mock-hero .btn {
font-size: 11px;
padding: 5px 12px;
border-radius: 999px;
border: 1px solid rgba(255,255,255,0.55);
}
/* mock 卡片网格 */
.mock-shelf { padding: 18px 22px 22px; }
.mock-shelf .shelf-title { font-size: 15px; font-weight: 700; color: #0f172a; text-align: center; }
.mock-shelf .shelf-sub { font-size: 11px; color: #64748b; text-align: center; margin: 4px 0 14px; }
.mock-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.mock-card {
border: 1px solid #e2e8f0;
border-radius: 9px;
background: #ffffff;
position: relative;
padding: 8px 10px 10px 12px;
}
.mock-card .spine {
position: absolute; left: 0; top: 0; bottom: 0;
width: 3px; border-radius: 9px 0 0 9px;
background: var(--c);
}
.mock-card .ph-media {
height: 56px;
border-radius: 6px;
background: linear-gradient(135deg, #101a30, #1e293b);
display: flex; align-items: center; justify-content: center;
color: #64748b; font-size: 10px;
}
.mock-card .ph-title { margin-top: 8px; font-size: 12.5px; font-weight: 700; color: #0f172a; display: flex; justify-content: space-between; align-items: center; }
.mock-card .badge { font-size: 9px; border-radius: 999px; padding: 2px 7px; white-space: nowrap; }
.badge.done { color: #047857; background: #d1fae5; }
.badge.wip { color: #92400e; background: #fef3c7; }
.mock-card .ph-desc { margin-top: 5px; font-size: 10px; line-height: 1.5; color: #475569; }
/* 标注锚点 */
.pin {
position: absolute;
width: 24px; height: 24px;
border-radius: 50%;
background: #0f172a; color: #fff;
font-size: 13px; font-weight: 700;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 0 0 4px rgba(15,23,42,0.12);
z-index: 5;
}
.coverflow-note {
position: absolute;
top: 130px;
left: 830px;
width: 410px;
}
/* ====== P3 卡片解剖 ====== */
.bigcard {
position: absolute;
top: 168px;
width: 384px;
background: #ffffff;
border: 1px solid #dbe2ea;
border-radius: 14px;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.10);
}
.bigcard.hovered {
box-shadow: 0 14px 36px rgba(15, 23, 42, 0.22);
border-color: #6366f1;
transform: translateY(-6px);
}
.bigcard .media {
height: 200px;
border-radius: 13px 13px 0 0;
background: linear-gradient(135deg, #0f172a, #1e293b);
display: flex; align-items: center; justify-content: center;
color: #94a3b8; font-size: 14px;
gap: 8px;
overflow: hidden;
}
.bigcard .media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bigcard .spinebar {
position: absolute; left: 0; top: 0; bottom: 0;
width: 5px;
background: #6366f1;
border-radius: 14px 0 0 14px;
opacity: 0.45;
}
.bigcard.hovered .spinebar { opacity: 1; }
.bigcard .cbody { padding: 16px 18px 16px 22px; }
.bigcard .crow { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.bigcard .ctitle { font-size: 19px; font-weight: 700; color: #0f172a; margin: 0; }
.bigcard.hovered .ctitle { color: #6366f1; }
.bigcard .cbadge { flex: none; font-size: 12px; border-radius: 999px; padding: 3px 10px; }
.bigcard .cdesc { margin: 10px 0 0; font-size: 14.5px; line-height: 1.6; color: #334155; }
.bigcard .chips { margin-top: 14px; display: flex; gap: 8px; min-height: 24px; align-items: center; }
.chip {
font-size: 12px;
color: #6366f1;
border: 1px solid #6366f1;
border-radius: 999px;
padding: 2px 10px;
}
.chip.ghost { color: #94a3b8; border-color: #cbd5e1; }
.leadline { position: absolute; height: 1px; background: #94a3b8; z-index: 4; }
.leaddot { position: absolute; width: 7px; height: 7px; border-radius: 50%; background: #0f172a; z-index: 4; }
.legendbox { position: absolute; width: 236px; }
/* ====== P4 交互与资产 ====== */
.flow {
display: flex;
align-items: stretch;
gap: 0;
margin-top: 6px;
}
.flow .step {
flex: 1;
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 10px;
padding: 14px 14px 12px;
position: relative;
margin-right: 26px;
}
.flow .step:last-child { margin-right: 0; }
.flow .step::after {
content: "";
position: absolute;
right: -20px; top: 50%;
width: 14px; height: 14px;
border-top: 2px solid #94a3b8;
border-right: 2px solid #94a3b8;
transform: translateY(-50%) rotate(45deg);
}
.flow .step:last-child::after { display: none; }
.flow .s-no { font-size: 12px; color: #6366f1; font-weight: 700; letter-spacing: 1px; }
.flow .s-t { font-size: 15px; font-weight: 700; color: #0f172a; margin: 4px 0 6px; }
.flow .s-d { font-size: 12.5px; line-height: 1.55; color: #475569; }
table.spec {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
color: #334155;
}
table.spec th {
text-align: left;
font-weight: 700;
color: #0f172a;
border-bottom: 2px solid #cbd5e1;
padding: 8px 10px 8px 0;
}
table.spec td { border-bottom: 1px solid #e8edf3; padding: 9px 10px 9px 0; vertical-align: top; }
table.spec td.k { color: #0f172a; font-weight: 600; white-space: nowrap; }
/* ====== P5 规范 tokens ====== */
.swatches { display: flex; gap: 12px; flex-wrap: wrap; }
.swatch { width: 108px; }
.swatch .c {
height: 54px;
border-radius: 9px;
border: 1px solid rgba(15,23,42,0.08);
}
.swatch .n { font-size: 12.5px; font-weight: 700; color: #0f172a; margin-top: 7px; }
.swatch .h { font-size: 11.5px; color: #64748b; }
/* 状态演示小卡 */
.statecard {
width: 210px;
border-radius: 10px;
background: #ffffff;
border: 1px solid #e2e8f0;
padding: 10px 12px;
box-shadow: 0 2px 6px rgba(15,23,42,0.08);
}
.statecard.hov {
border-color: #6366f1;
box-shadow: 0 10px 26px rgba(15,23,42,0.20);
transform: translateY(-4px);
}
.statecard .m { height: 66px; border-radius: 6px; background: linear-gradient(135deg, #0f172a, #1e293b); }
.statecard .t { font-size: 13px; font-weight: 700; color: #0f172a; margin-top: 8px; }
.statecard.hov .t { color: #6366f1; }
</style>
</head>
<body>
<!-- ================= P1 封面 ================= -->
<div class="page cover">
<div class="band"></div>
<div class="kicker-box">
<div class="kicker">STACK IDEALJS · 全栈工程师教程</div>
<div class="rule" style="width: 220px; margin-top: 14px;"></div>
</div>
<h1>课程书架<br>设计稿</h1>
<div class="subtitle">
课程预览从目录树改为卡片书架 · 每卡承载 title / desc / done / relations<br>
悬停卡片即循环播放该部分「学完能得到什么」的预期成果 GIF
</div>
<div class="minicards">
<div class="minicard" style="--c:#6366f1"></div>
<div class="minicard" style="--c:#ec4899"></div>
<div class="minicard" style="--c:#f59e0b"></div>
<div class="minicard" style="--c:#10b981"></div>
<div class="minicard" style="--c:#0ea5e9"></div>
<div class="minicard" style="--c:#8b5cf6"></div>
</div>
<div class="meta">
v1.0 · 2026-10<br>
分支 feat/course-shelf<br>
参考 00 · E-learning Learning Plan Dashboard(Dribbble,仅灵感)
</div>
</div>
<!-- ================= P2 整体布局 ================= -->
<div class="page">
<div class="pagehead">
<h2>01 整体布局 · 首页桌面态</h2>
<span class="sub">视口 1440px · 卡片网格 auto-fill minmax(280px, 1fr)</span>
</div>
<div class="browser">
<div class="bar">
<div class="dot"></div><div class="dot"></div><div class="dot"></div>
<div class="url">idealjs.github.io/stack/</div>
</div>
<div class="mock-hero">
<div class="t">stack idealjs</div>
<div class="btns"><span class="btn">快速开始 - 5min ⏱</span><span class="btn">参与讨论</span></div>
</div>
<div class="mock-shelf">
<div class="shelf-title">全栈工程师教程 · 课程书架</div>
<div class="shelf-sub">共 6 个部分。悬停卡片,预览学完这一部分能得到什么。</div>
<div class="mock-grid">
<div class="mock-card" style="--c:#6366f1"><span class="spine"></span>
<div class="ph-media">▶ 悬停预览预期成果</div>
<div class="ph-title">快速开始 <span class="badge done">✓ 已完成</span></div>
<div class="ph-desc">写出第一个网页并在浏览器打开,跑起你的第一个 HTTP 服务器。</div>
</div>
<div class="mock-card" style="--c:#ec4899"><span class="spine"></span>
<div class="ph-media">▶ 悬停预览预期成果</div>
<div class="ph-title">快速开始 React <span class="badge done">✓ 已完成</span></div>
<div class="ph-desc">用脚手架创建 React 应用,亲手拼出第一个 Todo List。</div>
</div>
<div class="mock-card" style="--c:#f59e0b"><span class="spine"></span>
<div class="ph-media">▶ 悬停预览预期成果</div>
<div class="ph-title">更多示例 <span class="badge done">✓ 已完成</span></div>
<div class="ph-desc">浮动布局等补充示例,巩固前面攒下的积木。</div>
</div>
<div class="mock-card" style="--c:#10b981"><span class="spine"></span>
<div class="ph-media">▶ 悬停预览预期成果</div>
<div class="ph-title">更多 <span class="badge done">✓ 已完成</span></div>
<div class="ph-desc">把示例工程构建为可发布产物,看清 build 到底做了什么。</div>
</div>
<div class="mock-card" style="--c:#0ea5e9"><span class="spine"></span>
<div class="ph-media">▶ 悬停预览预期成果</div>
<div class="ph-title">全栈基础 <span class="badge wip">写作中</span></div>
<div class="ph-desc">从网页到服务与数据库,串起完整的最小全栈闭环。</div>
</div>
<div class="mock-card" style="--c:#8b5cf6"><span class="spine"></span>
<div class="ph-media">▶ 悬停预览预期成果</div>
<div class="ph-title">高级教程 <span class="badge done">✓ 已完成</span></div>
<div class="ph-desc">数据流、纯函数、CAP——工程上的心智模型。</div>
</div>
</div>
</div>
<!-- 标注锚点 -->
<span class="pin" style="top: 73px; left: 740px;">1</span>
<span class="pin" style="top: 158px; left: 740px;">2</span>
<span class="pin" style="top: 258px; left: 740px;">3</span>
<span class="pin" style="top: 418px; left: 740px;">4</span>
</div>
<div class="coverflow-note">
<div class="panel">
<h3>布局要点</h3>
<div class="note"><span class="idx">1</span><span class="txt"><b>Hero 入口区</b>:保留「快速开始 5min」与「参与讨论」双按钮,合并为单条 hero,去除旧版双 hero 叠放。</span></div>
<div class="note"><span class="idx">2</span><span class="txt"><b>书架标题行</b>:交代总数与悬停玩法,读者未 hover 前即知道卡片有预期成果预览。</span></div>
<div class="note"><span class="idx">3</span><span class="txt"><b>卡片网格</b>:六部分各一卡,颜色书脊区分;顺序 = 侧栏学习顺序(垃圾桶/归档不入架)。</span></div>
<div class="note"><span class="idx">4</span><span class="txt"><b>整卡可点</b>:卡即链接,进入该部分分类索引页;hover 只做预览不导航。</span></div>
</div>
<div class="panel" style="margin-top: 18px;">
<h3>响应式</h3>
<div class="note"><span class="idx">A</span><span class="txt">≥1140px 三列 · 760–1140px 两列 · &lt;760px 单列(触屏降级策略见第 4 页)。</span></div>
</div>
</div>
<div class="footline">课程书架设计稿 · stack idealjs</div>
<div class="pagenum">02 / 05</div>
</div>
<!-- ================= P3 卡片解剖 · 默认态与 Hover 态 ================= -->
<div class="page">
<div class="pagehead">
<h2>02 卡片解剖 · 默认态 → 悬停态</h2>
<span class="sub">GIF 为动画,纸面以静帧 + 标注表达</span>
</div>
<!-- 左:默认态 -->
<div class="bigcard" style="left: 60px; top: 118px;">
<span class="spinebar"></span>
<div class="media"><span>▶</span> 悬停预览预期成果</div>
<div class="cbody">
<div class="crow">
<h3 class="ctitle">快速开始</h3>
<span class="cbadge badge done">✓ 已完成</span>
</div>
<p class="cdesc">写出第一个网页并在浏览器打开,跑起你的第一个 HTTP 服务器。</p>
<div class="chips"><span class="chip ghost">关联章节整理中</span></div>
</div>
</div>
<!-- 默认态标注(卡体 top130 · 媒体区130-346 · 标题行≈360 · 描述≈410 · chips≈462 · 卡底≈506) -->
<span class="pin" style="top: 205px; left: 466px;">1</span>
<span class="pin" style="top: 330px; left: 466px;">2</span>
<span class="pin" style="top: 330px; left: 44px;">3</span>
<span class="pin" style="top: 382px; left: 466px;">4</span>
<span class="pin" style="top: 424px; left: 466px;">5</span>
<span class="pin" style="top: 136px; left: 66px;">6</span>
<div class="legendbox" style="top: 486px; left: 60px; width: 384px;">
<div class="panel" style="padding: 12px 16px;">
<h3 style="margin-bottom: 6px;">默认态要素</h3>
<div class="note" style="margin-bottom: 4px;"><span class="idx">1</span><span class="txt"><b>媒体区占位</b>:16:9 深色底 + 播放提示,GIF 未加载,首屏零动图流量。</span></div>
<div class="note" style="margin-bottom: 4px;"><span class="idx">2</span><span class="txt"><b>标题</b>:章节名,即侧栏分类 label。</span></div>
<div class="note" style="margin-bottom: 4px;"><span class="idx">3</span><span class="txt"><b>完成徽标</b>:✓ 已完成 / 写作中,来自 courses.ts 的 done 字段。</span></div>
<div class="note" style="margin-bottom: 4px;"><span class="idx">4</span><span class="txt"><b>预期结果一句话</b>:desc 写「学完能做出什么」。</span></div>
<div class="note" style="margin-bottom: 0;"><span class="idx">5</span><span class="txt"><b>关系区</b>:关系列出前显示「整理中」,之后渲染 chips。</span></div>
</div>
</div>
<!-- 右:悬停态 -->
<div class="bigcard hovered" style="left: 700px; top: 118px;">
<span class="spinebar"></span>
<div class="media"><img src="assets/hello-world-frame.png" alt="GIF 播放中静帧"></div>
<div class="cbody">
<div class="crow">
<h3 class="ctitle">快速开始</h3>
<span class="cbadge badge done">✓ 已完成</span>
</div>
<p class="cdesc">写出第一个网页并在浏览器打开,跑起你的第一个 HTTP 服务器。</p>
<div class="chips"><span class="chip ghost">关联章节整理中</span></div>
</div>
</div>
<div class="legendbox" style="top: 486px; left: 700px; width: 384px;">
<div class="panel" style="padding: 12px 16px;">
<h3 style="margin-bottom: 6px;">悬停态变化</h3>
<div class="note" style="margin-bottom: 4px;"><span class="idx">6</span><span class="txt"><b>书脊高亮</b>:主题色竖条透明度 45% → 100%。</span></div>
<div class="note" style="margin-bottom: 4px;"><span class="idx">7</span><span class="txt"><b>GIF 挂载</b>:此刻才创建 &lt;img&gt;,按需加载并自动循环播放预期成果。</span></div>
<div class="note" style="margin-bottom: 0;"><span class="idx">8</span><span class="txt"><b>上浮 + 染色</b>:卡体上浮、阴影加深、描边与标题染主题色。</span></div>
</div>
</div>
<div class="footline">课程书架设计稿 · stack idealjs</div>
<div class="pagenum">03 / 05</div>
</div>
<!-- ================= P4 交互与资产 ================= -->
<div class="page">
<div class="pagehead">
<h2>03 交互流程 · 触发矩阵 · GIF 资产规范</h2>
<span class="sub">预期成果 GIF = 靶子「预期结果」的非文字化</span>
</div>
<div style="position:absolute; top: 128px; left: 40px; right: 40px;">
<div class="panel" style="padding-bottom: 18px;">
<h3>悬停播放时序</h3>
<div class="flow">
<div class="step"><div class="s-no">STEP 1</div><div class="s-t">指针进入</div><div class="s-d">mouseenter / focus-visible 触发 previewing=true。</div></div>
<div class="step"><div class="s-no">STEP 2</div><div class="s-t">挂载 img</div><div class="s-d">此刻才渲染 GIF &lt;img&gt;,浏览器按需拉取,首屏不载动图。</div></div>
<div class="step"><div class="s-no">STEP 3</div><div class="s-t">循环播放</div><div class="s-d">GIF loop=0 自动循环,无播放控件,静音无声。</div></div>
<div class="step"><div class="s-no">STEP 4</div><div class="s-t">移出保持缓存</div><div class="s-d">mouseleave 卸载 &lt;img&gt;;HTTP 缓存仍在,再次悬停瞬时恢复。</div></div>
</div>
</div>
</div>
<div style="position:absolute; top: 390px; left: 40px; width: 590px;">
<div class="panel">
<h3>触发矩阵</h3>
<table class="spec">
<tr><th>设备 / 输入</th><th>行为</th></tr>
<tr><td class="k">桌面 · 鼠标 hover</td><td>播放预览,移出停止;点击卡片进入章节。</td></tr>
<tr><td class="k">键盘 · Tab focus</td><td>与 hover 同态(可访问性),Enter 进入章节。</td></tr>
<tr><td class="k">触屏 · 无 hover</td><td>不做预览态;首次点击直接进入章节,避免误触消耗流量。</td></tr>
<tr><td class="k">弱网 / 关闭动图偏好</td><td>后续可选:prefers-reduced-motion 时不自动播放,显示静帧。</td></tr>
</table>
</div>
</div>
<div style="position:absolute; top: 390px; left: 662px; width: 578px;">
<div class="panel">
<h3>GIF 资产规范(6 张占位已入库)</h3>
<table class="spec">
<tr><th>项</th><th>约定</th></tr>
<tr><td class="k">尺寸 / 画幅</td><td>640 × 360 · 16:9 覆盖媒体区,object-fit: cover。</td></tr>
<tr><td class="k">时长与循环</td><td>3–8s · 无限循环(loop=0)· 无声。</td></tr>
<tr><td class="k">体积</td><td>单张 ≤ 300KB(当前占位 72–101KB)。</td></tr>
<tr><td class="k">命名替换</td><td>static/gifs/&lt;id&gt;.gif 同名覆盖即换片,零代码改动。</td></tr>
<tr><td class="k">内容口径</td><td>拍「学完后的真实产物」:浏览器里跑起来的页面、终端输出、可核对的结果。</td></tr>
</table>
</div>
</div>
<div class="footline">课程书架设计稿 · stack idealjs</div>
<div class="pagenum">04 / 05</div>
</div>
<!-- ================= P5 设计规范 ================= -->
<div class="page">
<div class="pagehead">
<h2>04 设计 Tokens · 色板 / 字号 / 状态</h2>
<span class="sub">与组件 CourseShelf/styles.module.css 一一对应</span>
</div>
<div style="position:absolute; top: 122px; left: 40px; width: 560px;">
<div class="panel">
<h3>课程色板(书脊 / 徽标 / chips 主题色)</h3>
<div class="swatches">
<div class="swatch"><div class="c" style="background:#6366f1"></div><div class="n">快速开始</div><div class="h">#6366F1 indigo</div></div>
<div class="swatch"><div class="c" style="background:#ec4899"></div><div class="n">React</div><div class="h">#EC4899 pink</div></div>
<div class="swatch"><div class="c" style="background:#f59e0b"></div><div class="n">更多示例</div><div class="h">#F59E0B amber</div></div>
<div class="swatch"><div class="c" style="background:#10b981"></div><div class="n">更多</div><div class="h">#10B981 emerald</div></div>
<div class="swatch"><div class="c" style="background:#0ea5e9"></div><div class="n">全栈基础</div><div class="h">#0EA5E9 sky</div></div>
<div class="swatch"><div class="c" style="background:#8b5cf6"></div><div class="n">高级教程</div><div class="h">#8B5CF6 violet</div></div>
</div>
<div style="margin-top: 16px;" class="rule"></div>
<table class="spec" style="margin-top: 12px;">
<tr><th>中性色</th><th>值</th></tr>
<tr><td class="k">标题 / 正文 / 弱文字</td><td>#0F172A · #334155 · #64748B(slate 900/700/500)</td></tr>
<tr><td class="k">边框 / 页底</td><td>#E2E8F0 · 背景白卡 #FFFFFF 页底 #F8FAFC</td></tr>
<tr><td class="k">完成 / 写作中徽标</td><td>字 #047857/#92400E · 底 #D1FAE5/#FEF3C7(暗色主题见组件)</td></tr>
</table>
</div>
</div>
<div style="position:absolute; top: 122px; left: 640px; width: 600px;">
<div class="panel">
<h3>字号 · 间距 · 圆角</h3>
<table class="spec">
<tr><th>Token</th><th>值</th><th>用途</th></tr>
<tr><td class="k">卡标题</td><td>16.8px / 700</td><td>卡片标题行</td></tr>
<tr><td class="k">卡描述</td><td>14.4px / 1.6</td><td>预期结果一句话</td></tr>
<tr><td class="k">徽标 / chips</td><td>11.5px</td><td>done 徽标与关系 chips</td></tr>
<tr><td class="k">网格间距 / 内边距</td><td>20px / 16·18·20px</td><td>grid gap / 卡内边距</td></tr>
<tr><td class="k">圆角</td><td>卡 12px · 媒体区随卡 · chips 999px</td><td>通用</td></tr>
<tr><td class="k">书脊</td><td>宽 4px,透明度 45%→100%</td><td>左缘主题色条</td></tr>
</table>
</div>
<div class="panel" style="margin-top: 16px;">
<h3 style="margin-bottom: 12px; border:none; padding-bottom: 0;">状态示意</h3>
<div style="display:flex; gap: 24px; align-items:flex-start;">
<div class="statecard" style="flex:none;"><div class="m"></div><div class="t">default</div></div>
<div class="statecard hov" style="flex:none;"><div class="m"></div><div class="t">hover / focus</div></div>
</div>
<div style="margin-top: 12px; font-size:12.5px; color:#475569; line-height:1.7;">
hover / focus-visible:卡体上浮,阴影加深至 0 6px 20px,边框与标题染主题色,书脊透明度升至 100%。键盘 Tab 与鼠标悬停共享同一态。
</div>
</div>
</div>
<div class="footline">课程书架设计稿 · stack idealjs · 关系 relations 待产品列出后填充 courses.ts</div>
<div class="pagenum">05 / 05</div>
</div>
</body>
</html>