功能:記分板側欄新增得分紀錄清單,並讓左右兩欄底部切齊
根本原因: 計分時只能看到當前比分,沒有逐球紀錄可回顧;且右側欄在「比賽結算」下方留有大片閒置空間。原本 .scoreboard-screen 用 align-items: start,記分板維持自然高度、側欄卻撐滿,導致兩欄底部不齊。 影響: 計分頁右側欄在比賽結算按鈕下方顯示逐球得分表:每列一球,得分隊亮綠標記並顯示當下比分,最新在最上、可上下捲動看舊資料;記分板與側欄底部切齊、不超出畫面,多出的高度分配給上下分數面板,中間橫幅維持原高度。 修法: App 把既有 pointLog 傳入 ScoreboardPage,新增 pointHistoryRows 反序累計比分並渲染 rail-history 清單;CSS 將 .scoreboard-screen 改 align-items: stretch(配合 align-self: start + min-height 100% 讓內容過高時可自然長高不重疊),.scoreboard-court 以 minmax(min-content, 1fr) 把撐高空間讓給隊伍區與分數面板;行動版清單改跨滿整行並以 32dvh 限高。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+272
-21
@@ -10,12 +10,28 @@
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit {
|
||||
min-height: 100dvh;
|
||||
height: 100dvh;
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
padding-bottom: max(12px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 記分板為單屏鎖定(body overflow hidden),標題列必須跟著視窗高度縮放。 */
|
||||
.app-shell-scoreboard-fit .eyebrow {
|
||||
margin-bottom: 6px;
|
||||
padding: 5px 10px;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .branding h1 {
|
||||
margin: 4px 0 0;
|
||||
font-size: clamp(1.1rem, 2.6dvh, 1.6rem);
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .nav-pill {
|
||||
padding: clamp(6px, 1.2dvh, 11px) 14px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -535,9 +551,12 @@
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 160px;
|
||||
gap: 14px;
|
||||
align-items: start;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
/* 記分板與右側欄一起撐滿可用高度,讓兩邊底部切齊;
|
||||
高度不足時允許整體長高改用頁面捲動,避免內容溢出背景。
|
||||
align-self: start 解除 shell 的 stretch 鎖定,讓內容能超過 1fr 列高。 */
|
||||
align-items: stretch;
|
||||
align-self: start;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.streak-banner {
|
||||
@@ -675,6 +694,8 @@
|
||||
|
||||
.scoreboard-court {
|
||||
display: grid;
|
||||
/* 被撐高時,多的空間分給上下兩個隊伍區,中間橫幅維持自然高度。 */
|
||||
grid-template-rows: minmax(min-content, 1fr) auto minmax(min-content, 1fr);
|
||||
gap: 14px;
|
||||
min-height: 0;
|
||||
padding: 12px;
|
||||
@@ -690,6 +711,15 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 隊伍區被撐高時,多的空間給分數面板(上方隊分數在第 3 列、下方隊在第 1 列)。 */
|
||||
.scoreboard-court > .scoreboard-team-section:first-child {
|
||||
grid-template-rows: auto auto minmax(min-content, 1fr);
|
||||
}
|
||||
|
||||
.scoreboard-court > .scoreboard-team-section:last-child {
|
||||
grid-template-rows: minmax(min-content, 1fr) auto auto;
|
||||
}
|
||||
|
||||
.scoreboard-team-head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 62px;
|
||||
@@ -701,7 +731,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
min-height: 64px;
|
||||
min-height: clamp(48px, 6.5dvh, 64px);
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 248, 232, 0.92);
|
||||
@@ -790,7 +820,7 @@
|
||||
grid-template-columns: 32px auto 1fr;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 46px;
|
||||
min-height: clamp(34px, 4.8dvh, 46px);
|
||||
width: min(100%, 440px);
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
@@ -888,7 +918,7 @@
|
||||
.score-panel-surface {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 172px;
|
||||
min-height: clamp(92px, 17dvh, 172px);
|
||||
border: 0;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
@@ -917,7 +947,7 @@
|
||||
|
||||
.score-panel-value {
|
||||
font-family: var(--mono);
|
||||
font-size: clamp(4.4rem, 11vw, 7.2rem);
|
||||
font-size: clamp(2.8rem, min(11vw, 12dvh), 7.2rem);
|
||||
line-height: 1;
|
||||
color: #0d544a;
|
||||
text-shadow: 0 2px 0 rgba(255, 255, 255, 0.42);
|
||||
@@ -931,7 +961,7 @@
|
||||
}
|
||||
|
||||
.scoreboard-center-banner p {
|
||||
font-size: clamp(1.5rem, 3vw, 2.3rem);
|
||||
font-size: clamp(1.15rem, min(3vw, 2.4dvh), 2.3rem);
|
||||
color: #fff7e9;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -942,9 +972,101 @@
|
||||
}
|
||||
|
||||
.scoreboard-rail {
|
||||
display: grid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-content: start;
|
||||
align-self: stretch;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.rail-history {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
background: rgba(8, 47, 73, 0.72);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.rail-history-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
padding: 0 2px;
|
||||
color: #f7fff8;
|
||||
}
|
||||
|
||||
.rail-history-head span {
|
||||
font-size: 0.82rem;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.rail-history-head small {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.78rem;
|
||||
color: rgba(247, 255, 248, 0.66);
|
||||
}
|
||||
|
||||
.rail-history-scroll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-y;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.rail-history-row {
|
||||
display: grid;
|
||||
grid-template-columns: 26px minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 4px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.rail-history-index {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 26px;
|
||||
border-radius: 6px;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.74rem;
|
||||
color: rgba(247, 255, 248, 0.62);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.rail-history-cell {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 26px;
|
||||
border-radius: 6px;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.94rem;
|
||||
font-weight: 600;
|
||||
color: #16342f;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.rail-history-cell-win {
|
||||
color: #24331a;
|
||||
background: linear-gradient(180deg, #ebf8a4, #d6e164);
|
||||
box-shadow: inset 0 0 0 1px rgba(111, 128, 27, 0.28);
|
||||
}
|
||||
|
||||
.rail-history-empty {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: 1 1 0;
|
||||
margin: 0;
|
||||
font-size: 0.82rem;
|
||||
color: rgba(247, 255, 248, 0.5);
|
||||
}
|
||||
|
||||
.rail-icon-grid {
|
||||
@@ -954,7 +1076,7 @@
|
||||
}
|
||||
|
||||
.rail-square-button {
|
||||
min-height: 52px;
|
||||
min-height: clamp(40px, 5.5dvh, 52px);
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
@@ -985,10 +1107,10 @@
|
||||
.rail-clock {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 68px;
|
||||
min-height: clamp(44px, 6dvh, 68px);
|
||||
border-radius: 12px;
|
||||
font-family: var(--mono);
|
||||
font-size: 1.7rem;
|
||||
font-size: clamp(1.1rem, 2.4dvh, 1.7rem);
|
||||
color: #fff7e9;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(255, 205, 96, 0.16), transparent 28%),
|
||||
@@ -1915,14 +2037,13 @@
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 手機單屏空間有限,裝飾用的英文膠囊直接省下高度。 */
|
||||
.app-shell-scoreboard-fit .eyebrow {
|
||||
margin-bottom: 6px;
|
||||
padding: 6px 10px;
|
||||
font-size: 0.66rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .branding h1 {
|
||||
margin: 8px 0 6px;
|
||||
margin: 4px 0 2px;
|
||||
font-size: 1.12rem;
|
||||
}
|
||||
|
||||
@@ -2046,16 +2167,48 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* 直向堆疊:court 保證至少內容高(不會被壓縮造成重疊),
|
||||
rail 拿剩餘空間,歷史區在 rail 內自動填滿、吸收各機型高度差。 */
|
||||
.scoreboard-screen {
|
||||
height: 100%;
|
||||
grid-template-rows: minmax(min-content, auto) minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.scoreboard-rail {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 94px 106px;
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
align-self: stretch;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 說明文字改單行省略,避免窄螢幕折行把發球條撐高。 */
|
||||
.serve-lane {
|
||||
grid-template-columns: 26px auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.serve-lane small {
|
||||
grid-column: auto;
|
||||
justify-self: end;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rail-pill-hold-wrap {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.rail-history {
|
||||
grid-column: 1 / -1;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
max-height: 32dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.rail-square-button {
|
||||
min-height: 38px;
|
||||
font-size: 0.84rem;
|
||||
@@ -2270,7 +2423,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) and (max-height: 840px) {
|
||||
@media (max-width: 720px) and (max-height: 960px) {
|
||||
.app-shell-scoreboard-fit .scoreboard-screen {
|
||||
gap: 5px;
|
||||
}
|
||||
@@ -2285,16 +2438,114 @@
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .score-panel-surface {
|
||||
min-height: clamp(82px, 14.2dvh, 96px);
|
||||
min-height: clamp(70px, 12dvh, 88px);
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .rail-square-button {
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .rail-icon-grid {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .score-panel-value {
|
||||
font-size: clamp(2.7rem, 13.5vw, 4.1rem);
|
||||
font-size: clamp(2.4rem, min(13.5vw, 10dvh), 4.1rem);
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .scoreboard-center-banner p {
|
||||
font-size: 0.96rem;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .scoreboard-center-banner small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .team-head-main {
|
||||
min-height: 40px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .scoreboard-name-chip {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .serve-lane {
|
||||
min-height: 30px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .rail-history {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 更矮的手機(iPhone SE 等):全面壓縮,確保單屏塞得下。 */
|
||||
@media (max-width: 720px) and (max-height: 700px) {
|
||||
.app-shell-scoreboard-fit .branding h1 {
|
||||
margin: 2px 0 0;
|
||||
font-size: 0.98rem;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .nav-pill {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.74rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 極矮螢幕導覽列改單行橫向捲動,省下折行高度。 */
|
||||
.app-shell-scoreboard-fit .topnav {
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .topbar-compact {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .score-panel-surface {
|
||||
min-height: clamp(48px, 10dvh, 70px);
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .score-panel-value {
|
||||
font-size: clamp(1.9rem, 8.5dvh, 2.7rem);
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .team-head-main {
|
||||
min-height: 34px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .scoreboard-name-chip {
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .serve-lane {
|
||||
min-height: 26px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .scoreboard-center-banner p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .rail-square-button,
|
||||
.app-shell-scoreboard-fit .rail-clock {
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.app-shell-scoreboard-fit .rail-pill {
|
||||
padding: 5px 7px;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 極矮螢幕(iPhone 5 世代)塞不下有意義的得分紀錄,直接隱藏。 */
|
||||
@media (max-width: 720px) and (max-height: 600px) {
|
||||
.app-shell-scoreboard-fit .rail-history {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.room-list-grid {
|
||||
|
||||
@@ -1115,6 +1115,7 @@ function App() {
|
||||
leftTeam={leftTeam}
|
||||
liveRoomId={liveRoomId}
|
||||
nextMatchSignal={nextMatchSignal}
|
||||
pointLog={pointLog}
|
||||
rightTeam={rightTeam}
|
||||
scoreState={scoreState}
|
||||
playCounts={playCounts}
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
CourtSide,
|
||||
GroupTeam,
|
||||
PlayerSlot,
|
||||
PointHistoryEntry,
|
||||
RoundGroup,
|
||||
ScoreSide,
|
||||
ScoreState,
|
||||
@@ -47,6 +48,7 @@ type ScoreboardPageProps = {
|
||||
liveRoomId: string | null
|
||||
nextMatchSignal: number
|
||||
playCounts: Record<string, number>
|
||||
pointLog: PointHistoryEntry[]
|
||||
rightTeam: GroupTeam | null
|
||||
scoreState: ScoreState
|
||||
selectedGroup: RoundGroup | null
|
||||
@@ -99,6 +101,7 @@ export function ScoreboardPage({
|
||||
liveRoomId,
|
||||
nextMatchSignal,
|
||||
playCounts,
|
||||
pointLog,
|
||||
rightTeam,
|
||||
scoreState,
|
||||
selectedGroup,
|
||||
@@ -216,6 +219,37 @@ export function ScoreboardPage({
|
||||
[rightTeam, scoreState.rightRightCourtPlayer],
|
||||
)
|
||||
|
||||
// 依 pointLog 累積雙方比分,最新一球排最上面。winner 0=上方(左)隊、1=下方(右)隊。
|
||||
const pointHistoryRows = useMemo(() => {
|
||||
const rows: Array<{
|
||||
rally: number
|
||||
winner: 0 | 1
|
||||
leftScore: number
|
||||
rightScore: number
|
||||
}> = []
|
||||
let left = 0
|
||||
let right = 0
|
||||
|
||||
for (const point of pointLog) {
|
||||
if (point.winner === 0) {
|
||||
left += 1
|
||||
} else {
|
||||
right += 1
|
||||
}
|
||||
|
||||
rows.push({
|
||||
rally: point.round + 1,
|
||||
winner: point.winner,
|
||||
leftScore: left,
|
||||
rightScore: right,
|
||||
})
|
||||
}
|
||||
|
||||
rows.reverse()
|
||||
|
||||
return rows
|
||||
}, [pointLog])
|
||||
|
||||
const currentServer =
|
||||
scoreState.serving === 'left'
|
||||
? leftTeam
|
||||
@@ -619,6 +653,43 @@ export function ScoreboardPage({
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="rail-history">
|
||||
<div className="rail-history-head">
|
||||
<span>得分紀錄</span>
|
||||
<small>{pointHistoryRows.length}</small>
|
||||
</div>
|
||||
|
||||
{pointHistoryRows.length > 0 ? (
|
||||
<div className="rail-history-scroll">
|
||||
{pointHistoryRows.map((row) => (
|
||||
<div className="rail-history-row" key={row.rally}>
|
||||
<span className="rail-history-index">{row.rally}</span>
|
||||
<span
|
||||
className={
|
||||
row.winner === 0
|
||||
? 'rail-history-cell rail-history-cell-win'
|
||||
: 'rail-history-cell'
|
||||
}
|
||||
>
|
||||
{row.winner === 0 ? row.leftScore : ''}
|
||||
</span>
|
||||
<span
|
||||
className={
|
||||
row.winner === 1
|
||||
? 'rail-history-cell rail-history-cell-win'
|
||||
: 'rail-history-cell'
|
||||
}
|
||||
>
|
||||
{row.winner === 1 ? row.rightScore : ''}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="rail-history-empty">尚無得分紀錄</p>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user