/* 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 { 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; } .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"], #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.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 (min-width: 641px) and (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) minmax(0, 1fr); grid-template-rows: min-content 1fr; gap: 0.5rem 0.75rem; padding: 0.6rem; } .pane > * { min-width: 0; } .toolbar .btn-tool { min-width: 0; overflow: hidden; } /* Four export buttons in the narrow right column: icons only. */ .pane-replay .toolbar .btn-tool span { display: none; } .pane-replay .toolbar .btn-tool .icon { width: 1.2rem; height: 1.2rem; } 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; } } /* --- Phone layout: full-width rows, edge to edge ------------------------ */ @media (max-width: 640px) { :root { /* Reserve under the board: top bar 40 + gap 6 + turn strip 32 + controls 36 + move list 96 + toolbar 36 + gaps and padding 26 = 276. 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 - 284px))); --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 - 372px))); } /* 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; } .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; } .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; } .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: 96px; } .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; } }