Complete companion repo for "Your First Hour with Claude Code" article. Includes step-by-step prompts, example output, troubleshooting, and cost guide. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
248 lines
6.5 KiB
HTML
248 lines
6.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Todo App</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: #1a1a2e;
|
|
color: #e0e0e0;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 2rem 1rem;
|
|
}
|
|
|
|
.container {
|
|
width: 100%;
|
|
max-width: 540px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.75rem;
|
|
font-weight: 600;
|
|
margin-bottom: 1.5rem;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.input-row {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.input-row input {
|
|
flex: 1;
|
|
padding: 0.75rem 1rem;
|
|
background: #16213e;
|
|
border: 1px solid #2a2a4a;
|
|
border-radius: 8px;
|
|
color: #e0e0e0;
|
|
font-size: 1rem;
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.input-row input:focus {
|
|
border-color: #4DB8A4;
|
|
}
|
|
|
|
.input-row input::placeholder {
|
|
color: #666;
|
|
}
|
|
|
|
.input-row button {
|
|
padding: 0.75rem 1.25rem;
|
|
background: #4DB8A4;
|
|
color: #1a1a2e;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.input-row button:hover {
|
|
background: #3da897;
|
|
}
|
|
|
|
.task-list {
|
|
list-style: none;
|
|
}
|
|
|
|
.task-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.875rem 1rem;
|
|
background: #16213e;
|
|
border-radius: 8px;
|
|
margin-bottom: 0.5rem;
|
|
transition: opacity 0.3s, transform 0.3s;
|
|
animation: slideIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes slideIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.task-item.completed .task-text {
|
|
text-decoration: line-through;
|
|
color: #555;
|
|
}
|
|
|
|
.task-checkbox {
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid #4DB8A4;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.task-item.completed .task-checkbox {
|
|
background: #4DB8A4;
|
|
}
|
|
|
|
.task-item.completed .task-checkbox::after {
|
|
content: '\2713';
|
|
color: #1a1a2e;
|
|
font-size: 0.75rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.task-text {
|
|
flex: 1;
|
|
font-size: 0.95rem;
|
|
line-height: 1.4;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.delete-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #555;
|
|
cursor: pointer;
|
|
font-size: 1.25rem;
|
|
padding: 0.25rem;
|
|
line-height: 1;
|
|
transition: color 0.2s;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.delete-btn:hover {
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
color: #555;
|
|
padding: 2rem;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.task-count {
|
|
text-align: center;
|
|
color: #555;
|
|
font-size: 0.8rem;
|
|
margin-top: 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Todo</h1>
|
|
<div class="input-row">
|
|
<input type="text" id="taskInput" placeholder="What needs to be done?" autofocus>
|
|
<button onclick="addTask()">Add</button>
|
|
</div>
|
|
<ul class="task-list" id="taskList"></ul>
|
|
<div class="task-count" id="taskCount"></div>
|
|
</div>
|
|
|
|
<script>
|
|
let tasks = JSON.parse(localStorage.getItem('todos') || '[]');
|
|
|
|
function saveTasks() {
|
|
localStorage.setItem('todos', JSON.stringify(tasks));
|
|
}
|
|
|
|
function renderTasks() {
|
|
const list = document.getElementById('taskList');
|
|
const countEl = document.getElementById('taskCount');
|
|
|
|
if (tasks.length === 0) {
|
|
list.innerHTML = '<li class="empty-state">No tasks yet. Add one above.</li>';
|
|
countEl.textContent = '';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = tasks.map((task, i) => `
|
|
<li class="task-item ${task.completed ? 'completed' : ''}">
|
|
<div class="task-checkbox" onclick="toggleTask(${i})"></div>
|
|
<span class="task-text">${escapeHtml(task.text)}</span>
|
|
<button class="delete-btn" onclick="deleteTask(${i})" title="Delete">×</button>
|
|
</li>
|
|
`).join('');
|
|
|
|
const active = tasks.filter(t => !t.completed).length;
|
|
countEl.textContent = `${active} task${active !== 1 ? 's' : ''} remaining`;
|
|
}
|
|
|
|
function addTask() {
|
|
const input = document.getElementById('taskInput');
|
|
const text = input.value.trim();
|
|
if (!text) return;
|
|
|
|
tasks.unshift({ text, completed: false, id: Date.now() });
|
|
input.value = '';
|
|
saveTasks();
|
|
renderTasks();
|
|
}
|
|
|
|
function toggleTask(index) {
|
|
tasks[index].completed = !tasks[index].completed;
|
|
saveTasks();
|
|
renderTasks();
|
|
}
|
|
|
|
function deleteTask(index) {
|
|
tasks.splice(index, 1);
|
|
saveTasks();
|
|
renderTasks();
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
document.getElementById('taskInput').addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') addTask();
|
|
});
|
|
|
|
renderTasks();
|
|
</script>
|
|
</body>
|
|
</html>
|