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 LINK_BAI_19. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn LINK_BAI_19. Hiển thị tất cả bài đăng

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

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 đăng phổ biến

💬 Bình luận

💬 Bình luận

📌 Danh sách bình luận