From b3b0e07c421f7f832d12259fab4be4f45fdc0612 Mon Sep 17 00:00:00 2001 From: bboysoul Date: Tue, 22 Sep 2026 10:11:37 +0800 Subject: [PATCH] feat: add restrained motion to pomodoro focus timer --- frontend/src/PomodoroPanel.vue | 9 ++++++--- frontend/src/style.css | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/PomodoroPanel.vue b/frontend/src/PomodoroPanel.vue index c84244c..0db3cbe 100644 --- a/frontend/src/PomodoroPanel.vue +++ b/frontend/src/PomodoroPanel.vue @@ -27,6 +27,9 @@ const remainingMs = computed(() => state.value.status === 'running' && state.val ? Math.max(0, state.value.endsAt - nowMs.value) : state.value.remainingMs) const clock = computed(() => formatPomodoroTime(remainingMs.value)) +const totalSeconds = computed(() => Math.max(0, Math.ceil(remainingMs.value / 1000))) +const clockMin = computed(() => String(Math.floor(totalSeconds.value / 60)).padStart(2, '0')) +const clockSec = computed(() => String(totalSeconds.value % 60).padStart(2, '0')) const phaseLabel = computed(() => state.value.phase === 'focus' ? '专注' : '休息') const statusLabel = computed(() => ({ idle: '准备开始', running: '进行中', paused: '已暂停', done: '已完成' })[state.value.status]) const progress = computed(() => { @@ -101,10 +104,10 @@ onUnmounted(() => {
-
{{ phaseLabel }}{{ statusLabel }}
- {{ clock }} +
{{ phaseLabel }}{{ statusLabel }}
+ {{ clockMin }}:{{ clockSec }}
-

{{ state.phase === 'focus' ? '留一点完整的时间,只做眼前这一件事。' : '离开屏幕,喝口水,再回来。' }}

+

{{ state.phase === 'focus' ? '留一点完整的时间,只做眼前这一件事。' : '离开屏幕,喝口水,再回来。' }}

diff --git a/frontend/src/style.css b/frontend/src/style.css index 14e7051..493b0ec 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -234,7 +234,7 @@ main:has(>.mvp-view .settings-sections) .settings-data>.inline-error{margin:14px @media(prefers-reduced-motion:reduce){:is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button){transition:none}:is(.primary,.primary-small,.soft-button,.secondary,.restore,.file-button,.danger-button):active:not(:disabled):not(.disabled){transform:none}} /* Local-first focus timer: continuous paper, one orange accent. */ -.pomodoro-view{width:min(100%,630px);margin:0 auto;padding:18px 0 64px}.pomodoro-heading{min-height:74px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e8e0d5}.pomodoro-heading h1{margin:4px 0 0;font-size:34px;line-height:1.1;letter-spacing:-.035em}.pomodoro-heading>svg{width:28px;height:28px;color:var(--accent)}.pomodoro-eyebrow{color:var(--muted);font-size:12px}.pomodoro-stage{padding:48px 0 32px;text-align:center;border-bottom:1px solid #e8e0d5}.pomodoro-stage-meta{display:flex;justify-content:center;align-items:baseline;gap:9px}.pomodoro-stage-meta span{font-size:14px;font-weight:750}.pomodoro-stage-meta small{color:var(--muted);font-size:11px}.pomodoro-clock{display:block;margin:18px 0 16px;font-size:clamp(68px,14vw,104px);font-weight:680;line-height:1;letter-spacing:-.065em;font-variant-numeric:tabular-nums}.pomodoro-progress{width:min(360px,80%);height:4px;margin:0 auto;background:#eadfce;overflow:hidden}.pomodoro-progress span{display:block;height:100%;background:var(--accent);transition:width .25s linear}.pomodoro-note{margin:23px auto 0;color:var(--muted);font-size:13px}.pomodoro-controls{min-height:52px;margin-top:28px;display:flex;justify-content:center;flex-wrap:wrap;gap:9px}.pomodoro-controls button{min-height:44px;min-width:112px}.pomodoro-summary{margin:14px 0 0;text-align:center;color:var(--muted);font-size:11px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:720px){.pomodoro-view{width:100%;padding:8px 12px calc(84px + var(--safe-area-bottom))}.pomodoro-heading h1{font-size:24px}.pomodoro-stage{padding-top:38px}.pomodoro-clock{font-size:clamp(64px,22vw,88px)}.pomodoro-controls button{min-height:44px;flex:1 1 128px}}@media(prefers-reduced-motion:reduce){.pomodoro-progress span{transition:none}} +.pomodoro-view{width:min(100%,630px);margin:0 auto;padding:18px 0 64px}.pomodoro-heading{min-height:74px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e8e0d5}.pomodoro-heading h1{margin:4px 0 0;font-size:34px;line-height:1.1;letter-spacing:-.035em}.pomodoro-heading>svg{width:28px;height:28px;color:var(--accent)}.pomodoro-eyebrow{color:var(--muted);font-size:12px}.pomodoro-stage{padding:48px 0 32px;text-align:center;border-bottom:1px solid #e8e0d5}.pomodoro-stage-meta{display:flex;justify-content:center;align-items:baseline;gap:9px}.pomodoro-stage-meta span{font-size:14px;font-weight:750}.pomodoro-stage-meta small{color:var(--muted);font-size:11px}.pomodoro-clock{display:block;margin:18px 0 16px;font-size:clamp(68px,14vw,104px);font-weight:680;line-height:1;letter-spacing:-.065em;font-variant-numeric:tabular-nums}.pomodoro-clock-min,.pomodoro-clock-sep,.pomodoro-clock-sec{display:inline-block}.pomodoro-clock-sec{min-width:1.28em;text-align:left;animation:pomodoro-tick .5s cubic-bezier(.2,.8,.3,1)}.pomodoro-clock-sep{transform:translateY(-.02em);color:var(--muted)}.pomodoro-stage-meta{-webkit-animation:pomodoro-phase-in .4s ease;animation:pomodoro-phase-in .4s ease}.pomodoro-note{-webkit-animation:pomodoro-phase-in .45s ease .03s both;animation:pomodoro-phase-in .45s ease .03s both}@keyframes pomodoro-tick{from{transform:translateY(26%);opacity:.55}to{transform:none;opacity:1}}@keyframes pomodoro-phase-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}.pomodoro-progress{width:min(360px,80%);height:4px;margin:0 auto;background:#eadfce;overflow:hidden}.pomodoro-progress span{display:block;height:100%;background:var(--accent);transition:width .25s linear}.pomodoro-note{margin:23px auto 0;color:var(--muted);font-size:13px}.pomodoro-controls{min-height:52px;margin-top:28px;display:flex;justify-content:center;flex-wrap:wrap;gap:9px}.pomodoro-controls button{min-height:44px;min-width:112px;transition:transform .14s ease,background-color .14s ease,border-color .14s ease,box-shadow .14s ease,color .14s ease}.pomodoro-controls button:hover:not(:disabled){transform:translateY(-2px)}.pomodoro-controls button:active:not(:disabled){transform:scale(.96)}.pomodoro-summary{margin:14px 0 0;text-align:center;color:var(--muted);font-size:11px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:720px){.pomodoro-view{width:100%;padding:8px 12px calc(84px + var(--safe-area-bottom))}.pomodoro-heading h1{font-size:24px}.pomodoro-stage{padding-top:38px}.pomodoro-clock{font-size:clamp(64px,22vw,88px)}.pomodoro-controls button{min-height:44px;flex:1 1 128px}}@media(prefers-reduced-motion:reduce){.pomodoro-progress span{transition:none}.pomodoro-clock-sec{animation:none}.pomodoro-stage-meta,.pomodoro-note{animation:none}.pomodoro-controls button{transition:none}.pomodoro-controls button:active:not(:disabled),.pomodoro-controls button:hover:not(:disabled){transform:none}} /* Approved task detail 01: paper flow. */ .detail{min-width:0;border-left:1px solid var(--line);background:#fffdf8;overflow:hidden;display:flex;flex-direction:column}.shell.detail-open{transition:none}