better eval mechanism and transparency
This commit is contained in:
67
index.html
67
index.html
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/main.ts
32
src/main.ts
@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user