<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TTS Jaringan Komputer</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600;700&family=Inter:wght@400;600;700&display=swap');
    body {
      font-family: 'Inter', sans-serif;
      background-color: #0f172a;
      color: #e2e8f0;
    }
    .grid-cell {
      aspect-ratio: 1;
      font-family: 'Fira Code', monospace;
      text-transform: uppercase;
      font-weight: 700;
      position: relative;
    }
    .cell-num {
      position: absolute;
      top: 2px;
      left: 3px;
      font-size: 0.65rem;
      line-height: 1;
      color: #94a3b8;
      pointer-events: none;
      font-weight: 600;
    }
    .custom-scrollbar::-webkit-scrollbar {
      width: 6px;
    }
    .custom-scrollbar::-webkit-scrollbar-track {
      background: #1e293b;
      border-radius: 4px;
    }
    .custom-scrollbar::-webkit-scrollbar-thumb {
      background: #475569;
      border-radius: 4px;
    }
    .custom-scrollbar::-webkit-scrollbar-thumb:hover {
      background: #64748b;
    }
  </style>
</head>
<body class="min-h-screen flex flex-col justify-between p-4 md:p-6 bg-slate-900">

  <!-- Header -->
  <header class="max-w-6xl w-full mx-auto mb-6 bg-slate-800/80 p-4 rounded-xl border border-slate-700 backdrop-blur-md shadow-lg flex flex-col md:flex-row items-center justify-between gap-4">
    <div class="flex items-center gap-3">
      <div class="p-3 bg-cyan-500/10 text-cyan-400 rounded-lg border border-cyan-500/20">
        <i class="fa-solid fa-network-wired text-2xl"></i>
      </div>
      <div>
        <h1 class="text-xl font-bold bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">TTS JARKOM</h1>
        <p class="text-xs text-slate-400">15 Soal Komponen & Konsep Jaringan Komputer</p>
      </div>
    </div>

    <!-- Status Bar -->
    <div class="flex items-center gap-6 bg-slate-900/60 px-4 py-2 rounded-lg border border-slate-700/50">
      <div class="flex items-center gap-2">
        <i class="fa-regular fa-clock text-amber-400"></i>
        <span id="timer" class="font-mono text-lg font-bold text-slate-200">00:00</span>
      </div>
      <div class="h-4 w-[1px] bg-slate-700"></div>
      <div class="flex items-center gap-2">
        <i class="fa-solid fa-trophy text-emerald-400"></i>
        <span id="score" class="font-mono text-lg font-bold text-slate-200">0 / 15</span>
      </div>
    </div>
  </header>

  <!-- Main Content -->
  <main class="max-w-6xl w-full mx-auto grid grid-cols-1 lg:grid-cols-12 gap-6 flex-grow items-start">
    
    <!-- TTS Grid Panel -->
    <section class="lg:col-span-7 bg-slate-800/50 p-4 md:p-6 rounded-xl border border-slate-700 shadow-xl flex flex-col items-center">
      <div id="grid-container" class="grid gap-1 bg-slate-950 p-2 md:p-3 rounded-lg border border-slate-800 shadow-inner max-w-full overflow-auto">
        <!-- Cells generated via JavaScript -->
      </div>

      <!-- Action Buttons -->
      <div class="flex flex-wrap gap-3 mt-6 w-full justify-center">
        <button onclick="checkAnswers()" class="flex-1 min-w-[130px] max-w-[200px] px-4 py-2.5 bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-400 hover:to-blue-500 text-slate-950 font-bold rounded-lg shadow-md hover:shadow-cyan-500/20 transition duration-200 flex items-center justify-center gap-2">
          <i class="fa-solid fa-circle-check"></i> Cek Jawaban
        </button>
        <button onclick="getHint()" class="flex-1 min-w-[130px] max-w-[200px] px-4 py-2.5 bg-slate-700 hover:bg-slate-600 text-amber-300 font-semibold rounded-lg border border-slate-600 hover:border-amber-400/50 transition duration-200 flex items-center justify-center gap-2">
          <i class="fa-solid fa-lightbulb text-amber-400"></i> Petunjuk
        </button>
        <button onclick="resetBoard()" class="px-4 py-2.5 bg-slate-800 hover:bg-rose-950/40 text-slate-300 hover:text-rose-400 font-semibold rounded-lg border border-slate-700 hover:border-rose-800 transition duration-200 flex items-center justify-center gap-2">
          <i class="fa-solid fa-rotate-right"></i> Reset
        </button>
      </div>
    </section>

    <!-- Clues Panel -->
    <section class="lg:col-span-5 bg-slate-800/50 p-4 md:p-6 rounded-xl border border-slate-700 shadow-xl flex flex-col h-[520px] md:h-[600px]">
      <h2 class="text-lg font-bold text-slate-200 mb-4 pb-2 border-b border-slate-700 flex items-center gap-2">
        <i class="fa-solid fa-list-check text-cyan-400"></i> Pertanyaan
      </h2>

      <div class="flex-1 overflow-y-auto space-y-6 pr-2 custom-scrollbar">
        <!-- Across Clues -->
        <div>
          <h3 class="text-sm font-bold text-cyan-400 uppercase tracking-wider mb-2 flex items-center gap-2">
            <i class="fa-solid fa-arrow-right-long"></i> Mendatar
          </h3>
          <div id="clues-across" class="space-y-2"></div>
        </div>

        <!-- Down Clues -->
        <div>
          <h3 class="text-sm font-bold text-emerald-400 uppercase tracking-wider mb-2 flex items-center gap-2">
            <i class="fa-solid fa-arrow-down-long"></i> Menurun
          </h3>
          <div id="clues-down" class="space-y-2"></div>
        </div>
      </div>
    </section>

  </main>

  <!-- Footer -->
  <footer class="max-w-6xl w-full mx-auto mt-6 text-center text-xs text-slate-500 py-2 border-t border-slate-800">
    Game TTS Komponen Jaringan Komputer &copy; 2026 - HTML/JS Interactive
  </footer>

  <script>
    // Config & Puzzle Data (15 Word Layout 13x13)
    const GRID_SIZE = 13;
    const WORDS = [
      { id: 1, word: "ROUTER", x: 2, y: 0, dir: "across", clue: "Perangkat penghubung antar jaringan yang berbeda subnet" },
      { id: 2, word: "SWITCH", x: 2, y: 0, dir: "down", clue: "Perangkat jaringan layer 2 yang menghubungkan device dalam 1 LAN" },
      { id: 3, word: "MODEM", x: 4, y: 0, dir: "down", clue: "Pengubah sinyal analog ke digital dan sebaliknya" },
      { id: 4, word: "FIREWALL", x: 0, y: 3, dir: "across", clue: "Sistem pengaman jaringan dari akses tidak sah" },
      { id: 5, word: "HUB", x: 8, y: 1, dir: "down", clue: "Perangkat jaringan lama yang memperluas jaringan dengan broadcast data ke semua port" },
      { id: 6, word: "ETHERNET", x: 2, y: 5, dir: "across", clue: "Standar teknologi jaringan kabel lokal (LAN)" },
      { id: 7, word: "SERVER", x: 6, y: 4, dir: "down", clue: "Komputer penyedia layanan atau data dalam jaringan" },
      { id: 8, word: "NIC", x: 9, y: 3, dir: "down", clue: "Kartu antarmuka jaringan pada komputer (Singkatan)" },
      { id: 9, word: "REPEATER", x: 1, y: 8, dir: "across", clue: "Penguat sinyal jaringan kabel yang lemah" },
      { id: 10, word: "IPADDRESS", x: 1, y: 8, dir: "down", clue: "Alamat numerik identitas perangkat dalam jaringan (Tanpa Spasi)" },
      { id: 11, word: "BRIDGE", x: 6, y: 10, dir: "across", clue: "Penghubung dua segmen jaringan lokal yang terpisah" },
      { id: 12, word: "GATEWAY", x: 11, y: 5, dir: "down", clue: "Gerbang utama untuk keluar ke jaringan lain/internet" },
      { id: 13, word: "CLIENT", x: 4, y: 12, dir: "across", clue: "Perangkat yang meminta layanan dari server" },
      { id: 14, word: "FIBER", x: 7, y: 8, dir: "down", clue: "Jenis kabel transmisi data berbasis cahaya (Kata Depan)" },
      { id: 15, word: "PORT", x: 2, y: 2, dir: "across", clue: "Soket colokan kabel jaringan atau nomor jalur protokol" }
    ];

    let gridState = Array(GRID_SIZE).fill(null).map(() => Array(GRID_SIZE).fill(null));
    let cellNumbers = Array(GRID_SIZE).fill(null).map(() => Array(GRID_SIZE).fill(null));
    let activeDirection = 'across';
    let currentFocusCell = null;
    let timerSeconds = 0;
    let timerInterval = null;
    let completedWords = new Set();

    // Map words into grid
    function buildPuzzleData() {
      WORDS.forEach(item => {
        let { word, x, y, dir, id } = item;
        cellNumbers[y][x] = cellNumbers[y][x] ? cellNumbers[y][x] + '/' + id : id;
        for (let i = 0; i < word.length; i++) {
          let cx = dir === 'across' ? x + i : x;
          let cy = dir === 'down' ? y + i : y;
          if (!gridState[cy][cx]) {
            gridState[cy][cx] = { char: word[i], words: [] };
          }
          gridState[cy][cx].words.push({ id, index: i });
        }
      });
    }

    // Initialize UI Grid
    function renderGrid() {
      const container = document.getElementById('grid-container');
      container.style.gridTemplateColumns = `repeat(${GRID_SIZE}, minmax(0, 1fr))`;
      container.innerHTML = '';

      for (let r = 0; r < GRID_SIZE; r++) {
        for (let c = 0; c < GRID_SIZE; c++) {
          const cellData = gridState[r][c];
          const cell = document.createElement('div');

          if (cellData) {
            cell.className = `grid-cell bg-slate-800 text-slate-100 border border-slate-700/80 rounded flex items-center justify-center cursor-pointer transition-colors duration-150 relative text-sm sm:text-base md:text-lg`;
            cell.dataset.row = r;
            cell.dataset.col = c;
            cell.id = `cell-${r}-${c}`;

            // Add number label if start of word
            if (cellNumbers[r][c]) {
              const numSpan = document.createElement('span');
              numSpan.className = 'cell-num';
              numSpan.textContent = cellNumbers[r][c];
              cell.appendChild(numSpan);
            }

            const input = document.createElement('input');
            input.type = 'text';
            input.maxLength = 1;
            input.className = 'w-full h-full text-center uppercase bg-transparent outline-none font-bold text-cyan-300 z-10 cursor-pointer';
            
            // Events
            input.addEventListener('focus', () => handleCellFocus(r, c));
            input.addEventListener('input', (e) => handleInput(e, r, c));
            input.addEventListener('keydown', (e) => handleKeyDown(e, r, c));

            cell.appendChild(input);
          } else {
            cell.className = 'grid-cell bg-slate-950/60 border border-slate-900/40 rounded pointer-events-none';
          }
          container.appendChild(cell);
        }
      }
    }

    // Render Clues List
    function renderClues() {
      const acrossContainer = document.getElementById('clues-across');
      const downContainer = document.getElementById('clues-down');

      acrossContainer.innerHTML = '';
      downContainer.innerHTML = '';

      WORDS.forEach(item => {
        const clueEl = document.createElement('div');
        clueEl.id = `clue-${item.id}`;
        clueEl.className = `p-2.5 rounded-lg border border-slate-700/50 bg-slate-800/30 hover:bg-slate-700/40 cursor-pointer transition text-xs sm:text-sm text-slate-300 flex items-start gap-2.5`;
        clueEl.onclick = () => focusWord(item.id);
        
        clueEl.innerHTML = `
          <span class="font-bold text-xs bg-slate-700 text-slate-200 px-2 py-0.5 rounded border border-slate-600 flex-shrink-0">${item.id}</span>
          <span class="flex-grow">${item.clue} <span class="text-slate-500 font-mono text-[11px]">(${item.word.length})</span></span>
        `;

        if (item.dir === 'across') {
          acrossContainer.appendChild(clueEl);
        } else {
          downContainer.appendChild(clueEl);
        }
      });
    }

    // Focus cell interaction
    function handleCellFocus(r, c) {
      if (currentFocusCell && currentFocusCell.r === r && currentFocusCell.c === c) {
        // Toggle direction if clicking same cell
        activeDirection = activeDirection === 'across' ? 'down' : 'across';
      }
      currentFocusCell = { r, c };
      highlightWord();
    }

    function handleInput(e, r, c) {
      const val = e.target.value.toUpperCase();
      e.target.value = val;

      if (val) {
        moveFocus(r, c, 1);
      }
      checkWordCompletionAuto();
    }

    function handleKeyDown(e, r, c) {
      if (e.key === 'Backspace' && !e.target.value) {
        moveFocus(r, c, -1);
      } else if (e.key === 'ArrowRight') {
        moveDirectional(r, c, 0, 1);
      } else if (e.key === 'ArrowLeft') {
        moveDirectional(r, c, 0, -1);
      } else if (e.key === 'ArrowDown') {
        moveDirectional(r, c, 1, 0);
      } else if (e.key === 'ArrowUp') {
        moveDirectional(r, c, -1, 0);
      }
    }

    function moveFocus(r, c, step) {
      const currentWordObj = getWordAt(r, c, activeDirection);
      if (!currentWordObj) return;

      const { wordData } = currentWordObj;
      const indexInWord = activeDirection === 'across' ? c - wordData.x : r - wordData.y;
      const nextIndex = indexInWord + step;

      if (nextIndex >= 0 && nextIndex < wordData.word.length) {
        const nextR = activeDirection === 'across' ? r : wordData.y + nextIndex;
        const nextC = activeDirection === 'across' ? wordData.x + nextIndex : c;
        const nextInput = document.querySelector(`#cell-${nextR}-${nextC} input`);
        if (nextInput) nextInput.focus();
      }
    }

    function moveDirectional(r, c, dr, dc) {
      let nr = r + dr;
      let nc = c + dc;
      while (nr >= 0 && nr < GRID_SIZE && nc >= 0 && nc < GRID_SIZE) {
        if (gridState[nr][nc]) {
          const nextInput = document.querySelector(`#cell-${nr}-${nc} input`);
          if (nextInput) {
            nextInput.focus();
            break;
          }
        }
        nr += dr;
        nc += dc;
      }
    }

    function getWordAt(r, c, dir) {
      if (!gridState[r][c]) return null;
      const match = gridState[r][c].words.find(w => {
        const wordData = WORDS.find(item => item.id === w.id);
        return wordData.dir === dir;
      });
      if (match) {
        return { wordData: WORDS.find(item => item.id === match.id) };
      }
      // Fallback if cell belongs to only 1 direction
      const fallbackId = gridState[r][c].words[0].id;
      return { wordData: WORDS.find(item => item.id === fallbackId) };
    }

    // Highlighting Active Word & Clues
    function highlightWord() {
      // Clear previous highlights
      document.querySelectorAll('.grid-cell').forEach(el => {
        el.classList.remove('bg-cyan-950/80', 'bg-cyan-600/30', 'border-cyan-400');
      });
      document.querySelectorAll('[id^="clue-"]').forEach(el => {
        el.classList.remove('bg-cyan-900/40', 'border-cyan-500', 'text-cyan-200');
      });

      if (!currentFocusCell) return;
      const { r, c } = currentFocusCell;
      const wordObj = getWordAt(r, c, activeDirection);

      if (wordObj) {
        const { wordData } = wordObj;
        activeDirection = wordData.dir; // Align direction

        // Highlight cells
        for (let i = 0; i < wordData.word.length; i++) {
          let cx = wordData.dir === 'across' ? wordData.x + i : wordData.x;
          let cy = wordData.dir === 'down' ? wordData.y + i : wordData.y;
          const cellEl = document.getElementById(`cell-${cy}-${cx}`);
          if (cellEl) {
            cellEl.classList.add('bg-cyan-900/40');
          }
        }

        // Active specific cell
        const activeCellEl = document.getElementById(`cell-${r}-${c}`);
        if (activeCellEl) {
          activeCellEl.classList.add('border-cyan-400', 'bg-cyan-800/50');
        }

        // Highlight clue
        const clueEl = document.getElementById(`clue-${wordData.id}`);
        if (clueEl) {
          clueEl.classList.add('bg-cyan-900/40', 'border-cyan-500', 'text-cyan-200');
          clueEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
        }
      }
    }

    function focusWord(wordId) {
      const wordData = WORDS.find(w => w.id === wordId);
      if (wordData) {
        activeDirection = wordData.dir;
        currentFocusCell = { r: wordData.y, c: wordData.x };
        const input = document.querySelector(`#cell-${wordData.y}-${wordData.x} input`);
        if (input) input.focus();
        highlightWord();
      }
    }

    // Validation & Hint Systems
    function checkWordCompletionAuto() {
      WORDS.forEach(item => {
        let userWord = "";
        for (let i = 0; i < item.word.length; i++) {
          let cx = item.dir === 'across' ? item.x + i : item.x;
          let cy = item.dir === 'down' ? item.y + i : item.y;
          const input = document.querySelector(`#cell-${cy}-${cx} input`);
          userWord += input ? input.value : "";
        }

        if (userWord === item.word) {
          completedWords.add(item.id);
          markWordCorrect(item);
        }
      });
      updateScore();
    }

    function markWordCorrect(wordData) {
      const clueEl = document.getElementById(`clue-${wordData.id}`);
      if (clueEl && !clueEl.classList.contains('line-through')) {
        clueEl.classList.add('line-through', 'opacity-50');
      }
    }

    function checkAnswers() {
      let correctCount = 0;
      WORDS.forEach(item => {
        let isCorrect = true;
        for (let i = 0; i < item.word.length; i++) {
          let cx = item.dir === 'across' ? item.x + i : item.x;
          let cy = item.dir === 'down' ? item.y + i : item.y;
          const cellEl = document.getElementById(`cell-${cy}-${cx}`);
          const input = document.querySelector(`#cell-${cy}-${cx} input`);
          
          if (!input || input.value.toUpperCase() !== item.word[i]) {
            isCorrect = false;
            if (input && input.value !== "") {
              cellEl.classList.add('bg-rose-950/60', 'border-rose-500');
              setTimeout(() => cellEl.classList.remove('bg-rose-950/60', 'border-rose-500'), 2000);
            }
          }
        }
        if (isCorrect) {
          correctCount++;
          completedWords.add(item.id);
          markWordCorrect(item);
        }
      });

      updateScore();

      if (correctCount === WORDS.length) {
        clearInterval(timerInterval);
        confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } });
        alert(`Selamat! Anda berhasil menyelesaikan semua 15 pertanyaan TTS Jarkom dalam waktu ${document.getElementById('timer').textContent}!`);
      }
    }

    function getHint() {
      if (!currentFocusCell) {
        alert("Pilih salah satu kotak terlebih dahulu!");
        return;
      }
      const { r, c } = currentFocusCell;
      const cellData = gridState[r][c];
      if (cellData) {
        const input = document.querySelector(`#cell-${r}-${c} input`);
        if (input) {
          input.value = cellData.char;
          input.classList.add('text-amber-400');
          checkWordCompletionAuto();
        }
      }
    }

    function updateScore() {
      document.getElementById('score').textContent = `${completedWords.size} / ${WORDS.length}`;
    }

    function startTimer() {
      clearInterval(timerInterval);
      timerSeconds = 0;
      timerInterval = setInterval(() => {
        timerSeconds++;
        const mins = String(Math.floor(timerSeconds / 60)).padStart(2, '0');
        const secs = String(timerSeconds % 60).padStart(2, '0');
        document.getElementById('timer').textContent = `${mins}:${secs}`;
      }, 1000);
    }

    function resetBoard() {
      if (confirm("Apakah Anda yakin ingin mereset papan TTS?")) {
        completedWords.clear();
        document.querySelectorAll('.grid-cell input').forEach(input => {
          input.value = '';
          input.classList.remove('text-amber-400');
        });
        document.querySelectorAll('[id^="clue-"]').forEach(el => {
          el.classList.remove('line-through', 'opacity-50');
        });
        updateScore();
        startTimer();
      }
    }

    // Startup Init
    buildPuzzleData();
    renderGrid();
    renderClues();
    startTimer();
  </script>
</body>
</html>