@@ -68,6 +68,53 @@ function showToast(msg, duration = 2500) {
6868 el . _timer = setTimeout ( ( ) => el . classList . add ( 'hidden' ) , duration ) ;
6969}
7070
71+ /* ─── Audio ───────────────────────────────────────────────────── */
72+ let _audioCtx = null ;
73+
74+ function getAudioCtx ( ) {
75+ if ( ! _audioCtx ) {
76+ _audioCtx = new ( window . AudioContext || window . webkitAudioContext ) ( ) ;
77+ }
78+ if ( _audioCtx . state === 'suspended' ) { _audioCtx . resume ( ) ; }
79+ return _audioCtx ;
80+ }
81+
82+ function playSound ( type ) {
83+ try {
84+ const ctx = getAudioCtx ( ) ;
85+ const o = ctx . createOscillator ( ) ;
86+ const g = ctx . createGain ( ) ;
87+ o . connect ( g ) ;
88+ g . connect ( ctx . destination ) ;
89+ if ( type === 'capture' ) {
90+ o . type = 'sawtooth' ;
91+ o . frequency . setValueAtTime ( 520 , ctx . currentTime ) ;
92+ o . frequency . exponentialRampToValueAtTime ( 180 , ctx . currentTime + 0.18 ) ;
93+ g . gain . setValueAtTime ( 0.35 , ctx . currentTime ) ;
94+ g . gain . exponentialRampToValueAtTime ( 0.001 , ctx . currentTime + 0.18 ) ;
95+ o . start ( ctx . currentTime ) ;
96+ o . stop ( ctx . currentTime + 0.18 ) ;
97+ } else if ( type === 'check' ) {
98+ o . type = 'square' ;
99+ o . frequency . setValueAtTime ( 880 , ctx . currentTime ) ;
100+ o . frequency . setValueAtTime ( 660 , ctx . currentTime + 0.08 ) ;
101+ g . gain . setValueAtTime ( 0.2 , ctx . currentTime ) ;
102+ g . gain . exponentialRampToValueAtTime ( 0.001 , ctx . currentTime + 0.25 ) ;
103+ o . start ( ctx . currentTime ) ;
104+ o . stop ( ctx . currentTime + 0.25 ) ;
105+ } else {
106+ // normal move
107+ o . type = 'sine' ;
108+ o . frequency . setValueAtTime ( 900 , ctx . currentTime ) ;
109+ o . frequency . exponentialRampToValueAtTime ( 650 , ctx . currentTime + 0.09 ) ;
110+ g . gain . setValueAtTime ( 0.22 , ctx . currentTime ) ;
111+ g . gain . exponentialRampToValueAtTime ( 0.001 , ctx . currentTime + 0.1 ) ;
112+ o . start ( ctx . currentTime ) ;
113+ o . stop ( ctx . currentTime + 0.1 ) ;
114+ }
115+ } catch ( e ) { /* ignore — AudioContext may be unavailable */ }
116+ }
117+
71118/* ─── Board Rendering ─────────────────────────────────────────── */
72119function buildBoard ( ) {
73120 const board = document . getElementById ( 'board' ) ;
@@ -420,6 +467,13 @@ function executeMove(from, to, promotion) {
420467 state . validMoves = [ ] ;
421468 state . moveCount ++ ;
422469
470+ // Play move sound
471+ if ( moveResult . captured ) {
472+ playSound ( 'capture' ) ;
473+ } else {
474+ playSound ( 'move' ) ;
475+ }
476+
423477 // Clear SQL input template after a successful board-click move
424478 const sqlMoveInput = document . getElementById ( 'sqlMoveInput' ) ;
425479 if ( sqlMoveInput && state . sqlInputHasTemplate ) {
@@ -438,6 +492,7 @@ function executeMove(from, to, promotion) {
438492 const endSQL = SQLGen . gameEnd ( state . chess , state . gameId ) ;
439493 appendSQL ( endSQL , 'Game Over' , null ) ;
440494 } else if ( state . chess . in_check ( ) ) {
495+ playSound ( 'check' ) ;
441496 const checkSQL = SQLGen . check ( state . chess . turn ( ) , state . gameId ) ;
442497 appendSQL ( checkSQL , '⚠ Check' , null ) ;
443498 }
@@ -732,7 +787,7 @@ function highlightSQL(code) {
732787 . replace ( kwRegex , '<span class="sql-kw">$1</span>' ) ;
733788}
734789
735- function appendSQL ( code , label , moveNum ) {
790+ function appendSQL ( code , label , moveNum , atEnd ) {
736791 const placeholder = document . getElementById ( 'sqlPlaceholder' ) ;
737792 if ( placeholder ) placeholder . remove ( ) ;
738793
@@ -766,12 +821,22 @@ function appendSQL(code, label, moveNum) {
766821
767822 block . appendChild ( labelEl ) ;
768823 block . appendChild ( codeEl ) ;
769- content . appendChild ( block ) ;
824+
825+ // New blocks slide in at the TOP so each submitted query moves content DOWN;
826+ // only the initial game-setup block is appended at the end.
827+ if ( atEnd ) {
828+ content . appendChild ( block ) ;
829+ } else {
830+ content . prepend ( block ) ;
831+ }
770832
771833 state . sqlBlocks . push ( { code, label, moveNum } ) ;
772834
773835 if ( document . getElementById ( 'chkAutoScroll' ) . checked ) {
774- content . scrollTop = content . scrollHeight ;
836+ // Scroll to top to reveal the freshly-prepended block
837+ if ( ! atEnd ) {
838+ content . scrollTop = 0 ;
839+ }
775840 }
776841}
777842
@@ -796,9 +861,13 @@ function startGame(whiteName, blackName, showSQL, existingPGN) {
796861 document . getElementById ( 'whitePlayerName' ) . textContent = state . whitePlayer ;
797862 document . getElementById ( 'blackPlayerName' ) . textContent = state . blackPlayer ;
798863
799- // Clear SQL input
864+ // Clear SQL input and pre-fill with sample query
800865 const sqlMoveInput = document . getElementById ( 'sqlMoveInput' ) ;
801- if ( sqlMoveInput ) sqlMoveInput . value = '' ;
866+ if ( sqlMoveInput ) {
867+ sqlMoveInput . value =
868+ `-- Sample: move the e-pawn two squares forward\nUPDATE chess_piece\nSET position = 'e4'\nWHERE position = 'e2';` ;
869+ }
870+ state . sqlInputHasTemplate = false ;
802871 clearSQLRunError ( ) ;
803872
804873 // SQL panel visibility
@@ -828,9 +897,9 @@ function startGame(whiteName, blackName, showSQL, existingPGN) {
828897 `<p class="placeholder-hint">Each chess move is translated into<br/>real SQL statements in real time.</p>` ;
829898 sqlContent . appendChild ( placeholder ) ;
830899
831- // Emit game-start SQL
900+ // Emit game-start SQL (placed at the end so move SQL slides in above it)
832901 const initSQL = SQLGen . gameStart ( state . gameId , state . whitePlayer , state . blackPlayer ) ;
833- appendSQL ( initSQL , 'Game Initialized' , null ) ;
902+ appendSQL ( initSQL , 'Game Initialized' , null , true ) ;
834903 }
835904
836905 // Load from PGN if provided (invite link)
@@ -955,10 +1024,10 @@ function init() {
9551024 if ( undone . color === 'w' ) state . capturedByWhite . pop ( ) ;
9561025 else state . capturedByBlack . pop ( ) ;
9571026 }
958- // Remove last SQL block from UI
1027+ // Remove most-recent SQL block from UI (prepended = first child, no id)
9591028 const content = document . getElementById ( 'sqlContent' ) ;
960- if ( content . lastChild && ! content . lastChild . id ) {
961- content . removeChild ( content . lastChild ) ;
1029+ if ( content . firstChild && ! content . firstChild . id ) {
1030+ content . removeChild ( content . firstChild ) ;
9621031 state . sqlBlocks . pop ( ) ;
9631032 }
9641033 state . selectedSquare = null ;
@@ -1021,6 +1090,15 @@ function init() {
10211090
10221091 // SQL Move Input
10231092 document . getElementById ( 'btnRunSQL' ) . addEventListener ( 'click' , runSQLMove ) ;
1093+ document . getElementById ( 'btnSampleSQL' ) . addEventListener ( 'click' , ( ) => {
1094+ const input = document . getElementById ( 'sqlMoveInput' ) ;
1095+ if ( input ) {
1096+ input . value =
1097+ `-- Sample: move the e-pawn two squares forward\nUPDATE chess_piece\nSET position = 'e4'\nWHERE position = 'e2';` ;
1098+ }
1099+ state . sqlInputHasTemplate = false ;
1100+ clearSQLRunError ( ) ;
1101+ } ) ;
10241102 document . getElementById ( 'btnClearInput' ) . addEventListener ( 'click' , ( ) => {
10251103 const input = document . getElementById ( 'sqlMoveInput' ) ;
10261104 if ( input ) input . value = '' ;
0 commit comments