I want you to add an auto-closing feature to the textarea:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NoirCode Studio</title>
    <style>
        /* Reset some default styles */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        html, body {
            height: 100%;
        }

        body {
            background-color: #1e1e1e;
            color: #cccccc;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            display: flex;
            flex-direction: column;
        }

        header {
            background-color: #252526;
            padding: 15px 20px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            font-size: 1.5rem;
            font-weight: bold;
            border-bottom: 1px solid #333;
            position: relative;
        }

        /* Tabs for Mobile */
        .tabs {
            display: none; /* Hidden by default, shown on mobile */
        }

        .tabs button {
            background: none;
            border: none;
            color: #cccccc;
            padding: 10px 15px;
            cursor: pointer;
            font-size: 1rem;
            transition: background-color 0.3s;
        }

        .tabs button.active, .tabs button:hover {
            background-color: #3a3a3a;
            border-radius: 4px;
        }

        .container {
            flex: 1;
            display: flex;
            overflow: hidden;
        }

        .editor, .preview {
            flex: 1;
            padding: 20px;
            overflow: auto;
        }

        .editor {
            background-color: #1e1e1e;
            border-right: 1px solid #333;
            display: flex;
            flex-direction: column;
        }

        .editor textarea {
            width: 100%;
            height: 100%;
            background-color: #1e1e1e;
            color: #cccccc;
            border: none;
            resize: none;
            font-family: 'Courier New', Courier, monospace;
            font-size: 1rem;
            outline: none;
        }

        .error {
            color: #e74c3c;
            margin-top: 10px;
            font-size: 0.9rem;
            display: none;
        }

        .preview {
            background-color: #2d2d2d;
            overflow: hidden;
        }

        /* Scrollbar styling for WebKit */
        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }

        ::-webkit-scrollbar-track {
            background: #1e1e1e;
        }

        ::-webkit-scrollbar-thumb {
            background-color: #555;
            border-radius: 4px;
        }

        /* Responsive Design */
        @media (max-width: 768px) {
            .container {
                flex-direction: column;
            }
            .editor, .preview {
                flex: 1; /* Allow flex items to grow */
                display: none; /* Hide both by default */
                height: 100%; /* Ensure they can take full height when displayed */
            }
            .editor.active, .preview.active {
                display: flex;
                flex-direction: column;
            }
            .editor {
                border-right: none;
                border-bottom: 1px solid #333;
            }
            .tabs {
                display: flex;
            }
            .error {
                display: block;
            }
        }
    </style>
</head>
<body>
    <header>
        <div>NoirCode Studio</div>
        <div class="tabs">
            <button id="tab-editor" class="active">Editor</button>
            <button id="tab-preview">Preview</button>
        </div>
    </header>
    <div class="container">
        <div class="editor active">
            <textarea id="code" placeholder="Type your custom code here...">
h1{Welcome to NoirCode Studio}

p{NoirCode Studio is a powerful and intuitive editor designed for NoirCode, a streamlined markup language that enhances HTML development.}

h2{Features}

ul\{
    li{Real-time preview}
    li{Error detection}
    li{Sleek design}
\}

h2{Getting Started}

p{Begin by typing your NoirCode in the editor. Use the preview pane to see the rendered HTML instantly. NoirCode Studio supports advanced functionalities like nesting elements, adding attributes, and repeating elements efficiently.}

div[title="Index: &index;"]{&index;. Hi}*5

button[role="button" title="Click me"].click-me.click-me2{Click me}
button[role="button" title="Click me"].click-me.click-me1{Click me}

html\{
<button class="test">Test 2</button>
\}

script\{
document.querySelectorAll('.click-me').forEach(btn => {
    btn.addEventListener('click', () => {
        alert('Button clicked!');
    });
});
\}

style\{
.click-me {
    background-color: #3498db;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    margin: 5px;
}
.click-me:hover {
    background-color: #2980b9;
}
\}
            </textarea>
            <div id="error-message" class="error"></div>
        </div>
        <div class="preview">
            <iframe id="previewFrame" srcdoc="" frameborder="0" style="width: 100%; height: 100%; background-color: transparent;"></iframe>
        </div>
    </div>

    <script>
        const textarea = document.getElementById('code');
        const previewFrame = document.getElementById('previewFrame');
        const errorMessage = document.getElementById('error-message');

        /**
         * Compiler Function:
         * Transforms custom NoirCode syntax into standard HTML.
         * Supports:
         * - Tag definitions (e.g., h1{Hello})
         * - Attributes and IDs (e.g., div[class="container"]#main{Content})
         * - Classes (e.g., .class1.class2)
         * - Nesting with '>' (e.g., div>p{Text})
         * - Sibling with '+' (e.g., h1{Title}+p{Subtitle})
         * - Repetition with '*' (e.g., div{&index;. Hi}*5)
         * - Raw HTML, CSS, and JS blocks (html\{ \}, style\{ \}, script\{ \})
         */
        function compileToHTML(customCode) {
            const lines = customCode.split('\n');
            let html = '';
            let errors = [];
            let i = 0;
            const totalLines = lines.length;

            while (i < totalLines) {
                let line = lines[i].trim();

                // Skip empty lines
                if (line === '') {
                    i++;
                    continue;
                }

                // Check for block start (e.g., html\{ )
                const blockStartMatch = line.match(/^(\w+)(\[[^\]]+\])?(#[\w\-]+)?((?:\.[\w\-]+)*?)\\\{$/);
                if (blockStartMatch) {
                    const tag = blockStartMatch[1].toLowerCase();
                    const attrs = blockStartMatch[2] ? replaceIndexPlaceholders(blockStartMatch[2].slice(1, -1), 0) : '';
                    const id = blockStartMatch[3] ? replaceIndexPlaceholders(blockStartMatch[3].slice(1), 0) : '';
                    const classesRaw = blockStartMatch[4] ? blockStartMatch[4] : '';
                    const classes = classesRaw.split('.').filter(cls => cls !== '');

                    // Collect block content until \}
                    let blockContent = '';
                    i++; // Move to the next line after block start
                    while (i < totalLines && lines[i].trim() !== '\\}') {
                        blockContent += lines[i] + '\n';
                        i++;
                    }

                    if (i >= totalLines) {
                        errors.push(`Unclosed block starting with <${tag}>.`);
                        break;
                    }

                    let innerHTML = '';

                    if (tag === 'script' || tag === 'style' || tag === 'html') {
                        // Treat content as raw code
                        innerHTML = blockContent;
                    } else {
                        // Recursively compile the block content
                        const compiledBlock = compileToHTML(blockContent);
                        if (compiledBlock.errors.length > 0) {
                            errors = errors.concat(compiledBlock.errors);
                        }
                        innerHTML = compiledBlock.html;
                    }

                    // Append the compiled block to the HTML
                    let attrString = '';
                    if (attrs) attrString += ` ${attrs}`;
                    if (id) attrString += ` id="${id}"`;
                    if (classes.length > 0) attrString += ` class="${classes.join(' ')}"`;

                    html += `<${tag}${attrString}>\n${innerHTML}</${tag}>\n`;

                    i++; // Skip the closing \}
                    continue;
                }

                // Handle sibling elements using '+'
                const siblingTokens = splitRespectingBlocks(line, '+');
                siblingTokens.forEach(token => {
                    // Handle nesting with '>'
                    const nestingTokens = splitRespectingBlocks(token, '>');
                    nestingTokens.forEach((nestedToken, index) => {
                        nestedToken = nestedToken.trim();
                        const isLast = index === nestingTokens.length - 1;

                        if (nestedToken === '') return; // Skip empty tokens

                        try {
                            const { tag, attributes, id, classes, content, repeat } = parseElement(nestedToken, i + 1);
                            let attrString = attributes || '';
                            if (id) attrString += ` id="${id}"`;
                            if (classes.length > 0) attrString += ` class="${classes.join(' ')}"`;

                            for (let j = 1; j <= repeat; j++) {
                                let finalContent = replaceIndexPlaceholders(content, j);
                                let finalAttrString = replaceIndexPlaceholders(attrString, j);
                                let finalId = replaceIndexPlaceholders(id, j);
                                let finalClasses = classes.map(cls => replaceIndexPlaceholders(cls, j));

                                if (finalId) {
                                    finalAttrString = finalAttrString.replace(` id="${id}"`, ` id="${finalId}"`);
                                }

                                if (finalClasses.length > 0) {
                                    // Replace existing class names with updated ones
                                    const classRegex = /class="([^"]+)"/;
                                    const match = finalAttrString.match(classRegex);
                                    if (match) {
                                        const existingClasses = match[1].split(' ');
                                        const updatedClasses = [...new Set([...existingClasses, ...finalClasses])];
                                        finalAttrString = finalAttrString.replace(classRegex, `class="${updatedClasses.join(' ')}"`);
                                    } else {
                                        finalAttrString += ` class="${finalClasses.join(' ')}"`;
                                    }
                                }

                                if (isLast) {
                                    if (finalContent) {
                                        html += `<${tag}${finalAttrString}>${finalContent}</${tag}>\n`;
                                    } else {
                                        html += `<${tag}${finalAttrString}></${tag}>\n`;
                                    }
                                } else {
                                    // For non-last tokens in nesting, open the tag
                                    html += `<${tag}${finalAttrString}>\n`;
                                }
                            }
                        } catch (err) {
                            errors.push(`Line ${err.line}: ${err.message}`);
                        }
                    });
                });

                i++;
            }

            return { html, errors };
        }

        /**
         * Splits a string by a delimiter but ignores delimiters inside quotes or blocks.
         * @param {string} str - The string to split.
         * @param {string} delimiter - The delimiter to split by.
         * @returns {Array} - Array of split tokens.
         */
        function splitRespectingBlocks(str, delimiter) {
            const tokens = [];
            let current = '';
            let inQuotes = false;
            let quoteChar = '';
            let escape = false;

            for (let i = 0; i < str.length; i++) {
                let char = str[i];

                if (escape) {
                    current += char;
                    escape = false;
                    continue;
                }

                if (char === '\\') {
                    escape = true;
                    current += char; // Keep the backslash for block parsing
                    continue;
                }

                if ((char === '"' || char === "'") && !inQuotes) {
                    inQuotes = true;
                    quoteChar = char;
                    current += char;
                    continue;
                }

                if (char === quoteChar && inQuotes) {
                    inQuotes = false;
                    quoteChar = '';
                    current += char;
                    continue;
                }

                if (!inQuotes && char === delimiter) {
                    tokens.push(current);
                    current = '';
                    continue;
                }

                current += char;
            }

            if (current !== '') {
                tokens.push(current);
            }

            return tokens;
        }

        /**
         * Replaces all instances of &index; with the provided index number.
         * @param {string} str - The string to process.
         * @param {number} index - The index to replace &index; with.
         * @returns {string} - The processed string.
         */
        function replaceIndexPlaceholders(str, index) {
            return str.replace(/&index;/g, index);
        }

        /**
         * Parses an element definition from the custom syntax.
         * @param {string} token - A token from the custom code.
         * @param {number} lineNumber - The line number for error reporting.
         * @returns {Object} - Parsed element details.
         */
        function parseElement(token, lineNumber) {
            // Updated regex to include classes
            // Example matches:
            // div[role="main"]#container.class1.class2{Content}*3
            const regex = /^(\w+)(\[[^\]]+\])?(#[\w\-]+)?((?:\.[\w\-]+)*)(\{[^}]*\})?(?:\*(\d+))?$/;
            const match = token.match(regex);

            if (!match) {
                throw { message: `Invalid syntax near "${token}".`, line: lineNumber };
            }

            const [ , tag, attrs, id, classNames, content, repeat ] = match;
            let attributes = '';
            let classes = [];

            if (attrs) {
                // Remove the surrounding brackets
                const attrContent = attrs.slice(1, -1);
                // Validate attribute format
                const attrRegex = /(\w+)="([^"]+)"/g;
                let attrMatch;
                while ((attrMatch = attrRegex.exec(attrContent)) !== null) {
                    const [ , attrName, attrValue ] = attrMatch;
                    attributes += ` ${attrName}="${attrValue}"`;
                }

                // Check if attributes are correctly formatted
                const validAttrFormat = [...attrContent.matchAll(attrRegex)].length > 0;
                // If attributes are present but no valid attributes were parsed
                if (attributes.trim() === '' && attrContent.trim() !== '') {
                    throw { message: `Attributes must be in the format key="value".`, line: lineNumber };
                }
            }

            if (classNames) {
                // Remove the leading dots and split into individual classes
                classes = classNames.split('.').filter(cls => cls !== '').map(cls => cls.trim());
            }

            const element = {
                tag: tag,
                attributes: attributes,
                id: id ? id.slice(1) : '', // Remove '#' from id
                classes: classes, // Array of classes
                content: content ? content.slice(1, -1) : '',
                repeat: repeat ? parseInt(repeat) : 1
            };

            return element;
        }

        /**
         * Updates the preview iframe based on the compiled HTML.
         */
        function updatePreview() {
            const customCode = textarea.value;
            let compilationResult;
            try {
                compilationResult = compileToHTML(customCode);
            } catch (error) {
                compilationResult = { html: '', errors: [`Unexpected error: ${error.message}`] };
            }

            const { html, errors } = compilationResult;

            if (errors && errors.length > 0) {
                errorMessage.textContent = errors.join(' | ');
                errorMessage.style.display = 'block';
                previewFrame.srcdoc = ''; // Clear preview if there are errors
            } else {
                errorMessage.textContent = '';
                errorMessage.style.display = 'none';
                // Wrap with basic HTML structure and custom dark theme
                const fullHTML = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Preview</title>
    <style>
        body {
            background-color: #1e1e1e;
            color: #cccccc;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            padding: 20px;
        }
        h1, h2, h3, h4, h5, h6 {
            color: #ffffff;
        }
        p {
            color: #dcdcdc;
            margin-bottom: 10px;
        }
        button {
            padding: 10px 20px;
            background-color: #3498db;
            color: #fff;
            border: none;
            cursor: pointer;
            border-radius: 4px;
            margin-top: 10px;
        }
        button:hover {
            background-color: #2980b9;
        }
        a {
            color: #9b59b6;
        }
        a:hover {
            color: #8e44ad;
        }
        /* Additional styles for various elements */
        ul {
            list-style-type: disc;
            padding-left: 20px;
            margin-bottom: 10px;
        }
        li {
            margin-bottom: 5px;
        }
        input, textarea {
            background-color: #333333;
            color: #ffffff;
            border: 1px solid #555555;
            padding: 8px;
            border-radius: 4px;
            margin-top: 5px;
        }
        input:focus, textarea:focus {
            border-color: #3498db;
            outline: none;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 10px;
        }
        th, td {
            border: 1px solid #555555;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #444444;
        }
        tr:nth-child(even) {
            background-color: #2d2d2d;
        }
        /* Embedded Styles */
        ${extractEmbeddedStyles(html)}
    </style>
</head>
<body>
${html}
</body>
</html>`;
                previewFrame.srcdoc = fullHTML;
            }
        }

        /**
         * Extracts embedded <style> blocks from the generated HTML to include in the preview's head.
         * @param {string} html - The generated HTML content.
         * @returns {string} - Extracted CSS from <style> blocks.
         */
        function extractEmbeddedStyles(html) {
            const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
            let styles = '';
            let match;
            while ((match = styleRegex.exec(html)) !== null) {
                styles += match[1] + '\n';
            }
            return styles;
        }

        // Initialize preview
        updatePreview();

        // Update preview on input with a slight debounce
        let timeout;
        textarea.addEventListener('input', () => {
            clearTimeout(timeout);
            timeout = setTimeout(updatePreview, 300);
        });

        // Tab functionality for mobile
        const tabEditor = document.getElementById('tab-editor');
        const tabPreview = document.getElementById('tab-preview');
        const editor = document.querySelector('.editor');
        const preview = document.querySelector('.preview');

        function activateTab(tab) {
            if (tab === 'editor') {
                tabEditor.classList.add('active');
                tabPreview.classList.remove('active');
                editor.classList.add('active');
                preview.classList.remove('active');
            } else if (tab === 'preview') {
                tabPreview.classList.add('active');
                tabEditor.classList.remove('active');
                preview.classList.add('active');
                editor.classList.remove('active');
            }
        }

        tabEditor.addEventListener('click', () => activateTab('editor'));
        tabPreview.addEventListener('click', () => activateTab('preview'));
    </script>
</body>
</html>

---

Add an input event listener to the textarea, making it auto-close quotes, single quotes, brackets, square brackets, angle brackets, curly brackets, and `\{` with `\}`... You know, the `\{ ... \}` is used for multi-line coding in NoirCode, therefore, it has to be autocompleted as well