[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\ncustom: ['https://github.com/DosX-dev/DosX-dev/blob/main/donate.md'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 DosX\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# What is Braux?\nBraux is a unique console system built on browser-based client tools.\nIt combines ease of use with powerful features, giving you a Unix-like\ncommand line with a choice of different themes and color schemes.\n\n# Installed?\n### https://dosx.su/terminal\n\n# To do\n* make command control the current user\n\n![](screen1.jpg)\n![](screen2.jpg)\n![](screen3.jpg)\n"
  },
  {
    "path": "source/index.htm",
    "content": "<!DOCTYPE html>\n<!--\n    Author: DosX\n-->\n\n<html>\n\n<head>\n    <title>Braux: Terminal</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"google\" content=\"notranslate\">\n    <link rel=\"stylesheet\" href=\"styles/global.css\">\n    <link rel=\"stylesheet\" href=\"styles/themes/dark.css\" id=\"theme-link\">\n</head>\n\n<body>\n    <div id=\"context-menu\">\n        <div class=\"item\" onclick=\"pushCommand('clear')\">Clear</div>\n        <div class=\"item\" onclick=\"pushCommand('about')\">About</div>\n        <hr>\n        <div style=\"margin-bottom: 7px; margin-left: 7px; color: gray;\" id=\"version-info\"></div>\n    </div>\n    <div id=\"app\" class=\"container\">\n        <div id=\"console\"></div>\n        <div class=\"prompt\">\n            <span><span class=\"pointer\">&gt; </span></span><input type=\"text\" autocapitalize=\"off\" autocomplete=\"off\"\n                autocorrect=\"off\" id=\"commandInput\" spellcheck=\"false\" placeholder=\"help\" maxlength=2048>\n        </div>\n    </div>\n\n    <!-- context menu -->\n    <script src=\"modules/context.js\"></script>\n\n    <!-- fingerprint -->\n    <script src=\"modules/fp-api.js\"></script>\n\n    <!-- file system [beta] -->\n    <script src=\"modules/io-fs.js\"></script>\n\n    <script src=\"modules/manifest.js\"></script>\n    <script src=\"modules/app.js\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "source/modules/app.js",
    "content": "// Github: https://github.com/DosX-dev/braux\n\nconst visual = {\n    themes: {\n        dark: 'styles/themes/dark.css',\n        light: 'styles/themes/light.css',\n        cherry: 'styles/themes/cherry.css',\n        hacker: 'styles/themes/hacker.css'\n    },\n    installTheme(theme) {\n        visual.setTheme(theme);\n        localStorage.setItem('console-theme', theme);\n    },\n    setTheme(theme) {\n        const linkId = 'theme-link',\n            link = document.getElementById(linkId);\n\n        if (link) {\n            link.href = theme;\n        } else {\n            const newLink = document.createElement('link');\n            newLink.id = linkId;\n            newLink.rel = 'stylesheet';\n            newLink.href = theme;\n            document.head.appendChild(newLink);\n        }\n    },\n    loadTheme() {\n        const theme = localStorage.getItem('console-theme');\n        if (theme) {\n            visual.setTheme(theme);\n        } else {\n            const preferredTheme = window.matchMedia &&\n                window.matchMedia('(prefers-color-scheme: dark)').matches ? visual.themes.dark : visual.themes.light;\n            visual.installTheme(preferredTheme);\n        }\n    }\n}\n\nvisual.loadTheme();\ndocument.getElementById('version-info').innerText = `version ${config.version}`;\n\nObject.entries({\n    \"app-default-config\": \"0\",\n    \"app-prompt\": navigator.userAgent\n}).forEach(([key, value]) => {\n    setDefaultPromptValue(key, value);\n});\n\nconst commandInput = document.getElementById(\"commandInput\");\nconst isWelcomeHiddenKey = 'isWelcomeHidden';\n\nfunction isWelcomeHidden() {\n    return localStorage.getItem(isWelcomeHiddenKey);\n}\n\nvar commandHistory = [],\n    currentCommandIndex = -1;\n\n\n\nconst userProfile = {\n    name: {\n        key: 'user',\n        defaultName: 'user',\n        get() {\n            const userNameValue = localStorage.getItem(userProfile.name.key);\n\n            if (userNameValue) {\n                return userNameValue;\n            } else {\n                this.set(this.defaultName);\n                return this.defaultName;\n            }\n        },\n        set(newUserName) {\n\n            if (newUserName.length < 1) {\n                console.error('Username cannot be empty.');\n                return;\n            } else if (!/^[a-zA-Z0-9]+$/.test(newUserName)) {\n                console.error('Invalid characters.');\n                return;\n            }\n\n            localStorage.setItem(userProfile.name.key, newUserName.toLowerCase());\n        }\n    }\n};\n\nfunction replaceTagsWithEntities(text) {\n    return replacedText = text.replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nfunction setFocus() {\n    if (window.getSelection().toString() == '' && commandInput !== document.activeElement) {\n        commandInput.focus();\n    }\n}\n\nasync function getIpInfo(ip = '') {\n    const response = await fetch('https://freeipapi.com/api/json/' + ip, {\n        method: 'GET'\n    });\n\n    const data = await response.json();\n\n    const processedData = Object.entries(data)\n        .map(([key, value]) => `<b>${key}</b>: ${value}`)\n        .join('\\n');\n\n    return processedData + '\\n<b>apiUsed</b>: freeipapi.com';\n}\n\nfunction wrapFirstWord(sentence) {\n    var words = sentence.split(' ');\n\n    if (words.length === 0) {\n        return '';\n    }\n\n    words[0] = `<span class=\"first\">${words[0]}</span>`;\n\n    return words.join(' ');\n}\n\nwindow.onerror = function(message, source, lineno, colno, error) {\n    console.error(`[APP]: ${message}`);\n};\n\nfunction autoScroll() {\n    window.scrollTo(0, document.body.scrollHeight);\n}\n\nfunction pushCommand(command, displayCommand = true, sudo = false) {\n    var consoleDiv = document.getElementById('console'),\n        output = document.createElement('div');\n    if (displayCommand) {\n        commandInput.placeholder = '';\n        output.classList.add('out');\n        output.innerHTML = '<span class=\"pointer\">&gt; </span><span class=\"command\">' + wrapFirstWord(replaceTagsWithEntities(command)) + '</span>';\n        consoleDiv.appendChild(output);\n    }\n\n    function executeAsRoot(callback) {\n        if (sudo) {\n            callback();\n        } else {\n            error('You do not have permission to execute this command.');\n            out(`Try '<u><span class=\"insert-cmd\">sudo ${command}</span></u>' (click to insert)`);\n        }\n    }\n\n    function getAllCommandArguments(array) {\n        let out = '';\n        for (let i = 1; i < array.length; i++) {\n            out += array[i] + (i == array.length - 1 ? '' : ' ');\n        }\n        return out;\n    }\n\n    function out(text) {\n        let outStd = document.createElement('div');\n        ['out', 'log'].forEach(outClass => {\n            outStd.classList.add(outClass);\n        });\n        outStd.innerHTML = text;\n        consoleDiv.appendChild(outStd);\n        autoScroll();\n    }\n\n    function error(text) {\n        out(`<span class=\"error\">${text}</span>`);\n        let lastCommands = document.getElementsByClassName(\"command\"),\n            lastCommand = lastCommands[lastCommands.length - 1];\n        lastCommand.style = 'color: rgba(255, 79, 79);';\n        lastCommand.innerHTML += '<span style=\"color: gray;\"> (!)</span>';\n        autoScroll();\n    }\n\n    console.error = error;\n    console.log = console.info = console.warn = out;\n\n    console.clear = () => pushCommand(\"clear\");\n\n    const commandArgs = command.trim().split(' ');\n\n    switch (commandArgs[0]) {\n        case 'help':\n            out(`<span class=\"insert-cmd\">help</span> - show this message\n<span class=\"insert-cmd\">clear</span> - clear console\n<span class=\"insert-cmd\">theme</span> [name] - change theme\n<span class=\"insert-cmd\">echo</span> [html] - format and write text in console\n<span class=\"insert-cmd\">ipinfo</span> [ip] - get info about IP (no domains support)\n<span class=\"insert-cmd\">history</span> - get a log of commands entered\n<span class=\"insert-cmd\">fingerprint</span> - get client information\n<span class=\"insert-cmd\">clear</span> - clear console\n\n<span class=\"insert-cmd\">sudo [command]</span> - execute command as root\n<span class=\"insert-cmd\">about</span> - get info about application\n<span class=\"insert-cmd\">reboot</span> - restart the application\n<span class=\"insert-cmd\">exit</span> - exit from the application\n\n* <span class=\"insert-cmd\">js</span> [code] - execute JavaScript (unsafe)\n* <span class=\"insert-cmd\">factory-reset</span> - reset all application data settings\n`);\n            break;\n\n\n\n        case 'clear':\n            consoleDiv.innerText = '';\n            out(\"<i>Console cleared.</i>\");\n            break;\n\n\n\n        case 'exit':\n            out('Goodbye!');\n            setTimeout(function() {\n                window.location.href = 'about:blank';\n            }, 300);\n            break;\n\n\n\n        case 'echo':\n            out(getAllCommandArguments(commandArgs))\n            break;\n\n\n\n        case 'fingerprint':\n            out(fingerprint());\n            break;\n\n\n\n        case 'factory-reset':\n            executeAsRoot(() => {\n                localStorage.clear();\n                out(`All data of '${document.domain}' erased`);\n                setTimeout(() => {\n                    pushCommand('reboot', false);\n                }, 750);\n            });\n            break;\n\n\n\n        case 'history':\n            switch (commandArgs[1]) {\n                case 'clear':\n                    clearCommandHistory();\n                    out('Command history cleared.');\n                    break;\n                case 'list':\n                    out(getCommandHistory());\n                    break;\n                default:\n                    out(`Arguments:\n * list (get history as numbered list)\n * clear (clear history)\n\nUsage: <span class=\"insert-cmd\">history list</span>`)\n            }\n            break;\n\n\n\n        case 'theme':\n            let isSeccuss = true;\n            switch (commandArgs[1]) {\n                case 'dark':\n                    visual.installTheme(visual.themes.dark);\n                    break;\n                case 'light':\n                    visual.installTheme(visual.themes.light);\n                    break;\n                case 'cherry':\n                    visual.installTheme(visual.themes.cherry);\n                    break;\n                case 'hacker':\n                    visual.installTheme(visual.themes.hacker);\n                    break;\n                default:\n                    isSeccuss = false;\n                    out(`Themes:\n * dark\n * light\n * cherry\n * hacker\n\nUsage: <span class=\"insert-cmd\">theme dark</span>`);\n            }\n            if (isSeccuss) {\n                out(`Theme installed: ${commandArgs[1]}`);\n            }\n            break;\n\n\n\n        case 'ipinfo':\n            out('Requesting...');\n            getIpInfo(commandArgs[1])\n                .then(dataString => {\n                    out(dataString);\n                })\n                .catch(error => {\n                    error('No API access');\n                });\n            break;\n\n\n\n        case 'js':\n            executeAsRoot(() => {\n                let codeToExec = getAllCommandArguments(commandArgs);\n\n                if (codeToExec.trim() == '') {\n                    error('Empty source!');\n                } else {\n                    try {\n                        out(`<span style=\"color: gray;\"><span class=\"pointer\">&lt; </span>${eval(codeToExec)}<span>`);\n                    } catch (exc) {\n                        error(`[VM]: ${exc}`);\n                    }\n                }\n            });\n            break;\n\n\n\n        case 'about':\n            out(` OS:       ${version.os}\n Kernel:   ${version.kernel}\n Shell:    ${version.shell}`);\n            break;\n\n\n\n        case 'reboot':\n            out('Rebooting...');\n            setTimeout(() => {\n                location.reload();\n            }, 300);\n            break;\n\n\n\n        case 'su':\n            error(`You can only use '<span class=\"insert-cmd\">sudo</span>'`);\n            break;\n\n\n\n        case 'sudo':\n            let commandToExecute = getAllCommandArguments(commandArgs);\n            if (commandToExecute.trim() == '') {\n                out(`Usage: sudo [command]`)\n            }\n            pushCommand(commandToExecute, false, true);\n            break;\n\n\n\n        case 'remove-intro':\n            localStorage.setItem(isWelcomeHiddenKey, String(true))\n            break;\n\n\n\n        case 'python': // Yes, I hate python\n            error('Do not embarrass yourself.');\n            break;\n\n\n\n        case '': // Empty prompt\n            break;\n        case '#': // Comment\n            break;\n        default:\n            error(`Command or packet '<u>${commandArgs[0]}</u>' not found!`);\n    }\n    autoScroll();\n}\n\nfunction pushCommandScript(command, sudo = false) {\n    command.split('\\n').forEach(element => {\n        pushCommand(element, false, sudo);\n    });\n}\n\npushCommand(`echo Welcome, ${userProfile.name.get()}!`, false);\n\nif (isWelcomeHidden() !== String(true)) {\n    pushCommand('echo ' +\n        `+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n<b>Braux</b> is a unique console system built on browser-based client tools.\nIt combines ease of use with powerful features, giving you a Unix-like\ncommand line with a choice of different themes and color schemes.\n\nGitHub -> <a target=\"_blank\" href=\"https://github.com/DosX-dev/braux\">https://github.com/DosX-dev/braux</a>\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\nUse '<u><span class=\"insert-cmd\">remove-intro</span></u>' to remove this message`, false);\n}\n\nfunction setDefaultPromptValue(name, defaultValue) {\n    const urlParams = new URLSearchParams(window.location.search);\n    if (!urlParams.has(name)) {\n        let locationBarSections = document.URL.split('/'),\n            locationBarLastSection = locationBarSections[locationBarSections.length - 1],\n            separator = locationBarLastSection.includes('?') ? '&' : '?',\n            newUrl = `${window.location.href + separator + name}=${defaultValue}`;\n        history.pushState({ path: newUrl }, '', newUrl);\n    }\n}\n\n// load history form localStorage\nconst storedHistory = localStorage.getItem(\"history\");\nif (storedHistory) {\n    commandHistory = JSON.parse(storedHistory);\n    currentCommandIndex = commandHistory.length;\n}\n\ncommandInput.addEventListener(\"keydown\", (event) => {\n    switch (event.keyCode) {\n        case 13: // Enter\n            let command = commandInput.value.trim();\n            if (command !== '') {\n                if (commandHistory.length === 0 || command !== commandHistory[commandHistory.length - 1]) {\n                    commandHistory.push(command);\n                }\n                currentCommandIndex = commandHistory.length;\n\n                // save history in localStorage\n                localStorage.setItem(\"history\", JSON.stringify(commandHistory));\n            }\n            break;\n        case 38: // Up\n            event.preventDefault();\n            if (currentCommandIndex > 0) {\n                currentCommandIndex--;\n                commandInput.value = commandHistory[currentCommandIndex];\n            }\n            break;\n        case 40: // Down\n            event.preventDefault();\n            if (currentCommandIndex < commandHistory.length - 1) {\n                currentCommandIndex++;\n                commandInput.value = commandHistory[currentCommandIndex];\n            } else {\n                currentCommandIndex = commandHistory.length;\n                commandInput.value = '';\n            }\n            break;\n        default:\n            break;\n    }\n});\n\n\nfunction getCommandHistory() {\n    return commandHistory.map((command, index) => `${index + 1}. <span class=\"insert-cmd\">${command}</span>`).join(\"\\n\");\n}\n\nfunction clearCommandHistory() {\n    commandHistory = [];\n    currentCommandIndex = 0;\n    localStorage.removeItem(\"history\");\n}\n\ndocument.addEventListener('DOMContentLoaded', () => {\n    commandInput.addEventListener('keypress', function(event) {\n        if (event.keyCode === 13) { // Enter\n            event.preventDefault();\n            pushCommand(commandInput.value.trim());\n            commandInput.value = '';\n        }\n    });\n});\n\nsetInterval(() => {\n    setFocus()\n}, 500);\n\ndocument.addEventListener('click', (event) => {\n    if (event.target.classList.contains('insert-cmd')) {\n        commandInput.value = event.target.textContent;\n    }\n});"
  },
  {
    "path": "source/modules/context.js",
    "content": "// Github: https://github.com/DosX-dev/braux\n\nconst contextMenu = document.getElementById(\"context-menu\");\nconst scope = document.querySelector(\"body\");\n\nconst normalizePozition = (mouseX, mouseY) => {\n    // ? compute what is the mouse position relative to the container element (scope)\n    let {\n        left: scopeOffsetX,\n        top: scopeOffsetY,\n    } = scope.getBoundingClientRect();\n\n    scopeOffsetX = scopeOffsetX < 0 ? 0 : scopeOffsetX;\n    scopeOffsetY = scopeOffsetY < 0 ? 0 : scopeOffsetY;\n\n    const scopeX = mouseX - scopeOffsetX,\n        scopeY = mouseY - scopeOffsetY;\n\n    // ? check if the element will go out of bounds\n    const outOfBoundsOnX =\n        scopeX + contextMenu.clientWidth > scope.clientWidth;\n\n    const outOfBoundsOnY =\n        scopeY + contextMenu.clientHeight > scope.clientHeight;\n\n    let normalizedX = mouseX,\n        normalizedY = mouseY;\n\n    // ? normalize on X\n    if (outOfBoundsOnX) {\n        normalizedX =\n            scopeOffsetX + scope.clientWidth - contextMenu.clientWidth;\n    }\n\n    // ? normalize on Y\n    if (outOfBoundsOnY) {\n        normalizedY =\n            scopeOffsetY + scope.clientHeight - contextMenu.clientHeight;\n    }\n\n    return {\n        normalizedX,\n        normalizedY\n    };\n};\n\nscope.addEventListener(\"contextmenu\", (event) => {\n    event.preventDefault();\n\n    const {\n        clientX: mouseX,\n        clientY: mouseY\n    } = event, {\n        normalizedX,\n        normalizedY\n    } = normalizePozition(mouseX, mouseY);\n\n    contextMenu.classList.remove(\"visible\");\n\n    contextMenu.style.top = `${normalizedY}px`;\n    contextMenu.style.left = `${normalizedX}px`;\n\n    setTimeout(() => {\n        contextMenu.classList.add(\"visible\");\n    });\n});\n\nscope.addEventListener(\"click\", (event) => {\n    // ? close the menu if the user clicks outside of it\n    if (event.target.offsetParent != contextMenu) {\n        contextMenu.classList.remove(\"visible\");\n    }\n});\n\nconst items = document.getElementsByClassName(\"item\");\n\nArray.from(items).forEach(item => {\n    item.addEventListener(\"click\", (event) => {\n        contextMenu.classList.remove('visible');\n    });\n});"
  },
  {
    "path": "source/modules/fp-api.js",
    "content": "// Github: https://github.com/DosX-dev/braux\n\nfunction fingerprint() {\n    var fingerprintData = {\n        'User Agent': navigator.userAgent,\n        'Browser Language': navigator.language,\n        'Cookies Enabled': navigator.cookieEnabled,\n        'Screen Resolution': screen.width + 'x' + screen.height,\n        'Available Screen Resolution': screen.availWidth + 'x' + screen.availHeight,\n        'Color Depth': screen.colorDepth,\n        'Timezone': Intl.DateTimeFormat().resolvedOptions().timeZone,\n        'Local Storage Enabled': typeof Storage !== 'undefined',\n        'Session Storage Enabled': typeof sessionStorage !== 'undefined',\n        'Do Not Track': Boolean(navigator.doNotTrack),\n        'Plugins': Array.from(navigator.plugins).map(plugin => plugin.name).join(', '),\n        'WebGL Vendor': getWebGLVendor(),\n        'WebGL Renderer': getWebGLRenderer(),\n        'WebGL Version': getWebGLVersion(),\n        'Web Audio API': isWebAudioAPISupported(),\n        'MIDI API': isMIDIAPIAvailable(),\n        'WebSockets Supported': isWebSocketsSupported(),\n        'Battery API Supported': isBatteryAPISupported(),\n        'WebVR API Supported': isWebVRAPIAvailable(),\n        'AudioContext Max Channels': getMaxAudioContextChannels(),\n        'Is Chromium based': isChromium()\n    };\n\n    var fingerprintString = Object.entries(fingerprintData)\n        .map(entry => `<b>${entry[0]}</b>` + ': ' + entry[1])\n        .join('\\n');\n\n    return fingerprintString;\n}\n\nfunction getWebGLVendor() {\n    var canvas = document.createElement('canvas'),\n        gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');\n    if (gl && gl.getExtension('WEBGL_debug_renderer_info')) { return gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_VENDOR_WEBGL); }\n    return 'N/A';\n}\n\nfunction getWebGLRenderer() {\n    var canvas = document.createElement('canvas'),\n        gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');\n    if (gl && gl.getExtension('WEBGL_debug_renderer_info')) { return gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL); }\n    return 'N/A';\n}\n\nfunction getWebGLVersion() {\n    var canvas = document.createElement('canvas'),\n        gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');\n    if (gl) { return gl.getParameter(gl.VERSION); }\n    return 'N/A';\n}\n\nfunction isWebAudioAPISupported() { return typeof window.AudioContext !== 'undefined' || typeof window.webkitAudioContext !== 'undefined'; }\n\nfunction isMIDIAPIAvailable() { return typeof navigator.requestMIDIAccess !== 'undefined'; }\n\nfunction isWebSocketsSupported() { return 'WebSocket' in window ? 'Supported' : 'Not supported'; }\n\nfunction isBatteryAPISupported() { return navigator.getBattery ? 'Supported' : 'Not supported'; }\n\nfunction isWebVRAPIAvailable() { return 'getVRDisplays' in navigator ? 'Supported' : 'Not supported'; }\n\nfunction isWebXRAPIAvailable() { return 'xr' in navigator ? 'Supported' : 'Not supported'; }\n\nfunction getMaxAudioContextChannels() {\n    try {\n        var audioContext = new(window.AudioContext || window.webkitAudioContext)(),\n            maxChannels = audioContext.destination.maxChannelCount;\n        audioContext.close();\n        return maxChannels;\n    } catch (exc) {\n        return 'N/A'\n    }\n}\n\nfunction isChromium() { return typeof window.chrome == 'object'; }\n\nfunction isOpera() { return typeof window.opera == 'object'; }"
  },
  {
    "path": "source/modules/io-fs.js",
    "content": "// Github: https://github.com/DosX-dev/braux\n\nclass FileSystem {\n    constructor() {\n        this.storage = window.localStorage;\n        this.files = JSON.parse(this.storage.getItem('files')) || {};\n        this.serializedFiles = JSON.stringify(this.files);\n        this.fileList = Object.keys(this.files).join('\\n');\n    }\n\n    createFile(name, content = '') {\n        if (!this.isValidFileName(name)) {\n            throw new Error('Invalid file name');\n        }\n\n        if (this.fileExists(name)) {\n            throw new Error('File already exists');\n        }\n\n        this.files[name] = content;\n        this.serializedFiles = JSON.stringify(this.files);\n        this.saveFiles();\n    }\n\n    deleteFile(name) {\n        if (!this.isValidFileName(name)) {\n            throw new Error('Invalid file name');\n        }\n\n        if (!this.fileExists(name)) {\n            throw new Error('File not found');\n        }\n\n        delete this.files[name];\n        this.serializedFiles = JSON.stringify(this.files);\n        this.saveFiles();\n    }\n\n    readFile(name) {\n        if (!this.isValidFileName(name)) {\n            throw new Error('Invalid file name');\n        }\n\n        if (!this.fileExists(name)) {\n            throw new Error('File not found');\n        }\n\n        return this.files[name];\n    }\n\n    writeFile(name, content) {\n        if (!this.isValidFileName(name)) {\n            throw new Error('Invalid file name');\n        }\n\n        if (!this.fileExists(name)) {\n            throw new Error('File not found');\n        }\n\n        this.files[name] = content;\n        this.serializedFiles = JSON.stringify(this.files);\n        this.saveFiles();\n    }\n\n    saveFiles() {\n        this.storage.setItem('files', this.serializedFiles);\n    }\n\n    getFileList() {\n        return this.fileList;\n    }\n\n    isValidFileName(name) {\n        const forbiddenChars = /[^\\w\\d-_]/;\n        return !forbiddenChars.test(name);\n    }\n\n    fileExists(name) {\n        return name in this.files;\n    }\n}\n\nconst IO = new FileSystem();"
  },
  {
    "path": "source/modules/manifest.js",
    "content": "// Github: https://github.com/DosX-dev/braux\n\nconst config = {\n        version: '3.20.25'\n    },\n    version = {\n        os: 'braux (client)',\n        kernel: 'VM-' + config.version,\n        shell: 'bash-js'\n    }"
  },
  {
    "path": "source/styles/global.css",
    "content": "html,\nbody {\n    transition: background-color 0.8s, color 0.8s;\n    cursor: default;\n    font-family: monospace;\n    margin: 0;\n    padding: 0;\n    zoom: 100%;\n    width: 100%;\n    height: 100%;\n}\n\ninput .container {\n    margin-left: 10px;\n}\n\ninput,\n.prompt {\n    user-select: none;\n}\n\n.command {\n    white-space: pre-wrap;\n}\n\n.first {\n    font-style: italic;\n}\n\n.log {\n    margin: 10px;\n    margin-bottom: 10px;\n    white-space: pre-wrap;\n}\n\nhr {\n    height: 1px;\n    border: none;\n}\n\n#console {\n    overflow-y: auto;\n    word-wrap: break-word;\n}\n\n::-webkit-scrollbar {\n    width: 10px;\n}\n\ninput[type=\"text\"] {\n    cursor: default;\n    background-color: transparent;\n    border: none;\n    font-family: monospace;\n    font-size: inherit;\n    width: 94vw;\n    outline: none;\n}\n\n.error {\n    transition: background-color 0.9s, color 0.9s;\n    border-radius: 4px;\n    height: 100%;\n}\n\na {\n    transition: background-color 0.3s, color 0.3s;\n    text-decoration-line: none;\n    border-bottom: 1px dotted;\n}\n\n.insert-cmd {\n    cursor: help;\n}\n\n#context-menu {\n    border: 1px solid;\n    position: fixed;\n    z-index: 10000;\n    width: 150px;\n    border-radius: 5px;\n    transform: scale(0);\n    transform-origin: top left;\n    user-select: none;\n}\n\n#context-menu.visible {\n    transform: scale(1);\n    transition: transform 200ms ease-in-out;\n}\n\n#context-menu .item {\n    transition: background-color 0.3s, color 0.3s;\n    padding: 8px 10px;\n    font-size: 15px;\n    cursor: pointer;\n    border-radius: inherit;\n}"
  },
  {
    "path": "source/styles/themes/cherry.css",
    "content": "body {\n    background-color: rgb(41, 0, 35);\n    color: white;\n}\n\nhr {\n    background-color: rgb(108, 0, 158);\n}\n\n::-webkit-scrollbar-track {\n    background-color: black;\n}\n\n::-webkit-scrollbar-thumb {\n    background-color: white;\n}\n\n::selection {\n    background-color: rgb(255, 0, 43);\n    color: white;\n}\n\ninput[type=\"text\"] {\n    color: white;\n    caret-color: rgb(255, 210, 247);\n}\n\n.error {\n    background-color: rgb(190, 44, 44);\n    color: white;\n}\n\na {\n    background-color: rgb(173, 0, 130);\n    color: white;\n}\n\na:hover {\n    background-color: white;\n    color: rgb(255, 0, 191);\n}\n\n.pointer {\n    color: white;\n}\n\n#context-menu {\n    background: #1b1a1a;\n    border-color: rgb(108, 0, 158);\n}\n\n#context-menu .item {\n    color: #eee;\n}\n\n#context-menu .item:hover {\n    background: #4e004e;\n}"
  },
  {
    "path": "source/styles/themes/dark.css",
    "content": "body {\n    background-color: black;\n    color: white;\n}\n\nhr {\n    background-color: rgb(66, 66, 66);\n}\n\n::-webkit-scrollbar-track {\n    background-color: black;\n}\n\n::-webkit-scrollbar-thumb {\n    background-color: white;\n}\n\n::selection {\n    background-color: white;\n    color: black;\n}\n\ninput[type=\"text\"] {\n    color: white;\n    caret-color: white;\n}\n\n.error {\n    background-color: rgb(116, 0, 0);\n    color: white;\n}\n\na {\n    background-color: rgb(43, 100, 255);\n    color: white;\n}\n\na:hover {\n    background-color: white;\n    color: rgb(57, 43, 255);\n}\n\n.pointer {\n    color: white;\n}\n\n#context-menu {\n    background: #1b1a1a;\n    border-color: rgb(66, 66, 66);\n}\n\n#context-menu .item {\n    color: #eee;\n}\n\n#context-menu .item:hover {\n    background: #343434;\n}"
  },
  {
    "path": "source/styles/themes/hacker.css",
    "content": "body {\n    background-color: rgb(0, 18, 19);\n    color: white;\n}\n\nhr {\n    background-color: rgb(0, 138, 0);\n}\n\n::-webkit-scrollbar-track {\n    background-color: black;\n}\n\n::-webkit-scrollbar-thumb {\n    background-color: lime;\n}\n\n::selection {\n    background-color: rgb(0, 138, 0);\n    color: white;\n}\n\ninput[type=\"text\"] {\n    color: white;\n    caret-color: lime;\n}\n\n.error {\n    background-color: rgb(255, 155, 155);\n    color: black;\n}\n\na {\n    background-color: rgb(23, 80, 0);\n    color: white;\n}\n\na:hover {\n    background-color: white;\n    color: rgb(57, 43, 255);\n}\n\n.pointer {\n    color: lime;\n}\n\n#context-menu {\n    background: #1b1a1a;\n    border-color: rgb(23, 80, 0);\n}\n\n#context-menu .item {\n    color: #eee;\n}\n\n#context-menu .item:hover {\n    background: #003612;\n}"
  },
  {
    "path": "source/styles/themes/light.css",
    "content": "body {\n    background-color: white;\n    color: black;\n}\n\nhr {\n    background-color: rgb(151, 151, 151);\n}\n\n::-webkit-scrollbar-track {\n    background-color: white;\n}\n\n::-webkit-scrollbar-thumb {\n    background-color: black;\n}\n\n::selection {\n    background-color: black;\n    color: white;\n}\n\ninput[type=\"text\"] {\n    color: black;\n    caret-color: black;\n}\n\n.error {\n    background-color: rgb(116, 0, 0);\n    color: white;\n}\n\na {\n    background-color: rgb(0, 63, 238);\n    color: white;\n}\n\na:hover {\n    background-color: rgb(0, 0, 0);\n}\n\n.pointer {\n    color: black;\n}\n\n#context-menu {\n    background: #eee;\n    border-color: rgb(151, 151, 151);\n}\n\n#context-menu .item {\n    color: #000000;\n}\n\n#context-menu .item:hover {\n    background: #dbdbdb;\n}"
  }
]