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.
Stworzysz responsywną stronę portfolio dla web developera, która będzie zawierać:
1<!DOCTYPE html>
2<html lang="pl">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Jan Kowalski - Web Developer Portfolio</title>
7 <link rel="stylesheet" href="styles.css">
8</head>
9<body>
10 <header class="header">
11 <nav class="nav">
12 <div class="nav__brand">
13 <a href="#" class="nav__logo">JK</a>
14 </div>
15 <ul class="nav__menu">
16 <li class="nav__item">
17 <a href="#home" class="nav__link">Home</a>
18 </li>
19 <li class="nav__item">
20 <a href="#about" class="nav__link">O mnie</a>
21 </li>
22 <li class="nav__item">
23 <a href="#projects" class="nav__link">Projekty</a>
24 </li>
25 <li class="nav__item">
26 <a href="#skills" class="nav__link">Umiejętności</a>
27 </li>
28 <li class="nav__item">
29 <a href="#contact" class="nav__link">Kontakt</a>
30 </li>
31 </ul>
32 <button class="theme-toggle" aria-label="Przełącz motyw">
33 <span class="theme-toggle__icon">🌙</span>
34 </button>
35 </nav>
36 </header>
37
38 <main>
39 <section id="home" class="hero">
40 <div class="hero__content">
41 <h1 class="hero__title">Jan Kowalski</h1>
42 <p class="hero__subtitle">Frontend Developer & UI/UX Designer</p>
43 <a href="#projects" class="hero__cta">Zobacz moje projekty</a>
44 </div>
45 <div class="hero__animation">
46 <div class="floating-shapes">
47 <div class="shape shape--1"></div>
48 <div class="shape shape--2"></div>
49 <div class="shape shape--3"></div>
50 </div>
51 </div>
52 </section>
53
54 <section id="about" class="about">
55 <div class="container">
56 <h2 class="section__title">O mnie</h2>
57 <div class="about__content">
58 <div class="about__text">
59 <p>Jestem pasjonatem tworzenia nowoczesnych aplikacji webowych...</p>
60 </div>
61 <div class="about__image">
62 <img src="profile.jpg" alt="Zdjęcie profilowe" loading="lazy">
63 </div>
64 </div>
65 </div>
66 </section>
67
68 <section id="projects" class="projects">
69 <div class="container">
70 <h2 class="section__title">Moje projekty</h2>
71 <div class="projects__grid">
72 <article class="project-card" data-category="react">
73 <div class="project-card__image">
74 <img src="project1.jpg" alt="Projekt 1" loading="lazy">
75 </div>
76 <div class="project-card__content">
77 <h3 class="project-card__title">E-commerce Dashboard</h3>
78 <p class="project-card__description">Nowoczesny dashboard do zarządzania sklepem internetowym.</p>
79 <div class="project-card__tech">
80 <span class="tech-tag">React</span>
81 <span class="tech-tag">TypeScript</span>
82 <span class="tech-tag">Tailwind</span>
83 </div>
84 <div class="project-card__actions">
85 <a href="#" class="btn btn--primary">Live Demo</a>
86 <a href="#" class="btn btn--secondary">GitHub</a>
87 </div>
88 </div>
89 </article>
90
91 <!-- Więcej kart projektów... -->
92 </div>
93 </div>
94 </section>
95
96 <section id="skills" class="skills">
97 <div class="container">
98 <h2 class="section__title">Umiejętności</h2>
99 <div class="skills__grid">
100 <div class="skill-category">
101 <h3 class="skill-category__title">Frontend</h3>
102 <div class="skill-category__items">
103 <div class="skill-item">
104 <span class="skill-item__name">HTML/CSS</span>
105 <div class="skill-item__bar">
106 <div class="skill-item__progress" data-level="95"></div>
107 </div>
108 </div>
109 <!-- Więcej umiejętności... -->
110 </div>
111 </div>
112 </div>
113 </div>
114 </section>
115
116 <section id="contact" class="contact">
117 <div class="container">
118 <h2 class="section__title">Kontakt</h2>
119 <form class="contact-form" novalidate>
120 <div class="form-group">
121 <label for="name" class="form-label">Imię</label>
122 <input type="text" id="name" name="name" class="form-input" required>
123 <span class="form-error" id="name-error"></span>
124 </div>
125 <div class="form-group">
126 <label for="email" class="form-label">Email</label>
127 <input type="email" id="email" name="email" class="form-input" required>
128 <span class="form-error" id="email-error"></span>
129 </div>
130 <div class="form-group">
131 <label for="message" class="form-label">Wiadomość</label>
132 <textarea id="message" name="message" class="form-textarea" required></textarea>
133 <span class="form-error" id="message-error"></span>
134 </div>
135 <button type="submit" class="btn btn--primary btn--full">Wyślij wiadomość</button>
136 </form>
137 </div>
138 </section>
139 </main>
140
141 <footer class="footer">
142 <div class="container">
143 <p>© 2024 Jan Kowalski. Wszystkie prawa zastrzeżone.</p>
144 </div>
145 </footer>
146
147 <script src="script.js"></script>
148</body>
149</html>1/* ===== CSS VARIABLES & DESIGN TOKENS ===== */
2:root {
3 /* Colors - Light theme */
4 --color-primary: #667eea;
5 --color-secondary: #764ba2;
6 --color-accent: #f093fb;
7 --color-text: #2d3748;
8 --color-text-muted: #718096;
9 --color-bg: #ffffff;
10 --color-surface: #f7fafc;
11 --color-border: #e2e8f0;
12
13 /* Typography */
14 --font-primary: 'Inter', system-ui, sans-serif;
15 --font-heading: 'Poppins', sans-serif;
16
17 /* Spacing */
18 --space-xs: 0.25rem;
19 --space-sm: 0.5rem;
20 --space-md: 1rem;
21 --space-lg: 1.5rem;
22 --space-xl: 2rem;
23 --space-2xl: 3rem;
24 --space-3xl: 4rem;
25
26 /* Layout */
27 --container-max-width: 1200px;
28 --border-radius: 0.5rem;
29 --border-radius-lg: 1rem;
30
31 /* Shadows */
32 --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
33 --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
34 --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
35
36 /* Transitions */
37 --transition-fast: 150ms ease;
38 --transition-normal: 250ms ease;
39 --transition-slow: 350ms ease;
40}
41
42/* Dark theme variables */
43[data-theme="dark"] {
44 --color-text: #f7fafc;
45 --color-text-muted: #a0aec0;
46 --color-bg: #1a202c;
47 --color-surface: #2d3748;
48 --color-border: #4a5568;
49}
50
51/* ===== CASCADE LAYERS ===== */
52@layer reset, base, components, utilities;
53
54@layer reset {
55 *, *::before, *::after {
56 box-sizing: border-box;
57 margin: 0;
58 padding: 0;
59 }
60
61 html {
62 scroll-behavior: smooth;
63 }
64
65 body {
66 line-height: 1.6;
67 -webkit-font-smoothing: antialiased;
68 }
69
70 img, picture, video, canvas, svg {
71 display: block;
72 max-width: 100%;
73 }
74
75 input, button, textarea, select {
76 font: inherit;
77 }
78}
79
80@layer base {
81 body {
82 font-family: var(--font-primary);
83 color: var(--color-text);
84 background: var(--color-bg);
85 transition: background-color var(--transition-normal),
86 color var(--transition-normal);
87 }
88
89 h1, h2, h3, h4, h5, h6 {
90 font-family: var(--font-heading);
91 font-weight: 600;
92 line-height: 1.2;
93 }
94
95 a {
96 color: var(--color-primary);
97 text-decoration: none;
98 transition: color var(--transition-fast);
99
100 &:hover {
101 color: var(--color-secondary);
102 }
103 }
104}
105
106/* ===== CONTAINER QUERIES ===== */
107.projects {
108 container-type: inline-size;
109 container-name: projects;
110}
111
112.project-card {
113 background: var(--color-surface);
114 border-radius: var(--border-radius-lg);
115 overflow: hidden;
116 box-shadow: var(--shadow-md);
117 transition: transform var(--transition-normal),
118 box-shadow var(--transition-normal);
119
120 &:hover {
121 transform: translateY(-4px);
122 box-shadow: var(--shadow-lg);
123 }
124}
125
126@container projects (min-width: 768px) {
127 .projects__grid {
128 display: grid;
129 grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
130 gap: var(--space-xl);
131 }
132
133 .project-card {
134 display: grid;
135 grid-template-rows: 200px 1fr;
136 }
137}
138
139@container projects (min-width: 1024px) {
140 .projects__grid {
141 grid-template-columns: repeat(3, 1fr);
142 }
143}
144
145/* ===== CSS GRID WITH SUBGRID ===== */
146.skills__grid {
147 display: grid;
148 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
149 gap: var(--space-xl);
150}
151
152.skill-category {
153 display: grid;
154 grid-template-rows: auto 1fr;
155 background: var(--color-surface);
156 border-radius: var(--border-radius-lg);
157 padding: var(--space-lg);
158
159 /* Użycie subgrid gdy będzie wspierane */
160 @supports (grid-template-rows: subgrid) {
161 grid-template-rows: subgrid;
162 }
163}
164
165/* ===== CSS NESTING ===== */
166.nav {
167 display: flex;
168 justify-content: space-between;
169 align-items: center;
170 padding: var(--space-md) var(--space-lg);
171 background: var(--color-surface);
172 backdrop-filter: blur(10px);
173 border-bottom: 1px solid var(--color-border);
174
175 &__brand {
176 font-weight: bold;
177 font-size: 1.5rem;
178 }
179
180 &__logo {
181 display: inline-flex;
182 align-items: center;
183 justify-content: center;
184 width: 40px;
185 height: 40px;
186 background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
187 color: white;
188 border-radius: 50%;
189 font-weight: bold;
190 }
191
192 &__menu {
193 display: flex;
194 list-style: none;
195 gap: var(--space-lg);
196
197 @media (max-width: 768px) {
198 display: none; /* Mobilne menu do implementacji */
199 }
200 }
201
202 &__link {
203 position: relative;
204 font-weight: 500;
205
206 &::after {
207 content: '';
208 position: absolute;
209 bottom: -4px;
210 left: 0;
211 width: 0;
212 height: 2px;
213 background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
214 transition: width var(--transition-normal);
215 }
216
217 &:hover::after,
218 &.active::after {
219 width: 100%;
220 }
221 }
222}
223
224/* ===== SCROLL-DRIVEN ANIMATIONS ===== */
225@supports (animation-timeline: scroll()) {
226 .hero__title {
227 animation: hero-fade-in linear;
228 animation-timeline: view();
229 animation-range: entry 0% cover 40%;
230 }
231
232 .section__title {
233 animation: slide-up linear;
234 animation-timeline: view();
235 animation-range: entry 20% cover 50%;
236 }
237
238 @keyframes hero-fade-in {
239 from {
240 opacity: 0;
241 transform: translateY(50px) scale(0.9);
242 }
243 to {
244 opacity: 1;
245 transform: translateY(0) scale(1);
246 }
247 }
248
249 @keyframes slide-up {
250 from {
251 opacity: 0;
252 transform: translateY(30px);
253 }
254 to {
255 opacity: 1;
256 transform: translateY(0);
257 }
258 }
259}
260
261/* ===== MODERN CSS FEATURES ===== */
262.hero {
263 min-height: 100vh;
264 display: grid;
265 grid-template-columns: 1fr 1fr;
266 align-items: center;
267 padding: var(--space-2xl);
268 background: linear-gradient(135deg,
269 color-mix(in srgb, var(--color-primary) 10%, transparent) 0%,
270 color-mix(in srgb, var(--color-secondary) 10%, transparent) 100%
271 );
272 position: relative;
273 overflow: hidden;
274
275 @media (max-width: 768px) {
276 grid-template-columns: 1fr;
277 text-align: center;
278 padding: var(--space-xl) var(--space-lg);
279 }
280}
281
282.floating-shapes {
283 position: absolute;
284 inset: 0;
285 pointer-events: none;
286}
287
288.shape {
289 position: absolute;
290 border-radius: 50%;
291 background: linear-gradient(45deg, var(--color-primary), var(--color-accent));
292 opacity: 0.1;
293 animation: float 6s ease-in-out infinite;
294
295 &--1 {
296 width: 100px;
297 height: 100px;
298 top: 20%;
299 left: 10%;
300 animation-delay: 0s;
301 }
302
303 &--2 {
304 width: 150px;
305 height: 150px;
306 top: 60%;
307 right: 20%;
308 animation-delay: 2s;
309 }
310
311 &--3 {
312 width: 80px;
313 height: 80px;
314 bottom: 20%;
315 left: 60%;
316 animation-delay: 4s;
317 }
318}
319
320@keyframes float {
321 0%, 100% {
322 transform: translateY(0) rotate(0deg);
323 }
324 50% {
325 transform: translateY(-20px) rotate(180deg);
326 }
327}
328
329/* ===== WEB COMPONENTS STYLING ===== */
330.skill-item {
331 margin-bottom: var(--space-md);
332
333 &__name {
334 display: block;
335 margin-bottom: var(--space-xs);
336 font-weight: 500;
337 }
338
339 &__bar {
340 width: 100%;
341 height: 8px;
342 background: var(--color-border);
343 border-radius: 4px;
344 overflow: hidden;
345 }
346
347 &__progress {
348 height: 100%;
349 background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
350 border-radius: 4px;
351 transform-origin: left;
352 animation: progress-fill 2s ease-out;
353
354 @supports (animation-timeline: view()) {
355 animation: progress-fill linear;
356 animation-timeline: view();
357 animation-range: entry 50% cover 80%;
358 }
359 }
360}
361
362@keyframes progress-fill {
363 from {
364 transform: scaleX(0);
365 }
366 to {
367 transform: scaleX(1);
368 }
369}
370
371/* ===== THEME TOGGLE ===== */
372.theme-toggle {
373 background: var(--color-surface);
374 border: 2px solid var(--color-border);
375 border-radius: 50%;
376 width: 44px;
377 height: 44px;
378 display: flex;
379 align-items: center;
380 justify-content: center;
381 cursor: pointer;
382 transition: all var(--transition-normal);
383
384 &:hover {
385 transform: scale(1.1);
386 background: var(--color-primary);
387 border-color: var(--color-primary);
388 color: white;
389 }
390
391 &__icon {
392 font-size: 1.2rem;
393 transition: transform var(--transition-normal);
394 }
395
396 &[data-theme="dark"] &__icon {
397 transform: rotate(180deg);
398 }
399}
400
401/* ===== FORM STYLING ===== */
402.contact-form {
403 max-width: 600px;
404 margin: 0 auto;
405 padding: var(--space-2xl);
406 background: var(--color-surface);
407 border-radius: var(--border-radius-lg);
408 box-shadow: var(--shadow-lg);
409}
410
411.form-group {
412 margin-bottom: var(--space-lg);
413 position: relative;
414}
415
416.form-label {
417 display: block;
418 margin-bottom: var(--space-xs);
419 font-weight: 500;
420 color: var(--color-text);
421}
422
423.form-input,
424.form-textarea {
425 width: 100%;
426 padding: var(--space-md);
427 border: 2px solid var(--color-border);
428 border-radius: var(--border-radius);
429 background: var(--color-bg);
430 color: var(--color-text);
431 transition: border-color var(--transition-fast);
432
433 &:focus {
434 outline: none;
435 border-color: var(--color-primary);
436 box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 20%, transparent);
437 }
438
439 &:invalid:not(:placeholder-shown) {
440 border-color: #ef4444;
441 }
442
443 &:valid:not(:placeholder-shown) {
444 border-color: #10b981;
445 }
446}
447
448.form-textarea {
449 min-height: 120px;
450 resize: vertical;
451}
452
453.form-error {
454 position: absolute;
455 bottom: -20px;
456 left: 0;
457 font-size: 0.875rem;
458 color: #ef4444;
459 opacity: 0;
460 transition: opacity var(--transition-fast);
461
462 &.visible {
463 opacity: 1;
464 }
465}
466
467/* ===== BUTTONS ===== */
468.btn {
469 display: inline-flex;
470 align-items: center;
471 justify-content: center;
472 padding: var(--space-md) var(--space-lg);
473 border: none;
474 border-radius: var(--border-radius);
475 font-weight: 500;
476 text-decoration: none;
477 cursor: pointer;
478 transition: all var(--transition-normal);
479 position: relative;
480 overflow: hidden;
481
482 &::before {
483 content: '';
484 position: absolute;
485 top: 0;
486 left: -100%;
487 width: 100%;
488 height: 100%;
489 background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
490 transition: left var(--transition-slow);
491 }
492
493 &:hover::before {
494 left: 100%;
495 }
496
497 &--primary {
498 background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
499 color: white;
500
501 &:hover {
502 transform: translateY(-2px);
503 box-shadow: var(--shadow-lg);
504 }
505 }
506
507 &--secondary {
508 background: transparent;
509 color: var(--color-primary);
510 border: 2px solid var(--color-primary);
511
512 &:hover {
513 background: var(--color-primary);
514 color: white;
515 }
516 }
517
518 &--full {
519 width: 100%;
520 }
521}
522
523/* ===== ACCESSIBILITY ===== */
524@media (prefers-reduced-motion: reduce) {
525 *,
526 *::before,
527 *::after {
528 animation-duration: 0.01ms !important;
529 animation-iteration-count: 1 !important;
530 transition-duration: 0.01ms !important;
531 scroll-behavior: auto !important;
532 }
533}
534
535@media (prefers-color-scheme: dark) {
536 :root:not([data-theme="light"]) {
537 color-scheme: dark;
538 }
539}
540
541/* Focus styles */
542:focus-visible {
543 outline: 2px solid var(--color-primary);
544 outline-offset: 2px;
545}
546
547/* Skip link */
548.skip-link {
549 position: absolute;
550 top: -40px;
551 left: 6px;
552 background: var(--color-primary);
553 color: white;
554 padding: 8px;
555 text-decoration: none;
556 border-radius: 4px;
557 z-index: 1000;
558
559 &:focus {
560 top: 6px;
561 }
562}
563
564/* ===== UTILITY CLASSES ===== */
565@layer utilities {
566 .container {
567 max-width: var(--container-max-width);
568 margin: 0 auto;
569 padding: 0 var(--space-lg);
570 }
571
572 .section__title {
573 font-size: 2.5rem;
574 text-align: center;
575 margin-bottom: var(--space-2xl);
576 background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
577 background-clip: text;
578 -webkit-background-clip: text;
579 -webkit-text-fill-color: transparent;
580 color: transparent;
581 }
582
583 .visually-hidden {
584 position: absolute !important;
585 width: 1px !important;
586 height: 1px !important;
587 padding: 0 !important;
588 margin: -1px !important;
589 overflow: hidden !important;
590 clip: rect(0, 0, 0, 0) !important;
591 white-space: nowrap !important;
592 border: 0 !important;
593 }
594
595 .text-gradient {
596 background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
597 background-clip: text;
598 -webkit-background-clip: text;
599 -webkit-text-fill-color: transparent;
600 }
601}1// Theme toggle functionality
2class ThemeManager {
3 constructor() {
4 this.theme = localStorage.getItem('theme') || 'light';
5 this.toggle = document.querySelector('.theme-toggle');
6 this.init();
7 }
8
9 init() {
10 this.setTheme(this.theme);
11 this.toggle.addEventListener('click', () => this.toggleTheme());
12 }
13
14 setTheme(theme) {
15 document.documentElement.setAttribute('data-theme', theme);
16 this.toggle.querySelector('.theme-toggle__icon').textContent =
17 theme === 'dark' ? '☀️' : '🌙';
18 localStorage.setItem('theme', theme);
19 this.theme = theme;
20 }
21
22 toggleTheme() {
23 const newTheme = this.theme === 'light' ? 'dark' : 'light';
24 this.setTheme(newTheme);
25 }
26}
27
28// Form validation
29class FormValidator {
30 constructor(form) {
31 this.form = form;
32 this.fields = {
33 name: {
34 element: form.querySelector('#name'),
35 rules: ['required', 'minLength:2']
36 },
37 email: {
38 element: form.querySelector('#email'),
39 rules: ['required', 'email']
40 },
41 message: {
42 element: form.querySelector('#message'),
43 rules: ['required', 'minLength:10']
44 }
45 };
46 this.init();
47 }
48
49 init() {
50 Object.values(this.fields).forEach(field => {
51 field.element.addEventListener('blur', () => this.validateField(field));
52 field.element.addEventListener('input', () => this.clearError(field));
53 });
54
55 this.form.addEventListener('submit', (e) => this.handleSubmit(e));
56 }
57
58 validateField(field) {
59 const value = field.element.value.trim();
60 const errors = [];
61
62 field.rules.forEach(rule => {
63 if (rule === 'required' && !value) {
64 errors.push('To pole jest wymagane');
65 } else if (rule === 'email' && value && !this.isValidEmail(value)) {
66 errors.push('Podaj poprawny adres email');
67 } else if (rule.startsWith('minLength:')) {
68 const minLength = parseInt(rule.split(':')[1]);
69 if (value && value.length < minLength) {
70 errors.push(`Minimum ${minLength} znaków`);
71 }
72 }
73 });
74
75 this.showError(field, errors[0]);
76 return errors.length === 0;
77 }
78
79 showError(field, message) {
80 const errorElement = document.querySelector(`#${field.element.name}-error`);
81 if (message) {
82 errorElement.textContent = message;
83 errorElement.classList.add('visible');
84 field.element.setAttribute('aria-invalid', 'true');
85 } else {
86 this.clearError(field);
87 }
88 }
89
90 clearError(field) {
91 const errorElement = document.querySelector(`#${field.element.name}-error`);
92 errorElement.classList.remove('visible');
93 field.element.removeAttribute('aria-invalid');
94 }
95
96 isValidEmail(email) {
97 return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
98 }
99
100 handleSubmit(e) {
101 e.preventDefault();
102
103 const isValid = Object.values(this.fields)
104 .map(field => this.validateField(field))
105 .every(valid => valid);
106
107 if (isValid) {
108 this.submitForm();
109 }
110 }
111
112 async submitForm() {
113 const submitButton = this.form.querySelector('button[type="submit"]');
114 const originalText = submitButton.textContent;
115
116 submitButton.textContent = 'Wysyłanie...';
117 submitButton.disabled = true;
118
119 try {
120 // Symulacja wysyłania
121 await new Promise(resolve => setTimeout(resolve, 2000));
122
123 // Success message
124 this.showSuccessMessage();
125 this.form.reset();
126 } catch (error) {
127 alert('Wystąpił błąd podczas wysyłania wiadomości');
128 } finally {
129 submitButton.textContent = originalText;
130 submitButton.disabled = false;
131 }
132 }
133
134 showSuccessMessage() {
135 const message = document.createElement('div');
136 message.className = 'success-message';
137 message.textContent = 'Wiadomość została wysłana pomyślnie!';
138 message.style.cssText = `
139 position: fixed;
140 top: 20px;
141 right: 20px;
142 background: #10b981;
143 color: white;
144 padding: 1rem 1.5rem;
145 border-radius: 0.5rem;
146 box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
147 z-index: 1000;
148 animation: slideIn 0.3s ease;
149 `;
150
151 document.body.appendChild(message);
152
153 setTimeout(() => {
154 message.style.animation = 'slideOut 0.3s ease';
155 setTimeout(() => message.remove(), 300);
156 }, 3000);
157 }
158}
159
160// Smooth scrolling for navigation
161class SmoothScrolling {
162 constructor() {
163 this.init();
164 }
165
166 init() {
167 document.querySelectorAll('a[href^="#"]').forEach(link => {
168 link.addEventListener('click', (e) => {
169 e.preventDefault();
170 const target = document.querySelector(link.getAttribute('href'));
171 if (target) {
172 target.scrollIntoView({
173 behavior: 'smooth',
174 block: 'start'
175 });
176 }
177 });
178 });
179 }
180}
181
182// Skills animation
183class SkillsAnimation {
184 constructor() {
185 this.init();
186 }
187
188 init() {
189 if ('IntersectionObserver' in window) {
190 this.observeSkills();
191 } else {
192 this.animateSkillsDirectly();
193 }
194 }
195
196 observeSkills() {
197 const observer = new IntersectionObserver((entries) => {
198 entries.forEach(entry => {
199 if (entry.isIntersecting) {
200 this.animateSkill(entry.target);
201 observer.unobserve(entry.target);
202 }
203 });
204 }, { threshold: 0.5 });
205
206 document.querySelectorAll('.skill-item__progress').forEach(skill => {
207 observer.observe(skill);
208 });
209 }
210
211 animateSkill(skillElement) {
212 const level = skillElement.dataset.level;
213 skillElement.style.transform = `scaleX(${level / 100})`;
214 }
215
216 animateSkillsDirectly() {
217 document.querySelectorAll('.skill-item__progress').forEach(skill => {
218 setTimeout(() => this.animateSkill(skill), 500);
219 });
220 }
221}
222
223// Initialize everything when DOM is loaded
224document.addEventListener('DOMContentLoaded', () => {
225 new ThemeManager();
226 new SmoothScrolling();
227 new SkillsAnimation();
228
229 const contactForm = document.querySelector('.contact-form');
230 if (contactForm) {
231 new FormValidator(contactForm);
232 }
233
234 // Add success animations
235 const style = document.createElement('style');
236 style.textContent = `
237 @keyframes slideIn {
238 from { transform: translateX(100%); opacity: 0; }
239 to { transform: translateX(0); opacity: 1; }
240 }
241 @keyframes slideOut {
242 from { transform: translateX(0); opacity: 1; }
243 to { transform: translateX(100%); opacity: 0; }
244 }
245 `;
246 document.head.appendChild(style);
247});1/* Critical CSS inlining */
2/* Tylko najważniejsze style w <head> */
3
4/* Lazy loading images */
5img[loading="lazy"] {
6 content-visibility: auto;
7 contain-intrinsic-size: 200px 150px;
8}
9
10/* CSS containment */
11.project-card {
12 contain: layout style paint;
13}
14
15/* Preload important resources */
16/* <link rel="preload" href="fonts/inter.woff2" as="font" type="font/woff2" crossorigin> */1# Portfolio Website - Nowoczesne techniki CSS
2
3Responsywna strona portfolio wykorzystująca najnowsze techniki CSS.
4
5## Użyte technologie
6
7- HTML5 Semantic markup
8- CSS3 z najnowszymi funkcjonalnościami
9- Vanilla JavaScript ES6+
10- Container Queries
11- CSS Nesting
12- Cascade Layers
13- CSS Grid & Subgrid
14
15## Instalacja
16
171. Sklonuj repozytorium
182. Otwórz index.html w przeglądarce
193. Uruchom lokalny serwer dla pełnej funkcjonalności
20
21## Funkcjonalności
22
23- ✅ Responsywny design z Container Queries
24- ✅ Dark/Light mode toggle
25- ✅ Smooth scroll animations
26- ✅ Form validation
27- ✅ WCAG 2.1 AA compliance
28- ✅ Cross-browser compatibility
29
30## Performance
31
32- Lighthouse Performance: 95+
33- First Contentful Paint: < 1.5s
34- Largest Contentful Paint: < 2.5s
35- Cumulative Layout Shift: < 0.1Ten projekt demonstruje praktyczne zastosowanie wszystkich zaawansowanych technik CSS poznanych w module:
Projekt można rozbudować o dodatkowe funkcjonalności i jest doskonałą bazą do dalszego rozwoju umiejętności CSS.