235 lines
5.6 KiB
HTML
235 lines
5.6 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Synth Visualiser</title>
|
||
<style>
|
||
:root {
|
||
--bg: #0d0d0d;
|
||
--panel: #1a1a1a;
|
||
--accent: #00e5ff;
|
||
--text: #e0e0e0;
|
||
--border: #2a2a2a;
|
||
--handle: #222;
|
||
--handle-hover:#2e2e2e;
|
||
--patchbay-h: 340px;
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
html, body {
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
body {
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
header {
|
||
padding: 0.6rem 1.5rem;
|
||
border-bottom: 1px solid var(--border);
|
||
font-size: 0.85rem;
|
||
letter-spacing: 0.15em;
|
||
text-transform: uppercase;
|
||
color: var(--accent);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
main {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* Top two panels side-by-side */
|
||
.panels-top {
|
||
flex: 1;
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1px;
|
||
background: var(--border);
|
||
min-height: 60px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Draggable divider between top panels and patch bay */
|
||
.resize-handle {
|
||
flex-shrink: 0;
|
||
height: 6px;
|
||
background: var(--handle);
|
||
cursor: ns-resize;
|
||
border-top: 1px solid #333;
|
||
border-bottom: 1px solid #111;
|
||
user-select: none;
|
||
touch-action: none;
|
||
}
|
||
.resize-handle:hover,
|
||
.resize-handle.active {
|
||
background: var(--handle-hover);
|
||
}
|
||
/* Widen the grab area without making the visual gap bigger */
|
||
.resize-handle::before {
|
||
content: "";
|
||
display: block;
|
||
height: 12px;
|
||
margin-top: -3px;
|
||
}
|
||
|
||
/* Patch bay panel — height controlled by JS drag */
|
||
.panel--patchbay {
|
||
flex-shrink: 0;
|
||
height: var(--patchbay-h);
|
||
min-height: 80px;
|
||
max-height: calc(100vh - 220px);
|
||
border-top: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
background: var(--panel);
|
||
}
|
||
|
||
/* Virtual keyboard — fixed height */
|
||
.panel--keyboard {
|
||
flex-shrink: 0;
|
||
height: 100px;
|
||
border-top: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
background: var(--panel);
|
||
}
|
||
|
||
.panel {
|
||
background: var(--panel);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.panel__label {
|
||
padding: 0.35rem 0.75rem;
|
||
font-size: 0.65rem;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: #555;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Canvas fills remaining space; buffer sizing is done in JS */
|
||
.panel canvas {
|
||
flex: 1;
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
|
||
footer {
|
||
flex-shrink: 0;
|
||
padding: 0.4rem 1.5rem;
|
||
font-size: 0.65rem;
|
||
color: #444;
|
||
border-top: 1px solid #1e1e1e;
|
||
display: flex;
|
||
gap: 1.5rem;
|
||
}
|
||
#status { color: var(--accent); }
|
||
|
||
#loader {
|
||
position: fixed; inset: 0;
|
||
background: var(--bg);
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 1rem; color: var(--accent);
|
||
z-index: 100; transition: opacity 0.4s;
|
||
}
|
||
#loader.hidden { opacity: 0; pointer-events: none; }
|
||
|
||
#tooltip {
|
||
position: fixed;
|
||
z-index: 200;
|
||
max-width: 320px;
|
||
background: #1a1e2c;
|
||
border: 1px solid #3b4560;
|
||
border-radius: 8px;
|
||
padding: 12px 14px;
|
||
font-size: 0.72rem;
|
||
line-height: 1.65;
|
||
color: #c8d0e0;
|
||
pointer-events: none;
|
||
box-shadow: 0 6px 28px rgba(0,0,0,0.75);
|
||
display: none;
|
||
}
|
||
#tooltip h2 {
|
||
font-size: 0.78rem;
|
||
color: #e8eaf6;
|
||
margin-bottom: 6px;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
#tooltip strong { color: #a5d8ff; }
|
||
#tooltip code {
|
||
background: rgba(255,255,255,0.08);
|
||
border-radius: 3px;
|
||
padding: 0 3px;
|
||
font-size: 0.68rem;
|
||
color: #94e2d5;
|
||
}
|
||
#tooltip blockquote {
|
||
border-left: 2px solid #3b4560;
|
||
margin: 6px 0 0;
|
||
padding-left: 8px;
|
||
color: #8899aa;
|
||
}
|
||
#tooltip ul {
|
||
margin: 4px 0 0 14px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="loader">Loading WASM module…</div>
|
||
<div id="tooltip"></div>
|
||
|
||
<header>Analogue Synth Visualiser</header>
|
||
|
||
<main>
|
||
<div class="panels-top">
|
||
<section class="panel">
|
||
<div class="panel__label">Oscilloscope</div>
|
||
<canvas id="oscilloscope-canvas"></canvas>
|
||
</section>
|
||
<section class="panel">
|
||
<div class="panel__label">Spectrum</div>
|
||
<canvas id="spectrum-canvas"></canvas>
|
||
</section>
|
||
</div>
|
||
|
||
<div class="resize-handle" id="resize-handle" title="Drag to resize patch bay"></div>
|
||
|
||
<section class="panel panel--patchbay" id="patchbay-panel">
|
||
<div class="panel__label">Patch Bay</div>
|
||
<canvas id="patchbay-canvas"></canvas>
|
||
</section>
|
||
|
||
<section class="panel panel--keyboard">
|
||
<div class="panel__label">Keyboard <span id="kb-hint" style="color:#333;font-size:0.6rem;">Z–M · Q–U · S D G H J · 2 3 5 6 7</span></div>
|
||
<canvas id="keyboard-canvas"></canvas>
|
||
</section>
|
||
</main>
|
||
|
||
<footer>
|
||
<span id="status">Idle</span>
|
||
<span id="sample-rate"></span>
|
||
<span id="frame-time"></span>
|
||
</footer>
|
||
|
||
<script type="module" src="./bootstrap.js"></script>
|
||
</body>
|
||
</html>
|