Krok 10 z 10

Tvůj projekt

📖 Lekce

Kombinujeme vše dohromady!

V posledních 9 krocích jsi se naučil/a:

  • HTML - struktura stranky
  • CSS - vzhled a animace
  • JavaScript - interaktivita a logika
  • PHP - serverové zpracování a ukládání dat

Teď to všechno spojíme do kompletní webové aplikace!

Prompt engineering pro AI

Jak efektivně komunikovat s AI:

  • Buď konkrétní - "Červené tlačítko se zaoblením 8px" > "hezké tlačítko"
  • Popisuj kroky - "Když kliknu na tlačítko, zobrazí se formulář"
  • Dávej kontext - "Mám PHP stránku s formulářem, přidej validaci"
  • Iteruj - "Změň barvu na modrou" > "Přidej stín" > "Zpomali animaci"
Nejlepší prompt: popíšeš CO chceš vidět, ne JAK to naprogramovat. AI se postará o kód.

Tvůj další krok

Teď máš všechny základy! Tady jsou nápady, co můžeš vytvořit:

  • Osobní portfolio / vizitka
  • Blog s komentari
  • E-shop s produkty
  • Online hru
  • Kalkulačku nebo nástroj
  • Landing page pro svuj projekt
Řekni AI: "Chci vytvořit [tvůj nápad]. Použij HTML, CSS, JS a PHP. Chci to v jednom souboru." A máš hotovo!
💻 Příklad k vyzkoušení
index.php
🚀 Vyzkoušej na Vibmy
<?php
/**
 * Mini Todo aplikace
 * Kombinace HTML, CSS, JavaScript a PHP v jednom souboru!
 */

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

// Načtení úkolů
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));
}

// Zpracování API požadavků
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("j.n.Y H:i")
                ];
                saveTodos($todos);
                echo json_encode(["ok" => true]);
            } else {
                echo json_encode(["ok" => false, "error" => "Prázdný úkol"]);
            }
            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="cs">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo aplikace</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; Moje úkoly</h1>
        <p class="subtitle">Organizuj svůj den</p>

        <div class="stats">
            <div class="stat stat-total">
                <div class="stat-num" id="statTotal"><?php echo $total; ?></div>
                <div class="stat-label">Celkem</div>
            </div>
            <div class="stat stat-done">
                <div class="stat-num" id="statDone"><?php echo $done; ?></div>
                <div class="stat-label">Hotovo</div>
            </div>
            <div class="stat stat-left">
                <div class="stat-num" id="statLeft"><?php echo $total - $done; ?></div>
                <div class="stat-label">Zbývá</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="Nový úkol..." onkeydown="if(event.key==='Enter')addTodo()">
            <select id="todoCategory">
                <option value="general">&#x1F4CB; Obecné</option>
                <option value="work">&#x1F4BC; Práce</option>
                <option value="personal">&#x1F464; Osobní</option>
                <option value="shopping">&#x1F6D2; Nákupy</option>
            </select>
            <button class="add-btn" onclick="addTodo()">+</button>
        </div>

        <div class="filters">
            <button class="filter-btn active" onclick="setFilter('all', this)">Vše</button>
            <button class="filter-btn" onclick="setFilter('active', this)">Aktivní</button>
            <button class="filter-btn" onclick="setFilter('done', this)">Hotové</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; Žádné úkoly. Přidej první!</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>
← Zpět 10 / 10 🏆 Dokončit kurz