Gemini 3.8 Flash
by GoogleReasoningClean
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
- 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;">×</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>