Files
xianyan/docs/toolsapi/preview/fortune_design.html
Developer 228095f80a chore: 完成v6.7.0版本迭代更新
本次更新涵盖多个功能模块的优化与新增:
1. 新增Wi-Fi直连配对方式与协作画布模块
2. 完成设备管理重命名API与前端适配
3. 优化日签卡片空数据保护与UI细节
4. 新增剪贴板工具与每日运势会话
5. 修复应用锁恢复、语音消息录制等已知问题
6. 完善文件传输统计与配对逻辑
7. 更新安卓权限配置与iOS隐私描述
8. 新增自动化测试脚本与文档
9. 清理旧版审计报告与测试文件
2026-05-14 05:35:18 +08:00

1484 lines
61 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark" data-style="ancient">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>每日运势 - 交互式设计预览</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700;900&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
:root {
--primary: #C41E3A;
--primary-light: #E8475D;
--primary-dark: #8B1528;
--gold: #D4A843;
--gold-light: #F0D68A;
--gold-dark: #A07C2E;
--radius-sm: 8px;
--radius-md: 14px;
--radius-lg: 20px;
--font-serif: 'Noto Serif SC', serif;
--font-sans: 'Noto Sans SC', sans-serif;
--transition: 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
[data-theme="dark"] {
--bg: #0D0D0D;
--bg-elevated: #141414;
--bg-card: #1A1A1A;
--bg-card-light: #252525;
--bg-input: #1E1E1E;
--text: #F5F0E8;
--text-secondary: #9A9590;
--text-dim: #5A5550;
--border: #2A2A2A;
--border-light: #333;
--shadow-card: 0 4px 24px rgba(0,0,0,0.4);
--divider: rgba(255,255,255,0.06);
--overlay: rgba(0,0,0,0.6);
}
[data-theme="light"] {
--bg: #F2F2F7;
--bg-elevated: #FFFFFF;
--bg-card: #FFFFFF;
--bg-card-light: #F9F9FB;
--bg-input: #F2F2F7;
--text: #1C1C1E;
--text-secondary: #636366;
--text-dim: #AEAEB2;
--border: #E5E5EA;
--border-light: #D1D1D6;
--shadow-card: 0 2px 16px rgba(0,0,0,0.08);
--divider: rgba(0,0,0,0.05);
--overlay: rgba(0,0,0,0.3);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
min-height: 100vh;
transition: background var(--transition), color var(--transition);
-webkit-font-smoothing: antialiased;
}
.app-shell {
max-width: 480px;
margin: 0 auto;
min-height: 100vh;
position: relative;
}
.nav-bar {
position: sticky;
top: 0;
z-index: 100;
background: var(--bg-elevated);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
transition: background var(--transition);
}
.nav-bar-inner {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
}
.nav-title {
font-size: 17px;
font-weight: 600;
display: flex;
align-items: center;
gap: 6px;
}
.nav-actions {
display: flex;
gap: 4px;
}
.nav-btn {
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: var(--bg-card-light);
color: var(--text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.nav-btn:hover { background: var(--border-light); }
.nav-btn svg { width: 18px; height: 18px; }
.nav-tabs {
display: flex;
padding: 0 16px 10px;
gap: 2px;
}
.nav-tab {
flex: 1;
padding: 7px 0;
border-radius: 8px;
border: none;
background: transparent;
color: var(--text-dim);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.25s;
font-family: var(--font-sans);
}
.nav-tab.active {
background: var(--primary);
color: #fff;
}
.section { display: none; padding: 16px; }
.section.active { display: block; }
/* ====== SVG Icon System ====== */
.icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
flex-shrink: 0;
}
.icon svg { width: 100%; height: 100%; fill: currentColor; }
.icon-sm { width: 16px; height: 16px; }
.icon-lg { width: 24px; height: 24px; }
.icon-xl { width: 32px; height: 32px; }
/* ====== Timeline ====== */
.timeline { padding: 4px 0; }
.tl-item {
margin-bottom: 12px;
border-radius: var(--radius-lg);
overflow: hidden;
transition: all var(--transition);
}
.tl-item:last-child { margin-bottom: 0; }
.tl-summary {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
cursor: pointer;
transition: all 0.25s;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.tl-item.expanded .tl-summary {
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
border-bottom-color: transparent;
background: var(--bg-card-light);
}
.tl-summary:active { transform: scale(0.985); }
.tl-dot {
width: 40px;
height: 40px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 18px;
position: relative;
}
.tl-dot.today {
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
box-shadow: 0 2px 10px rgba(196,30,58,0.35);
}
.tl-dot.past {
background: var(--bg-input);
}
.tl-info { flex: 1; min-width: 0; }
.tl-date-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 3px;
}
.tl-date { font-size: 14px; font-weight: 600; color: var(--text); }
.tl-level-badge {
padding: 2px 10px;
border-radius: 10px;
font-size: 12px;
font-weight: 600;
}
.tl-level-badge.daji { background: rgba(212,168,67,0.15); color: var(--gold); }
.tl-level-badge.zhongji { background: rgba(76,175,80,0.12); color: #66BB6A; }
.tl-level-badge.ji { background: rgba(66,165,245,0.12); color: #42A5F5; }
.tl-level-badge.xiaoji { background: rgba(171,71,188,0.12); color: #AB47BC; }
.tl-level-badge.moji { background: rgba(255,167,38,0.12); color: #FFA726; }
.tl-preview {
font-size: 12px;
color: var(--text-dim);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tl-dims-row {
display: flex;
gap: 4px;
margin-top: 6px;
flex-wrap: wrap;
}
.tl-dim-chip {
padding: 1px 7px;
border-radius: 6px;
font-size: 10px;
background: var(--divider);
color: var(--text-secondary);
}
.tl-chevron {
color: var(--text-dim);
transition: transform 0.3s;
}
.tl-item.expanded .tl-chevron { transform: rotate(180deg); }
.tl-detail {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
background: var(--bg-card);
border: 1px solid var(--border);
border-top: none;
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.tl-item.expanded .tl-detail {
max-height: 2000px;
}
.tl-detail-inner {
padding: 16px;
}
.tl-actions {
display: flex;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--divider);
}
.action-btn {
padding: 8px 16px;
border-radius: 20px;
border: 1px solid var(--border);
background: transparent;
color: var(--text-secondary);
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 4px;
font-family: var(--font-sans);
}
.action-btn:hover { border-color: var(--primary); color: var(--primary-light); }
.action-btn.primary {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.action-btn.primary:hover { background: var(--primary-dark); }
.action-btn:active { transform: scale(0.96); }
.action-btn svg { width: 14px; height: 14px; }
/* ====== Fortune Card Styles ====== */
.fortune-card {
border-radius: var(--radius-lg);
overflow: hidden;
transition: all var(--transition);
}
/* --- Ancient Style --- */
[data-style="ancient"] .fortune-card {
background: linear-gradient(160deg, #1C1008, #2A1810, #1C1008);
border: 1px solid rgba(212,168,67,0.2);
box-shadow: var(--shadow-card);
padding: 24px 20px;
position: relative;
}
[data-theme="light"][data-style="ancient"] .fortune-card {
background: linear-gradient(160deg, #FFF8E7, #FFF3D6, #FFF8E7);
border-color: rgba(160,124,46,0.2);
}
[data-style="ancient"] .fortune-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none'%3E%3Cg fill='%23D4A843' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
pointer-events: none;
}
.fc-ornament {
text-align: center;
font-size: 11px;
color: var(--gold-dark);
letter-spacing: 8px;
margin-bottom: 14px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.fc-ornament::before, .fc-ornament::after {
content: '';
flex: 1;
max-width: 60px;
height: 1px;
background: linear-gradient(90deg, transparent, var(--gold-dark), transparent);
}
.fc-level { text-align: center; }
.fc-level-char {
font-family: var(--font-serif);
font-size: 64px;
font-weight: 900;
background: linear-gradient(180deg, var(--gold-light), var(--gold), var(--gold-dark));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
line-height: 1.1;
filter: drop-shadow(0 2px 6px rgba(212,168,67,0.25));
}
.fc-level-sub {
font-family: var(--font-serif);
font-size: 14px;
color: var(--gold);
margin-top: 2px;
letter-spacing: 4px;
}
.fc-level-score {
font-size: 12px;
color: var(--gold-dark);
margin-top: 6px;
}
.fc-sign {
text-align: center;
font-family: var(--font-serif);
font-size: 14px;
color: var(--gold-light);
line-height: 1.9;
margin: 16px 0;
padding: 12px;
border-top: 1px solid rgba(212,168,67,0.12);
border-bottom: 1px solid rgba(212,168,67,0.12);
}
.fc-dims {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-top: 14px;
}
.fc-dim {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
background: rgba(212,168,67,0.05);
border-radius: var(--radius-sm);
border: 1px solid rgba(212,168,67,0.08);
}
.fc-dim-icon { width: 18px; height: 18px; flex-shrink: 0; }
.fc-dim-icon svg { width: 100%; height: 100%; }
.fc-dim-info { flex: 1; }
.fc-dim-name { font-size: 10px; color: var(--text-dim); }
.fc-dim-val { font-size: 12px; color: var(--gold); font-weight: 500; }
.fc-dim-bar {
width: 100%;
height: 3px;
background: rgba(212,168,67,0.12);
border-radius: 2px;
margin-top: 3px;
overflow: hidden;
}
.fc-dim-bar-fill {
height: 100%;
border-radius: 2px;
background: linear-gradient(90deg, var(--gold-dark), var(--gold));
transition: width 0.8s ease;
}
.fc-lucky {
display: flex;
justify-content: space-around;
margin-top: 14px;
padding-top: 14px;
border-top: 1px solid rgba(212,168,67,0.08);
}
.fc-lucky-item { text-align: center; }
.fc-lucky-label { font-size: 10px; color: var(--text-dim); margin-bottom: 3px; }
.fc-lucky-value { font-size: 12px; color: var(--gold-light); font-weight: 500; }
.fc-yiji {
display: flex;
gap: 10px;
margin-top: 14px;
}
.fc-yi, .fc-ji {
flex: 1;
padding: 8px 10px;
border-radius: var(--radius-sm);
font-size: 11px;
}
.fc-yi { background: rgba(76,175,80,0.06); border: 1px solid rgba(76,175,80,0.1); }
.fc-ji { background: rgba(244,67,54,0.06); border: 1px solid rgba(244,67,54,0.1); }
.fc-yj-label { font-weight: 600; margin-bottom: 4px; display: flex; align-items: center; gap: 4px; }
.fc-yi .fc-yj-label { color: #4CAF50; }
.fc-ji .fc-yj-label { color: #F44336; }
.fc-yj-label svg { width: 12px; height: 12px; }
.fc-yj-tags { display: flex; flex-wrap: wrap; gap: 3px; }
.fc-yj-tag { padding: 1px 7px; border-radius: 4px; font-size: 10px; }
.fc-yi .fc-yj-tag { background: rgba(76,175,80,0.1); color: #81C784; }
.fc-ji .fc-yj-tag { background: rgba(244,67,54,0.1); color: #EF9A9A; }
.fc-date {
text-align: center;
font-size: 10px;
color: var(--text-dim);
margin-top: 12px;
letter-spacing: 1px;
}
/* --- WeChat Style --- */
[data-style="wechat"] .fortune-card {
background: linear-gradient(160deg, #1A2332, #1E2D3D, #1A2332);
border: 1px solid rgba(100,180,255,0.1);
box-shadow: var(--shadow-card);
padding: 20px;
}
[data-theme="light"][data-style="wechat"] .fortune-card {
background: linear-gradient(160deg, #E3F2FD, #BBDEFB, #E3F2FD);
border-color: rgba(66,165,245,0.15);
}
[data-style="wechat"] .fc-level-char {
font-size: 48px;
-webkit-text-fill-color: #42A5F5;
background: none;
filter: none;
}
[data-style="wechat"] .fc-level-sub { color: #64B5F6; }
[data-style="wechat"] .fc-level-score { color: #546E7A; }
[data-style="wechat"] .fc-sign {
color: #90CAF9;
border-color: rgba(66,165,245,0.12);
}
[data-style="wechat"] .fc-dim {
background: rgba(66,165,245,0.05);
border-color: rgba(66,165,245,0.08);
}
[data-style="wechat"] .fc-dim-val { color: #64B5F6; }
[data-style="wechat"] .fc-dim-bar { background: rgba(66,165,245,0.1); }
[data-style="wechat"] .fc-dim-bar-fill { background: linear-gradient(90deg, #1565C0, #42A5F5); }
[data-style="wechat"] .fc-lucky { border-color: rgba(66,165,245,0.08); }
[data-style="wechat"] .fc-lucky-value { color: #90CAF9; }
[data-style="wechat"] .fc-ornament { color: #546E7A; }
[data-style="wechat"] .fc-ornament::before,
[data-style="wechat"] .fc-ornament::after { background: linear-gradient(90deg, transparent, #546E7A, transparent); }
/* --- Apple Style --- */
[data-style="apple"] .fortune-card {
background: #FFFFFF;
border: 1px solid #E5E5EA;
box-shadow: var(--shadow-card);
padding: 20px;
color: #1C1C1E;
}
[data-theme="dark"][data-style="apple"] .fortune-card {
background: #1C1C1E;
border-color: #38383A;
color: #F5F0E8;
}
[data-style="apple"] .fc-level-char {
font-size: 48px;
-webkit-text-fill-color: #FF375F;
background: none;
filter: none;
}
[data-style="apple"] .fc-level-sub { color: #8E8E93; }
[data-style="apple"] .fc-level-score { color: #AEAEB2; }
[data-style="apple"] .fc-sign {
color: #636366;
border-color: #E5E5EA;
}
[data-style="apple"] .fc-dim {
background: #F2F2F7;
border-color: transparent;
}
[data-theme="dark"][data-style="apple"] .fc-dim { background: #2C2C2E; }
[data-style="apple"] .fc-dim-val { color: #1C1C1E; }
[data-theme="dark"][data-style="apple"] .fc-dim-val { color: #F5F0E8; }
[data-style="apple"] .fc-dim-bar { background: rgba(0,0,0,0.06); }
[data-theme="dark"][data-style="apple"] .fc-dim-bar { background: rgba(255,255,255,0.08); }
[data-style="apple"] .fc-dim-bar-fill { background: #FF375F; }
[data-style="apple"] .fc-lucky { border-color: #E5E5EA; }
[data-theme="dark"][data-style="apple"] .fc-lucky { border-color: #38383A; }
[data-style="apple"] .fc-lucky-value { color: #1C1C1E; font-weight: 600; }
[data-theme="dark"][data-style="apple"] .fc-lucky-value { color: #F5F0E8; }
[data-style="apple"] .fc-lucky-label { color: #8E8E93; }
[data-style="apple"] .fc-yi { background: rgba(52,199,89,0.06); border-color: rgba(52,199,89,0.1); }
[data-style="apple"] .fc-ji { background: rgba(255,59,48,0.06); border-color: rgba(255,59,48,0.1); }
[data-style="apple"] .fc-yi .fc-yj-tag { background: rgba(52,199,89,0.1); color: #34C759; border-radius: 10px; }
[data-style="apple"] .fc-ji .fc-yj-tag { background: rgba(255,59,48,0.1); color: #FF3B30; border-radius: 10px; }
[data-style="apple"] .fc-ornament { color: #AEAEB2; }
[data-style="apple"] .fc-ornament::before,
[data-style="apple"] .fc-ornament::after { background: linear-gradient(90deg, transparent, #AEAEB2, transparent); }
[data-style="apple"] .fc-date { color: #AEAEB2; }
/* ====== Settings Page ====== */
.settings-panel {
background: var(--bg-card);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
overflow: hidden;
transition: background var(--transition), border-color var(--transition);
}
.settings-group {
padding: 14px 16px;
border-bottom: 1px solid var(--divider);
}
.settings-group:last-child { border-bottom: none; }
.sg-title {
font-size: 12px;
color: var(--text-dim);
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.s-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 9px 0;
}
.s-row:not(:last-child) { border-bottom: 1px solid var(--divider); }
.s-label { font-size: 14px; color: var(--text); display: flex; align-items: center; gap: 8px; }
.s-label svg { width: 18px; height: 18px; color: var(--text-secondary); }
.s-desc { font-size: 11px; color: var(--text-dim); margin-top: 1px; }
.ios-toggle {
width: 44px;
height: 26px;
border-radius: 13px;
background: #39393D;
position: relative;
cursor: pointer;
transition: background 0.3s;
flex-shrink: 0;
}
.ios-toggle.on { background: #34C759; }
.ios-toggle::after {
content: '';
position: absolute;
width: 22px;
height: 22px;
border-radius: 50%;
background: #fff;
top: 2px;
left: 2px;
transition: transform 0.3s;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.ios-toggle.on::after { transform: translateX(18px); }
.style-selector {
display: flex;
gap: 8px;
}
.style-opt {
flex: 1;
padding: 10px 6px;
border-radius: var(--radius-md);
border: 2px solid var(--border);
text-align: center;
cursor: pointer;
transition: all 0.25s;
background: transparent;
}
.style-opt.active {
border-color: var(--primary);
background: rgba(196,30,58,0.06);
}
.style-opt .so-icon { font-size: 22px; margin-bottom: 3px; }
.style-opt .so-name { font-size: 11px; color: var(--text-secondary); }
.style-opt.active .so-name { color: var(--primary-light); font-weight: 500; }
.chip-group { display: flex; gap: 6px; flex-wrap: wrap; }
.chip {
padding: 6px 14px;
border-radius: 16px;
border: 1px solid var(--border);
background: transparent;
color: var(--text-secondary);
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
font-family: var(--font-sans);
}
.chip.active {
border-color: var(--primary);
background: rgba(196,30,58,0.08);
color: var(--primary-light);
}
.s-nav {
font-size: 13px;
color: var(--primary-light);
cursor: pointer;
display: flex;
align-items: center;
gap: 2px;
}
.s-nav svg { width: 14px; height: 14px; }
/* ====== Session Page ====== */
.session-list {
background: var(--bg-card);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
overflow: hidden;
transition: all var(--transition);
}
.session-item {
display: flex;
align-items: center;
gap: 12px;
padding: 13px 16px;
border-bottom: 1px solid var(--divider);
cursor: pointer;
transition: background 0.15s;
}
.session-item:hover { background: var(--divider); }
.session-item:last-child { border-bottom: none; }
.sv-avatar {
width: 44px;
height: 44px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
flex-shrink: 0;
}
.sv-avatar.fortune { background: rgba(196,30,58,0.1); }
.sv-avatar.discover { background: rgba(66,165,245,0.1); }
.sv-avatar.footprint { background: rgba(76,175,80,0.1); }
.sv-info { flex: 1; min-width: 0; }
.sv-name { font-size: 15px; font-weight: 500; color: var(--text); }
.sv-sub { font-size: 12px; color: var(--text-dim); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sv-meta { text-align: right; flex-shrink: 0; }
.sv-time { font-size: 11px; color: var(--text-dim); }
.sv-badge {
display: inline-block;
min-width: 16px;
height: 16px;
border-radius: 8px;
background: var(--primary);
color: #fff;
font-size: 10px;
line-height: 16px;
text-align: center;
margin-top: 3px;
padding: 0 4px;
}
.push-mock {
background: var(--bg-card);
backdrop-filter: blur(20px);
border-radius: 16px;
padding: 12px 14px;
display: flex;
align-items: center;
gap: 10px;
margin-top: 16px;
border: 1px solid var(--border);
box-shadow: var(--shadow-card);
}
.push-icon {
width: 34px;
height: 34px;
border-radius: 8px;
background: var(--primary);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
flex-shrink: 0;
}
.push-body { flex: 1; }
.push-title { font-size: 13px; font-weight: 600; color: var(--text); }
.push-text { font-size: 12px; color: var(--text-secondary); margin-top: 1px; }
.push-time { font-size: 11px; color: var(--text-dim); flex-shrink: 0; }
/* ====== API Section ====== */
.api-card {
padding: 12px 14px;
background: var(--bg-card);
border-radius: var(--radius-md);
border: 1px solid var(--border);
margin-bottom: 8px;
transition: all var(--transition);
}
.api-method {
display: inline-block;
padding: 1px 7px;
border-radius: 4px;
font-size: 10px;
font-weight: 700;
margin-right: 6px;
}
.api-method.get { background: #1B5E20; color: #4CAF50; }
.api-method.post { background: #E65100; color: #FF9800; }
.api-path { font-size: 13px; color: var(--text); font-family: 'SF Mono', monospace; }
.api-desc { font-size: 11px; color: var(--text-dim); margin-top: 4px; }
.data-block {
background: #0D1117;
border-radius: var(--radius-md);
padding: 14px;
font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 11px;
line-height: 1.6;
overflow-x: auto;
border: 1px solid #21262D;
margin-top: 10px;
}
.dk { color: #79C0FF; }
.ds { color: #A5D6FF; }
.dn { color: #F2CC60; }
.db { color: #FF7B72; }
.dc { color: #8B949E; }
.section-title {
font-size: 18px;
font-weight: 700;
color: var(--text);
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 6px;
}
.section-title svg { width: 20px; height: 20px; color: var(--primary-light); }
.section-desc {
font-size: 12px;
color: var(--text-dim);
margin-bottom: 16px;
}
.card-gap { margin-top: 16px; }
/* Theme indicator */
.theme-indicator {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
gap: 8px;
z-index: 200;
}
.theme-fab {
width: 44px;
height: 44px;
border-radius: 50%;
border: 2px solid var(--border);
background: var(--bg-card);
color: var(--text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 12px rgba(0,0,0,0.3);
transition: all 0.25s;
font-size: 18px;
}
.theme-fab:hover { transform: scale(1.1); }
.theme-fab:active { transform: scale(0.95); }
/* Sort indicator */
.sort-indicator {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: var(--text-dim);
padding: 8px 0;
}
.sort-indicator svg { width: 12px; height: 12px; }
/* Animations */
@keyframes slideUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.anim-in { animation: slideUp 0.35s ease both; }
.anim-in:nth-child(2) { animation-delay: 0.06s; }
.anim-in:nth-child(3) { animation-delay: 0.12s; }
.anim-in:nth-child(4) { animation-delay: 0.18s; }
.anim-in:nth-child(5) { animation-delay: 0.24s; }
</style>
</head>
<body>
<div class="app-shell">
<div class="nav-bar">
<div class="nav-bar-inner">
<div class="nav-title">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a15 15 0 0 1 4 10 15 15 0 0 1-4 10 15 15 0 0 1-4-10A15 15 0 0 1 12 2z"/><line x1="2" y1="12" x2="22" y2="12"/></svg>
每日运势
</div>
<div class="nav-actions">
<button class="nav-btn" title="切换主题" onclick="toggleTheme()">
<svg class="theme-icon-dark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<svg class="theme-icon-light" style="display:none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
</button>
<button class="nav-btn" title="设置" onclick="switchSection('settings')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</button>
</div>
</div>
<div class="nav-tabs">
<button class="nav-tab active" onclick="switchSection('timeline',this)">时间线</button>
<button class="nav-tab" onclick="switchSection('cards',this)">卡片样式</button>
<button class="nav-tab" onclick="switchSection('settings',this)">设置</button>
<button class="nav-tab" onclick="switchSection('session',this)">会话入口</button>
<button class="nav-tab" onclick="switchSection('api',this)">API</button>
</div>
</div>
<!-- ====== Timeline Section (Main) ====== -->
<div class="section active" id="sec-timeline">
<div class="sort-indicator">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
最新在上面 · <span style="color:var(--primary-light);cursor:pointer" onclick="switchSection('settings',null)">可在设置中更改</span>
</div>
<div class="timeline" id="timeline-container">
<!-- Today - Expanded by default -->
<div class="tl-item expanded anim-in" data-date="2026-05-13" data-is-today="true">
<div class="tl-summary" onclick="toggleTimeline(this)">
<div class="tl-dot today">🔮</div>
<div class="tl-info">
<div class="tl-date-row">
<span class="tl-date">今天 · 5月13日</span>
<span class="tl-level-badge daji">大吉</span>
</div>
<div class="tl-preview">鹏程万里风正举,一帆风顺向天涯…</div>
<div class="tl-dims-row">
<span class="tl-dim-chip">❤️ 中吉</span>
<span class="tl-dim-chip">💼 吉</span>
<span class="tl-dim-chip">💰 小吉</span>
<span class="tl-dim-chip">💪 大吉</span>
</div>
</div>
<div class="tl-chevron icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<div class="tl-detail">
<div class="tl-detail-inner">
<div class="fortune-card" id="fortune-card-today"></div>
</div>
<div class="tl-actions">
<button class="action-btn primary" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
换一签
</button>
<button class="action-btn" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
编辑
</button>
<button class="action-btn" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>
分享
</button>
</div>
</div>
</div>
<!-- Yesterday -->
<div class="tl-item anim-in" data-date="2026-05-12">
<div class="tl-summary" onclick="toggleTimeline(this)">
<div class="tl-dot past">📜</div>
<div class="tl-info">
<div class="tl-date-row">
<span class="tl-date">昨天 · 5月12日</span>
<span class="tl-level-badge zhongji">中吉</span>
</div>
<div class="tl-preview">春风化雨润无声,静待花开自有情…</div>
<div class="tl-dims-row">
<span class="tl-dim-chip">❤️ 吉</span>
<span class="tl-dim-chip">💼 小吉</span>
<span class="tl-dim-chip">💰 中吉</span>
</div>
</div>
<div class="tl-chevron icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<div class="tl-detail">
<div class="tl-detail-inner">
<div class="fortune-card" id="fortune-card-yesterday"></div>
</div>
<div class="tl-actions">
<button class="action-btn" style="opacity:0.5;cursor:default" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
已锁定
</button>
<button class="action-btn" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
编辑
</button>
</div>
</div>
</div>
<!-- Day before yesterday -->
<div class="tl-item anim-in" data-date="2026-05-11">
<div class="tl-summary" onclick="toggleTimeline(this)">
<div class="tl-dot past">📜</div>
<div class="tl-info">
<div class="tl-date-row">
<span class="tl-date">前天 · 5月11日</span>
<span class="tl-level-badge ji"></span>
</div>
<div class="tl-preview">山高水远路漫漫,行到尽头是坦途…</div>
<div class="tl-dims-row">
<span class="tl-dim-chip">❤️ 末吉</span>
<span class="tl-dim-chip">💼 中吉</span>
<span class="tl-dim-chip">💰 吉</span>
</div>
</div>
<div class="tl-chevron icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<div class="tl-detail">
<div class="tl-detail-inner">
<div class="fortune-card" id="fortune-card-day11"></div>
</div>
<div class="tl-actions">
<button class="action-btn" style="opacity:0.5;cursor:default" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
已锁定
</button>
<button class="action-btn" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
编辑
</button>
</div>
</div>
</div>
<!-- May 10 -->
<div class="tl-item anim-in" data-date="2026-05-10">
<div class="tl-summary" onclick="toggleTimeline(this)">
<div class="tl-dot past">📜</div>
<div class="tl-info">
<div class="tl-date-row">
<span class="tl-date">5月10日</span>
<span class="tl-level-badge xiaoji">小吉</span>
</div>
<div class="tl-preview">月到中天分外明,守得云开见月明…</div>
</div>
<div class="tl-chevron icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<div class="tl-detail">
<div class="tl-detail-inner">
<div class="fortune-card" id="fortune-card-day10"></div>
</div>
<div class="tl-actions">
<button class="action-btn" style="opacity:0.5;cursor:default" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
已锁定
</button>
<button class="action-btn" onclick="event.stopPropagation()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
编辑
</button>
</div>
</div>
</div>
</div>
</div>
<!-- ====== Card Styles Section ====== -->
<div class="section" id="sec-cards">
<div class="section-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
卡片样式预览
</div>
<div class="section-desc">点击下方切换风格 · 卡片跟随动态主题</div>
<div class="style-selector" style="margin-bottom:16px">
<div class="style-opt active" onclick="switchStyle('ancient',this)">🏯<div class="so-name">古风签筒</div></div>
<div class="style-opt" onclick="switchStyle('wechat',this)">📊<div class="so-name">微信运动</div></div>
<div class="style-opt" onclick="switchStyle('apple',this)"><div class="so-name">Apple风</div></div>
</div>
<div class="fortune-card" id="fortune-card-preview"></div>
</div>
<!-- ====== Settings Section ====== -->
<div class="section" id="sec-settings">
<div class="section-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
运势设置
</div>
<div class="section-desc">管理显示项目 · 推送时间 · 风格切换 · 排序方式</div>
<div class="settings-panel">
<div class="settings-group">
<div class="sg-title">🎨 卡片风格</div>
<div class="style-selector">
<div class="style-opt active" onclick="switchStyle('ancient',this)">🏯<div class="so-name">古风签筒</div></div>
<div class="style-opt" onclick="switchStyle('wechat',this)">📊<div class="so-name">微信运动</div></div>
<div class="style-opt" onclick="switchStyle('apple',this)"><div class="so-name">Apple风</div></div>
</div>
</div>
<div class="settings-group">
<div class="sg-title">📋 显示项目</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>爱情运势</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>事业运势</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>财运</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>健康</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>学业</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>人际</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>幸运指标</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>宜忌</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>签文</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
</div>
<div class="settings-group">
<div class="sg-title">⏰ 推送设置</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>每日推送</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div style="margin-top:8px">
<div style="font-size:11px;color:var(--text-dim);margin-bottom:6px">推送时间</div>
<div class="chip-group">
<span class="chip" onclick="selectChip(this)">06:00</span>
<span class="chip" onclick="selectChip(this)">07:00</span>
<span class="chip active" onclick="selectChip(this)">08:00</span>
<span class="chip" onclick="selectChip(this)">09:00</span>
<span class="chip" onclick="selectChip(this)">10:00</span>
</div>
</div>
<div style="margin-top:10px">
<div style="font-size:11px;color:var(--text-dim);margin-bottom:6px">推送频率</div>
<div class="chip-group">
<span class="chip active" onclick="selectChip(this)">每天1次</span>
<span class="chip" onclick="selectChip(this)">早晚各1次</span>
<span class="chip" onclick="selectChip(this)">自定义</span>
</div>
</div>
</div>
<div class="settings-group">
<div class="sg-title">🔄 排序方式</div>
<div class="s-row">
<div>
<div class="s-label">时间线排序</div>
<div class="s-desc">控制运势卡片的显示顺序</div>
</div>
</div>
<div style="margin-top:6px">
<div class="chip-group">
<span class="chip active" onclick="selectSort(this,'newest')">最新在上面</span>
<span class="chip" onclick="selectSort(this,'oldest')">最早在上面</span>
</div>
</div>
</div>
<div class="settings-group">
<div class="sg-title">🔧 扩展设置</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a15 15 0 0 1 4 10 15 15 0 0 1-4 10 15 15 0 0 1-4-10A15 15 0 0 1 12 2z"/></svg>星座绑定</div>
<div class="s-nav">设置 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>生日绑定</div>
<div class="s-nav">设置 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/></svg>运势提醒</div>
<div class="ios-toggle" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>60秒新闻</div>
<div class="ios-toggle" onclick="this.classList.toggle('on')"></div>
</div>
<div class="s-row">
<div class="s-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="17" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="17" y1="18" x2="3" y2="18"/></svg>每日微语</div>
<div class="ios-toggle on" onclick="this.classList.toggle('on')"></div>
</div>
</div>
</div>
</div>
<!-- ====== Session Section ====== -->
<div class="section" id="sec-session">
<div class="section-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
会话流入口
</div>
<div class="section-desc">灵感页面会话列表 · 新增"每日运势"系统会话</div>
<div class="session-list">
<div class="session-item" style="background:rgba(196,30,58,0.04)">
<div class="sv-avatar fortune">🔮</div>
<div class="sv-info">
<div class="sv-name">每日运势 <span style="font-size:10px;color:var(--primary-light);margin-left:4px;padding:1px 6px;border-radius:6px;background:rgba(196,30,58,0.1)">NEW</span></div>
<div class="sv-sub">今日大吉 · 爱情中吉 · 事业吉 · 财运小吉</div>
</div>
<div class="sv-meta">
<div class="sv-time">08:00</div>
<div class="sv-badge">1</div>
</div>
</div>
<div class="session-item">
<div class="sv-avatar discover">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</div>
<div class="sv-info">
<div class="sv-name">发现</div>
<div class="sv-sub">热门句子 · 分类浏览 · 每日推荐</div>
</div>
<div class="sv-meta"><div class="sv-time">昨天</div></div>
</div>
<div class="session-item">
<div class="sv-avatar footprint">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
</div>
<div class="sv-info">
<div class="sv-name">足迹</div>
<div class="sv-sub">浏览 · 点赞 · 收藏 · 稍后读 · 笔记</div>
</div>
<div class="sv-meta"><div class="sv-time">3天前</div></div>
</div>
<div class="session-item">
<div class="sv-avatar" style="background:rgba(212,168,67,0.1)">📅</div>
<div class="sv-info">
<div class="sv-name">日签卡片</div>
<div class="sv-sub">每日一句 · 样式切换 · 一键分享</div>
</div>
<div class="sv-meta"><div class="sv-time">6小时前</div></div>
</div>
</div>
<div class="card-gap"></div>
<div class="section-title" style="font-size:15px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
推送消息示例
</div>
<div class="push-mock">
<div class="push-icon">🔮</div>
<div class="push-body">
<div class="push-title">每日运势</div>
<div class="push-text">今日大吉!爱情中吉,事业吉,财运小吉…</div>
</div>
<div class="push-time">08:00</div>
</div>
</div>
<!-- ====== API Section ====== -->
<div class="section" id="sec-api">
<div class="section-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
API 接口设计
</div>
<div class="section-desc">服务端接口 · 数据结构 · 数据库设计</div>
<div class="api-card"><span class="api-method get">GET</span><span class="api-path">/api/fortune/daily</span><div class="api-desc">获取今日运势 · hash(uid+date)确定性生成</div></div>
<div class="api-card"><span class="api-method get">GET</span><span class="api-path">/api/fortune/daily?regen=1</span><div class="api-desc">重新生成今日运势 · 换一签 · 仅今天可用</div></div>
<div class="api-card"><span class="api-method get">GET</span><span class="api-path">/api/fortune/history?uid=xxx&page=1</span><div class="api-desc">获取历史运势列表 · 分页</div></div>
<div class="api-card"><span class="api-method post">POST</span><span class="api-path">/api/fortune/config</span><div class="api-desc">更新用户运势配置 · 显示/推送/风格/排序</div></div>
<div class="api-card"><span class="api-method get">GET</span><span class="api-path">/api/fortune/themes</span><div class="api-desc">获取可用卡片风格列表</div></div>
<div class="api-card"><span class="api-method get">GET</span><span class="api-path">/api/fortune/image?uid=xxx&date=2026-05-13</span><div class="api-desc">获取运势卡片图片 · GD库生成 · CDN缓存</div></div>
<div class="api-card"><span class="api-method get">GET</span><span class="api-path">/api/fortune/60s</span><div class="api-desc">获取今日60秒新闻 · 附加到运势卡片</div></div>
<div class="card-gap"></div>
<div class="section-title" style="font-size:15px">📦 运势数据结构</div>
<div class="data-block">
<span class="dc">{</span>
<span class="dk">"date"</span>: <span class="ds">"2026-05-13"</span>,
<span class="dk">"uid"</span>: <span class="ds">"user_abc123"</span>,
<span class="dk">"fortune_level"</span>: <span class="ds">"大吉"</span>,
<span class="dk">"fortune_score"</span>: <span class="dn">92</span>,
<span class="dk">"sign_text"</span>: <span class="ds">"鹏程万里风正举..."</span>,
<span class="dk">"dimensions"</span>: <span class="dc">{</span>
<span class="dk">"love"</span>: <span class="dc">{</span> <span class="dk">"level"</span>: <span class="ds">"中吉"</span>, <span class="dk">"score"</span>: <span class="dn">78</span> <span class="dc">}</span>,
<span class="dk">"career"</span>: <span class="dc">{</span> <span class="dk">"level"</span>: <span class="ds">"吉"</span>, <span class="dk">"score"</span>: <span class="dn">85</span> <span class="dc">}</span>,
<span class="dk">"wealth"</span>: <span class="dc">{</span> <span class="dk">"level"</span>: <span class="ds">"小吉"</span>, <span class="dk">"score"</span>: <span class="dn">70</span> <span class="dc">}</span>,
<span class="dk">"health"</span>: <span class="dc">{</span> <span class="dk">"level"</span>: <span class="ds">"大吉"</span>, <span class="dk">"score"</span>: <span class="dn">95</span> <span class="dc">}</span>,
<span class="dk">"study"</span>: <span class="dc">{</span> <span class="dk">"level"</span>: <span class="ds">"末吉"</span>, <span class="dk">"score"</span>: <span class="dn">60</span> <span class="dc">}</span>,
<span class="dk">"social"</span>: <span class="dc">{</span> <span class="dk">"level"</span>: <span class="ds">"中吉"</span>, <span class="dk">"score"</span>: <span class="dn">75</span> <span class="dc">}</span>
<span class="dc">}</span>,
<span class="dk">"lucky"</span>: <span class="dc">{</span>
<span class="dk">"number"</span>: <span class="dn">7</span>, <span class="dk">"color"</span>: <span class="ds">"朱红"</span>,
<span class="dk">"direction"</span>: <span class="ds">"东南"</span>, <span class="dk">"constellation"</span>: <span class="ds">"天秤"</span>
<span class="dc">}</span>,
<span class="dk">"suitable"</span>: [<span class="ds">"求财"</span>,<span class="ds">"出行"</span>,<span class="ds">"签约"</span>],
<span class="dk">"unsuitable"</span>: [<span class="ds">"动土"</span>,<span class="ds">"嫁娶"</span>],
<span class="dk">"theme"</span>: <span class="ds">"ancient"</span>,
<span class="dk">"is_today"</span>: <span class="db">true</span>,
<span class="dk">"can_regenerate"</span>: <span class="db">true</span>,
<span class="dk">"sort_order"</span>: <span class="ds">"newest_first"</span>
<span class="dc">}</span>
</div>
<div class="card-gap"></div>
<div class="section-title" style="font-size:15px">🗄️ 数据库表</div>
<div class="data-block">
<span class="db">-- 签文数据库</span>
<span class="dk">tool_fortune_data</span> (
id, type(<span class="ds">'sign'|'yi'|'ji'|'lucky'</span>),
level, content, category, weight
)
<span class="db">-- 用户运势记录</span>
<span class="dk">tool_fortune_record</span> (
id, uid, date, fortune_level, fortune_score,
sign_text, dimensions_json, lucky_json,
suitable_json, unsuitable_json, theme,
regen_count, image_url, created_at
)
<span class="db">-- 用户运势配置</span>
<span class="dk">tool_fortune_config</span> (
id, uid, theme, show_dims_json,
push_enabled, push_time, push_freq,
sort_order, constellation, birthday,
extra_json, updated_at
)
</div>
</div>
</div>
<!-- Floating theme toggle -->
<div class="theme-indicator">
<div class="theme-fab" title="切换主题" onclick="toggleTheme()">🌓</div>
</div>
<script>
const SVG_ICONS = {
heart: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',
briefcase: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>',
dollar: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>',
pulse: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>',
book: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
users: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>',
check: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>',
x: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
};
const fortuneData = {
today: {
level: '大吉', sub: '上上签', score: 92,
sign: '鹏程万里风正举<br>一帆风顺向天涯<br>贵人相助逢佳运<br>事事如意福无涯',
dims: [
{ icon: 'heart', name: '爱情', level: '中吉', pct: 78 },
{ icon: 'briefcase', name: '事业', level: '吉', pct: 85 },
{ icon: 'dollar', name: '财运', level: '小吉', pct: 70 },
{ icon: 'pulse', name: '健康', level: '大吉', pct: 95 },
{ icon: 'book', name: '学业', level: '末吉', pct: 60 },
{ icon: 'users', name: '人际', level: '中吉', pct: 75 },
],
lucky: [{ l: '幸运数字', v: '7' }, { l: '幸运颜色', v: '朱红' }, { l: '幸运方位', v: '东南' }, { l: '幸运星座', v: '天秤' }],
yi: ['求财', '出行', '签约'], ji: ['动土', '嫁娶'],
date: '丙午年 五月十三 · 星期二',
},
yesterday: {
level: '中吉', sub: '中上签', score: 78,
sign: '春风化雨润无声<br>静待花开自有情<br>莫道前路多坎坷<br>柳暗花明又一村',
dims: [
{ icon: 'heart', name: '爱情', level: '吉', pct: 82 },
{ icon: 'briefcase', name: '事业', level: '小吉', pct: 68 },
{ icon: 'dollar', name: '财运', level: '中吉', pct: 78 },
{ icon: 'pulse', name: '健康', level: '吉', pct: 80 },
{ icon: 'book', name: '学业', level: '末吉', pct: 55 },
{ icon: 'users', name: '人际', level: '小吉', pct: 65 },
],
lucky: [{ l: '幸运数字', v: '3' }, { l: '幸运颜色', v: '靛蓝' }, { l: '幸运方位', v: '西北' }, { l: '幸运星座', v: '双鱼' }],
yi: ['纳财', '安床'], ji: ['开市', '出行'],
date: '丙午年 五月十二 · 星期一',
},
day11: {
level: '吉', sub: '中签', score: 75,
sign: '山高水远路漫漫<br>行到尽头是坦途<br>莫愁前路无知己<br>天下谁人不识君',
dims: [
{ icon: 'heart', name: '爱情', level: '末吉', pct: 55 },
{ icon: 'briefcase', name: '事业', level: '中吉', pct: 78 },
{ icon: 'dollar', name: '财运', level: '吉', pct: 75 },
{ icon: 'pulse', name: '健康', level: '小吉', pct: 68 },
{ icon: 'book', name: '学业', level: '中吉', pct: 76 },
{ icon: 'users', name: '人际', level: '吉', pct: 80 },
],
lucky: [{ l: '幸运数字', v: '5' }, { l: '幸运颜色', v: '翠绿' }, { l: '幸运方位', v: '正南' }, { l: '幸运星座', v: '射手' }],
yi: ['祭祀', '修造'], ji: ['嫁娶', '入宅'],
date: '丙午年 五月十一 · 星期日',
},
day10: {
level: '小吉', sub: '中下签', score: 65,
sign: '月到中天分外明<br>守得云开见月明<br>但将冷眼观螃蟹<br>看你横行到几时',
dims: [
{ icon: 'heart', name: '爱情', level: '小吉', pct: 65 },
{ icon: 'briefcase', name: '事业', level: '末吉', pct: 58 },
{ icon: 'dollar', name: '财运', level: '小吉', pct: 62 },
{ icon: 'pulse', name: '健康', level: '吉', pct: 80 },
{ icon: 'book', name: '学业', level: '中吉', pct: 72 },
{ icon: 'users', name: '人际', level: '末吉', pct: 55 },
],
lucky: [{ l: '幸运数字', v: '9' }, { l: '幸运颜色', v: '琥珀' }, { l: '幸运方位', v: '东北' }, { l: '幸运星座', v: '双子' }],
yi: ['祈福', '求嗣'], ji: ['动土', '安葬'],
date: '丙午年 五月初十 · 星期六',
},
};
function buildFortuneCard(data) {
let dimsHtml = data.dims.map(d => `
<div class="fc-dim">
<div class="fc-dim-icon">${SVG_ICONS[d.icon] || ''}</div>
<div class="fc-dim-info">
<div class="fc-dim-name">${d.name}</div>
<div class="fc-dim-val">${d.level}</div>
<div class="fc-dim-bar"><div class="fc-dim-bar-fill" style="width:${d.pct}%"></div></div>
</div>
</div>`).join('');
let luckyHtml = data.lucky.map(l => `
<div class="fc-lucky-item"><div class="fc-lucky-label">${l.l}</div><div class="fc-lucky-value">${l.v}</div></div>`).join('');
let yiTags = data.yi.map(t => `<span class="fc-yj-tag">${t}</span>`).join('');
let jiTags = data.ji.map(t => `<span class="fc-yj-tag">${t}</span>`).join('');
return `
<div class="fc-ornament">✦ 签 ✦</div>
<div class="fc-level">
<div class="fc-level-char">${data.level}</div>
<div class="fc-level-sub">${data.sub}</div>
<div class="fc-level-score">综合运势 ${data.score} 分</div>
</div>
<div class="fc-sign">${data.sign}</div>
<div class="fc-dims">${dimsHtml}</div>
<div class="fc-lucky">${luckyHtml}</div>
<div class="fc-yiji">
<div class="fc-yi">
<div class="fc-yj-label">${SVG_ICONS.check} 宜</div>
<div class="fc-yj-tags">${yiTags}</div>
</div>
<div class="fc-ji">
<div class="fc-yj-label">${SVG_ICONS.x} 忌</div>
<div class="fc-yj-tags">${jiTags}</div>
</div>
</div>
<div class="fc-date">${data.date}</div>`;
}
function initCards() {
document.getElementById('fortune-card-today').innerHTML = buildFortuneCard(fortuneData.today);
document.getElementById('fortune-card-yesterday').innerHTML = buildFortuneCard(fortuneData.yesterday);
document.getElementById('fortune-card-day11').innerHTML = buildFortuneCard(fortuneData.day11);
document.getElementById('fortune-card-day10').innerHTML = buildFortuneCard(fortuneData.day10);
document.getElementById('fortune-card-preview').innerHTML = buildFortuneCard(fortuneData.today);
}
function toggleTimeline(summaryEl) {
const item = summaryEl.closest('.tl-item');
item.classList.toggle('expanded');
}
function switchSection(name, tabEl) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.getElementById('sec-' + name).classList.add('active');
document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
if (tabEl) tabEl.classList.add('active');
else document.querySelectorAll('.nav-tab').forEach(t => { if (t.textContent.includes({'timeline':'时间线','cards':'卡片','settings':'设置','session':'会话','api':'API'}[name])) t.classList.add('active'); });
}
function toggleTheme() {
const html = document.documentElement;
const isDark = html.getAttribute('data-theme') === 'dark';
html.setAttribute('data-theme', isDark ? 'light' : 'dark');
document.querySelector('.theme-icon-dark').style.display = isDark ? 'none' : 'block';
document.querySelector('.theme-icon-light').style.display = isDark ? 'block' : 'none';
}
function switchStyle(style, el) {
document.documentElement.setAttribute('data-style', style);
document.querySelectorAll('.style-opt').forEach(o => o.classList.remove('active'));
if (el) el.classList.add('active');
else document.querySelectorAll('.style-opt').forEach(o => { if (o.querySelector('.so-name').textContent.includes({'ancient':'古风','wechat':'微信','apple':'Apple'}[style])) o.classList.add('active'); });
}
function selectChip(el) {
el.parentElement.querySelectorAll('.chip').forEach(c => c.classList.remove('active'));
el.classList.add('active');
}
function selectSort(el, order) {
el.parentElement.querySelectorAll('.chip').forEach(c => c.classList.remove('active'));
el.classList.add('active');
const container = document.getElementById('timeline-container');
const items = Array.from(container.children);
if (order === 'oldest') items.reverse();
items.forEach(item => container.appendChild(item));
const indicator = document.querySelector('.sort-indicator');
if (indicator) indicator.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
${order === 'newest' ? '最新在上面' : '最早在上面'} · <span style="color:var(--primary-light);cursor:pointer" onclick="switchSection('settings',null)">可在设置中更改</span>`;
}
initCards();
</script>
</body>
</html>