Chào mừng các bạn đến với Rcom Dăm Yi blog - Kho tài liệu bổ ích!, Chúng tôi sẽ từng bước hoàn thiện để bạn đọc cảm thấy hài lòng, hữu ích!
Hiển thị các bài đăng có nhãn Khóa học web tĩnh. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn Khóa học web tĩnh. Hiển thị tất cả bài đăng

Thứ Sáu, 23 tháng 1, 2026

📚 Khóa học Web tĩnh (HTML • CSS • JavaScript)

Trang điều hướng trung tâm – tìm bài nhanh, học đúng lộ trình.

0 / 20 bài đã học

Bài 20: Tổng kết khóa học & Định hướng phát triển tiếp theo

🎯 Mục tiêu

  • Hệ thống lại toàn bộ kiến thức đã học
  • Biết mình đang ở đâu trong lộ trình Front-end
  • Biết các hướng phát triển tiếp theo phù hợp
  • Biết cách biến web tĩnh thành giá trị thực tế

📚 Tổng kết khóa học Web tĩnh

Bạn đã hoàn thành 20 bài, bao gồm:

  • ✅ HTML cơ bản → semantic → SEO
  • ✅ CSS cơ bản → Flexbox → Grid → Responsive
  • ✅ JavaScript DOM, Event, Array, Object
  • ✅ Mini Project: To-do, Quiz, Landing Page, Course Hub
  • ✅ Triển khai thực tế trên Blogspot
20
Bài học
6
Mini Project
1
Capstone

Ứng dụng

🧭 Lộ trình học tiếp (Roadmap)

1️⃣ Front-end nâng cao

  • JavaScript ES6+: arrow function, destructuring, module
  • Responsive nâng cao, animation
  • Framework: React / Vue

2️⃣ Web thực chiến

  • Portfolio cá nhân
  • Website giới thiệu doanh nghiệp
  • Landing page bán hàng

3️⃣ Back-end (khi cần)

  • Node.js / PHP
  • API, Database
  • Authentication

💰 Ứng dụng & Kiếm tiền từ Web tĩnh

  • Nhận làm website giới thiệu (3–10 triệu/trang)
  • Làm landing page cho quảng cáo
  • Bán template HTML/CSS
  • Xây blog + Google AdSense
  • Xây khóa học như chính bạn đang làm 😄
👉 Web tĩnh không lỗi thời – nó nhẹ, nhanh, rẻ và rất phù hợp với doanh nghiệp nhỏ.

✍️ Bài tập cuối khóa

  1. Tạo 1 website hoàn chỉnh (5–7 trang hoặc 1 trang lớn).
  2. Áp dụng SEO cơ bản + responsive.
  3. Deploy lên Blogspot hoặc GitHub Pages.
  4. Viết 1 bài giới thiệu sản phẩm/khóa học của bạn.

💻 Code mẫu: Banner hoàn thành khóa học


<div class="finish">
  <h2>🎓 Bạn đã hoàn thành khóa học Web tĩnh!</h2>
  <p>
    HTML • CSS • JavaScript • Blogspot
  </p>
  <p>
    🚀 Hãy tiếp tục xây dựng dự án của riêng bạn.
  </p>
</div>
    

🏁 Lời kết

Nếu bạn đọc được đến đây, nghĩa là bạn đã đi rất xa. Kiến thức chỉ thực sự có giá trị khi bạn dùng nó để tạo ra sản phẩm.

Chúc bạn tiếp tục học tốt, làm được nhiều dự án, và nếu cần – hãy quay lại chỉnh sửa, nâng cấp khóa học này 💪

Bài 19: Capstone Project – Trang “Course Hub / Portfolio” 1 trang (chuẩn Blogspot)

🎯 Mục tiêu

  • Tạo 1 trang tổng hợp để giới thiệu khóa học + điều hướng bài học
  • Ôn lại HTML semantic + CSS Flex/Grid + JS DOM/Event
  • Có tìm kiếm bài học, tab lọc (HTML/CSS/JS/Project)
  • Có Dark Mode
  • Lưu “đã học” + theme vào localStorage
  • Dễ thay link thật để dùng trên Blogspot

📌 Cách dùng trên Blogspot

  1. Tạo 1 Trang mới: “Course Hub”
  2. Dán toàn bộ code bên dưới
  3. Thay các link LINK_BAI_01… bằng URL bài thật
  4. Publish

✅ Trang này có thể thay cho Menu bài 16 (nâng cấp hơn).

💻 Code mẫu (Capstone 1 trang)


<div class="hub" id="hub">

  <header class="top">
    <div class="brand">
      <div class="logo">⚡</div>
      <div>
        <h1>Course Hub – Web tĩnh</h1>
        <p>HTML • CSS • JavaScript • Blogspot Ready</p>
      </div>
    </div>

    <div class="actions">
      <button class="btn ghost" id="btnTheme">🌙 Dark mode</button>
      <a class="btn primary" href="LINK_MENU_KHOA_HOC" target="_blank">Mở Menu (cũ)</a>
    </div>
  </header>

  <section class="panel">
    <div class="stats">
      <div class="stat"><b id="doneCount">0</b><br>Bài đã học</div>
      <div class="stat"><b>19</b><br>Tổng bài</div>
      <div class="stat"><b>5</b><br>Mini Project</div>
      <div class="stat"><b id="now">--:--:--</b><br>Giờ hiện tại</div>
    </div>

    <div class="bar">
      <input id="search" type="text" placeholder="Tìm bài... (vd: grid, fetch, quiz, seo)">
      <button class="btn dark" id="btnReset">Reset</button>
    </div>

    <div class="tabs" id="tabs">
      <button class="tab active" data-tab="all">Tất cả</button>
      <button class="tab" data-tab="html">HTML</button>
      <button class="tab" data-tab="css">CSS</button>
      <button class="tab" data-tab="js">JavaScript</button>
      <button class="tab" data-tab="project">Project</button>
      <button class="tab" data-tab="publish">Xuất bản</button>
    </div>

    <div class="list" id="list"></div>

    <div class="foot">
      <span id="hint">Gợi ý: bấm “Đã học” để lưu tiến độ.</span>
    </div>
  </section>

</div>

<style>
  :root{
    --bg:#f7f8fa;
    --card:#ffffff;
    --text:#111;
    --muted:#555;
    --border:#e5e5e5;
    --soft:#f3f4f6;
    --primary:#2563eb;
    --dark:#111;
  }
  .hub.dark{
    --bg:#0b1220;
    --card:#0f172a;
    --text:#e5e7eb;
    --muted:#cbd5e1;
    --border:#1f2937;
    --soft:#111827;
    --primary:#60a5fa;
    --dark:#e5e7eb;
  }

  .hub{
    background:var(--bg);
    color:var(--text);
    font-family: Arial, Helvetica, sans-serif;
    line-height:1.7;
    max-width: 1060px;
    margin: 16px auto;
    padding: 0;
    border-radius: 18px;
  }

  .top{
    display:flex;
    justify-content: space-between;
    gap: 12px;
    align-items:center;
    padding: 16px;
    background:var(--card);
    border:1px solid var(--border);
    border-radius: 18px;
  }

  .brand{
    display:flex;
    gap: 12px;
    align-items:center;
  }
  .logo{
    width: 44px;
    height: 44px;
    border-radius: 14px;
    display:flex;
    align-items:center;
    justify-content:center;
    background: var(--soft);
    border:1px solid var(--border);
    font-size: 22px;
  }
  .brand h1{ margin:0; font-size: 20px; }
  .brand p{ margin:0; color:var(--muted); font-size: 13px; }

  .actions{
    display:flex;
    gap: 10px;
    flex-wrap:wrap;
    justify-content:flex-end;
  }

  .panel{
    margin-top: 12px;
    padding: 16px;
    background:var(--card);
    border:1px solid var(--border);
    border-radius: 18px;
  }

  .stats{
    display:grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
  }
  .stat{
    background:var(--soft);
    border:1px solid var(--border);
    border-radius: 16px;
    padding: 10px 12px;
    text-align:center;
  }
  .stat b{ font-size: 18px; }

  .bar{
    display:flex;
    gap: 10px;
    flex-wrap:wrap;
    justify-content:center;
    margin: 12px 0 10px;
  }
  input{
    padding:10px 12px;
    border:1px solid var(--border);
    border-radius: 12px;
    min-width: 280px;
    width: min(520px, 100%);
    background:var(--card);
    color:var(--text);
    outline:none;
  }

  .btn{
    padding: 10px 12px;
    border-radius: 12px;
    border:0;
    cursor:pointer;
    font-weight: 800;
    text-decoration:none;
    display:inline-block;
    text-align:center;
  }
  .btn.primary{ background:var(--primary); color:#fff; }
  .btn.dark{ background:var(--dark); color: #fff; }
  .hub.dark .btn.dark{ background: #e5e7eb; color:#0b1220; }
  .btn.ghost{ background:var(--soft); border:1px solid var(--border); color:var(--text); }

  .tabs{
    display:flex;
    gap: 8px;
    flex-wrap:wrap;
    justify-content:center;
    margin-bottom: 12px;
  }
  .tab{
    padding: 8px 10px;
    border-radius: 999px;
    border:1px solid var(--border);
    background:var(--card);
    color:var(--text);
    cursor:pointer;
    font-weight: 800;
    font-size: 13px;
  }
  .tab.active{
    background: var(--primary);
    color:#fff;
    border-color: transparent;
  }

  .list{
    display:flex;
    flex-direction:column;
    gap: 10px;
  }

  .item{
    display:flex;
    justify-content: space-between;
    gap: 12px;
    flex-wrap:wrap;
    align-items:center;
    background:var(--card);
    border:1px solid var(--border);
    border-radius: 16px;
    padding: 12px;
  }

  .left{
    flex:1;
    min-width: 240px;
  }
  .title{
    font-weight: 900;
    color:var(--text);
    text-decoration:none;
  }
  .title:hover{ text-decoration: underline; }
  .meta{
    margin-top: 2px;
    font-size: 13px;
    color:var(--muted);
  }
  .pill{
    display:inline-block;
    margin-right: 6px;
    font-size: 12px;
    padding: 2px 10px;
    border-radius: 999px;
    background: var(--soft);
    border:1px solid var(--border);
    color:var(--text);
  }

  .right{
    display:flex;
    gap: 8px;
    flex-wrap:wrap;
  }

  .done .title{ color: #16a34a; }
  .hub.dark .done .title{ color: #86efac; }

  .foot{
    margin-top: 12px;
    text-align:center;
    color:var(--muted);
  }

  @media (max-width: 900px){
    .top{ flex-direction:column; align-items:flex-start; }
    .actions{ width:100%; justify-content:flex-start; }
    .stats{ grid-template-columns: 1fr 1fr; }
  }
</style>

<script>
  // ===== DATA: danh sách bài (bạn thay link thật vào url) =====
  const LESSONS = [
    { id:"bai-01", no:1,  title:"Bài 1: Trình soạn thảo + cấu trúc HTML cơ bản", tag:"html",    url:"LINK_BAI_01", key:"editor html co ban" },
    { id:"bai-02", no:2,  title:"Bài 2: Các phần tử HTML cơ bản",               tag:"html",    url:"LINK_BAI_02", key:"heading p div span list" },
    { id:"bai-03", no:3,  title:"Bài 3: Bảng trong HTML",                       tag:"html",    url:"LINK_BAI_03", key:"table thead tbody tr td" },
    { id:"bai-04", no:4,  title:"Bài 4: Link • Ảnh • Audio • Video",            tag:"html",    url:"LINK_BAI_04", key:"a img audio video iframe" },

    { id:"bai-05", no:5,  title:"Bài 5: Form cơ bản",                           tag:"html",    url:"LINK_BAI_05", key:"form input textarea select" },
    { id:"bai-06", no:6,  title:"Bài 6: Semantic HTML + bố cục",                 tag:"html",    url:"LINK_BAI_06", key:"semantic header nav main section footer" },

    { id:"bai-07", no:7,  title:"Bài 7: CSS Selector + Box Model",              tag:"css",     url:"LINK_BAI_07", key:"css selector box model margin padding" },
    { id:"bai-08", no:8,  title:"Bài 8: CSS Flexbox",                           tag:"css",     url:"LINK_BAI_08", key:"flex justify align wrap gap" },
    { id:"bai-09", no:9,  title:"Bài 9: CSS Grid + Responsive",                 tag:"css",     url:"LINK_BAI_09", key:"grid repeat minmax responsive" },

    { id:"bai-10", no:10, title:"Bài 10: JavaScript cơ bản (DOM + Event)",      tag:"js",      url:"LINK_BAI_10", key:"js dom event let const" },
    { id:"bai-11", no:11, title:"Bài 11: Mini Project – To-do List",            tag:"project", url:"LINK_BAI_11", key:"todo project dom" },
    { id:"bai-12", no:12, title:"Bài 12: Mini Project – Quiz trắc nghiệm",      tag:"project", url:"LINK_BAI_12", key:"quiz radio cham diem" },
    { id:"bai-13", no:13, title:"Bài 13: Array + Object (CRUD mini)",           tag:"js",      url:"LINK_BAI_13", key:"array object crud filter map splice" },
    { id:"bai-14", no:14, title:"Bài 14: Fetch API",                            tag:"js",      url:"LINK_BAI_14", key:"fetch api json async await" },
    { id:"bai-15", no:15, title:"Bài 15: Mini Project – Landing Page",          tag:"project", url:"LINK_BAI_15", key:"landing one page scroll" },

    { id:"bai-16", no:16, title:"Bài 16: Menu khóa học + Template bài học",     tag:"publish", url:"LINK_BAI_16", key:"menu template blogspot" },
    { id:"bai-17", no:17, title:"Bài 17: SEO + tối ưu tốc độ",                  tag:"publish", url:"LINK_BAI_17", key:"seo performance lazy alt title" },
    { id:"bai-18", no:18, title:"Bài 18: Xuất bản + Analytics + checklist",     tag:"publish", url:"LINK_BAI_18", key:"analytics ga4 publish checklist" },

    { id:"bai-19", no:19, title:"Bài 19: Capstone – Course Hub 1 trang",        tag:"project", url:"#",            key:"capstone hub" }
  ];

  const hub = document.getElementById("hub");
  const listEl = document.getElementById("list");
  const searchEl = document.getElementById("search");
  const btnReset = document.getElementById("btnReset");
  const btnTheme = document.getElementById("btnTheme");
  const doneCountEl = document.getElementById("doneCount");
  const nowEl = document.getElementById("now");
  const hint = document.getElementById("hint");

  const KEY_DONE = "hub_done_v1";
  const KEY_THEME = "hub_theme_v1";

  function getDone(){
    try{ return JSON.parse(localStorage.getItem(KEY_DONE) || "[]"); }
    catch(e){ return []; }
  }
  function setDone(arr){
    localStorage.setItem(KEY_DONE, JSON.stringify(arr));
  }

  function setTheme(isDark){
    hub.classList.toggle("dark", isDark);
    localStorage.setItem(KEY_THEME, isDark ? "dark" : "light");
    btnTheme.textContent = isDark ? "☀️ Light mode" : "🌙 Dark mode";
  }

  function getTheme(){
    return (localStorage.getItem(KEY_THEME) || "light") === "dark";
  }

  function tagLabel(tag){
    if(tag==="html") return "HTML";
    if(tag==="css") return "CSS";
    if(tag==="js") return "JS";
    if(tag==="project") return "PROJECT";
    if(tag==="publish") return "XUẤT BẢN";
    return "ALL";
  }

  function render(items){
    const done = getDone();
    listEl.innerHTML = "";

    if(items.length === 0){
      listEl.innerHTML = "<div class='meta'>Không tìm thấy bài phù hợp.</div>";
      return;
    }

    items.forEach(it => {
      const row = document.createElement("div");
      row.className = "item" + (done.includes(it.id) ? " done" : "");

      row.innerHTML = `
        <div class="left">
          <span class="pill">${tagLabel(it.tag)}</span>
          <a class="title" href="${it.url}" target="_blank">${it.title}</a>
          <div class="meta">Từ khóa: ${it.key}</div>
        </div>
        <div class="right">
          <button class="btn ghost mark" data-id="${it.id}">${done.includes(it.id) ? "Đã học ✓" : "Đã học"}</button>
          <button class="btn primary copy" data-url="${it.url}">Copy link</button>
        </div>
      `;
      listEl.appendChild(row);
    });

    // gắn sự kiện
    listEl.querySelectorAll(".mark").forEach(btn => {
      btn.addEventListener("click", () => {
        const id = btn.dataset.id;
        const done = getDone();
        const idx = done.indexOf(id);
        if(idx === -1) done.push(id);
        else done.splice(idx, 1);
        setDone(done);
        applyFilters(); // render lại theo bộ lọc hiện tại
        updateDoneCount();
      });
    });

    listEl.querySelectorAll(".copy").forEach(btn => {
      btn.addEventListener("click", async () => {
        const url = btn.dataset.url;
        try{
          await navigator.clipboard.writeText(url);
          btn.textContent = "Đã copy ✓";
          setTimeout(() => btn.textContent = "Copy link", 900);
        } catch(e){
          alert("Không copy được. Bạn hãy copy thủ công: " + url);
        }
      });
    });
  }

  function updateDoneCount(){
    doneCountEl.textContent = getDone().length;
  }

  // ===== Filters =====
  let currentTab = "all";

  function applyFilters(){
    const key = searchEl.value.trim().toLowerCase();
    let items = LESSONS.slice();

    if(currentTab !== "all"){
      items = items.filter(x => x.tag === currentTab);
    }

    if(key){
      items = items.filter(x => (x.title + " " + x.key).toLowerCase().includes(key));
    }

    render(items);
  }

  document.querySelectorAll(".tab").forEach(tab => {
    tab.addEventListener("click", () => {
      document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
      tab.classList.add("active");
      currentTab = tab.dataset.tab;
      applyFilters();
      hint.textContent = "Đang lọc: " + tagLabel(currentTab === "all" ? "all" : currentTab);
    });
  });

  searchEl.addEventListener("input", applyFilters);
  btnReset.addEventListener("click", () => {
    searchEl.value = "";
    currentTab = "all";
    document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
    document.querySelector('.tab[data-tab="all"]').classList.add("active");
    hint.textContent = "Gợi ý: bấm “Đã học” để lưu tiến độ.";
    applyFilters();
  });

  // ===== Theme =====
  btnTheme.addEventListener("click", () => {
    setTheme(!hub.classList.contains("dark"));
  });

  // ===== Clock =====
  setInterval(() => {
    nowEl.textContent = new Date().toLocaleTimeString("vi-VN");
  }, 1000);

  // Init
  setTheme(getTheme());
  updateDoneCount();
  applyFilters();
</script>
    

✅ Bạn chỉ cần thay link LINK_BAI_01…LINK_BAI_18LINK_MENU_KHOA_HOC là dùng được ngay.

✍️ Bài tập

  1. Thay link thật cho 5 bài đầu tiên và test “Copy link”.
  2. Đổi số “Tổng bài” theo đúng tổng khóa học của bạn.
  3. Thêm 1 tab mới “Nâng cao” và gắn 2 bài vào đó.
  4. (Nâng cao) Thêm thanh progress (%) dựa trên số bài đã học.

✅ Đáp án gợi ý

Progress % (gợi ý nhanh)


const percent = Math.round(getDone().length / LESSONS.length * 100);
    

Bài 18: Hoàn thiện & xuất bản khóa học Web tĩnh trên Blogspot

🎯 Mục tiêu

  • Hoàn tất cấu trúc khóa học (Menu → Bài học)
  • Áp dụng checklist xuất bản chuẩn Blogspot
  • Thiết lập bảo mật cơ bản cho web tĩnh
  • Gắn Google Analytics để theo dõi học viên
  • Biết cách bảo trì, cập nhật nội dung

✅ Checklist trước khi xuất bản

  1. Mỗi bài = 1 bài viết / 1 trang riêng
  2. Menu khóa học (Bài 16) đã thay đủ link thật
  3. Mỗi bài có đúng cấu trúc: Mục tiêu → Lý thuyết → Ví dụ → Bài tập → Đáp án
  4. Chỉ có 1 thẻ <h1> mỗi bài
  5. Ảnh có alt, có loading="lazy"
  6. Code hiển thị rõ (nền sáng – chữ đen)
  7. Test trên DesktopMobile

🔐 Bảo mật cơ bản cho Web tĩnh

1) Không nhúng mã lạ

  • Chỉ dùng JS tự viết hoặc nguồn tin cậy
  • Tránh copy script không rõ nguồn

2) Tránh XSS (ở mức cơ bản)

Khi hiển thị dữ liệu người dùng nhập (form demo), tránh dùng innerHTML nếu không cần thiết.


// TỐT
out.textContent = userInput;

// HẠN CHẾ (chỉ dùng khi bạn kiểm soát nội dung)
out.innerHTML = "<b>" + safeText + "</b>";
    

3) Không lưu thông tin nhạy cảm

  • Không lưu email, số điện thoại thật trong localStorage
  • Form ở khóa học chỉ nên là demo

📊 Gắn Google Analytics (GA4)

Bước 1: Tạo GA4 → lấy Measurement ID (dạng G-XXXX).

Bước 2: Blogspot → ThemeEdit HTML.

Bước 3: Dán trước thẻ </head>:


<!-- Google Analytics GA4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX');
</script>
    

👉 Sau 24–48h, bạn sẽ thấy lượt xem bài học, thời gian ở lại trang.

🛠️ Quản lý & bảo trì khóa học

  • Đặt tên bài rõ ràng: Bài 08 – CSS Flexbox
  • Cập nhật nội dung nhưng giữ nguyên URL
  • Ghim Menu khóa học ở đầu blog
  • Sao lưu code Menu + Template bài học

Gợi ý nâng cấp tiếp theo

  • Thêm mục “Hỏi & Đáp” (FAQ)
  • Thêm bài “Bài tập tổng hợp”
  • Gắn comment để học viên trao đổi

💻 Code mẫu: Banner hoàn thành khóa học


<div class="finish">
  <h2>🎉 Chúc mừng!</h2>
  <p>
    Bạn đã hoàn thành khóa học Web tĩnh (HTML • CSS • JavaScript).
  </p>
  <p>
    👉 Hãy quay lại Menu khóa học để ôn tập hoặc chia sẻ cho bạn bè.
  </p>
</div>

<style>
.finish{
  margin: 20px 0;
  padding: 16px 18px;
  border-radius: 16px;
  background:#ecfdf5;
  border:1px solid #bbf7d0;
  text-align:center;
}
.finish h2{ margin-top:0; }
</style>
    

✍️ Bài tập

  1. Kiểm tra lại Menu khóa học → sửa link sai (nếu có).
  2. Gắn Google Analytics cho blog.
  3. Thêm banner “Hoàn thành khóa học” vào bài cuối.
  4. Viết 1 bài giới thiệu khóa học (landing post).

🏁 Kết thúc khóa học

Bạn đã hoàn thành 18 bài về thiết kế Web tĩnh bằng HTML, CSS, JavaScript và triển khai thực tế trên Blogspot.

  • ✔ Có hệ thống bài học hoàn chỉnh
  • ✔ Có menu, template, mini project
  • ✔ Có SEO, Analytics, checklist triển khai

🚀 Từ đây, bạn có thể tiếp tục học Front-end nâng cao (Responsive sâu hơn, ES6+, framework…).

Bài 17: Tối ưu Web tĩnh & SEO cơ bản (chuẩn Blogspot)

🎯 Mục tiêu

  • Hiểu SEO là gì và vì sao web tĩnh vẫn cần SEO
  • Biết các thẻ SEO quan trọng trong HTML
  • Biết tối ưu tốc độ tải trang (CSS/JS/ảnh)
  • Áp dụng chuẩn SEO phù hợp với Blogspot
  • Biết kiểm tra nhanh SEO & performance

📘 SEO cơ bản cho Web tĩnh

SEO (Search Engine Optimization) là tối ưu để Google hiểu và xếp hạng trang của bạn tốt hơn.

1) Các thẻ SEO quan trọng

  • <title> – Tiêu đề trang (rất quan trọng)
  • <meta name="description">
  • <h1> – chỉ nên có 1
  • <h2> → <h3> – cấu trúc nội dung
  • alt cho ảnh

<title>Khóa học Web tĩnh HTML CSS JavaScript</title>
<meta name="description" content="Học HTML CSS JavaScript cơ bản, chuẩn Blogspot">
    

2) Cấu trúc heading đúng


<h1>Bài 17: SEO cho Web tĩnh</h1>
<h2>SEO là gì</h2>
<h3>On-page SEO</h3>
    

⚡ Tối ưu tốc độ tải trang

1) CSS & JS

  • Viết CSS gọn gàng, không trùng lặp
  • JS đặt cuối trang (trước </body>)
  • Tránh tạo quá nhiều DOM không cần thiết

2) Ảnh (rất hay bị lỗi)

  • Dùng ảnh đúng kích thước
  • Luôn có alt
  • Không nhúng ảnh quá nặng (>300KB)

<img src="banner.jpg"
     alt="Khóa học Web tĩnh HTML CSS JS"
     width="800"
     height="400"
     loading="lazy">
    

3) Lazy loading

Thuộc tính loading="lazy" giúp ảnh/video chỉ tải khi người dùng cuộn tới.

💻 Code mẫu (Trang bài học chuẩn SEO + tối ưu)


<!doctype html>
<html lang="vi">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Bài 17 – SEO cho Web tĩnh | Khóa học HTML CSS JS</title>
  <meta name="description"
        content="Học cách tối ưu SEO và tốc độ cho web tĩnh HTML CSS JavaScript, chuẩn Blogspot">

  <style>
    body{
      font-family: Arial;
      margin:0;
      background:#f7f8fa;
      line-height:1.7;
      color:#111;
    }
    .wrap{
      max-width:900px;
      margin:20px auto;
      background:#fff;
      border:1px solid #e5e5e5;
      border-radius:16px;
      padding:18px;
    }
    h1,h2,h3{ line-height:1.4; }
    img{
      max-width:100%;
      border-radius:12px;
      border:1px solid #e5e5e5;
    }
    .note{
      background:#f3f4f6;
      border:1px solid #e5e7eb;
      border-radius:12px;
      padding:10px 12px;
      margin:12px 0;
    }
  </style>
</head>

<body>

  <article class="wrap">
    <h1>Bài 17: Tối ưu SEO cho Web tĩnh</h1>

    <p>
      Đây là ví dụ một trang bài học được viết theo chuẩn SEO,
      phù hợp để đăng trên Blogspot.
    </p>

    <h2>1. SEO On-page</h2>
    <p>
      On-page SEO là tối ưu ngay trong nội dung HTML.
    </p>

    <h3>Thẻ tiêu đề và mô tả</h3>
    <p>
      Google đọc thẻ <code>title</code> và <code>description</code> đầu tiên.
    </p>

    <h2>2. Tối ưu hình ảnh</h2>
    <img src="https://via.placeholder.com/800x400"
         alt="Ví dụ tối ưu ảnh SEO"
         loading="lazy">

    <div class="note">
      Ảnh có alt + lazy loading giúp SEO và tốc độ tốt hơn.
    </div>

    <h2>3. Trải nghiệm người dùng</h2>
    <p>
      Trang tải nhanh, chữ dễ đọc, mobile-friendly → Google đánh giá cao.
    </p>
  </article>

  <script>
    // JS tối thiểu – không ảnh hưởng SEO
    console.log("SEO page loaded");
  </script>

</body>
</html>
    

👉 Đây là mẫu trang bài học chuẩn SEO bạn có thể dùng cho các bài sau.

🔍 Công cụ kiểm tra (miễn phí)

  • Google PageSpeed Insights
  • Lighthouse (Chrome DevTools)
  • Search Google: site:tenblog.blogspot.com

✍️ Bài tập

  1. Thêm meta description cho Bài 1 → Bài 5.
  2. Kiểm tra 1 trang bằng PageSpeed Insights.
  3. Đổi lại tiêu đề (<title>) cho hấp dẫn hơn.
  4. (Nâng cao) Tạo sitemap.xml cho Blogspot.

🎉 Hoàn thành

Sau Bài 17, bạn đã có:

  • Kiến thức HTML/CSS/JS cơ bản → nâng cao
  • Nhiều mini project thực tế
  • Website web tĩnh hoàn chỉnh, nhúng Blogspot
  • Nền tảng để tiếp tục học Front-end nâng cao

Bài 16: Chuẩn Blogspot – Tạo menu khóa học + template trang bài học

🎯 Mục tiêu

  • Tạo 1 trang Menu khóa học để học viên bấm vào từng bài
  • Biết cách thay URL thật của mỗi bài trên Blogspot
  • Template bài học dùng lại: Mục tiêu → Lý thuyết → Ví dụ → Bài tập → Đáp án
  • Có JS hỗ trợ: tìm kiếm bài, copy link, đánh dấu “đã học” (localStorage)

📌 Phần A: Menu khóa học (copy dán vào 1 Trang / 1 Bài viết)

👉 Bạn tạo 1 Trang mới trong Blogspot (ví dụ: “Khóa học Web tĩnh”) rồi dán nguyên khối code dưới đây.
Sau đó chỉ cần thay LINK_BAI_01… thành link bài viết thật.


<div class="course">
  <h1 class="course-title">📚 Khóa học Web tĩnh (HTML • CSS • JavaScript)</h1>

  <div class="course-top">
    <input id="q" class="course-search" type="text" placeholder="Tìm bài học... (vd: flex, grid, form)">
    <button id="btnReset" class="btn dark">Hiện tất cả</button>
  </div>

  <div class="course-note">
    ✅ Click vào tiêu đề để mở bài • ⭐ Bấm “Đã học” để đánh dấu • 🔗 Copy link để chia sẻ
  </div>

  <div class="course-list" id="list">

    <!-- MẪU 1 ITEM: sửa href thành link thật của Blogspot -->
    <div class="item" data-key="bai 1 editor html css js co ban">
      <div class="left">
        <a class="title" href="LINK_BAI_01" target="_blank">Bài 1: Trình soạn thảo + cấu trúc HTML cơ bản</a>
        <div class="meta">HTML cơ bản • bố cục • ví dụ đơn giản</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-01">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_01">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 2 phan tu html co ban heading p div span">
      <div class="left">
        <a class="title" href="LINK_BAI_02" target="_blank">Bài 2: Các phần tử HTML cơ bản</a>
        <div class="meta">Heading • Paragraph • List • Div/Span</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-02">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_02">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 3 bang html table thead tbody tr td">
      <div class="left">
        <a class="title" href="LINK_BAI_03" target="_blank">Bài 3: Bảng trong HTML</a>
        <div class="meta">table • thead • tbody • tr/td • border</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-03">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_03">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 4 media link image audio video iframe">
      <div class="left">
        <a class="title" href="LINK_BAI_04" target="_blank">Bài 4: Link • Ảnh • Audio • Video</a>
        <div class="meta">a/img/audio/video/iframe</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-04">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_04">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 5 form input textarea select button">
      <div class="left">
        <a class="title" href="LINK_BAI_05" target="_blank">Bài 5: Form cơ bản</a>
        <div class="meta">input • textarea • select • validation</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-05">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_05">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 6 semantic header nav main section article aside footer">
      <div class="left">
        <a class="title" href="LINK_BAI_06" target="_blank">Bài 6: Semantic HTML + bố cục trang</a>
        <div class="meta">header/nav/main/section/article/aside/footer</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-06">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_06">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 7 css selector box model padding margin border">
      <div class="left">
        <a class="title" href="LINK_BAI_07" target="_blank">Bài 7: CSS Selector + Box Model</a>
        <div class="meta">tag/class/id • padding • border • margin</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-07">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_07">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 8 flexbox justify align gap wrap">
      <div class="left">
        <a class="title" href="LINK_BAI_08" target="_blank">Bài 8: CSS Flexbox</a>
        <div class="meta">display:flex • justify-content • align-items • wrap</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-08">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_08">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 9 grid repeat minmax responsive">
      <div class="left">
        <a class="title" href="LINK_BAI_09" target="_blank">Bài 9: CSS Grid + Responsive</a>
        <div class="meta">grid-template-columns • repeat • minmax</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-09">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_09">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 10 js bien kieu du lieu dom su kien">
      <div class="left">
        <a class="title" href="LINK_BAI_10" target="_blank">Bài 10: JavaScript cơ bản (DOM + Event)</a>
        <div class="meta">let/const • string/number • DOM • click/input</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-10">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_10">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 11 todo list project">
      <div class="left">
        <a class="title" href="LINK_BAI_11" target="_blank">Bài 11: Mini Project – To-do List</a>
        <div class="meta">Thêm/xóa/đánh dấu • DOM • event</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-11">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_11">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 12 quiz trac nghiem cham diem">
      <div class="left">
        <a class="title" href="LINK_BAI_12" target="_blank">Bài 12: Mini Project – Quiz trắc nghiệm</a>
        <div class="meta">radio • chấm điểm • tô đúng/sai</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-12">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_12">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 13 array object crud them xoa tim">
      <div class="left">
        <a class="title" href="LINK_BAI_13" target="_blank">Bài 13: Array + Object (CRUD mini)</a>
        <div class="meta">push/filter/map/splice • render list</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-13">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_13">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 14 fetch api json render">
      <div class="left">
        <a class="title" href="LINK_BAI_14" target="_blank">Bài 14: Fetch API</a>
        <div class="meta">fetch • async/await • loading/error</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-14">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_14">Copy link</button>
      </div>
    </div>

    <div class="item" data-key="bai 15 landing page one page scroll menu">
      <div class="left">
        <a class="title" href="LINK_BAI_15" target="_blank">Bài 15: Mini Project – Landing Page</a>
        <div class="meta">section • smooth scroll • back to top • menu active</div>
      </div>
      <div class="right">
        <button class="btn ghost mark" data-id="bai-15">Đã học</button>
        <button class="btn primary copy" data-url="LINK_BAI_15">Copy link</button>
      </div>
    </div>

  </div>

  <div class="course-footer" id="status">0/15 bài đã học</div>
</div>

<style>
  .course{max-width:980px;margin:16px auto;font-family:Arial;line-height:1.7}
  .course-title{text-align:center;margin:0 0 14px}
  .course-top{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-bottom:10px}
  .course-search{padding:10px 12px;border:1px solid #ddd;border-radius:12px;min-width:280px}
  .course-note{background:#f3f4f6;border:1px solid #e5e7eb;border-radius:14px;padding:10px 12px;color:#111;margin-bottom:12px}
  .course-list{display:flex;flex-direction:column;gap:10px}
  .item{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center;background:#fff;border:1px solid #e5e5e5;border-radius:16px;padding:12px}
  .left{min-width:240px;flex:1}
  .title{font-weight:800;color:#111;text-decoration:none}
  .title:hover{text-decoration:underline}
  .meta{color:#555;font-size:13px;margin-top:3px}
  .right{display:flex;gap:8px;flex-wrap:wrap}
  .btn{padding:9px 12px;border-radius:12px;border:0;cursor:pointer;font-weight:800}
  .btn.primary{background:#2563eb;color:#fff}
  .btn.dark{background:#111;color:#fff}
  .btn.ghost{background:#f3f4f6;border:1px solid #e5e7eb;color:#111}
  .done .title{color:#16a34a}
  .course-footer{margin-top:12px;text-align:center;color:#555}
</style>

<script>
  const q = document.getElementById("q");
  const list = document.getElementById("list");
  const status = document.getElementById("status");
  const btnReset = document.getElementById("btnReset");

  const KEY = "course_done_v1";

  function getDone(){
    try{ return JSON.parse(localStorage.getItem(KEY) || "[]"); }
    catch(e){ return []; }
  }
  function setDone(arr){
    localStorage.setItem(KEY, JSON.stringify(arr));
  }

  function updateCount(){
    const done = getDone();
    status.textContent = done.length + "/15 bài đã học";
  }

  function applyDoneUI(){
    const done = getDone();
    document.querySelectorAll(".mark").forEach(btn => {
      const id = btn.dataset.id;
      const item = btn.closest(".item");
      const isDone = done.includes(id);
      item.classList.toggle("done", isDone);
      btn.textContent = isDone ? "Đã học ✓" : "Đã học";
    });
    updateCount();
  }

  // Tìm kiếm
  q.addEventListener("input", () => {
    const key = q.value.trim().toLowerCase();
    document.querySelectorAll(".item").forEach(it => {
      const hay = (it.dataset.key || "").toLowerCase();
      it.style.display = (!key || hay.includes(key)) ? "flex" : "none";
    });
  });

  btnReset.addEventListener("click", () => {
    q.value = "";
    q.dispatchEvent(new Event("input"));
  });

  // Mark “đã học”
  document.querySelectorAll(".mark").forEach(btn => {
    btn.addEventListener("click", () => {
      const id = btn.dataset.id;
      const done = getDone();
      const idx = done.indexOf(id);
      if(idx === -1) done.push(id);
      else done.splice(idx, 1);
      setDone(done);
      applyDoneUI();
    });
  });

  // Copy link
  document.querySelectorAll(".copy").forEach(btn => {
    btn.addEventListener("click", async () => {
      const url = btn.dataset.url;
      try{
        await navigator.clipboard.writeText(url);
        btn.textContent = "Đã copy ✓";
        setTimeout(() => btn.textContent = "Copy link", 900);
      } catch(e){
        alert("Không copy được. Bạn hãy copy thủ công: " + url);
      }
    });
  });

  applyDoneUI();
</script>
    

Cách thay link: mở từng bài viết Blogspot → copy URL → dán vào chỗ LINK_BAI_01, LINK_BAI_02...

🧩 Phần B: Template trang bài học (dùng lại cho Bài 17 trở đi)

👉 Mỗi bài giảng là 1 bài viết/1 trang trên Blogspot. Bạn chỉ cần copy template dưới đây, rồi thay nội dung từng phần.


<div class="lesson">
  <h1 class="lesson-title">[TÊN BÀI] – [CHỦ ĐỀ]</h1>

  <section class="lesson-box">
    <h2>🎯 Mục tiêu</h2>
    <ul>
      <li>...</li>
    </ul>
  </section>

  <section class="lesson-box">
    <h2>📘 Lý thuyết ngắn</h2>
    <p>...</p>
  </section>

  <section class="lesson-box">
    <h2>💻 Ví dụ</h2>
    <pre class="code"><code>
<!-- dán code ví dụ ở đây -->
    </code></pre>
  </section>

  <section class="lesson-box">
    <h2>✍️ Bài tập</h2>
    <ol>
      <li>...</li>
    </ol>
  </section>

  <section class="lesson-box">
    <h2>✅ Đáp án gợi ý</h2>
    <pre class="code"><code>
<!-- gợi ý đáp án ở đây -->
    </code></pre>
  </section>
</div>

<style>
.lesson{max-width:900px;margin:20px auto;font-family:Arial,Helvetica,sans-serif;line-height:1.7}
.lesson-title{text-align:center;margin-bottom:24px}
.lesson-box{border:1px solid #e5e5e5;border-radius:12px;padding:16px 18px;margin-bottom:18px;background:#fff}
.lesson-box h2{margin-top:0}
.code{background:#f8f9fb;color:#111;border:1px solid #ddd;border-radius:10px;padding:14px;overflow-x:auto;font-size:14px}
.code code{font-family:Consolas,monospace}
code{background:#eee;padding:2px 6px;border-radius:6px}
</style>
    

✍️ Bài tập

  1. Tạo 1 Trang “Menu khóa học” và dán code phần A.
  2. Tạo 1 Bài viết “Bài 1” và dán nội dung bài 1 (bạn đã có ở các bài trước).
  3. Copy link bài 1 và thay vào LINK_BAI_01 trong menu.
  4. Làm tương tự cho Bài 2 → Bài 15.

✅ Đáp án gợi ý (quy trình nhanh)

  1. Blogspot → Pages (Trang) → New Page → dán Menu → Publish
  2. Blogspot → Posts (Bài đăng) → New Post → dán Bài 1 → Publish
  3. Mở bài 1 → copy URL → quay lại Menu → dán vào LINK_BAI_01 → Update

Bài 15: Mini Project – Landing Page 1 trang (HTML + CSS + JS)

🎯 Mục tiêu

  • Vận dụng HTML semantic + CSS (Flex/Grid) để dựng landing page
  • Biết tạo menu cuộn đến section (anchor link)
  • Biết làm nút “Lên đầu trang” bằng JavaScript
  • Biết tạo hiệu ứng scroll mượt
  • (Nâng cao) Hiển thị trạng thái menu đang chọn

📘 Mô tả sản phẩm

Bạn sẽ làm một Landing Page gồm các phần:

  • Header + Menu
  • Hero (giới thiệu) + nút CTA
  • Features (3–6 tính năng)
  • Gallery (lưới ảnh giả lập)
  • Contact (form)
  • Footer

💻 Code mẫu (Landing Page hoàn chỉnh)


<!doctype html>
<html lang="vi">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Bài 15 - Landing Page</title>

  <style>
    *{ box-sizing:border-box; }
    html{ scroll-behavior: smooth; }

    body{
      margin:0;
      font-family: Arial;
      background:#f7f8fa;
      line-height: 1.7;
      color:#111;
    }

    /* ===== HEADER ===== */
    header{
      position: sticky;
      top:0;
      z-index: 10;
      background:#111;
      color:#fff;
      border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    .nav{
      max-width: 1100px;
      margin: 0 auto;
      padding: 12px 16px;
      display:flex;
      align-items:center;
      justify-content: space-between;
      gap: 12px;
    }
    .brand{
      font-weight: 800;
      letter-spacing: 0.4px;
    }
    .menu{
      display:flex;
      gap: 12px;
      flex-wrap:wrap;
      justify-content: flex-end;
    }
    .menu a{
      color:#fff;
      text-decoration:none;
      font-weight: 700;
      padding: 6px 10px;
      border-radius: 10px;
      opacity: .9;
    }
    .menu a:hover{ background: rgba(255,255,255,0.12); opacity:1; }
    .menu a.active{ background:#2563eb; opacity:1; }

    /* ===== SECTIONS ===== */
    .wrap{
      max-width: 1100px;
      margin: 0 auto;
      padding: 18px 16px 40px;
    }
    section{
      background:#fff;
      border:1px solid #e5e5e5;
      border-radius: 16px;
      padding: 18px;
      margin-top: 16px;
    }

    /* ===== HERO ===== */
    .hero{
      display:grid;
      grid-template-columns: 1.2fr 0.8fr;
      gap: 16px;
      align-items:center;
      overflow:hidden;
    }
    .hero h1{ margin:0 0 8px; font-size: 30px; }
    .hero p{ margin:0; color:#333; }
    .cta{
      display:flex;
      gap: 10px;
      margin-top: 12px;
      flex-wrap:wrap;
    }
    .btn{
      display:inline-block;
      padding: 10px 14px;
      border-radius: 12px;
      border:0;
      cursor:pointer;
      font-weight: 800;
      text-decoration:none;
      text-align:center;
    }
    .btn.primary{ background:#2563eb; color:#fff; }
    .btn.dark{ background:#111; color:#fff; }

    .hero-card{
      border:1px solid #e5e5e5;
      border-radius: 16px;
      padding: 14px;
      background: #f3f4f6;
    }
    .kpi{
      display:grid;
      grid-template-columns: 1fr 1fr;
      gap: 10px;
      margin-top: 10px;
    }
    .kpi .item{
      background:#fff;
      border:1px solid #e5e5e5;
      border-radius: 14px;
      padding: 12px;
      text-align:center;
    }
    .kpi b{ font-size: 18px; }

    /* ===== FEATURES ===== */
    .grid3{
      display:grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 14px;
    }
    .feature{
      border:1px solid #e5e5e5;
      border-radius: 16px;
      padding: 14px;
      background:#fff;
    }
    .feature h3{ margin:0 0 8px; }
    .feature p{ margin:0; color:#333; }

    /* ===== GALLERY ===== */
    .gallery{
      display:grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 12px;
    }
    .ph{
      height: 130px;
      border-radius: 16px;
      border:1px solid #e5e5e5;
      background:#e5e7eb;
      display:flex;
      align-items:center;
      justify-content:center;
      font-weight: 800;
    }

    /* ===== CONTACT ===== */
    .form{
      display:grid;
      grid-template-columns: 1fr 1fr;
      gap: 12px;
    }
    label{ font-weight: 700; display:block; margin-bottom: 6px; }
    input, textarea{
      width:100%;
      padding: 10px 12px;
      border:1px solid #ddd;
      border-radius: 12px;
      outline:none;
      font-size: 14px;
    }
    textarea{ grid-column: 1 / -1; resize: vertical; min-height: 120px; }
    .form .full{ grid-column: 1 / -1; }
    .msg{
      margin-top: 10px;
      padding: 10px 12px;
      border-radius: 12px;
      background:#f3f4f6;
      border:1px solid #e5e7eb;
      color:#111;
    }

    /* ===== FOOTER ===== */
    footer{
      text-align:center;
      padding: 18px 0 30px;
      color:#555;
      font-size: 14px;
    }

    /* ===== BACK TO TOP ===== */
    #toTop{
      position: fixed;
      right: 16px;
      bottom: 16px;
      display:none;
      padding: 10px 12px;
      border-radius: 999px;
      border:0;
      cursor:pointer;
      background:#111;
      color:#fff;
      font-weight: 800;
    }

    /* ===== RESPONSIVE ===== */
    @media (max-width: 900px){
      .hero{ grid-template-columns: 1fr; }
      .grid3{ grid-template-columns: 1fr; }
      .form{ grid-template-columns: 1fr; }
      .menu{ justify-content:center; }
    }
  </style>
</head>

<body>

  <header>
    <div class="nav">
      <div class="brand">Web Tĩnh Course</div>
      <nav class="menu" id="menu">
        <a href="#home" class="active">Home</a>
        <a href="#features">Features</a>
        <a href="#gallery">Gallery</a>
        <a href="#contact">Contact</a>
      </nav>
    </div>
  </header>

  <div class="wrap">

    <section id="home" class="hero">
      <div>
        <h1>Landing Page 1 trang – web tĩnh</h1>
        <p>
          Bạn đang xem một mẫu landing page dùng HTML + CSS + JavaScript.
          Nó phù hợp để nhúng Blogspot, làm giới thiệu khóa học, sản phẩm, dịch vụ.
        </p>

        <div class="cta">
          <a class="btn primary" href="#contact">Đăng ký ngay</a>
          <button class="btn dark" id="btnDemo">Xem thông báo</button>
        </div>

        <div class="msg" id="heroMsg">Sẵn sàng.</div>
      </div>

      <div class="hero-card">
        <b>Tóm tắt nhanh</b>
        <div class="kpi">
          <div class="item"><b>15</b><br>Bài học</div>
          <div class="item"><b>3</b><br>Mini Project</div>
          <div class="item"><b>HTML</b><br>Cấu trúc</div>
          <div class="item"><b>JS</b><br>Tương tác</div>
        </div>
      </div>
    </section>

    <section id="features">
      <h2>Features</h2>
      <div class="grid3">
        <div class="feature">
          <h3>1) Dễ học</h3>
          <p>Bố cục rõ ràng, code ngắn gọn, thực hành ngay.</p>
        </div>
        <div class="feature">
          <h3>2) Chuẩn Blogspot</h3>
          <p>Nhúng được vào bài viết/Trang mà không cần server.</p>
        </div>
        <div class="feature">
          <h3>3) Có tương tác</h3>
          <p>JavaScript xử lý nút bấm, form, và hiệu ứng.</p>
        </div>
      </div>
    </section>

    <section id="gallery">
      <h2>Gallery</h2>
      <p style="margin-top:0;color:#333">Mẫu lưới ảnh giả lập bằng CSS Grid.</p>
      <div class="gallery">
        <div class="ph">Ảnh 1</div>
        <div class="ph">Ảnh 2</div>
        <div class="ph">Ảnh 3</div>
        <div class="ph">Ảnh 4</div>
        <div class="ph">Ảnh 5</div>
        <div class="ph">Ảnh 6</div>
      </div>
    </section>

    <section id="contact">
      <h2>Contact</h2>
      <p style="margin-top:0;color:#333">Form demo (không gửi server), JS chỉ kiểm tra và hiển thị.</p>

      <form id="contactForm" class="form">
        <div>
          <label for="cname">Họ tên</label>
          <input id="cname" type="text" placeholder="Nhập họ tên..." required>
        </div>

        <div>
          <label for="cemail">Email</label>
          <input id="cemail" type="email" placeholder="vd: abc@gmail.com" required>
        </div>

        <div class="full">
          <label for="cmsg">Nội dung</label>
          <textarea id="cmsg" placeholder="Bạn muốn tư vấn gì?" required></textarea>
        </div>

        <div class="full">
          <button class="btn primary" type="submit">Gửi</button>
          <span class="msg" id="contactMsg">Chưa gửi.</span>
        </div>
      </form>
    </section>

    <footer>
      © 2026 • Landing Page Demo • HTML/CSS/JS
    </footer>

  </div>

  <button id="toTop" title="Lên đầu trang">↑</button>

  <script>
    // ===== 1) Nút demo thông báo =====
    const btnDemo = document.getElementById("btnDemo");
    const heroMsg = document.getElementById("heroMsg");
    btnDemo.addEventListener("click", () => {
      const t = new Date().toLocaleTimeString("vi-VN");
      heroMsg.textContent = "Bạn vừa bấm nút lúc: " + t;
    });

    // ===== 2) Form contact (demo) =====
    const form = document.getElementById("contactForm");
    const contactMsg = document.getElementById("contactMsg");
    form.addEventListener("submit", (e) => {
      e.preventDefault();
      const name = document.getElementById("cname").value.trim();
      const email = document.getElementById("cemail").value.trim();
      const message = document.getElementById("cmsg").value.trim();

      if(name.length < 2){
        contactMsg.textContent = "❌ Tên quá ngắn.";
        return;
      }
      if(message.length < 5){
        contactMsg.textContent = "❌ Nội dung quá ngắn.";
        return;
      }

      contactMsg.textContent = "✅ Đã nhận: " + name + " (" + email + ")";
      form.reset();
    });

    // ===== 3) Back to Top =====
    const toTop = document.getElementById("toTop");
    window.addEventListener("scroll", () => {
      if(window.scrollY > 400) toTop.style.display = "block";
      else toTop.style.display = "none";
      highlightMenu();
    });
    toTop.addEventListener("click", () => {
      window.scrollTo({ top: 0, behavior: "smooth" });
    });

    // ===== 4) Highlight menu theo section đang xem =====
    const menuLinks = Array.from(document.querySelectorAll("#menu a"));
    const sections = menuLinks
      .map(a => document.querySelector(a.getAttribute("href")))
      .filter(Boolean);

    function highlightMenu(){
      const y = window.scrollY + 120; // bù header
      let currentId = "home";

      sections.forEach(sec => {
        if(sec.offsetTop <= y) currentId = sec.id;
      });

      menuLinks.forEach(a => {
        const id = a.getAttribute("href").replace("#","");
        a.classList.toggle("active", id === currentId);
      });
    }
    highlightMenu();
  </script>

</body>
</html>
    

👉 Copy code → lưu thành bai15.html → mở bằng Chrome.
Cuộn trang để thấy menu active đổi theo section + nút “↑” hiện khi cuộn xuống.

✍️ Bài tập

  1. Đổi nội dung “Features” thành đúng khóa học của bạn (HTML/CSS/JS).
  2. Thêm section mới “Pricing” (bảng giá) và thêm vào menu.
  3. Đổi gallery thành ảnh thật (dùng <img>).
  4. (Nâng cao) Làm menu dạng nút hamburger trên mobile.

✅ Đáp án gợi ý

1) Thêm section Pricing (gợi ý)


<a href="#pricing">Pricing</a>

<section id="pricing">
  <h2>Pricing</h2>
  ...
</section>
    

Bài 14: JavaScript Fetch API – Tải dữ liệu và hiển thị ra giao diện

🎯 Mục tiêu

  • Hiểu “API” là gì và vì sao cần gọi API trong web
  • Biết dùng fetch() để lấy dữ liệu dạng JSON
  • Biết xử lý then() hoặc async/await (mình dùng async/await cho dễ học)
  • Biết render danh sách dữ liệu ra HTML bằng DOM
  • Biết xử lý trạng thái: đang tải / lỗi / thành công

📘 Lý thuyết ngắn

1) API là gì?

API là nơi cung cấp dữ liệu cho ứng dụng. Web của bạn có thể “xin dữ liệu” từ API rồi hiển thị ra giao diện. Ví dụ: danh sách bài viết, sản phẩm, đơn hàng...

2) Fetch API


fetch("https://example.com/api")
  .then(res => res.json())
  .then(data => console.log(data));
    

3) Async/Await (dễ đọc hơn)


async function load(){
  const res = await fetch(url);
  const data = await res.json();
}
    

Lưu ý: Nếu mở file .html offline, một số trình duyệt vẫn cho gọi API HTTPS bình thường. Nếu máy bạn chặn, hãy thử chạy bằng Live Server hoặc đưa lên Blogspot.

💻 Code mẫu (Tải bài viết từ API và hiển thị)


<!doctype html>
<html lang="vi">
<head>
  <meta charset="utf-8">
  <title>Bài 14 - Fetch API</title>

  <style>
    body{
      font-family: Arial;
      background:#f7f8fa;
      padding: 24px;
      line-height: 1.7;
    }
    .app{
      max-width: 900px;
      margin: 0 auto;
      background:#fff;
      border:1px solid #e5e5e5;
      border-radius: 14px;
      padding: 18px;
    }
    h1{ margin-top:0; text-align:center; }

    .toolbar{
      display:flex;
      gap:10px;
      flex-wrap:wrap;
      justify-content: center;
      margin: 12px 0 16px;
    }
    button{
      padding:10px 14px;
      border:0;
      border-radius: 10px;
      cursor:pointer;
      background:#2563eb;
      color:#fff;
      font-weight:700;
    }
    button.gray{ background:#111; }

    input{
      padding:10px 12px;
      border:1px solid #ddd;
      border-radius: 10px;
      min-width: 240px;
    }

    .status{
      padding: 10px 12px;
      border-radius: 12px;
      background:#f3f4f6;
      border:1px solid #e5e7eb;
      margin-bottom: 12px;
    }

    .list{
      display:grid;
      grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
      gap: 14px;
    }

    .card{
      border:1px solid #e5e5e5;
      border-radius: 12px;
      padding: 14px;
      background:#fff;
    }
    .card h3{
      margin: 0 0 8px;
      font-size: 16px;
    }
    .card p{
      margin: 0;
      color:#333;
      font-size: 14px;
    }

    .muted{ color:#555; }
  </style>
</head>

<body>

  <div class="app">
    <h1>🌐 Fetch API - Danh sách bài viết</h1>

    <div class="toolbar">
      <button id="btnLoad">Tải dữ liệu</button>
      <button class="gray" id="btnClear">Xóa hiển thị</button>
      <input id="search" type="text" placeholder="Tìm theo tiêu đề...">
    </div>

    <div class="status" id="status">
      <span class="muted">Chưa tải dữ liệu.</span>
    </div>

    <div class="list" id="list"></div>
  </div>

  <script>
    const API_URL = "https://jsonplaceholder.typicode.com/posts";

    const btnLoad = document.getElementById("btnLoad");
    const btnClear = document.getElementById("btnClear");
    const search = document.getElementById("search");
    const statusEl = document.getElementById("status");
    const listEl = document.getElementById("list");

    let posts = [];

    function setStatus(text){
      statusEl.innerHTML = text;
    }

    function render(list){
      listEl.innerHTML = "";
      if(list.length === 0){
        listEl.innerHTML = "<p class='muted'>Không có dữ liệu để hiển thị.</p>";
        return;
      }

      list.forEach(p => {
        const div = document.createElement("div");
        div.className = "card";
        div.innerHTML = `
          <h3>${p.id}. ${p.title}</h3>
          <p>${p.body}</p>
        `;
        listEl.appendChild(div);
      });
    }

    async function loadPosts(){
      try{
        setStatus("⏳ Đang tải dữ liệu...");
        const res = await fetch(API_URL);

        if(!res.ok){
          throw new Error("HTTP " + res.status);
        }

        const data = await res.json();

        // lấy 12 bài cho gọn
        posts = data.slice(0, 12);

        setStatus("✅ Tải thành công: <b>" + posts.length + "</b> bài viết.");
        render(posts);
      } catch(err){
        setStatus("❌ Lỗi khi tải dữ liệu: <b>" + err.message + "</b>");
      }
    }

    function applySearch(){
      const key = search.value.trim().toLowerCase();
      if(!key){
        render(posts);
        return;
      }
      const filtered = posts.filter(p => p.title.toLowerCase().includes(key));
      render(filtered);
    }

    btnLoad.addEventListener("click", loadPosts);
    btnClear.addEventListener("click", () => {
      posts = [];
      listEl.innerHTML = "";
      search.value = "";
      setStatus("<span class='muted'>Đã xóa hiển thị.</span>");
    });
    search.addEventListener("input", applySearch);
  </script>

</body>
</html>
    

👉 Copy code → lưu thành bai14.html → mở bằng Chrome.
Bấm “Tải dữ liệu”, sau đó gõ tìm kiếm theo tiêu đề.

✍️ Bài tập

  1. Đổi API sang https://jsonplaceholder.typicode.com/users và hiển thị tên + email.
  2. Thêm dropdown chọn số lượng hiển thị (5/10/20).
  3. (Nâng cao) Thêm nút “Tải lại” và hiển thị thời gian tải (giờ:phút:giây).
  4. (Nâng cao) Tạo modal xem chi tiết bài viết khi click card.

✅ Đáp án gợi ý

1) Đổi sang users


const API_URL = "https://jsonplaceholder.typicode.com/users";
...
div.innerHTML = `
  <h3>${u.name}</h3>
  <p>${u.email}</p>
`;
    

2) Lấy thời gian tải (gợi ý)


const t = new Date();
const timeText = t.toLocaleTimeString("vi-VN");
setStatus("✅ Tải xong lúc: <b>" + timeText + "</b>");
    

Bài 13: JavaScript Array & Object – Quản lý danh sách (thêm/xóa/tìm kiếm)

🎯 Mục tiêu

  • Hiểu Array (mảng) và Object (đối tượng) trong JavaScript
  • Biết dùng các hàm mảng: push, filter, map, find, splice
  • Biết render (vẽ) danh sách ra HTML bằng DOM
  • Tạo mini CRUD: Thêm / Xóa / Tìm kiếm
  • (Nâng cao) Lưu dữ liệu bằng localStorage

📘 Lý thuyết ngắn

1) Array (Mảng): danh sách nhiều phần tử.


const nums = [1, 2, 3];
nums.push(4); // thêm cuối
    

2) Object (Đối tượng): lưu dữ liệu theo cặp key–value.


const product = { id: 1, name: "Nước suối", price: 10000 };
console.log(product.name); // "Nước suối"
    

3) Mảng object (rất hay gặp trong web)


const products = [
  { id: 1, name: "Cà phê", price: 25000 },
  { id: 2, name: "Trà sữa", price: 35000 }
];
    

4) Các hàm mảng quan trọng

  • map(): biến đổi mảng → mảng mới
  • filter(): lọc theo điều kiện
  • find(): tìm 1 phần tử
  • splice(): xóa/sửa tại vị trí index

💻 Code mẫu (Mini app quản lý sản phẩm)


<!doctype html>
<html lang="vi">
<head>
  <meta charset="utf-8">
  <title>Bài 13 - Array & Object CRUD</title>

  <style>
    body{
      font-family: Arial;
      background:#f7f8fa;
      padding: 24px;
      line-height: 1.7;
    }
    .app{
      max-width: 860px;
      margin: 0 auto;
      background:#fff;
      border:1px solid #e5e5e5;
      border-radius: 14px;
      padding: 18px;
    }
    h1{ margin-top:0; text-align:center; }

    .grid{
      display:grid;
      grid-template-columns: 1fr 1fr 1fr auto;
      gap: 10px;
      margin: 12px 0;
    }
    @media (max-width: 800px){
      .grid{ grid-template-columns: 1fr 1fr; }
    }

    input{
      padding:10px 12px;
      border:1px solid #ddd;
      border-radius: 10px;
      width:100%;
      box-sizing:border-box;
    }

    button{
      padding:10px 14px;
      border:0;
      border-radius: 10px;
      cursor:pointer;
      background:#2563eb;
      color:#fff;
      font-weight:700;
    }
    button.gray{ background:#111; }
    button.red{ background:#ef4444; }

    .toolbar{
      display:flex;
      gap:10px;
      flex-wrap:wrap;
      align-items:center;
      justify-content: space-between;
      margin-top: 8px;
    }

    .table{
      width: 100%;
      border-collapse: collapse;
      margin-top: 12px;
    }
    .table th, .table td{
      border: 1px solid #ddd;
      padding: 10px;
      text-align:left;
      vertical-align: top;
    }
    .table th{
      background:#111;
      color:#fff;
    }

    .badge{
      display:inline-block;
      padding:2px 10px;
      border-radius: 999px;
      background:#eef2ff;
      border:1px solid #dbeafe;
      font-size: 12px;
      margin-right: 6px;
    }

    .msg{
      margin-top: 10px;
      padding: 10px 12px;
      border-radius: 12px;
      background:#f3f4f6;
      border:1px solid #e5e7eb;
      color:#111;
    }
  </style>
</head>

<body>

  <div class="app">
    <h1>🧾 Quản lý sản phẩm (Array + Object)</h1>

    <div class="grid">
      <input id="name" type="text" placeholder="Tên sản phẩm (vd: Trà sữa)">
      <input id="price" type="number" placeholder="Giá (vd: 35000)">
      <input id="category" type="text" placeholder="Danh mục (vd: Đồ uống)">
      <button id="btnAdd">Thêm</button>
    </div>

    <div class="toolbar">
      <div>
        <span class="badge">Thêm</span>
        <span class="badge">Xóa</span>
        <span class="badge">Tìm kiếm</span>
      </div>

      <div style="display:flex; gap:10px; flex-wrap:wrap;">
        <input id="search" type="text" placeholder="Tìm theo tên..." style="min-width:220px">
        <button class="gray" id="btnClear">Xóa hết</button>
      </div>
    </div>

    <table class="table">
      <thead>
        <tr>
          <th>ID</th>
          <th>Tên</th>
          <th>Danh mục</th>
          <th>Giá</th>
          <th>Thao tác</th>
        </tr>
      </thead>
      <tbody id="tbody"></tbody>
    </table>

    <div class="msg" id="msg">Sẵn sàng.</div>
  </div>

  <script>
    // ====== 1) Dữ liệu ban đầu (mảng object) ======
    let products = [
      { id: 1, name: "Cà phê", category: "Đồ uống", price: 25000 },
      { id: 2, name: "Trà sữa", category: "Đồ uống", price: 35000 },
      { id: 3, name: "Snack", category: "Ăn vặt", price: 15000 }
    ];

    // ====== 2) Lấy element ======
    const elName = document.getElementById("name");
    const elPrice = document.getElementById("price");
    const elCategory = document.getElementById("category");
    const btnAdd = document.getElementById("btnAdd");
    const elSearch = document.getElementById("search");
    const btnClear = document.getElementById("btnClear");
    const tbody = document.getElementById("tbody");
    const msg = document.getElementById("msg");

    // ====== 3) Helpers ======
    function formatMoney(n){
      return Number(n).toLocaleString("vi-VN") + " đ";
    }

    function setMsg(text){
      msg.textContent = text;
    }

    function nextId(){
      // lấy id lớn nhất + 1
      const maxId = products.length ? Math.max(...products.map(p => p.id)) : 0;
      return maxId + 1;
    }

    // ====== 4) Render ======
    function render(list){
      tbody.innerHTML = "";

      if(list.length === 0){
        tbody.innerHTML = '<tr><td colspan="5">Không có dữ liệu.</td></tr>';
        return;
      }

      list.forEach(p => {
        const tr = document.createElement("tr");

        tr.innerHTML = `
          <td>${p.id}</td>
          <td>${p.name}</td>
          <td>${p.category}</td>
          <td>${formatMoney(p.price)}</td>
          <td><button class="red" data-id="${p.id}">Xóa</button></td>
        `;

        tbody.appendChild(tr);
      });

      // gắn sự kiện xóa cho các nút vừa render
      tbody.querySelectorAll("button[data-id]").forEach(btn => {
        btn.addEventListener("click", () => {
          const id = Number(btn.dataset.id);
          removeById(id);
        });
      });
    }

    // ====== 5) Thêm ======
    function addProduct(){
      const name = elName.value.trim();
      const category = elCategory.value.trim();
      const price = Number(elPrice.value);

      if(name.length < 2){
        setMsg("❌ Tên sản phẩm quá ngắn.");
        return;
      }
      if(!category){
        setMsg("❌ Vui lòng nhập danh mục.");
        return;
      }
      if(!price || price <= 0){
        setMsg("❌ Giá không hợp lệ.");
        return;
      }

      const p = { id: nextId(), name, category, price };
      products.push(p); // push vào mảng

      elName.value = "";
      elCategory.value = "";
      elPrice.value = "";

      setMsg("✅ Đã thêm: " + p.name);
      applySearch(); // render theo filter hiện tại
    }

    // ====== 6) Xóa ======
    function removeById(id){
      const idx = products.findIndex(p => p.id === id);
      if(idx === -1) return;

      const removed = products[idx];
      products.splice(idx, 1); // xóa 1 phần tử tại idx

      setMsg("🗑️ Đã xóa: " + removed.name);
      applySearch();
    }

    // ====== 7) Tìm kiếm ======
    function applySearch(){
      const key = elSearch.value.trim().toLowerCase();
      if(!key){
        render(products);
        return;
      }
      const filtered = products.filter(p => p.name.toLowerCase().includes(key));
      render(filtered);
    }

    // ====== 8) Xóa hết ======
    function clearAll(){
      if(!confirm("Bạn chắc chắn muốn xóa hết dữ liệu?")) return;
      products = [];
      setMsg("Đã xóa hết dữ liệu.");
      applySearch();
    }

    // ====== 9) Gắn sự kiện ======
    btnAdd.addEventListener("click", addProduct);
    elSearch.addEventListener("input", applySearch);
    btnClear.addEventListener("click", clearAll);

    // ====== 10) Render lần đầu ======
    render(products);
  </script>

</body>
</html>
    

👉 Copy code → lưu thành bai13.html → mở bằng Chrome.
Thử thêm sản phẩm, gõ tìm kiếm, bấm xóa từng dòng.

✍️ Bài tập

  1. Thêm cột “Ngày tạo” (tự lấy ngày hiện tại bằng JS).
  2. Thêm chức năng “Sắp xếp theo giá tăng dần/giảm dần”.
  3. (Nâng cao) Lưu mảng products vào localStorage và tải lại vẫn còn.
  4. (Nâng cao) Thêm nút “Sửa” (edit) cho mỗi dòng.

✅ Đáp án gợi ý

1) Lưu localStorage (gợi ý)


// Lưu
localStorage.setItem("products", JSON.stringify(products));

// Tải
products = JSON.parse(localStorage.getItem("products") || "[]");
    

2) Sắp xếp tăng dần


products.sort((a,b) => a.price - b.price);
render(products);
    

Bài 12: Mini Project – Quiz trắc nghiệm (có chấm điểm)

🎯 Mục tiêu

  • Biết tạo câu hỏi trắc nghiệm bằng HTML (radio)
  • Biết xử lý chấm điểm bằng JavaScript
  • Hiểu cách lưu dữ liệu câu hỏi trong mảng (array) và đối tượng (object)
  • Biết cập nhật giao diện bằng DOM

📘 Mô tả bài toán

Bạn sẽ làm một trang Quiz có 5 câu hỏi:

  • Chọn đáp án (A/B/C/D)
  • Bấm “Nộp bài” để chấm điểm
  • Hiển thị số câu đúng + tổng điểm
  • Tô màu câu đúng/sai
  • (Nâng cao) Có nút “Làm lại”

📘 Kiến thức dùng trong bài

  • Radio: chỉ chọn 1 đáp án trong một nhóm (cùng name).
  • Array/Object: lưu danh sách câu hỏi để dễ mở rộng.
  • DOM: JS đọc đáp án người dùng chọn và cập nhật kết quả.

💻 Code mẫu (Quiz hoàn chỉnh)


<!doctype html>
<html lang="vi">
<head>
  <meta charset="utf-8">
  <title>Bài 12 - Quiz trắc nghiệm</title>

  <style>
    body{
      font-family: Arial;
      background:#f7f8fa;
      padding: 24px;
      line-height: 1.7;
    }
    .app{
      max-width: 760px;
      margin: 0 auto;
      background:#fff;
      border:1px solid #e5e5e5;
      border-radius: 14px;
      padding: 18px;
    }
    h1{ margin-top:0; text-align:center; }
    .q{
      border:1px solid #e5e5e5;
      border-radius: 12px;
      padding: 14px;
      margin: 12px 0;
      background:#fff;
    }
    .q.correct{ background:#ecfdf5; border-color:#bbf7d0; }
    .q.wrong{ background:#fff1f2; border-color:#fecdd3; }

    .answers label{
      display:block;
      padding: 6px 0;
      cursor:pointer;
    }
    .row{
      display:flex;
      gap: 10px;
      flex-wrap: wrap;
      justify-content: center;
      margin-top: 14px;
    }
    button{
      padding: 10px 14px;
      border:0;
      border-radius: 10px;
      cursor:pointer;
      background:#2563eb;
      color:#fff;
      font-weight: 700;
    }
    button.gray{ background:#111; }
    .result{
      margin-top: 14px;
      padding: 12px;
      border-radius: 12px;
      background:#f3f4f6;
      border:1px solid #e5e7eb;
    }
    small{ color:#555; }
  </style>
</head>

<body>

  <div class="app">
    <h1>🧠 Quiz trắc nghiệm</h1>
    <p>Chọn đáp án đúng rồi bấm <b>Nộp bài</b>.</p>

    <div id="quiz"></div>

    <div class="row">
      <button id="btnSubmit">Nộp bài</button>
      <button class="gray" id="btnReset">Làm lại</button>
    </div>

    <div class="result" id="result">
      <small>Kết quả sẽ hiển thị ở đây.</small>
    </div>
  </div>

  <script>
    // 1) Dữ liệu câu hỏi (có thể thêm bớt dễ dàng)
    const questions = [
      {
        text: "HTML dùng để làm gì?",
        options: ["Tạo cấu trúc", "Làm đẹp", "Tạo tương tác", "Lưu dữ liệu"],
        answer: 0
      },
      {
        text: "CSS dùng để làm gì?",
        options: ["Chấm điểm", "Làm đẹp giao diện", "Tạo server", "Tạo database"],
        answer: 1
      },
      {
        text: "Thẻ nào tạo liên kết?",
        options: ["<img>", "<a>", "<p>", "<div>"],
        answer: 1
      },
      {
        text: "Trong JS, từ khóa khai báo biến có thể đổi giá trị là?",
        options: ["const", "let", "varrr", "fixed"],
        answer: 1
      },
      {
        text: "Thuộc tính nào bắt buộc nhập form?",
        options: ["required", "checked", "href", "alt"],
        answer: 0
      }
    ];

    const quizEl = document.getElementById("quiz");
    const resultEl = document.getElementById("result");
    const btnSubmit = document.getElementById("btnSubmit");
    const btnReset = document.getElementById("btnReset");

    // 2) Render câu hỏi ra HTML
    function renderQuiz(){
      quizEl.innerHTML = "";
      questions.forEach((q, i) => {
        const box = document.createElement("div");
        box.className = "q";
        box.setAttribute("data-index", i);

        let html = "<h3>Câu " + (i+1) + ": " + q.text + "</h3>";
        html += '<div class="answers">';

        q.options.forEach((opt, j) => {
          html +=
            '<label>' +
            '<input type="radio" name="q' + i + '" value="' + j + '"> ' +
            opt +
            '</label>';
        });

        html += "</div>";
        box.innerHTML = html;
        quizEl.appendChild(box);
      });

      resultEl.innerHTML = "<small>Kết quả sẽ hiển thị ở đây.</small>";
    }

    // 3) Chấm điểm
    function grade(){
      let correct = 0;

      const boxes = document.querySelectorAll(".q");
      boxes.forEach((box, i) => {
        box.classList.remove("correct", "wrong");

        const checked = document.querySelector('input[name="q' + i + '"]:checked');
        const userAnswer = checked ? Number(checked.value) : -1;

        if(userAnswer === questions[i].answer){
          correct++;
          box.classList.add("correct");
        } else {
          box.classList.add("wrong");
        }
      });

      const total = questions.length;
      const score = Math.round((correct / total) * 10);

      resultEl.innerHTML =
        "✅ Đúng: <b>" + correct + "/" + total + "</b> câu" +
        "<br>🎯 Điểm: <b>" + score + "/10</b>";
    }

    // 4) Reset
    function resetQuiz(){
      renderQuiz();
    }

    btnSubmit.addEventListener("click", grade);
    btnReset.addEventListener("click", resetQuiz);

    // chạy lần đầu
    renderQuiz();
  </script>

</body>
</html>
    

👉 Copy code → lưu thành bai12.html → mở bằng Chrome.
Bấm “Nộp bài” để chấm điểm và xem câu đúng/sai được tô màu.

✍️ Bài tập

  1. Thêm 3 câu hỏi mới vào mảng questions.
  2. Đổi thang điểm từ 10 sang 100.
  3. (Nâng cao) Nếu người học bỏ trống câu nào, thông báo “Bạn chưa làm hết”.
  4. (Nâng cao) Hiển thị đáp án đúng sau khi nộp bài.

✅ Đáp án gợi ý

1) Thang điểm 100


const score = Math.round((correct / total) * 100);
    

2) Kiểm tra bỏ trống


let unanswered = 0;
const checked = document.querySelector('input[name="q' + i + '"]:checked');
if(!checked) unanswered++;

if(unanswered > 0){
  resultEl.innerHTML = "⚠️ Bạn chưa làm hết: " + unanswered + " câu.";
  return;
}
    

3) Hiển thị đáp án đúng (gợi ý)


// Sau khi chấm, bạn có thể thêm dòng:
box.innerHTML += "<p><b>Đáp án đúng:</b> " + questions[i].options[questions[i].answer] + "</p>";
    

Bài đăng phổ biến

💬 Bình luận

💬 Bình luận

📌 Danh sách bình luận