Files
xianyan/index.html
Developer f9c19463f9 chore: 批量更新v6.5.21版本,整合多项功能修复与优化
主要变更:
1. 新增多风格音效资源与管理文档
2. 修复翻译服务空响应处理与Dio日志异常捕获
3. 完善Web端平台适配与路径获取Stub
4. 优化设备配对与文件传输功能
5. 新增角色命名常量与摇一摇检测器
6. 修复Riverpod dispose与鸿蒙导航路由
7. 新增每日通知服务与流体着色器
8. 优化备份服务与数据管理页面
9. 新增隐私设置附近设备发现选项
10. 重构诗词提供者支持历史记录
11. 完善桌面端构建配置与开发脚本
12. 清理旧版工具部署脚本
2026-05-21 00:19:14 +08:00

1804 lines
53 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>闲言 AppBar 扩展设计 - 交互原型 v2</title>
<style>
:root {
--primary: #007AFF;
--secondary: #5AC8FA;
--background: #F2F2F7;
--bg-secondary: #FFFFFF;
--text: #1C1C1E;
--text-secondary: #8E8E93;
--text-tertiary: #AEAEB2;
--separator: #C6C6C8;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
--shadow-md: 0 4px 12px rgba(0,0,0,0.12);
--shadow-lg: 0 8px 30px rgba(0,0,0,0.18);
--font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', system-ui, sans-serif;
--space-1: 4px;
--space-2: 8px;
--space-3: 16px;
--space-4: 24px;
--space-5: 32px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font-family);
background: var(--background);
color: var(--text);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
.phone-frame {
width: 393px;
height: 852px;
margin: 20px auto;
border-radius: 44px;
overflow: hidden;
position: relative;
background: var(--background);
box-shadow: 0 20px 60px rgba(0,0,0,0.3), 0 0 0 1px rgba(0,0,0,0.1);
}
.status-bar {
height: 54px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 28px;
font-size: 15px;
font-weight: 600;
position: relative;
z-index: 10;
}
.status-bar .time { font-weight: 700; font-size: 16px; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.status-bar .icons svg { width: 18px; height: 18px; }
.app-content {
height: calc(852px - 54px - 83px);
overflow-y: auto;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1), filter 0.5s ease;
position: relative;
}
.app-content.pushed {
transform: scale(0.92) translateY(20px);
filter: blur(2px);
border-radius: 20px;
}
.app-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 16px 8px;
position: relative;
}
.app-bar-left {
display: flex;
align-items: center;
gap: 8px;
}
.character-avatar {
width: 48px;
height: 48px;
position: relative;
cursor: pointer;
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
flex-shrink: 0;
}
.character-avatar:active {
transform: scale(0.85);
}
.character-avatar.bounce {
animation: characterBounce 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes characterBounce {
0% { transform: scale(1); }
30% { transform: scale(0.8) rotate(-5deg); }
50% { transform: scale(1.15) rotate(3deg); }
70% { transform: scale(0.95) rotate(-1deg); }
100% { transform: scale(1) rotate(0); }
}
.character-canvas {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
overflow: visible;
}
.app-title {
font-size: 28px;
font-weight: 700;
letter-spacing: -0.5px;
color: var(--text);
cursor: pointer;
transition: transform 0.15s;
user-select: none;
}
.app-title:active {
transform: scale(0.96);
}
.app-bar-center {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
padding: 6px 12px;
border-radius: 20px;
transition: background 0.2s;
position: relative;
overflow: hidden;
max-width: 160px;
}
.app-bar-center:hover {
background: rgba(0,0,0,0.04);
}
.app-bar-center:active {
background: rgba(0,0,0,0.08);
transform: scale(0.97);
}
.date-text {
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
letter-spacing: 0.2px;
white-space: nowrap;
}
.date-text.marquee {
display: inline-block;
animation: marqueeScroll 6s linear infinite;
}
@keyframes marqueeScroll {
0% { transform: translateX(0); }
15% { transform: translateX(0); }
85% { transform: translateX(calc(-100% + 136px)); }
100% { transform: translateX(calc(-100% + 136px)); }
}
.date-badge {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--primary);
position: absolute;
top: 4px;
right: 4px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.3); }
}
.app-bar-right {
display: flex;
align-items: center;
gap: 8px;
}
.search-btn {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(120,120,128,0.08);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.2s, background 0.2s;
}
.search-btn:active {
transform: scale(0.9);
background: rgba(120,120,128,0.15);
}
.search-btn svg {
width: 18px;
height: 18px;
color: var(--text-secondary);
}
.daily-card {
margin: 8px 16px;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: var(--radius-xl);
color: white;
min-height: 160px;
position: relative;
overflow: hidden;
}
.daily-card::after {
content: '';
position: absolute;
top: -30%;
right: -20%;
width: 200px;
height: 200px;
border-radius: 50%;
background: rgba(255,255,255,0.1);
}
.daily-card .quote {
font-size: 18px;
font-weight: 500;
line-height: 1.6;
margin-bottom: 12px;
position: relative;
z-index: 1;
}
.daily-card .author {
font-size: 13px;
opacity: 0.8;
position: relative;
z-index: 1;
}
.action-row {
display: flex;
gap: 12px;
padding: 12px 16px;
}
.action-card {
flex: 1;
padding: 14px;
border-radius: var(--radius-lg);
background: var(--bg-secondary);
box-shadow: var(--shadow-sm);
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: transform 0.2s;
}
.action-card:active { transform: scale(0.97); }
.action-card .icon { font-size: 20px; }
.action-card .label { font-size: 14px; font-weight: 500; }
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 16px 8px;
}
.section-title {
font-size: 20px;
font-weight: 700;
}
.sentence-list {
padding: 0 16px;
}
.sentence-item {
padding: 14px;
background: var(--bg-secondary);
border-radius: var(--radius-md);
margin-bottom: 10px;
box-shadow: var(--shadow-sm);
}
.sentence-item .text {
font-size: 15px;
line-height: 1.6;
margin-bottom: 8px;
}
.sentence-item .meta {
font-size: 12px;
color: var(--text-secondary);
display: flex;
justify-content: space-between;
}
.bottom-bar {
height: 83px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: flex-start;
justify-content: space-around;
padding-top: 8px;
background: rgba(255,255,255,0.72);
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border-top: 0.5px solid rgba(0,0,0,0.08);
z-index: 5;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
cursor: pointer;
padding: 4px 16px;
transition: transform 0.2s;
}
.tab-item:active { transform: scale(0.9); }
.tab-item .tab-icon {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
}
.tab-item .tab-label {
font-size: 10px;
color: var(--text-secondary);
font-weight: 500;
}
.tab-item.active .tab-label {
color: var(--primary);
font-weight: 600;
}
.home-indicator {
position: absolute;
bottom: 8px;
left: 50%;
transform: translateX(-50%);
width: 134px;
height: 5px;
border-radius: 3px;
background: rgba(0,0,0,0.2);
}
/* ========== Sheet Overlay ========== */
.sheet-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0);
z-index: 100;
pointer-events: none;
transition: background 0.4s ease;
}
.sheet-overlay.active {
background: rgba(0,0,0,0.32);
pointer-events: auto;
}
.sheet-container {
position: absolute;
bottom: 0;
left: 0;
right: 0;
transform: translateY(100%);
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
z-index: 101;
}
.sheet-container.active {
transform: translateY(0);
}
.sheet {
background: rgba(255,255,255,0.88);
backdrop-filter: blur(60px) saturate(200%);
-webkit-backdrop-filter: blur(60px) saturate(200%);
border-radius: 20px 20px 0 0;
padding: 0 0 34px;
max-height: 700px;
overflow-y: auto;
}
.sheet-handle {
width: 36px;
height: 5px;
border-radius: 3px;
background: rgba(0,0,0,0.15);
margin: 8px auto 0;
}
.sheet-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px 12px;
}
.sheet-title {
font-size: 18px;
font-weight: 700;
}
.sheet-close {
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(0,0,0,0.06);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 14px;
color: var(--text-secondary);
transition: background 0.2s;
}
.sheet-close:hover { background: rgba(0,0,0,0.1); }
.sheet-section {
padding: 0 20px;
margin-bottom: 16px;
}
.sheet-section-title {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.sheet-section-title .counter {
font-size: 12px;
font-weight: 500;
color: var(--primary);
text-transform: none;
}
.sheet-section-title .counter.warn {
color: #FF3B30;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.info-card {
padding: 14px;
border-radius: var(--radius-md);
background: rgba(255,255,255,0.6);
border: 1px solid rgba(0,0,0,0.04);
transition: transform 0.2s;
}
.info-card:hover { transform: scale(1.02); }
.info-card .info-icon {
font-size: 24px;
margin-bottom: 6px;
}
.info-card .info-value {
font-size: 20px;
font-weight: 700;
color: var(--text);
line-height: 1.2;
}
.info-card .info-label {
font-size: 12px;
color: var(--text-secondary);
margin-top: 2px;
}
.info-card.wide {
grid-column: 1 / -1;
}
.custom-text-input {
width: 100%;
padding: 14px 16px;
border-radius: var(--radius-md);
background: rgba(255,255,255,0.6);
border: 1px solid rgba(0,0,0,0.06);
font-size: 15px;
font-family: var(--font-family);
color: var(--text);
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
resize: none;
}
.custom-text-input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(0,122,255,0.15);
}
.custom-text-input::placeholder {
color: var(--text-tertiary);
}
.custom-text-input.over-limit {
border-color: #FF3B30;
box-shadow: 0 0 0 3px rgba(255,59,48,0.15);
}
.char-counter {
text-align: right;
font-size: 12px;
color: var(--text-tertiary);
margin-top: 4px;
transition: color 0.2s;
}
.char-counter.over {
color: #FF3B30;
font-weight: 600;
}
.display-config {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.config-chip {
padding: 8px 14px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
border: 1.5px solid rgba(0,0,0,0.08);
background: rgba(255,255,255,0.6);
color: var(--text);
user-select: none;
position: relative;
}
.config-chip.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.config-chip.disabled {
opacity: 0.4;
cursor: not-allowed;
transform: scale(0.95);
}
.config-chip:hover:not(.active):not(.disabled) {
background: rgba(0,122,255,0.08);
border-color: rgba(0,122,255,0.3);
}
.config-chip .chip-check {
display: none;
margin-left: 4px;
}
.config-chip.active .chip-check {
display: inline;
}
/* Toggle Switch */
.toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
}
.toggle-label {
font-size: 15px;
font-weight: 500;
}
.toggle-desc {
font-size: 12px;
color: var(--text-secondary);
margin-top: 2px;
}
.toggle-switch {
width: 51px;
height: 31px;
border-radius: 16px;
background: rgba(120,120,128,0.16);
position: relative;
cursor: pointer;
transition: background 0.3s;
flex-shrink: 0;
}
.toggle-switch.active {
background: #34C759;
}
.toggle-switch .toggle-knob {
width: 27px;
height: 27px;
border-radius: 50%;
background: white;
position: absolute;
top: 2px;
left: 2px;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.toggle-switch.active .toggle-knob {
transform: translateX(20px);
}
/* Animation Intensity Slider */
.intensity-slider {
width: 100%;
-webkit-appearance: none;
appearance: none;
height: 4px;
border-radius: 2px;
background: rgba(120,120,128,0.16);
outline: none;
margin: 8px 0;
}
.intensity-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 28px;
height: 28px;
border-radius: 50%;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
cursor: pointer;
}
.intensity-labels {
display: flex;
justify-content: space-between;
font-size: 11px;
color: var(--text-tertiary);
}
.save-btn {
width: 100%;
padding: 14px;
border-radius: var(--radius-md);
background: var(--primary);
color: white;
font-size: 16px;
font-weight: 600;
border: none;
cursor: pointer;
transition: transform 0.2s, opacity 0.2s;
font-family: var(--font-family);
}
.save-btn:active { transform: scale(0.98); opacity: 0.9; }
/* ========== Character SVG Styles ========== */
.char-ear-l, .char-ear-r {
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-origin: center bottom;
}
.char-ear-l.wiggle {
animation: earWiggleL 0.4s ease;
}
.char-ear-r.wiggle {
animation: earWiggleR 0.4s ease;
}
@keyframes earWiggleL {
0% { transform: rotate(0); }
25% { transform: rotate(-8deg); }
50% { transform: rotate(5deg); }
75% { transform: rotate(-3deg); }
100% { transform: rotate(0); }
}
@keyframes earWiggleR {
0% { transform: rotate(0); }
25% { transform: rotate(8deg); }
50% { transform: rotate(-5deg); }
75% { transform: rotate(3deg); }
100% { transform: rotate(0); }
}
.char-nose {
transition: transform 0.15s ease;
transform-origin: center center;
}
.char-nose.twitch {
animation: noseTwitch 0.3s ease;
}
@keyframes noseTwitch {
0% { transform: scale(1, 1); }
30% { transform: scale(1.3, 0.7); }
60% { transform: scale(0.9, 1.1); }
100% { transform: scale(1, 1); }
}
.char-cheek {
transition: rx 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
}
.char-cheek.puff {
animation: cheekPuff 0.5s ease;
}
@keyframes cheekPuff {
0% { rx: 3.5; }
40% { rx: 5.5; }
100% { rx: 3.5; }
}
.char-whisker {
transition: transform 0.2s ease;
transform-origin: right center;
}
.char-whisker.twitch {
animation: whiskerTwitch 0.3s ease;
}
@keyframes whiskerTwitch {
0% { transform: rotate(0); }
30% { transform: rotate(-5deg); }
60% { transform: rotate(3deg); }
100% { transform: rotate(0); }
}
.char-eye {
transition: all 0.15s ease;
}
.char-mouth {
transition: all 0.2s ease;
}
.char-blush {
transition: opacity 0.3s ease;
}
/* ========== Interaction Hint ========== */
.interaction-hint {
position: absolute;
top: 60px;
left: 16px;
background: rgba(0,0,0,0.75);
color: white;
padding: 8px 14px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
z-index: 50;
opacity: 0;
transform: translateY(-8px);
transition: all 0.3s ease;
pointer-events: none;
backdrop-filter: blur(10px);
}
.interaction-hint.show {
opacity: 1;
transform: translateY(0);
}
/* ========== Page Layout ========== */
.prototype-container {
display: flex;
gap: 40px;
justify-content: center;
align-items: flex-start;
padding: 40px 20px;
flex-wrap: wrap;
}
.prototype-label {
text-align: center;
margin-bottom: 16px;
font-size: 14px;
font-weight: 600;
color: var(--text-secondary);
letter-spacing: 0.5px;
}
.prototype-wrapper {
position: relative;
}
/* ========== Dark Mode ========== */
@media (prefers-color-scheme: dark) {
:root {
--background: #000000;
--bg-secondary: #1C1C1E;
--text: #FFFFFF;
--text-secondary: #98989D;
--text-tertiary: #636366;
--separator: #38383A;
}
.sheet {
background: rgba(44,44,46,0.88);
}
.info-card {
background: rgba(44,44,46,0.6);
border-color: rgba(255,255,255,0.06);
}
.custom-text-input {
background: rgba(44,44,46,0.6);
border-color: rgba(255,255,255,0.08);
color: white;
}
.config-chip {
background: rgba(44,44,46,0.6);
border-color: rgba(255,255,255,0.1);
}
.bottom-bar {
background: rgba(28,28,30,0.72);
border-top-color: rgba(255,255,255,0.08);
}
.search-btn {
background: rgba(120,120,128,0.2);
}
.sheet-close {
background: rgba(255,255,255,0.1);
}
}
/* ========== Feature List ========== */
.feature-section {
max-width: 900px;
margin: 40px auto;
padding: 0 20px;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.feature-item {
padding: 16px;
border-radius: var(--radius-md);
background: var(--bg-secondary);
box-shadow: var(--shadow-sm);
display: flex;
align-items: flex-start;
gap: 12px;
}
.feature-item .f-icon {
font-size: 28px;
flex-shrink: 0;
}
.feature-item .f-title {
font-size: 14px;
font-weight: 700;
margin-bottom: 4px;
}
.feature-item .f-desc {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.4;
}
@media (max-width: 768px) {
.feature-grid { grid-template-columns: 1fr; }
.prototype-container { flex-direction: column; align-items: center; }
}
</style>
</head>
<body>
<div class="prototype-container">
<div class="prototype-wrapper">
<div class="prototype-label">📱 AppBar 交互原型 v2点击角色/闲言/日期体验)</div>
<div class="phone-frame" id="phoneFrame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.08 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33v15.33C7 21.4 7.6 22 8.33 22h7.33c.74 0 1.34-.6 1.34-1.33V5.33C17 4.6 16.4 4 15.67 4z"/></svg>
</div>
</div>
<div class="app-content" id="appContent">
<div class="app-bar">
<div class="app-bar-left">
<div class="character-avatar" id="characterAvatar" onclick="onCharacterClick()" ondblclick="onCharacterDblClick()">
<svg class="character-canvas" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="bodyGrad" cx="50%" cy="38%" r="52%">
<stop offset="0%" stop-color="#FFE0C0"/>
<stop offset="60%" stop-color="#FFD4A8"/>
<stop offset="100%" stop-color="#F0B070"/>
</radialGradient>
<radialGradient id="cheekGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#FF9A9E" stop-opacity="0.7"/>
<stop offset="100%" stop-color="#FF9A9E" stop-opacity="0"/>
</radialGradient>
<radialGradient id="noseGrad" cx="40%" cy="30%" r="60%">
<stop offset="0%" stop-color="#F5C4A0"/>
<stop offset="100%" stop-color="#E8A060"/>
</radialGradient>
<linearGradient id="earGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFD4A8"/>
<stop offset="100%" stop-color="#E8A560"/>
</linearGradient>
<filter id="shadow3d">
<feDropShadow dx="0" dy="1.5" stdDeviation="2" flood-color="#000" flood-opacity="0.12"/>
</filter>
<filter id="softGlow">
<feGaussianBlur in="SourceGraphic" stdDeviation="0.5"/>
</filter>
</defs>
<!-- Left Ear -->
<g class="char-ear-l" id="earL">
<path d="M 11 16 L 7 3 L 18 13 Z" fill="url(#earGrad)" stroke="#D89850" stroke-width="0.5" filter="url(#shadow3d)"/>
<path d="M 12 14 L 9 5 L 16 12 Z" fill="#FFBCBC" opacity="0.5"/>
</g>
<!-- Right Ear -->
<g class="char-ear-r" id="earR">
<path d="M 37 16 L 41 3 L 30 13 Z" fill="url(#earGrad)" stroke="#D89850" stroke-width="0.5" filter="url(#shadow3d)"/>
<path d="M 36 14 L 39 5 L 32 12 Z" fill="#FFBCBC" opacity="0.5"/>
</g>
<!-- Head -->
<ellipse cx="24" cy="27" rx="17" ry="16" fill="url(#bodyGrad)" filter="url(#shadow3d)"/>
<!-- Head Highlight -->
<ellipse cx="20" cy="20" rx="9" ry="5" fill="white" opacity="0.12" transform="rotate(-12 20 20)"/>
<!-- Left Cheek -->
<ellipse class="char-cheek char-blush" cx="13" cy="30" rx="3.5" ry="2.2" fill="url(#cheekGrad)" id="blushL" style="opacity:0"/>
<!-- Right Cheek -->
<ellipse class="char-cheek char-blush" cx="35" cy="30" rx="3.5" ry="2.2" fill="url(#cheekGrad)" id="blushR" style="opacity:0"/>
<!-- Eyes -->
<g id="eyesGroup">
<!-- Left Eye -->
<ellipse class="char-eye" cx="18" cy="26" rx="2.8" ry="3.3" fill="#2C2C2E" id="eyeL">
<animate attributeName="ry" values="3.3;3.3;0.5;3.3;3.3" dur="4s" repeatCount="indefinite" keyTimes="0;0.44;0.48;0.52;1"/>
</ellipse>
<ellipse cx="17" cy="25" rx="1.1" ry="1.1" fill="white" opacity="0.92" id="eyeHL"/>
<ellipse cx="19.5" cy="27" rx="0.5" ry="0.5" fill="white" opacity="0.4"/>
<!-- Right Eye -->
<ellipse class="char-eye" cx="30" cy="26" rx="2.8" ry="3.3" fill="#2C2C2E" id="eyeR">
<animate attributeName="ry" values="3.3;3.3;0.5;3.3;3.3" dur="4s" repeatCount="indefinite" keyTimes="0;0.44;0.48;0.52;1"/>
</ellipse>
<ellipse cx="29" cy="25" rx="1.1" ry="1.1" fill="white" opacity="0.92" id="eyeHR"/>
<ellipse cx="31.5" cy="27" rx="0.5" ry="0.5" fill="white" opacity="0.4"/>
</g>
<!-- Nose -->
<g class="char-nose" id="noseGroup">
<ellipse cx="24" cy="29.5" rx="2" ry="1.3" fill="url(#noseGrad)" opacity="0.7"/>
<ellipse cx="23.5" cy="29.2" rx="0.8" ry="0.5" fill="white" opacity="0.25"/>
</g>
<!-- Mouth -->
<path class="char-mouth" id="mouthPath" d="M 20 32 Q 24 36 28 32" fill="none" stroke="#C87850" stroke-width="1.5" stroke-linecap="round"/>
<!-- Whiskers -->
<g class="char-whisker" id="whiskerL">
<line x1="5" y1="27" x2="14" y2="28" stroke="#C87850" stroke-width="0.6" opacity="0.35"/>
<line x1="5" y1="30" x2="14" y2="30" stroke="#C87850" stroke-width="0.6" opacity="0.35"/>
<line x1="6" y1="33" x2="14" y2="32" stroke="#C87850" stroke-width="0.6" opacity="0.3"/>
</g>
<g class="char-whisker" id="whiskerR">
<line x1="34" y1="28" x2="43" y2="27" stroke="#C87850" stroke-width="0.6" opacity="0.35"/>
<line x1="34" y1="30" x2="43" y2="30" stroke="#C87850" stroke-width="0.6" opacity="0.35"/>
<line x1="34" y1="32" x2="42" y2="33" stroke="#C87850" stroke-width="0.6" opacity="0.3"/>
</g>
</svg>
</div>
<span class="app-title" id="appTitle" onclick="onTitleClick()">闲言</span>
</div>
<div class="app-bar-center" id="dateArea" onclick="openSheet()">
<span class="date-text" id="dateDisplay">5月20日</span>
<span class="date-badge" id="dateBadge" style="display:none"></span>
</div>
<div class="app-bar-right">
<div class="search-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</div>
</div>
</div>
<div class="interaction-hint" id="hint">🐱 喵~ 点击我试试!</div>
<div class="daily-card">
<div class="quote">人生如逆旅,我亦是行人。</div>
<div class="author">— 苏轼《临江仙》</div>
</div>
<div class="action-row">
<div class="action-card">
<span class="icon">🎨</span>
<span class="label">创作卡片</span>
</div>
<div class="action-card">
<span class="icon">📝</span>
<span class="label">编辑此句</span>
</div>
</div>
<div class="section-header">
<span class="section-title">📖 句子广场</span>
<span style="font-size:13px;color:var(--primary);cursor:pointer">最新 ↓</span>
</div>
<div class="sentence-list">
<div class="sentence-item">
<div class="text">山有木兮木有枝,心悦君兮君不知。</div>
<div class="meta"><span>《越人歌》</span><span>❤️ 2.3k</span></div>
</div>
<div class="sentence-item">
<div class="text">落霞与孤鹜齐飞,秋水共长天一色。</div>
<div class="meta"><span>王勃《滕王阁序》</span><span>❤️ 1.8k</span></div>
</div>
<div class="sentence-item">
<div class="text">但愿人长久,千里共婵娟。</div>
<div class="meta"><span>苏轼《水调歌头》</span><span>❤️ 3.1k</span></div>
</div>
</div>
<div style="height:140px"></div>
</div>
<div class="bottom-bar">
<div class="tab-item active">
<div class="tab-icon">🐱</div>
<div class="tab-label">闲言</div>
</div>
<div class="tab-item">
<div class="tab-icon">🧭</div>
<div class="tab-label">发现</div>
</div>
<div class="tab-item">
<div class="tab-icon">👤</div>
<div class="tab-label">我的</div>
</div>
<div class="home-indicator"></div>
</div>
<div class="sheet-overlay" id="sheetOverlay" onclick="closeSheet()"></div>
<div class="sheet-container" id="sheetContainer">
<div class="sheet">
<div class="sheet-handle"></div>
<div class="sheet-header">
<span class="sheet-title">日期栏设置</span>
<div class="sheet-close" onclick="closeSheet()"></div>
</div>
<!-- Weather Section -->
<div class="sheet-section">
<div class="sheet-section-title">🌤 天气信息</div>
<div class="info-grid">
<div class="info-card">
<div class="info-icon">☀️</div>
<div class="info-value">26°</div>
<div class="info-label">晴 · 杭州</div>
</div>
<div class="info-card">
<div class="info-icon">💧</div>
<div class="info-value">62%</div>
<div class="info-label">湿度</div>
</div>
<div class="info-card">
<div class="info-icon">🌬</div>
<div class="info-value">东南3级</div>
<div class="info-label">风向风力</div>
</div>
<div class="info-card">
<div class="info-icon">🛡</div>
<div class="info-value"></div>
<div class="info-label">空气质量</div>
</div>
</div>
</div>
<!-- Device Section -->
<div class="sheet-section">
<div class="sheet-section-title">📱 设备信息</div>
<div class="info-grid">
<div class="info-card">
<div class="info-icon">📲</div>
<div class="info-value">iPhone 16</div>
<div class="info-label">设备型号</div>
</div>
<div class="info-card">
<div class="info-icon">🔋</div>
<div class="info-value">85%</div>
<div class="info-label">电池状态</div>
</div>
</div>
</div>
<!-- Network Section -->
<div class="sheet-section">
<div class="sheet-section-title">🌐 网络/IP</div>
<div class="info-grid">
<div class="info-card wide">
<div class="info-icon">📍</div>
<div class="info-value">192.168.1.***</div>
<div class="info-label">IP归属地 · 浙江杭州 · WiFi</div>
</div>
</div>
</div>
<!-- Custom Text Section -->
<div class="sheet-section">
<div class="sheet-section-title">✏️ 自定义文案 <span class="counter" id="charCounter">5/14</span></div>
<textarea class="custom-text-input" rows="2" placeholder="一句话、一个词、一句诗最多14字" id="customText" oninput="onCustomTextInput()" maxlength="14">今日宜读书</textarea>
</div>
<!-- Display Config Section -->
<div class="sheet-section">
<div class="sheet-section-title">📋 日期栏显示项 <span class="counter" id="chipCounter">2/3</span></div>
<div class="display-config" id="chipContainer">
<div class="config-chip active" data-key="date" onclick="toggleChip(this)">📅 日期<span class="chip-check"></span></div>
<div class="config-chip active" data-key="weather" onclick="toggleChip(this)">🌤 天气<span class="chip-check"></span></div>
<div class="config-chip" data-key="temp" onclick="toggleChip(this)">🌡 温度</div>
<div class="config-chip" data-key="city" onclick="toggleChip(this)">📍 城市</div>
<div class="config-chip" data-key="device" onclick="toggleChip(this)">📱 设备</div>
<div class="config-chip" data-key="battery" onclick="toggleChip(this)">🔋 电池</div>
<div class="config-chip" data-key="ip" onclick="toggleChip(this)">🌐 IP</div>
<div class="config-chip" data-key="custom" onclick="toggleChip(this)">✏️ 自定义</div>
</div>
</div>
<!-- Marquee Toggle -->
<div class="sheet-section">
<div class="toggle-row">
<div>
<div class="toggle-label">🔄 轮播显示</div>
<div class="toggle-desc">文字超过10字时自动滚动轮播</div>
</div>
<div class="toggle-switch active" id="marqueeToggle" onclick="toggleMarquee()">
<div class="toggle-knob"></div>
</div>
</div>
</div>
<!-- Animation Intensity -->
<div class="sheet-section">
<div class="sheet-section-title">⚡ 动画强度</div>
<input type="range" class="intensity-slider" id="intensitySlider" min="0" max="100" value="70" oninput="updateIntensity(this.value)">
<div class="intensity-labels">
<span>关闭</span>
<span>柔和</span>
<span>标准</span>
<span>强烈</span>
</div>
</div>
<div class="sheet-section">
<button class="save-btn" onclick="closeSheet()">保存设置</button>
</div>
</div>
</div>
</div>
<div style="display:flex;gap:16px;justify-content:center;margin-top:16px;flex-wrap:wrap">
<div style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary)">
<span style="width:8px;height:8px;border-radius:50%;background:#FF3B30;display:inline-block"></span>
① 角色 - 点击/双击/长按(耳朵鼻子脸蛋全动画)
</div>
<div style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary)">
<span style="width:8px;height:8px;border-radius:50%;background:#FF9500;display:inline-block"></span>
② 闲言 - 点击后角色看向标题
</div>
<div style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary)">
<span style="width:8px;height:8px;border-radius:50%;background:#34C759;display:inline-block"></span>
③ 日期 - 最多3项+轮播+14字限制
</div>
</div>
</div>
</div>
<!-- ========== Feature List ========== -->
<div class="feature-section">
<h2 style="font-size:24px;font-weight:700;margin-bottom:8px;text-align:center">🎯 方案A 动画系统设计</h2>
<p style="font-size:14px;color:var(--text-secondary);text-align:center;margin-bottom:24px">增强 CustomPainter · 3D质感 · 全部件动画 · 动画强度联动</p>
<div class="feature-grid">
<div class="feature-item">
<div class="f-icon">👂</div>
<div>
<div class="f-title">耳朵动画</div>
<div class="f-desc">点击摆动、惊讶竖起、开心下垂、挠痒快速抖动。独立transform-origin受动画强度影响幅度</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">👃</div>
<div>
<div class="f-title">鼻子动画</div>
<div class="f-desc">点击抽动、闻嗅缩放、挠痒快速颤动。使用scaleXY错位缩放模拟3D皱鼻</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">😊</div>
<div>
<div class="f-title">脸蛋动画</div>
<div class="f-desc">开心鼓起、害羞膨胀、挠痒交替鼓腮。rx属性动画+腮红opacity联动</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">👁</div>
<div>
<div class="f-title">眼睛动画</div>
<div class="f-desc">自动眨眼、点击闭眼、惊讶放大、看向闲言偏移、跟随手指。双高光点模拟3D球面</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">👄</div>
<div>
<div class="f-title">嘴巴动画</div>
<div class="f-desc">微笑弧线、惊讶O型、开心大笑、挠痒交替张合。贝塞尔曲线动态控制点</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">🐱</div>
<div>
<div class="f-title">胡须动画</div>
<div class="f-desc">点击摆动、挠痒快速颤动。独立transform-origin左右对称旋转</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">👆</div>
<div>
<div class="f-title">点击 → 全部件联动</div>
<div class="f-desc">单次点击触发:眨眼+耳朵摆+鼻子抽+嘴巴变,所有部件协同而非单一动画</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">👆👆</div>
<div>
<div class="f-title">双击 → 特殊反应</div>
<div class="f-desc">爱心眼+脸蛋鼓起+耳朵竖起+嘴巴大笑持续2s后恢复。confetti粒子特效</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon"></div>
<div>
<div class="f-title">长按 → 挠痒循环</div>
<div class="f-desc">耳朵快速抖动+鼻子颤动+脸蛋交替鼓+嘴巴张合+胡须摆动,循环播放直到松手</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">👀</div>
<div>
<div class="f-title">眼睛跟随手指</div>
<div class="f-desc">Listener监听全局指针计算偏移量映射到眼球位移最大偏移2px。受动画强度影响灵敏度</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">📝</div>
<div>
<div class="f-title">点击闲言 → 看向标题</div>
<div class="f-desc">眼球向右偏移+头微倾+耳朵微调0.3s后回正。模拟"听到名字转头"的自然感</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon"></div>
<div>
<div class="f-title">动画强度联动</div>
<div class="f-desc">读取themeSettingsProvider.animationIntensity影响弹跳幅度/耳朵摆角/鼻子缩放比/腮红透明度/眨眼频率</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">📅</div>
<div>
<div class="f-title">日期栏最多3项</div>
<div class="f-desc">chip选择限制3个超出时未选chip变灰禁用。自定义文案14字上限超限红框提示</div>
</div>
</div>
<div class="feature-item">
<div class="f-icon">🔄</div>
<div>
<div class="f-title">轮播显示</div>
<div class="f-desc">显示文本超过10字时自动横向滚动轮播可开关。使用AnimatedMarquee组件3s一周期</div>
</div>
</div>
</div>
</div>
<script>
const overlay = document.getElementById('sheetOverlay');
const container = document.getElementById('sheetContainer');
const appContent = document.getElementById('appContent');
const characterAvatar = document.getElementById('characterAvatar');
const hint = document.getElementById('hint');
const dateDisplay = document.getElementById('dateDisplay');
const dateBadge = document.getElementById('dateBadge');
const customText = document.getElementById('customText');
let hintTimeout;
let isLongPress = false;
let longPressTimer;
let marqueeEnabled = true;
let animIntensity = 0.7;
const MAX_CHIPS = 3;
const MAX_CHARS = 14;
const MARQUEE_THRESHOLD = 10;
setTimeout(() => {
hint.classList.add('show');
setTimeout(() => hint.classList.remove('show'), 3000);
}, 1000);
// ========== Sheet ==========
function openSheet() {
overlay.classList.add('active');
container.classList.add('active');
appContent.classList.add('pushed');
}
function closeSheet() {
overlay.classList.remove('active');
container.classList.remove('active');
appContent.classList.remove('pushed');
}
// ========== Character Interactions ==========
const expressions = ['blink', 'smile', 'surprise', 'wink', 'pout'];
let expressionIndex = 0;
function onCharacterClick() {
if (isLongPress) return;
const intensity = animIntensity;
characterAvatar.classList.add('bounce');
setTimeout(() => characterAvatar.classList.remove('bounce'), 600);
triggerEarWiggle(intensity);
triggerNoseTwitch(intensity);
triggerWhiskerTwitch(intensity);
const expr = expressions[expressionIndex % expressions.length];
expressionIndex++;
showExpression(expr, intensity);
showHint(getExpressionHint(expr));
}
function onCharacterDblClick() {
const intensity = Math.min(animIntensity * 1.3, 1);
characterAvatar.classList.add('bounce');
setTimeout(() => characterAvatar.classList.remove('bounce'), 600);
triggerEarWiggle(intensity * 1.5);
triggerNoseTwitch(intensity);
triggerCheekPuff(intensity);
triggerWhiskerTwitch(intensity);
showExpression('love', intensity);
showHint('😍 好开心!双击有惊喜~');
}
// Click "闲言" title → character looks toward it
function onTitleClick() {
const intensity = animIntensity;
const eyeL = document.getElementById('eyeL');
const eyeR = document.getElementById('eyeR');
const eyeHL = document.getElementById('eyeHL');
const eyeHR = document.getElementById('eyeHR');
const offsetX = 3 * intensity;
const offsetY = -0.5 * intensity;
eyeL.setAttribute('cx', 18 + offsetX);
eyeR.setAttribute('cx', 30 + offsetX);
eyeL.setAttribute('cy', 26 + offsetY);
eyeR.setAttribute('cy', 26 + offsetY);
eyeHL.setAttribute('cx', 17 + offsetX);
eyeHR.setAttribute('cx', 29 + offsetX);
triggerEarWiggle(intensity * 0.5);
triggerNoseTwitch(intensity * 0.3);
const mouth = document.getElementById('mouthPath');
mouth.setAttribute('d', 'M 20 32 Q 24 35 28 32');
showHint('🐱 嗯?叫我干嘛~');
setTimeout(() => {
eyeL.setAttribute('cx', 18);
eyeR.setAttribute('cx', 30);
eyeL.setAttribute('cy', 26);
eyeR.setAttribute('cy', 26);
eyeHL.setAttribute('cx', 17);
eyeHR.setAttribute('cx', 29);
mouth.setAttribute('d', 'M 20 32 Q 24 36 28 32');
}, 1200);
}
// Long press
characterAvatar.addEventListener('mousedown', startLongPress);
characterAvatar.addEventListener('touchstart', startLongPress);
characterAvatar.addEventListener('mouseup', endLongPress);
characterAvatar.addEventListener('mouseleave', endLongPress);
characterAvatar.addEventListener('touchend', endLongPress);
function startLongPress(e) {
isLongPress = false;
longPressTimer = setTimeout(() => {
isLongPress = true;
showHint('😹 哈哈哈别挠了~');
tickleAnimation();
}, 500);
}
function endLongPress() {
clearTimeout(longPressTimer);
characterAvatar.style.transform = '';
setTimeout(() => { isLongPress = false; }, 100);
}
let tickleInterval;
function tickleAnimation() {
if (!isLongPress) { clearInterval(tickleInterval); return; }
const intensity = animIntensity;
let frame = 0;
clearInterval(tickleInterval);
tickleInterval = setInterval(() => {
if (!isLongPress) { clearInterval(tickleInterval); return; }
frame++;
const angle = Math.sin(frame * 0.8) * 8 * intensity;
characterAvatar.style.transform = `rotate(${angle}deg)`;
if (frame % 3 === 0) {
triggerEarWiggle(intensity * 0.6);
triggerNoseTwitch(intensity * 0.4);
triggerWhiskerTwitch(intensity * 0.5);
}
if (frame % 6 === 0) {
triggerCheekPuff(intensity * 0.5);
}
const mouth = document.getElementById('mouthPath');
if (frame % 4 < 2) {
mouth.setAttribute('d', 'M 19 32 Q 24 37 29 32');
} else {
mouth.setAttribute('d', 'M 20 33 Q 24 35 28 33');
}
const blushL = document.getElementById('blushL');
const blushR = document.getElementById('blushR');
blushL.style.opacity = '0.7';
blushR.style.opacity = '0.7';
}, 120);
}
// ========== Part Animations ==========
function triggerEarWiggle(intensity) {
const earL = document.getElementById('earL');
const earR = document.getElementById('earR');
earL.classList.remove('wiggle');
earR.classList.remove('wiggle');
void earL.offsetWidth;
earL.style.setProperty('--wiggle-deg', (8 * intensity) + 'deg');
earL.classList.add('wiggle');
earR.classList.add('wiggle');
setTimeout(() => {
earL.classList.remove('wiggle');
earR.classList.remove('wiggle');
}, 400);
}
function triggerNoseTwitch(intensity) {
const nose = document.getElementById('noseGroup');
nose.classList.remove('twitch');
void nose.offsetWidth;
nose.classList.add('twitch');
setTimeout(() => nose.classList.remove('twitch'), 300);
}
function triggerCheekPuff(intensity) {
const blushL = document.getElementById('blushL');
const blushR = document.getElementById('blushR');
blushL.classList.remove('puff');
blushR.classList.remove('puff');
void blushL.offsetWidth;
blushL.classList.add('puff');
blushR.classList.add('puff');
blushL.style.opacity = String(0.5 * intensity);
blushR.style.opacity = String(0.5 * intensity);
setTimeout(() => {
blushL.classList.remove('puff');
blushR.classList.remove('puff');
blushL.style.opacity = '0';
blushR.style.opacity = '0';
}, 500);
}
function triggerWhiskerTwitch(intensity) {
const wL = document.getElementById('whiskerL');
const wR = document.getElementById('whiskerR');
wL.classList.remove('twitch');
wR.classList.remove('twitch');
void wL.offsetWidth;
wL.classList.add('twitch');
wR.classList.add('twitch');
setTimeout(() => {
wL.classList.remove('twitch');
wR.classList.remove('twitch');
}, 300);
}
function showExpression(type, intensity) {
intensity = intensity || animIntensity;
const mouth = document.getElementById('mouthPath');
const blushL = document.getElementById('blushL');
const blushR = document.getElementById('blushR');
const eyeL = document.getElementById('eyeL');
const eyeR = document.getElementById('eyeR');
switch(type) {
case 'blink':
eyeL.setAttribute('ry', '0.5');
eyeR.setAttribute('ry', '0.5');
setTimeout(() => {
eyeL.setAttribute('ry', '3.3');
eyeR.setAttribute('ry', '3.3');
}, 200);
break;
case 'smile':
mouth.setAttribute('d', 'M 18 31 Q 24 38 30 31');
blushL.style.opacity = String(0.6 * intensity);
blushR.style.opacity = String(0.6 * intensity);
triggerCheekPuff(intensity);
setTimeout(() => {
mouth.setAttribute('d', 'M 20 32 Q 24 36 28 32');
blushL.style.opacity = '0';
blushR.style.opacity = '0';
}, 1500);
break;
case 'surprise':
mouth.setAttribute('d', 'M 21 32 Q 24 37 27 32');
eyeL.setAttribute('ry', String(4 * intensity));
eyeR.setAttribute('ry', String(4 * intensity));
setTimeout(() => {
mouth.setAttribute('d', 'M 20 32 Q 24 36 28 32');
eyeL.setAttribute('ry', '3.3');
eyeR.setAttribute('ry', '3.3');
}, 1200);
break;
case 'wink':
eyeR.setAttribute('ry', '0.5');
mouth.setAttribute('d', 'M 20 32 Q 25 36 28 32');
triggerEarWiggle(intensity * 0.3);
setTimeout(() => {
eyeR.setAttribute('ry', '3.3');
mouth.setAttribute('d', 'M 20 32 Q 24 36 28 32');
}, 800);
break;
case 'pout':
mouth.setAttribute('d', 'M 21 33 Q 24 31 27 33');
blushL.style.opacity = String(0.4 * intensity);
blushR.style.opacity = String(0.4 * intensity);
setTimeout(() => {
mouth.setAttribute('d', 'M 20 32 Q 24 36 28 32');
blushL.style.opacity = '0';
blushR.style.opacity = '0';
}, 1000);
break;
case 'love':
mouth.setAttribute('d', 'M 18 31 Q 24 38 30 31');
blushL.style.opacity = String(0.8 * intensity);
blushR.style.opacity = String(0.8 * intensity);
eyeL.setAttribute('ry', String(4.2 * intensity));
eyeR.setAttribute('ry', String(4.2 * intensity));
triggerCheekPuff(intensity);
setTimeout(() => {
mouth.setAttribute('d', 'M 20 32 Q 24 36 28 32');
blushL.style.opacity = '0';
blushR.style.opacity = '0';
eyeL.setAttribute('ry', '3.3');
eyeR.setAttribute('ry', '3.3');
}, 2000);
break;
}
}
function getExpressionHint(type) {
const hints = {
'blink': '🐱 喵~ 你点我干嘛?',
'smile': '😊 开心!',
'surprise': '😲 哇!吓我一跳!',
'wink': '😉 给你抛个媚眼~',
'pout': '😤 哼,不跟你玩了~',
'love': '😍 好开心!'
};
return hints[type] || '🐱 喵~';
}
function showHint(text) {
hint.textContent = text;
hint.classList.add('show');
clearTimeout(hintTimeout);
hintTimeout = setTimeout(() => hint.classList.remove('show'), 2000);
}
// ========== Eye Tracking ==========
document.addEventListener('mousemove', (e) => {
if (isLongPress) return;
const rect = characterAvatar.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = (e.clientX - centerX) / window.innerWidth * 3 * animIntensity;
const dy = (e.clientY - centerY) / window.innerHeight * 2 * animIntensity;
const eyeL = document.getElementById('eyeL');
const eyeR = document.getElementById('eyeR');
const eyeHL = document.getElementById('eyeHL');
const eyeHR = document.getElementById('eyeHR');
if (eyeL && eyeR) {
eyeL.setAttribute('cx', 18 + dx);
eyeR.setAttribute('cx', 30 + dx);
eyeL.setAttribute('cy', 26 + dy);
eyeR.setAttribute('cy', 26 + dy);
eyeHL.setAttribute('cx', 17 + dx);
eyeHR.setAttribute('cx', 29 + dx);
}
});
// ========== Date Display Config ==========
function toggleChip(el) {
const activeChips = document.querySelectorAll('.config-chip.active');
if (el.classList.contains('active')) {
el.classList.remove('active');
} else {
if (activeChips.length >= MAX_CHIPS) {
showHint(`⚠️ 最多选择${MAX_CHIPS}个显示项`);
el.style.transform = 'scale(0.9)';
setTimeout(() => el.style.transform = '', 200);
return;
}
el.classList.add('active');
}
updateChipStates();
updateDateDisplay();
}
function updateChipStates() {
const activeChips = document.querySelectorAll('.config-chip.active');
const allChips = document.querySelectorAll('.config-chip');
allChips.forEach(chip => {
if (!chip.classList.contains('active') && activeChips.length >= MAX_CHIPS) {
chip.classList.add('disabled');
} else {
chip.classList.remove('disabled');
}
});
const counter = document.getElementById('chipCounter');
counter.textContent = `${activeChips.length}/${MAX_CHIPS}`;
counter.className = 'counter' + (activeChips.length >= MAX_CHIPS ? ' warn' : '');
}
function onCustomTextInput() {
const text = customText.value;
const len = text.length;
if (len > MAX_CHARS) {
customText.value = text.substring(0, MAX_CHARS);
}
const counter = document.getElementById('charCounter');
const displayLen = Math.min(len, MAX_CHARS);
counter.textContent = `${displayLen}/${MAX_CHARS}`;
counter.className = 'counter' + (displayLen >= MAX_CHARS ? ' warn' : '');
if (len > MAX_CHARS) {
customText.classList.add('over-limit');
setTimeout(() => customText.classList.remove('over-limit'), 500);
}
updateDateDisplay();
}
function toggleMarquee() {
const toggle = document.getElementById('marqueeToggle');
marqueeEnabled = !marqueeEnabled;
toggle.classList.toggle('active', marqueeEnabled);
updateDateDisplay();
}
function updateIntensity(val) {
animIntensity = val / 100;
}
function updateDateDisplay() {
const activeChips = document.querySelectorAll('.config-chip.active');
const customTextVal = customText.value;
const parts = [];
activeChips.forEach(chip => {
const key = chip.dataset.key;
switch(key) {
case 'date': parts.push('5月20日'); break;
case 'weather': parts.push('☀️'); break;
case 'temp': parts.push('26°'); break;
case 'city': parts.push('杭州'); break;
case 'device': parts.push('iPhone'); break;
case 'battery': parts.push('🔋85%'); break;
case 'ip': parts.push('192.168.*'); break;
case 'custom': if (customTextVal) parts.push(customTextVal); break;
}
});
const displayText = parts.join(' · ') || '5月20日';
dateDisplay.textContent = displayText;
dateDisplay.classList.remove('marquee');
if (displayText.length > MARQUEE_THRESHOLD && marqueeEnabled) {
dateDisplay.classList.add('marquee');
}
dateBadge.style.display = parts.length > 1 ? 'block' : 'none';
}
// Initial
updateChipStates();
updateDateDisplay();
</script>
</body>
</html>