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.
You'll create a responsive portfolio page for a web developer that will include:
1<!DOCTYPE html>
2<html lang="en">
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">About</a>
21 </li>
22 <li class="nav__item">
23 <a href="#projects" class="nav__link">Projects</a>
24 </li>
25 <li class="nav__item">
26 <a href="#skills" class="nav__link">Skills</a>
27 </li>
28 <li class="nav__item">
29 <a href="#contact" class="nav__link">Contact</a>
30 </li>
31 </ul>
32 <button class="theme-toggle" aria-label="Toggle theme">
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">View My Projects</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">About Me</h2>
57 <div class="about__content">
58 <div class="about__text">
59 <p>I am passionate about creating modern web applications...</p>
60 </div>
61 <div class="about__image">
62 <img src="profile.jpg" alt="Profile photo" 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">My Projects</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="Project 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">A modern dashboard for managing an online store.</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 <!-- More project cards... -->
92 </div>
93 </div>
94 </section>
95
96 <section id="skills" class="skills">
97 <div class="container">
98 <h2 class="section__title">Skills</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 <!-- More skills... -->
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">Contact</h2>
119 <form class="contact-form" novalidate>
120 <div class="form-group">
121 <label for="name" class="form-label">Name</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">Message</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">Send Message</button>
136 </form>
137 </div>
138 </section>
139 </main>
140
141 <footer class="footer">
142 <div class="container">
143 <p>© 2024 Jan Kowalski. All rights reserved.</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 /* Use subgrid when supported */
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; /* Mobile menu to be implemented */
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/* ===== SKILL ITEMS ===== */
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(:
440## Practical Application
441
442Practice this concept in the editor below.
443 border-color: #ef4444;
444 }
445
446 &:valid:not(:
447## Practical Application
448
449Practice this concept in the editor below.
450 border-color: #10b981;
451 }
452}
453
454.form-textarea {
455 min-height: 120px;
456 resize: vertical;
457}
458
459.form-error {
460 position: absolute;
461 bottom: -20px;
462 left: 0;
463 font-size: 0.875rem;
464 color: #ef4444;
465 opacity: 0;
466 transition: opacity var(--transition-fast);
467
468 &.visible {
469 opacity: 1;
470 }
471}
472
473/* ===== BUTTONS ===== */
474.btn {
475 display: inline-flex;
476 align-items: center;
477 justify-content: center;
478 padding: var(--space-md) var(--space-lg);
479 border: none;
480 border-radius: var(--border-radius);
481 font-weight: 500;
482 text-decoration: none;
483 cursor: pointer;
484 transition: all var(--transition-normal);
485 position: relative;
486 overflow: hidden;
487
488 &::before {
489 content: '';
490 position: absolute;
491 top: 0;
492 left: -100%;
493 width: 100%;
494 height: 100%;
495 background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
496 transition: left var(--transition-slow);
497 }
498
499 &:hover::before {
500 left: 100%;
501 }
502
503 &--primary {
504 background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
505 color: white;
506
507 &:hover {
508 transform: translateY(-2px);
509 box-shadow: var(--shadow-lg);
510 }
511 }
512
513 &--secondary {
514 background: transparent;
515 color: var(--color-primary);
516 border: 2px solid var(--color-primary);
517
518 &:hover {
519 background: var(--color-primary);
520 color: white;
521 }
522 }
523
524 &--full {
525 width: 100%;
526 }
527}
528
529/* ===== ACCESSIBILITY ===== */
530@media (prefers-reduced-motion: reduce) {
531 *,
532 *::before,
533 *::after {
534 animation-duration: 0.01ms !important;
535 animation-iteration-count: 1 !important;
536 transition-duration: 0.01ms !important;
537 scroll-behavior: auto !important;
538 }
539}
540
541@media (prefers-color-scheme: dark) {
542 :root:not([data-theme="light"]) {
543 color-scheme: dark;
544 }
545}
546
547/* Focus styles */
548:focus-visible {
549 outline: 2px solid var(--color-primary);
550 outline-offset: 2px;
551}
552
553/* Skip link */
554.skip-link {
555 position: absolute;
556 top: -40px;
557 left: 6px;
558 background: var(--color-primary);
559 color: white;
560 padding: 8px;
561 text-decoration: none;
562 border-radius: 4px;
563 z-index: 1000;
564
565 &:focus {
566 top: 6px;
567 }
568}
569
570/* ===== UTILITY CLASSES ===== */
571@layer utilities {
572 .container {
573 max-width: var(--container-max-width);
574 margin: 0 auto;
575 padding: 0 var(--space-lg);
576 }
577
578 .section__title {
579 font-size: 2.5rem;
580 text-align: center;
581 margin-bottom: var(--space-2xl);
582 background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
583 background-clip: text;
584 -webkit-background-clip: text;
585 -webkit-text-fill-color: transparent;
586 color: transparent;
587 }
588
589 .visually-hidden {
590 position: absolute !important;
591 width: 1px !important;
592 height: 1px !important;
593 padding: 0 !important;
594 margin: -1px !important;
595 overflow: hidden !important;
596 clip: rect(0, 0, 0, 0) !important;
597 white-space: nowrap !important;
598 border: 0 !important;
599 }
600
601 .text-gradient {
602 background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
603 background-clip: text;
604 -webkit-background-clip: text;
605 -webkit-text-fill-color: transparent;
606 }
607}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('This field is required');
65 } else if (rule === 'email' && value && !this.isValidEmail(value)) {
66 errors.push('Please enter a valid email address');
67 } else if (rule.startsWith('minLength:')) {
68 const minLength = parseInt(rule.split(':')[1]);
69 if (value && value.length < minLength) {
70 errors.push(`Minimum ${minLength} characters`);
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 = 'Sending...';
117 submitButton.disabled = true;
118
119 try {
120 // Simulating sending
121 await new Promise(resolve => setTimeout(resolve, 2000));
122
123 // Success message
124 this.showSuccessMessage();
125 this.form.reset();
126 } catch (error) {
127 alert('An error occurred while sending the message');
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 = 'Message sent successfully!';
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/* Only the most important styles in <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 - Modern CSS Techniques
2
3Responsive portfolio page utilizing the latest CSS techniques.
4
5## Technologies Used
6
7- HTML5 Semantic markup
8- CSS3 with latest features
9- Vanilla JavaScript ES6+
10- Container Queries
11- CSS Nesting
12- Cascade Layers
13- CSS Grid & Subgrid
14
15## Installation
16
171. Clone the repository
182. Open index.html in a browser
193. Run a local server for full functionality
20
21## Features
22
23- Responsive design with 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.1This project demonstrates the practical application of all advanced CSS techniques learned in this module:
The project can be expanded with additional features and serves as an excellent foundation for further CSS skill development.