1073 lines
37 KiB
HTML
1073 lines
37 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>Bitfielder</title>
|
|
|
|
<!-- PWA Meta Tags -->
|
|
<link rel="manifest" href="/manifest.json">
|
|
<meta name="theme-color" content="#000000">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="apple-mobile-web-app-title" content="Bitfielder">
|
|
<link rel="apple-touch-icon" href="/icon-192.png">
|
|
<link rel="icon" type="image/png" sizes="192x192" href="/icon-192.png">
|
|
<link rel="icon" type="image/png" sizes="512x512" href="/icon-512.png">
|
|
<meta name="description" content="Bitfielder is a live bitfield shader editor for creating visual patterns using bitwise operations.">
|
|
<meta name="author" content="BuboBubo">
|
|
<meta name="keywords" content="shader, bitfield, visual, patterns, programming, interactive, editor">
|
|
<meta property="og:title" content="Bitfielder - Bitfield Shader App">
|
|
<meta property="og:description" content="Interactive bitfield shader editor for creating visual patterns using bitwise operations">
|
|
<meta property="og:type" content="website">
|
|
<meta name="twitter:card" content="summary">
|
|
<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">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--ui-opacity: 0.3;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
background: #000;
|
|
color: #fff;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
overflow: hidden;
|
|
touch-action: manipulation; /* Allow pan and zoom but disable double-tap zoom */
|
|
}
|
|
|
|
#canvas {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
image-rendering: pixelated;
|
|
image-rendering: -moz-crisp-edges;
|
|
image-rendering: crisp-edges;
|
|
touch-action: none; /* Disable all touch gestures on canvas for shader interaction */
|
|
pointer-events: auto; /* Allow canvas interactions */
|
|
}
|
|
|
|
#topbar {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 40px;
|
|
background: rgba(0, 0, 0, var(--ui-opacity));
|
|
border-bottom: 1px solid #333;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 20px;
|
|
z-index: 100;
|
|
pointer-events: auto; /* Ensure topbar can be clicked */
|
|
touch-action: manipulation; /* Allow normal touch interactions */
|
|
}
|
|
|
|
#topbar .title {
|
|
color: #fff;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
margin-right: 20px;
|
|
}
|
|
|
|
#topbar .controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-left: auto;
|
|
align-items: center;
|
|
}
|
|
|
|
#topbar .controls-desktop {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
|
|
#topbar .controls-mobile {
|
|
display: none;
|
|
gap: 8px;
|
|
align-items: center;
|
|
margin-left: auto;
|
|
}
|
|
|
|
#hamburger-menu {
|
|
display: none;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid #555;
|
|
color: #fff;
|
|
width: 36px;
|
|
height: 36px;
|
|
padding: 0;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
#hamburger-menu:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
#hamburger-menu svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
#mobile-menu {
|
|
position: fixed;
|
|
top: 40px;
|
|
right: -320px;
|
|
width: 320px;
|
|
max-width: 80vw;
|
|
height: calc(100vh - 40px);
|
|
background: rgba(0, 0, 0, var(--ui-opacity));
|
|
backdrop-filter: blur(3px);
|
|
border-left: 1px solid rgba(255, 255, 255, 0.1);
|
|
z-index: 150;
|
|
transition: right 0.3s ease;
|
|
overflow-y: auto;
|
|
padding: 20px;
|
|
pointer-events: auto; /* Ensure mobile menu can be clicked */
|
|
touch-action: manipulation; /* Allow normal touch interactions */
|
|
}
|
|
|
|
#mobile-menu.open {
|
|
right: 0;
|
|
}
|
|
|
|
#mobile-menu h3 {
|
|
color: #fff;
|
|
font-size: 16px;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 10px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.mobile-menu-section {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.mobile-menu-item {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.mobile-menu-item label {
|
|
display: block;
|
|
color: #ccc;
|
|
font-size: 12px;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.mobile-menu-item select,
|
|
.mobile-menu-item input[type="range"] {
|
|
width: 100%;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid #555;
|
|
color: #fff;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.mobile-menu-buttons {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.mobile-menu-buttons button {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid #555;
|
|
color: #fff;
|
|
padding: 12px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 14px;
|
|
text-align: left;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.mobile-menu-buttons button:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
#mobile-menu-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 149;
|
|
pointer-events: none; /* Don't block clicks when hidden */
|
|
}
|
|
|
|
#mobile-menu-overlay.open {
|
|
display: block;
|
|
pointer-events: auto; /* Only block clicks when visible */
|
|
}
|
|
|
|
#topbar button {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid #555;
|
|
color: #fff;
|
|
padding: 6px 12px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 12px;
|
|
}
|
|
|
|
#topbar button:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.icon-button {
|
|
width: 36px;
|
|
height: 36px;
|
|
padding: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.icon-button svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
/* Lucide icon styles */
|
|
[data-lucide] {
|
|
display: inline-block;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
button svg {
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Ensure all button contents don't intercept clicks */
|
|
button *, button svg, button [data-lucide] {
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
#editor-panel {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 140px;
|
|
display: flex;
|
|
align-items: stretch;
|
|
gap: 10px;
|
|
padding: 10px;
|
|
z-index: 100;
|
|
transition: all 0.3s ease;
|
|
pointer-events: auto; /* Ensure editor panel can be clicked */
|
|
touch-action: manipulation; /* Allow normal touch interactions */
|
|
}
|
|
|
|
#editor-panel.minimal {
|
|
height: 50px;
|
|
bottom: 20px;
|
|
left: 20px;
|
|
right: 20px;
|
|
padding: 5px;
|
|
}
|
|
|
|
#editor {
|
|
flex: 1;
|
|
background: rgba(0, 0, 0, var(--ui-opacity));
|
|
backdrop-filter: blur(2px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 4px;
|
|
color: #fff;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 16px;
|
|
padding: 15px;
|
|
resize: none;
|
|
outline: none;
|
|
transition: all 0.3s ease;
|
|
touch-action: manipulation; /* Allow normal touch interactions for text editing */
|
|
}
|
|
|
|
#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: 'IBM Plex Mono', 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 {
|
|
padding: 12px 15px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
#eval-btn.minimal {
|
|
padding: 10px 20px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
#help-popup {
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: rgba(0, 0, 0, 0.95);
|
|
border: 1px solid #555;
|
|
border-radius: 8px;
|
|
padding: 30px;
|
|
z-index: 1000;
|
|
max-width: 90vw;
|
|
width: 800px;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
display: none;
|
|
pointer-events: auto; /* Ensure help popup can be clicked */
|
|
touch-action: manipulation; /* Allow normal touch interactions */
|
|
}
|
|
|
|
#help-popup h3 {
|
|
margin-bottom: 20px;
|
|
color: #fff;
|
|
font-size: 18px;
|
|
text-align: center;
|
|
}
|
|
|
|
.help-content {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 30px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
#help-popup .help-section {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
#help-popup .help-section h4 {
|
|
color: #ccc;
|
|
margin-bottom: 10px;
|
|
font-size: 14px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
padding-bottom: 5px;
|
|
}
|
|
|
|
#help-popup .help-section p {
|
|
color: #999;
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
#help-popup .close-btn {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 15px;
|
|
background: none;
|
|
border: none;
|
|
color: #999;
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
#show-ui-btn {
|
|
position: fixed;
|
|
top: 10px;
|
|
right: 10px;
|
|
background: rgba(0, 0, 0, var(--ui-opacity));
|
|
border: 1px solid #555;
|
|
color: #fff;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 12px;
|
|
z-index: 1000;
|
|
display: none;
|
|
}
|
|
|
|
#show-ui-btn:hover {
|
|
background: rgba(0, 0, 0, 0.9);
|
|
}
|
|
|
|
#shader-library {
|
|
position: fixed;
|
|
top: 40px;
|
|
left: -300px;
|
|
width: 300px;
|
|
height: calc(100vh - 40px);
|
|
background: rgba(0, 0, 0, calc(var(--ui-opacity) + 0.1));
|
|
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
|
z-index: 90;
|
|
transition: left 0.3s ease;
|
|
backdrop-filter: blur(3px);
|
|
overflow-y: auto;
|
|
pointer-events: auto; /* Ensure shader library can be clicked */
|
|
touch-action: manipulation; /* Allow normal touch interactions */
|
|
}
|
|
|
|
|
|
#shader-library-trigger {
|
|
position: fixed;
|
|
top: 40px;
|
|
left: 0;
|
|
width: 20px;
|
|
height: calc(100vh - 40px);
|
|
z-index: 91;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#shader-library-trigger:hover + #shader-library,
|
|
#shader-library:hover {
|
|
left: 0;
|
|
}
|
|
|
|
#shader-library.open {
|
|
left: 0;
|
|
}
|
|
|
|
.library-header {
|
|
padding: 20px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.library-header h3 {
|
|
margin: 0 0 15px 0;
|
|
color: #fff;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.save-shader {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.search-shader {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.search-shader input {
|
|
width: 100%;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid #555;
|
|
color: #fff;
|
|
padding: 6px 8px;
|
|
border-radius: 4px;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.search-shader input::placeholder {
|
|
color: #999;
|
|
}
|
|
|
|
.save-shader input {
|
|
flex: 1;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid #555;
|
|
color: #fff;
|
|
padding: 6px 8px;
|
|
border-radius: 4px;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.save-shader button {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid #555;
|
|
color: #fff;
|
|
padding: 6px 12px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.save-shader button:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.shader-list {
|
|
padding: 0 20px 20px 20px;
|
|
}
|
|
|
|
.shader-item {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 4px;
|
|
margin-bottom: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.shader-item-header {
|
|
padding: 10px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.shader-item-header:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.shader-name {
|
|
color: #fff;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.shader-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
|
|
.shader-action {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
color: #ccc;
|
|
cursor: pointer;
|
|
font-size: 10px;
|
|
padding: 4px 6px;
|
|
border-radius: 3px;
|
|
transition: all 0.2s ease;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
}
|
|
|
|
.shader-action:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
color: #fff;
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.shader-action.rename {
|
|
background: rgba(52, 152, 219, 0.3);
|
|
border-color: rgba(52, 152, 219, 0.5);
|
|
}
|
|
|
|
.shader-action.rename:hover {
|
|
background: rgba(52, 152, 219, 0.5);
|
|
}
|
|
|
|
.shader-action.delete {
|
|
background: rgba(231, 76, 60, 0.3);
|
|
border-color: rgba(231, 76, 60, 0.5);
|
|
}
|
|
|
|
.shader-action.delete:hover {
|
|
background: rgba(231, 76, 60, 0.5);
|
|
}
|
|
|
|
.shader-code {
|
|
padding: 8px 10px;
|
|
background: rgba(0, 0, 0, var(--ui-opacity));
|
|
color: #ccc;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 11px;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
word-break: break-all;
|
|
}
|
|
|
|
#performance-warning {
|
|
position: fixed;
|
|
top: 50px;
|
|
right: 20px;
|
|
background: rgba(255, 0, 0, 0.8);
|
|
color: #fff;
|
|
padding: 10px 15px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
z-index: 1001;
|
|
display: none;
|
|
}
|
|
|
|
/* Responsive Design */
|
|
@media (max-width: 768px) {
|
|
#topbar .controls {
|
|
margin-left: auto;
|
|
}
|
|
|
|
#topbar .controls-desktop {
|
|
display: none;
|
|
}
|
|
|
|
#topbar .controls-mobile {
|
|
display: flex;
|
|
}
|
|
|
|
#hamburger-menu {
|
|
display: flex;
|
|
}
|
|
|
|
#topbar {
|
|
height: 40px;
|
|
padding: 0 10px;
|
|
}
|
|
|
|
#topbar .title {
|
|
margin-right: auto;
|
|
}
|
|
|
|
#topbar .controls {
|
|
flex-wrap: wrap;
|
|
gap: 5px;
|
|
margin-left: 0;
|
|
}
|
|
|
|
#topbar button {
|
|
padding: 4px 8px;
|
|
font-size: 11px;
|
|
}
|
|
|
|
#topbar label {
|
|
font-size: 11px !important;
|
|
margin-right: 5px !important;
|
|
}
|
|
|
|
#topbar select {
|
|
padding: 2px !important;
|
|
font-size: 11px !important;
|
|
}
|
|
|
|
#help-popup {
|
|
width: 95vw;
|
|
max-width: 95vw;
|
|
max-height: 90vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.help-content {
|
|
grid-template-columns: 1fr;
|
|
gap: 20px;
|
|
}
|
|
|
|
#editor-panel {
|
|
height: 120px;
|
|
}
|
|
|
|
#editor {
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
}
|
|
|
|
#shader-library {
|
|
width: 100%;
|
|
left: -100%;
|
|
top: 40px;
|
|
height: calc(100vh - 40px);
|
|
}
|
|
|
|
#shader-library-trigger {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
#topbar {
|
|
padding: 5px;
|
|
}
|
|
|
|
#topbar .title {
|
|
font-size: 12px;
|
|
}
|
|
|
|
#topbar button {
|
|
padding: 3px 6px;
|
|
font-size: 10px;
|
|
}
|
|
|
|
#topbar label {
|
|
font-size: 10px !important;
|
|
}
|
|
|
|
#topbar select {
|
|
font-size: 10px !important;
|
|
}
|
|
|
|
#help-popup {
|
|
padding: 15px;
|
|
}
|
|
|
|
#help-popup h3 {
|
|
font-size: 16px;
|
|
}
|
|
|
|
#help-popup .help-section h4 {
|
|
font-size: 13px;
|
|
}
|
|
|
|
#help-popup .help-section p {
|
|
font-size: 11px;
|
|
}
|
|
|
|
#editor-panel {
|
|
height: 100px;
|
|
}
|
|
|
|
#editor {
|
|
font-size: 12px;
|
|
padding: 8px;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1200px) {
|
|
.help-content {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="canvas"></canvas>
|
|
|
|
<button id="show-ui-btn">Show UI</button>
|
|
|
|
<div id="topbar">
|
|
<div class="title">Bitfielder</div>
|
|
<div class="controls">
|
|
<div class="controls-desktop">
|
|
<label style="color: #ccc; font-size: 12px; margin-right: 10px;">
|
|
Resolution:
|
|
<select id="resolution-select" style="background: rgba(255,255,255,0.1); border: 1px solid #555; color: #fff; padding: 4px; border-radius: 4px;">
|
|
<option value="1">Full (1x)</option>
|
|
<option value="2">Half (2x)</option>
|
|
<option value="4">Quarter (4x)</option>
|
|
<option value="8">Eighth (8x)</option>
|
|
<option value="16">Sixteenth (16x)</option>
|
|
<option value="32">Thirty-second (32x)</option>
|
|
</select>
|
|
</label>
|
|
<label style="color: #ccc; font-size: 12px; margin-right: 10px;">
|
|
FPS:
|
|
<select id="fps-select" style="background: rgba(255,255,255,0.1); border: 1px solid #555; color: #fff; padding: 4px; border-radius: 4px;">
|
|
<option value="15">15 FPS</option>
|
|
<option value="30" selected>30 FPS</option>
|
|
<option value="60">60 FPS</option>
|
|
</select>
|
|
</label>
|
|
<label style="color: #ccc; font-size: 12px; margin-right: 10px;">
|
|
Value Mode:
|
|
<select id="value-mode-select" style="background: rgba(255,255,255,0.1); border: 1px solid #555; color: #fff; padding: 4px; border-radius: 4px;">
|
|
<option value="integer" selected>Integer (0-255)</option>
|
|
<option value="float">Float (0.0-1.0)</option>
|
|
<option value="polar">Polar (angle-based)</option>
|
|
<option value="distance">Distance (radial)</option>
|
|
<option value="wave">Wave (ripple)</option>
|
|
</select>
|
|
</label>
|
|
<label style="color: #ccc; font-size: 12px; margin-right: 10px;">
|
|
Render Mode:
|
|
<select id="render-mode-select" style="background: rgba(255,255,255,0.1); border: 1px solid #555; color: #fff; padding: 4px; border-radius: 4px;">
|
|
<option value="classic" selected>Classic</option>
|
|
<option value="grayscale">Grayscale</option>
|
|
<option value="red">Red Channel</option>
|
|
<option value="green">Green Channel</option>
|
|
<option value="blue">Blue Channel</option>
|
|
<option value="rgb">RGB Split</option>
|
|
<option value="hsv">HSV</option>
|
|
<option value="rainbow">Rainbow</option>
|
|
<option value="thermal">Thermal</option>
|
|
<option value="neon">Neon</option>
|
|
<option value="cyberpunk">Cyberpunk</option>
|
|
<option value="vaporwave">Vaporwave</option>
|
|
<option value="dithered">Dithered</option>
|
|
<option value="palette">Palette</option>
|
|
</select>
|
|
</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="fullscreen-btn">Fullscreen</button>
|
|
<button id="hide-ui-btn">Hide UI</button>
|
|
<button id="random-btn">Random</button>
|
|
<button id="audio-btn">Enable Audio</button>
|
|
<button id="share-btn">Share</button>
|
|
<button id="export-png-btn">Export PNG</button>
|
|
</div>
|
|
<div class="controls-mobile">
|
|
<button id="library-btn-mobile" class="icon-button" aria-label="Shader Library"></button>
|
|
<button id="random-btn-mobile" class="icon-button" aria-label="Random"></button>
|
|
<button id="hide-ui-btn-mobile" class="icon-button" aria-label="Hide UI"></button>
|
|
<button id="hamburger-menu" class="icon-button" aria-label="Menu"></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="mobile-menu-overlay"></div>
|
|
<div id="mobile-menu">
|
|
<h3>Settings</h3>
|
|
<div class="mobile-menu-section">
|
|
<div class="mobile-menu-item">
|
|
<label>Resolution</label>
|
|
<select id="resolution-select-mobile">
|
|
<option value="1">Full (1x)</option>
|
|
<option value="2">Half (2x)</option>
|
|
<option value="4">Quarter (4x)</option>
|
|
<option value="8">Eighth (8x)</option>
|
|
<option value="16">Sixteenth (16x)</option>
|
|
<option value="32">Thirty-second (32x)</option>
|
|
</select>
|
|
</div>
|
|
<div class="mobile-menu-item">
|
|
<label>FPS</label>
|
|
<select id="fps-select-mobile">
|
|
<option value="15">15 FPS</option>
|
|
<option value="30" selected>30 FPS</option>
|
|
<option value="60">60 FPS</option>
|
|
</select>
|
|
</div>
|
|
<div class="mobile-menu-item">
|
|
<label>Value Mode</label>
|
|
<select id="value-mode-select-mobile">
|
|
<option value="integer" selected>Integer (0-255)</option>
|
|
<option value="float">Float (0.0-1.0)</option>
|
|
<option value="polar">Polar (angle-based)</option>
|
|
<option value="distance">Distance (radial)</option>
|
|
<option value="wave">Wave (ripple)</option>
|
|
</select>
|
|
</div>
|
|
<div class="mobile-menu-item">
|
|
<label>Render Mode</label>
|
|
<select id="render-mode-select-mobile">
|
|
<option value="classic" selected>Classic</option>
|
|
<option value="grayscale">Grayscale</option>
|
|
<option value="red">Red Channel</option>
|
|
<option value="green">Green Channel</option>
|
|
<option value="blue">Blue Channel</option>
|
|
<option value="rgb">RGB Split</option>
|
|
<option value="hsv">HSV</option>
|
|
<option value="rainbow">Rainbow</option>
|
|
<option value="thermal">Thermal</option>
|
|
<option value="neon">Neon</option>
|
|
<option value="cyberpunk">Cyberpunk</option>
|
|
<option value="vaporwave">Vaporwave</option>
|
|
<option value="dithered">Dithered</option>
|
|
<option value="palette">Palette</option>
|
|
</select>
|
|
</div>
|
|
<div class="mobile-menu-item">
|
|
<label>UI Opacity: <span id="opacity-value-mobile">30%</span></label>
|
|
<input type="range" id="opacity-slider-mobile" min="10" max="100" value="30">
|
|
</div>
|
|
</div>
|
|
<div class="mobile-menu-section">
|
|
<div class="mobile-menu-buttons">
|
|
<button id="help-btn-mobile"><span class="icon"></span> Help</button>
|
|
<button id="fullscreen-btn-mobile"><span class="icon"></span> Fullscreen</button>
|
|
<button id="audio-btn-mobile"><span class="icon"></span> Enable Audio</button>
|
|
<button id="share-btn-mobile"><span class="icon"></span> Share</button>
|
|
<button id="export-png-btn-mobile"><span class="icon"></span> Export PNG</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="editor-panel">
|
|
<textarea id="editor" placeholder="Enter shader code... (x, y, t, i, mouseX, mouseY, mousePressed, touchCount, accelX, audioLevel, bassLevel...)" spellcheck="false">x^y</textarea>
|
|
<button id="eval-btn">Eval</button>
|
|
</div>
|
|
|
|
<div id="shader-library-trigger"></div>
|
|
<div id="shader-library">
|
|
<div class="library-header">
|
|
<h3>Shader Library</h3>
|
|
<div class="save-shader">
|
|
<input type="text" id="shader-name-input" placeholder="Shader name..." maxlength="30">
|
|
<button id="save-shader-btn">Save</button>
|
|
</div>
|
|
<div class="search-shader">
|
|
<input type="text" id="shader-search-input" placeholder="Search shaders...">
|
|
</div>
|
|
</div>
|
|
<div class="shader-list" id="shader-list">
|
|
<!-- Saved shaders will appear here -->
|
|
</div>
|
|
</div>
|
|
|
|
<div id="help-popup">
|
|
<button class="close-btn">×</button>
|
|
<h3>Bitfielder Help</h3>
|
|
|
|
<div class="help-content">
|
|
<div class="help-section">
|
|
<h4>Keyboard Shortcuts</h4>
|
|
<p><strong>Ctrl+Enter</strong> - Execute shader code</p>
|
|
<p><strong>F11</strong> - Toggle fullscreen</p>
|
|
<p><strong>H</strong> - Hide/show UI</p>
|
|
<p><strong>R</strong> - Generate random shader</p>
|
|
<p><strong>S</strong> - Share current shader (copy URL)</p>
|
|
<p><strong>?</strong> - Show this help</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Variables</h4>
|
|
<p><strong>x, y</strong> - Pixel coordinates</p>
|
|
<p><strong>t</strong> - Time (enables animation)</p>
|
|
<p><strong>i</strong> - Pixel index</p>
|
|
<p><strong>mouseX, mouseY</strong> - Mouse position (0.0 to 1.0)</p>
|
|
<p><strong>mousePressed</strong> - Mouse button down (true/false)</p>
|
|
<p><strong>mouseVX, mouseVY</strong> - Mouse velocity</p>
|
|
<p><strong>mouseClickTime</strong> - Time since last click (ms)</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Touch & Gestures</h4>
|
|
<p><strong>touchCount</strong> - Number of active touches</p>
|
|
<p><strong>touch0X, touch0Y</strong> - Primary touch position</p>
|
|
<p><strong>touch1X, touch1Y</strong> - Secondary touch position</p>
|
|
<p><strong>pinchScale</strong> - Pinch zoom scale factor</p>
|
|
<p><strong>pinchRotation</strong> - Pinch rotation angle</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Device Motion</h4>
|
|
<p><strong>accelX, accelY, accelZ</strong> - Accelerometer data</p>
|
|
<p><strong>gyroX, gyroY, gyroZ</strong> - Gyroscope rotation rates</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Audio Reactive</h4>
|
|
<p><strong>audioLevel</strong> - Overall audio volume (0.0-1.0)</p>
|
|
<p><strong>bassLevel</strong> - Low frequencies (0.0-1.0)</p>
|
|
<p><strong>midLevel</strong> - Mid frequencies (0.0-1.0)</p>
|
|
<p><strong>trebleLevel</strong> - High frequencies (0.0-1.0)</p>
|
|
<p>Click "Enable Audio" to activate microphone</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Operators</h4>
|
|
<p><strong>^ & |</strong> - XOR, AND, OR</p>
|
|
<p><strong><< >></strong> - Bit shift left/right</p>
|
|
<p><strong>+ - * / %</strong> - Math operations</p>
|
|
<p><strong>== != < ></strong> - Comparisons (return 0/1)</p>
|
|
<p><strong>? :</strong> - Ternary operator (condition ? true : false)</p>
|
|
<p><strong>~ **</strong> - Bitwise NOT, exponentiation</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Math Functions</h4>
|
|
<p><strong>sin, cos, tan</strong> - Trigonometric functions</p>
|
|
<p><strong>abs, sqrt, pow</strong> - Absolute, square root, power</p>
|
|
<p><strong>floor, ceil, round</strong> - Rounding functions</p>
|
|
<p><strong>min, max</strong> - Minimum and maximum</p>
|
|
<p><strong>random</strong> - Random number 0-1</p>
|
|
<p><strong>log, exp</strong> - Natural logarithm, exponential</p>
|
|
<p><strong>PI, E</strong> - Math constants</p>
|
|
<p>Use without Math. prefix: <code>sin(x)</code> not <code>Math.sin(x)</code></p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Value Modes</h4>
|
|
<p><strong>Integer (0-255):</strong> Traditional mode for large values</p>
|
|
<p><strong>Float (0.0-1.0):</strong> Bitfield shader mode, inverts and clamps values</p>
|
|
<p><strong>Polar (angle-based):</strong> Spiral patterns combining angle and radius</p>
|
|
<p><strong>Distance (radial):</strong> Concentric wave rings with variable frequency</p>
|
|
<p><strong>Wave (ripple):</strong> Multi-source interference with amplitude falloff</p>
|
|
<p>Each mode transforms your expression differently!</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Advanced Features</h4>
|
|
<p><strong>Array indexing:</strong> <code>[1,2,4,8][floor(t%4)]</code></p>
|
|
<p><strong>Complex expressions:</strong> <code>x>y ? sin(x) : cos(y)</code></p>
|
|
<p><strong>Nested functions:</strong> <code>pow(sin(x), abs(y-x))</code></p>
|
|
<p><strong>Logical operators:</strong> <code>x&&y</code>, <code>x||y</code></p>
|
|
<p>No character or length limits - use any JavaScript!</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Shader Library</h4>
|
|
<p>Hover over the <strong>right edge</strong> of the screen to access the shader library</p>
|
|
<p>Save shaders with custom names and search through them</p>
|
|
<p>Use <strong>edit</strong> to rename, <strong>del</strong> to delete</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Render Modes</h4>
|
|
<p><strong>Classic</strong> - Original colorful mode</p>
|
|
<p><strong>Grayscale</strong> - Black and white</p>
|
|
<p><strong>Red/Green/Blue</strong> - Single color channels</p>
|
|
<p><strong>HSV</strong> - Hue-based coloring</p>
|
|
<p><strong>Rainbow</strong> - Spectrum coloring</p>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h4>Export</h4>
|
|
<p><strong>Export PNG</strong> - Save current frame as image</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="help-section" style="grid-column: 1 / -1; margin-top: 20px; text-align: center; padding-top: 20px; border-bottom: none;">
|
|
<h4>About</h4>
|
|
<p><strong>Bitfielder</strong> - Interactive bitfield shader editor</p>
|
|
<p>Created by <strong>BuboBubo</strong> (Raphaël Forment)</p>
|
|
<p>Website: <a href="https://raphaelforment.fr" target="_blank" style="color: #4A9EFF;">raphaelforment.fr</a></p>
|
|
<p>Source: <a href="https://git.raphaelforment.fr" target="_blank" style="color: #4A9EFF;">git.raphaelforment.fr</a></p>
|
|
<p>License: <strong>AGPL 3.0</strong></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="performance-warning">
|
|
Performance warning: Shader taking too long to render!
|
|
</div>
|
|
|
|
<script type="module" src="/src/main.ts"></script>
|
|
|
|
<!-- PWA Service Worker Registration -->
|
|
<script>
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', () => {
|
|
navigator.serviceWorker.register('/sw.js')
|
|
.then(registration => {
|
|
console.log('SW registered: ', registration);
|
|
})
|
|
.catch(registrationError => {
|
|
console.log('SW registration failed: ', registrationError);
|
|
});
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|