<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Square Dance Caller โ€” Choreography Reader</title>
<style>
  @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@300;400;500&display=swap');

  :root {
    --bg: #0d0d0d;
    --surface: #141414;
    --surface2: #1c1c1c;
    --border: #2a2a2a;
    --accent: #e8c84a;
    --accent2: #c45c2a;
    --text: #e8e4da;
    --text-muted: #6b6760;
    --text-dim: #3d3b37;
    --active-bg: #1e1a0a;
    --active-border: #e8c84a;
    --active-text: #f5e88a;
    --divider: #2e1f00;
  }

  * { box-sizing: border-box; margin: 0; padding: 0; }

  body {
    background: var(--bg);
    color: var(--text);
    font-family: 'IBM Plex Mono', monospace;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  /* === HEADER === */
  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 24px;
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    flex-shrink: 0;
    gap: 16px;
  }

  .logo {
    font-family: 'Bebas Neue', cursive;
    font-size: 22px;
    letter-spacing: 3px;
    color: var(--accent);
    white-space: nowrap;
  }
  .logo span { color: var(--text-muted); }

  .file-controls {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
  }

  .btn {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text);
    font-family: 'IBM Plex Mono', monospace;
    font-size: 11px;
    padding: 6px 14px;
    cursor: pointer;
    letter-spacing: 1px;
    text-transform: uppercase;
    transition: all 0.15s;
  }
  .btn:hover { border-color: var(--accent); color: var(--accent); }
  .btn.primary {
    border-color: var(--accent);
    color: var(--accent);
    background: rgba(232,200,74,0.06);
  }
  .btn.primary:hover { background: rgba(232,200,74,0.14); }

  #fileInput, #folderInput { display: none; }

  /* === STATUS BAR === */
  .status-bar {
    display: flex;
    align-items: center;
    gap: 0;
    padding: 0 24px;
    background: var(--surface2);
    border-bottom: 1px solid var(--border);
    font-size: 10px;
    color: var(--text-muted);
    letter-spacing: 0.8px;
    text-transform: uppercase;
    flex-shrink: 0;
    height: 30px;
    overflow: hidden;
  }
  .status-item {
    padding: 0 16px 0 0;
    margin: 0 16px 0 0;
    border-right: 1px solid var(--border);
    white-space: nowrap;
  }
  .status-item:last-child { border-right: none; }
  .status-item strong { color: var(--accent); }

  /* === LAYOUT === */
  .main-layout {
    display: flex;
    flex: 1;
    overflow: hidden;
  }

  /* === SIDEBAR === */
  .sidebar {
    width: 220px;
    min-width: 220px;
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    background: var(--surface);
    overflow: hidden;
  }
  .sidebar-header {
    padding: 10px 14px;
    font-size: 9px;
    letter-spacing: 2px;
    color: var(--text-muted);
    text-transform: uppercase;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
  }
  .file-list { overflow-y: auto; flex: 1; }
  .file-list::-webkit-scrollbar { width: 4px; }
  .file-list::-webkit-scrollbar-track { background: transparent; }
  .file-list::-webkit-scrollbar-thumb { background: var(--border); }

  .file-item {
    padding: 9px 14px;
    cursor: pointer;
    font-size: 11px;
    color: var(--text-muted);
    border-bottom: 1px solid #1a1a1a;
    transition: all 0.1s;
    display: flex;
    align-items: center;
    gap: 8px;
    overflow: hidden;
  }
  .file-item:hover { background: var(--surface2); color: var(--text); }
  .file-item.active {
    background: rgba(232,200,74,0.07);
    color: var(--accent);
    border-left: 2px solid var(--accent);
    padding-left: 12px;
  }
  .file-item .fi-icon { opacity: 0.5; flex-shrink: 0; }
  .file-item .fi-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .file-item .fi-count { margin-left: auto; font-size: 9px; color: var(--text-dim); flex-shrink: 0; }
  .file-item.active .fi-count { color: var(--accent); opacity: 0.5; }

  /* === SEQ SIDEBAR === */
  .seq-sidebar {
    width: 60px;
    min-width: 60px;
    border-right: 1px solid var(--border);
    background: var(--bg);
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  .seq-sidebar-header {
    padding: 10px 0;
    font-size: 9px;
    letter-spacing: 1px;
    color: var(--text-dim);
    text-transform: uppercase;
    border-bottom: 1px solid var(--border);
    text-align: center;
    flex-shrink: 0;
  }
  .seq-list { overflow-y: auto; flex: 1; }
  .seq-list::-webkit-scrollbar { width: 3px; }
  .seq-list::-webkit-scrollbar-track { background: var(--bg); }
  .seq-list::-webkit-scrollbar-thumb { background: var(--border); }
  .seq-item {
    width: 100%;
    padding: 8px 4px;
    text-align: center;
    font-size: 10px;
    color: var(--text-dim);
    cursor: pointer;
    border-bottom: 1px solid #161616;
    transition: all 0.1s;
    font-family: 'IBM Plex Mono', monospace;
  }
  .seq-item:hover { color: var(--text-muted); background: var(--surface); }
  .seq-item.active {
    color: var(--accent);
    background: rgba(232,200,74,0.07);
    border-left: 2px solid var(--accent);
  }

  /* === READER === */
  .reader-pane { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
  .reader-scroll {
    flex: 1;
    overflow-y: auto;
    padding: 24px 0;
    scroll-behavior: smooth;
  }
  .reader-scroll::-webkit-scrollbar { width: 6px; }
  .reader-scroll::-webkit-scrollbar-track { background: transparent; }
  .reader-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

  .seq-title {
    font-family: 'Bebas Neue', cursive;
    font-size: 13px;
    letter-spacing: 4px;
    color: var(--text-dim);
    padding: 0 40px 16px;
    text-transform: uppercase;
  }

  .call-line {
    display: flex;
    align-items: center;
    padding: 10px 40px;
    gap: 20px;
    cursor: pointer;
    transition: background 0.08s;
    position: relative;
    border-left: 3px solid transparent;
  }
  .call-line:hover { background: rgba(255,255,255,0.02); }
  .call-line .line-num {
    font-size: 10px;
    color: var(--text-dim);
    min-width: 28px;
    text-align: right;
    flex-shrink: 0;
    font-family: 'IBM Plex Mono', monospace;
    user-select: none;
  }
  .call-line .line-text {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 17px;
    color: var(--text-muted);
    letter-spacing: 0.5px;
    line-height: 1.4;
    transition: color 0.1s;
    word-break: break-word;
  }

  .call-line.active {
    background: var(--active-bg);
    border-left: 3px solid var(--active-border);
  }
  .call-line.active .line-num { color: var(--accent); opacity: 0.6; }
  .call-line.active .line-text {
    color: var(--active-text);
    font-size: 20px;
    font-weight: 600;
    text-shadow: 0 0 30px rgba(232,200,74,0.25);
  }
  .call-line.active::after {
    content: 'โ–ถ';
    position: absolute;
    right: 24px;
    color: var(--accent);
    opacity: 0.5;
    font-size: 10px;
  }

  .call-line.prev1 .line-text { color: #5a5650; font-size: 18px; }
  .call-line.prev2 .line-text { color: #3d3b37; }
  .call-line.next1 .line-text { color: #5a5650; font-size: 18px; }
  .call-line.next2 .line-text { color: #3d3b37; }

  .seq-divider {
    display: flex;
    align-items: center;
    padding: 20px 40px;
    gap: 16px;
    color: var(--text-dim);
    font-size: 10px;
    letter-spacing: 2px;
    text-transform: uppercase;
    user-select: none;
  }
  .seq-divider::before, .seq-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--divider);
  }

  /* === EMPTY STATE === */
  .empty-state {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 24px;
    padding: 40px;
    text-align: center;
  }
  .empty-icon { font-size: 48px; opacity: 0.15; }
  .empty-title {
    font-family: 'Bebas Neue', cursive;
    font-size: 28px;
    letter-spacing: 5px;
    color: var(--text-muted);
  }
  .empty-desc {
    font-family: 'IBM Plex Sans', sans-serif;
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.7;
    max-width: 400px;
  }
  .empty-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
  .kbd {
    display: inline-block;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 1px 6px;
    font-size: 10px;
    color: var(--text-muted);
    font-family: 'IBM Plex Mono', monospace;
  }

  /* === FOOTER === */
  footer {
    border-top: 1px solid var(--border);
    padding: 8px 24px;
    display: flex;
    gap: 24px;
    font-size: 10px;
    color: var(--text-dim);
    background: var(--surface);
    flex-shrink: 0;
    letter-spacing: 0.5px;
    flex-wrap: wrap;
  }
  footer .hint { display: flex; align-items: center; gap: 6px; }

  .fade-in { animation: fadeIn 0.2s ease; }
  @keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
</style>
</head>
<body>

<header>
  <div class="logo">Square<span>ยท</span>Dance <span>// Caller</span></div>
  <div class="file-controls">
    <button class="btn primary" onclick="document.getElementById('folderInput').click()">๐Ÿ“ Open Folder</button>
    <button class="btn" onclick="document.getElementById('fileInput').click()">๐Ÿ“„ Open File(s)</button>
    <input type="file" id="folderInput" accept=".txt" webkitdirectory multiple>
    <input type="file" id="fileInput" accept=".txt" multiple>
    <button class="btn" onclick="clearAll()">โœ• Clear</button>
  </div>
</header>

<div class="status-bar" id="statusBar">
  <div class="status-item">No file loaded</div>
</div>

<div class="main-layout">
  <div class="sidebar" id="sidebar">
    <div class="sidebar-header">Files</div>
    <div class="file-list" id="fileList"></div>
  </div>

  <div class="seq-sidebar" id="seqSidebar">
    <div class="seq-sidebar-header">Seq</div>
    <div class="seq-list" id="seqList"></div>
  </div>

  <div class="reader-pane">
    <div class="reader-scroll" id="readerScroll">
      <div class="empty-state" id="emptyState">
        <div class="empty-icon">๐ŸŽต</div>
        <div class="empty-title">Square Dance Caller</div>
        <div class="empty-desc">
          Load a <strong>folder</strong> of <code>.txt</code> files or individual files.<br>
          Sequences within each file are separated by <strong>@</strong>.<br><br>
          Use <span class="kbd">โ†‘</span> <span class="kbd">โ†“</span> to move between calls,
          <span class="kbd">โ†</span> <span class="kbd">โ†’</span> to switch sequences,
          and <span class="kbd">Tab</span> to switch files.
        </div>
        <div class="empty-actions">
          <button class="btn primary" onclick="document.getElementById('folderInput').click()">๐Ÿ“ Open Folder</button>
          <button class="btn" onclick="document.getElementById('fileInput').click()">๐Ÿ“„ Open File(s)</button>
        </div>
      </div>
      <div id="linesContainer" style="display:none"></div>
    </div>
  </div>
</div>

<footer>
  <div class="hint"><span class="kbd">โ†‘</span><span class="kbd">โ†“</span> Move between calls</div>
  <div class="hint"><span class="kbd">โ†</span><span class="kbd">โ†’</span> Switch sequences</div>
  <div class="hint"><span class="kbd">Tab</span> Switch files</div>
  <div class="hint"><span class="kbd">Home</span> / <span class="kbd">End</span> First / last call</div>
  <div class="hint"><span class="kbd">Space</span> Next call</div>
</footer>

<script>
// ============================================================
//  STATE
// ============================================================
let files = [];
let currentFile = 0;
let currentSeq = 0;
let currentLine = 0;
let allLines = [];
let seqBoundaries = [];

// ============================================================
//  FILE / FOLDER LOADING
// ============================================================
function handleFileList(fileList) {
  const txtFiles = Array.from(fileList)
    .filter(f => f.name.toLowerCase().endsWith('.txt'))
    .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }));

  if (!txtFiles.length) {
    alert('No .txt files found.');
    return;
  }

  let loaded = 0;
  const results = new Array(txtFiles.length);

  txtFiles.forEach((f, i) => {
    const reader = new FileReader();
    reader.onload = ev => {
      results[i] = { name: f.name, raw: ev.target.result };
      loaded++;
      if (loaded === txtFiles.length) processFiles(results);
    };
    reader.readAsText(f, 'UTF-8');
  });
}

document.getElementById('folderInput').addEventListener('change', e => {
  handleFileList(e.target.files);
  e.target.value = '';
});

document.getElementById('fileInput').addEventListener('change', e => {
  handleFileList(e.target.files);
  e.target.value = '';
});

function processFiles(results) {
  files = results.map(r => ({
    name: r.name,
    sequences: parseSequences(r.raw)
  }));
  currentFile = 0;
  currentSeq = 0;
  currentLine = 0;
  renderFileList();
  renderCurrentFile();
  updateStatus();
}

function parseSequences(raw) {
  return raw.split('@')
    .map(block => block.split('\n')
      .map(l => l.trim())
      .filter(l => l.length > 0))
    .filter(seq => seq.length > 0);
}

// ============================================================
//  RENDERING
// ============================================================
function renderFileList() {
  const container = document.getElementById('fileList');
  container.innerHTML = '';
  files.forEach((f, i) => {
    const div = document.createElement('div');
    div.className = 'file-item' + (i === currentFile ? ' active' : '');
    div.innerHTML = `<span class="fi-icon">โ—ˆ</span>
      <span class="fi-name">${escapeHtml(f.name)}</span>
      <span class="fi-count">${f.sequences.length}s</span>`;
    div.onclick = () => switchFile(i);
    container.appendChild(div);
  });
}

function renderCurrentFile() {
  if (!files.length) {
    document.getElementById('emptyState').style.display = 'flex';
    document.getElementById('linesContainer').style.display = 'none';
    document.getElementById('seqList').innerHTML = '';
    return;
  }
  document.getElementById('emptyState').style.display = 'none';
  const container = document.getElementById('linesContainer');
  container.style.display = 'block';
  container.innerHTML = '';
  container.classList.add('fade-in');
  setTimeout(() => container.classList.remove('fade-in'), 300);

  const file = files[currentFile];
  allLines = [];
  seqBoundaries = [];

  const seqList = document.getElementById('seqList');
  seqList.innerHTML = '';

  file.sequences.forEach((seq, si) => {
    const dividerEl = document.createElement('div');
    dividerEl.className = 'seq-divider';
    dividerEl.textContent = `Sequence ${si + 1}`;
    container.appendChild(dividerEl);

    const startLine = allLines.length;

    seq.forEach((lineText, li) => {
      const row = document.createElement('div');
      row.className = 'call-line';
      row.dataset.globalIdx = allLines.length;
      row.dataset.seqIdx = si;
      row.innerHTML = `<span class="line-num">${li + 1}</span>
        <span class="line-text">${escapeHtml(lineText)}</span>`;
      row.onclick = () => goToLine(parseInt(row.dataset.globalIdx));
      container.appendChild(row);
      allLines.push(row);
    });

    seqBoundaries.push({ seqIdx: si, startLine, endLine: allLines.length - 1 });

    const sItem = document.createElement('div');
    sItem.className = 'seq-item' + (si === currentSeq ? ' active' : '');
    sItem.textContent = si + 1;
    sItem.onclick = () => jumpToSeq(si);
    seqList.appendChild(sItem);
  });

  goToLine(seqBoundaries[currentSeq]?.startLine ?? 0, false);
}

function escapeHtml(text) {
  return text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
}

function goToLine(globalIdx, doScroll = true) {
  currentLine = Math.max(0, Math.min(globalIdx, allLines.length - 1));

  for (const b of seqBoundaries) {
    if (currentLine >= b.startLine && currentLine <= b.endLine) {
      currentSeq = b.seqIdx;
      break;
    }
  }

  allLines.forEach((row, i) => {
    row.classList.remove('active','prev1','prev2','next1','next2');
    const diff = i - currentLine;
    if (diff === 0)       row.classList.add('active');
    else if (diff === -1) row.classList.add('prev1');
    else if (diff === -2) row.classList.add('prev2');
    else if (diff === 1)  row.classList.add('next1');
    else if (diff === 2)  row.classList.add('next2');
  });

  document.querySelectorAll('.seq-item').forEach((el, i) => {
    el.classList.toggle('active', i === currentSeq);
  });

  if (doScroll && allLines[currentLine]) {
    allLines[currentLine].scrollIntoView({ block: 'center', behavior: 'smooth' });
  }

  updateStatus();
}

function jumpToSeq(seqIdx) {
  currentSeq = seqIdx;
  const b = seqBoundaries[seqIdx];
  if (b) goToLine(b.startLine);
}

function switchFile(idx) {
  currentFile = idx;
  currentSeq = 0;
  currentLine = 0;
  renderFileList();
  renderCurrentFile();
  updateStatus();
}

function clearAll() {
  files = [];
  currentFile = 0; currentSeq = 0; currentLine = 0;
  allLines = []; seqBoundaries = [];
  document.getElementById('fileList').innerHTML = '';
  document.getElementById('seqList').innerHTML = '';
  document.getElementById('linesContainer').innerHTML = '';
  document.getElementById('linesContainer').style.display = 'none';
  document.getElementById('emptyState').style.display = 'flex';
  updateStatus();
}

function updateStatus() {
  const bar = document.getElementById('statusBar');
  if (!files.length) {
    bar.innerHTML = '<div class="status-item">No file loaded</div>';
    return;
  }
  const file = files[currentFile];
  const b = seqBoundaries[currentSeq];
  const lineInSeq = b ? currentLine - b.startLine + 1 : 0;
  const linesInSeq = b ? b.endLine - b.startLine + 1 : 0;
  bar.innerHTML = `
    <div class="status-item">๐Ÿ“„ <strong>${escapeHtml(file.name)}</strong></div>
    <div class="status-item">File <strong>${currentFile+1}</strong> / ${files.length}</div>
    <div class="status-item">Sequence <strong>${currentSeq+1}</strong> / ${file.sequences.length}</div>
    <div class="status-item">Call <strong>${lineInSeq}</strong> / ${linesInSeq}</div>
  `;
}

// ============================================================
//  KEYBOARD
// ============================================================
document.addEventListener('keydown', e => {
  if (!files.length) return;
  // Ignore if focus is on a button/input
  if (['INPUT','BUTTON','SELECT','TEXTAREA'].includes(e.target.tagName)) return;

  switch (e.key) {
    case 'ArrowDown':
    case ' ':
      e.preventDefault(); moveDown(); break;
    case 'ArrowUp':
      e.preventDefault(); moveUp(); break;
    case 'ArrowRight':
      e.preventDefault(); nextSeq(); break;
    case 'ArrowLeft':
      e.preventDefault(); prevSeq(); break;
    case 'Tab':
      e.preventDefault();
      if (e.shiftKey) switchFile((currentFile - 1 + files.length) % files.length);
      else            switchFile((currentFile + 1) % files.length);
      break;
    case 'Home':
      e.preventDefault();
      goToLine(seqBoundaries[currentSeq]?.startLine ?? 0); break;
    case 'End':
      e.preventDefault();
      goToLine(seqBoundaries[currentSeq]?.endLine ?? allLines.length - 1); break;
  }
});

function moveDown() {
  const b = seqBoundaries[currentSeq];
  if (!b) return;
  if (currentLine < b.endLine) goToLine(currentLine + 1);
  else nextSeq();
}

function moveUp() {
  const b = seqBoundaries[currentSeq];
  if (!b) return;
  if (currentLine > b.startLine) goToLine(currentLine - 1);
  else prevSeq();
}

function nextSeq() {
  const file = files[currentFile];
  if (currentSeq < file.sequences.length - 1) {
    jumpToSeq(currentSeq + 1);
  } else if (currentFile < files.length - 1) {
    switchFile(currentFile + 1);
  }
}

function prevSeq() {
  if (currentSeq > 0) {
    jumpToSeq(currentSeq - 1);
  } else if (currentFile > 0) {
    const prevIdx = currentFile - 1;
    switchFile(prevIdx);
    jumpToSeq(files[prevIdx].sequences.length - 1);
  }
}
</script>
</body>
</html>