Kurs HTML i CSS · Moduł 9: Nowoczesny CSS

Projekt - Nowoczesna strona portfolio z zaawansowanymi technikami CSS

14 min czytania ·
W tej lekcji6

Czas na stworzenie kompleksowego projektu, który pokaże wszystkie zaawansowane techniki CSS, które poznałeś w tym module. Stworzymy nowoczesną stronę portfolio wykorzystującą najnowsze możliwości CSS.

Opis projektu

Stworzysz responsywną stronę portfolio dla web developera, która będzie zawierać:

  • Sekcję hero z animacjami scroll-driven
  • Portfolio projektów z container queries
  • Sekcję umiejętności z CSS Grid i Subgrid
  • Formularz kontaktowy z zaawansowaną walidacją
  • Dark/light mode toggle
  • Zaawansowane animacje i mikrointerakcje

Wymagania funkcjonalne

1. Struktura strony

<!DOCTYPE html>
<html lang="pl">
<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">O mnie</a>
        </li>
        <li class="nav__item">
          <a href="#projects" class="nav__link">Projekty</a>
        </li>
        <li class="nav__item">
          <a href="#skills" class="nav__link">Umiejętności</a>
        </li>
        <li class="nav__item">
          <a href="#contact" class="nav__link">Kontakt</a>
        </li>
      </ul>
      <button class="theme-toggle" aria-label="Przełącz motyw">
        <span class="theme-toggle__icon">Ciemny</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">Zobacz moje projekty</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">O mnie</h2>
        <div class="about__content">
          <div class="about__text">
            <p>Jestem pasjonatem tworzenia nowoczesnych aplikacji webowych...</p>
          </div>
          <div class="about__image">
            <img src="profile.jpg" alt="Zdjęcie profilowe" loading="lazy">
          </div>
        </div>
      </div>
    </section>

    <section id="projects" class="projects">
      <div class="container">
        <h2 class="section__title">Moje projekty</h2>
        <div class="projects__grid">
          <article class="project-card" data-category="react">
            <div class="project-card__image">
              <img src="project1.jpg" alt="Projekt 1" loading="lazy">
            </div>
            <div class="project-card__content">
              <h3 class="project-card__title">E-commerce Dashboard</h3>
              <p class="project-card__description">Nowoczesny dashboard do zarządzania sklepem internetowym.</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>

          <!-- Więcej kart projektów... -->
        </div>
      </div>
    </section>

    <section id="skills" class="skills">
      <div class="container">
        <h2 class="section__title">Umiejętności</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>
              <!-- Więcej umiejętności... -->
            </div>
          </div>
        </div>
      </div>
    </section>

    <section id="contact" class="contact">
      <div class="container">
        <h2 class="section__title">Kontakt</h2>
        <form class="contact-form" novalidate>
          <div class="form-group">
            <label for="name" class="form-label">Imię</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">Wiadomość</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">Wyślij wiadomość</button>
        </form>
      </div>
    </section>
  </main>

  <footer class="footer">
    <div class="container">
      <p>&copy; 2024 Jan Kowalski. Wszystkie prawa zastrzeżone.</p>
    </div>
  </footer>

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

2. CSS z wykorzystaniem zaawansowanych technik

/* ===== 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);

  /* Użycie subgrid gdy będzie wspierane */
  @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; /* Mobilne menu do implementacji */
    }
  }

  &__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);
  }
}

/* ===== WEB COMPONENTS STYLING ===== */
.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 dla interakcji

// 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' ? 'Jasny' : 'Ciemny';
    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('To pole jest wymagane');
      } else if (rule === 'email' && value && !this.isValidEmail(value)) {
        errors.push('Podaj poprawny adres email');
      } else if (rule.startsWith('minLength:')) {
        const minLength = parseInt(rule.split(':')[1]);
        if (value && value.length < minLength) {
          errors.push(`Minimum ${minLength} znaków`);
        }
      }
    });

    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 = 'Wysyłanie...';
    submitButton.disabled = true;

    try {
      // Symulacja wysyłania
      await new Promise(resolve => setTimeout(resolve, 2000));

      // Success message
      this.showSuccessMessage();
      this.form.reset();
    } catch (error) {
      alert('Wystąpił błąd podczas wysyłania wiadomości');
    } finally {
      submitButton.textContent = originalText;
      submitButton.disabled = false;
    }
  }

  showSuccessMessage() {
    const message = document.createElement('div');
    message.className = 'success-message';
    message.textContent = 'Wiadomość została wysłana pomyślnie!';
    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);
});

Wymagania techniczne

1. Użyj wszystkich poznanych technik

  • Container Queries - responsywna siatka projektów
  • CSS Nesting - organizacja kodu CSS
  • CSS Variables - system design tokens
  • Cascade Layers - zarządzanie kaskadą
  • Web Components styling - komponenty UI
  • WCAG compliance - dostępność
  • BEM methodology - nazewnictwo klas
  • Stylelint - jakość kodu CSS
  • Cross-browser support - kompatybilność

2. Performance optimization

/* Critical CSS inlining */
/* Tylko najważniejsze style w <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

  • Responsywność na wszystkich urządzeniach
  • Dostępność z klawiaturą
  • Screen reader compatibility
  • Performance (Lighthouse score > 90)
  • Cross-browser testing (Chrome, Firefox, Safari, Edge)
  • Dark/light mode functionality
  • Form validation
  • Smooth animations

Dodatkowe wyzwania

Poziom zaawansowany

  1. View Transitions API - dodaj płynne przejścia między sekcjami
  2. CSS Scroll Snap - snap scrolling między sekcjami
  3. CSS @property - animowane custom properties
  4. Intersection Observer - lazy loading i animacje
  5. Service Worker - caching dla offline functionality

Poziom ekspercki

  1. CSS Houdini Paint API - custom gradients
  2. CSS Parser API - dynamiczne style
  3. Web Animations API - zaawansowane animacje JS
  4. CSS Typed OM - programatyczne manipulowanie CSS

Dokumentacja projektu

README.md

# Portfolio Website - Nowoczesne techniki CSS

Responsywna strona portfolio wykorzystująca najnowsze techniki CSS.

## Użyte technologie

- HTML5 Semantic markup
- CSS3 z najnowszymi funkcjonalnościami
- Vanilla JavaScript ES6+
- Container Queries
- CSS Nesting
- Cascade Layers
- CSS Grid & Subgrid

## Instalacja

1. Sklonuj repozytorium
2. Otwórz index.html w przeglądarce
3. Uruchom lokalny serwer dla pełnej funkcjonalności

## Funkcjonalności

- Responsywny design z 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

Podsumowanie projektu

Ten projekt demonstruje praktyczne zastosowanie wszystkich zaawansowanych technik CSS poznanych w module:

  1. Architektura CSS - Cascade Layers, CSS Nesting, BEM
  2. Layout nowoczesny - Container Queries, CSS Grid, Subgrid
  3. Stylowanie zaawansowane - CSS Variables, custom properties
  4. Interakcje - animacje, hover effects, scroll-driven animations
  5. Dostępność - WCAG compliance, focus management
  6. Performance - CSS containment, lazy loading
  7. Cross-browser - progressive enhancement, fallbacks

Projekt można rozbudować o dodatkowe funkcjonalności i jest doskonałą bazą do dalszego rozwoju umiejętności CSS.

Kod do tej lekcji: index.html
<!DOCTYPE html>
<html lang="pl">
<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>Karta adaptacyjna</h2>
            <p>Uzywa container queries jesli dostepne.</p>
        </div>
    </div>
</body>
</html>

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Za pomocą czego w projekcie portfolio definiuje się design tokens, takie jak kolory i odstępy?

  2. 2. W jaki sposób w projekcie realizowany jest tryb ciemny/jasny w samym CSS?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Układanie w pionie

    Ułóż tokeny odstępów projektu (--space-*) od najmniejszej do największej wartości:

Przydatne artykuły