.breath-circle-container.hold { animation: holdPulse 1.6s ease-in-out infinite; } #bridge-phase { transition: opacity .25s ease; opacity:1; } .breath-circle-container.hold #bridge-ring { animation: glowPulse 1.6s ease-in-out infinite; } @keyframes glowPulse { 0%,100% { filter:drop-shadow(0 0 6px var(--gold)); } 50% { filter:drop-shadow(0 0 22px var(--gold)); } } #bridge-ring { transition:none; } #bridge-ring.ring-inhale { stroke:#5BC0EB; filter:drop-shadow(0 0 12px #5BC0EB); animation: inhaleFill 4s cubic-bezier(.25,.46,.45,.94) forwards, pulseInhale 2s ease-in-out infinite; } #bridge-ring.ring-hold { stroke:var(--gold); filter:drop-shadow(0 0 14px var(--gold)); animation: glowPulse 1.6s ease-in-out infinite; stroke-dashoffset:0; } #bridge-ring.ring-exhale { stroke:#56C596; filter:drop-shadow(0 0 12px #56C596); animation: exhaleEmpty 8s cubic-bezier(.25,.46,.45,.94) forwards, pulseExhale 2.5s ease-in-out infinite; } @keyframes inhaleFill { from { stroke-dashoffset:502; } to { stroke-dashoffset:0; } } @keyframes exhaleEmpty { from { stroke-dashoffset:0; } to { stroke-dashoffset:502; } } @keyframes pulseInhale { 0%,100% { filter:drop-shadow(0 0 8px #5BC0EB); } 50% { filter:drop-shadow(0 0 20px #5BC0EB); } } @keyframes pulseExhale { 0%,100% { filter:drop-shadow(0 0 8px #56C596); } 50% { filter:drop-shadow(0 0 18px #56C596); } } #bridge-overlay { transition: background .6s ease; } #bridge-overlay .breath-circle-container { transition: transform .6s cubic-bezier(.22,1,.36,1); } @keyframes holdPulse { 0%{transform:scale(1)} 50%{transform:scale(1.08)} 100%{transform:scale(1)} } .breath-circle-svg { width:100%; height:100%; transform:rotate(-90deg); } .breath-circle-ring { fill:none; stroke:#E5E5E5; stroke-width:8; } .breath-circle-active { fill:none; stroke:var(--gold); stroke-width:8; stroke-linecap:round; transition:stroke-dashoffset 1s ease; } #breath-ring { transition:stroke-dashoffset .3s ease, stroke-color .3s ease, filter .3s ease; } #breath-ring.ring-inhale { stroke:#5BC0EB; filter:drop-shadow(0 0 8px #5BC0EB); } #breath-ring.ring-hold { stroke:var(--gold); filter:drop-shadow(0 0 12px var(--gold)); } #breath-ring.ring-exhale { stroke:#56C596; filter:drop-shadow(0 0 8px #56C596); } .breath-phase-text { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); font-size:24px; font-weight:600; color:var(--main); } .breath-timer { font-size:24px; font-weight:700; color:var(--gold); margin-bottom:10px; } .breath-btn { width:100%; height:44px; background:var(--active-bg); color:var(--active-text); border:none; border-radius:22px; font-size:15px; font-weight:600; cursor:pointer; } .breath-btn.stop { background:var(--red); } .coach-card { background:var(--card); border-radius:16px; padding:16px; margin-top:16px; border:1px dashed var(--gold); animation:fadeIn .6s ease both; animation-delay:.3s; } .coach-header { display:flex; align-items:center; gap:8px; margin-bottom:8px; font-size:13px; color:var(--gold); font-weight:600; } .coach-q { font-family:'Noto Serif SC',serif; font-size:16px; color:var(--main); line-height:1.6; margin-bottom:12px; } .coach-reply-box { display:flex; gap:8px; } .coach-input { flex:1; padding:10px; border:1px solid var(--border); border-radius:12px; background:var(--input-bg); color:var(--main); outline:none; font-size:14px; } .coach-send-btn { padding:0 16px; background:var(--gold); color:#1A1A1A; border-radius:12px; border:none; font-weight:600; cursor:pointer; } /* V2.2: Config File Cloud UI */ .cloud-card { background:var(--card); border:1px solid var(--border); border-radius:16px; padding:16px; margin-bottom:16px; } .cloud-card.synced { border-color:var(--green); } .cloud-status-row { font-size:14px; font-weight:600; margin-bottom:8px; display:flex; align-items:center; gap:8px; } .cloud-dot { width:8px; height:8px; border-radius:50%; background:#CCC; } .cloud-dot.ok { background:var(--green); box-shadow:0 0 8px var(--green); } .cloud-actions { display:flex; gap:8px; margin-top:12px; } .cloud-btn { flex:1; height:40px; border:none; border-radius:12px; font-size:13px; font-weight:600; cursor:pointer; position:relative; overflow:hidden; } .cloud-btn.primary { background:var(--active-bg); color:var(--active-text); } .cloud-btn.secondary { background:var(--input-bg); color:var(--main); } .cloud-btn.loading { pointer-events:none; opacity:.75; } .cloud-btn.loading::after { content:""; position:absolute; inset:0; background:rgba(0,0,0,.08); border-radius:inherit; } .cloud-btn.loading::before { content:""; position:absolute; width:18px; height:18px; top:50%; left:50%; margin:-9px 0 0 -9px; border:2px solid currentColor; border-right-color:transparent; border-radius:50%; animation:spin .7s linear infinite; z-index:1; } @keyframes spin { to { transform: rotate(360deg); } } /* V2.0 Insights */ .insight-section { margin-top:16px; margin-bottom:16px; } .tag-cloud { display:flex; flex-wrap:wrap; gap:6px; } .tag-item { padding:4px 8px; background:var(--input-bg); border-radius:8px; font-size:12px; color:var(--sub); } .tag-item.hot { background:var(--gold); color:#1A1A1A; } .pattern-item { padding:12px; background:var(--input-bg); border-radius:12px; margin-bottom:8px; font-size:13px; } .resonance-card { background:var(--card); border-radius:20px; padding:20px; margin-bottom:16px; } .resonance-text { font-family:'Noto Serif SC',serif; font-size:16px; color:var(--gold); line-height:1.6; margin-bottom:12px; } .resonance-meta { display:flex; justify-content:space-between; align-items:center; } .resonance-btn { display:flex; align-items:center; gap:4px; padding:4px 10px; background:transparent; border:1px solid var(--border); border-radius:14px; color:var(--sub); cursor:pointer; font-size:11px; } .resonance-btn.active { background:rgba(201,169,110,.15); border-color:var(--gold); color:var(--gold); } .resonance-count { font-size:14px; font-weight:600; } .resonance-btn.active { box-shadow:0 0 0 3px var(--gold), 0 0 16px rgba(201,169,110,.35); transition:box-shadow .5s; } /* V2.4 — 播种与觉察 */ .seed-section { margin-top:20px; padding:20px; background:linear-gradient(135deg,#F7F5F1,#F0EFEB); border:1px solid #E8E3D8; border-radius:16px; animation:seedFadeIn .6s cubic-bezier(.22,1,.36,1) both; } @keyframes seedFadeIn { from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:translateY(0)} } .seed-heading { font-size:15px; font-weight:600; color:#5A4A3A; margin-bottom:4px; } .seed-sub { font-size:12px; color:#A89880; margin-bottom:14px; line-height:1.6; } .seed-input-wrapper { display:flex; gap:8px; } .seed-input { flex:1; padding:12px 14px; background:#FFF; border:1px solid #E0D5C8; border-radius:12px; font-size:15px; outline:none; transition:border-color .2s; } .seed-input:focus { border-color:var(--gold); box-shadow:0 0 0 3px rgba(201,169,110,.15); } .seed-btn { padding:12px 20px; background:var(--gold); color:#1A1A1A; border:none; border-radius:12px; font-size:14px; font-weight:600; cursor:pointer; transition:transform .15s; } .seed-btn:active { transform:scale(.95); } .seed-btn.done { background:#56C596; } .seed-hint { display:inline-block; margin-top:10px; font-size:12px; color:#B0A090; cursor:pointer; text-decoration:underline; text-decoration-style:dotted; text-underline-offset:3px; } .seed-hint:active { color:var(--gold); } .seed-old-pattern { margin-top:12px; padding:10px 14px; background:rgba(201,169,110,.08); border-left:3px solid var(--gold); border-radius:8px; font-size:13px; color:#5A4A3A; line-height:1.5; animation:fadeIn .4s ease both; } .seed-result { margin-top:12px; display:flex; align-items:center; gap:8px; padding:10px 14px; background:rgba(86,197,150,.1); border-radius:10px; font-size:14px; color:#2A6B4A; animation:fadeIn .4s ease both; } .seed-result-icon { font-size:18px; } /* 种子花园视图 */ .garden-tabs { display:flex; gap:0; margin-bottom:16px; background:var(--input-bg); border-radius:12px; padding:3px; } .garden-tab { flex:1; text-align:center; padding:8px; font-size:13px; color:var(--sub); cursor:pointer; border-radius:10px; transition:all .2s; } .garden-tab.active { background:var(--card); color:var(--main); font-weight:600; box-shadow:0 2px 8px rgba(0,0,0,.06); } .garden-item { display:flex; gap:12px; align-items:flex-start; padding:14px; background:var(--card); border-radius:16px; margin-bottom:10px; box-shadow:0 2px 8px rgba(0,0,0,.02); } .garden-icon { font-size:24px; flex-shrink:0; margin-top:2px; } .garden-content { flex:1; min-width:0; } .garden-seed { font-size:14px; font-weight:600; color:var(--main); margin-bottom:2px; } .garden-meta { font-size:11px; color:var(--sub); } .garden-old { font-size:12px; color:#B0A090; margin-top:4px; padding:4px 8px; background:var(--input-bg); border-radius:6px; display:inline-block; } .garden-sub { display:flex; gap:6px; margin:0 0 12px 0; justify-content:center; } .garden-sub-btn { padding:4px 14px; font-size:12px; color:var(--sub); background:transparent; border:1px solid var(--border); border-radius:20px; cursor:pointer; transition:all .2s; } .garden-sub-btn.active { background:var(--gold); color:#1A1A1A; border-color:var(--gold); } .garden-level { display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; font-size:20px; } .garden-stat { text-align:center; padding:12px; margin-bottom:12px; background:rgba(201,169,110,.06); border-radius:12px; font-size:12px; color:var(--sub); line-height:1.6; } .water-btn { display:inline-flex; align-items:center; gap:3px; padding:4px 10px; background:transparent; border:1px solid var(--border); border-radius:14px; color:var(--sub); cursor:pointer; font-size:11px; margin-left:6px; } .water-btn.active { background:rgba(86,197,150,.12); border-color:#56C596; color:#56C596; }
「 我 」我是谁一吸一呼,回到你自己
🌱 今天你想照见哪个维度?
🌿
综合觉察
💼
工作/事业
❤️
关系/情感
🧘
身体/健康
1. 今天最重要的事是什么?
2. 这件事,说明我是一个怎样的人?
3. 这个判断,和我对自己的认知一致吗?
4. 此刻,是谁在回答?
💡 今日洞察
证据库
📋 时间线
🌱 种子花园
共鸣广场你不是一个人
💫 全部
🌱 种子集
点击「种子集」可只看大家今天一起种下的积极意图
🫁 呼吸练习一吸一呼,回到你自己
准备好了吗?
4-7-8

吸气 4 秒 → 憋气 7 秒 → 呼气 8 秒

反身引擎
🌙
0总记录
0%觉知占比
📊 一致性 vs 觉知
📈 近 7 天状态
⏰ 每日提醒
提醒时间
⚙️ 设置与数据
正在读取配置...
版本 V2.4.1
🪞
照见
📚
记录
💫
共鸣
🌬️
呼吸
🤔
引擎
✅ 已记录
关闭
长按图片保存到相册
window.DASHSCOPE_KEY='sk-sp-9f51b4617ae94a5781b7027935dfa78c'; window.App = { quotes: [{text:"你未曾见过的那一面,才是你。",author:"荣格"},{text:"未经审视的人生是不值得过的。",author:"苏格拉底"},{text:"真正的发现之旅,不在于寻找新风景,而在于拥有新的眼睛。",author:"普鲁斯特"},{text:"知人者智,自知者明。",author:"老子"},{text:"所谓无底深渊,下去,也是前程万里。",author:"木心"},{text:"我们都在阴沟里,但仍有人仰望星空。",author:"王尔德"}], themes: { general:{q1:"今天最重要的事是什么?",p1:"一桩事,足矣。",q2:"这件事,说明我是一个怎样的人?",p2:"一个词或一句话。"}, work:{q1:"今天在工作中,我最在意的是什么?",p1:"一个项目、一次沟通...",q2:"这种在意,说明我对工作的态度是?",p2:"责任、焦虑、成就..."}, relation:{q1:"今天在和谁的互动中,我最有情绪?",p1:"家人、伴侣、同事...",q2:"这种情绪背后,是我怎样的需求?",p2:"被理解、被尊重、控制..."}, body:{q1:"今天我的身体在对我说什么?",p1:"疲惫、紧绷、轻松...",q2:"我听到它的声音了吗?我回应了吗?",p2:"忽视、照顾、接纳..."} }, currentTheme:'general', lastItem:null, reminderInterval:null, recognition:null, isRecording:false, targetFieldId:null, resonanceData:[], breathMode:'478', voiceGuideEnabled: false, startVoiceGuide: function() { if (!('speechSynthesis' in window)) return; this.voiceGuideEnabled = true; this.speak('吸气...'); }, speak: function(text) { if (!this.voiceGuideEnabled) return; if (!window.speechSynthesis) return; var u = new SpeechSynthesisUtterance(text); u.lang = 'zh-CN'; u.rate = 0.9; speechSynthesis.speak(u); }, stopVoiceGuide: function() { this.voiceGuideEnabled = false; if ('speechSynthesis' in window) speechSynthesis.cancel(); }, breathInterval:null, isBreathing:false, isMeditating:false, medTime:600, medInterval:null, audioCtx:null, whiteNoiseNode:null, isSoundOn:false, noiseGain:null, cloudWorkerUrl: '', cloudUsername: '', // V2.3: Username as KV Key bindEvents: function() { var tabs = document.querySelectorAll('.tab'); tabs.forEach(function(tab) { tab.addEventListener('click', function() { tabs.forEach(function(t) { t.classList.remove('active'); }); tab.classList.add('active'); var idx = tab.getAttribute('data-idx'); document.querySelectorAll('.page').forEach(function(p) { p.classList.remove('active'); }); var target = document.querySelector('.page[data-idx="' + idx + '"]'); if (target) target.classList.add('active'); }); }); var remBtn = document.getElementById('btn-toggle-reminder'); if (remBtn) { remBtn.addEventListener('click', function() { if (window.App && window.App.toggleReminder) window.App.toggleReminder(); }); } var clearBtn = document.getElementById('btn-clear-data'); if (clearBtn) { clearBtn.addEventListener('click', function() { if (window.App && window.App.store && window.App.store.clear) window.App.store.clear(); }); } var soundBtn = document.getElementById('sound-toggle'); if (soundBtn) { soundBtn.addEventListener('click', function() { if (window.App && window.App.toggleSound) App.toggleSound(); }); } var darkBtn = document.getElementById('dark-mode-toggle'); if (darkBtn) { darkBtn.addEventListener('click', function() { if (window.App && window.App.initDarkMode) App.initDarkMode(); }); } var usernameInput = document.getElementById('cloud-username'); if (usernameInput) { usernameInput.addEventListener('keypress', function(e) { if (e.key === 'Enter' && window.App && window.App.saveUsername) App.saveUsername(); }); } }, init: async function() { this.bindEvents(); // V2.3: Use Worker URL from inline meta tag (injected by Pages at deploy time) this.cloudWorkerUrl = (document.querySelector('meta[name="whoami-worker-url"]')?.content || '').replace(/\/$/, ''); // V2.3: Load username from localStorage (never auto-generate) this.cloudUsername = localStorage.getItem('whoami_username') || ''; this.renderCloudUI(); this.store.init(); this.setDailyQuote(); this.initDarkMode(); this.initSwipe(); this.initReminder(); this.initVoice(); this.loadResonance(); this.updateStreak(); this.renderHistory(); this.renderProfile(); if (this.cloudWorkerUrl && this.cloudUsername) { this._pullCloudRaw().then(d=>{if(d&&d.entries&&d.entries.length){this.store.save(d.entries);if(d.streak){localStorage.setItem('whoami_streak',JSON.stringify(d.streak));}this.renderProfile();this.updateStreak();}else this.pushCloud(true);}); } }, showToast: function(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 3000); }, fetchWithTimeout: function(url, opts, ms) { const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), ms || 15000); return fetch(url, Object.assign({}, opts, { signal: ctrl.signal })) .finally(() => clearTimeout(timer)); }, showPrompt: function(msg) { return new Promise(resolve => { const el = document.getElementById('prompt-overlay'); document.getElementById('prompt-text').textContent = msg; el.classList.add('active'); const input = document.getElementById('prompt-input'); input.value = ''; setTimeout(() => input.focus(), 50); const done = (v) => { el.classList.remove('active'); resolve(v); }; document.getElementById('prompt-ok').onclick = () => done(input.value); document.getElementById('prompt-cancel').onclick = () => done(null); input.onkeydown = (e) => { if (e.key === 'Enter') done(input.value); if (e.key === 'Escape') done(null); }; }); }, vibrate: function(ms) { if (navigator.vibrate) { navigator.vibrate(ms || 10); } }, // V2.3: Username as Key - strict validation _validUsername: function(u) { return /^[a-zA-Z0-9_-]{3,32}$/.test(u); }, // V2.3: Username-based Cloud UI renderCloudUI: function() { const card = document.getElementById('cloud-card'); const dot = document.getElementById('cloud-dot'); const text = document.getElementById('cloud-text'); const connected = document.getElementById('cloud-connected'); const disconnected = document.getElementById('cloud-disconnected'); const loginPanel = document.getElementById('cloud-login'); const usernameInput = document.getElementById('cloud-username'); const usernameStatus = document.getElementById('username-status'); if (this.cloudWorkerUrl && this.cloudUsername) { // Connected state card.classList.add('synced'); dot.classList.add('ok'); text.textContent = '已连接云端 (' + this.cloudUsername + ')'; connected.style.display = 'block'; disconnected.style.display = 'none'; loginPanel.style.display = 'none'; } else if (this.cloudWorkerUrl) { // Worker URL exists but no username set - show login panel card.classList.remove('synced'); dot.classList.remove('ok'); text.textContent = '等待登录'; connected.style.display = 'none'; disconnected.style.display = 'none'; loginPanel.style.display = 'block'; if (usernameInput) usernameInput.value = this.cloudUsername || ''; } else { // No Worker URL configured card.classList.remove('synced'); dot.classList.remove('ok'); text.textContent = '本地模式 (未配置 config.json)'; connected.style.display = 'none'; disconnected.style.display = 'block'; loginPanel.style.display = 'none'; } }, saveUsername: async function() { const input = document.getElementById('cloud-username'); const status = document.getElementById('username-status'); const username = input.value.trim(); if (!this._validUsername(username)) { status.textContent = '⚠️ 仅允许 3-32 位字母、数字、下划线或连字符'; status.style.color = 'var(--red)'; return; } this.cloudUsername = username; localStorage.setItem('whoami_username', username); status.textContent = '✅ 用户名已保存'; status.style.color = 'var(--green)'; this.renderCloudUI(); // 先尝试从云端恢复 const cloudData = await this._pullCloudRaw(); if (cloudData && cloudData.entries && cloudData.entries.length > 0) { await this.store.save(cloudData.entries); if (cloudData.streak) localStorage.setItem('whoami_streak', JSON.stringify(cloudData.streak)); this.renderProfile(); this.renderHistory(); this.updateStreak(); } else { // 云端空 → 把本地数据推上去 await this.pushCloud(true); } }, logoutCloud: async function() { if(!(await App.showConfirm('确定退出云端吗?(本地数据保留)'))) return; this.cloudUsername = ''; localStorage.removeItem('whoami_username'); this.renderCloudUI(); this.showToast('🔌 已退出云端'); }, _pullCloudRaw: async function() { if (!this.cloudWorkerUrl || !this.cloudUsername) return null; try { const res = await this.fetchWithTimeout(`${this.cloudWorkerUrl}/sync?k=${encodeURIComponent(this.cloudUsername)}`, {}, 10000); if (!res || !res.ok) return null; const text = await res.text(); if (!text) return null; const data = JSON.parse(text); // Support both old format (array) and new format ({entries, streak}) if (Array.isArray(data)) return {entries: data, streak: null}; if (data && data.entries) return data; return null; } catch { return null; } }, pushCloud: function(silent) { if (!this.cloudWorkerUrl) { if(!silent) this.showToast('❌ 云端未配置,无法同步'); return; } if (!this.cloudUsername) { if(!silent) this.showToast('⚠️ 请先在云端登录'); return; } const btn = document.getElementById('btn-push'); if (btn) btn.classList.add('loading'); if (!silent) this.showToast('☁️ 正在同步...'); this.store.get().then(data => { const payload = {entries: Array.isArray(data) ? data : [], streak: JSON.parse(localStorage.getItem('whoami_streak')||'{"count":0,"lastDate":""}')}; return this.fetchWithTimeout(`${this.cloudWorkerUrl}/sync?k=${encodeURIComponent(this.cloudUsername)}`, { method:'POST', headers:{'content-type':'application/json'}, body:JSON.stringify(payload) }, 15000); }).then(res => { if (res && res.ok && !silent) this.showToast('✅ 同步成功'); else if (res && !res.ok) this.showToast('❌ 同步失败: HTTP ' + res.status); }).catch(e => { const err = typeof e === 'string' ? e : (e && e.message) ? e.message : JSON.stringify(e); if(!silent) this.showToast('❌ 同步失败: ' + err); }).finally(() => { if (btn) btn.classList.remove('loading'); }); }, pullCloud: function() { if (!this.cloudWorkerUrl) { this.showToast('❌ 云端未配置,无法恢复'); return; } if (!this.cloudUsername) { this.showToast('⚠️ 请先在云端登录'); return; } const btn = document.getElementById('btn-pull'); if (btn) btn.classList.add('loading'); this.showToast('📥 正在从云端恢复...'); this.fetchWithTimeout(`${this.cloudWorkerUrl}/sync?k=${encodeURIComponent(this.cloudUsername)}`, {}, 15000) .then(res => { if (!res.ok) throw new Error('HTTP ' + res.status); return res.text().then(t => { try { return JSON.parse(t); } catch { return []; } }); }) .then(data => { // Support both old format (array) and new format ({entries, streak}) const entries = Array.isArray(data) ? data : (data && data.entries ? data.entries : null); if (entries && entries.length > 0) { return this.store.save(entries).then(() => { if (data && data.streak) localStorage.setItem('whoami_streak', JSON.stringify(data.streak)); return {count: entries.length}; }); } this.showToast('☁️ 云端暂无数据'); return null; }).then(out => { if (out && out.count) { this.showToast(`✅ 已恢复 ${out.count} 条记录`); this.renderProfile(); this.renderHistory(); this.updateStreak(); } }) .catch(e => { const err = typeof e === 'string' ? e : (e && e.message) ? e.message : JSON.stringify(e); this.showToast('❌ 恢复失败: ' + err); }) .finally(() => { if (btn) btn.classList.remove('loading'); }); }, setDailyQuote: function() { const d=Math.floor((new Date()-new Date(new Date().getFullYear(),0,0))/864e5), q=this.quotes[d%this.quotes.length]; document.getElementById('quote-text').textContent=q.text; document.getElementById('quote-author').textContent='—— '+q.author; }, initDarkMode: function() { const isDark = localStorage.getItem('whoami_theme') === 'dark'; const btn = document.getElementById('dark-mode-toggle'); if (isDark) { document.body.classList.add('dark'); btn.textContent = '☀️'; } else { document.body.classList.remove('dark'); btn.textContent = '🌙'; } btn.onclick = () => { const d = document.body.classList.toggle('dark'); localStorage.setItem('whoami_theme', d ? 'dark' : 'light'); btn.textContent = d ? '☀️' : '🌙'; }; }, initSwipe: function() { const slider=document.getElementById('slider'), tabs=document.querySelectorAll('.tab'); let sx=0,sy=0; slider.addEventListener('touchstart',e=>{sx=e.touches[0].clientX;sy=e.touches[0].clientY;},{passive:true}); slider.addEventListener('touchend',e=>{const dx=sx-e.changedTouches[0].clientX,dy=sy-e.changedTouches[0].clientY;if(Math.abs(dx)>30&&Math.abs(dx)>Math.abs(dy)){let n=Math.round(slider.scrollLeft/window.innerWidth)+(dx>0?1:-1);if(n>=0&&n<=4){slider.scrollTo({left:n*window.innerWidth,behavior:'smooth'});this.updateTabs(n);}}},{passive:true}); slider.addEventListener('scroll',()=>{const i=Math.round(slider.scrollLeft/window.innerWidth);if(!tabs[i].classList.contains('active'))this.updateTabs(i);},{passive:true}); tabs.forEach(t=>t.addEventListener('click',()=>{const idx=parseInt(t.dataset.idx);slider.scrollTo({left:idx*window.innerWidth,behavior:'smooth'});this.updateTabs(idx);})); }, updateTabs: function(idx) { const tabs=document.querySelectorAll('.tab'); tabs.forEach(t=>t.classList.remove('active'));tabs[idx].classList.add('active'); document.activeElement&&document.activeElement.blur(); if(idx===1){this.renderHistory();this.updateStreak();this.bindSelectAll();} if(idx===2)this.renderResonance(); if(idx===3)this.renderBreathStats(); if(idx===4)this.renderProfile(); }, wizard: { _busy: false, showStep: function(id, anim) { document.querySelectorAll('.wizard-step').forEach(el=>{el.classList.remove('active');el.style.display='none';}); const el=document.getElementById(id); el.style.display='block'; void el.offsetWidth; if(anim!==false)el.classList.add('active'); }, nextStep: function(n) { if(this._busy) return; if(n===2&&!document.getElementById('q1').value.trim()){App.showToast('🤔 先想一下...');return;} if(n===3&&!document.getElementById('q2').value.trim()){App.showToast('🤔 先想一下...');return;} this._busy = true; this.showStep('step-'+n); setTimeout(() => { this._busy = false; }, 400); document.activeElement && document.activeElement.blur(); }, prevStep: function(n) { this.showStep('step-'+n,false); document.activeElement && document.activeElement.blur(); } }, selectTheme: function(theme) { this.vibrate(15); this.currentTheme=theme; const t=this.themes[theme]; document.getElementById('q1-label').textContent='1. '+t.q1; document.getElementById('q1').placeholder=t.p1; document.getElementById('q2-label').textContent='2. '+t.q2; document.getElementById('q2').placeholder=t.p2; document.querySelectorAll('.theme-btn').forEach(b=>b.classList.remove('active')); document.querySelector(`.theme-btn[onclick="App.selectTheme('${theme}')"]`).classList.add('active'); }, switchBreathMode: function(mode) { this.breathMode = mode; document.getElementById('mode-478').classList.toggle('active', mode === '478'); document.getElementById('mode-awareness').classList.toggle('active', mode === 'awareness'); const timer = document.getElementById('breath-timer'); const desc = document.getElementById('breath-desc'); if (mode === 'awareness') { if (timer && !this['_breathe']) { timer.textContent = '4-4-6'; } if (desc) desc.textContent = '吸气 4 秒 → 憋气 4 秒 → 呼气 6 秒'; } else { if (timer && !this['_breathe']) { timer.textContent = '4-7-8'; } if (desc) desc.textContent = '吸气 4 秒 → 憋气 7 秒 → 呼气 8 秒'; } }, selectQ4: function(val) { document.getElementById('step4-btns').style.display = 'flex'; }, startBridgeBreath: function() { this._bridgeActive = true; const ring = document.getElementById('bridge-ring'); const phase = document.getElementById('bridge-phase'); const timer = document.getElementById('bridge-timer'); const ct = document.querySelector('#bridge-ring')?.closest('.breath-circle-container'); if (!ring || !phase || !timer) return; const showPhase = (text, color, cls, ringCls) => { phase.style.opacity = '0'; setTimeout(() => { phase.textContent = text; phase.style.color = color; phase.style.opacity = '1'; }, 120); timer.style.color = color; if (ct) { if (cls) ct.classList.add(cls); else ct.classList.remove('hold'); } ring.classList.remove('ring-inhale','ring-hold','ring-exhale'); if (ringCls) ring.classList.add(ringCls); }; // Inhale 4s — ring fills, ease-in-out showPhase('吸气...', '#5BC0EB', null, 'ring-inhale'); let n = 4; timer.textContent = n; this._breathIntervals = this._breathIntervals || []; const i1 = setInterval(() => { n--; if (n <= 0) clearInterval(i1); timer.textContent = n; }, 1000); this._breathIntervals.push(i1); // Hold 7s — pulse glow setTimeout(() => { clearInterval(i1); if (!this._bridgeActive) return; showPhase('屏息...', 'var(--gold)', 'hold', 'ring-hold'); n = 7; timer.textContent = n; const i2 = setInterval(() => { n--; if (n <= 0) clearInterval(i2); timer.textContent = n; }, 1000); // Exhale 8s — ring empties, smooth setTimeout(() => { clearInterval(i2); if (!this._bridgeActive) return; showPhase('呼气...', '#56C596', null, 'ring-exhale'); n = 8; timer.textContent = n; const i3 = setInterval(() => { n--; if (n <= 0) clearInterval(i3); timer.textContent = n; }, 1000); setTimeout(() => { clearInterval(i3); this._bridgeActive = false; document.getElementById('bridge-overlay').style.display = 'none'; this.wizard.showStep('step-4'); }, 8000); }, 7000); }, 4000); }, enterBreathing: function() { if(!document.querySelector('input[name=q3]:checked')){App.showToast('🤔 选一下吧...');return;} document.getElementById('bridge-overlay').style.display='flex'; App.startBridgeBreath(); }, submitAll: async function() { if(!document.querySelector('input[name=q4]:checked')){App.showToast('🤔 选一下吧...');return;} const q4=document.querySelector('input[name=q4]:checked').value; const item={id:Date.now(),date:new Date().toISOString(),theme:this.currentTheme,q1:document.getElementById('q1').value.trim(),q2:document.getElementById('q2').value.trim(),q3:document.querySelector('input[name=q3]:checked').value,q4:q4,new_seed:'',old_pattern:''}; try{await this.store.add(item);}catch(e){this.showToast('⚠️ 存储失败');return;} this.lastItem=item; this.checkInStreak(); this.showToast('✅ 已照见此刻'); this.generateInsight(item); this.wizard.showStep('step-result'); if(this.cloudWorkerUrl && this.cloudUsername) this.pushCloud(true); // V2.4 展示播种 this._resetSeedUI(); setTimeout(()=>{ const ss=document.getElementById('seed-section'); if(ss) ss.style.display='block'; }, 600); }, generateInsight: function(item) { const t1=item.q1, t2=item.q2, c=(t1+' '+t2).toLowerCase(); const neg=/焦虑|愤怒|痛苦|害怕|恐惧|恨|烦|累|疲惫|压力|紧张|担心|不安|难过|失落|孤独|寂寞|迷茫|困惑/.test(c); const pos=/感恩|感谢|幸福|开心|喜悦|满足|爱|温暖|期待|希望|轻松/.test(c); let lines=[]; // Layer 1: Q4 awareness state if(item.q4==='觉知') lines.push('你带着觉知在经历这一切。看着念头升起又落下,不评判,只看着。这就是「参」。'); else if(item.q4==='感受') lines.push('感受在告诉你什么?情绪不是敌人,它是一封信,写着你现在真正在意的。'); else lines.push('你在思考这件事。头脑在运转是正常的——只要知道那是头脑,而不是你,就够了。'); // Layer 2: Theme context if(item.theme==='work') { if(neg) lines.push('工作中的压力,往往来自我们赋予事情的意义超过了事情本身。'); else lines.push('在工作中觉察自己,是最高效的修行。'); } else if(item.theme==='relation') lines.push('关系的镜子最明亮——你在对方身上看到的,往往是你自己的一部分。'); else if(item.theme==='body') lines.push('身体从不说谎。它一直在替你的心说话。'); // Layer 3: Keyword matching - richer dictionary const kw={ '焦虑':'焦虑背后,总是藏着一个未被满足的期待。找到它,就找到了出口。', '担心':'你担心的事,99%不会发生。那1%,你也有能力面对。', '紧张':'紧张的背后是你在乎。在乎不是弱点,是你在意。', '愤怒':'愤怒是一层外衣,里面包着的通常是受伤或无力感。', '害怕':'害怕的不是事情本身,而是你对这件事的解读。', '压力':'压力不是事情给的,是你对事情的期待给的。', '累':'疲惫是身体在敲门,告诉你该停下来听听自己了。', '忙':'忙是最大的借口——忙到你连自己是谁都忘了问。', '工作':'工作只是你做的事,不是你这个人。别忘了。', '孩子':'孩子是你的一面镜子——你希望他成为的,往往是你还没成为的。', '家人':'最亲密的关系最容不下伪装。家人面前摘下面具,就是修行。', '伴侣':'你与伴侣的关系,就是你与世界的关系的缩影。', '感恩':'能看见美好,本身就是一种美好。', '幸福':'幸福不是拥有更多,而是对已有的感到满足。', '迷茫':'迷茫不是迷路,是你正在蜕变的信号。', '信心':'信心不是等来的,是走一步长一点的。', '放弃':'想放弃的时候,恰恰是离突破最近的时候。' }; const found=Object.keys(kw).find(k=>c.includes(k)); if(found) lines.push(kw[found]); // Layer 4: Sentiment + consistency combo if(neg && item.q3==='矛盾') lines.push('你一边抗拒它,一边认同它。看见这个矛盾,就是松动的开始。'); else if(neg && item.q3==='一致') lines.push('你清楚地知道这让你不舒服,但你没有逃避——这本身就是勇气。'); else if(pos && item.q3==='一致') lines.push('身心一致的状态,是最好的状态。记住这种感觉。'); else if(pos && item.q3==='矛盾') lines.push('即使感到美好,内心仍有矛盾——允许这种张力存在,它也是你的一部分。'); // Layer 5: Q1+Q2 relationship const matchQ2=t2.match(/我是(.+)的人/); if(matchQ2) { const identity=matchQ2[1]; if(item.q3==='一致') lines.push('你认同「'+identity+'」这个描述,说明你对自己有清晰的认知。'); else lines.push('你说自己是'+identity+',但又不完全认同——这个缝隙里,藏着成长的空间。'); } // Layer 6: q2 length/type analysis if(t2.length<=3 && !neg && !pos) lines.push('一个词的回答往往藏着一整片海——愿意的话,下次可以多写一点。'); // Deduplicate and assemble const unique=[...new Set(lines)]; const insight=unique.join('\n\n'); document.getElementById('insight-text').textContent=insight; this.generateCoachQuestion(item); }, coachQuestions:{ '焦虑':'你写到了焦虑。如果这个焦虑是身体里的一个位置,它在哪?它在说什么?', '担心':'你写到了担心。最坏的情况如果真的发生了——那个处境里的你,会怎么面对?', '愤怒':'你写到了愤怒。愤怒指向的那个人或那件事,真正触动你的是什么?是失望、伤害,还是无能为力?', '累':'你写到了累。你一直在满足谁的期待?如果停下来,你会让谁失望?', '压力':'你写到了压力。这压力是事情本身的重量,还是你对自己设下的标准?', '迷茫':'你写到了迷茫。你在寻找一个答案,还是害怕面对答案本身?', '工作':'你写到了工作。如果这些事明天起没人知道是你做的——你还会做吗?', '责任':'你写到了责任。责任是你选择的,还是你觉得自己「应该」承担的?', '家人':'你写到了家人。家人最让你放不下的是什么——是爱,还是某种「应该」?', '孩子':'你写到了孩子。你希望孩子成为什么样的人——还是希望他成为你没成为的人?', '伴侣':'你写到了关系。你在关系中给出去的和想要得到的,哪个更难开口?', '怕':'你写到了怕。如果你不怕了,你会怎么做?那个答案里的你和现在有什么不同?', '恨':'你写到了恨。恨的对面是什么?你敢看它一眼吗?', '放下':'你写到了放下。放不下,是这件事太重,还是你抓着它的手不想松开?', '意义':'你写到了意义。你做的事和你这个人之间,隔着多远?', '时间':'你写到了时间。你是没有时间,还是没有为自己留时间?', '自己':'你写到了「自己」。你说「自己」的时候,指的是哪个你?', '角色':'你写到了角色。这个角色如果卸下,剩下的那个你,还认识自己吗?', '安全感':'你写到了安全感。安全感从哪里来——从外面得到的东西,还是从里面安放的能力?' }, _coachRound: 0, generateCoachQuestion: async function(item) { this._coachRound = 0; let question = ''; if (window.DASHSCOPE_KEY && window.DASHSCOPE_KEY.length > 10) { try { let ac = new AbortController(); setTimeout(() => ac.abort(), 6000); const p = '你是觉察教练。用一句话问一个开放式的参究问题(不超过25字,引用户原文,不评判不建议)'; const c = '用户今天写了:\nQ1:' + item.q1 + '\nQ2:' + item.q2 + '\nQ3:' + item.q3 + '\nQ4:' + item.q4; const b = JSON.stringify({ model: 'qwen-turbo', messages: [{ role: 'system', content: p }, { role: 'user', content: c }] }); const res = await fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', { signal: ac.signal, method: 'POST', headers: { 'content-type': 'application/json', 'Authorization': 'Bearer ' + window.DASHSCOPE_KEY }, body: b }); if (res && res.ok) { const d = await res.json(); if (d && d.choices && d.choices[0] && d.choices[0].message) question = d.choices[0].message.content.trim(); } } catch (e) { } } if (!question) { const combined = (item.q1 + " " + item.q2).toLowerCase(); for (const [key, q] of Object.entries(this.coachQuestions)) { if (combined.includes(key)) { question = q; break; } } if (!question) { const defs = ['你刚才写的那句话里,哪个词最重?看着它,不要解释。', '如果刚才的回答不是用头脑,而是用心来回答——答案会变吗?', '你写下的这些东西,如果只能说一句给明天的自己,你会说什么?']; question = defs[Math.floor(Math.random() * defs.length)]; } } document.getElementById('coach-q').textContent = question; document.getElementById('coach-card').style.display = 'block'; }, replyCoach: async function() { const input=document.getElementById('coach-input'), reply=input.value.trim(); if(!reply)return; this.vibrate(10); const btn=document.querySelector('.coach-send-btn');if(btn){btn.style.transform='scale(.92)';btn.style.transition='transform .12s';setTimeout(()=>{btn.textContent='✓';btn.style.background='var(--green)';btn.style.color='#FFF';btn.style.transform='';setTimeout(()=>{btn.textContent='发送';btn.style.background='';btn.style.color='';},400);},120);} input.style.transition='opacity .15s, transform .15s';input.style.opacity='0';input.style.transform='translateX(10px)';setTimeout(()=>{input.value='';input.style.opacity='1';input.style.transform='';},150); const followUp={q:document.getElementById('coach-q').textContent,a:reply}; if(this.lastItem){if(!this.lastItem.coachReply)this.lastItem.coachReply=[]; this.lastItem.coachReply.push(followUp); const store=await this.store.get(); const idx=store.findIndex(i=>i.id===this.lastItem.id); if(idx!==-1){store[idx].coachReply=this.lastItem.coachReply;await this.store.save(store);}} input.value=''; this._coachRound = (this._coachRound || 0) + 1; const round = this._coachRound; let followQ = ''; if(round >= 3) { followQ = '你已经探到这里了。不用再回答——停在这里,呼吸三次。这个觉察,是属于你的。'; document.getElementById('coach-card').style.display='none'; this.showToast('🧘 参究已完成'); return; } // Try AI for follow-up via DashScope if (!followQ && window.DASHSCOPE_KEY && window.DASHSCOPE_KEY.length > 10 && this.lastItem) { try { let ac = new AbortController(); setTimeout(() => ac.abort(), 6000); const historyText = (this.lastItem.coachReply || []).map((h, i) => '追问' + (i + 1) + ':' + h.q + '\n回答:' + h.a).join('\n'); const p = '你是觉察教练。基于对话历史问一个更深的参究问题(不超过25字,引用户原文)'; const c = '用户今天写了:\nQ1:' + this.lastItem.q1 + '\nQ2:' + this.lastItem.q2 + '\nQ3:' + this.lastItem.q3 + '\nQ4:' + this.lastItem.q4 + '\n\n之前的对话:\n' + historyText + '\n\n用户刚刚又回答了:' + reply.slice(0, 50); const b = JSON.stringify({ model: 'qwen-turbo', messages: [{ role: 'system', content: p }, { role: 'user', content: c }] }); const res = await fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', { signal: ac.signal, method: 'POST', headers: { 'content-type': 'application/json', 'Authorization': 'Bearer ' + window.DASHSCOPE_KEY }, body: b }); if (res && res.ok) { const d = await res.json(); if (d && d.choices && d.choices[0] && d.choices[0].message) followQ = d.choices[0].message.content.trim(); } } catch (e) { } } // Fallback to rule-based if (!followQ) { const r = reply.toLowerCase(); const hasFeel = /感觉|觉得|知道|认为/.test(r); const hasEmotion = /难过|开心|愤怒|害怕|焦虑|平静|不安|委屈/.test(r); const hasPerson = /他|她|他们|老板|同事|家人|孩子/.test(r); const hasI = /我/.test(r); if(round === 1) { if(hasEmotion && hasI) followQ = '你刚才说「'+reply.slice(0,12)+'」——那个感受在被你说出来之前,它在身体的哪个位置?'; else if(hasFeel) followQ = '你说的「感觉」,是头脑里的判断,还是身体里的感受?两者很不一样。'; else if(!hasI) followQ = '你刚才说的这件事里——你在哪里?试着把答案里加上「我」,看看感受会不会不一样。'; else followQ = '看着你写的这个回答——它是从你心里流出来的,还是从习惯里跑出来的?'; } else { if(hasEmotion) followQ = '这个情绪不是你,它只是经过你。你能看着它,而不被它卷走吗?'; else if(hasPerson) followQ = '你提到的那个人——你在乎的是他/她本身,还是你在他/她面前的自己?'; else followQ = '你刚才的回答,如果用一个画面来形容——那是什么样的画面?'; } } document.getElementById('coach-q').textContent=followQ; document.getElementById('coach-card').style.display='block'; }, // V2.4 — 播种与觉察 _resetSeedUI: function() { const sec=document.getElementById('seed-section'), input=document.getElementById('seed-input'), btn=document.getElementById('seed-btn'), hint=document.getElementById('seed-hint'), op=document.getElementById('seed-old-pattern'), res=document.getElementById('seed-result'); if(sec) sec.style.display='none'; if(input) input.value=''; if(btn) { btn.textContent='种下 →'; btn.className='seed-btn'; btn.disabled=false; } if(hint) hint.style.display='inline-block'; if(op) op.style.display='none'; if(res) res.style.display='none'; }, saveSeed: function() { const input=document.getElementById('seed-input'), btn=document.getElementById('seed-btn'), hint=document.getElementById('seed-hint'), res=document.getElementById('seed-result'); const val=input.value.trim(); if(!val) { this.showToast('🌱 写一颗种子吧...'); return; } this.lastItem.new_seed = val; // Save to store this.store.get().then(list=>{ const idx=list.findIndex(i=>i.id===this.lastItem.id); if(idx!==-1){ list[idx].new_seed=val; this.store.save(list); } }); btn.textContent='✓'; btn.className='seed-btn done'; btn.disabled=true; input.disabled=true; if(hint) hint.style.display='none'; res.innerHTML='🌱 已种下「'+val+'」的种子。每天照见,它就会生长。'; res.style.display='flex'; this.vibrate(10); if(this.cloudWorkerUrl && this.cloudUsername) this.pushCloud(true); }, showOldPattern: async function() { const box=document.getElementById('seed-old-pattern'); if(box.style.display==='block'){ box.style.display='none'; return; } box.innerHTML='🔍 正在回顾你的记录...'; box.style.display='block'; // Try AI for old pattern summary if(window.DASHSCOPE_KEY && window.DASHSCOPE_KEY.length>10 && this.lastItem){ try{ const list=await this.store.get(); const recent=list.filter(i=>i.id!==this.lastItem.id).sort((a,b)=>new Date(b.date)-new Date(a.date)).slice(0,5); const historyText=recent.map(i=>'Q1:'+i.q1+' Q2:'+i.q2+' Q3:'+i.q3+' Q4:'+i.q4+(i.new_seed?' 播种:'+i.new_seed:'')).join('\n'); const prompt='你是正念教练。从以下近期记录中,总结一条该用户可能携带的旧模式/习惯性反应(一句话,不超过30字,不加评判,用「我注意到你」开头)。记录:\n'+historyText+'\n今天的记录:Q1:'+this.lastItem.q1+' Q2:'+this.lastItem.q2; const body=JSON.stringify({model:'qwen-turbo',messages:[{role:'system',content:'你是一位正念教练,用一句话总结用户的旧模式,不评判,用「我注意到你」开头。'},{role:'user',content:prompt}]}); const ac=new AbortController();setTimeout(()=>ac.abort(),6000); const res=await fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',{signal:ac.signal,method:'POST',headers:{'content-type':'application/json','Authorization':'Bearer '+window.DASHSCOPE_KEY},body}); if(res&&res.ok){const d=await res.json();if(d&&d.choices&&d.choices[0]&&d.choices[0].message){const pattern=d.choices[0].message.content.trim();if(pattern){box.innerHTML='🌰 旧模式觉知
'+pattern+'
';this.lastItem.old_pattern=pattern;this.store.get().then(list=>{const idx=list.findIndex(i=>i.id===this.lastItem.id);if(idx!==-1){list[idx].old_pattern=pattern;this.store.save(list);}});return;}}} }catch(e){} } // Fallback: use recent patterns box.innerHTML='🌰 旧模式觉知
请留意你今天的回答中反复出现的模式。觉察它,就是改变的开始。
'; }, _gardenView: 'timeline', _gardenFilter: 'all', switchGardenView: function(view) { this._gardenView = view; const tabs=document.querySelectorAll('.garden-tab'); tabs.forEach(t=>t.classList.toggle('active',t.getAttribute('onclick').includes(view))); if(view==='garden') { this.renderSeedGarden(); } else { this.renderHistory(document.getElementById('search-input')?.value); } }, _seedCategory: function(text) { if(!text) return ''; var stop=['的','了','在','是','我','不','也','就','把','被','让','有','和','与','为','对','从','上','下','中']; var w=text.replace(/[^\u4e00-\u9fa5\w\-]/g,' ').split(/\s+/).filter(Boolean); for(var i=0;i🌱
还没有播下种子
完成照见后,在洞察下方播下一颗种子吧
'; box.innerHTML='
'+msg+'
'; return; } // Calculate seed levels by category var cats={}; seeded.forEach(function(i){if(i.new_seed){var c=App._seedCategory(i.new_seed);if(c){cats[c]=(cats[c]||0)+1;}}}); // Filter bar box.innerHTML='
🌱 全部 '+seeded.length+'🌱 新种子🌰 旧模式
'; // Stats line var catEntries=Object.entries(cats).sort(function(a,b){return b[1]-a[1];}); if(catEntries.length){ box.insertAdjacentHTML('beforeend','
🌱 '+catEntries.length+' 种种子类型 · 最多:「'+catEntries[0][0]+'」 ('+catEntries[0][1]+'次)
'); } // Render items seeded.forEach(function(item){ var date=App.formatDate(item.date); var icon, levelText; if(item.new_seed){ var cat=App._seedCategory(item.new_seed); var cnt=cats[cat]||1; if(cnt>=4){ icon='🌸'; levelText='🌸 开花 · 种了 '+cnt+'次'; } else if(cnt>=2){ icon='🌿'; levelText='🌿 发芽 · 种了 '+cnt+'次'; } else { icon='🌱'; levelText='🌱 种子 · 第1次'; } } else { icon='🌰'; levelText='旧模式'; } var seedText=item.new_seed||'旧模式觉察'; var oldPattern=item.old_pattern?'
🌰 '+item.old_pattern+'
':''; var themeIcon={work:'💼',relation:'❤️',body:'🧘',general:'🌿'}[item.theme]||'🌿'; box.insertAdjacentHTML('beforeend', '
' +'
'+icon+'
' +'
' +'
'+seedText+'
' +'
'+levelText+'
' +'
'+themeIcon+' '+date+'
' +oldPattern +'
' +'
'); }); }, store: { db:null, init:function(){if(!window.indexedDB)return;const req=indexedDB.open('whoami_db',1);req.onupgradeneeded=e=>e.target.result.createObjectStore('entries',{keyPath:'id'});req.onsuccess=e=>this.db=e.target.result;}, async get(){if(this.db)return new Promise(r=>{const tx=this.db.transaction('entries','readonly');tx.objectStore('entries').getAll().onsuccess=e=>r(e.target.result);}); return this._getLocal();}, _getLocal(){try{return JSON.parse(localStorage.getItem('whoami_entries')||'[]');}catch(e){return[];}}, async save(entries){if(this.db){const tx=this.db.transaction('entries','readwrite');tx.objectStore('entries').clear();entries.forEach(e=>tx.objectStore('entries').put(e));} localStorage.setItem('whoami_entries',JSON.stringify(entries));}, async add(entry){const list=await this.get();list.unshift(entry);await this.save(list);return list;}, remove:async function(id){const ok=await App.showConfirm('确定删除这条记录吗?');if(!ok)return;const list=await this.get();await this.save(list.filter(i=>i.id!==id));App.renderHistory();App.renderProfile();App._syncResonanceDelete(id);}, clear:async function(){const code=(''+Math.random()).slice(2,6);const input=await App.showPrompt('⚠️ 清空前请输入验证码:'+code);if(input!==code){App.showToast('验证码错误,已取消。');return;}if(this.db){const tx=this.db.transaction('entries','readwrite');tx.objectStore('entries').clear();}localStorage.removeItem('whoami_entries');App.showToast('已清空');App.renderHistory();App.renderProfile();}, importData:function(file){const reader=new FileReader();reader.onload=e=>{try{const data=JSON.parse(e.target.result);if(Array.isArray(data)){this.save(data);App.showToast('已导入 '+data.length+' 条记录');setTimeout(()=>window.location.reload(),1000);}else{App.showToast('格式不正确。');}}catch(err){App.showToast('解析失败。');}};reader.readAsText(file);} }, batchDelete: async function() { const cbs=document.querySelectorAll('.del-cb:checked');const ids=[];cbs.forEach(cb=>ids.push(Number(cb.dataset.id))); if(!ids.length){this.showToast('请先选择要删除的记录');return;} if(!(await App.showConfirm('确定删除选中的 '+ids.length+' 条记录吗?'))) return; const list=await this.store.get();await this.store.save(list.filter(i=>!ids.includes(i.id))); ids.forEach(id=>App._syncResonanceDelete(id)); this.renderHistory();this.renderProfile();this.showToast('✅ 已删除 '+ids.length+' 条'); }, _syncResonanceDelete: function(recordId) { const saved=localStorage.getItem('whoami_resonance');if(!saved)return; const data=JSON.parse(saved);const before=data.length; const filtered=data.filter(i=>i.recordId!==recordId); if(filtered.lengthcb.checked=checked);}; }, formatDate: function(iso){const d=new Date(iso);return `${d.getMonth()+1}月${d.getDate()}日`;}, renderHistory: async function(keyword){const list=await this.store.get(),box=document.getElementById('timeline-list');box.innerHTML='';let filtered=keyword?list.filter(i=>i.q1.toLowerCase().includes(keyword)||i.q2.toLowerCase().includes(keyword)):list;filtered.sort((a,b)=>new Date(b.date)-new Date(a.date));if(!filtered.length){box.innerHTML='
🔍 没有找到记录
换个关键词试试,或者去照见完成新记录
';return;}filtered.forEach(item=>{const themeIcon={work:'💼',relation:'❤️',body:'🧘',general:'🌿'}[item.theme]||'🌿';const seedBadge=item.new_seed?` 🌱 ${item.new_seed}`:'';box.insertAdjacentHTML('beforeend',`
${themeIcon} ${this.formatDate(item.date)}×
${item.q1}
${item.q2}
状态:${item.q4}${seedBadge}
`);});}, renderProfile: async function(){this.bindSelectAll();const list=await this.store.get();document.getElementById('stat-total').textContent=list.length;const aware=list.filter(i=>i.q4==='觉知').length,ratio=list.length?Math.round(aware/list.length*100):0;document.getElementById('stat-awared').textContent=ratio+'%';if(list.length>0){const c=list.filter(i=>i.q3==='一致').length;document.getElementById('bar-consistent').style.width=(c/list.length*100)+'%';document.getElementById('bar-other').style.width=(100-c/list.length*100)+'%';document.getElementById('bar-awareness').style.width=ratio+'%';const cc=document.getElementById('chart-container');cc.innerHTML='';const now=new Date();for(let i=6;i>=0;i--){const d=new Date(now);d.setDate(d.getDate()-i);const de=list.filter(e=>new Date(e.date).toDateString()===d.toDateString());let cls='low',h='10%';if(de.length>0){const ac=de.filter(e=>e.q4==='觉知').length;if(ac===de.length){cls='active';h='100%';}else if(ac>0){cls='mixed';h='50%';}}const bar=document.createElement('div');bar.className='chart-bar '+cls;bar.style.height=h;cc.appendChild(bar);}this.renderInsights(list);App.renderSeedStats();}}, renderInsights: function(list){ if(!list||!list.length){document.getElementById('deep-insights').style.display='none';return;} const counts={q1:0,q2:0,q3:0,q4:0};const themes={};const q3Counts={一致:0,矛盾:0}; list.forEach(i=>{ counts[i.q4]=(counts[i.q4]||0)+1; themes[i.theme]=(themes[i.theme]||0)+1; q3Counts[i.q3]=(q3Counts[i.q3]||0)+1; }); const aware=counts['觉知']||0; const ratio=Math.round(aware/list.length*100); const segments=`觉知 ${ratio}% · 非觉知 ${100-ratio}%`; const topTheme=Object.entries(themes).sort((a,b)=>b[1]-a[1])[0]; const themeLabel={work:'工作/事业',relation:'关系/情感',body:'身体/健康',general:'综合觉察'}[topTheme[0]]||topTheme[0]; // Generate insight lines based on patterns const lines=[]; lines.push(`
${segments}
`); lines.push(`
📌 高频主题:${themeLabel}(${topTheme[1]} 次)
`); // Q4 pattern const nian=counts['念头']||0,gan=counts['感受']||0; if(nian>gan*1.5) lines.push(`
💭 你更多在头脑层面回应(念头 ${Math.round(nian/list.length*100)}%),可以多让身体参与进来
`); else if(gan>nian*1.5) lines.push(`
❤️ 你对感受很敏锐,这是很好的觉察起点
`); // Consistency pattern const conRate=Math.round((q3Counts['一致']||0)/list.length*100); if(conRate>70) lines.push(`
🔑 你的自我认知较一致(${conRate}%),一致性是你的力量
`); else if(conRate<40) lines.push(`
🔑 你经常觉察到「矛盾」——这正是松动认知僵化的入口
`); // Theme-specific insight const insights={ work:['你在工作中的觉察,往往指向对效率与意义的追问。效率是手段,意义才是终点。','你记录工作最频繁的时候,往往是你最需要被看见的时候。','工作中的矛盾感,可能不是你不够好,而是标准太完美。'], relation:['你在关系中的觉察,最真实也最脆弱。关系是一面镜子——你看到的对方,往往是你自己的一部分。','你对关系的敏感,说明你在乎。在乎本身就是一种力量。','关系中的矛盾,常常是边界模糊的信号。看见边界,就看见了自由。'], body:['身体的觉察是最诚实的。头脑会骗人,身体不会。','你开始关照身体,这是从「向外求」转向「向内安」的标志。','身体的疲惫,有时是心在喊停。听见它。'], general:['综合觉察是最基础的功夫。每天看自己一眼,就是最大的善意。','你对自己的觉察越细,你对世界的理解就越深。','每一次「照见」,都是一次重新认识自己的机会。'] }; const themeInsights=insights[topTheme[0]]||insights.general; lines.push(`
💡 ${themeLabel}洞察
${themeInsights[Math.floor(Math.random()*themeInsights.length)]}
`); document.getElementById('insight-pattern').innerHTML=lines.join(''); document.getElementById('deep-insights').style.display='block'; }, renderSeedStats: async function() { var sec=document.getElementById('seed-stats-section'),box=document.getElementById('seed-stats');if(!sec||!box)return; var list=await this.store.get(); var seeded=list.filter(function(i){return i.new_seed;}).sort(function(a,b){return new Date(b.date)-new Date(a.date);}); if(!seeded.length){sec.style.display='none';return;} sec.style.display='block'; var cats={};var totalSeeds=seeded.length;var thisMonth=0;var now=new Date(); seeded.forEach(function(i){ var d=new Date(i.date);if(d.getMonth()===now.getMonth()&&d.getFullYear()===now.getFullYear())thisMonth++; if(i.new_seed){var c=App._seedCategory(i.new_seed);if(c){cats[c]=(cats[c]||0)+1;}} }); var catEntries=Object.entries(cats).sort(function(a,b){return b[1]-a[1];}); var top=catEntries[0]; var lines=[]; lines.push('🌱 共播种 '+totalSeeds+'颗种子,'+Object.keys(cats).length+'种类型'); if(thisMonth>0)lines.push('📅 本月播种 '+thisMonth+'颗'); if(top)lines.push('🌸 最多:「'+top[0]+'」 ('+top[1]+'次)'); var tags='';catEntries.slice(0,5).forEach(function(e,i){var icons=['🌱','🌿','🌸','🌼','🍂'];tags+=''+(icons[i]||'🌱')+e[0]+' '+e[1]+'';}); box.innerHTML='
'+lines.join('
')+'
'+'
'+tags+'
'; }, generateWeeklyReport: async function(){const list=await this.store.get();const now=new Date(),weekAgo=new Date(now);weekAgo.setDate(now.getDate()-7);const weekData=list.filter(i=>new Date(i.date)>=weekAgo);if(weekData.length<1){this.showToast('📊 本周记录不足');return;}this.showToast('📊 正在生成...');setTimeout(()=>{const canvas=document.createElement('canvas'),ctx=canvas.getContext('2d');const w=750,h=1400;canvas.width=w;canvas.height=h;ctx.fillStyle=document.body.classList.contains('dark')?'#1A1A1A':'#FFFFFF';ctx.fillRect(0,0,w,h);ctx.fillStyle='#C9A96E';ctx.fillRect(0,0,16,h);ctx.fillStyle=document.body.classList.contains('dark')?'#EAEAEA':'#1A1A1A';ctx.font='bold 64px serif';ctx.textAlign='center';ctx.fillText('觉察周报',395,120);ctx.fillStyle='#999';ctx.font='24px sans-serif';ctx.fillText(`${weekAgo.getMonth()+1}.${weekAgo.getDate()} — ${now.getMonth()+1}.${now.getDate()}`,395,170);const total=weekData.length,aware=weekData.filter(i=>i.q4==='觉知').length,awareRatio=total>0?Math.round(aware/total*100):0;const cx=375,cy=350,r=110;ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.fillStyle=document.body.classList.contains('dark')?'#333':'#F0F0F0';ctx.fill();ctx.beginPath();ctx.moveTo(cx,cy);ctx.arc(cx,cy,r,-Math.PI/2,(-Math.PI/2)+(Math.PI*2*awareRatio/100));ctx.fillStyle='#C9A96E';ctx.fill();ctx.fillStyle=document.body.classList.contains('dark')?'#EAEAEA':'#1A1A1A';ctx.font='bold 72px sans-serif';ctx.textAlign='center';ctx.fillText(awareRatio+'%',cx,cy+20);ctx.fillStyle='#999';ctx.font='24px sans-serif';ctx.fillText('觉知占比',cx,cy+60);const quote=this.quotes[Math.floor(Math.random()*this.quotes.length)];ctx.fillStyle='#999';ctx.font='italic 30px serif';ctx.textAlign='center';ctx.fillText('"'+quote.text+'"',375,1100);ctx.fillStyle='#666';ctx.font='24px sans-serif';ctx.fillText('—— '+quote.author,375,1150);ctx.fillStyle='#C9A96E';ctx.font='bold 22px sans-serif';ctx.fillText('Whoami · V2.3.10',375,1250);document.getElementById('share-img').src=canvas.toDataURL('image/png');document.getElementById('share-modal').classList.add('active');},200);}, generateShareImage: function(){if(!this.lastItem)return;this.showToast('生成中...');var self=this;setTimeout(function(){ var c=document.createElement('canvas'),x=c.getContext('2d');c.width=750;c.height=1300; // 暖色渐变背景 var grad=x.createLinearGradient(0,0,0,1300); grad.addColorStop(0,'#FDF8F0');grad.addColorStop(0.5,'#F7F0E3');grad.addColorStop(1,'#EDE5D5'); x.fillStyle=grad;x.fillRect(0,0,750,1300); // 顶部装饰细线 x.strokeStyle='#D4C4A8';x.lineWidth=1;x.beginPath();x.moveTo(100,70);x.lineTo(650,70);x.stroke(); x.fillStyle='#C9A96E';x.beginPath();x.arc(375,70,3,0,Math.PI*2);x.fill(); // 标题 - 居中 x.textAlign='center'; x.fillStyle='#1A1A1A';x.font='bold 32px system-ui,-apple-system,sans-serif'; x.fillText('「 我 」我是谁',375,55); // 日期 var d=new Date(self.lastItem.date); var dateStr=d.getFullYear()+'.'+(d.getMonth()+1)+'.'+d.getDate(); x.fillStyle='#999';x.font='12px system-ui,-apple-system,sans-serif'; x.fillText(dateStr,375,85); // 主题标签 var themeLabels=['💼 工作/事业','❤️ 关系/情感','🧘 身体/健康','🌿 综合觉察']; var themeIdx={work:0,relation:1,body:2,general:3}[self.lastItem.theme]||3; var tag=themeLabels[themeIdx]; var tagW=x.measureText(tag).width+40; x.fillStyle='#F5F0E5';x.beginPath();x.roundRect(375-tagW/2,100,tagW,26,13);x.fill(); x.fillStyle='#888';x.font='12px system-ui,-apple-system,sans-serif';x.textAlign='center'; x.fillText(tag,375,118); // 内容 var x0=75,y=165; var items=[['今天最重要的事',self.lastItem.q1],['这说明我是怎样的人',self.lastItem.q2],['是否与自我认知一致',self.lastItem.q3],['此刻是谁在回答',self.lastItem.q4]]; for(var i=0;i570){x.font='18px system-ui,-apple-system,sans-serif';var ln='';for(var j=0;j570){x.fillText(ln,x0,y);y+=26;ln=val[j];}else ln=nxt;}if(ln)x.fillText(ln,x0,y);y+=16;} else{x.fillText(val,x0,y);y+=28;} if(i<3)y+=14;} // 分隔线 y+=4;x.strokeStyle='#D4C4A8';x.lineWidth=1;x.beginPath();x.moveTo(100,y);x.lineTo(650,y);x.stroke(); x.fillStyle='#C9A96E';x.beginPath();x.arc(375,y,3,0,Math.PI*2);x.fill(); // 今日洞察 y+=26;var insightEl=document.getElementById('insight-text');var insightTxt=insightEl?insightEl.textContent:''; if(insightTxt){ x.textAlign='left';x.fillStyle='#A89880';x.font='12px system-ui,-apple-system,sans-serif';x.fillText('今日洞察',x0,y);y+=22; x.fillStyle='#4A4030';x.font='15px system-ui,-apple-system,sans-serif'; var ds=insightTxt.split('\n').filter(Boolean); for(var k=0;k570){x.font='14px system-ui,-apple-system,sans-serif';var ln='';for(var j=0;j570){x.fillText(ln,x0,y);y+=24;ln=t[j];}else ln=nxt;}if(ln)x.fillText(ln,x0,y);y+=18;} else{x.fillText(t,x0,y);y+=24;}} y+=10;} // 分隔线 x.strokeStyle='#D4C4A8';x.lineWidth=1;x.beginPath();x.moveTo(100,y);x.lineTo(650,y);x.stroke(); x.fillStyle='#C9A96E';x.beginPath();x.arc(375,y,3,0,Math.PI*2);x.fill(); // 金句 - 居中 y+=30;x.textAlign='center'; var quote=self.quotes[Math.floor(Math.random()*self.quotes.length)]; var qt='「'+quote.text+'」'; x.fillStyle='#5A5040';x.font='italic 18px serif'; if(x.measureText(qt).width>580){x.font='16px serif'; var words=qt.split('');var line='';var lines=[]; for(var j=0;j580){lines.push(line);line=words[j];}else line=test;} if(line)lines.push(line); for(var li=0;li{const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([JSON.stringify(d,null,2)]));a.download='whoami_backup_'+new Date().toISOString().slice(0,10)+'.json';a.click();this.showToast('📤 备份已生成');});}, toggleReminder: function() { const enabled=localStorage.getItem('whoami_reminder_enabled')==='true',btn=document.getElementById('btn-toggle-reminder'),status=document.getElementById('reminder-status'),time=document.getElementById('reminder-time').value||'21:00'; if(enabled){localStorage.setItem('whoami_reminder_enabled','false');btn.textContent='开启提醒';btn.style.background='';btn.style.color='';if(this.reminderInterval)clearInterval(this.reminderInterval);status.textContent='';this.showToast('⏰ 提醒已关闭');}else{if(Notification&&Notification.permission==='default')Notification.requestPermission();localStorage.setItem('whoami_reminder_enabled','true');localStorage.setItem('whoami_reminder_time',time);btn.textContent='关闭提醒';btn.style.background='var(--red)';btn.style.color='#fff';status.textContent='✅ 已开启 (每日 '+time+')';this.startReminderCheck();this.showToast('⏰ 提醒已开启');} }, saveReminderTime: function() { const time=document.getElementById('reminder-time').value||'21:00';localStorage.setItem('whoami_reminder_time',time);const enabled=localStorage.getItem('whoami_reminder_enabled')==='true';if(enabled)document.getElementById('reminder-status').textContent='✅ 已开启 (每日 '+time+')';else{document.getElementById('reminder-status').textContent='⏰ 已保存: '+time;setTimeout(()=>{const s=document.getElementById('reminder-status');if(s&&s.textContent.includes('已保存'))s.textContent='';},2000);}this.showToast('⏰ 提醒时间已保存'); }, initReminder: function() { if(localStorage.getItem('whoami_reminder_enabled')==='true'){const t=localStorage.getItem('whoami_reminder_time')||'21:00';document.getElementById('reminder-time').value=t;document.getElementById('btn-toggle-reminder').textContent='关闭提醒';document.getElementById('reminder-status').textContent='✅ 已开启 (每日 '+t+')';this.startReminderCheck();} }, startReminderCheck: function() { if(this.reminderInterval)clearInterval(this.reminderInterval);this.reminderInterval=setInterval(()=>this.checkReminder(),30000);this.checkReminder(); }, checkReminder: function() { if(localStorage.getItem('whoami_reminder_enabled')!=='true')return;const ts=document.getElementById('reminder-time').value||'21:00',now=new Date(),[h,m]=ts.split(':').map(Number);const rt=new Date(now.getFullYear(),now.getMonth(),now.getDate(),h,m),diff=now-rt;if(diff>=0&&diff<60000){const today=now.toDateString();if(localStorage.getItem('whoami_reminder_last')===today)return;localStorage.setItem('whoami_reminder_last',today);this.triggerReminder();} }, triggerReminder: function() { if(Notification&&Notification.permission==='granted')new Notification('「 我 」我是谁',{body:'今天最重要的事是什么?来照见此刻吧。'});this.showToast('🌙 提醒:今天最重要的事是什么?'); }, initVoice: function() { const SR=window.SpeechRecognition||window.webkitSpeechRecognition; if(SR){this.recognition=new SR();this.recognition.lang='zh-CN';this.recognition.continuous=false;this.recognition.interimResults=false;this.recognition.onresult=event=>{const r=event.results[0][0].transcript;if(this.targetFieldId){document.getElementById(this.targetFieldId).value+=r;this.showToast('🎤 识别成功');}this.isRecording=false;this.updateMicUI();};this.recognition.onerror=()=>{this.showToast('🎤 识别失败');this.isRecording=false;this.updateMicUI();};this.recognition.onend=()=>{this.isRecording=false;this.updateMicUI();};}else{document.querySelectorAll('.mic-btn').forEach(b=>b.style.display='none');} }, startVoice: function(id) { if(this.isRecording){this.recognition.stop();this.isRecording=false;}else{this.targetFieldId=id;this.recognition.start();this.isRecording=true;this.showToast('🎤 正在聆听...');}this.updateMicUI(); }, updateMicUI: function() { const btn=this.targetFieldId?document.querySelector(`button[onclick="App.startVoice('${this.targetFieldId}')"]`):null;if(btn){if(this.isRecording)btn.classList.add('recording');else btn.classList.remove('recording');} }, loadResonance: async function() { const def=[{id:1,text:"今天最重要的事是陪孩子搭积木。这说明我是个愿意慢下来的人。",q3:"一致",time:"2小时前",resonances:12,userResonated:false},{id:2,text:"最重要的事是完成那份报告。说明我是个有责任感的人。",q3:"矛盾",time:"5小时前",resonances:8,userResonated:false},{id:3,text:"今天什么都没做,只是好好呼吸了一次。说明我还活着。",q3:"一致",time:"昨天",resonances:23,userResonated:false}]; // Initialize with local data first var saved=localStorage.getItem('whoami_resonance');this.resonanceData=saved?JSON.parse(saved):def;if(this.resonanceData){var seen={};this.resonanceData=this.resonanceData.filter(function(i){var key=i.recordId||i.text+i.q3;if(seen[key])return false;seen[key]=1;return true;});}this.renderResonance(); // Then try API to update - dedup by recordId if(this.cloudWorkerUrl){try{const res=await this.fetchWithTimeout(this.cloudWorkerUrl+'/resonance',{},8000);if(res&&res.ok){const data=await res.json();if(data&&data.items&&data.items.length){const existing=this.resonanceData||[];const seen={};existing.forEach(i=>{if(i.recordId)seen[i.recordId]=1;});const merged=data.items.slice();existing.forEach(i=>{if(i.recordId&&!seen[i.recordId])merged.push(i);});this.resonanceData=merged;localStorage.setItem('whoami_resonance',JSON.stringify(merged));this.renderResonance();return;}}}catch(e){/* fallback to local */}}}, _timeAgo: function(iso) { if(!iso) return '刚刚'; var now=Date.now(),then=new Date(iso).getTime(),diff=now-then; if(diff<60000) return '刚刚'; if(diff<3600000) return Math.floor(diff/60000)+'分钟前'; if(diff<86400000) return Math.floor(diff/3600000)+'小时前'; return Math.floor(diff/86400000)+'天前'; }, _resonanceFilter: 'all', switchResonanceFilter: function(filter) { this._resonanceFilter = filter; var tabs=document.querySelectorAll('#page-resonance .garden-tab'); tabs.forEach(function(t){t.classList.toggle('active',t.getAttribute('onclick').includes(filter));}); this.renderResonance(); }, renderResonance: function() { var box=document.getElementById('resonance-list');box.innerHTML='';var data=this.resonanceData||[];var filtered=this._resonanceFilter==='seed'?data.filter(function(i){return i.seed;}):data;if(!filtered.length){var emptyMsg=this._resonanceFilter==='seed'?'
🌱
还没有种子集
发布觉察时,分享你今天种下的种子
':'
💫 暂无共鸣
完成今日觉察,匿名发布到共鸣广场
';box.innerHTML=emptyMsg;return;}filtered.forEach(function(item){var seedTag=item.seed?'
🌱 大家一起来种下「'+item.seed+'」的种子
':'';var waterBtn=item.seed?'
💧 '+(item.waters||0)+'
':'';box.insertAdjacentHTML('beforeend','
💫 匿名觉察 · '+(item.time||App._timeAgo(item.created_at))+'
"'+item.text+'"
'+seedTag+'
状态:'+item.q3+'
🙏 同感 · '+(item.resonances||0)+'
'+waterBtn+'
');});box.insertAdjacentHTML('afterbegin','
📮 匿名发布我的觉察
');}, waterSeed: function(id) { var item=this.resonanceData.find(function(i){return i.id===id;});if(!item||!item.seed)return;item.userWatered=!item.userWatered;item.waters=(item.waters||0)+(item.userWatered?1:-1);if(item.waters<0)item.waters=0;localStorage.setItem('whoami_resonance',JSON.stringify(this.resonanceData));this.renderResonance(); }, toggleResonance: function(id) { const item=this.resonanceData.find(i=>i.id===id);if(!item)return;item.userResonated=!item.userResonated;item.resonances=(item.resonances||0)+(item.userResonated?1:-1);if(item.resonances<0)item.resonances=0;localStorage.setItem('whoami_resonance',JSON.stringify(this.resonanceData));this.renderResonance(); }, publishResonance: async function() { if(!this.lastItem){this.showToast('请先完成今日记录');return;}if(!(await App.showConfirm('💫 匿名发布觉察?')))return;var rid=this.lastItem.id;if((this.resonanceData||[]).some(function(i){return i.recordId===rid;})){this.showToast('⚠️ 这条记录已发布过');return;}var text='📍 '+this.lastItem.q1+' · 这说明我是:'+this.lastItem.q2;var seed=this.lastItem.new_seed||'';var payload={recordId:rid,text:text,q3:this.lastItem.q3};if(seed)payload.seed=seed;if(this.cloudWorkerUrl){this.fetchWithTimeout(this.cloudWorkerUrl+'/resonance',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(payload)},5000).then(function(res){if(res&&res.ok)App.showToast('✅ 已发布到共鸣广场');else App.showToast('⚠️ 云端发布失败,已保存本地');}).catch(function(){App.showToast('⚠️ 网络异常,已保存本地');});}var item={id:Date.now(),recordId:rid,text:text,q3:this.lastItem.q3,time:'刚刚',resonances:0,userResonated:false};if(seed)item.seed=seed;this.resonanceData.unshift(item);localStorage.setItem('whoami_resonance',JSON.stringify(this.resonanceData));this.renderResonance();if(!this.cloudWorkerUrl)this.showToast('已匿名发布');}, checkInStreak: async function() { const today=new Date().toDateString();let streak=JSON.parse(localStorage.getItem('whoami_streak')||'{"count":0,"lastDate":""}');const yesterday=new Date();yesterday.setDate(yesterday.getDate()-1);if(streak.lastDate===today)return;if(streak.lastDate===yesterday.toDateString()){streak.count++;}else{streak.count=1;}streak.lastDate=today;localStorage.setItem('whoami_streak',JSON.stringify(streak));await this.updateStreak(); }, updateStreak: async function() { const streak=JSON.parse(localStorage.getItem('whoami_streak')||'{"count":0,"lastDate":""}');const el=document.getElementById('streak-display');if(!el)return;const today=new Date().toDateString(),yesterday=new Date();yesterday.setDate(yesterday.getDate()-1);let dc=streak.count;if(streak.lastDate!==today&&streak.lastDate!==yesterday.toDateString())dc=0;if(dc>0){const total=await App.store.get();const tc=total.length;el.innerHTML=`
🔥
${dc} 天
连续觉察中,保持!
累计 ${tc} 次
`}else el.innerHTML=''; }, showConfirm: function(text, onConfirm) { return new Promise(resolve => { const el = document.getElementById('confirm-overlay'); document.getElementById('confirm-text').textContent = text; document.getElementById('confirm-ok').onclick = () => { el.style.display = 'none'; resolve(true); }; document.getElementById('confirm-cancel').onclick = () => { el.style.display = 'none'; resolve(false); }; el.style.display = 'flex'; }); }, }; App.breathExerciseAllowed = true; App.init(); // breath exercise patch if (window.App && !window.App.toggleBreathExercise) { window.App.toggleBreathExercise = function(suffix) { suffix = suffix || ''; if (this['_breathe' + suffix]) this.stopBreathExercise(suffix); else this.startBreathExercise(suffix); }; window.App.startBreathExercise = function(suffix) { suffix = suffix || ''; if (!this.breathExerciseAllowed) return; this.isBreathing = true; this.breathExerciseAllowed = false; this['_breathe' + suffix] = true; const btn = document.getElementById('breath-start-btn' + suffix); if (btn) { btn.textContent = '停止练习'; btn.classList.add('stop'); } this.runBreathCycle(0, suffix); }; window.App.stopBreathExercise = function(suffix) { suffix = suffix || ''; this.isBreathing = false; // Clear all timers if (this.breathInterval) { clearTimeout(this.breathInterval); this.breathInterval = null; } // Also clear any running interval timers inside runBreathCycle const phase = document.getElementById('breath-phase' + suffix); const timer = document.getElementById('breath-timer' + suffix); const ring = document.getElementById('breath-ring' + suffix); const container = ring ? ring.closest('.breath-circle-container') : null; // Reset to initial state if (container) container.classList.remove('hold'); if (ring) { ring.style.transition = 'none'; ring.style.strokeDashoffset = '502'; ring.classList.remove('ring-inhale','ring-hold','ring-exhale'); // Force reflow void ring.offsetWidth; } if (phase) { phase.textContent = '准备好了吗?'; phase.style.color = ''; } if (timer) { timer.textContent = (this.breathMode === 'awareness') ? '4-4-6' : '4-7-8'; timer.style.color = ''; } const btn = document.getElementById('breath-start-btn' + suffix); if (btn) { btn.textContent = '开始练习'; btn.classList.remove('stop'); } this['_breathe' + suffix] = false; // Clear any stray intervals from the breath cycle if (this._breathIntervals) { this._breathIntervals.forEach(id=>clearInterval(id)); this._breathIntervals=[]; } setTimeout(() => { this.breathExerciseAllowed = true; }, 0); }; window.App.runBreathCycle = function(cycle, suffix) { suffix = suffix || ''; if (!this.isBreathing || cycle >= 4) { this.stopBreathExercise(suffix); this.showToast('练习完成'); this.trackBreathCompletion(); return; } const phase = document.getElementById('breath-phase' + suffix); const timer = document.getElementById('breath-timer' + suffix); const ring = document.getElementById('breath-ring' + suffix); if (!phase || !timer || !ring) return; const container = document.querySelector('#breath-ring' + suffix).closest('.breath-circle-container'); const mode = this.breathMode || '478'; const isAware = mode === 'awareness'; const inhaleDur = isAware ? 4 : 4; const holdDur = isAware ? 4 : 7; const exhaleDur = isAware ? 6 : 8; const inhaleText = isAware ? '杯子是空的' : '吸气...'; const holdText = isAware ? '水在流动' : '屏息...'; const exhaleText = isAware ? '我是空杯' : '呼气...'; phase.textContent = inhaleText; phase.style.color = '#5BC0EB'; if (container) container.classList.remove('hold'); ring.style.transition = 'none'; ring.style.strokeDashoffset = '502'; ring.classList.remove('ring-inhale','ring-hold','ring-exhale'); ring.classList.add('ring-inhale'); void ring.offsetWidth; ring.style.transition = 'stroke-dashoffset ' + inhaleDur + 's linear'; ring.style.strokeDashoffset = '0'; let count = inhaleDur; timer.textContent = count; const ii = setInterval(() => { count--; if (count <= 0) clearInterval(ii); timer.textContent = count; }, 1000); if (!this._breathIntervals) this._breathIntervals = []; this._breathIntervals.push(ii); this.breathInterval = setTimeout(() => { clearInterval(ii); if (!this.isBreathing) return; phase.textContent = holdText; phase.style.color = 'var(--gold)'; if (container) container.classList.add('hold'); ring.classList.remove('ring-inhale','ring-hold','ring-exhale'); ring.classList.add('ring-hold'); count = holdDur; timer.textContent = count; const hi = setInterval(() => { count--; if (count <= 0) clearInterval(hi); timer.textContent = count; }, 1000); this.breathInterval = setTimeout(() => { clearInterval(hi); if (!this.isBreathing) return; if (container) container.classList.remove('hold'); phase.textContent = exhaleText; phase.style.color = '#56C596'; ring.classList.remove('ring-inhale','ring-hold','ring-exhale'); ring.classList.add('ring-exhale'); ring.style.transition = 'none'; ring.style.strokeDashoffset = '0'; void ring.offsetWidth; ring.style.transition = 'stroke-dashoffset ' + exhaleDur + 's linear'; ring.style.strokeDashoffset = '502'; count = exhaleDur; timer.textContent = count; const ei = setInterval(() => { count--; if (count <= 0) clearInterval(ei); timer.textContent = count; }, 1000); this.breathInterval = setTimeout(() => { clearInterval(ei); if (this.isBreathing) this.runBreathCycle(cycle + 1, suffix); }, exhaleDur * 1000); }, holdDur * 1000); }, inhaleDur * 1000); }; window.App.trackBreathCompletion = function() { const key = 'whoami_breath_count'; const count = parseInt(localStorage.getItem(key) || '0') + 1; localStorage.setItem(key, String(count)); }; window.App.renderBreathStats = function() { const el = document.getElementById('breath-stats'); if (!el) return; const count = parseInt(localStorage.getItem('whoami_breath_count') || '0'); el.innerHTML = count > 0 ? '🧘 已完成 ' + count + ' 次呼吸练习' : '开始你的第一次呼吸练习吧'; }; } document.getElementById('search-input').addEventListener('input',e=>App.renderHistory(e.target.value)); document.getElementById('btn-share').addEventListener('click',()=>App.generateShareImage());