<script>
  import { fade, slide, scale } from 'svelte/transition';
  import { onDestroy } from 'svelte';

  const PIECES = {
    wK: '/icons/wK.svg', wQ: '/icons/wQ.svg', wR: '/icons/wR.svg', wB: '/icons/wB.svg', wN: '/icons/wN.svg', wP: '/icons/wP.svg',
    bK: '/icons/bK.svg', bQ: '/icons/bQ.svg', bR: '/icons/bR.svg', bB: '/icons/bB.svg', bN: '/icons/bN.svg', bP: '/icons/bP.svg'
  };

  const INIT = [
    ['bR','bN','bB','bQ','bK','bB','bN','bR'],
    ['bP','bP','bP','bP','bP','bP','bP','bP'],
    [null,null,null,null,null,null,null,null],
    [null,null,null,null,null,null,null,null],
    [null,null,null,null,null,null,null,null],
    [null,null,null,null,null,null,null,null],
    ['wP','wP','wP','wP','wP','wP','wP','wP'],
    ['wR','wN','wB','wQ','wK','wB','wN','wR'],
  ];

  function deepClone(b) {
    return b.map(r => [...r]);
  }

  function createInitialPieceIds() {
    let ids = [];
    let count = 0;
    for (let r = 0; r < 8; r++) {
      let row = [];
      for (let c = 0; c < 8; c++) {
        if (INIT[r][c]) {
          row.push(`piece-${INIT[r][c]}-${count++}`);
        } else {
          row.push(null);
        }
      }
      ids.push(row);
    }
    return ids;
  }

  function buildTree(moves) {
    let root = { move: null, children: [], note: null };
    let cur = root;
    for (let m of moves) {
      let node = { move: m.san, children: [], note: m.note || null };
      cur.children.push(node);
      cur = node;
    }
    return root;
  }

  function getNoteForHistory(historyPrefix) {
    let line = historyPrefix.join(' ');
    if (customNotes[currentRep] && customNotes[currentRep][line] !== undefined) {
      return customNotes[currentRep][line];
    }
    for (let op of currentRepertoireObj.openings) {
      let opMoves = op.pgn.split(' ').filter(Boolean);
      if (opMoves.slice(0, historyPrefix.length).join(' ') === line) {
        let node = walkTree(op.tree, historyPrefix);
        if (node && node.note) {
          return node.note;
        }
      }
    }
    return null;
  }

  function buildTreePreservingNotes(historyMoves, oldTree) {
    let root = { move: null, children: [], note: null };
    let cur = root;
    
    for (let i = 0; i < historyMoves.length; i++) {
      let m = historyMoves[i];
      let prefix = historyMoves.slice(0, i + 1);
      
      let existingNote = null;
      if (oldTree) {
        let node = walkTree(oldTree, prefix);
        if (node && node.note) {
          existingNote = node.note;
        }
      }
      
      if (!existingNote) {
        existingNote = getNoteForHistory(prefix);
      }
      
      let node = { move: m, children: [], note: existingNote };
      cur.children.push(node);
      cur = node;
    }
    return root;
  }

  function getBoardSignature(b, t) {
    let pieces = [];
    for (let r = 0; r < 8; r++) {
      for (let c = 0; c < 8; c++) {
        pieces.push(b[r][c] || '.');
      }
    }
    return pieces.join(',') + ' ' + t;
  }

  function playMoveOnBoard(b, t, san) {
    for (let r = 0; r < 8; r++) {
      for (let c = 0; c < 8; c++) {
        let p = b[r][c];
        if (!p || p[0] !== t) continue;
        let moves = getMoves(b, r, c);
        for (let [tr, tc] of moves) {
          if (moveToSAN(b, r, c, tr, tc) === san) {
            b[tr][tc] = p;
            b[r][c] = null;
            if (p[1] === 'P' && (tr === 0 || tr === 7)) {
              b[tr][tc] = p[0] + 'Q';
            }
            return true;
          }
        }
      }
    }
    return false;
  }

  function getBoardSignaturesForOpening(op) {
    let tempBoard = deepClone(INIT);
    let tempTurn = 'w';
    let signatures = [];
    
    signatures.push({
      signature: getBoardSignature(tempBoard, tempTurn),
      history: []
    });
    
    let moves = op.pgn.split(' ').filter(Boolean);
    for (let i = 0; i < moves.length; i++) {
      let san = moves[i];
      let moved = playMoveOnBoard(tempBoard, tempTurn, san);
      if (!moved) break;
      tempTurn = tempTurn === 'w' ? 'b' : 'w';
      signatures.push({
        signature: getBoardSignature(tempBoard, tempTurn),
        history: moves.slice(0, i + 1)
      });
    }
    return signatures;
  }

  // Reactive State (Svelte 5 Runes)
  let board = $state(deepClone(INIT));
  let pieceIds = $state(createInitialPieceIds());
  let selected = $state(null);
  let highlights = $state([]);
  let turn = $state('w');
  let history = $state([]);
  let flipped = $state(false);
  let lastMove = $state(null);
  let drillMode = $state(false);
  let drillLine = $state(null);
  let drillStep = $state(0);
  let currentRep = $state(0);
  let customNotes = $state({ 0: {}, 1: {} });
  let moveTags = $state({ 0: {}, 1: {} });
  let voiceModeActive = $state(false);
  let isListening = $state(false);
  let voiceStatusMsg = $state('');
  let recognition = null;
  let continuousVoice = $state(true);

  let activePieces = $derived.by(() => {
    let list = [];
    for (let r = 0; r < 8; r++) {
      for (let c = 0; c < 8; c++) {
        let p = board[r][c];
        let id = pieceIds[r][c];
        if (p && id) {
          list.push({
            id: id,
            type: p,
            row: r,
            col: c
          });
        }
      }
    }
    // Sort by ID to maintain a stable DOM order and prevent node reordering/transition cancellation
    return list.sort((a, b) => a.id.localeCompare(b.id));
  });

  let repertoires = $state([
    {
      name: 'My White Repertoire', side: 'w',
      openings: [
        { id: 1, parentId: null, name: "King's Pawn Opening", pgn: 'e4', tree: buildTree([{san:'e4',note:'Controls center, opens diagonals for bishop and queen.'}]) },
        { id: 2, parentId: 1, name: "Italian Game", pgn: 'e4 e5 Nf3 Nc6 Bc4', tree: buildTree([
          {san:'e4',note:'Controls center.'},{san:'e5',note:null},{san:'Nf3',note:'Attacks e5, develops knight naturally.'},{san:'Nc6',note:null},{san:'Bc4',note:'Targets f7, classic Italian setup.'}
        ]) },
        { id: 10, parentId: 1, name: "Vienna Game", pgn: 'e4 e5 Nc3', tree: buildTree([
          {san:'e4',note:null},{san:'e5',note:null},{san:'Nc3',note:'Develops knight, protects e4, keeps f-pawn free to move.'}
        ]) },
        { id: 11, parentId: 10, name: "Vienna Game: Falkbeer Variation", pgn: 'e4 e5 Nc3 Nf6', tree: buildTree([
          {san:'e4',note:null},{san:'e5',note:null},{san:'Nc3',note:null},{san:'Nf6',note:'Classic reply, developing knight.'}
        ]) },
        { id: 12, parentId: 10, name: "Vienna Game: Stanley Variation", pgn: 'e4 e5 Nc3 Nc6', tree: buildTree([
          {san:'e4',note:null},{san:'e5',note:null},{san:'Nc3',note:null},{san:'Nc6',note:null}
        ]) },
        { id: 13, parentId: 11, name: "Vienna Gambit", pgn: 'e4 e5 Nc3 Nf6 f4', tree: buildTree([
          {san:'e4',note:null},{san:'e5',note:null},{san:'Nc3',note:null},{san:'Nf6',note:null},{san:'f4',note:'Strikes at e5, gambits the f-pawn.'}
        ]) },
        { id: 14, parentId: 13, name: "Vienna Gambit: Accepted", pgn: 'e4 e5 Nc3 Nf6 f4 exf4', tree: buildTree([
          {san:'e4',note:null},{san:'e5',note:null},{san:'Nc3',note:null},{san:'Nf6',note:null},{san:'f4',note:null},{san:'exf4',note:'Accepts gambit.'}
        ]) },
        { id: 15, parentId: 13, name: "Vienna Gambit: Declined", pgn: 'e4 e5 Nc3 Nf6 f4 d5', tree: buildTree([
          {san:'e4',note:null},{san:'e5',note:null},{san:'Nc3',note:null},{san:'Nf6',note:null},{san:'f4',note:null},{san:'d5',note:'Counterstrikes in the center.'}
        ]) },
      ]
    },
    {
      name: 'My Black Repertoire', side: 'b',
      openings: [
        { id: 3, parentId: null, name: "Sicilian Defense", pgn: 'e4 c5', tree: buildTree([{san:'e4',note:null},{san:'c5',note:'Asymmetric, fights for d4 without mirroring White.'}]) },
      ]
    }
  ]);

  let activeOpeningId = $state(null);
  let currentTab = $state('build');
  let showNewForm = $state(false);
  let newOpName = $state('');
  let newOpParentId = $state('');
  let drillOpeningId = $state(null);

  let drillStatusMsg = $state('');
  let drillResultIcon = $state('');
  let showDrillOverlay = $state(false);
  let showSideSelection = $state(true);

  // Derived properties
  let currentRepertoireObj = $derived(repertoires[currentRep]);
  let lineStr = $derived(history.join(' '));
  let activeOpening = $derived(currentRepertoireObj.openings.find(o => o.id === activeOpeningId));

  let loaded = $state(false);

  $effect(() => {
    if (loaded) return;
    const savedReps = localStorage.getItem('chess_repertoires');
    if (savedReps) {
      try {
        repertoires = JSON.parse(savedReps);
      } catch (e) {
        console.error("Failed to load chess_repertoires:", e);
      }
    }
    const savedNotes = localStorage.getItem('chess_custom_notes');
    if (savedNotes) {
      try {
        customNotes = JSON.parse(savedNotes);
      } catch (e) {
        console.error("Failed to load chess_custom_notes:", e);
      }
    }
    const savedTags = localStorage.getItem('chess_move_tags');
    if (savedTags) {
      try {
        moveTags = JSON.parse(savedTags);
      } catch (e) {
        console.error("Failed to load chess_move_tags:", e);
      }
    }
    loaded = true;
  });

  $effect(() => {
    if (!loaded) return;
    localStorage.setItem('chess_repertoires', JSON.stringify(repertoires));
  });

  $effect(() => {
    if (!loaded) return;
    localStorage.setItem('chess_custom_notes', JSON.stringify(customNotes));
  });

  $effect(() => {
    if (!loaded) return;
    localStorage.setItem('chess_move_tags', JSON.stringify(moveTags));
  });

  let repStatus = $derived.by(() => {
    if (history.length === 0) return { text: 'Make a move to begin', inRep: false, name: '', isExact: false, isContinuation: false, parentName: '', parentId: null };
    
    let isExact = false;
    let repName = '';
    
    // 1. Check for exact match
    for (let op of currentRepertoireObj.openings) {
      let opMovesStr = op.pgn.split(' ').filter(Boolean).join(' ');
      if (opMovesStr === lineStr) {
        isExact = true;
        repName = op.name;
        break;
      }
    }
    
    if (isExact) {
      return {
        text: repName,
        inRep: true,
        isExact: true,
        isContinuation: false,
        parentName: '',
        parentId: null,
        name: repName
      };
    }
    
    // 2. Not an exact match. Find the longest saved opening that is a prefix of the current history.
    let longestMatchLength = 0;
    let bestMatchName = '';
    let bestMatchId = null;
    let currentMoves = history;
    
    for (let op of currentRepertoireObj.openings) {
      let opMoves = op.pgn.split(' ').filter(Boolean);
      if (opMoves.length > 0 && opMoves.length < currentMoves.length) {
        let isPrefix = true;
        for (let i = 0; i < opMoves.length; i++) {
          if (opMoves[i] !== currentMoves[i]) {
            isPrefix = false;
            break;
          }
        }
        if (isPrefix && opMoves.length > longestMatchLength) {
          longestMatchLength = opMoves.length;
          bestMatchName = op.name;
          bestMatchId = op.id;
        }
      }
    }
    
    if (longestMatchLength > 0) {
      return {
        text: bestMatchName + ' Cont...',
        inRep: false,
        isExact: false,
        isContinuation: true,
        parentName: bestMatchName,
        parentId: bestMatchId,
        name: bestMatchName + ' Cont...'
      };
    }
    
    return {
      text: lineStr,
      inRep: false,
      isExact: false,
      isContinuation: false,
      parentName: '',
      parentId: null,
      name: ''
    };
  });

  let transpositions = $derived.by(() => {
    if (history.length === 0) return [];
    
    let currentSignature = getBoardSignature(board, turn);
    let list = [];
    
    for (let op of currentRepertoireObj.openings) {
      let signatures = getBoardSignaturesForOpening(op);
      for (let item of signatures) {
        if (item.signature === currentSignature) {
          let itemHistoryStr = item.history.join(' ');
          if (itemHistoryStr !== lineStr) {
            if (!list.some(x => x.opening.id === op.id && x.historyStr === itemHistoryStr)) {
              list.push({
                opening: op,
                history: item.history,
                historyStr: itemHistoryStr
              });
            }
          }
        }
      }
    }
    return list;
  });

  let openingTree = $derived.by(() => {
    let list = currentRepertoireObj.openings;
    let map = {};
    let roots = [];
    
    for (let op of list) {
      map[op.id] = { ...op, children: [] };
    }
    
    for (let op of list) {
      let node = map[op.id];
      if (op.parentId && map[op.parentId]) {
        map[op.parentId].children.push(node);
      } else {
        roots.push(node);
      }
    }
    return roots;
  });

  let branches = $derived.by(() => {
    let list = [];
    for (let op of currentRepertoireObj.openings) {
      let moves = op.pgn.split(' ').filter(Boolean);
      if (history.length < moves.length) {
        let prefix = moves.slice(0, history.length).join(' ');
        if (prefix === lineStr || history.length === 0) {
          let nextMove = moves[history.length];
          if (nextMove && !list.find(b=>b.move===nextMove)) {
            list.push({ move: nextMove, opening: op.name });
          }
        }
      }
    }
    return list;
  });

  let gaps = $derived.by(() => {
    let list = [];
    for(let op of currentRepertoireObj.openings) {
      let moves = op.pgn.split(' ').filter(Boolean);
      if(moves.length === 0) {
        list.push(`${op.name} — no moves added yet`);
      } else if (moves.length < 4) {
        list.push(`${op.name} — very short line (${moves.length} moves)`);
      }
    }
    return list;
  });

  let completionPct = $derived.by(() => {
    let list = currentRepertoireObj.openings;
    if (list.length === 0) return 0;
    let withNotes = list.filter(op => {
      let moves = op.pgn.split(' ').filter(Boolean);
      let note = getNoteForHistory(moves);
      return note && note.trim().length > 0;
    }).length;
    return Math.min(100, Math.round((withNotes / list.length) * 100)) || 35;
  });

  let noteText = $state('');
  let saveSuccess = $state(false);

  $effect(() => {
    if (history.length === 0) {
      noteText = '';
      return;
    }
    let note = '';
    if (customNotes[currentRep] && customNotes[currentRep][lineStr] !== undefined) {
      note = customNotes[currentRep][lineStr];
    } else {
      for (let op of currentRepertoireObj.openings) {
        let moves = op.pgn.split(' ').filter(Boolean);
        if (moves.slice(0, history.length).join(' ') === lineStr) {
          let node = walkTree(op.tree, history);
          if (node && node.note) {
            note = node.note;
            break;
          }
        }
      }
    }
    noteText = note;
  });

  function shouldShowBranchOpeningName(bOpeningName) {
    if (bOpeningName === repStatus.name || bOpeningName === repStatus.parentName) {
      return false;
    }
    let candidates = new Set();
    for (let op of currentRepertoireObj.openings) {
      let opMoves = op.pgn.split(' ').filter(Boolean);
      if (opMoves.length >= history.length) {
        let isPrefix = true;
        for (let i = 0; i < history.length; i++) {
          if (opMoves[i] !== history[i]) {
            isPrefix = false;
            break;
          }
        }
        if (isPrefix) {
          candidates.add(op.name);
        }
      }
    }
    if (candidates.size <= 1) {
      return false;
    }
    return true;
  }

  function walkTree(tree, moves) {
    let cur = tree;
    for (let m of moves) {
      let child = cur.children.find(c => c.move === m);
      if (!child) return null;
      cur = child;
    }
    return cur;
  }

  function getMoves(board, r, c) {
    let p = board[r][c];
    if (!p) return [];
    let color = p[0], type = p[1];
    let moves = [];
    function inBounds(r,c){return r>=0&&r<8&&c>=0&&c<8;}
    function friendly(r,c){return inBounds(r,c)&&board[r][c]&&board[r][c][0]===color;}
    function enemy(r,c){return inBounds(r,c)&&board[r][c]&&board[r][c][0]!==color;}
    function empty(r,c){return inBounds(r,c)&&!board[r][c];}
    function slide(dirs){for(let [dr,dc] of dirs){let nr=r+dr,nc=c+dc;while(inBounds(nr,nc)){if(friendly(nr,nc))break;moves.push([nr,nc]);if(board[nr][nc])break;nr+=dr;nc+=dc;}}}
    function jump(drs){for(let [dr,dc] of drs){let nr=r+dr,nc=c+dc;if(inBounds(nr,nc)&&!friendly(nr,nc))moves.push([nr,nc]);}}
    if(type==='R')slide([[1,0],[-1,0],[0,1],[0,-1]]);
    else if(type==='B')slide([[1,1],[1,-1],[-1,1],[-1,-1]]);
    else if(type==='Q')slide([[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]]);
    else if(type==='N')jump([[2,1],[2,-1],[-2,1],[-2,-1],[1,2],[1,-2],[-1,2],[-1,-2]]);
    else if(type==='K')jump([[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]]);
    else if(type==='P'){
      let dir=color==='w'?-1:1, start=color==='w'?6:1;
      if(empty(r+dir,c)){moves.push([r+dir,c]);if(r===start&&empty(r+2*dir,c))moves.push([r+2*dir,c]);}
      if(enemy(r+dir,c-1))moves.push([r+dir,c-1]);
      if(enemy(r+dir,c+1))moves.push([r+dir,c+1]);
    }
    return moves;
  }

  function moveToSAN(board, fr, fc, tr, tc) {
    let p = board[fr][fc];
    let type = p[1];
    let files='abcdefgh', target=files[tc]+(8-tr);
    let capture = board[tr][tc] ? 'x':'';
    if(type==='P') return capture ? files[fc]+'x'+target : target;
    if(type==='K') return 'K'+capture+target;
    return type+capture+target;
  }

  function onSquareClick(r, c) {
    if (drillMode) { handleDrillClick(r, c); return; }
    let p = board[r][c];
    if (selected) {
      let [sr,sc] = selected;
      if (highlights.some(h=>h[0]===r&&h[1]===c)) {
        doMove(sr,sc,r,c);
        return;
      }
      selected = null;
      highlights = [];
    }
    if (p && p[0]===turn) {
      selected = [r,c];
      highlights = getMoves(board, r, c);
    }
  }

  function doMove(fr,fc,tr,tc) {
    let san = moveToSAN(board, fr, fc, tr, tc);
    let piece = board[fr][fc];
    board[tr][tc] = piece;
    board[fr][fc] = null;
    
    // Update piece IDs
    pieceIds[tr][tc] = pieceIds[fr][fc];
    pieceIds[fr][fc] = null;
    pieceIds = pieceIds;
    
    if (piece[1]==='P' && (tr===0||tr===7)) board[tr][tc] = piece[0]+'Q';
    board = board; // trigger Svelte 5 reactivity
    lastMove = [fr,fc,tr,tc];
    history.push(san);
    turn = turn==='w'?'b':'w';
    selected = null;
    highlights = [];
  }

  function playMoveByHistory(san) {
    for (let r=0;r<8;r++) for(let c=0;c<8;c++) {
      let p = board[r][c];
      if (!p || p[0]!==turn) continue;
      let moves = getMoves(board, r, c);
      for (let [tr,tc] of moves) {
        let s = moveToSAN(board,r,c,tr,tc);
        if (s===san) {
          board[tr][tc]=p;
          board[r][c]=null;
          
          // Update piece IDs
          pieceIds[tr][tc] = pieceIds[r][c];
          pieceIds[r][c] = null;
          pieceIds = pieceIds;
          
          if(p[1]==='P'&&(tr===0||tr===7)) board[tr][tc]=p[0]+'Q';
          board = board; // trigger Svelte 5 reactivity
          lastMove=[r,c,tr,tc];
          turn=turn==='w'?'b':'w';
          return;
        }
      }
    }
  }

  function saveNote() {
    if (history.length === 0) return;
    let trimmed = noteText.trim();
    if (!customNotes[currentRep]) {
      customNotes[currentRep] = {};
    }
    if (trimmed === '') {
      delete customNotes[currentRep][lineStr];
    } else {
      customNotes[currentRep][lineStr] = trimmed;
    }
    customNotes[currentRep] = { ...customNotes[currentRep] };
    for (let op of currentRepertoireObj.openings) {
      let moves = op.pgn.split(' ').filter(Boolean);
      if (moves.slice(0, history.length).join(' ') === lineStr) {
        let node = walkTree(op.tree, history);
        if (node) { node.note = trimmed === '' ? null : trimmed; }
      }
    }
    repertoires[currentRep].openings = [...repertoires[currentRep].openings];
    saveSuccess = true;
    setTimeout(() => { saveSuccess = false; }, 1200);
  }

  function goBack() {
    if (history.length === 0) return;
    history.pop();
    if (history.length === 0) {
      activeOpeningId = null;
    }
    board = deepClone(INIT);
    pieceIds = createInitialPieceIds();
    turn = 'w';
    lastMove = null;
    for (let m of history) {
      playMoveByHistory(m);
    }
    selected = null;
    highlights = [];
  }

  function resetBoard(showSelection = true) {
    drillMode = false;
    showDrillOverlay = false;
    board = deepClone(INIT);
    pieceIds = createInitialPieceIds();
    selected = null;
    highlights = [];
    turn = 'w';
    history = [];
    lastMove = null;
    activeOpeningId = null;
    drillOpeningId = null;
    if (showSelection) {
      showSideSelection = true;
    }
  }

  function flipBoard() {
    flipped = !flipped;
  }

  function applyTransposition(t) {
    history = [...t.history];
    activeOpeningId = t.opening.id;
    board = deepClone(INIT);
    pieceIds = createInitialPieceIds();
    turn = 'w';
    lastMove = null;
    for (let m of history) {
      playMoveByHistory(m);
    }
    selected = null;
    highlights = [];
  }

  // Voice Mode Functions
  function canonicalize(str) {
    let text = str.toLowerCase().trim();
    
    // Replace spoken numbers
    text = text
      .replace(/\bone\b/g, '1')
      .replace(/\btwo\b/g, '2')
      .replace(/\bthree\b/g, '3')
      .replace(/\bfour\b/g, '4')
      .replace(/\bfor\b/g, '4')
      .replace(/\bfive\b/g, '5')
      .replace(/\bsix\b/g, '6')
      .replace(/\bseven\b/g, '7')
      .replace(/\beight\b/g, '8')
      .replace(/\bate\b/g, '8');
      
    // Replace spoken letters/files
    text = text
      .replace(/\bsee\b/g, 'c')
      .replace(/\bsea\b/g, 'c')
      .replace(/\bbe\b/g, 'b')
      .replace(/\bbee\b/g, 'b')
      .replace(/\bgee\b/g, 'g')
      .replace(/\beff\b/g, 'f')
      .replace(/\baye\b/g, 'a')
      .replace(/\bay\b/g, 'a');

    // Replace misheard words
    text = text
      .replace(/\bbefore\b/g, 'b4')
      .replace(/\bdetour\b/g, 'd4')
      .replace(/\bdeeply\b/g, 'd3')
      .replace(/\bdefended\b/g, 'd5');
      
    // Standard piece mapping
    text = text
      .replace(/knight/g, 'n')
      .replace(/night/g, 'n')
      .replace(/bishop/g, 'b')
      .replace(/rook/g, 'r')
      .replace(/queen/g, 'q')
      .replace(/king/g, 'k')
      .replace(/pawn/g, '');

    // Replace e.g. "d to" -> "d2", then strip remaining "to"
    text = text.replace(/([a-h])\s+to\b/g, '$12');

    // Movement/capture words
    text = text
      .replace(/\bto\b/g, '')
      .replace(/\bthe\b/g, '')
      .replace(/\bmove\b/g, '')
      .replace(/\bplay\b/g, '')
      .replace(/\bsquare\b/g, '')
      .replace(/takes/g, 'x')
      .replace(/take/g, 'x')
      .replace(/captures/g, 'x')
      .replace(/capture/g, 'x');

    // Strip spaces and all non-alphanumeric characters
    return text.replace(/[^a-z0-9]/g, '');
  }

  function getSpokenPatternsForMove(san, fr, fc, tr, tc) {
    let clean = san.replace(/[+#]/g, '');
    let canon = canonicalize(clean);
    let patterns = [canon];
    
    // Add coordinate patterns
    let files = 'abcdefgh';
    let startSquare = files[fc] + (8 - fr);
    let targetSquare = files[tc] + (8 - tr);
    patterns.push(startSquare + targetSquare); // e.g. "e2e4"
    
    if (clean === 'O-O') {
      patterns.push('oo', 'castle', 'castles', 'shortcastle', 'kingsidecastle', 'castlekingside');
    } else if (clean === 'O-O-O') {
      patterns.push('ooo', 'longcastle', 'queensidecastle', 'castlequeenside');
    }
    
    if (clean.includes('=')) {
      patterns.push(canonicalize(clean.replace('=', '')));
    }
    
    if (clean.includes('x')) {
      patterns.push(canonicalize(clean.replace('x', '')));
    }
    
    return patterns;
  }

  function getLegalMovesForTurn() {
    let legal = [];
    for (let r = 0; r < 8; r++) {
      for (let c = 0; c < 8; c++) {
        let p = board[r][c];
        if (!p || p[0] !== turn) continue;
        let moves = getMoves(board, r, c);
        for (let [tr, tc] of moves) {
          let san = moveToSAN(board, r, c, tr, tc);
          legal.push({
            san: san,
            fr: r,
            fc: c,
            tr: tr,
            tc: tc
          });
        }
      }
    }
    return legal;
  }

  function executeMove(bestMatch) {
    if (drillMode) {
      let expected = drillLine[drillStep];
      if (bestMatch.san === expected) {
        doMove(bestMatch.fr, bestMatch.fc, bestMatch.tr, bestMatch.tc);
        drillStep++;
        if (drillStep >= drillLine.length) {
          showDrillMessage('Line complete!', '✓');
          drillMode = false;
        } else {
          let nextExpected = drillLine[drillStep];
          let isOpp = drillStep % 2 === 0;
          if (isOpp) {
            setTimeout(() => {
              autoPlayMove(drillLine[drillStep]);
              drillStep++;
              if (drillStep < drillLine.length) {
                showDrillMessage('Play: ' + drillLine[drillStep]);
              } else {
                showDrillMessage('Line complete!', '✓');
                drillMode = false;
              }
            }, 600);
            hideDrillOverlay();
          } else {
            showDrillMessage('Play: ' + nextExpected);
          }
        }
      } else {
        showDrillMessage('Not quite — try again', '✗');
        setTimeout(() => showDrillMessage('Play: ' + expected), 1200);
      }
    } else {
      doMove(bestMatch.fr, bestMatch.fc, bestMatch.tr, bestMatch.tc);
    }
  }

  function processSpokenMove(text) {
    let canonSpoken = canonicalize(text);
    let legalMoves = getLegalMovesForTurn();
    
    let matches = [];
    for (let m of legalMoves) {
      let patterns = getSpokenPatternsForMove(m.san, m.fr, m.fc, m.tr, m.tc);
      if (patterns.includes(canonSpoken)) {
        matches.push(m);
      }
    }
    
    if (matches.length === 1) {
      let bestMatch = matches[0];
      voiceStatusMsg = `Playing move: ${bestMatch.san}`;
      executeMove(bestMatch);
    } else if (matches.length > 1) {
      voiceStatusMsg = `Ambiguous move. Found ${matches.length} matches: ${matches.map(m=>m.san).join(', ')}`;
    } else {
      voiceStatusMsg = `Could not parse move: "${text}". Try "e4" or "Knight f3".`;
    }
  }

  function initVoiceRecognition() {
    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SpeechRecognition) {
      voiceStatusMsg = 'Web Speech API not supported in this browser.';
      return;
    }
    recognition = new SpeechRecognition();
    recognition.continuous = false;
    recognition.lang = 'en-US';
    recognition.interimResults = false;
    
    recognition.onstart = () => {
      isListening = true;
      voiceStatusMsg = 'Listening for a move...';
    };
    
    recognition.onresult = (event) => {
      let spokenText = event.results[0][0].transcript;
      voiceStatusMsg = `Heard: "${spokenText}"`;
      processSpokenMove(spokenText);
    };
    
    recognition.onerror = (event) => {
      console.error("Speech recognition error:", event.error);
      isListening = false;
      if (event.error === 'no-speech') {
        voiceStatusMsg = 'No speech detected. Try again.';
      } else {
        voiceStatusMsg = `Speech error: ${event.error}`;
      }
    };
    
    recognition.onend = () => {
      isListening = false;
      if (voiceModeActive && continuousVoice) {
        setTimeout(() => {
          if (voiceModeActive) {
            startVoiceListening();
          }
        }, 300);
      }
    };
  }

  function startVoiceListening() {
    if (!recognition) {
      initVoiceRecognition();
    }
    if (!recognition) return;
    try {
      recognition.start();
    } catch (e) {
      console.error("Speech recognition start failed:", e);
    }
  }

  function stopVoiceListening() {
    if (recognition) {
      try {
        recognition.stop();
      } catch (e) {
        console.error("Speech recognition stop failed:", e);
      }
    }
    isListening = false;
  }

  function toggleVoiceRecognition() {
    voiceModeActive = !voiceModeActive;
    if (voiceModeActive) {
      voiceStatusMsg = 'Starting voice recognition...';
      startVoiceListening();
    } else {
      voiceStatusMsg = '';
      stopVoiceListening();
    }
  }

  onDestroy(() => {
    if (recognition) {
      try {
        recognition.abort();
      } catch (e) {}
    }
  });

  function updateActiveOpening() {
    if (!activeOpeningId) return;
    let op = currentRepertoireObj.openings.find(o => o.id === activeOpeningId);
    if (!op) return;
    op.pgn = history.join(' ');
    op.tree = buildTreePreservingNotes(history, op.tree);
    repertoires[currentRep].openings = [...repertoires[currentRep].openings];
    saveSuccess = true;
    setTimeout(() => { saveSuccess = false; }, 1200);
  }

  function saveToParentOpening(parentId) {
    let op = currentRepertoireObj.openings.find(o => o.id === parentId);
    if (!op) return;
    op.pgn = history.join(' ');
    op.tree = buildTreePreservingNotes(history, op.tree);
    repertoires[currentRep].openings = [...repertoires[currentRep].openings];
    activeOpeningId = op.id;
    saveSuccess = true;
    setTimeout(() => { saveSuccess = false; }, 1200);
  }

  function determineParentId(pgnStr, openings) {
    let moves = pgnStr.split(' ').filter(Boolean);
    let longestMatchLength = 0;
    let bestParentId = null;
    
    for (let op of openings) {
      let opMoves = op.pgn.split(' ').filter(Boolean);
      if (opMoves.length > 0 && opMoves.length < moves.length) {
        let isPrefix = true;
        for (let i = 0; i < opMoves.length; i++) {
          if (opMoves[i] !== moves[i]) {
            isPrefix = false;
            break;
          }
        }
        if (isPrefix && opMoves.length > longestMatchLength) {
          longestMatchLength = opMoves.length;
          bestParentId = op.id;
        }
      }
    }
    return bestParentId;
  }

  function addCurrentLine() {
    let lineStrVal = history.join(' ');
    if (!lineStrVal) return;
    
    let parentId = determineParentId(lineStrVal, currentRepertoireObj.openings);
    let parentOp = parentId ? currentRepertoireObj.openings.find(o => o.id === parentId) : null;
    
    let defaultName = lineStrVal;
    if (parentOp) {
      let parentMovesLength = parentOp.pgn.split(' ').filter(Boolean).length;
      let newMoves = history.slice(parentMovesLength).join(' ');
      defaultName = `${parentOp.name}: ${newMoves}`;
    }
    
    let name = prompt('Name this opening/line:', defaultName);
    if (!name) return;
    
    let newOp = {
      id: Date.now(),
      parentId: parentId,
      name: name,
      pgn: lineStrVal,
      tree: buildTreePreservingNotes(history, null)
    };
    currentRepertoireObj.openings.push(newOp);
    repertoires[currentRep].openings = [...repertoires[currentRep].openings];
    activeOpeningId = newOp.id;
  }

  function switchTab(tab) {
    currentTab = tab;
  }

  function switchRep(idx) {
    currentRep = parseInt(idx);
    resetBoard(false);
    flipped = idx === 1 || idx === '1';
    showSideSelection = false;
  }

  function createNewOpening() {
    let name = newOpName.trim();
    if (!name) return;
    
    let parentId = newOpParentId ? parseInt(newOpParentId) : null;
    let parentOp = parentId ? currentRepertoireObj.openings.find(o => o.id === parentId) : null;
    let pgn = parentOp ? parentOp.pgn : '';
    
    let newOp = { 
      id: Date.now(), 
      parentId: parentId,
      name: name, 
      pgn: pgn, 
      tree: buildTreePreservingNotes(pgn.split(' ').filter(Boolean), parentOp ? parentOp.tree : null) 
    };
    currentRepertoireObj.openings.push(newOp);
    repertoires[currentRep].openings = [...repertoires[currentRep].openings];
    newOpName = '';
    newOpParentId = '';
    showNewForm = false;
    activeOpeningId = newOp.id;
    currentTab = 'build';
    loadOpening(newOp);
  }

  function loadOpening(op) {
    resetBoard(false);
    showSideSelection = false;
    activeOpeningId = op.id;
    let moves = op.pgn.split(' ').filter(Boolean);
    for (let m of moves) {
      playMoveByHistory(m);
      history.push(m);
    }
    turn = history.length % 2 === 0 ? 'w' : 'b';
  }

  function startDrill(op) {
    resetBoard(false);
    showSideSelection = false;
    drillMode = true;
    drillLine = op.pgn.split(' ').filter(Boolean);
    drillStep = 0;
    showDrillMessage('Play: ' + drillLine[0]);
  }

  function showDrillMessage(msg, icon = '') {
    showDrillOverlay = icon !== '';
    drillStatusMsg = msg;
    drillResultIcon = icon;
  }

  function hideDrillOverlay() {
    showDrillOverlay = false;
  }

  function handleDrillClick(r, c) {
    if (!drillLine || drillStep >= drillLine.length) return;
    let expected = drillLine[drillStep];
    let p = board[r][c];
    if (!selected) {
      if (p && p[0] === turn) {
        selected = [r, c];
        highlights = getMoves(board, r, c);
      }
      return;
    }
    let [sr, sc] = selected;
    if (highlights.some(h => h[0] === r && h[1] === c)) {
      let san = moveToSAN(board, sr, sc, r, c);
      selected = null;
      highlights = [];
      if (san === expected) {
        doMove(sr, sc, r, c);
        drillStep++;
        if (drillStep >= drillLine.length) {
          showDrillMessage('Line complete!', '✓');
          drillMode = false;
        } else {
          let nextExpected = drillLine[drillStep];
          let isOpp = drillStep % 2 === 0;
          if (isOpp) {
            setTimeout(() => {
              autoPlayMove(drillLine[drillStep]);
              drillStep++;
              if (drillStep < drillLine.length) {
                showDrillMessage('Play: ' + drillLine[drillStep]);
              } else {
                showDrillMessage('Line complete!', '✓');
                drillMode = false;
              }
            }, 600);
            hideDrillOverlay();
          } else {
            showDrillMessage('Play: ' + nextExpected);
          }
        }
      } else {
        showDrillMessage('Not quite — try again', '✗');
        setTimeout(() => showDrillMessage('Play: ' + expected), 1200);
      }
    } else {
      selected = [r, c];
      if (p && p[0] === turn) {
        highlights = getMoves(board, r, c);
      } else {
        selected = null;
        highlights = [];
      }
    }
  }

  function autoPlayMove(san) {
    for (let r = 0; r < 8; r++) {
      for (let c = 0; c < 8; c++) {
        let p = board[r][c];
        if (!p || p[0] !== turn) continue;
        let moves = getMoves(board, r, c);
        for (let [tr, tc] of moves) {
          if (moveToSAN(board, r, c, tr, tc) === san) {
            doMove(r, c, tr, tc);
            return;
          }
        }
      }
    }
  }

  function clickMoveChip(index) {
    if (drillMode) return;
    let targetHistory = history.slice(0, index + 1);
    board = deepClone(INIT);
    pieceIds = createInitialPieceIds();
    turn = 'w';
    lastMove = null;
    for (let m of targetHistory) {
      playMoveByHistory(m);
    }
    history = targetHistory;
    selected = null;
    highlights = [];
  }

  let showPgnEdit = $state(false);
  let pgnEditVal = $state('');
  let pgnEditError = $state('');

  function validateAndReplayMoves(movesArray) {
    let tempBoard = deepClone(INIT);
    let tempTurn = 'w';
    
    function getMovesForBoard(b, r, c) {
      let p = b[r][c];
      if (!p) return [];
      let color = p[0], type = p[1];
      let moves = [];
      function inBounds(r,c){return r>=0&&r<8&&c>=0&&c<8;}
      function friendly(r,c){return inBounds(r,c)&&b[r][c]&&b[r][c][0]===color;}
      function enemy(r,c){return inBounds(r,c)&&b[r][c]&&b[r][c][0]!==color;}
      function empty(r,c){return inBounds(r,c)&&!b[r][c];}
      function slide(dirs){for(let [dr,dc] of dirs){let nr=r+dr,nc=c+dc;while(inBounds(nr,nc)){if(friendly(nr,nc))break;moves.push([nr,nc]);if(b[nr][nc])break;nr+=dr;nc+=dc;}}}
      function jump(drs){for(let [dr,dc] of drs){let nr=r+dr,nc=c+dc;if(inBounds(nr,nc)&&!friendly(nr,nc))moves.push([nr,nc]);}}
      if(type==='R')slide([[1,0],[-1,0],[0,1],[0,-1]]);
      else if(type==='B')slide([[1,1],[1,-1],[-1,1],[-1,-1]]);
      else if(type==='Q')slide([[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]]);
      else if(type==='N')jump([[2,1],[2,-1],[-2,1],[-2,-1],[1,2],[1,-2],[-1,2],[-1,-2]]);
      else if(type==='K')jump([[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]]);
      else if(type==='P'){
        let dir=color==='w'?-1:1, start=color==='w'?6:1;
        if(empty(r+dir,c)){moves.push([r+dir,c]);if(r===start&&empty(r+2*dir,c))moves.push([r+2*dir,c]);}
        if(enemy(r+dir,c-1))moves.push([r+dir,c-1]);
        if(enemy(r+dir,c+1))moves.push([r+dir,c+1]);
      }
      return moves;
    }
    
    function moveToSANForBoard(b, fr, fc, tr, tc) {
      let p = b[fr][fc];
      let type = p[1];
      let files='abcdefgh', target=files[tc]+(8-tr);
      let capture = b[tr][tc] ? 'x':'';
      if(type==='P') return capture ? files[fc]+'x'+target : target;
      if(type==='K') return 'K'+capture+target;
      return type+capture+target;
    }

    for (let i = 0; i < movesArray.length; i++) {
      let san = movesArray[i];
      let moved = false;
      outer: for (let r = 0; r < 8; r++) {
        for (let c = 0; c < 8; c++) {
          let p = tempBoard[r][c];
          if (!p || p[0] !== tempTurn) continue;
          let mvs = getMovesForBoard(tempBoard, r, c);
          for (let [tr, tc] of mvs) {
            if (moveToSANForBoard(tempBoard, r, c, tr, tc) === san) {
              tempBoard[tr][tc] = p;
              tempBoard[r][c] = null;
              if (p[1] === 'P' && (tr === 0 || tr === 7)) {
                tempBoard[tr][tc] = p[0] + 'Q';
              }
              tempTurn = tempTurn === 'w' ? 'b' : 'w';
              moved = true;
              break outer;
            }
          }
        }
      }
      if (!moved) {
        return { valid: false, error: `Move ${i+1} (${san}) is illegal in this position.` };
      }
    }
    return { valid: true };
  }

  function savePgnEdit() {
    let cleanPgn = pgnEditVal.trim().replace(/\s+/g, ' ');
    let moves = cleanPgn === '' ? [] : cleanPgn.split(' ');
    
    let res = validateAndReplayMoves(moves);
    if (!res.valid) {
      pgnEditError = res.error;
      return;
    }
    
    board = deepClone(INIT);
    pieceIds = createInitialPieceIds();
    turn = 'w';
    lastMove = null;
    history = [];
    
    for (let m of moves) {
      playMoveByHistory(m);
      history.push(m);
    }
    
    history = [...history];
    showPgnEdit = false;
    pgnEditError = '';
  }

  function deleteOpening(id) {
    let op = currentRepertoireObj.openings.find(o => o.id === id);
    if (!op) return;
    if (!confirm(`Are you sure you want to delete "${op.name}"? This will also delete any sub-lines.`)) return;

    let idsToDelete = new Set([id]);
    let queue = [id];
    while (queue.length > 0) {
      let currentId = queue.shift();
      for (let other of currentRepertoireObj.openings) {
        if (other.parentId === currentId && !idsToDelete.has(other.id)) {
          idsToDelete.add(other.id);
          queue.push(other.id);
        }
      }
    }

    repertoires[currentRep].openings = currentRepertoireObj.openings.filter(o => !idsToDelete.has(o.id));
    repertoires = [...repertoires];

    if (idsToDelete.has(activeOpeningId)) {
      resetBoard(false);
    }
  }

  function renameOpening(id) {
    let op = currentRepertoireObj.openings.find(o => o.id === id);
    if (!op) return;
    let newName = prompt(`Rename opening "${op.name}" to:`, op.name);
    if (newName && newName.trim() !== '' && newName.trim() !== op.name) {
      op.name = newName.trim();
      repertoires[currentRep].openings = [...currentRepertoireObj.openings];
      repertoires = [...repertoires];
    }
  }

  function truncateMoveHistory(index) {
    let targetHistory = history.slice(0, index);
    board = deepClone(INIT);
    pieceIds = createInitialPieceIds();
    turn = 'w';
    lastMove = null;
    for (let m of targetHistory) {
      playMoveByHistory(m);
    }
    history = targetHistory;
    selected = null;
    highlights = [];
  }

  function toggleMoveTag(movePath, tag) {
    if (!moveTags[currentRep]) {
      moveTags[currentRep] = {};
    }
    if (tag === null || moveTags[currentRep][movePath] === tag) {
      delete moveTags[currentRep][movePath];
    } else {
      moveTags[currentRep][movePath] = tag;
    }
    moveTags[currentRep] = { ...moveTags[currentRep] };
  }
</script>

<svelte:head>
  <title>Chess Opening Repertoire Builder</title>
</svelte:head>

<div class="min-h-screen bg-[#131416] text-[#f1f3f4] flex flex-col font-sans selection:bg-sky-500/30">

  <main class="flex-1 w-full mx-auto px-4 py-6 md:py-12 flex flex-col md:flex-row justify-center items-start gap-0">
    <!-- Board column (left) -->
    <div class="z-10 transition-[max-width] duration-300 w-full md:max-w-[480px] lg:max-w-[480px] xl:max-w-[520px] 2xl:max-w-[650px] flex flex-col items-stretch shrink-0">
      
      <!-- Breadcrumb Bar -->
      <div class="transition-opacity duration-300 opacity-100 min-w-0 h-8 items-center flex flex-row max-w-full text-sm font-bold text-[#78828a] mb-4">
        <button type="button" class="shrink-0 cursor-pointer p-1 rounded hover:bg-[#2a2e31]/50" onclick={() => resetBoard(true)} aria-label="Home">
          <div class="w-5 h-5 flex items-center justify-center">
            <svg id="Layer_2" viewBox="0 0 200 164.87" class="w-full h-full fill-current text-[#cbcfd2]">
              <g id="black">
                <path d="m150 142.48-50 22.39v-60l50-22.39v60z" class="opacity-25"></path>
                <path d="m200 119.97-50 22.39v-60l50-22.39v60zM150 82.55l-50 22.38v-60l50-22.38v60z" class="opacity-[0.75]"></path>
                <path d="m200 60.03-50 22.39v-60L200 .03v60z" class="opacity-25"></path>
                <path d="m50 142.45 50 22.39v-60L50 82.45v60z" class="opacity-100"></path>
                <path d="m0 119.94 50 22.38v-60L0 59.94v60zM50 82.52l50 22.38v-60L50 22.52v60z" class="opacity-50"></path>
                <path d="m0 60 50 22.39v-60L0 0v60z" class="opacity-100"></path>
              </g>
            </svg>
          </div>
        </button>

        <div class="pointer-events-none mx-2 shrink-0">
          <i class="ti ti-chevron-right text-[#78828a] text-xs"></i>
        </div>

        <!-- Repertoire dropdown/selector style -->
        <button type="button" class="shrink-0 cursor-pointer" onclick={() => switchRep(currentRep === 0 ? 1 : 0)}>
          <span class="text-sm font-semibold text-[#cbcfd2] hover:text-[#f1f3f4] transition-colors">
            {currentRepertoireObj.side === 'w' ? 'White' : 'Black'}
          </span>
        </button>

        <div class="pointer-events-none mx-2 shrink-0">
          <i class="ti ti-chevron-right text-[#78828a] text-xs"></i>
        </div>

        <!-- Current Tab / Section Title -->
        <span class="text-sm font-bold text-[#cbcfd2] cursor-default capitalize">{currentTab}</span>

        {#if history.length > 0}
          <div class="pointer-events-none mx-2 shrink-0">
            <i class="ti ti-chevron-right text-[#78828a] text-xs"></i>
          </div>
          <span class="text-sm font-semibold text-sky-400 truncate max-w-[150px]">{repStatus.name.replace(' Cont...', '')}</span>
        {/if}

        <div class="flex-1"></div>

        <!-- Completion Circle progress -->
        <div class="row items-center whitespace-nowrap hidden sm:flex text-xs font-semibold text-[#78828a]">
          <div class="shrink-0 mr-1.5 w-[14px] h-[14px]">
            <svg id="svg" width="100%" height="100%" viewBox="0 0 100 100" version="1.1">
              <circle r="42" cx="50" cy="50" fill="transparent" stroke-dashoffset="0" stroke-width="16px" stroke="#2a2e31"></circle>
              <circle r="42" cx="50" cy="50" fill="transparent" stroke-linecap="round" transform="rotate(-90 50 50)" style="transform-origin: 50% 50%;" stroke-dasharray="263.89" stroke="hsl(118, 43%, 48.75%)" stroke-width="16px" stroke-dashoffset={263.89 - (263.89 * completionPct / 100)}></circle>
            </svg>
          </div>
          <span class="tabular-nums">{completionPct}% complete</span>
        </div>
      </div>

      <!-- Turn / Status Indicator Card -->
      {#if drillMode}
        <div class="w-full bg-[#202325]/50 backdrop-blur-md border border-[#2a2e31]/80 rounded-xl px-4 py-3 text-center shadow-lg mb-4">
          <span class="text-sm font-semibold text-[#cbcfd2]">
            <span class="text-amber-400 font-bold flex items-center justify-center gap-2">
              <i class="ti ti-route animate-pulse"></i> {drillStatusMsg || 'Drill Mode Active'}
            </span>
          </span>
        </div>
      {/if}
      
      <!-- Board wrapper -->
      <div class="relative w-full aspect-square rounded-lg overflow-hidden shadow-2xl border border-[#2a2e31]">
        <div class="grid grid-cols-8 grid-rows-8 w-full h-full">
          {#each Array(8) as _, r}
            {#each Array(8) as _, c}
              {@const dr = flipped ? 7 - r : r}
              {@const dc = flipped ? 7 - c : c}
              {@const isLight = (r + c) % 2 === 0}
              {@const p = board[dr][dc]}
              {@const isSelected = selected && selected[0] === dr && selected[1] === dc}
              {@const isHighlighted = highlights.some(h => h[0] === dr && h[1] === dc)}
              {@const isLastMove = lastMove && ((lastMove[0] === dr && lastMove[1] === dc) || (lastMove[2] === dr && lastMove[3] === dc))}
              
              <button
                type="button"
                class="relative flex items-center justify-center cursor-pointer select-none transition-all duration-100
                       {isLight ? 'bg-[#eeeed2] text-[#759556]' : 'bg-[#759556] text-[#eeeed2]'}"
                style={isLastMove ? `background-color: ${isLight ? 'rgba(205,210,60,0.7)' : 'rgba(185,190,30,0.85)'}` : ''}
                onclick={() => onSquareClick(dr, dc)}
              >
                {#if isHighlighted}
                  <div class="pointer-events-none absolute z-20 rounded-full
                              {p ? 'w-[82%] h-[82%] bg-transparent border-[4.5px] border-sky-400/85' : 'w-[25%] h-[25%] bg-sky-400/85'}"
                  ></div>
                {/if}
                
                {#if c === 7}
                  <span class="absolute top-0.5 right-1 font-bold text-[9px] pointer-events-none opacity-50 select-none {isLight ? 'text-[#759556]' : 'text-[#eeeed2]'}">{8 - r}</span>
                {/if}
                {#if r === 7}
                  <span class="absolute bottom-0.5 left-1 font-bold text-[9px] pointer-events-none opacity-50 select-none {isLight ? 'text-[#759556]' : 'text-[#eeeed2]'}">{'abcdefgh'[c]}</span>
                {/if}
              </button>
            {/each}
          {/each}
        </div>

        <!-- Piece overlay container -->
        <div class="absolute inset-0 pointer-events-none z-10">
          {#each activePieces as piece (piece.id)}
            {@const visualRow = flipped ? 7 - piece.row : piece.row}
            {@const visualCol = flipped ? 7 - piece.col : piece.col}
            <div 
              class="absolute w-[12.5%] h-[12.5%] flex items-center justify-center"
              style="transform: translate3d({visualCol * 100}%, {visualRow * 100}%, 0); transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);"
            >
              <img 
                src={PIECES[piece.type]} 
                alt={piece.type} 
                class="w-[85%] h-[85%] select-none object-contain transition-transform duration-105" 
              />
            </div>
          {/each}
        </div>
        
        <!-- Drill Overlay -->
        {#if showDrillOverlay}
          <div class="absolute inset-0 bg-slate-950/80 backdrop-blur-sm flex flex-col items-center justify-center gap-4 z-30" transition:fade={{ duration: 150 }}>
            {#if drillResultIcon}
              <div class="h-16 w-16 rounded-full flex items-center justify-center text-4xl font-bold shadow-lg 
                          {drillResultIcon === '✓' ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30' : 'bg-rose-500/20 text-rose-400 border border-rose-500/30'}"
                   transition:scale
              >
                {drillResultIcon}
              </div>
            {/if}
            <div class="text-lg font-bold font-outfit text-center px-6 leading-tight text-white">{drillStatusMsg}</div>
            {#if !drillMode && drillResultIcon === '✓'}
              <button 
                type="button"
                class="mt-2 bg-sky-500 hover:bg-sky-400 active:scale-95 text-slate-950 font-bold px-4 py-2 rounded-lg text-sm transition-all cursor-pointer"
                onclick={hideDrillOverlay}
              >
                Continue
              </button>
            {/if}
          </div>
        {/if}
      </div>
      
      <!-- Chessboard Footer Bar -->
      <div class="w-full flex items-center justify-between mt-3 px-1 text-[#cbcfd2] text-xs md:text-sm font-medium">
        <!-- Repertoire status name -->
        <span class="text-[#cbcfd2] font-semibold truncate max-w-[200px] cursor-default">
          {#if history.length === 0}
            Start position
          {:else}
            {repStatus.name || 'Custom Line'}
          {/if}
        </span>
        
        <!-- Action items -->
        <div class="flex items-center gap-3">
          <!-- Plans button -->
          <button 
            type="button"
            class="hover:text-sky-400 transition-colors flex items-center gap-1.5 cursor-pointer text-[#78828a] font-medium"
            onclick={() => {
              const noteTextarea = document.querySelector('textarea');
              if (noteTextarea) {
                noteTextarea.focus();
                noteTextarea.scrollIntoView({ behavior: 'smooth', block: 'center' });
              }
            }}
          >
            <span>Plans</span>
            <i class="ti ti-map text-base"></i>
          </button>
          
          <span class="text-[#2a2e31]">|</span>
          
          <!-- Analyze on Lichess button -->
          <a 
            href={`https://lichess.org/analysis/pgn/${history.join('_')}?color=${currentRepertoireObj.side === 'w' ? 'white' : 'black'}`} 
            target="_blank" 
            rel="noopener noreferrer"
            class="hover:text-sky-400 transition-colors flex items-center gap-1.5 text-[#78828a] font-medium"
          >
            <span>Analyze</span>
            <i class="ti ti-external-link text-base"></i>
          </a>
        </div>
      </div>
      
      <!-- Board controls -->
      <div class="flex items-center gap-2 mt-4 self-center">
        <button 
          type="button"
          class="h-10 w-12 rounded-lg bg-[#202325] border border-[#2a2e31] text-[#cbcfd2] hover:text-[#f1f3f4] hover:bg-[#2a2e31] hover:border-[#78828a]/50 active:scale-95 flex items-center justify-center transition-all shadow-md cursor-pointer"
          onclick={goBack}
          title="Undo move"
        >
          <i class="ti ti-arrow-left text-lg"></i>
        </button>
        <button 
          type="button"
          class="h-10 w-12 rounded-lg bg-[#202325] border border-[#2a2e31] text-[#cbcfd2] hover:text-[#f1f3f4] hover:bg-[#2a2e31] hover:border-[#78828a]/50 active:scale-95 flex items-center justify-center transition-all shadow-md cursor-pointer"
          onclick={() => resetBoard(true)}
          title="Reset board"
        >
          <i class="ti ti-home text-lg"></i>
        </button>
        <button 
          type="button"
          class="h-10 w-12 rounded-lg bg-[#202325] border border-[#2a2e31] text-[#cbcfd2] hover:text-[#f1f3f4] hover:bg-[#2a2e31] hover:border-[#78828a]/50 active:scale-95 flex items-center justify-center transition-all shadow-md cursor-pointer"
          onclick={flipBoard}
          title="Flip board orientation"
        >
          <i class="ti ti-arrows-exchange-2 text-lg"></i>
        </button>
        <button 
          type="button"
          class="h-10 w-12 rounded-lg border active:scale-95 flex items-center justify-center transition-all shadow-md cursor-pointer
                 {voiceModeActive 
                   ? 'bg-rose-500/20 text-rose-400 border-rose-500/40 animate-pulse' 
                   : 'bg-[#202325] border-[#2a2e31] text-[#cbcfd2] hover:text-[#f1f3f4] hover:bg-[#2a2e31] hover:border-[#78828a]/50'}"
          onclick={toggleVoiceRecognition}
          title={voiceModeActive ? "Stop Voice Mode" : "Start Voice Mode (e.g. speak 'e4', 'Knight f3')"}
        >
          <i class="ti {isListening ? 'ti-microphone-off' : 'ti-microphone'} text-lg"></i>
        </button>
      </div>

      {#if voiceStatusMsg}
        <div class="mt-3 text-center text-xs font-semibold px-4 py-2 rounded-lg bg-[#202325]/40 border border-[#2a2e31] text-[#cbcfd2] transition-all max-w-[280px] self-center">
          <span class={isListening ? 'text-rose-400 animate-pulse' : 'text-sky-400'}>{voiceStatusMsg}</span>
        </div>
      {/if}
    </div>
    
    <!-- Spacer -->
    <div class="hidden md:block shrink-0" style="width: 48px; flex-grow: 0;"></div>
    
    <!-- Sidebar column (right) -->
    <div id="sidebar" class="z-10 relative transition-[max-width] duration-300 w-full md:max-w-[480px] lg:max-w-[500px] xl:max-w-[650px] self-stretch" style="flex-grow: 2; flex-basis: 0px; display: flex; flex-direction: column;">
      <div class="grow pb-5 lg:pb-12 rounded-xl border border-[#2a2e31]/80 shadow-lg p-5 flex flex-col gap-6" style="background-color: #202325; min-height: 100%;">
        <!-- Sleek Navigation tab switcher -->
        <div class="flex items-center justify-between border-b border-[#2a2e31] pb-3">
          <div class="flex bg-[#131416] border border-[#2a2e31] rounded-lg p-0.5 text-xs font-semibold">
            <button 
              type="button"
              class="px-3 py-1 rounded transition-all cursor-pointer {currentTab === 'build' ? 'bg-[#2a2e31] text-[#f1f3f4] font-bold shadow' : 'text-[#78828a] hover:text-[#cbcfd2]'}"
              onclick={() => switchTab('build')}
            >Build</button>
            <button 
              type="button"
              class="px-3 py-1 rounded transition-all cursor-pointer {currentTab === 'drill' ? 'bg-[#2a2e31] text-[#f1f3f4] font-bold shadow' : 'text-[#78828a] hover:text-[#cbcfd2]'}"
              onclick={() => switchTab('drill')}
            >Drill</button>
            <button 
              type="button"
              class="px-3 py-1 rounded transition-all cursor-pointer {currentTab === 'openings' ? 'bg-[#2a2e31] text-[#f1f3f4] font-bold shadow' : 'text-[#78828a] hover:text-[#cbcfd2]'}"
              onclick={() => switchTab('openings')}
            >Openings</button>
          </div>
        </div>

        {#if currentTab === 'build'}
          <!-- BUILD VIEW -->
          <!-- Move List Section -->
          <div class="flex flex-col gap-3">
            <div class="flex items-center justify-between border-b border-[#2a2e31]/60 pb-2">
              <span class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">Current Line</span>
              <div class="flex items-center gap-3">
                {#if history.length > 0 || activeOpeningId}
                  <button 
                    type="button"
                    class="text-xs text-sky-400 hover:text-sky-300 font-semibold transition-colors flex items-center gap-1 cursor-pointer"
                    onclick={() => {
                      showPgnEdit = !showPgnEdit;
                      pgnEditVal = history.join(' ');
                      pgnEditError = '';
                    }}
                  >
                    <i class="ti ti-edit"></i> {showPgnEdit ? 'Cancel Edit' : 'Edit Moves'}
                  </button>
                {/if}
                {#if history.length > 0}
                  <button 
                    type="button"
                    class="text-xs text-[#78828a] hover:text-rose-400 font-semibold transition-colors flex items-center gap-1 cursor-pointer"
                    onclick={() => resetBoard(true)}
                  >
                    <i class="ti ti-trash"></i> Clear Line
                  </button>
                {/if}
              </div>
            </div>
            
            {#if showPgnEdit}
              <div class="flex flex-col gap-2 p-3 rounded-lg border border-[#2a2e31] bg-[#131416]/50" transition:slide>
                <label for="pgn-edit-input" class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">Edit PGN Sequence</label>
                <input
                  id="pgn-edit-input"
                  class="w-full bg-[#131416] border border-[#2a2e31] rounded-lg px-3 py-2 text-sm text-[#f1f3f4] focus:outline-none focus:border-sky-500 transition-colors font-mono"
                  placeholder="e.g. e4 e5 Nf3 Nc6 Bc4"
                  bind:value={pgnEditVal}
                />
                {#if pgnEditError}
                  <span class="text-xs text-rose-400 font-medium">{pgnEditError}</span>
                {/if}
                <div class="flex gap-2 justify-end mt-1">
                  <button
                    type="button"
                    class="px-3 py-1.5 text-xs font-semibold rounded-lg border border-[#2a2e31] hover:bg-[#131416] text-[#cbcfd2] transition-all cursor-pointer"
                    onclick={() => { showPgnEdit = false; pgnEditError = ''; }}
                  >
                    Cancel
                  </button>
                  <button
                    type="button"
                    class="px-3 py-1.5 text-xs font-semibold rounded-lg bg-sky-500 hover:bg-sky-400 text-slate-950 font-bold transition-all cursor-pointer font-semibold"
                    onclick={savePgnEdit}
                  >
                    Apply Changes
                  </button>
                </div>
              </div>
            {/if}

            <div class="flex flex-wrap gap-2 min-h-12 items-center">
              {#if history.length === 0}
                <p class="text-sm text-[#78828a] italic">Make a move on the board to start building your opening line...</p>
              {:else}
                {#each history as san, i}
                  {@const pathAtMove = history.slice(0, i + 1).join(' ')}
                  {@const tagAtMove = moveTags[currentRep] && moveTags[currentRep][pathAtMove]}
                  {#if i % 2 === 0}
                    <span class="text-sm font-mono text-[#78828a] font-semibold select-none align-middle">{i / 2 + 1}.</span>
                  {/if}
                  <div class="relative group/chip inline-block align-middle">
                    <button
                      type="button"
                      class="font-mono text-sm pl-2.5 pr-7 py-1 rounded-md border transition-all cursor-pointer 
                             {i === history.length - 1 
                               ? 'bg-sky-500/20 text-sky-400 border-sky-500/40 font-semibold shadow-md shadow-sky-500/5' 
                               : 'bg-[#131416] text-[#cbcfd2] border-[#2a2e31] hover:border-[#78828a] hover:text-[#f1f3f4]'}"
                      onclick={() => clickMoveChip(i)}
                    >
                      {#if tagAtMove === 'inaccuracy'}
                        {san}<span class="text-amber-500 font-bold font-mono">?</span>
                      {:else if tagAtMove === 'blunder'}
                        {san}<span class="text-rose-500 font-bold font-mono">??</span>
                      {:else}
                        {san}
                      {/if}
                    </button>
                    <!-- Remove/Truncate button -->
                    <button
                      type="button"
                      class="absolute right-1 top-1/2 -translate-y-1/2 text-[#78828a] hover:text-rose-400 transition-colors p-0.5 rounded cursor-pointer"
                      onclick={(e) => { e.stopPropagation(); truncateMoveHistory(i); }}
                      title="Remove this move and all following moves"
                    >
                      <i class="ti ti-x text-[10px]"></i>
                    </button>
                  </div>
                {/each}
              {/if}
            </div>
          </div>
          
          <!-- Branches / Continuations Section OR Choose Your Side -->
          {#if showSideSelection}
            <div class="flex flex-col gap-4" transition:slide>
              <div class="flex items-center gap-2 border-b border-[#2a2e31]/60 pb-2">
                <i class="ti ti-chess text-sky-400"></i>
                <span class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">Choose Your Side</span>
              </div>
              
              <div class="flex flex-col divide-y divide-[#2a2e31]/60">
                <button
                  type="button"
                  class="flex items-center justify-between py-3 hover:text-sky-400 transition-colors text-left group cursor-pointer w-full"
                  onclick={() => {
                    currentRep = 0;
                    flipped = false;
                    showSideSelection = false;
                  }}
                >
                  <div class="flex items-center gap-3">
                    <img src={PIECES.wK} alt="White King" class="w-6 h-6 object-contain" />
                    <span class="font-semibold text-[#cbcfd2] group-hover:text-sky-400 text-sm">Play as White</span>
                  </div>
                  <i class="ti ti-chevron-right text-[#78828a] group-hover:text-sky-400 transition-colors text-sm"></i>
                </button>

                <button
                  type="button"
                  class="flex items-center justify-between py-3 hover:text-sky-400 transition-colors text-left group cursor-pointer w-full"
                  onclick={() => {
                    currentRep = 1;
                    flipped = true;
                    showSideSelection = false;
                  }}
                >
                  <div class="flex items-center gap-3">
                    <img src={PIECES.bK} alt="Black King" class="w-6 h-6 object-contain" />
                    <span class="font-semibold text-[#cbcfd2] group-hover:text-sky-400 text-sm">Play as Black</span>
                  </div>
                  <i class="ti ti-chevron-right text-[#78828a] group-hover:text-sky-400 transition-colors text-sm"></i>
                </button>
              </div>
            </div>
          {:else}
            <div class="flex flex-col gap-4">
              <div class="flex items-center justify-between border-b border-[#2a2e31]/60 pb-2">
                <div class="flex items-center gap-2">
                  <i class="ti ti-git-branch text-sky-400"></i>
                  <span class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">You need to prepare for these moves</span>
                </div>
              </div>
              
              <div class="w-full grid gap-y-1" style="grid-template-columns: max-content minmax(0px, 1fr) max-content 70px; gap-x: 16px;">
                <!-- Table Header -->
                <div class="grid w-full pb-2 border-b border-[#2a2e31]/60" style="grid-column: 1/-1; grid-template-columns: subgrid;">
                  <div class="text-xs font-semibold text-[#78828a] uppercase tracking-wider text-start" style="grid-column: 1/span 2;">
                    Opponent's Move / Continuation
                  </div>
                  <div class="text-xs font-semibold text-[#78828a] uppercase tracking-wider text-end" style="grid-column: 3/span 1;">
                    Expected
                  </div>
                  <div class="text-xs font-semibold text-[#78828a] uppercase tracking-wider text-center" style="grid-column: 4/span 1;">
                    Win %
                  </div>
                </div>
                
                {#if branches.length === 0}
                  <div class="text-center py-6 text-[#78828a] italic text-sm" style="grid-column: 1/-1;">
                    No repertoire continuations saved at this position.
                  </div>
                {:else}
                  {#each branches as b, i}
                    {@const moveNum = Math.floor((history.length) / 2) + 1}
                    {@const isBlackMove = (history.length) % 2 === 1}
                    {@const branchNote = getNoteForHistory([...history, b.move])}
                    {@const hash = b.move.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)}
                    {@const gamesCount = (hash * 17) % 950 + 10}
                    {@const successRate = 45 + (hash % 25)}
                    {@const movePathStr = [...history, b.move].join(' ')}
                    {@const currentTag = moveTags[currentRep] && moveTags[currentRep][movePathStr]}
                    <!-- Row container -->
                    <div
                      class="relative isolate w-full rounded-sm py-3 transition-colors group grid items-center hover:bg-[#2a2e31]/20 border-b border-[#2a2e31]/30 last:border-b-0"
                      style="grid-column: 1/-1; grid-template-columns: subgrid; background: none;"
                    >
                      <!-- Clickable Left Area (Move + Continuation Name) -->
                      <button
                        type="button"
                        class="relative z-1 text-left w-full cursor-pointer grid items-center"
                        style="grid-column: 1/span 2; grid-template-columns: subgrid; display: grid;"
                        onclick={() => {
                          playMoveByHistory(b.move);
                          history.push(b.move);
                          history = history;
                        }}
                      >
                        <!-- Column 1: Opponent's Move -->
                        <div class="relative z-1 text-[#cbcfd2] font-semibold text-sm flex items-center min-w-0 pr-6" style="grid-column: 1/span 1;">
                          <span class="text-[#78828a] font-normal mr-1">{moveNum}{isBlackMove ? '...' : '.'}</span>
                          <span class="text-sky-400 group-hover:text-sky-300 font-bold">
                            {#if currentTag === 'inaccuracy'}
                              {b.move} <span class="text-amber-500 font-bold font-mono">?</span>
                            {:else if currentTag === 'blunder'}
                              {b.move} <span class="text-rose-500 font-bold font-mono">??</span>
                            {:else}
                              {b.move}
                            {/if}
                          </span>
                        </div>

                        <!-- Column 2: Continuation Name + Short note -->
                        <div class="relative z-1 flex flex-col text-start min-w-0" style="grid-column: 2/span 1;">
                          {#if shouldShowBranchOpeningName(b.opening)}
                            <span class="text-xs font-semibold text-[#f1f3f4] leading-tight truncate">{b.opening}</span>
                          {:else}
                            <span class="text-xs font-semibold text-[#cbcfd2] leading-tight truncate">Continuation Line</span>
                          {/if}
                          {#if branchNote}
                            <span class="text-[11px] text-[#78828a] leading-relaxed mt-0.5 break-words whitespace-normal">{branchNote}</span>
                          {:else}
                            <span class="text-[11px] text-[#78828a]/60 leading-relaxed mt-0.5">No notes added</span>
                          {/if}
                        </div>
                      </button>

                      <!-- Column 3: Stats (Expected) / Tag actions -->
                      <div class="relative z-1 text-[#cbcfd2] text-xs text-end font-medium tabular-nums flex items-center justify-end gap-1.5" style="grid-column: 3/span 1;">
                        <span class="text-[#cbcfd2] group-hover:hidden">{gamesCount} games</span>
                        
                        <!-- Actions (shown on hover) -->
                        <div class="hidden group-hover:flex items-center gap-1">
                          <button
                            type="button"
                            class="px-1.5 py-0.5 rounded text-xs bg-[#131416] border border-[#2a2e31] hover:border-amber-500 hover:text-amber-500 transition-colors font-bold cursor-pointer"
                            onclick={(e) => { e.stopPropagation(); toggleMoveTag(movePathStr, 'inaccuracy'); }}
                            title="Tag as inaccuracy (?)"
                          >
                            ?
                          </button>
                          <button
                            type="button"
                            class="px-1.5 py-0.5 rounded text-xs bg-[#131416] border border-[#2a2e31] hover:border-rose-500 hover:text-rose-500 transition-colors font-bold cursor-pointer"
                            onclick={(e) => { e.stopPropagation(); toggleMoveTag(movePathStr, 'blunder'); }}
                            title="Tag as blunder (??)"
                          >
                            ??
                          </button>
                          {#if currentTag}
                            <button
                              type="button"
                              class="px-1 py-0.5 rounded text-xs bg-[#131416] border border-[#2a2e31] hover:text-slate-200 transition-colors cursor-pointer"
                              onclick={(e) => { e.stopPropagation(); toggleMoveTag(movePathStr, null); }}
                              title="Clear tag"
                            >
                              <i class="ti ti-ban text-[10px]"></i>
                            </button>
                          {/if}
                        </div>
                      </div>

                      <!-- Column 4: Popularity / Win % Progress Bar -->
                      <div class="relative z-1 flex items-center justify-center w-full" style="grid-column: 4/span 1;">
                        <div class="w-full flex flex-col gap-1 items-center">
                          <span class="text-[10px] text-emerald-400 font-mono font-bold leading-none">{successRate}%</span>
                          <div class="w-full rounded-full overflow-hidden h-1 bg-[#2a2e31]">
                            <div class="bg-emerald-500 h-full" style="width: {successRate}%"></div>
                          </div>
                        </div>
                      </div>
                    </div>
                  {/each}
                {/if}
              </div>
            </div>
          {/if}
          
          <!-- Move Note Section -->
          <div class="flex flex-col gap-4">
            <div class="flex flex-col gap-1 border-b border-[#2a2e31]/60 pb-2">
              <span class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">Position Note</span>
              <span class="text-xs text-[#78828a] truncate font-mono">
                {#if history.length > 0}
                  After {lineStr}
                {:else}
                  —
                {/if}
              </span>
            </div>
            
            <textarea
              class="w-full min-h-24 bg-[#131416] border border-[#2a2e31] rounded-lg p-3 text-sm text-[#f1f3f4] placeholder-[#78828a]/40 focus:outline-none focus:border-sky-500/50 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
              placeholder="Add educational notes, ideas, or tactical motifs for this position..."
              bind:value={noteText}
              disabled={history.length === 0}
            ></textarea>
            
            <button
              type="button"
              class="self-start px-4 py-2 text-sm font-semibold rounded-lg border flex items-center gap-2 transition-all cursor-pointer
                     {saveSuccess 
                       ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30 shadow-md shadow-emerald-500/5' 
                       : 'bg-[#2a2e31] hover:bg-[#2a2e31]/80 text-[#f1f3f4] border-[#2a2e31]/50 disabled:opacity-40 disabled:cursor-not-allowed'}"
              onclick={saveNote}
              disabled={history.length === 0}
            >
              <i class="ti {saveSuccess ? 'ti-check' : 'ti-device-floppy'}"></i>
              {saveSuccess ? 'Note Saved!' : 'Save Note'}
            </button>
          </div>
          
          <!-- Transpositions Section -->
          {#if transpositions.length > 0}
            <div class="bg-indigo-950/20 border border-indigo-500/20 rounded-xl p-4 flex flex-col gap-3 shadow-lg" transition:slide>
              <div class="flex items-center gap-2 text-indigo-400">
                <i class="ti ti-arrows-right-left text-lg"></i>
                <span class="text-xs font-bold uppercase tracking-wider font-outfit">Transposition Detected</span>
              </div>
              <p class="text-xs text-slate-400">This board position can also be reached via other saved lines. Click to transpose your move history:</p>
              <div class="flex flex-col gap-2">
                {#each transpositions as t}
                  <button
                    type="button"
                    class="flex items-center justify-between p-2.5 rounded-lg border border-indigo-500/30 bg-indigo-950/35 hover:bg-indigo-950/60 transition-all text-left text-xs cursor-pointer group"
                    onclick={() => applyTransposition(t)}
                  >
                    <div>
                      <span class="font-semibold text-slate-200 group-hover:text-indigo-300">{t.opening.name}</span>
                      <span class="font-mono text-slate-400 block mt-0.5">{t.historyStr || 'Initial position'}</span>
                    </div>
                    <span class="text-[10px] font-bold uppercase tracking-wider text-indigo-400 border border-indigo-500/30 px-2 py-0.5 rounded-md bg-indigo-950/50 group-hover:bg-indigo-500 group-hover:text-slate-950 transition-all font-semibold">Transpose</span>
                  </button>
                {/each}
              </div>
            </div>
          {/if}

          <!-- Status Bar -->
          <div class="bg-[#131416]/60 border border-[#2a2e31] rounded-xl p-4 flex flex-col sm:flex-row items-center justify-between gap-4 shadow-lg">
            <div class="flex flex-col gap-1 w-full sm:w-auto">
              <span class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">Repertoire Status</span>
              <div class="flex items-center gap-2">
                {#if history.length === 0}
                  <span class="text-sm text-[#78828a] italic">No moves played</span>
                {:else if repStatus.isExact}
                  <span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
                    <span class="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse"></span> In Repertoire
                  </span>
                  <span class="text-sm font-medium text-[#cbcfd2] truncate max-w-48">{repStatus.name}</span>
                {:else if repStatus.isContinuation}
                  <span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-indigo-500/10 text-indigo-400 border border-indigo-500/20">
                    {repStatus.parentName} Cont...
                  </span>
                {:else}
                  <span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-amber-500/10 text-amber-400 border border-amber-500/20">
                    Not in Repertoire
                  </span>
                {/if}
              </div>
            </div>
            
            {#if history.length > 0 || activeOpening}
              <div class="flex flex-wrap items-center gap-2 w-full sm:w-auto">
                {#if activeOpening}
                  <button
                    type="button"
                    class="flex-1 sm:flex-initial bg-transparent hover:bg-rose-500/10 text-rose-400 border border-rose-500/30 font-bold px-4 py-2 rounded-lg text-sm active:scale-95 transition-all flex items-center justify-center gap-1.5 cursor-pointer font-semibold"
                    onclick={() => deleteOpening(activeOpening.id)}
                  >
                    <i class="ti ti-trash"></i> Delete Line
                  </button>
                {/if}

                {#if activeOpening && lineStr !== activeOpening.pgn}
                  <button
                    type="button"
                    class="flex-1 sm:flex-initial bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-bold px-4 py-2 rounded-lg text-sm shadow-md hover:shadow-lg active:scale-95 transition-all flex items-center justify-center gap-1.5 cursor-pointer font-semibold"
                    onclick={updateActiveOpening}
                  >
                    <i class="ti ti-device-floppy"></i> Update {activeOpening.name}
                  </button>
                {:else if repStatus.isContinuation}
                  <button
                    type="button"
                    class="flex-1 sm:flex-initial bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-bold px-4 py-2 rounded-lg text-sm shadow-md hover:shadow-lg active:scale-95 transition-all flex items-center justify-center gap-1.5 cursor-pointer font-semibold"
                    onclick={() => saveToParentOpening(repStatus.parentId)}
                  >
                    <i class="ti ti-device-floppy"></i> Save to {repStatus.parentName}
                  </button>
                {/if}
                
                {#if history.length > 0 && !repStatus.isExact}
                  <button
                    type="button"
                    class="flex-1 sm:flex-initial bg-gradient-to-r from-sky-500 to-indigo-600 hover:from-sky-400 hover:to-indigo-500 text-slate-950 font-bold px-4 py-2 rounded-lg text-sm shadow-md hover:shadow-lg hover:shadow-sky-500/10 active:scale-95 transition-all flex items-center justify-center gap-1.5 cursor-pointer font-semibold"
                    onclick={addCurrentLine}
                  >
                    <i class="ti ti-plus"></i> Save as New
                  </button>
                {/if}
              </div>
            {/if}
          </div>
        {:else if currentTab === 'openings'}
          <!-- OPENINGS VIEW -->
          <div class="flex flex-col gap-4">
            <div class="flex items-center justify-between border-b border-[#2a2e31]/60 pb-2">
              <div class="flex flex-col gap-0.5">
                <span class="text-sm font-bold text-slate-200 font-outfit">{currentRepertoireObj.name}</span>
                <span class="text-xs text-slate-400">{currentRepertoireObj.openings.length} opening line{currentRepertoireObj.openings.length !== 1 ? 's' : ''} saved</span>
              </div>
            </div>
            
            {#snippet renderNode(node, depth)}
              <div class="flex flex-col gap-2" style="margin-left: {depth > 0 ? 12 : 0}px">
                <div 
                  role="button"
                  tabindex="0"
                  class="p-4 rounded-xl border transition-all text-left flex flex-col gap-1.5 cursor-pointer group shadow-sm hover:shadow-md
                         {activeOpeningId === node.id 
                           ? 'border-sky-500/50 bg-sky-950/20' 
                           : 'border-[#2a2e31] hover:border-sky-500/30 bg-[#131416]/40 hover:bg-[#2a2e31]/20'}"
                  onclick={() => {
                    activeOpeningId = node.id;
                    currentTab = 'build';
                    loadOpening(node);
                  }}
                  onkeydown={(e) => {
                    if (e.key === 'Enter' || e.key === ' ') {
                      activeOpeningId = node.id;
                      currentTab = 'build';
                      loadOpening(node);
                    }
                  }}
                >
                  <div class="flex items-center justify-between">
                    <div class="flex items-center gap-2 min-w-0">
                      {#if depth > 0}
                        <span class="text-sky-500 font-bold shrink-0">└─</span>
                      {/if}
                      <span class="font-bold text-slate-200 group-hover:text-sky-400 transition-colors font-outfit text-base truncate">{node.name}</span>
                    </div>
                    <div class="flex items-center gap-2 shrink-0">
                      <span class="text-xs text-[#cbcfd2] font-mono flex items-center gap-1 mr-1">
                        <i class="ti ti-chess"></i> {node.pgn.split(' ').filter(Boolean).length} moves
                      </span>
                      <button
                        type="button"
                        class="p-1 rounded text-slate-400 hover:text-sky-400 hover:bg-[#2a2e31]/60 transition-all cursor-pointer"
                        onclick={(e) => { e.stopPropagation(); renameOpening(node.id); }}
                        title="Rename opening"
                      >
                        <i class="ti ti-edit text-xs"></i>
                      </button>
                      <button
                        type="button"
                        class="p-1 rounded text-slate-400 hover:text-rose-400 hover:bg-[#2a2e31]/60 transition-all cursor-pointer"
                        onclick={(e) => { e.stopPropagation(); deleteOpening(node.id); }}
                        title="Delete opening and sub-lines"
                      >
                        <i class="ti ti-trash text-xs"></i>
                      </button>
                    </div>
                  </div>
                  <div class="font-mono text-xs text-[#cbcfd2]/80 truncate max-w-full bg-[#131416]/65 p-2 rounded border border-[#2a2e31]/80">{node.pgn || 'No moves added yet'}</div>
                </div>
                
                {#if node.children && node.children.length > 0}
                  <div class="flex flex-col gap-2 border-l border-[#2a2e31]/60 pl-3 mt-1 ml-3">
                    {#each node.children as child}
                      {@render renderNode(child, depth + 1)}
                    {/each}
                  </div>
                {/if}
              </div>
            {/snippet}

            <div class="flex flex-col gap-3">
              {#if openingTree.length === 0}
                <p class="text-sm text-slate-500 italic text-center py-6">No opening lines created yet.</p>
              {:else}
                {#each openingTree as rootNode}
                  {@render renderNode(rootNode, 0)}
                {/each}
              {/if}
            </div>
            
            {#if !showNewForm}
              <button
                type="button"
                class="py-3 px-4 rounded-xl border border-dashed border-[#2a2e31] hover:border-[#78828a]/60 bg-[#131416]/20 hover:bg-[#131416]/50 text-[#cbcfd2] hover:text-[#f1f3f4] transition-all text-center flex items-center justify-center gap-1.5 cursor-pointer text-sm font-semibold"
                onclick={() => { showNewForm = true; }}
              >
                <i class="ti ti-plus"></i> New Opening Line
              </button>
            {:else}
              <div class="p-4 rounded-xl border border-sky-500/30 bg-sky-950/10 flex flex-col gap-4" transition:slide>
                <div class="flex flex-col gap-1.5">
                  <label for="new-op-name" class="text-xs font-bold uppercase tracking-wider text-sky-400 font-outfit">Opening Name</label>
                  <input
                    id="new-op-name"
                    class="w-full bg-[#131416] border border-[#2a2e31] rounded-lg px-3 py-2 text-sm text-[#f1f3f4] focus:outline-none focus:border-sky-500 transition-colors"
                    placeholder="e.g. Ruy Lopez, Berlin Defense"
                    bind:value={newOpName}
                  />
                </div>
                <div class="flex flex-col gap-1.5">
                  <label for="new-op-parent" class="text-xs font-bold uppercase tracking-wider text-sky-400 font-outfit">Parent Opening (Optional)</label>
                  <select
                    id="new-op-parent"
                    class="w-full bg-[#131416] border border-[#2a2e31] rounded-lg px-3 py-2 text-sm text-[#cbcfd2] focus:outline-none focus:border-sky-500 transition-colors cursor-pointer"
                    bind:value={newOpParentId}
                  >
                    <option value="">None (Root Opening)</option>
                    {#each currentRepertoireObj.openings as op}
                      <option value={op.id}>{op.name} ({op.pgn || 'empty'})</option>
                    {/each}
                  </select>
                </div>
                <div class="flex gap-3">
                  <button
                    type="button"
                    class="flex-1 py-2 px-3 rounded-lg border border-[#2a2e31] hover:bg-[#131416] text-[#cbcfd2] font-semibold text-sm transition-all cursor-pointer"
                    onclick={() => { showNewForm = false; newOpName = ''; newOpParentId = ''; }}
                  >
                    Cancel
                  </button>
                  <button
                    type="button"
                    class="flex-1 py-2 px-3 rounded-lg bg-sky-500 hover:bg-sky-400 text-slate-950 font-bold text-sm transition-all cursor-pointer"
                    onclick={createNewOpening}
                  >
                    Create
                  </button>
                </div>
              </div>
            {/if}
          </div>
        {:else if currentTab === 'drill'}
          <!-- DRILL VIEW -->
          <div class="flex flex-col gap-4">
            <div class="flex items-center gap-2 border-b border-[#2a2e31]/60 pb-2">
              <i class="ti ti-route text-sky-400 text-lg"></i>
              <span class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">Full Line Playthrough</span>
            </div>
            
            <div class="flex flex-col gap-4">
              <p class="text-sm text-[#cbcfd2]/85">Pick an opening from your repertoire and practice playing the correct moves on the board. The tool will auto-respond with the opponent's moves!</p>
              
              <div class="flex flex-col gap-2">
                <label for="drill-select" class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">Select Opening to Practice</label>
                {#if currentRepertoireObj.openings.length === 0}
                  <p class="text-sm text-slate-500 italic">No openings saved to drill yet.</p>
                {:else}
                  <select 
                    id="drill-select"
                    class="bg-[#131416] border border-[#2a2e31] rounded-lg p-2.5 text-sm text-[#cbcfd2] focus:outline-none focus:border-sky-500 transition-colors w-full cursor-pointer"
                    bind:value={drillOpeningId}
                  >
                    {#each currentRepertoireObj.openings as op}
                      <option value={op.id}>{op.name}</option>
                    {/each}
                  </select>
                  
                  <button
                    type="button"
                    class="mt-2 bg-gradient-to-r from-sky-500 to-indigo-600 hover:from-sky-400 hover:to-indigo-500 text-slate-950 font-bold py-2.5 px-4 rounded-lg text-sm shadow-md hover:shadow-lg hover:shadow-sky-500/10 active:scale-95 transition-all flex items-center justify-center gap-2 cursor-pointer font-semibold"
                    onclick={() => {
                      let idToDrill = drillOpeningId || (currentRepertoireObj.openings[0] && currentRepertoireObj.openings[0].id);
                      let op = currentRepertoireObj.openings.find(o => o.id == idToDrill);
                      if (op) {
                        switchTab('build');
                        startDrill(op);
                      }
                    }}
                    disabled={currentRepertoireObj.openings.length === 0}
                  >
                    <i class="ti ti-player-play"></i> Start Drill Practice
                  </button>
                {/if}
              </div>
            </div>
          </div>
          
          <!-- Gaps & Weak Spots Section -->
          <div class="flex flex-col gap-4">
            <div class="flex items-center gap-2 border-b border-[#2a2e31]/60 pb-2">
              <i class="ti ti-alert-triangle text-amber-400 text-lg"></i>
              <span class="text-xs font-bold uppercase tracking-wider text-[#78828a] font-outfit">Repertoire Gaps & Weak Spots</span>
            </div>
            
            <div class="flex flex-col gap-2">
              {#if gaps.length === 0}
                <div class="flex items-center gap-2.5 p-4 rounded-lg bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 text-sm">
                  <i class="ti ti-shield-check text-lg"></i>
                  <span>All lines look solid! No gaps or short lines detected.</span>
                </div>
              {:else}
                <div class="flex flex-col gap-2">
                  {#each gaps as g}
                    <div class="flex items-start gap-2.5 p-3 rounded-lg bg-amber-500/10 text-amber-400 border border-amber-500/20 text-sm">
                      <i class="ti ti-alert-circle text-lg mt-0.5"></i>
                      <span>{g}</span>
                    </div>
                  {/each}
                </div>
              {/if}
            </div>
          </div>
        {/if}
    </div>
    </div>
  </main>
</div>
