CodeWorlds

Projekt Końcowy: Responsywna Strona Landing Page "Egipska Przygoda"

Kurs HTML i CSS · Moduł 9: Nowoczesny CSS

Witaj w ostatniej komnacie naszej egipskiej podróży! Jestem Mohamed i dzisiaj staniemy przed ostatecznym wyzwaniem: stworzeniem kompletnej, profesjonalnej strony landing page, która połączy WSZYSTKIE umiejętności zdobyte w tym module! Znasz już dziesiątki narzędzi, ale każde ćwiczyłeś osobno. Prawdziwy sprawdzian zaczyna się wtedy, gdy siatka, kontenery, formularz i dostępność muszą zagrać razem na jednej stronie.

Podobnie jak budowa Wielkiej Piramidy w Gizie wymagała połączenia wszystkich umiejętności starożytnych budowniczych, od podstaw po najbardziej zaawansowane techniki, tak i Twój projekt końcowy będzie syntezą całej wiedzy o HTML i CSS!

Cel projektu

Stworzysz responsywną stronę landing page dla fikcyjnej agencji turystycznej "Egipska Przygoda", która organizuje wycieczki do starożytnego Egiptu. Strona będzie wykorzystywać:

  1. HTML semantyczny - dostępna struktura
  2. CSS Grid i Flexbox - zaawansowane layouty
  3. Container Queries - responsywne komponenty
  4. Modern CSS - najnowsze funkcje CSS
  5. Animacje - płynne przejścia i efekty
  6. Performance - optymalizacja i najlepsze praktyki

Struktura projektu

Sekcje strony:

  1. Hero Section - główny baner z CTA (Call To Action)
  2. Features - karty z features używające container queries
  3. Tours - galeria wycieczek w CSS Grid
  4. Testimonials - opinie klientów z carousel
  5. Pricing - sekcja cenowa
  6. Contact Form - formularz kontaktowy
  7. Footer - stopka z social links

Wymagania techniczne

1. HTML Semantyczny

Zaczynamy od szkieletu. Nagłówek z nawigacją, sekcje z identyfikatorami, karty w elementach article, opinie w blockquote i formularz z etykietami. Kod jest długi, więc czytaj go sekcja po sekcji:

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  <meta name="description" content="Odkryj tajemnice starożytnego Egiptu z Egipską Przygodą - najlepsza agencja turystyczna">
7  <title>Egipska Przygoda - Wycieczki do Starożytnego Egiptu</title>
8  <link rel="stylesheet" href="styles.css">
9</head>
10<body>
11  <!-- Navigation -->
12  <header class="header" role="banner">
13    <nav class="nav" role="navigation" aria-label="Główna nawigacja">
14      <div class="container">
15        <a href="#" class="logo">
16          <span class="logo-icon"></span>
17          <span class="logo-text">Egipska Przygoda</span>
18        </a>
19        <ul class="nav-list">
20          <li><a href="#features">Dlaczego my</a></li>
21          <li><a href="#tours">Wycieczki</a></li>
22          <li><a href="#testimonials">Opinie</a></li>
23          <li><a href="#pricing">Cennik</a></li>
24          <li><a href="#contact" class="btn-primary">Kontakt</a></li>
25        </ul>
26      </div>
27    </nav>
28  </header>
29
30  <!-- Hero Section -->
31  <section class="hero" id="hero">
32    <div class="hero-content">
33      <h1 class="hero-title">
34        Odkryj Tajemnice<br>
35        <span class="gradient-text">Starożytnego Egiptu</span>
36      </h1>
37      <p class="hero-subtitle">
38        Przenieś się w czasie o 5000 lat wstecz i poznaj cuda faraonów,
39        piramid i hieroglifów w niezapomnianej podróży życia!
40      </p>
41      <div class="hero-cta">
42        <a href="#tours" class="btn btn-large btn-primary">
43          Przeglądaj Wycieczki
44        </a>
45        <a href="#contact" class="btn btn-large btn-secondary">
46          Skontaktuj się z nami
47        </a>
48      </div>
49    </div>
50    <div class="hero-image">
51      <div class="pyramid-illustration"></div>
52    </div>
53  </section>
54
55  <!-- Features -->
56  <section class="features" id="features">
57    <div class="container">
58      <h2 class="section-title">Dlaczego Egipska Przygoda?</h2>
59      <p class="section-subtitle">
60        Jesteśmy liderami w organizacji wycieczek do Egiptu od 20 lat
61      </p>
62
63      <div class="features-grid">
64        <article class="feature-card">
65          <div class="feature-icon"></div>
66          <h3>Ekskluzywny Dostęp</h3>
67          <p>Dostęp do miejsc niedostępnych dla zwykłych turystów,
68             w tym prywatne zwiedzanie komnat piramid</p>
69        </article>
70
71        <article class="feature-card">
72          <div class="feature-icon"></div>
73          <h3>Eksperci Egiptologii</h3>
74          <p>Przewodnicy z tytułami naukowymi, pasjonaci historii
75             starożytnego Egiptu</p>
76        </article>
77
78        <article class="feature-card">
79          <div class="feature-icon"></div>
80          <h3>Luksusowe Hotele</h3>
81          <p>Noclegi w najlepszych hotelach 5*, z widokiem na Nil
82             i piramidy</p>
83        </article>
84
85        <article class="feature-card">
86          <div class="feature-icon"></div>
87          <h3>Autentyczna Kuchnia</h3>
88          <p>Degustacja tradycyjnych egipskich potraw przygotowanych
89             przez lokalnych szefów kuchni</p>
90        </article>
91      </div>
92    </div>
93  </section>
94
95  <!-- Tours Grid -->
96  <section class="tours" id="tours">
97    <div class="container">
98      <h2 class="section-title">Nasze Wycieczki</h2>
99      <p class="section-subtitle">
100        Wybierz wycieczkę dopasowaną do Twoich marzeń
101      </p>
102
103      <div class="tours-grid">
104        <article class="tour-card tour-card--featured">
105          <div class="tour-image">
106            <img src="/api/placeholder/600/400" alt="Piramidy w Gizie">
107            <span class="tour-badge">Bestseller</span>
108          </div>
109          <div class="tour-content">
110            <h3>Klasyczny Egipt</h3>
111            <p class="tour-duration">⏱7 dni / 6 nocy</p>
112            <p class="tour-description">
113              Kairo, Giza, Luksor, Dolina Królów, rejs po Nilu
114            </p>
115            <ul class="tour-highlights">
116              <li>Wielka Piramida w Gizie</li>
117              <li>Muzeum Egipskie w Kairze</li>
118              <li>Świątynia Karnak</li>
119              <li>Rejs statkiem po Nilu</li>
120            </ul>
121            <div class="tour-footer">
122              <div class="tour-price">
123                <span class="price-from">od</span>
124                <span class="price-amount">4,999</span>
125                <span class="price-currency">PLN</span>
126              </div>
127              <a href="#contact" class="btn btn-primary">Rezerwuj</a>
128            </div>
129          </div>
130        </article>
131
132        <article class="tour-card">
133          <div class="tour-image">
134            <img src="/api/placeholder/600/400" alt="Abu Simbel">
135          </div>
136          <div class="tour-content">
137            <h3>Egipskie Cuda</h3>
138            <p class="tour-duration">⏱10 dni / 9 nocy</p>
139            <p class="tour-description">
140              Wszystkie główne atrakcje + Abu Simbel i Alexandria
141            </p>
142            <ul class="tour-highlights">
143              <li>Wszystko z Klasycznego Egiptu</li>
144              <li>Świątynie Abu Simbel</li>
145              <li>Biblioteka Aleksandryjska</li>
146              <li>Oaza Siwa</li>
147            </ul>
148            <div class="tour-footer">
149              <div class="tour-price">
150                <span class="price-from">od</span>
151                <span class="price-amount">7,499</span>
152                <span class="price-currency">PLN</span>
153              </div>
154              <a href="#contact" class="btn btn-primary">Rezerwuj</a>
155            </div>
156          </div>
157        </article>
158
159        <article class="tour-card">
160          <div class="tour-image">
161            <img src="/api/placeholder/600/400" alt="Nurkowanie w Morzu Czerwonym">
162          </div>
163          <div class="tour-content">
164            <h3>Egipt + Morze Czerwone</h3>
165            <p class="tour-duration">⏱12 dni / 11 nocy</p>
166            <p class="tour-description">
167              Historia + relaks i nurkowanie w Hurghadzie
168            </p>
169            <ul class="tour-highlights">
170              <li>Zwiedzanie zabytków</li>
171              <li>5 dni all inclusive w Hurghadzie</li>
172              <li>Nurkowanie z rafa koralowa</li>
173              <li>Safari na pustyni</li>
174            </ul>
175            <div class="tour-footer">
176              <div class="tour-price">
177                <span class="price-from">od</span>
178                <span class="price-amount">6,299</span>
179                <span class="price-currency">PLN</span>
180              </div>
181              <a href="#contact" class="btn btn-primary">Rezerwuj</a>
182            </div>
183          </div>
184        </article>
185      </div>
186    </div>
187  </section>
188
189  <!-- Testimonials -->
190  <section class="testimonials" id="testimonials">
191    <div class="container">
192      <h2 class="section-title">Co Mówią Nasi Klienci</h2>
193
194      <div class="testimonials-grid">
195        <blockquote class="testimonial-card">
196          <div class="testimonial-rating"></div>
197          <p class="testimonial-text">
198            "Niesamowite przeżycie! Przewodnicy byli niezwykle kompetentni,
199            a organizacja wycieczki na najwyższym poziomie. Piramidy na żywo
200            robią wrażenie tysiąckrotnie większe niż na zdjęciach!"
201          </p>
202          <cite class="testimonial-author">
203            <strong>Anna Kowalska</strong>
204            <span>Warszawa</span>
205          </cite>
206        </blockquote>
207
208        <blockquote class="testimonial-card">
209          <div class="testimonial-rating"></div>
210          <p class="testimonial-text">
211            "Jako egiptolog byłem sceptyczny, ale ta wycieczka przekroczyła
212            moje oczekiwania. Dostęp do miejsc, których nie pokazują w
213            standardowych wycieczpach, był bezcenny!"
214          </p>
215          <cite class="testimonial-author">
216            <strong>Dr. Jan Nowak</strong>
217            <span>Kraków</span>
218          </cite>
219        </blockquote>
220
221        <blockquote class="testimonial-card">
222          <div class="testimonial-rating"></div>
223          <p class="testimonial-text">
224            "Podróż życia! Szczególnie zachwycił mnie rejs po Nilu i
225            zwiedzanie Doliny Królów. Wszystko było perfekcyjnie zorganizowane."
226          </p>
227          <cite class="testimonial-author">
228            <strong>Maria Wiśniewska</strong>
229            <span>Gdańsk</span>
230          </cite>
231        </blockquote>
232      </div>
233    </div>
234  </section>
235
236  <!-- Contact Form -->
237  <section class="contact" id="contact">
238    <div class="container">
239      <div class="contact-grid">
240        <div class="contact-info">
241          <h2>Zarezerwuj Swoją Przygodę</h2>
242          <p>Wypełnij formularz, a nasz konsultant skontaktuje się z Tobą
243             w ciągu 24 godzin!</p>
244
245          <div class="contact-details">
246            <div class="contact-item">
247              <span class="contact-icon"></span>
248              <div>
249                <strong>Telefon</strong>
250                <p>+48 123 456 789</p>
251              </div>
252            </div>
253            <div class="contact-item">
254              <span class="contact-icon"></span>
255              <div>
256                <strong>Email</strong>
257                <p>kontakt@egipskaprzygoda.pl</p>
258              </div>
259            </div>
260            <div class="contact-item">
261              <span class="contact-icon"></span>
262              <div>
263                <strong>Adres</strong>
264                <p>ul. Piramid 123, 00-001 Warszawa</p>
265              </div>
266            </div>
267          </div>
268        </div>
269
270        <form class="contact-form" action="#" method="POST">
271          <div class="form-group">
272            <label for="name">Imię i nazwisko</label>
273            <input type="text" id="name" name="name" required>
274          </div>
275
276          <div class="form-group">
277            <label for="email">Email</label>
278            <input type="email" id="email" name="email" required>
279          </div>
280
281          <div class="form-group">
282            <label for="phone">Telefon</label>
283            <input type="tel" id="phone" name="phone" required>
284          </div>
285
286          <div class="form-group">
287            <label for="tour">Wybierz wycieczkę</label>
288            <select id="tour" name="tour" required>
289              <option value="">Wybierz...</option>
290              <option value="classic">Klasyczny Egipt - 7 dni</option>
291              <option value="wonders">Egipskie Cuda - 10 dni</option>
292              <option value="red-sea">Egipt + Morze Czerwone - 12 dni</option>
293            </select>
294          </div>
295
296          <div class="form-group">
297            <label for="message">Wiadomość (opcjonalnie)</label>
298            <textarea id="message" name="message" rows="4"></textarea>
299          </div>
300
301          <button type="submit" class="btn btn-primary btn-large">
302            Wyślij zapytanie
303          </button>
304        </form>
305      </div>
306    </div>
307  </section>
308
309  <!-- Footer -->
310  <footer class="footer">
311    <div class="container">
312      <div class="footer-grid">
313        <div class="footer-section">
314          <h3>Egipska Przygoda</h3>
315          <p>Odkrywamy tajemnice starożytnego Egiptu od 2004 roku</p>
316          <div class="social-links">
317            <a href="#" aria-label="Facebook"></a>
318            <a href="#" aria-label="Instagram"></a>
319            <a href="#" aria-label="Twitter"></a>
320            <a href="#" aria-label="YouTube"></a>
321          </div>
322        </div>
323
324        <div class="footer-section">
325          <h4>Wycieczki</h4>
326          <ul>
327            <li><a href="#">Klasyczny Egipt</a></li>
328            <li><a href="#">Egipskie Cuda</a></li>
329            <li><a href="#">Egipt + Morze Czerwone</a></li>
330            <li><a href="#">Wycieczki grupowe</a></li>
331          </ul>
332        </div>
333
334        <div class="footer-section">
335          <h4>Informacje</h4>
336          <ul>
337            <li><a href="#">O nas</a></li>
338            <li><a href="#">Regulamin</a></li>
339            <li><a href="#">Polityka prywatności</a></li>
340            <li><a href="#">FAQ</a></li>
341          </ul>
342        </div>
343
344        <div class="footer-section">
345          <h4>Kontakt</h4>
346          <ul>
347            <li>+48 123 456 789</li>
348            <li>kontakt@egipskaprzygoda.pl</li>
349            <li>ul. Piramid 123, Warszawa</li>
350          </ul>
351        </div>
352      </div>
353
354      <div class="footer-bottom">
355        <p>&copy; 2024 Egipska Przygoda. Wszystkie prawa zastrzeżone.</p>
356      </div>
357    </div>
358  </footer>
359</body>
360</html>

Każda sekcja ma własne id, dzięki czemu linki z nawigacji przewijają stronę do właściwego miejsca. Pola formularza są powiązane z etykietami przez for i id, a atrybut required blokuje wysłanie pustego pola. lang="pl" podpowiada czytnikom ekranu, w jakim języku czytać treść.

2. CSS - Kompletne style

Arkusz stylów dzielą komentarze na sekcje: reset i zmienne, nagłówek, hero, features, wycieczki i kolejne części strony. Paleta, odstępy i cienie żyją w zmiennych CSS na :root:

1/* ==========================================
2   CSS RESET & BASE STYLES
3   ========================================== */
4*, *::before, *::after {
5  margin: 0;
6  padding: 0;
7  box-sizing: border-box;
8}
9
10:root {
11  /* Colors - Egyptian theme */
12  --color-primary: #D4AF37; /* Gold */
13  --color-primary-dark: #B8941F;
14  --color-secondary: #2C3E50; /* Dark blue */
15  --color-accent: #E67E22; /* Orange */
16
17  --color-bg: #FFFFFF;
18  --color-bg-alt: #F8F9FA;
19  --color-text: #2C3E50;
20  --color-text-light: #6C757D;
21
22  /* Spacing */
23  --spacing-xs: 0.5rem;
24  --spacing-sm: 1rem;
25  --spacing-md: 2rem;
26  --spacing-lg: 4rem;
27  --spacing-xl: 6rem;
28
29  /* Typography */
30  --font-primary: 'Segoe UI', system-ui, sans-serif;
31  --font-size-base: 1rem;
32  --font-size-lg: 1.125rem;
33  --font-size-xl: 1.25rem;
34  --font-size-2xl: 1.5rem;
35  --font-size-3xl: 2rem;
36  --font-size-4xl: 3rem;
37
38  /* Layout */
39  --container-max-width: 1200px;
40  --border-radius: 12px;
41
42  /* Shadows */
43  --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
44  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
45  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
46
47  /* Transitions */
48  --transition: all 0.3s ease;
49}
50
51html {
52  scroll-behavior: smooth;
53}
54
55body {
56  font-family: var(--font-primary);
57  font-size: var(--font-size-base);
58  line-height: 1.6;
59  color: var(--color-text);
60  background: var(--color-bg);
61}
62
63img {
64  max-width: 100%;
65  height: auto;
66  display: block;
67}
68
69a {
70  color: inherit;
71  text-decoration: none;
72}
73
74ul {
75  list-style: none;
76}
77
78/* ==========================================
79   UTILITIES
80   ========================================== */
81.container {
82  max-width: var(--container-max-width);
83  margin: 0 auto;
84  padding: 0 var(--spacing-md);
85}
86
87.section-title {
88  font-size: var(--font-size-3xl);
89  font-weight: 700;
90  text-align: center;
91  margin-bottom: var(--spacing-sm);
92  color: var(--color-secondary);
93}
94
95.section-subtitle {
96  font-size: var(--font-size-lg);
97  text-align: center;
98  color: var(--color-text-light);
99  margin-bottom: var(--spacing-lg);
100}
101
102.gradient-text {
103  background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
104  -webkit-background-clip: text;
105  -webkit-text-fill-color: transparent;
106  background-clip: text;
107}
108
109/* Buttons */
110.btn {
111  display: inline-block;
112  padding: var(--spacing-sm) var(--spacing-md);
113  border-radius: var(--border-radius);
114  font-weight: 600;
115  text-align: center;
116  cursor: pointer;
117  transition: var(--transition);
118  border: 2px solid transparent;
119}
120
121.btn-primary {
122  background: var(--color-primary);
123  color: var(--color-secondary);
124}
125
126.btn-primary:hover {
127  background: var(--color-primary-dark);
128  transform: translateY(-2px);
129  box-shadow: var(--shadow-md);
130}
131
132.btn-secondary {
133  background: transparent;
134  border-color: var(--color-primary);
135  color: var(--color-primary);
136}
137
138.btn-secondary:hover {
139  background: var(--color-primary);
140  color: var(--color-secondary);
141}
142
143.btn-large {
144  padding: var(--spacing-md) var(--spacing-lg);
145  font-size: var(--font-size-lg);
146}
147
148/* ==========================================
149   HEADER & NAVIGATION
150   ========================================== */
151.header {
152  position: sticky;
153  top: 0;
154  background: rgba(255, 255, 255, 0.98);
155  backdrop-filter: blur(10px);
156  box-shadow: var(--shadow-sm);
157  z-index: 1000;
158}
159
160.nav .container {
161  display: flex;
162  justify-content: space-between;
163  align-items: center;
164  padding-top: var(--spacing-sm);
165  padding-bottom: var(--spacing-sm);
166}
167
168.logo {
169  display: flex;
170  align-items: center;
171  gap: var(--spacing-sm);
172  font-size: var(--font-size-xl);
173  font-weight: 700;
174  color: var(--color-secondary);
175}
176
177.logo-icon {
178  font-size: var(--font-size-2xl);
179}
180
181.nav-list {
182  display: flex;
183  gap: var(--spacing-md);
184  align-items: center;
185}
186
187.nav-list a {
188  transition: var(--transition);
189}
190
191.nav-list a:not(.btn-primary):hover {
192  color: var(--color-primary);
193}
194
195/* ==========================================
196   HERO SECTION
197   ========================================== */
198.hero {
199  display: grid;
200  grid-template-columns: 1fr 1fr;
201  gap: var(--spacing-lg);
202  align-items: center;
203  min-height: calc(100vh - 80px);
204  padding: var(--spacing-xl) var(--spacing-md);
205  background: linear-gradient(135deg, #F8F9FA 0%, #E9ECEF 100%);
206}
207
208.hero-content {
209  max-width: 600px;
210}
211
212.hero-title {
213  font-size: var(--font-size-4xl);
214  font-weight: 800;
215  line-height: 1.2;
216  margin-bottom: var(--spacing-md);
217}
218
219.hero-subtitle {
220  font-size: var(--font-size-xl);
221  color: var(--color-text-light);
222  margin-bottom: var(--spacing-lg);
223}
224
225.hero-cta {
226  display: flex;
227  gap: var(--spacing-md);
228  flex-wrap: wrap;
229}
230
231.hero-image {
232  display: flex;
233  justify-content: center;
234  align-items: center;
235}
236
237.pyramid-illustration {
238  width: 400px;
239  height: 400px;
240  background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
241  clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
242  animation: float 6s ease-in-out infinite;
243}
244
245@keyframes float {
246  0%, 100% { transform: translateY(0); }
247  50% { transform: translateY(-20px); }
248}
249
250/* ==========================================
251   FEATURES SECTION
252   ========================================== */
253.features {
254  padding: var(--spacing-xl) 0;
255  background: var(--color-bg);
256}
257
258.features-grid {
259  display: grid;
260  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
261  gap: var(--spacing-lg);
262  container-type: inline-size;
263}
264
265.feature-card {
266  background: white;
267  padding: var(--spacing-lg);
268  border-radius: var(--border-radius);
269  box-shadow: var(--shadow-md);
270  text-align: center;
271  transition: var(--transition);
272}
273
274.feature-card:hover {
275  transform: translateY(-8px);
276  box-shadow: var(--shadow-lg);
277}
278
279.feature-icon {
280  font-size: 4rem;
281  margin-bottom: var(--spacing-md);
282}
283
284.feature-card h3 {
285  font-size: var(--font-size-xl);
286  margin-bottom: var(--spacing-sm);
287  color: var(--color-secondary);
288}
289
290/* Container query dla feature cards */
291@container (max-width: 300px) {
292  .feature-card {
293    padding: var(--spacing-md);
294  }
295
296  .feature-icon {
297    font-size: 3rem;
298  }
299}
300
301/* ==========================================
302   TOURS SECTION
303   ========================================== */
304.tours {
305  padding: var(--spacing-xl) 0;
306  background: var(--color-bg-alt);
307}
308
309.tours-grid {
310  display: grid;
311  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
312  gap: var(--spacing-lg);
313}
314
315.tour-card {
316  background: white;
317  border-radius: var(--border-radius);
318  overflow: hidden;
319  box-shadow: var(--shadow-md);
320  transition: var(--transition);
321  container-type: inline-size;
322}
323
324.tour-card:hover {
325  transform: translateY(-8px);
326  box-shadow: var(--shadow-lg);
327}
328
329.tour-card--featured {
330  grid-column: span 2;
331}
332
333@media (max-width: 768px) {
334  .tour-card--featured {
335    grid-column: span 1;
336  }
337}
338
339.tour-image {
340  position: relative;
341  aspect-ratio: 16 / 9;
342  overflow: hidden;
343}
344
345.tour-image img {
346  width: 100%;
347  height: 100%;
348  object-fit: cover;
349  transition: var(--transition);
350}
351
352.tour-card:hover .tour-image img {
353  transform: scale(1.1);
354}
355
356.tour-badge {
357  position: absolute;
358  top: var(--spacing-md);
359  right: var(--spacing-md);
360  background: var(--color-accent);
361  color: white;
362  padding: var(--spacing-xs) var(--spacing-md);
363  border-radius: 50px;
364  font-weight: 600;
365  font-size: 0.875rem;
366}
367
368.tour-content {
369  padding: var(--spacing-lg);
370}
371
372.tour-content h3 {
373  font-size: var(--font-size-2xl);
374  margin-bottom: var(--spacing-sm);
375  color: var(--color-secondary);
376}
377
378.tour-duration {
379  color: var(--color-text-light);
380  margin-bottom: var(--spacing-sm);
381}
382
383.tour-highlights {
384  margin: var(--spacing-md) 0;
385}
386
387.tour-highlights li {
388  padding: var(--spacing-xs) 0;
389  color: var(--color-text);
390}
391
392.tour-footer {
393  display: flex;
394  justify-content: space-between;
395  align-items: center;
396  margin-top: var(--spacing-md);
397  padding-top: var(--spacing-md);
398  border-top: 1px solid var(--color-bg-alt);
399}
400
401.tour-price {
402  display: flex;
403  align-items: baseline;
404  gap: var(--spacing-xs);
405}
406
407.price-amount {
408  font-size: var(--font-size-3xl);
409  font-weight: 700;
410  color: var(--color-primary);
411}
412
413.price-from,
414.price-currency {
415  font-size: var(--font-size-base);
416  color: var(--color-text-light);
417}
418
419/* ==========================================
420   TESTIMONIALS SECTION
421   ========================================== */
422.testimonials {
423  padding: var(--spacing-xl) 0;
424  background: var(--color-bg);
425}
426
427.testimonials-grid {
428  display: grid;
429  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
430  gap: var(--spacing-lg);
431}
432
433.testimonial-card {
434  background: white;
435  padding: var(--spacing-lg);
436  border-radius: var(--border-radius);
437  box-shadow: var(--shadow-md);
438  border-left: 4px solid var(--color-primary);
439}
440
441.testimonial-rating {
442  font-size: var(--font-size-lg);
443  margin-bottom: var(--spacing-md);
444}
445
446.testimonial-text {
447  font-size: var(--font-size-lg);
448  line-height: 1.8;
449  margin-bottom: var(--spacing-md);
450  font-style: italic;
451  color: var(--color-text);
452}
453
454.testimonial-author {
455  display: flex;
456  flex-direction: column;
457  gap: var(--spacing-xs);
458  font-style: normal;
459}
460
461.testimonial-author strong {
462  color: var(--color-secondary);
463  font-size: var(--font-size-lg);
464}
465
466.testimonial-author span {
467  color: var(--color-text-light);
468}
469
470/* ==========================================
471   CONTACT SECTION
472   ========================================== */
473.contact {
474  padding: var(--spacing-xl) 0;
475  background: var(--color-bg-alt);
476}
477
478.contact-grid {
479  display: grid;
480  grid-template-columns: 1fr 1fr;
481  gap: var(--spacing-xl);
482}
483
484@media (max-width: 768px) {
485  .contact-grid {
486    grid-template-columns: 1fr;
487  }
488}
489
490.contact-info h2 {
491  font-size: var(--font-size-3xl);
492  margin-bottom: var(--spacing-md);
493  color: var(--color-secondary);
494}
495
496.contact-details {
497  margin-top: var(--spacing-lg);
498}
499
500.contact-item {
501  display: flex;
502  gap: var(--spacing-md);
503  margin-bottom: var(--spacing-md);
504}
505
506.contact-icon {
507  font-size: var(--font-size-2xl);
508}
509
510.contact-form {
511  background: white;
512  padding: var(--spacing-lg);
513  border-radius: var(--border-radius);
514  box-shadow: var(--shadow-md);
515}
516
517.form-group {
518  margin-bottom: var(--spacing-md);
519}
520
521.form-group label {
522  display: block;
523  margin-bottom: var(--spacing-xs);
524  font-weight: 600;
525  color: var(--color-secondary);
526}
527
528.form-group input,
529.form-group select,
530.form-group textarea {
531  width: 100%;
532  padding: var(--spacing-sm);
533  border: 2px solid var(--color-bg-alt);
534  border-radius: var(--border-radius);
535  font-family: inherit;
536  font-size: var(--font-size-base);
537  transition: var(--transition);
538}
539
540.form-group input:focus,
541.form-group select:focus,
542.form-group textarea:focus {
543  outline: none;
544  border-color: var(--color-primary);
545}
546
547/* ==========================================
548   FOOTER
549   ========================================== */
550.footer {
551  background: var(--color-secondary);
552  color: white;
553  padding: var(--spacing-xl) 0 var(--spacing-md);
554}
555
556.footer-grid {
557  display: grid;
558  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
559  gap: var(--spacing-lg);
560  margin-bottom: var(--spacing-lg);
561}
562
563.footer-section h3,
564.footer-section h4 {
565  margin-bottom: var(--spacing-md);
566  color: var(--color-primary);
567}
568
569.footer-section ul li {
570  margin-bottom: var(--spacing-xs);
571}
572
573.footer-section a:hover {
574  color: var(--color-primary);
575}
576
577.social-links {
578  display: flex;
579  gap: var(--spacing-md);
580  margin-top: var(--spacing-md);
581  font-size: var(--font-size-2xl);
582}
583
584.footer-bottom {
585  text-align: center;
586  padding-top: var(--spacing-md);
587  border-top: 1px solid rgba(255, 255, 255, 0.1);
588  color: rgba(255, 255, 255, 0.7);
589}
590
591/* ==========================================
592   RESPONSIVE DESIGN
593   ========================================== */
594@media (max-width: 768px) {
595  .hero {
596    grid-template-columns: 1fr;
597    text-align: center;
598  }
599
600  .hero-title {
601    font-size: var(--font-size-3xl);
602  }
603
604  .pyramid-illustration {
605    width: 250px;
606    height: 250px;
607  }
608
609  .hero-cta {
610    justify-content: center;
611  }
612
613  .nav-list {
614    flex-wrap: wrap;
615    gap: var(--spacing-sm);
616  }
617}

Zwróć uwagę na trzy miejsca. Siatka features jest kontenerem, więc karty zmieniają odstępy przez @container. Wyróżniona wycieczka zajmuje dwie kolumny, a poniżej 768px wraca do jednej. Zdjęcia wycieczek trzymają proporcje dzięki aspect-ratio: 16 / 9.

3. Oko Horusa: selektor :has()

Brakuje jeszcze jednego narzędzia. Pseudoklasa :has() wybiera element, jeśli zawiera określone dziecko, więc rodzic może reagować na to, co ma w środku, jak oko Horusa zaglądające do wnętrza komnaty:

1/* Karta z plakietką (np. Bestseller) dostaje złotą obwódkę */
2.tour-card:has(.tour-badge) {
3  outline: 3px solid var(--color-primary);
4}
5
6/* Etykieta pola, które użytkownik wypełnił błędnie */
7.form-group:has(input:user-invalid) label {
8  color: #c0392b;
9}
10
11/* Artykuł, którego bezpośrednim dzieckiem jest obraz */
12article:has(> img) {
13  border: 2px solid gold;
14}

Obwódkę dostanie tylko karta z plakietką Bestseller, bo pozostałe jej nie mają. :user-invalid zadziała dopiero po interakcji z polem, więc pusty formularz nie świeci na czerwono od progu. Zapis > img oznacza bezpośrednie dziecko, a article:not(:has(img, video)) wybierze artykuły bez zdjęć i filmów. :has() działa we wszystkich głównych przeglądarkach od końca 2023 roku.

Wskazówki implementacji

1. Performance

Obrazy poniżej pierwszego ekranu ładuj leniwie, a kluczowe zasoby zapowiadaj przeglądarce wcześniej:

1<!-- Lazy loading dla obrazów -->
2<img src="image.jpg" alt="Description" loading="lazy">
3
4<!-- Preload dla kluczowych zasobów -->
5<link rel="preload" as="image" href="hero-image.jpg">

Uważaj na jedno: obrazu w sekcji hero nie ładuj leniwie, bo widać go od razu i opóźniłbyś jego wyświetlenie. Właśnie dla niego jest preload.

2. Accessibility

Semantyczne znaczniki i etykiety ARIA pomagają czytnikom ekranu zrozumieć stronę:

1<!-- Używaj semantycznych znaczników -->
2<nav role="navigation" aria-label="Główna nawigacja">
3<main role="main">
4<footer role="contentinfo">
5
6<!-- Dodaj aria-labels tam gdzie potrzeba -->
7<button aria-label="Zamknij modal">×</button>

Role w tym przykładzie są nadmiarowe, bo nav, main i footer mają je domyślnie. Zasada brzmi: nie dodawaj roli, którą element już ma. aria-label jest za to niezbędny przy przyciskach z samą ikoną.

3. SEO

Znaczniki meta opisują stronę wyszukiwarkom i serwisom społecznościowym:

1<!-- Meta tags dla SEO -->
2<meta name="description" content="Opis strony">
3<meta name="keywords" content="egipt, wycieczki, piramidy">
4<meta property="og:title" content="Egipska Przygoda">
5<meta property="og:description" content="Najlepsze wycieczki do Egiptu">
6<meta property="og:image" content="og-image.jpg">

Opis z description często trafia do wyników wyszukiwania, a znaczniki Open Graph budują podgląd linku. keywords możesz pominąć, bo Google nie bierze go pod uwagę od 2009 roku.

Podsumowanie

Gratulacje, młody budowniczy! Stworzyłeś kompletną, profesjonalną stronę landing page, która łączy:

HTML semantyczny - dostępna struktura CSS Grid & Flexbox - zaawansowane layouty Container Queries - responsywne komponenty Modern CSS - najnowsze funkcje Animacje - płynne efekty Performance - optymalizacja

Podobnie jak budowa Wielkiej Piramidy w Gizie była kulminacją wiedzy starożytnych budowniczych, tak Twój projekt końcowy jest syntezą wszystkiego, czego nauczyłeś się w tym module! W zadaniu końcowym zbudujesz jeszcze egipski dashboard, w którym połączysz Container Queries, :has(), zagnieżdżanie, @layer, właściwości logiczne, clamp(), aspect-ratio, :is() i :where(), scroll-snap, color-mix(), @supports oraz accent-color. Moja rada: dodawaj je po jednej i po każdej sprawdzaj stronę w dwóch przeglądarkach. W piaskownicy poniżej znajdziesz egipski dashboard, na którym przećwiczysz te techniki.

Kod do tej lekcji: index.html
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>Master Architecture Dashboard Totha</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <div class="egyptian-container">
11        <header class="pharaoh-header">
12            <div class="header-content">
13                <span class="ankh-symbol"></span>
14                <h1 class="pharaoh-title">Master Architecture Dashboard Totha</h1>
15                <span class="eye-of-horus">𓂀</span>
16            </div>
17            <p class="header-subtitle">Summa całej mądrości architektury oprogramowania z Nekropolii w Gizie</p>
18        </header>
19
20        <!-- Clean Architecture Layers -->
21        <section class="pyramid-section">
22            <h2 class="section-title">
23                <span class="hieroglyph"></span>
24                Clean Architecture - Hierarchia Świątyni
25            </h2>
26            <div class="pyramid-diagram">
27                <div class="pyramid-layer pyramid-layer--entities">
28                    <div class="layer-content">
29                        <h3>Entities</h3>
30                        <p>Encje Biznesowe</p>
31                        <span class="layer-icon"></span>
32                    </div>
33                </div>
34                <div class="pyramid-layer pyramid-layer--usecases">
35                    <div class="layer-content">
36                        <h3>Use Cases</h3>
37                        <p>Przypadki Użycia</p>
38                        <span class="layer-icon"></span>
39                    </div>
40                </div>
41                <div class="pyramid-layer pyramid-layer--adapters">
42                    <div class="layer-content">
43                        <h3>Interface Adapters</h3>
44                        <p>Adaptery Interfejsów</p>
45                        <span class="layer-icon"></span>
46                    </div>
47                </div>
48                <div class="pyramid-layer pyramid-layer--frameworks">
49                    <div class="layer-content">
50                        <h3>Frameworks & Drivers</h3>
51                        <p>Frameworki i Sterowniki</p>
52                        <span class="layer-icon"></span>
53                    </div>
54                </div>
55            </div>
56        </section>
57
58        <!-- Design Patterns Categories -->
59        <section class="papyrus-section">
60            <h2 class="section-title">
61                <span class="hieroglyph"></span>
62                Wzorce Projektowe - Zwoje Mądrości
63            </h2>
64            <div class="patterns-grid">
65                <article class="pattern-category pattern-category--creational">
66                    <div class="pattern-header">
67                        <h3>Creational Patterns</h3>
68                        <span class="pattern-icon"></span>
69                    </div>
70                    <ul class="pattern-list">
71                        <li class="pattern-item">Singleton</li>
72                        <li class="pattern-item">Factory</li>
73                        <li class="pattern-item">Builder</li>
74                        <li class="pattern-item">Prototype</li>
75                    </ul>
76                </article>
77
78                <article class="pattern-category pattern-category--structural">
79                    <div class="pattern-header">
80                        <h3>Structural Patterns</h3>
81                        <span class="pattern-icon"></span>
82                    </div>
83                    <ul class="pattern-list">
84                        <li class="pattern-item">Adapter</li>
85                        <li class="pattern-item">Decorator</li>
86                        <li class="pattern-item">Facade</li>
87                        <li class="pattern-item">Proxy</li>
88                    </ul>
89                </article>
90
91                <article class="pattern-category pattern-category--behavioral">
92                    <div class="pattern-header">
93                        <h3>Behavioral Patterns</h3>
94                        <span class="pattern-icon"></span>
95                    </div>
96                    <ul class="pattern-list">
97                        <li class="pattern-item">Observer</li>
98                        <li class="pattern-item">Strategy</li>
99                        <li class="pattern-item">Command</li>
100                        <li class="pattern-item">State</li>
101                    </ul>
102                </article>
103
104                <article class="pattern-category pattern-category--architectural">
105                    <div class="pattern-header">
106                        <h3>Architectural Patterns</h3>
107                        <span class="pattern-icon"></span>
108                    </div>
109                    <ul class="pattern-list">
110                        <li class="pattern-item">MVC</li>
111                        <li class="pattern-item">MVVM</li>
112                        <li class="pattern-item">Hexagonal</li>
113                        <li class="pattern-item">Layered</li>
114                    </ul>
115                </article>
116            </div>
117        </section>
118
119        <!-- Microservices Patterns -->
120        <section class="obelisk-section">
121            <h2 class="section-title">
122                <span class="hieroglyph"></span>
123                Mikrousługi - Obeliski Imperium
124            </h2>
125            <div class="microservices-grid">
126                <div class="microservice-card microservice-card--gateway">
127                    <div class="card-icon"></div>
128                    <h3>API Gateway</h3>
129                    <p>Brama Pałacu</p>
130                    <div class="card-status status--active">Active</div>
131                </div>
132
133                <div class="microservice-card microservice-card--discovery">
134                    <div class="card-icon"></div>
135                    <h3>Service Discovery</h3>
136                    <p>Mapa Imperium</p>
137                    <div class="card-status status--active">Active</div>
138                </div>
139
140                <div class="microservice-card microservice-card--breaker">
141                    <div class="card-icon"></div>
142                    <h3>Circuit Breaker</h3>
143                    <p>Strażnik Bramy</p>
144                    <div class="card-status status--active">Active</div>
145                </div>
146
147                <div class="microservice-card microservice-card--saga">
148                    <div class="card-icon"></div>
149                    <h3>Saga Pattern</h3>
150                    <p>Kroniki Wydarzeń</p>
151                    <div class="card-status status--active">Active</div>
152                </div>
153
154                <div class="microservice-card microservice-card--sourcing">
155                    <div class="card-icon">⏳</div>
156                    <h3>Event Sourcing</h3>
157                    <p>History Królestwa</p>
158                    <div class="card-status status--active">Active</div>
159                </div>
160
161                <div class="microservice-card microservice-card--cqrs">
162                    <div class="card-icon"></div>
163                    <h3>CQRS</h3>
164                    <p>Podział Władzy</p>
165                    <div class="card-status status--active">Active</div>
166                </div>
167            </div>
168        </section>
169
170        <!-- Performance Patterns -->
171        <section class="granary-section">
172            <h2 class="section-title">
173                <span class="hieroglyph"></span>
174                Performance - Magazyny Zbożowe
175            </h2>
176            <div class="performance-grid">
177                <div class="performance-metric">
178                    <div class="metric-header">
179                        <span class="metric-icon"></span>
180                        <h3>Caching</h3>
181                    </div>
182                    <div class="metric-strategies">
183                        <span class="strategy-badge">LRU</span>
184                        <span class="strategy-badge">TTL</span>
185                        <span class="strategy-badge">Write-through</span>
186                        <span class="strategy-badge">Cache-aside</span>
187                    </div>
188                    <div class="metric-bar">
189                        <div class="metric-fill metric-fill--high"></div>
190                    </div>
191                </div>
192
193                <div class="performance-metric">
194                    <div class="metric-header">
195                        <span class="metric-icon"></span>
196                        <h3>Load Balancing</h3>
197                    </div>
198                    <div class="metric-strategies">
199                        <span class="strategy-badge">Round Robin</span>
200                        <span class="strategy-badge">Least Conn</span>
201                        <span class="strategy-badge">IP Hash</span>
202                    </div>
203                    <div class="metric-bar">
204                        <div class="metric-fill metric-fill--high"></div>
205                    </div>
206                </div>
207
208                <div class="performance-metric">
209                    <div class="metric-header">
210                        <span class="metric-icon"></span>
211                        <h3>Database Sharding</h3>
212                    </div>
213                    <div class="metric-strategies">
214                        <span class="strategy-badge">Geographic</span>
215                        <span class="strategy-badge">Hash-based</span>
216                        <span class="strategy-badge">Range-based</span>
217                    </div>
218                    <div class="metric-bar">
219                        <div class="metric-fill metric-fill--medium"></div>
220                    </div>
221                </div>
222
223                <div class="performance-metric">
224                    <div class="metric-header">
225                        <span class="metric-icon"></span>
226                        <h3>Optimization</h3>
227                    </div>
228                    <div class="metric-strategies">
229                        <span class="strategy-badge">Profiling</span>
230                        <span class="strategy-badge">Indexing</span>
231                        <span class="strategy-badge">Compression</span>
232                    </div>
233                    <div class="metric-bar">
234                        <div class="metric-fill metric-fill--high"></div>
235                    </div>
236                </div>
237            </div>
238        </section>
239
240        <!-- Security Layers -->
241        <section class="fortress-section">
242            <h2 class="section-title">
243                <span class="hieroglyph"></span>
244                Security - Fortyfikacje Faraona
245            </h2>
246            <div class="security-layers">
247                <div class="security-layer security-layer--authentication">
248                    <div class="layer-number">1</div>
249                    <div class="layer-details">
250                        <h3>Authentication</h3>
251                        <p>Uwierzytelnienie tożsamości</p>
252                        <div class="security-badges">
253                            <span class="sec-badge">JWT</span>
254                            <span class="sec-badge">OAuth 2.0</span>
255                            <span class="sec-badge">SSO</span>
256                        </div>
257                    </div>
258                </div>
259
260                <div class="security-layer security-layer--authorization">
261                    <div class="layer-number">2</div>
262                    <div class="layer-details">
263                        <h3>Authorization</h3>
264                        <p>Kontrola dostępu i uprawnień</p>
265                        <div class="security-badges">
266                            <span class="sec-badge">RBAC</span>
267                            <span class="sec-badge">ABAC</span>
268                            <span class="sec-badge">ACL</span>
269                        </div>
270                    </div>
271                </div>
272
273                <div class="security-layer security-layer--validation">
274                    <div class="layer-number">3</div>
275                    <div class="layer-details">
276                        <h3>Input Validation</h3>
277                        <p>Walidacja i sanityzacja danych</p>
278                        <div class="security-badges">
279                            <span class="sec-badge">XSS Protection</span>
280                            <span class="sec-badge">SQL Injection</span>
281                            <span class="sec-badge">CSRF</span>
282                        </div>
283                    </div>
284                </div>
285
286                <div class="security-layer security-layer--encryption">
287                    <div class="layer-number">4</div>
288                    <div class="layer-details">
289                        <h3>Data Encryption</h3>
290                        <p>Encryption danych</p>
291                        <div class="security-badges">
292                            <span class="sec-badge">TLS/SSL</span>
293                            <span class="sec-badge">AES-256</span>
294                            <span class="sec-badge">RSA</span>
295                        </div>
296                    </div>
297                </div>
298
299                <div class="security-layer security-layer--monitoring">
300                    <div class="layer-number">5</div>
301                    <div class="layer-details">
302                        <h3>Audit & Monitoring</h3>
303                        <p>Logowanie i monitoring nopieczeństwa</p>
304                        <div class="security-badges">
305                            <span class="sec-badge">SIEM</span>
306                            <span class="sec-badge">IDS/IPS</span>
307                            <span class="sec-badge">Alerting</span>
308                        </div>
309                    </div>
310                </div>
311            </div>
312        </section>
313
314        <!-- Footer with blessing -->
315        <footer class="pharaoh-footer">
316            <div class="footer-content">
317                <p class="blessing">
318                    <span class="ankh-symbol"></span>
319                    Błogosławieństwo Totha - Boga Mądrości i Pisma
320                    <span class="ankh-symbol"></span>
321                </p>
322                <p class="footer-quote">
323                    "Architektura godna pyramids Gizy - trwała i monumentalna przez tysiąclecia"
324                </p>
325            </div>
326        </footer>
327    </div>
328</body>
329</html>

Pamiętaj: To dopiero początek Twojej podróży! Te fundamenty pozwolą Ci budować coraz większe i bardziej zaawansowane projekty. Powodzenia, młody faraonie internetu!

Sprawdź się

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

  1. 1. Co robi pseudo-klasa :has() w CSS?

Zadania praktyczne w grze

  • Klikanie w kolejności

    Ułóż składnię selektora :has() dla stylowania rodzica zawierającego obrazek:

  • Edytor kodu

    Stwórz: article:has(img) { border: 2px solid gold }, article:has(video) { background: #1a1a2e }, article:not(:has(img, video)) { opacity: 0.7 }. Testuj z różną zawartością - rodzic reaguje na dzieci!

Przydatne artykuły