Source: scripts/utils/selectorBuilder.js

class SelectorBuilder {
    /** @type {string} */
    id
    /** @type {Array<CircuitSet>} */
    circuitSets

    /**
     * @param prefix {string} distinguish between different, is used in the id of the html element
     * @param mode {window.definitions.mode} defines paths for the created CircuitMaps
     * */
    constructor(prefix = "", mode = window.definitions.mode.learn) {
        this.id = prefix + "accordion"
        this.prefix = prefix;
    }

    // Build accordion with items and modals
    /**
     * @param accordionId {string} id of the html element that is appended to the document
     * @param circuitSets {Array<CircuitSet>} based on the circuit sets the accordion items are generated
     * the set is saved in this.circuitSets and used later on for updates of the accordion*/
    buildAccordionSelectors(accordionId, circuitSets) {
        this.id = accordionId;
        this.circuitSets = circuitSets;
        let accordion = document.createElement("div");
        accordion.classList.add("accordion", "accordion-flush", "mt-5", "mx-auto");
        accordion.id = this.id;
        accordion.style = "max-width: 500px;";

        for (let circuitSet of this.circuitSets) {
            if (circuitSet.identifier === window.definitions.selectorIDs.quickstart) continue;

            let item = this.buildAccordionItem(circuitSet.identifier, accordionId);
            let modal = this.buildOverviewModal(circuitSet);

            accordion.appendChild(item);
            document.body.appendChild(modal);
        }
        return accordion;
    }

    // Build one accordion item
    buildAccordionItem(identifier, accordionId) {
        let accordionItem = document.createElement("div");
        accordionItem.classList.add("accordion-item");
        accordionItem.innerHTML = `
            <h2 class="accordion-header" id="flush-heading-${this.prefix}${identifier}" style="display: flex; justify-content: space-between; background-color: ${colors.currentBsBackground}; font-size: x-large">
                <button id="${identifier}-${this.prefix}acc-btn" 
                        style="color: ${colors.currentHeadingsForeground}; background-color: ${colors.currentBsBackground}" 
                        class="accordion-button collapsed" 
                        type="button" 
                        data-bs-toggle="collapse" 
                        data-bs-target="#flush-collapse-${this.prefix}${identifier}" 
                        aria-expanded="false" 
                        aria-controls="flush-collapse-${this.prefix}${identifier}">
                    ${languageManager.currentLang.selector.selectorHeadings[identifier]}
                </button>
            </h2>
            <div id="flush-collapse-${this.prefix}${identifier}" 
                 class="accordion-collapse collapse" 
                 aria-labelledby="flush-heading-${this.prefix}${identifier}" 
                 data-bs-parent="#${accordionId}">
                <div class="accordion-body" style="background-color: inherit">
                    <div class="container vcCheckBox" 
                         style="text-align: left; max-width: 350px; padding: 0; color:${colors.currentHeadingsForeground};">
                        <button onclick="document.getElementById('${identifier}-${this.prefix}overviewModal').blur()" 
                                id="${identifier}-${this.prefix}overviewModalBtn" 
                                type="button" 
                                class="btn my-1 btn-primary modalOverviewBtn" 
                                data-bs-toggle="modal" 
                                data-bs-target="#${identifier}-${this.prefix}overviewModal" 
                                style="color: ${colors.currentHeadingsForeground}; border: 1px solid ${colors.currentHeadingsForeground};">
                            ${languageManager.currentLang.selector.overviewModalBtn}
                        </button>
                    </div>
                    ${this.createCarousel(identifier)}
                </div>
            </div>`;
        return accordionItem;
    }

    buildOverviewModal(circuitSet) {
        let modalId = `${circuitSet.identifier}-${this.prefix}overviewModal`;
        let modal = document.createElement("div");
        modal.classList.add("modal", "fade", "modal-xl");
        modal.id = modalId;
        modal.tabIndex = "-1";
        modal.setAttribute("aria-labelledby", `${modalId}Label`);
        modal.setAttribute("aria-hidden", "true");

        modal.innerHTML = `
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header" style="background: ${colors.currentBackground}; color: white;">
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body" style="background: ${colors.currentBackground}; color: white;">
                        ${this.generateOverviewGrid(circuitSet)}
                    </div>
                    <div class="modal-footer justify-content-center" style="background: ${colors.currentBackground}">
                        <button id="${modalId}CloseBtn" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
                            ${languageManager.currentLang.simplifier.closeBtn}
                        </button>
                    </div>
                </div>
            </div>`;
        return modal;
    }

    /** @param circuitSet {CircuitSet} */
    generateOverviewGrid(circuitSet) {
        let grid = document.createElement("div");
        grid.classList.add("row");
        for (let circuit of circuitSet.circuitMaps) {
            let col = document.createElement("div");
            col.classList.add("col-md-4", "col-sm-6", "col-12", "mb-4", "text-center", "justify-content-center");
            col.innerHTML = `
                <div id="${circuit.circuitDivID}-overviewModal" class="svg-selector mx-auto" style="
                border-color: black;"></div>
                <button id="${circuit.btn}-modalBtn" onclick="document.getElementById('${circuit.btn}-modalBtn').blur()" class="btn btn-warning text-dark px-5 circuitStartBtnModal">start</button>
                `;
            grid.appendChild(col);
        }
        return grid.outerHTML;
    }

    // Build carousel wrapper
    createCarousel(identifier) {
        const carouselElements = this.createCarouselElements(identifier);
        return `
            <div id="${identifier}-${this.prefix}carousel" class="carousel slide" data-interval="false">
                <div class="carousel-inner">
                    ${carouselElements}
                </div>
                <button id="${identifier}-${this.prefix}prev-btn" 
                        class="carousel-control-prev" 
                        type="button" 
                        data-bs-target="#${identifier}-${this.prefix}carousel" 
                        data-bs-slide="prev">
                    <span class="carousel-control-prev-icon" aria-hidden="true" style="background-color: ${colors.prevNextBtnBackgroundColor};"></span>
                    <span class="visually-hidden">Previous</span>
                </button>
                <button id="${identifier}-${this.prefix}next-btn" 
                        class="carousel-control-next" 
                        type="button" 
                        data-bs-target="#${identifier}-${this.prefix}carousel" 
                        data-bs-slide="next">
                    <span class="carousel-control-next-icon" aria-hidden="true" style="background-color: ${colors.prevNextBtnBackgroundColor};"></span>
                    <span class="visually-hidden">Next</span>
                </button>
            </div>`;
    }

    // Carousel item for one circuit
    carouselItem(circuit) {
        return `
            <div class="carousel-item justify-content-center">
                <div id="${circuit.btnOverlay}" class="img-overlay">
                    <button id="${circuit.btn}" class="btn btn-warning text-dark px-5 circuitStartBtn">
                        <div class="fill-layer"></div>
                        <div class="progress-stripes"></div>
                        <span class="button-text">start</span>
                    </button>
                </div>
                <div id="${circuit.circuitDivID}" class="svg-selector mx-auto" style="border-color: ${colors.currentForeground}"></div>
            </div>`;
    }

    // Collect all carousel items
    createCarouselElements(identifier) {
        let carouselElementString = "";
        for (const circuitSet of this.circuitSets) {
            if (circuitSet.identifier !== identifier) continue;
            for (const circuit of circuitSet.circuitMaps) {
                carouselElementString += this.carouselItem(circuit);
            }
        }
        // Mark first as active
        return carouselElementString.replace("carousel-item justify", "carousel-item active justify");
    }

    updateSelectorCounters() {
        for (let circuitSet of this.circuitSets) {
            // set number of done circuits/available circuits
            let availableCircuits = circuitSet.circuitMaps.length;
            let identifier = circuitSet.identifier;
            // Search in storage for done circuits
            // Storage will store e.g.: doneCircuits-res = ["00_hetznecker.txt", "01_ohm.txt"]
            let doneCircuits = storageManager.circuitsDone.loadValue(identifier);

            // Check names of done circuits with available circuits
            let doneCount = 0;
            if (identifier === window.definitions.selectorIDs.wheatstone) {
                availableCircuits = state.options.length; // Wheatstone selector has different available circuits
                for (let i = 0; i < state.options.length; i++) {
                    let optionName = "option_" + i;
                    if (doneCircuits.includes(optionName)) {
                        doneCount++;
                    }
                }
            } else {
                for (const circuit of circuitSet.circuitMaps) {
                    if (doneCircuits.includes(circuit.circuitFile)) {
                        doneCount++;
                    }
                }
            }
            // Set counter text
            let counterText = `${doneCount}/${availableCircuits}`;
            let counterElement = document.getElementById(`${identifier}-selector-counter`);
            if (window.definitions.noCounter.includes(identifier)) {
                continue;
            }
            else if (counterElement) {
                counterElement.textContent = counterText;
                counterElement.style.color = doneCount === availableCircuits ? colors.keyYellow : colors.keyGreyedOut;
                if (doneCount === availableCircuits && availableCircuits > 0) {
                    let animationShown = storageManager.animationShown.loadValue(identifier)
                    if (animationShown !== "true") {
                        storageManager.animationShown.setValue(identifier, true);
                        if (pageManager.simplifierPage.style.display === "block") {
                            this.showFinishedSelectorAnimation(identifier);
                        }
                    }
                }
            } else {
                console.warn(`Counter element for ${identifier} not found.`);
            }
        }
    }

// ######################### Setup #######################################
    /**
     * @param circuitSet {CircuitSet} each map of the circuit set gets an overview svg and a start button overlay
     * */
    async setupSelector(circuitSet) {
        return new Promise(async (resolve) => {
            try {
                for (const [idx, circuit] of circuitSet.circuitMaps.entries()) {
                    await this.setupSpecificCircuitSelector(circuit);
                    this._showFirstCircuitAsSelected(idx, circuitSet);
                }
                if (moreThanOneCircuitInSet(circuitSet)) {
                    this.setupNextAndPrevButtons(circuitSet);
                } else {
                    this.hideNextAndPrevButtons(circuitSet);
                }
                resolve();
            } catch (error) {
                console.trace(error)
                console.error(`Error setting up circuit selector for ${circuitSet.identifier}:`, error);
                showMessage(error, "error", false);
                pushErrorEventMatomo(errorActions.circuitSelectorSetupError, `(${circuitSet.identifier})` + error);
            }
        });
    }

    _showFirstCircuitAsSelected(idx, circuitSet) {
        if ((idx === 0)) {
            if (circuitSet.circuitMaps[0] !== undefined && circuitSet.circuitMaps[0] !== null) {
                this.showCircuitAsSelected(document.getElementById(circuitSet.circuitMaps[0].circuitDivID),
                    document.getElementById(circuitSet.circuitMaps[0].btnOverlay));
            }
        }
    }

    /**
     * [bar description]
     * @param circuitMap {CircuitMap}
     * @return {string} svg-data
     */
    async setupSpecificCircuitSelector(circuitMap) {
        /** @type {HTMLElement} */
        const circuitDiv = document.getElementById(circuitMap.circuitDivID);
        /** @type {HTMLElement} */
        const startBtn = document.getElementById(circuitMap.btn);
        /** @type {HTMLElement} */
        const btnOverlay = document.getElementById(circuitMap.btnOverlay);

        // Fill div with svg
        try {
            let svgData = await circuitMap.svgData()

            svgData = setSvgWidthTo(svgData, "100%");
            svgData = setSvgColorMode(svgData);
            circuitDiv.innerHTML = svgData;
            this.addVoltFreqOverlay(circuitDiv, circuitMap);
            hideSvgArrows(circuitDiv);
            hideLabels(circuitDiv);

            // Setup specific circuit in overview modal
            this.setupOverviewModalCircuit(circuitMap, circuitDiv);

            this.setupSelectionCircuit(circuitDiv, startBtn, btnOverlay);
            // Disable start buttons for all selector groups except quickstart when pyodide is not ready
            if (circuitMap.selectorGroup !== window.definitions.selectorIDs.quickstart) {
                if (!state.pyodideReady) {
                    startBtn.disabled = true;
                } else {
                    startBtn.style.backgroundColor = colors.keyYellow;
                }
            } else {
                startBtn.style.backgroundColor = colors.keyYellow;
            }
            startBtn.addEventListener("click", () =>
                this.circuitSelectorStartButtonPressed(circuitMap));
        } catch (error) {
            console.trace(error)
            console.error(`Error loading ${circuitMap.overViewSvgFile}:`, error);
            showMessage(error, "error", false);
            pushErrorEventMatomo(errorActions.loadingOverviewError, `(${circuitMap.selectorGroup})` + error);
        }
    }

    enableStartBtns() {
        let startBtns = document.getElementsByClassName("circuitStartBtn");
        for (const startBtn of startBtns) {
            startBtn.disabled = false;
        }
        startBtns = document.getElementsByClassName("circuitStartBtnModal");
        for (const startBtn of startBtns) {
            startBtn.disabled = false;
        }
    }

    addVoltFreqOverlay(circuitDiv, circuitMap) {
        // Add voltage and frequency overlay for R, L, C and mixed Circuits
        if ([window.definitions.selectorIDs.quickstart, window.definitions.selectorIDs.symbolic, window.definitions.selectorIDs.wheatstone].includes(circuitMap.selectorGroup)){
            // nothing here
        } else {
            if (circuitMap.frequency === undefined || circuitMap.frequency === null) {
                circuitDiv.insertBefore(this.createVoltOverlay(circuitMap), circuitDiv.firstChild);
            } else {
                circuitDiv.insertBefore(this.createVoltFreqOverlay(circuitMap), circuitDiv.firstChild);
            }
        }
    }

    createVoltOverlay(circuitMap) {
        let div = document.createElement("div");
        div.id = `${circuitMap.btnOverlay}-volt-freq`;
        div.classList.add("volt-freq-overlay");
        div.innerHTML = `<p style="color: ${colors.currentForeground}; position:absolute; top:20px; right: 0; ">${circuitMap.voltage}</p>`;
        return div;
    }

    createVoltFreqOverlay(circuitMap) {
        let div = document.createElement("div");
        div.id = `${circuitMap.btnOverlay}-volt-freq`;
        div.classList.add("volt-freq-overlay");
        div.innerHTML = `<p style="color: ${colors.currentForeground}; position:absolute; top:20px; right: 0; ">${circuitMap.voltage}</p>
                         <p style="color: ${colors.currentForeground}; position:absolute; top:40px; right: 0; ">${circuitMap.frequency}</p>`;
        return div;
    }

    setupOverviewModalCircuit(circuitMap, circuitDiv) {
        if (circuitMap.selectorGroup !== window.definitions.selectorIDs.quickstart) {
            let gridElement;
            let overviewStartBtn;
            let modal;

            gridElement = document.getElementById(`${circuitMap.circuitDivID}-overviewModal`);
            overviewStartBtn = document.getElementById(`${circuitMap.btn}-modalBtn`);
            modal = document.getElementById(`${circuitMap.selectorGroup}-${this.prefix}overviewModal`);

            gridElement.innerHTML = circuitDiv.innerHTML;  // copy svg without arrows to modal
            if (!state.pyodideReady) {
                overviewStartBtn.disabled = true;
            }
            overviewStartBtn.addEventListener("click", () => {
                // we need the bootstrap modal instance in order to close it
                var modalInstance = bootstrap.Modal.getInstance(modal) || new bootstrap.Modal(modal);
                modalInstance.hide();
                this.circuitSelectorStartButtonPressed(circuitMap);
            });
        }
    }

    resetSelectorSelections(circuitSet) {
        for (const circuit of circuitSet) {
            this.resetSelection(circuit);
        }
    }

    setupNextAndPrevButtons(circuitSet) {
        let next;
        let prev;

        next = document.getElementById(`${circuitSet.identifier}${this.prefix}-next-btn`);
        prev = document.getElementById(`${circuitSet.identifier}${this.prefix}-prev-btn`);

        if (!(next && prev)) return  // not all carusels need all selectors -> cant find all next and prev elements

        next.addEventListener("click", () => {
            this.resetSelectorSelections(circuitSet.circuitMaps);
        })
        prev.addEventListener("click", () => {
            this.resetSelectorSelections(circuitSet.circuitMaps);
        })
    }

    hideNextAndPrevButtons(circuitSet) {
        let next;
        let prev;

        next = document.getElementById(`${circuitSet.identifier}${this.prefix}-next-btn`);
        prev = document.getElementById(`${circuitSet.identifier}${this.prefix}-prev-btn`);

        if (next !== null && prev !== null) {
            next.hidden = true;
            prev.hidden = true;
        }
    }

    /**
     * sets the circuitMap passed as the current in the state object and starts the according simplifier page (
     * stepwise, wheatstone, kirchhoff ...
     * @param circuitMap {CircuitMap}
     * */
    async circuitSelectorStartButtonPressed(circuitMap) {
        // this clears the container for each of the simplifier pages
        SimplifierPage.clear();

        state.currentCircuitMap = circuitMap;
        state.pictureCounter = 0;
        state.allValuesMap = new Map();

        if (circuitMap.selectorGroup === window.definitions.selectorIDs.kirchhoff) {
            pageManager.pages.newKirchhoffPage.reset()
            pageManager.changePage(pageManager.pages.newKirchhoffPage, true);
        }
        else if (circuitMap.selectorGroup === window.definitions.selectorIDs.wheatstone) {
            if (state.currentCircuitFromUserZip) {
                state.options = getWheatstoneValues();
            }
            pageManager.pages.newWheatstonePage.reset()
            pageManager.changePage(pageManager.pages.newWheatstonePage, true); //TODO isUserCircuit, change state.options or work on concept how to use user values, ...
        }
        else if (circuitMap.selectorGroup === window.definitions.selectorIDs.magnetic) {
            pageManager.pages.newMagneticPage.reset()
            pageManager.changePage(pageManager.pages.newMagneticPage, true)
        }
        else {
            pageManager.pages.newStepwisePage.reset();
            pageManager.changePage(pageManager.pages.newStepwisePage, true);
        }
    }

    showCircuitAsSelected(circuit, btnOverlay) {
        circuit.style.borderColor = colors.keyYellow;
        circuit.style.opacity = "0.5";
        btnOverlay.style.display = "block";
        if (!(btnOverlay.id.includes(window.definitions.selectorIDs.quickstart)
            || btnOverlay.id.includes(window.definitions.selectorIDs.symbolic)
            || btnOverlay.id.includes(window.definitions.selectorIDs.wheatstone))) {
            let overlay = document.getElementById(`${btnOverlay.id}-volt-freq`);
            if (overlay) overlay.style.opacity = "0.5";
        }
    }
    showCircuitAsUnselected(circuit, btnOverlay) {
        circuit.style.borderColor = colors.currentForeground;
        circuit.style.opacity = "1";
        btnOverlay.style.display = "none";
        if (!(btnOverlay.id.includes(window.definitions.selectorIDs.quickstart)
            || btnOverlay.id.includes(window.definitions.selectorIDs.symbolic)
            || btnOverlay.id.includes(window.definitions.selectorIDs.wheatstone))) {
            let overlay = document.getElementById(`${btnOverlay.id}-volt-freq`);
            if (overlay) overlay.style.opacity = "1";
        }
    }

    setupSelectionCircuit(circuit, startBtn, startBtnOverlay) {
        circuit.addEventListener("click", () => {this.showCircuitAsSelected(circuit, startBtnOverlay)})
        startBtnOverlay.addEventListener("click", () => {this.showCircuitAsUnselected(circuit, startBtnOverlay)})
    }

    resetSelection(circuitMap) {
        const circuit = document.getElementById(circuitMap.circuitDivID);
        const overlay = document.getElementById(circuitMap.btnOverlay);
        circuit.style.borderColor = colors.currentForeground;
        circuit.style.opacity = "1";
        overlay.style.display = "none";
        if (!((circuitMap.selectorGroup === window.definitions.selectorIDs.quickstart) || (circuitMap.selectorGroup === window.definitions.selectorIDs.symbolic))) {
            document.getElementById(`${circuitMap.btnOverlay}-volt-freq`).style.opacity = "1";
        }
    }

    showFinishedSelectorAnimation(identifier) {
        const container = document.createElement("div");
        container.style.position = "fixed";
        container.style.top = "50%";
        container.style.left = "50%";
        container.style.transform = "translate(-50%, -50%)";
        container.style.zIndex = "9999";
        container.style.display = "flex";
        container.style.flexDirection = "column";
        container.style.alignItems = "center";
        container.style.pointerEvents = "none";
        container.style.opacity = "0";
        container.style.transition = "opacity 1s ease";
        container.style.padding = "100%"; // full screen blurred
        container.style.backdropFilter = "blur(10px)";

        const star = document.createElement("div");
        star.innerHTML = `<svg id="star-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="250px" viewBox="0 -0.5 33 33" version="1.1">
                            <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
                            <title>star</title>
                            <desc>Created with Sketch.</desc>
                            <defs></defs>
                            <g id="Vivid.JS" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
                                <g id="Vivid-Icons" transform="translate(-903.000000, -411.000000)" fill="#FFC107">
                                    <g id="Icons" transform="translate(37.000000, 169.000000)">
                                        <g id="star" transform="translate(858.000000, 234.000000)">
                                            <g transform="translate(7.000000, 8.000000)" id="Shape">
                                                <polygon points="27.865 31.83 17.615 26.209 7.462 32.009 9.553 20.362 0.99 12.335 12.532 10.758 17.394 0 22.436 10.672 34 12.047 25.574 20.22"></polygon>
                                            </g>
                                        </g>
                                    </g>
                                </g>
                            </g>
                        </svg>`;

        const text = document.createElement("div");
        text.textContent = languageManager.currentLang.selector.selectorHeadings[identifier];
        text.style.fontSize = "48px";
        text.style.color = colors.currentHeadingsForeground;
        text.style.fontWeight = "bold";
        text.style.marginTop = "20px";
        text.style.position = "absolute";
        text.style.top = "60%";

        // Append elements to container
        container.appendChild(star);
        container.appendChild(text);
        document.body.appendChild(container);

        // Fade in
        setTimeout(() => {
            container.style.opacity = "1";
        }, 250);

        // Fade out
        setTimeout(() => {
            container.style.opacity = "0";
        }, 3000);

        // Remove
        setTimeout(() => {
            container.remove();
        }, 4000);

        let duration = 3;

        let end = Date.now() + (duration * 1000 - 500); // Just a bit shorter :)
        let confettiColors = ['#ffc107', '#ffc107'];

        (function frame() {
            confetti({
                particleCount: 2,
                angle: 60,
                spread: 55,
                origin: { x: 0 },
                colors: confettiColors
            });
            confetti({
                particleCount: 2,
                angle: 120,
                spread: 55,
                origin: { x: 1 },
                colors: confettiColors
            });

            if (Date.now() < end) {
                requestAnimationFrame(frame);
            }
        }());

        // Check if this was the last selector
        this.checkAllDone();
    }

    checkAllDone() {
        let allDone = true;
        for (let circuitSet of this.circuitSets) {
            if (circuitSet.identifier === window.definitions.selectorIDs.quickstart) continue;
            let doneCircuits = storageManager.circuitsDone.loadValue(circuitSet.identifier);
            if (circuitSet.identifier === window.definitions.selectorIDs.wheatstone) {
                // Check options
                if (doneCircuits.length < state.options.length) {
                    allDone = false;
                    break;
                }
            } else {
                // Rest of the circuit sets
                if (doneCircuits.length < circuitSet.circuitMaps.length) {
                    allDone = false;
                    break;
                }
            }
        }
        if (allDone) {
            if (document.readyState === "loading") {
                window.addEventListener("DOMContentLoaded", () => {
                    this.addSmoothStarsOverLogo();
                });
            } else {
                this.addSmoothStarsOverLogo();
            }
        }
    }

    addSmoothStarsOverLogo() {
        const logo = document.getElementById("nav-logo");

        const wrapper = document.createElement("span");
        wrapper.id = "smooth-stars-wrapper";
        wrapper.style.position = "relative";
        wrapper.style.display = "inline-block";
        logo.parentNode.insertBefore(wrapper, logo);
        wrapper.appendChild(logo);

        function createStar() {
            const star = document.createElement("span");
            star.textContent = Math.random() > 0.5 ? "✦" : "★";

            const size = Math.random() * 6 + 6;
            const duration = Math.random() * 1000 + 1500;

            const x = Math.random() * wrapper.offsetWidth;
            const startY = wrapper.offsetHeight - 2;

            // horizontale Verschiebung zufällig ±10px
            const horizontalShift = (Math.random() - 0.5) * 20; // von -10 bis +10 px

            Object.assign(star.style, {
                position: "absolute",
                left: `${x}px`,
                top: `${startY}px`,
                fontSize: `${size}px`,
                color: "#ffc107",
                opacity: 0,
                pointerEvents: "none",
                zIndex: 0,
                transform: "translateX(0px) translateY(0px) scale(0.5)",
                transition: `opacity ${duration * 0.3}ms ease-in, transform ${duration}ms ease-out`
            });

            wrapper.appendChild(star);

            // Animation starten: opacity auf 1, vertikal + horizontal verschieben, skalieren
            requestAnimationFrame(() => {
                requestAnimationFrame(() => {
                    star.style.opacity = 1;
                    star.style.transform = `translateX(${horizontalShift}px) translateY(-40px) scale(1)`;
                });
            });

            // Fade-out starten kurz vor Ende, opacity runter, transform bleibt gleich
            setTimeout(() => {
                star.style.opacity = 0;
            }, duration * 0.8);

            // Entfernen
            setTimeout(() => {
                star.remove();
            }, duration);
        }

        setInterval(() => {
            createStar();
        }, 700);
    }

    updateColor(){
        let selectorDiv = document.getElementById(this.id)
        let items = selectorDiv.querySelectorAll(".accordion-item");
        this.#updateColor(items)
        items = selectorDiv.querySelectorAll(".carousel-item");
        this.#updateSvgs(items)
    }

    updateLang(usedAccordionHeadings){
        if (usedAccordionHeadings) {
            // Accordion headings
            for (let i = 0; i < usedAccordionHeadings.length; i++) {
                const accBtn = document.getElementById(`${usedAccordionHeadings[i].identifier}-${this.prefix}acc-btn`);
                if (accBtn) {
                    accBtn.innerHTML = languageManager.currentLang.selector.selectorHeadings[usedAccordionHeadings[i].identifier];
                }
            }
            let accordion = document.getElementById(this.id);
            for (let i = 0; i < usedAccordionHeadings.length; i++) {
                let overviewModalBtn = accordion.querySelector(`#${usedAccordionHeadings[i].identifier}-${this.prefix}overviewModalBtn`);
                if (overviewModalBtn) {
                    overviewModalBtn.innerHTML = languageManager.currentLang.selector.overviewModalBtn;
                }
            }
        }
    }

    /** @param items {NodeListOf<HTMLElement>} */
    #updateColor(items){
        for (let item of items){
            //update heading of selector accordion
            let header = item.querySelector(".accordion-header");
            header.style.backgroundColor = colors.currentBsBackground;
            let btn = item.querySelector(".accordion-button")
            btn.style.color = colors.currentHeadingsForeground;
            btn.style.background = colors.currentBsBackground;
            //update body of heading accordion
            let body = item.querySelector(".accordion-body");
            body.style.backgroundColor = colors.currentBsBackground;
            let overviewBtn = body.querySelector(".btn.my-1.btn-primary.modalOverviewBtn")
            overviewBtn.style.color = colors.currentHeadingsForeground;
            overviewBtn.style.borderColor = colors.currentHeadingsForeground;

        }
    }

    /** @param items {NodeListOf<HTMLElement>} */
    #updateSvgs(items){
        for (let item of items){
            let svg = item.querySelector("svg")
            svg.innerHTML = svg.innerHTML.replaceAll(colors.getOldSvgForegroundColor, colors.currentForeground);

            let overlay = item.querySelector(".svg-selector")
            overlay.style.borderColor = colors.currentForeground;

            let voltOverlays = document.querySelectorAll(".volt-freq-overlay p");
            for (let p of voltOverlays) {
                p.style.color = colors.currentForeground;
            }
        }
    }
}