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>