Files
xianyan/docs/mockups/leisure_timeline.html
Developer 355191aaf6 feat(leisure): 新增闲情逸致模块与多项功能优化
本次提交完成多项核心更新:
1. 新增闲情逸致功能模块,包含时间线、收藏标注、季节主题等基础框架
2. 替换hive为社区维护的hive_ce包,修复依赖兼容问题
3. 统一替换"开发中"提示为"当前设备不支持",优化用户提示文案
4. 新增多项功能开关与特性标志,统一管理不可用功能提示
5. 完善用户账户洞察系统,新增头像审核中状态检测
6. 优化TTS语音朗读服务,修复Android端引擎初始化问题
7. 重构知识图谱缩放手势逻辑,解决缩放不跟手问题
8. 新增精灵头像组件,替换默认聊天头像样式
9. 新增外部链接跳转确认弹窗,提升使用安全性
10. 升级后端API接口,新增签到配置获取与补签积分规则动态读取
11. 完善多语言翻译覆盖率限制,非中文语言仅显示最高50%进度
12. 新增HTTP缓存拦截器,优化网络请求性能
13. 新增恢复出厂设置选项,完善数据管理功能

同时修复了多处代码细节问题:简化字符串拼接、优化布局代码、移除多余代码等。
2026-05-27 08:06:54 +08:00

1372 lines
39 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=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>闲情逸致 — 时间线主页面</title>
<style>
:root {
--primary: #6C63FF;
--primary-light: #8B83FF;
--accent: #4ECDC4;
--secondary: #FF6B6B;
--success: #10B981;
--warning: #F59E0B;
--error: #EF4444;
--info: #3B82F6;
--bg-primary: #FAFAFA;
--bg-secondary: #F5F5F5;
--bg-card: #FFFFFF;
--bg-elevated: #FFFFFF;
--text-primary: #1A1A2E;
--text-secondary: #6B7280;
--text-hint: #9CA3AF;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--spring: #4ECDC4;
--summer: #FF6B6B;
--autumn: #F59E0B;
--winter: #3B82F6;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
.phone-frame {
max-width: 430px;
margin: 0 auto;
min-height: 100vh;
position: relative;
background: var(--bg-primary);
}
/* ===== AppBar ===== */
.appbar {
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
background: rgba(250, 250, 250, 0.78);
border-bottom: 0.5px solid rgba(0,0,0,0.08);
padding: 12px var(--space-md);
display: flex;
align-items: center;
justify-content: space-between;
}
.appbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.appbar-back {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
cursor: pointer;
border: none;
color: var(--text-primary);
}
.appbar-title {
font-size: 17px;
font-weight: 600;
letter-spacing: -0.2px;
}
.appbar-subtitle {
font-size: 12px;
color: var(--text-hint);
margin-top: 1px;
}
.appbar-right {
display: flex;
gap: 8px;
}
.appbar-btn {
width: 36px;
height: 36px;
border-radius: 10px;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
cursor: pointer;
border: none;
color: var(--text-secondary);
transition: all 0.2s;
}
.appbar-btn:active {
transform: scale(0.92);
background: rgba(0,0,0,0.06);
}
/* ===== Swap Button (Fixed) ===== */
.swap-btn-fixed {
position: fixed;
top: 72px;
left: 50%;
transform: translateX(-50%);
z-index: 99;
width: 44px;
height: 44px;
border-radius: 50%;
background: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
cursor: pointer;
border: none;
box-shadow: 0 4px 12px rgba(108, 99, 255, 0.35);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.swap-btn-fixed:active {
transform: translateX(-50%) scale(0.88);
}
.swap-btn-fixed .swap-icon {
transition: transform 0.3s;
}
.swap-btn-fixed.swapped .swap-icon {
transform: rotate(180deg);
}
/* ===== Timeline ===== */
.timeline {
padding: 20px var(--space-md) 100px;
position: relative;
}
.timeline-line {
position: absolute;
left: 50%;
top: 0;
bottom: 100px;
width: 2px;
background: linear-gradient(to bottom, transparent, var(--primary) 5%, var(--primary) 95%, transparent);
transform: translateX(-50%);
opacity: 0.2;
}
/* ===== Date Node ===== */
.date-node {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 8px;
}
.date-node-dot {
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--primary);
border: 3px solid var(--bg-primary);
box-shadow: 0 0 0 2px var(--primary), 0 2px 8px rgba(108,99,255,0.3);
z-index: 2;
position: relative;
}
.date-node-dot.today {
width: 18px;
height: 18px;
background: var(--accent);
box-shadow: 0 0 0 2px var(--accent), 0 2px 12px rgba(78,205,196,0.4);
animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot {
0%, 100% { box-shadow: 0 0 0 2px var(--accent), 0 2px 12px rgba(78,205,196,0.4); }
50% { box-shadow: 0 0 0 4px var(--accent), 0 2px 16px rgba(78,205,196,0.6); }
}
.date-node-label {
margin-top: 6px;
text-align: center;
}
.date-node-month {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.date-node-weekday {
font-size: 11px;
color: var(--text-hint);
}
.date-node-weekday.weekend {
color: var(--secondary);
font-weight: 600;
}
.date-node-season {
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
margin-top: 3px;
font-weight: 500;
}
.season-spring { background: rgba(78,205,196,0.12); color: var(--spring); }
.season-summer { background: rgba(255,107,107,0.12); color: var(--summer); }
.season-autumn { background: rgba(245,158,11,0.12); color: var(--autumn); }
.season-winter { background: rgba(59,130,246,0.12); color: var(--winter); }
/* ===== Today Divider ===== */
.today-divider {
display: flex;
align-items: center;
gap: 12px;
margin: 12px 0 16px;
padding: 0 8px;
}
.today-divider-line {
flex: 1;
height: 1.5px;
background: linear-gradient(to right, transparent, var(--accent), transparent);
}
.today-divider-text {
font-size: 13px;
font-weight: 600;
color: var(--accent);
white-space: nowrap;
letter-spacing: 1px;
}
/* ===== Card Row (Left + Right) ===== */
.card-row {
display: flex;
gap: 10px;
margin-bottom: 6px;
position: relative;
}
.card-row .card-col {
flex: 1;
min-width: 0;
}
/* ===== Timeline Card ===== */
.tl-card {
background: var(--bg-card);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
border: 0.5px solid rgba(0,0,0,0.04);
position: relative;
}
.tl-card:active {
transform: scale(0.97);
box-shadow: var(--shadow-md);
}
.tl-card.food-card {
border-left: 3px solid var(--warning);
}
.tl-card.play-card {
border-left: 3px solid var(--accent);
}
.tl-card .card-header {
padding: 10px 12px 6px;
display: flex;
align-items: center;
justify-content: space-between;
}
.card-type-badge {
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 600;
}
.badge-food {
background: rgba(245,158,11,0.12);
color: var(--warning);
}
.badge-play {
background: rgba(78,205,196,0.12);
color: var(--accent);
}
.card-price-badge {
font-size: 9px;
padding: 1px 6px;
border-radius: 6px;
font-weight: 600;
}
.price-free { background: rgba(16,185,129,0.12); color: var(--success); }
.price-paid { background: rgba(245,158,11,0.12); color: var(--warning); }
.price-commercial { background: rgba(255,107,107,0.12); color: var(--secondary); }
.price-unknown { background: rgba(156,163,175,0.12); color: var(--text-hint); }
.tl-card .card-body {
padding: 0 12px 10px;
}
.card-title {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
line-height: 1.3;
margin-bottom: 4px;
}
.card-desc {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.4;
}
.card-location {
font-size: 11px;
color: var(--text-hint);
margin-top: 6px;
display: flex;
align-items: center;
gap: 3px;
}
.card-risk {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
background: rgba(239,68,68,0.1);
color: var(--error);
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 2px;
margin-top: 4px;
}
.card-sun-info {
font-size: 10px;
color: var(--text-hint);
margin-top: 4px;
display: flex;
gap: 8px;
}
.card-actions {
padding: 6px 12px 8px;
display: flex;
gap: 6px;
border-top: 0.5px solid rgba(0,0,0,0.04);
}
.card-action-btn {
font-size: 11px;
padding: 3px 8px;
border-radius: 8px;
background: var(--bg-secondary);
color: var(--text-secondary);
border: none;
cursor: pointer;
display: flex;
align-items: center;
gap: 3px;
transition: all 0.2s;
}
.card-action-btn:active {
transform: scale(0.95);
background: rgba(0,0,0,0.06);
}
.card-action-btn.bookmarked {
background: rgba(108,99,255,0.1);
color: var(--primary);
}
/* ===== Checkbox items in card ===== */
.card-checklist {
padding: 4px 12px 8px;
}
.checklist-item {
display: flex;
align-items: center;
gap: 6px;
padding: 3px 0;
font-size: 12px;
color: var(--text-secondary);
}
.checklist-item input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--primary);
}
.checklist-item.checked {
color: var(--text-hint);
text-decoration: line-through;
}
/* ===== Bottom Multi-select Bar ===== */
.bottom-bar {
position: fixed;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 100%;
max-width: 430px;
z-index: 100;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
background: rgba(250,250,250,0.85);
border-top: 0.5px solid rgba(0,0,0,0.08);
padding: 10px var(--space-md);
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
}
.bottom-bar-label {
font-size: 11px;
color: var(--text-hint);
margin-bottom: 6px;
font-weight: 500;
}
.bottom-bar-chips {
display: flex;
gap: 8px;
overflow-x: auto;
padding-bottom: 2px;
}
.bottom-chip {
font-size: 12px;
padding: 5px 12px;
border-radius: 16px;
background: var(--bg-secondary);
color: var(--text-secondary);
border: 1px solid transparent;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 4px;
}
.bottom-chip.active {
background: rgba(108,99,255,0.1);
color: var(--primary);
border-color: rgba(108,99,255,0.3);
}
.bottom-chip:active {
transform: scale(0.95);
}
/* ===== Search External Button ===== */
.search-ext-btn {
position: fixed;
bottom: 80px;
right: 16px;
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--bg-card);
box-shadow: var(--shadow-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
cursor: pointer;
border: 0.5px solid rgba(0,0,0,0.06);
z-index: 99;
transition: all 0.3s;
}
.search-ext-btn:active {
transform: scale(0.9);
}
/* ===== Season Color Themes ===== */
.theme-spring { --season-color: var(--spring); }
.theme-summer { --season-color: var(--summer); }
.theme-autumn { --season-color: var(--autumn); }
.theme-winter { --season-color: var(--winter); }
/* ===== Dark Mode ===== */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1A1A2E;
--bg-secondary: #16213E;
--bg-card: #2D2D44;
--bg-elevated: #333355;
--text-primary: #E5E5E5;
--text-secondary: #9CA3AF;
--text-hint: #6B7280;
}
.appbar {
background: rgba(26,26,46,0.78);
border-bottom-color: rgba(255,255,255,0.06);
}
.tl-card {
border-color: rgba(255,255,255,0.06);
}
.bottom-bar {
background: rgba(26,26,46,0.85);
border-top-color: rgba(255,255,255,0.06);
}
.search-ext-btn {
background: var(--bg-card);
border-color: rgba(255,255,255,0.06);
}
}
/* ===== Decision Bar ===== */
.decision-bar {
padding: 10px var(--space-md) 6px;
display: flex;
flex-direction: column;
gap: 8px;
background: var(--bg-primary);
border-bottom: 0.5px solid rgba(0,0,0,0.06);
}
.decision-row {
display: flex;
align-items: center;
gap: 6px;
}
.decision-label {
font-size: 11px;
color: var(--text-hint);
font-weight: 500;
white-space: nowrap;
min-width: 52px;
}
.decision-btns {
display: flex;
gap: 4px;
flex: 1;
}
.decision-btn {
font-size: 11px;
padding: 4px 10px;
border-radius: 14px;
background: var(--bg-secondary);
color: var(--text-secondary);
border: 1px solid transparent;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s;
}
.decision-btn.active {
background: rgba(108,99,255,0.1);
color: var(--primary);
border-color: rgba(108,99,255,0.3);
font-weight: 600;
}
.decision-btn:active {
transform: scale(0.95);
}
.season-btn {
width: 28px;
height: 28px;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
transition: all 0.2s;
background: var(--bg-secondary);
}
.season-btn.active {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(108,99,255,0.2);
transform: scale(1.1);
}
.season-btn:active {
transform: scale(0.9);
}
.dark-toggle {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--bg-secondary);
border: 1px solid rgba(0,0,0,0.06);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
transition: all 0.3s;
}
.dark-toggle:active {
transform: scale(0.9);
}
.dark-toggle.active {
background: #1A1A2E;
border-color: rgba(108,99,255,0.3);
}
/* ===== Search Panel ===== */
.search-panel-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 200;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.search-panel-overlay.show {
opacity: 1;
pointer-events: auto;
}
.search-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.9);
z-index: 201;
background: var(--bg-card);
border-radius: var(--radius-xl);
padding: var(--space-lg);
box-shadow: var(--shadow-lg);
width: 320px;
opacity: 0;
pointer-events: none;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.search-panel.show {
opacity: 1;
pointer-events: auto;
transform: translate(-50%, -50%) scale(1);
}
.search-panel-title {
font-size: 16px;
font-weight: 600;
text-align: center;
margin-bottom: var(--space-md);
}
.search-panel-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.search-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 14px 8px;
border-radius: var(--radius-lg);
background: var(--bg-secondary);
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
}
.search-option:active {
transform: scale(0.95);
background: rgba(108,99,255,0.08);
border-color: rgba(108,99,255,0.2);
}
.search-option-icon {
font-size: 28px;
}
.search-option-name {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
}
/* ===== Card Style Variants ===== */
.card-style-flat .tl-card {
box-shadow: none;
border: 1px solid rgba(0,0,0,0.06);
border-radius: var(--radius-md);
}
.card-style-flat .tl-card .card-header {
padding: 8px 10px 4px;
}
.card-style-flat .tl-card .card-body {
padding: 0 10px 8px;
}
.card-style-image .tl-card {
border-radius: var(--radius-xl);
}
.card-style-image .tl-card .card-header {
padding: 0;
position: relative;
}
.card-style-image .tl-card .card-header .card-type-badge {
position: absolute;
top: 8px;
left: 8px;
z-index: 2;
}
.card-style-image .tl-card .card-header .card-price-badge {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
}
.card-style-image .tl-card .card-img-placeholder {
height: 80px;
width: 100%;
}
.card-style-image .tl-card.food-card .card-img-placeholder {
background: linear-gradient(135deg, #FFF3E0, #FFE0B2);
}
.card-style-image .tl-card.play-card .card-img-placeholder {
background: linear-gradient(135deg, #E0F7FA, #B2EBF2);
}
/* ===== Layout: Left Line Right Card ===== */
.layout-left-line .timeline-line {
left: 24px;
}
.layout-left-line .date-node {
align-items: flex-start;
flex-direction: row;
gap: 12px;
padding-left: 0;
}
.layout-left-line .date-node-dot {
flex-shrink: 0;
}
.layout-left-line .date-node-label {
margin-top: 0;
text-align: left;
}
.layout-left-line .card-row {
padding-left: 48px;
}
/* ===== Dark Mode Manual ===== */
body.dark-mode {
--bg-primary: #1A1A2E;
--bg-secondary: #16213E;
--bg-card: #2D2D44;
--bg-elevated: #333355;
--text-primary: #E5E5E5;
--text-secondary: #9CA3AF;
--text-hint: #6B7280;
}
body.dark-mode .appbar {
background: rgba(26,26,46,0.78);
border-bottom-color: rgba(255,255,255,0.06);
}
body.dark-mode .tl-card {
border-color: rgba(255,255,255,0.06);
}
body.dark-mode .bottom-bar {
background: rgba(26,26,46,0.85);
border-top-color: rgba(255,255,255,0.06);
}
body.dark-mode .search-ext-btn {
background: var(--bg-card);
border-color: rgba(255,255,255,0.06);
}
body.dark-mode .decision-bar {
border-bottom-color: rgba(255,255,255,0.06);
}
body.dark-mode .search-panel {
background: var(--bg-card);
}
body.dark-mode .search-option {
background: var(--bg-secondary);
}
body.dark-mode .toggle {
background: #3A3A5C;
}
/* ===== Animations ===== */
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.slide-left {
animation: slideLeft 0.4s ease-out;
}
@keyframes slideLeft {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
.slide-right {
animation: slideRight 0.4s ease-out;
}
@keyframes slideRight {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
</style>
</head>
<body>
<div class="phone-frame">
<!-- AppBar -->
<div class="appbar">
<div class="appbar-left">
<button class="appbar-back"></button>
<div>
<div class="appbar-title">🌸 闲情逸致</div>
<div class="appbar-subtitle">闲时与你立黄昏,灶前笑问粥可温</div>
</div>
</div>
<div class="appbar-right">
<button class="appbar-btn" title="外部搜索" onclick="showSearchPanel()">🔍</button>
<button class="appbar-btn" title="设置" onclick="window.location.href='leisure_settings.html'">⚙️</button>
</div>
</div>
<!-- Swap Button (Fixed) -->
<button class="swap-btn-fixed" id="swapBtn" title="交换左右位置" onclick="toggleSwap()">
<span class="swap-icon"></span>
</button>
<!-- Decision Bar -->
<div class="decision-bar">
<div class="decision-row">
<span class="decision-label">卡片样式</span>
<div class="decision-btns">
<button class="decision-btn active" onclick="setCardStyle(this, 'fancy')">精致卡片</button>
<button class="decision-btn" onclick="setCardStyle(this, 'flat')">扁平简约</button>
<button class="decision-btn" onclick="setCardStyle(this, 'image')">图文卡片</button>
</div>
</div>
<div class="decision-row">
<span class="decision-label">布局方向</span>
<div class="decision-btns">
<button class="decision-btn active" onclick="setLayout(this, 'center')">中轴对称</button>
<button class="decision-btn" onclick="setLayout(this, 'left')">左线右卡</button>
</div>
</div>
<div class="decision-row">
<span class="decision-label">季节色</span>
<div class="decision-btns">
<button class="season-btn active" onclick="setSeason(this, 'spring')" title="春">🌿</button>
<button class="season-btn" onclick="setSeason(this, 'summer')" title="夏">☀️</button>
<button class="season-btn" onclick="setSeason(this, 'autumn')" title="秋">🍂</button>
<button class="season-btn" onclick="setSeason(this, 'winter')" title="冬">❄️</button>
</div>
<span class="decision-label" style="margin-left:8px">暗色</span>
<button class="dark-toggle" id="darkToggle" onclick="toggleDarkMode()" title="暗色模式">🌙</button>
</div>
</div>
<!-- Timeline -->
<div class="timeline">
<div class="timeline-line"></div>
<!-- ===== Yesterday Node ===== -->
<div class="date-node fade-in">
<div class="date-node-dot"></div>
<div class="date-node-label">
<div class="date-node-month">5月26日</div>
<div class="date-node-weekday">星期二</div>
<div class="date-node-season season-summer">☀️ 夏</div>
</div>
</div>
<div class="card-row">
<div class="card-col slide-left">
<div class="tl-card food-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-food">🍜 吃</span>
</div>
<div class="card-body">
<div class="card-title">云南野生菌火锅</div>
<div class="card-desc">雨季第一波菌子,牛肝菌·鸡枞·松茸</div>
<div class="card-location">📍 云南·昆明</div>
<div class="card-risk">⚠️ 高原反应 · 野生菌中毒风险</div>
<div class="card-sun-info">
<span>🌅 06:20</span>
<span>🌇 19:48</span>
</div>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
</div>
</div>
</div>
<div class="card-col slide-right">
<div class="tl-card play-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-play">🎯 玩</span>
<span class="card-price-badge price-free">免费</span>
</div>
<div class="card-body">
<div class="card-title">翠湖公园赏荷</div>
<div class="card-desc">初夏荷花渐开,红嘴鸥已北迁</div>
<div class="card-location">📍 云南·昆明·翠湖</div>
<div class="card-sun-info">
<span>🌅 06:20</span>
<span>🌇 19:48</span>
</div>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
</div>
</div>
</div>
</div>
<!-- ===== Today Divider ===== -->
<div class="today-divider fade-in">
<div class="today-divider-line"></div>
<div class="today-divider-text">✨ 今天 · 5月27日</div>
<div class="today-divider-line"></div>
</div>
<!-- ===== Today Node ===== -->
<div class="date-node fade-in">
<div class="date-node-dot today"></div>
<div class="date-node-label">
<div class="date-node-month">5月27日</div>
<div class="date-node-weekday weekend">星期三</div>
<div class="date-node-season season-summer">☀️ 夏</div>
</div>
</div>
<!-- Today Card Row 1 -->
<div class="card-row">
<div class="card-col slide-left">
<div class="tl-card food-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-food">🍜 吃</span>
</div>
<div class="card-body">
<div class="card-title">杨梅季 🍇</div>
<div class="card-desc">仙居杨梅·东魁·荸荠种,酸甜多汁</div>
<div class="card-location">📍 浙江·仙居</div>
<div class="card-sun-info">
<span>🌅 05:12</span>
<span>🌇 19:02</span>
</div>
</div>
<div class="card-checklist">
<label class="checklist-item"><input type="checkbox"> 买杨梅</label>
<label class="checklist-item"><input type="checkbox"> 做杨梅酒</label>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
<button class="card-action-btn" onclick="event.stopPropagation(); alert('添加到笔记')">📝 笔记</button>
</div>
</div>
</div>
<div class="card-col slide-right">
<div class="tl-card play-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-play">🎯 玩</span>
<span class="card-price-badge price-paid">付费</span>
</div>
<div class="card-body">
<div class="card-title">西湖夜游 🌙</div>
<div class="card-desc">印象西湖演出·断桥残雪·曲院风荷</div>
<div class="card-location">📍 浙江·杭州·西湖</div>
<div class="card-sun-info">
<span>🌅 05:12</span>
<span>🌇 19:02</span>
</div>
</div>
<div class="card-checklist">
<label class="checklist-item"><input type="checkbox"> 买门票</label>
<label class="checklist-item"><input type="checkbox"> 看演出</label>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
<button class="card-action-btn" onclick="event.stopPropagation(); alert('添加到笔记')">📝 笔记</button>
</div>
</div>
</div>
</div>
<!-- Today Card Row 2 (Multiple cards per node) -->
<div class="card-row">
<div class="card-col slide-left">
<div class="tl-card food-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-food">🍜 吃</span>
</div>
<div class="card-body">
<div class="card-title">小龙虾 🦞</div>
<div class="card-desc">盱眙十三香·潜江油焖·麻辣蒜蓉</div>
<div class="card-location">📍 江苏·盱眙</div>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
</div>
</div>
</div>
<div class="card-col slide-right">
<div class="tl-card play-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-play">🎯 玩</span>
<span class="card-price-badge price-commercial">商业化</span>
</div>
<div class="card-body">
<div class="card-title">薰衣草花海 💜</div>
<div class="card-desc">伊犁河谷薰衣草初开,紫色花海</div>
<div class="card-location">📍 新疆·伊犁</div>
<div class="card-risk">⚠️ 高海拔 · 强紫外线</div>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
</div>
</div>
</div>
</div>
<!-- ===== Tomorrow Node ===== -->
<div class="date-node fade-in">
<div class="date-node-dot"></div>
<div class="date-node-label">
<div class="date-node-month">5月28日</div>
<div class="date-node-weekday">星期四</div>
<div class="date-node-season season-summer">☀️ 夏</div>
</div>
</div>
<div class="card-row">
<div class="card-col slide-left">
<div class="tl-card food-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-food">🍜 吃</span>
</div>
<div class="card-body">
<div class="card-title">荔枝初上市 🫒</div>
<div class="card-desc">妃子笑·桂味·糯米糍,岭南佳果</div>
<div class="card-location">📍 广东·从化</div>
<div class="card-sun-info">
<span>🌅 05:48</span>
<span>🌇 19:18</span>
</div>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
</div>
</div>
</div>
<div class="card-col slide-right">
<div class="tl-card play-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-play">🎯 玩</span>
<span class="card-price-badge price-unknown">未知</span>
</div>
<div class="card-body">
<div class="card-title">纳木错观星 ✨</div>
<div class="card-desc">银河拱桥·星空露营·日出金山</div>
<div class="card-location">📍 西藏·纳木错 · 海拔4718m</div>
<div class="card-risk">⚠️ 高海拔 · 高原反应 · 低温</div>
<div class="card-sun-info">
<span>🌅 07:32</span>
<span>🌇 21:05</span>
</div>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
</div>
</div>
</div>
</div>
<!-- ===== Day After Tomorrow ===== -->
<div class="date-node fade-in">
<div class="date-node-dot"></div>
<div class="date-node-label">
<div class="date-node-month">5月29日</div>
<div class="date-node-weekday weekend">星期五</div>
<div class="date-node-season season-summer">☀️ 夏</div>
</div>
</div>
<div class="card-row">
<div class="card-col slide-left">
<div class="tl-card food-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-food">🍜 吃</span>
</div>
<div class="card-body">
<div class="card-title">青岛啤酒节 🍺</div>
<div class="card-desc">鲜啤·扎啤·精酿,海鲜配啤酒</div>
<div class="card-location">📍 山东·青岛</div>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
</div>
</div>
</div>
<div class="card-col slide-right">
<div class="tl-card play-card" onclick="window.location.href='leisure_card_detail.html'">
<div class="card-header">
<span class="card-type-badge badge-play">🎯 玩</span>
<span class="card-price-badge price-free">免费</span>
</div>
<div class="card-body">
<div class="card-title">呼伦贝尔草原 🌿</div>
<div class="card-desc">草原骑马·蒙古包·篝火晚会</div>
<div class="card-location">📍 内蒙古·呼伦贝尔 · 海拔650m</div>
<div class="card-risk">⚠️ 边境地区 · 注意安全</div>
</div>
<div class="card-actions">
<button class="card-action-btn" onclick="toggleBookmark(this)">🔖 收藏</button>
<button class="card-action-btn" onclick="event.stopPropagation(); window.location.href='leisure_share_card.html'">📤 分享</button>
</div>
</div>
</div>
</div>
</div><!-- /timeline -->
<!-- External Search FAB -->
<button class="search-ext-btn" onclick="showSearchPanel()">🔎</button>
<!-- Search Panel Overlay -->
<div class="search-panel-overlay" id="searchOverlay" onclick="hideSearchPanel()"></div>
<div class="search-panel" id="searchPanel">
<div class="search-panel-title">🔍 选择搜索平台</div>
<div class="search-panel-grid">
<div class="search-option" onclick="openSearch('baidu')">
<span class="search-option-icon">🔵</span>
<span class="search-option-name">百度</span>
</div>
<div class="search-option" onclick="openSearch('amap')">
<span class="search-option-icon">🗺️</span>
<span class="search-option-name">高德</span>
</div>
<div class="search-option" onclick="openSearch('dianping')">
<span class="search-option-icon"></span>
<span class="search-option-name">大众点评</span>
</div>
<div class="search-option" onclick="openSearch('xiaohongshu')">
<span class="search-option-icon">📕</span>
<span class="search-option-name">小红书</span>
</div>
<div class="search-option" onclick="openSearch('douyin')">
<span class="search-option-icon">🎵</span>
<span class="search-option-name">抖音</span>
</div>
</div>
</div>
<!-- Bottom Multi-select Bar -->
<div class="bottom-bar">
<div class="bottom-bar-label">标注筛选</div>
<div class="bottom-bar-chips">
<button class="bottom-chip active" onclick="toggleChip(this)">🌸 花期</button>
<button class="bottom-chip" onclick="toggleChip(this)">🦞 美食</button>
<button class="bottom-chip" onclick="toggleChip(this)">🏔️ 高海拔</button>
<button class="bottom-chip" onclick="toggleChip(this)">⚠️ 风险</button>
<button class="bottom-chip" onclick="toggleChip(this)">🆓 免费</button>
<button class="bottom-chip" onclick="toggleChip(this)">💰 付费</button>
<button class="bottom-chip" onclick="toggleChip(this)">🌅 日出</button>
<button class="bottom-chip" onclick="toggleChip(this)">🌊 观海</button>
</div>
</div>
</div><!-- /phone-frame -->
<script>
let swapped = false;
function toggleSwap() {
swapped = !swapped;
const btn = document.getElementById('swapBtn');
btn.classList.toggle('swapped', swapped);
document.querySelectorAll('.card-row').forEach(row => {
const cols = row.querySelectorAll('.card-col');
if (cols.length === 2) {
if (swapped) {
row.style.flexDirection = 'row-reverse';
} else {
row.style.flexDirection = 'row';
}
}
});
}
function toggleBookmark(btn) {
btn.classList.toggle('bookmarked');
if (btn.classList.contains('bookmarked')) {
btn.innerHTML = '🔖 已收藏';
} else {
btn.innerHTML = '🔖 收藏';
}
}
function toggleChip(chip) {
chip.classList.toggle('active');
}
function setCardStyle(btn, style) {
btn.parentElement.querySelectorAll('.decision-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const frame = document.querySelector('.phone-frame');
frame.classList.remove('card-style-flat', 'card-style-image');
if (style === 'flat') frame.classList.add('card-style-flat');
if (style === 'image') frame.classList.add('card-style-image');
}
function setLayout(btn, layout) {
btn.parentElement.querySelectorAll('.decision-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const timeline = document.querySelector('.timeline');
timeline.classList.remove('layout-left-line');
if (layout === 'left') timeline.classList.add('layout-left-line');
}
function setSeason(btn, season) {
btn.parentElement.querySelectorAll('.season-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const colors = { spring: '#4ECDC4', summer: '#FF6B6B', autumn: '#F59E0B', winter: '#3B82F6' };
document.documentElement.style.setProperty('--primary', colors[season]);
document.documentElement.style.setProperty('--primary-light', colors[season] + 'CC');
}
function toggleDarkMode() {
const toggle = document.getElementById('darkToggle');
toggle.classList.toggle('active');
document.body.classList.toggle('dark-mode');
toggle.innerHTML = document.body.classList.contains('dark-mode') ? '☀️' : '🌙';
}
function showSearchPanel() {
document.getElementById('searchOverlay').classList.add('show');
document.getElementById('searchPanel').classList.add('show');
}
function hideSearchPanel() {
document.getElementById('searchOverlay').classList.remove('show');
document.getElementById('searchPanel').classList.remove('show');
}
function openSearch(platform) {
const urls = {
baidu: 'https://www.baidu.com',
amap: 'https://www.amap.com',
dianping: 'https://www.dianping.com',
xiaohongshu: 'https://www.xiaohongshu.com',
douyin: 'https://www.douyin.com'
};
hideSearchPanel();
alert('跳转到 ' + platform + ': ' + urls[platform]);
}
</script>
</body>
</html>