Kurs HTML i CSS · Moduł 9: Nowoczesny CSS
Projekt Końcowy: Responsywna Strona Landing Page "Egipska Przygoda"
W tej lekcji5
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ć:
- HTML semantyczny - dostępna struktura
- CSS Grid i Flexbox - zaawansowane layouty
- Container Queries - responsywne komponenty
- Modern CSS - najnowsze funkcje CSS
- Animacje - płynne przejścia i efekty
- Performance - optymalizacja i najlepsze praktyki
Struktura projektu
Sekcje strony:
- Hero Section - główny baner z CTA (Call To Action)
- Features - karty z features używające container queries
- Tours - galeria wycieczek w CSS Grid
- Testimonials - opinie klientów z carousel
- Pricing - sekcja cenowa
- Contact Form - formularz kontaktowy
- 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:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Odkryj tajemnice starożytnego Egiptu z Egipską Przygodą - najlepsza agencja turystyczna">
<title>Egipska Przygoda - Wycieczki do Starożytnego Egiptu</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Navigation -->
<header class="header" role="banner">
<nav class="nav" role="navigation" aria-label="Główna nawigacja">
<div class="container">
<a href="#" class="logo">
<span class="logo-icon">◆</span>
<span class="logo-text">Egipska Przygoda</span>
</a>
<ul class="nav-list">
<li><a href="#features">Dlaczego my</a></li>
<li><a href="#tours">Wycieczki</a></li>
<li><a href="#testimonials">Opinie</a></li>
<li><a href="#pricing">Cennik</a></li>
<li><a href="#contact" class="btn-primary">Kontakt</a></li>
</ul>
</div>
</nav>
</header>
<!-- Hero Section -->
<section class="hero" id="hero">
<div class="hero-content">
<h1 class="hero-title">
Odkryj Tajemnice<br>
<span class="gradient-text">Starożytnego Egiptu</span>
</h1>
<p class="hero-subtitle">
Przenieś się w czasie o 5000 lat wstecz i poznaj cuda faraonów,
piramid i hieroglifów w niezapomnianej podróży życia!
</p>
<div class="hero-cta">
<a href="#tours" class="btn btn-large btn-primary">
Przeglądaj Wycieczki
</a>
<a href="#contact" class="btn btn-large btn-secondary">
Skontaktuj się z nami
</a>
</div>
</div>
<div class="hero-image">
<div class="pyramid-illustration"></div>
</div>
</section>
<!-- Features -->
<section class="features" id="features">
<div class="container">
<h2 class="section-title">Dlaczego Egipska Przygoda?</h2>
<p class="section-subtitle">
Jesteśmy liderami w organizacji wycieczek do Egiptu od 20 lat
</p>
<div class="features-grid">
<article class="feature-card">
<div class="feature-icon">◆</div>
<h3>Ekskluzywny Dostęp</h3>
<p>Dostęp do miejsc niedostępnych dla zwykłych turystów,
w tym prywatne zwiedzanie komnat piramid</p>
</article>
<article class="feature-card">
<div class="feature-icon">◆</div>
<h3>Eksperci Egiptologii</h3>
<p>Przewodnicy z tytułami naukowymi, pasjonaci historii
starożytnego Egiptu</p>
</article>
<article class="feature-card">
<div class="feature-icon">◆</div>
<h3>Luksusowe Hotele</h3>
<p>Noclegi w najlepszych hotelach 5*, z widokiem na Nil
i piramidy</p>
</article>
<article class="feature-card">
<div class="feature-icon">◆</div>
<h3>Autentyczna Kuchnia</h3>
<p>Degustacja tradycyjnych egipskich potraw przygotowanych
przez lokalnych szefów kuchni</p>
</article>
</div>
</div>
</section>
<!-- Tours Grid -->
<section class="tours" id="tours">
<div class="container">
<h2 class="section-title">Nasze Wycieczki</h2>
<p class="section-subtitle">
Wybierz wycieczkę dopasowaną do Twoich marzeń
</p>
<div class="tours-grid">
<article class="tour-card tour-card--featured">
<div class="tour-image">
<img src="/api/placeholder/600/400" alt="Piramidy w Gizie">
<span class="tour-badge">Bestseller</span>
</div>
<div class="tour-content">
<h3>Klasyczny Egipt</h3>
<p class="tour-duration">⏱7 dni / 6 nocy</p>
<p class="tour-description">
Kairo, Giza, Luksor, Dolina Królów, rejs po Nilu
</p>
<ul class="tour-highlights">
<li>Wielka Piramida w Gizie</li>
<li>Muzeum Egipskie w Kairze</li>
<li>Świątynia Karnak</li>
<li>Rejs statkiem po Nilu</li>
</ul>
<div class="tour-footer">
<div class="tour-price">
<span class="price-from">od</span>
<span class="price-amount">4,999</span>
<span class="price-currency">PLN</span>
</div>
<a href="#contact" class="btn btn-primary">Rezerwuj</a>
</div>
</div>
</article>
<article class="tour-card">
<div class="tour-image">
<img src="/api/placeholder/600/400" alt="Abu Simbel">
</div>
<div class="tour-content">
<h3>Egipskie Cuda</h3>
<p class="tour-duration">⏱10 dni / 9 nocy</p>
<p class="tour-description">
Wszystkie główne atrakcje + Abu Simbel i Alexandria
</p>
<ul class="tour-highlights">
<li>Wszystko z Klasycznego Egiptu</li>
<li>Świątynie Abu Simbel</li>
<li>Biblioteka Aleksandryjska</li>
<li>Oaza Siwa</li>
</ul>
<div class="tour-footer">
<div class="tour-price">
<span class="price-from">od</span>
<span class="price-amount">7,499</span>
<span class="price-currency">PLN</span>
</div>
<a href="#contact" class="btn btn-primary">Rezerwuj</a>
</div>
</div>
</article>
<article class="tour-card">
<div class="tour-image">
<img src="/api/placeholder/600/400" alt="Nurkowanie w Morzu Czerwonym">
</div>
<div class="tour-content">
<h3>Egipt + Morze Czerwone</h3>
<p class="tour-duration">⏱12 dni / 11 nocy</p>
<p class="tour-description">
Historia + relaks i nurkowanie w Hurghadzie
</p>
<ul class="tour-highlights">
<li>Zwiedzanie zabytków</li>
<li>5 dni all inclusive w Hurghadzie</li>
<li>Nurkowanie z rafa koralowa</li>
<li>Safari na pustyni</li>
</ul>
<div class="tour-footer">
<div class="tour-price">
<span class="price-from">od</span>
<span class="price-amount">6,299</span>
<span class="price-currency">PLN</span>
</div>
<a href="#contact" class="btn btn-primary">Rezerwuj</a>
</div>
</div>
</article>
</div>
</div>
</section>
<!-- Testimonials -->
<section class="testimonials" id="testimonials">
<div class="container">
<h2 class="section-title">Co Mówią Nasi Klienci</h2>
<div class="testimonials-grid">
<blockquote class="testimonial-card">
<div class="testimonial-rating">5/5</div>
<p class="testimonial-text">
"Niesamowite przeżycie! Przewodnicy byli niezwykle kompetentni,
a organizacja wycieczki na najwyższym poziomie. Piramidy na żywo
robią wrażenie tysiąckrotnie większe niż na zdjęciach!"
</p>
<cite class="testimonial-author">
<strong>Anna Kowalska</strong>
<span>Warszawa</span>
</cite>
</blockquote>
<blockquote class="testimonial-card">
<div class="testimonial-rating">5/5</div>
<p class="testimonial-text">
"Jako egiptolog byłem sceptyczny, ale ta wycieczka przekroczyła
moje oczekiwania. Dostęp do miejsc, których nie pokazują w
standardowych wycieczpach, był bezcenny!"
</p>
<cite class="testimonial-author">
<strong>Dr. Jan Nowak</strong>
<span>Kraków</span>
</cite>
</blockquote>
<blockquote class="testimonial-card">
<div class="testimonial-rating">5/5</div>
<p class="testimonial-text">
"Podróż życia! Szczególnie zachwycił mnie rejs po Nilu i
zwiedzanie Doliny Królów. Wszystko było perfekcyjnie zorganizowane."
</p>
<cite class="testimonial-author">
<strong>Maria Wiśniewska</strong>
<span>Gdańsk</span>
</cite>
</blockquote>
</div>
</div>
</section>
<!-- Contact Form -->
<section class="contact" id="contact">
<div class="container">
<div class="contact-grid">
<div class="contact-info">
<h2>Zarezerwuj Swoją Przygodę</h2>
<p>Wypełnij formularz, a nasz konsultant skontaktuje się z Tobą
w ciągu 24 godzin!</p>
<div class="contact-details">
<div class="contact-item">
<span class="contact-icon">◆</span>
<div>
<strong>Telefon</strong>
<p>+48 123 456 789</p>
</div>
</div>
<div class="contact-item">
<span class="contact-icon">◆</span>
<div>
<strong>Email</strong>
<p>kontakt@egipskaprzygoda.pl</p>
</div>
</div>
<div class="contact-item">
<span class="contact-icon">◆</span>
<div>
<strong>Adres</strong>
<p>ul. Piramid 123, 00-001 Warszawa</p>
</div>
</div>
</div>
</div>
<form class="contact-form" action="#" method="POST">
<div class="form-group">
<label for="name">Imię i nazwisko</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="phone">Telefon</label>
<input type="tel" id="phone" name="phone" required>
</div>
<div class="form-group">
<label for="tour">Wybierz wycieczkę</label>
<select id="tour" name="tour" required>
<option value="">Wybierz...</option>
<option value="classic">Klasyczny Egipt - 7 dni</option>
<option value="wonders">Egipskie Cuda - 10 dni</option>
<option value="red-sea">Egipt + Morze Czerwone - 12 dni</option>
</select>
</div>
<div class="form-group">
<label for="message">Wiadomość (opcjonalnie)</label>
<textarea id="message" name="message" rows="4"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-large">
Wyślij zapytanie
</button>
</form>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h3>Egipska Przygoda</h3>
<p>Odkrywamy tajemnice starożytnego Egiptu od 2004 roku</p>
<div class="social-links">
<a href="#" aria-label="Facebook">Facebook</a>
<a href="#" aria-label="Instagram">Instagram</a>
<a href="#" aria-label="Twitter">Twitter</a>
<a href="#" aria-label="YouTube">YouTube</a>
</div>
</div>
<div class="footer-section">
<h4>Wycieczki</h4>
<ul>
<li><a href="#">Klasyczny Egipt</a></li>
<li><a href="#">Egipskie Cuda</a></li>
<li><a href="#">Egipt + Morze Czerwone</a></li>
<li><a href="#">Wycieczki grupowe</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Informacje</h4>
<ul>
<li><a href="#">O nas</a></li>
<li><a href="#">Regulamin</a></li>
<li><a href="#">Polityka prywatności</a></li>
<li><a href="#">FAQ</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Kontakt</h4>
<ul>
<li>+48 123 456 789</li>
<li>kontakt@egipskaprzygoda.pl</li>
<li>ul. Piramid 123, Warszawa</li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>© 2024 Egipska Przygoda. Wszystkie prawa zastrzeżone.</p>
</div>
</div>
</footer>
</body>
</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:
/* ==========================================
CSS RESET & BASE STYLES
========================================== */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
/* Colors - Egyptian theme */
--color-primary: #D4AF37; /* Gold */
--color-primary-dark: #B8941F;
--color-secondary: #2C3E50; /* Dark blue */
--color-accent: #E67E22; /* Orange */
--color-bg: #FFFFFF;
--color-bg-alt: #F8F9FA;
--color-text: #2C3E50;
--color-text-light: #6C757D;
/* Spacing */
--spacing-xs: 0.5rem;
--spacing-sm: 1rem;
--spacing-md: 2rem;
--spacing-lg: 4rem;
--spacing-xl: 6rem;
/* Typography */
--font-primary: 'Segoe UI', system-ui, sans-serif;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 2rem;
--font-size-4xl: 3rem;
/* Layout */
--container-max-width: 1200px;
--border-radius: 12px;
/* Shadows */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
/* Transitions */
--transition: all 0.3s ease;
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-primary);
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--color-text);
background: var(--color-bg);
}
img {
max-width: 100%;
height: auto;
display: block;
}
a {
color: inherit;
text-decoration: none;
}
ul {
list-style: none;
}
/* ==========================================
UTILITIES
========================================== */
.container {
max-width: var(--container-max-width);
margin: 0 auto;
padding: 0 var(--spacing-md);
}
.section-title {
font-size: var(--font-size-3xl);
font-weight: 700;
text-align: center;
margin-bottom: var(--spacing-sm);
color: var(--color-secondary);
}
.section-subtitle {
font-size: var(--font-size-lg);
text-align: center;
color: var(--color-text-light);
margin-bottom: var(--spacing-lg);
}
.gradient-text {
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Buttons */
.btn {
display: inline-block;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
font-weight: 600;
text-align: center;
cursor: pointer;
transition: var(--transition);
border: 2px solid transparent;
}
.btn-primary {
background: var(--color-primary);
color: var(--color-secondary);
}
.btn-primary:hover {
background: var(--color-primary-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.btn-secondary {
background: transparent;
border-color: var(--color-primary);
color: var(--color-primary);
}
.btn-secondary:hover {
background: var(--color-primary);
color: var(--color-secondary);
}
.btn-large {
padding: var(--spacing-md) var(--spacing-lg);
font-size: var(--font-size-lg);
}
/* ==========================================
HEADER & NAVIGATION
========================================== */
.header {
position: sticky;
top: 0;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(10px);
box-shadow: var(--shadow-sm);
z-index: 1000;
}
.nav .container {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: var(--spacing-sm);
padding-bottom: var(--spacing-sm);
}
.logo {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-xl);
font-weight: 700;
color: var(--color-secondary);
}
.logo-icon {
font-size: var(--font-size-2xl);
}
.nav-list {
display: flex;
gap: var(--spacing-md);
align-items: center;
}
.nav-list a {
transition: var(--transition);
}
.nav-list a:not(.btn-primary):hover {
color: var(--color-primary);
}
/* ==========================================
HERO SECTION
========================================== */
.hero {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-lg);
align-items: center;
min-height: calc(100vh - 80px);
padding: var(--spacing-xl) var(--spacing-md);
background: linear-gradient(135deg, #F8F9FA 0%, #E9ECEF 100%);
}
.hero-content {
max-width: 600px;
}
.hero-title {
font-size: var(--font-size-4xl);
font-weight: 800;
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
.hero-subtitle {
font-size: var(--font-size-xl);
color: var(--color-text-light);
margin-bottom: var(--spacing-lg);
}
.hero-cta {
display: flex;
gap: var(--spacing-md);
flex-wrap: wrap;
}
.hero-image {
display: flex;
justify-content: center;
align-items: center;
}
.pyramid-illustration {
width: 400px;
height: 400px;
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
/* ==========================================
FEATURES SECTION
========================================== */
.features {
padding: var(--spacing-xl) 0;
background: var(--color-bg);
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--spacing-lg);
container-type: inline-size;
}
.feature-card {
background: white;
padding: var(--spacing-lg);
border-radius: var(--border-radius);
box-shadow: var(--shadow-md);
text-align: center;
transition: var(--transition);
}
.feature-card:hover {
transform: translateY(-8px);
box-shadow: var(--shadow-lg);
}
.feature-icon {
font-size: 4rem;
margin-bottom: var(--spacing-md);
}
.feature-card h3 {
font-size: var(--font-size-xl);
margin-bottom: var(--spacing-sm);
color: var(--color-secondary);
}
/* Container query dla feature cards */
@container (max-width: 300px) {
.feature-card {
padding: var(--spacing-md);
}
.feature-icon {
font-size: 3rem;
}
}
/* ==========================================
TOURS SECTION
========================================== */
.tours {
padding: var(--spacing-xl) 0;
background: var(--color-bg-alt);
}
.tours-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: var(--spacing-lg);
}
.tour-card {
background: white;
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: var(--shadow-md);
transition: var(--transition);
container-type: inline-size;
}
.tour-card:hover {
transform: translateY(-8px);
box-shadow: var(--shadow-lg);
}
.tour-card--featured {
grid-column: span 2;
}
@media (max-width: 768px) {
.tour-card--featured {
grid-column: span 1;
}
}
.tour-image {
position: relative;
aspect-ratio: 16 / 9;
overflow: hidden;
}
.tour-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: var(--transition);
}
.tour-card:hover .tour-image img {
transform: scale(1.1);
}
.tour-badge {
position: absolute;
top: var(--spacing-md);
right: var(--spacing-md);
background: var(--color-accent);
color: white;
padding: var(--spacing-xs) var(--spacing-md);
border-radius: 50px;
font-weight: 600;
font-size: 0.875rem;
}
.tour-content {
padding: var(--spacing-lg);
}
.tour-content h3 {
font-size: var(--font-size-2xl);
margin-bottom: var(--spacing-sm);
color: var(--color-secondary);
}
.tour-duration {
color: var(--color-text-light);
margin-bottom: var(--spacing-sm);
}
.tour-highlights {
margin: var(--spacing-md) 0;
}
.tour-highlights li {
padding: var(--spacing-xs) 0;
color: var(--color-text);
}
.tour-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: var(--spacing-md);
padding-top: var(--spacing-md);
border-top: 1px solid var(--color-bg-alt);
}
.tour-price {
display: flex;
align-items: baseline;
gap: var(--spacing-xs);
}
.price-amount {
font-size: var(--font-size-3xl);
font-weight: 700;
color: var(--color-primary);
}
.price-from,
.price-currency {
font-size: var(--font-size-base);
color: var(--color-text-light);
}
/* ==========================================
TESTIMONIALS SECTION
========================================== */
.testimonials {
padding: var(--spacing-xl) 0;
background: var(--color-bg);
}
.testimonials-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: var(--spacing-lg);
}
.testimonial-card {
background: white;
padding: var(--spacing-lg);
border-radius: var(--border-radius);
box-shadow: var(--shadow-md);
border-left: 4px solid var(--color-primary);
}
.testimonial-rating {
font-size: var(--font-size-lg);
margin-bottom: var(--spacing-md);
}
.testimonial-text {
font-size: var(--font-size-lg);
line-height: 1.8;
margin-bottom: var(--spacing-md);
font-style: italic;
color: var(--color-text);
}
.testimonial-author {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
font-style: normal;
}
.testimonial-author strong {
color: var(--color-secondary);
font-size: var(--font-size-lg);
}
.testimonial-author span {
color: var(--color-text-light);
}
/* ==========================================
CONTACT SECTION
========================================== */
.contact {
padding: var(--spacing-xl) 0;
background: var(--color-bg-alt);
}
.contact-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-xl);
}
@media (max-width: 768px) {
.contact-grid {
grid-template-columns: 1fr;
}
}
.contact-info h2 {
font-size: var(--font-size-3xl);
margin-bottom: var(--spacing-md);
color: var(--color-secondary);
}
.contact-details {
margin-top: var(--spacing-lg);
}
.contact-item {
display: flex;
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
}
.contact-icon {
font-size: var(--font-size-2xl);
}
.contact-form {
background: white;
padding: var(--spacing-lg);
border-radius: var(--border-radius);
box-shadow: var(--shadow-md);
}
.form-group {
margin-bottom: var(--spacing-md);
}
.form-group label {
display: block;
margin-bottom: var(--spacing-xs);
font-weight: 600;
color: var(--color-secondary);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: var(--spacing-sm);
border: 2px solid var(--color-bg-alt);
border-radius: var(--border-radius);
font-family: inherit;
font-size: var(--font-size-base);
transition: var(--transition);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--color-primary);
}
/* ==========================================
FOOTER
========================================== */
.footer {
background: var(--color-secondary);
color: white;
padding: var(--spacing-xl) 0 var(--spacing-md);
}
.footer-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.footer-section h3,
.footer-section h4 {
margin-bottom: var(--spacing-md);
color: var(--color-primary);
}
.footer-section ul li {
margin-bottom: var(--spacing-xs);
}
.footer-section a:hover {
color: var(--color-primary);
}
.social-links {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-md);
font-size: var(--font-size-2xl);
}
.footer-bottom {
text-align: center;
padding-top: var(--spacing-md);
border-top: 1px solid rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.7);
}
/* ==========================================
RESPONSIVE DESIGN
========================================== */
@media (max-width: 768px) {
.hero {
grid-template-columns: 1fr;
text-align: center;
}
.hero-title {
font-size: var(--font-size-3xl);
}
.pyramid-illustration {
width: 250px;
height: 250px;
}
.hero-cta {
justify-content: center;
}
.nav-list {
flex-wrap: wrap;
gap: var(--spacing-sm);
}
}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:
/* Karta z plakietką (np. Bestseller) dostaje złotą obwódkę */
.tour-card:has(.tour-badge) {
outline: 3px solid var(--color-primary);
}
/* Etykieta pola, które użytkownik wypełnił błędnie */
.form-group:has(input:user-invalid) label {
color: #c0392b;
}
/* Artykuł, którego bezpośrednim dzieckiem jest obraz */
article:has(> img) {
border: 2px solid gold;
}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:
<!-- Lazy loading dla obrazów -->
<img src="image.jpg" alt="Description" loading="lazy">
<!-- Preload dla kluczowych zasobów -->
<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ę:
<!-- Używaj semantycznych znaczników -->
<nav role="navigation" aria-label="Główna nawigacja">
<main role="main">
<footer role="contentinfo">
<!-- Dodaj aria-labels tam gdzie potrzeba -->
<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:
<!-- Meta tags dla SEO -->
<meta name="description" content="Opis strony">
<meta name="keywords" content="egipt, wycieczki, piramidy">
<meta property="og:title" content="Egipska Przygoda">
<meta property="og:description" content="Najlepsze wycieczki do Egiptu">
<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
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Master Architecture Dashboard Totha</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="egyptian-container">
<header class="pharaoh-header">
<div class="header-content">
<span class="ankh-symbol">𓋹</span>
<h1 class="pharaoh-title">Master Architecture Dashboard Totha</h1>
<span class="eye-of-horus">𓂀</span>
</div>
<p class="header-subtitle">Summa całej mądrości architektury oprogramowania z Nekropolii w Gizie</p>
</header>
<!-- Clean Architecture Layers -->
<section class="pyramid-section">
<h2 class="section-title">
<span class="hieroglyph">𓉐</span>
Clean Architecture - Hierarchia Świątyni
</h2>
<div class="pyramid-diagram">
<div class="pyramid-layer pyramid-layer--entities">
<div class="layer-content">
<h3>Entities</h3>
<p>Encje Biznesowe</p>
<span class="layer-icon">◆</span>
</div>
</div>
<div class="pyramid-layer pyramid-layer--usecases">
<div class="layer-content">
<h3>Use Cases</h3>
<p>Przypadki Użycia</p>
<span class="layer-icon">◆</span>
</div>
</div>
<div class="pyramid-layer pyramid-layer--adapters">
<div class="layer-content">
<h3>Interface Adapters</h3>
<p>Adaptery Interfejsów</p>
<span class="layer-icon">◆</span>
</div>
</div>
<div class="pyramid-layer pyramid-layer--frameworks">
<div class="layer-content">
<h3>Frameworks & Drivers</h3>
<p>Frameworki i Sterowniki</p>
<span class="layer-icon">◆</span>
</div>
</div>
</div>
</section>
<!-- Design Patterns Categories -->
<section class="papyrus-section">
<h2 class="section-title">
<span class="hieroglyph">𓉐</span>
Wzorce Projektowe - Zwoje Mądrości
</h2>
<div class="patterns-grid">
<article class="pattern-category pattern-category--creational">
<div class="pattern-header">
<h3>Creational Patterns</h3>
<span class="pattern-icon">◆</span>
</div>
<ul class="pattern-list">
<li class="pattern-item">Singleton</li>
<li class="pattern-item">Factory</li>
<li class="pattern-item">Builder</li>
<li class="pattern-item">Prototype</li>
</ul>
</article>
<article class="pattern-category pattern-category--structural">
<div class="pattern-header">
<h3>Structural Patterns</h3>
<span class="pattern-icon">◆</span>
</div>
<ul class="pattern-list">
<li class="pattern-item">Adapter</li>
<li class="pattern-item">Decorator</li>
<li class="pattern-item">Facade</li>
<li class="pattern-item">Proxy</li>
</ul>
</article>
<article class="pattern-category pattern-category--behavioral">
<div class="pattern-header">
<h3>Behavioral Patterns</h3>
<span class="pattern-icon">◆</span>
</div>
<ul class="pattern-list">
<li class="pattern-item">Observer</li>
<li class="pattern-item">Strategy</li>
<li class="pattern-item">Command</li>
<li class="pattern-item">State</li>
</ul>
</article>
<article class="pattern-category pattern-category--architectural">
<div class="pattern-header">
<h3>Architectural Patterns</h3>
<span class="pattern-icon">◆</span>
</div>
<ul class="pattern-list">
<li class="pattern-item">MVC</li>
<li class="pattern-item">MVVM</li>
<li class="pattern-item">Hexagonal</li>
<li class="pattern-item">Layered</li>
</ul>
</article>
</div>
</section>
<!-- Microservices Patterns -->
<section class="obelisk-section">
<h2 class="section-title">
<span class="hieroglyph">𓉐</span>
Mikrousługi - Obeliski Imperium
</h2>
<div class="microservices-grid">
<div class="microservice-card microservice-card--gateway">
<div class="card-icon">◆</div>
<h3>API Gateway</h3>
<p>Brama Pałacu</p>
<div class="card-status status--active">Active</div>
</div>
<div class="microservice-card microservice-card--discovery">
<div class="card-icon">◆</div>
<h3>Service Discovery</h3>
<p>Mapa Imperium</p>
<div class="card-status status--active">Active</div>
</div>
<div class="microservice-card microservice-card--breaker">
<div class="card-icon">◆</div>
<h3>Circuit Breaker</h3>
<p>Strażnik Bramy</p>
<div class="card-status status--active">Active</div>
</div>
<div class="microservice-card microservice-card--saga">
<div class="card-icon">◆</div>
<h3>Saga Pattern</h3>
<p>Kroniki Wydarzeń</p>
<div class="card-status status--active">Active</div>
</div>
<div class="microservice-card microservice-card--sourcing">
<div class="card-icon">⏳</div>
<h3>Event Sourcing</h3>
<p>Historia Królestwa</p>
<div class="card-status status--active">Active</div>
</div>
<div class="microservice-card microservice-card--cqrs">
<div class="card-icon">◆</div>
<h3>CQRS</h3>
<p>Podział Władzy</p>
<div class="card-status status--active">Active</div>
</div>
</div>
</section>
<!-- Performance Patterns -->
<section class="granary-section">
<h2 class="section-title">
<span class="hieroglyph">𓉐</span>
Performance - Magazyny Zbożowe
</h2>
<div class="performance-grid">
<div class="performance-metric">
<div class="metric-header">
<span class="metric-icon">■</span>
<h3>Caching</h3>
</div>
<div class="metric-strategies">
<span class="strategy-badge">LRU</span>
<span class="strategy-badge">TTL</span>
<span class="strategy-badge">Write-through</span>
<span class="strategy-badge">Cache-aside</span>
</div>
<div class="metric-bar">
<div class="metric-fill metric-fill--high"></div>
</div>
</div>
<div class="performance-metric">
<div class="metric-header">
<span class="metric-icon">◆</span>
<h3>Load Balancing</h3>
</div>
<div class="metric-strategies">
<span class="strategy-badge">Round Robin</span>
<span class="strategy-badge">Least Conn</span>
<span class="strategy-badge">IP Hash</span>
</div>
<div class="metric-bar">
<div class="metric-fill metric-fill--high"></div>
</div>
</div>
<div class="performance-metric">
<div class="metric-header">
<span class="metric-icon">◆</span>
<h3>Database Sharding</h3>
</div>
<div class="metric-strategies">
<span class="strategy-badge">Geographic</span>
<span class="strategy-badge">Hash-based</span>
<span class="strategy-badge">Range-based</span>
</div>
<div class="metric-bar">
<div class="metric-fill metric-fill--medium"></div>
</div>
</div>
<div class="performance-metric">
<div class="metric-header">
<span class="metric-icon">◆</span>
<h3>Optimization</h3>
</div>
<div class="metric-strategies">
<span class="strategy-badge">Profiling</span>
<span class="strategy-badge">Indexing</span>
<span class="strategy-badge">Compression</span>
</div>
<div class="metric-bar">
<div class="metric-fill metric-fill--high"></div>
</div>
</div>
</div>
</section>
<!-- Security Layers -->
<section class="fortress-section">
<h2 class="section-title">
<span class="hieroglyph">𓉐</span>
Security - Fortyfikacje Faraona
</h2>
<div class="security-layers">
<div class="security-layer security-layer--authentication">
<div class="layer-number">1</div>
<div class="layer-details">
<h3>Authentication</h3>
<p>Uwierzytelnienie tożsamości</p>
<div class="security-badges">
<span class="sec-badge">JWT</span>
<span class="sec-badge">OAuth 2.0</span>
<span class="sec-badge">SSO</span>
</div>
</div>
</div>
<div class="security-layer security-layer--authorization">
<div class="layer-number">2</div>
<div class="layer-details">
<h3>Authorization</h3>
<p>Kontrola dostępu i uprawnień</p>
<div class="security-badges">
<span class="sec-badge">RBAC</span>
<span class="sec-badge">ABAC</span>
<span class="sec-badge">ACL</span>
</div>
</div>
</div>
<div class="security-layer security-layer--validation">
<div class="layer-number">3</div>
<div class="layer-details">
<h3>Input Validation</h3>
<p>Walidacja i sanityzacja danych</p>
<div class="security-badges">
<span class="sec-badge">XSS Protection</span>
<span class="sec-badge">SQL Injection</span>
<span class="sec-badge">CSRF</span>
</div>
</div>
</div>
<div class="security-layer security-layer--encryption">
<div class="layer-number">4</div>
<div class="layer-details">
<h3>Data Encryption</h3>
<p>Szyfrowanie danych</p>
<div class="security-badges">
<span class="sec-badge">TLS/SSL</span>
<span class="sec-badge">AES-256</span>
<span class="sec-badge">RSA</span>
</div>
</div>
</div>
<div class="security-layer security-layer--monitoring">
<div class="layer-number">5</div>
<div class="layer-details">
<h3>Audit & Monitoring</h3>
<p>Logowanie i monitoring bezpieczeństwa</p>
<div class="security-badges">
<span class="sec-badge">SIEM</span>
<span class="sec-badge">IDS/IPS</span>
<span class="sec-badge">Alerting</span>
</div>
</div>
</div>
</div>
</section>
<!-- Footer with blessing -->
<footer class="pharaoh-footer">
<div class="footer-content">
<p class="blessing">
<span class="ankh-symbol">𓋹</span>
Błogosławieństwo Totha - Boga Mądrości i Pisma
<span class="ankh-symbol">𓋹</span>
</p>
<p class="footer-quote">
"Architektura godna piramid Gizy - trwała i monumentalna przez tysiąclecia"
</p>
</div>
</footer>
</div>
</body>
</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!
Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. 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!