Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Projekt - Nowoczesna strona portfolio z zaawansowanymi technikami CSS

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

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>&copy; 2024 Jan Kowalski. Wszystkie prawa zastrzeżone.</p>
144    </div>
145  </footer>
146
147  <script src="script.js"></script>
148</body>
149</html>

2. CSS z wykorzystaniem zaawansowanych technik

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}

3. JavaScript dla interakcji

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

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

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> */

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

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.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.

Przejdź do CodeWorlds