修正記分板在 iPad 尺寸的版面與修復 dev server 啟動問題

This commit is contained in:
2026-05-18 17:40:58 +08:00
parent 30a8e1a44c
commit 3677162747
7 changed files with 4599 additions and 4598 deletions

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
/*.stackdump

View File

@@ -6,7 +6,7 @@
"scripts": {
"dev": "concurrently \"npm:dev:server\" \"npm:dev:client\"",
"dev:client": "vite",
"dev:server": "node --watch server/server.mjs",
"dev:server": "node server/server.mjs",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +1,38 @@
:root {
--page-bg: #eff5df;
--page-bg-2: #dbe8c6;
--panel-strong: #0a332d;
--panel-soft: #587169;
--border: rgba(7, 51, 44, 0.12);
--shadow:
0 24px 60px rgba(19, 43, 34, 0.12),
0 8px 20px rgba(19, 43, 34, 0.08);
--sans: 'Bahnschrift', 'Trebuchet MS', sans-serif;
--heading: 'Arial Black', 'Bahnschrift', sans-serif;
--mono: 'Consolas', 'Courier New', monospace;
font: 18px/1.5 var(--sans);
color: var(--panel-strong);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
-webkit-user-select: none;
user-select: none;
}
:root {
--page-bg: #eff5df;
--page-bg-2: #dbe8c6;
--panel-strong: #0a332d;
--panel-soft: #587169;
--border: rgba(7, 51, 44, 0.12);
--shadow:
0 24px 60px rgba(19, 43, 34, 0.12),
0 8px 20px rgba(19, 43, 34, 0.08);
--sans: 'Bahnschrift', 'Trebuchet MS', sans-serif;
--heading: 'Arial Black', 'Bahnschrift', sans-serif;
--mono: 'Consolas', 'Courier New', monospace;
font: 18px/1.5 var(--sans);
color: var(--panel-strong);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
-webkit-user-select: none;
user-select: none;
}
html {
min-height: 100%;
}
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at top left, rgba(255, 214, 10, 0.35), transparent 28%),
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at top left, rgba(255, 214, 10, 0.35), transparent 28%),
radial-gradient(circle at bottom right, rgba(11, 88, 73, 0.2), transparent 32%),
linear-gradient(180deg, var(--page-bg), var(--page-bg-2));
}
@@ -43,17 +43,17 @@ body.body-scoreboard {
}
body::before {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(255, 255, 255, 0.14) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.14) 1px, transparent 1px);
background-size: 48px 48px;
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.45), transparent 78%);
}
content: '';
position: fixed;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(255, 255, 255, 0.14) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.14) 1px, transparent 1px);
background-size: 48px 48px;
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.45), transparent 78%);
}
#root {
min-height: 100vh;
}
@@ -62,46 +62,46 @@ body.body-scoreboard #root {
min-height: 100dvh;
overflow: hidden;
}
input,
textarea,
select,
button,
[contenteditable='true'] {
-webkit-user-select: auto;
user-select: auto;
}
h1,
h2,
h3,
p {
margin: 0;
}
h1,
h2,
h3 {
font-family: var(--heading);
letter-spacing: 0.02em;
}
h1 {
margin: 18px 0 14px;
font-size: clamp(2.8rem, 8vw, 5rem);
line-height: 0.94;
}
h2 {
font-size: clamp(1.6rem, 3vw, 2.2rem);
}
p {
color: var(--panel-soft);
}
@media (max-width: 720px) {
:root {
font-size: 16px;
}
}
input,
textarea,
select,
button,
[contenteditable='true'] {
-webkit-user-select: auto;
user-select: auto;
}
h1,
h2,
h3,
p {
margin: 0;
}
h1,
h2,
h3 {
font-family: var(--heading);
letter-spacing: 0.02em;
}
h1 {
margin: 18px 0 14px;
font-size: clamp(2.8rem, 8vw, 5rem);
line-height: 0.94;
}
h2 {
font-size: clamp(1.6rem, 3vw, 2.2rem);
}
p {
color: var(--panel-soft);
}
@media (max-width: 720px) {
:root {
font-size: 16px;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +1,40 @@
export type LoadStatus = 'idle' | 'loading' | 'loaded' | 'empty' | 'error'
export type ScoreSide = 'left' | 'right'
export type PlayerSlot = 'playerA' | 'playerB'
export type CourtSide = 'left' | 'right'
export type GroupTeam = {
id: number
playerA: string
playerB: string
isPlaceholderA: boolean
isPlaceholderB: boolean
}
export type RoundGroup = {
id: number
teams: GroupTeam[]
}
export type MatchResultsRecord = {
time: number
personnel: string
battlecombination: string | null
}
export type Matchup = {
leftTeamId: number | null
rightTeamId: number | null
}
export type ActiveMatchup = {
leftTeam: GroupTeam | null
rightTeam: GroupTeam | null
}
export type LoadStatus = 'idle' | 'loading' | 'loaded' | 'empty' | 'error'
export type ScoreSide = 'left' | 'right'
export type PlayerSlot = 'playerA' | 'playerB'
export type CourtSide = 'left' | 'right'
export type GroupTeam = {
id: number
playerA: string
playerB: string
isPlaceholderA: boolean
isPlaceholderB: boolean
}
export type RoundGroup = {
id: number
teams: GroupTeam[]
}
export type MatchResultsRecord = {
time: number
personnel: string
battlecombination: string | null
}
export type Matchup = {
leftTeamId: number | null
rightTeamId: number | null
}
export type ActiveMatchup = {
leftTeam: GroupTeam | null
rightTeam: GroupTeam | null
}
export type ScoreState = {
scoreLeft: number
scoreRight: number
@@ -47,117 +47,117 @@ export type ScoreState = {
leftRightCourtPlayer: PlayerSlot
rightRightCourtPlayer: PlayerSlot
}
export type PointHistoryEntry = {
round: number
starter: number
winCount: number
winner: 0 | 1
}
export type ScoreSnapshot = {
pointLog: PointHistoryEntry[]
scoreState: ScoreState
}
export type MatchHistoryItem = {
id: string
playedAt: string
matchDate: string
source: 'db' | 'manual' | 'idle'
groupId: number
leftTeamName: string
rightTeamName: string
scoreLeft: number
scoreRight: number
winner: string
}
export type HistoryUploadPayload = {
dayOfWeek: number
players: string[]
score: [number, number]
scoreList: Array<[number, number, number, 0 | 1]>
team: [string[], string[]]
time: number
type: 0 | 1
winScore: number
}
export type HistoryUploadResponse = {
id: number
}
export type HistoryRecord = {
id: number
time: number
dayOfWeek: number
score: string
winScore: number
type: 0 | 1
players: string
team: string
scoreList: string | null
}
export type HistoryListItem = {
id: number
time: number
playedAt: string
dayOfWeek: number
dayLabel: string
score: [number, number]
winScore: number
type: 0 | 1
typeLabel: string
players: string[]
team: [string[], string[]]
scoreList: Array<[number, number, number, 0 | 1]>
leftTeamName: string
rightTeamName: string
winnerTeamName: string
}
export type LiveRoomStatus = 'live' | 'finished'
export type LiveRoomSession = {
hostToken: string
roomId: string
status: LiveRoomStatus
}
export type LiveRoomSummary = {
roomId: string
createdAt: string
leftTeamName: string
rightTeamName: string
scoreLeft: number
scoreRight: number
status: LiveRoomStatus
targetScore: number
updatedAt: string
}
export type LiveRoomPayload = {
groupId: number | null
leftTeamName: string
matchupLabel: string
pointLog: PointHistoryEntry[]
rightTeamName: string
scoreState: ScoreState
targetDate: string
}
export type LiveRoomDetail = LiveRoomPayload & {
createdAt: string
roomId: string
status: LiveRoomStatus
updatedAt: string
winnerTeamName: string | null
}
export type LiveRoomUpdatePayload = LiveRoomPayload & {
hostToken: string
status: LiveRoomStatus
winnerTeamName: string | null
}
export type PointHistoryEntry = {
round: number
starter: number
winCount: number
winner: 0 | 1
}
export type ScoreSnapshot = {
pointLog: PointHistoryEntry[]
scoreState: ScoreState
}
export type MatchHistoryItem = {
id: string
playedAt: string
matchDate: string
source: 'db' | 'manual' | 'idle'
groupId: number
leftTeamName: string
rightTeamName: string
scoreLeft: number
scoreRight: number
winner: string
}
export type HistoryUploadPayload = {
dayOfWeek: number
players: string[]
score: [number, number]
scoreList: Array<[number, number, number, 0 | 1]>
team: [string[], string[]]
time: number
type: 0 | 1
winScore: number
}
export type HistoryUploadResponse = {
id: number
}
export type HistoryRecord = {
id: number
time: number
dayOfWeek: number
score: string
winScore: number
type: 0 | 1
players: string
team: string
scoreList: string | null
}
export type HistoryListItem = {
id: number
time: number
playedAt: string
dayOfWeek: number
dayLabel: string
score: [number, number]
winScore: number
type: 0 | 1
typeLabel: string
players: string[]
team: [string[], string[]]
scoreList: Array<[number, number, number, 0 | 1]>
leftTeamName: string
rightTeamName: string
winnerTeamName: string
}
export type LiveRoomStatus = 'live' | 'finished'
export type LiveRoomSession = {
hostToken: string
roomId: string
status: LiveRoomStatus
}
export type LiveRoomSummary = {
roomId: string
createdAt: string
leftTeamName: string
rightTeamName: string
scoreLeft: number
scoreRight: number
status: LiveRoomStatus
targetScore: number
updatedAt: string
}
export type LiveRoomPayload = {
groupId: number | null
leftTeamName: string
matchupLabel: string
pointLog: PointHistoryEntry[]
rightTeamName: string
scoreState: ScoreState
targetDate: string
}
export type LiveRoomDetail = LiveRoomPayload & {
createdAt: string
roomId: string
status: LiveRoomStatus
updatedAt: string
winnerTeamName: string | null
}
export type LiveRoomUpdatePayload = LiveRoomPayload & {
hostToken: string
status: LiveRoomStatus
winnerTeamName: string | null
}