/* 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. The panel takes a quarter of the width between 260px and 420px; the board takes everything else the window leaves beside it and under the top bar, so the margins give way first, the board second, and the panel is never pushed out of view. 3.5rem and 2rem are the app's padding and gaps (8px each side, a gap of up to 2rem beside the board) plus 8px of slack; the host's embed sizes its frame to this formula. */ --topbar-height: 2.5rem; --panel-width: clamp(260px, 24vw, 420px); --board-edge: clamp(280px, min(100vw - var(--panel-width) - 3.5rem, 100vh - var(--topbar-height) - 2rem), 840px); --board-pad: clamp(14px, 1.04vw, 20px); } 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 { width: 1.5rem; height: 1.5rem; flex: none; } .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; container-type: inline-size; } /* A narrow panel shows the export buttons as icons. */ @container (max-width: 300px) { .toolbar .btn-tool span { display: none; } .toolbar .btn-tool .icon { width: 1.2rem; height: 1.2rem; } .controls .btn { font-size: 0.78rem; padding: 0.5rem 0.2rem; } } .toolbar .btn-tool { min-width: 0; 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 ----------------------------------------------------- */ /* The export row wraps the exit button onto a row of its own. */ .pane-replay .toolbar { flex-wrap: wrap; row-gap: 0.6rem; } .pane-replay .btn-exit { flex: 1 0 100%; } .pane-replay .btn-exit .label-short { display: none; } .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; white-space: nowrap; } .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"], #reset-overlay 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, #reset-overlay 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-aside { margin: 0.9rem 0 0; text-align: center; font-size: 0.85rem; } .auth-help { margin: 0 0 0.9rem; font-size: 0.85rem; line-height: 1.45; color: var(--muted); } .link-btn { padding: 0; background: none; border: none; color: var(--periwinkle); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; } .link-btn:hover { color: var(--fg); } .link-btn:focus-visible { outline: 2px solid var(--periwinkle); outline-offset: 2px; border-radius: 2px; } .auth-form input[type="password"]#reset-password, .auth-form input[type="password"]#reset-password-2 { width: 100%; } .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.hint { color: var(--muted); } .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; } .import-modal { max-width: 460px; } .pgn-input { min-height: 9rem; } .file-label { margin: 0; } #import-file { max-width: 55%; font-size: 0.8rem; color: var(--muted); } .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; } /* A row's delete control sits beside the row; a confirmation replaces it. */ .game-item { display: flex; align-items: stretch; gap: 0.35rem; } .game-item .game-row { flex: 1; min-width: 0; } .game-delete { flex: none; width: 2.2rem; padding: 0; background: var(--surface-2); border: 1px solid var(--line); color: var(--muted); } .game-delete:hover:not(:disabled) { color: var(--red); border-color: var(--red); transform: none; } .game-confirm { flex: none; display: flex; flex-direction: column; justify-content: center; gap: 0.3rem; padding: 0 0.2rem; } .game-confirm .btn { padding: 0.3rem 0.5rem; font-size: 0.78rem; } .game-origin { color: var(--periwinkle); } .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; } /* --- Phone layout: full-width rows, edge to edge ------------------------ */ @media (max-width: 699px) { :root { /* Reserve under the board, in rem so a larger system font grows it: top bar 2.5 + gap 0.4 + turn strip 2 + controls 2.25 + move list 4 + toolbar 2.25 + gaps and padding 2.35 = 15.75rem. Small viewport units, so the browser's bars never cover the panel. At a 1.5x system font (rem 24px) on a 412x734 viewport this still leaves a 356px board; the move list scrolls inside its 4rem. The replay pane is taller (head, navigation, export, exit), so a replay reserves more and the board gives way on a short screen rather than the controls dropping below the fold. */ --board-edge: max(240px, min(100vw, calc(100svh - 15.75rem))); --board-pad: 14px; --panel-width: 100%; --panel-height: auto; --topbar-height: 2.5rem; } body { overflow-y: auto; overflow-x: hidden; } /* A replay's panel: head 2.3 + list 4 + navigation 2.2 + export row 2.25 + gaps and padding 2.85, under the top bar 2.9 = 16.5rem. */ body[data-mode="replay"] { --board-edge: max(240px, min(100vw, calc(100svh - 16.5rem))); } /* The app is exactly the viewport, so the panel and its move list shrink to fit; only when their minimums do not fit does the page scroll. */ .app { padding: 0; gap: 6px; height: 100svh; min-height: 100svh; overflow: visible; } .topbar { border-radius: 0; border-left: none; border-right: none; border-top: none; padding: 0 0.6rem; } .brand-name, #games-btn span, #auth-label { display: none; } #games-btn, #auth-btn { padding: 0.35rem 0.55rem; } .workspace { flex: 1 1 auto; min-height: 0; flex-direction: column; justify-content: flex-start; gap: 6px; overflow: visible; } .board-area { flex: none; } .board-area { padding: var(--board-pad) 6px 6px 16px; } .board-wrapper { width: calc(var(--board-edge) - 22px); height: calc(var(--board-edge) - 22px); } .coordinates.ranks { left: -14px; } .coordinates.files { top: -15px; } /* The panel never clips: what the viewport cannot hold scrolls the page. */ .panel { display: flex; flex-direction: column; width: 100%; flex: 1 1 auto; min-height: 0; height: auto; border-radius: 0; border-left: none; border-right: none; border-bottom: none; overflow: visible; container-type: normal; } /* min-height 0: a flex item's automatic minimum is its content height, which would let a long move list push the controls below the fold instead of scrolling inside its box. */ .pane, .pane-live, .pane-replay { display: flex; flex: 1 1 auto; flex-direction: column; gap: 0.4rem; padding: 0.5rem 0.6rem 0.6rem; height: auto; min-height: 0; container-type: inline-size; container-name: pane; } .pane > * { min-width: 0; } .turn-strip { min-height: 2rem; padding: 0.25rem 0.6rem; } .pane-live .controls { grid-template-columns: repeat(4, 1fr); grid-template-rows: auto; gap: 0.35rem; } .pane-live .controls .btn { padding: 0.5rem 0.2rem; font-size: 0.85rem; min-height: 2.25rem; height: auto; } .moves { flex: 1 1 auto; min-height: 4rem; } .move-grid { grid-template-columns: 2.2rem 1fr 1fr; font-size: 0.9rem; } .toolbar .btn-tool { padding: 0.45rem 0.3rem; font-size: 0.82rem; min-width: 0; } /* Replay: a one-line head (result, players, ply; no badge, no meta), the list, the navigation row, then export and exit on one row. */ .pane-replay .replay-head { flex-direction: row; align-items: center; gap: 0.5rem; padding: 0.35rem 0.6rem; min-height: 2.3rem; } .pane-replay .replay-title-row { display: contents; } .pane-replay .badge { display: none; } .pane-replay .replay-result { flex: none; white-space: nowrap; } .pane-replay .replay-players { order: 1; flex: 1 1 auto; } .pane-replay .replay-ply { order: 2; margin-left: auto; } .pane-replay .replay-meta { display: none; } .pane-replay .btn-exit { flex: 1.3 1 0; min-width: 0; padding: 0.45rem 0.3rem; font-size: 0.82rem; } .pane-replay .btn-exit #replay-exit-label { display: none; } .pane-replay .btn-exit .label-short { display: inline; } .pane-replay .replay-nav { display: grid; grid-template-columns: repeat(5, 1fr) 1.4fr 1fr; gap: 0.3rem; } .pane-replay .replay-nav .speed { margin: 0; } .pane-replay .replay-nav .speed select { width: 100%; } .pane-replay .btn-nav { width: 100%; height: 2.2rem; } .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; } } /* Narrow phones, and phones whose system font is large: the pane is a container on phones and rem in a container query is the root font size, so 24rem is 384px at the default size and 600px at a 1.5x font. Then the export buttons show icons alone and the replay head keeps the score and the players but not the termination. */ @container pane (max-width: 24rem) { .pane-replay .toolbar .btn-tool span, .pane-replay .btn-exit span, .pane-replay .btn-exit .label-short { display: none; } .pane-replay .toolbar .btn-tool .icon, .pane-replay .btn-exit .icon { width: 1.2rem; height: 1.2rem; } .pane-replay .replay-term { display: none; } .pane-live .controls .btn { font-size: 0.8rem; } } /* --- Motion ---------------------------------------------------------- */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }