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>
415 lines
12 KiB
HTML
415 lines
12 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;
|
|
}
|
|
|
|
.search-row {
|
|
position: relative;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.search-row input {
|
|
width: 100%;
|
|
padding: 0.625rem 2.5rem 0.625rem 1rem;
|
|
background: #16213e;
|
|
border: 1px solid #2a2a4a;
|
|
border-radius: 8px;
|
|
color: #e0e0e0;
|
|
font-size: 0.9rem;
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.search-row input:focus {
|
|
border-color: #4DB8A4;
|
|
}
|
|
|
|
.search-row input::placeholder {
|
|
color: #555;
|
|
}
|
|
|
|
.search-clear {
|
|
position: absolute;
|
|
right: 0.75rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
background: none;
|
|
border: none;
|
|
color: #555;
|
|
font-size: 1.1rem;
|
|
cursor: pointer;
|
|
display: none;
|
|
}
|
|
|
|
.search-clear.visible {
|
|
display: block;
|
|
}
|
|
|
|
.search-clear:hover {
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.filters {
|
|
display: flex;
|
|
gap: 0.375rem;
|
|
margin-bottom: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.filter-btn {
|
|
padding: 0.375rem 0.75rem;
|
|
background: #16213e;
|
|
border: 1px solid #2a2a4a;
|
|
border-radius: 6px;
|
|
color: #888;
|
|
font-size: 0.8rem;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.filter-btn:hover {
|
|
border-color: #4DB8A4;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.filter-btn.active {
|
|
background: #4DB8A4;
|
|
color: #1a1a2e;
|
|
border-color: #4DB8A4;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.input-row {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.input-row input[type="text"] {
|
|
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[type="text"]:focus {
|
|
border-color: #4DB8A4;
|
|
}
|
|
|
|
.input-row input[type="text"]::placeholder {
|
|
color: #666;
|
|
}
|
|
|
|
.input-row select {
|
|
padding: 0.75rem 0.5rem;
|
|
background: #16213e;
|
|
border: 1px solid #2a2a4a;
|
|
border-radius: 8px;
|
|
color: #e0e0e0;
|
|
font-size: 0.85rem;
|
|
outline: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.category-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.category-dot.work { background: #5B9FD6; }
|
|
.category-dot.personal { background: #4DB8A4; }
|
|
.category-dot.health { background: #E87020; }
|
|
.category-dot.learning { background: #A87BDB; }
|
|
|
|
.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="search-row">
|
|
<input type="text" id="searchInput" placeholder="Search tasks...">
|
|
<button class="search-clear" id="searchClear" onclick="clearSearch()">×</button>
|
|
</div>
|
|
|
|
<div class="filters" id="filters">
|
|
<button class="filter-btn active" onclick="setFilter('all')">All</button>
|
|
<button class="filter-btn" onclick="setFilter('work')">Work</button>
|
|
<button class="filter-btn" onclick="setFilter('personal')">Personal</button>
|
|
<button class="filter-btn" onclick="setFilter('health')">Health</button>
|
|
<button class="filter-btn" onclick="setFilter('learning')">Learning</button>
|
|
</div>
|
|
|
|
<div class="input-row">
|
|
<input type="text" id="taskInput" placeholder="What needs to be done?" autofocus>
|
|
<select id="categorySelect">
|
|
<option value="work">Work</option>
|
|
<option value="personal">Personal</option>
|
|
<option value="health">Health</option>
|
|
<option value="learning">Learning</option>
|
|
</select>
|
|
<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-v2') || '[]');
|
|
let activeFilter = 'all';
|
|
let searchQuery = '';
|
|
|
|
const categoryLabels = {
|
|
work: 'Work',
|
|
personal: 'Personal',
|
|
health: 'Health',
|
|
learning: 'Learning'
|
|
};
|
|
|
|
function saveTasks() {
|
|
localStorage.setItem('todos-v2', JSON.stringify(tasks));
|
|
}
|
|
|
|
function getFilteredTasks() {
|
|
return tasks.filter(task => {
|
|
const matchesFilter = activeFilter === 'all' || task.category === activeFilter;
|
|
const matchesSearch = !searchQuery ||
|
|
task.text.toLowerCase().includes(searchQuery) ||
|
|
(categoryLabels[task.category] || '').toLowerCase().includes(searchQuery);
|
|
return matchesFilter && matchesSearch;
|
|
});
|
|
}
|
|
|
|
function renderTasks() {
|
|
const list = document.getElementById('taskList');
|
|
const countEl = document.getElementById('taskCount');
|
|
const filtered = getFilteredTasks();
|
|
|
|
if (filtered.length === 0) {
|
|
const msg = tasks.length === 0
|
|
? 'No tasks yet. Add one above.'
|
|
: 'No tasks found.';
|
|
list.innerHTML = `<li class="empty-state">${msg}</li>`;
|
|
countEl.textContent = '';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = filtered.map((task) => {
|
|
const idx = tasks.indexOf(task);
|
|
return `
|
|
<li class="task-item ${task.completed ? 'completed' : ''}">
|
|
<div class="task-checkbox" onclick="toggleTask(${idx})"></div>
|
|
<div class="category-dot ${task.category || 'work'}"></div>
|
|
<span class="task-text">${escapeHtml(task.text)}</span>
|
|
<button class="delete-btn" onclick="deleteTask(${idx})" 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 select = document.getElementById('categorySelect');
|
|
const text = input.value.trim();
|
|
if (!text) return;
|
|
|
|
tasks.unshift({
|
|
text,
|
|
completed: false,
|
|
category: select.value,
|
|
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 setFilter(filter) {
|
|
activeFilter = filter;
|
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
btn.classList.toggle('active', btn.textContent.toLowerCase() === filter);
|
|
});
|
|
renderTasks();
|
|
}
|
|
|
|
function clearSearch() {
|
|
document.getElementById('searchInput').value = '';
|
|
searchQuery = '';
|
|
document.getElementById('searchClear').classList.remove('visible');
|
|
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();
|
|
});
|
|
|
|
document.getElementById('searchInput').addEventListener('input', (e) => {
|
|
searchQuery = e.target.value.toLowerCase();
|
|
document.getElementById('searchClear').classList.toggle('visible', searchQuery.length > 0);
|
|
renderTasks();
|
|
});
|
|
|
|
renderTasks();
|
|
</script>
|
|
</body>
|
|
</html>
|