better eval mechanism and transparency

This commit is contained in:
2025-07-05 18:19:39 +02:00
parent 8082ec66ea
commit bacc6f0325
3 changed files with 90 additions and 16 deletions

View File

@ -14,6 +14,10 @@
<meta name="twitter:title" content="Bitfielder - Bitfield Shader App"> <meta name="twitter:title" content="Bitfielder - Bitfield Shader App">
<meta name="twitter:description" content="Interactive bitfield shader editor for creating visual patterns using bitwise operations"> <meta name="twitter:description" content="Interactive bitfield shader editor for creating visual patterns using bitwise operations">
<style> <style>
:root {
--ui-opacity: 0.3;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -44,7 +48,7 @@
left: 0; left: 0;
right: 0; right: 0;
height: 40px; height: 40px;
background: rgba(0, 0, 0, 0.8); background: rgba(0, 0, 0, var(--ui-opacity));
border-bottom: 1px solid #333; border-bottom: 1px solid #333;
display: flex; display: flex;
align-items: center; align-items: center;
@ -86,11 +90,11 @@
left: 0; left: 0;
right: 0; right: 0;
height: 140px; height: 140px;
background: rgba(0, 0, 0, 0.6);
border-top: 1px solid rgba(255, 255, 255, 0.1);
display: flex; display: flex;
align-items: stretch;
gap: 10px;
padding: 10px;
z-index: 100; z-index: 100;
backdrop-filter: blur(5px);
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@ -99,14 +103,15 @@
bottom: 20px; bottom: 20px;
left: 20px; left: 20px;
right: 20px; right: 20px;
border-radius: 8px; padding: 5px;
border: 1px solid rgba(255, 255, 255, 0.2);
} }
#editor { #editor {
width: 100%; flex: 1;
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, var(--ui-opacity));
border: none; backdrop-filter: blur(2px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
color: #fff; color: #fff;
font-family: monospace; font-family: monospace;
font-size: 16px; font-size: 16px;
@ -116,10 +121,38 @@
transition: all 0.3s ease; transition: all 0.3s ease;
} }
#eval-btn {
background: rgba(0, 0, 0, var(--ui-opacity));
backdrop-filter: blur(2px);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #fff;
padding: 20px 30px;
font-family: monospace;
font-size: 16px;
font-weight: bold;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;
align-self: stretch;
}
#eval-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.4);
}
#eval-btn:active {
transform: scale(0.95);
}
#editor.minimal { #editor.minimal {
padding: 12px 15px; padding: 12px 15px;
font-size: 14px; font-size: 14px;
background: rgba(0, 0, 0, 0.3); }
#eval-btn.minimal {
padding: 10px 20px;
font-size: 14px;
} }
#help-popup { #help-popup {
@ -191,7 +224,7 @@
position: fixed; position: fixed;
top: 10px; top: 10px;
right: 10px; right: 10px;
background: rgba(0, 0, 0, 0.7); background: rgba(0, 0, 0, var(--ui-opacity));
border: 1px solid #555; border: 1px solid #555;
color: #fff; color: #fff;
padding: 8px 12px; padding: 8px 12px;
@ -213,11 +246,11 @@
right: -300px; right: -300px;
width: 300px; width: 300px;
height: 100vh; height: 100vh;
background: rgba(0, 0, 0, 0.9); background: rgba(0, 0, 0, calc(var(--ui-opacity) + 0.1));
border-left: 1px solid rgba(255, 255, 255, 0.1); border-left: 1px solid rgba(255, 255, 255, 0.1);
z-index: 90; z-index: 90;
transition: right 0.3s ease; transition: right 0.3s ease;
backdrop-filter: blur(10px); backdrop-filter: blur(3px);
overflow-y: auto; overflow-y: auto;
} }
@ -376,7 +409,7 @@
.shader-code { .shader-code {
padding: 8px 10px; padding: 8px 10px;
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, var(--ui-opacity));
color: #ccc; color: #ccc;
font-family: monospace; font-family: monospace;
font-size: 11px; font-size: 11px;
@ -555,6 +588,11 @@
<option value="rainbow">Rainbow</option> <option value="rainbow">Rainbow</option>
</select> </select>
</label> </label>
<label style="color: #ccc; font-size: 12px; margin-right: 10px;">
UI Opacity:
<input type="range" id="opacity-slider" min="10" max="100" value="30" style="width: 80px; vertical-align: middle;">
<span id="opacity-value" style="font-size: 11px;">30%</span>
</label>
<button id="help-btn">?</button> <button id="help-btn">?</button>
<button id="fullscreen-btn">Fullscreen</button> <button id="fullscreen-btn">Fullscreen</button>
<button id="hide-ui-btn">Hide UI</button> <button id="hide-ui-btn">Hide UI</button>
@ -566,6 +604,7 @@
<div id="editor-panel"> <div id="editor-panel">
<textarea id="editor" placeholder="Enter shader code... (x, y, t, i, mouseX, mouseY)" spellcheck="false">x^y</textarea> <textarea id="editor" placeholder="Enter shader code... (x, y, t, i, mouseX, mouseY)" spellcheck="false">x^y</textarea>
<button id="eval-btn">Eval</button>
</div> </div>
<div id="shader-library-trigger"></div> <div id="shader-library-trigger"></div>

View File

@ -11,6 +11,7 @@ interface AppSettings {
fps: number; fps: number;
lastShaderCode: string; lastShaderCode: string;
renderMode: string; renderMode: string;
uiOpacity?: number;
} }
export class Storage { export class Storage {
@ -97,7 +98,8 @@ export class Storage {
resolution: 1, resolution: 1,
fps: 30, fps: 30,
lastShaderCode: 'x^y', lastShaderCode: 'x^y',
renderMode: 'classic' renderMode: 'classic',
uiOpacity: 0.3
}; };
return stored ? { ...defaults, ...JSON.parse(stored) } : defaults; return stored ? { ...defaults, ...JSON.parse(stored) } : defaults;
} catch (error) { } catch (error) {
@ -106,7 +108,8 @@ export class Storage {
resolution: 1, resolution: 1,
fps: 30, fps: 30,
lastShaderCode: 'x^y', lastShaderCode: 'x^y',
renderMode: 'classic' renderMode: 'classic',
uiOpacity: 0.3
}; };
} }
} }

View File

@ -58,6 +58,9 @@ class BitfielderApp {
const resolutionSelect = document.getElementById('resolution-select') as HTMLSelectElement; const resolutionSelect = document.getElementById('resolution-select') as HTMLSelectElement;
const fpsSelect = document.getElementById('fps-select') as HTMLSelectElement; const fpsSelect = document.getElementById('fps-select') as HTMLSelectElement;
const renderModeSelect = document.getElementById('render-mode-select') as HTMLSelectElement; const renderModeSelect = document.getElementById('render-mode-select') as HTMLSelectElement;
const opacitySlider = document.getElementById('opacity-slider') as HTMLInputElement;
const opacityValue = document.getElementById('opacity-value')!;
const evalBtn = document.getElementById('eval-btn')!;
const helpPopup = document.getElementById('help-popup')!; const helpPopup = document.getElementById('help-popup')!;
const closeBtn = helpPopup.querySelector('.close-btn')!; const closeBtn = helpPopup.querySelector('.close-btn')!;
@ -76,6 +79,8 @@ class BitfielderApp {
resolutionSelect.addEventListener('change', () => this.updateResolution()); resolutionSelect.addEventListener('change', () => this.updateResolution());
fpsSelect.addEventListener('change', () => this.updateFPS()); fpsSelect.addEventListener('change', () => this.updateFPS());
renderModeSelect.addEventListener('change', () => this.updateRenderMode()); renderModeSelect.addEventListener('change', () => this.updateRenderMode());
opacitySlider.addEventListener('input', () => this.updateUIOpacity());
evalBtn.addEventListener('click', () => this.evalShader());
closeBtn.addEventListener('click', () => this.hideHelp()); closeBtn.addEventListener('click', () => this.hideHelp());
// Library events // Library events
@ -175,6 +180,7 @@ class BitfielderApp {
const topbar = document.getElementById('topbar')!; const topbar = document.getElementById('topbar')!;
const editorPanel = document.getElementById('editor-panel')!; const editorPanel = document.getElementById('editor-panel')!;
const editor = document.getElementById('editor')!; const editor = document.getElementById('editor')!;
const evalBtn = document.getElementById('eval-btn')!;
const showUiBtn = document.getElementById('show-ui-btn')!; const showUiBtn = document.getElementById('show-ui-btn')!;
if (this.uiVisible) { if (this.uiVisible) {
@ -182,12 +188,14 @@ class BitfielderApp {
topbar.classList.remove('hidden'); topbar.classList.remove('hidden');
editorPanel.classList.remove('minimal'); editorPanel.classList.remove('minimal');
editor.classList.remove('minimal'); editor.classList.remove('minimal');
evalBtn.classList.remove('minimal');
showUiBtn.style.display = 'none'; showUiBtn.style.display = 'none';
} else { } else {
// Hide topbar, make editor minimal // Hide topbar, make editor minimal
topbar.classList.add('hidden'); topbar.classList.add('hidden');
editorPanel.classList.add('minimal'); editorPanel.classList.add('minimal');
editor.classList.add('minimal'); editor.classList.add('minimal');
evalBtn.classList.add('minimal');
showUiBtn.style.display = 'block'; showUiBtn.style.display = 'block';
} }
@ -200,11 +208,13 @@ class BitfielderApp {
const topbar = document.getElementById('topbar')!; const topbar = document.getElementById('topbar')!;
const editorPanel = document.getElementById('editor-panel')!; const editorPanel = document.getElementById('editor-panel')!;
const editor = document.getElementById('editor')!; const editor = document.getElementById('editor')!;
const evalBtn = document.getElementById('eval-btn')!;
const showUiBtn = document.getElementById('show-ui-btn')!; const showUiBtn = document.getElementById('show-ui-btn')!;
topbar.classList.remove('hidden'); topbar.classList.remove('hidden');
editorPanel.classList.remove('minimal'); editorPanel.classList.remove('minimal');
editor.classList.remove('minimal'); editor.classList.remove('minimal');
evalBtn.classList.remove('minimal');
showUiBtn.style.display = 'none'; showUiBtn.style.display = 'none';
// Recalculate canvas size when UI is shown // Recalculate canvas size when UI is shown
@ -272,6 +282,22 @@ class BitfielderApp {
Storage.saveSettings({ renderMode }); Storage.saveSettings({ renderMode });
} }
private updateUIOpacity(): void {
const opacitySlider = document.getElementById('opacity-slider') as HTMLInputElement;
const opacityValue = document.getElementById('opacity-value')!;
const opacity = parseInt(opacitySlider.value) / 100;
document.documentElement.style.setProperty('--ui-opacity', opacity.toString());
opacityValue.textContent = `${opacitySlider.value}%`;
Storage.saveSettings({ uiOpacity: opacity });
}
private evalShader(): void {
this.shader.setCode(this.editor.value);
this.render();
}
private exportPNG(): void { private exportPNG(): void {
const link = document.createElement('a'); const link = document.createElement('a');
link.download = `bitfielder-${Date.now()}.png`; link.download = `bitfielder-${Date.now()}.png`;
@ -289,6 +315,12 @@ class BitfielderApp {
(document.getElementById('fps-select') as HTMLSelectElement).value = settings.fps.toString(); (document.getElementById('fps-select') as HTMLSelectElement).value = settings.fps.toString();
(document.getElementById('render-mode-select') as HTMLSelectElement).value = settings.renderMode || 'classic'; (document.getElementById('render-mode-select') as HTMLSelectElement).value = settings.renderMode || 'classic';
// Apply UI opacity
const opacity = settings.uiOpacity ?? 0.3;
document.documentElement.style.setProperty('--ui-opacity', opacity.toString());
(document.getElementById('opacity-slider') as HTMLInputElement).value = (opacity * 100).toString();
document.getElementById('opacity-value')!.textContent = `${Math.round(opacity * 100)}%`;
// Load last shader code if no URL hash // Load last shader code if no URL hash
if (!window.location.hash) { if (!window.location.hash) {
this.editor.value = settings.lastShaderCode; this.editor.value = settings.lastShaderCode;