Step 10 of 10

Your Project

📖 Lesson

Combining everything!

In the last 9 steps you learned:

  • HTML - page structure
  • CSS - appearance and animations
  • JavaScript - interactivity and logic
  • PHP - server processing and data storage

Now we'll combine it all into a complete web application!

Prompt engineering for AI

How to communicate effectively with AI:

  • Be specific - "Red button with 8px border radius" > "nice button"
  • Describe steps - "When I click the button, a form appears"
  • Give context - "I have a PHP page with a form, add validation"
  • Iterate - "Change color to blue" > "Add shadow" > "Slow down animation"
Best prompt: describe WHAT you want to see, not HOW to program it. AI takes care of the code.

Your next step

Now you have all the basics! Here are ideas for what you can create:

  • Personal portfolio / business card
  • Blog with comments
  • E-shop with products
  • Online game
  • Calculator or tool
  • Landing page for your project
Tell AI: "I want to create [your idea]. Use HTML, CSS, JS and PHP. I want it in a single file." And you're done!
💻 Example to try
index.php
🚀 Try on Vibmy
<?php
/**
 * Mini Todo App
 * Combining HTML, CSS, JavaScript and PHP in one file!
 */

$dataFile = __DIR__ . "/todos_data.json";

// Load tasks
function loadTodos() {
    global $dataFile;
    if (!file_exists($dataFile)) return [];
    $content = file_get_contents($dataFile);
    return json_decode($content, true) ?: [];
}

function saveTodos($todos) {
    global $dataFile;
    file_put_contents($dataFile, json_encode($todos, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}

// Process API requests
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_SERVER["CONTENT_TYPE"]) && strpos($_SERVER["CONTENT_TYPE"], "application/json") !== false) {
    header("Content-Type: application/json");
    $input = json_decode(file_get_contents("php://input"), true);
    $action = $input["action"] ?? "";
    $todos = loadTodos();

    switch ($action) {
        case "add":
            $text = trim($input["text"] ?? "");
            $category = $input["category"] ?? "general";
            if (strlen($text) >= 1) {
                $todos[] = [
                    "id" => uniqid(),
                    "text" => htmlspecialchars($text),
                    "done" => false,
                    "category" => $category,
                    "created" => date("M j, Y g:i A")
                ];
                saveTodos($todos);
                echo json_encode(["ok" => true]);
            } else {
                echo json_encode(["ok" => false, "error" => "Empty task"]);
            }
            break;

        case "toggle":
            $id = $input["id"] ?? "";
            foreach ($todos as &$todo) {
                if ($todo["id"] === $id) {
                    $todo["done"] = !$todo["done"];
                    break;
                }
            }
            saveTodos($todos);
            echo json_encode(["ok" => true]);
            break;

        case "delete":
            $id = $input["id"] ?? "";
            $todos = array_values(array_filter($todos, fn($t) => $t["id"] !== $id));
            saveTodos($todos);
            echo json_encode(["ok" => true]);
            break;

        case "list":
            echo json_encode(["ok" => true, "todos" => $todos]);
            break;
    }
    exit;
}

$todos = loadTodos();
$total = count($todos);
$done = count(array_filter($todos, fn($t) => $t["done"]));
?>
<!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 {
            min-height: 100vh;
            font-family: "Segoe UI", system-ui, sans-serif;
            background: linear-gradient(135deg, #0f0c29, #1a1a3e);
            color: #e0e0e0;
            display: flex;
            align-items: flex-start;
            justify-content: center;
            padding: 2rem;
        }
        .app {
            width: 100%;
            max-width: 600px;
            margin-top: 1rem;
        }
        h1 {
            text-align: center;
            font-size: 2.2rem;
            background: linear-gradient(to right, #00d4aa, #7b68ee);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            margin-bottom: 0.3rem;
        }
        .subtitle { text-align: center; color: #6b7280; margin-bottom: 1.5rem; }
        /* Stats */
        .stats {
            display: flex;
            gap: 1rem;
            margin-bottom: 1.5rem;
        }
        .stat {
            flex: 1;
            text-align: center;
            padding: 0.8rem;
            background: rgba(255,255,255,0.04);
            border-radius: 12px;
            border: 1px solid rgba(255,255,255,0.06);
        }
        .stat-num { font-size: 1.8rem; font-weight: 800; }
        .stat-label { font-size: 0.75rem; color: #6b7280; text-transform: uppercase; letter-spacing: 1px; }
        .stat-total .stat-num { color: #7b68ee; }
        .stat-done .stat-num { color: #00d4aa; }
        .stat-left .stat-num { color: #f59e0b; }
        /* Add form */
        .add-form {
            display: flex;
            gap: 0.5rem;
            margin-bottom: 1rem;
        }
        .add-form input {
            flex: 1;
            padding: 0.8rem 1rem;
            background: rgba(255,255,255,0.06);
            border: 2px solid rgba(255,255,255,0.1);
            border-radius: 12px;
            color: #e0e0e0;
            font-size: 1rem;
            outline: none;
            transition: border-color 0.3s;
        }
        .add-form input:focus { border-color: #7b68ee; }
        .add-form select {
            padding: 0.8rem;
            background: rgba(255,255,255,0.06);
            border: 2px solid rgba(255,255,255,0.1);
            border-radius: 12px;
            color: #e0e0e0;
            font-size: 0.9rem;
            outline: none;
        }
        .add-form select option { background: #1a1a3e; }
        .add-btn {
            padding: 0.8rem 1.5rem;
            background: linear-gradient(135deg, #00d4aa, #7b68ee);
            color: white;
            border: none;
            border-radius: 12px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            white-space: nowrap;
        }
        .add-btn:hover { transform: scale(1.05); }
        /* Filters */
        .filters {
            display: flex;
            gap: 0.5rem;
            margin-bottom: 1rem;
            flex-wrap: wrap;
        }
        .filter-btn {
            padding: 0.4rem 1rem;
            border: 1px solid rgba(255,255,255,0.15);
            border-radius: 20px;
            background: none;
            color: #8892a4;
            cursor: pointer;
            font-size: 0.85rem;
            transition: all 0.3s;
        }
        .filter-btn.active { border-color: #00d4aa; color: #00d4aa; background: rgba(0,212,170,0.1); }
        .filter-btn:hover { border-color: #00d4aa; }
        /* Todo list */
        .todo-list { list-style: none; }
        .todo-item {
            display: flex;
            align-items: center;
            gap: 0.8rem;
            padding: 1rem;
            background: rgba(255,255,255,0.03);
            border: 1px solid rgba(255,255,255,0.06);
            border-radius: 12px;
            margin-bottom: 0.5rem;
            transition: all 0.3s;
            animation: slideIn 0.3s ease;
        }
        .todo-item:hover { border-color: rgba(0,212,170,0.2); }
        .todo-item.done { opacity: 0.5; }
        .todo-item.done .todo-text { text-decoration: line-through; }
        .todo-check {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            border: 2px solid rgba(255,255,255,0.2);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.3s;
            flex-shrink: 0;
        }
        .todo-check:hover { border-color: #00d4aa; }
        .todo-check.checked { background: #00d4aa; border-color: #00d4aa; }
        .todo-text { flex: 1; font-size: 0.95rem; }
        .todo-cat {
            font-size: 0.7rem;
            padding: 0.2rem 0.6rem;
            border-radius: 10px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        .cat-work { background: rgba(123,104,238,0.15); color: #a78bfa; }
        .cat-personal { background: rgba(0,212,170,0.15); color: #00d4aa; }
        .cat-shopping { background: rgba(245,158,11,0.15); color: #f59e0b; }
        .cat-general { background: rgba(255,255,255,0.1); color: #8892a4; }
        .todo-delete {
            width: 28px;
            height: 28px;
            border: none;
            background: rgba(255,107,157,0.1);
            color: #ff6b9d;
            border-radius: 8px;
            cursor: pointer;
            font-size: 0.9rem;
            transition: all 0.3s;
            flex-shrink: 0;
        }
        .todo-delete:hover { background: rgba(255,107,157,0.3); transform: scale(1.1); }
        .todo-date { font-size: 0.75rem; color: #555; }
        .empty-state { text-align: center; padding: 3rem; color: #555; font-size: 1.1rem; }
        .progress-bar {
            width: 100%;
            height: 6px;
            background: rgba(255,255,255,0.06);
            border-radius: 3px;
            margin-bottom: 1.5rem;
            overflow: hidden;
        }
        .progress-fill {
            height: 100%;
            background: linear-gradient(to right, #00d4aa, #7b68ee);
            border-radius: 3px;
            transition: width 0.5s ease;
        }
        @keyframes slideIn { from { opacity:0; transform:translateX(-20px); } to { opacity:1; transform:translateX(0); } }
        @media (max-width: 600px) {
            body { padding: 1rem; }
            .add-form { flex-wrap: wrap; }
            .add-form input, .add-form select { width: 100%; }
            .stats { gap: 0.5rem; }
        }
    </style>
</head>
<body>
    <div class="app">
        <h1>&#x2705; My Tasks</h1>
        <p class="subtitle">Organize your day</p>

        <div class="stats">
            <div class="stat stat-total">
                <div class="stat-num" id="statTotal"><?php echo $total; ?></div>
                <div class="stat-label">Total</div>
            </div>
            <div class="stat stat-done">
                <div class="stat-num" id="statDone"><?php echo $done; ?></div>
                <div class="stat-label">Done</div>
            </div>
            <div class="stat stat-left">
                <div class="stat-num" id="statLeft"><?php echo $total - $done; ?></div>
                <div class="stat-label">Left</div>
            </div>
        </div>

        <div class="progress-bar">
            <div class="progress-fill" id="progressFill" style="width:<?php echo $total > 0 ? round($done/$total*100) : 0; ?>%"></div>
        </div>

        <div class="add-form">
            <input type="text" id="todoInput" placeholder="New task..." onkeydown="if(event.key==='Enter')addTodo()">
            <select id="todoCategory">
                <option value="general">&#x1F4CB; General</option>
                <option value="work">&#x1F4BC; Work</option>
                <option value="personal">&#x1F464; Personal</option>
                <option value="shopping">&#x1F6D2; Shopping</option>
            </select>
            <button class="add-btn" onclick="addTodo()">+</button>
        </div>

        <div class="filters">
            <button class="filter-btn active" onclick="setFilter('all', this)">All</button>
            <button class="filter-btn" onclick="setFilter('active', this)">Active</button>
            <button class="filter-btn" onclick="setFilter('done', this)">Done</button>
        </div>

        <ul class="todo-list" id="todoList"></ul>
    </div>

    <script>
        let todos = <?php echo json_encode(loadTodos()); ?>;
        let currentFilter = "all";

        function renderTodos() {
            const list = document.getElementById("todoList");
            const filtered = todos.filter(t => {
                if (currentFilter === "active") return !t.done;
                if (currentFilter === "done") return t.done;
                return true;
            });

            if (filtered.length === 0) {
                list.innerHTML = '<div class="empty-state">&#x1F4AD; No tasks. Add your first one!</div>';
            } else {
                list.innerHTML = filtered.map(t => '<li class="todo-item ' + (t.done ? "done" : "") + '">' +
                    '<div class="todo-check ' + (t.done ? "checked" : "") + '" onclick="toggleTodo(\x27' + t.id + '\x27)">' + (t.done ? "&#x2714;" : "") + '</div>' +
                    '<div style="flex:1"><div class="todo-text">' + t.text + '</div><div class="todo-date">' + t.created + '</div></div>' +
                    '<span class="todo-cat cat-' + t.category + '">' + t.category + '</span>' +
                    '<button class="todo-delete" onclick="deleteTodo(\x27' + t.id + '\x27)">&#x2716;</button>' +
                    '</li>').join("");
            }

            const total = todos.length;
            const done = todos.filter(t => t.done).length;
            document.getElementById("statTotal").textContent = total;
            document.getElementById("statDone").textContent = done;
            document.getElementById("statLeft").textContent = total - done;
            document.getElementById("progressFill").style.width = (total > 0 ? Math.round(done / total * 100) : 0) + "%";
        }

        async function apiCall(data) {
            const resp = await fetch(location.href, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(data)
            });
            return await resp.json();
        }

        async function addTodo() {
            const input = document.getElementById("todoInput");
            const cat = document.getElementById("todoCategory").value;
            const text = input.value.trim();
            if (!text) return;

            await apiCall({ action: "add", text: text, category: cat });
            const result = await apiCall({ action: "list" });
            todos = result.todos;
            input.value = "";
            renderTodos();
        }

        async function toggleTodo(id) {
            await apiCall({ action: "toggle", id: id });
            const result = await apiCall({ action: "list" });
            todos = result.todos;
            renderTodos();
        }

        async function deleteTodo(id) {
            await apiCall({ action: "delete", id: id });
            const result = await apiCall({ action: "list" });
            todos = result.todos;
            renderTodos();
        }

        function setFilter(filter, btn) {
            currentFilter = filter;
            document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
            btn.classList.add("active");
            renderTodos();
        }

        renderTodos();
    </script>
</body>
</html>
← Back 10 / 10 🏆 Finish course