Files
chess/internal/server/webserver/chess-client-web/style.css
T

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;
}
}