Spaces:
Running
Running
Develop an interactive Mandala Art Maker web application featuring a circular canvas with radial symmetry drawing capabilities. The interface should include a prominent color palette with vibrant neon options positioned at the right of the screen, alongside essential drawing tools. Incorporate various geometric shapes (circles, triangles, squares) that automatically replicate in a circular pattern while drawing. Core functionalities must include: Download PNG button for saving artwork, Clear Canvas option to start fresh, Undo/Redo buttons for easy mistake correction, and a brush size slider to adjust stroke width. The drawing mechanism should work in a circular pattern, automatically mirroring strokes across multiple axes to create symmetrical mandala designs. Ensure the tool maintains smooth performance and provides real-time feedback as users create their mandala artwork. | |
Not close, this is what i am loooking, but only HTML, CSS, JS, I can paste i wordpress, import React, { useRef, useState } from "react"; // Neon palette const NEON_COLORS = [ "#39FF14", // neon green "#FF2079", // neon pink "#00FFF7", // neon cyan "#FFFB00", // neon yellow "#FF00F7", // neon magenta "#FF8C00", // neon orange "#00FFD1", // aqua neon "#F72585", // deep pinkish ]; // Shape options const SHAPES = [ { name: "Freehand", value: "freehand" }, { name: "Circle", value: "circle" }, { name: "Square", value: "square" }, { name: "Triangle", value: "triangle" }, ]; const MANDALA_SYMMETRY = 6; // 6-way by default function MandalaArtMaker() { // Brush const [brushColor, setBrushColor] = useState(NEON_COLORS[0]); const [brushSize, setBrushSize] = useState(6); // Shape const [shapeType, setShapeType] = useState("freehand"); // Draw state const [isDrawing, setIsDrawing] = useState(false); const [curStroke, setCurStroke] = useState([]); const [startPt, setStartPt] = useState(null); const [history, setHistory] = useState([]); const [redoStack, setRedoStack] = useState([]); // Canvas ref const svgRef = useRef(null); // Sizing const CANVAS_SIZE = 500; const center = { x: CANVAS_SIZE / 2, y: CANVAS_SIZE / 2 }; // --- Drawing Operations --- // Normalize coordinates to center const toCenter = (x, y) => ({ x: x - center.x, y: y - center.y, }); const fromCenter = (x, y) => ({ x: x + center.x, y: y + center.y, }); // For symmetry: rotate a point (cx, cy) by theta (in radians), returns {x, y} function rotatePoint(cx, cy, theta) { return { x: cx * Math.cos(theta) - cy * Math.sin(theta), y: cx * Math.sin(theta) + cy * Math.cos(theta), }; } // Mouse events function handlePointerDown(e) { const rect = svgRef.current.getBoundingClientRect(); const x = (e.touches?.[0]?.clientX || e.clientX) - rect.left; const y = (e.touches?.[0]?.clientY || e.clientY) - rect.top; setIsDrawing(true); setRedoStack([]); // clear redo on new stroke setCurStroke([{ x, y }]); setStartPt({ x, y }); } function handlePointerMove(e) { if (!isDrawing) return; const rect = svgRef.current.getBoundingClientRect(); const x = (e.touches?.[0]?.clientX || e.clientX) - rect.left; const y = (e.touches?.[0]?.clientY || e.clientY) - rect.top; setCurStroke((s) => [...s, { x, y }]); } function handlePointerUp() { if (!isDrawing || !curStroke.length) return; // Store stroke in history setHistory((h) => [ ...h, { points: curStroke, color: brushColor, size: brushSize, symmetry: MANDALA_SYMMETRY, shape: shapeType, start: startPt, end: curStroke[curStroke.length - 1], }, ]); setCurStroke([]); setStartPt(null); setIsDrawing(false); } // Undo/Redo/Clear function undo() { setHistory((h) => { if (!h.length) return h; setRedoStack((r) => [h[h.length - 1], ...r]); return h.slice(0, h.length - 1); }); } function redo() { setRedoStack((r) => { if (!r.length) return r; setHistory((h) => [...h, r[0]]); return r.slice(1); }); } function clearCanvas() { setHistory([]); setRedoStack([]); setCurStroke([]); setIsDrawing(false); } // Download PNG function downloadPNG() { const svg = svgRef.current; const serializer = new XMLSerializer(); const source = serializer.serializeToString(svg); // Add xmlns if missing const svg64 = btoa( unescape(encodeURIComponent(source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"'))) ); const image64 = `data:image/svg+xml;base64,${svg64}`; // Create a hidden canvas to draw the SVG const canvas = document.createElement("canvas"); canvas.width = CANVAS_SIZE; canvas.height = CANVAS_SIZE; const ctx = canvas.getContext("2d"); const img = new window.Image(); img.onload = function () { ctx.drawImage(img, 0, 0); const link = document.createElement("a"); link.download = "mandala.png"; link.href = canvas.toDataURL("image/png"); link.click(); }; img.src = image64; } // --- Symmetric Drawing with SVG --- // Draw a shape n-times around the center, each time rotated function renderSymmetricShape(stroke) { const reps = []; const thetaStep = (2 * Math.PI) / stroke.symmetry; if (stroke.shape === "freehand") { // Replicate the freehand stroke around the center for (let i = 0; i < stroke.symmetry; ++i) { const theta = thetaStep * i; let pathD = ""; stroke.points.forEach((pt, idx) => { // Center const local = toCenter(pt.x, pt.y); const rotated = rotatePoint(local.x, local.y, theta); const drawPt = fromCenter(rotated.x, rotated.y); pathD += idx === 0 ? `M ${drawPt.x} ${drawPt.y}` : ` L ${drawPt.x} ${drawPt.y}`; }); reps.push( <path key={i} d={pathD} stroke={stroke.color} strokeWidth={stroke.size} strokeLinecap="round" strokeLinejoin="round" fill="none" style={{ filter: `drop-shadow(0 0 6px ${stroke.color})`, transition: "all 0.15s", }} /> ); } return reps; } // Shapes - treat as "line" from start to end const { x: x1, y: y1 } = toCenter(stroke.start.x, stroke.start.y); const { x: x2, y: y2 } = toCenter(stroke.end.x, stroke.end.y); for (let i = 0; i < stroke.symmetry; ++i) { const theta = thetaStep * i; const s = rotatePoint(x1, y1, theta); const e = rotatePoint(x2, y2, theta); const p1 = fromCenter(s.x, s.y); const p2 = fromCenter(e.x, e.y); if (stroke.shape === "circle") { // Circle: center is start, radius = distance to end const r = Math.hypot(e.x - s.x, e.y - s.y); reps.push( <circle key={i} cx={p1.x} cy={p1.y} r={r} stroke={stroke.color} strokeWidth={stroke.size} fill="none" style={{ filter: `drop-shadow(0 0 6px ${stroke.color})`, transition: "all 0.15s", }} /> ); } else if (stroke.shape === "square") { // Square: center = start, size = distance to end * sqrt(2) const dx = p2.x - p1.x; const dy = p2.y - p1.y; const size = Math.hypot(dx, dy) * Math.sqrt(2); reps.push( <rect key={i} x={p1.x - size / 2} y={p1.y - size / 2} width={size} height={size} stroke={stroke.color} strokeWidth={stroke.size} fill="none" rx={size * 0.13} ry={size * 0.13} style={{ filter: `drop-shadow(0 0 6px ${stroke.color})`, transition: "all 0.15s", }} /> ); } else if (stroke.shape === "triangle") { // Equilateral: center=start, point to end // Get vector from s to e const dx = e.x - s.x; const dy = e.y - s.y; const len = Math.hypot(dx, dy); const angleStart = Math.atan2(dy, dx); // Points for triangle const pts = []; for (let k = 0; k < 3; ++k) { const angle = angleStart + (2 * Math.PI * k) / 3; const pt = { x: p1.x + len * Math.cos(angle), y: p1.y + len * Math.sin(angle), }; pts.push(pt); } reps.push( <polygon key={i} points={pts.map((pt) => `${pt.x},${pt.y}`).join(" ")} stroke={stroke.color} strokeWidth={stroke.size} fill="none" style={{ filter: `drop-shadow(0 0 6px ${stroke.color})`, transition: "all 0.15s", }} /> ); } } return reps; } // --- UI Rendering --- // ------------------------ // Render current stroke const liveStroke = isDrawing && curStroke.length ? renderSymmetricShape({ points: curStroke, color: brushColor, size: brushSize, symmetry: MANDALA_SYMMETRY, shape: shapeType, start: startPt ?? curStroke[0], end: curStroke[curStroke.length - 1], }) : null; // Accessibility: Keyboard controls for undo, redo, clear function handleKeyDown(e) { if ((e.metaKey || e.ctrlKey) && e.key === "z") { // Undo undo(); e.preventDefault(); } else if ((e.metaKey || e.ctrlKey) && e.key === "y") { // Redo redo(); e.preventDefault(); } else if (e.key === "Delete" || e.key === "Backspace") { clearCanvas(); e.preventDefault(); } } // --- Main Render --- return ( <div className="min-h-screen flex flex-col items-center justify-center bg-neutral-900 dark:bg-black py-10" tabIndex={0} onKeyDown={handleKeyDown} aria-label="Mandala Art Maker Main App" style={{ outline: "none" }} > {/* Top Bar: Title & Palette */} <header className="mb-6 w-full max-w-xl flex flex-col md:flex-row items-start md:items-center justify-between"> <div className="flex-1 mb-4 md:mb-0"> <h1 className="text-2xl sm:text-3xl font-bold tracking-tight text-shadow-md" style={{ color: "#00FFF7", textShadow: "0px 0px 12px #00FFF7cc, 0px 0px 4px #fff", }} > Mandala Art Maker </h1> <span className="block text-sm font-medium text-gray-300 opacity-80 mt-0.5"> Draw neon geometric mandalas. Undo/Redo, download as PNG, and more. </span> </div> <nav className="flex items-center gap-4 bg-neutral-950 dark:bg-neutral-900 px-3 py-2 rounded-2xl shadow-inner shadow-cyan-400/5 border border-neutral-800" aria-label="Pick color" > {NEON_COLORS.map((col, idx) => ( <button key={col} aria-label={`Select neon color ${idx + 1}`} className={`transition-all rounded-full border-2 outline-none focus-visible:ring ring-cyan-300 ${brushColor === col ? "scale-110 border-neutral-200 shadow-[0_0_16px_"+col+"]" : "border-transparent"} `} style={{ width: 28, height: 28, background: col, boxShadow: `0 0 8px ${col}`, marginRight: idx !== NEON_COLORS.length - 1 ? 6 : 0, }} onClick={() => setBrushColor(col)} tabIndex={0} ></button> ))} </nav> </header> <section className="mb-4 flex flex-col md:flex-row gap-2 w-full max-w-xl" aria-label="Drawing shape, brush & controls" > {/* Shape chooser */} <select aria-label="Choose shape" value={shapeType} onChange={(e) => setShapeType(e.target.value)} className="bg-neutral-800 text-white px-4 py-2 rounded-xl font-semibold shadow shadow-cyan-400/10 caret-cyan-400 focus:ring-2 focus:ring-cyan-300 transition focus:outline-none" > {SHAPES.map((s) => ( <option key={s.value} value={s.value}> {s.name} </option> ))} </select> <div className="flex items-center bg-neutral-800 px-4 py-2 rounded-xl shadow shadow-cyan-400/10" role="group" aria-label="Brush size control" > <span className="mr-3 text-sm text-gray-300">Brush</span> <input type="range" min={2} max={24} value={brushSize} onChange={(e) => setBrushSize(+e.target.value)} step={1} aria-label="Brush size" className="accent-cyan-400 w-24" /> <span className="ml-2 text-cyan-300 text-sm" style={{ minWidth: 16, fontVariantNumeric: "tabular-nums" }} > {brushSize} </span> </div> {/* Controls */} <div className="flex w-full justify-end gap-2 ml-auto"> <button aria-label="Undo" disabled={!history.length} className={`px-3 py-2 rounded-xl font-bold shadow transition-all ${history.length ? "text-cyan-300 hover:bg-neutral-800 active:bg-neutral-700" : "text-neutral-800 cursor-not-allowed"} `} onClick={undo} tabIndex={0} > ⬅️ Undo </button> <button aria-label="Redo" disabled={!redoStack.length} className={`px-3 py-2 rounded-xl font-bold shadow transition-all ${redoStack.length ? "text-cyan-300 hover:bg-neutral-800 active:bg-neutral-700" : "text-neutral-800 cursor-not-allowed"} `} onClick={redo} tabIndex={0} > ➡️ Redo </button> <button aria-label="Clear canvas" className="px-3 py-2 rounded-xl bg-pink-500/90 text-white font-bold shadow-lg hover:bg-pink-600 active:opacity-60 transition-all focus:ring focus:ring-pink-300" onClick={clearCanvas} tabIndex={0} > 🗑️ Clear </button> <button aria-label="Download as PNG" className="px-3 py-2 rounded-xl bg-cyan-400 text-neutral-900 font-bold shadow hover:bg-cyan-300 active:opacity-80 transition-all focus:ring focus:ring-cyan-200" onClick={downloadPNG} tabIndex={0} > ⬇️ Download </button> </div> </section> {/* Main Canvas */} <main className="bg-neutral-950 dark:bg-black shadow-lg rounded-3xl flex items-center justify-center p-4" style={{ border: "3px solid #00fff799", boxShadow: "0 0 24px #00fff766, 0 0 16px #39FF14aa, 0 0 0 12px #00FFF733", }} > <svg ref={svgRef} width={CANVAS_SIZE} height={CANVAS_SIZE} style={{ display: "block", background: "#11151c radial-gradient(circle, #191c25 50%, #11151c 95%)", borderRadius: "40px", touchAction: "none", cursor: shapeType === "freehand" ? "crosshair" : "pointer", boxShadow: "0 0 64px #0ff5, 0 0 5px #fff2", }} onPointerDown={handlePointerDown} onPointerMove={handlePointerMove} onPointerUp={handlePointerUp} onPointerLeave={handlePointerUp} onTouchStart={handlePointerDown} onTouchMove={handlePointerMove} onTouchEnd={handlePointerUp} tabIndex={0} aria-label="Drawing area" > {/* Mandala symmetry guides */} {[...Array(MANDALA_SYMMETRY)].map((_, i) => { const theta = (2 * Math.PI * i) / MANDALA_SYMMETRY; const x2 = center.x + Math.cos(theta) * (CANVAS_SIZE / 2); const y2 = center.y + Math.sin(theta) * (CANVAS_SIZE / 2); return ( <line key={i} x1={center.x} y1={center.y} x2={x2} y2={y2} stroke="#2dfffc33" strokeWidth={1.5} strokeDasharray="10 7" style={{ pointerEvents: "none" }} /> ); })} {/* Past strokes */} {history.map((stroke, i) => ( <g key={i}>{renderSymmetricShape(stroke)}</g> ))} {/* Live stroke */} {liveStroke} {/* Center point for ref */} <circle cx={center.x} cy={center.y} r={5} fill="#fff" fillOpacity={0.23} stroke="#00fff7" strokeWidth={2} style={{ pointerEvents: "none" }} /> </svg> </main> {/* Instructions */} <div className="max-w-xl mt-6 text-center text-md text-zinc-400 opacity-70 px-4"> <b>Draw creative neon mandala art!</b> <br /> <span className="text-cyan-400">Tip:</span> Hold <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>Z</kbd> for undo, <kbd>Y</kbd> for redo, or <kbd>Delete</kbd> to clear canvas. Choose shapes, brush, and neon colors above. </div> </div> ); } export default MandalaArtMaker; | |
IT ahould allow continue drawing, as i select another color and my prevous drawig is gon. | |
not working, need toolbar in left, allow mixing multiple colors, | |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Neon Mandala Creator</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> body { background-color: #0a0a1a; color: white; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 1rem; } canvas { background-color: #0a0a0a !important; border: 2px solid #333; border-radius: 12px; box-shadow: 0 0 30px rgba(0, 150, 255, 0.2); max-width: 100%; height: auto; display: block; } .color { width: 30px; height: 30px; border-radius: 50%; cursor: pointer; transition: all 0.3s; position: relative; overflow: hidden; box-shadow: 0 0 5px currentColor; } .color::after { content: ''; position: absolute; top: -10px; left: -10px; right: -10px; bottom: -10px; background: currentColor; opacity: 0.3; filter: blur(5px); z-index: -1; } .color:hover { transform: scale(1.15); box-shadow: 0 0 15px currentColor; } .symmetry-shape { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 6px; background-color: #1a1a2e; border: 1px solid #2a2a4a; transition: all 0.2s; color: #6a6a8a; } .symmetry-shape:hover, .symmetry-shape.active { background-color: #3b82f6; color: white; transform: scale(1.05); box-shadow: 0 0 10px rgba(59, 130, 246, 0.5); } .tool-btn { padding: 10px 16px; border-radius: 8px; background-color: #1a1a2e; border: 1px solid #2a2a4a; cursor: pointer; transition: all 0.2s; font-size: 14px; display: flex; align-items: center; justify-content: center; gap: 8px; color: white !important; } .tool-btn:hover { background-color: #3b82f6; color: white; box-shadow: 0 0 10px rgba(59, 130, 246, 0.5); } .sidebar { background-color: #16213e; border: 1px solid #2a3a6a; box-shadow: 0 0 20px rgba(0, 100, 255, 0.2); border-radius: 12px; } .title { color: white; font-weight: 600; font-size: 1.5rem; background: linear-gradient(90deg, #3b82f6, #8b5cf6); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; } .section-title { color: #e2e8f0; font-weight: 500; font-size: 1rem; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; } .section-title i { color: #3b82f6; } .back-link { font-family: 'Arial', sans-serif; font-weight: bold; font-size: 1.2rem; text-decoration: none; color: #00ffff; text-shadow: 0 0 5px #00ffff, 0 0 10px #00ffff; margin-bottom: 20px; transition: all 0.3s; display: inline-block; padding: 5px 15px; border-radius: 5px; background: rgba(0, 255, 255, 0.1); border: 1px solid rgba(0, 255, 255, 0.3); } .back-link:hover { text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff; transform: scale(1.05); background: rgba(0, 255, 255, 0.2); } @media (max-width: 768px) { .flex-col-on-mobile { flex-direction: column; } canvas { width: 100%; height: 300px; } .sidebar { width: 100%; } .action-buttons { flex-direction: row !important; justify-content: space-between; } .tool-btn { width: 48%; } } </style> </head> <body> <a href="https://niftytechfinds.com" class="back-link"> <i class="fas fa-arrow-left"></i> Return to Niftytechfinds </a> <div class="flex flex-col md:flex-row gap-6 w-full max-w-6xl flex-col-on-mobile"> <!-- Canvas --> <div class="flex-1 flex justify-center"> <canvas id="canvas" width="600" height="600"></canvas> </div> <!-- Right Sidebar --> <div class="w-full md:w-80 sidebar p-6 flex flex-col gap-6"> <div class="flex items-center gap-3 mb-4"> <i class="fas fa-magic text-2xl text-blue-400"></i> <h2 class="title">Neon Mandala Creator</h2> </div> <!-- Color Picker --> <div> <h3 class="section-title"> <i class="fas fa-palette"></i> Neon Colors </h3> <div class="grid grid-cols-4 gap-3"> <div class="color" style="color: #ff00ff; background-color: #ff00ff;" data-color="#ff00ff" title="Electric Pink"></div> <div class="color" style="color: #00ffff; background-color: #00ffff;" data-color="#00ffff" title="Cyan Burst"></div> <div class="color" style="color: #ff5500; background-color: #ff5500;" data-color="#ff5500" title="Neon Orange"></div> <div class="color" style="color: #55ff00; background-color: #55ff00;" data-color="#55ff00" title="Lime Zap"></div> <div class="color" style="color: #ff00aa; background-color: #ff00aa;" data-color="#ff00aa" title="Pink Pulse"></div> <div class="color" style="color: #aa00ff; background-color: #aa00ff;" data-color="#aa00ff" title="Purple Haze"></div> <div class="color" style="color: #00ffaa; background-color: #00ffaa;" data-color="#00ffaa" title="Mint Flash"></div> <div class="color" style="color: #ffaa00; background-color: #ffaa00;" data-color="#ffaa00" title="Amber Glow"></div> <div class="color" style="color: #ff0055; background-color: #ff0055;" data-color="#ff0055" title="Ruby Beam"></div> <div class="color" style="color: #5500ff; background-color: #5500ff;" data-color="#5500ff" title="Royal Beam"></div> <div class="color" style="color: #00ff55; background-color: #00ff55;" data-color="#00ff55" title="Emerald Shock"></div> <div class="color" style="color: #ff55ff; background-color: #ff55ff;" data-color="#ff55ff" title="Magenta Blast"></div> </div> <div class="mt-4"> <h3 class="section-title"> <i class="fas fa-eyedropper"></i> Custom Color </h3> <input type="color" id="customColor" value="#ffff00" class="w-full h-10 cursor-pointer rounded-lg border border-[#2a3a6a] bg-[#1a1a2e]"> </div> </div> <!-- Brush Size --> <div> <h3 class="section-title"> <i class="fas fa-paint-brush"></i> Brush Size </h3> <input type="range" min="1" max="20" value="4" id="brushSize" class="w-full mb-2 accent-[#3b82f6]"> <div class="flex justify-between text-sm text-gray-400"> <span>Small</span> <span>Large</span> </div> </div> <!-- Symmetry Shapes --> <div> <h3 class="section-title"> <i class="fas fa-shapes"></i> Symmetry </h3> <div class="grid grid-cols-3 gap-3"> <div class="symmetry-shape" data-symmetry="4" title="4-fold"> <i class="fas fa-square"></i> </div> <div class="symmetry-shape" data-symmetry="6" title="6-fold"> <i class="fas fa-hexagon"></i> </div> <div class="symmetry-shape" data-symmetry="8" title="8-fold"> <i class="fas fa-star"></i> </div> <div class="symmetry-shape" data-symmetry="12" title="12-fold"> <i class="fas fa-sun"></i> </div> <div class="symmetry-shape active" data-symmetry="16" title="16-fold"> <i class="fas fa-infinity"></i> </div> <div class="symmetry-shape" data-symmetry="24" title="24-fold"> <i class="fas fa-circle"></i> </div> </div> </div> <!-- Actions --> <div class="mt-auto flex flex-col gap-3 action-buttons"> <button onclick="clearCanvas()" class="tool-btn bg-red-600 hover:bg-red-700"> <i class="fas fa-trash-alt"></i> Clear Canvas </button> <button onclick="downloadCanvas()" class="tool-btn bg-blue-600 hover:bg-blue-700"> <i class="fas fa-download"></i> Save Artwork </button> </div> </div> </div> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let drawing = false; let brushSize = 4; let symmetry = 16; // Default to 16-fold symmetry let color = '#00ffff'; let prev = { x: 0, y: 0 }; const center = { x: canvas.width / 2, y: canvas.height / 2 }; // Initialize canvas with black background function initCanvas() { ctx.fillStyle = '#0a0a0a'; ctx.fillRect(0, 0, canvas.width, canvas.height); } // Set canvas size based on device function resizeCanvas() { const container = canvas.parentElement; const size = Math.min(container.clientWidth, container.clientHeight, 600); canvas.width = size; canvas.height = size; center.x = canvas.width / 2; center.y = canvas.height / 2; initCanvas(); } // Initial resize and initialization resizeCanvas(); window.addEventListener('resize', resizeCanvas); function drawSymmetric(x, y, px, py) { const angle = (2 * Math.PI) / symmetry; const dx = x - center.x; const dy = y - center.y; const pdx = px - center.x; const pdy = py - center.y; ctx.save(); ctx.translate(center.x, center.y); for (let i = 0; i < symmetry; i++) { ctx.rotate(angle); ctx.beginPath(); ctx.moveTo(pdx, pdy); ctx.lineTo(dx, dy); ctx.strokeStyle = color; ctx.lineWidth = brushSize; ctx.lineCap = 'round'; ctx.shadowColor = color; ctx.shadowBlur = brushSize/2; ctx.stroke(); } ctx.restore(); } // Touch support function getPosition(e) { if (e.type.includes('touch')) { const touch = e.touches[0] || e.changedTouches[0]; const rect = canvas.getBoundingClientRect(); return { x: touch.clientX - rect.left, y: touch.clientY - rect.top }; } else { const rect = canvas.getBoundingClientRect(); return { x: e.clientX - rect.left, y: e.clientY - rect.top }; } } function startDrawing(e) { drawing = true; prev = getPosition(e); e.preventDefault(); // Prevent scrolling on touch devices } function stopDrawing() { drawing = false; } function draw(e) { if (!drawing) return; const pos = getPosition(e); drawSymmetric(pos.x, pos.y, prev.x, prev.y); prev = pos; e.preventDefault(); // Prevent scrolling on touch devices } // Mouse events canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseleave', stopDrawing); canvas.addEventListener('mousemove', draw); // Touch events canvas.addEventListener('touchstart', startDrawing); canvas.addEventListener('touchend', stopDrawing); canvas.addEventListener('touchcancel', stopDrawing); canvas.addEventListener('touchmove', draw); // Color selection document.querySelectorAll('.color').forEach(el => { el.addEventListener('click', () => { color = el.getAttribute('data-color'); document.getElementById('customColor').value = color; // Show selected color feedback document.querySelectorAll('.color').forEach(c => c.style.transform = 'scale(1)'); el.style.transform = 'scale(1.2)'; }); }); document.getElementById('customColor').addEventListener('input', e => { color = e.target.value; // Reset color selection when custom color is chosen document.querySelectorAll('.color').forEach(c => c.style.transform = 'scale(1)'); }); // Brush size selection document.getElementById('brushSize').addEventListener('input', e => { brushSize = e.target.value; }); // Symmetry selection document.querySelectorAll('.symmetry-shape').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.symmetry-shape').forEach(b => b.classList.remove('active')); btn.classList.add('active'); symmetry = parseInt(btn.getAttribute('data-symmetry')); }); }); function clearCanvas() { ctx.fillStyle = '#0a0a0a'; ctx.fillRect(0, 0, canvas.width, canvas.height); } function downloadCanvas() { // Create a temporary canvas to ensure black background in download const tempCanvas = document.createElement('canvas'); tempCanvas.width = canvas.width; tempCanvas.height = canvas.height; const tempCtx = tempCanvas.getContext('2d'); // Fill with black background tempCtx.fillStyle = '#0a0a0a'; tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); // Draw the original canvas content tempCtx.drawImage(canvas, 0, 0); // Create download link const link = document.createElement('a'); link.download = 'neon-mandala-' + new Date().toISOString().slice(0, 10) + '.png'; link.href = tempCanvas.toDataURL('image/png'); link.click(); } // Initialize canvas on load window.addEventListener('load', initCanvas); </script> </body> </html> In the earlier ones, Imprve the colors and blend mode, shape, rest all fine. | |
No Use my main ap but keep pattern from what you draw earlier | |
Add these colors const NEON_COLORS = [ "#39FF14", // neon green "#FF2079", // neon pink "#00FFF7", // neon cyan "#FFFB00", // neon yellow "#FF00F7", // neon magenta "#FF8C00", // neon orange "#00FFD1", // aqua neon "#F72585", // deep pinkish | |
We are targeting to create a database where use can calculate almost everything related to finance, such as Calculate LTV, DTI, DSR RAtion, and all other finance related tools and formula inblended in CSS. | |
On right side can we add amortizaion table, also in LTV tab a visual showing debt over value of property in inforgraphic, do similiar for all tabs |