前端修复: - 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个页面全部渲染正常
432 lines
14 KiB
HTML
432 lines
14 KiB
HTML
<!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>
|