279 lines
8.9 KiB
HTML
279 lines
8.9 KiB
HTML
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>FloatingLines Dev</title>
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"three": "https://cdn.jsdelivr.net/npm/three@0.183.0/build/three.module.js"
|
|
}
|
|
}
|
|
</script>
|
|
<style>
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
html,
|
|
body {
|
|
width: 100%;
|
|
height: 100%;
|
|
background: #000;
|
|
overflow: hidden;
|
|
font-family: ui-monospace, 'Cascadia Code', monospace;
|
|
}
|
|
|
|
#app {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
#container {
|
|
flex: 1;
|
|
min-height: 0;
|
|
position: relative;
|
|
}
|
|
|
|
/* ── Footer ─────────────────────────────────────────────────────── */
|
|
#controls {
|
|
flex-shrink: 0;
|
|
background: #0d0d0f;
|
|
border-top: 1px solid #222;
|
|
padding: 10px 14px;
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 10px 16px;
|
|
max-height: 220px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.ctrl-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
min-width: 0;
|
|
}
|
|
.ctrl-group h3 {
|
|
font-size: 9px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: #555;
|
|
padding-bottom: 3px;
|
|
border-bottom: 1px solid #1e1e1e;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
min-width: 0;
|
|
}
|
|
.row label {
|
|
font-size: 10px;
|
|
color: #777;
|
|
white-space: nowrap;
|
|
min-width: 70px;
|
|
flex-shrink: 0;
|
|
}
|
|
.row input[type='range'] {
|
|
flex: 1;
|
|
min-width: 0;
|
|
height: 3px;
|
|
accent-color: #a855f7;
|
|
cursor: pointer;
|
|
}
|
|
.row .val {
|
|
font-size: 10px;
|
|
color: #bbb;
|
|
min-width: 36px;
|
|
text-align: right;
|
|
flex-shrink: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<div id="container"></div>
|
|
|
|
<footer id="controls">
|
|
<!-- Col 1: Allgemein -->
|
|
<div class="ctrl-group">
|
|
<h3>Allgemein</h3>
|
|
<div class="row">
|
|
<label for="speed">Speed</label>
|
|
<input type="range" id="speed" min="0.1" max="3" step="0.05" value="1" />
|
|
<span class="val" id="speed-val">1.00</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Col 2: Middle Wave -->
|
|
<div class="ctrl-group">
|
|
<h3>Middle Wave</h3>
|
|
<div class="row">
|
|
<label for="midCount">Lines</label>
|
|
<input type="range" id="midCount" min="0" max="40" step="1" value="15" />
|
|
<span class="val" id="midCount-val">10</span>
|
|
</div>
|
|
<div class="row">
|
|
<label for="midDist">Distance</label>
|
|
<input type="range" id="midDist" min="1" max="30" step="0.5" value="6" />
|
|
<span class="val" id="midDist-val">6.0</span>
|
|
</div>
|
|
<div class="row">
|
|
<label for="midX">X</label>
|
|
<input type="range" id="midX" min="-20" max="20" step="0.1" value="5" />
|
|
<span class="val" id="midX-val">5.0</span>
|
|
</div>
|
|
<div class="row">
|
|
<label for="midY">Y</label>
|
|
<input type="range" id="midY" min="-2" max="2" step="0.05" value="0" />
|
|
<span class="val" id="midY-val">0.00</span>
|
|
</div>
|
|
<div class="row">
|
|
<label for="midR">Rotate</label>
|
|
<input type="range" id="midR" min="-2" max="2" step="0.05" value="0.2" />
|
|
<span class="val" id="midR-val">0.20</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Col 3: Gradient -->
|
|
<div class="ctrl-group">
|
|
<h3>Gradient</h3>
|
|
<div class="row" style="flex-direction: column; align-items: flex-start; gap: 4px">
|
|
<label style="color: #555; font-size: 9px">Farbstopps (je Zeile ein Hex)</label>
|
|
<textarea
|
|
id="gradientInput"
|
|
spellcheck="false"
|
|
style="
|
|
width: 100%;
|
|
height: 80px;
|
|
background: #111;
|
|
border: 1px solid #2a2a2a;
|
|
color: #ccc;
|
|
font-family: inherit;
|
|
font-size: 10px;
|
|
padding: 4px 6px;
|
|
resize: none;
|
|
border-radius: 3px;
|
|
outline: none;
|
|
line-height: 1.5;
|
|
"
|
|
>
|
|
#e947f5
|
|
#2f4ba2
|
|
#0a0a12</textarea
|
|
>
|
|
<button
|
|
id="applyGradient"
|
|
style="
|
|
background: #1e1e2e;
|
|
border: 1px solid #333;
|
|
color: #a855f7;
|
|
font-family: inherit;
|
|
font-size: 10px;
|
|
padding: 3px 10px;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
align-self: flex-end;
|
|
"
|
|
>
|
|
Anwenden
|
|
</button>
|
|
</div>
|
|
<div class="row" style="margin-top: 4px">
|
|
<input
|
|
type="checkbox"
|
|
id="clearGradient"
|
|
style="accent-color: #a855f7; cursor: pointer; width: 12px; height: 12px"
|
|
/>
|
|
<label for="clearGradient" style="cursor: pointer; min-width: unset">
|
|
Gradient deaktivieren
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
|
|
<script type="module">
|
|
import FloatingLines from './floating-lines.js'
|
|
|
|
const container = document.getElementById('container')
|
|
|
|
const fl = new FloatingLines(container, {
|
|
enabledWaves: ['middle'],
|
|
lineCount: [10],
|
|
lineDistance: [2],
|
|
interactive: false,
|
|
parallax: false,
|
|
linesGradient: ['#e947f5', '#2f4ba2', '#0a0a12'],
|
|
})
|
|
|
|
// ── Hilfsfunktionen ────────────────────────────────────────────────
|
|
function slider(id, decimals, onChange) {
|
|
const input = document.getElementById(id)
|
|
const display = document.getElementById(id + '-val')
|
|
input.addEventListener('input', () => {
|
|
const v = parseFloat(input.value)
|
|
if (display) display.textContent = v.toFixed(decimals)
|
|
onChange(v)
|
|
})
|
|
}
|
|
|
|
function hexToVec3(hex) {
|
|
let v = hex.trim().replace('#', '')
|
|
if (v.length === 3) v = v[0] + v[0] + v[1] + v[1] + v[2] + v[2]
|
|
return [
|
|
parseInt(v.slice(0, 2), 16) / 255,
|
|
parseInt(v.slice(2, 4), 16) / 255,
|
|
parseInt(v.slice(4, 6), 16) / 255,
|
|
]
|
|
}
|
|
|
|
// ── Allgemein ──────────────────────────────────────────────────────
|
|
slider('speed', 2, (v) => (fl.uniforms.animationSpeed.value = v))
|
|
|
|
// ── Middle Wave ────────────────────────────────────────────────────
|
|
slider('midCount', 0, (v) => (fl.uniforms.middleLineCount.value = Math.round(v)))
|
|
slider('midDist', 1, (v) => (fl.uniforms.middleLineDistance.value = v * 0.01))
|
|
slider('midX', 1, (v) => (fl.uniforms.middleWavePosition.value.x = v))
|
|
slider('midY', 2, (v) => (fl.uniforms.middleWavePosition.value.y = v))
|
|
slider('midR', 2, (v) => (fl.uniforms.middleWavePosition.value.z = v))
|
|
|
|
// ── Gradient ───────────────────────────────────────────────────────
|
|
const MAX_STOPS = 8
|
|
|
|
function applyGradient() {
|
|
const lines = document
|
|
.getElementById('gradientInput')
|
|
.value.split('\n')
|
|
.map((s) => s.trim())
|
|
.filter((s) => s.length > 0)
|
|
const stops = lines.slice(0, MAX_STOPS)
|
|
fl.uniforms.lineGradientCount.value = stops.length
|
|
stops.forEach((hex, i) => {
|
|
const [r, g, b] = hexToVec3(hex)
|
|
fl.uniforms.lineGradient.value[i].set(r, g, b)
|
|
})
|
|
}
|
|
|
|
document.getElementById('applyGradient').addEventListener('click', applyGradient)
|
|
|
|
document.getElementById('clearGradient').addEventListener('change', (e) => {
|
|
fl.uniforms.lineGradientCount.value = e.target.checked
|
|
? 0
|
|
: document
|
|
.getElementById('gradientInput')
|
|
.value.split('\n')
|
|
.filter((s) => s.trim()).length
|
|
})
|
|
</script>
|
|
</body>
|
|
</html>
|