/* 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; --panel-width: clamp(260px, 22vw, 380px); /* The board takes what the window leaves beside the panel and under the top bar, so the panel is never pushed out of view and the row never scrolls: 5rem covers the gaps and the frame's padding. The host's 30rem desktop frame leaves 416px. */ --board-edge: clamp(280px, min(100vw - var(--panel-width) - 5rem, 100vh - var(--topbar-height) - 2.5rem), 600px); --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 ----------------------------------------------------- */ .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 6 + toolbar 2.25 + gaps and padding 2.35 = 17.75rem. Small viewport units, so the browser's bars never cover the panel. 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 - 17.75rem))); --board-pad: 14px; --panel-width: 100%; --panel-height: auto; --topbar-height: 2.5rem; } body { overflow-y: auto; overflow-x: hidden; } body[data-mode="replay"] { --board-edge: max(240px, min(100vw, calc(100svh - 23.25rem))); } /* 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; } .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: 6rem; } .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: head, list, navigation, export and exit as rows. */ .pane-replay .replay-head { padding: 0.35rem 0.6rem; } .pane-replay .replay-meta { display: none; } .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; } .pane-replay #replay-exit { padding: 0.5rem; font-size: 0.85rem; } .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; } } /* Very narrow phones: icon-only export buttons. */ @media (max-width: 380px) { .pane-replay .toolbar .btn-tool span { display: none; } .pane-replay .toolbar .btn-tool .icon { width: 1.2rem; height: 1.2rem; } .pane-live .controls .btn { font-size: 0.8rem; } } /* --- Motion ---------------------------------------------------------- */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }