Models 44

Gemini 3.8 Flash

by GoogleReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
49.4 s
Cost
$0.029
Output tokens
7,736
Spent thinking
0 tokens
Speed
169 tok/s
Console errors
0
First token after
3.6 s
First visible text after
3.6 s
Prompt tokens
507
Code size
22.3 KB
Finish reason
stop
Served by
Google
Model id
google/gemini-3.8-flash
Reasoning sent
{"effort":"low"}
Max output
65,536 tokens
Generated
Nov 12, 2026

What it was asked

The prompt
Build an interactive Kanban board in vanilla HTML, CSS and JavaScript (no framework, no library). Requirements: - Three columns: "To do", "In progress", "Done", each showing its card count. - Pre-populate the board with 6 realistic cards spread across the columns so the first screenshot looks alive. - Each card has a title and a priority (low / medium / high) shown as a colored badge. - Add a card through a small modal form (title required), and delete a card with a button on it. - Drag and drop cards between columns with the mouse, with a clear visual drop indicator. - A search field that filters cards live by title. - A light / dark theme toggle. - State lives in memory only (no storage APIs).
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 837 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Interactive Kanban Board</title>
  <style>
    :root {
      --bg: #f4f5f7;
      --surface: #ffffff;
      --surface-subtle: #ebecf0;
      --text: #172b4d;
      --text-muted: #5e6c84;
      --border: #dfe1e6;
      --primary: #0052cc;
      --primary-hover: #0747a6;
      --danger: #de350b;
      --danger-hover: #bf2600;
      --shadow: 0 1px 3px rgba(9, 30, 66, 0.15), 0 0 1px rgba(9, 30, 66, 0.2);
      --shadow-drag: 0 12px 24px -6px rgba(9, 30, 66, 0.25);
      
      --p-high-bg: #ffebe6;
      --p-high-text: #bf2600;
      --p-med-bg: #fff0b3;
      --p-med-text: #172b4d;
      --p-low-bg: #e3fcef;
      --p-low-text: #006644;
      
      --drop-indicator: #0052cc;
    }

    [data-theme="dark"] {
      --bg: #0f1217;
      --surface: #1c2128;
      --surface-subtle: #252c35;
      --text: #e6edf3;
      --text-muted: #8b949e;
      --border: #30363d;
      --primary: #2f81f7;
      --primary-hover: #388bfd;
      --danger: #f85149;
      --danger-hover: #da3633;
      --shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
      --shadow-drag: 0 14px 28px rgba(0, 0, 0, 0.6);

      --p-high-bg: rgba(248, 81, 73, 0.18);
      --p-high-text: #ff7b72;
      --p-med-bg: rgba(210, 153, 34, 0.2);
      --p-med-text: #f2cc60;
      --p-low-bg: rgba(63, 185, 80, 0.18);
      --p-low-text: #7ee787;

      --drop-indicator: #58a6ff;
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    }

    body {
      background-color: var(--bg);
      color: var(--text);
      display: flex;
      flex-direction: column;
      height: 100vh;
      overflow: hidden;
      transition: background-color 0.2s, color 0.2s;
    }

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

    .brand {
      display: flex;
      align-items: center;
      gap: 10px;
      font-weight: 700;
      font-size: 1.15rem;
      letter-spacing: -0.02em;
    }

    .brand-icon {
      width: 28px;
      height: 28px;
      background: var(--primary);
      border-radius: 6px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
    }

    .controls {
      display: flex;
      align-items: center;
      gap: 12px;
    }

    .search-wrapper {
      position: relative;
      display: flex;
      align-items: center;
    }

    .search-icon {
      position: absolute;
      left: 10px;
      width: 14px;
      height: 14px;
      fill: var(--text-muted);
      pointer-events: none;
    }

    .search-input {
      background: var(--surface-subtle);
      border: 1px solid var(--border);
      border-radius: 6px;
      padding: 7px 12px 7px 32px;
      font-size: 0.85rem;
      color: var(--text);
      outline: none;
      width: 200px;
      transition: all 0.2s;
    }

    .search-input:focus {
      border-color: var(--primary);
      width: 240px;
      background: var(--surface);
    }

    .btn {
      background: var(--primary);
      color: #fff;
      border: none;
      border-radius: 6px;
      padding: 7px 14px;
      font-size: 0.85rem;
      font-weight: 500;
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: 6px;
      transition: background 0.15s;
    }

    .btn:hover {
      background: var(--primary-hover);
    }

    .btn-icon {
      background: var(--surface-subtle);
      border: 1px solid var(--border);
      color: var(--text);
      padding: 7px;
      border-radius: 6px;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.15s;
    }

    .btn-icon:hover {
      background: var(--border);
    }

    /* Board Area */
    .board-container {
      flex: 1;
      padding: 20px 24px;
      overflow-x: auto;
      overflow-y: hidden;
      display: flex;
      gap: 20px;
    }

    .column {
      flex: 1;
      min-width: 280px;
      max-width: 400px;
      background: var(--surface-subtle);
      border: 1px solid var(--border);
      border-radius: 8px;
      display: flex;
      flex-direction: column;
      max-height: 100%;
    }

    .column-header {
      padding: 12px 16px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 1px solid transparent;
    }

    .column-title-group {
      display: flex;
      align-items: center;
      gap: 8px;
      font-size: 0.9rem;
      font-weight: 600;
    }

    .badge-count {
      background: var(--border);
      color: var(--text-muted);
      border-radius: 12px;
      padding: 2px 8px;
      font-size: 0.75rem;
      font-weight: 700;
    }

    .cards-list {
      flex: 1;
      overflow-y: auto;
      padding: 8px 12px 12px 12px;
      display: flex;
      flex-direction: column;
      gap: 10px;
      position: relative;
    }

    /* Card Styling */
    .card {
      background: var(--surface);
      border-radius: 6px;
      padding: 12px;
      box-shadow: var(--shadow);
      border: 1px solid var(--border);
      cursor: grab;
      user-select: none;
      display: flex;
      flex-direction: column;
      gap: 8px;
      position: relative;
      transition: transform 0.1s, box-shadow 0.15s, border-color 0.15s;
    }

    .card:hover {
      border-color: #85b8ff;
    }

    .card.dragging {
      opacity: 0.45;
      cursor: grabbing;
    }

    .card-top {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      gap: 8px;
    }

    .card-title {
      font-size: 0.88rem;
      font-weight: 500;
      line-height: 1.35;
      word-break: break-word;
    }

    .btn-delete {
      background: transparent;
      border: none;
      color: var(--text-muted);
      cursor: pointer;
      padding: 2px;
      border-radius: 4px;
      opacity: 0.6;
      transition: all 0.15s;
      flex-shrink: 0;
    }

    .btn-delete:hover {
      opacity: 1;
      color: var(--danger);
      background: rgba(222, 53, 11, 0.1);
    }

    .card-meta {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-top: 2px;
    }

    .priority-badge {
      font-size: 0.7rem;
      font-weight: 600;
      padding: 3px 8px;
      border-radius: 4px;
      text-transform: uppercase;
      letter-spacing: 0.04em;
    }

    .priority-low {
      background: var(--p-low-bg);
      color: var(--p-low-text);
    }

    .priority-medium {
      background: var(--p-med-bg);
      color: var(--p-med-text);
    }

    .priority-high {
      background: var(--p-high-bg);
      color: var(--p-high-text);
    }

    .drop-indicator {
      height: 4px;
      background: var(--drop-indicator);
      border-radius: 2px;
      margin: 2px 0;
      pointer-events: none;
      position: relative;
    }

    .drop-indicator::before,
    .drop-indicator::after {
      content: "";
      position: absolute;
      top: -3px;
      width: 10px;
      height: 10px;
      background: var(--drop-indicator);
      border-radius: 50%;
    }
    .drop-indicator::before { left: -4px; }
    .drop-indicator::after { right: -4px; }

    /* Modal Styling */
    .modal-backdrop {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      background: rgba(0, 0, 0, 0.5);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 100;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s ease;
      backdrop-filter: blur(2px);
    }

    .modal-backdrop.open {
      opacity: 1;
      pointer-events: auto;
    }

    .modal {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: 10px;
      width: 90%;
      max-width: 440px;
      box-shadow: var(--shadow-drag);
      display: flex;
      flex-direction: column;
      overflow: hidden;
      transform: scale(0.95);
      transition: transform 0.2s ease;
    }

    .modal-backdrop.open .modal {
      transform: scale(1);
    }

    .modal-header {
      padding: 16px 20px;
      border-bottom: 1px solid var(--border);
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-size: 1rem;
      font-weight: 600;
    }

    .modal-body {
      padding: 20px;
      display: flex;
      flex-direction: column;
      gap: 16px;
    }

    .form-group {
      display: flex;
      flex-direction: column;
      gap: 6px;
    }

    .form-group label {
      font-size: 0.8rem;
      font-weight: 600;
      color: var(--text-muted);
    }

    .form-input, .form-select {
      background: var(--surface-subtle);
      border: 1px solid var(--border);
      border-radius: 6px;
      padding: 9px 12px;
      font-size: 0.88rem;
      color: var(--text);
      outline: none;
      transition: border-color 0.15s;
    }

    .form-input:focus, .form-select:focus {
      border-color: var(--primary);
      background: var(--surface);
    }

    .modal-footer {
      padding: 14px 20px;
      border-top: 1px solid var(--border);
      display: flex;
      justify-content: flex-end;
      gap: 10px;
      background: var(--surface-subtle);
    }

    .btn-secondary {
      background: transparent;
      color: var(--text);
      border: 1px solid var(--border);
    }

    .btn-secondary:hover {
      background: var(--border);
    }

    @media (max-width: 600px) {
      header {
        padding: 10px 14px;
      }
      .brand span {
        display: none;
      }
      .search-input {
        width: 130px;
      }
      .search-input:focus {
        width: 150px;
      }
      .board-container {
        padding: 12px;
        gap: 12px;
      }
      .column {
        min-width: 240px;
      }
    }
  </style>
</head>
<body>

  <header>
    <div class="brand">
      <div class="brand-icon">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
          <path d="M4 4h4v16H4V4zm6 0h4v10h-4V4zm6 0h4v6h-4V4z"/>
        </svg>
      </div>
      <span>KanbanFlow</span>
    </div>

    <div class="controls">
      <div class="search-wrapper">
        <svg class="search-icon" viewBox="0 0 24 24">
          <path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
        </svg>
        <input type="text" id="searchInput" class="search-input" placeholder="Search cards..." />
      </div>

      <button id="addCardBtn" class="btn">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
        Add Card
      </button>

      <button id="themeToggleBtn" class="btn-icon" aria-label="Toggle theme">
        <svg id="themeIcon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <!-- Moon icon -->
          <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
        </svg>
      </button>
    </div>
  </header>

  <main class="board-container" id="boardContainer">
    <!-- Columns injected by JS -->
  </main>

  <!-- Add Card Modal -->
  <div class="modal-backdrop" id="modalBackdrop">
    <div class="modal">
      <div class="modal-header">
        <span>Create New Task</span>
        <button id="modalCloseBtn" class="btn-delete" style="font-size: 1.2rem; padding: 4px;">&times;</button>
      </div>
      <form id="cardForm">
        <div class="modal-body">
          <div class="form-group">
            <label for="cardTitleInput">Task Title *</label>
            <input type="text" id="cardTitleInput" class="form-input" placeholder="e.g. Design search filtering UI" required />
          </div>
          <div class="form-group">
            <label for="cardColSelect">Column</label>
            <select id="cardColSelect" class="form-select">
              <option value="todo">To do</option>
              <option value="inprogress">In progress</option>
              <option value="done">Done</option>
            </select>
          </div>
          <div class="form-group">
            <label for="cardPrioritySelect">Priority</label>
            <select id="cardPrioritySelect" class="form-select">
              <option value="low">Low</option>
              <option value="medium" selected>Medium</option>
              <option value="high">High</option>
            </select>
          </div>
        </div>
        <div class="modal-footer">
          <button type="button" id="modalCancelBtn" class="btn btn-secondary">Cancel</button>
          <button type="submit" class="btn">Create Card</button>
        </div>
      </form>
    </div>
  </div>

  <script>
    // In-memory state
    const state = {
      columns: [
        { id: 'todo', title: 'To do' },
        { id: 'inprogress', title: 'In progress' },
        { id: 'done', title: 'Done' }
      ],
      cards: [
        { id: 'c1', colId: 'todo', title: 'Refactor auth middleware to JWT', priority: 'high' },
        { id: 'c2', colId: 'todo', title: 'Write unit tests for checkout flow', priority: 'medium' },
        { id: 'c3', colId: 'inprogress', title: 'Implement dark theme support', priority: 'low' },
        { id: 'c4', colId: 'inprogress', title: 'Optimize payload delivery over WebSocket', priority: 'high' },
        { id: 'c5', colId: 'done', title: 'Audit accessibility across main dashboard', priority: 'medium' },
        { id: 'c6', colId: 'done', title: 'Upgrade build toolchain to Vite v5', priority: 'low' }
      ],
      searchQuery: '',
      theme: 'light'
    };

    // DOM References
    const boardContainer = document.getElementById('boardContainer');
    const searchInput = document.getElementById('searchInput');
    const addCardBtn = document.getElementById('addCardBtn');
    const themeToggleBtn = document.getElementById('themeToggleBtn');
    const themeIcon = document.getElementById('themeIcon');
    const modalBackdrop = document.getElementById('modalBackdrop');
    const modalCloseBtn = document.getElementById('modalCloseBtn');
    const modalCancelBtn = document.getElementById('modalCancelBtn');
    const cardForm = document.getElementById('cardForm');
    const cardTitleInput = document.getElementById('cardTitleInput');
    const cardColSelect = document.getElementById('cardColSelect');
    const cardPrioritySelect = document.getElementById('cardPrioritySelect');

    let draggedCardId = null;
    let dropIndicator = document.createElement('div');
    dropIndicator.className = 'drop-indicator';

    // Render Board
    function renderBoard() {
      boardContainer.innerHTML = '';

      state.columns.forEach(col => {
        // Filter cards
        const columnCards = state.cards.filter(c => {
          const matchCol = c.colId === col.id;
          const matchSearch = c.title.toLowerCase().includes(state.searchQuery.toLowerCase());
          return matchCol && matchSearch;
        });

        const totalColCards = state.cards.filter(c => c.colId === col.id).length;

        // Create column element
        const colEl = document.createElement('div');
        colEl.className = 'column';
        colEl.dataset.colId = col.id;

        colEl.innerHTML = `
          <div class="column-header">
            <div class="column-title-group">
              <span>${col.title}</span>
              <span class="badge-count">${totalColCards}</span>
            </div>
          </div>
          <div class="cards-list" data-col-id="${col.id}"></div>
        `;

        const cardsList = colEl.querySelector('.cards-list');

        columnCards.forEach(card => {
          const cardEl = createCardElement(card);
          cardsList.appendChild(cardEl);
        });

        // Set up Drag & Drop events on cards-list
        setupDropZone(cardsList, col.id);

        boardContainer.appendChild(colEl);
      });
    }

    function createCardElement(card) {
      const el = document.createElement('div');
      el.className = 'card';
      el.draggable = true;
      el.dataset.cardId = card.id;

      el.innerHTML = `
        <div class="card-top">
          <span class="card-title">${escapeHTML(card.title)}</span>
          <button class="btn-delete" title="Delete card" aria-label="Delete">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <polyline points="3 6 5 6 21 6"></polyline>
              <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
            </svg>
          </button>
        </div>
        <div class="card-meta">
          <span class="priority-badge priority-${card.priority}">${card.priority}</span>
        </div>
      `;

      // Delete action
      el.querySelector('.btn-delete').addEventListener('click', (e) => {
        e.stopPropagation();
        deleteCard(card.id);
      });

      // Drag actions
      el.addEventListener('dragstart', (e) => {
        draggedCardId = card.id;
        el.classList.add('dragging');
        e.dataTransfer.effectAllowed = 'move';
        e.dataTransfer.setData('text/plain', card.id);
      });

      el.addEventListener('dragend', () => {
        el.classList.remove('dragging');
        draggedCardId = null;
        if (dropIndicator.parentNode) {
          dropIndicator.parentNode.removeChild(dropIndicator);
        }
      });

      return el;
    }

    function setupDropZone(cardsList, colId) {
      cardsList.addEventListener('dragover', (e) => {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';

        const afterElement = getDragAfterElement(cardsList, e.clientY);
        if (afterElement == null) {
          cardsList.appendChild(dropIndicator);
        } else {
          cardsList.insertBefore(dropIndicator, afterElement);
        }
      });

      cardsList.addEventListener('dragleave', (e) => {
        // If leaving the container completely
        if (!cardsList.contains(e.relatedTarget)) {
          if (dropIndicator.parentNode === cardsList) {
            cardsList.removeChild(dropIndicator);
          }
        }
      });

      cardsList.addEventListener('drop', (e) => {
        e.preventDefault();
        if (!draggedCardId) return;

        const targetCard = state.cards.find(c => c.id === draggedCardId);
        if (!targetCard) return;

        // Determine destination index
        const afterElement = getDragAfterElement(cardsList, e.clientY);
        
        // Remove card from original position in state
        const originalIndex = state.cards.findIndex(c => c.id === draggedCardId);
        state.cards.splice(originalIndex, 1);

        targetCard.colId = colId;

        if (afterElement == null) {
          // Push to end of array
          state.cards.push(targetCard);
        } else {
          const afterCardId = afterElement.dataset.cardId;
          const targetIndex = state.cards.findIndex(c => c.id === afterCardId);
          state.cards.splice(targetIndex, 0, targetCard);
        }

        if (dropIndicator.parentNode) {
          dropIndicator.parentNode.removeChild(dropIndicator);
        }

        renderBoard();
      });
    }

    function getDragAfterElement(container, y) {
      const draggableElements = [...container.querySelectorAll('.card:not(.dragging)')];

      return draggableElements.reduce((closest, child) => {
        const box = child.getBoundingClientRect();
        const offset = y - box.top - box.height / 2;
        if (offset < 0 && offset > closest.offset) {
          return { offset: offset, element: child };
        } else {
          return closest;
        }
      }, { offset: Number.NEGATIVE_INFINITY }).element;
    }

    function deleteCard(id) {
      state.cards = state.cards.filter(c => c.id !== id);
      renderBoard();
    }

    function escapeHTML(str) {
      const div = document.createElement('div');
      div.textContent = str;
      return div.innerHTML;
    }

    // Modal controls
    function openModal() {
      modalBackdrop.classList.add('open');
      cardTitleInput.value = '';
      cardTitleInput.focus();
    }

    function closeModal() {
      modalBackdrop.classList.remove('open');
    }

    addCardBtn.addEventListener('click', openModal);
    modalCloseBtn.addEventListener('click', closeModal);
    modalCancelBtn.addEventListener('click', closeModal);

    modalBackdrop.addEventListener('click', (e) => {
      if (e.target === modalBackdrop) closeModal();
    });

    cardForm.addEventListener('submit', (e) => {
      e.preventDefault();
      const title = cardTitleInput.value.trim();
      if (!title) return;

      const newCard = {
        id: 'c_' + Date.now(),
        colId: cardColSelect.value,
        title: title,
        priority: cardPrioritySelect.value
      };

      state.cards.push(newCard);
      closeModal();
      renderBoard();
    });

    // Search filter
    searchInput.addEventListener('input', (e) => {
      state.searchQuery = e.target.value;
      renderBoard();
    });

    // Theme Toggle
    themeToggleBtn.addEventListener('click', () => {
      state.theme = state.theme === 'light' ? 'dark' : 'light';
      document.documentElement.setAttribute('data-theme', state.theme);

      // Swap Icon
      if (state.theme === 'dark') {
        themeIcon.innerHTML = `
          <circle cx="12" cy="12" r="5"></circle>
          <line x1="12" y1="1" x2="12" y2="3"></line>
          <line x1="12" y1="21" x2="12" y2="23"></line>
          <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
          <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
          <line x1="1" y1="12" x2="3" y2="12"></line>
          <line x1="21" y1="12" x2="23" y2="12"></line>
          <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
          <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
        `;
      } else {
        themeIcon.innerHTML = `<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>`;
      }
    });

    // Keyboard shortcuts
    window.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && modalBackdrop.classList.contains('open')) {
        closeModal();
      }
    });

    // Initial render
    renderBoard();
  </script>
</body>
</html>