Galériák és képek
Mozaikgaléria
Élő előnézet – a komponens teljes méretben.
Élő előnézet
HTML · CSS · JS
<section class="we-mosaic" data-mosaic aria-label="Fotógaléria">
<header class="we-mosaic__header">
<div>
<span class="we-mosaic__eyebrow">VÁLOGATOTT PILLANATOK</span>
<h2>Világunk képekben<span>.</span></h2>
</div>
<p>Minden kép egy külön történet.</p>
</header>
<div class="we-mosaic__grid">
<button class="we-mosaic__item we-mosaic__item--feature"
type="button"
data-photo
data-title="A hegyek csendje"
data-description="A természet nagysága néha szavak nélkül mesél."
aria-label="A hegyek csendje – kép megnyitása">
<img
src="https://images.unsplash.com/photo-1470770841072-f978cf4d019e?auto=format&fit=crop&w=1400&q=85"
alt="Hegyek és tó a reggeli fényben"
loading="lazy">
<span class="we-mosaic__overlay">
<span class="we-mosaic__category">TERMÉSZET</span>
<span class="we-mosaic__title">A hegyek csendje</span>
<span class="we-mosaic__open">Megnézem <span>↗</span></span>
</span>
</button>
<button class="we-mosaic__item"
type="button"
data-photo
data-title="Erdei ösvény"
data-description="Egy út, amelyen jó egy kicsit elveszni."
aria-label="Erdei ösvény – kép megnyitása">
<img
src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=900&q=85"
alt="Napfényben fürdő erdő"
loading="lazy">
<span class="we-mosaic__overlay">
<span class="we-mosaic__category">ERDŐ</span>
<span class="we-mosaic__title">Erdei ösvény</span>
<span class="we-mosaic__open">Megnézem <span>↗</span></span>
</span>
</button>
<button class="we-mosaic__item"
type="button"
data-photo
data-title="Aranyló horizont"
data-description="Amikor a nap utolsó fénye megérinti a tájat."
aria-label="Aranyló horizont – kép megnyitása">
<img
src="https://images.unsplash.com/photo-1470252649378-9c29740c9fa8?auto=format&fit=crop&w=900&q=85"
alt="Aranyló napfelkelte"
loading="lazy">
<span class="we-mosaic__overlay">
<span class="we-mosaic__category">FÉNYEK</span>
<span class="we-mosaic__title">Aranyló horizont</span>
<span class="we-mosaic__open">Megnézem <span>↗</span></span>
</span>
</button>
<button class="we-mosaic__item"
type="button"
data-photo
data-title="A végtelen kék"
data-description="Sós levegő, hullámok és a szabadság érzése."
aria-label="A végtelen kék – kép megnyitása">
<img
src="https://images.unsplash.com/photo-1518837695005-2083093ee35b?auto=format&fit=crop&w=900&q=85"
alt="Tengerparti hullámok"
loading="lazy">
<span class="we-mosaic__overlay">
<span class="we-mosaic__category">TENGER</span>
<span class="we-mosaic__title">A végtelen kék</span>
<span class="we-mosaic__open">Megnézem <span>↗</span></span>
</span>
</button>
<button class="we-mosaic__item"
type="button"
data-photo
data-title="Hegyek között"
data-description="A táj, amely újra és újra útnak hív."
aria-label="Hegyek között – kép megnyitása">
<img
src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&q=85"
alt="Magas hegycsúcsok"
loading="lazy">
<span class="we-mosaic__overlay">
<span class="we-mosaic__category">UTAZÁS</span>
<span class="we-mosaic__title">Hegyek között</span>
<span class="we-mosaic__open">Megnézem <span>↗</span></span>
</span>
</button>
</div>
<footer class="we-mosaic__footer">
<span>ÖT KÉP · VÉGTELEN TÖRTÉNET</span>
<span class="we-mosaic__footer-line"></span>
<span>KATTINTS A FOTÓKRA A NAGYÍTÁSHOZ</span>
</footer>
</section>
.we-mosaic {
--mosaic-bg: #111714;
--mosaic-text: #f4f1e8;
--mosaic-muted: #aeb8ae;
--mosaic-accent: #c5a66d;
width: 100%;
padding: clamp(28px, 5vw, 68px) clamp(16px, 4vw, 48px);
color: var(--mosaic-text);
background: var(--mosaic-bg);
font-family: Georgia, "Times New Roman", serif;
box-sizing: border-box;
}
.we-mosaic *,
.we-mosaic *::before,
.we-mosaic *::after {
box-sizing: border-box;
}
.we-mosaic__header {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 24px;
max-width: 1300px;
margin: 0 auto 30px;
}
.we-mosaic__eyebrow {
display: block;
margin-bottom: 12px;
color: var(--mosaic-accent);
font-family: Arial, sans-serif;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.24em;
}
.we-mosaic__header h2 {
margin: 0;
color: var(--mosaic-text);
font-size: clamp(29px, 4.2vw, 52px);
font-weight: 400;
line-height: 1.1;
letter-spacing: -0.035em;
}
.we-mosaic__header h2 > span {
color: var(--mosaic-accent);
}
.we-mosaic__header > p {
max-width: 230px;
margin: 0 0 4px;
color: var(--mosaic-muted);
font-family: Arial, sans-serif;
font-size: 13px;
line-height: 1.7;
text-align: right;
}
.we-mosaic__grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-rows: repeat(2, minmax(190px, 22vw));
gap: 12px;
max-width: 1300px;
margin: 0 auto;
}
.we-mosaic__item {
position: relative;
display: block;
min-width: 0;
min-height: 0;
overflow: hidden;
padding: 0;
border: 0;
border-radius: 2px;
background: #28352e;
color: inherit;
text-align: left;
cursor: pointer;
isolation: isolate;
}
.we-mosaic__item--feature {
grid-column: span 2;
grid-row: span 2;
}
.we-mosaic__item img {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
transition:
transform 800ms cubic-bezier(.2, .65, .25, 1),
filter 500ms ease;
}
.we-mosaic__item::after {
position: absolute;
z-index: 1;
inset: 0;
content: "";
background: linear-gradient(
180deg,
rgb(9 15 11 / 2%) 25%,
rgb(9 15 11 / 25%) 52%,
rgb(9 15 11 / 88%) 100%
);
opacity: 0.85;
transition: opacity 400ms ease;
}
.we-mosaic__item:hover img {
transform: scale(1.065);
filter: saturate(1.08);
}
.we-mosaic__item:hover::after {
opacity: 1;
}
.we-mosaic__item:focus-visible {
outline: 2px solid var(--mosaic-accent);
outline-offset: 4px;
z-index: 3;
}
.we-mosaic__overlay {
position: absolute;
z-index: 2;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
padding: clamp(14px, 2vw, 24px);
transform: translateY(8px);
transition: transform 400ms ease;
}
.we-mosaic__item:hover .we-mosaic__overlay,
.we-mosaic__item:focus-visible .we-mosaic__overlay {
transform: translateY(0);
}
.we-mosaic__category {
margin-bottom: 8px;
color: #e6ca91;
font-family: Arial, sans-serif;
font-size: 9px;
font-weight: 700;
letter-spacing: 0.2em;
}
.we-mosaic__title {
color: #fff;
font-size: clamp(16px, 1.8vw, 24px);
font-weight: 400;
line-height: 1.2;
}
.we-mosaic__item--feature .we-mosaic__title {
font-size: clamp(25px, 3.2vw, 43px);
}
.we-mosaic__open {
display: flex;
align-items: center;
gap: 10px;
max-height: 0;
margin-top: 0;
overflow: hidden;
color: #e7e3d9;
font-family: Arial, sans-serif;
font-size: 11px;
opacity: 0;
transition:
max-height 350ms ease,
margin-top 350ms ease,
opacity 350ms ease;
}
.we-mosaic__open > span {
color: var(--mosaic-accent);
font-size: 17px;
}
.we-mosaic__item:hover .we-mosaic__open,
.we-mosaic__item:focus-visible .we-mosaic__open {
max-height: 30px;
margin-top: 12px;
opacity: 1;
}
.we-mosaic__footer {
display: flex;
align-items: center;
gap: 18px;
max-width: 1300px;
margin: 22px auto 0;
color: var(--mosaic-muted);
font-family: Arial, sans-serif;
font-size: 9px;
letter-spacing: 0.12em;
}
.we-mosaic__footer-line {
flex: 1;
height: 1px;
background: rgb(197 166 109 / 35%);
}
/* Nagyított képnézet */
.we-mosaic__lightbox {
position: fixed;
z-index: 99999;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 28px;
background: rgb(5 9 7 / 94%);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition:
opacity 250ms ease,
visibility 250ms ease;
}
.we-mosaic__lightbox.is-open {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.we-mosaic__lightbox-inner {
position: relative;
display: flex;
flex-direction: column;
width: min(100%, 1100px);
max-height: 100%;
transform: translateY(12px) scale(.985);
transition: transform 300ms ease;
}
.we-mosaic__lightbox.is-open .we-mosaic__lightbox-inner {
transform: translateY(0) scale(1);
}
.we-mosaic__lightbox-image-wrap {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 0;
flex: 1;
}
.we-mosaic__lightbox-image {
display: block;
max-width: 100%;
max-height: min(72vh, 760px);
width: auto;
height: auto;
object-fit: contain;
box-shadow: 0 24px 80px rgb(0 0 0 / 35%);
}
.we-mosaic__lightbox-caption {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 20px;
padding: 20px 2px 0;
}
.we-mosaic__lightbox-caption h3 {
margin: 0 0 7px;
color: #f4f1e8;
font-size: clamp(20px, 3vw, 30px);
font-weight: 400;
}
.we-mosaic__lightbox-caption p {
margin: 0;
color: #b6beb6;
font-family: Arial, sans-serif;
font-size: 13px;
line-height: 1.6;
}
.we-mosaic__lightbox-count {
flex-shrink: 0;
padding-top: 5px;
color: var(--mosaic-accent);
font-family: Arial, sans-serif;
font-size: 12px;
letter-spacing: .12em;
}
.we-mosaic__lightbox-close,
.we-mosaic__lightbox-arrow {
position: absolute;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border: 1px solid rgb(255 255 255 / 25%);
border-radius: 50%;
background: rgb(10 15 12 / 55%);
color: #fff;
font-family: Arial, sans-serif;
font-size: 24px;
cursor: pointer;
transition:
color 180ms ease,
background 180ms ease,
border-color 180ms ease;
}
.we-mosaic__lightbox-close:hover,
.we-mosaic__lightbox-arrow:hover {
border-color: var(--mosaic-accent);
background: var(--mosaic-accent);
color: #111714;
}
.we-mosaic__lightbox-close {
top: 18px;
right: 18px;
}
.we-mosaic__lightbox-arrow {
top: 50%;
transform: translateY(-50%);
}
.we-mosaic__lightbox-prev {
left: 12px;
}
.we-mosaic__lightbox-next {
right: 12px;
}
.we-mosaic__lightbox-close:focus-visible,
.we-mosaic__lightbox-arrow:focus-visible {
outline: 2px solid var(--mosaic-accent);
outline-offset: 4px;
}
@media (max-width: 760px) {
.we-mosaic__header {
align-items: flex-start;
flex-direction: column;
gap: 12px;
}
.we-mosaic__header > p {
text-align: left;
}
.we-mosaic__grid {
grid-template-rows: repeat(2, minmax(155px, 30vw));
gap: 8px;
}
.we-mosaic__overlay {
padding: 12px;
transform: none;
}
.we-mosaic__open {
display: none;
}
.we-mosaic__category {
font-size: 8px;
}
.we-mosaic__title {
font-size: clamp(14px, 2.8vw, 19px);
}
.we-mosaic__item--feature .we-mosaic__title {
font-size: clamp(22px, 4.5vw, 32px);
}
.we-mosaic__footer {
flex-wrap: wrap;
gap: 10px;
font-size: 8px;
}
}
@media (max-width: 480px) {
.we-mosaic__grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: 290px 155px 155px;
}
.we-mosaic__item--feature {
grid-column: span 2;
grid-row: span 1;
}
.we-mosaic__lightbox {
padding: 16px;
}
.we-mosaic__lightbox-image {
max-height: 65vh;
}
.we-mosaic__lightbox-arrow {
top: auto;
bottom: 95px;
width: 40px;
height: 40px;
}
.we-mosaic__lightbox-prev {
left: 5px;
}
.we-mosaic__lightbox-next {
right: 5px;
}
.we-mosaic__lightbox-close {
top: 10px;
right: 10px;
}
.we-mosaic__lightbox-caption {
padding-top: 15px;
}
}
@media (prefers-reduced-motion: reduce) {
.we-mosaic *,
.we-mosaic__lightbox,
.we-mosaic__lightbox-inner {
transition-duration: .01ms !important;
}
}
document.querySelectorAll("[data-mosaic]").forEach((gallery) => {
if (gallery.dataset.mosaicInitialized === "true") {
return;
}
const photos = Array.from(gallery.querySelectorAll("[data-photo]"));
if (!photos.length) {
console.warn("Mozaikgaléria: nem találhatók képek.", gallery);
return;
}
gallery.dataset.mosaicInitialized = "true";
let activeIndex = 0;
let previousFocus = null;
const lightbox = document.createElement("div");
lightbox.className = "we-mosaic__lightbox";
lightbox.setAttribute("role", "dialog");
lightbox.setAttribute("aria-modal", "true");
lightbox.setAttribute("aria-label", "Képnézegető");
lightbox.innerHTML = `
<button class="we-mosaic__lightbox-close"
type="button"
aria-label="Bezárás">×</button>
<div class="we-mosaic__lightbox-inner">
<div class="we-mosaic__lightbox-image-wrap">
<button class="we-mosaic__lightbox-arrow we-mosaic__lightbox-prev"
type="button"
aria-label="Előző kép">‹</button>
<img class="we-mosaic__lightbox-image"
alt=""
src="">
<button class="we-mosaic__lightbox-arrow we-mosaic__lightbox-next"
type="button"
aria-label="Következő kép">›</button>
</div>
<div class="we-mosaic__lightbox-caption">
<div>
<h3></h3>
<p></p>
</div>
<span class="we-mosaic__lightbox-count"></span>
</div>
</div>
`;
document.body.appendChild(lightbox);
const closeButton = lightbox.querySelector(
".we-mosaic__lightbox-close"
);
const prevButton = lightbox.querySelector(
".we-mosaic__lightbox-prev"
);
const nextButton = lightbox.querySelector(
".we-mosaic__lightbox-next"
);
const largeImage = lightbox.querySelector(
".we-mosaic__lightbox-image"
);
const heading = lightbox.querySelector(
".we-mosaic__lightbox-caption h3"
);
const description = lightbox.querySelector(
".we-mosaic__lightbox-caption p"
);
const counter = lightbox.querySelector(
".we-mosaic__lightbox-count"
);
function getPhotoData(index) {
const button = photos[index];
const image = button.querySelector("img");
return {
src: image ? image.currentSrc || image.src : "",
alt: image ? image.alt : "",
title: button.dataset.title || (image ? image.alt : "Fotó"),
description: button.dataset.description || ""
};
}
function showPhoto(index) {
activeIndex = (index + photos.length) % photos.length;
const photo = getPhotoData(activeIndex);
largeImage.src = photo.src;
largeImage.alt = photo.alt;
heading.textContent = photo.title;
description.textContent = photo.description;
counter.textContent =
String(activeIndex + 1).padStart(2, "0") +
" / " +
String(photos.length).padStart(2, "0");
}
function openLightbox(index) {
previousFocus = document.activeElement;
showPhoto(index);
lightbox.classList.add("is-open");
document.body.style.overflow = "hidden";
closeButton.focus();
}
function closeLightbox() {
lightbox.classList.remove("is-open");
document.body.style.overflow = "";
if (previousFocus && typeof previousFocus.focus === "function") {
previousFocus.focus();
}
}
photos.forEach((button, index) => {
button.addEventListener("click", () => {
openLightbox(index);
});
});
closeButton.addEventListener("click", closeLightbox);
prevButton.addEventListener("click", () => {
showPhoto(activeIndex - 1);
});
nextButton.addEventListener("click", () => {
showPhoto(activeIndex + 1);
});
lightbox.addEventListener("click", (event) => {
if (event.target === lightbox) {
closeLightbox();
}
});
document.addEventListener("keydown", (event) => {
if (!lightbox.classList.contains("is-open")) {
return;
}
if (event.key === "Escape") {
event.preventDefault();
closeLightbox();
} else if (event.key === "ArrowRight") {
event.preventDefault();
showPhoto(activeIndex + 1);
} else if (event.key === "ArrowLeft") {
event.preventDefault();
showPhoto(activeIndex - 1);
}
});
});