W tym projekcie stworzymy profesjonalną, w pełni responsywną galerię portfolio dla fotografa. Wykorzystamy wszystkie techniki z tego modułu: media queries, jednostki responsywne (vw, vh), viewport, optymalizację obrazów oraz podejście mobile-first. Galeria będzie zawierać nawigację, sekcję hero, siatkę zdjęć z efektami hover, filtrowanie kategorii oraz lightbox do przeglądania zdjęć.
Nasza galeria portfolio będzie miała następujące funkcjonalności:
Zaczynamy od stworzenia struktury HTML z myślą o urządzeniach mobilnych:
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 - Portfolio Fotografa</title>
7 <link rel="stylesheet" href="styles.css">
8</head>
9<body>
10 <!-- Nawigacja -->
11 <nav class="navbar">
12 <div class="nav-container">
13 <div class="nav-logo">JK Photography</div>
14 <button class="nav-toggle" aria-label="Menu">
15 <span></span>
16 <span></span>
17 <span></span>
18 </button>
19 <ul class="nav-menu">
20 <li><a href="#home">Start</a></li>
21 <li><a href="#portfolio">Portfolio</a></li>
22 <li><a href="#about">O mnie</a></li>
23 <li><a href="#contact">Kontakt</a></li>
24 </ul>
25 </div>
26 </nav>
27
28 <!-- Sekcja Hero -->
29 <section id="home" class="hero">
30 <div class="hero-content">
31 <h1 class="hero-title">Jan Kowalski</h1>
32 <p class="hero-subtitle">Fotografia jest moją pasją</p>
33 <a href="#portfolio" class="hero-button">Zobacz portfolio</a>
34 </div>
35 <div class="hero-overlay"></div>
36 </section>
37
38 <!-- Sekcja Portfolio -->
39 <section id="portfolio" class="portfolio">
40 <div class="container">
41 <h2 class="section-title">Moje Portfolio</h2>
42
43 <!-- Filtry kategorii -->
44 <div class="filter-buttons">
45 <button class="filter-btn active" data-filter="all">Wszystkie</button>
46 <button class="filter-btn" data-filter="nature">Natura</button>
47 <button class="filter-btn" data-filter="portrait">Portrety</button>
48 <button class="filter-btn" data-filter="architecture">Architektura</button>
49 </div>
50
51 <!-- Galeria -->
52 <div class="gallery">
53 <div class="gallery-item" data-category="nature">
54 <picture>
55 <source media="(max-width: 768px)" srcset="img/nature1-mobile.jpg">
56 <source media="(max-width: 1200px)" srcset="img/nature1-tablet.jpg">
57 <img src="img/nature1-desktop.jpg" alt="Krajobraz górski">
58 </picture>
59 <div class="gallery-overlay">
60 <h3>Krajobraz górski</h3>
61 <p>Natura</p>
62 </div>
63 </div>
64
65 <div class="gallery-item" data-category="portrait">
66 <picture>
67 <source media="(max-width: 768px)" srcset="img/portrait1-mobile.jpg">
68 <source media="(max-width: 1200px)" srcset="img/portrait1-tablet.jpg">
69 <img src="img/portrait1-desktop.jpg" alt="Portret kobiety">
70 </picture>
71 <div class="gallery-overlay">
72 <h3>Portret</h3>
73 <p>Portrety</p>
74 </div>
75 </div>
76
77 <!-- Więcej elementów galerii... -->
78 </div>
79 </div>
80 </section>
81
82 <!-- Lightbox -->
83 <div class="lightbox" id="lightbox">
84 <span class="lightbox-close">×</span>
85 <img class="lightbox-content" id="lightbox-img">
86 <div class="lightbox-caption"></div>
87 </div>
88</body>
89</html>Rozpoczynamy stylowanie od urządzeń mobilnych:
1/* Reset i podstawowe style */
2* {
3 margin: 0;
4 padding: 0;
5 box-sizing: border-box;
6}
7
8:root {
9 --primary-color: #333;
10 --secondary-color: #f4f4f4;
11 --accent-color: #ff6b6b;
12 --text-light: #fff;
13 --overlay-dark: rgba(0, 0, 0, 0.7);
14}
15
16body {
17 font-family: 'Arial', sans-serif;
18 line-height: 1.6;
19 color: var(--primary-color);
20}
21
22/* Nawigacja - Mobile First */
23.navbar {
24 background-color: var(--primary-color);
25 padding: 1rem;
26 position: fixed;
27 width: 100%;
28 top: 0;
29 z-index: 1000;
30}
31
32.nav-container {
33 display: flex;
34 justify-content: space-between;
35 align-items: center;
36}
37
38.nav-logo {
39 color: var(--text-light);
40 font-size: 1.5rem;
41 font-weight: bold;
42}
43
44/* Hamburger menu dla mobile */
45.nav-toggle {
46 background: none;
47 border: none;
48 cursor: pointer;
49 display: flex;
50 flex-direction: column;
51 gap: 4px;
52}
53
54.nav-toggle span {
55 width: 25px;
56 height: 3px;
57 background-color: var(--text-light);
58 transition: all 0.3s ease;
59}
60
61.nav-menu {
62 position: absolute;
63 top: 100%;
64 left: 0;
65 width: 100%;
66 background-color: var(--primary-color);
67 list-style: none;
68 display: none;
69 flex-direction: column;
70}
71
72.nav-menu.active {
73 display: flex;
74}
75
76.nav-menu li {
77 padding: 1rem;
78 text-align: center;
79}
80
81.nav-menu a {
82 color: var(--text-light);
83 text-decoration: none;
84 transition: color 0.3s ease;
85}
86
87/* Hero section z jednostkami viewport */
88.hero {
89 height: 100vh;
90 width: 100vw;
91 position: relative;
92 display: flex;
93 align-items: center;
94 justify-content: center;
95 background-image: url('img/hero-mobile.jpg');
96 background-size: cover;
97 background-position: center;
98}
99
100.hero-overlay {
101 position: absolute;
102 top: 0;
103 left: 0;
104 width: 100%;
105 height: 100%;
106 background-color: var(--overlay-dark);
107}
108
109.hero-content {
110 position: relative;
111 z-index: 1;
112 text-align: center;
113 color: var(--text-light);
114 padding: 0 1rem;
115}
116
117.hero-title {
118 font-size: clamp(2rem, 8vw, 4rem);
119 margin-bottom: 1rem;
120}
121
122.hero-subtitle {
123 font-size: clamp(1rem, 4vw, 1.5rem);
124 margin-bottom: 2rem;
125}
126
127.hero-button {
128 display: inline-block;
129 padding: 0.8rem 2rem;
130 background-color: var(--accent-color);
131 color: var(--text-light);
132 text-decoration: none;
133 border-radius: 5px;
134 transition: all 0.3s ease;
135}
136
137/* Portfolio section */
138.portfolio {
139 padding: 4rem 1rem;
140 background-color: var(--secondary-color);
141}
142
143.container {
144 max-width: 1200px;
145 margin: 0 auto;
146}
147
148.section-title {
149 text-align: center;
150 font-size: clamp(1.5rem, 5vw, 2.5rem);
151 margin-bottom: 2rem;
152}
153
154/* Filtry */
155.filter-buttons {
156 display: flex;
157 flex-wrap: wrap;
158 justify-content: center;
159 gap: 0.5rem;
160 margin-bottom: 2rem;
161}
162
163.filter-btn {
164 padding: 0.5rem 1rem;
165 border: 2px solid var(--primary-color);
166 background-color: transparent;
167 cursor: pointer;
168 transition: all 0.3s ease;
169 border-radius: 25px;
170}
171
172.filter-btn.active,
173.filter-btn:hover {
174 background-color: var(--primary-color);
175 color: var(--text-light);
176}
177
178/* Galeria - Mobile First Grid */
179.gallery {
180 display: grid;
181 grid-template-columns: 1fr;
182 gap: 1rem;
183}
184
185.gallery-item {
186 position: relative;
187 overflow: hidden;
188 cursor: pointer;
189 aspect-ratio: 1;
190}
191
192.gallery-item img {
193 width: 100%;
194 height: 100%;
195 object-fit: cover;
196 transition: transform 0.3s ease;
197}
198
199.gallery-overlay {
200 position: absolute;
201 bottom: 0;
202 left: 0;
203 right: 0;
204 background: linear-gradient(to top, var(--overlay-dark), transparent);
205 color: var(--text-light);
206 padding: 1rem;
207 transform: translateY(100%);
208 transition: transform 0.3s ease;
209}
210
211.gallery-item:hover .gallery-overlay {
212 transform: translateY(0);
213}
214
215.gallery-item:hover img {
216 transform: scale(1.1);
217}
218
219/* Lightbox */
220.lightbox {
221 display: none;
222 position: fixed;
223 z-index: 2000;
224 left: 0;
225 top: 0;
226 width: 100vw;
227 height: 100vh;
228 background-color: var(--overlay-dark);
229 align-items: center;
230 justify-content: center;
231}
232
233.lightbox.active {
234 display: flex;
235}
236
237.lightbox-content {
238 max-width: 90vw;
239 max-height: 90vh;
240 object-fit: contain;
241}
242
243.lightbox-close {
244 position: absolute;
245 top: 1rem;
246 right: 1rem;
247 color: var(--text-light);
248 font-size: 2rem;
249 cursor: pointer;
250}
251
252/* Media Queries - Tablet */
253@media (min-width: 768px) {
254 /* Nawigacja dla tabletu */
255 .nav-toggle {
256 display: none;
257 }
258
259 .nav-menu {
260 position: static;
261 display: flex;
262 flex-direction: row;
263 width: auto;
264 }
265
266 .nav-menu li {
267 padding: 0 1rem;
268 }
269
270 /* Hero dla tabletu */
271 .hero {
272 background-image: url('img/hero-tablet.jpg');
273 }
274
275 /* Galeria - 2 kolumny */
276 .gallery {
277 grid-template-columns: repeat(2, 1fr);
278 }
279}
280
281/* Media Queries - Desktop */
282@media (min-width: 1024px) {
283 /* Hero dla desktop */
284 .hero {
285 background-image: url('img/hero-desktop.jpg');
286 }
287
288 /* Galeria - 3 kolumny */
289 .gallery {
290 grid-template-columns: repeat(3, 1fr);
291 gap: 1.5rem;
292 }
293
294 /* Efekty hover tylko na desktop */
295 .gallery-overlay {
296 opacity: 0;
297 transform: translateY(0);
298 background: var(--overlay-dark);
299 }
300
301 .gallery-item:hover .gallery-overlay {
302 opacity: 1;
303 }
304}
305
306/* Media Queries - Duże ekrany */
307@media (min-width: 1440px) {
308 .gallery {
309 grid-template-columns: repeat(4, 1fr);
310 }
311}
312
313/* Orientacja landscape dla mobile */
314@media (max-width: 768px) and (orientation: landscape) {
315 .hero {
316 height: 100vw;
317 }
318
319 .hero-title {
320 font-size: 2rem;
321 }
322}
323
324/* High DPI displays */
325@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
326 .hero {
327 background-image: url('img/hero-mobile@2x.jpg');
328 }
329
330 @media (min-width: 768px) {
331 .hero {
332 background-image: url('img/hero-tablet@2x.jpg');
333 }
334 }
335
336 @media (min-width: 1024px) {
337 .hero {
338 background-image: url('img/hero-desktop@2x.jpg');
339 }
340 }
341}1<!-- Użyj elementu picture dla responsywnych obrazów -->
2<picture>
3 <source media="(max-width: 768px)"
4 srcset="img/photo-mobile.webp"
5 type="image/webp">
6 <source media="(max-width: 768px)"
7 srcset="img/photo-mobile.jpg"
8 type="image/jpeg">
9 <source media="(max-width: 1200px)"
10 srcset="img/photo-tablet.webp"
11 type="image/webp">
12 <source media="(max-width: 1200px)"
13 srcset="img/photo-tablet.jpg"
14 type="image/jpeg">
15 <source srcset="img/photo-desktop.webp"
16 type="image/webp">
17 <img src="img/photo-desktop.jpg"
18 alt="Opis zdjęcia"
19 loading="lazy">
20</picture>1/* Użyj will-change dla elementów animowanych */
2.gallery-item {
3 will-change: transform;
4}
5
6/* Optymalizacja dla wydajności */
7.gallery-item img {
8 will-change: transform;
9 backface-visibility: hidden;
10}
11
12/* Użyj CSS containment */
13.gallery-item {
14 contain: layout style;
15}
16
17/* Prefers reduced motion */
18@media (prefers-reduced-motion: reduce) {
19 * {
20 animation-duration: 0.01ms !important;
21 animation-iteration-count: 1 !important;
22 transition-duration: 0.01ms !important;
23 }
24}
25
26/* Dark mode support */
27@media (prefers-color-scheme: dark) {
28 :root {
29 --primary-color: #fff;
30 --secondary-color: #1a1a1a;
31 --text-light: #333;
32 }
33
34 body {
35 background-color: #1a1a1a;
36 color: #fff;
37 }
38}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="Portfolio fotograficzne Jana Kowalskiego">
7 <title>Jan Kowalski - Portfolio Fotografa</title>
8 <link rel="stylesheet" href="styles.css">
9 <link rel="preconnect" href="https://fonts.googleapis.com">
10 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11</head>
12<body>
13 <!-- Nawigacja -->
14 <nav class="navbar">
15 <div class="nav-container">
16 <div class="nav-logo">JK Photography</div>
17 <button class="nav-toggle" aria-label="Menu">
18 <span></span>
19 <span></span>
20 <span></span>
21 </button>
22 <ul class="nav-menu">
23 <li><a href="#home">Start</a></li>
24 <li><a href="#portfolio">Portfolio</a></li>
25 <li><a href="#about">O mnie</a></li>
26 <li><a href="#contact">Kontakt</a></li>
27 </ul>
28 </div>
29 </nav>
30
31 <!-- Sekcja Hero -->
32 <section id="home" class="hero">
33 <div class="hero-content">
34 <h1 class="hero-title">Jan Kowalski</h1>
35 <p class="hero-subtitle">Fotografia jest moją pasją</p>
36 <a href="#portfolio" class="hero-button">Zobacz portfolio</a>
37 </div>
38 <div class="hero-overlay"></div>
39 </section>
40
41 <!-- Sekcja Portfolio -->
42 <section id="portfolio" class="portfolio">
43 <div class="container">
44 <h2 class="section-title">Moje Portfolio</h2>
45
46 <!-- Filtry kategorii -->
47 <div class="filter-buttons">
48 <button class="filter-btn active" data-filter="all">Wszystkie</button>
49 <button class="filter-btn" data-filter="nature">Natura</button>
50 <button class="filter-btn" data-filter="portrait">Portrety</button>
51 <button class="filter-btn" data-filter="architecture">Architektura</button>
52 </div>
53
54 <!-- Galeria -->
55 <div class="gallery">
56 <!-- Przykładowe elementy galerii -->
57 <div class="gallery-item" data-category="nature">
58 <picture>
59 <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=1">
60 <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=1">
61 <img src="https://picsum.photos/800/800?random=1" alt="Krajobraz górski">
62 </picture>
63 <div class="gallery-overlay">
64 <h3>Krajobraz górski</h3>
65 <p>Natura</p>
66 </div>
67 </div>
68
69 <div class="gallery-item" data-category="portrait">
70 <picture>
71 <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=2">
72 <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=2">
73 <img src="https://picsum.photos/800/800?random=2" alt="Portret">
74 </picture>
75 <div class="gallery-overlay">
76 <h3>Portret</h3>
77 <p>Portrety</p>
78 </div>
79 </div>
80
81 <div class="gallery-item" data-category="architecture">
82 <picture>
83 <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=3">
84 <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=3">
85 <img src="https://picsum.photos/800/800?random=3" alt="Architektura">
86 </picture>
87 <div class="gallery-overlay">
88 <h3>Nowoczesna architektura</h3>
89 <p>Architektura</p>
90 </div>
91 </div>
92
93 <div class="gallery-item" data-category="nature">
94 <picture>
95 <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=4">
96 <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=4">
97 <img src="https://picsum.photos/800/800?random=4" alt="Las">
98 </picture>
99 <div class="gallery-overlay">
100 <h3>Leśna ścieżka</h3>
101 <p>Natura</p>
102 </div>
103 </div>
104
105 <div class="gallery-item" data-category="portrait">
106 <picture>
107 <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=5">
108 <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=5">
109 <img src="https://picsum.photos/800/800?random=5" alt="Portret dziecka">
110 </picture>
111 <div class="gallery-overlay">
112 <h3>Dziecięca radość</h3>
113 <p>Portrety</p>
114 </div>
115 </div>
116
117 <div class="gallery-item" data-category="architecture">
118 <picture>
119 <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=6">
120 <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=6">
121 <img src="https://picsum.photos/800/800?random=6" alt="Most">
122 </picture>
123 <div class="gallery-overlay">
124 <h3>Most o zachodzie</h3>
125 <p>Architektura</p>
126 </div>
127 </div>
128 </div>
129 </div>
130 </section>
131
132 <!-- Lightbox -->
133 <div class="lightbox" id="lightbox">
134 <span class="lightbox-close">×</span>
135 <img class="lightbox-content" id="lightbox-img">
136 <div class="lightbox-caption"></div>
137 </div>
138
139 <script src="script.js"></script>
140</body>
141</html>Ten projekt demonstruje wszystkie kluczowe koncepty responsywnego projektowania:
Projekt jest w pełni funkcjonalny i gotowy do użycia jako portfolio fotografa lub może być łatwo dostosowany do innych celów.