diff --git a/web/index.html b/web/index.html index 2ff78b5..fd51816 100644 --- a/web/index.html +++ b/web/index.html @@ -5,6 +5,7 @@ + VoicePaste @@ -24,13 +25,21 @@
- +
+ + +
+

按住说话

diff --git a/web/style.css b/web/style.css index 9fb99b7..2cd0aa9 100644 --- a/web/style.css +++ b/web/style.css @@ -7,17 +7,25 @@ } :root { - --bg: #0a0a0a; - --surface: #161616; - --surface-hover: #1e1e1e; - --border: #2a2a2a; - --text: #e8e8e8; - --text-dim: #888; - --accent: #3b82f6; - --accent-glow: rgba(59, 130, 246, 0.3); - --danger: #ef4444; - --success: #22c55e; - --radius: 12px; + /* Warm dark palette — subtle indigo undertone */ + --bg: #08080d; + --surface: #111117; + --surface-hover: #17171e; + --surface-active: #1c1c25; + --border: #1e1e2a; + --border-active: #2c2c3e; + --text: #eaeaef; + --text-secondary: #9e9eb5; + --text-dim: #5a5a6e; + --accent: #6366f1; + --accent-hover: #818cf8; + --accent-glow: rgba(99, 102, 241, 0.2); + --accent-glow-md: rgba(99, 102, 241, 0.35); + --accent-glow-lg: rgba(99, 102, 241, 0.08); + --danger: #f43f5e; + --success: #34d399; + --radius: 14px; + --radius-sm: 8px; --safe-top: env(safe-area-inset-top, 0px); --safe-bottom: env(safe-area-inset-bottom, 0px); } @@ -26,8 +34,8 @@ html, body { height: 100%; font-family: - -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", - sans-serif; + "SF Pro Display", -apple-system, BlinkMacSystemFont, "PingFang SC", + "Hiragino Sans GB", "Microsoft YaHei", sans-serif; background: var(--bg); color: var(--text); -webkit-font-smoothing: antialiased; @@ -37,55 +45,90 @@ body { overflow: hidden; } +/* Subtle ambient glow at top */ +body::after { + content: ""; + position: fixed; + top: -30%; + left: 50%; + transform: translateX(-50%); + width: 600px; + height: 600px; + background: radial-gradient( + circle, + rgba(99, 102, 241, 0.04) 0%, + transparent 70% + ); + pointer-events: none; + z-index: 0; +} + #app { + position: relative; + z-index: 1; display: flex; flex-direction: column; height: 100%; max-width: 480px; margin: 0 auto; - padding: calc(16px + var(--safe-top)) 16px calc(16px + var(--safe-bottom)); + padding: calc(16px + var(--safe-top)) 20px calc(16px + var(--safe-bottom)); } -/* Header */ +/* ─── Header ─── */ header { display: flex; align-items: center; justify-content: space-between; - padding: 8px 0 16px; + padding: 8px 0 20px; flex-shrink: 0; } header h1 { - font-size: 20px; - font-weight: 600; - letter-spacing: -0.02em; + font-size: 22px; + font-weight: 700; + letter-spacing: -0.03em; } .status { display: flex; align-items: center; - gap: 6px; - font-size: 13px; + gap: 7px; + font-size: 12px; + font-weight: 500; color: var(--text-dim); + padding: 5px 12px; + border-radius: 20px; + background: var(--surface); + border: 1px solid var(--border); + transition: all 0.25s ease; } .status .dot { - width: 8px; - height: 8px; + width: 7px; + height: 7px; border-radius: 50%; background: var(--text-dim); - transition: background 0.3s; + transition: all 0.3s ease; + flex-shrink: 0; +} + +.status.connected { + border-color: rgba(52, 211, 153, 0.15); } .status.connected .dot { background: var(--success); + box-shadow: 0 0 6px rgba(52, 211, 153, 0.5); } + .status.disconnected .dot { background: var(--danger); + box-shadow: 0 0 6px rgba(244, 63, 94, 0.4); } + .status.connecting .dot { background: var(--accent); - animation: pulse 1.2s ease-in-out infinite; + animation: pulse 1.4s ease-in-out infinite; } @keyframes pulse { @@ -94,97 +137,175 @@ header h1 { opacity: 1; } 50% { - opacity: 0.4; + opacity: 0.3; } } -/* Preview */ +/* ─── Preview ─── */ #preview-section { flex-shrink: 0; - padding-bottom: 16px; + padding-bottom: 12px; } .preview-box { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); - padding: 16px; + padding: 16px 18px; min-height: 80px; max-height: 160px; overflow-y: auto; - transition: border-color 0.3s; + transition: + border-color 0.3s ease, + box-shadow 0.3s ease, + background 0.3s ease; } .preview-box.active { - border-color: var(--accent); - box-shadow: 0 0 0 1px var(--accent-glow); + border-color: rgba(99, 102, 241, 0.4); + box-shadow: + 0 0 0 1px var(--accent-glow), + 0 4px 24px -4px rgba(99, 102, 241, 0.15); + background: linear-gradient( + 180deg, + rgba(99, 102, 241, 0.03) 0%, + var(--surface) 100% + ); } #preview-text { font-size: 16px; - line-height: 1.5; + line-height: 1.6; word-break: break-word; } #preview-text.placeholder { color: var(--text-dim); - font-style: italic; } -/* Mic Button */ +/* ─── Mic Button ─── */ #mic-section { display: flex; - justify-content: center; - padding: 24px 0; + flex-direction: column; + align-items: center; + padding: 20px 0 16px; flex-shrink: 0; + gap: 14px; +} + +.mic-wrapper { + position: relative; + display: flex; + align-items: center; + justify-content: center; } #mic-btn { - width: 88px; - height: 88px; + position: relative; + z-index: 1; + width: 96px; + height: 96px; border-radius: 50%; border: 2px solid var(--border); - background: var(--surface); - color: var(--text-dim); + background: linear-gradient(145deg, var(--surface-hover), var(--surface)); + color: var(--text-secondary); display: flex; align-items: center; justify-content: center; cursor: pointer; - transition: all 0.2s; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); -webkit-user-select: none; touch-action: none; + box-shadow: + 0 2px 12px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.04); +} + +#mic-btn svg { + transition: transform 0.2s ease; } #mic-btn:disabled { - opacity: 0.4; + opacity: 0.3; cursor: not-allowed; } #mic-btn:not(:disabled):active, #mic-btn.recording { background: var(--accent); - border-color: var(--accent); + border-color: var(--accent-hover); color: #fff; - transform: scale(1.08); - box-shadow: 0 0 24px var(--accent-glow); + transform: scale(1.06); + box-shadow: + 0 0 32px var(--accent-glow-md), + 0 0 80px var(--accent-glow); } #mic-btn.recording { - animation: mic-pulse 1s ease-in-out infinite; + animation: mic-breathe 1.8s ease-in-out infinite; } -@keyframes mic-pulse { +@keyframes mic-breathe { 0%, 100% { - box-shadow: 0 0 24px var(--accent-glow); + box-shadow: + 0 0 32px var(--accent-glow-md), + 0 0 80px var(--accent-glow); } 50% { box-shadow: - 0 0 48px var(--accent-glow), - 0 0 80px rgba(59, 130, 246, 0.15); + 0 0 48px var(--accent-glow-md), + 0 0 120px var(--accent-glow), + 0 0 200px var(--accent-glow-lg); } } -/* History */ + +/* Wave rings — radiate outward when recording */ +.mic-rings { + position: absolute; + inset: 0; + pointer-events: none; +} + +.mic-rings .ring { + position: absolute; + inset: 0; + border-radius: 50%; + border: 1.5px solid var(--accent); + opacity: 0; +} + +#mic-btn.recording + .mic-rings .ring { + animation: ring-expand 2.4s cubic-bezier(0.2, 0, 0.2, 1) infinite; +} + +#mic-btn.recording + .mic-rings .ring:nth-child(2) { + animation-delay: 0.8s; +} + +#mic-btn.recording + .mic-rings .ring:nth-child(3) { + animation-delay: 1.6s; +} + +@keyframes ring-expand { + 0% { + transform: scale(1); + opacity: 0.35; + } + 100% { + transform: scale(2); + opacity: 0; + } +} + +.mic-hint { + font-size: 13px; + color: var(--text-dim); + letter-spacing: 0.01em; + transition: color 0.3s ease; +} + +/* ─── History ─── */ #history-section { flex: 1; min-height: 0; @@ -192,102 +313,145 @@ header h1 { flex-direction: column; overflow: hidden; } + .history-header { display: flex; align-items: center; justify-content: space-between; - padding-bottom: 8px; + padding-bottom: 10px; flex-shrink: 0; } + .history-header h2 { - font-size: 15px; + font-size: 13px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; - letter-spacing: 0.04em; + letter-spacing: 0.06em; } + .text-btn { background: none; border: none; - color: var(--accent); - font-size: 13px; + color: var(--text-dim); + font-size: 12px; + font-weight: 500; cursor: pointer; - padding: 4px 8px; - border-radius: 6px; - transition: background 0.2s; + padding: 4px 10px; + border-radius: var(--radius-sm); + transition: all 0.15s ease; } + .text-btn:active { - background: rgba(59, 130, 246, 0.1); + color: var(--danger); + background: rgba(244, 63, 94, 0.08); } + #history-list { list-style: none; flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; } + #history-list li { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); - padding: 12px 14px; + padding: 14px 16px; margin-bottom: 8px; font-size: 14px; - line-height: 1.4; + line-height: 1.5; cursor: pointer; - transition: background 0.15s; + transition: all 0.15s ease; display: flex; align-items: flex-start; - gap: 10px; + gap: 12px; + animation: slide-up 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; + animation-delay: calc(var(--i, 0) * 40ms); } + #history-list li:active { - background: var(--surface-hover); + background: var(--surface-active); + border-color: var(--border-active); + transform: scale(0.985); } + #history-list li .hist-text { flex: 1; word-break: break-word; } + #history-list li .hist-time { font-size: 11px; color: var(--text-dim); white-space: nowrap; flex-shrink: 0; padding-top: 2px; + font-variant-numeric: tabular-nums; } + +@keyframes slide-up { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + #history-empty { text-align: center; - padding: 32px 0; + padding: 40px 0; } + .placeholder { color: var(--text-dim); font-size: 14px; } -/* Scrollbar */ + +/* ─── Scrollbar ─── */ +.preview-box::-webkit-scrollbar, #history-list::-webkit-scrollbar { - width: 4px; + width: 3px; } + +.preview-box::-webkit-scrollbar-track, #history-list::-webkit-scrollbar-track { background: transparent; } + +.preview-box::-webkit-scrollbar-thumb, #history-list::-webkit-scrollbar-thumb { background: var(--border); - border-radius: 2px; + border-radius: 3px; } -/* Toast */ + +/* ─── Toast ─── */ .toast { position: fixed; - bottom: calc(100px + var(--safe-bottom, 0px)); + bottom: calc(80px + var(--safe-bottom, 0px)); left: 50%; - transform: translateX(-50%); - background: #222; - color: #eee; - padding: 8px 18px; - border-radius: 20px; - font-size: 14px; + transform: translateX(-50%) translateY(8px); + background: rgba(28, 28, 37, 0.85); + color: var(--text); + padding: 10px 22px; + border-radius: 24px; + font-size: 13px; + font-weight: 500; z-index: 999; opacity: 0; - transition: opacity 0.3s; + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); pointer-events: none; + border: 1px solid var(--border); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); } + .toast.show { opacity: 1; + transform: translateX(-50%) translateY(0); }