JSFiddle - React, Tailwind, and code Playground

by Sharxxy

HTML

<div id="toolbar">
  <img src="https://via.placeholder.com/120x30?text=AquaMotion" alt="AquaMotion" id="logo">
  <button data-tool="pencil" class="active">✏️ Pencil</button>
  <button data-tool="eraser">🩹 Eraser</button>
  <button data-tool="line">➖ Line</button>
  <button data-tool="rect">⬛ Rectangle</button>
  <button data-tool="circle">⭕ Circle</button>
  Color: <input type="color" id="colorPicker" value="#00bfff">
  Size: <input type="range" id="brushSize" min="1" max="50" value="5">
  <button id="undo">↩️ Undo</button>
  <button id="redo">↪️ Redo</button>
  <button id="playPause">▶️ Play</button>
  FPS: <input type="number" id="fps" value="12" min="1" max="60">
  Canvas W: <input type="number" id="canvasWidth" value="800" min="100" max="2000">
  H: <input type="number" id="canvasHeight" value="600" min="100" max="2000">
</div>

<div id="main">
  <div id="canvasContainer">
    <canvas id="canvas" width="800" height="600"></canvas>
  </div>
  <div id="sidebar">
    <h3>Frames</h3>
    <div id="framesList"></div>
    <button id="addFrame">+ Add Frame</button>
    <button id="dupFrame">Duplicate Frame</button>
    <button id="delFrame">Delete Frame</button>
    <h3>Audio</h3>
    <input type="file" id="audioFile">
    Start (s): <input type="number" id="audioStart" value="0" min="0">
    Duration (s): <input type="number" id="audioDuration" value="1" min="0.1" step="0.1">
    <button id="addAudio">Add to Frame</button>
  </div>
</div>

CSS

body {margin:0; font-family:sans-serif; background:#0b1e3d; color:#fff;}
#toolbar {
  display:flex; align-items:center; gap:5px; padding:5px; background:linear-gradient(90deg,#1e3c72,#2a5298); border-bottom:2px solid #4fc3f7;
  flex-wrap: wrap;
}
#logo {height:30px; margin-right:10px;}
button {padding:5px 10px; background:rgba(255,255,255,0.1); border:none; color:#fff; border-radius:5px; cursor:pointer;}
button.active {background:#4fc3f7;}
button:hover {background:rgba(255,255,255,0.2);}
input[type=color], input[type=range], input[type=number], input[type=file]{background:rgba(255,255,255,0.1); color:#fff; border:none; border-radius:3px; padding:2px;}

#main {display:flex; height:calc(100vh - 60px);}
#canvasContainer {flex:3; display:flex; justify-content:center; align-items:center; background:#07213f; overflow:hidden; border-right:2px solid #4fc3f7;}
canvas {background:#0c2a50; border:1px solid #4fc3f7; cursor:crosshair;}

#sidebar {flex:1; background:linear-gradient(#0c3a6b,#0b1e3d); padding:10px; display:flex; flex-direction:column; gap:10px; overflow-y:auto; border-left:2px solid #4fc3f7;}
#framesList .frameItem {padding:5px; background:rgba(255,255,255,0.1); border-radius:3px; cursor:pointer; margin-bottom:3px;}
#framesList .frameItem.active {background:#4fc3f7;}
h3 {margin:5px 0; color:#4fc3f7;}

JavaScript

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

let currentTool = 'pencil';
let brushColor = '#00bfff';
let brushSize = 5;
let drawing = false;
let startPos = null;

let frames = [{strokes:[], audios:[]}]; // frames with vector strokes and audios
let currentFrame = 0;
let undoStack = [];
let redoStack = [];
let fps = 12;
let playInterval = null;

// DOM elements
const colorPicker = document.getElementById('colorPicker');
const brushSizeInput = document.getElementById('brushSize');
const fpsInput = document.getElementById('fps');
const canvasWidthInput = document.getElementById('canvasWidth');
const canvasHeightInput = document.getElementById('canvasHeight');
const framesList = document.getElementById('framesList');
const audioFileInput = document.getElementById('audioFile');
const audioStartInput = document.getElementById('audioStart');
const audioDurationInput = document.getElementById('audioDuration');
const addAudioBtn = document.getElementById('addAudio');

// --- Tool selection ---
document.querySelectorAll('#toolbar button[data-tool]').forEach(btn=>{
    btn.onclick = () => {
        currentTool = btn.dataset.tool;
        document.querySelectorAll('#toolbar button[data-tool]').forEach(b=>b.classList.remove('active'));
        btn.classList.add('active');
    }
});
colorPicker.onchange = e => brushColor = e.target.value;
brushSizeInput.oninput = e => brushSize = parseInt(e.target.value);

// --- Canvas size ---
canvasWidthInput.onchange = ()=>{canvas.width=parseInt(canvasWidthInput.value); redraw();}
canvasHeightInput.onchange = ()=>{canvas.height=parseInt(canvasHeightInput.value); redraw();}

// --- Drawing ---
canvas.addEventListener('mousedown', e=>{
    drawing=true;
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    startPos = {x,y};
    if(currentTool==='pencil'||currentTool==='eraser'){
...