1616'use strict' ;
1717
1818/* ─── Configuration ──────────────────────────────────────────── */
19+ const SAMPLE_SQL_QUERY =
20+ `-- Sample: move the e-pawn two squares forward\nUPDATE chess_piece\nSET position = 'e4'\nWHERE position = 'e2';` ;
21+
1922const PIECE_UNICODE = {
2023 wK : '♔' , wQ : '♕' , wR : '♖' , wB : '♗' , wN : '♘' , wP : '♙' ,
2124 bK : '♚' , bQ : '♛' , bR : '♜' , bB : '♝' , bN : '♞' , bP : '♟' ,
@@ -68,6 +71,53 @@ function showToast(msg, duration = 2500) {
6871 el . _timer = setTimeout ( ( ) => el . classList . add ( 'hidden' ) , duration ) ;
6972}
7073
74+ /* ─── Audio ───────────────────────────────────────────────────── */
75+ let _audioCtx = null ;
76+
77+ function getAudioCtx ( ) {
78+ if ( ! _audioCtx ) {
79+ _audioCtx = new ( window . AudioContext || window . webkitAudioContext ) ( ) ;
80+ }
81+ if ( _audioCtx . state === 'suspended' ) { _audioCtx . resume ( ) ; }
82+ return _audioCtx ;
83+ }
84+
85+ function playSound ( type ) {
86+ try {
87+ const ctx = getAudioCtx ( ) ;
88+ const o = ctx . createOscillator ( ) ;
89+ const g = ctx . createGain ( ) ;
90+ o . connect ( g ) ;
91+ g . connect ( ctx . destination ) ;
92+ if ( type === 'capture' ) {
93+ o . type = 'sawtooth' ;
94+ o . frequency . setValueAtTime ( 520 , ctx . currentTime ) ;
95+ o . frequency . exponentialRampToValueAtTime ( 180 , ctx . currentTime + 0.18 ) ;
96+ g . gain . setValueAtTime ( 0.35 , ctx . currentTime ) ;
97+ g . gain . exponentialRampToValueAtTime ( 0.001 , ctx . currentTime + 0.18 ) ;
98+ o . start ( ctx . currentTime ) ;
99+ o . stop ( ctx . currentTime + 0.18 ) ;
100+ } else if ( type === 'check' ) {
101+ o . type = 'square' ;
102+ o . frequency . setValueAtTime ( 880 , ctx . currentTime ) ;
103+ o . frequency . setValueAtTime ( 660 , ctx . currentTime + 0.08 ) ;
104+ g . gain . setValueAtTime ( 0.2 , ctx . currentTime ) ;
105+ g . gain . exponentialRampToValueAtTime ( 0.001 , ctx . currentTime + 0.25 ) ;
106+ o . start ( ctx . currentTime ) ;
107+ o . stop ( ctx . currentTime + 0.25 ) ;
108+ } else {
109+ // normal move
110+ o . type = 'sine' ;
111+ o . frequency . setValueAtTime ( 900 , ctx . currentTime ) ;
112+ o . frequency . exponentialRampToValueAtTime ( 650 , ctx . currentTime + 0.09 ) ;
113+ g . gain . setValueAtTime ( 0.22 , ctx . currentTime ) ;
114+ g . gain . exponentialRampToValueAtTime ( 0.001 , ctx . currentTime + 0.1 ) ;
115+ o . start ( ctx . currentTime ) ;
116+ o . stop ( ctx . currentTime + 0.1 ) ;
117+ }
118+ } catch ( e ) { /* ignore — AudioContext may be unavailable */ }
119+ }
120+
71121/* ─── Board Rendering ─────────────────────────────────────────── */
72122function buildBoard ( ) {
73123 const board = document . getElementById ( 'board' ) ;
@@ -420,6 +470,13 @@ function executeMove(from, to, promotion) {
420470 state . validMoves = [ ] ;
421471 state . moveCount ++ ;
422472
473+ // Play move sound
474+ if ( moveResult . captured ) {
475+ playSound ( 'capture' ) ;
476+ } else {
477+ playSound ( 'move' ) ;
478+ }
479+
423480 // Clear SQL input template after a successful board-click move
424481 const sqlMoveInput = document . getElementById ( 'sqlMoveInput' ) ;
425482 if ( sqlMoveInput && state . sqlInputHasTemplate ) {
@@ -438,6 +495,7 @@ function executeMove(from, to, promotion) {
438495 const endSQL = SQLGen . gameEnd ( state . chess , state . gameId ) ;
439496 appendSQL ( endSQL , 'Game Over' , null ) ;
440497 } else if ( state . chess . in_check ( ) ) {
498+ playSound ( 'check' ) ;
441499 const checkSQL = SQLGen . check ( state . chess . turn ( ) , state . gameId ) ;
442500 appendSQL ( checkSQL , '⚠ Check' , null ) ;
443501 }
@@ -732,7 +790,7 @@ function highlightSQL(code) {
732790 . replace ( kwRegex , '<span class="sql-kw">$1</span>' ) ;
733791}
734792
735- function appendSQL ( code , label , moveNum ) {
793+ function appendSQL ( code , label , moveNum , atEnd ) {
736794 const placeholder = document . getElementById ( 'sqlPlaceholder' ) ;
737795 if ( placeholder ) placeholder . remove ( ) ;
738796
@@ -766,12 +824,25 @@ function appendSQL(code, label, moveNum) {
766824
767825 block . appendChild ( labelEl ) ;
768826 block . appendChild ( codeEl ) ;
769- content . appendChild ( block ) ;
827+
828+ // New blocks slide in at the TOP so each submitted query moves content DOWN;
829+ // only the initial game-setup block is appended at the end.
830+ if ( atEnd ) {
831+ content . appendChild ( block ) ;
832+ } else {
833+ content . prepend ( block ) ;
834+ }
770835
771836 state . sqlBlocks . push ( { code, label, moveNum } ) ;
772837
773838 if ( document . getElementById ( 'chkAutoScroll' ) . checked ) {
774- content . scrollTop = content . scrollHeight ;
839+ if ( atEnd ) {
840+ // Game-init block appended at bottom — scroll down to show it
841+ content . scrollTop = content . scrollHeight ;
842+ } else {
843+ // Move blocks prepended at top — scroll up to reveal the new block
844+ content . scrollTop = 0 ;
845+ }
775846 }
776847}
777848
@@ -796,9 +867,12 @@ function startGame(whiteName, blackName, showSQL, existingPGN) {
796867 document . getElementById ( 'whitePlayerName' ) . textContent = state . whitePlayer ;
797868 document . getElementById ( 'blackPlayerName' ) . textContent = state . blackPlayer ;
798869
799- // Clear SQL input
870+ // Clear SQL input and pre-fill with sample query
800871 const sqlMoveInput = document . getElementById ( 'sqlMoveInput' ) ;
801- if ( sqlMoveInput ) sqlMoveInput . value = '' ;
872+ if ( sqlMoveInput ) {
873+ sqlMoveInput . value = SAMPLE_SQL_QUERY ;
874+ }
875+ state . sqlInputHasTemplate = true ;
802876 clearSQLRunError ( ) ;
803877
804878 // SQL panel visibility
@@ -828,9 +902,9 @@ function startGame(whiteName, blackName, showSQL, existingPGN) {
828902 `<p class="placeholder-hint">Each chess move is translated into<br/>real SQL statements in real time.</p>` ;
829903 sqlContent . appendChild ( placeholder ) ;
830904
831- // Emit game-start SQL
905+ // Emit game-start SQL (placed at the end so move SQL slides in above it)
832906 const initSQL = SQLGen . gameStart ( state . gameId , state . whitePlayer , state . blackPlayer ) ;
833- appendSQL ( initSQL , 'Game Initialized' , null ) ;
907+ appendSQL ( initSQL , 'Game Initialized' , null , true ) ;
834908 }
835909
836910 // Load from PGN if provided (invite link)
@@ -955,10 +1029,10 @@ function init() {
9551029 if ( undone . color === 'w' ) state . capturedByWhite . pop ( ) ;
9561030 else state . capturedByBlack . pop ( ) ;
9571031 }
958- // Remove last SQL block from UI
1032+ // Remove most-recent SQL block from UI (prepended = first child, no id)
9591033 const content = document . getElementById ( 'sqlContent' ) ;
960- if ( content . lastChild && ! content . lastChild . id ) {
961- content . removeChild ( content . lastChild ) ;
1034+ if ( content . firstChild && ! content . firstChild . id ) {
1035+ content . removeChild ( content . firstChild ) ;
9621036 state . sqlBlocks . pop ( ) ;
9631037 }
9641038 state . selectedSquare = null ;
@@ -1021,6 +1095,14 @@ function init() {
10211095
10221096 // SQL Move Input
10231097 document . getElementById ( 'btnRunSQL' ) . addEventListener ( 'click' , runSQLMove ) ;
1098+ document . getElementById ( 'btnSampleSQL' ) . addEventListener ( 'click' , ( ) => {
1099+ const input = document . getElementById ( 'sqlMoveInput' ) ;
1100+ if ( input ) {
1101+ input . value = SAMPLE_SQL_QUERY ;
1102+ }
1103+ state . sqlInputHasTemplate = true ;
1104+ clearSQLRunError ( ) ;
1105+ } ) ;
10241106 document . getElementById ( 'btnClearInput' ) . addEventListener ( 'click' , ( ) => {
10251107 const input = document . getElementById ( 'sqlMoveInput' ) ;
10261108 if ( input ) input . value = '' ;
0 commit comments