```html
<!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>
    <link rel="stylesheet" href="./style/style.css">
</head>
<body>
    <header>
        <div class="title">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 NoirCode here..." spellcheck="false">
p{NoirCode is a custom shorthand language designed for building HTML structures with ease. It allows defining tags, attributes, and content in a concise manner. It supports nesting, repetition, inline JavaScript, and comments.}

h2{Basic Syntax}

p{A simple element is defined using the syntax:}
p\{
  raw\{
    tagName[attributes]#id.className1.className2{content}
  \}
\}

ul\{
  li{tagName: The HTML tag to use, such as div, span, h1.}
  li{attributes: Optional attributes in the form of attr="value".}
  li{id: Optional ID for the element.}
  li{className: Optional classes for styling.}
  li{content: The inner content of the tag.}
\}

# Example
h2{Example}

p{Here's how to create a simple division with attributes and styles:}
pre\{
  div[style="color: red"]#myId.myClass{Hello, World!}
\}

# Comments
h2{Comments}

p\{
  raw\{
    Comments can be included with # at the beginning of a line or block comments with #{ to start and #} to end.
  \}
\}

# Nesting
h2{Nesting Elements}

p{Elements can be nested within each other using the > operator.}
pre\{
  div\{
    p{This is a paragraph inside a div.}
    span{Inline content}
  \}
\}

# Repetition
h2{Repetition}

p{To repeat an element, append *n at the end of the line where n is the number of repetitions.}
pre\{
  li{Item &index;}*3
\}

# Inline JavaScript
h2{Inline JavaScript}

p{Inline JavaScript is supported using the <$ $> syntax.}
pre\{
  p{Current date: <$ var date = new Date().toISOString().split('T')[0].split('-').reverse().join('/'); date $>}
\}

# Handling Errors
h2{Handling Errors}

p{Syntax errors are reported during the compilation process. Common issues include mismatched blocks or invalid tag definitions.}

# Advanced Example
h2{Advanced Example}

p{Combining various features:}
pre\{
  ul\{
    li#unique{First Item}
    li{Repeated Item &index;}*3
    li\{
      div.inner{Nested DIV}
      p{Inline JS Year: <$ date $>}
    \}
  \}
\}

h2{HTML Block}

p{The HTML Block allows you to write HTML code within NoirCode.}

html\{
  <p title="Check the code in the editor">This paragraph is written in HTML. Check the code on the left side in the editor.</p>
\}

h2{Style Block}

p#style-block-overview{The Style Block allows you to write CSS code to style your website. In fact, this paragraph is styled using the Style Block.}

style\{
  #style-block-overview {
    text-decoration: underline;
  }
\}

h2{Script Block}

p{The Script Block allows you to write full JavaScript code, unlike the inline JS feature, you can write your JS code as if you're using HTML's script tag.}
p{Click this button to see how JS works here ↓↓↓}
button[onclick="clickMe();"]{Click me}

script\{
  function clickMe() {
    alert('Clicked');
  }
\}

h2{Raw Block}

p\{
  raw\{
    <div>The Raw Block allows you to write normal text without worrying about errors that might occur when using non-NoirCode syntax. In fact, you're seeing the div elements not because it's a bug, but because we used the Raw Block to write plain text.</div>
  \}
\}

# End of Documentation
h2{Conclusion}
p{This concludes the basic overview of NoirCode. Explore and experiment to discover more possibilities!}</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 src="./script.js"></script>
</body>
</html>
```

```scss
* {
    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;
}

.title::after {
    content: 'BETA';
    margin-left: 6px;
    background-color: #fff7c7;
    color: #97742b;
    padding: 3px 5px;
    border-radius: 5px;
    font-size: 12px;
    position: relative;
    bottom: 3px;
}

.tabs {
    display: none;

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

        &.active,
        &: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;

            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;
            }
        }

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

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

::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

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

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

@media (max-width: 768px) {
    .container {
        flex-direction: column;

        .editor,
        .preview {
            display: none !important;
            flex: none;
            height: 100%;
            width: 100%;
        }

        .editor.active,
        .preview.active {
            display: flex !important;
            flex-direction: column;
        }

        .editor {
            border-right: none;
            border-bottom: 1px solid #333;
        }
    }

    header .tabs {
        display: flex;
    }

    .error {
        display: block;
    }
}

@media (min-width: 769px) {
    .container {
        flex-direction: row;

        .editor,
        .preview {
            display: flex;
            flex: 1;
            height: auto;
            width: 50%;
        }

        .editor {
            border-right: 1px solid #333;
            border-bottom: none;
        }

        .preview {
            border-left: 1px solid #333;
        }
    }

    header .tabs {
        display: none;
    }

    .error {
        display: none;
    }
}
```

```javascript
const textarea = document.getElementById('code');
const previewFrame = document.getElementById('previewFrame');
const errorMessage = document.getElementById('error-message');

/**
 * Compiles custom code to HTML.
 * @param {string} customCode - The custom code to compile.
 * @returns {Object} - An object containing the compiled HTML and any errors.
 */
function compileToHTML(customCode) {
    const lines = customCode.split('\n');
    let html = '';
    let errors = [];
    let i = 0;
    const totalLines = lines.length;
    let inBlockComment = false;

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

        if (inBlockComment) {
            if (line.endsWith('#}')) {
                inBlockComment = false;
            }
            i++;
            continue;
        }

        if (line.startsWith('#{')) {
            inBlockComment = true;
            i++;
            continue;
        }

        line = removeInlineComments(line);

        if (line === '') {
            i++;
            continue;
        }

        if (line.startsWith('#')) {
            i++;
            continue;
        }

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

            let blockContent = '';
            i++;
            let openBlocks = 1;

            while (i < totalLines && openBlocks > 0) {
                let currentLine = lines[i].trim();

                if (currentLine.endsWith('\\{')) {
                    openBlocks++;
                }

                if (currentLine === '\\}') {
                    openBlocks--;
                    if (openBlocks === 0) {
                        i++;
                        break;
                    }
                }

                if (openBlocks > 0) {
                    blockContent += lines[i] + '\n';
                }

                i++;
            }

            if (openBlocks > 0) {
                errors.push(`Unclosed block starting with <${tag}>.`);
                break;
            }

            let innerHTML = '';

            if (tag === 'script' || tag === 'style' || tag === 'html') {
                innerHTML = blockContent;
            } else if (tag === 'raw') {
                // Handle raw blocks by escaping content
                innerHTML = escapeHTML(blockContent);
                html += `${innerHTML}\n`;
                continue; // Skip the rest of the loop to avoid wrapping in a tag
            } else {
                let compiledBlock;
                try {
                    compiledBlock = compileToHTML(blockContent);
                } catch (error) {
                    errors.push(`Line ${i + 1}: Unexpected error: ${error.message}`);
                    compiledBlock = { html: '', errors: [`Line ${i + 1}: Unexpected error: ${error.message}`] };
                }

                if (compiledBlock.errors.length > 0) {
                    errors = errors.concat(compiledBlock.errors);
                }
                innerHTML = compiledBlock.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`;

            continue;
        }

        const inlineMatch = line.match(/^([\w-]+)(\[[^\]]+\])?(#[\w\-]+)?((?:\.[\w\-]+)*?)(?:\{([^}]*)\})?(?:\*(\d+))?$/);
        if (inlineMatch) {
            try {
                const tag = inlineMatch[1].toLowerCase();
                const attrs = inlineMatch[2] ? inlineMatch[2].slice(1, -1) : '';
                const id = inlineMatch[3] ? inlineMatch[3].slice(1) : '';
                const classesRaw = inlineMatch[4] ? inlineMatch[4] : '';
                const classes = classesRaw.split('.').filter(cls => cls !== '');
                const content = inlineMatch[5] || '';
                const repeat = inlineMatch[6] ? parseInt(inlineMatch[6]) : 1;

                for (let j = 1; j <= repeat; j++) {
                    const currentContent = replaceIndexPlaceholders(content, j);
                    const finalAttributes = replaceIndexPlaceholders(attrs, j);
                    const finalId = replaceIndexPlaceholders(id, j);
                    const finalClasses = classes.map(cls => replaceIndexPlaceholders(cls, j));

                    let currentAttrString = '';
                    if (finalAttributes) currentAttrString += ` ${finalAttributes}`;
                    if (finalId) currentAttrString += ` id="${finalId}"`;
                    if (finalClasses.length > 0) currentAttrString += ` class="${finalClasses.join(' ')}"`;

                    html += `<${tag}${currentAttrString}>${currentContent}</${tag}>\n`;
                }

                i++;
            } catch (err) {
                errors.push(`Line ${i + 1}: ${err.message}`);
            }
            continue;
        }

        const siblingTokens = splitRespectingBlocks(line, '+');
        siblingTokens.forEach(token => {
            const nestingTokens = splitRespectingBlocks(token, '>');
            const nestedHTML = processNesting(nestingTokens, 0, errors, i + 1);
            html += nestedHTML;
        });

        const textLineMatch = line.match(/^[^+\>]+$/);
        if (!blockStartMatch && !inlineMatch && textLineMatch) {
            html += `${escapeHTML(line)}\n`;
        }

        i++;
    }

    try {
        html = processInlineJS(html, errors);
    } catch (error) {
        errors.push(`Inline JS Error: ${error.message}`);
    }

    return { html, errors };

    /**
 * Processes and replaces inline JavaScript expressions within the HTML.
 * @param {string} htmlContent - The HTML content to process.
 * @param {Array} errors - The array to collect any errors.
 * @returns {string} - The HTML with inline JavaScript expressions evaluated.
 */
    function processInlineJS(htmlContent, errors) {
        const jsRegex = /<\$\s*([\s\S]+?)\s*\$>/g;
        return htmlContent.replace(jsRegex, (match, p1) => {
            try {
                const iframeWindow = previewFrame.contentWindow;

                if (!iframeWindow) {
                    throw new Error("Preview frame's window is not accessible.");
                }

                const result = iframeWindow.eval(p1);

                return result !== undefined ? escapeHTML(result.toString()) : '';
            } catch (err) {
                errors.push(`Inline JS Evaluation Error: ${err.message} in expression "${p1}"`);
                return `<span style="color: red;">Error</span>`;
            }
        });
    }

    /**
     * Recursive function to process nested tokens.
     * @param {Array} nestingTokens - Array of tokens split by '>'.
     * @param {number} index - Current index in the nestingTokens array.
     * @param {Array} errors - Array to collect error messages.
     * @param {number} lineNumber - Current line number for error reporting.
     * @returns {string} - The generated HTML string after processing nesting.
     */
    function processNesting(nestingTokens, index, errors, lineNumber) {
        if (index >= nestingTokens.length) return '';

        const nestedToken = nestingTokens[index].trim();
        const isLast = index === nestingTokens.length - 1;

        if (nestedToken === '') return '';

        let generatedHTML = '';

        try {
            const { tag, attributes, id, classes, content, repeat } = parseElement(nestedToken, lineNumber);

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

                let finalAttrString = '';
                if (finalAttributes) finalAttrString += ` ${finalAttributes}`;
                if (finalId) finalAttrString += ` id="${finalId}"`;
                if (finalClasses.length > 0) finalAttrString += ` class="${finalClasses.join(' ')}"`;

                generatedHTML += `<${tag}${finalAttrString}>`;

                if (finalContent) {
                    generatedHTML += finalContent;
                }

                if (!isLast) {
                    generatedHTML += '\n';
                    generatedHTML += processNesting(nestingTokens, index + 1, errors, lineNumber);
                    generatedHTML += `</${tag}>\n`;
                } else {
                    generatedHTML += `</${tag}>\n`;
                }
            }
        } catch (err) {
            if (nestedToken.startsWith('\\}')) {
                return '';
            } else {
                errors.push(`Line ${lineNumber}: ${err.message}`);
                generatedHTML += `${escapeHTML(nestedToken)}\n`;
            }
        }

        return generatedHTML;
    }
}

/**
 * Removes inline comments from a line of code.
 * @param {string} line - The line of code to process.
 * @returns {string} - The line without inline comments.
 */
function removeInlineComments(line) {
    let inQuotes = false;
    let quoteChar = '';
    let escape = false;

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

        if (escape) {
            escape = false;
            continue;
        }

        if (char === '\\') {
            escape = true;
            continue;
        }

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

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

        if (!inQuotes && char === '#' && (i === 0 || /\s/.test(line[i - 1]))) {
            return line.substring(0, i).trim();
        }
    }

    return line;
}

/**
 * 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;
    let blockDepth = 0;

    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;
            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 === '{') {
            blockDepth++;
            current += char;
            continue;
        }

        if (!inQuotes && char === '}') {
            blockDepth = Math.max(blockDepth - 1, 0);
            current += char;
            continue;
        }

        if (!inQuotes && blockDepth === 0 && 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) {
    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;

    const attributes = attrs ? attrs.slice(1, -1) : '';
    const classes = classNames ? classNames.split('.').filter(cls => cls !== '').map(cls => cls.trim()) : [];

    const element = {
        tag: tag,
        attributes: attributes,
        id: id ? id.slice(1) : '',
        classes: classes,
        content: content || '',
        repeat: repeat ? parseInt(repeat) : 1
    };

    return element;
}

/**
 * Escapes HTML special characters in a string.
 * @param {string} str - The string to escape.
 * @returns {string} - The escaped string.
 */
function escapeHTML(str) {
    const replacements = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;'
    };
    return str.replace(/[&<>"']/g, match => replacements[match]);
}

function updatePreview() {
    const textarea = document.getElementById('code');
    const previewFrame = document.getElementById('previewFrame');
    const errorMessage = document.getElementById('error-message');

    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 = '';
    } else {
        errorMessage.textContent = '';
        errorMessage.style.display = 'none';
        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;
}

updatePreview();

let timeout;
const textareaElement = document.getElementById('code');

textareaElement.addEventListener('input', () => {
    clearTimeout(timeout);
    timeout = setTimeout(updatePreview, 300);
});

const tabEditor = document.getElementById('tab-editor');
const tabPreview = document.getElementById('tab-preview');
const editor = document.querySelector('.editor');
const preview = document.querySelector('.preview');

/**
 * Activates the specified tab and deactivates the other.
 * @param {string} tab - The tab to activate ('editor' or 'preview').
 */
function activateTab(tab) {
    tabEditor.classList.remove('active');
    tabPreview.classList.remove('active');

    editor.classList.remove('active');
    preview.classList.remove('active');

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

    handleResponsiveTabs();
}

function handleResponsiveTabs() {
    const isMobile = window.innerWidth <= 768;

    if (isMobile) {
        if (!editor.classList.contains('active') && !preview.classList.contains('active')) {
            editor.classList.add('active');
        }
    } else {
        editor.classList.add('active');
        preview.classList.add('active');
    }
}

handleResponsiveTabs();

window.addEventListener('resize', handleResponsiveTabs);

tabEditor.addEventListener('click', () => activateTab('editor'));
tabPreview.addEventListener('click', () => activateTab('preview'));

textareaElement.addEventListener('keydown', e => {
    const openingChars = {
        '"': '"',
        "'": "'",
        '(': ')',
        '{': '}',
        '[': ']',
        '<': '>'
    };

    const closingChars = Object.values(openingChars);

    if (e.key === 'Backspace') {
        const cursorPos = textareaElement.selectionStart;
        const textBeforeCursor = textareaElement.value.substring(0, cursorPos);
        const lastTwoChars = textBeforeCursor.slice(-2);
        return;
    }

    if (e.key === '"' || e.key === "'") {
        const closingChar = openingChars[e.key];
        const cursorPos = textareaElement.selectionStart;
        const beforeCursor = textareaElement.value.substring(0, cursorPos);
        const afterCursor = textareaElement.value.substring(cursorPos);

        if (afterCursor.startsWith(closingChar)) {
            e.preventDefault();
            textareaElement.setSelectionRange(cursorPos + 1, cursorPos + 1);
        } else {
            e.preventDefault();
            textareaElement.value = beforeCursor + e.key + closingChar + afterCursor;
            setTimeout(() => {
                textareaElement.setSelectionRange(cursorPos + 1, cursorPos + 1);
                textareaElement.dispatchEvent(new Event('input'));
            }, 0);
        }
        return;
    }

    if (e.key in openingChars) {
        e.preventDefault();
        const closingChar = openingChars[e.key];
        const cursorPos = textareaElement.selectionStart;
        const beforeCursor = textareaElement.value.substring(0, cursorPos);
        const afterCursor = textareaElement.value.substring(cursorPos);

        textareaElement.value = beforeCursor + e.key + closingChar + afterCursor;
        setTimeout(() => {
            textareaElement.setSelectionRange(cursorPos + e.key.length, cursorPos + e.key.length);
            textareaElement.dispatchEvent(new Event('input'));
        }, 0);
    }

    if (e.key === 'Tab') {
        e.preventDefault();

        const start = textareaElement.selectionStart;
        const end = textareaElement.selectionEnd;

        const value = textareaElement.value;
        const newValue = value.substring(0, start) + '  ' + value.substring(end);

        textareaElement.value = newValue;

        textareaElement.selectionStart = textareaElement.selectionEnd = start + 2;

        textareaElement.dispatchEvent(new Event('input'));
    }

    if (closingChars.includes(e.key)) {
        const cursorPos = textareaElement.selectionStart;
        const nextChar = textareaElement.value.substring(cursorPos, cursorPos + 1);
        if (e.key === nextChar) {
            e.preventDefault();
            textareaElement.setSelectionRange(cursorPos + 1, cursorPos + 1);
        }
    }
});
```

Can you add a vertical line counter behind the textarea? (input event on the textarea)