Files
NextEdu/docs/mockups/paper-sticky-design.html
SpecialX d1ad7a1f75
Some checks failed
Security / deep-security-scan (push) Failing after 2m10s
DR Drill / dr-drill (push) Failing after 1m32s
CI / scheduled-backup (push) Failing after 31s
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled
docs(architecture): update impact map, data, troubleshooting, add mockups
- Update 004_architecture_impact_map.md and 005_architecture_data.json

- Remove obsolete _update_004.cjs helper script

- Update troubleshooting/known-issues.md

- Add docs/mockups/ directory
2026-07-04 23:02:14 +08:00

800 lines
23 KiB
HTML
Raw Permalink 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" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>备课编辑器 · 无边记纸感设计</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
/* shadcn 中性令牌light */
--background: #ffffff;
--foreground: #0a0a0a;
--card: #ffffff;
--card-foreground: #0a0a0a;
--muted: #f5f5f4;
--muted-foreground: #78716c;
--border: #e7e5e4;
--input: #e7e5e4;
--primary: #1c1917;
--primary-foreground: #fafaf9;
--secondary: #f5f5f4;
--secondary-foreground: #1c1917;
--accent: #f5f5f4;
--accent-foreground: #1c1917;
--ring: #1c1917;
/* 纸感专用 */
--paper: #fefefe;
--paper-edge: #f8f8f7;
--paper-shadow: 0 1px 2px rgba(15,15,15,0.04), 0 8px 24px rgba(15,15,15,0.04);
--paper-shadow-active: 0 1px 2px rgba(15,15,15,0.06), 0 12px 36px rgba(15,15,15,0.08);
/* 便签 */
--sticky-bg: #fffdf5;
--sticky-bg-alt: #f7f6f1;
--sticky-border: #ebe8df;
--sticky-shadow: 0 1px 1px rgba(15,15,15,0.04), 0 2px 4px rgba(15,15,15,0.04);
--sticky-shadow-hover: 0 2px 4px rgba(15,15,15,0.06), 0 6px 14px rgba(15,15,15,0.06);
/* 锚点高亮 */
--anchor-range: rgba(28, 25, 23, 0.08);
--anchor-range-active: rgba(28, 25, 23, 0.16);
--anchor-point: #1c1917;
/* 节点类型色点(极克制) */
--dot-objective: #4b5563;
--dot-teach: #1c1917;
--dot-exercise: #6b7280;
--dot-summary: #525252;
--dot-textbook: #44403c;
--dot-default: #a8a29e;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--muted);
color: var(--foreground);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: "ss01", "cv11";
overflow: hidden;
}
.app {
display: grid;
grid-template-rows: 52px 1fr;
height: 100vh;
}
/* 顶部工具栏 */
.topbar {
display: flex;
align-items: center;
gap: 12px;
padding: 0 20px;
background: var(--background);
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.topbar .title-input {
background: transparent;
border: none;
outline: none;
font-family: 'Fraunces', serif;
font-weight: 600;
font-size: 16px;
color: var(--foreground);
width: 280px;
letter-spacing: -0.01em;
}
.topbar .meta {
color: var(--muted-foreground);
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.topbar .meta-chip {
padding: 3px 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--background);
font-size: 11px;
}
.topbar .spacer { flex: 1; }
.topbar .status {
font-size: 11px;
color: var(--muted-foreground);
font-variant-numeric: tabular-nums;
}
.topbar .status::before {
content: "";
display: inline-block;
width: 5px; height: 5px;
border-radius: 50%;
background: #16a34a;
margin-right: 6px;
vertical-align: middle;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 5px;
background: var(--background);
font-size: 12px;
color: var(--foreground);
cursor: pointer;
transition: background .15s;
font-family: inherit;
}
.btn:hover { background: var(--muted); }
.btn.primary {
background: var(--primary);
color: var(--primary-foreground);
border-color: var(--primary);
}
.btn.primary:hover { background: #292524; }
.btn.ghost {
border-color: transparent;
background: transparent;
}
.btn.ghost:hover { background: var(--muted); }
.btn-icon {
width: 28px; height: 28px;
padding: 0;
justify-content: center;
}
/* 主区域三栏 */
.main {
display: grid;
grid-template-columns: 220px 1fr 360px;
overflow: hidden;
}
/* 左侧节点树 */
.sidebar {
border-right: 1px solid var(--border);
background: var(--background);
padding: 16px 12px;
overflow-y: auto;
}
.sidebar-head {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted-foreground);
padding: 0 8px 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-head .count {
font-weight: 500;
color: var(--muted-foreground);
letter-spacing: 0;
}
.node-list {
display: flex;
flex-direction: column;
gap: 1px;
}
.node-item {
display: flex;
align-items: center;
gap: 9px;
padding: 7px 8px;
border-radius: 5px;
font-size: 13px;
color: var(--foreground);
cursor: pointer;
transition: background .12s;
line-height: 1.3;
}
.node-item:hover { background: var(--muted); }
.node-item.active {
background: var(--muted);
color: var(--foreground);
}
.node-item.active .dot { transform: scale(1.15); }
.node-item .dot {
width: 6px; height: 6px;
border-radius: 50%;
flex-shrink: 0;
transition: transform .15s;
}
.node-item .label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.node-item .kind {
font-size: 10px;
color: var(--muted-foreground);
font-weight: 500;
letter-spacing: 0.02em;
}
.sidebar-add {
margin-top: 10px;
padding: 7px 8px;
border: 1px dashed var(--border);
border-radius: 5px;
text-align: center;
font-size: 12px;
color: var(--muted-foreground);
cursor: pointer;
transition: all .15s;
}
.sidebar-add:hover {
border-color: var(--foreground);
color: var(--foreground);
}
/* 中间纸区 */
.paper-area {
overflow-y: auto;
padding: 48px 32px 120px;
background:
radial-gradient(circle at 50% 0%, rgba(0,0,0,0.015) 0%, transparent 60%),
var(--muted);
}
.paper {
background: var(--paper);
max-width: 720px;
margin: 0 auto;
padding: 64px 72px;
box-shadow: var(--paper-shadow);
border-radius: 2px;
border: 1px solid var(--paper-edge);
min-height: 800px;
transition: box-shadow .25s;
font-family: 'Fraunces', Georgia, serif;
color: #1a1a1a;
line-height: 1.7;
font-size: 16px;
position: relative;
}
.paper:focus-within { box-shadow: var(--paper-shadow-active); }
.paper-header {
font-family: 'Inter', sans-serif;
font-size: 11px;
color: var(--muted-foreground);
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
}
.paper h1 {
font-family: 'Fraunces', serif;
font-weight: 600;
font-size: 30px;
line-height: 1.2;
margin: 0 0 8px;
letter-spacing: -0.015em;
color: #0a0a0a;
}
.paper .subtitle {
font-family: 'Fraunces', serif;
font-style: italic;
font-weight: 400;
font-size: 16px;
color: #525252;
margin-bottom: 32px;
}
.paper h2 {
font-family: 'Fraunces', serif;
font-weight: 500;
font-size: 20px;
margin: 28px 0 12px;
color: #1a1a1a;
}
.paper p {
margin: 0 0 16px;
font-size: 16px;
line-height: 1.75;
color: #1a1a1a;
}
.paper ul, .paper ol {
margin: 0 0 16px;
padding-left: 22px;
}
.paper li {
margin-bottom: 6px;
line-height: 1.7;
}
.paper strong { font-weight: 600; }
.paper em { font-style: italic; }
/* 锚点标记 */
.anchor-range {
background: var(--anchor-range);
border-radius: 2px;
padding: 1px 2px;
margin: 0 -2px;
cursor: pointer;
transition: background .15s;
border-bottom: 1.5px solid var(--anchor-point);
}
.anchor-range:hover, .anchor-range.active {
background: var(--anchor-range-active);
}
.anchor-range::after {
content: attr(data-label);
display: inline-block;
margin-left: 4px;
padding: 0 4px;
background: var(--anchor-point);
color: #fff;
font-family: 'Inter', sans-serif;
font-size: 9px;
font-weight: 600;
border-radius: 2px;
vertical-align: middle;
letter-spacing: 0.02em;
line-height: 14px;
height: 14px;
}
.anchor-point {
display: inline-block;
width: 16px; height: 16px;
line-height: 16px;
text-align: center;
border-radius: 50%;
background: var(--anchor-point);
color: #fff;
font-family: 'Inter', sans-serif;
font-size: 9px;
font-weight: 600;
margin: 0 2px;
vertical-align: middle;
cursor: pointer;
transition: transform .15s;
}
.anchor-point:hover, .anchor-point.active {
transform: scale(1.15);
}
/* 纸上的工具条(聚焦时显示) */
.paper-toolbar {
position: sticky;
top: 0;
margin: -64px -72px 24px;
padding: 10px 72px;
background: rgba(255,255,255,0.92);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--border);
display: flex;
gap: 2px;
opacity: 0;
pointer-events: none;
transition: opacity .2s;
z-index: 5;
}
.paper:focus-within .paper-toolbar { opacity: 1; pointer-events: auto; }
.tb-btn {
width: 28px; height: 28px;
border: none;
background: transparent;
border-radius: 4px;
cursor: pointer;
font-family: 'Inter', sans-serif;
font-size: 13px;
color: var(--foreground);
display: inline-flex;
align-items: center;
justify-content: center;
transition: background .12s;
}
.tb-btn:hover { background: var(--muted); }
.tb-btn.b { font-weight: 700; }
.tb-btn.i { font-style: italic; font-family: 'Fraunces', serif; }
.tb-btn.h1 { font-size: 11px; font-weight: 600; }
.tb-btn.ul { font-size: 14px; }
.tb-btn.divider {
width: 1px;
background: var(--border);
margin: 4px 4px;
pointer-events: none;
}
/* 右侧便签区 */
.sticky-area {
border-left: 1px solid var(--border);
background: var(--muted);
padding: 24px 20px;
overflow-y: auto;
}
.sticky-area-head {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted-foreground);
padding: 0 0 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.sticky-area-head .hint {
font-weight: 400;
text-transform: none;
letter-spacing: 0;
font-size: 11px;
}
.sticky {
background: var(--sticky-bg);
border: 1px solid var(--sticky-border);
border-radius: 4px;
padding: 14px 16px;
margin-bottom: 12px;
box-shadow: var(--sticky-shadow);
transition: all .2s;
cursor: pointer;
position: relative;
}
.sticky:hover {
box-shadow: var(--sticky-shadow-hover);
transform: translateY(-1px);
}
.sticky.active {
border-color: var(--foreground);
box-shadow: 0 0 0 1px var(--foreground), var(--sticky-shadow-hover);
}
.sticky-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.sticky-head .dot {
width: 6px; height: 6px;
border-radius: 50%;
flex-shrink: 0;
}
.sticky-head .kind {
font-family: 'Inter', sans-serif;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted-foreground);
}
.sticky-head .spacer { flex: 1; }
.sticky-head .anchor-ref {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--muted-foreground);
background: var(--background);
padding: 1px 5px;
border-radius: 2px;
border: 1px solid var(--border);
}
.sticky-title {
font-family: 'Inter', sans-serif;
font-size: 14px;
font-weight: 600;
color: var(--foreground);
margin: 0 0 6px;
line-height: 1.35;
}
.sticky-body {
font-family: 'Inter', sans-serif;
font-size: 12.5px;
line-height: 1.55;
color: #404040;
}
.sticky-body p { margin: 0 0 6px; }
.sticky-body ul { margin: 4px 0; padding-left: 16px; }
.sticky-body li { margin-bottom: 2px; }
.sticky-footer {
margin-top: 10px;
padding-top: 8px;
border-top: 1px dashed var(--sticky-border);
display: flex;
gap: 6px;
font-family: 'Inter', sans-serif;
font-size: 10px;
color: var(--muted-foreground);
}
.sticky-footer .tag {
padding: 1px 5px;
background: var(--background);
border: 1px solid var(--border);
border-radius: 2px;
}
.sticky-add {
border: 1px dashed var(--border);
background: transparent;
padding: 14px 16px;
border-radius: 4px;
text-align: center;
font-family: 'Inter', sans-serif;
font-size: 12px;
color: var(--muted-foreground);
cursor: pointer;
transition: all .15s;
}
.sticky-add:hover {
border-color: var(--foreground);
color: var(--foreground);
background: var(--background);
}
/* 滚动条 */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.12);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.2); }
/* 微动效 */
@keyframes paperIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes stickyIn {
from { opacity: 0; transform: translateX(8px); }
to { opacity: 1; transform: translateX(0); }
}
.paper { animation: paperIn .5s cubic-bezier(.2,.7,.3,1); }
.sticky { animation: stickyIn .4s cubic-bezier(.2,.7,.3,1) backwards; }
.sticky:nth-child(2) { animation-delay: .05s; }
.sticky:nth-child(3) { animation-delay: .1s; }
.sticky:nth-child(4) { animation-delay: .15s; }
.sticky:nth-child(5) { animation-delay: .2s; }
@keyframes cursorBlink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.caret {
display: inline-block;
width: 1.5px;
height: 1.1em;
background: var(--anchor-point);
vertical-align: text-bottom;
margin-left: 1px;
animation: cursorBlink 1s infinite;
}
</style>
</head>
<body>
<div class="app">
<!-- 顶部工具栏 -->
<header class="topbar">
<input class="title-input" value="第一单元 · 走进大自然" />
<div class="meta">
<span class="meta-chip">二年级 · 语文</span>
<span class="meta-chip">人教版 · 上册</span>
</div>
<div class="spacer"></div>
<span class="status">已保存 · 14:32</span>
<button class="btn ghost btn-icon" title="撤销"></button>
<button class="btn ghost btn-icon" title="重做"></button>
<button class="btn ghost">历史版本</button>
<button class="btn ghost">一致性校验</button>
<button class="btn ghost">AI 反馈</button>
<button class="btn primary">发布课案</button>
</header>
<div class="main">
<!-- 左侧节点树 -->
<aside class="sidebar">
<div class="sidebar-head">
<span>结构</span>
<span class="count">6</span>
</div>
<div class="node-list">
<div class="node-item">
<span class="dot" style="background:var(--dot-textbook)"></span>
<span class="label">教材正文</span>
<span class="kind">正文</span>
</div>
<div class="node-item active">
<span class="dot" style="background:var(--dot-objective)"></span>
<span class="label">教学目标</span>
<span class="kind">目标</span>
</div>
<div class="node-item">
<span class="dot" style="background:var(--dot-teach)"></span>
<span class="label">字词学习</span>
<span class="kind">新授</span>
</div>
<div class="node-item">
<span class="dot" style="background:var(--dot-teach)"></span>
<span class="label">课文精读</span>
<span class="kind">新授</span>
</div>
<div class="node-item">
<span class="dot" style="background:var(--dot-exercise)"></span>
<span class="label">课堂练习</span>
<span class="kind">练习</span>
</div>
<div class="node-item">
<span class="dot" style="background:var(--dot-summary)"></span>
<span class="label">本课小结</span>
<span class="kind">总结</span>
</div>
</div>
<div class="sidebar-add">+ 添加节点</div>
</aside>
<!-- 中间纸区 -->
<main class="paper-area">
<article class="paper" contenteditable="false" tabindex="0">
<!-- 浮动工具条(聚焦时显示) -->
<div class="paper-toolbar">
<button class="tb-btn b" title="加粗">B</button>
<button class="tb-btn i" title="斜体">I</button>
<span class="tb-btn divider"></span>
<button class="tb-btn h1" title="标题">H1</button>
<button class="tb-btn h1" title="副标题" style="font-size:10px;">H2</button>
<span class="tb-btn divider"></span>
<button class="tb-btn ul" title="无序列表"></button>
<button class="tb-btn ul" title="有序列表" style="font-size:11px;">1.</button>
<span class="tb-btn divider"></span>
<button class="tb-btn" title="引用" style="font-family:'Fraunces',serif;font-style:italic;font-size:14px;">"</button>
<button class="tb-btn" title="分隔线"></button>
</div>
<div class="paper-header">
<span>教材正文 · 可编辑副本</span>
<span>第 1 / 3 页</span>
</div>
<h1>1 走进大自然</h1>
<p class="subtitle">—— 选自人教版二年级上册</p>
<p>春天的<span class="anchor-range" data-label="A"></span>轻轻吹过田野,带来泥土的芬芳。柳枝抽出嫩芽,小草从泥土里探出头来,<span class="anchor-range" data-label="B">燕子也从南方飞回来了</span></p>
<p>夏天,<span class="anchor-range" data-label="C">雷雨过后,天空中常常会出现一道美丽的彩虹</span>。池塘里的荷花开了,青蛙在荷叶上<span class="anchor-point">1</span>呱呱地唱歌。</p>
<h2>观察要点</h2>
<ul>
<li><strong>花朵的颜色变化</strong>:从早到晚,颜色会有什么不同?</li>
<li><span class="anchor-point">2</span> <strong>叶片的形状</strong>:心形、针形、掌状……</li>
<li><strong>气温的回升</strong>:用温度计记录一周的气温。</li>
</ul>
<p>秋天,树叶<span class="anchor-range" data-label="D">变黄了,一片片飘落下来</span>,像一封封金色的信。田野里,稻谷弯下了腰,<span class="anchor-point">3</span>农民伯伯露出了丰收的喜悦。</p>
<p>冬天,<span class="anchor-range" data-label="E">雪花纷纷扬扬地飘落</span>,大地披上了银装。孩子们在雪地里堆雪人、打雪仗,<span class="caret"></span></p>
</article>
</main>
<!-- 右侧便签区 -->
<aside class="sticky-area">
<div class="sticky-area-head">
<span>教学节点</span>
<span class="hint">5 个便签 · 锚定到正文</span>
</div>
<div class="sticky active">
<div class="sticky-head">
<span class="dot" style="background:var(--dot-objective)"></span>
<span class="kind">教学目标</span>
<span class="spacer"></span>
<span class="anchor-ref">A · B</span>
</div>
<h3 class="sticky-title">感受四季变化</h3>
<div class="sticky-body">
<p>1. 认识"春、夏、秋、冬"四个生字,会写"风、燕、荷、稻"等 12 个字。</p>
<p>2. 正确、流利、有感情地朗读课文,背诵第一自然段。</p>
<p>3. 感受四季的美丽,培养观察自然的兴趣。</p>
</div>
<div class="sticky-footer">
<span class="tag">朗读</span>
<span class="tag">识字</span>
<span class="tag">2 课时</span>
</div>
</div>
<div class="sticky">
<div class="sticky-head">
<span class="dot" style="background:var(--dot-teach)"></span>
<span class="kind">新授</span>
<span class="spacer"></span>
<span class="anchor-ref">C · 1</span>
</div>
<h3 class="sticky-title">字词学习</h3>
<div class="sticky-body">
<p>重点字词:</p>
<ul>
<li>芬芳 — 香气</li>
<li>嫩芽 — 初生的芽</li>
<li>纷纷扬扬 — 飘洒的样子</li>
</ul>
</div>
<div class="sticky-footer">
<span class="tag">生字 12</span>
<span class="tag">词语 8</span>
</div>
</div>
<div class="sticky">
<div class="sticky-head">
<span class="dot" style="background:var(--dot-teach)"></span>
<span class="kind">新授</span>
<span class="spacer"></span>
<span class="anchor-ref">2 · D</span>
</div>
<h3 class="sticky-title">课文精读</h3>
<div class="sticky-body">
<p>引导提问:</p>
<ul>
<li>为什么说"燕子从南方飞回来了"是春天的信号?</li>
<li>课文按什么顺序写的?你怎么看出来的?</li>
</ul>
</div>
<div class="sticky-footer">
<span class="tag">精读</span>
<span class="tag">15 分钟</span>
</div>
</div>
<div class="sticky">
<div class="sticky-head">
<span class="dot" style="background:var(--dot-exercise)"></span>
<span class="kind">课堂练习</span>
<span class="spacer"></span>
<span class="anchor-ref">E</span>
</div>
<h3 class="sticky-title">仿写练习</h3>
<div class="sticky-body">
<p>模仿课文"春天的风轻轻吹过田野,带来泥土的芬芳",写一句自己观察到的季节变化。</p>
</div>
<div class="sticky-footer">
<span class="tag">仿写</span>
<span class="tag">8 分钟</span>
</div>
</div>
<div class="sticky">
<div class="sticky-head">
<span class="dot" style="background:var(--dot-summary)"></span>
<span class="kind">总结</span>
<span class="spacer"></span>
<span class="anchor-ref">3</span>
</div>
<h3 class="sticky-title">本课小结</h3>
<div class="sticky-body">
<p>回顾四季特点,串联课文脉络。引导学生用一句话说说自己最喜欢的季节。</p>
</div>
<div class="sticky-footer">
<span class="tag">回顾</span>
<span class="tag">5 分钟</span>
</div>
</div>
<div class="sticky-add">+ 添加教学节点</div>
</aside>
</div>
</div>
</body>
</html>