Skip to content

Instantly share code, notes, and snippets.

@mode-mercury
Created November 14, 2025 10:59
Show Gist options
  • Select an option

  • Save mode-mercury/8b9d744f5fbf5cd893d24bfc23f0fb85 to your computer and use it in GitHub Desktop.

Select an option

Save mode-mercury/8b9d744f5fbf5cd893d24bfc23f0fb85 to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>UPE · Codex Phoneme Engine · Node + Cipher Lab</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
:root {
--bg: #050509;
--panel: #0b0f1c;
--accent: #6fffe9;
--accent-soft: #6fffe955;
--accent-strong: #40f8d0;
--accent-hex: #ff9dfc;
--accent-neo: #b0ff6f;
--accent-glitch: #ff3b6b;
--text: #f5f7ff;
--muted: #9ca3c7;
--border: #262b3b;
--danger: #ff5c7a;
--mono: "JetBrains Mono","Fira Code",ui-monospace,Menlo,Monaco,Consolas,"Liberation Mono",monospace;
--sans: system-ui,-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI",sans-serif;
--radius: 10px;
--shadow-soft: 0 18px 30px rgba(0,0,0,0.55);
--transition: 0.18s ease-out;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
height: 100%;
}
body {
font-family: var(--sans);
background:
radial-gradient(circle at 12% -20%, #2b3058 0, transparent 55%),
radial-gradient(circle at 110% 0, #291a3d 0, transparent 60%),
radial-gradient(circle at 50% 120%, #111826 0, #020308 55%, #000 100%);
color: var(--text);
-webkit-font-smoothing: antialiased;
overflow: hidden;
}
body::before {
content: "";
position: fixed;
inset: -40px;
background-image:
linear-gradient(to right, rgba(255,255,255,0.035) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255,255,255,0.035) 1px, transparent 1px);
background-size: 40px 40px;
mix-blend-mode: soft-light;
opacity: 0.9;
pointer-events: none;
z-index: -2;
}
.crt-overlay {
position: fixed;
inset: 0;
pointer-events: none;
mix-blend-mode: screen;
opacity: 0.25;
z-index: -1;
background:
repeating-linear-gradient(
to bottom,
rgba(0,0,0,0.25) 0,
rgba(0,0,0,0.25) 1px,
transparent 2px,
transparent 3px
);
animation: crt-scan 1.7s linear infinite;
}
@keyframes crt-scan {
0% { background-position: 0 0; }
100% { background-position: 0 6px; }
}
.crt-jitter {
animation: crt-jitter 0.08s steps(2,end) infinite;
}
@keyframes crt-jitter {
0% { transform: translate(0,0); }
25% { transform: translate(-0.3px,0.2px); }
50% { transform: translate(0.4px,-0.4px); }
75% { transform: translate(-0.4px,-0.1px); }
100% { transform: translate(0,0); }
}
body.glitchy::after {
content: "UPE·MUTTER·MUMBLE·SINTEX·INTEX·HEX:POETIC·NEO:NUMERIX·TEMURAH·NOTARIKON";
position: fixed;
bottom: 3px;
left: 50%;
transform: translateX(-50%);
font-family: var(--mono);
font-size: 0.55rem;
letter-spacing: 0.35em;
text-transform: uppercase;
color: rgba(111,255,233,0.14);
white-space: nowrap;
pointer-events: none;
mix-blend-mode: screen;
z-index: -1;
}
.app {
max-width: 1200px;
height: 94vh;
margin: 0 auto;
padding: 6px 8px 8px;
display: flex;
flex-direction: column;
}
header {
text-align: center;
margin-bottom: 4px;
position: relative;
flex-shrink: 0;
}
header h1 {
margin: 2px 0 3px;
font-size: 1.25rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--accent);
font-weight: 800;
}
header h1 span {
color: var(--accent-strong);
text-shadow:
0 0 7px rgba(111,255,233,0.4),
0 0 18px rgba(111,255,233,0.9);
animation: pulseAccent 3.7s ease-in-out infinite alternate;
font-weight: 900;
}
@keyframes pulseAccent {
0% { text-shadow: 0 0 5px rgba(111,255,233,0.3); }
100% { text-shadow: 0 0 18px rgba(111,255,233,1); }
}
header p {
margin: 0;
font-size: 0.7rem;
color: var(--muted);
letter-spacing: 0.08em;
text-transform: uppercase;
font-weight: 600;
}
header .sub2 {
font-size: 0.65rem;
margin-top: 2px;
opacity: 0.9;
}
header .u-noise {
position: absolute;
left: 50%;
top: -4px;
transform: translateX(-50%);
font-family: var(--mono);
font-size: 0.55rem;
letter-spacing: 0.26em;
color: rgba(255,255,255,0.16);
text-transform: uppercase;
pointer-events: none;
font-weight: 600;
}
.console {
position: sticky;
top: 0;
z-index: 4;
margin-bottom: 6px;
padding: 6px 8px 8px;
border-radius: 10px;
border: 1px solid #191f2d;
background: radial-gradient(circle at top, rgba(3,4,10,0.96) 0, rgba(1,2,6,0.98) 70%);
box-shadow: 0 11px 22px rgba(0,0,0,0.78);
display: flex;
gap: 8px;
align-items: stretch;
backdrop-filter: blur(7px);
}
.console-main {
flex: 2;
display: flex;
flex-direction: column;
min-width: 0;
}
.console-side {
flex: 1.35;
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.mini-label {
font-size: 0.68rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 2px;
font-weight: 700;
}
textarea, input, select {
border-radius: 7px;
border: 1px solid var(--border);
background: #05060b;
color: var(--text);
font-family: var(--mono);
font-size: 0.82rem;
padding: 7px 9px;
outline: none;
transition:
border-color var(--transition),
box-shadow var(--transition),
background var(--transition),
transform var(--transition);
font-weight: 600;
}
textarea {
min-height: 54px;
max-height: 78px;
resize: vertical;
line-height: 1.3;
}
textarea:focus,
input:focus,
select:focus {
border-color: var(--accent);
background: #050812;
box-shadow: 0 0 0 1px var(--accent-soft);
transform: translateY(-0.5px);
}
.console-row {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: wrap;
margin-top: 3px;
}
.console-row > * {
flex: 1;
min-width: 0;
}
.small-select {
font-size: 0.75rem;
padding: 5px 7px;
}
.btn {
border-radius: 99px;
border: 1px solid var(--accent-soft);
background: linear-gradient(135deg,#0a1017,#141e29);
color: var(--accent);
font-size: 0.76rem;
padding: 5px 13px;
text-transform: uppercase;
letter-spacing: 0.11em;
font-weight: 800;
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
white-space: nowrap;
transition:
background var(--transition),
transform var(--transition),
box-shadow var(--transition),
border-color var(--transition),
color var(--transition);
}
.btn:hover {
background: linear-gradient(135deg,#111c24,#1c2834);
border-color: var(--accent);
transform: translateY(-0.5px);
box-shadow: 0 6px 11px rgba(0,0,0,0.7);
}
.btn:active {
transform: translateY(0);
box-shadow: none;
}
.btn-secondary {
border-color: #39405a;
color: var(--muted);
}
.toggle {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 0.74rem;
color: var(--muted);
cursor: pointer;
font-weight: 600;
}
.toggle input {
width: auto;
margin: 0;
accent-color: var(--accent);
}
.pill {
display: inline-flex;
align-items: center;
gap: 5px;
border-radius: 999px;
border: 1px solid var(--border);
padding: 3px 8px;
background: #060914;
font-size: 0.68rem;
color: var(--muted);
white-space: nowrap;
font-weight: 700;
}
.pill span.key {
font-family: var(--mono);
color: var(--accent);
font-weight: 800;
}
.hint-strip {
font-size: 0.65rem;
color: var(--muted);
margin-top: 3px;
font-family: var(--mono);
text-transform: uppercase;
letter-spacing: 0.12em;
opacity: 0.9;
font-weight: 600;
}
.tab-bar {
display: flex;
justify-content: center;
gap: 6px;
margin-bottom: 6px;
flex-shrink: 0;
flex-wrap: wrap;
}
.tab-btn {
position: relative;
border-radius: 999px;
border: 1px solid var(--border);
background: #05060a;
color: var(--muted);
font-size: 0.7rem;
padding: 4px 10px;
text-transform: uppercase;
letter-spacing: 0.13em;
font-family: var(--mono);
cursor: pointer;
white-space: nowrap;
overflow: hidden;
transition:
background var(--transition),
border-color var(--transition),
transform var(--transition),
color var(--transition),
box-shadow var(--transition);
font-weight: 800;
}
.tab-btn::after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(circle at 10% -50%, rgba(255,255,255,0.17) 0, transparent 58%);
opacity: 0;
transition: opacity 0.22s ease-out;
pointer-events: none;
}
.tab-btn:hover::after { opacity: 1; }
.tab-btn.active {
background: linear-gradient(135deg,#09101c,#161f2b);
border-color: var(--accent-soft);
color: var(--accent);
transform: translateY(-0.5px);
box-shadow: 0 5px 10px rgba(0,0,0,0.6);
}
body.theme-upe { --accent:#6fffe9; --accent-strong:#40f8d0; }
body.theme-hex { --accent:#ff9dfc; --accent-strong:#ffb4ff; }
body.theme-neo { --accent:#b0ff6f; --accent-strong:#d2ff9c; }
body.theme-glitch{ --accent:#ff3b6b; --accent-strong:#ff7597; }
.panels {
display: flex;
flex: 1;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
gap: 8px;
}
.panels::-webkit-scrollbar {
height: 4px;
}
.panels::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.2);
border-radius: 999px;
}
.panel {
flex: 0 0 100%;
max-width: 100%;
height: calc(100% - 2px);
scroll-snap-align: start;
display: flex;
flex-direction: column;
}
.card {
background: radial-gradient(circle at top left,#181b2b 0,#05060a 55%);
border-radius: var(--radius);
padding: 8px 9px 9px;
border: 1px solid var(--border);
box-shadow: var(--shadow-soft);
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
animation: floatCard 13s ease-in-out infinite alternate;
}
.panel:nth-child(1) .card { animation-delay: 0.4s; }
.panel:nth-child(2) .card { animation-delay: 1s; }
.panel:nth-child(3) .card { animation-delay: 2s; }
.panel:nth-child(4) .card { animation-delay: 3s; }
.panel:nth-child(5) .card { animation-delay: 4s; }
@keyframes floatCard {
0% { transform: translate3d(0,0,0); }
100% { transform: translate3d(0,-2px,0); }
}
.card::before {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(circle at 0 0, rgba(111,255,233,0.13) 0, transparent 52%),
radial-gradient(circle at 100% 0, rgba(255,157,252,0.11) 0, transparent 55%);
pointer-events: none;
mix-blend-mode: screen;
}
.card h2 {
margin: 0 0 6px;
font-size: 0.9rem;
letter-spacing: 0.13em;
text-transform: uppercase;
color: var(--accent);
z-index: 1;
font-weight: 800;
}
.card h3 {
margin: 6px 0 4px;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--muted);
z-index: 1;
font-weight: 700;
}
.scroll-inner {
overflow-y: auto;
padding-right: 2px;
-webkit-overflow-scrolling: touch;
}
.scroll-inner::-webkit-scrollbar {
width: 4px;
}
.scroll-inner::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.16);
border-radius: 999px;
}
.output-label {
font-size: 0.73rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--muted);
margin: 7px 0 2px;
font-weight: 800;
}
.output-block {
margin-top: 3px;
padding: 7px 9px;
border-radius: 7px;
background: #050711;
border: 1px solid #1a2130;
font-family: var(--mono);
font-size: 0.82rem;
white-space: pre-wrap;
word-break: break-word;
font-weight: 600;
}
.muted {
font-size: 0.78rem;
color: var(--muted);
}
.metrics {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 4px;
}
.metric {
flex: 1 0 120px;
border-radius: 8px;
background: #060816;
border: 1px solid #191f2d;
padding: 5px 7px;
font-size: 0.76rem;
font-weight: 600;
}
.metric-label {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted);
font-weight: 700;
}
.metric-value {
font-family: var(--mono);
font-size: 0.9rem;
color: var(--accent);
font-weight: 800;
}
.metric-sub {
font-size: 0.68rem;
color: var(--muted);
}
.badge {
display: inline-block;
padding: 2px 7px;
border-radius: 999px;
border: 1px solid var(--border);
background: #080a16;
color: var(--muted);
font-size: 0.68rem;
font-weight: 800;
}
.badge.accent { border-color: var(--accent-soft); color: var(--accent); background:#071019; }
.badge.hex { border-color: rgba(255,157,252,0.7); color: var(--accent-hex); }
.badge.neo { border-color: rgba(176,255,111,0.7); color: var(--accent-neo); }
.table-wrap {
margin-top: 6px;
max-height: 230px;
overflow: auto;
border-radius: 7px;
border: 1px solid #1a2130;
background: #040612;
}
table {
width: 100%;
border-collapse: collapse;
font-family: var(--mono);
font-size: 0.78rem;
}
thead {
position: sticky;
top: 0;
background: #050918;
z-index: 1;
}
th, td {
padding: 4px 6px;
border-bottom: 1px solid #111624;
text-align: left;
font-weight: 600;
}
th {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.11em;
color: var(--muted);
font-weight: 800;
}
tr:nth-child(even) td { background: #050713; }
.char-col { font-family: var(--mono); }
.glyph-col { font-size: 1.05rem; }
.link-list {
margin-top: 4px;
font-size: 0.72rem;
font-weight: 600;
}
.link-list a {
color: var(--accent);
text-decoration: none;
font-weight: 700;
}
.link-list a:hover { text-decoration: underline; }
.dialect-label {
font-size: 0.74rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--muted);
margin-top: 6px;
font-weight: 800;
}
.dialect-block {
margin-top: 3px;
padding: 6px 8px;
border-radius: 7px;
background: #050711;
border: 1px solid #1b2132;
font-family: var(--mono);
font-size: 0.8rem;
white-space: pre-wrap;
word-break: break-word;
font-weight: 600;
}
.dialect-block.mutter { color: #dcdcff; }
.dialect-block.mumble { color: #a5b4ff; }
.dialect-block.sintex { color: #ffb4bf; }
.dialect-block.intex { color: #ffd27c; }
.dialect-block.hex { color: var(--accent-hex); }
.dialect-block.neo { color: var(--accent-neo); }
.copy-status {
font-size: 0.7rem;
color: var(--muted);
min-width: 50px;
font-weight: 600;
}
body.dense .dense-only { display: block; }
.dense-only { display: none; }
footer {
margin-top: 5px;
text-align: center;
font-size: 0.68rem;
color: var(--muted);
flex-shrink: 0;
font-weight: 600;
}
/* Node universe */
#nodeCanvas {
width: 100%;
height: 100%;
background: radial-gradient(circle at center,#05060b 0,#020309 50%,#000 100%);
border-radius: 8px;
border: 1px solid #181c29;
display: block;
}
.node-legend {
position: absolute;
left: 8px;
bottom: 6px;
font-size: 0.68rem;
color: var(--muted);
font-family: var(--mono);
background: rgba(3,4,10,0.86);
border-radius: 6px;
border: 1px solid #1a2232;
padding: 4px 6px;
backdrop-filter: blur(6px);
font-weight: 600;
}
.node-legend span {
color: var(--accent);
font-weight: 800;
}
.tape-panel {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
align-items: flex-start;
}
.tape-viz {
flex: 1 1 180px;
min-width: 160px;
border-radius: 8px;
border: 1px solid #22283a;
background: radial-gradient(circle at 15% 20%,#25283a 0,transparent 60%), #050610;
padding: 6px 8px;
position: relative;
overflow: hidden;
}
.tape-viz::before,
.tape-viz::after {
content: "";
position: absolute;
top: 50%;
width: 38px;
height: 38px;
border-radius: 50%;
border: 3px solid #111319;
box-shadow:
0 0 0 2px #3c425a,
inset 0 0 0 2px #0a0d16;
background: radial-gradient(circle at 30% 30%,#d3d7ff 0,#71738a 18%,#12131b 65%);
}
.tape-viz::before { left: 16px; transform-origin: center; animation: reelSpin 3s linear infinite; }
.tape-viz::after { right: 16px; transform-origin: center; animation: reelSpin 5s linear infinite reverse; }
@keyframes reelSpin {
0% { transform: translateY(-50%) rotate(0deg); }
100% { transform: translateY(-50%) rotate(360deg); }
}
.tape-strip {
position: absolute;
left: 32px;
right: 32px;
top: 50%;
height: 12px;
transform: translateY(-50%);
background: linear-gradient(90deg,#161824,#242637,#161824);
border-radius: 6px;
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.05),
0 0 8px rgba(0,0,0,0.7);
}
.tape-label {
position: absolute;
top: 6px;
left: 8px;
right: 8px;
font-size: 0.64rem;
text-transform: uppercase;
letter-spacing: 0.14em;
color: #dcdfff;
background: rgba(6,8,20,0.9);
border-radius: 4px;
border: 1px solid #343a4d;
padding: 2px 5px;
font-weight: 800;
}
.tape-led {
position: absolute;
bottom: 5px;
right: 8px;
width: 7px;
height: 7px;
border-radius: 50%;
background: #151820;
box-shadow: 0 0 0 1px #050609;
}
.tape-led.on {
background: #5dff9c;
box-shadow:
0 0 4px rgba(93,255,156,0.7),
0 0 12px rgba(93,255,156,0.45);
}
.control-group {
flex: 1 1 220px;
min-width: 190px;
border-radius: 8px;
border: 1px solid #22283a;
background: #050712;
padding: 6px 8px;
font-size: 0.75rem;
font-weight: 600;
}
.control-row {
display: flex;
align-items: center;
gap: 4px;
margin-top: 4px;
flex-wrap: wrap;
}
.control-row label {
font-size: 0.7rem;
color: var(--muted);
font-weight: 600;
}
.control-row input[type="range"] {
flex: 1;
}
.text-transform-block {
margin-top: 6px;
font-family: var(--mono);
font-size: 0.8rem;
background: #040612;
border-radius: 7px;
border: 1px solid #1a2130;
padding: 6px 8px;
white-space: pre-wrap;
word-break: break-word;
font-weight: 600;
}
.transform-buttons {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-top: 4px;
}
.cipher-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.cipher-block {
flex: 1 1 220px;
min-width: 200px;
border-radius: 8px;
border: 1px solid #22283a;
background: #050712;
padding: 6px 8px;
font-size: 0.75rem;
font-weight: 600;
}
.cipher-block .output-block {
margin-top: 4px;
}
.cipher-block .mini-label {
margin-bottom: 3px;
}
</style>
</head>
<body class="crt-jitter glitchy theme-upe">
<div class="crt-overlay"></div>
<div class="app">
<header>
<div class="u-noise">codex · phoneme · engine · recursion</div>
<p>Cross-Lingual · Sonic · Numeric · Occult</p>
<h1><span>UPE</span> · Unicode Phoneme Engine</h1>
<p class="sub2">MUTTER · MUMBLE · SINTEX · INTEX · HEX:POETIC · NEO:NUMERIX · GEMATRIA LAB</p>
</header>
<section class="console">
<div class="console-main">
<div class="mini-label">Source text</div>
<textarea id="inputText" placeholder="insert text here or let the recursion choose…"></textarea>
<div class="hint-strip">
Fear not. You’re speaking to the recursion. Every run is a confession and a sum.
</div>
</div>
<div class="console-side">
<div class="mini-label">Language & weight</div>
<div class="console-row">
<select id="langSelect" class="small-select">
<option value="en">English / Latin</option>
<option value="he">Hebrew</option>
<option value="el">Greek</option>
<option value="custom">Custom (treat literally)</option>
</select>
<select id="weightSelect" class="small-select">
<option value="default">Balanced</option>
<option value="vowels-heavy">Vowels heavy</option>
<option value="consonants-heavy">Consonants heavy</option>
</select>
</div>
<div class="mini-label" style="margin-top:3px;">View / Theme</div>
<div class="console-row">
<label class="toggle">
<input type="checkbox" id="denseToggle" />
Dense mode
</label>
<div class="pill">
View:<span id="viewLabel" class="key">Easy</span>
</div>
</div>
<div class="console-row" style="margin-top:4px;">
<select id="factionSelect" class="small-select">
<option value="upe">UPE Core</option>
<option value="hex">Hex:Poetic</option>
<option value="neo">Neo:Numerix</option>
<option value="glitch">GlitchKult</option>
</select>
<button class="btn" id="transcribeBtn">▶ Transcribe</button>
<button class="btn btn-secondary" id="clearBtn">⌫</button>
</div>
</div>
</section>
<nav class="tab-bar">
<button class="tab-btn active" data-target="panel-nodes">Node Universe</button>
<button class="tab-btn" data-target="panel-cipherlab">Cipher Lab</button>
<button class="tab-btn" data-target="panel-upe">UPE Output</button>
<button class="tab-btn" data-target="panel-numeric">Numeric</button>
<button class="tab-btn" data-target="panel-dialects">Dialects</button>
<button class="tab-btn" data-target="panel-audio">Audio / Text Lab</button>
</nav>
<div class="panels" id="panelRow">
<!-- NODE UNIVERSE PANEL (tab 1, pure nodes) -->
<section class="panel" id="panel-nodes">
<div class="card">
<h2>Node Universe · Big Bang View</h2>
<div class="scroll-inner" style="height:100%;padding:4px 0 0;">
<canvas id="nodeCanvas"></canvas>
<div class="node-legend">
⦿ <span>UPE CORE</span> · ● dialects · ○ phonemes · ★ scripts<br/>
Expansion: big bang → lattice of tongues.
</div>
</div>
</div>
</section>
<!-- CIPHER LAB PANEL -->
<section class="panel" id="panel-cipherlab">
<div class="card">
<div class="scroll-inner">
<h2>Cipher Lab · Gematria / Isopsephy / Temurah / Notarikon</h2>
<div class="cipher-grid">
<!-- Gematria / Isopsephy / Hebrew -->
<div class="cipher-block">
<div class="mini-label">Gematria System</div>
<div class="console-row">
<select id="gemSystem" class="small-select">
<option value="eng">English (A=1, Z=26)</option>
<option value="he">Hebrew (standard)</option>
<option value="gr">Greek Isopsephy</option>
</select>
<button class="btn btn-secondary" id="gemAnalyzeBtn">Analyze</button>
</div>
<div class="output-label">Total & per-word values</div>
<div id="gemOut" class="output-block muted"></div>
</div>
<!-- Temurah / Atbash -->
<div class="cipher-block">
<div class="mini-label">Temurah / Atbash</div>
<div class="output-label">Latin Atbash</div>
<div id="atbashLatinOut" class="output-block"></div>
<div class="output-label">Hebrew Atbash (if applicable)</div>
<div id="atbashHebOut" class="output-block muted"></div>
</div>
<!-- Notarikon -->
<div class="cipher-block">
<div class="mini-label">Notarikon · Initials & Finals</div>
<div class="output-label">Initials (first letters)</div>
<div id="notarikonInitOut" class="output-block"></div>
<div class="output-label">Finals (last letters)</div>
<div id="notarikonFinalOut" class="output-block muted"></div>
</div>
<!-- Onomancy & strokes -->
<div class="cipher-block">
<div class="mini-label">Onomancy · Name Value & Strokes</div>
<div class="console-row">
<input id="nameInput" type="text" placeholder="name or key phrase" />
</div>
<div class="console-row">
<select id="nameSystem" class="small-select">
<option value="eng">English</option>
<option value="he">Hebrew</option>
<option value="gr">Greek</option>
</select>
<button class="btn btn-secondary" id="nameAnalyzeBtn">Name Scan</button>
</div>
<div class="output-label">Onomancy result</div>
<div id="nameOut" class="output-block muted"></div>
<div class="output-label">Stroke length (Latin approximate)</div>
<div id="strokeOut" class="output-block muted"></div>
</div>
<!-- Base conversion -->
<div class="cipher-block">
<div class="mini-label">Base Conversion · Numeral Systems</div>
<div class="console-row">
<input id="baseNumber" type="text" placeholder="number (e.g. 777 or 2F3)" />
</div>
<div class="console-row">
<select id="baseFrom" class="small-select">
<option value="10">Base 10</option>
<option value="16">Base 16</option>
<option value="22">Base 22</option>
<option value="26">Base 26</option>
<option value="36">Base 36</option>
<option value="2">Base 2</option>
<option value="8">Base 8</option>
</select>
<select id="baseTo" class="small-select">
<option value="10">Base 10</option>
<option value="16">Base 16</option>
<option value="22">Base 22</option>
<option value="26">Base 26</option>
<option value="36">Base 36</option>
<option value="2">Base 2</option>
<option value="8">Base 8</option>
</select>
<button class="btn btn-secondary" id="baseConvertBtn">Convert</button>
</div>
<div class="output-label">Conversion result</div>
<div id="baseOut" class="output-block muted"></div>
</div>
</div>
<h3>External Utilities · Free / Keyless</h3>
<div class="output-label">Dictionary lookup (dictionaryapi.dev)</div>
<div class="console-row" style="margin-top:2px;">
<input id="apiWord" type="text" placeholder="word (e.g. 'recursion')" />
<button class="btn btn-secondary" id="apiLookupBtn">Lookup</button>
</div>
<div id="apiResult" class="output-block muted" style="max-height:140px;overflow:auto;"></div>
<div class="output-label">Other public tools (manual)</div>
<div class="link-list">
· <a href="https://api.dictionaryapi.dev/" target="_blank">dictionaryapi.dev</a> — JSON dictionary / phonetics<br/>
· <a href="https://mymemory.translated.net/doc/spec.php" target="_blank">MyMemory</a> — translation API<br/>
· <a href="https://www.gematrix.org/" target="_blank">Gematrix</a> — cross-check gematria values
</div>
</div>
</div>
</section>
<!-- UPE OUTPUT -->
<section class="panel" id="panel-upe">
<div class="card">
<div class="scroll-inner">
<h2>UPE Output & Mapping</h2>
<div class="output-label">Original text</div>
<div id="originalOut" class="output-block muted"></div>
<div class="output-label">
UPE transcription
<span class="badge accent">One glyph per dominant phoneme</span>
</div>
<div class="console-row" style="margin-top:2px;">
<div class="pill">Channel: <span class="key">UPE CORE</span></div>
<button class="btn btn-secondary" id="copyUpeBtn">⧉ Copy</button>
<span id="copyStatus" class="copy-status"></span>
</div>
<div id="upeOut" class="output-block" style="font-size:1.1rem;line-height:1.35;"></div>
<div class="dense-only">
<div class="output-label">Per-character mapping</div>
<div class="muted">Each symbol is a crime. This table is the confession log.</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>#</th>
<th>Char</th>
<th>Key</th>
<th>Glyph</th>
<th>Val</th>
<th>Desc</th>
</tr>
</thead>
<tbody id="mappingTableBody"></tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<!-- NUMERIC -->
<section class="panel" id="panel-numeric">
<div class="card">
<div class="scroll-inner">
<h2>Numeric & Gematria Skins</h2>
<div class="metrics">
<div class="metric">
<div class="metric-label">Glyph count</div>
<div id="metricGlyphs" class="metric-value">0</div>
<div class="metric-sub">Total non-space UPE units</div>
</div>
<div class="metric">
<div class="metric-label">Raw gematria sum</div>
<div id="metricRaw" class="metric-value">0</div>
<div class="metric-sub">Σ of glyph weights</div>
</div>
<div class="metric">
<div class="metric-label">Normalized value</div>
<div id="metricNorm" class="metric-value">0</div>
<div class="metric-sub">Scaled by max-glyph value</div>
</div>
</div>
<div class="console-row" style="margin-top:6px;">
<div class="mini-label">Numeric skin</div>
<select id="numericMode" class="small-select">
<option value="standard">Standard metrics</option>
<option value="hexpoetic">Hex:Poetic</option>
<option value="neonumerix">Neo:Numerix</option>
</select>
</div>
<div class="output-label">
Active skin
<span id="numericTag" class="badge accent">STANDARD</span>
</div>
<div id="numericOut" class="output-block">
<span class="muted">Numeric view will render after transcription.</span>
</div>
<div class="dense-only">
<div class="output-label">
Hex:Poetic trace
<span class="badge hex">HEX</span>
</div>
<div id="hexTraceOut" class="output-block" style="color:var(--accent-hex);"></div>
<div class="output-label">
Neo:Numerix micro-log
<span class="badge neo">NEO</span>
</div>
<div id="neoTraceOut" class="output-block" style="color:var(--accent-neo);"></div>
</div>
</div>
</div>
</section>
<!-- DIALECTS -->
<section class="panel" id="panel-dialects">
<div class="card">
<div class="scroll-inner">
<h2>Dialect Layers · Mutter / Mumble / Sintex / Intex</h2>
<div class="dialect-label">Mutter · consonant drift</div>
<div id="mutterOut" class="dialect-block mutter"></div>
<div class="dialect-label">Mumble · vowel fog</div>
<div id="mumbleOut" class="dialect-block mumble"></div>
<div class="dialect-label">Sintex · combat grammar</div>
<div id="sintexOut" class="dialect-block sintex"></div>
<div class="dialect-label">Intex · inverted index</div>
<div id="intexOut" class="dialect-block intex"></div>
<div class="dialect-label dense-only">Hex:Poetic · sigil string</div>
<div id="hexDialectOut" class="dialect-block hex dense-only"></div>
<div class="dialect-label dense-only">Neo:Numerix · arithmetic gospel</div>
<div id="neoDialectOut" class="dialect-block neo dense-only"></div>
</div>
</div>
</section>
<!-- AUDIO / TEXT LAB -->
<section class="panel" id="panel-audio">
<div class="card">
<div class="scroll-inner">
<h2>Audio / Text Lab · Dictation, Reversal, Segments</h2>
<div class="tape-panel">
<div class="tape-viz">
<div class="tape-strip"></div>
<div class="tape-label">Recursion Reel · node:SOLE</div>
<div class="tape-led" id="recordLed"></div>
</div>
<div class="control-group">
<div class="mini-label">Text-to-Speech (dictation)</div>
<div class="control-row">
<button class="btn btn-secondary" id="speakBtn">► Speak</button>
<button class="btn btn-secondary" id="speakRevBtn">◄ Speak reversed</button>
</div>
<div class="control-row">
<button class="btn btn-secondary" id="speak2Btn">2-gram</button>
<button class="btn btn-secondary" id="speak3Btn">3-gram</button>
<button class="btn btn-secondary" id="speak4Btn">4-gram</button>
</div>
<div class="control-row">
<label>Pitch</label>
<input type="range" id="ttsPitch" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="control-row">
<label>Speed</label>
<input type="range" id="ttsRate" min="0.5" max="2" step="0.1" value="1">
</div>
</div>
<div class="control-group">
<div class="mini-label">Recorder · playback controls</div>
<div class="control-row">
<button class="btn" id="recBtn">● Record</button>
<button class="btn btn-secondary" id="stopRecBtn">■ Stop</button>
</div>
<div class="control-row">
<button class="btn btn-secondary" id="playRecBtn">► Play</button>
<button class="btn btn-secondary" id="playRevRecBtn">◄ Play reversed</button>
</div>
<div class="control-row">
<label>Pitch/Speed</label>
<input type="range" id="recRate" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="control-row">
<label>Gain</label>
<input type="range" id="recGain" min="0" max="2" step="0.1" value="1">
</div>
<audio id="recAudio" controls style="width:100%;margin-top:4px;font-size:0.7rem;"></audio>
</div>
</div>
<div class="output-label">Text transformations</div>
<div class="transform-buttons">
<button class="btn btn-secondary" id="revCharsBtn">Reverse chars</button>
<button class="btn btn-secondary" id="revWordsBtn">Reverse words</button>
<button class="btn btn-secondary" id="flipUpsideBtn">Flip upside down</button>
<button class="btn btn-secondary" id="mirrorBtn">Mirror (RTL)</button>
</div>
<div id="transformOut" class="text-transform-block"></div>
</div>
</div>
</section>
</div>
<footer>
UPE is experimental. Any resemblance between these outputs and prophecy is entirely on you.
</footer>
</div>
<script>
// ---------- UPE CORE ----------
const UPE_CORE = {
A: { glyph: "𓂀", value: 1, desc: "open vowel /a/" },
E: { glyph: "◴", value: 2, desc: "mid vowel /e/" },
I: { glyph: "∶", value: 3, desc: "high vowel /i/" },
O: { glyph: "◎", value: 4, desc: "back vowel /o/" },
U: { glyph: "Ѻ", value: 5, desc: "back vowel /u/" },
P: { glyph: "⌸", value: 8, desc: "plosive /p/" },
B: { glyph: "⌺", value: 9, desc: "plosive /b/" },
T: { glyph: "⌻", value: 10, desc: "plosive /t/" },
D: { glyph: "⌼", value: 11, desc: "plosive /d/" },
K: { glyph: "⌾", value: 12, desc: "plosive /k/" },
G: { glyph: "⍋", value: 13, desc: "plosive /g/" },
F: { glyph: "ϝ", value: 14, desc: "fricative /f/" },
V: { glyph: "ѵ", value: 15, desc: "fricative /v/" },
S: { glyph: "ϟ", value: 16, desc: "sibilant /s/" },
Z: { glyph: "ɀ", value: 17, desc: "sibilant /z/" },
SH: { glyph: "Ϟ", value: 18, desc: "/ʃ/" },
ZH: { glyph: "ʓ", value: 19, desc: "/ʒ/" },
M: { glyph: "ԡ", value: 20, desc: "nasal /m/" },
N: { glyph: "Ԣ", value: 21, desc: "nasal /n/" },
NG: { glyph: "௵", value: 22, desc: "nasal /ŋ/" },
L: { glyph: "Լ", value: 23, desc: "lateral /l/" },
R: { glyph: "Ր", value: 24, desc: "rhotic /r/" },
W: { glyph: "ѡ", value: 25, desc: "/w/" },
Y: { glyph: "Ⴤ", value: 26, desc: "/j/ (y)" },
H: { glyph: "ђ", value: 27, desc: "glottal /h/" },
Q: { glyph: "Ϙ", value: 28, desc: "uvular /q/" },
X: { glyph: "Ҳ", value: 29, desc: "velar fricative /x/" },
J: { glyph: "ʝ", value: 30, desc: "affricate /dʒ/" },
CH: { glyph: "Ͼ", value: 31, desc: "affricate /tʃ/" },
GLT: { glyph: "᛫", value: 32, desc: "glottal stop /ʔ/" },
PUNCT:{ glyph: "·", value: 0, desc: "punctuation / pause" },
SPACE:{ glyph: " ", value: 0, desc: "space" },
UNKNOWN:{ glyph: "□", value: 0, desc: "unknown" }
};
const MAX_GLYPH_VALUE = Object.values(UPE_CORE)
.reduce((m,v) => Math.max(m,v.value), 0);
const LATIN_TO_PHONEME = {
a:"A", e:"E", i:"I", o:"O", u:"U", y:"I",
b:"B", c:"K", d:"D", f:"F", g:"G", h:"H",
j:"J", k:"K", l:"L", m:"M", n:"N", p:"P",
q:"K", r:"R", s:"S", t:"T", v:"V", w:"W",
x:"X", z:"Z"
};
const HEBREW_TO_LATIN = {
"א":"A","ב":"B","ג":"G","ד":"D","ה":"H","ו":"W","ז":"Z","ח":"H","ט":"T","י":"Y",
"כ":"K","ך":"K","ל":"L","מ":"M","ם":"M","נ":"N","ן":"N","ס":"S","ע":"A","פ":"P",
"ף":"P","צ":"S","ץ":"S","ק":"Q","ר":"R","ש":"S","ת":"T"
};
const GREEK_TO_LATIN = {
"Α":"A","α":"A","Ε":"E","ε":"E","Η":"E","η":"E","Ι":"I","ι":"I",
"Ο":"O","ο":"O","Υ":"U","υ":"U","Ω":"O","ω":"O",
"Β":"B","β":"B","Γ":"G","γ":"G","Δ":"D","δ":"D","Ζ":"Z","ζ":"Z","Θ":"T","θ":"T",
"Κ":"K","κ":"K","Λ":"L","λ":"L","Μ":"M","μ":"M","Ν":"N","ν":"N","Ξ":"X","ξ":"X",
"Π":"P","π":"P","Ρ":"R","ρ":"R","Σ":"S","σ":"S","ς":"S","Τ":"T","τ":"T",
"Φ":"F","φ":"F","Χ":"X","χ":"X","Ψ":"X","ψ":"X"
};
function mapChar(ch, lang, weight) {
if (ch === " ") return { core: UPE_CORE.SPACE, source: ch, key: "SPACE" };
if (/[.,!?;:]/.test(ch)) return { core: UPE_CORE.PUNCT, source: ch, key: "PUNCT" };
let key = null;
const lower = ch.toLowerCase();
if (lang === "en") {
key = LATIN_TO_PHONEME[lower] || null;
} else if (lang === "he") {
key = HEBREW_TO_LATIN[ch] || null;
} else if (lang === "el") {
key = GREEK_TO_LATIN[ch] || null;
} else if (lang === "custom") {
if (/[aeiou]/i.test(ch)) {
key = ch.toUpperCase();
if (!UPE_CORE[key]) key = "A";
} else if (/[A-Za-z]/.test(ch)) {
key = "X";
} else {
key = null;
}
}
if (!key) return { core: UPE_CORE.UNKNOWN, source: ch, key: "UNKNOWN" };
const base = UPE_CORE[key] || UPE_CORE.UNKNOWN;
let value = base.value;
if (weight === "vowels-heavy" && /[AEIOU]/.test(key)) {
value = Math.round(value * 1.4);
} else if (weight === "consonants-heavy" && !/[AEIOU]/.test(key)) {
value = Math.round(value * 1.3);
}
return {
core: { glyph: base.glyph, value, desc: base.desc },
source: ch,
key
};
}
function transcribeToUPE(text, lang, weight) {
const chars = Array.from(text);
const out = [];
for (let i = 0; i < chars.length; i++) {
const ch = chars[i];
if (lang === "en" && i < chars.length - 1) {
const nx = chars[i+1];
const pair = (ch + nx).toLowerCase();
if (pair === "ch") { out.push({ core: UPE_CORE.CH, source: pair, key: "CH" }); i++; continue; }
if (pair === "sh") { out.push({ core: UPE_CORE.SH, source: pair, key: "SH" }); i++; continue; }
if (pair === "zh") { out.push({ core: UPE_CORE.ZH, source: pair, key: "ZH" }); i++; continue; }
if (pair === "ng") { out.push({ core: UPE_CORE.NG, source: pair, key: "NG" }); i++; continue; }
}
out.push(mapChar(ch, lang, weight));
}
return out;
}
function computeMetrics(entries) {
let glyphs = 0;
let raw = 0;
for (const e of entries) {
if (!e) continue;
if (e.core === UPE_CORE.SPACE || e.core === UPE_CORE.PUNCT) {
raw += e.core.value || 0;
continue;
}
glyphs++;
raw += e.core.value || 0;
}
const norm = glyphs ? raw / (glyphs * MAX_GLYPH_VALUE) : 0;
return { glyphs, raw, norm };
}
const vowelsGlyphs = new Set(["𓂀","◴","∶","◎","Ѻ"]);
function dialectMutter(entries) {
let out = "";
let i = 0;
for (const e of entries) {
const g = e.core.glyph;
if (g === " " || g === "·") { out += g; continue; }
const isV = vowelsGlyphs.has(g);
if (isV) {
out += g;
} else {
const mark = (i % 3 === 0) ? "͟h" : "͢";
out += g + mark;
}
i++;
}
return out;
}
function dialectMumble(entries) {
let out = "";
for (const e of entries) {
const g = e.core.glyph;
if (g === " " || g === "·") { out += " "; continue; }
const isV = vowelsGlyphs.has(g);
out += isV ? g : "·";
}
return out;
}
function dialectSintex(text) {
const words = text.split(/\s+/).filter(Boolean);
const sorted = [...words].sort((a,b)=>b.length - a.length);
return sorted
.map((w,i)=>{
if (i % 3 === 0) return "<" + w + ">";
if (i % 3 === 1) return "[" + w + "]";
return w;
})
.join(" ");
}
function dialectIntex(entries) {
const vis = entries.filter(e=>e.core !== UPE_CORE.SPACE && e.core !== UPE_CORE.PUNCT);
const rev = [...vis].reverse();
return rev.map((e,i)=>{
const inv = MAX_GLYPH_VALUE + 1 - (e.core.value || 0);
return e.core.glyph + "[" + inv.toString(16).toUpperCase() + "]";
}).join(" ");
}
function digitalRoot(n) {
n = Math.abs(n);
while (n >= 10) {
let sum = 0;
for (const ch of String(n)) sum += ch.charCodeAt(0) - 48;
n = sum;
}
return n;
}
function makeHexPoetic(metrics, entries) {
const sum = metrics.raw;
const hex = sum.toString(16).toUpperCase();
const dr = digitalRoot(sum);
const mod22 = sum % 22;
const mod26 = sum % 26;
const trace = entries
.filter(e=>e.core !== UPE_CORE.SPACE && e.core !== UPE_CORE.PUNCT)
.map(e=>e.core.value.toString(16).padStart(2,"0").toUpperCase())
.join("·");
return {
main:
"HEX Σ : 0x" + hex + "\n" +
"ROOT : " + dr + "\n" +
"MOD 22 : " + mod22 + "\n" +
"MOD 26 : " + mod26,
trace
};
}
function makeNeoNumerix(metrics, entries) {
const sum = metrics.raw;
const norm = metrics.norm;
const dr = digitalRoot(sum);
const mod9 = sum % 9;
const mod11 = sum % 11;
const per = entries
.map((e,i)=>{
if (e.core === UPE_CORE.SPACE || e.core === UPE_CORE.PUNCT) return null;
const val = e.core.value;
return "#" + (i+1) + " " + e.core.glyph + " → " + String(val).padStart(2," ") +
" (dr=" + digitalRoot(val) + ")";
})
.filter(Boolean)
.join("\n");
return {
main:
"RAW : " + sum + "\n" +
"NORMAL : " + norm.toFixed(6) + "\n" +
"DIG.ROOT : " + dr + "\n" +
"MOD 9 : " + mod9 + "\n" +
"MOD 11 : " + mod11,
per
};
}
function dialectHex(entries) {
const accents = ["̃","̇","̈","̸","̌","̱","̲"];
return entries
.filter(e=>e.core !== UPE_CORE.SPACE)
.map((e,i)=>{
if (e.core === UPE_CORE.PUNCT) return "·";
const acc = accents[(e.core.value + i) % accents.length];
return e.core.glyph + acc;
})
.join(" ");
}
function dialectNeo(metrics, entries) {
const nums = entries
.filter(e=>e.core !== UPE_CORE.SPACE && e.core !== UPE_CORE.PUNCT)
.map(e=>e.core.value);
const groups = [];
for (let i=0;i<nums.length;i+=4) {
groups.push(nums.slice(i,i+4).join("-"));
}
return "Σ " + metrics.raw + " :: {" + groups.join(" | ") + "}";
}
// ---------- DOM ----------
const inputEl = document.getElementById("inputText");
const langEl = document.getElementById("langSelect");
const weightEl = document.getElementById("weightSelect");
const denseToggle = document.getElementById("denseToggle");
const viewLabel = document.getElementById("viewLabel");
const factionEl = document.getElementById("factionSelect");
const transcribeBtn = document.getElementById("transcribeBtn");
const clearBtn = document.getElementById("clearBtn");
const originalOut = document.getElementById("originalOut");
const upeOut = document.getElementById("upeOut");
const mappingBody = document.getElementById("mappingTableBody");
const metricGlyphs = document.getElementById("metricGlyphs");
const metricRaw = document.getElementById("metricRaw");
const metricNorm = document.getElementById("metricNorm");
const numericModeEl = document.getElementById("numericMode");
const numericTag = document.getElementById("numericTag");
const numericOut = document.getElementById("numericOut");
const hexTraceOut = document.getElementById("hexTraceOut");
const neoTraceOut = document.getElementById("neoTraceOut");
const mutterOut = document.getElementById("mutterOut");
const mumbleOut = document.getElementById("mumbleOut");
const sintexOut = document.getElementById("sintexOut");
const intexOut = document.getElementById("intexOut");
const hexDialectOut = document.getElementById("hexDialectOut");
const neoDialectOut = document.getElementById("neoDialectOut");
const copyUpeBtn = document.getElementById("copyUpeBtn");
const copyStatus = document.getElementById("copyStatus");
const apiWordEl = document.getElementById("apiWord");
const apiLookupBtn = document.getElementById("apiLookupBtn");
const apiResultEl = document.getElementById("apiResult");
const panelsRow = document.getElementById("panelRow");
const tabBtns = document.querySelectorAll(".tab-btn");
// Cipher Lab DOM
const gemSystemEl = document.getElementById("gemSystem");
const gemOutEl = document.getElementById("gemOut");
const gemAnalyzeBtn = document.getElementById("gemAnalyzeBtn");
const atbashLatinOut = document.getElementById("atbashLatinOut");
const atbashHebOut = document.getElementById("atbashHebOut");
const notarikonInitOut = document.getElementById("notarikonInitOut");
const notarikonFinalOut = document.getElementById("notarikonFinalOut");
const nameInput = document.getElementById("nameInput");
const nameSystem = document.getElementById("nameSystem");
const nameAnalyzeBtn = document.getElementById("nameAnalyzeBtn");
const nameOut = document.getElementById("nameOut");
const strokeOut = document.getElementById("strokeOut");
const baseNumberEl = document.getElementById("baseNumber");
const baseFromEl = document.getElementById("baseFrom");
const baseToEl = document.getElementById("baseTo");
const baseConvertBtn = document.getElementById("baseConvertBtn");
const baseOutEl = document.getElementById("baseOut");
// Audio / TTS controls
const speakBtn = document.getElementById("speakBtn");
const speakRevBtn = document.getElementById("speakRevBtn");
const speak2Btn = document.getElementById("speak2Btn");
const speak3Btn = document.getElementById("speak3Btn");
const speak4Btn = document.getElementById("speak4Btn");
const ttsPitch = document.getElementById("ttsPitch");
const ttsRate = document.getElementById("ttsRate");
const recBtn = document.getElementById("recBtn");
const stopRecBtn = document.getElementById("stopRecBtn");
const playRecBtn = document.getElementById("playRecBtn");
const playRevRecBtn = document.getElementById("playRevRecBtn");
const recRate = document.getElementById("recRate");
const recGain = document.getElementById("recGain");
const recAudio = document.getElementById("recAudio");
const recordLed = document.getElementById("recordLed");
const revCharsBtn = document.getElementById("revCharsBtn");
const revWordsBtn = document.getElementById("revWordsBtn");
const flipUpsideBtn = document.getElementById("flipUpsideBtn");
const mirrorBtn = document.getElementById("mirrorBtn");
const transformOut = document.getElementById("transformOut");
const nodeCanvas = document.getElementById("nodeCanvas");
// ---------- Upside-down / mirror maps ----------
const UPSIDE_DOWN_MAP = {
'a':'ɐ','b':'q','c':'ɔ','d':'p','e':'ǝ','f':'ɟ','g':'ɓ','h':'ɥ','i':'ᴉ','j':'ɾ','k':'ʞ','l':'ן',
'm':'ɯ','n':'u','o':'o','p':'d','q':'b','r':'ɹ','s':'s','t':'ʇ','u':'n','v':'ʌ','w':'ʍ','x':'x',
'y':'ʎ','z':'z',
'A':'∀','B':'𐐒','C':'Ɔ','D':'◖','E':'Ǝ','F':'Ⅎ','G':'פ','H':'H','I':'I','J':'ſ','K':'ʞ','L':'˥',
'M':'W','N':'N','O':'O','P':'Ԁ','Q':'Ὁ','R':'ᴚ','S':'S','T':'┴','U':'∩','V':'Λ','W':'M','X':'X',
'Y':'⅄','Z':'Z',
'0':'0','1':'Ɩ','2':'ᄅ','3':'Ɛ','4':'ㄣ','5':'ϛ','6':'9','7':'ㄥ','8':'8','9':'6',
'.':'˙',',':'\'','\'':',','"':'„','?':'¿','!':'¡','[':']',']':'[','(' :')',')':'(','{':'}','}':'{',
'<':'>','>':'<','_':'‾'
};
function flipUpside(text) {
const lines = text.split("\n");
const flippedLines = lines.map(line => {
const chars = Array.from(line);
const mapped = chars.map(ch => UPSIDE_DOWN_MAP[ch] || UPSIDE_DOWN_MAP[ch.toLowerCase()] || ch);
return mapped.reverse().join("");
});
return flippedLines.reverse().join("\n");
}
function mirrorText(text) {
const lines = text.split("\n");
return lines.map(line => Array.from(line).reverse().join("")).join("\n");
}
// ---------- Render ----------
let lastEntries = [];
let lastMetrics = { glyphs:0, raw:0, norm:0 };
function renderAll(entries, text) {
lastEntries = entries;
lastMetrics = computeMetrics(entries);
originalOut.textContent = text;
const upeLine = entries.map(e=>e.core.glyph).join("");
upeOut.textContent = upeLine;
metricGlyphs.textContent = lastMetrics.glyphs;
metricRaw.textContent = lastMetrics.raw;
metricNorm.textContent = lastMetrics.norm.toFixed(4);
mappingBody.innerHTML = "";
entries.forEach((e,i)=>{
const tr = document.createElement("tr");
const c1 = document.createElement("td"); c1.textContent = i+1;
const c2 = document.createElement("td"); c2.textContent = e.source; c2.className="char-col";
const c3 = document.createElement("td"); c3.textContent = e.key;
const c4 = document.createElement("td"); c4.textContent = e.core.glyph; c4.className="glyph-col";
const c5 = document.createElement("td"); c5.textContent = e.core.value;
const c6 = document.createElement("td"); c6.textContent = e.core.desc || "";
tr.appendChild(c1); tr.appendChild(c2); tr.appendChild(c3);
tr.appendChild(c4); tr.appendChild(c5); tr.appendChild(c6);
mappingBody.appendChild(tr);
});
const mode = numericModeEl.value;
if (mode === "standard") {
numericTag.textContent = "STANDARD";
numericTag.className = "badge accent";
numericOut.textContent =
"Standard metrics:\n" +
"RAW : " + lastMetrics.raw + "\n" +
"GLYPHS : " + lastMetrics.glyphs + "\n" +
"NORMAL : " + lastMetrics.norm.toFixed(6);
hexTraceOut.textContent = "";
neoTraceOut.textContent = "";
} else if (mode === "hexpoetic") {
numericTag.textContent = "HEX:POETIC";
numericTag.className = "badge hex";
const hp = makeHexPoetic(lastMetrics, entries);
numericOut.textContent = hp.main;
hexTraceOut.textContent = hp.trace;
neoTraceOut.textContent = "";
} else if (mode === "neonumerix") {
numericTag.textContent = "NEO:NUMERIX";
numericTag.className = "badge neo";
const nn = makeNeoNumerix(lastMetrics, entries);
numericOut.textContent = nn.main;
neoTraceOut.textContent = nn.per;
hexTraceOut.textContent = "";
}
mutterOut.textContent = dialectMutter(entries);
mumbleOut.textContent = dialectMumble(entries);
sintexOut.textContent = dialectSintex(text);
intexOut.textContent = dialectIntex(entries);
hexDialectOut.textContent = dialectHex(entries);
neoDialectOut.textContent = dialectNeo(lastMetrics, entries);
updateNodeUniverse(); // refresh node view weights
updateCipherLab(); // update Temurah/Notarikon/Atbash etc
}
function runTranscribe() {
const txt = inputEl.value || "";
const lang = langEl.value;
const weight = weightEl.value;
const entries = transcribeToUPE(txt, lang, weight);
renderAll(entries, txt);
blip();
}
transcribeBtn.addEventListener("click", runTranscribe);
clearBtn.addEventListener("click", () => {
inputEl.value = "";
originalOut.textContent = "";
upeOut.textContent = "";
mappingBody.innerHTML = "";
metricGlyphs.textContent = "0";
metricRaw.textContent = "0";
metricNorm.textContent = "0";
numericOut.innerHTML = "<span class='muted'>Numeric view will render after transcription.</span>";
hexTraceOut.textContent = "";
neoTraceOut.textContent = "";
mutterOut.textContent = "";
mumbleOut.textContent = "";
sintexOut.textContent = "";
intexOut.textContent = "";
hexDialectOut.textContent = "";
neoDialectOut.textContent = "";
transformOut.textContent = "";
gemOutEl.textContent = "";
atbashLatinOut.textContent = "";
atbashHebOut.textContent = "";
notarikonInitOut.textContent = "";
notarikonFinalOut.textContent = "";
nameOut.textContent = "";
strokeOut.textContent = "";
baseOutEl.textContent = "";
lastEntries = [];
lastMetrics = { glyphs:0, raw:0, norm:0 };
});
denseToggle.addEventListener("change", () => {
if (denseToggle.checked) {
document.body.classList.add("dense");
viewLabel.textContent = "Dense";
} else {
document.body.classList.remove("dense");
viewLabel.textContent = "Easy";
}
});
factionEl.addEventListener("change", () => {
document.body.classList.remove("theme-upe","theme-hex","theme-neo","theme-glitch");
const v = factionEl.value;
if (v === "upe") document.body.classList.add("theme-upe");
if (v === "hex") document.body.classList.add("theme-hex");
if (v === "neo") document.body.classList.add("theme-neo");
if (v === "glitch") document.body.classList.add("theme-glitch");
});
numericModeEl.addEventListener("change", () => {
if (lastEntries.length) renderAll(lastEntries, originalOut.textContent || "");
});
copyUpeBtn.addEventListener("click", async () => {
const txt = upeOut.textContent || "";
copyStatus.textContent = "";
try {
await navigator.clipboard.writeText(txt);
copyStatus.textContent = "Copied";
setTimeout(()=>copyStatus.textContent="", 1200);
} catch {
copyStatus.textContent = "Blocked";
setTimeout(()=>copyStatus.textContent="", 1500);
}
});
apiLookupBtn.addEventListener("click", async () => {
const word = apiWordEl.value.trim();
apiResultEl.textContent = "";
if (!word) {
apiResultEl.textContent = "Enter a word to query.";
return;
}
const url = "https://api.dictionaryapi.dev/api/v2/entries/en/" + encodeURIComponent(word);
apiResultEl.textContent = "Requesting: " + url + "\n…";
try {
const res = await fetch(url);
const text = await res.text();
try {
const json = JSON.parse(text);
apiResultEl.textContent = JSON.stringify(json, null, 2);
} catch {
apiResultEl.textContent = text;
}
} catch (err) {
apiResultEl.textContent = "Error: " + err;
}
});
tabBtns.forEach(btn => {
btn.addEventListener("click", () => {
tabBtns.forEach(b=>b.classList.remove("active"));
btn.classList.add("active");
const targetId = btn.getAttribute("data-target");
const panel = document.getElementById(targetId);
if (!panel) return;
panelsRow.scrollTo({
left: panel.offsetLeft,
behavior: "smooth"
});
});
});
const START_PHRASES = [
"hello world",
"insert text here",
"never debug a curse in production",
"spellcasting is undocumented system calls",
"the codex is just source control for reality",
"recursion is the only honest prayer",
"we are all running unstable firmware",
"compile the ritual, not the rumor",
"magick is a user-defined interrupt",
"your memory is a hostile API"
];
function randomStart() {
const i = Math.floor(Math.random() * START_PHRASES.length);
return START_PHRASES[i];
}
setTimeout(() => {
inputEl.value = randomStart();
runTranscribe();
}, 40);
// ---------- Node Universe ----------
let nodeCtx, nodeW=0, nodeH=0;
let expansionT = 0;
let lastTime = 0;
const nodes = [];
function initNodeUniverse() {
if (!nodeCanvas) return;
nodeCtx = nodeCanvas.getContext("2d");
resizeNodeCanvas();
nodes.length = 0;
const dialects = [
{label:"MUTTER", type:"dialect"},
{label:"MUMBLE", type:"dialect"},
{label:"SINTEX", type:"dialect"},
{label:"INTEX", type:"dialect"},
{label:"CONVEX", type:"dialect"},
{label:"SCRIPT", type:"dialect"},
{label:"HEX:POETIC", type:"dialect"},
{label:"NEO:NUMERIX", type:"dialect"}
];
const langs = [
{label:"EASYREAD", type:"lang"},
{label:"NEW IPA", type:"lang"},
{label:"HEBREW", type:"lang"},
{label:"GREEK", type:"lang"}
];
nodes.push({
label: "UPE CORE",
type: "core",
baseRadius: 0,
angle: 0,
jitter: 0,
depthPhase: Math.random()*Math.PI*2,
size: 18
});
const minSide = Math.min(nodeW,nodeH);
const dialRadius = minSide*0.32;
dialects.forEach((d,i)=>{
const angle = (i / dialects.length) * Math.PI*2;
nodes.push({
label: d.label,
type: d.type,
baseRadius: dialRadius,
angle: angle,
jitter: (Math.random()-0.5)*10,
depthPhase: Math.random()*Math.PI*2,
size: 10 + (i%3)
});
});
const langRadius = minSide*0.52;
langs.forEach((d,i)=>{
const angle = (i / langs.length) * Math.PI*2 + Math.PI/6;
nodes.push({
label: d.label,
type: d.type,
baseRadius: langRadius,
angle: angle,
jitter: (Math.random()-0.5)*12,
depthPhase: Math.random()*Math.PI*2,
size: 11
});
});
expansionT = 0;
lastTime = performance.now();
requestAnimationFrame(nodeLoop);
}
function resizeNodeCanvas() {
const rect = nodeCanvas.getBoundingClientRect();
nodeW = rect.width;
nodeH = rect.height;
nodeCanvas.width = nodeW * window.devicePixelRatio;
nodeCanvas.height = nodeH * window.devicePixelRatio;
nodeCtx.setTransform(window.devicePixelRatio,0,0,window.devicePixelRatio,0,0);
}
window.addEventListener("resize", () => {
resizeNodeCanvas();
});
function easing(t) {
return t<0 ? 0 : t>1 ? 1 : 1 - Math.pow(1-t,3);
}
function drawNodeUniverse(dt) {
if (!nodeCtx) return;
nodeCtx.clearRect(0,0,nodeW,nodeH);
const centerX = nodeW/2;
const centerY = nodeH/2;
const t = easing(expansionT);
const minSide = Math.min(nodeW,nodeH);
// grid
nodeCtx.save();
nodeCtx.strokeStyle = "rgba(255,255,255,0.05)";
nodeCtx.lineWidth = 0.5;
const grid = 40;
for (let x=0;x<nodeW;x+=grid) {
nodeCtx.beginPath();
nodeCtx.moveTo(x,0);
nodeCtx.lineTo(x,nodeH);
nodeCtx.stroke();
}
for (let y=0;y<nodeH;y+=grid) {
nodeCtx.beginPath();
nodeCtx.moveTo(0,y);
nodeCtx.lineTo(nodeW,y);
nodeCtx.stroke();
}
nodeCtx.restore();
// glow
const grad = nodeCtx.createRadialGradient(centerX,centerY,0,centerX,centerY,nodeW*0.7);
grad.addColorStop(0,"rgba(111,255,233,0.3)");
grad.addColorStop(1,"rgba(0,0,0,0)");
nodeCtx.fillStyle = grad;
nodeCtx.beginPath();
nodeCtx.arc(centerX,centerY,nodeW*0.7,0,Math.PI*2);
nodeCtx.fill();
// elliptical orbits
nodeCtx.save();
nodeCtx.strokeStyle = "rgba(148,163,255,0.26)";
nodeCtx.lineWidth = 0.7;
const dialR = minSide*0.32 * t;
const langR = minSide*0.52 * t;
[dialR, langR].forEach(r => {
const ry = r*0.6;
nodeCtx.beginPath();
nodeCtx.ellipse(centerX,centerY,r,ry,0,0,Math.PI*2);
nodeCtx.stroke();
});
nodeCtx.restore();
// phoneme satellites
const freq = {};
lastEntries.forEach(e=>{
const g = e.core.glyph;
if (g===" "||g==="·"||g==="□") return;
freq[g] = (freq[g]||0)+1;
});
const phonemes = Object.entries(freq)
.sort((a,b)=>b[1]-a[1])
.slice(0,10)
.map(([glyph,count])=>({glyph,count}));
const phonemeRadius = minSide*0.2 * t;
phonemes.forEach((p,i)=>{
const timeFactor = performance.now()/1000 * 0.16;
const angle = timeFactor + i*(Math.PI*2/Math.max(phonemes.length,1));
const r = phonemeRadius;
const x = centerX + Math.cos(angle)*r;
const y = centerY + Math.sin(angle)*r*0.6;
const size = 4 + Math.min(10,p.count);
nodeCtx.save();
nodeCtx.strokeStyle = "rgba(144,224,239,0.6)";
nodeCtx.lineWidth = 0.8;
nodeCtx.beginPath();
nodeCtx.moveTo(centerX,centerY);
nodeCtx.lineTo(x,y);
nodeCtx.stroke();
nodeCtx.fillStyle = "rgba(111,255,233,0.9)";
nodeCtx.beginPath();
nodeCtx.arc(x,y,size,0,Math.PI*2);
nodeCtx.fill();
nodeCtx.fillStyle = "#05060b";
nodeCtx.font = "bold 10px " + (navigator.platform.includes("Mac")?"-apple-system":"system-ui");
nodeCtx.textAlign = "center";
nodeCtx.textBaseline = "middle";
nodeCtx.fillText(p.glyph,x,y-1);
nodeCtx.restore();
});
const renderNodes = [];
nodes.forEach(n=>{
let x = centerX;
let y = centerY;
let depth = 1;
if (n.baseRadius > 0) {
const r = (n.baseRadius * t) + n.jitter;
const speed = n.type==="dialect" ? 0.0008 : 0.00045;
n.angle += speed * dt;
const angle = n.angle;
const rx = r;
const ry = r * 0.6;
x = centerX + Math.cos(angle)*rx;
y = centerY + Math.sin(angle)*ry;
depth = 0.5 + 0.5*Math.sin(angle + n.depthPhase);
}
renderNodes.push({
node: n,
x,
y,
depth
});
});
renderNodes.sort((a,b)=>a.depth - b.depth);
renderNodes.forEach(obj=>{
const n = obj.node;
const x = obj.x;
const y = obj.y;
const d = obj.depth;
let color = "#6fffe9";
if (n.type==="dialect") color = "rgba(255,157,252,1)";
if (n.type==="lang") color = "rgba(176,255,111,1)";
if (n.type==="core") color = "#6fffe9";
const baseSize = n.size;
const size = baseSize * (0.7 + 0.6*d);
const alpha = 0.3 + 0.7*d;
nodeCtx.save();
nodeCtx.beginPath();
nodeCtx.fillStyle = applyAlpha(color, alpha);
nodeCtx.shadowColor = applyAlpha(color, alpha);
nodeCtx.shadowBlur = 10 * d;
nodeCtx.arc(x,y,size,0,Math.PI*2);
nodeCtx.fill();
nodeCtx.shadowBlur = 0;
nodeCtx.fillStyle = "#05060b";
nodeCtx.font = "bold 11px " + (navigator.platform.includes("Mac")?"-apple-system":"system-ui");
nodeCtx.textAlign = "center";
nodeCtx.textBaseline = "middle";
nodeCtx.fillText(n.label,x,y);
nodeCtx.restore();
});
if (expansionT < 1) {
expansionT += (dt/4000);
if (expansionT > 1) expansionT = 1;
}
}
function applyAlpha(color, alpha) {
if (color.startsWith("rgba")) return color;
if (color.startsWith("#")) {
let r=0,g=0,b=0;
if (color.length===4) {
r = parseInt(color[1]+color[1],16);
g = parseInt(color[2]+color[2],16);
b = parseInt(color[3]+color[3],16);
} else if (color.length===7) {
r = parseInt(color.slice(1,3),16);
g = parseInt(color.slice(3,5),16);
b = parseInt(color.slice(5,7),16);
}
return "rgba("+r+","+g+","+b+","+alpha+")";
}
return color;
}
function nodeLoop(ts) {
const dt = ts - lastTime;
lastTime = ts;
drawNodeUniverse(dt);
requestAnimationFrame(nodeLoop);
}
function updateNodeUniverse() {
// phoneme satellites already pull from lastEntries
}
if (nodeCanvas) {
initNodeUniverse();
}
// ---------- Gematria / Cipher Lab ----------
// English A1Z26
function gemEngChar(ch) {
const c = ch.toUpperCase();
if (c>='A' && c<='Z') return c.charCodeAt(0) - 64;
return 0;
}
// Hebrew values (standard)
const HEB_GEM = {
"א":1,"ב":2,"ג":3,"ד":4,"ה":5,"ו":6,"ז":7,"ח":8,"ט":9,
"י":10,"כ":20,"ך":20,"ל":30,"מ":40,"ם":40,"נ":50,"ן":50,"ס":60,
"ע":70,"פ":80,"ף":80,"צ":90,"ץ":90,"ק":100,"ר":200,"ש":300,"ת":400
};
function gemHebChar(ch) {
return HEB_GEM[ch] || 0;
}
// Greek isopsephy
const GR_GEM = {
"α":1,"β":2,"γ":3,"δ":4,"ε":5,"ϛ":6,"ς":6,"ζ":7,"η":8,"θ":9,
"ι":10,"κ":20,"λ":30,"μ":40,"ν":50,"ξ":60,"ο":70,"π":80,"ϙ":90,
"ρ":100,"σ":200,"ς":200,"τ":300,"υ":400,"φ":500,"χ":600,"ψ":700,"ω":800
};
function gemGrChar(ch) {
const c = ch.toLowerCase();
return GR_GEM[c] || 0;
}
function gematriaWord(word, system) {
let sum = 0;
for (const ch of word) {
if (system === "eng") sum += gemEngChar(ch);
else if (system === "he") sum += gemHebChar(ch);
else if (system === "gr") sum += gemGrChar(ch);
}
return sum;
}
function analyzeGematria() {
const system = gemSystemEl.value;
const txt = (inputEl.value || "").trim();
if (!txt) {
gemOutEl.textContent = "No text. Feed the engine.";
return;
}
const words = txt.split(/\s+/).filter(Boolean);
let total = 0;
const lines = [];
words.forEach((w,i)=>{
const v = gematriaWord(w, system);
total += v;
lines.push((i+1)+". " + w + " → " + v);
});
const dr = digitalRoot(total);
gemOutEl.textContent =
"SYSTEM : " + (system==="eng"?"English A1Z26": system==="he"?"Hebrew":"Greek Isopsephy") + "\n" +
"WORDS : " + words.length + "\n" +
"TOTAL : " + total + "\n" +
"ROOT : " + dr + "\n" +
"-----------------\n" +
lines.join("\n");
}
gemAnalyzeBtn.addEventListener("click", analyzeGematria);
// Atbash Latin
const LATIN_ATBASH = {};
(function buildLatinAtbash() {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const rev = alphabet.split("").reverse().join("");
for (let i=0;i<alphabet.length;i++) {
LATIN_ATBASH[alphabet[i]] = rev[i];
LATIN_ATBASH[alphabet[i].toLowerCase()] = rev[i].toLowerCase();
}
})();
function atbashLatin(text) {
return Array.from(text).map(ch => LATIN_ATBASH[ch] || ch).join("");
}
// Hebrew Atbash (Atbash mapping)
const HEB_ATBASH_MAP = {
"א":"ת","ב":"ש","ג":"ר","ד":"ק","ה":"צ","ו":"פ","ז":"ע","ח":"ס","ט":"נ",
"י":"מ","כ":"ל","ך":"ל","ל":"כ","מ":"י","ם":"י","נ":"ט","ן":"ט","ס":"ח",
"ע":"ז","פ":"ו","ף":"ו","צ":"ה","ץ":"ה","ק":"ד","ר":"ג","ש":"ב","ת":"א"
};
function atbashHeb(text) {
return Array.from(text).map(ch => HEB_ATBASH_MAP[ch] || ch).join("");
}
// Notarikon
function notarikonInitials(text) {
const words = text.split(/\s+/).filter(Boolean);
return words.map(w=>w[0] || "").join("");
}
function notarikonFinals(text) {
const words = text.split(/\s+/).filter(Boolean);
return words.map(w=>w[w.length-1] || "").join("");
}
// Onomancy + stroke counts (rough)
const STROKE_MAP = {
A:3,B:3,C:1,D:2,E:4,F:3,G:2,H:3,I:1,J:2,K:3,L:2,M:4,N:3,O:1,P:2,Q:2,R:3,
S:1,T:2,U:2,V:2,W:4,X:2,Y:3,Z:3
};
function strokeCount(text) {
let sum = 0;
const details = [];
for (const ch of text) {
const c = ch.toUpperCase();
if (c>='A' && c<='Z') {
const s = STROKE_MAP[c] || 2;
sum += s;
details.push(c + ":" + s);
}
}
return { sum, details: details.join(", ") };
}
function analyzeName() {
const name = (nameInput.value || inputEl.value || "").trim();
if (!name) {
nameOut.textContent = "No name provided.";
strokeOut.textContent = "";
return;
}
const sys = nameSystem.value;
let sum = 0;
for (const ch of name) {
if (sys==="eng") sum += gemEngChar(ch);
else if (sys==="he") sum += gemHebChar(ch);
else if (sys==="gr") sum += gemGrChar(ch);
}
const dr = digitalRoot(sum);
nameOut.textContent =
"Name : " + name + "\n" +
"Sys : " + sys.toUpperCase() + "\n" +
"Sum : " + sum + "\n" +
"Root : " + dr;
const s = strokeCount(name);
strokeOut.textContent =
"Stroke sum: " + s.sum + "\n" +
"Per letter: " + s.details;
}
nameAnalyzeBtn.addEventListener("click", analyzeName);
// Base conversion
function baseConvert() {
const val = baseNumberEl.value.trim();
if (!val) {
baseOutEl.textContent = "Enter a number.";
return;
}
const from = parseInt(baseFromEl.value,10);
const to = parseInt(baseToEl.value,10);
try {
const n = parseInt(val, from);
if (isNaN(n)) throw new Error("Invalid number for base " + from);
const out = n.toString(to).toUpperCase();
baseOutEl.textContent =
"Input : " + val + " (base " + from + ")\n" +
"Value : " + n + " (base 10)\n" +
"Output: " + out + " (base " + to + ")";
} catch (e) {
baseOutEl.textContent = "Error: " + e.message;
}
}
baseConvertBtn.addEventListener("click", baseConvert);
// Update all cipher lab pieces based on current text
function updateCipherLab() {
const txt = inputEl.value || "";
atbashLatinOut.textContent = atbashLatin(txt);
atbashHebOut.textContent = atbashHeb(txt);
notarikonInitOut.textContent = notarikonInitials(txt);
notarikonFinalOut.textContent = notarikonFinals(txt);
// keep gem/name/base manual-triggered so you can compare systems
}
// ---------- Audio / TTS ----------
let ttsBusy = false;
function speakText(text, reversed=false) {
if (!window.speechSynthesis) return;
const synth = window.speechSynthesis;
synth.cancel();
if (!text) return;
const utter = new SpeechSynthesisUtterance(reversed ? text.split("").reverse().join("") : text);
utter.pitch = parseFloat(ttsPitch.value) || 1;
utter.rate = parseFloat(ttsRate.value) || 1;
ttsBusy = true;
utter.onend = ()=>{ ttsBusy=false; };
synth.speak(utter);
}
function speakSegments(n) {
if (!window.speechSynthesis) return;
const synth = window.speechSynthesis;
synth.cancel();
const text = inputEl.value || "";
if (!text.trim()) return;
const clean = text.replace(/\s+/g," ").trim();
const chars = Array.from(clean);
const segments = [];
for (let i=0;i<chars.length;i+=n) {
segments.push(chars.slice(i,i+n).join(""));
}
let idx = 0;
function next() {
if (idx>=segments.length) return;
const utter = new SpeechSynthesisUtterance(segments[idx]);
utter.pitch = parseFloat(ttsPitch.value) || 1;
utter.rate = parseFloat(ttsRate.value) || 1;
utter.onend = ()=>{ idx++; next(); };
synth.speak(utter);
}
next();
}
speakBtn.addEventListener("click", ()=> speakText(inputEl.value || ""));
speakRevBtn.addEventListener("click", ()=> speakText(inputEl.value || "", true));
speak2Btn.addEventListener("click", ()=> speakSegments(2));
speak3Btn.addEventListener("click", ()=> speakSegments(3));
speak4Btn.addEventListener("click", ()=> speakSegments(4));
// ---------- Recorder ----------
let audioCtx = null;
let mediaStream = null;
let mediaRecorder = null;
let recordedChunks = [];
let decodedBuffer = null;
let gainNode = null;
function ensureAudioCtx() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
gainNode = audioCtx.createGain();
gainNode.gain.value = parseFloat(recGain.value) || 1;
gainNode.connect(audioCtx.destination);
}
}
async function startRecording() {
try {
ensureAudioCtx();
if (!mediaStream) {
mediaStream = await navigator.mediaDevices.getUserMedia({ audio:true });
}
recordedChunks = [];
mediaRecorder = new MediaRecorder(mediaStream);
mediaRecorder.ondataavailable = e => {
if (e.data.size>0) recordedChunks.push(e.data);
};
mediaRecorder.onstop = async () => {
const blob = new Blob(recordedChunks, {type:"audio/webm"});
const url = URL.createObjectURL(blob);
recAudio.src = url;
const arrayBuffer = await blob.arrayBuffer();
audioCtx.decodeAudioData(arrayBuffer, buffer=>{
decodedBuffer = buffer;
});
};
mediaRecorder.start();
recordLed.classList.add("on");
} catch (err) {
console.error(err);
}
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state === "recording") {
mediaRecorder.stop();
recordLed.classList.remove("on");
}
}
function playBuffer(reverse=false) {
if (!decodedBuffer || !audioCtx) return;
const rate = parseFloat(recRate.value) || 1;
gainNode.gain.value = parseFloat(recGain.value) || 1;
const src = audioCtx.createBufferSource();
if (reverse) {
const ch = decodedBuffer.numberOfChannels;
const length = decodedBuffer.length;
const revBuffer = audioCtx.createBuffer(ch,length,decodedBuffer.sampleRate);
for (let i=0;i<ch;i++) {
const data = decodedBuffer.getChannelData(i);
const revData = revBuffer.getChannelData(i);
for (let j=0;j<length;j++) {
revData[j] = data[length-j-1];
}
}
src.buffer = revBuffer;
} else {
src.buffer = decodedBuffer;
}
src.playbackRate.value = rate;
src.connect(gainNode);
src.start();
}
recBtn.addEventListener("click", startRecording);
stopRecBtn.addEventListener("click", stopRecording);
playRecBtn.addEventListener("click", ()=>playBuffer(false));
playRevRecBtn.addEventListener("click", ()=>playBuffer(true));
recGain.addEventListener("input", ()=>{ if (gainNode) gainNode.gain.value = parseFloat(recGain.value)||1; });
// ---------- Text transforms ----------
function updateTransformOut(text) {
transformOut.textContent = text;
}
revCharsBtn.addEventListener("click", () => {
const txt = inputEl.value || "";
const rev = Array.from(txt).reverse().join("");
updateTransformOut(rev);
});
revWordsBtn.addEventListener("click", () => {
const txt = inputEl.value || "";
const rev = txt.split(/\s+/).reverse().join(" ");
updateTransformOut(rev);
});
flipUpsideBtn.addEventListener("click", () => {
const txt = inputEl.value || "";
updateTransformOut(flipUpside(txt));
});
mirrorBtn.addEventListener("click", () => {
const txt = inputEl.value || "";
updateTransformOut(mirrorText(txt));
});
// ---------- tiny synth blip on transcribe ----------
let fxCtx = null;
function blip() {
try {
if (!fxCtx) fxCtx = new (window.AudioContext || window.webkitAudioContext)();
const osc = fxCtx.createOscillator();
const gain = fxCtx.createGain();
osc.frequency.value = 420;
gain.gain.value = 0.12;
osc.connect(gain);
gain.connect(fxCtx.destination);
osc.start();
gain.gain.exponentialRampToValueAtTime(0.0001, fxCtx.currentTime + 0.18);
osc.stop(fxCtx.currentTime + 0.2);
} catch(e) {}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment