AquaMotion — Upgraded
by Sharxxy
HTML
<div id="toolbar">
<img id="logo" src="https://via.placeholder.com/160x30?text=AquaMotion" alt="AquaMotion logo">
<button class="btn tool active" data-tool="pencil">✏️ Pencil (B)</button>
<button class="btn tool" data-tool="eraser">🩹 Eraser (E)</button>
<button class="btn tool" data-tool="line">➖ Line</button>
<button class="btn tool" data-tool="rect">⬛ Rectangle</button>
<button class="btn tool" data-tool="circle">⭕ Circle</button>
<label class="mutedText">Color:</label><input type="color" id="colorPicker" value="#00bfff">
<label class="mutedText">Size:</label><input type="range" id="brushSize" min="1" max="60" value="5">
<button class="btn" id="undo">↩️ Undo</button>
<button class="btn" id="redo">↪️ Redo</button>
<button class="btn" id="playPause">▶️ Play (Space)</button>
<label class="mutedText">FPS:</label><input type="number" id="fps" value="12" min="1" max="60" style="width:60px;">
<label class="mutedText">W:</label><input type="number" id="canvasWidth" value="800" min="100" max="2000" style="width:80px;">
<label class="mutedText">H:</label><input type="number" id="canvasHeight" value="600" min="100" max="2000" style="width:80px;">
<button class="btn" id="resizeCanvas">Resize</button>
</div>
<div id="main">
<div id="canvasColumn">
<div id="canvasWrap">
<div id="canvasContainer"></div>
</div>
<div id="controlsRow">
<div class="small">Onion: <label><input type="checkbox" id="onionPrev" checked> Prev</label> <label><input type="checkbox" id="onionNext"> Next</label></div>
<div class="small">Onion Opacity: <input type="range" id="onionOpacity" min="0.05" max="0.9" step="0.05" value="0.45"></div>
<div style="flex:1"></div>
<button class="btn" id="addLayer">+ Layer</button>
<button class="btn" id="delLayer">− Layer</button>
</div>
</div>
<aside id="sidebar">
<h3>Frames</h3>
<div id="framesList"></div>
<div...
CSS
/* ---------- THEME / LAYOUT ---------- */
:root{
--accent: #4fc3f7;
--bg-1: #0b1e3d;
--bg-2: #07213f;
--panel: #0c3a6b;
--muted: rgba(255,255,255,0.08);
--glass: rgba(255,255,255,0.06);
--white: #ffffff;
}
html,body{height:100%; margin:0; font-family:Inter,system-ui,Segoe UI,Roboto,"Helvetica Neue",Arial; background:var(--bg-1); color:var(--white); -webkit-font-smoothing:antialiased;}
#toolbar{display:flex;align-items:center;gap:8px;padding:8px;background:linear-gradient(90deg,#1e3c72,#2a5298);border-bottom:2px solid var(--accent);flex-wrap:wrap;}
#logo{height:30px;}
.btn{padding:7px 10px;background:var(--glass);border:none;color:var(--white);border-radius:6px;cursor:pointer;}
.btn.active{background:var(--accent); color:#042033;}
input[type=color], input[type=range], input[type=number], select, input[type=file]{background:var(--glass);color:var(--white);border:none;border-radius:4px;padding:5px;}
#main{display:flex;height:calc(100vh - 64px);}
#canvasColumn{flex:3;display:flex;flex-direction:column;gap:8px;padding:12px;background:var(--bg-2);border-right:2px solid var(--accent);box-sizing:border-box;}
#canvasWrap{flex:1;display:flex;justify-content:center;align-items:center; background: #04122a;border:1px solid rgba(255,255,255,0.03);position:relative;overflow:hidden;}
#canvasContainer{position:relative; width:fit-content;height:fit-content;}
.layerCanvas{position:absolute;left:0;top:0; image-rendering:pixelated; border:1px solid var(--muted); box-shadow:0 6px 30px rgba(0,0,0,0.6);}
#controlsRow{display:flex;gap:8px;align-items:center;}
#sidebar{flex:1;background:linear-gradient(#0c3a6b,#0b1e3d);padding:12px;display:flex;flex-direction:column;gap:10px;overflow-y:auto;border-left:2px solid var(--accent);box-sizing:border-box;}
h3{margin:6px 0;color:var(--accent);font-size:14px;}
...
JavaScript
/* ===========================
AquaMotion — core app logic
=========================== */
const canvasContainer = document.getElementById('canvasContainer');
const canvasWidthInput = document.getElementById('canvasWidth');
const canvasHeightInput = document.getElementById('canvasHeight');
const resizeCanvasBtn = document.getElementById('resizeCanvas');
let app = {
width: parseInt(canvasWidthInput.value,10) || 800,
height: parseInt(canvasHeightInput.value,10) || 600,
currentFrame: 0,
frames: [], // each frame: { layers: [ImageBitmap|dataURL], audios: [{url, start, duration}], layerCount, layerMeta: [{visible, name}], undoStacks: [[],...], redoStacks: [...] }
currentLayerIndex: 0,
playing:false,
fps: 12,
playIntervalId: null,
tool: 'pencil',
color: '#00bfff',
size: 5,
onionPrev: true,
onionNext: false,
onionOpacity: 0.45,
};
/* ---------- UTIL ---------- */
function createEmptyImageData(w,h){
const off = document.createElement('canvas');
off.width = w; off.height = h;
const ctx = off.getContext('2d');
ctx.clearRect(0,0,w,h);
return off.toDataURL();
}
function dataURLToImage(dataURL){
return new Promise((res,rej)=>{
const img = new Image();
img.onload = ()=>res(img);
img.onerror = rej;
img.src = dataURL;
});
}
function makeCanvasLayer(w,h, zIndex=1){
const c = document.createElement('canvas');
c.className = 'layerCanvas';
c.width = w;
c.height = h;
c.style.width = w + 'px';
c.style.height = h + 'px';
c.style.zIndex = zIndex;
return c;
}
/* ---------- INITIAL FRAME SETUP ---------- */
function newEmptyFrame(layerCount=2){
const layers = [];
const layerMeta = [];
const undoStacks = [];
const redoStacks = [];
for(let i=0;i<layerCount;i++){
layers.push(createEmptyImageData(app.width, app.height));
layerMeta.push({visible:true, name: 'Layer ' + (i+1)});
undoStacks.push([]);
redoStacks.push([]);
}
...