Files
chess/internal/server/webserver/chess-client-web/style.css
T
lixen d48a28fad6
test / test (push) Canceled after 0s
test / e2e (push) Canceled after 0s
v0.14.0 web replay UI, My games, CLI replay
2026-10-19 18:07:41 -04:00

1117 lines
29 KiB
CSS

/* Chess web client. Dark theme matching lixen.com. The board colours and
piece glyph treatment are fixed (see pieceMap in app.js); everything else is
open. Styles live here: the host's CSP has style-src 'unsafe-inline', but
state is expressed through classes and data attributes, never inline style.
Two layouts share the markup: board beside the panel from 979px up, and
board over the panel below (the host's embed frame switches its height at
the same width). Both must fit the host's frame: 30rem tall side by side,
46rem (capped at 80svh) stacked. */
*, *::before, *::after { box-sizing: border-box; }
:root {
/* Host-site palette */
--bg: #11111b;
--surface: #1a1b26;
--surface-2: #20222f;
--surface-3: #262a3d;
--line: #2d3148;
--line-strong: #3b4261;
--fg: #c0caf5;
--fg-soft: #a9b1d6;
--muted: #7982a9;
--royal: #5f57f5;
--royal-soft: #38348f;
--periwinkle: #8ea2ff;
--blue: #2563eb;
--blue-deep: #1e40af;
--white: #ffffff;
--cyan: #7dcfff;
--green: #9ece6a;
--yellow: #e0af68;
--red: #f7768e;
/* Board (fixed) */
--square-light: #f0d9b5;
--square-dark: #b58863;
--square-selected: #6a994e;
--move-from: #5090d3;
--move-to: #81b3f0;
--checkmated-king: #8b0000;
--radius: 8px;
--radius-sm: 6px;
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
/* Side-by-side sizes: the board edge includes its coordinate margin. It
grows with the window but never past what is left under the top bar
(the host's 30rem frame leaves 416px), so the embed keeps a 360px
board while a full window gets up to 560px. */
--topbar-height: 2.5rem;
--board-edge: clamp(360px, min(32vw, calc(100vh - 64px)), 560px);
--board-pad: clamp(16px, 1.04vw, 20px);
--panel-width: clamp(280px, 20vw, 380px);
}
html, body {
margin: 0;
padding: 0;
height: 100%;
}
body {
font-family: var(--font);
font-size: 15px;
line-height: 1.4;
color: var(--fg-soft);
background: var(--bg);
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
.visually-hidden {
position: absolute !important;
width: 1px; height: 1px;
margin: -1px; padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.hidden { display: none !important; }
.icon {
width: 1.1em;
height: 1.1em;
flex: none;
}
/* --- Frame ----------------------------------------------------------- */
.app {
height: 100%;
padding: 8px;
display: flex;
flex-direction: column;
gap: 8px;
overflow: hidden;
}
.topbar {
flex: none;
height: var(--topbar-height);
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0 0.75rem;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
}
.brand {
display: flex;
align-items: center;
gap: 0.4rem;
color: var(--fg);
font-weight: 600;
letter-spacing: 0.01em;
}
.brand-mark {
font-size: 1.35rem;
line-height: 1;
color: var(--periwinkle);
}
.health {
display: flex;
align-items: center;
gap: 0.35rem;
padding-left: 0.5rem;
border-left: 1px solid var(--line);
}
.topbar-spacer { flex: 1; }
/* Status dots: colour by data-status. */
.dot {
display: inline-block;
width: 0.6rem;
height: 0.6rem;
border-radius: 50%;
background: var(--line-strong);
flex: none;
transition: background 0.2s;
}
.dot[data-status="healthy"], .dot[data-status="ok"], .dot[data-status="authenticated"] { background: var(--green); }
.dot[data-status="disabled"], .dot[data-status="anonymous"] { background: var(--yellow); }
.dot[data-status="degraded"], .dot[data-status="error"] { background: var(--red); }
.dot[data-status="unknown"], .dot[data-status="idle"] { background: var(--line-strong); }
/* --- Buttons --------------------------------------------------------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
padding: 0.55rem 0.8rem;
border: 1px solid transparent;
border-radius: var(--radius-sm);
font-size: 0.9rem;
font-weight: 500;
line-height: 1.1;
color: var(--white);
background: var(--surface-3);
transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.15s;
white-space: nowrap;
}
.btn:hover:not(:disabled) { transform: translateY(-1px); }
.btn:focus-visible { outline: 2px solid var(--periwinkle); outline-offset: 2px; }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary { background: var(--blue); }
.btn-primary:hover:not(:disabled) { background: var(--royal); }
.btn-secondary { background: var(--blue-deep); }
.btn-secondary:hover:not(:disabled) { background: var(--royal); }
.btn-danger { background: var(--red); color: var(--bg); }
.btn-danger:hover:not(:disabled) { filter: brightness(1.1); }
.btn-ghost {
background: transparent;
border-color: var(--line);
color: var(--fg-soft);
}
.btn-ghost:hover:not(:disabled) { border-color: var(--royal); background: rgba(95, 87, 245, 0.15); color: var(--fg); }
.btn-sm { padding: 0.35rem 0.6rem; font-size: 0.85rem; }
.btn-icon {
padding: 0.3rem;
background: transparent;
color: var(--muted);
}
.btn-icon:hover:not(:disabled) { color: var(--fg); background: var(--surface-3); transform: none; }
.btn-block { width: 100%; }
/* Compact tool buttons in a row (export, replay nav). */
.btn-tool {
flex: 1;
padding: 0.4rem 0.3rem;
font-size: 0.78rem;
background: var(--surface-2);
border-color: var(--line);
color: var(--fg-soft);
}
.btn-tool:hover:not(:disabled) { border-color: var(--royal); color: var(--fg); transform: none; }
.btn-tool.done { border-color: var(--green); color: var(--green); }
.btn-nav {
width: 2.1rem;
height: 2.1rem;
padding: 0;
background: var(--surface-2);
border-color: var(--line);
color: var(--fg);
}
.btn-nav:hover:not(:disabled) { border-color: var(--royal); background: var(--surface-3); transform: none; }
.btn-nav .icon { width: 1.2rem; height: 1.2rem; }
.btn-play[aria-pressed="true"] { background: var(--royal); border-color: var(--royal); }
.btn-play .icon-pause { display: none; }
.btn-play[aria-pressed="true"] .icon-play { display: none; }
.btn-play[aria-pressed="true"] .icon-pause { display: block; }
#replay-flip[aria-pressed="true"] { border-color: var(--periwinkle); color: var(--periwinkle); }
#auth-btn[data-status="authenticated"] { color: var(--fg); }
/* --- Workspace ------------------------------------------------------- */
.workspace {
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
gap: clamp(1rem, 2vw, 2rem);
overflow: hidden;
}
.board-area {
position: relative;
flex: none;
width: var(--board-edge);
height: var(--board-edge);
padding: var(--board-pad);
display: flex;
align-items: center;
justify-content: center;
}
.board-wrapper {
position: relative;
width: calc(var(--board-edge) - 2 * var(--board-pad));
height: calc(var(--board-edge) - 2 * var(--board-pad));
}
.coordinates {
position: absolute;
display: flex;
color: var(--muted);
font-size: 0.72rem;
font-weight: 500;
user-select: none;
pointer-events: none;
}
.coordinates.files {
top: -17px;
left: 0;
right: 0;
justify-content: space-around;
padding: 0 2px;
}
.coordinates.ranks {
left: -16px;
top: 0;
bottom: 0;
flex-direction: column;
justify-content: space-around;
padding: 2px 0;
}
#board {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(8, 1fr);
border: 3px solid var(--line-strong);
border-radius: 4px;
overflow: hidden;
}
.square {
display: flex;
justify-content: center;
align-items: center;
/* Glyphs scale with the square: 72% of its edge. */
font-size: calc((var(--board-edge) - 2 * var(--board-pad)) / 8 * 0.72);
cursor: pointer;
user-select: none;
position: relative;
overflow: hidden;
line-height: 1;
}
body[data-mode="replay"] .square, .square.inert { cursor: default; }
.square.light { background-color: var(--square-light); }
.square.dark { background-color: var(--square-dark); }
.square.selected { background-color: var(--square-selected) !important; }
.square.last-move-from { background-color: var(--move-from) !important; }
.square.last-move-to { background-color: var(--move-to) !important; }
.square.white-piece { color: var(--white); text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
.square.black-piece { color: var(--bg); text-shadow: 1px 1px 2px rgba(255,255,255,0.2); }
/* Move feedback: an overlay fades in and out over the square, so the
square's own colour is never touched. */
.square.flash-green::before, .square.flash-red::before {
content: '';
position: absolute;
inset: 0;
animation: flashFade 0.4s ease-out;
pointer-events: none;
}
.square.flash-green::before { background-color: rgba(154, 206, 106, 0.8); }
.square.flash-red::before { background-color: rgba(247, 118, 142, 0.8); }
@keyframes flashFade {
0% { opacity: 0; }
50% { opacity: 1; }
100% { opacity: 0; }
}
.square.mated-king::after {
content: '';
position: absolute;
inset: 4px;
border: 3px solid var(--red);
border-radius: 2px;
pointer-events: none;
z-index: 1;
}
.square.mated-king.white-piece,
.square.mated-king.black-piece { color: var(--checkmated-king) !important; }
/* Toast: a short message under the board, inside the board area so it never
moves the layout. */
.toast-host {
position: absolute;
left: 0;
right: 0;
bottom: -4px;
display: flex;
justify-content: center;
pointer-events: none;
z-index: 20;
}
.toast {
max-width: 92%;
padding: 0.35rem 0.8rem;
border-radius: 999px;
background: var(--surface-3);
border: 1px solid var(--line-strong);
color: var(--fg);
font-size: 0.82rem;
font-weight: 500;
text-align: center;
opacity: 0;
transform: translateY(6px);
transition: opacity 0.2s, transform 0.2s;
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
}
.toast.show { opacity: 1; transform: none; }
.toast.error { color: var(--red); border-color: rgba(247, 118, 142, 0.5); }
.toast.info { color: var(--cyan); border-color: rgba(125, 207, 255, 0.4); }
.toast.success { color: var(--green); border-color: rgba(158, 206, 106, 0.5); }
/* --- Panel ----------------------------------------------------------- */
.panel {
flex: none;
width: var(--panel-width);
height: var(--board-edge);
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
overflow: hidden;
}
.pane {
height: 100%;
padding: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
body[data-mode="live"] .pane-replay { display: none; }
body[data-mode="replay"] .pane-live { display: none; }
.turn-strip {
flex: none;
display: flex;
align-items: center;
gap: 0.5rem;
min-height: 1.8rem;
padding: 0.3rem 0.6rem;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
font-size: 0.85rem;
color: var(--fg);
}
.turn-dot {
width: 0.7rem;
height: 0.7rem;
border: 1px solid var(--line-strong);
}
.turn-dot[data-status="white"] { background: var(--white); }
.turn-dot[data-status="black"] { background: var(--bg); }
.turn-dot[data-status="thinking"] { background: var(--yellow); animation: pulse 1.5s infinite; }
.turn-dot[data-status="white-wins"], .turn-dot[data-status="black-wins"] { background: var(--red); }
.turn-dot[data-status="draw"], .turn-dot[data-status="stalemate"] { background: var(--yellow); }
.turn-dot[data-status="stuck"] { background: var(--red); }
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.9); }
}
.turn-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.controls {
flex: none;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.4rem;
}
.controls .btn { padding: 0.5rem 0.3rem; font-size: 0.82rem; }
.moves {
flex: 1;
min-height: 0;
position: relative;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
overflow-y: auto;
overflow-x: hidden;
padding: 0.5rem 0.6rem;
scrollbar-width: thin;
scrollbar-color: var(--line-strong) transparent;
}
.moves::-webkit-scrollbar { width: 6px; }
.moves::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 3px; }
.move-grid {
display: grid;
grid-template-columns: 2.4rem 1fr 1fr;
gap: 0.15rem 0.3rem;
font-family: var(--mono);
font-size: 0.88rem;
align-items: stretch;
}
.move-grid:empty { display: none; }
.move-grid:empty + .moves-empty { display: block; }
.moves-empty {
display: none;
margin: 0;
padding: 0.25rem 0.1rem;
color: var(--muted);
font-size: 0.85rem;
line-height: 1.45;
}
.move-number {
color: var(--yellow);
text-align: right;
font-weight: 600;
padding: 0.2rem 0.2rem 0.2rem 0;
}
.move, .move-empty {
display: block;
width: 100%;
padding: 0.2rem 0.45rem;
border: 1px solid transparent;
border-radius: 4px;
background: transparent;
text-align: left;
font: inherit;
color: var(--fg);
}
.move-black { color: var(--cyan); }
.move-empty { color: var(--muted); }
.move-grid-replay .move { cursor: pointer; }
.move-grid-replay .move:hover { background: var(--surface-3); }
.move-grid-replay .move:focus-visible { outline: 2px solid var(--periwinkle); outline-offset: -2px; }
.move-grid-replay .move[aria-current="true"] { background: var(--royal); color: var(--white) !important; border-color: var(--royal); }
/* The live list marks the last move quietly. */
.move-grid:not(.move-grid-replay) .move[aria-current="true"] { background: var(--surface-3); border-color: var(--line-strong); }
.toolbar {
flex: none;
display: flex;
gap: 0.35rem;
}
/* --- Replay pane ----------------------------------------------------- */
.replay-head {
flex: none;
display: flex;
flex-direction: column;
gap: 0.15rem;
padding: 0.4rem 0.6rem;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
font-size: 0.82rem;
}
.replay-title-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.badge {
display: inline-block;
padding: 0.05rem 0.45rem;
border-radius: 999px;
background: var(--royal-soft);
color: var(--white);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.replay-result { color: var(--fg); font-weight: 600; }
.replay-ply { margin-left: auto; color: var(--muted); font-family: var(--mono); font-size: 0.78rem; }
.replay-players {
color: var(--fg);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.replay-meta {
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.78rem;
}
.replay-nav {
flex: none;
display: flex;
align-items: center;
gap: 0.3rem;
}
.speed {
margin-left: auto;
display: flex;
align-items: center;
}
.speed select {
height: 2.1rem;
padding: 0 0.4rem;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
color: var(--fg);
font-size: 0.8rem;
}
.speed select:focus-visible { outline: 2px solid var(--periwinkle); }
/* --- Modals ---------------------------------------------------------- */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 2000;
justify-content: center;
align-items: center;
backdrop-filter: blur(4px);
padding: 1rem;
}
.modal-overlay.show { display: flex; }
.modal {
background: var(--surface);
border: 1px solid var(--line-strong);
border-radius: 12px;
padding: 1.25rem 1.5rem;
width: 100%;
max-width: 400px;
color: var(--fg-soft);
/* The overlay is fixed to the IFRAME viewport, so a modal taller than the
frame scrolls inside itself instead of clipping. */
max-height: 100%;
overflow-y: auto;
overscroll-behavior: contain;
box-shadow: 0 20px 50px rgba(0,0,0,0.5);
}
.modal h2 {
margin: 0 0 0.75rem;
color: var(--periwinkle);
text-align: center;
font-size: 1.2rem;
}
.modal-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.modal-head h2 { margin: 0; text-align: left; }
.form-group { margin-bottom: 0.9rem; }
.form-group label, .form-group .group-label {
display: block;
margin-bottom: 0.4rem;
color: var(--cyan);
font-size: 0.9rem;
}
.form-group.form-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.form-group.form-row .group-label { margin: 0; }
.radio-group {
display: flex;
gap: 1.25rem;
}
.radio-group label {
display: flex;
align-items: center;
gap: 0.4rem;
margin: 0;
color: var(--fg-soft);
font-size: 0.9rem;
}
input[type="range"] {
width: 100%;
height: 4px;
background: var(--line-strong);
border-radius: 2px;
outline: none;
appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
background: var(--periwinkle);
border-radius: 50%;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
background: var(--periwinkle);
border: none;
border-radius: 50%;
cursor: pointer;
}
input[type="range"]:focus-visible { outline: 2px solid var(--periwinkle); outline-offset: 4px; }
.fen-input,
.auth-form input[type="text"],
.auth-form input[type="email"],
.auth-form input[type="password"],
.account-form input[type="password"] {
width: 100%;
padding: 0.5rem 0.6rem;
background: var(--bg);
border: 1px solid var(--line-strong);
border-radius: var(--radius-sm);
color: var(--fg);
font-size: 0.9rem;
}
.fen-input {
font-family: var(--mono);
font-size: 0.82rem;
resize: none;
white-space: pre-wrap;
word-wrap: break-word;
}
.fen-input:focus, .auth-form input:focus, .account-form input:focus {
outline: none;
border-color: var(--royal);
}
.modal-buttons {
display: flex;
justify-content: center;
gap: 0.75rem;
margin-top: 1.1rem;
}
.auth-modal, .account-modal { max-width: 360px; }
.auth-tabs {
display: flex;
margin-bottom: 0.9rem;
border-bottom: 1px solid var(--line-strong);
}
.auth-tab {
flex: 1;
padding: 0.6rem;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
color: var(--fg-soft);
font-size: 0.95rem;
transition: color 0.2s, border-color 0.2s;
}
.auth-tab:hover { color: var(--periwinkle); }
.auth-tab.active { color: var(--periwinkle); border-bottom-color: var(--periwinkle); }
/* A reserved slot: an empty message keeps its height and is only invisible,
so a message never moves the form or pushes the buttons out of a short
frame. */
.modal-message {
display: flex;
align-items: center;
justify-content: center;
min-height: 2.3rem;
margin-bottom: 0.6rem;
padding: 0.35rem 0.75rem;
border: 1px solid transparent;
border-radius: var(--radius-sm);
font-size: 0.85rem;
line-height: 1.3;
text-align: center;
visibility: hidden;
}
.modal-message.show { visibility: visible; }
.modal-message.error { background: rgba(247, 118, 142, 0.12); color: var(--red); border-color: rgba(247, 118, 142, 0.4); }
.modal-message.success { background: rgba(158, 206, 106, 0.12); color: var(--green); border-color: rgba(158, 206, 106, 0.4); }
.auth-form input:disabled, .account-form input:disabled, .auth-tab:disabled,
.fen-input:disabled, input[type="range"]:disabled { opacity: 0.5; cursor: not-allowed; }
.radio-group input:disabled + span { opacity: 0.5; }
.account-name { text-align: center; margin: 0.25rem 0; }
.danger-zone {
margin-top: 1.25rem;
padding-top: 0.9rem;
border-top: 1px solid var(--line-strong);
}
.danger-zone summary { cursor: pointer; color: var(--red); text-align: center; }
.danger-zone p { margin: 0.75rem 0 0.9rem; font-size: 0.85rem; line-height: 1.4; }
.danger-zone .modal-buttons { margin-top: 0.9rem; }
.confirm-modal { max-width: 320px; }
.promotion-modal { max-width: 320px; }
.promotion-choices {
display: flex;
justify-content: center;
gap: 0.5rem;
}
.promotion-choice {
width: 56px;
height: 56px;
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
line-height: 1;
background: var(--square-light);
border: 2px solid var(--line-strong);
border-radius: 6px;
transition: border-color 0.2s, background 0.2s;
}
.promotion-choice:hover, .promotion-choice:focus-visible {
outline: none;
border-color: var(--royal);
background: var(--square-selected);
}
.promotion-choices.white .promotion-choice { color: var(--white); text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
.promotion-choices.black .promotion-choice { color: var(--bg); text-shadow: 1px 1px 2px rgba(255,255,255,0.2); }
/* --- My games drawer ------------------------------------------------- */
.games-modal {
max-width: 580px;
height: 100%;
max-height: 100%;
display: flex;
flex-direction: column;
padding: 1rem 1.25rem;
}
.games-filters {
flex: none;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.6rem;
}
.segmented {
display: inline-flex;
border: 1px solid var(--line-strong);
border-radius: 999px;
overflow: hidden;
}
.seg {
padding: 0.25rem 0.7rem;
background: transparent;
border: none;
color: var(--muted);
font-size: 0.8rem;
}
.seg:hover { color: var(--fg); }
.seg.active { background: var(--royal-soft); color: var(--white); }
.seg:focus-visible { outline: 2px solid var(--periwinkle); outline-offset: -2px; }
.games-body {
flex: 1;
min-height: 0;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--line-strong) transparent;
padding-right: 0.25rem;
}
.games-section { margin-bottom: 1rem; }
.games-section.hidden { display: none; }
.games-heading {
margin: 0 0 0.4rem;
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
}
.games-note {
margin: 0 0 0.6rem;
font-size: 0.85rem;
color: var(--muted);
}
.games-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.game-row {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 0.75rem;
width: 100%;
padding: 0.45rem 0.6rem;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
color: var(--fg-soft);
text-align: left;
transition: border-color 0.15s, background 0.15s;
}
.game-row:hover, .game-row:focus-visible {
outline: none;
border-color: var(--royal);
background: var(--surface-3);
}
.game-row.current { border-color: var(--periwinkle); }
.mini-board {
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(8, 1fr);
width: 72px;
height: 72px;
border: 1px solid var(--line-strong);
border-radius: 3px;
overflow: hidden;
flex: none;
}
.mini-board span {
display: flex;
align-items: center;
justify-content: center;
font-size: 8px;
line-height: 1;
user-select: none;
}
.mini-board .light { background: var(--square-light); }
.mini-board .dark { background: var(--square-dark); }
.mini-board .white-piece { color: var(--white); text-shadow: 0 0 1px rgba(0,0,0,0.6); }
.mini-board .black-piece { color: var(--bg); }
.game-info { min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.game-players {
color: var(--fg);
font-weight: 500;
font-size: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.game-outcome { font-size: 0.82rem; }
.game-outcome .pgn-result { font-family: var(--mono); color: var(--yellow); margin-right: 0.35rem; }
.game-outcome.ongoing .pgn-result { color: var(--green); }
.game-when { font-size: 0.78rem; color: var(--muted); }
.game-go {
color: var(--periwinkle);
display: flex;
align-items: center;
}
.games-status {
margin: 0.4rem 0;
font-size: 0.85rem;
color: var(--muted);
text-align: center;
}
.games-status.error { color: var(--red); }
.games-status:empty { display: none; }
#games-more { margin-top: 0.4rem; }
#games-more.hidden { display: none; }
/* --- Stacked layout (board over panel) -------------------------------- */
@media (max-width: 978px) {
:root {
--panel-width: clamp(360px, 45vw, 440px);
--panel-height: 230px;
/* Board edge = the smaller of the width-derived size and the height
that is left: topbar 40 + gaps 16 + panel 230 + app padding 16 +
slack 40 = 342. A short frame shrinks the board before anything
scrolls. */
--board-edge: max(280px, min(clamp(360px, 45vw, 440px), calc(100vh - 342px)));
--board-pad: clamp(16px, 2.04vw, 20px);
}
body { overflow-y: auto; overflow-x: hidden; scrollbar-gutter: stable; }
.app { height: auto; min-height: 100%; }
.workspace {
flex-direction: column;
gap: 0.5rem;
overflow: visible;
}
.panel {
width: var(--panel-width);
height: var(--panel-height);
}
.pane {
display: grid;
grid-template-columns: clamp(150px, 18.4vw, 180px) 1fr;
grid-template-rows: min-content 1fr;
gap: 0.5rem 0.75rem;
padding: 0.6rem;
}
body[data-mode="replay"] .pane-live, body[data-mode="live"] .pane-replay { display: none; }
.pane-live .turn-strip { grid-column: 1; grid-row: 1; }
.pane-live .controls {
grid-column: 1;
grid-row: 2;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
align-self: stretch;
}
.pane-live .controls .btn { padding: 0.3rem 0.2rem; font-size: 0.78rem; min-height: 2rem; }
.pane-live .moves { grid-column: 2; grid-row: 1 / 3; }
.pane-live .toolbar { grid-column: 2; grid-row: 3; }
.pane-live { grid-template-rows: min-content 1fr min-content; }
.pane-live .controls { grid-row: 2 / 4; }
/* Replay: head, navigation and the exit button down the left; the move
list with the export row on the right. */
.pane-replay { grid-template-rows: min-content min-content 1fr min-content; }
.pane-replay .replay-head { grid-column: 1; grid-row: 1; padding: 0.3rem 0.5rem; }
.pane-replay .replay-nav {
grid-column: 1;
grid-row: 2;
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 0.25rem;
}
.pane-replay .replay-nav .speed { grid-column: 1 / 5; margin: 0; }
.pane-replay .replay-nav .speed select { width: 100%; }
.pane-replay #replay-exit { grid-column: 1; grid-row: 3 / 5; align-self: end; padding: 0.35rem; font-size: 0.78rem; }
.pane-replay .moves { grid-column: 2; grid-row: 1 / 4; }
.pane-replay .toolbar { grid-column: 2; grid-row: 4; }
.pane-replay .btn-nav { width: 100%; height: 1.65rem; }
.pane-replay .btn-nav .icon { width: 1rem; height: 1rem; }
.pane-replay .speed select { height: 1.65rem; font-size: 0.72rem; padding: 0 0.2rem; }
.pane-replay .replay-meta { display: none; }
.pane-replay .replay-title-row { flex-wrap: wrap; gap: 0.2rem 0.4rem; }
.pane-replay .replay-players { font-size: 0.76rem; }
.pane-replay .replay-result { font-size: 0.76rem; }
.move-grid { grid-template-columns: 2rem 1fr 1fr; font-size: 0.84rem; }
.btn-tool { font-size: 0.74rem; padding: 0.3rem 0.2rem; }
}
@media (max-width: 530px) {
:root {
--panel-width: min(92vw, 440px);
--board-edge: max(240px, min(min(92vw, 440px), calc(100vh - 332px)));
--board-pad: 12px;
}
.brand-name, #games-btn span, #auth-label { display: none; }
/* Icon-only export row in the replay pane: four buttons in 190px. */
.pane-replay .toolbar .btn-tool span { display: none; }
.pane-replay .toolbar .btn-tool .icon { width: 1.2rem; height: 1.2rem; }
#games-btn, #auth-btn { padding: 0.35rem 0.5rem; }
.pane { grid-template-columns: 140px 1fr; }
.games-modal { padding: 0.75rem; }
.game-row { gap: 0.5rem; padding: 0.4rem; }
.mini-board { width: 56px; height: 56px; }
.mini-board span { font-size: 6px; }
.modal-overlay { padding: 0.5rem; }
}
/* --- Motion ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation: none !important;
transition: none !important;
}
}