From b3809b5d4f94a9e2dd32982afe02b58c27553f99 Mon Sep 17 00:00:00 2001 From: JianMiau Date: Thu, 16 Apr 2026 16:49:02 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A3=9C=E4=B8=8A=E5=85=88=E6=94=BB=E5=8B=BE?= =?UTF-8?q?=E9=81=B8=E9=A1=AF=E7=A4=BA=E4=B8=A6=E6=9B=B4=E6=96=B0=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ src/App.css | 56 ++++++++++++++++++++++++++++++++++++ src/pages/ScoreboardPage.tsx | 17 +++++++++-- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f69c9a..000eea4 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ - 右側快速套用預設隊伍 - 可設定幾分獲勝,預設 `21` - 必須先設定先攻,之後點分數即可直接加分 + - 尚未設定先攻時,`先攻` 文字會做動畫提醒 + - 選定先攻後,該方的先攻方框會直接顯示打勾 - 第一分後 `設定隊伍` 會改成 `上一步` - 支援上下交換兩隊位置、左右交換隊內站位 - 歷史戰績頁 diff --git a/src/App.css b/src/App.css index 8fafc13..8b00cac 100644 --- a/src/App.css +++ b/src/App.css @@ -547,6 +547,7 @@ } .serve-lane { + position: relative; display: grid; grid-template-columns: 32px auto 1fr; align-items: center; @@ -579,11 +580,29 @@ 0 10px 18px rgba(8, 47, 73, 0.12); } +.serve-lane-prompt { + background: rgba(255, 248, 232, 0.12); + box-shadow: inset 0 0 0 1px rgba(255, 236, 202, 0.12); +} + +.serve-lane-arrow { + display: none; +} + .serve-lane small { justify-self: end; color: rgba(248, 244, 234, 0.72); } +.serve-lane-prompt > span:nth-of-type(2) { + color: #f7ffbe; + font-weight: 800; + text-shadow: + 0 0 12px rgba(235, 248, 164, 0.5), + 0 0 22px rgba(235, 248, 164, 0.24); + animation: serve-label-pulse 0.9s ease-in-out infinite; +} + .serve-lane-locked { box-shadow: inset 0 0 0 1px rgba(214, 225, 100, 0.42); } @@ -593,6 +612,39 @@ height: 24px; border-radius: 4px; background: linear-gradient(180deg, #fff8e8, #f0dfbd); + box-shadow: inset 0 0 0 1px rgba(195, 154, 88, 0.22); +} + +.serve-lane-box-checked { + position: relative; + background: linear-gradient(180deg, #ebf8a4, #d6e164); + box-shadow: + inset 0 0 0 1px rgba(111, 128, 27, 0.26), + 0 0 0 1px rgba(214, 225, 100, 0.14); +} + +.serve-lane-box-checked::after { + content: '✓'; + position: absolute; + inset: 0; + display: grid; + place-items: center; + font-size: 1rem; + font-weight: 800; + color: #38501f; +} + +@keyframes serve-label-pulse { + 0%, + 100% { + transform: scale(1); + opacity: 0.82; + } + + 50% { + transform: scale(1.08); + opacity: 1; + } } .score-panel-surface { @@ -1479,6 +1531,10 @@ padding: 4px 6px; } + .serve-lane-arrow { + left: 52px; + } + .team-number { width: 28px; height: 28px; diff --git a/src/pages/ScoreboardPage.tsx b/src/pages/ScoreboardPage.tsx index 4de119c..8039383 100644 --- a/src/pages/ScoreboardPage.tsx +++ b/src/pages/ScoreboardPage.tsx @@ -276,6 +276,7 @@ export function ScoreboardPage({ onSwapTeams={onSwapMatchup} score={scoreState.scoreLeft} serviceCourt={scoreState.serving === 'left' ? servingCourt : null} + showServingPrompt={scoreState.serving === null} team={leftTeam} teamSlot="top" /> @@ -303,6 +304,7 @@ export function ScoreboardPage({ onSwapTeams={onSwapMatchup} score={scoreState.scoreRight} serviceCourt={scoreState.serving === 'right' ? servingCourt : null} + showServingPrompt={scoreState.serving === null} team={rightTeam} teamSlot="bottom" /> @@ -379,6 +381,7 @@ type ScoreboardTeamPanelProps = { onSwapTeams: () => void score: number serviceCourt: CourtSide | null + showServingPrompt: boolean team: GroupTeam | null teamSlot: 'top' | 'bottom' } @@ -395,6 +398,7 @@ function ScoreboardTeamPanel({ onSwapTeams, score, serviceCourt, + showServingPrompt, team, teamSlot, }: ScoreboardTeamPanelProps) { @@ -450,13 +454,22 @@ function ScoreboardTeamPanel({ const serveBar = (