From 6402ef268cf376bc905c1d6ab1d7850fc1774dfd Mon Sep 17 00:00:00 2001 From: Josh-Heaps Date: Wed, 10 Jun 2026 16:58:35 -0600 Subject: [PATCH] Rework the chess page into a chess.com-style layout Board with player bars and captured trays on the left, a game panel with move list, status, and action buttons on the right. The page is locked to the viewport (no scrolling); on phones the panel collapses into a slide-up menu reachable from a slim status bar. Cache-busts the chess scripts too. Co-Authored-By: Claude Opus 4.8 (1M context) --- JoshHeaps.Net/Pages/Chess.cshtml | 71 ++- JoshHeaps.Net/Pages/_Layout.cshtml | 2 +- JoshHeaps.Net/wwwroot/css/chess/game.css | 10 +- JoshHeaps.Net/wwwroot/css/chess/site.css | 522 +++++++++++++++++------ 4 files changed, 450 insertions(+), 155 deletions(-) diff --git a/JoshHeaps.Net/Pages/Chess.cshtml b/JoshHeaps.Net/Pages/Chess.cshtml index 314b112..d87778c 100644 --- a/JoshHeaps.Net/Pages/Chess.cshtml +++ b/JoshHeaps.Net/Pages/Chess.cshtml @@ -6,7 +6,14 @@ }
-
+
+
+ + Opponent +
+ +
+
@for (int i = 0; i < 64; i++) @@ -14,18 +21,41 @@
}
-
-
-

Chess

-

Click a button to start a game :)

-
+
+ + You +
+ +
+ -
- - - - Watch other games → + + + + +
+ Start a game to play. +
@@ -61,17 +91,20 @@ @section Scripts { - - - - - - - - + + + + + + + + } @section Styles { + + + } \ No newline at end of file diff --git a/JoshHeaps.Net/Pages/_Layout.cshtml b/JoshHeaps.Net/Pages/_Layout.cshtml index d0a8f1a..a122e08 100644 --- a/JoshHeaps.Net/Pages/_Layout.cshtml +++ b/JoshHeaps.Net/Pages/_Layout.cshtml @@ -1,6 +1,6 @@ @{ Layout = null; - ViewData["cssVersion"] = "1.0.5"; // <--- change this once to bust cache + ViewData["cssVersion"] = "1.0.7"; // <--- change this once to bust cache } diff --git a/JoshHeaps.Net/wwwroot/css/chess/game.css b/JoshHeaps.Net/wwwroot/css/chess/game.css index 7ca7512..88640c4 100644 --- a/JoshHeaps.Net/wwwroot/css/chess/game.css +++ b/JoshHeaps.Net/wwwroot/css/chess/game.css @@ -1,12 +1,4 @@ -#boardContainer { - position: relative; - width: fit-content; - margin: 2vw auto; -} - -#chessBoard { - width: 60vw; - height: 60vw; +#chessBoard { display: grid; grid-template-columns: repeat(8, 1fr); grid-template-rows: repeat(8, 1fr); diff --git a/JoshHeaps.Net/wwwroot/css/chess/site.css b/JoshHeaps.Net/wwwroot/css/chess/site.css index bdf63a9..6e20e1f 100644 --- a/JoshHeaps.Net/wwwroot/css/chess/site.css +++ b/JoshHeaps.Net/wwwroot/css/chess/site.css @@ -1,159 +1,429 @@ -html, body { - background-color: #2b2c30; - color: #d6d6d6; - cursor: default; +:root { + --bg: #2b2c30; + --panel: #21232a; + --panel-row: rgba(255, 255, 255, 0.035); + --bar: #1e2026; + --accent: #8cd5ed; + --accent-ink: #10222a; + --text: #d6d6d6; + --muted: #8b8f99; + --line: #34373f; + --bar-h: clamp(34px, 5.5vh, 50px); + --radius: 12px; + --font: 'Outfit', system-ui, -apple-system, Segoe UI, sans-serif; +} + +html, body { height: 100%; margin: 0; + background: var(--bg); + color: var(--text); + font-family: var(--font); + cursor: default; + /* The chess page is a fixed, single-screen app: never scroll. */ + overflow: hidden; } -#startGameBtn { - background-color: #8cd5ed; - color: #262626; - border-radius: 30px; - border: 0px; - cursor: pointer; - order: 1; - padding: 2vh; - margin: 2vh; +main { + height: 100%; } -#startCPUGame { - background-color: #8cd5ed; - color: #262626; - border-radius: 30px; - border: 0px; - cursor: pointer; - order: 1; - padding: 2vh; - margin: 2vh; -} - -#copyPgnBtn { - background-color: #8cd5ed; - color: #262626; - border-radius: 30px; - border: 0px; - cursor: pointer; - order: 1; - padding: 2vh; - margin: 2vh; -} - -#watchLink { - color: #8cd5ed; - text-decoration: none; - margin: 2vh; - order: 2; -} - -#watchLink:hover { - text-decoration: underline; -} +/* ---- Layout ------------------------------------------------------------ */ +/* Portrait / narrow: board on top, panel stacked below. */ #chessContainer { + height: 100vh; + height: 100dvh; + box-sizing: border-box; display: flex; flex-direction: column; align-items: center; - justify-content: space-evenly; - height: 100vh; - box-sizing: border-box; - padding: 5vw; + gap: clamp(8px, 1.6vh, 16px); + padding: clamp(10px, 2vh, 20px); + overflow: hidden; +} + +#boardArea { + display: flex; + flex-direction: column; + gap: 6px; + flex: 0 0 auto; + width: min(94vw, 56vh); } #chessBoard { - aspect-ratio: 1 / 1; - width: 90vw; /* use the smaller of width or height */ + width: 100%; height: auto; - max-height: 90vw; + aspect-ratio: 1 / 1; + border-radius: 6px; + overflow: hidden; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45); } -#boardContainer { - align-content: center; - flex-grow: 1; -} - -.sideContent { +#gamePanel { + width: min(94vw, 56vh); + flex: 1 1 auto; + min-height: 0; display: flex; flex-direction: column; - align-content: center; - justify-content: center; - text-align: center; + background: var(--panel); + border-radius: var(--radius); + overflow: hidden; + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35); } -#textContainer { - flex-direction: column; - order: -1; - flex-shrink: 1; - font-size: large; - margin: 5vw; -} - -#buttonContainer { - flex-grow: 3; -} - -/* Put UI to left/right when screen is short */ +/* Landscape / wide: board left, game panel right (chess.com style). */ @media (min-aspect-ratio: 1/1) { #chessContainer { - display: grid; - grid-template-columns: auto, auto; - grid-template-rows: auto, auto; - align-items: center; flex-direction: row; + align-items: center; justify-content: center; - align-items: center; - height: 100vh; - padding: 0; + gap: clamp(16px, 3vw, 48px); + padding: clamp(12px, 3vh, 28px); } - .sideContent { - display: flex; - flex-direction: column; - flex-grow: 1; - flex-shrink: 0; - align-items: center; - text-align: center; - padding: min(5vw, 5vh); - } - - #boardContainer { - grid-row: 1 / span 2; - grid-column: 1; - } - - #textContainer { - grid-row: 1; - grid-column: 2; - text-align: center; - font-size: x-large; - } - - #buttonContainer { - grid-row: 2; - grid-column: 2; - text-align: left; - align-self: start; - } - - #startGameBtn { - padding: 1vw; - font-size: large; - } - - #startCPUGame { - padding: 1vw; - margin: 1vw; - font-size: large; + #boardArea { + width: auto; + height: 100%; + justify-content: center; + flex: 0 0 auto; } #chessBoard { - aspect-ratio: 1 / 1; - flex-shrink: 1; - width: min(90vh, 90vw); - max-height: 90vh; + width: min(72vh, 54vw); + height: min(72vh, 54vw); } - #difficultyButtonContainer { - grid-template-columns: repeat(10, 1fr); + .playerBar { + width: min(72vh, 54vw); } -} \ No newline at end of file + + #gamePanel { + width: clamp(300px, 26vw, 380px); + height: min(86vh, 100%); + align-self: center; + } +} + +/* ---- Player bars ------------------------------------------------------- */ + +.playerBar { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + box-sizing: border-box; + height: var(--bar-h); + padding: 0 12px; + background: var(--bar); + border-radius: 8px; +} + +.playerDot { + width: 14px; + height: 14px; + border-radius: 50%; + flex: 0 0 auto; +} + +.playerDot.white { + background: #ededed; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4) inset; +} + +.playerDot.black { + background: #2c2c2c; + box-shadow: 0 0 0 1px #565656 inset; +} + +.playerName { + font-weight: 600; + white-space: nowrap; +} + +.capturedTray { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 1px; + flex: 1; + min-width: 0; + overflow: hidden; +} + +.capturedPiece { + height: clamp(15px, calc(var(--bar-h) * 0.58), 26px); + width: auto; + margin-right: -5px; +} + +.advantage { + margin-left: auto; + font-weight: 700; + color: var(--accent); + white-space: nowrap; +} + +/* ---- Game panel -------------------------------------------------------- */ + +.panelHeader { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 16px; + border-bottom: 1px solid var(--line); + flex: 0 0 auto; +} + +.panelLogo { + font-size: 1.5rem; + color: var(--accent); + line-height: 1; +} + +.panelHeader h1 { + margin: 0; + font-size: 1.25rem; + font-weight: 700; + letter-spacing: 0.01em; +} + +#moveList { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + padding: 6px 0; +} + +.movePlaceholder { + margin: 0; + padding: 18px 16px; + color: var(--muted); + font-size: 0.95rem; +} + +.moveRow { + display: grid; + grid-template-columns: 2.4em 1fr 1fr; + align-items: center; + gap: 6px; + padding: 4px 14px; + font-size: 0.98rem; +} + +.moveRow:nth-child(odd) { + background: var(--panel-row); +} + +.moveNum { + color: var(--muted); + font-variant-numeric: tabular-nums; +} + +.moveSan { + padding: 2px 7px; + border-radius: 5px; +} + +.moveSan.latest { + background: var(--accent); + color: var(--accent-ink); + font-weight: 600; +} + +#statusLine { + flex: 0 0 auto; + padding: 12px 16px; + border-top: 1px solid var(--line); + color: var(--muted); + font-weight: 500; +} + +#statusLine.alert { + color: var(--accent); + font-weight: 700; +} + +#panelButtons { + flex: 0 0 auto; + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px 16px 16px; + border-top: 1px solid var(--line); +} + +.btn { + font-family: inherit; + font-weight: 600; + font-size: clamp(0.92rem, 1vw, 1.05rem); + border: 0; + border-radius: 8px; + padding: clamp(10px, 1.4vh, 14px) 16px; + cursor: pointer; + transition: transform 0.08s ease, filter 0.15s ease, background 0.15s ease; +} + +.btn:active { + transform: translateY(1px); +} + +.btn-primary { + background: var(--accent); + color: var(--accent-ink); +} + +.btn-primary:hover { + filter: brightness(1.08); +} + +.btn-secondary { + background: #363a44; + color: var(--text); +} + +.btn-secondary:hover { + background: #424752; +} + +.btn-ghost { + background: transparent; + color: var(--accent); + border: 1px solid var(--line); +} + +.btn-ghost:hover { + background: #2a2e37; +} + +#watchLink { + color: var(--muted); + text-decoration: none; + text-align: center; + font-size: 0.9rem; + margin-top: 2px; +} + +#watchLink:hover { + color: var(--accent); +} + +/* ---- Mobile chrome (hidden on desktop) --------------------------------- */ + +.iconBtn { + background: transparent; + border: 0; + color: var(--text); + font-size: 1.7rem; + line-height: 1; + cursor: pointer; + padding: 0 4px; +} + +.iconBtn:hover { + color: var(--accent); +} + +#menuClose { + margin-left: auto; +} + +/* Default (tablet/desktop): no mobile bar, menu button, or sheet chrome. */ +#mobileBar, +#menuBackdrop, +#menuClose { + display: none; +} + +/* ---- Phone layout ------------------------------------------------------ */ +/* The full panel doesn't fit alongside a usable board on a phone, so we show + the board + a slim status/menu bar, and tuck the move list and secondary + actions into a slide-up sheet. The page itself still never scrolls. */ +@media (max-width: 640px) { + #chessContainer { + flex-direction: column; + justify-content: flex-start; + gap: 8px; + padding: 8px; + } + + #boardArea { + width: min(94vw, 60vh); + flex: 0 0 auto; + } + + .playerBar { + width: 100%; + } + + /* Right rail becomes a hidden bottom sheet, revealed by the Menu button. */ + #gamePanel { + display: none; + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: auto; + width: 100%; + height: auto; + max-height: 85dvh; + border-radius: 16px 16px 0 0; + z-index: 60; + box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.5); + } + + body.menu-open #gamePanel { + display: flex; + } + + /* The slim bar already shows status, so hide the sheet's inline copy. */ + #gamePanel #statusLine { + display: none; + } + + #menuClose { + display: block; + } + + #menuBackdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.55); + z-index: 55; + } + + body.menu-open #menuBackdrop { + display: block; + } + + #mobileBar { + display: flex; + align-items: center; + gap: 10px; + width: min(94vw, 60vh); + flex: 0 0 auto; + box-sizing: border-box; + background: var(--panel); + border-radius: 10px; + padding: 8px 10px; + } + + #mobileStatus { + flex: 1; + min-width: 0; + color: var(--muted); + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + #mobileStatus.alert { + color: var(--accent); + } + + #mobileBar #menuToggle { + flex: 0 0 auto; + padding: 10px 20px; + } +}