diff --git a/static/app.js b/static/app.js index 0b2d2ee..b69a4f2 100644 --- a/static/app.js +++ b/static/app.js @@ -1,98 +1,124 @@ -const API_BASE = ''; +const API = ''; -// 状态 -let currentRecordId = null; -let lastRecordTime = 0; -let longPressTimer = null; +// ===== STATE ===== +let currentId = null; +let lastClick = 0; +let pressTimer = null; let isLongPress = false; -const LONG_PRESS_DURATION = 500; // 长按触发时间 +const DEBOUNCE = 200; // 200ms 防抖 +const LONG_PRESS = 600; // 600ms 长按 -// DOM 元素 -const triggerArea = document.getElementById('trigger-area'); +// ===== DOM ===== const dinBtn = document.getElementById('din-btn'); -const inputOverlay = document.getElementById('input-overlay'); -const quickInput = document.getElementById('quick-input'); +const floatInput = document.getElementById('float-input'); +const floatField = document.getElementById('float-field'); +const overlay = document.getElementById('overlay'); const toast = document.getElementById('toast'); -const achievementToast = document.getElementById('achievement-toast'); +const confirmOverlay = document.getElementById('confirm-overlay'); +const confirmOk = document.getElementById('confirm-ok'); +const confirmCancel = document.getElementById('confirm-cancel'); -// ========== 核心交互:超快记录 ========== - -// 点击/触摸开始 -dinBtn.addEventListener('touchstart', handleStart, { passive: false }); -dinBtn.addEventListener('mousedown', handleStart); - -// 点击/触摸结束 -dinBtn.addEventListener('touchend', handleEnd, { passive: false }); -dinBtn.addEventListener('mouseup', handleEnd); -dinBtn.addEventListener('mouseleave', cancelLongPress); - -// 防止双击缩放 -dinBtn.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false }); - -function handleStart(e) { - e.preventDefault(); - isLongPress = false; - - // 视觉反馈 - dinBtn.classList.add('recording'); - - // 启动长按计时器 - longPressTimer = setTimeout(() => { - isLongPress = true; - dinBtn.classList.remove('recording'); - // 长按直接进入输入模式 - createRecord(true); - }, LONG_PRESS_DURATION); +// ===== CUSTOM CONFIRM DIALOG ===== +function showConfirm(message, title = '确认删除', icon = '🗑️') { + return new Promise((resolve) => { + document.querySelector('.confirm-title').textContent = title; + document.querySelector('.confirm-message').textContent = message; + document.querySelector('.confirm-icon').textContent = icon; + + confirmOverlay.classList.add('active'); + + const onOk = () => { + confirmOverlay.classList.remove('active'); + cleanup(); + resolve(true); + }; + + const onCancel = () => { + confirmOverlay.classList.remove('active'); + cleanup(); + resolve(false); + }; + + const cleanup = () => { + confirmOk.removeEventListener('click', onOk); + confirmCancel.removeEventListener('click', onCancel); + confirmOverlay.removeEventListener('click', onOverlayClick); + }; + + const onOverlayClick = (e) => { + if (e.target === confirmOverlay) { + onCancel(); + } + }; + + confirmOk.addEventListener('click', onOk); + confirmCancel.addEventListener('click', onCancel); + confirmOverlay.addEventListener('click', onOverlayClick); + }); } -function handleEnd(e) { +// ===== BUTTON INTERACTION ===== + +dinBtn.addEventListener('touchstart', onPress, { passive: false }); +dinBtn.addEventListener('mousedown', onPress); +dinBtn.addEventListener('touchend', onRelease, { passive: false }); +dinBtn.addEventListener('mouseup', onRelease); +dinBtn.addEventListener('mouseleave', onCancel); + +function onPress(e) { e.preventDefault(); - clearTimeout(longPressTimer); + if (Date.now() - lastClick < DEBOUNCE) return; + + isLongPress = false; + dinBtn.classList.add('recording'); + + pressTimer = setTimeout(() => { + isLongPress = true; + dinBtn.classList.remove('recording'); + record(true); // 长按 = 记录 + 输入 + }, LONG_PRESS); +} + +function onRelease(e) { + e.preventDefault(); + clearTimeout(pressTimer); dinBtn.classList.remove('recording'); - // 如果不是长按,直接记录 if (!isLongPress) { - createRecord(false); + lastClick = Date.now(); + record(false); // 短按 = 仅记录 } } -function cancelLongPress() { - clearTimeout(longPressTimer); +function onCancel() { + clearTimeout(pressTimer); dinBtn.classList.remove('recording'); } -// ========== 记录逻辑 ========== +// ===== RECORD ===== -async function createRecord(showInput = false) { - // 防抖:1秒内不能重复点击 - const now = Date.now(); - if (now - lastRecordTime < 1000) return; - lastRecordTime = now; +async function record(openInput = false) { + showToast('已记录!'); try { - // 立即显示反馈(不等待网络) - showToast('已记录!'); - - // 后台发送请求 - const res = await fetch(`${API_BASE}/api/din`, { + const res = await fetch(`${API}/api/din`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: '' }) }); const data = await res.json(); - currentRecordId = data.id; + currentId = data.id; - // 更新统计和列表 - updateStats(); - prependToList(data); + // 并行更新 UI + Promise.all([ + refreshStats(), + addChip(data), + checkAchievements() + ]); - // 检查成就 - checkNewAchievements(); - - // 如果需要输入,显示输入面板 - if (showInput) { - showInputPanel(data.created_at); + if (openInput) { + openFloatInput(data.created_at, false); // false = 新建模式 } } catch (err) { @@ -101,319 +127,290 @@ async function createRecord(showInput = false) { } } -// ========== 输入面板 ========== +// ===== FLOAT INPUT ===== -function showInputPanel(timestamp) { - document.getElementById('input-timestamp').textContent = formatTimeOnly(timestamp); - inputOverlay.classList.add('active'); - quickInput.value = ''; - setTimeout(() => quickInput.focus(), 100); +function openFloatInput(timestamp, isEdit = false) { + document.getElementById('float-time').textContent = formatTime(timestamp); + document.querySelector('.float-title').textContent = isEdit ? '编辑记录' : '添加备注'; + floatField.value = ''; + floatInput.classList.add('active'); + overlay.classList.add('active'); + setTimeout(() => floatField.focus(), 50); } -function hideInputPanel() { - inputOverlay.classList.remove('active'); - quickInput.blur(); +function closeFloatInput() { + floatInput.classList.remove('active'); + overlay.classList.remove('active'); + floatField.blur(); } -// 保存按钮 +// 保存 document.getElementById('btn-save').addEventListener('click', async () => { - if (!currentRecordId) return; - - const content = quickInput.value.trim(); - if (!content) { - hideInputPanel(); + const content = floatField.value.trim(); + if (!content || !currentId) { + closeFloatInput(); return; } + await fetch(`${API}/api/din/${currentId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content }) + }); + + updateChip(currentId, content); + closeFloatInput(); + showToast('已保存'); +}); + +// 删除 +document.getElementById('btn-delete').addEventListener('click', async () => { + if (!currentId) { + console.log('No currentId to delete'); + closeFloatInput(); + return; + } + + const confirmed = await showConfirm('确定要删除这条记录吗?此操作不可恢复。'); + if (!confirmed) return; + try { - await fetch(`${API_BASE}/api/din/${currentRecordId}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content }) - }); + const res = await fetch(`${API}/api/din/${currentId}`, { method: 'DELETE' }); - // 更新列表中的内容 - updateListItem(currentRecordId, content); - hideInputPanel(); - showToast('已保存'); + if (!res.ok) { + throw new Error('Delete failed'); + } + // 移除 chip - 使用字符串比较 + const chip = document.querySelector(`.recent-chip[data-id="${currentId}"]`); + console.log('Looking for chip with id:', currentId, 'found:', chip); + if (chip) chip.remove(); + + await refreshStats(); + closeFloatInput(); + showToast('已删除'); + currentId = null; // 清除当前ID } catch (err) { - showToast('保存失败', false); + console.error('Delete error:', err); + showToast('删除失败', false); } }); -// 跳过按钮 -document.getElementById('btn-skip').addEventListener('click', () => { - hideInputPanel(); -}); +// 跳过 +document.getElementById('btn-skip').addEventListener('click', closeFloatInput); // 点击遮罩关闭 -inputOverlay.addEventListener('click', (e) => { - if (e.target === inputOverlay) { - hideInputPanel(); - } -}); +overlay.addEventListener('click', closeFloatInput); // 回车保存 -quickInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - document.getElementById('btn-save').click(); - } +floatField.addEventListener('keypress', e => { + if (e.key === 'Enter') document.getElementById('btn-save').click(); }); -// 快速标签 +// 快捷标签 document.querySelectorAll('.tag').forEach(tag => { tag.addEventListener('click', () => { - const text = tag.dataset.text; - quickInput.value = text; + floatField.value = tag.dataset.text; document.getElementById('btn-save').click(); }); }); -// ========== 数据更新 ========== +// ===== CHIPS ===== -async function updateStats() { - try { - const res = await fetch(`${API_BASE}/api/stats`); - const stats = await res.json(); - - document.getElementById('stat-today').textContent = stats.today; - document.getElementById('stat-week').textContent = stats.week; - document.getElementById('stat-total').textContent = stats.total; - document.getElementById('recent-count').textContent = stats.total; - } catch (err) { - console.error('更新统计失败:', err); - } -} - -function prependToList(record) { - const list = document.getElementById('recent-list'); - const emptyMsg = list.querySelector('.recent-item .empty'); - if (emptyMsg) { - list.innerHTML = ''; - } +function addChip(record) { + const container = document.getElementById('recent-chips'); + const empty = container.querySelector('.empty'); + if (empty) empty.remove(); - const item = document.createElement('div'); - item.className = 'recent-item'; - item.dataset.id = record.id; - item.innerHTML = ` - ${formatTimeOnly(record.created_at)} - (未备注) + const chip = document.createElement('div'); + chip.className = 'recent-chip'; + chip.dataset.id = record.id; + chip.innerHTML = ` + ${formatTime(record.created_at)} + 新记录 `; - list.insertBefore(item, list.firstChild); + chip.addEventListener('click', () => { + currentId = String(record.id); // 确保是字符串 + console.log('Chip clicked, currentId set to:', currentId); + openFloatInput(record.created_at, true); // true = 编辑模式 + }); - // 保持最多10条 - while (list.children.length > 10) { - list.removeChild(list.lastChild); + container.insertBefore(chip, container.firstChild); + + // 保持最多 10 个 + while (container.children.length > 10) { + container.removeChild(container.lastChild); } } -function updateListItem(id, content) { - const item = document.querySelector(`.recent-item[data-id="${id}"]`); - if (item) { - const contentEl = item.querySelector('.recent-content'); - contentEl.textContent = content; - contentEl.classList.remove('empty'); +function updateChip(id, content) { + const chip = document.querySelector(`.recent-chip[data-id="${id}"]`); + if (chip) { + chip.querySelector('span:last-child').textContent = content; } } -// ========== 成就检查 ========== +// ===== STATS ===== -let lastAchievementCount = 0; - -async function checkNewAchievements() { +async function refreshStats() { try { - const res = await fetch(`${API_BASE}/api/achievements`); + const res = await fetch(`${API}/api/stats`); + const s = await res.json(); + + document.getElementById('stat-today').textContent = s.today; + document.getElementById('stat-week').textContent = s.week; + document.getElementById('stat-month').textContent = s.month; + document.getElementById('stat-total').textContent = s.total; + + // 增长率 + updateGrowth('growth-today', s.day_growth); + updateGrowth('growth-week', s.week_growth); + updateGrowth('growth-month', s.month_growth); + } catch (err) { + console.error(err); + } +} + +function updateGrowth(id, value) { + const el = document.getElementById(id); + if (!el) return; + if (value > 0) { + el.textContent = `+${value}%`; + el.classList.remove('negative'); + } else if (value < 0) { + el.textContent = `${value}%`; + el.classList.add('negative'); + } else { + el.textContent = '-'; + } +} + +// ===== ACHIEVEMENTS ===== + +async function checkAchievements() { + try { + const res = await fetch(`${API}/api/achievements`); const data = await res.json(); - if (data.unlocked_count > lastAchievementCount) { - // 有新成就解锁 - const newAchievements = data.achievements.filter(a => a.unlocked).slice(-1); - if (newAchievements.length > 0) { - showAchievementToast(newAchievements[0]); - } - } - lastAchievementCount = data.unlocked_count; - } catch (err) { - console.error('检查成就失败:', err); - } -} - -function showAchievementToast(achievement) { - const icon = achievementToast.querySelector('.achievement-icon'); - const text = document.getElementById('achievement-text'); - - icon.textContent = achievement.icon; - text.textContent = `解锁:${achievement.name}`; - - achievementToast.classList.add('show'); - - setTimeout(() => { - achievementToast.classList.remove('show'); - }, 3000); -} - -// ========== 辅助函数 ========== - -function showToast(message, success = true) { - toast.textContent = message; - toast.style.background = success ? '#ff4757' : '#ff6b6b'; - toast.classList.add('show'); - - setTimeout(() => { - toast.classList.remove('show'); - }, 1500); -} - -function formatTimeOnly(isoString) { - const date = new Date(isoString); - const hours = date.getHours().toString().padStart(2, '0'); - const minutes = date.getMinutes().toString().padStart(2, '0'); - return `${hours}:${minutes}`; -} - -// ========== 初始化 ========== - -async function init() { - await updateStats(); - await loadRecent(); - await checkNewAchievements(); -} - -async function loadRecent() { - try { - const res = await fetch(`${API_BASE}/api/din?limit=10`); - const records = await res.json(); + document.getElementById('ach-progress').textContent = + `${data.unlocked_count}/${data.total_count}`; - const list = document.getElementById('recent-list'); - document.getElementById('recent-count').textContent = records.length; - - if (records.length === 0) { - list.innerHTML = '
点击大按钮开始记录
'; - return; - } - - list.innerHTML = records.map(r => ` -
- ${formatTimeOnly(r.created_at)} - ${r.content || '(未备注)'} + const list = document.getElementById('achievements-list'); + list.innerHTML = data.achievements.map(a => ` +
+ ${a.icon} + ${a.name}
`).join(''); + } catch (err) { - console.error('加载记录失败:', err); + console.error(err); } } -// ========== 历史记录面板 ========== +// ===== HISTORY PANEL ===== -const historyOverlay = document.getElementById('history-overlay'); +const historyPanel = document.getElementById('history-panel'); const historyList = document.getElementById('history-list'); -// 打开历史面板 -document.getElementById('view-all').addEventListener('click', () => { - historyOverlay.classList.add('active'); +document.getElementById('btn-view-all').addEventListener('click', () => { + historyPanel.classList.add('active'); loadHistory(); }); -// 关闭历史面板 -document.getElementById('close-history').addEventListener('click', () => { - historyOverlay.classList.remove('active'); -}); - -// 点击遮罩关闭 -historyOverlay.addEventListener('click', (e) => { - if (e.target === historyOverlay) { - historyOverlay.classList.remove('active'); - } +document.getElementById('history-close').addEventListener('click', () => { + historyPanel.classList.remove('active'); }); async function loadHistory() { try { - const res = await fetch(`${API_BASE}/api/din?limit=100`); + const res = await fetch(`${API}/api/din?limit=100`); const records = await res.json(); if (records.length === 0) { - historyList.innerHTML = '
暂无记录
'; + historyList.innerHTML = '
暂无记录
'; return; } - // 按日期分组 - const grouped = groupByDate(records); - - historyList.innerHTML = Object.entries(grouped).map(([date, items]) => ` -
-
${date}
- ${items.map(r => ` -
- ${formatTimeOnly(r.created_at)} - ${r.content || '(未备注)'} -
- - -
-
- `).join('')} + historyList.innerHTML = records.map(r => ` +
+ ${formatTime(r.created_at)} + ${r.content || '(未备注)'} +
+ + +
`).join(''); + } catch (err) { - console.error('加载历史失败:', err); - showToast('加载失败', false); + console.error(err); } } -function groupByDate(records) { - const groups = {}; - records.forEach(r => { - const date = new Date(r.created_at); - const key = `${date.getMonth() + 1}月${date.getDate()}日`; - if (!groups[key]) groups[key] = []; - groups[key].push(r); +window.editRecord = async (id, current) => { + const content = prompt('修改备注:', current); + if (content === null) return; + + await fetch(`${API}/api/din/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: content.trim() }) }); - return groups; -} - -// 编辑历史记录 -window.editHistoryItem = async function(id, currentContent) { - const newContent = prompt('修改备注:', currentContent); - if (newContent === null) return; - try { - await fetch(`${API_BASE}/api/din/${id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content: newContent.trim() }) - }); - - showToast('已更新'); - loadHistory(); // 刷新历史列表 - loadRecent(); // 刷新最近列表 - } catch (err) { - showToast('更新失败', false); - } + loadHistory(); + updateChip(id, content); + showToast('已更新'); }; -// 删除历史记录 -window.deleteHistoryItem = async function(id) { - if (!confirm('确定删除这条记录?')) return; +window.deleteRecord = async (id) => { + const confirmed = await showConfirm('确定要删除这条记录吗?此操作不可恢复。'); + if (!confirmed) return; - try { - await fetch(`${API_BASE}/api/din/${id}`, { method: 'DELETE' }); - - showToast('已删除'); - loadHistory(); // 刷新历史列表 - loadRecent(); // 刷新最近列表 - updateStats(); // 刷新统计 - } catch (err) { - showToast('删除失败', false); - } + await fetch(`${API}/api/din/${id}`, { method: 'DELETE' }); + + loadHistory(); + refreshStats(); + + const chip = document.querySelector(`.recent-chip[data-id="${id}"]`); + if (chip) chip.remove(); + + showToast('已删除'); }; -function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML.replace(/'/g, "'").replace(/"/g, """); +// ===== UTILS ===== + +function showToast(msg, success = true) { + toast.textContent = msg; + toast.style.background = success ? '#ff6b6b' : '#ff5252'; + toast.classList.add('show'); + setTimeout(() => toast.classList.remove('show'), 1500); +} + +function formatTime(iso) { + const d = new Date(iso); + return `${d.getHours().toString().padStart(2,'0')}:${d.getMinutes().toString().padStart(2,'0')}`; +} + +function esc(str) { + return str.replace(/'/g, "\\'").replace(/"/g, '\\"'); +} + +// ===== INIT ===== + +async function init() { + await refreshStats(); + await checkAchievements(); + + // 加载最近记录 + try { + const res = await fetch(`${API}/api/din?limit=10`); + const records = await res.json(); + records.reverse().forEach(r => addChip(r)); + } catch (err) { + console.error(err); + } } -// 启动 init(); diff --git a/static/index.html b/static/index.html index 7836f47..8f05a01 100644 --- a/static/index.html +++ b/static/index.html @@ -3,7 +3,7 @@ - + @@ -18,90 +18,135 @@ - -
- -

点击任意处记录 · 长按快捷输入

+
+ +
+

🦐 din

+

Do It Now - 想到就做

+
+ + +
+
+
0
+
今日
+
-
+
+
+
0
+
本周
+
-
+
+
+
0
+
本月
+
-
+
+
+
0
+
总计
+
+
+ + +
+ +

点击记录 · 长按添加备注

+
+ + +
+
+ 最近记录 + 查看全部 → +
+
+
点击上方按钮开始
+
+
+ + +
+
+ 成就 + 0/24 +
+
+ +
+
- -
-
-
0
-
今日
+ +
+ + +
+
+ 添加备注 + --:--
-
-
0
-
本周
+ + +
+ 💼 工作 + 📚 学习 + 💪 运动 + 😴 休息 + 💡 创意
-
-
0
-
总计
+ +
+ + +
- - - - -
-
- 最近记录 - 查看全部 → + +
+
+ 历史记录 +
-
-
- 点击大按钮开始记录 -
-
-
- - -
-
-
-

历史记录

- -
-
- -
-
-
- - -
-
-
- 添加备注 - --:-- -
- -
- 💼 工作 - 📚 学习 - 💪 运动 - 😴 休息 - 💡 创意 - 📝 其他 -
-
- - -
+
+
已记录!
- - -
- 🏆 - 解锁成就 + + +
+
+
🗑️
+
确认删除
+
确定要删除这条记录吗?
+
+ + +
+
diff --git a/static/style.css b/static/style.css index 697b770..711fe02 100644 --- a/static/style.css +++ b/static/style.css @@ -1,3 +1,7 @@ +/* ===== DIN: Clear & Fast ===== + * 基于第一版优化:保持清晰,提升速度 + */ + * { margin: 0; padding: 0; @@ -6,365 +10,454 @@ } body { - font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif; - background: #0d0d0f; + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', sans-serif; + background: linear-gradient(180deg, #1a1a2e 0%, #0f0f1a 100%); color: #fff; min-height: 100vh; - overflow-x: hidden; + padding: 20px; + padding-bottom: max(20px, env(safe-area-inset-bottom)); } -/* 主按钮 - 占据屏幕中心 */ -.din-trigger { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - z-index: 10; - background: radial-gradient(ellipse at center, #1a1a2e 0%, #0d0d0f 70%); +.container { + max-width: 600px; + margin: 0 auto; } -.big-btn { - width: 200px; - height: 200px; - border-radius: 50%; - border: none; - background: linear-gradient(145deg, #ff4757, #ff3838); - box-shadow: - 0 20px 60px rgba(255, 71, 87, 0.4), - 0 0 0 20px rgba(255, 71, 87, 0.1), - inset 0 -4px 20px rgba(0,0,0,0.2), - inset 0 4px 20px rgba(255,255,255,0.2); - cursor: pointer; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); - user-select: none; - -webkit-user-select: none; +/* ===== HEADER ===== */ +header { + text-align: center; + margin-bottom: 24px; } -.big-btn:active { - transform: scale(0.92); - box-shadow: - 0 10px 30px rgba(255, 71, 87, 0.3), - 0 0 0 10px rgba(255, 71, 87, 0.05), - inset 0 -2px 10px rgba(0,0,0,0.2); +header h1 { + font-size: 2rem; + margin-bottom: 4px; } -.big-btn.recording { - animation: pulse-record 0.6s ease-out; -} - -@keyframes pulse-record { - 0% { transform: scale(1); box-shadow: 0 20px 60px rgba(255, 71, 87, 0.4), 0 0 0 20px rgba(255, 71, 87, 0.1); } - 50% { transform: scale(1.05); box-shadow: 0 30px 80px rgba(255, 71, 87, 0.6), 0 0 0 40px rgba(255, 71, 87, 0); } - 100% { transform: scale(1); box-shadow: 0 20px 60px rgba(255, 71, 87, 0.4), 0 0 0 20px rgba(255, 71, 87, 0.1); } -} - -.btn-icon { - font-size: 4rem; - margin-bottom: 5px; -} - -.btn-text { - font-size: 1.1rem; - font-weight: 700; - letter-spacing: 2px; - text-transform: uppercase; -} - -.hint { - margin-top: 30px; - color: #666; +.subtitle { + color: #888; font-size: 0.85rem; } -/* 底部统计 - 常驻显示 */ -.stats-bar { - position: fixed; - bottom: 0; - left: 0; - right: 0; - display: flex; - justify-content: space-around; - padding: 20px; - background: linear-gradient(transparent, rgba(13,13,15,0.95) 40%); - z-index: 20; +/* ===== STATS: 简化但清晰 ===== */ +.stats { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + margin-bottom: 24px; } -.stat-item { +.stat-card { + background: rgba(255,255,255,0.05); + border-radius: 16px; + padding: 16px 8px; text-align: center; + border: 1px solid rgba(255,255,255,0.08); + transition: transform 0.2s; } -.stat-num { - font-size: 1.8rem; +.stat-card:active { + transform: scale(0.98); +} + +.stat-card.total { + background: rgba(255,107,107,0.12); + border-color: rgba(255,107,107,0.25); +} + +.stat-value { + font-size: 1.6rem; font-weight: 700; - color: #fff; - line-height: 1; + margin-bottom: 4px; } -.stat-num.total { - color: #ff4757; +.stat-card.total .stat-value { + color: #ff6b6b; } .stat-label { font-size: 0.7rem; - color: #666; - margin-top: 4px; - text-transform: uppercase; - letter-spacing: 1px; + color: #888; + margin-bottom: 2px; } -/* 快速输入层 - 从底部滑出 */ -.input-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0,0,0,0.8); - backdrop-filter: blur(10px); - z-index: 100; +.stat-growth { + font-size: 0.65rem; + color: #4ade80; +} + +.stat-growth.negative { + color: #f87171; +} + +/* ===== MAIN BUTTON: 更大更容易点 ===== */ +.main-action { display: flex; - flex-direction: column; - justify-content: flex-end; + justify-content: center; + margin-bottom: 24px; + position: relative; +} + +.din-button { + width: 180px; + height: 180px; + border-radius: 50%; + border: none; + background: transparent; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; +} + +.din-button:hover { + transform: scale(1.05); +} + +.din-button:active { + transform: scale(0.95); +} + +.din-button.recording { + animation: pulse-btn 1.2s ease-in-out infinite; +} + +@keyframes pulse-btn { + 0%, 100% { + filter: drop-shadow(0 0 20px rgba(255,107,107,0.4)); + transform: scale(1); + } + 50% { + filter: drop-shadow(0 0 40px rgba(255,107,107,0.6)); + transform: scale(1.02); + } +} + +.btn-icon { + width: 100%; + height: 100%; + transition: transform 0.2s; +} + +.din-button:active .btn-icon { + transform: scale(0.92); +} + +/* 提示 */ +.hint { + position: absolute; + bottom: -40px; + text-align: center; + color: #666; + font-size: 0.8rem; +} + +/* ===== FLOATING INPUT: 快速输入不跳转 ===== */ +.float-input { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.9); + background: rgba(30,30,40,0.98); + backdrop-filter: blur(20px); + border-radius: 24px; + padding: 24px; + width: 90%; + max-width: 400px; + border: 1px solid rgba(255,255,255,0.1); + z-index: 100; opacity: 0; visibility: hidden; - transition: all 0.3s ease; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); } -.input-overlay.active { +.float-input.active { opacity: 1; visibility: visible; + transform: translate(-50%, -50%) scale(1); } -.input-panel { - background: #1a1a1f; - border-radius: 24px 24px 0 0; - padding: 24px; - transform: translateY(100%); - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -.input-overlay.active .input-panel { - transform: translateY(0); -} - -.input-header { +.float-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } -.input-title { +.float-title { font-size: 1.1rem; font-weight: 600; } -.timestamp { +.float-time { font-size: 0.8rem; color: #666; font-family: 'SF Mono', monospace; } -.quick-input { +.float-field { width: 100%; - background: #0d0d0f; - border: 2px solid #333; - border-radius: 16px; - padding: 18px 20px; + background: rgba(0,0,0,0.3); + border: 2px solid rgba(255,255,255,0.1); + border-radius: 14px; + padding: 16px; font-size: 1.1rem; color: #fff; + margin-bottom: 16px; outline: none; transition: border-color 0.2s; - margin-bottom: 16px; } -.quick-input:focus { - border-color: #ff4757; +.float-field:focus { + border-color: #ff6b6b; } -.quick-input::placeholder { +.float-field::placeholder { color: #555; } -/* 快速标签 */ -.quick-tags { +/* Quick tags */ +.tags { display: flex; - gap: 10px; + gap: 8px; flex-wrap: wrap; - margin-bottom: 20px; + margin-bottom: 16px; } .tag { - padding: 8px 16px; - background: #25252a; - border: 1px solid #333; + padding: 10px 16px; + background: rgba(255,255,255,0.06); + border: 1px solid rgba(255,255,255,0.08); border-radius: 20px; font-size: 0.9rem; color: #aaa; cursor: pointer; - transition: all 0.2s; + transition: all 0.15s; } .tag:hover, .tag:active { - background: #ff4757; - border-color: #ff4757; + background: #ff6b6b; + border-color: #ff6b6b; color: #fff; + transform: scale(0.98); } -/* 操作按钮 */ -.input-actions { +.float-actions { display: flex; gap: 12px; } .btn { flex: 1; - padding: 16px; - border-radius: 14px; + padding: 14px; + border-radius: 12px; border: none; font-size: 1rem; font-weight: 600; cursor: pointer; - transition: all 0.2s; + transition: all 0.15s; } -.btn-save { - background: #ff4757; +.btn-primary { + background: #ff6b6b; color: #fff; } -.btn-save:active { - background: #ff3838; +.btn-primary:active { + background: #ff5252; transform: scale(0.98); } -.btn-skip { - background: #25252a; +.btn-secondary { + background: rgba(255,255,255,0.08); color: #888; } -.btn-skip:active { - background: #333; +.btn-secondary:active { + background: rgba(255,255,255,0.12); } -/* 最近记录 - 右侧面板 (桌面) / 底部 (手机) */ -.recent-panel { +.btn-danger { + background: rgba(255,59,48,0.15); + color: #ff3b30; +} + +.btn-danger:active { + background: rgba(255,59,48,0.25); + transform: scale(0.98); +} + +/* Overlay */ +.overlay { position: fixed; - top: 20px; - right: 20px; - width: 280px; - max-height: calc(100vh - 140px); - background: rgba(26,26,31,0.8); - backdrop-filter: blur(20px); - border-radius: 20px; - padding: 20px; - overflow-y: auto; - z-index: 15; -} - -.recent-title { - font-size: 0.9rem; - color: #888; - margin-bottom: 16px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.recent-count { - color: #ff4757; - font-weight: 600; -} - -.recent-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.recent-item { - display: flex; - align-items: flex-start; - gap: 12px; - padding: 12px; - background: rgba(255,255,255,0.03); - border-radius: 12px; - transition: background 0.2s; -} - -.recent-item:hover { - background: rgba(255,255,255,0.06); -} - -.recent-time { - font-size: 0.75rem; - color: #555; - white-space: nowrap; - font-family: 'SF Mono', monospace; -} - -.recent-content { - flex: 1; - font-size: 0.9rem; - color: #ddd; - word-break: break-all; -} - -.recent-content.empty { - color: #555; - font-style: italic; -} - -.view-all { - font-size: 0.8rem; - color: #ff4757; - cursor: pointer; - transition: opacity 0.2s; -} - -.view-all:hover { - opacity: 0.8; -} - -/* 历史记录面板 */ -.history-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0,0,0,0.9); - backdrop-filter: blur(20px); - z-index: 150; + inset: 0; + background: rgba(0,0,0,0.7); + backdrop-filter: blur(5px); + z-index: 99; opacity: 0; visibility: hidden; - transition: all 0.3s ease; + transition: all 0.25s; } -.history-overlay.active { +.overlay.active { opacity: 1; visibility: visible; } +/* ===== RECENT STRIP: 横向快捷查看 ===== */ +.recent-strip { + background: rgba(255,255,255,0.03); + border-radius: 16px; + padding: 16px; + margin-bottom: 16px; +} + +.recent-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.recent-title { + font-size: 0.85rem; + color: #888; +} + +.view-all { + font-size: 0.8rem; + color: #ff6b6b; + cursor: pointer; +} + +.recent-chips { + display: flex; + gap: 8px; + overflow-x: auto; + scrollbar-width: none; + -ms-overflow-style: none; + padding-bottom: 4px; +} + +.recent-chips::-webkit-scrollbar { + display: none; +} + +.recent-chip { + flex-shrink: 0; + padding: 10px 14px; + background: rgba(255,255,255,0.05); + border-radius: 10px; + font-size: 0.85rem; + color: #aaa; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 6px; +} + +.recent-chip:hover, .recent-chip:active { + background: rgba(255,255,255,0.1); +} + +.recent-chip .time { + font-size: 0.7rem; + color: #666; + font-family: 'SF Mono', monospace; +} + +.recent-chip.empty { + color: #555; + font-style: italic; +} + +/* ===== ACHIEVEMENTS: 横向滚动 ===== */ +.achievements { + background: rgba(255,255,255,0.03); + border-radius: 16px; + padding: 16px; +} + +.achievements-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.achievements-title { + font-size: 0.85rem; + color: #888; +} + +.achievements-progress { + font-size: 0.8rem; + color: #666; +} + +.achievements-scroll { + display: flex; + gap: 10px; + overflow-x: auto; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.achievements-scroll::-webkit-scrollbar { + display: none; +} + +.achievement-badge { + flex-shrink: 0; + width: 70px; + text-align: center; + padding: 12px 8px; + background: rgba(255,255,255,0.03); + border-radius: 12px; + border: 1px solid rgba(255,255,255,0.05); + opacity: 0.4; + transition: all 0.2s; +} + +.achievement-badge.unlocked { + opacity: 1; + background: rgba(255,215,0,0.08); + border-color: rgba(255,215,0,0.2); +} + +.achievement-icon { + font-size: 1.8rem; + display: block; + margin-bottom: 4px; +} + +.achievement-name { + font-size: 0.65rem; + color: #888; +} + +/* ===== HISTORY PANEL ===== */ .history-panel { - position: absolute; + position: fixed; top: 0; - left: 0; right: 0; bottom: 0; + width: 100%; + max-width: 420px; + background: linear-gradient(180deg, #1a1a2e 0%, #0f0f1a 100%); + z-index: 200; + transform: translateX(100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: flex; flex-direction: column; - max-width: 600px; - margin: 0 auto; +} + +.history-panel.active { + transform: translateX(0); } .history-header { @@ -372,84 +465,53 @@ body { justify-content: space-between; align-items: center; padding: 20px; - border-bottom: 1px solid rgba(255,255,255,0.1); + padding-top: max(20px, env(safe-area-inset-top)); + border-bottom: 1px solid rgba(255,255,255,0.05); } -.history-header h2 { - font-size: 1.2rem; +.history-title { + font-size: 1.1rem; font-weight: 600; } -.close-btn { +.history-close { width: 40px; height: 40px; border-radius: 50%; border: none; - background: rgba(255,255,255,0.1); + background: rgba(255,255,255,0.08); color: #fff; font-size: 1.2rem; cursor: pointer; - transition: all 0.2s; -} - -.close-btn:hover { - background: rgba(255,255,255,0.15); } .history-list { flex: 1; overflow-y: auto; - padding: 20px; - display: flex; - flex-direction: column; - gap: 12px; + padding: 16px; } .history-item { display: flex; align-items: center; gap: 12px; - padding: 16px; - background: rgba(255,255,255,0.05); - border-radius: 16px; - transition: all 0.2s; -} - -.history-item:hover { - background: rgba(255,255,255,0.08); -} - -.history-date { - font-size: 0.7rem; - color: #666; - min-width: 60px; - text-align: center; -} - -.history-date .day { - font-size: 1.2rem; - font-weight: 700; - color: #fff; - display: block; -} - -.history-date .month { - font-size: 0.6rem; - text-transform: uppercase; + padding: 14px; + background: rgba(255,255,255,0.03); + border-radius: 12px; + margin-bottom: 8px; } .history-time { font-size: 0.75rem; - color: #888; + color: #666; min-width: 50px; font-family: 'SF Mono', monospace; } .history-content { flex: 1; - font-size: 0.95rem; + font-size: 0.9rem; color: #ddd; - word-break: break-all; } .history-content.empty { @@ -459,62 +521,41 @@ body { .history-actions { display: flex; - gap: 8px; + gap: 6px; } .history-actions button { - width: 36px; - height: 36px; - border-radius: 50%; + width: 32px; + height: 32px; + border-radius: 8px; border: none; - background: rgba(255,255,255,0.1); + background: rgba(255,255,255,0.06); color: #888; - font-size: 1rem; + font-size: 0.9rem; cursor: pointer; - transition: all 0.2s; - display: flex; - align-items: center; - justify-content: center; } .history-actions button:hover { - background: rgba(255,255,255,0.15); - color: #fff; + background: rgba(255,255,255,0.1); } .history-actions .btn-delete:hover { - background: #ff4757; + background: #ff6b6b; color: #fff; } -/* 历史记录分组 */ -.history-group { - margin-bottom: 24px; -} - -.history-date-header { - font-size: 0.8rem; - color: #666; - padding: 8px 0; - margin-bottom: 8px; - border-bottom: 1px solid rgba(255,255,255,0.05); - text-transform: uppercase; - letter-spacing: 1px; -} - -/* Toast */ -.toast { +/* ===== TOAST ===== */ .toast { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.8); - background: #ff4757; + background: #ff6b6b; color: #fff; padding: 16px 32px; border-radius: 30px; font-weight: 600; - z-index: 200; + z-index: 300; opacity: 0; visibility: hidden; transition: all 0.3s ease; @@ -526,101 +567,119 @@ body { transform: translate(-50%, -50%) scale(1); } -/* 成就徽章 */ -.achievement-toast { - position: fixed; - top: 20px; - left: 50%; - transform: translateX(-50%) translateY(-100px); - background: linear-gradient(135deg, #ffd700, #ffaa00); - color: #000; - padding: 16px 24px; - border-radius: 16px; - display: flex; - align-items: center; - gap: 12px; - font-weight: 600; - z-index: 200; - transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); -} - -.achievement-toast.show { - transform: translateX(-50%) translateY(0); -} - -.achievement-icon { - font-size: 1.8rem; -} - -/* 移动端适配 */ -@media (max-width: 768px) { - .big-btn { - width: 160px; - height: 160px; +/* ===== RESPONSIVE ===== */ +@media (max-width: 400px) { + .stats { + gap: 6px; } - .btn-icon { + .stat-card { + padding: 12px 4px; + } + + .stat-value { + font-size: 1.3rem; + } + + .din-button { + width: 170px; + height: 170px; + } + + .btn-text { font-size: 3rem; } - - .recent-panel { - position: fixed; - top: auto; - bottom: 100px; - left: 20px; - right: 20px; - width: auto; - max-height: 200px; - background: rgba(13,13,15,0.95); - } - - .stats-bar { - padding-bottom: 30px; - } } -/* 隐藏的成就面板 - 可展开 */ -.achievements-btn { +/* ===== CUSTOM CONFIRM DIALOG ===== */ +.confirm-overlay { position: fixed; - top: 20px; - left: 20px; - width: 48px; - height: 48px; - border-radius: 50%; - background: rgba(255,255,255,0.1); - border: none; + inset: 0; + background: rgba(0,0,0,0.7); + backdrop-filter: blur(8px); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transition: all 0.25s ease; +} + +.confirm-overlay.active { + opacity: 1; + visibility: visible; +} + +.confirm-box { + background: linear-gradient(145deg, #1e1e2e, #1a1a2e); + border-radius: 20px; + padding: 28px 24px; + width: 90%; + max-width: 320px; + text-align: center; + border: 1px solid rgba(255,255,255,0.08); + box-shadow: 0 25px 50px rgba(0,0,0,0.4); + transform: scale(0.9); + transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); +} + +.confirm-overlay.active .confirm-box { + transform: scale(1); +} + +.confirm-icon { + font-size: 3rem; + margin-bottom: 12px; +} + +.confirm-title { + font-size: 1.2rem; + font-weight: 600; + margin-bottom: 8px; color: #fff; - font-size: 1.5rem; +} + +.confirm-message { + font-size: 0.9rem; + color: #888; + margin-bottom: 24px; + line-height: 1.5; +} + +.confirm-actions { + display: flex; + gap: 12px; +} + +.confirm-btn { + flex: 1; + padding: 14px 20px; + border-radius: 12px; + border: none; + font-size: 1rem; + font-weight: 600; cursor: pointer; - z-index: 30; transition: all 0.2s; } -.achievements-btn:hover { - background: rgba(255,255,255,0.15); +.confirm-btn.cancel { + background: rgba(255,255,255,0.08); + color: #aaa; } -/* 滑动指示器 */ -.swipe-indicator { - position: fixed; - bottom: 100px; - left: 50%; - transform: translateX(-50%); - display: flex; - gap: 8px; - z-index: 25; +.confirm-btn.cancel:hover, .confirm-btn.cancel:active { + background: rgba(255,255,255,0.12); } -.swipe-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: rgba(255,255,255,0.2); - transition: all 0.3s; +.confirm-btn.delete { + background: linear-gradient(145deg, #ff6b6b, #ee5a5a); + color: #fff; + box-shadow: 0 4px 15px rgba(255,107,107,0.3); } -.swipe-dot.active { - background: #ff4757; - width: 24px; - border-radius: 4px; +.confirm-btn.delete:hover, .confirm-btn.delete:active { + background: linear-gradient(145deg, #ff5252, #dd4a4a); + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(255,107,107,0.4); }