Catalogue and tags

I’m having trouble with
tags and categories

Working in
Characters wiki site

What I’m trying to do
I don’t understand how to make a catalog of things with tags, like games on Steam or movies on movie platforms, so that you can search not only by name, but also by category for easy navigation

What I’ve tried so far
I tried the store’s tools, but it’s inconvenient that there are always prices, availability, and other similar things

You want to build something like …

To be able to test the setup → navigate to → JS-Fiddle

Copy and paste the code to the HTML-AREA and run it.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CATALOG-APP</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap');

        body {
            font-family: 'Inter', sans-serif;
            background-color: #0a0f18;
            background-image: radial-gradient(circle at top, #141c2a 0%, #0a0f18 70%);
            color: #e2e8f0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .holographic-container {
            background: rgba(23, 33, 50, 0.4);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(45, 55, 72, 0.5);
            box-shadow: 0 0 40px rgba(59, 130, 246, 0.1);
            transition: box-shadow 0.3s ease;
        }
        .holographic-container:hover {
            box-shadow: 0 0 60px rgba(59, 130, 246, 0.2);
        }

        .card-highlight-glow {
            transition: transform 0.3s ease, box-shadow 0.3s ease, background-color 0.3s ease;
            position: relative;
            overflow: hidden;
        }

        .card-highlight-glow:hover {
            transform: translateY(-5px);
            box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
        }
        
        .glow-sci-fi { box-shadow: 0 0 20px rgba(59, 130, 246, 0.6); }
        .bg-highlight-sci-fi { background-color: rgba(59, 130, 246, 0.2); }
        .glow-fantasy { box-shadow: 0 0 20px rgba(139, 92, 246, 0.6); }
        .bg-highlight-fantasy { background-color: rgba(139, 92, 246, 0.2); }
        .glow-action { box-shadow: 0 0 20px rgba(239, 68, 68, 0.6); }
        .bg-highlight-action { background-color: rgba(239, 68, 68, 0.2); }
        .glow-thriller { box-shadow: 0 0 20px rgba(249, 115, 22, 0.6); }
        .bg-highlight-thriller { background-color: rgba(249, 115, 22, 0.2); }
        .glow-noir { box-shadow: 0 0 20px rgba(75, 85, 99, 0.6); }
        .bg-highlight-noir { background-color: rgba(75, 85, 99, 0.2); }
        .glow-mystery { box-shadow: 0 0 20px rgba(99, 102, 241, 0.6); }
        .bg-highlight-mystery { background-color: rgba(99, 102, 241, 0.2); }
        .glow-adventure { box-shadow: 0 0 20px rgba(16, 185, 129, 0.6); }
        .bg-highlight-adventure { background-color: rgba(16, 185, 129, 0.2); }
        .glow-superhero { box-shadow: 0 0 20px rgba(236, 72, 153, 0.6); }
        .bg-highlight-superhero { background-color: rgba(236, 72, 153, 0.2); }
        .glow-family { box-shadow: 0 0 20px rgba(251, 191, 36, 0.6); }
        .bg-highlight-family { background-color: rgba(251, 191, 36, 0.2); }
        .glow-crime { box-shadow: 0 0 20px rgba(30, 41, 59, 0.6); }
        .bg-highlight-crime { background-color: rgba(30, 41, 59, 0.2); }
        .glow-drama { box-shadow: 0 0 20px rgba(52, 211, 153, 0.6); }
        .bg-highlight-drama { background-color: rgba(52, 211, 153, 0.2); }
        .glow-comedy { box-shadow: 0 0 20px rgba(124, 58, 237, 0.6); }
        .bg-highlight-comedy { background-color: rgba(124, 58, 237, 0.2); }
        .glow-animation { box-shadow: 0 0 20px rgba(249, 168, 212, 0.6); }
        .bg-highlight-animation { background-color: rgba(249, 168, 212, 0.2); }
        .glow-musical { box-shadow: 0 0 20px rgba(129, 140, 248, 0.6); }
        .bg-highlight-musical { background-color: rgba(129, 140, 248, 0.2); }
        .glow-romance { box-shadow: 0 0 20px rgba(244, 114, 182, 0.6); }
        .bg-highlight-romance { background-color: rgba(244, 114, 182, 0.2); }
        .glow-biography { box-shadow: 0 0 20px rgba(100, 116, 139, 0.6); }
        .bg-highlight-biography { background-color: rgba(100, 116, 139, 0.2); }
        .glow-history { box-shadow: 0 0 20px rgba(220, 38, 38, 0.6); }
        .bg-highlight-history { background-color: rgba(220, 38, 38, 0.2); }
        .glow-war { box-shadow: 0 0 20px rgba(22, 163, 74, 0.6); }
        .bg-highlight-war { background-color: rgba(22, 163, 74, 0.2); }
        .glow-horror { box-shadow: 0 0 20px rgba(0, 0, 0, 0.6); }
        .bg-highlight-horror { background-color: rgba(0, 0, 0, 0.2); }
        .glow-western { box-shadow: 0 0 20px rgba(120, 113, 108, 0.6); }
        .bg-highlight-western { background-color: rgba(120, 113, 108, 0.2); }
        .glow-satire { box-shadow: 0 0 20px rgba(15, 23, 42, 0.6); }
        .bg-highlight-satire { background-color: rgba(15, 23, 42, 0.2); }
        .glow-supernatural { box-shadow: 0 0 20px rgba(255, 255, 255, 0.6); }
        .bg-highlight-supernatural { background-color: rgba(255, 255, 255, 0.1); }
        
        .input-style {
            background-color: rgba(30, 41, 59, 0.7);
            border: 1px solid rgba(71, 85, 105, 0.5);
            color: #e2e8f0;
            transition: all 0.2s ease;
        }
        .input-style:focus {
            outline: none;
            border-color: #38bdf8;
            box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.5);
        }

        .btn-style {
            background: rgba(59, 130, 246, 0.3);
            border: 1px solid rgba(59, 130, 246, 0.5);
            color: #fff;
            transition: all 0.2s ease;
            box-shadow: 0 0 10px rgba(59, 130, 246, 0.3);
        }
        .btn-style:hover {
            background: rgba(59, 130, 246, 0.5);
            box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
        }

        .toggle-switch-label {
            background: rgba(55, 65, 81, 0.7);
        }
        .toggle-switch-input:checked + .toggle-switch-label {
            background: #2563eb;
        }

        .json-viewer-container {
            background-color: rgba(15, 23, 42, 0.9);
            color: #34d399;
            border: 1px solid #1e293b;
        }
        
        .modal-content {
            background-color: rgba(15, 23, 42, 0.9);
            border: 1px solid rgba(45, 55, 72, 0.5);
            backdrop-filter: blur(5px);
            color: #e2e8f0;
        }
        .modal-content h3 {
            color: #38bdf8;
        }
        .modal-content .category-tag {
             background-color: rgba(59, 130, 246, 0.2);
             color: #93c5fd;
             border: 1px solid rgba(59, 130, 246, 0.5);
        }
    </style>
</head>
<body class="p-4 md:p-8 flex items-center justify-center">

    <div class="container mx-auto max-w-5xl holographic-container rounded-3xl p-6 md:p-10 flex flex-col gap-8">
        <h1 class="text-4xl font-extrabold text-cyan-400 text-center font-[Orbitron] tracking-wider">CATALOG-APP</h1>

        <div class="flex flex-col md:flex-row gap-4 md:gap-6 items-center">
            <div class="relative flex-grow w-full">
                <input type="text" id="searchInput" placeholder="Search by title or description..." class="w-full p-4 pl-12 rounded-xl focus:outline-none input-style text-lg">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
                </svg>
            </div>
            <div class="flex flex-col md:flex-row gap-4 w-full md:w-auto">
                <button id="resetButton" class="font-semibold py-3 px-6 rounded-xl shadow-lg w-full md:w-auto btn-style bg-gray-700 hover:bg-gray-800">
                    Reset
                </button>
                <button id="showJsonBtn" class="font-semibold py-3 px-6 rounded-xl shadow-lg w-full md:w-auto btn-style">
                    Toggle JSON
                </button>
            </div>
        </div>

        <div class="flex flex-col gap-4">
            <div class="flex items-center justify-center gap-4 text-gray-400">
                <span class="text-sm font-medium">Multi-select</span>
                <label class="toggle-switch-container">
                    <input type="checkbox" id="selectionModeSwitch" class="toggle-switch-input">
                    <span class="toggle-switch-label"></span>
                </label>
                <span class="text-sm font-medium">Single-select</span>
            </div>
            <div id="filterButtons" class="flex flex-wrap gap-2 md:gap-3 justify-center">
            </div>
        </div>
        
        <div id="jsonOutput" class="json-viewer-container hidden p-6 rounded-2xl">
            <pre><code id="jsonCode" class="text-sm"></code></pre>
        </div>

        <div class="relative min-h-[300px]">
            <div id="loadingIndicator" class="absolute inset-0 flex items-center justify-center bg-gray-800 bg-opacity-75 z-10 hidden rounded-2xl">
                <div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-cyan-500"></div>
            </div>

            <div id="noResultsMessage" class="absolute inset-0 flex items-center justify-center p-4 z-0 hidden">
                <p class="text-2xl text-gray-500 font-medium text-center">No items match your search criteria.</p>
            </div>

            <div id="catalogContainer" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
            </div>
        </div>
    </div>

    <div id="itemModal" class="modal fixed inset-0 z-50 overflow-auto bg-black bg-opacity-80 flex items-center justify-center opacity-0 pointer-events-none transition-opacity duration-300">
        <div class="modal-content transform scale-95 transition-transform duration-300 w-11/12 md:w-3/4 lg:w-1/2 p-8 rounded-3xl relative">
            <button id="closeModalBtn" class="absolute top-4 right-4 text-gray-500 hover:text-gray-300 transition-colors duration-200">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                </svg>
            </button>
            <h3 id="modalTitle" class="text-3xl font-extrabold text-cyan-400 mb-2 font-[Orbitron]"></h3>
            <div id="modalCategories" class="flex flex-wrap gap-2 mb-4"></div>
            <p id="modalDescription" class="text-md text-gray-300"></p>
        </div>
    </div>

    <script>
        const allItemsData = [
            { id: 1, title: 'The Matrix', categories: ['sci-fi', 'action', 'thriller'], description: 'A computer hacker learns about the true nature of his reality.' },
            { id: 2, title: 'Lord of the Rings', categories: ['fantasy', 'adventure'], description: 'A meek Hobbit from the Shire sets out to destroy the One Ring.' },
            { id: 3, title: 'Blade Runner', categories: ['sci-fi', 'noir', 'mystery'], description: 'A bounty hunter must hunt down four replicants.' },
            { id: 4, title: 'Dune', categories: ['sci-fi', 'adventure'], description: 'A noble family becomes embroiled in a war for control of a valuable asset.' },
            { id: 5, title: 'Avengers: Endgame', categories: ['action', 'superhero'], description: 'Adrift in space, Tony Stark sends a message to Pepper Potts.' },
            { id: 6, title: 'The Hobbit', categories: ['fantasy', 'adventure', 'family'], description: 'A young hobbit is swept into an epic quest.' },
            { id: 7, title: 'Star Wars: A New Hope', categories: ['sci-fi', 'action', 'adventure'], description: 'Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, and a wookiee.' },
            { id: 8, title: 'Game of Thrones', categories: ['fantasy', 'drama', 'action'], description: 'Nine noble families fight for control over the lands of Westeros.' },
            { id: 9, title: 'Interstellar', categories: ['sci-fi', 'drama', 'adventure'], description: 'A team of explorers travel through a wormhole in space in an attempt to ensure humanity\'s survival.' },
            { id: 10, title: 'Pulp Fiction', categories: ['crime', 'thriller', 'comedy'], description: 'The lives of two mob hitmen, a boxer, a gangster\'s wife, and a pair of diner bandits intertwine.' },
            { id: 11, title: 'Jurassic Park', categories: ['sci-fi', 'adventure', 'thriller'], description: 'A pragmatic paleontologist tours an island theme park of cloned dinosaurs.' },
            { id: 12, title: 'The Godfather', categories: ['crime', 'drama'], description: 'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire.' },
            { id: 13, title: 'Inception', categories: ['sci-fi', 'action', 'thriller'], description: 'A thief who steals corporate secrets through the use of dream-sharing technology.' },
            { id: 14, title: 'The Dark Knight', categories: ['action', 'superhero', 'crime'], description: 'Batman faces a new super-villain, the Joker, who wreaks havoc on Gotham.' },
            { id: 15, title: 'Fight Club', categories: ['drama', 'thriller'], description: 'An insomniac office worker looking for a way to change his life crosses paths with a devil-may-care soap maker.' },
            { id: 16, title: 'Forrest Gump', categories: ['drama', 'romance'], description: 'The presidencies of Kennedy and Johnson, the Vietnam War, and other history told from the perspective of an Alabama man with an IQ of 75.' },
            { id: 17, title: 'The Lion King', categories: ['animation', 'musical', 'family'], description: 'A young lion prince flees his kingdom only to learn about responsibility.' },
            { id: 18, title: 'Schindler\'s List', categories: ['biography', 'drama', 'history'], description: 'A German industrialist saves more than a thousand Jewish refugees from the Holocaust.' },
            { id: 19, title: 'Gladiator', categories: ['action', 'drama', 'history'], description: 'A Roman general is betrayed and his family murdered by an emperor\'s corrupt son.' },
            { id: 20, title: 'The Departed', categories: ['crime', 'thriller'], description: 'An undercover cop and a mole in the police force try to identify each other.' },
            { id: 21, title: 'Toy Story', categories: ['animation', 'comedy', 'family'], description: 'A cowboy doll is profoundly threatened and jealous when a new spaceman toy supplants him.' },
            { id: 22, title: 'Mad Max: Fury Road', categories: ['action', 'sci-fi', 'post-apocalyptic'], description: 'In a post-apocalyptic wasteland, a woman rebels against a tyrannical ruler.' },
            { id: 23, title: 'Coco', categories: ['animation', 'fantasy', 'musical'], description: 'Aspiring musician Miguel, confronted with his family\'s ancestral ban on music, enters the Land of the Dead.' },
            { id: 24, title: 'Get Out', categories: ['horror', 'thriller', 'mystery'], description: 'A young African-American man visits his white girlfriend\'s parents for the first time.' },
            { id: 25, title: 'Back to the Future', categories: ['sci-fi', 'comedy'], description: 'Marty McFly is accidentally sent thirty years into the past in a time-traveling DeLorean.' },
            { id: 26, title: 'The Social Network', categories: ['biography', 'drama'], description: 'Harvard student Mark Zuckerberg creates the social networking site that would become known as Facebook.' },
            { id: 27, title: 'Finding Nemo', categories: ['animation', 'adventure', 'family'], description: 'A clownfish goes on a journey to find his missing son.' },
            { id: 28, title: 'The Silence of the Lambs', categories: ['thriller', 'crime'], description: 'A young F.B.I. cadet must receive the help of an incarcerated cannibal.' },
            { id: 29, title: 'Django Unchained', categories: ['western', 'action'], description: 'A freed slave sets out to rescue his wife from a brutal Mississippi plantation owner.' },
            { id: 30, title: 'Inglourious Basterds', categories: ['war', 'action', 'comedy'], description: 'A group of Jewish-American soldiers is assigned to assassinate Nazi leaders.' },
            { id: 31, title: 'Parasite', categories: ['thriller', 'drama'], description: 'The poor Kim family con their way into becoming the servants of the wealthy Park family.' },
            { id: 32, title: 'Arrival', categories: ['sci-fi', 'drama'], description: 'Linguist Louise Banks is recruited by the military to communicate with alien lifeforms.' },
            { id: 33, title: 'Goodfellas', categories: ['biography', 'crime', 'drama'], description: 'The story of Henry Hill and his life in the mob, covering his relationship with his wife Karen.' },
            { id: 34, title: 'La La Land', categories: ['musical', 'romance', 'drama'], description: 'A jazz pianist and an aspiring actress meet and fall in love in Los Angeles.' },
            { id: 35, title: 'Spirited Away', categories: ['animation', 'fantasy', 'adventure'], description: 'During her family\'s move to the suburbs, a sullen 10-year-old girl wanders into a world ruled by gods.' },
            { id: 36, title: 'E.T. the Extra-Terrestrial', categories: ['sci-fi', 'family', 'adventure'], description: 'A troubled child summons the courage to help a friendly alien escape Earth.' },
            { id: 37, title: 'Joker', categories: ['crime', 'drama', 'thriller'], description: 'A mentally troubled comedian embarks on a downward spiral that leads to a life of crime.' },
            { id: 38, title: 'The Grand Budapest Hotel', categories: ['comedy', 'adventure'], description: 'The adventures of a legendary concierge at a famous European hotel between the first and second World Wars.' },
            { id: 39, title: 'Saving Private Ryan', categories: ['war', 'drama'], description: 'Following the Normandy Landings, a group of U.S. soldiers goes behind enemy lines.' },
            { id: 40, title: '1917', categories: ['war', 'drama', 'action'], description: 'Two young British soldiers are given an impossible mission during World War I.' },
            { id: 41, title: 'Donnie Darko', categories: ['sci-fi', 'mystery', 'coming-of-age'], description: 'A troubled teenager is plagued by visions of a man in a rabbit suit who manipulates him to commit a series of crimes.' },
            { id: 42, title: 'The Shining', categories: ['horror', 'thriller', 'mystery'], description: 'A family heads to an isolated hotel for the winter where a sinister presence influences the father.' },
            { id: 43, title: '2001: A Space Odyssey', categories: ['sci-fi', 'adventure'], description: 'Humanity finds a mysterious, obviously artificial, object buried beneath the lunar surface.' },
            { id: 44, title: 'Eternal Sunshine of the Spotless Mind', categories: ['romance', 'sci-fi', 'drama'], description: 'A couple undergoes a procedure to erase each other from their memories.' },
            { id: 45, title: 'No Country for Old Men', categories: ['crime', 'thriller'], description: 'Violence and mayhem ensue after a hunter stumbles upon a drug deal gone wrong.' },
            { id: 46, title: 'The Shawshank Redemption', categories: ['drama'], description: 'Two imprisoned men bond over a number of years, finding solace and eventual redemption.' },
            { id: 47, title: 'Fight Club', categories: ['drama', 'thriller', 'action'], description: 'An insomniac office worker looking for a way to change his life crosses paths with a devil-may-care soap maker.' },
            { id: 48, title: 'Pulp Fiction', categories: ['crime', 'drama', 'comedy'], description: 'The lives of two mob hitmen, a boxer, a gangster\'s wife, and a pair of diner bandits intertwine.' },
            { id: 49, title: 'The Departed', categories: ['crime', 'drama', 'thriller'], description: 'An undercover cop and a mole in the police force try to identify each other.' },
            { id: 50, title: 'Inception', categories: ['sci-fi', 'action', 'thriller'], description: 'A thief who steals corporate secrets through the use of dream-sharing technology.' },
            { id: 51, title: 'Forrest Gump', categories: ['drama', 'romance'], description: 'The presidencies of Kennedy and Johnson, the Vietnam War, and other history from the perspective of an Alabama man with an IQ of 75.' }
        ];

        const state = {
            catalogItems: [],
            selectedFilters: new Set(),
            searchTerm: '',
            isSingleSelect: false,
        };

        const catalogContainer = document.getElementById('catalogContainer');
        const searchInput = document.getElementById('searchInput');
        const filterButtonsContainer = document.getElementById('filterButtons');
        const loadingIndicator = document.getElementById('loadingIndicator');
        const noResultsMessage = document.getElementById('noResultsMessage');
        const selectionModeSwitch = document.getElementById('selectionModeSwitch');
        const resetButton = document.getElementById('resetButton');
        const showJsonBtn = document.getElementById('showJsonBtn');
        const jsonOutputDiv = document.getElementById('jsonOutput');
        const jsonCodeElement = document.getElementById('jsonCode');

        const itemModal = document.getElementById('itemModal');
        const closeModalBtn = document.getElementById('closeModalBtn');
        const modalTitle = document.getElementById('modalTitle');
        const modalDescription = document.getElementById('modalDescription');
        const modalCategories = document.getElementById('modalCategories');

        const categoryHighlightClasses = {
            'sci-fi': { glow: 'glow-sci-fi', bg: 'bg-highlight-sci-fi', tagBg: 'bg-highlight-sci-fi' },
            'fantasy': { glow: 'glow-fantasy', bg: 'bg-highlight-fantasy', tagBg: 'bg-highlight-fantasy' },
            'action': { glow: 'glow-action', bg: 'bg-highlight-action', tagBg: 'bg-highlight-action' },
            'thriller': { glow: 'glow-thriller', bg: 'bg-highlight-thriller', tagBg: 'bg-highlight-thriller' },
            'noir': { glow: 'glow-noir', bg: 'bg-highlight-noir', tagBg: 'bg-highlight-noir' },
            'mystery': { glow: 'glow-mystery', bg: 'bg-highlight-mystery', tagBg: 'bg-highlight-mystery' },
            'adventure': { glow: 'glow-adventure', bg: 'bg-highlight-adventure', tagBg: 'bg-highlight-adventure' },
            'superhero': { glow: 'glow-superhero', bg: 'bg-highlight-superhero', tagBg: 'bg-highlight-superhero' },
            'family': { glow: 'glow-family', bg: 'bg-highlight-family', tagBg: 'bg-highlight-family' },
            'crime': { glow: 'glow-crime', bg: 'bg-highlight-crime', tagBg: 'bg-highlight-crime' },
            'drama': { glow: 'glow-drama', bg: 'bg-highlight-drama', tagBg: 'bg-highlight-drama' },
            'comedy': { glow: 'glow-comedy', bg: 'bg-highlight-comedy', tagBg: 'bg-highlight-comedy' },
            'animation': { glow: 'glow-animation', bg: 'bg-highlight-animation', tagBg: 'bg-highlight-animation' },
            'musical': { glow: 'glow-musical', bg: 'bg-highlight-musical', tagBg: 'bg-highlight-musical' },
            'romance': { glow: 'glow-romance', bg: 'bg-highlight-romance', tagBg: 'bg-highlight-romance' },
            'biography': { glow: 'glow-biography', bg: 'bg-highlight-biography', tagBg: 'bg-highlight-biography' },
            'history': { glow: 'glow-history', bg: 'bg-highlight-history', tagBg: 'bg-highlight-history' },
            'war': { glow: 'glow-war', bg: 'bg-highlight-war', tagBg: 'bg-highlight-war' },
            'horror': { glow: 'glow-horror', bg: 'bg-highlight-horror', tagBg: 'bg-highlight-horror' },
            'western': { glow: 'glow-western', bg: 'bg-highlight-western', tagBg: 'bg-highlight-western' },
            'satire': { glow: 'glow-satire', bg: 'bg-highlight-satire', tagBg: 'bg-highlight-satire' },
            'supernatural': { glow: 'glow-supernatural', bg: 'bg-highlight-supernatural', tagBg: 'bg-highlight-supernatural' },
            'post-apocalyptic': { glow: 'glow-sci-fi', bg: 'bg-highlight-sci-fi', tagBg: 'bg-highlight-sci-fi' },
            'coming-of-age': { glow: 'glow-drama', bg: 'bg-highlight-drama', tagBg: 'bg-highlight-drama' },
        };

        function showLoading() {
            loadingIndicator.classList.remove('hidden');
            catalogContainer.classList.add('hidden');
            noResultsMessage.classList.add('hidden');
            jsonOutputDiv.classList.add('hidden');
        }

        function hideLoading() {
            loadingIndicator.classList.add('hidden');
            catalogContainer.classList.remove('hidden');
        }
        
        function showNoResults() {
            catalogContainer.classList.add('hidden');
            noResultsMessage.classList.remove('hidden');
        }

        function openModal(item) {
            modalTitle.textContent = item.title;
            modalDescription.textContent = item.description;
            modalCategories.innerHTML = '';
            item.categories.forEach(category => {
                const span = document.createElement('span');
                const catClasses = categoryHighlightClasses[category] ? `bg-opacity-20 ${categoryHighlightClasses[category].tagBg}` : 'bg-gray-600 bg-opacity-20';
                span.className = `category-tag text-xs font-semibold px-3 py-1 rounded-full ${catClasses}`;
                span.textContent = category;
                modalCategories.appendChild(span);
            });
            itemModal.classList.add('active');
            itemModal.classList.remove('opacity-0', 'pointer-events-none');
            setTimeout(() => {
                itemModal.querySelector('.modal-content').classList.remove('scale-95');
            }, 10);
        }

        function closeModal() {
            itemModal.querySelector('.modal-content').classList.add('scale-95');
            itemModal.classList.add('opacity-0', 'pointer-events-none');
        }

        function renderCatalog(items) {
            catalogContainer.innerHTML = '';
            if (items.length === 0) {
                showNoResults();
                return;
            }
            
            hideLoading();
            const hasActiveFilters = state.selectedFilters.size > 0;

            items.forEach(item => {
                const itemCard = document.createElement('div');
                itemCard.dataset.itemId = item.id;
                
                let cardClasses = ['bg-gray-800', 'bg-opacity-40', 'rounded-3xl', 'shadow-xl', 'p-6', 'flex', 'flex-col', 'gap-3', 'card-highlight-glow', 'cursor-pointer', 'border', 'border-transparent'];
                
                if (hasActiveFilters) {
                    const matchingCategory = item.categories.find(cat => state.selectedFilters.has(cat));
                    if (matchingCategory && categoryHighlightClasses[matchingCategory]) {
                        const { glow, bg } = categoryHighlightClasses[matchingCategory];
                        cardClasses.push(glow, bg, 'border-cyan-500', 'border-opacity-50');
                    }
                }

                itemCard.className = cardClasses.join(' ');

                itemCard.innerHTML = `
                    <h3 class="text-xl font-bold text-cyan-400">${item.title}</h3>
                    <p class="text-sm text-gray-400 line-clamp-3">${item.description}</p>
                    <div class="flex flex-wrap gap-2 mt-auto">
                        ${item.categories.map(category => {
                            const catHighlight = categoryHighlightClasses[category];
                            const tagBgClass = catHighlight ? `${catHighlight.tagBg} bg-opacity-20 text-white border-${catHighlight.tagBg.split('-')[1]}-400` : 'bg-gray-600 bg-opacity-20 text-gray-200 border-gray-500';
                            return `<span class="category-tag text-xs font-semibold px-3 py-1 rounded-full ${tagBgClass} border">${category}</span>`;
                        }).join('')}
                    </div>
                `;
                catalogContainer.appendChild(itemCard);
            });
        }
        
        function renderFilterButtons() {
            const allCategories = new Set();
            allItemsData.forEach(item => {
                item.categories.forEach(category => allCategories.add(category));
            });
            
            const sortedCategories = Array.from(allCategories).sort();

            filterButtonsContainer.innerHTML = `
                <button data-filter="all" class="filter-btn btn-style bg-cyan-600 hover:bg-cyan-700 text-white font-semibold py-2 px-6 rounded-full shadow-md transition-all duration-200">All</button>
            `;
            
            sortedCategories.forEach(category => {
                const button = document.createElement('button');
                button.className = 'filter-btn bg-gray-600 text-gray-200 font-medium py-2 px-6 rounded-full shadow-sm hover:bg-gray-700 transition-colors duration-200';
                button.dataset.filter = category;
                button.textContent = category.charAt(0).toUpperCase() + category.slice(1);
                filterButtonsContainer.appendChild(button);
            });
            
            updateFilterButtonStyles();
        }
        
        function updateFilterButtonStyles() {
            document.querySelectorAll('.filter-btn').forEach(btn => {
                const filter = btn.dataset.filter;
                if (filter === 'all') {
                    if (state.selectedFilters.size === 0) {
                        btn.classList.remove('bg-gray-600', 'text-gray-200', 'hover:bg-gray-700');
                        btn.classList.add('bg-cyan-600', 'text-white', 'hover:bg-cyan-700', 'btn-style');
                    } else {
                        btn.classList.remove('bg-cyan-600', 'text-white', 'hover:bg-cyan-700', 'btn-style');
                        btn.classList.add('bg-gray-600', 'text-gray-200', 'hover:bg-gray-700');
                    }
                } else {
                    if (state.selectedFilters.has(filter)) {
                        btn.classList.remove('bg-gray-600', 'text-gray-200', 'hover:bg-gray-700');
                        btn.classList.add('bg-cyan-600', 'text-white', 'hover:bg-cyan-700', 'btn-style');
                    } else {
                        btn.classList.remove('bg-cyan-600', 'text-white', 'hover:bg-gray-700', 'btn-style');
                        btn.classList.add('bg-gray-600', 'text-gray-200', 'hover:bg-gray-700');
                    }
                }
            });
        }
        
        async function fetchCatalogItems() {
            showLoading();
            await new Promise(resolve => setTimeout(resolve, 800));
            state.catalogItems = allItemsData;
            updateCatalog();
        }

        function updateCatalog() {
            const searchTerm = state.searchTerm.toLowerCase();
            
            const filteredItems = state.catalogItems.filter(item => {
                const searchMatch = item.title.toLowerCase().includes(searchTerm) || item.description.toLowerCase().includes(searchTerm);
                
                const categoryMatch = state.selectedFilters.size === 0 || item.categories.some(category => state.selectedFilters.has(category));
                
                return searchMatch && categoryMatch;
            });
            
            renderCatalog(filteredItems);
        }
        
        searchInput.addEventListener('input', (event) => {
            state.searchTerm = event.target.value;
            updateCatalog();
        });
        
        filterButtonsContainer.addEventListener('click', (event) => {
            const button = event.target.closest('.filter-btn');
            if (!button) return;
            
            const filter = button.dataset.filter;
            
            if (filter === 'all') {
                state.selectedFilters.clear();
            } else if (state.isSingleSelect) {
                state.selectedFilters.clear();
                state.selectedFilters.add(filter);
            } else {
                if (state.selectedFilters.has(filter)) {
                    state.selectedFilters.delete(filter);
                } else {
                    state.selectedFilters.add(filter);
                }
            }
            updateFilterButtonStyles();
            updateCatalog();
        });

        selectionModeSwitch.addEventListener('change', (event) => {
            state.isSingleSelect = event.target.checked;
            if (state.isSingleSelect && state.selectedFilters.size > 1) {
                const firstFilter = state.selectedFilters.values().next().value;
                state.selectedFilters.clear();
                state.selectedFilters.add(firstFilter);
            }
            updateFilterButtonStyles();
            updateCatalog();
        });

        resetButton.addEventListener('click', () => {
            state.searchTerm = '';
            state.selectedFilters.clear();
            state.isSingleSelect = false;

            searchInput.value = '';
            selectionModeSwitch.checked = false;
            jsonOutputDiv.classList.add('hidden');

            updateFilterButtonStyles();
            updateCatalog();
        });
        
        showJsonBtn.addEventListener('click', () => {
            const isHidden = jsonOutputDiv.classList.contains('hidden');
            if (isHidden) {
                const appState = {
                    searchTerm: state.searchTerm,
                    selectedFilters: Array.from(state.selectedFilters),
                    isSingleSelect: state.isSingleSelect,
                };
                const jsonString = JSON.stringify(appState, null, 2);
                jsonCodeElement.textContent = jsonString;
                jsonOutputDiv.classList.remove('hidden');
                catalogContainer.classList.add('hidden');
                noResultsMessage.classList.add('hidden');
            } else {
                jsonOutputDiv.classList.add('hidden');
                if (state.catalogItems.length > 0) {
                    catalogContainer.classList.remove('hidden');
                } else {
                    noResultsMessage.classList.remove('hidden');
                }
            }
        });

        catalogContainer.addEventListener('click', (event) => {
            const card = event.target.closest('div[data-item-id]');
            if (!card) return;

            const itemId = parseInt(card.dataset.itemId);
            const item = allItemsData.find(i => i.id === itemId);

            if (item) {
                openModal(item);
            }
        });
        
        closeModalBtn.addEventListener('click', closeModal);
        itemModal.addEventListener('click', (event) => {
            if (event.target === itemModal) {
                closeModal();
            }
        });

        document.addEventListener('DOMContentLoaded', () => {
            renderFilterButtons();
            fetchCatalogItems();
        });

    </script>
</body>
</html>


1 Like

or add some code to your search page for filtering the tags.
Something like this could be adjusted to suit your needs

import wixData from 'wix-data';

$w.onReady(function () {
    $w('#gamesTags').onChange(applyGameFilter);

    $w("#resetButton").onClick(function () {
        $w("#gamesTags").value = undefined;
        applyGameFilter();
    });

    function applyGameFilter() {
        let query = wixData.query('Games');
        let selectedGames = $w('#gamesTags').value;
        if (selectedGames && selectedGames.length > 0) {
            query = query.hasSome('games', selectedGames);
        }

        query.find()
            .then((results) => {
                console.log("Filtered items by games:", results.items.length);
                $w('#gamesRepeater').data = results.items;
            })
            .catch((err) => {
                console.error("Error filtering games:", err);
            });
    }
});

If I knew how and understood how to code a website, I wouldn’t use website builders

If I knew how and understood how to code a website, I wouldn’t use website builders

Yes that is for sure a statement !!!

Now the question is, when you will start to learn programming?
Or i go further and will ask you → DO YOU REALLY NEED TO KNOW HOW TO CODE → to be able to generate codings?

This is a really good question right?

Maybe 3-years ago, it was neccessary to study programming languages to be capable to code! Nower days, since everything → GOES-VIBRANT <–, it is not neccessary anymore to learn any of the programming languages!

Do not understand me wrong → IT IS FOR SURE GOOD TO KNOW HOW TO CODE <— especially in programming languages like → HTML, CSS, JS, PYTHON (the rest is not really interesting xDDDD), but it is not a → MUST ← anymore!!!

Your hidden statemant was → VIBRANT <— :grin: search for it and learn how to speed up and improve your own learning.

We all have to do some evolution →

View post on imgur.com

you can stay where you are → (having always the same problems of being stucked during your content creations, because of leck of knowledge, or you take some time to gain some new knowlefge → to BOOST-UP your work.

And believe me, once you understood what i mean → you will be happy about my written post here. :grin:

The last one on the pic → is the vibrant one :wink: → and he even don’t know how to code!!!