about summary refs log tree commit diff stats
path: root/html/playground
diff options
context:
space:
mode:
Diffstat (limited to 'html/playground')
-rw-r--r--html/playground/APL386.ttfbin0 -> 203668 bytes
-rw-r--r--html/playground/counter.html262
-rw-r--r--html/playground/index.html248
-rw-r--r--html/playground/little-regex.html728
-rw-r--r--html/playground/regex.html477
-rw-r--r--html/playground/scheme.html533
6 files changed, 2248 insertions, 0 deletions
diff --git a/html/playground/APL386.ttf b/html/playground/APL386.ttf
new file mode 100644
index 0000000..5e3a338
--- /dev/null
+++ b/html/playground/APL386.ttf
Binary files differdiff --git a/html/playground/counter.html b/html/playground/counter.html
new file mode 100644
index 0000000..1b16f54
--- /dev/null
+++ b/html/playground/counter.html
@@ -0,0 +1,262 @@
+<html lang="en"><head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>JavaScript Playground</title>
+    <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles.">
+    <style>
+        body {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            background-color: #ddd;
+            padding: 10px;
+            height: 100vh;
+            margin: 0;
+        }
+
+        textarea {
+            width: 100%;
+            height: 64%;
+            font-family: monospace;
+            background-color: #FFFEEC;
+            border: 2px solid #000;
+            scrollbar-width: none;            
+            font-size: 1rem;
+            margin-bottom: 10px;
+            padding: 10px;
+            box-sizing: border-box;
+            resize: none;
+            border-bottom: 12px solid teal;
+            -webkit-user-modify: read-write-plaintext-only;
+        }
+
+        textarea::-webkit-scrollbar {
+            display: none;
+        }
+
+        textarea::selection {
+            background-color: #EFECA7;
+        }
+
+        textarea:focus {
+            outline: none;
+        }
+
+        #console {
+            width: 100%;
+            height: 22%;
+            background-color: #000;
+            color: #0fc;
+            font-family: monospace;
+            font-size: 1rem;
+            overflow-y: auto;
+            padding: 10px;
+            box-sizing: border-box;
+            white-space: pre-wrap;
+        }
+
+        .button-container {
+            width: 100%;
+            display: flex;
+            justify-content: flex-end;
+            margin-bottom: 10px;
+        }
+
+        button {
+            padding: 10px 20px;
+            font-size: 1rem;
+            margin-left: 10px;
+            cursor: pointer;
+            border: none;
+            transition: background-color 0.3s ease;
+        }
+
+        button:hover, button:focus {
+            outline: none; 
+        }
+
+        button.run {
+            background-color: teal;
+            color: #FFFFFF;
+        }
+    </style>
+</head>
+<body>
+
+    <div class="playground" id="playground">    
+        <div class="seesaw" id="seesaw"></div>
+        <div class="slide" id="slide"></div>
+    </div>
+
+    <div class="button-container">
+        <button onclick="clearEverything()">Clear</button>
+        <button onclick="downloadCodeAndEditor()">Download</button>
+        <button onclick="shareCode()">Share</button>
+        <button onclick="evaluateCode()" class="run">Run Code</button>
+    </div>
+    <textarea id="codeInput">mount(playground => {
+    const d = document.createElement('div');
+    const p = document.createElement('p');
+    const b = document.createElement('button');
+    const b2 = document.createElement('button');
+    
+    d.style = 'border: 1px solid black; padding: 2em';
+    p.textContent = 'Count: 0';
+    p.style = 'text-align: center; font-weight: bold';
+    b.textContent = 'Click Me';
+    b2.textContent = 'Reset';
+    b2.style = 'background-color: red; color: white';
+    d.append(p,b,b2);
+    
+    let count = 0;
+    b.onclick  = () => { count++;    p.innerHTML = 'Count: ' + count; }
+    b2.onclick = () => { count = 0;  p.innerHTML = 'Count: ' + count; }
+
+    playground.appendChild(d);
+});
+</textarea>
+    <div id="console"></div>
+
+    <script>
+        function evaluateCode() {
+            const code = document.getElementById('codeInput').value;
+            const consoleDiv = document.getElementById('console');
+            consoleDiv.innerHTML = '';
+
+            // Custom console.log function to output to the console div
+            const originalConsoleLog = console.log;
+            console.log = function(...args) {
+                args.forEach(arg => {
+                    const output = document.createElement('div');
+                    output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg;
+                    consoleDiv.appendChild(output);
+                });
+                originalConsoleLog.apply(console, args);
+            };
+
+            try {
+                eval(code);
+            } catch (error) {
+                const errorOutput = document.createElement('div');
+                errorOutput.textContent = error;
+                errorOutput.style.color = 'red';
+                consoleDiv.appendChild(errorOutput);
+            }
+
+            // Restore browser's console.log
+            console.log = originalConsoleLog;
+        }
+
+        function downloadCodeAndEditor() {
+            const codeInput = document.getElementById('codeInput').value;
+            const htmlContent = document.documentElement.outerHTML.replace(
+                /<textarea id="codeInput"[^>]*>.*<\/textarea>/,
+                `<textarea id="codeInput">${codeInput}</textarea>`
+            );
+
+            const blob = new Blob([htmlContent], { type: 'text/html' });
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = 'code_editor.html';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+        }
+
+        function shareCode() {
+            const code = document.getElementById('codeInput').value;
+            const encodedCode = btoa(encodeURIComponent(code));
+            window.location.hash = encodedCode;
+            window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href);
+        }
+
+        function clearEverything() {
+            if (!confirm('Are you sure you want to reset the playground?')) {
+                return;
+            } else {               
+                window.location.hash = '';
+                window.location.reload();
+            }
+        }
+
+        function loadCodeFromHash() {
+            const hash = window.location.hash.substring(1);
+            if (hash) {
+                try {
+                    const decodedCode = decodeURIComponent(atob(hash));
+                    document.getElementById('codeInput').value = decodedCode;
+                } catch (error) {
+                    console.error('Failed to decode the URL hash:', error);
+                }
+            }
+        }
+
+        function help() {
+            const helpText = `
+            Welcome to the JavaScript Playground! Here are some tips to get you started:
+
+            1. Enter your JavaScript code in the textarea.
+            2. Click the "Run Code" button to execute your code.
+            3. The console output will be displayed below the textarea.
+            4. Click the "Clear" button to reset the playground.
+            5. Click the "Download" button to save your code and editor as an HTML file.
+            6. Click the "Share" button to generate a URL to share your code with others.
+            7. You can also press "Cmd + Enter" to run your code.
+            8. There's an empty div above the buttons with the id "playground"
+            9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()"
+            10. You can use the "clear()" function to clear the content's of the console.
+
+            Go nuts! Share scrappy fiddles!
+            `;
+            console.log(helpText);
+        }
+
+        function clear() {
+            document.getElementById('console').innerHTML = '';
+        }
+
+        function mountHelp() {
+            console.log(`
+            The mount function is used to mount stuff to the playground div.
+            It takes a function as an argument, which in turn receives the playground div as an argument.
+            Before mounting, it clears the playground div.
+            Here's an example of how to use the mount function:
+
+            mount(playground => {
+                const h1 = document.createElement('h1');
+                h1.textContent = 'Hell is empty and all the devils are here.';
+                playground.appendChild(h1);
+            });
+
+            This will add an h1 element to the playground div.
+            `);
+        }
+
+        function mount(mountFunction) {
+            const playground = document.getElementById('playground');
+            if (!playground) {
+                console.error("Couldn't find a div with the id 'playground'! You may need to reload the page.");
+                return;
+            }
+
+            if (playground.innerHTML.trim() !== "") {
+                playground.innerHTML = "";
+            }
+            mountFunction(playground);
+        }
+
+
+        document.getElementById('codeInput').addEventListener('keydown', function(event) {
+            if (event.metaKey && event.key === 'Enter') {
+                event.preventDefault();
+                evaluateCode();
+            }
+        });
+
+        window.onload = loadCodeFromHash;
+    </script>
+
+
+</body></html>
\ No newline at end of file
diff --git a/html/playground/index.html b/html/playground/index.html
new file mode 100644
index 0000000..a236d2f
--- /dev/null
+++ b/html/playground/index.html
@@ -0,0 +1,248 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>JavaScript Playground</title>
+    <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles.">
+    <style>
+        html, body { height: 100%; margin: 0; padding: 0; }
+        body { display: flex; flex-direction: column; background-color: #ddd; }
+        #app { display: flex; flex-direction: column; padding: 10px; height: 100%; box-sizing: border-box; }
+        .button-container { width: 100%; display: flex; justify-content: flex-end; margin-bottom: 10px; flex-shrink: 0; }
+        button { padding: 10px 20px; font-size: 1rem; margin-left: 10px; cursor: pointer; border: none; transition: background-color 0.3s ease; }
+        button:hover, button:focus { outline: none; }
+        button:disabled { background-color: #999; cursor: not-allowed; }
+        button.run { background-color: teal; color: #FFFFFF; }
+        textarea { width: 100%; height: 64%; font-family: monospace; background-color: #FFFEEC; border: 2px solid #000; scrollbar-width: none; font-size: 1rem; margin-bottom: 10px; padding: 10px; box-sizing: border-box; resize: none; border-bottom: 12px solid teal; -webkit-user-modify: read-write-plaintext-only; flex-shrink: 0; }
+        textarea::-webkit-scrollbar { display: none; }
+        textarea::selection { background-color: #EFECA7; }
+        textarea:focus { outline: none; }
+        #console { width: 100%; height: 22%; background-color: #000; color: #0fc; font-family: monospace; font-size: 1rem; overflow-y: auto; padding: 10px; box-sizing: border-box; white-space: pre-wrap; flex-grow: 1; }
+        #console .log-item { margin-bottom: 5px; border-bottom: 1px solid #333; }
+        #console details { cursor: pointer; }
+        #console summary { outline: none; }
+    </style>
+</head>
+<body>
+    <div id="app"></div>
+
+    <iframe id="sandbox" style="display: none;" title="JavaScript Sandbox"></iframe>
+
+    <script>
+        /**
+         * ----------------------------------------------------------------
+         * Functional Utilities
+         * ----------------------------------------------------------------
+         */
+
+        const pipe = (...fns) => (initialValue) => fns.reduce((acc, fn) => fn(acc), initialValue);
+        const compose = (...fns) => (initialValue) => fns.reduceRight((acc, fn) => fn(acc), initialValue);
+
+        /**
+         * ----------------------------------------------------------------
+         * The Elm Architecture (Model, View, Update) Implementation
+         * ----------------------------------------------------------------
+         */
+
+        const init = {
+            code: `
+            const add = (a, b) => a + b;
+            const multiply = (a, b) => a * b;
+
+            const addAndMultiply = pipe(add, multiply);
+
+            console.log(addAndMultiply(2, 3));
+`,
+            consoleOutput: [],
+            sandboxReady: false,
+        };
+
+        const view = (model) => `
+            <div class="button-container">
+                <button onclick="dispatch({ type: 'CLEAR' })">Clear</button>
+                <button onclick="dispatch({ type: 'DOWNLOAD' })">Download</button>
+                <button onclick="dispatch({ type: 'SHARE' })">Share</button>
+                <button onclick="dispatch({ type: 'RUN_CODE' })" class="run" ${!model.sandboxReady ? 'disabled title="Sandbox is loading..."' : ''}>Run Code</button>
+            </div>
+            <textarea id="codeInput" oninput="dispatch({ type: 'CODE_CHANGED', payload: this.value })" onkeydown="handleKeyDown(event)">${model.code}</textarea>
+            <div id="console">
+                ${model.consoleOutput.map(log => {
+                    if (typeof log === 'object' && log !== null) {
+                        return `<div class="log-item"><details><summary>${Object.prototype.toString.call(log)}</summary><pre>${JSON.stringify(log, null, 2)}</pre></details></div>`;
+                    }
+                    return `<div class="log-item">${log}</div>`;
+                }).join('')}
+            </div>
+        `;
+
+        const update = (msg, model) => {
+            switch (msg.type) {
+                case 'CODE_CHANGED':
+                    return { ...model, code: msg.payload };
+                
+                case 'RUN_CODE':
+                    return model;
+
+                case 'CONSOLE_LOG':
+                    return { ...model, consoleOutput: [...model.consoleOutput, ...msg.payload] };
+
+                case 'CLEAR':
+                    if (confirm('Are you sure you want to reset the playground?')) {
+                        window.location.hash = '';
+                        window.location.reload();
+                    }
+                    return model;
+
+                case 'CLEAR_CONSOLE':
+                     return { ...model, consoleOutput: [] };
+                
+                case 'SHARE':
+                case 'DOWNLOAD':
+                    return model;
+
+                case 'SANDBOX_READY':
+                    return { ...model, sandboxReady: true };
+
+                default:
+                    return model;
+            }
+        };
+
+        /**
+         * ----------------------------------------------------------------
+         * Application Core & Side-Effect Handling
+         * ----------------------------------------------------------------
+         */
+
+        let model = null;
+        const appContainer = document.getElementById('app');
+        const sandbox = document.getElementById('sandbox');
+
+        const dispatch = (msg) => {
+            const newModel = update(msg, model);
+            handleSideEffects(msg, newModel);
+            model = newModel;
+            render(model);
+        };
+
+        const handleSideEffects = (msg, model) => {
+             if (msg.type === 'RUN_CODE') {
+                if (!model.sandboxReady) return;
+                dispatch({ type: 'CLEAR_CONSOLE' });
+                sandbox.contentWindow.postMessage({ code: model.code }, '*');
+            } else if (msg.type === 'SHARE') {
+                const encodedCode = btoa(encodeURIComponent(model.code));
+                window.location.hash = encodedCode;
+                window.prompt("Copy the URL to share.", window.location.href);
+            } else if (msg.type === 'DOWNLOAD') {
+                const htmlContent = document.documentElement.outerHTML.replace(
+                    /<textarea id="codeInput"[^>]*>.*<\/textarea>/,
+                    `<textarea id="codeInput">${model.code}</textarea>`
+                );
+                const blob = new Blob([htmlContent], { type: 'text/html' });
+                const url = URL.createObjectURL(blob);
+                const a = document.createElement('a');
+                a.href = url;
+                a.download = 'playground.html';
+                a.click();
+                URL.revokeObjectURL(url);
+            }
+        };
+
+        const render = (model) => {
+            const focusedElementId = document.activeElement?.id;
+            const selectionStart = document.activeElement?.selectionStart;
+            const selectionEnd = document.activeElement?.selectionEnd;
+
+            appContainer.innerHTML = view(model);
+
+            if (focusedElementId === 'codeInput') {
+                const focusedElement = document.getElementById(focusedElementId);
+                if (focusedElement) {
+                    focusedElement.focus();
+                    if (typeof selectionStart === 'number') {
+                       focusedElement.selectionStart = selectionStart;
+                       focusedElement.selectionEnd = selectionEnd;
+                    }
+                }
+            }
+        };
+
+        const handleKeyDown = (event) => {
+            if (event.key === 'Tab') {
+                event.preventDefault();
+                const start = event.target.selectionStart;
+                const end = event.target.selectionEnd;
+                event.target.value = event.target.value.substring(0, start) + "  " + event.target.value.substring(end);
+                dispatch({ type: 'CODE_CHANGED', payload: event.target.value });
+                event.target.selectionStart = event.target.selectionEnd = start + 2;
+            }
+            if (event.metaKey && event.key === 'Enter') {
+                event.preventDefault();
+                dispatch({ type: 'RUN_CODE' });
+            }
+        };
+        
+        /**
+         * ----------------------------------------------------------------
+         * Sandbox Initialization
+         * ----------------------------------------------------------------
+         */
+         
+        const sandboxSrc = `
+            <script>
+                const pipe = ${pipe.toString()};
+                const compose = ${compose.toString()};
+            
+                const originalConsoleLog = console.log;
+                console.log = (...args) => {
+                    parent.postMessage({ type: 'CONSOLE_LOG', payload: args }, '*');
+                };
+
+                window.addEventListener('message', (event) => {
+                    try {
+                        eval(event.data.code);
+                    } catch (e) {
+                        console.log(e.toString());
+                    }
+                });
+            <\/script>
+        `;
+
+        window.addEventListener('message', (event) => {
+            if (event.source === sandbox.contentWindow && event.data.type === 'CONSOLE_LOG') {
+                dispatch(event.data);
+            }
+        });
+        
+        /**
+         * ----------------------------------------------------------------
+         * Application Entry Point
+         * ----------------------------------------------------------------
+         */
+        
+        const hash = window.location.hash.substring(1);
+        if (hash) {
+            try {
+                const decodedCode = decodeURIComponent(atob(hash));
+                model = { ...init, code: decodedCode };
+            } catch (error) {
+                console.error('Failed to decode the URL hash:', error);
+                model = init;
+            }
+        } else {
+            model = init;
+        }
+
+        render(model);
+
+        sandbox.onload = () => {
+            dispatch({ type: 'SANDBOX_READY' });
+        };
+
+        sandbox.srcdoc = sandboxSrc;
+
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/html/playground/little-regex.html b/html/playground/little-regex.html
new file mode 100644
index 0000000..c09139f
--- /dev/null
+++ b/html/playground/little-regex.html
@@ -0,0 +1,728 @@
+<html lang="en"><head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>JavaScript Playground</title>
+    <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles.">
+    <style>
+        body {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            background-color: #ddd;
+            padding: 10px;
+            height: 100vh;
+            margin: 0;
+        }
+
+        textarea {
+            width: 100%;
+            height: 64%;
+            font-family: monospace;
+            background-color: #FFFEEC;
+            border: 2px solid #000;
+            scrollbar-width: none;            
+            font-size: 1rem;
+            margin-bottom: 10px;
+            padding: 10px;
+            box-sizing: border-box;
+            resize: none;
+            border-bottom: 12px solid teal;
+            -webkit-user-modify: read-write-plaintext-only;
+        }
+
+        textarea::-webkit-scrollbar {
+            display: none;
+        }
+
+        textarea::selection {
+            background-color: #EFECA7;
+        }
+
+        textarea:focus {
+            outline: none;
+        }
+
+        #console {
+            width: 100%;
+            height: 22%;
+            background-color: #000;
+            color: #0fc;
+            font-family: monospace;
+            font-size: 1rem;
+            overflow-y: auto;
+            padding: 10px;
+            box-sizing: border-box;
+            white-space: pre-wrap;
+        }
+
+        .button-container {
+            width: 100%;
+            display: flex;
+            justify-content: flex-end;
+            margin-bottom: 10px;
+        }
+
+        button {
+            padding: 10px 20px;
+            font-size: 1rem;
+            margin-left: 10px;
+            cursor: pointer;
+            border: none;
+            transition: background-color 0.3s ease;
+        }
+
+        button:hover, button:focus {
+            outline: none; 
+        }
+
+        button.run {
+            background-color: teal;
+            color: #FFFFFF;
+        }
+    </style>
+</head>
+<body>
+
+    <div class="playground" id="playground">    
+        <div class="seesaw" id="seesaw"></div>
+        <div class="slide" id="slide"></div>
+    </div>
+
+    <div class="button-container">
+        <button onclick="clearEverything()">Clear</button>
+        <button onclick="downloadCodeAndEditor()">Download</button>
+        <button onclick="shareCode()">Share</button>
+        <button onclick="evaluateCode()" class="run">Run Code</button>
+    </div>
+    <textarea id="codeInput">// Inspired by <https://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html>
+const matchHere = (pattern, text) => {
+    if (pattern.length === 0) return true;
+    
+    // If pattern ends with $, match end of string
+    if (pattern[0] === '
+    <div id="console"></div>
+
+    <script>
+        function evaluateCode() {
+            const code = document.getElementById('codeInput').value;
+            const consoleDiv = document.getElementById('console');
+            consoleDiv.innerHTML = '';
+
+            // Custom console.log function to output to the console div
+            const originalConsoleLog = console.log;
+            console.log = function(...args) {
+                args.forEach(arg => {
+                    const output = document.createElement('div');
+                    output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg;
+                    consoleDiv.appendChild(output);
+                });
+                originalConsoleLog.apply(console, args);
+            };
+
+            try {
+                eval(code);
+            } catch (error) {
+                const errorOutput = document.createElement('div');
+                errorOutput.textContent = error;
+                errorOutput.style.color = 'red';
+                consoleDiv.appendChild(errorOutput);
+            }
+
+            // Restore browser's console.log
+            console.log = originalConsoleLog;
+        }
+
+        function downloadCodeAndEditor() {
+            const codeInput = document.getElementById('codeInput').value;
+            const htmlContent = document.documentElement.outerHTML.replace(
+                /<textarea id="codeInput"[^>]*>.*<\/textarea>/,
+                `<textarea id="codeInput">${codeInput}</textarea>`
+            );
+
+            const blob = new Blob([htmlContent], { type: 'text/html' });
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = 'code_editor.html';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+        }
+
+        function shareCode() {
+            const code = document.getElementById('codeInput').value;
+            const encodedCode = btoa(encodeURIComponent(code));
+            window.location.hash = encodedCode;
+            window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href);
+        }
+
+        function clearEverything() {
+            if (!confirm('Are you sure you want to reset the playground?')) {
+                return;
+            } else {               
+                window.location.hash = '';
+                window.location.reload();
+            }
+        }
+
+        function loadCodeFromHash() {
+            const hash = window.location.hash.substring(1);
+            if (hash) {
+                try {
+                    const decodedCode = decodeURIComponent(atob(hash));
+                    document.getElementById('codeInput').value = decodedCode;
+                } catch (error) {
+                    console.error('Failed to decode the URL hash:', error);
+                }
+            }
+        }
+
+        function help() {
+            const helpText = `
+            Welcome to the JavaScript Playground! Here are some tips to get you started:
+
+            1. Enter your JavaScript code in the textarea.
+            2. Click the "Run Code" button to execute your code.
+            3. The console output will be displayed below the textarea.
+            4. Click the "Clear" button to reset the playground.
+            5. Click the "Download" button to save your code and editor as an HTML file.
+            6. Click the "Share" button to generate a URL to share your code with others.
+            7. You can also press "Cmd + Enter" to run your code.
+            8. There's an empty div above the buttons with the id "playground"
+            9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()"
+            10. You can use the "clear()" function to clear the content's of the console.
+
+            Go nuts! Share scrappy fiddles!
+            `;
+            console.log(helpText);
+        }
+
+        function clear() {
+            document.getElementById('console').innerHTML = '';
+        }
+
+        function mountHelp() {
+            console.log(`
+            The mount function is used to mount stuff to the playground div.
+            It takes a function as an argument, which in turn receives the playground div as an argument.
+            Before mounting, it clears the playground div.
+            Here's an example of how to use the mount function:
+
+            mount(playground => {
+                const h1 = document.createElement('h1');
+                h1.textContent = 'Hell is empty and all the devils are here.';
+                playground.appendChild(h1);
+            });
+
+            This will add an h1 element to the playground div.
+            `);
+        }
+
+        function mount(mountFunction) {
+            const playground = document.getElementById('playground');
+            if (!playground) {
+                console.error("Couldn't find a div with the id 'playground'! You may need to reload the page.");
+                return;
+            }
+
+            if (playground.innerHTML.trim() !== "") {
+                playground.innerHTML = "";
+            }
+            mountFunction(playground);
+        }
+
+
+        document.getElementById('codeInput').addEventListener('keydown', function(event) {
+            if (event.metaKey && event.key === 'Enter') {
+                event.preventDefault();
+                evaluateCode();
+            }
+        });
+
+        window.onload = loadCodeFromHash;
+    </script>
+
+
+</body></html> && pattern.length === 1) return text.length === 0;
+
+    // If next char is '*', match zero or more occurrences of prev char
+    if (pattern[1] === '*') return matchStar(pattern[0], pattern.slice(2), text);
+
+    // Match . or literal character
+    if (text.length !== 0 && (pattern[0] === '.' || pattern[0] === text[0])) {
+        return matchHere(pattern.slice(1), text.slice(1));
+    }
+
+    return false;
+};
+
+const matchStar = (prevChar, pattern, text) => {
+    // Try matching zero occurrences first
+    if (matchHere(pattern, text)) return true;
+
+    // Then, match one or more occurrences of prevChar
+    while (text.length > 0 && (text[0] === prevChar || prevChar === '.')) {
+        text = text.slice(1);
+        if (matchHere(pattern, text)) return true;
+    }
+
+    return false;
+};
+
+const match = (pattern, text) => {
+    // Handle ^ anchor at the beginning
+    if (pattern[0] === '^') {
+        return matchHere(pattern.slice(1), text);
+    }
+
+    // Otherwise, check the entire string
+    for (let i = 0; i <= text.length; i++) {
+        if (matchHere(pattern, text.slice(i))) return true;
+    }
+
+    return false;
+};
+
+const assertEqual = (actual, expected, testName) => 
+    console.log(actual === expected ? `Passed: ${testName}` : `Failed: ${testName}. Expected ${expected} but got ${actual}`);
+
+assertEqual(match("ab*c", "ac"), true, "Star match 'ab*c' vs 'ac' (zero occurrences)");
+assertEqual(match("ab*c", "abbbc"), true, "Star match 'ab*c' vs 'abbbc' (multiple occurrences)");
+assertEqual(match("^ab.*c$", "abc"), true, "Complex match '^ab.*c
+    <div id="console"></div>
+
+    <script>
+        function evaluateCode() {
+            const code = document.getElementById('codeInput').value;
+            const consoleDiv = document.getElementById('console');
+            consoleDiv.innerHTML = '';
+
+            // Custom console.log function to output to the console div
+            const originalConsoleLog = console.log;
+            console.log = function(...args) {
+                args.forEach(arg => {
+                    const output = document.createElement('div');
+                    output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg;
+                    consoleDiv.appendChild(output);
+                });
+                originalConsoleLog.apply(console, args);
+            };
+
+            try {
+                eval(code);
+            } catch (error) {
+                const errorOutput = document.createElement('div');
+                errorOutput.textContent = error;
+                errorOutput.style.color = 'red';
+                consoleDiv.appendChild(errorOutput);
+            }
+
+            // Restore browser's console.log
+            console.log = originalConsoleLog;
+        }
+
+        function downloadCodeAndEditor() {
+            const codeInput = document.getElementById('codeInput').value;
+            const htmlContent = document.documentElement.outerHTML.replace(
+                /<textarea id="codeInput"[^>]*>.*<\/textarea>/,
+                `<textarea id="codeInput">${codeInput}</textarea>`
+            );
+
+            const blob = new Blob([htmlContent], { type: 'text/html' });
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = 'code_editor.html';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+        }
+
+        function shareCode() {
+            const code = document.getElementById('codeInput').value;
+            const encodedCode = btoa(encodeURIComponent(code));
+            window.location.hash = encodedCode;
+            window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href);
+        }
+
+        function clearEverything() {
+            if (!confirm('Are you sure you want to reset the playground?')) {
+                return;
+            } else {               
+                window.location.hash = '';
+                window.location.reload();
+            }
+        }
+
+        function loadCodeFromHash() {
+            const hash = window.location.hash.substring(1);
+            if (hash) {
+                try {
+                    const decodedCode = decodeURIComponent(atob(hash));
+                    document.getElementById('codeInput').value = decodedCode;
+                } catch (error) {
+                    console.error('Failed to decode the URL hash:', error);
+                }
+            }
+        }
+
+        function help() {
+            const helpText = `
+            Welcome to the JavaScript Playground! Here are some tips to get you started:
+
+            1. Enter your JavaScript code in the textarea.
+            2. Click the "Run Code" button to execute your code.
+            3. The console output will be displayed below the textarea.
+            4. Click the "Clear" button to reset the playground.
+            5. Click the "Download" button to save your code and editor as an HTML file.
+            6. Click the "Share" button to generate a URL to share your code with others.
+            7. You can also press "Cmd + Enter" to run your code.
+            8. There's an empty div above the buttons with the id "playground"
+            9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()"
+            10. You can use the "clear()" function to clear the content's of the console.
+
+            Go nuts! Share scrappy fiddles!
+            `;
+            console.log(helpText);
+        }
+
+        function clear() {
+            document.getElementById('console').innerHTML = '';
+        }
+
+        function mountHelp() {
+            console.log(`
+            The mount function is used to mount stuff to the playground div.
+            It takes a function as an argument, which in turn receives the playground div as an argument.
+            Before mounting, it clears the playground div.
+            Here's an example of how to use the mount function:
+
+            mount(playground => {
+                const h1 = document.createElement('h1');
+                h1.textContent = 'Hell is empty and all the devils are here.';
+                playground.appendChild(h1);
+            });
+
+            This will add an h1 element to the playground div.
+            `);
+        }
+
+        function mount(mountFunction) {
+            const playground = document.getElementById('playground');
+            if (!playground) {
+                console.error("Couldn't find a div with the id 'playground'! You may need to reload the page.");
+                return;
+            }
+
+            if (playground.innerHTML.trim() !== "") {
+                playground.innerHTML = "";
+            }
+            mountFunction(playground);
+        }
+
+
+        document.getElementById('codeInput').addEventListener('keydown', function(event) {
+            if (event.metaKey && event.key === 'Enter') {
+                event.preventDefault();
+                evaluateCode();
+            }
+        });
+
+        window.onload = loadCodeFromHash;
+    </script>
+
+
+</body></html> vs 'abc'");
+assertEqual(match("^ab.*c$", "abcd"), false, "Complex mismatch '^ab.*c
+    <div id="console"></div>
+
+    <script>
+        function evaluateCode() {
+            const code = document.getElementById('codeInput').value;
+            const consoleDiv = document.getElementById('console');
+            consoleDiv.innerHTML = '';
+
+            // Custom console.log function to output to the console div
+            const originalConsoleLog = console.log;
+            console.log = function(...args) {
+                args.forEach(arg => {
+                    const output = document.createElement('div');
+                    output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg;
+                    consoleDiv.appendChild(output);
+                });
+                originalConsoleLog.apply(console, args);
+            };
+
+            try {
+                eval(code);
+            } catch (error) {
+                const errorOutput = document.createElement('div');
+                errorOutput.textContent = error;
+                errorOutput.style.color = 'red';
+                consoleDiv.appendChild(errorOutput);
+            }
+
+            // Restore browser's console.log
+            console.log = originalConsoleLog;
+        }
+
+        function downloadCodeAndEditor() {
+            const codeInput = document.getElementById('codeInput').value;
+            const htmlContent = document.documentElement.outerHTML.replace(
+                /<textarea id="codeInput"[^>]*>.*<\/textarea>/,
+                `<textarea id="codeInput">${codeInput}</textarea>`
+            );
+
+            const blob = new Blob([htmlContent], { type: 'text/html' });
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = 'code_editor.html';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+        }
+
+        function shareCode() {
+            const code = document.getElementById('codeInput').value;
+            const encodedCode = btoa(encodeURIComponent(code));
+            window.location.hash = encodedCode;
+            window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href);
+        }
+
+        function clearEverything() {
+            if (!confirm('Are you sure you want to reset the playground?')) {
+                return;
+            } else {               
+                window.location.hash = '';
+                window.location.reload();
+            }
+        }
+
+        function loadCodeFromHash() {
+            const hash = window.location.hash.substring(1);
+            if (hash) {
+                try {
+                    const decodedCode = decodeURIComponent(atob(hash));
+                    document.getElementById('codeInput').value = decodedCode;
+                } catch (error) {
+                    console.error('Failed to decode the URL hash:', error);
+                }
+            }
+        }
+
+        function help() {
+            const helpText = `
+            Welcome to the JavaScript Playground! Here are some tips to get you started:
+
+            1. Enter your JavaScript code in the textarea.
+            2. Click the "Run Code" button to execute your code.
+            3. The console output will be displayed below the textarea.
+            4. Click the "Clear" button to reset the playground.
+            5. Click the "Download" button to save your code and editor as an HTML file.
+            6. Click the "Share" button to generate a URL to share your code with others.
+            7. You can also press "Cmd + Enter" to run your code.
+            8. There's an empty div above the buttons with the id "playground"
+            9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()"
+            10. You can use the "clear()" function to clear the content's of the console.
+
+            Go nuts! Share scrappy fiddles!
+            `;
+            console.log(helpText);
+        }
+
+        function clear() {
+            document.getElementById('console').innerHTML = '';
+        }
+
+        function mountHelp() {
+            console.log(`
+            The mount function is used to mount stuff to the playground div.
+            It takes a function as an argument, which in turn receives the playground div as an argument.
+            Before mounting, it clears the playground div.
+            Here's an example of how to use the mount function:
+
+            mount(playground => {
+                const h1 = document.createElement('h1');
+                h1.textContent = 'Hell is empty and all the devils are here.';
+                playground.appendChild(h1);
+            });
+
+            This will add an h1 element to the playground div.
+            `);
+        }
+
+        function mount(mountFunction) {
+            const playground = document.getElementById('playground');
+            if (!playground) {
+                console.error("Couldn't find a div with the id 'playground'! You may need to reload the page.");
+                return;
+            }
+
+            if (playground.innerHTML.trim() !== "") {
+                playground.innerHTML = "";
+            }
+            mountFunction(playground);
+        }
+
+
+        document.getElementById('codeInput').addEventListener('keydown', function(event) {
+            if (event.metaKey && event.key === 'Enter') {
+                event.preventDefault();
+                evaluateCode();
+            }
+        });
+
+        window.onload = loadCodeFromHash;
+    </script>
+
+
+</body></html> vs 'abcd'");</textarea>
+    <div id="console"></div>
+
+    <script>
+        function evaluateCode() {
+            const code = document.getElementById('codeInput').value;
+            const consoleDiv = document.getElementById('console');
+            consoleDiv.innerHTML = '';
+
+            // Custom console.log function to output to the console div
+            const originalConsoleLog = console.log;
+            console.log = function(...args) {
+                args.forEach(arg => {
+                    const output = document.createElement('div');
+                    output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg;
+                    consoleDiv.appendChild(output);
+                });
+                originalConsoleLog.apply(console, args);
+            };
+
+            try {
+                eval(code);
+            } catch (error) {
+                const errorOutput = document.createElement('div');
+                errorOutput.textContent = error;
+                errorOutput.style.color = 'red';
+                consoleDiv.appendChild(errorOutput);
+            }
+
+            // Restore browser's console.log
+            console.log = originalConsoleLog;
+        }
+
+        function downloadCodeAndEditor() {
+            const codeInput = document.getElementById('codeInput').value;
+            const htmlContent = document.documentElement.outerHTML.replace(
+                /<textarea id="codeInput"[^>]*>.*<\/textarea>/,
+                `<textarea id="codeInput">${codeInput}</textarea>`
+            );
+
+            const blob = new Blob([htmlContent], { type: 'text/html' });
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = 'code_editor.html';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+        }
+
+        function shareCode() {
+            const code = document.getElementById('codeInput').value;
+            const encodedCode = btoa(encodeURIComponent(code));
+            window.location.hash = encodedCode;
+            window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href);
+        }
+
+        function clearEverything() {
+            if (!confirm('Are you sure you want to reset the playground?')) {
+                return;
+            } else {               
+                window.location.hash = '';
+                window.location.reload();
+            }
+        }
+
+        function loadCodeFromHash() {
+            const hash = window.location.hash.substring(1);
+            if (hash) {
+                try {
+                    const decodedCode = decodeURIComponent(atob(hash));
+                    document.getElementById('codeInput').value = decodedCode;
+                } catch (error) {
+                    console.error('Failed to decode the URL hash:', error);
+                }
+            }
+        }
+
+        function help() {
+            const helpText = `
+            Welcome to the JavaScript Playground! Here are some tips to get you started:
+
+            1. Enter your JavaScript code in the textarea.
+            2. Click the "Run Code" button to execute your code.
+            3. The console output will be displayed below the textarea.
+            4. Click the "Clear" button to reset the playground.
+            5. Click the "Download" button to save your code and editor as an HTML file.
+            6. Click the "Share" button to generate a URL to share your code with others.
+            7. You can also press "Cmd + Enter" to run your code.
+            8. There's an empty div above the buttons with the id "playground"
+            9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()"
+            10. You can use the "clear()" function to clear the content's of the console.
+
+            Go nuts! Share scrappy fiddles!
+            `;
+            console.log(helpText);
+        }
+
+        function clear() {
+            document.getElementById('console').innerHTML = '';
+        }
+
+        function mountHelp() {
+            console.log(`
+            The mount function is used to mount stuff to the playground div.
+            It takes a function as an argument, which in turn receives the playground div as an argument.
+            Before mounting, it clears the playground div.
+            Here's an example of how to use the mount function:
+
+            mount(playground => {
+                const h1 = document.createElement('h1');
+                h1.textContent = 'Hell is empty and all the devils are here.';
+                playground.appendChild(h1);
+            });
+
+            This will add an h1 element to the playground div.
+            `);
+        }
+
+        function mount(mountFunction) {
+            const playground = document.getElementById('playground');
+            if (!playground) {
+                console.error("Couldn't find a div with the id 'playground'! You may need to reload the page.");
+                return;
+            }
+
+            if (playground.innerHTML.trim() !== "") {
+                playground.innerHTML = "";
+            }
+            mountFunction(playground);
+        }
+
+
+        document.getElementById('codeInput').addEventListener('keydown', function(event) {
+            if (event.metaKey && event.key === 'Enter') {
+                event.preventDefault();
+                evaluateCode();
+            }
+        });
+
+        window.onload = loadCodeFromHash;
+    </script>
+
+
+</body></html>
\ No newline at end of file
diff --git a/html/playground/regex.html b/html/playground/regex.html
new file mode 100644
index 0000000..41f50e9
--- /dev/null
+++ b/html/playground/regex.html
@@ -0,0 +1,477 @@
+<html lang="en"><head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>JavaScript Playground</title>
+    <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles.">
+    <style>
+        body {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            background-color: #ddd;
+            padding: 10px;
+            height: 100vh;
+            margin: 0;
+        }
+
+        textarea {
+            width: 100%;
+            height: 64%;
+            font-family: monospace;
+            background-color: #FFFEEC;
+            border: 2px solid #000;
+            scrollbar-width: none;            
+            font-size: 1rem;
+            margin-bottom: 10px;
+            padding: 10px;
+            box-sizing: border-box;
+            resize: none;
+            border-bottom: 12px solid teal;
+            -webkit-user-modify: read-write-plaintext-only;
+        }
+
+        textarea::-webkit-scrollbar {
+            display: none;
+        }
+
+        textarea::selection {
+            background-color: #EFECA7;
+        }
+
+        textarea:focus {
+            outline: none;
+        }
+
+        #console {
+            width: 100%;
+            height: 22%;
+            background-color: #000;
+            color: #0fc;
+            font-family: monospace;
+            font-size: 1rem;
+            overflow-y: auto;
+            padding: 10px;
+            box-sizing: border-box;
+            white-space: pre-wrap;
+        }
+
+        .button-container {
+            width: 100%;
+            display: flex;
+            justify-content: flex-end;
+            margin-bottom: 10px;
+        }
+
+        button {
+            padding: 10px 20px;
+            font-size: 1rem;
+            margin-left: 10px;
+            cursor: pointer;
+            border: none;
+            transition: background-color 0.3s ease;
+        }
+
+        button:hover, button:focus {
+            outline: none; 
+        }
+
+        button.run {
+            background-color: teal;
+            color: #FFFFFF;
+        }
+    </style>
+</head>
+<body>
+
+    <div class="playground" id="playground">    
+        <div class="seesaw" id="seesaw"></div>
+        <div class="slide" id="slide"></div>
+    </div>
+
+    <div class="button-container">
+        <button onclick="clearEverything()">Clear</button>
+        <button onclick="downloadCodeAndEditor()">Download</button>
+        <button onclick="shareCode()">Share</button>
+        <button onclick="evaluateCode()" class="run">Run Code</button>
+    </div>
+    <textarea id="codeInput">const tokenize = (pattern) => {
+    const tokens = [];
+    let i = 0;
+
+    while (i < pattern.length) {
+        const char = pattern[i];
+
+        if (char === '.' || char === '*' || char === '(' || char === ')' || char === '|') {
+            tokens.push({
+                type: char,
+                value: char
+            });
+        } else if (char === '\\') { // Handle escaped characters
+            i++;
+            tokens.push({
+                type: 'literal',
+                value: pattern[i]
+            });
+        } else if (char === '[') { // Handle character classes
+            let charClass = '';
+            i++;
+            while (pattern[i] !== ']' && i < pattern.length) {
+                charClass += pattern[i];
+                i++;
+            }
+            tokens.push({
+                type: 'charClass',
+                value: charClass
+            });
+        } else {
+            tokens.push({
+                type: 'literal',
+                value: char
+            });
+        }
+        i++;
+    }
+
+    return tokens;
+};
+
+
+
+
+const parse = (tokens) => {
+    let i = 0;
+
+    const parseSequenceExpression = () => {
+        const node = {
+            type: 'sequence',
+            elements: []
+        };
+
+        while (i < tokens.length) {
+            const token = tokens[i];
+
+            if (token.type === 'literal') {
+                node.elements.push({
+                    type: 'literal',
+                    value: token.value
+                });
+            } else if (token.type === '*') {
+                const lastElement = node.elements.pop();
+                node.elements.push({
+                    type: 'star',
+                    element: lastElement
+                });
+            } else if (token.type === '.') {
+                node.elements.push({
+                    type: 'dot'
+                });
+            } else if (token.type === 'charClass') {
+                node.elements.push({
+                    type: 'charClass',
+                    value: token.value
+                });
+            } else if (token.type === '|') {
+                i++;
+                const right = parseSequenceExpression();
+                return {
+                    type: 'alternation',
+                    left: node,
+                    right
+                };
+            } else if (token.type === '(') {
+                i++;
+                node.elements.push(parseSequenceExpression());
+            } else if (token.type === ')') {
+                break; // End of a grouping
+            }
+
+            i++;
+        }
+
+        return node;
+    };
+
+    return parseSequenceExpression();
+};
+
+
+
+const evaluateMatch = (node) => (input) => {
+    if (node.type === 'literal') {
+        return input[0] === node.value ? input.slice(1) : null;
+    }
+    if (node.type === 'dot') {
+        return input.length > 0 ? input.slice(1) : null;
+    }
+    if (node.type === 'star') {
+        let remainder = input;
+        while (remainder !== null) {
+            const next = evaluateMatch(node.element)(remainder);
+            if (next === null) {
+                break;
+            }
+            remainder = next;
+        }
+        return remainder;
+    }
+    if (node.type === 'charClass') {
+        return node.value.includes(input[0]) ? input.slice(1) : null;
+    }
+    if (node.type === 'alternation') {
+        const remainderLeft = evaluateMatch(node.left)(input);
+        const remainderRight = evaluateMatch(node.right)(input);
+        return remainderLeft !== null ? remainderLeft : remainderRight;
+    }
+    if (node.type === 'sequence') {
+        let remainder = input;
+        for (const element of node.elements) {
+            remainder = evaluateMatch(element)(remainder);
+            if (remainder === null) {
+                return null;
+            }
+        }
+        return remainder;
+    }
+};
+
+
+
+
+const assertEqual = (expected, actual, message) => {
+    if (expected !== actual) {
+        console.error(`FAIL: ${message}`);
+    } else {
+        console.log(`PASS: ${message}`);
+    }
+};
+
+
+
+const runTests = () => {
+    const tests = [{
+            pattern: "a.b*c",
+            input: "abbbc",
+            expected: true
+        },
+        {
+            pattern: "a.b*c",
+            input: "abc",
+            expected: true
+        },
+        {
+            pattern: "a.b*c",
+            input: "ac",
+            expected: true
+        },
+        {
+            pattern: "a.b*c",
+            input: "abbc",
+            expected: true
+        },
+        {
+            pattern: "a.b*c",
+            input: "axbc",
+            expected: false
+        },
+
+        // Character class tests
+        {
+            pattern: "[abc]x",
+            input: "bx",
+            expected: true
+        },
+        {
+            pattern: "[abc]x",
+            input: "dx",
+            expected: false
+        },
+
+        // Grouping and alternation tests
+        {
+            pattern: "(a|b)c",
+            input: "ac",
+            expected: true
+        },
+        {
+            pattern: "(a|b)c",
+            input: "bc",
+            expected: true
+        },
+        {
+            pattern: "(a|b)c",
+            input: "cc",
+            expected: false
+        },
+
+        // Escaped characters tests
+        {
+            pattern: "a\\.b",
+            input: "a.b",
+            expected: true
+        },
+        {
+            pattern: "a\\.b",
+            input: "a-b",
+            expected: false
+        },
+        {
+            pattern: "a\\*b",
+            input: "a*b",
+            expected: true
+        }
+    ];
+
+    tests.forEach(({ pattern, input, expected }, index) => {
+        const tokens = tokenize(pattern);
+        const ast = parse(tokens);
+        const result = evaluateMatch(ast)(input) !== null;
+        assertEqual(expected, result, `Test ${index + 1}`);
+    });
+};
+
+runTests();</textarea>
+    <div id="console"><div>PASS: Test 1</div><div>PASS: Test 2</div><div>PASS: Test 4</div><div>PASS: Test 6</div><div>PASS: Test 7</div><div>PASS: Test 8</div><div>PASS: Test 9</div><div>PASS: Test 10</div><div>PASS: Test 11</div><div>PASS: Test 12</div><div>PASS: Test 13</div></div>
+
+    <script>
+        function evaluateCode() {
+            const code = document.getElementById('codeInput').value;
+            const consoleDiv = document.getElementById('console');
+            consoleDiv.innerHTML = '';
+
+            // Custom console.log function to output to the console div
+            const originalConsoleLog = console.log;
+            console.log = function(...args) {
+                args.forEach(arg => {
+                    const output = document.createElement('div');
+                    output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg;
+                    consoleDiv.appendChild(output);
+                });
+                originalConsoleLog.apply(console, args);
+            };
+
+            try {
+                eval(code);
+            } catch (error) {
+                const errorOutput = document.createElement('div');
+                errorOutput.textContent = error;
+                errorOutput.style.color = 'red';
+                consoleDiv.appendChild(errorOutput);
+            }
+
+            // Restore browser's console.log
+            console.log = originalConsoleLog;
+        }
+
+        function downloadCodeAndEditor() {
+            const codeInput = document.getElementById('codeInput').value;
+            const htmlContent = document.documentElement.outerHTML.replace(
+                /<textarea id="codeInput"[^>]*>.*<\/textarea>/,
+                `<textarea id="codeInput">${codeInput}</textarea>`
+            );
+
+            const blob = new Blob([htmlContent], { type: 'text/html' });
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = 'code_editor.html';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+        }
+
+        function shareCode() {
+            const code = document.getElementById('codeInput').value;
+            const encodedCode = btoa(encodeURIComponent(code));
+            window.location.hash = encodedCode;
+            window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href);
+        }
+
+        function clearEverything() {
+            if (!confirm('Are you sure you want to reset the playground?')) {
+                return;
+            } else {               
+                window.location.hash = '';
+                window.location.reload();
+            }
+        }
+
+        function loadCodeFromHash() {
+            const hash = window.location.hash.substring(1);
+            if (hash) {
+                try {
+                    const decodedCode = decodeURIComponent(atob(hash));
+                    document.getElementById('codeInput').value = decodedCode;
+                } catch (error) {
+                    console.error('Failed to decode the URL hash:', error);
+                }
+            }
+        }
+
+        function help() {
+            const helpText = `
+            Welcome to the JavaScript Playground! Here are some tips to get you started:
+
+            1. Enter your JavaScript code in the textarea.
+            2. Click the "Run Code" button to execute your code.
+            3. The console output will be displayed below the textarea.
+            4. Click the "Clear" button to reset the playground.
+            5. Click the "Download" button to save your code and editor as an HTML file.
+            6. Click the "Share" button to generate a URL to share your code with others.
+            7. You can also press "Cmd + Enter" to run your code.
+            8. There's an empty div above the buttons with the id "playground"
+            9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()"
+            10. You can use the "clear()" function to clear the content's of the console.
+
+            Go nuts! Share scrappy fiddles!
+            `;
+            console.log(helpText);
+        }
+
+        function clear() {
+            document.getElementById('console').innerHTML = '';
+        }
+
+        function mountHelp() {
+            console.log(`
+            The mount function is used to mount stuff to the playground div.
+            It takes a function as an argument, which in turn receives the playground div as an argument.
+            Before mounting, it clears the playground div.
+            Here's an example of how to use the mount function:
+
+            mount(playground => {
+                const h1 = document.createElement('h1');
+                h1.textContent = 'Hell is empty and all the devils are here.';
+                playground.appendChild(h1);
+            });
+
+            This will add an h1 element to the playground div.
+            `);
+        }
+
+        function mount(mountFunction) {
+            const playground = document.getElementById('playground');
+            if (!playground) {
+                console.error("Couldn't find a div with the id 'playground'! You may need to reload the page.");
+                return;
+            }
+
+            if (playground.innerHTML.trim() !== "") {
+                playground.innerHTML = "";
+            }
+            mountFunction(playground);
+        }
+
+
+        document.getElementById('codeInput').addEventListener('keydown', function(event) {
+            if (event.metaKey && event.key === 'Enter') {
+                event.preventDefault();
+                evaluateCode();
+            }
+        });
+
+        window.onload = loadCodeFromHash;
+    </script>
+
+
+</body></html>
\ No newline at end of file
diff --git a/html/playground/scheme.html b/html/playground/scheme.html
new file mode 100644
index 0000000..b8ecd6f
--- /dev/null
+++ b/html/playground/scheme.html
@@ -0,0 +1,533 @@
+<html lang="en"><head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>JavaScript Playground</title>
+    <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles.">
+    <style>
+        body {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            background-color: #ddd;
+            padding: 10px;
+            height: 100vh;
+            margin: 0;
+        }
+
+        textarea {
+            width: 100%;
+            height: 64%;
+            font-family: monospace;
+            background-color: #FFFEEC;
+            border: 2px solid #000;
+            scrollbar-width: none;            
+            font-size: 1rem;
+            margin-bottom: 10px;
+            padding: 10px;
+            box-sizing: border-box;
+            resize: none;
+            border-bottom: 12px solid teal;
+            -webkit-user-modify: read-write-plaintext-only;
+        }
+
+        textarea::-webkit-scrollbar {
+            display: none;
+        }
+
+        textarea::selection {
+            background-color: #EFECA7;
+        }
+
+        textarea:focus {
+            outline: none;
+        }
+
+        #console {
+            width: 100%;
+            height: 22%;
+            background-color: #000;
+            color: #0fc;
+            font-family: monospace;
+            font-size: 1rem;
+            overflow-y: auto;
+            padding: 10px;
+            box-sizing: border-box;
+            white-space: pre-wrap;
+        }
+
+        .button-container {
+            width: 100%;
+            display: flex;
+            justify-content: flex-end;
+            margin-bottom: 10px;
+        }
+
+        button {
+            padding: 10px 20px;
+            font-size: 1rem;
+            margin-left: 10px;
+            cursor: pointer;
+            border: none;
+            transition: background-color 0.3s ease;
+        }
+
+        button:hover, button:focus {
+            outline: none; 
+        }
+
+        button.run {
+            background-color: teal;
+            color: #FFFFFF;
+        }
+    </style>
+</head>
+<body>
+
+    <div class="playground" id="playground">    
+        <div class="seesaw" id="seesaw"></div>
+        <div class="slide" id="slide"></div>
+    </div>
+
+    <div class="button-container">
+        <button onclick="clearEverything()">Clear</button>
+        <button onclick="downloadCodeAndEditor()">Download</button>
+        <button onclick="shareCode()">Share</button>
+        <button onclick="evaluateCode()" class="run">Run Code</button>
+    </div>
+    <textarea id="codeInput">function tokenizeScheme(input) {
+    const tokens = [];
+    let current = 0;
+
+    const isWhitespace = (char) => /\s/.test(char);
+    const isDigit = (char) => /[0-9]/.test(char);
+    const isParen = (char) => char === '(' || char === ')';
+    // Symbols can include letters, numbers, and some punctuation like - _ ! ?
+    const isSymbolChar = (char) => /[a-zA-Z0-9\+\-\*\/\=\?\!\_]/.test(char);
+
+    while (current < input.length) {
+        let char = input[current];
+
+        if (isWhitespace(char)) {
+            current++;
+            continue;
+        }
+
+        if (isParen(char)) {
+            tokens.push({ type: 'paren', value: char });
+            current++;
+            continue;
+        }
+
+        if (isDigit(char) || (char === '-' && isDigit(input[current + 1]))) {
+            let number = '';
+            while (isDigit(char) || char === '-') {
+                number += char;
+                char = input[++current];
+            }
+            tokens.push({ type: 'number', value: number });
+            continue;
+        }
+
+        // Handle symbols, including letters, numbers, punctuation
+        if (isSymbolChar(char)) {
+            let symbol = '';
+            while (isSymbolChar(char)) {
+                symbol += char;
+                char = input[++current];
+            }
+            tokens.push({ type: 'symbol', value: symbol });
+            continue;
+        }
+
+        throw new Error(`Unexpected character: ${char}`);
+    }
+
+    return tokens;
+}
+
+
+function parseScheme(tokens) {
+    let current = 0;
+
+    function walk() {
+        let token = tokens[current];
+
+        if (token.type === 'number') {
+            current++;
+            return { type: 'NumberLiteral', value: Number(token.value) };
+        }
+
+        if (token.type === 'symbol') {
+            current++;
+            return { type: 'Symbol', value: token.value };
+        }
+
+        if (token.type === 'paren' && token.value === '(') {
+            current++;
+            const node = { type: 'List', value: [] };
+
+            while (!(tokens[current].type === 'paren' && tokens[current].value === ')')) {
+                node.value.push(walk());
+            }
+
+            current++; // Skip closing ')'
+            return node;
+        }
+
+        throw new Error(`Unexpected token: ${token.type}`);
+    }
+
+    return walk();
+}
+
+const globalEnv = {
+    '+': (...args) => args.reduce((acc, curr) => acc + curr),
+    '-': (...args) => args.reduce((acc, curr) => acc - curr),
+    '*': (...args) => args.reduce((acc, curr) => acc * curr),
+    '/': (a, b) => a / b, // Only two arguments for division
+    'eq?': (...args) => args.every((val, i, arr) => val === arr[0]),
+    'car': (list) => {
+        if (list.type !== 'List' || list.value.length === 0) {
+            throw new Error('car expects a non-empty list');
+        }
+        return list.value[0];
+    },
+    'cdr': (list) => {
+        if (list.type !== 'List' || list.value.length === 0) {
+            throw new Error('cdr expects a non-empty list');
+        }
+        return { type: 'List', value: list.value.slice(1) };
+    },
+    'cons': (a, b) => {
+        if (b.type !== 'List') {
+            throw new Error('cons expects second argument to be a list');
+        }
+        return { type: 'List', value: [a].concat(b.value) };
+    },
+    'null?': (list) => list.type === 'List' && list.value.length === 0,
+    'zero?': (n) => n === 0,
+    'atom?': (x) => typeof x !== 'object' || x === null,
+    'number?': (x) => typeof x === 'number',
+    'add1': (n) => n + 1,
+    'sub1': (n) => n - 1,
+    'quote': (x) => x,  // Simply return the quoted expression
+    'and': (...args) => args.every(Boolean),
+    'or': (...args) => args.some(Boolean),
+    'true': true,
+    'false': false
+};
+
+
+
+
+function evaluate(node, env = globalEnv) {
+    if (node.type === 'NumberLiteral') {
+        return node.value;
+    }
+
+    if (node.type === 'Symbol') {
+        if (env[node.value] !== undefined) {
+            return env[node.value];
+        }
+        throw new Error(`Undefined symbol: ${node.value}`);
+    }
+
+    if (node.type === 'List') {
+        const [first, ...rest] = node.value;
+
+        // Is the first element a symbol, like an operator or function name?
+        if (first.type === 'Symbol') {
+            const operator = first.value;
+
+            // Special case for define
+            if (operator === 'define') {
+                const [symbol, expr] = rest;
+                env[symbol.value] = evaluate(expr, env);
+                return;
+            }
+
+            // Special case for lambda
+            if (operator === 'lambda') {
+                const [params, body] = rest;
+
+                // Create a closure to return
+                return function (...args) {
+                    const lambdaEnv = { ...env };
+
+                    // Bind each argument to the corresponding parameter...
+                    params.value.forEach((param, i) => {
+                        lambdaEnv[param.value] = args[i];
+                    });
+
+                    // ...and then evaluate the body with the environment
+                    return evaluate(body, lambdaEnv);
+                };
+            }
+
+            // Special case for if
+            if (operator === 'if') {
+                const [test, consequent, alternate] = rest;
+                const condition = evaluate(test, env);
+                return condition ? evaluate(consequent, env) : evaluate(alternate, env);
+            }
+
+            // Special case for quote
+            if (operator === 'quote') {
+                return rest[0];  // Return the quoted expression without evaluating it
+            }
+
+            // Special case for cond
+            if (operator === 'cond') {
+                for (let clause of rest) {
+                    const [test, expr] = clause.value;
+                    if (evaluate(test, env)) {
+                        return evaluate(expr, env);
+                    }
+                }
+                return null; // No matching condition
+            }
+
+            // Special case for letrec (recursive let)
+            if (operator === 'letrec') {
+                const [bindings, body] = rest;
+                const letEnv = { ...env };
+
+                // Loop through bindings and evaluate each
+                bindings.value.forEach(binding => {
+                    const [name, expr] = binding.value;
+                    letEnv[name.value] = evaluate(expr, letEnv);
+                });
+
+                return evaluate(body, letEnv);
+            }
+        }
+
+        // Evaluate the first element
+        const func = evaluate(first, env);
+
+        if (typeof func !== 'function') {
+            throw new Error(`Expected a function but got: ${func}`);
+        }
+
+        const args = rest.map(arg => evaluate(arg, env));
+        return func(...args);
+    }
+
+    throw new Error(`Unexpected node type: ${node.type}`);
+}
+
+
+
+function evalScheme(input) {
+    const tokens = tokenizeScheme(input);
+    const ast = parseScheme(tokens);
+    return evaluate(ast);
+}
+
+
+
+
+
+
+
+function mountRepl(playground) {
+    // Create a REPL container
+    const replContainer = document.createElement('div');
+    replContainer.style.display = 'flex';
+    replContainer.style.flexDirection = 'column';
+    replContainer.style.width = '100%';
+
+    // Create an input field for the Scheme expressions
+    const input = document.createElement('textarea');
+    input.placeholder = "Scheme here...";
+    input.style.width = '100%';
+    input.style.height = '100px';
+    input.style.marginBottom = '10px';
+    input.style.fontFamily = 'monospace';
+
+    // Create a button to evaluate the expression
+    const evalButton = document.createElement('button');
+    evalButton.textContent = 'Evaluate';
+
+    // Create a container to display the results
+    const output = document.createElement('pre');
+    output.style.width = '100%';
+    output.style.height = '200px';
+    output.style.overflowY = 'auto';
+    output.style.backgroundColor = '#f0f0f0';
+    output.style.padding = '10px';
+    output.style.fontFamily = 'monospace';
+
+    // Add the input, button, and output to the REPL container
+    replContainer.appendChild(input);
+    replContainer.appendChild(evalButton);
+    replContainer.appendChild(output);
+
+    // Add the REPL container to the playground div
+    playground.appendChild(replContainer);
+
+    evalButton.addEventListener('click', () => {
+        const expression = input.value.trim();
+        if (expression) {
+            try {
+                // Evaluate the expression
+                const result = evalScheme(expression);
+                // Append the result to the output area
+                output.textContent += `> ${expression}\n${result}\n\n`;
+            } catch (error) {
+                // Error if the expression is invalid
+                output.textContent += `> ${expression}\nError: ${error.message}\n\n`;
+            }
+        }
+        // Clear input after evaluation
+        input.value = '';
+    });
+}
+
+
+mount(mountRepl);</textarea>
+    <div id="console"></div>
+
+    <script>
+        function evaluateCode() {
+            const code = document.getElementById('codeInput').value;
+            const consoleDiv = document.getElementById('console');
+            consoleDiv.innerHTML = '';
+
+            // Custom console.log function to output to the console div
+            const originalConsoleLog = console.log;
+            console.log = function(...args) {
+                args.forEach(arg => {
+                    const output = document.createElement('div');
+                    output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg;
+                    consoleDiv.appendChild(output);
+                });
+                originalConsoleLog.apply(console, args);
+            };
+
+            try {
+                eval(code);
+            } catch (error) {
+                const errorOutput = document.createElement('div');
+                errorOutput.textContent = error;
+                errorOutput.style.color = 'red';
+                consoleDiv.appendChild(errorOutput);
+            }
+
+            // Restore browser's console.log
+            console.log = originalConsoleLog;
+        }
+
+        function downloadCodeAndEditor() {
+            const codeInput = document.getElementById('codeInput').value;
+            const htmlContent = document.documentElement.outerHTML.replace(
+                /<textarea id="codeInput"[^>]*>.*<\/textarea>/,
+                `<textarea id="codeInput">${codeInput}</textarea>`
+            );
+
+            const blob = new Blob([htmlContent], { type: 'text/html' });
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = 'code_editor.html';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+        }
+
+        function shareCode() {
+            const code = document.getElementById('codeInput').value;
+            const encodedCode = btoa(encodeURIComponent(code));
+            window.location.hash = encodedCode;
+            window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href);
+        }
+
+        function clearEverything() {
+            if (!confirm('Are you sure you want to reset the playground?')) {
+                return;
+            } else {               
+                window.location.hash = '';
+                window.location.reload();
+            }
+        }
+
+        function loadCodeFromHash() {
+            const hash = window.location.hash.substring(1);
+            if (hash) {
+                try {
+                    const decodedCode = decodeURIComponent(atob(hash));
+                    document.getElementById('codeInput').value = decodedCode;
+                } catch (error) {
+                    console.error('Failed to decode the URL hash:', error);
+                }
+            }
+        }
+
+        function help() {
+            const helpText = `
+            Welcome to the JavaScript Playground! Here are some tips to get you started:
+
+            1. Enter your JavaScript code in the textarea.
+            2. Click the "Run Code" button to execute your code.
+            3. The console output will be displayed below the textarea.
+            4. Click the "Clear" button to reset the playground.
+            5. Click the "Download" button to save your code and editor as an HTML file.
+            6. Click the "Share" button to generate a URL to share your code with others.
+            7. You can also press "Cmd + Enter" to run your code.
+            8. There's an empty div above the buttons with the id "playground"
+            9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()"
+            10. You can use the "clear()" function to clear the content's of the console.
+
+            Go nuts! Share scrappy fiddles!
+            `;
+            console.log(helpText);
+        }
+
+        function clear() {
+            document.getElementById('console').innerHTML = '';
+        }
+
+        function mountHelp() {
+            console.log(`
+            The mount function is used to mount stuff to the playground div.
+            It takes a function as an argument, which in turn receives the playground div as an argument.
+            Before mounting, it clears the playground div.
+            Here's an example of how to use the mount function:
+
+            mount(playground => {
+                const h1 = document.createElement('h1');
+                h1.textContent = 'Hell is empty and all the devils are here.';
+                playground.appendChild(h1);
+            });
+
+            This will add an h1 element to the playground div.
+            `);
+        }
+
+        function mount(mountFunction) {
+            const playground = document.getElementById('playground');
+            if (!playground) {
+                console.error("Couldn't find a div with the id 'playground'! You may need to reload the page.");
+                return;
+            }
+
+            if (playground.innerHTML.trim() !== "") {
+                playground.innerHTML = "";
+            }
+            mountFunction(playground);
+        }
+
+
+        document.getElementById('codeInput').addEventListener('keydown', function(event) {
+            if (event.metaKey && event.key === 'Enter') {
+                event.preventDefault();
+                evaluateCode();
+            }
+        });
+
+        window.onload = loadCodeFromHash;
+    </script>
+
+
+</body></html>
\ No newline at end of file