Bài 20: Mini Project – Website Portfolio cá nhân

Bạn đã học hết 19 bài về HTML, CSS, responsive design, animations, SCSS… Bây giờ đến lúc tạo một trang Portfolio thực sự chuyên nghiệp — một trang web mà bạn có thể chia sẻ với nhà tuyển dụng, khách hàng, hoặc đơn giản là tự hào với bạn bè.

Bài này sẽ hướng dẫn bạn xây dựng Portfolio hoàn chỉnh: HTML semantic, SCSS clean, responsive, animations, best practices — tất cả những gì bạn đã học.

Yêu cầu dự án hoàn chỉnh

Trang Portfolio cá nhân Professional:

  • Header/Navigation: Sticky header, smooth scroll navigation
  • Hero Section: Impressive intro với gradient, animation
  • About Section: Thông tin cá nhân, kỹ năng, timeline
  • Projects/Portfolio: Hiển thị dự án với cards, filters
  • Skills Section: Skill bars hoặc tag cloud
  • Blog/Articles: Danh sách bài viết gần đây (tuỳ chọn)
  • Contact Section: Form liên hệ hoặc thông tin contact
  • Footer: Social links, copyright
  • Responsive: Mobile-first, hoạt động trên tất cả thiết bị
  • Animations: Smooth transitions, hover effects, fade-in on scroll
  • Dark Mode: Tuỳ chọn, toggle light/dark theme
  • Performance: Optimized, fast load time
  • SEO: Meta tags, semantic HTML

Bước 1: HTML (Semantic & Complete)

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="Portfolio của Nguyễn Văn A - Frontend Developer. Xem các dự án và kỹ năng của tôi.">
  <meta name="keywords" content="portfolio, developer, frontend, html, css, javascript">
  <meta name="author" content="Nguyễn Văn A">
  <title>Nguyễn Văn A - Frontend Developer Portfolio</title>
  
  <!-- Google Fonts -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Lato:wght@300;400;700&display=swap" rel="stylesheet">
  
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <!-- ========== HEADER/NAV ========== -->
  <header class="header">
    <div class="header__container">
      <h1 class="header__logo">
        <a href="#home" class="header__logo-link">NVA</a>
      </h1>
      
      <button class="header__menu-toggle" id="menuToggle" aria-label="Toggle menu">
        <span class="hamburger"></span>
      </button>

      <nav class="header__nav" id="navMenu">
        <a href="#home" class="header__nav-link">Trang chủ</a>
        <a href="#about" class="header__nav-link">Về tôi</a>
        <a href="#projects" class="header__nav-link">Dự án</a>
        <a href="#skills" class="header__nav-link">Kỹ năng</a>
        <a href="#contact" class="header__nav-link">Liên hệ</a>
      </nav>
    </div>
  </header>

  <!-- ========== HERO SECTION ========== -->
  <section id="home" class="hero">
    <div class="hero__content">
      <h1 class="hero__title">Xin chào, tôi là Nguyễn Văn A</h1>
      <p class="hero__subtitle">Frontend Developer & Web Designer</p>
      <p class="hero__description">
        Tôi tạo những trang web đẹp, responsive, và user-friendly. 
        Đam mê với HTML, CSS, JavaScript, và React.
      </p>
      <div class="hero__cta">
        <a href="#projects" class="btn btn--primary">Xem dự án</a>
        <a href="#contact" class="btn btn--secondary">Liên hệ</a>
      </div>
    </div>
    <div class="hero__image">
      <div class="hero__image-placeholder"></div>
    </div>
  </section>

  <!-- ========== ABOUT SECTION ========== -->
  <section id="about" class="about">
    <div class="container">
      <h2 class="section-title">Về tôi</h2>
      
      <div class="about__content">
        <div class="about__text">
          <p>
            Tôi là một lập trình viên frontend có 3 năm kinh nghiệm trong việc xây dựng 
            những ứng dụng web hiện đại. Tôi chuyên về HTML5, CSS3, JavaScript, và React.
          </p>
          <p>
            Tôi yêu thích học hỏi, làm việc nhóm, và giải quyết các vấn đề phức tạp. 
            Mục tiêu của tôi là tạo những trang web không chỉ đẹp về mặt thiết kế, 
            mà còn tối ưu về hiệu suất và user experience.
          </p>
          <p>
            Ngoài công việc, tôi thích viết blog, tham gia những cuộc hội thảo tech, 
            và đóng góp cho các dự án open source.
          </p>
        </div>

        <div class="about__timeline">
          <h3 class="about__subtitle">Hành trình</h3>
          <div class="timeline">
            <div class="timeline__item">
              <div class="timeline__dot"></div>
              <div class="timeline__content">
                <h4>Frontend Developer</h4>
                <p class="timeline__meta">2022 - Hiện tại | Tech Company</p>
                <p>Phát triển và bảo trì các ứng dụng web bằng React.</p>
              </div>
            </div>

            <div class="timeline__item">
              <div class="timeline__dot"></div>
              <div class="timeline__content">
                <h4>Junior Frontend Developer</h4>
                <p class="timeline__meta">2021 - 2022 | Startup</p>
                <p>Phát triển frontend cho ứng dụng web e-commerce.</p>
              </div>
            </div>

            <div class="timeline__item">
              <div class="timeline__dot"></div>
              <div class="timeline__content">
                <h4>Tốt nghiệp</h4>
                <p class="timeline__meta">2021 | Đại học Công Nghệ</p>
                <p>Chuyên ngành Công nghệ thông tin.</p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>

  <!-- ========== PROJECTS SECTION ========== -->
  <section id="projects" class="projects">
    <div class="container">
      <h2 class="section-title">Dự án</h2>

      <div class="projects__filters">
        <button class="filter-btn filter-btn--active" data-filter="all">Tất cả</button>
        <button class="filter-btn" data-filter="react">React</button>
        <button class="filter-btn" data-filter="vanilla">Vanilla JS</button>
      </div>

      <div class="projects__grid">
        <!-- Project 1 -->
        <article class="project-card" data-category="react">
          <div class="project-card__image">
            <div class="project-card__image-placeholder"></div>
          </div>
          <div class="project-card__content">
            <h3 class="project-card__title">E-commerce Store</h3>
            <p class="project-card__description">
              Ứng dụng web bán hàng xây dựng bằng React, Redux, và Node.js API.
            </p>
            <div class="project-card__tags">
              <span class="tag">React</span>
              <span class="tag">Redux</span>
              <span class="tag">Node.js</span>
            </div>
            <a href="#" class="project-card__link">Xem dự án →</a>
          </div>
        </article>

        <!-- Project 2 -->
        <article class="project-card" data-category="vanilla">
          <div class="project-card__image">
            <div class="project-card__image-placeholder"></div>
          </div>
          <div class="project-card__content">
            <h3 class="project-card__title">Task Management App</h3>
            <p class="project-card__description">
              Ứng dụng quản lý công việc với vanilla JavaScript và Local Storage.
            </p>
            <div class="project-card__tags">
              <span class="tag">JavaScript</span>
              <span class="tag">HTML/CSS</span>
              <span class="tag">Storage</span>
            </div>
            <a href="#" class="project-card__link">Xem dự án →</a>
          </div>
        </article>

        <!-- Project 3 -->
        <article class="project-card" data-category="react">
          <div class="project-card__image">
            <div class="project-card__image-placeholder"></div>
          </div>
          <div class="project-card__content">
            <h3 class="project-card__title">Weather App</h3>
            <p class="project-card__description">
              Ứng dụng thời tiết real-time sử dụng React Hooks và OpenWeather API.
            </p>
            <div class="project-card__tags">
              <span class="tag">React</span>
              <span class="tag">API</span>
              <span class="tag">Hooks</span>
            </div>
            <a href="#" class="project-card__link">Xem dự án →</a>
          </div>
        </article>

        <!-- Project 4 -->
        <article class="project-card" data-category="vanilla">
          <div class="project-card__image">
            <div class="project-card__image-placeholder"></div>
          </div>
          <div class="project-card__content">
            <h3 class="project-card__title">Blog Platform</h3>
            <p class="project-card__description">
              Nền tảng blog với search, categories, và responsive design.
            </p>
            <div class="project-card__tags">
              <span class="tag">HTML</span>
              <span class="tag">CSS</span>
              <span class="tag">JavaScript</span>
            </div>
            <a href="#" class="project-card__link">Xem dự án →</a>
          </div>
        </article>
      </div>
    </div>
  </section>

  <!-- ========== SKILLS SECTION ========== -->
  <section id="skills" class="skills">
    <div class="container">
      <h2 class="section-title">Kỹ năng</h2>

      <div class="skills__grid">
        <div class="skill-card">
          <h3 class="skill-card__title">Frontend</h3>
          <ul class="skill-list">
            <li>HTML5</li>
            <li>CSS3 / SCSS</li>
            <li>JavaScript (ES6+)</li>
            <li>React & Hooks</li>
          </ul>
        </div>

        <div class="skill-card">
          <h3 class="skill-card__title">Tools & Platforms</h3>
          <ul class="skill-list">
            <li>Git / GitHub</li>
            <li>VS Code</li>
            <li>Figma</li>
            <li>Webpack</li>
          </ul>
        </div>

        <div class="skill-card">
          <h3 class="skill-card__title">Soft Skills</h3>
          <ul class="skill-list">
            <li>Problem Solving</li>
            <li>Teamwork</li>
            <li>Communication</li>
            <li>Time Management</li>
          </ul>
        </div>
      </div>
    </div>
  </section>

  <!-- ========== CONTACT SECTION ========== -->
  <section id="contact" class="contact">
    <div class="container">
      <h2 class="section-title">Liên hệ</h2>
      <p class="contact__intro">
        Tôi luôn sẵn sàng nghe những ý tưởng mới và cơ hội hợp tác. 
        Hãy liên hệ với tôi qua form dưới đây!
      </p>

      <div class="contact__wrapper">
        <!-- Contact Info -->
        <div class="contact__info">
          <h3>Thông tin</h3>
          <ul class="contact__list">
            <li>
              <strong>Email:</strong>
              <a href="mailto:nguyenvana@email.com">nguyenvana@email.com</a>
            </li>
            <li>
              <strong>Phone:</strong>
              <a href="tel:+84123456789">+84 (0) 123 456 789</a>
            </li>
            <li>
              <strong>Location:</strong>
              Hà Nội, Việt Nam
            </li>
          </ul>

          <h3>Mạng xã hội</h3>
          <div class="social-links">
            <a href="https://github.com" class="social-link" title="GitHub">GitHub</a>
            <a href="https://linkedin.com" class="social-link" title="LinkedIn">LinkedIn</a>
            <a href="https://twitter.com" class="social-link" title="Twitter">Twitter</a>
          </div>
        </div>

        <!-- Contact Form -->
        <form class="contact__form" id="contactForm">
          <div class="form-group">
            <label for="name">Tên <span class="required">*</span></label>
            <input type="text" id="name" name="name" required>
          </div>

          <div class="form-group">
            <label for="email">Email <span class="required">*</span></label>
            <input type="email" id="email" name="email" required>
          </div>

          <div class="form-group">
            <label for="subject">Tiêu đề <span class="required">*</span></label>
            <input type="text" id="subject" name="subject" required>
          </div>

          <div class="form-group">
            <label for="message">Lời nhắn <span class="required">*</span></label>
            <textarea id="message" name="message" rows="5" required></textarea>
          </div>

          <button type="submit" class="btn btn--primary btn--large">Gửi</button>
        </form>
      </div>
    </div>
  </section>

  <!-- ========== FOOTER ========== -->
  <footer class="footer">
    <div class="container">
      <p>© 2024 Nguyễn Văn A. Tất cả quyền được bảo lưu.</p>
      <p>Thiết kế & Xây dựng bằng HTML, CSS, SCSS, & JavaScript</p>
    </div>
  </footer>

  <script src="main.js"></script>
</body>
</html>

Bước 2: SCSS (Clean & Organized)

/* ========== VARIABLES ========== */

$colors: (
  'primary': #3498db,
  'secondary': #2c3e50,
  'accent': #e74c3c,
  'success': #27ae60,
  'light': #f9f9f9,
  'dark': #1a1a1a,
  'text': #333,
  'text-light': #666,
  'border': #ddd,
);

$spacing-unit: 8px;
$spacing: (
  'xs': $spacing-unit,
  'sm': $spacing-unit * 2,
  'md': $spacing-unit * 3,
  'lg': $spacing-unit * 5,
  'xl': $spacing-unit * 10,
);

$font-size: (
  'sm': 14px,
  'base': 16px,
  'lg': 20px,
  'xl': 28px,
  '2xl': 36px,
  '3xl': 48px,
);

$border-radius: 8px;
$transition: 0.3s ease;

/* ========== MIXINS ========== */

@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

@mixin flex-between {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

@mixin button-base {
  padding: map-get($spacing, 'sm') map-get($spacing, 'md');
  border: none;
  border-radius: $border-radius;
  cursor: pointer;
  font-weight: 600;
  transition: all $transition;
  font-size: map-get($font-size, 'base');
  text-decoration: none;
  display: inline-block;

  &:hover {
    transform: translateY(-2px);
  }

  &:active {
    transform: translateY(0);
  }
}

@mixin button-color($bg-color) {
  background-color: $bg-color;
  color: white;

  &:hover {
    background-color: darken($bg-color, 10%);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  }
}

@mixin heading {
  font-family: 'Playfair Display', serif;
  font-weight: 700;
  line-height: 1.2;
  color: map-get($colors, 'dark');
}

@mixin section-padding {
  padding: map-get($spacing, 'xl') 0;
}

/* ========== RESET & BASE ========== */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  font-size: 16px;
  scroll-behavior: smooth;
}

body {
  font-family: 'Lato', sans-serif;
  line-height: 1.6;
  color: map-get($colors, 'text');
  background-color: map-get($colors, 'light');
}

a {
  color: map-get($colors, 'primary');
  text-decoration: none;
  transition: color $transition;

  &:hover {
    color: map-get($colors, 'secondary');
  }

  &:focus {
    outline: 2px solid map-get($colors, 'primary');
    outline-offset: 2px;
  }
}

/* ========== UTILITIES ========== */

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 map-get($spacing, 'md');
}

.section-title {
  @include heading;
  font-size: map-get($font-size, '2xl');
  margin-bottom: map-get($spacing, 'xl');
  text-align: center;
}

.btn {
  @include button-base;

  &--primary {
    @include button-color(map-get($colors, 'primary'));
  }

  &--secondary {
    background-color: transparent;
    border: 2px solid map-get($colors, 'primary');
    color: map-get($colors, 'primary');

    &:hover {
      background-color: map-get($colors, 'primary');
      color: white;
    }
  }

  &--large {
    padding: map-get($spacing, 'md') map-get($spacing, 'lg');
    font-size: map-get($font-size, 'lg');
  }
}

/* ========== HEADER ========== */

.header {
  position: sticky;
  top: 0;
  background-color: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  z-index: 1000;

  &__container {
    @include flex-between;
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 map-get($spacing, 'md');
    height: 70px;
  }

  &__logo {
    @include heading;
    font-size: map-get($font-size, 'xl');
  }

  &__logo-link {
    color: map-get($colors, 'primary');
  }

  &__nav {
    @include flex-center;
    gap: map-get($spacing, 'lg');

    @media (max-width: 768px) {
      display: none;
    }
  }

  &__nav-link {
    font-weight: 500;
    position: relative;

    &::after {
      content: '';
      position: absolute;
      bottom: -5px;
      left: 0;
      width: 0;
      height: 2px;
      background-color: map-get($colors, 'primary');
      transition: width $transition;
    }

    &:hover::after {
      width: 100%;
    }
  }

  &__menu-toggle {
    display: none;
    background: none;
    border: none;
    cursor: pointer;

    @media (max-width: 768px) {
      display: block;
    }
  }
}

/* ========== HERO SECTION ========== */

.hero {
  @include section-padding;
  background: linear-gradient(135deg, map-get($colors, 'primary') 0%, map-get($colors, 'secondary') 100%);
  color: white;
  display: grid;
  grid-template-columns: 1fr;
  gap: map-get($spacing, 'xl');
  align-items: center;

  @media (min-width: 769px) {
    grid-template-columns: 1fr 1fr;
  }

  &__content {
    animation: fadeInLeft 0.8s ease;
  }

  &__title {
    @include heading;
    font-size: map-get($font-size, '2xl');
    margin-bottom: map-get($spacing, 'md');
    color: white;

    @media (min-width: 769px) {
      font-size: map-get($font-size, '3xl');
    }
  }

  &__subtitle {
    font-size: map-get($font-size, 'lg');
    margin-bottom: map-get($spacing, 'md');
    opacity: 0.95;
  }

  &__description {
    font-size: map-get($font-size, 'base');
    margin-bottom: map-get($spacing, 'lg');
    opacity: 0.9;
  }

  &__cta {
    @include flex-center;
    gap: map-get($spacing, 'md');

    @media (max-width: 480px) {
      flex-direction: column;
    }
  }

  &__image {
    @include flex-center;
  }

  &__image-placeholder {
    width: 300px;
    height: 300px;
    background: rgba(255, 255, 255, 0.2);
    border-radius: 50%;
    animation: pulse 2s ease-in-out infinite;
  }
}

/* ========== ABOUT SECTION ========== */

.about {
  @include section-padding;
  background-color: white;

  &__content {
    display: grid;
    grid-template-columns: 1fr;
    gap: map-get($spacing, 'xl');

    @media (min-width: 769px) {
      grid-template-columns: 1fr 1fr;
    }
  }

  &__text {
    p {
      margin-bottom: map-get($spacing, 'md');
      color: map-get($colors, 'text-light');
      line-height: 1.8;
    }
  }

  &__subtitle {
    @include heading;
    font-size: map-get($font-size, 'lg');
    margin-bottom: map-get($spacing, 'lg');
  }
}

/* ========== TIMELINE ========== */

.timeline {
  &__item {
    display: flex;
    gap: map-get($spacing, 'lg');
    margin-bottom: map-get($spacing, 'xl');
    position: relative;

    &::before {
      content: '';
      position: absolute;
      left: 20px;
      top: 60px;
      width: 2px;
      height: calc(100% + 40px);
      background-color: map-get($colors, 'border');
    }

    &:last-child::before {
      display: none;
    }
  }

  &__dot {
    width: 40px;
    height: 40px;
    background-color: map-get($colors, 'primary');
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 5px;
    position: relative;
    z-index: 1;
  }

  &__content {
    flex: 1;
    background-color: map-get($colors, 'light');
    padding: map-get($spacing, 'md');
    border-radius: $border-radius;

    h4 {
      @include heading;
      font-size: map-get($font-size, 'lg');
      margin-bottom: map-get($spacing, 'sm');
    }

    p {
      color: map-get($colors, 'text-light');
    }
  }

  &__meta {
    font-weight: 600;
    color: map-get($colors, 'primary');
    font-size: map-get($font-size, 'sm');
  }
}

/* ========== PROJECTS SECTION ========== */

.projects {
  @include section-padding;
  background-color: map-get($colors, 'light');

  &__filters {
    @include flex-center;
    gap: map-get($spacing, 'md');
    margin-bottom: map-get($spacing, 'xl');
    flex-wrap: wrap;
  }

  &__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: map-get($spacing, 'lg');
  }
}

.filter-btn {
  padding: map-get($spacing, 'sm') map-get($spacing, 'md');
  border: 2px solid map-get($colors, 'border');
  background-color: white;
  border-radius: $border-radius;
  cursor: pointer;
  transition: all $transition;

  &:hover {
    border-color: map-get($colors, 'primary');
    color: map-get($colors, 'primary');
  }

  &--active {
    background-color: map-get($colors, 'primary');
    border-color: map-get($colors, 'primary');
    color: white;
  }
}

.project-card {
  background: white;
  border-radius: $border-radius;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: all $transition;
  animation: fadeInUp 0.6s ease;

  &:hover {
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
    transform: translateY(-5px);

    .project-card__image {
      transform: scale(1.05);
    }
  }

  &__image {
    width: 100%;
    height: 250px;
    overflow: hidden;
    background-color: #f0f0f0;
  }

  &__image-placeholder {
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  }

  &__content {
    padding: map-get($spacing, 'md');
  }

  &__title {
    @include heading;
    font-size: map-get($font-size, 'lg');
    margin-bottom: map-get($spacing, 'sm');
  }

  &__description {
    color: map-get($colors, 'text-light');
    margin-bottom: map-get($spacing, 'md');
    font-size: map-get($font-size, 'sm');
  }

  &__tags {
    display: flex;
    gap: map-get($spacing, 'sm');
    margin-bottom: map-get($spacing, 'md');
    flex-wrap: wrap;
  }

  &__link {
    color: map-get($colors, 'primary');
    font-weight: 600;
    font-size: map-get($font-size, 'sm');

    &:hover {
      color: map-get($colors, 'secondary');
    }
  }
}

.tag {
  display: inline-block;
  background-color: map-get($colors, 'light');
  color: map-get($colors, 'primary');
  padding: map-get($spacing, 'xs') map-get($spacing, 'sm');
  border-radius: 20px;
  font-size: map-get($font-size, 'sm');
  font-weight: 500;
}

/* ========== SKILLS SECTION ========== */

.skills {
  @include section-padding;
  background-color: white;

  &__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: map-get($spacing, 'lg');
  }
}

.skill-card {
  background-color: map-get($colors, 'light');
  padding: map-get($spacing, 'lg');
  border-radius: $border-radius;
  transition: all $transition;

  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    transform: translateY(-5px);
  }

  &__title {
    @include heading;
    font-size: map-get($font-size, 'lg');
    margin-bottom: map-get($spacing, 'md');
    color: map-get($colors, 'primary');
  }
}

.skill-list {
  list-style: none;

  li {
    padding: map-get($spacing, 'sm') 0;
    padding-left: map-get($spacing, 'md');
    position: relative;
    color: map-get($colors, 'text-light');

    &::before {
      content: '✓';
      position: absolute;
      left: 0;
      color: map-get($colors, 'success');
      font-weight: bold;
    }
  }
}

/* ========== CONTACT SECTION ========== */

.contact {
  @include section-padding;
  background-color: map-get($colors, 'light');

  &__intro {
    text-align: center;
    font-size: map-get($font-size, 'lg');
    margin-bottom: map-get($spacing, 'xl');
    color: map-get($colors, 'text-light');
    max-width: 600px;
    margin-left: auto;
    margin-right: auto;
  }

  &__wrapper {
    display: grid;
    grid-template-columns: 1fr;
    gap: map-get($spacing, 'xl');

    @media (min-width: 769px) {
      grid-template-columns: 1fr 1fr;
    }
  }

  &__info {
    h3 {
      @include heading;
      font-size: map-get($font-size, 'lg');
      margin-bottom: map-get($spacing, 'md');
    }
  }

  &__list {
    list-style: none;
    margin-bottom: map-get($spacing, 'xl');

    li {
      margin-bottom: map-get($spacing, 'md');
      color: map-get($colors, 'text-light');

      strong {
        color: map-get($colors, 'dark');
      }

      a {
        color: map-get($colors, 'primary');
      }
    }
  }

  &__form {
    display: flex;
    flex-direction: column;
    gap: map-get($spacing, 'md');
  }
}

.form-group {
  display: flex;
  flex-direction: column;

  label {
    font-weight: 600;
    margin-bottom: map-get($spacing, 'sm');
    color: map-get($colors, 'dark');
  }

  input,
  textarea {
    padding: map-get($spacing, 'sm') map-get($spacing, 'md');
    border: 1px solid map-get($colors, 'border');
    border-radius: $border-radius;
    font-family: inherit;
    font-size: map-get($font-size, 'base');
    transition: all $transition;

    &:focus {
      outline: none;
      border-color: map-get($colors, 'primary');
      box-shadow: 0 0 8px rgba(map-get($colors, 'primary'), 0.2);
    }
  }
}

.required {
  color: map-get($colors, 'accent');
}

.social-links {
  @include flex-center;
  gap: map-get($spacing, 'md');
  flex-wrap: wrap;
}

.social-link {
  padding: map-get($spacing, 'sm') map-get($spacing, 'md');
  background-color: map-get($colors, 'primary');
  color: white;
  border-radius: $border-radius;
  transition: all $transition;

  &:hover {
    background-color: map-get($colors, 'secondary');
    transform: translateY(-2px);
  }
}

/* ========== FOOTER ========== */

.footer {
  background-color: map-get($colors, 'dark');
  color: white;
  padding: map-get($spacing, 'lg') 0;
  text-align: center;
  font-size: map-get($font-size, 'sm');

  p {
    margin: map-get($spacing, 'sm') 0;
  }
}

/* ========== ANIMATIONS ========== */

@keyframes fadeInLeft {
  from {
    opacity: 0;
    transform: translateX(-30px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes pulse {
  0%, 100% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.05);
    opacity: 0.8;
  }
}

/* ========== RESPONSIVE ========== */

@media (max-width: 480px) {
  .section-title {
    font-size: map-get($font-size, 'xl');
  }

  .hero__title {
    font-size: map-get($font-size, 'xl');
  }

  .container {
    padding: 0 map-get($spacing, 'md');
  }
}

Bước 3: JavaScript (Interactivity)

// ========== SMOOTH SCROLL & ACTIVE NAV ==========

document.querySelectorAll('a[href^="#"]').forEach(link => {
  link.addEventListener('click', function(e) {
    e.preventDefault();
    const target = document.querySelector(this.getAttribute('href'));
    if (target) {
      target.scrollIntoView({ behavior: 'smooth' });
      // Close mobile menu
      document.getElementById('navMenu').style.display = 'none';
    }
  });
});

// ========== MOBILE MENU TOGGLE ==========

const menuToggle = document.getElementById('menuToggle');
const navMenu = document.getElementById('navMenu');

menuToggle.addEventListener('click', () => {
  navMenu.style.display = navMenu.style.display === 'none' ? 'block' : 'none';
});

// ========== PROJECT FILTER ==========

const filterBtns = document.querySelectorAll('.filter-btn');
const projectCards = document.querySelectorAll('.project-card');

filterBtns.forEach(btn => {
  btn.addEventListener('click', () => {
    // Update active button
    filterBtns.forEach(b => b.classList.remove('filter-btn--active'));
    btn.classList.add('filter-btn--active');

    // Filter projects
    const filterValue = btn.getAttribute('data-filter');
    projectCards.forEach(card => {
      if (filterValue === 'all' || card.getAttribute('data-category') === filterValue) {
        card.style.display = 'block';
        setTimeout(() => card.style.opacity = '1', 10);
      } else {
        card.style.opacity = '0';
        setTimeout(() => card.style.display = 'none', 300);
      }
    });
  });
});

// ========== FORM SUBMISSION ==========

const contactForm = document.getElementById('contactForm');
if (contactForm) {
  contactForm.addEventListener('submit', function(e) {
    e.preventDefault();

    const formData = new FormData(this);
    const data = Object.fromEntries(formData);

    // Simple validation
    if (!data.name || !data.email || !data.message) {
      alert('Vui lòng điền đầy đủ thông tin');
      return;
    }

    // Simulate form submission
    console.log('Form submitted:', data);
    alert('Cảm ơn bạn! Tôi sẽ liên hệ lại sớm.');
    this.reset();
  });
}

// ========== FADE IN ON SCROLL ==========

const observerOptions = {
  threshold: 0.1,
  rootMargin: '0px 0px -100px 0px'
};

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.style.opacity = '1';
      entry.target.style.transform = 'translateY(0)';
    }
  });
}, observerOptions);

// Observe all sections
document.querySelectorAll('section').forEach(section => {
  section.style.opacity = '0';
  section.style.transform = 'translateY(20px)';
  section.style.transition = 'all 0.6s ease';
  observer.observe(section);
});

Deploy & Launch

🚀 Deploy lên internet (miễn phí):

  • GitHub Pages: Miễn phí, dễ dùng, perfect cho portfolio static
  • Netlify: Drag & drop deploy, free SSL, preview URL
  • Vercel: Siêu nhanh, hỗ trợ serverless functions
  • Firebase Hosting: Google-backed, free tier generous

Bước deploy cơ bản:
1. Tạo GitHub repo
2. Push code lên GitHub
3. Kết nối repo với Netlify/Vercel
4. Auto deploy mỗi khi push code
5. Bám tên miền tuỳ chọn (`.com`, `.dev`…)

Checklist hoàn thành

✅ Checklist dự án hoàn chỉnh:

**HTML/Semantic:**
– [ ] Sử dụng proper semantic tags
– [ ] Meta tags (description, keywords)
– [ ] Alt text cho hình ảnh
– [ ] Tất cả link hoạt động

**CSS/SCSS:**
– [ ] Mobile-first responsive
– [ ] Tất cả breakpoints tested
– [ ] Consistent spacing, colors
– [ ] Smooth transitions/animations
– [ ] Accessibility (color contrast, focus states)

**JavaScript:**
– [ ] Smooth scroll navigation
– [ ] Mobile menu toggle
– [ ] Form validation
– [ ] Filter functionality (nếu có)

**Performance:**
– [ ] Compress images
– [ ] Minify CSS/JS
– [ ] Google PageSpeed Insights ≥ 90
– [ ] Load time < 3 seconds**Testing:** - [ ] Mobile (iPhone, Android) - [ ] Tablet (iPad) - [ ] Desktop (various resolutions) - [ ] All browsers (Chrome, Firefox, Safari, Edge) - [ ] Accessibility (axe DevTools)**SEO:** - [ ] Title tag - [ ] Meta description - [ ] Keywords - [ ] Open Graph tags - [ ] Sitemap.xml (tuỳ chọn)**Deployment:** - [ ] Domain name - [ ] HTTPS enabled - [ ] Live preview working - [ ] Analytics setup (tuỳ chọn)

Tóm tắt dự án

  • Complete Portfolio: 8 sections (header, hero, about, projects, skills, contact, footer)
  • Semantic HTML5: Proper structure, accessibility, SEO-friendly
  • SCSS: Clean, organized, DRY code với variables, mixins
  • Responsive Design: Mobile-first, 3+ breakpoints
  • Animations: Smooth transitions, fade-in effects
  • Interactivity: JavaScript functionality (filters, forms, smooth scroll)
  • Best Practices: Accessibility, performance, SEO
  • Professional: Production-ready, deployable live

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

🎉 Chúc mừng! Bạn đã hoàn thành toàn bộ 20 bài học HTML & CSS cơ bản!

✅ Bạn đã học được:

  • Bài 1-5: HTML cơ bản (tags, form, semantic)
  • Bài 6-11: CSS cơ bản (selectors, box model, layout, responsive)
  • Bài 12-18: CSS nâng cao (transform, animation, typography, accessibility)
  • Bài 19-20: SCSS & Dự án thực tế

📚 Tiếp theo, bạn có thể học:

  • 🔹 JavaScript: Tạo interactivity thực sự, không dùng code sẵn
  • 🔹 React/Vue: Frontend frameworks để build app chuyên nghiệp
  • 🔹 Backend (Node.js, Python, PHP): Xây dựng full-stack applications
  • 🔹 Database (SQL, MongoDB): Lưu trữ data
  • 🔹 DevOps & Deployment: Deploy & scale applications

🚀 Hành trình lập trình web của bạn vừa mới bắt đầu! Hãy tiếp tục học hỏi, xây dựng dự án, và tham gia cộng đồng. Chúc bạn thành công! 💪

Để lại bình luận

Email của bạn sẽ không được hiển thị.