Kurs HTML i CSS · Moduł 9: Nowoczesny CSS
Projekt - Nowoczesna strona portfolio z zaawansowanymi technikami CSS
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>© 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
- View Transitions API - dodaj płynne przejścia między sekcjami
- CSS Scroll Snap - snap scrolling między sekcjami
- CSS @property - animowane custom properties
- Intersection Observer - lazy loading i animacje
- Service Worker - caching dla offline functionality
Poziom ekspercki
- CSS Houdini Paint API - custom gradients
- CSS Parser API - dynamiczne style
- Web Animations API - zaawansowane animacje JS
- 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.1Podsumowanie projektu
Ten projekt demonstruje praktyczne zastosowanie wszystkich zaawansowanych technik CSS poznanych w module:
- Architektura CSS - Cascade Layers, CSS Nesting, BEM
- Layout nowoczesny - Container Queries, CSS Grid, Subgrid
- Stylowanie zaawansowane - CSS Variables, custom properties
- Interakcje - animacje, hover effects, scroll-driven animations
- Dostępność - WCAG compliance, focus management
- Performance - CSS containment, lazy loading
- 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. Za pomocą czego w projekcie portfolio definiuje się design tokens, takie jak kolory i odstępy?
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: