• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
01Critter Models 02
└ Locust└ Butterfly└ Cockroach└ Spider└ Scorpion└ Dragon└ Brazilian Treehopper└ Tardigrade└ Diatoms└ Bumblebee└ Wasp└ Hammerhead Worm└ Giraffe Weevil└ Trilobite Beetle
02VantaJS Shaders
└ Clouds└ Waves└ Cells└ Rings└ Halo└ Globe└ Net└ Dots└ Birds└ Trunk└ Topology└ About VantaJS
03App Bundle 02
└ Gutindex└ Insect Builder└ OpenLayers
04Cathedrals
└ Lattice└ Lost└ Lucent└ Lumen└ Glass└ Disco└ Hyperion└ Latent
05App Bundle 01
└ Robohash└ PrismJS└ Horoscope└ Binary Jazz└ Morse Code└ URL Extractor└ Entity Escaper└ Phonetic Alphabet└ Toki Pona└ Semaphore└ Cut Up Machine└ Text Mixer└ Currency Freaks└ Host IO└ CrUX API└ Logo Dev└ Web Speech API└ GEO DB
06Retroscopic
└ Galaxy└ Ocean└ Slime└ 24-Cell└ Unicorn└ Polyp└ Oneiric 1└ Oneiric 2└ Node└ Torus Knot└ GLSL Grid└ Liquid Crystal└ Silky└ Butterfly└ Ocean 2└ Icosahedron└ Luma└ Swarm└ Broken Solid└ Hypersphere└ Constraints└ Psycho Killer
01 / 03

CUT UP-MACHINE - INTERACTIVE APP SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>The Cut Up Machine</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Special+Elite&family=DM+Mono:ital,wght@0,300;0,400;1,300&display=swap" rel="stylesheet">
    <style>
        :root {
            color-scheme: light dark;
            --color-text-primary: #e8e6e3;
            --color-text-secondary: #b0ada8;
            --color-text-tertiary: #7a7672;
            --color-background-primary: #121212;
            --color-background-secondary: #1e1e1e;
            --color-border-primary: #e8e6e3;
            --color-border-secondary: #3a3a3a;
            --color-border-tertiary: #2a2a2a;
            --color-border-danger: #e05555;
            --border-radius-md: 8px;
        }
        
        html:not(.dark) {
            --color-text-primary: #121212;
            --color-text-secondary: #4a4a4a;
            --color-text-tertiary: #7a7672;
            --color-background-primary: #f5f5f5;
            --color-background-secondary: #ffffff;
            --color-border-primary: #121212;
            --color-border-secondary: #cccccc;
            --color-border-tertiary: #e0e0e0;
        }

        *,
        *::before,
        *::after {
            box-sizing: border-box;
        }

        html,
        body {
            margin: 0;
            padding: 0;
            background: transparent;
            min-height: 100vh;
        }

        .sr-only {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0, 0, 0, 0);
            white-space: nowrap;
            border: 0;
        }

        .cut-wrap {
            font-family: 'DM Mono', monospace;
            width: 100%;
        }

        .cut-title {
            font-family: 'Special Elite', cursive;
            font-size: 28px;
            letter-spacing: 0.04em;
            color: var(--color-text-primary);
            margin: 0 0 0.25rem;
        }

        .cut-sub {
            font-size: 12px;
            color: var(--color-text-tertiary);
            letter-spacing: 0.1em;
            text-transform: uppercase;
            margin: 0 0 2rem;
        }

        .cut-label {
            font-size: 11px;
            letter-spacing: 0.12em;
            text-transform: uppercase;
            color: var(--color-text-tertiary);
            margin-bottom: 6px;
        }

        textarea {
            width: 100%;
            box-sizing: border-box;
            min-height: 140px;
            font-family: 'DM Mono', monospace;
            font-size: 14px;
            line-height: 1.6;
            padding: 12px 14px;
            border: 0.5px solid var(--color-border-secondary);
            border-radius: var(--border-radius-md);
            background: var(--color-background-secondary);
            color: var(--color-text-primary);
            resize: vertical;
            outline: none;
            transition: border-color 0.15s;
        }

        textarea:focus {
            border-color: var(--color-border-primary);
        }

        textarea::placeholder {
            color: var(--color-text-tertiary);
        }

        .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 12px;
            align-items: center;
            margin: 1rem 0;
        }

        .mode-group {
            display: flex;
            gap: 6px;
            flex-wrap: wrap;
        }

        .mode-btn {
            font-family: 'DM Mono', monospace;
            font-size: 11px;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            padding: 5px 12px;
            border: 0.5px solid var(--color-border-secondary);
            border-radius: 99px;
            background: transparent;
            color: var(--color-text-secondary);
            cursor: pointer;
            transition: all 0.15s;
        }

        .mode-btn:hover {
            background: var(--color-background-secondary);
            color: var(--color-text-primary);
        }

        .mode-btn.active {
            background: var(--color-text-primary);
            color: var(--color-background-primary);
            border-color: var(--color-text-primary);
        }

        .cut-btn {
            font-family: 'Special Elite', cursive;
            font-size: 15px;
            letter-spacing: 0.06em;
            padding: 8px 28px;
            background: var(--color-text-primary);
            color: var(--color-background-primary);
            border: none;
            border-radius: var(--border-radius-md);
            cursor: pointer;
            transition: opacity 0.15s, transform 0.1s;
            margin-left: auto;
        }

        .cut-btn:hover {
            opacity: 0.85;
        }

        .cut-btn:active {
            transform: scale(0.97);
        }

        .output-area {
            position: relative;
            margin-top: 1.5rem;
            min-height: 60px;
        }

        .output-box {
            font-family: 'Special Elite', cursive;
            font-size: 15px;
            line-height: 1.85;
            letter-spacing: 0.02em;
            padding: 18px 20px;
            border: 0.5px solid var(--color-border-tertiary);
            border-radius: var(--border-radius-md);
            background: var(--color-background-secondary);
            color: var(--color-text-primary);
            white-space: pre-wrap;
            word-break: break-word;
            min-height: 80px;
            transition: opacity 0.25s;
        }

        .output-box.empty {
            color: var(--color-text-tertiary);
            font-family: 'DM Mono', monospace;
            font-size: 13px;
            font-style: italic;
        }

        .output-actions {
            display: flex;
            gap: 8px;
            margin-top: 10px;
            justify-content: flex-end;
        }

        .action-btn {
            font-family: 'DM Mono', monospace;
            font-size: 11px;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            padding: 5px 14px;
            border: 0.5px solid var(--color-border-secondary);
            border-radius: 99px;
            background: transparent;
            color: var(--color-text-secondary);
            cursor: pointer;
            transition: all 0.15s;
            display: flex;
            align-items: center;
            gap: 5px;
        }

        .action-btn:hover {
            background: var(--color-background-secondary);
            color: var(--color-text-primary);
        }

        .intensity-row {
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 11px;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--color-text-tertiary);
        }

        .intensity-row input[type=range] {
            width: 90px;
            accent-color: var(--color-text-primary);
        }

        .intensity-val {
            font-weight: 500;
            color: var(--color-text-secondary);
            min-width: 24px;
        }

        .scissor-anim {
            display: inline-block;
            transition: transform 0.3s;
        }

        .cutting .scissor-anim {
            animation: snip 0.35s ease;
        }

        @keyframes snip {
            0% {
                transform: rotate(0deg) scale(1);
            }
            40% {
                transform: rotate(-20deg) scale(1.15);
            }
            70% {
                transform: rotate(10deg) scale(0.9);
            }
            100% {
                transform: rotate(0deg) scale(1);
            }
        }

        .tag-cloud {
            display: flex;
            flex-wrap: wrap;
            gap: 6px;
            margin-top: 12px;
        }

        .word-tag {
            font-family: 'DM Mono', monospace;
            font-size: 12px;
            padding: 3px 10px;
            border: 0.5px solid var(--color-border-tertiary);
            border-radius: 99px;
            background: var(--color-background-primary);
            color: var(--color-text-secondary);
            cursor: pointer;
            transition: all 0.15s;
            user-select: none;
        }

        .word-tag:hover {
            background: var(--color-background-secondary);
            color: var(--color-text-primary);
            border-color: var(--color-border-secondary);
        }

        .word-tag.selected {
            background: var(--color-text-primary);
            color: var(--color-background-primary);
            border-color: var(--color-text-primary);
        }

        .divider {
            border: none;
            border-top: 0.5px solid var(--color-border-tertiary);
            margin: 1.5rem 0;
        }

        .history-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
        }

        .history-label {
            font-size: 11px;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            color: var(--color-text-tertiary);
        }

        .history-clear {
            font-size: 11px;
            color: var(--color-text-tertiary);
            cursor: pointer;
            background: none;
            border: none;
            font-family: 'DM Mono', monospace;
            letter-spacing: 0.06em;
            padding: 0;
        }

        .history-clear:hover {
            color: var(--color-text-secondary);
        }

        .history-item {
            font-family: 'Special Elite', cursive;
            font-size: 13px;
            color: var(--color-text-secondary);
            line-height: 1.6;
            padding: 8px 12px;
            border-left: 2px solid var(--color-border-tertiary);
            margin-bottom: 8px;
            cursor: pointer;
            transition: border-color 0.15s, color 0.15s;
        }

        .history-item:hover {
            border-color: var(--color-border-primary);
            color: var(--color-text-primary);
        }

        /* Custom white-on-black scrollbar styling */
        html.dark {
            scrollbar-color: #b0ada8 #1a1a1a;
            scrollbar-width: thin;
        }

        html.dark ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }

        html.dark ::-webkit-scrollbar-track {
            background: #1a1a1a;
            border-radius: 4px;
        }

        html.dark ::-webkit-scrollbar-thumb {
            background: #b0ada8;
            border-radius: 4px;
        }

        html.dark ::-webkit-scrollbar-thumb:hover {
            background: #e8e6e3;
        }

        html.dark ::-webkit-scrollbar-corner {
            background: #1a1a1a;
        }
    </style>
</head>
<body>
    <div class="cut-wrap">
        <h2 class="sr-only">Cut-Up Machine — randomise and rearrange text in the style of Burroughs and Tzara</h2>
        <p class="cut-sub">Burroughs · Gysin · Tzara — chance operations on language</p>

        <div class="cut-label">Your text</div>
        <textarea id="inputText" placeholder="Paste or type any text here — a poem, a paragraph, a letter, a dream…"></textarea>

        <div class="controls">
            <div>
                <div class="cut-label" style="margin-bottom:4px;">Mode</div>
                <div class="mode-group">
                    <button class="mode-btn active" data-mode="words">Words</button>
                    <button class="mode-btn" data-mode="phrases">Phrases</button>
                    <button class="mode-btn" data-mode="lines">Lines</button>
                    <button class="mode-btn" data-mode="fold">Fold-in</button>
                </div>
            </div>
            <div class="intensity-row" style="margin-top:18px;">
                <span>Chaos</span>
                <input type="range" id="intensity" min="0" max="100" value="70" step="1">
                <span class="intensity-val" id="intensityVal">70</span>
            </div>
            <button class="cut-btn" id="cutBtn" onclick="doCutUp()">Cut Up ✂</button>
        </div>

        <div class="output-area">
            <div class="cut-label">Result</div>
            <div class="output-box empty" id="outputBox">Your cut-up will appear here…</div>
            <div class="output-actions" id="outputActions" style="display:none;">
                <button class="action-btn" onclick="copyOutput()"><i class="ti ti-copy" aria-hidden="true"></i> Copy</button>
                <button class="action-btn" onclick="doCutUp()"><i class="ti ti-refresh" aria-hidden="true"></i> Re-cut</button>
                <button class="action-btn" onclick="useAsInput()"><i class="ti ti-arrow-down" aria-hidden="true"></i> Use as input</button>
            </div>
        </div>

        <div id="wordPickSection" style="display:none;">
            <hr class="divider">
            <div class="cut-label">Pick your fragments <span style="font-weight:400;text-transform:none;letter-spacing:0;font-size:12px;color:var(--color-text-tertiary);">(click to select, then re-cut)</span></div>
            <div class="tag-cloud" id="tagCloud"></div>
            <div style="margin-top:10px;display:flex;gap:8px;">
                <button class="action-btn" onclick="selectAll()">Select all</button>
                <button class="action-btn" onclick="selectNone()">Clear</button>
                <button class="action-btn" onclick="cutFromSelected()">Cut selected ✂</button>
            </div>
        </div>

        <div id="historySection" style="display:none;">
            <hr class="divider">
            <div class="history-row">
                <span class="history-label">History</span>
                <button class="history-clear" onclick="clearHistory()">Clear</button>
            </div>
            <div id="historyList"></div>
        </div>
    </div>

    <script>
        (function() {
            function syncTheme() {
                try {
                    if (window.parent && window.parent.document) {
                        const isDark = window.parent.document.documentElement.classList.contains('dark');
                        if (isDark) {
                            document.documentElement.classList.add('dark');
                        } else {
                            document.documentElement.classList.remove('dark');
                        }
                    }
                } catch (e) {}
            }
            syncTheme();
            try {
                if (window.parent && window.parent.document) {
                    const observer = new MutationObserver(syncTheme);
                    observer.observe(window.parent.document.documentElement, { attributes: true, attributeFilter: ['class'] });
                }
            } catch (e) {}
        })();

        let mode = 'words';
        let history = [];
        let fragments = [];

        document.querySelectorAll('.mode-btn').forEach(b => {
            b.addEventListener('click', () => {
                document.querySelectorAll('.mode-btn').forEach(x => x.classList.remove('active'));
                b.classList.add('active');
                mode = b.dataset.mode;
                if (mode === 'fold') {
                    document.getElementById('wordPickSection').style.display = 'none';
                }
            });
        });

        document.getElementById('intensity').addEventListener('input', function() {
            document.getElementById('intensityVal').textContent = this.value;
        });

        function shuffle(arr, pct) {
            let a = arr.slice();
            let swaps = Math.floor((pct / 100) * a.length * 2);
            for (let i = 0; i < swaps; i++) {
                let x = Math.floor(Math.random() * a.length);
                let y = Math.floor(Math.random() * a.length);
                [a[x], a[y]] = [a[y], a[x]];
            }
            return a;
        }

        function getFragments(text, m) {
            if (m === 'words') {
                return text.split(/\s+/).filter(w => w.length > 0);
            } else if (m === 'phrases') {
                return text.split(/[,;.!?—]+/).map(s => s.trim()).filter(s => s.length > 0);
            } else if (m === 'lines') {
                return text.split(/\n+/).map(s => s.trim()).filter(s => s.length > 0);
            } else if (m === 'fold') {
                return null;
            }
        }

        function foldIn(text) {
            let lines = text.split(/\n/).filter(l => l.trim().length > 0);
            if (lines.length < 2) {
                let words = text.split(/\s+/).filter(w => w.length > 0);
                let half = Math.ceil(words.length / 2);
                let a = words.slice(0, half);
                let b = words.slice(half);
                let result = [];
                for (let i = 0; i < Math.max(a.length, b.length); i++) {
                    if (a[i]) result.push(a[i]);
                    if (b[i]) result.push(b[i]);
                }
                return result.join(' ');
            }
            let half = Math.ceil(lines.length / 2);
            let a = lines.slice(0, half);
            let b = lines.slice(half);
            let result = [];
            for (let i = 0; i < Math.max(a.length, b.length); i++) {
                let pa = a[i] ? a[i].trim().split(/\s+/) : [];
                let pb = b[i] ? b[i].trim().split(/\s+/) : [];
                let merged = [];
                for (let j = 0; j < Math.max(pa.length, pb.length); j++) {
                    if (pa[j]) merged.push(pa[j]);
                    if (pb[j]) merged.push(pb[j]);
                }
                result.push(merged.join(' '));
            }
            return result.join('\n');
        }

        function doCutUp() {
            const text = document.getElementById('inputText').value.trim();
            if (!text) {
                flashEmpty();
                return;
            }
            const pct = parseInt(document.getElementById('intensity').value);

            let result = '';
            if (mode === 'fold') {
                result = foldIn(text);
            } else {
                fragments = getFragments(text, mode);
                if (!fragments || fragments.length === 0) {
                    flashEmpty();
                    return;
                }
                let shuffled = shuffle(fragments, pct);
                if (mode === 'words') {
                    let lines = [];
                    let lineLen = 4 + Math.floor(Math.random() * 5);
                    for (let i = 0; i < shuffled.length; i += lineLen) {
                        lines.push(shuffled.slice(i, i + lineLen).join(' '));
                        lineLen = 4 + Math.floor(Math.random() * 5);
                    }
                    result = lines.join('\n');
                } else {
                    result = shuffled.join(mode === 'phrases' ? ', ' : '\n');
                }
                buildTagCloud(fragments);
            }

            animateScissors();
            showResult(result);
            addToHistory(result);
        }

        function animateScissors() {
            const btn = document.getElementById('cutBtn');
            btn.classList.add('cutting');
            document.getElementById('scissorIcon').parentElement.classList.add('cutting');
            setTimeout(() => {
                btn.classList.remove('cutting');
                document.getElementById('scissorIcon').parentElement && document.getElementById(
                    'scissorIcon').parentElement.classList.remove('cutting');
            }, 400);
        }

        function showResult(text) {
            const box = document.getElementById('outputBox');
            box.style.opacity = '0';
            setTimeout(() => {
                box.textContent = text;
                box.classList.remove('empty');
                box.style.opacity = '1';
                document.getElementById('outputActions').style.display = 'flex';
            }, 180);
        }

        function flashEmpty() {
            const ta = document.getElementById('inputText');
            ta.style.borderColor = 'var(--color-border-danger)';
            setTimeout(() => ta.style.borderColor = '', 600);
        }

        function buildTagCloud(frags) {
            if (mode === 'fold') return;
            const cloud = document.getElementById('tagCloud');
            const section = document.getElementById('wordPickSection');
            cloud.innerHTML = '';
            let unique = [...new Set(frags)].slice(0, 60);
            unique.forEach(w => {
                const tag = document.createElement('span');
                tag.className = 'word-tag selected';
                tag.textContent = w;
                tag.addEventListener('click', () => tag.classList.toggle('selected'));
                cloud.appendChild(tag);
            });
            section.style.display = '';
        }

        function selectAll() {
            document.querySelectorAll('.word-tag').forEach(t => t.classList.add('selected'));
        }

        function selectNone() {
            document.querySelectorAll('.word-tag').forEach(t => t.classList.remove('selected'));
        }

        function cutFromSelected() {
            const selected = [...document.querySelectorAll('.word-tag.selected')].map(t => t.textContent);
            if (selected.length === 0) return;
            const pct = parseInt(document.getElementById('intensity').value);
            let shuffled = shuffle(selected, pct);
            let lines = [];
            let lineLen = 4 + Math.floor(Math.random() * 5);
            for (let i = 0; i < shuffled.length; i += lineLen) {
                lines.push(shuffled.slice(i, i + lineLen).join(' '));
                lineLen = 4 + Math.floor(Math.random() * 5);
            }
            const result = lines.join('\n');
            animateScissors();
            showResult(result);
            addToHistory(result);
        }

        function copyOutput() {
            const text = document.getElementById('outputBox').textContent;
            if (navigator.clipboard) navigator.clipboard.writeText(text);
        }

        function useAsInput() {
            const text = document.getElementById('outputBox').textContent;
            document.getElementById('inputText').value = text;
            fragments = getFragments(text, mode);
            if (fragments) buildTagCloud(fragments);
        }

        function addToHistory(text) {
            history.unshift(text.slice(0, 120) + (text.length > 120 ? '…' : ''));
            if (history.length > 5) history.pop();
            renderHistory();
        }

        function renderHistory() {
            const section = document.getElementById('historySection');
            const list = document.getElementById('historyList');
            if (history.length === 0) {
                section.style.display = 'none';
                return;
            }
            section.style.display = '';
            list.innerHTML = '';
            history.forEach((h, i) => {
                const div = document.createElement('div');
                div.className = 'history-item';
                div.textContent = h;
                div.title = 'Click to restore';
                div.addEventListener('click', () => {
                    document.getElementById('outputBox').textContent = history[i];
                    document.getElementById('outputBox').classList.remove('empty');
                    document.getElementById('outputActions').style.display = 'flex';
                });
                list.appendChild(div);
            });
        }

        function clearHistory() {
            history = [];
            renderHistory();
        }
    </script>
    <script>
        (function syncTheme() {
            try {
                if (window.parent && window.parent !== window) {
                    const parentHtml = window.parent.document.documentElement;
                    const localHtml = document.documentElement;
                    
                    const updateTheme = () => {
                        if (parentHtml.classList.contains('dark')) {
                            localHtml.classList.add('dark');
                        } else {
                            localHtml.classList.remove('dark');
                        }
                    };
                    
                    updateTheme();
                    
                    const observer = new MutationObserver((mutations) => {
                        mutations.forEach((mutation) => {
                            if (mutation.attributeName === 'class') {
                                updateTheme();
                            }
                        });
                    });
                    observer.observe(parentHtml, { attributes: true });
                    return;
                }
            } catch (e) {
                console.warn('Could not sync theme with parent window.', e);
            }
            
            if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
                document.documentElement.classList.add('dark');
            }
        })();
    </script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.