Vissza a webes elemekhez
Galériák és képek

Modern galéria

Élő előnézet – a komponens teljes méretben.

Élő előnézet
HTML · CSS · JS
HTML
<section class="architecture-gallery" id="architecture-gallery">
    <div class="ag-heading">
        <div>
            <span class="ag-eyebrow">VÁLOGATOTT ALKOTÁSOK / 001</span>
            <h2>Vizuális <em>történetek</em></h2>
        </div>
        <p class="ag-intro">
            Minden épület egy gondolat. Minden részlet egy új nézőpont.
        </p>
    </div>

    <div class="ag-toolbar">
        <div class="ag-filters" role="group" aria-label="Galéria szűrése">
            <button class="ag-filter active" data-filter="all">Összes kép</button>
            <button class="ag-filter" data-filter="modern">Modern</button>
            <button class="ag-filter" data-filter="details">Részletek</button>
            <button class="ag-filter" data-filter="spaces">Terek</button>
        </div>
        <div class="ag-total">
            <span class="ag-count">04</span>
            <span>ALKOTÁS</span>
        </div>
    </div>

    <div class="ag-grid">
        <article class="ag-card ag-large" data-category="modern"
            data-title="A jövő vonalai"
            data-description="Modern építészet és letisztult geometria.">
            <button class="ag-image-button" aria-label="A jövő vonalai – kép nagyítása">
                <img src="https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&amp;fit=crop&amp;w=1400&amp;q=85"
                    alt="Modern épület letisztult geometriai formákkal" loading="lazy">
                <span class="ag-overlay"></span>
                <span class="ag-open">↗</span>
                <span class="ag-index">01 / 04</span>
            </button>
            <div class="ag-caption">
                <div>
                    <span class="ag-category">MODERN ÉPÍTÉSZET</span>
                    <h3>A jövő vonalai</h3>
                </div>
                <span class="ag-arrow">↗</span>
            </div>
        </article>

        <article class="ag-card ag-tall" data-category="details"
            data-title="A részletek nyelve"
            data-description="Formák, arányok és apró építészeti felfedezések.">
            <button class="ag-image-button" aria-label="A részletek nyelve – kép nagyítása">
                <img src="https://images.unsplash.com/photo-1511818966892-d7d671e672a2?auto=format&amp;fit=crop&amp;w=1000&amp;q=85"
                    alt="Különleges építészeti forma és homlokzat" loading="lazy">
                <span class="ag-overlay"></span>
                <span class="ag-open">↗</span>
                <span class="ag-index">02 / 04</span>
            </button>
            <div class="ag-caption">
                <div>
                    <span class="ag-category">ÉPÍTÉSZETI RÉSZLETEK</span>
                    <h3>A részletek nyelve</h3>
                </div>
                <span class="ag-arrow">↗</span>
            </div>
        </article>

        <article class="ag-card ag-wide" data-category="spaces"
            data-title="Tér és arány"
            data-description="Az épített környezet és a geometria találkozása.">
            <button class="ag-image-button" aria-label="Tér és arány – kép nagyítása">
                <img src="https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?auto=format&amp;fit=crop&amp;w=1400&amp;q=85"
                    alt="Világos, modern belső tér" loading="lazy">
                <span class="ag-overlay"></span>
                <span class="ag-open">↗</span>
                <span class="ag-index">03 / 04</span>
            </button>
            <div class="ag-caption">
                <div>
                    <span class="ag-category">TÉR ÉS GEOMETRIA</span>
                    <h3>Tér és arány</h3>
                </div>
                <span class="ag-arrow">↗</span>
            </div>
        </article>

        <article class="ag-card ag-medium" data-category="modern"
            data-title="Időtlen formák"
            data-description="Az építészet karaktere a formák találkozásában.">
            <button class="ag-image-button" aria-label="Időtlen formák – kép nagyítása">
                <img src="https://images.unsplash.com/photo-1518005020951-eccb494ad742?auto=format&amp;fit=crop&amp;w=1400&amp;q=85"
                    alt="Modern építészeti kompozíció" loading="lazy">
                <span class="ag-overlay"></span>
                <span class="ag-open">↗</span>
                <span class="ag-index">04 / 04</span>
            </button>
            <div class="ag-caption">
                <div>
                    <span class="ag-category">FORMA ÉS KARAKTER</span>
                    <h3>Időtlen formák</h3>
                </div>
                <span class="ag-arrow">↗</span>
            </div>
        </article>
    </div>

    <div class="ag-end" aria-hidden="true">
        <span></span><b>A.</b><span></span>
    </div>

    <div class="ag-lightbox" role="dialog" aria-modal="true"
        aria-label="Képnézegető" aria-hidden="true">
        <button class="ag-close" aria-label="Bezárás">×</button>
        <button class="ag-nav ag-prev" aria-label="Előző kép">←</button>

        <div class="ag-lightbox-content">
            <div class="ag-lightbox-image">
                <img src="" alt="">
            </div>
            <div class="ag-lightbox-info">
                <span class="ag-lightbox-number">01 / 04</span>
                <div>
                    <h3></h3>
                    <p></p>
                </div>
            </div>
        </div>

        <button class="ag-nav ag-next" aria-label="Következő kép">→</button>
    </div>
</section>
CSS
/* ==========================================
   ARCHITECTURE GALLERY
   Önálló, névterezett galéria
========================================== */

.architecture-gallery {
    --ag-bg: #171815;
    --ag-gold: #c4a46a;
    --ag-gold-light: #e3c995;
    --ag-text: #f1eee7;
    --ag-muted: #929087;
    --ag-line: rgba(196, 164, 106, .25);
    --ag-serif: "Cormorant Garamond", Georgia, serif;
    --ag-sans: "Manrope", Arial, sans-serif;

    position: relative;
    padding: 100px 7.1%;
    background: var(--ag-bg);
    color: var(--ag-text);
    font-family: var(--ag-sans);
    font-size: 14px;
    line-height: 1.7;
    overflow: hidden;
}

.architecture-gallery *,
.architecture-gallery *::before,
.architecture-gallery *::after {
    box-sizing: border-box;
}

.architecture-gallery button {
    color: inherit;
    font: inherit;
}

.architecture-gallery button:focus-visible {
    outline: 2px solid var(--ag-gold-light);
    outline-offset: 4px;
}

.architecture-gallery .ag-heading {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 35px;
    margin-bottom: 48px;
}

.architecture-gallery .ag-eyebrow {
    color: var(--ag-gold);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 2.6px;
}

.architecture-gallery .ag-heading h2 {
    margin: 13px 0 0;
    font: 400 clamp(42px, 5.3vw, 70px)/1.05 var(--ag-serif);
    letter-spacing: -1px;
}

.architecture-gallery .ag-heading h2 em {
    color: var(--ag-gold);
    font-weight: 400;
}

.architecture-gallery .ag-intro {
    max-width: 235px;
    margin: 0;
    padding-bottom: 6px;
    color: var(--ag-muted);
    font-size: 11px;
    line-height: 2;
}

.architecture-gallery .ag-toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 20px;
    padding: 15px 0;
    margin-bottom: 28px;
    border-top: 1px solid var(--ag-line);
    border-bottom: 1px solid var(--ag-line);
}

.architecture-gallery .ag-filters {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.architecture-gallery .ag-filter {
    padding: 9px 15px;
    border: 1px solid transparent;
    background: transparent;
    color: #96948a;
    cursor: pointer;
    font-size: 10px;
    transition: color .25s, border-color .25s, background .25s;
}

.architecture-gallery .ag-filter:hover,
.architecture-gallery .ag-filter.active {
    border-color: var(--ag-line);
    background: rgba(196, 164, 106, .07);
    color: var(--ag-gold-light);
}

.architecture-gallery .ag-total {
    display: flex;
    align-items: baseline;
    gap: 9px;
    white-space: nowrap;
    color: var(--ag-muted);
    font-size: 8px;
    letter-spacing: 1.5px;
}

.architecture-gallery .ag-count {
    color: var(--ag-gold);
    font: 400 27px/1 var(--ag-serif);
    letter-spacing: 0;
}

/* Aszimmetrikus képrács */

.architecture-gallery .ag-grid {
    display: grid;
    grid-template-columns: 1.15fr .85fr;
    grid-template-rows: 290px 290px;
    gap: 48px 28px;
    align-items: start;
}

.architecture-gallery .ag-card {
    min-width: 0;
    animation: agCardIn .65s cubic-bezier(.2, .75, .25, 1) both;
}

.architecture-gallery .ag-card.ag-hidden {
    display: none;
}

.architecture-gallery .ag-large {
    grid-column: 1;
    grid-row: 1;
}

.architecture-gallery .ag-tall {
    grid-column: 2;
    grid-row: 1 / span 2;
    margin-top: 65px;
}

.architecture-gallery .ag-wide {
    grid-column: 1;
    grid-row: 2;
}

.architecture-gallery .ag-medium {
    grid-column: 1 / -1;
    width: 64%;
    margin: 35px auto 0;
}

.architecture-gallery .ag-image-button {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 1.65 / 1;
    padding: 0;
    overflow: hidden;
    border: 0;
    background: #252620;
    cursor: zoom-in;
    text-align: left;
}

.architecture-gallery .ag-tall .ag-image-button {
    aspect-ratio: 3 / 4.2;
}

.architecture-gallery .ag-medium .ag-image-button {
    aspect-ratio: 1.9 / 1;
}

.architecture-gallery .ag-image-button img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: saturate(.72) brightness(.85);
    transition: transform .9s cubic-bezier(.2, .75, .25, 1),
                filter .6s;
}

.architecture-gallery .ag-card:hover .ag-image-button img {
    transform: scale(1.055);
    filter: saturate(.95) brightness(.97);
}

.architecture-gallery .ag-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(
        180deg,
        transparent 45%,
        rgba(12, 13, 11, .55) 100%
    );
    pointer-events: none;
    transition: background .4s;
}

.architecture-gallery .ag-card:hover .ag-overlay {
    background: linear-gradient(
        180deg,
        rgba(12, 13, 11, .03),
        rgba(12, 13, 11, .65)
    );
}

.architecture-gallery .ag-open {
    position: absolute;
    top: 18px;
    right: 18px;
    display: grid;
    place-items: center;
    width: 43px;
    height: 43px;
    border: 1px solid rgba(255, 255, 255, .5);
    border-radius: 50%;
    color: #fff;
    font-size: 18px;
    opacity: 0;
    transform: translate(-5px, 5px);
    transition: opacity .35s, transform .35s, background .35s;
}

.architecture-gallery .ag-card:hover .ag-open,
.architecture-gallery .ag-image-button:focus-visible .ag-open {
    opacity: 1;
    transform: translate(0, 0);
    background: rgba(196, 164, 106, .25);
}

.architecture-gallery .ag-index {
    position: absolute;
    bottom: 17px;
    left: 20px;
    color: rgba(255, 255, 255, .9);
    font-size: 9px;
    letter-spacing: 2px;
}

.architecture-gallery .ag-caption {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 15px;
    padding-top: 19px;
}

.architecture-gallery .ag-category {
    display: block;
    margin-bottom: 5px;
    color: var(--ag-gold);
    font-size: 8px;
    font-weight: 600;
    letter-spacing: 2px;
}

.architecture-gallery .ag-caption h3 {
    margin: 0;
    color: var(--ag-text);
    font: 400 28px/1.2 var(--ag-serif);
    transition: color .3s;
}

.architecture-gallery .ag-card:hover .ag-caption h3 {
    color: var(--ag-gold-light);
}

.architecture-gallery .ag-arrow {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    border: 1px solid var(--ag-line);
    border-radius: 50%;
    color: var(--ag-gold);
    transition: transform .3s, background .3s;
}

.architecture-gallery .ag-card:hover .ag-arrow {
    transform: translate(3px, -3px);
    background: rgba(196, 164, 106, .1);
}

.architecture-gallery .ag-end {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 20px;
    margin-top: 90px;
}

.architecture-gallery .ag-end span {
    width: 80px;
    height: 1px;
    background: var(--ag-line);
}

.architecture-gallery .ag-end b {
    color: var(--ag-gold);
    font: 500 25px/1 var(--ag-serif);
}

/* Teljes képernyős képnézegető */

.architecture-gallery .ag-lightbox {
    position: fixed;
    z-index: 99999;
    inset: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 30px;
    padding: 75px 7%;
    background: rgba(9, 10, 8, .97);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .35s, visibility .35s;
}

.architecture-gallery .ag-lightbox.ag-opened {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
}

.architecture-gallery .ag-close {
    position: absolute;
    top: 25px;
    right: 35px;
    z-index: 3;
    width: 46px;
    height: 46px;
    border: 1px solid var(--ag-line);
    background: transparent;
    color: var(--ag-gold-light);
    font-size: 30px;
    line-height: 1;
    cursor: pointer;
    transition: background .3s, transform .3s;
}

.architecture-gallery .ag-close:hover {
    background: rgba(196, 164, 106, .15);
    transform: rotate(90deg);
}

.architecture-gallery .ag-lightbox-content {
    width: min(100%, 1050px);
    min-width: 0;
}

.architecture-gallery .ag-lightbox-image {
    height: min(68vh, 720px);
    display: flex;
    align-items: center;
    justify-content: center;
}

.architecture-gallery .ag-lightbox-image img {
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    box-shadow: 0 20px 80px rgba(0, 0, 0, .4);
}

.architecture-gallery .ag-lightbox-info {
    display: flex;
    align-items: center;
    gap: 28px;
    padding-top: 24px;
    margin-top: 22px;
    border-top: 1px solid var(--ag-line);
}

.architecture-gallery .ag-lightbox-number {
    flex-shrink: 0;
    color: var(--ag-gold);
    font: 400 27px/1 var(--ag-serif);
}

.architecture-gallery .ag-lightbox-info h3 {
    margin: 0;
    font: 400 29px/1.2 var(--ag-serif);
}

.architecture-gallery .ag-lightbox-info p {
    margin: 4px 0 0;
    color: var(--ag-muted);
    font-size: 11px;
}

.architecture-gallery .ag-nav {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 47px;
    height: 47px;
    border: 1px solid var(--ag-line);
    border-radius: 50%;
    background: transparent;
    color: var(--ag-gold-light);
    font-size: 20px;
    cursor: pointer;
    transition: background .3s, color .3s;
}

.architecture-gallery .ag-nav:hover {
    background: var(--ag-gold);
    color: #111210;
}

@keyframes agCardIn {
    from {
        opacity: 0;
        transform: translateY(18px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Tablet */

@media (max-width: 1050px) {
    .architecture-gallery {
        padding-inline: 5%;
    }

    .architecture-gallery .ag-grid {
        grid-template-rows: 245px 245px;
    }

    .architecture-gallery .ag-caption h3 {
        font-size: 25px;
    }
}

/* Mobil */

@media (max-width: 720px) {
    .architecture-gallery {
        padding: 70px 6%;
    }

    .architecture-gallery .ag-heading {
        display: block;
        margin-bottom: 32px;
    }

    .architecture-gallery .ag-heading h2 {
        font-size: clamp(43px, 10vw, 60px);
    }

    .architecture-gallery .ag-intro {
        margin-top: 15px;
        max-width: 300px;
    }

    .architecture-gallery .ag-toolbar {
        align-items: flex-start;
        flex-direction: column;
        gap: 12px;
        padding-block: 14px;
    }

    .architecture-gallery .ag-filters {
        gap: 5px;
    }

    .architecture-gallery .ag-filter {
        padding: 8px 10px;
        font-size: 9px;
    }

    .architecture-gallery .ag-grid {
        grid-template-columns: 1fr;
        grid-template-rows: auto;
        gap: 36px;
    }

    .architecture-gallery .ag-large,
    .architecture-gallery .ag-tall,
    .architecture-gallery .ag-wide,
    .architecture-gallery .ag-medium {
        grid-column: 1;
        grid-row: auto;
        width: 100%;
        margin: 0;
    }

    .architecture-gallery .ag-image-button,
    .architecture-gallery .ag-tall .ag-image-button,
    .architecture-gallery .ag-medium .ag-image-button {
        aspect-ratio: 1.28 / 1;
    }

    .architecture-gallery .ag-tall .ag-image-button {
        aspect-ratio: 1 / 1.13;
    }

    .architecture-gallery .ag-caption h3 {
        font-size: 27px;
    }

    .architecture-gallery .ag-open {
        top: 12px;
        right: 12px;
        width: 36px;
        height: 36px;
        opacity: 1;
        transform: none;
        font-size: 15px;
    }

    .architecture-gallery .ag-end {
        margin-top: 60px;
    }

    .architecture-gallery .ag-lightbox {
        gap: 10px;
        padding: 65px 5% 35px;
    }

    .architecture-gallery .ag-close {
        top: 13px;
        right: 15px;
        width: 38px;
        height: 38px;
    }

    .architecture-gallery .ag-lightbox-content {
        width: calc(100% - 70px);
    }

    .architecture-gallery .ag-lightbox-image {
        height: 55vh;
    }

    .architecture-gallery .ag-nav {
        width: 34px;
        height: 34px;
        font-size: 16px;
    }

    .architecture-gallery .ag-lightbox-info {
        align-items: flex-start;
        gap: 12px;
    }

    .architecture-gallery .ag-lightbox-number {
        font-size: 21px;
    }

    .architecture-gallery .ag-lightbox-info h3 {
        font-size: 24px;
    }

    .architecture-gallery .ag-lightbox-info p {
        font-size: 10px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .architecture-gallery *,
    .architecture-gallery *::before,
    .architecture-gallery *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
}
JavaScript
(() => {
    "use strict";

    const galleries = document.querySelectorAll(".architecture-gallery");

    galleries.forEach(gallery => {
        if (gallery.dataset.agInitialized === "true") return;
        gallery.dataset.agInitialized = "true";

        const cards = Array.from(gallery.querySelectorAll(".ag-card"));
        const filters = Array.from(gallery.querySelectorAll(".ag-filter"));
        const count = gallery.querySelector(".ag-count");

        const lightbox = gallery.querySelector(".ag-lightbox");
        const lightboxImage = lightbox.querySelector(".ag-lightbox-image img");
        const lightboxNumber = lightbox.querySelector(".ag-lightbox-number");
        const lightboxTitle = lightbox.querySelector(".ag-lightbox-info h3");
        const lightboxDescription = lightbox.querySelector(".ag-lightbox-info p");

        const closeButton = gallery.querySelector(".ag-close");
        const prevButton = gallery.querySelector(".ag-prev");
        const nextButton = gallery.querySelector(".ag-next");

        let visibleCards = [];
        let currentIndex = 0;
        let previousFocus = null;
        let previousBodyOverflow = "";

        const pad = number => String(number).padStart(2, "0");

        function updateCards(category = "all") {
            visibleCards = cards.filter(card => {
                const matches =
                    category === "all" ||
                    card.dataset.category === category;

                card.classList.toggle("ag-hidden", !matches);

                return matches;
            });

            count.textContent = pad(visibleCards.length);

            visibleCards.forEach((card, index) => {
                card.style.animationDelay = `${index * 90}ms`;

                const indexLabel = card.querySelector(".ag-index");
                if (indexLabel) {
                    indexLabel.textContent =
                        `${pad(index + 1)} / ${pad(visibleCards.length)}`;
                }
            });
        }

        // Kategóriaszűrés
        filters.forEach(button => {
            button.addEventListener("click", () => {
                filters.forEach(filter => {
                    filter.classList.remove("active");
                });

                button.classList.add("active");
                updateCards(button.dataset.filter);
            });
        });

        // A kártya saját képét használjuk, így a nagyított
        // változat útvonala is ugyanaz marad.
        function getCardData(card) {
            const image = card.querySelector(".ag-image-button img");

            return {
                src: image.currentSrc || image.src,
                alt: image.alt || card.dataset.title || "Építészeti kép",
                title: card.dataset.title || "Építészeti alkotás",
                description: card.dataset.description || ""
            };
        }

        function renderImage() {
            const card = visibleCards[currentIndex];
            if (!card) return;

            const data = getCardData(card);

            lightboxImage.src = data.src;
            lightboxImage.alt = data.alt;
            lightboxTitle.textContent = data.title;
            lightboxDescription.textContent = data.description;

            lightboxNumber.textContent =
                `${pad(currentIndex + 1)} / ${pad(visibleCards.length)}`;
        }

        function openLightbox(index) {
            if (!visibleCards.length) return;

            currentIndex =
                (index + visibleCards.length) % visibleCards.length;

            previousFocus = document.activeElement;
            previousBodyOverflow = document.body.style.overflow;

            renderImage();

            lightbox.classList.add("ag-opened");
            lightbox.setAttribute("aria-hidden", "false");
            document.body.style.overflow = "hidden";

            closeButton.focus();
        }

        function closeLightbox() {
            if (!lightbox.classList.contains("ag-opened")) return;

            lightbox.classList.remove("ag-opened");
            lightbox.setAttribute("aria-hidden", "true");

            document.body.style.overflow = previousBodyOverflow;
            lightboxImage.removeAttribute("src");
            lightboxImage.alt = "";

            if (previousFocus && typeof previousFocus.focus === "function") {
                previousFocus.focus();
            }
        }

        function moveLightbox(direction) {
            if (!visibleCards.length) return;

            currentIndex =
                (currentIndex + direction + visibleCards.length) %
                visibleCards.length;

            renderImage();
        }

        // Kép megnyitása
        cards.forEach(card => {
            const button = card.querySelector(".ag-image-button");

            button.addEventListener("click", () => {
                const index = visibleCards.indexOf(card);

                if (index !== -1) {
                    openLightbox(index);
                }
            });
        });

        closeButton.addEventListener("click", closeLightbox);
        prevButton.addEventListener("click", () => moveLightbox(-1));
        nextButton.addEventListener("click", () => moveLightbox(1));

        // Kattintás a sötét háttérre: bezárás
        lightbox.addEventListener("click", event => {
            if (event.target === lightbox) {
                closeLightbox();
            }
        });

        // Billentyűzetes kezelés
        document.addEventListener("keydown", event => {
            if (!lightbox.classList.contains("ag-opened")) return;

            if (event.key === "Escape") {
                closeLightbox();
            } else if (event.key === "ArrowLeft") {
                event.preventDefault();
                moveLightbox(-1);
            } else if (event.key === "ArrowRight") {
                event.preventDefault();
                moveLightbox(1);
            } else if (event.key === "Tab") {
                const focusable = [
                    closeButton,
                    prevButton,
                    nextButton
                ];

                const first = focusable[0];
                const last = focusable[focusable.length - 1];

                if (
                    event.shiftKey &&
                    document.activeElement === first
                ) {
                    event.preventDefault();
                    last.focus();
                } else if (
                    !event.shiftKey &&
                    document.activeElement === last
                ) {
                    event.preventDefault();
                    first.focus();
                }
            }
        });

        // Ha valamelyik távoli kép nem érhető el,
        // jelezzük a kártyán és a nagyított képnél is.
        gallery.querySelectorAll(".ag-image-button img").forEach(image => {
            image.addEventListener("error", () => {
                image.alt = "A kép nem tölthető be.";
            });
        });

        updateCards();
    });
})();