HTML and CSS course ยท Module 9: Modern CSS

Project - Modern Portfolio Page with Advanced CSS Techniques

14 min read ยท
In this lesson6

It's time to create a comprehensive project that will showcase all the advanced CSS techniques you've learned in this module. We'll create a modern portfolio page utilizing the latest CSS capabilities.

Project Description

You'll create a responsive portfolio page for a web developer that will include:

  • A hero section with scroll-driven animations
  • Project portfolio with container queries
  • A skills section with CSS Grid and Subgrid
  • A contact form with advanced validation
  • Dark/light mode toggle
  • Advanced animations and micro-interactions

Functional Requirements

1. Page Structure

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Jan Kowalski - Web Developer Portfolio</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <header class="header">
    <nav class="nav">
      <div class="nav__brand">
        <a href="#" class="nav__logo">JK</a>
      </div>
      <ul class="nav__menu">
        <li class="nav__item">
          <a href="#home" class="nav__link">Home</a>
        </li>
        <li class="nav__item">
          <a href="#about" class="nav__link">About</a>
        </li>
        <li class="nav__item">
          <a href="#projects" class="nav__link">Projects</a>
        </li>
        <li class="nav__item">
          <a href="#skills" class="nav__link">Skills</a>
        </li>
        <li class="nav__item">
          <a href="#contact" class="nav__link">Contact</a>
        </li>
      </ul>
      <button class="theme-toggle" aria-label="Toggle theme">
        <span class="theme-toggle__icon">Dark</span>
      </button>
    </nav>
  </header>

  <main>
    <section id="home" class="hero">
      <div class="hero__content">
        <h1 class="hero__title">Jan Kowalski</h1>
        <p class="hero__subtitle">Frontend Developer & UI/UX Designer</p>
        <a href="#projects" class="hero__cta">View My Projects</a>
      </div>
      <div class="hero__animation">
        <div class="floating-shapes">
          <div class="shape shape--1"></div>
          <div class="shape shape--2"></div>
          <div class="shape shape--3"></div>
        </div>
      </div>
    </section>

    <section id="about" class="about">
      <div class="container">
        <h2 class="section__title">About Me</h2>
        <div class="about__content">
          <div class="about__text">
            <p>I am passionate about creating modern web applications...</p>
          </div>
          <div class="about__image">
            <img src="profile.jpg" alt="Profile photo" loading="lazy">
          </div>
        </div>
      </div>
    </section>

    <section id="projects" class="projects">
      <div class="container">
        <h2 class="section__title">My Projects</h2>
        <div class="projects__grid">
          <article class="project-card" data-category="react">
            <div class="project-card__image">
              <img src="project1.jpg" alt="Project 1" loading="lazy">
            </div>
            <div class="project-card__content">
              <h3 class="project-card__title">E-commerce Dashboard</h3>
              <p class="project-card__description">A modern dashboard for managing an online store.</p>
              <div class="project-card__tech">
                <span class="tech-tag">React</span>
                <span class="tech-tag">TypeScript</span>
                <span class="tech-tag">Tailwind</span>
              </div>
              <div class="project-card__actions">
                <a href="#" class="btn btn--primary">Live Demo</a>
                <a href="#" class="btn btn--secondary">GitHub</a>
              </div>
            </div>
          </article>

          <!-- More project cards... -->
        </div>
      </div>
    </section>

    <section id="skills" class="skills">
      <div class="container">
        <h2 class="section__title">Skills</h2>
        <div class="skills__grid">
          <div class="skill-category">
            <h3 class="skill-category__title">Frontend</h3>
            <div class="skill-category__items">
              <div class="skill-item">
                <span class="skill-item__name">HTML/CSS</span>
                <div class="skill-item__bar">
                  <div class="skill-item__progress" data-level="95"></div>
                </div>
              </div>
              <!-- More skills... -->
            </div>
          </div>
        </div>
      </div>
    </section>

    <section id="contact" class="contact">
      <div class="container">
        <h2 class="section__title">Contact</h2>
        <form class="contact-form" novalidate>
          <div class="form-group">
            <label for="name" class="form-label">Name</label>
            <input type="text" id="name" name="name" class="form-input" required>
            <span class="form-error" id="name-error"></span>
          </div>
          <div class="form-group">
            <label for="email" class="form-label">Email</label>
            <input type="email" id="email" name="email" class="form-input" required>
            <span class="form-error" id="email-error"></span>
          </div>
          <div class="form-group">
            <label for="message" class="form-label">Message</label>
            <textarea id="message" name="message" class="form-textarea" required></textarea>
            <span class="form-error" id="message-error"></span>
          </div>
          <button type="submit" class="btn btn--primary btn--full">Send Message</button>
        </form>
      </div>
    </section>
  </main>

  <footer class="footer">
    <div class="container">
      <p>&copy; 2024 Jan Kowalski. All rights reserved.</p>
    </div>
  </footer>

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

2. CSS Using Advanced Techniques

/* ===== CSS VARIABLES & DESIGN TOKENS ===== */
:root {
  /* Colors - Light theme */
  --color-primary: #667eea;
  --color-secondary: #764ba2;
  --color-accent: #f093fb;
  --color-text: #2d3748;
  --color-text-muted: #718096;
  --color-bg: #ffffff;
  --color-surface: #f7fafc;
  --color-border: #e2e8f0;

  /* Typography */
  --font-primary: 'Inter', system-ui, sans-serif;
  --font-heading: 'Poppins', sans-serif;

  /* Spacing */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;
  --space-2xl: 3rem;
  --space-3xl: 4rem;

  /* Layout */
  --container-max-width: 1200px;
  --border-radius: 0.5rem;
  --border-radius-lg: 1rem;

  /* Shadows */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);

  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-normal: 250ms ease;
  --transition-slow: 350ms ease;
}

/* Dark theme variables */
[data-theme="dark"] {
  --color-text: #f7fafc;
  --color-text-muted: #a0aec0;
  --color-bg: #1a202c;
  --color-surface: #2d3748;
  --color-border: #4a5568;
}

/* ===== CASCADE LAYERS ===== */
@layer reset, base, components, utilities;

@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }

  html {
    scroll-behavior: smooth;
  }

  body {
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
  }

  img, picture, video, canvas, svg {
    display: block;
    max-width: 100%;
  }

  input, button, textarea, select {
    font: inherit;
  }
}

@layer base {
  body {
    font-family: var(--font-primary);
    color: var(--color-text);
    background: var(--color-bg);
    transition: background-color var(--transition-normal),
                color var(--transition-normal);
  }

  h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-heading);
    font-weight: 600;
    line-height: 1.2;
  }

  a {
    color: var(--color-primary);
    text-decoration: none;
    transition: color var(--transition-fast);

    &:hover {
      color: var(--color-secondary);
    }
  }
}

/* ===== CONTAINER QUERIES ===== */
.projects {
  container-type: inline-size;
  container-name: projects;
}

.project-card {
  background: var(--color-surface);
  border-radius: var(--border-radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  transition: transform var(--transition-normal),
              box-shadow var(--transition-normal);

  &:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-lg);
  }
}

@container projects (min-width: 768px) {
  .projects__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
    gap: var(--space-xl);
  }

  .project-card {
    display: grid;
    grid-template-rows: 200px 1fr;
  }
}

@container projects (min-width: 1024px) {
  .projects__grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ===== CSS GRID WITH SUBGRID ===== */
.skills__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--space-xl);
}

.skill-category {
  display: grid;
  grid-template-rows: auto 1fr;
  background: var(--color-surface);
  border-radius: var(--border-radius-lg);
  padding: var(--space-lg);

  /* Use subgrid when supported */
  @supports (grid-template-rows: subgrid) {
    grid-template-rows: subgrid;
  }
}

/* ===== CSS NESTING ===== */
.nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-md) var(--space-lg);
  background: var(--color-surface);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-border);

  &__brand {
    font-weight: bold;
    font-size: 1.5rem;
  }

  &__logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
    color: white;
    border-radius: 50%;
    font-weight: bold;
  }

  &__menu {
    display: flex;
    list-style: none;
    gap: var(--space-lg);

    @media (max-width: 768px) {
      display: none; /* Mobile menu to be implemented */
    }
  }

  &__link {
    position: relative;
    font-weight: 500;

    &::after {
      content: '';
      position: absolute;
      bottom: -4px;
      left: 0;
      width: 0;
      height: 2px;
      background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
      transition: width var(--transition-normal);
    }

    &:hover::after,
    &.active::after {
      width: 100%;
    }
  }
}

/* ===== SCROLL-DRIVEN ANIMATIONS ===== */
@supports (animation-timeline: scroll()) {
  .hero__title {
    animation: hero-fade-in linear;
    animation-timeline: view();
    animation-range: entry 0% cover 40%;
  }

  .section__title {
    animation: slide-up linear;
    animation-timeline: view();
    animation-range: entry 20% cover 50%;
  }

  @keyframes hero-fade-in {
    from {
      opacity: 0;
      transform: translateY(50px) scale(0.9);
    }
    to {
      opacity: 1;
      transform: translateY(0) scale(1);
    }
  }

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

/* ===== MODERN CSS FEATURES ===== */
.hero {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  padding: var(--space-2xl);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--color-primary) 10%, transparent) 0%,
    color-mix(in srgb, var(--color-secondary) 10%, transparent) 100%
  );
  position: relative;
  overflow: hidden;

  @media (max-width: 768px) {
    grid-template-columns: 1fr;
    text-align: center;
    padding: var(--space-xl) var(--space-lg);
  }
}

.floating-shapes {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.shape {
  position: absolute;
  border-radius: 50%;
  background: linear-gradient(45deg, var(--color-primary), var(--color-accent));
  opacity: 0.1;
  animation: float 6s ease-in-out infinite;

  &--1 {
    width: 100px;
    height: 100px;
    top: 20%;
    left: 10%;
    animation-delay: 0s;
  }

  &--2 {
    width: 150px;
    height: 150px;
    top: 60%;
    right: 20%;
    animation-delay: 2s;
  }

  &--3 {
    width: 80px;
    height: 80px;
    bottom: 20%;
    left: 60%;
    animation-delay: 4s;
  }
}

@keyframes float {
  0%, 100% {
    transform: translateY(0) rotate(0deg);
  }
  50% {
    transform: translateY(-20px) rotate(180deg);
  }
}

/* ===== SKILL ITEMS ===== */
.skill-item {
  margin-bottom: var(--space-md);

  &__name {
    display: block;
    margin-bottom: var(--space-xs);
    font-weight: 500;
  }

  &__bar {
    width: 100%;
    height: 8px;
    background: var(--color-border);
    border-radius: 4px;
    overflow: hidden;
  }

  &__progress {
    height: 100%;
    background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
    border-radius: 4px;
    transform-origin: left;
    animation: progress-fill 2s ease-out;

    @supports (animation-timeline: view()) {
      animation: progress-fill linear;
      animation-timeline: view();
      animation-range: entry 50% cover 80%;
    }
  }
}

@keyframes progress-fill {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

/* ===== THEME TOGGLE ===== */
.theme-toggle {
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  border-radius: 50%;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all var(--transition-normal);

  &:hover {
    transform: scale(1.1);
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: white;
  }

  &__icon {
    font-size: 1.2rem;
    transition: transform var(--transition-normal);
  }

  &[data-theme="dark"] &__icon {
    transform: rotate(180deg);
  }
}

/* ===== FORM STYLING ===== */
.contact-form {
  max-width: 600px;
  margin: 0 auto;
  padding: var(--space-2xl);
  background: var(--color-surface);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-lg);
}

.form-group {
  margin-bottom: var(--space-lg);
  position: relative;
}

.form-label {
  display: block;
  margin-bottom: var(--space-xs);
  font-weight: 500;
  color: var(--color-text);
}

.form-input,
.form-textarea {
  width: 100%;
  padding: var(--space-md);
  border: 2px solid var(--color-border);
  border-radius: var(--border-radius);
  background: var(--color-bg);
  color: var(--color-text);
  transition: border-color var(--transition-fast);

  &:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 20%, transparent);
  }

  &:invalid:not(:placeholder-shown) {
    border-color: #ef4444;
  }

  &:valid:not(:placeholder-shown) {
    border-color: #10b981;
  }
}

.form-textarea {
  min-height: 120px;
  resize: vertical;
}

.form-error {
  position: absolute;
  bottom: -20px;
  left: 0;
  font-size: 0.875rem;
  color: #ef4444;
  opacity: 0;
  transition: opacity var(--transition-fast);

  &.visible {
    opacity: 1;
  }
}

/* ===== BUTTONS ===== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-md) var(--space-lg);
  border: none;
  border-radius: var(--border-radius);
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: all var(--transition-normal);
  position: relative;
  overflow: hidden;

  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
    transition: left var(--transition-slow);
  }

  &:hover::before {
    left: 100%;
  }

  &--primary {
    background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
    color: white;

    &:hover {
      transform: translateY(-2px);
      box-shadow: var(--shadow-lg);
    }
  }

  &--secondary {
    background: transparent;
    color: var(--color-primary);
    border: 2px solid var(--color-primary);

    &:hover {
      background: var(--color-primary);
      color: white;
    }
  }

  &--full {
    width: 100%;
  }
}

/* ===== ACCESSIBILITY ===== */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
  }
}

/* Focus styles */
:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* Skip link */
.skip-link {
  position: absolute;
  top: -40px;
  left: 6px;
  background: var(--color-primary);
  color: white;
  padding: 8px;
  text-decoration: none;
  border-radius: 4px;
  z-index: 1000;

  &:focus {
    top: 6px;
  }
}

/* ===== UTILITY CLASSES ===== */
@layer utilities {
  .container {
    max-width: var(--container-max-width);
    margin: 0 auto;
    padding: 0 var(--space-lg);
  }

  .section__title {
    font-size: 2.5rem;
    text-align: center;
    margin-bottom: var(--space-2xl);
    background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
  }

  .visually-hidden {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
  }

  .text-gradient {
    background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
  }
}

3. JavaScript for Interactions

// Theme toggle functionality
class ThemeManager {
  constructor() {
    this.theme = localStorage.getItem('theme') || 'light';
    this.toggle = document.querySelector('.theme-toggle');
    this.init();
  }

  init() {
    this.setTheme(this.theme);
    this.toggle.addEventListener('click', () => this.toggleTheme());
  }

  setTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme);
    this.toggle.querySelector('.theme-toggle__icon').textContent =
      theme === 'dark' ? 'Light' : 'Dark';
    localStorage.setItem('theme', theme);
    this.theme = theme;
  }

  toggleTheme() {
    const newTheme = this.theme === 'light' ? 'dark' : 'light';
    this.setTheme(newTheme);
  }
}

// Form validation
class FormValidator {
  constructor(form) {
    this.form = form;
    this.fields = {
      name: {
        element: form.querySelector('#name'),
        rules: ['required', 'minLength:2']
      },
      email: {
        element: form.querySelector('#email'),
        rules: ['required', 'email']
      },
      message: {
        element: form.querySelector('#message'),
        rules: ['required', 'minLength:10']
      }
    };
    this.init();
  }

  init() {
    Object.values(this.fields).forEach(field => {
      field.element.addEventListener('blur', () => this.validateField(field));
      field.element.addEventListener('input', () => this.clearError(field));
    });

    this.form.addEventListener('submit', (e) => this.handleSubmit(e));
  }

  validateField(field) {
    const value = field.element.value.trim();
    const errors = [];

    field.rules.forEach(rule => {
      if (rule === 'required' && !value) {
        errors.push('This field is required');
      } else if (rule === 'email' && value && !this.isValidEmail(value)) {
        errors.push('Please enter a valid email address');
      } else if (rule.startsWith('minLength:')) {
        const minLength = parseInt(rule.split(':')[1]);
        if (value && value.length < minLength) {
          errors.push(`Minimum ${minLength} characters`);
        }
      }
    });

    this.showError(field, errors[0]);
    return errors.length === 0;
  }

  showError(field, message) {
    const errorElement = document.querySelector(`#${field.element.name}-error`);
    if (message) {
      errorElement.textContent = message;
      errorElement.classList.add('visible');
      field.element.setAttribute('aria-invalid', 'true');
    } else {
      this.clearError(field);
    }
  }

  clearError(field) {
    const errorElement = document.querySelector(`#${field.element.name}-error`);
    errorElement.classList.remove('visible');
    field.element.removeAttribute('aria-invalid');
  }

  isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  handleSubmit(e) {
    e.preventDefault();

    const isValid = Object.values(this.fields)
      .map(field => this.validateField(field))
      .every(valid => valid);

    if (isValid) {
      this.submitForm();
    }
  }

  async submitForm() {
    const submitButton = this.form.querySelector('button[type="submit"]');
    const originalText = submitButton.textContent;

    submitButton.textContent = 'Sending...';
    submitButton.disabled = true;

    try {
      // Simulating sending
      await new Promise(resolve => setTimeout(resolve, 2000));

      // Success message
      this.showSuccessMessage();
      this.form.reset();
    } catch (error) {
      alert('An error occurred while sending the message');
    } finally {
      submitButton.textContent = originalText;
      submitButton.disabled = false;
    }
  }

  showSuccessMessage() {
    const message = document.createElement('div');
    message.className = 'success-message';
    message.textContent = 'Message sent successfully!';
    message.style.cssText = `
      position: fixed;
      top: 20px;
      right: 20px;
      background: #10b981;
      color: white;
      padding: 1rem 1.5rem;
      border-radius: 0.5rem;
      box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
      z-index: 1000;
      animation: slideIn 0.3s ease;
    `;

    document.body.appendChild(message);

    setTimeout(() => {
      message.style.animation = 'slideOut 0.3s ease';
      setTimeout(() => message.remove(), 300);
    }, 3000);
  }
}

// Smooth scrolling for navigation
class SmoothScrolling {
  constructor() {
    this.init();
  }

  init() {
    document.querySelectorAll('a[href^="#"]').forEach(link => {
      link.addEventListener('click', (e) => {
        e.preventDefault();
        const target = document.querySelector(link.getAttribute('href'));
        if (target) {
          target.scrollIntoView({
            behavior: 'smooth',
            block: 'start'
          });
        }
      });
    });
  }
}

// Skills animation
class SkillsAnimation {
  constructor() {
    this.init();
  }

  init() {
    if ('IntersectionObserver' in window) {
      this.observeSkills();
    } else {
      this.animateSkillsDirectly();
    }
  }

  observeSkills() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          this.animateSkill(entry.target);
          observer.unobserve(entry.target);
        }
      });
    }, { threshold: 0.5 });

    document.querySelectorAll('.skill-item__progress').forEach(skill => {
      observer.observe(skill);
    });
  }

  animateSkill(skillElement) {
    const level = skillElement.dataset.level;
    skillElement.style.transform = `scaleX(${level / 100})`;
  }

  animateSkillsDirectly() {
    document.querySelectorAll('.skill-item__progress').forEach(skill => {
      setTimeout(() => this.animateSkill(skill), 500);
    });
  }
}

// Initialize everything when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
  new ThemeManager();
  new SmoothScrolling();
  new SkillsAnimation();

  const contactForm = document.querySelector('.contact-form');
  if (contactForm) {
    new FormValidator(contactForm);
  }

  // Add success animations
  const style = document.createElement('style');
  style.textContent = `
    @keyframes slideIn {
      from { transform: translateX(100%); opacity: 0; }
      to { transform: translateX(0); opacity: 1; }
    }
    @keyframes slideOut {
      from { transform: translateX(0); opacity: 1; }
      to { transform: translateX(100%); opacity: 0; }
    }
  `;
  document.head.appendChild(style);
});

Technical Requirements

1. Use All Learned Techniques

  • Container Queries - responsive project grid
  • CSS Nesting - CSS code organization
  • CSS Variables - design token system
  • Cascade Layers - cascade management
  • Web Components styling - UI components
  • WCAG compliance - accessibility
  • BEM methodology - class naming
  • Stylelint - CSS code quality
  • Cross-browser support - compatibility

2. Performance Optimization

/* Critical CSS inlining */
/* Only the most important styles in <head> */

/* Lazy loading images */
img[loading="lazy"] {
  content-visibility: auto;
  contain-intrinsic-size: 200px 150px;
}

/* CSS containment */
.project-card {
  contain: layout style paint;
}

/* Preload important resources */
/* <link rel="preload" href="fonts/inter.woff2" as="font" type="font/woff2" crossorigin> */

3. Testing Checklist

  • Responsiveness on all devices
  • Keyboard accessibility
  • Screen reader compatibility
  • Performance (Lighthouse score > 90)
  • Cross-browser testing (Chrome, Firefox, Safari, Edge)
  • Dark/light mode functionality
  • Form validation
  • Smooth animations

Additional Challenges

Advanced Level

  1. View Transitions API - add smooth transitions between sections
  2. CSS Scroll Snap - snap scrolling between sections
  3. CSS @property - animated custom properties
  4. Intersection Observer - lazy loading and animations
  5. Service Worker - caching for offline functionality

Expert Level

  1. CSS Houdini Paint API - custom gradients
  2. CSS Parser API - dynamic styles
  3. Web Animations API - advanced JS animations
  4. CSS Typed OM - programmatic CSS manipulation

Project Documentation

README.md

# Portfolio Website - Modern CSS Techniques

Responsive portfolio page utilizing the latest CSS techniques.

## Technologies Used

- HTML5 Semantic markup
- CSS3 with latest features
- Vanilla JavaScript ES6+
- Container Queries
- CSS Nesting
- Cascade Layers
- CSS Grid & Subgrid

## Installation

1. Clone the repository
2. Open index.html in a browser
3. Run a local server for full functionality

## Features

- Responsive design with Container Queries
- Dark/Light mode toggle
- Smooth scroll animations
- Form validation
- WCAG 2.1 AA compliance
- Cross-browser compatibility

## Performance

- Lighthouse Performance: 95+
- First Contentful Paint: < 1.5s
- Largest Contentful Paint: < 2.5s
- Cumulative Layout Shift: < 0.1

Project Summary

This project demonstrates the practical application of all advanced CSS techniques learned in this module:

  1. CSS Architecture - Cascade Layers, CSS Nesting, BEM
  2. Modern Layout - Container Queries, CSS Grid, Subgrid
  3. Advanced Styling - CSS Variables, custom properties
  4. Interactions - animations, hover effects, scroll-driven animations
  5. Accessibility - WCAG compliance, focus management
  6. Performance - CSS containment, lazy loading
  7. Cross-browser - progressive enhancement, fallbacks

The project can be expanded with additional features and serves as an excellent foundation for further CSS skill development.

Code for this lesson: index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>@supports</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>@supports - Progressive Enhancement</h1>
    <div class="grid-demo">
        <div class="box">Element 1</div>
        <div class="box">Element 2</div>
        <div class="box">Element 3</div>
        <div class="box">Element 4</div>
        <div class="box">Element 5</div>
        <div class="box">Element 6</div>
    </div>
    <div class="container-demo">
        <div class="card">
            <h2>Adaptive card</h2>
            <p>Uses container queries if available.</p>
        </div>
    </div>
</body>
</html>

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What is used in the portfolio project to define design tokens such as colors and spacing?

  2. 2. How is the dark/light theme implemented in the CSS itself in the project?

These are 2 of 3 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Vertical ordering

    Arrange the project's spacing tokens (--space-*) from the smallest to the largest value:

Useful articles