Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 12 additions & 21 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
--bg-elevated: #21262d;
--bg-border: #30363d;

/* Board size: at least 50 % of viewport width, capped at 760 px */
--board-sz: clamp(320px, 50vw, 760px);

--text-primary: #e6edf3;
--text-secondary: #8b949e;
--text-muted: #484f58;
Expand Down Expand Up @@ -169,7 +172,7 @@ a { color: var(--accent-blue); }
align-items: center;
gap: .6rem;
width: 100%;
max-width: 480px;
max-width: var(--board-sz);
padding: .4rem .6rem;
background: var(--bg-surface);
border: 1px solid var(--bg-border);
Expand Down Expand Up @@ -261,9 +264,8 @@ a { color: var(--accent-blue); }
border-radius: 3px;
overflow: hidden;
box-shadow: var(--shadow-lg);
/* Size is set dynamically in JS but default here */
width: min(57vw, 460px);
height: min(57vw, 460px);
width: var(--board-sz);
height: var(--board-sz);
}

/* Individual squares */
Expand Down Expand Up @@ -308,7 +310,7 @@ a { color: var(--accent-blue); }

/* Pieces */
.piece {
font-size: calc(min(57vw, 460px) / 10);
font-size: calc(var(--board-sz) / 10);
line-height: 1;
pointer-events: none;
display: flex;
Expand All @@ -327,7 +329,7 @@ a { color: var(--accent-blue); }
align-items: center;
gap: .5rem;
width: 100%;
max-width: 480px;
max-width: var(--board-sz);
}

.game-status-text {
Expand All @@ -340,7 +342,7 @@ a { color: var(--accent-blue); }
/* ── Move History ───────────────────────────────────────────── */
.move-history-box {
width: 100%;
max-width: 480px;
max-width: var(--board-sz);
background: var(--bg-surface);
border: 1px solid var(--bg-border);
border-radius: var(--radius);
Expand Down Expand Up @@ -472,7 +474,7 @@ a { color: var(--accent-blue); }
animation: fadeSlideIn .2s ease;
}
@keyframes fadeSlideIn {
from { opacity: 0; transform: translateY(6px); }
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}

Expand Down Expand Up @@ -837,14 +839,7 @@ input:checked + .slider::before { transform: translateX(18px); }
border-top: none;
}

.board-grid {
width: min(90vw, 420px);
height: min(90vw, 420px);
}

.piece {
font-size: calc(min(90vw, 420px) / 10);
}
:root { --board-sz: min(90vw, 420px); }
}

@media (max-width: 480px) {
Expand All @@ -853,11 +848,7 @@ input:checked + .slider::before { transform: translateX(18px); }
.chess-panel { padding: .5rem; }
.header-controls { gap: .3rem; }

.board-grid {
width: min(92vw, 360px);
height: min(92vw, 360px);
}
.piece { font-size: calc(min(92vw, 360px) / 10); }
:root { --board-sz: min(92vw, 360px); }

#btnToggleSQL #sqlToggleLabel { display: none; }
}
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ <h2 class="sql-title">
placeholder="UPDATE chess_piece&#10;SET position = 'e4'&#10;WHERE position = 'e2';&#10;&#10;-- or shorthand: e2 e4"></textarea>
<div class="sql-input-actions">
<span class="sql-run-error hidden" id="sqlRunError"></span>
<button type="button" id="btnSampleSQL" class="btn btn-xs" title="Load a sample move query">⚑ Sample</button>
<button type="button" id="btnClearInput" class="btn btn-xs">Clear</button>
<button type="button" id="btnRunSQL" class="btn btn-primary btn-sm">▶ Run</button>
</div>
Expand Down
102 changes: 92 additions & 10 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
'use strict';

/* ─── Configuration ──────────────────────────────────────────── */
const SAMPLE_SQL_QUERY =
`-- Sample: move the e-pawn two squares forward\nUPDATE chess_piece\nSET position = 'e4'\nWHERE position = 'e2';`;

const PIECE_UNICODE = {
wK: '♔', wQ: '♕', wR: '♖', wB: '♗', wN: '♘', wP: '♙',
bK: '♚', bQ: '♛', bR: '♜', bB: '♝', bN: '♞', bP: '♟',
Expand Down Expand Up @@ -68,6 +71,53 @@ function showToast(msg, duration = 2500) {
el._timer = setTimeout(() => el.classList.add('hidden'), duration);
}

/* ─── Audio ───────────────────────────────────────────────────── */
let _audioCtx = null;

function getAudioCtx() {
if (!_audioCtx) {
_audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (_audioCtx.state === 'suspended') { _audioCtx.resume(); }
return _audioCtx;
}

function playSound(type) {
try {
const ctx = getAudioCtx();
const o = ctx.createOscillator();
const g = ctx.createGain();
o.connect(g);
g.connect(ctx.destination);
if (type === 'capture') {
o.type = 'sawtooth';
o.frequency.setValueAtTime(520, ctx.currentTime);
o.frequency.exponentialRampToValueAtTime(180, ctx.currentTime + 0.18);
g.gain.setValueAtTime(0.35, ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.18);
o.start(ctx.currentTime);
o.stop(ctx.currentTime + 0.18);
} else if (type === 'check') {
o.type = 'square';
o.frequency.setValueAtTime(880, ctx.currentTime);
o.frequency.setValueAtTime(660, ctx.currentTime + 0.08);
g.gain.setValueAtTime(0.2, ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.25);
o.start(ctx.currentTime);
o.stop(ctx.currentTime + 0.25);
} else {
// normal move
o.type = 'sine';
o.frequency.setValueAtTime(900, ctx.currentTime);
o.frequency.exponentialRampToValueAtTime(650, ctx.currentTime + 0.09);
g.gain.setValueAtTime(0.22, ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.1);
o.start(ctx.currentTime);
o.stop(ctx.currentTime + 0.1);
}
} catch (e) { /* ignore — AudioContext may be unavailable */ }
}

/* ─── Board Rendering ─────────────────────────────────────────── */
function buildBoard() {
const board = document.getElementById('board');
Expand Down Expand Up @@ -420,6 +470,13 @@ function executeMove(from, to, promotion) {
state.validMoves = [];
state.moveCount++;

// Play move sound
if (moveResult.captured) {
playSound('capture');
} else {
playSound('move');
}

// Clear SQL input template after a successful board-click move
const sqlMoveInput = document.getElementById('sqlMoveInput');
if (sqlMoveInput && state.sqlInputHasTemplate) {
Expand All @@ -438,6 +495,7 @@ function executeMove(from, to, promotion) {
const endSQL = SQLGen.gameEnd(state.chess, state.gameId);
appendSQL(endSQL, 'Game Over', null);
} else if (state.chess.in_check()) {
playSound('check');
const checkSQL = SQLGen.check(state.chess.turn(), state.gameId);
appendSQL(checkSQL, '⚠ Check', null);
}
Expand Down Expand Up @@ -732,7 +790,7 @@ function highlightSQL(code) {
.replace(kwRegex, '<span class="sql-kw">$1</span>');
}

function appendSQL(code, label, moveNum) {
function appendSQL(code, label, moveNum, atEnd) {
const placeholder = document.getElementById('sqlPlaceholder');
if (placeholder) placeholder.remove();

Expand Down Expand Up @@ -766,12 +824,25 @@ function appendSQL(code, label, moveNum) {

block.appendChild(labelEl);
block.appendChild(codeEl);
content.appendChild(block);

// New blocks slide in at the TOP so each submitted query moves content DOWN;
// only the initial game-setup block is appended at the end.
if (atEnd) {
content.appendChild(block);
} else {
content.prepend(block);
}

state.sqlBlocks.push({ code, label, moveNum });

if (document.getElementById('chkAutoScroll').checked) {
content.scrollTop = content.scrollHeight;
if (atEnd) {
// Game-init block appended at bottom — scroll down to show it
content.scrollTop = content.scrollHeight;
} else {
// Move blocks prepended at top — scroll up to reveal the new block
content.scrollTop = 0;
}
}
}

Expand All @@ -796,9 +867,12 @@ function startGame(whiteName, blackName, showSQL, existingPGN) {
document.getElementById('whitePlayerName').textContent = state.whitePlayer;
document.getElementById('blackPlayerName').textContent = state.blackPlayer;

// Clear SQL input
// Clear SQL input and pre-fill with sample query
const sqlMoveInput = document.getElementById('sqlMoveInput');
if (sqlMoveInput) sqlMoveInput.value = '';
if (sqlMoveInput) {
sqlMoveInput.value = SAMPLE_SQL_QUERY;
}
state.sqlInputHasTemplate = true;
clearSQLRunError();

// SQL panel visibility
Expand Down Expand Up @@ -828,9 +902,9 @@ function startGame(whiteName, blackName, showSQL, existingPGN) {
`<p class="placeholder-hint">Each chess move is translated into<br/>real SQL statements in real time.</p>`;
sqlContent.appendChild(placeholder);

// Emit game-start SQL
// Emit game-start SQL (placed at the end so move SQL slides in above it)
const initSQL = SQLGen.gameStart(state.gameId, state.whitePlayer, state.blackPlayer);
appendSQL(initSQL, 'Game Initialized', null);
appendSQL(initSQL, 'Game Initialized', null, true);
}

// Load from PGN if provided (invite link)
Expand Down Expand Up @@ -955,10 +1029,10 @@ function init() {
if (undone.color === 'w') state.capturedByWhite.pop();
else state.capturedByBlack.pop();
}
// Remove last SQL block from UI
// Remove most-recent SQL block from UI (prepended = first child, no id)
const content = document.getElementById('sqlContent');
if (content.lastChild && !content.lastChild.id) {
content.removeChild(content.lastChild);
if (content.firstChild && !content.firstChild.id) {
content.removeChild(content.firstChild);
state.sqlBlocks.pop();
}
state.selectedSquare = null;
Expand Down Expand Up @@ -1021,6 +1095,14 @@ function init() {

// SQL Move Input
document.getElementById('btnRunSQL').addEventListener('click', runSQLMove);
document.getElementById('btnSampleSQL').addEventListener('click', () => {
const input = document.getElementById('sqlMoveInput');
if (input) {
input.value = SAMPLE_SQL_QUERY;
}
state.sqlInputHasTemplate = true;
clearSQLRunError();
});
document.getElementById('btnClearInput').addEventListener('click', () => {
const input = document.getElementById('sqlMoveInput');
if (input) input.value = '';
Expand Down
Loading