Files
nj/docs/opendesign/screens/stickers.html
iven b320641d9c
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
fix(app): 全链路验证修复 — 编译错误/CORS/迁移/启动脚本
前端修复:
- calendar_page: 移除不存在的 JournalEntry.content getter
- responsive_scaffold: 移除不存在的 notchThickness 参数
- splash_page: SingleTickerProvider → TickerProvider (多 AnimationController)
- profile_page: UserRoleType.name → .code (修复运行时崩溃)
- 导入缺失的 user.dart

后端修复:
- class_service: generate_class_code 取 UUID 后6位(随机部分)避免碰撞
- diary_role_seed: 移除不存在的 id 列,使用复合主键 ON CONFLICT

基础设施:
- config/default.toml: CORS 改为通配符(开发模式)
- scripts/dev.sh: 统一启动脚本(自动清理端口)
- docs/opendesign/: Open Design 设计规格 HTML 原型稿

验证结果: flutter analyze 0 error, cargo test 77/77 通过, 17个页面全部渲染正常
2026-06-02 01:03:58 +08:00

432 lines
14 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">
<meta name="viewport" content="width=390, height=844, initial-scale=1, viewport-fit=cover">
<title>暖记 — 贴纸素材库</title>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/tokens.css">
<link rel="stylesheet" href="../css/components.css">
<style>
body {
width: 390px;
height: 844px;
overflow: hidden;
background: var(--bg);
position: relative;
}
/* Global focus styles */
button:focus-visible, a:focus-visible, [role="tab"]:focus-visible {
box-shadow: 0 0 0 3px var(--focus-ring);
outline: none;
}
.content-area {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow-y: auto;
padding: 0 var(--space-5);
padding-top: calc(var(--safe-top) + var(--space-2));
padding-bottom: var(--safe-bottom);
scrollbar-width: none;
}
.content-area::-webkit-scrollbar { display: none; }
.top-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-4);
}
.top-bar h2 {
font-family: var(--font-display);
font-size: var(--text-2xl);
font-weight: 700;
}
.back-btn {
min-width: 44px;
min-height: 44px;
border-radius: 50%;
border: none;
background: var(--surface);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--fg);
box-shadow: var(--elev-soft);
}
.back-btn svg { width: 20px; height: 20px; }
/* Search */
.search-box {
display: flex;
align-items: center;
gap: var(--space-3);
background: var(--surface);
border-radius: var(--radius-pill);
padding: var(--space-3) var(--space-5);
margin-bottom: var(--space-5);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
}
.search-box svg { width: 20px; height: 20px; color: var(--muted); flex-shrink: 0; }
.search-box input {
flex: 1;
border: none;
outline: none;
font-size: var(--text-base);
background: transparent;
color: var(--fg);
font-family: var(--font-body);
}
.search-box input::placeholder { color: var(--meta); }
/* Category tabs */
.cat-tabs {
display: flex;
gap: var(--space-2);
margin-bottom: var(--space-5);
overflow-x: auto;
scrollbar-width: none;
padding-bottom: 4px;
}
.cat-tabs::-webkit-scrollbar { display: none; }
.cat-tab {
padding: 8px 18px;
min-height: 44px;
border-radius: var(--radius-pill);
border: 1.5px solid var(--border);
background: var(--surface);
font-size: var(--text-sm);
font-weight: 500;
color: var(--fg-2);
cursor: pointer;
white-space: nowrap;
transition: all var(--motion-fast);
}
.cat-tab.active {
background: var(--accent);
color: var(--accent-on);
border-color: var(--accent);
}
.cat-tab:hover:not(.active) { border-color: var(--accent); color: var(--accent); }
/* Featured pack */
.featured-pack {
background: linear-gradient(135deg, var(--secondary-soft), var(--secondary-soft));
border-radius: var(--radius-lg);
padding: var(--space-5);
margin-bottom: var(--space-5);
display: flex;
gap: var(--space-4);
align-items: center;
cursor: pointer;
transition: transform var(--motion-fast);
}
.featured-pack:hover { transform: translateY(-2px); }
.featured-icon {
width: 72px;
height: 72px;
border-radius: var(--radius-md);
background: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
box-shadow: var(--elev-soft);
flex-shrink: 0;
}
.featured-info h4 {
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 700;
margin-bottom: 4px;
}
.featured-info p {
font-size: var(--text-sm);
color: var(--muted);
margin-bottom: var(--space-2);
}
.featured-info .tag {
display: inline-block;
padding: 3px 10px;
background: var(--secondary);
color: var(--accent-on);
border-radius: var(--radius-pill);
font-size: 11px;
font-weight: 600;
}
/* Sticker pack grid */
.pack-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.pack-card {
background: var(--surface);
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
cursor: pointer;
transition: all var(--motion-fast);
}
.pack-card:hover { transform: translateY(-2px); box-shadow: var(--elev-medium); }
.pack-preview {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
gap: 8px;
}
.pack-info {
padding: var(--space-3) var(--space-4);
}
.pack-info h5 {
font-family: var(--font-display);
font-size: var(--text-sm);
font-weight: 600;
margin-bottom: 2px;
}
.pack-info .count {
font-size: var(--text-xs);
color: var(--muted);
}
.pack-info .price {
font-size: var(--text-xs);
font-weight: 600;
color: var(--accent);
margin-top: 4px;
}
.pack-info .free {
color: var(--secondary);
}
/* Individual stickers grid */
.section-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-4);
}
.section-head h3 {
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 700;
}
.section-head a {
font-size: var(--text-sm);
color: var(--accent);
text-decoration: none;
font-weight: 500;
min-height: 44px;
display: inline-flex;
align-items: center;
}
.sticker-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.sticker-cell {
aspect-ratio: 1;
background: var(--surface);
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
cursor: pointer;
transition: all var(--motion-fast) var(--ease-bounce);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
position: relative;
min-height: 44px;
}
.sticker-cell:hover { transform: scale(1.08); border-color: var(--accent); }
.sticker-cell:active { transform: scale(0.95); }
.sticker-cell .fav {
position: absolute;
top: 4px;
right: 4px;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--surface);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity var(--motion-fast);
min-width: 44px;
min-height: 44px;
padding: 13px;
box-sizing: content-box;
top: -8px;
right: -8px;
}
.sticker-cell:hover .fav { opacity: 1; }
.sticker-cell .fav svg { width: 10px; height: 10px; color: var(--rose); }
.sticker-cell .fav.active svg { fill: var(--rose); }
/* Empty state for empty favorites */
.empty-state {
display: none; /* Shown via JS when favorites are empty */
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-10) var(--space-5);
text-align: center;
}
.empty-state .empty-circle {
width: 80px;
height: 80px;
border-radius: 50%;
border: 2px dashed var(--border);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-4);
font-size: 32px;
opacity: 0.6;
}
.empty-state .empty-text {
font-size: var(--text-base);
color: var(--muted);
font-weight: 500;
margin-bottom: var(--space-2);
}
.empty-state .empty-hint {
font-size: var(--text-sm);
color: var(--meta);
}
</style>
</head>
<body>
<div class="content-area">
<!-- Top bar -->
<div class="top-bar anim-fade">
<button class="back-btn" aria-label="返回">
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M13 17l-6-6 6-6"/></svg>
</button>
<h2>贴纸素材</h2>
<div style="width:44px"></div>
</div>
<!-- Search -->
<div class="search-box anim-fade" style="animation-delay: 0.05s">
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="9" cy="9" r="6"/><path d="M14 14l4 4"/></svg>
<input type="text" placeholder="搜索贴纸、素材包..." aria-label="搜索贴纸">
</div>
<!-- Categories -->
<div class="cat-tabs anim-fade" style="animation-delay: 0.1s">
<button class="cat-tab active" aria-label="推荐分类">推荐</button>
<button class="cat-tab" aria-label="可爱分类">可爱</button>
<button class="cat-tab" aria-label="植物分类">植物</button>
<button class="cat-tab" aria-label="手绘分类">手绘</button>
<button class="cat-tab" aria-label="校园分类">校园</button>
<button class="cat-tab" aria-label="节日分类">节日</button>
<button class="cat-tab" aria-label="文字分类">文字</button>
<button class="cat-tab" aria-label="和纸胶带分类">和纸胶带</button>
</div>
<!-- Featured pack -->
<div class="featured-pack anim-slide" style="animation-delay: 0.15s">
<div class="featured-icon">🌸</div>
<div class="featured-info">
<h4>春日花园系列</h4>
<p>32款手绘花卉贴纸让你的日记春意盎然</p>
<span class="tag">限时免费</span>
</div>
</div>
<!-- Pack grid -->
<div class="section-head anim-fade" style="animation-delay: 0.2s">
<h3>热门素材包</h3>
<a href="#" aria-label="查看全部素材包">查看全部</a>
</div>
<div class="pack-grid anim-fade" style="animation-delay: 0.25s">
<div class="pack-card">
<div class="pack-preview" style="background: var(--tertiary-soft)">☕📚</div>
<div class="pack-info">
<h5>学习日常</h5>
<div class="count">24款贴纸</div>
<div class="price free">免费</div>
</div>
</div>
<div class="pack-card">
<div class="pack-preview" style="background: var(--secondary-soft)">🌿🍃</div>
<div class="pack-info">
<h5>清新植物</h5>
<div class="count">36款贴纸</div>
<div class="price">¥6</div>
</div>
</div>
<div class="pack-card">
<div class="pack-preview" style="background: var(--rose-soft)">🎀💕</div>
<div class="pack-info">
<h5>甜蜜少女</h5>
<div class="count">28款贴纸</div>
<div class="price">¥3</div>
</div>
</div>
<div class="pack-card">
<div class="pack-preview" style="background: var(--secondary-soft)">🦋✨</div>
<div class="pack-info">
<h5>梦幻星空</h5>
<div class="count">20款贴纸</div>
<div class="price free">免费</div>
</div>
</div>
</div>
<!-- Individual stickers -->
<div class="section-head anim-fade" style="animation-delay: 0.3s">
<h3>精选贴纸</h3>
<a href="#" aria-label="查看收藏夹">收藏夹</a>
</div>
<div class="sticker-grid anim-fade" style="animation-delay: 0.35s">
<div class="sticker-cell" aria-label="樱花贴纸">🌸<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="星星贴纸"><div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="树叶贴纸">🌿<div class="fav active"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="太阳贴纸">☀️<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="蛋糕贴纸">🍰<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="音乐贴纸">🎵<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="书本贴纸">📚<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="月亮贴纸">🌙<div class="fav active"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="蝴蝶结贴纸">🎀<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="枫叶贴纸">🍂<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="咖啡贴纸"><div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="蝴蝶贴纸">🦋<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
</div>
<!-- Empty state for empty favorites (hidden by default) -->
<div class="empty-state" id="emptyFavorites">
<div class="empty-circle">💜</div>
<div class="empty-text">收藏夹是空的</div>
<div class="empty-hint">长按贴纸可添加到收藏夹</div>
</div>
<div style="height: var(--space-8)"></div>
</div>
<div class="dynamic-island" aria-hidden="true"></div>
<div class="home-indicator" style="position:absolute;bottom:8px;left:50%;transform:translateX(-50%);z-index:101" aria-hidden="true"></div>
<script src="../js/theme-switcher.js"></script>
</body>
</html>