{"id":12,"date":"2026-09-04T15:09:42","date_gmt":"2026-09-04T15:09:42","guid":{"rendered":"https:\/\/ragbunnie.site\/?page_id=12"},"modified":"2026-09-04T15:12:18","modified_gmt":"2026-09-04T15:12:18","slug":"ragbunnie","status":"publish","type":"page","link":"https:\/\/ragbunnie.site\/?page_id=12","title":{"rendered":"Ragbunnie"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n<title>RagBunnie Online Drawing Canvas<\/title>\n<style>\n  :root{\n    --bg: #141414;\n    --panel: #1e1e1e;\n    --panel-hover: #2a2a2a;\n    --accent: #ffffff;\n    --text: #c0c0c0;\n    --text-dim: #707070;\n    --border: #2c2c2c;\n    --panel-w: 240px;\n    --layers-w: 230px;\n  }\n  *{box-sizing:border-box;}\n  html,body{\n    margin:0; padding:0; height:100%; overflow:hidden;\n    background:var(--bg); color:var(--text);\n    font-family:-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n    user-select:none; -webkit-user-select:none;\n  }\n  #app{\n    position:relative; width:100vw; height:100vh;\n    display:flex; flex-direction:row;\n  }\n\n  \/* --- Preset Modal --- *\/\n  #presetModal {\n    position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;\n    background: rgba(0, 0, 0, 0.85); display: flex; align-items: center;\n    justify-content: center; z-index: 100; backdrop-filter: blur(5px);\n  }\n  .modal-content {\n    background: var(--panel); border: 1px solid var(--border);\n    padding: 30px; border-radius: 12px; width: 400px; max-width: 90vw;\n    text-align: center; box-shadow: 0 10px 30px rgba(0,0,0,0.5);\n  }\n  .modal-title {\n    font-size: 16px; font-weight: 600; color: var(--accent); margin-bottom: 8px;\n    text-transform: uppercase; letter-spacing: 1px;\n  }\n  .modal-desc {\n    font-size: 12px; color: var(--text-dim); margin-bottom: 24px;\n  }\n  .preset-options {\n    display: flex; flex-direction: column; gap: 12px;\n  }\n  .preset-btn {\n    padding: 14px; border-radius: 8px; border: 1px solid var(--border);\n    background: transparent; color: var(--text); cursor: pointer;\n    font-size: 13px; font-weight: 500; transition: all .2s;\n    display: flex; flex-direction: column; align-items: center; gap: 4px;\n  }\n  .preset-btn:hover {\n    background: var(--panel-hover); border-color: var(--accent); color: var(--accent);\n    transform: translateY(-2px);\n  }\n  .preset-btn span {\n    font-size: 10.5px; color: var(--text-dim); font-weight: normal;\n  }\n\n  \/* --- Monochrome Sidebar --- *\/\n  #panel{\n    position:relative; height:100vh; width:var(--panel-w);\n    min-width:var(--panel-w);\n    background:var(--panel); border-right:1px solid var(--border);\n    padding:16px; overflow-y:auto;\n    transition:margin-left .22s ease, opacity .18s ease;\n    z-index: 25;\n  }\n  #panel.collapsed{\n    margin-left:calc(var(--panel-w) * -1);\n    opacity:0;\n    pointer-events:none;\n  }\n\n  #menuToggle{\n    position:absolute; top:12px; left:12px; z-index:30;\n    width:36px; height:36px; border-radius:8px;\n    background:var(--panel); border:1px solid var(--border);\n    color:var(--text); cursor:pointer;\n    display:flex; align-items:center; justify-content:center;\n    transition:background .15s;\n  }\n  #menuToggle:hover{background:var(--panel-hover); color:var(--accent);}\n\n  \/* --- Layers Panel (Right) --- *\/\n  #layersPanel{\n    position:relative; height:100vh; width:var(--layers-w);\n    min-width:var(--layers-w);\n    background:var(--panel); border-left:1px solid var(--border);\n    padding:16px; overflow-y:auto;\n    transition:margin-right .22s ease, opacity .18s ease;\n    z-index: 25;\n  }\n  #layersPanel.collapsed{\n    margin-right:calc(var(--layers-w) * -1);\n    opacity:0;\n    pointer-events:none;\n  }\n  #layersToggle{\n    position:absolute; top:12px; right:12px; z-index:30;\n    width:36px; height:36px; border-radius:8px;\n    background:var(--panel); border:1px solid var(--border);\n    color:var(--text); cursor:pointer;\n    display:flex; align-items:center; justify-content:center;\n    transition:background .15s;\n  }\n  #layersToggle:hover{background:var(--panel-hover); color:var(--accent);}\n\n  #canvasWrap{\n    position:relative; flex:1 1 auto; height:100vh; overflow:hidden;\n  }\n  canvas{\n    position:absolute; top:0; left:0;\n    touch-action:none; background:#ffffff;\n    width:100%; height:100%;\n  }\n\n  .section{margin-bottom:20px;}\n  .section-title{\n    font-size:10px; text-transform:uppercase; letter-spacing:1px;\n    color:var(--text-dim); margin-bottom:8px; font-weight:600;\n  }\n  \n  .group{margin-bottom:12px;}\n  .group label{\n    display:flex; justify-content:space-between; font-size:11.5px;\n    margin-bottom:4px; color:var(--text);\n  }\n  .group span.val{color:var(--accent); font-weight:500;}\n\n  input[type=range]{\n    width:100%; appearance:none; height:4px; background:var(--border);\n    border-radius:2px; outline:none; accent-color:var(--accent); cursor:pointer;\n  }\n  \n  .color-row {\n    display: flex; gap: 6px; align-items: center;\n  }\n  input[type=color]{\n    -webkit-appearance:none; border:none; width:32px; height:32px;\n    border-radius:6px; cursor:pointer; background:none; padding:0;\n  }\n  input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }\n  input[type=color]::-webkit-color-swatch { border: 1px solid var(--border); border-radius: 6px; }\n\n  .row{display:flex; gap:6px;}\n  .row button{flex:1;}\n\n  button.tool{\n    padding:8px; border-radius:6px; border:1px solid var(--border);\n    background:transparent; color:var(--text-dim); cursor:pointer;\n    display:flex; align-items:center; justify-content:center;\n    transition:all .15s;\n  }\n  button.tool svg{ width:16px; height:16px; fill:currentColor; }\n  button.tool:hover{background:var(--panel-hover); color:var(--text); border-color:var(--text-dim);}\n  button.tool.active{\n    background:var(--accent); border-color:var(--accent); color:#000000;\n  }\n\n  .checkbox-group {\n    display: flex; align-items: center; gap: 8px; font-size: 11.5px; cursor: pointer; margin-top: 4px; color: var(--text);\n  }\n  .checkbox-group input { cursor: pointer; accent-color: var(--accent); }\n\n  hr{border:none; border-top:1px solid var(--border); margin:16px 0;}\n  \n  #coords{\n    position:absolute; bottom:10px; right:14px; z-index:5;\n    font-size:10px; color:var(--text-dim); background:rgba(20,20,20,.8);\n    padding:3px 6px; border-radius:4px; pointer-events:none;\n    border: 1px solid var(--border);\n  }\n\n  #layersList{ display:flex; flex-direction:column; gap:8px; }\n  .layer-item{\n    border:1px solid var(--border); border-radius:8px; padding:8px;\n    background:transparent; cursor:pointer; transition:border-color .15s, background .15s;\n  }\n  .layer-item:hover{ background:var(--panel-hover); }\n  .layer-item.active{ border-color:var(--accent); background:var(--panel-hover); }\n\n  .layer-row{ display:flex; align-items:center; gap:6px; margin-bottom:6px; }\n  .layer-row .minibtn{\n    width:22px; height:22px; flex:none; border-radius:5px; border:1px solid var(--border);\n    background:transparent; color:var(--text-dim); cursor:pointer;\n    display:flex; align-items:center; justify-content:center;\n  }\n  .layer-row .minibtn:hover{ background:var(--panel-hover); color:var(--text); }\n  .layer-row .minibtn svg{ width:12px; height:12px; fill:currentColor; }\n  .layer-row .minibtn.off{ color:var(--text-dim); opacity:.4; }\n\n  .layer-name{\n    flex:1; min-width:0; background:transparent; border:none; outline:none;\n    color:var(--text); font-size:11.5px; padding:2px 0; border-bottom:1px solid transparent;\n  }\n  .layer-name:focus{ border-bottom:1px solid var(--text-dim); }\n\n  .blend-select{\n    width:100%; background:#141414; color:var(--text); border:1px solid var(--border);\n    border-radius:5px; font-size:10.5px; padding:4px 6px; margin-bottom:6px; cursor:pointer;\n  }\n\n  .opacity-row{ display:flex; align-items:center; gap:6px; }\n  .opacity-row input[type=range]{ flex:1; }\n  .opacity-row span{ font-size:10px; color:var(--accent); width:30px; text-align:right; flex:none; }\n\n  #addLayerBtn{\n    width:100%; margin-top:4px; padding:8px; border-radius:6px; border:1px dashed var(--border);\n    background:transparent; color:var(--text-dim); cursor:pointer; font-size:11.5px;\n    display:flex; align-items:center; justify-content:center; gap:6px;\n  }\n  #addLayerBtn:hover{ background:var(--panel-hover); color:var(--text); border-color:var(--text-dim); }\n  #addLayerBtn svg{ width:13px; height:13px; fill:currentColor; }\n\n  @media (max-aspect-ratio: 1\/1){\n    #panel{\n      position:absolute; top:0; left:0; z-index:15;\n      box-shadow:4px 0 24px rgba(0,0,0,.6);\n    }\n    #panel.collapsed{ margin-left:calc(var(--panel-w) * -1); }\n    #layersPanel{\n      position:absolute; top:0; right:0; z-index:15;\n      box-shadow:-4px 0 24px rgba(0,0,0,.6);\n    }\n    #layersPanel.collapsed{ margin-right:calc(var(--layers-w) * -1); }\n  }\n<\/style>\n<\/head>\n<body>\n\n<div id=\"presetModal\">\n  <div class=\"modal-content\">\n    <div class=\"modal-title\">Initial Setup<\/div>\n    <div class=\"modal-desc\">Select a performance mode to begin drawing according to your device&#8217;s capabilities.<\/div>\n    <div class=\"preset-options\">\n      <button class=\"preset-btn\" data-preset=\"quality\">\n        Quality Preset\n        <span>High canvas resolution and dense stroke precision<\/span>\n      <\/button>\n      <button class=\"preset-btn\" data-preset=\"normal\">\n        Normal\n        <span>Balanced and standard configuration<\/span>\n      <\/button>\n      <button class=\"preset-btn\" data-preset=\"performance\">\n        Performance Preset (Android Recommended)\n        <span>Maximum fluidity and GPU optimization<\/span>\n      <\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<div id=\"app\">\n\n  <button id=\"menuToggle\" title=\"Menu\">\n    <svg viewBox=\"0 0 24 24\"><path d=\"M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z\"\/><\/svg>\n  <\/button>\n\n  <button id=\"layersToggle\" title=\"Layers\">\n    <svg viewBox=\"0 0 24 24\"><path d=\"M12 2 1 8l11 6 11-6-11-6zm0 8.53L4.24 8 12 3.47 19.76 8 12 10.53zM1 13.5l11 6 11-6-1.8-.98L12 17.4 2.8 12.52 1 13.5zm0 4.5 11 6 11-6-1.8-.98L12 21.9 2.8 17.02 1 18z\"\/><\/svg>\n  <\/button>\n\n  <div id=\"panel\" class=\"collapsed\">\n    <div style=\"height: 28px;\"><\/div>\n\n    <div class=\"section\" style=\"margin-top:12px;\">\n      <div class=\"section-title\">Tools<\/div>\n      <div class=\"row\" style=\"margin-bottom:6px;\">\n        <button class=\"tool active\" id=\"btnDraw\" data-tool=\"draw\" title=\"Brush\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z\"\/><\/svg>\n        <\/button>\n        <button class=\"tool\" id=\"btnBlend\" data-tool=\"blend\" title=\"Smudge Mixer\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z\"\/><\/svg>\n        <\/button>\n        <button class=\"tool\" id=\"btnEraser\" data-tool=\"eraser\" title=\"Eraser\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M16.24 3.56l4.95 4.94c.78.79.78 2.05 0 2.84L12 20.53a4.008 4.008 0 0 1-5.66 0L2.81 17c-.78-.79-.78-2.05 0-2.84l10.6-10.6c.79-.78 2.05-.78 2.83 0M4.25 15.58L9.5 20.83c.78.78 2.05.78 2.83 0L17 16.33 12.67 12l-8.42 3.58z\"\/><\/svg>\n        <\/button>\n      <\/div>\n      <div class=\"row\">\n        <button class=\"tool\" id=\"btnShape\" data-tool=\"shape\" title=\"Shapes (Straight &#038; Curved)\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM11 7h2v2h-2zm0 4h2v2h-2zm-4 0h2v2H7zm8 0h2v2h-2zm-4 4h2v2h-2z\"\/><\/svg>\n        <\/button>\n        <button class=\"tool\" id=\"btnLasso\" data-tool=\"lasso\" title=\"Area Fill \/ Lasso\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\/><\/svg>\n        <\/button>\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionColor\">\n      <div class=\"section-title\">Color<\/div>\n      <div class=\"color-row\">\n        <input type=\"color\" id=\"colorPicker\" value=\"#111111\">\n        <span class=\"val\" id=\"colorVal\" style=\"font-size: 11px; font-family: monospace;\">#111111<\/span>\n      <\/div>\n    <\/div>\n\n    <hr>\n\n    <div class=\"section\" id=\"sectionBrush\">\n      <div class=\"section-title\">Brush Settings<\/div>\n      \n      <div class=\"group\">\n        <label>Tip Shape \/ Pattern<\/label>\n        <div class=\"row\" style=\"margin-bottom:6px;\">\n          <button class=\"tool active\" data-brush=\"circle\" title=\"Circle\">\u25cf<\/button>\n          <button class=\"tool\" data-brush=\"square\" title=\"Square\">\u25a0<\/button>\n          <button class=\"tool\" data-brush=\"diamond\" title=\"Diamond\">\u25c6<\/button>\n          <button class=\"tool\" data-brush=\"star\" title=\"Star\">\u2605<\/button>\n        <\/div>\n        <button class=\"tool\" id=\"btnCustomBrush\" style=\"width:100%; font-size:11.5px;\" title=\"Load custom tip (Max 60x60px)\">\n          Load Custom Pattern&#8230;\n        <\/button>\n        <input type=\"file\" id=\"customBrushInput\" accept=\"image\/*\" style=\"display:none;\">\n      <\/div>\n\n      <div class=\"group\">\n        <label class=\"checkbox-group\">\n          <input type=\"checkbox\" id=\"chkRotate\"> Rotate pattern with direction\n        <\/label>\n      <\/div>\n\n      <div class=\"group\">\n        <label>Taper \/ Expand Ends <span class=\"val\" id=\"taperVal\">0%<\/span><\/label>\n        <input type=\"range\" id=\"taper\" min=\"-100\" max=\"100\" value=\"0\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Spacing <span class=\"val\" id=\"spaceVal\">15%<\/span><\/label>\n        <input type=\"range\" id=\"spacing\" min=\"5\" max=\"100\" value=\"15\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Smoothing <span class=\"val\" id=\"stabVal\">40%<\/span><\/label>\n        <input type=\"range\" id=\"stab\" min=\"0\" max=\"99\" value=\"40\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Base Size <span class=\"val\" id=\"widthVal\">6 px<\/span><\/label>\n        <input type=\"range\" id=\"width\" min=\"1\" max=\"150\" value=\"6\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Pressure Sensitivity <span class=\"val\" id=\"pressVal\">50%<\/span><\/label>\n        <input type=\"range\" id=\"pressureSens\" min=\"0\" max=\"100\" value=\"50\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Velocity Dynamics <span class=\"val\" id=\"velVal\">0<\/span><\/label>\n        <input type=\"range\" id=\"velocity\" min=\"-200\" max=\"200\" value=\"0\">\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionShape\" style=\"display:none;\">\n      <div class=\"section-title\">Shape Settings<\/div>\n      <div class=\"group\">\n        <label>Stroke Type<\/label>\n        <div class=\"row\">\n          <button class=\"tool active\" id=\"btnShapeLine\" title=\"Straight Line\">Line<\/button>\n          <button class=\"tool\" id=\"btnShapeCurve\" title=\"Rounded Curve\">Bezier Curve<\/button>\n        <\/div>\n      <\/div>\n      <div class=\"group\">\n        <label>Shape Thickness <span class=\"val\" id=\"shapeWidthVal\">6 px<\/span><\/label>\n        <input type=\"range\" id=\"shapeWidth\" min=\"1\" max=\"150\" value=\"6\">\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionBlend\" style=\"display:none;\">\n      <div class=\"section-title\">Blend Settings<\/div>\n      <div class=\"group\">\n        <label>Mix Intensity <span class=\"val\" id=\"blendStrengthVal\">50%<\/span><\/label>\n        <input type=\"range\" id=\"blendStrength\" min=\"1\" max=\"100\" value=\"50\">\n      <\/div>\n      <div class=\"group\">\n        <label>Mixer Size <span class=\"val\" id=\"blendWidthVal\">20 px<\/span><\/label>\n        <input type=\"range\" id=\"blendWidth\" min=\"1\" max=\"150\" value=\"20\">\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionEraser\" style=\"display:none;\">\n      <div class=\"section-title\">Eraser Settings<\/div>\n      <div class=\"group\">\n        <label>Eraser Size <span class=\"val\" id=\"eraserWidthVal\">20 px<\/span><\/label>\n        <input type=\"range\" id=\"eraserWidth\" min=\"1\" max=\"150\" value=\"20\">\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionLasso\" style=\"display:none;\">\n      <div class=\"section-title\">Lasso Settings<\/div>\n      <div class=\"group\">\n        <label>Fill Mode<\/label>\n        <div class=\"row\">\n          <button class=\"tool active\" id=\"btnFillSolid\" title=\"Solid\">Solid<\/button>\n          <button class=\"tool\" id=\"btnFillHalftone\" title=\"Halftone\">Halftone<\/button>\n        <\/div>\n      <\/div>\n      <div class=\"group\" id=\"halftoneSettings\" style=\"display:none; margin-top: 8px;\">\n        <label>Dot Size <span class=\"val\" id=\"dotSizeVal\">4 px<\/span><\/label>\n        <input type=\"range\" id=\"dotSize\" min=\"2\" max=\"15\" value=\"4\">\n        \n        <label style=\"margin-top:6px;\">Grid Spacing <span class=\"val\" id=\"dotSpaceVal\">12 px<\/span><\/label>\n        <input type=\"range\" id=\"dotSpace\" min=\"6\" max=\"30\" value=\"12\">\n      <\/div>\n    <\/div>\n\n    <hr>\n\n    <div class=\"section\">\n      <div class=\"section-title\">Actions<\/div>\n      <div class=\"row\">\n        <button class=\"tool\" id=\"btnUndo\" title=\"Undo\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z\"\/><\/svg>\n        <\/button>\n        <button class=\"tool\" id=\"btnClear\" title=\"Clear active layer\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\"\/><\/svg>\n        <\/button>\n      <\/div>\n      <div class=\"row\" style=\"margin-top:6px;\">\n        <button class=\"tool\" id=\"btnSave\" style=\"width:100%\" title=\"Save full composite image\">\n          <svg viewBox=\"0 0 24 24\" style=\"margin-right:6px;\"><path d=\"M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2z\"\/><\/svg>\n          <span style=\"font-size:11.5px\">Save Full PNG<\/span>\n        <\/button>\n      <\/div>\n      <div class=\"row\" style=\"margin-top:6px;\">\n        <button class=\"tool\" id=\"btnSaveLayers\" style=\"width:100%\" title=\"Export layers separately\">\n          <svg viewBox=\"0 0 24 24\" style=\"margin-right:6px;\"><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z\"\/><\/svg>\n          <span style=\"font-size:11.5px\">Export Separate Layers<\/span>\n        <\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div id=\"canvasWrap\">\n    <canvas id=\"canvas\"><\/canvas>\n    <div id=\"coords\"><\/div>\n  <\/div>\n\n  <div id=\"layersPanel\" class=\"collapsed\">\n    <div style=\"height: 28px;\"><\/div>\n    <div class=\"section\" style=\"margin-top:12px;\">\n      <div class=\"section-title\">Layers<\/div>\n      <div id=\"layersList\"><\/div>\n      <button id=\"addLayerBtn\" title=\"New layer\">\n        <svg viewBox=\"0 0 24 24\"><path d=\"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"\/><\/svg>\n        New Layer\n      <\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  const canvas = document.getElementById('canvas');\n  const ctx = canvas.getContext('2d', { alpha: false });\n  const wrap = document.getElementById('canvasWrap');\n\n  const isAndroid = \/android\/i.test(navigator.userAgent || navigator.vendor || window.opera);\n\n  let RES_MULT = isAndroid ? 1 : 2; \n  let dpr = Math.min(Math.max(window.devicePixelRatio || 1, 1), 2) * RES_MULT;\n\n  let zoom = 1;\n  let panX = 0;\n  let panY = 0;\n  let appInitialized = false;\n\n  let animFrameId = null;\n  let needsRedraw = false;\n\n  function requestRedraw() {\n    if (!needsRedraw) {\n      needsRedraw = true;\n      animFrameId = requestAnimationFrame(executeRedraw);\n    }\n  }\n\n  function executeRedraw() {\n    needsRedraw = false;\n    const cssW = wrap.clientWidth;\n    const cssH = wrap.clientHeight;\n\n    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    ctx.fillStyle = '#141414';\n    ctx.fillRect(0, 0, cssW, cssH);\n\n    ctx.save();\n    ctx.translate(panX, panY);\n    ctx.scale(zoom, zoom);\n\n    ctx.fillStyle = '#ffffff';\n    ctx.fillRect(0, 0, cssW, cssH);\n\n    layers.forEach(l => {\n      if(!l.visible) return;\n      ctx.globalAlpha = l.opacity;\n      ctx.globalCompositeOperation = l.blend;\n      ctx.drawImage(l.canvas, 0, 0, cssW, cssH);\n    });\n\n    ctx.restore();\n  }\n\n  window.redrawCanvas = requestRedraw;\n\n  const BLEND_MODES = [\n    ['source-over','Normal'],\n    ['multiply','Multiply'],\n    ['screen','Screen'],\n    ['overlay','Overlay'],\n    ['lighter','Lighter'],\n    ['darken','Darken'],\n    ['lighten','Lighten'],\n    ['color-dodge','Color Dodge'],\n    ['color-burn','Color Burn'],\n    ['hard-light','Hard Light'],\n    ['soft-light','Soft Light'],\n    ['difference','Difference'],\n    ['exclusion','Exclusion'],\n    ['hue','Hue'],\n    ['saturation','Saturation'],\n    ['color','Color'],\n    ['luminosity','Luminosity']\n  ];\n\n  let layers = [];\n  let activeLayerId = null;\n  let layerCounter = 0;\n\n  function getActiveLayer(){\n    return layers.find(l => l.id === activeLayerId) || layers[layers.length-1];\n  }\n\n  function makeLayerCanvas(cssW, cssH){\n    const c = document.createElement('canvas');\n    c.width = Math.round(cssW * dpr);\n    c.height = Math.round(cssH * dpr);\n    const lctx = c.getContext('2d', { willReadFrequently: true });\n    lctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    return {canvas:c, ctx:lctx};\n  }\n\n  function addLayer(name){\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    const {canvas:c, ctx:lctx} = makeLayerCanvas(cssW, cssH);\n    const layer = {\n      id: 'L' + (layerCounter++),\n      name: name || ('Layer ' + (layers.length + 1)),\n      canvas: c, ctx: lctx,\n      opacity: 1, blend: 'source-over', visible: true\n    };\n    layers.push(layer);\n    activeLayerId = layer.id;\n    renderLayers();\n    return layer;\n  }\n\n  function deleteLayer(id){\n    if(layers.length <= 1) return;\n    pushHistory();\n    const idx = layers.findIndex(l => l.id === id);\n    if(idx === -1) return;\n    layers.splice(idx, 1);\n    if(activeLayerId === id){\n      activeLayerId = layers[Math.max(0, idx-1)].id;\n    }\n    renderLayers();\n    redrawCanvas();\n  }\n\n  function moveLayer(id, dir){\n    const idx = layers.findIndex(l => l.id === id);\n    const newIdx = idx + dir;\n    if(newIdx < 0 || newIdx >= layers.length) return;\n    pushHistory();\n    const tmp = layers[idx];\n    layers[idx] = layers[newIdx];\n    layers[newIdx] = tmp;\n    renderLayers();\n    redrawCanvas();\n  }\n\n  function renderLayers(){\n    const list = document.getElementById('layersList');\n    list.innerHTML = '';\n    for(let i = layers.length - 1; i >= 0; i--){\n      const l = layers[i];\n      const item = document.createElement('div');\n      item.className = 'layer-item' + (l.id === activeLayerId ? ' active' : '');\n\n      const row = document.createElement('div');\n      row.className = 'layer-row';\n\n      const eyeBtn = document.createElement('button');\n      eyeBtn.className = 'minibtn' + (l.visible ? '' : ' off');\n      eyeBtn.title = 'Show \/ hide';\n      eyeBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M12 5c-5 0-9.27 3.11-11 7.5C2.73 16.89 7 20 12 20s9.27-3.11 11-7.5C21.27 8.11 17 5 12 5zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8a3 3 0 100 6 3 3 0 000-6z\"\/><\/svg>';\n      eyeBtn.addEventListener('click', (e)=>{\n        e.stopPropagation();\n        l.visible = !l.visible;\n        redrawCanvas();\n        renderLayers();\n      });\n\n      const nameInput = document.createElement('input');\n      nameInput.className = 'layer-name';\n      nameInput.value = l.name;\n      nameInput.addEventListener('click', e => e.stopPropagation());\n      nameInput.addEventListener('input', e => { l.name = e.target.value; });\n\n      const upBtn = document.createElement('button');\n      upBtn.className = 'minibtn';\n      upBtn.title = 'Move up';\n      upBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M7 14l5-5 5 5H7z\"\/><\/svg>';\n      upBtn.addEventListener('click', (e)=>{ e.stopPropagation(); moveLayer(l.id, 1); });\n\n      const downBtn = document.createElement('button');\n      downBtn.className = 'minibtn';\n      downBtn.title = 'Move down';\n      downBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M7 10l5 5 5-5H7z\"\/><\/svg>';\n      downBtn.addEventListener('click', (e)=>{ e.stopPropagation(); moveLayer(l.id, -1); });\n\n      const delBtn = document.createElement('button');\n      delBtn.className = 'minibtn';\n      delBtn.title = 'Delete layer';\n      delBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\"\/><\/svg>';\n      delBtn.addEventListener('click', (e)=>{ e.stopPropagation(); deleteLayer(l.id); });\n\n      row.appendChild(eyeBtn);\n      row.appendChild(nameInput);\n      row.appendChild(upBtn);\n      row.appendChild(downBtn);\n      if(layers.length > 1) row.appendChild(delBtn);\n\n      const blendSelect = document.createElement('select');\n      blendSelect.className = 'blend-select';\n      BLEND_MODES.forEach(([val, label])=>{\n        const opt = document.createElement('option');\n        opt.value = val; opt.textContent = label;\n        if(val === l.blend) opt.selected = true;\n        blendSelect.appendChild(opt);\n      });\n      blendSelect.addEventListener('click', e => e.stopPropagation());\n      blendSelect.addEventListener('change', e => {\n        l.blend = e.target.value;\n        redrawCanvas();\n      });\n\n      const opRow = document.createElement('div');\n      opRow.className = 'opacity-row';\n      const opRange = document.createElement('input');\n      opRange.type = 'range'; opRange.min = 0; opRange.max = 100;\n      opRange.value = Math.round(l.opacity * 100);\n      opRange.addEventListener('click', e => e.stopPropagation());\n      const opLabel = document.createElement('span');\n      opLabel.textContent = Math.round(l.opacity * 100) + '%';\n      opRange.addEventListener('input', e => {\n        l.opacity = (+e.target.value) \/ 100;\n        opLabel.textContent = e.target.value + '%';\n        redrawCanvas();\n      });\n      opRow.appendChild(opRange);\n      opRow.appendChild(opLabel);\n\n      item.addEventListener('click', ()=>{\n        activeLayerId = l.id;\n        renderLayers();\n      });\n\n      item.appendChild(row);\n      item.appendChild(blendSelect);\n      item.appendChild(opRow);\n      list.appendChild(item);\n    }\n  }\n\n  document.getElementById('addLayerBtn').addEventListener('click', ()=>{\n    pushHistory();\n    addLayer();\n    redrawCanvas();\n  });\n\n  function resizeLayerCanvas(layer, cssW, cssH){\n    const prevCanvas = layer.canvas;\n    const prevW = prevCanvas.width, prevH = prevCanvas.height;\n    const temp = document.createElement('canvas');\n    temp.width = prevW; temp.height = prevH;\n    temp.getContext('2d').drawImage(prevCanvas, 0, 0);\n\n    layer.canvas.width = Math.round(cssW * dpr);\n    layer.canvas.height = Math.round(cssH * dpr);\n    layer.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n    if(prevW > 0 && prevH > 0){\n      layer.ctx.drawImage(temp, 0, 0, prevW, prevH, 0, 0, cssW, cssH);\n    }\n  }\n\n  function resize(){\n    if(!appInitialized) return;\n    const cssW = wrap.clientWidth;\n    const cssH = wrap.clientHeight;\n\n    canvas.width = Math.round(cssW * dpr);\n    canvas.height = Math.round(cssH * dpr);\n    canvas.style.width = cssW + 'px';\n    canvas.style.height = cssH + 'px';\n\n    if(layers.length === 0){\n      addLayer('Layer 1');\n    } else {\n      layers.forEach(l => resizeLayerCanvas(l, cssW, cssH));\n    }\n    redrawCanvas();\n  }\n\n  window.addEventListener('resize', resize);\n\n  wrap.addEventListener('wheel', (e)=>{\n    e.preventDefault();\n    const rect = canvas.getBoundingClientRect();\n    const mouseX = e.clientX - rect.left;\n    const mouseY = e.clientY - rect.top;\n\n    const zoomFactor = e.deltaY < 0 ? 1.15 : 1\/1.15;\n    const newZoom = Math.max(0.1, Math.min(20, zoom * zoomFactor));\n\n    panX = mouseX - (mouseX - panX) * (newZoom \/ zoom);\n    panY = mouseY - (mouseY - panY) * (newZoom \/ zoom);\n    zoom = newZoom;\n\n    redrawCanvas();\n  }, {passive: false});\n\n  const menuToggle = document.getElementById('menuToggle');\n  const panel = document.getElementById('panel');\n  menuToggle.addEventListener('click', ()=>{\n    panel.classList.toggle('collapsed');\n    setTimeout(resize, 230);\n  });\n\n  const layersToggle = document.getElementById('layersToggle');\n  const layersPanel = document.getElementById('layersPanel');\n  layersToggle.addEventListener('click', ()=>{\n    layersPanel.classList.toggle('collapsed');\n    setTimeout(resize, 230);\n  });\n\n  const colorPicker = document.getElementById('colorPicker');\n  const colorVal = document.getElementById('colorVal');\n  const sectionColor = document.getElementById('sectionColor');\n  const stabInput = document.getElementById('stab');\n  const stabVal = document.getElementById('stabVal');\n  const widthInput = document.getElementById('width');\n  const widthVal = document.getElementById('widthVal');\n  const blendWidthInput = document.getElementById('blendWidth');\n  const blendWidthVal = document.getElementById('blendWidthVal');\n  const blendStrengthInput = document.getElementById('blendStrength');\n  const blendStrengthVal = document.getElementById('blendStrengthVal');\n  const eraserWidthInput = document.getElementById('eraserWidth');\n  const eraserWidthVal = document.getElementById('eraserWidthVal');\n  const pressInput = document.getElementById('pressureSens');\n  const pressVal = document.getElementById('pressVal');\n  const velInput = document.getElementById('velocity');\n  const velVal = document.getElementById('velVal');\n  const spaceInput = document.getElementById('spacing');\n  const spaceVal = document.getElementById('spaceVal');\n  const taperInput = document.getElementById('taper');\n  const taperVal = document.getElementById('taperVal');\n  \n  const chkRotate = document.getElementById('chkRotate');\n\n  const btnCustomBrush = document.getElementById('btnCustomBrush');\n  const customBrushInput = document.getElementById('customBrushInput');\n  let customBrushPattern = null;\n\n  const btnFillSolid = document.getElementById('btnFillSolid');\n  const btnFillHalftone = document.getElementById('btnFillHalftone');\n  const halftoneSettings = document.getElementById('halftoneSettings');\n  const dotSizeInput = document.getElementById('dotSize');\n  const dotSizeVal = document.getElementById('dotSizeVal');\n  const dotSpaceInput = document.getElementById('dotSpace');\n  const dotSpaceVal = document.getElementById('dotSpaceVal');\n\n  const btnShapeLine = document.getElementById('btnShapeLine');\n  const btnShapeCurve = document.getElementById('btnShapeCurve');\n  const shapeWidthInput = document.getElementById('shapeWidth');\n  const shapeWidthVal = document.getElementById('shapeWidthVal');\n\n  const sectionBrush = document.getElementById('sectionBrush');\n  const sectionShape = document.getElementById('sectionShape');\n  const sectionBlend = document.getElementById('sectionBlend');\n  const sectionEraser = document.getElementById('sectionEraser');\n  const sectionLasso = document.getElementById('sectionLasso');\n  const coords = document.getElementById('coords');\n\n  const btnDraw = document.getElementById('btnDraw');\n  const btnBlend = document.getElementById('btnBlend');\n  const btnEraser = document.getElementById('btnEraser');\n  const btnShape = document.getElementById('btnShape');\n  const btnLasso = document.getElementById('btnLasso');\n  const btnUndo = document.getElementById('btnUndo');\n  const btnClear = document.getElementById('btnClear');\n  const btnSave = document.getElementById('btnSave');\n  const btnSaveLayers = document.getElementById('btnSaveLayers');\n\n  let currentColor = colorPicker.value;\n  let stabilization = parseInt(stabInput.value,10);\n  let baseWidth = parseInt(widthInput.value,10);\n  let blendWidth = parseInt(blendWidthInput.value,10);\n  let blendStrength = parseInt(blendStrengthInput.value,10);\n  let eraserWidth = parseInt(eraserWidthInput.value,10);\n  let pressureSensitivity = parseInt(pressInput.value,10);\n  let velRelation = parseInt(velInput.value,10);\n  let spacingValue = parseInt(spaceInput.value,10);\n  let taperValue = parseInt(taperInput.value, 10);\n  let brushShape = 'circle';\n  let tool = 'draw';\n  let fillMode = 'solid';\n  let shapeMode = 'line';\n  let shapeWidth = parseInt(shapeWidthInput.value, 10);\n  let dotSize = parseInt(dotSizeInput.value, 10);\n  let dotSpace = parseInt(dotSpaceInput.value, 10);\n\n  const brushButtons = document.querySelectorAll('[data-brush]');\n  brushButtons.forEach(btn => {\n    btn.addEventListener('click', () => {\n      brushButtons.forEach(b => b.classList.remove('active'));\n      btn.classList.add('active');\n      brushShape = btn.getAttribute('data-brush');\n      customBrushPattern = null;\n      btnCustomBrush.style.borderColor = 'var(--border)';\n      btnCustomBrush.textContent = 'Load Custom Pattern...';\n    });\n  });\n\n  btnCustomBrush.addEventListener('click', () => customBrushInput.click());\n  customBrushInput.addEventListener('change', (e) => {\n    const file = e.target.files[0];\n    if(!file) return;\n    const reader = new FileReader();\n    reader.onload = function(event) {\n      const img = new Image();\n      img.onload = function() {\n        let w = img.width;\n        let h = img.height;\n        const maxLimit = 60;\n        if(w > maxLimit || h > maxLimit) {\n          if(w > h) {\n            h = Math.round((h * maxLimit) \/ w);\n            w = maxLimit;\n          } else {\n            w = Math.round((w * maxLimit) \/ h);\n            h = maxLimit;\n          }\n        }\n        const tempCanvas = document.createElement('canvas');\n        tempCanvas.width = w;\n        tempCanvas.height = h;\n        const tCtx = tempCanvas.getContext('2d');\n        tCtx.drawImage(img, 0, 0, w, h);\n\n        customBrushPattern = tempCanvas;\n        brushShape = 'custom';\n        brushButtons.forEach(b => b.classList.remove('active'));\n        btnCustomBrush.style.borderColor = 'var(--accent)';\n        btnCustomBrush.textContent = `Pattern Loaded (${w}x${h}px)`;\n      };\n      img.src = event.target.result;\n    };\n    reader.readAsDataURL(file);\n  });\n\n  colorVal.textContent = currentColor;\n  colorPicker.addEventListener('input', e=>{\n    currentColor = e.target.value;\n    colorVal.textContent = currentColor;\n  });\n  stabInput.addEventListener('input', e=>{\n    stabilization = parseInt(e.target.value,10);\n    stabVal.textContent = stabilization + '%';\n  });\n  widthInput.addEventListener('input', e=>{\n    baseWidth = parseInt(e.target.value,10);\n    widthVal.textContent = baseWidth + ' px';\n  });\n  blendWidthInput.addEventListener('input', e=>{\n    blendWidth = parseInt(e.target.value,10);\n    blendWidthVal.textContent = blendWidth + ' px';\n  });\n  blendStrengthInput.addEventListener('input', e=>{\n    blendStrength = parseInt(e.target.value,10);\n    blendStrengthVal.textContent = blendStrength + '%';\n  });\n  eraserWidthInput.addEventListener('input', e=>{\n    eraserWidth = parseInt(e.target.value,10);\n    eraserWidthVal.textContent = eraserWidth + ' px';\n  });\n  pressInput.addEventListener('input', e=>{\n    pressureSensitivity = parseInt(e.target.value,10);\n    pressVal.textContent = pressureSensitivity + '%';\n  });\n  velInput.addEventListener('input', e=>{\n    velRelation = parseInt(e.target.value,10);\n    velVal.textContent = velRelation;\n  });\n  spaceInput.addEventListener('input', e=>{\n    spacingValue = parseInt(e.target.value,10);\n    spaceVal.textContent = spacingValue + '%';\n  });\n  taperInput.addEventListener('input', e=>{\n    taperValue = parseInt(e.target.value, 10);\n    taperVal.textContent = taperValue + '%';\n  });\n  shapeWidthInput.addEventListener('input', e => {\n    shapeWidth = parseInt(e.target.value, 10);\n    shapeWidthVal.textContent = shapeWidth + ' px';\n  });\n  dotSizeInput.addEventListener('input', e=>{\n    dotSize = parseInt(e.target.value, 10);\n    dotSizeVal.textContent = dotSize + ' px';\n  });\n  dotSpaceInput.addEventListener('input', e=>{\n    dotSpace = parseInt(e.target.value, 10);\n    dotSpaceVal.textContent = dotSpace + ' px';\n  });\n\n  btnFillSolid.addEventListener('click', () => {\n    fillMode = 'solid';\n    btnFillSolid.classList.add('active');\n    btnFillHalftone.classList.remove('active');\n    halftoneSettings.style.display = 'none';\n  });\n  btnFillHalftone.addEventListener('click', () => {\n    fillMode = 'halftone';\n    btnFillHalftone.classList.add('active');\n    btnFillSolid.classList.remove('active');\n    halftoneSettings.style.display = 'block';\n  });\n\n  btnShapeLine.addEventListener('click', () => {\n    shapeMode = 'line';\n    btnShapeLine.classList.add('active');\n    btnShapeCurve.classList.remove('active');\n  });\n  btnShapeCurve.addEventListener('click', () => {\n    shapeMode = 'curve';\n    btnShapeCurve.classList.add('active');\n    btnShapeLine.classList.remove('active');\n  });\n\n  function setTool(t){\n    tool = t;\n    btnDraw.classList.toggle('active', t==='draw');\n    btnBlend.classList.toggle('active', t==='blend');\n    btnEraser.classList.toggle('active', t==='eraser');\n    btnShape.classList.toggle('active', t==='shape');\n    btnLasso.classList.toggle('active', t==='lasso');\n\n    sectionBrush.style.display = (t === 'draw') ? 'block' : 'none';\n    sectionShape.style.display = (t === 'shape') ? 'block' : 'none';\n    sectionBlend.style.display = (t === 'blend') ? 'block' : 'none';\n    sectionEraser.style.display = (t === 'eraser') ? 'block' : 'none';\n    sectionLasso.style.display = (t === 'lasso') ? 'block' : 'none';\n    sectionColor.style.display = (t === 'eraser' || t === 'blend') ? 'none' : 'block';\n  }\n  btnDraw.addEventListener('click', ()=> setTool('draw'));\n  btnBlend.addEventListener('click', ()=> setTool('blend'));\n  btnEraser.addEventListener('click', ()=> setTool('eraser'));\n  btnShape.addEventListener('click', ()=> setTool('shape'));\n  btnLasso.addEventListener('click', ()=> setTool('lasso'));\n\n  let history = [];\n  const MAX_HISTORY = 15;\n  function pushHistory(){\n    try{\n      const snap = {\n        activeLayerId,\n        layers: layers.map(l => ({\n          id:l.id, name:l.name, blend:l.blend, opacity:l.opacity, visible:l.visible,\n          dataURL: l.canvas.toDataURL('image\/png')\n        }))\n      };\n      history.push(snap);\n      if(history.length > MAX_HISTORY) history.shift();\n    }catch(e){}\n  }\n\n  function restoreSnapshot(snap){\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    const loaders = snap.layers.map(ld => new Promise(resolve=>{\n      const img = new Image();\n      img.onload = ()=>{\n        const {canvas:c, ctx:lctx} = makeLayerCanvas(cssW, cssH);\n        lctx.drawImage(img, 0, 0, cssW, cssH);\n        resolve({id:ld.id, name:ld.name, blend:ld.blend, opacity:ld.opacity, visible:ld.visible, canvas:c, ctx:lctx});\n      };\n      img.src = ld.dataURL;\n    }));\n    Promise.all(loaders).then(newLayers=>{\n      layers = newLayers;\n      activeLayerId = snap.activeLayerId;\n      renderLayers();\n      redrawCanvas();\n    });\n  }\n\n  btnUndo.addEventListener('click', ()=>{\n    if(history.length === 0) return;\n    restoreSnapshot(history.pop());\n  });\n\n  btnClear.addEventListener('click', ()=>{\n    pushHistory();\n    const layer = getActiveLayer();\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    layer.ctx.save();\n    layer.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    layer.ctx.clearRect(0, 0, cssW, cssH);\n    layer.ctx.restore();\n    redrawCanvas();\n  });\n\n  btnSave.addEventListener('click', ()=>{\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    const out = document.createElement('canvas');\n    out.width = Math.round(cssW * dpr);\n    out.height = Math.round(cssH * dpr);\n    const octx = out.getContext('2d');\n    octx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    octx.fillStyle = '#ffffff';\n    octx.fillRect(0, 0, cssW, cssH);\n    layers.forEach(l => {\n      if(!l.visible) return;\n      octx.globalAlpha = l.opacity;\n      octx.globalCompositeOperation = l.blend;\n      octx.drawImage(l.canvas, 0, 0, cssW, cssH);\n    });\n    const link = document.createElement('a');\n    link.download = 'full_drawing.png';\n    link.href = out.toDataURL('image\/png');\n    link.click();\n  });\n\n  btnSaveLayers.addEventListener('click', ()=>{\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    layers.forEach((l, index) => {\n      const out = document.createElement('canvas');\n      out.width = Math.round(cssW * dpr);\n      out.height = Math.round(cssH * dpr);\n      const octx = out.getContext('2d');\n      octx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      octx.drawImage(l.canvas, 0, 0, cssW, cssH);\n\n      const link = document.createElement('a');\n      link.download = `${l.name.replace(\/\\s+\/g, '_')}_${index + 1}.png`;\n      link.href = out.toDataURL('image\/png');\n      link.click();\n    });\n  });\n\n  function getPos(e){\n    const rect = canvas.getBoundingClientRect();\n    const clientX = e.clientX - rect.left;\n    const clientY = e.clientY - rect.top;\n    return {\n      x: (clientX - panX) \/ zoom,\n      y: (clientY - panY) \/ zoom\n    };\n  }\n  function dist(a,b){ return Math.hypot(b.x-a.x, b.y-a.y); }\n\n  let drawing = false;\n  let rawPoint = null;\n  let stablePoint = null;\n  let lastTime = 0;\n  let smoothedSpeed = 0;\n  let lastStampPos = null;\n  let distanceSinceLastStamp = 0;\n  let currentStrokePoints = [];\n  let smudgeColorSample = null;\n\n  let activePointers = new Map();\n  let isMultiTouch = false;\n  let initialPinchDist = 0;\n  let initialZoom = 1;\n  let initialPanCenter = {x: 0, y: 0};\n  let startPanOffset = {x: 0, y: 0};\n\n  function computeWidth(speed, pressure){\n    let activeWidth = baseWidth;\n    if(tool === 'eraser') activeWidth = eraserWidth;\n    if(tool === 'blend') activeWidth = blendWidth;\n\n    const normSpeed = Math.min(speed \/ 2.2, 1);\n    const relSpeed = velRelation \/ 100;\n    const speedFactor = 1 + relSpeed * normSpeed;\n    const rawPress = (pressure !== undefined && pressure > 0) ? pressure : 0.5;\n    const pressFactor = 1 + (rawPress - 0.5) * (pressureSensitivity \/ 50);\n    return Math.max(1, activeWidth * speedFactor * pressFactor);\n  }\n\n  function sampleColorAt(x, y) {\n    const layer = getActiveLayer();\n    const px = Math.round(x * dpr);\n    const py = Math.round(y * dpr);\n    if(px < 0 || py < 0 || px >= layer.canvas.width || py >= layer.canvas.height) return null;\n    const pData = layer.ctx.getImageData(px, py, 1, 1).data;\n    if(pData[3] === 0) return null;\n    return `rgb(${pData[0]},${pData[1]},${pData[2]})`;\n  }\n\n  function drawStamp(x, y, size, color, angle = 0){\n    const layer = getActiveLayer();\n    const lctx = layer.ctx;\n    lctx.save();\n\n    if(tool === 'eraser'){\n      lctx.globalCompositeOperation = 'destination-out';\n      lctx.fillStyle = 'rgba(0,0,0,1)';\n      lctx.strokeStyle = 'rgba(0,0,0,1)';\n    } else if(tool === 'blend'){\n      lctx.globalCompositeOperation = 'source-over';\n      if(!smudgeColorSample) {\n        let sampled = sampleColorAt(x, y);\n        if(sampled) smudgeColorSample = sampled;\n        else return;\n      }\n      lctx.fillStyle = smudgeColorSample;\n      lctx.strokeStyle = smudgeColorSample;\n      lctx.globalAlpha = (blendStrength \/ 100);\n    } else {\n      lctx.globalCompositeOperation = 'source-over';\n      lctx.fillStyle = color;\n      lctx.strokeStyle = color;\n    }\n\n    const r = size \/ 2;\n\n    if(chkRotate.checked && angle !== 0 && tool !== 'eraser'){\n      lctx.translate(x, y);\n      lctx.rotate(angle);\n      lctx.translate(-x, -y);\n    }\n\n    if(tool !== 'blend' && brushShape === 'custom' && customBrushPattern){\n      lctx.drawImage(customBrushPattern, x - r, y - r, size, size);\n    } else if(brushShape === 'circle' || tool === 'blend'){\n      lctx.beginPath();\n      lctx.arc(x, y, r, 0, Math.PI * 2);\n      lctx.fill();\n    } else if(brushShape === 'square'){\n      lctx.fillRect(x - r, y - r, size, size);\n    } else if(brushShape === 'diamond'){\n      lctx.beginPath();\n      lctx.moveTo(x, y - r);\n      lctx.lineTo(x + r, y);\n      lctx.lineTo(x, y + r);\n      lctx.lineTo(x - r, y);\n      lctx.closePath();\n      lctx.fill();\n    } else if(brushShape === 'star'){\n      lctx.beginPath();\n      let spikes = 5;\n      let outerRadius = r;\n      let innerRadius = r \/ 2;\n      let rot = Math.PI \/ 2 * 3;\n      let step = Math.PI \/ spikes;\n\n      lctx.moveTo(x, y - outerRadius);\n      for (let i = 0; i < spikes; i++) {\n        let xCoord = x + Math.cos(rot) * outerRadius;\n        let yCoord = y + Math.sin(rot) * outerRadius;\n        lctx.lineTo(xCoord, yCoord);\n        rot += step;\n\n        xCoord = x + Math.cos(rot) * innerRadius;\n        yCoord = y + Math.sin(rot) * innerRadius;\n        lctx.lineTo(xCoord, yCoord);\n        rot += step;\n      }\n      lctx.lineTo(x, y - outerRadius);\n      lctx.closePath();\n      lctx.fill();\n    }\n    lctx.restore();\n  }\n\n  function startDraw(e){\n    activePointers.set(e.pointerId, {clientX: e.clientX, clientY: e.clientY});\n\n    if(activePointers.size >= 2){\n      if(drawing){\n        endDraw(e);\n      }\n      isMultiTouch = true;\n      const pts = Array.from(activePointers.values());\n      initialPinchDist = Math.hypot(pts[1].clientX - pts[0].clientX, pts[1].clientY - pts[0].clientY);\n      initialZoom = zoom;\n      startPanOffset = {x: panX, y: panY};\n      initialPanCenter = {\n        x: (pts[0].clientX + pts[1].clientX) \/ 2,\n        y: (pts[0].clientY + pts[1].clientY) \/ 2\n      };\n      return;\n    }\n\n    if(isMultiTouch) return;\n    if(tool !== 'draw' && tool !== 'eraser' && tool !== 'blend') return;\n    \n    drawing = true;\n    pushHistory();\n    const p = getPos(e);\n    \n    rawPoint = {x: p.x, y: p.y};\n    stablePoint = {x: p.x, y: p.y};\n    \n    lastTime = performance.now();\n    smoothedSpeed = 0;\n    smudgeColorSample = (tool === 'blend') ? sampleColorAt(p.x, p.y) : null;\n    \n    const initialW = computeWidth(0, e.pressure);\n    lastStampPos = {x: p.x, y: p.y};\n    distanceSinceLastStamp = 0;\n    currentStrokePoints = [{x: p.x, y: p.y, w: initialW}];\n\n    const paintColor = (tool === 'eraser') ? '#ffffff' : currentColor;\n    drawStamp(p.x, p.y, initialW, paintColor, 0);\n    redrawCanvas();\n  }\n\n  function moveDraw(e){\n    if(activePointers.has(e.pointerId)){\n      activePointers.set(e.pointerId, {clientX: e.clientX, clientY: e.clientY});\n    }\n\n    if(isMultiTouch && activePointers.size >= 2){\n      e.preventDefault();\n      const pts = Array.from(activePointers.values());\n      const currentDist = Math.hypot(pts[1].clientX - pts[0].clientX, pts[1].clientY - pts[0].clientY);\n      const currentCenter = {\n        x: (pts[0].clientX + pts[1].clientX) \/ 2,\n        y: (pts[0].clientY + pts[1].clientY) \/ 2\n      };\n\n      const zoomFactor = currentDist \/ initialPinchDist;\n      const newZoom = Math.max(0.1, Math.min(20, initialZoom * zoomFactor));\n\n      const rect = canvas.getBoundingClientRect();\n      const centerX = initialPanCenter.x - rect.left;\n      const centerY = initialPanCenter.y - rect.top;\n\n      panX = currentCenter.x - rect.left - (centerX - startPanOffset.x) * (newZoom \/ initialZoom);\n      panY = currentCenter.y - rect.top - (centerY - startPanOffset.y) * (newZoom \/ initialZoom);\n      zoom = newZoom;\n\n      redrawCanvas();\n      return;\n    }\n\n    const p = getPos(e);\n    coords.textContent = Math.round(p.x) + ', ' + Math.round(p.y);\n    if(!drawing || (tool !== 'draw' && tool !== 'eraser' && tool !== 'blend')) return;\n    e.preventDefault();\n\n    rawPoint = p;\n    const now = performance.now();\n    const dt = Math.max(now - lastTime, 1);\n    lastTime = now;\n\n    const pull = 1 - (stabilization\/100)*0.95;\n    stablePoint.x += (rawPoint.x - stablePoint.x) * pull;\n    stablePoint.y += (rawPoint.y - stablePoint.y) * pull;\n\n    const d = dist(lastStampPos, stablePoint);\n    const instSpeed = d \/ dt;\n\n    smoothedSpeed += (instSpeed - smoothedSpeed) * pull;\n    const currentWidth = computeWidth(smoothedSpeed, e.pressure);\n\n    let angle = 0;\n    if(chkRotate.checked && tool !== 'eraser' && tool !== 'blend'){\n      angle = Math.atan2(stablePoint.y - lastStampPos.y, stablePoint.x - lastStampPos.x);\n    }\n\n    currentStrokePoints.push({x: stablePoint.x, y: stablePoint.y, w: currentWidth});\n\n    const spacingPx = Math.max(1, currentWidth * (spacingValue \/ 100));\n    distanceSinceLastStamp += d;\n\n    if (distanceSinceLastStamp >= spacingPx) {\n      const steps = Math.floor(distanceSinceLastStamp \/ spacingPx);\n      const paintColor = (tool === 'eraser') ? '#ffffff' : currentColor;\n      for (let i = 1; i <= steps; i++) {\n        const t = (i * spacingPx) \/ distanceSinceLastStamp;\n        const interpX = lastStampPos.x + (stablePoint.x - lastStampPos.x) * t;\n        const interpY = lastStampPos.y + (stablePoint.y - lastStampPos.y) * t;\n        \n        if(tool === 'blend') {\n          let sampled = sampleColorAt(interpX, interpY);\n          if(sampled) smudgeColorSample = sampled;\n        }\n\n        drawStamp(interpX, interpY, currentWidth, paintColor, angle);\n      }\n      lastStampPos = {x: stablePoint.x, y: stablePoint.y};\n      distanceSinceLastStamp %= spacingPx;\n      redrawCanvas();\n    }\n  }\n\n  function endDraw(e){\n    activePointers.delete(e.pointerId);\n\n    if(activePointers.size < 2){\n      isMultiTouch = false;\n    }\n\n    if(drawing &#038;&#038; (tool==='draw' || tool==='eraser' || tool==='blend')){\n      drawing = false;\n      \n      if(tool === 'draw' &#038;&#038; taperValue !== 0 &#038;&#038; currentStrokePoints.length > 2){\n        const totalPts = currentStrokePoints.length;\n        const taperWindow = Math.min(Math.floor(totalPts \/ 2), 25);\n        \n        for(let i = 0; i < totalPts; i++){\n          let pt = currentStrokePoints[i];\n          let factor = 1;\n          \n          if(i < taperWindow){\n            let pProg = i \/ taperWindow;\n            if(taperValue > 0) factor = pProg + (1 - pProg) * (1 - taperValue \/ 100);\n            else factor = pProg + (1 - pProg) * (1 + Math.abs(taperValue) \/ 100);\n          } else if(i > totalPts - taperWindow){\n            let pProg = (totalPts - i) \/ taperWindow;\n            if(taperValue > 0) factor = pProg + (1 - pProg) * (1 - taperValue \/ 100);\n            else factor = pProg + (1 - pProg) * (1 + Math.abs(taperValue) \/ 100);\n          }\n          \n          let modifiedWidth = Math.max(1, pt.w * factor);\n          drawStamp(pt.x, pt.y, modifiedWidth, currentColor, 0);\n        }\n        redrawCanvas();\n      }\n\n      rawPoint = null; \n      stablePoint = null; \n      lastStampPos = null;\n      currentStrokePoints = [];\n      smudgeColorSample = null;\n    }\n  }\n\n  \/\/ --- Shape Tool (Lines & Bezier Curves) ---\n  let shapeStart = null;\n  let shapeCurrent = null;\n  let shapeActive = false;\n\n  function startShape(e){\n    if(tool !== 'shape') return;\n    e.preventDefault();\n    shapeActive = true;\n    shapeStart = getPos(e);\n    shapeCurrent = {x: shapeStart.x, y: shapeStart.y};\n  }\n\n  function moveShape(e){\n    if(!shapeActive || tool !== 'shape') return;\n    e.preventDefault();\n    shapeCurrent = getPos(e);\n    drawShapePreviewOverlay();\n  }\n\n  function endShape(e){\n    if(!shapeActive || tool !== 'shape') return;\n    shapeActive = false;\n    pushHistory();\n    commitShapeToLayer();\n    shapeStart = null;\n    shapeCurrent = null;\n    redrawCanvas();\n  }\n\n  function drawShapePreviewOverlay(){\n    redrawCanvas();\n    ctx.save();\n    ctx.translate(panX, panY);\n    ctx.scale(zoom, zoom);\n    ctx.strokeStyle = currentColor;\n    ctx.lineWidth = shapeWidth \/ zoom;\n    ctx.lineCap = 'round';\n    ctx.lineJoin = 'round';\n    ctx.beginPath();\n    ctx.moveTo(shapeStart.x, shapeStart.y);\n    if(shapeMode === 'line'){\n      ctx.lineTo(shapeCurrent.x, shapeCurrent.y);\n    } else {\n      let cx = (shapeStart.x + shapeCurrent.x) \/ 2 + (shapeCurrent.y - shapeStart.y) * 0.3;\n      let cy = (shapeStart.y + shapeCurrent.y) \/ 2 - (shapeCurrent.x - shapeStart.x) * 0.3;\n      ctx.quadraticCurveTo(cx, cy, shapeCurrent.x, shapeCurrent.y);\n    }\n    ctx.stroke();\n    ctx.restore();\n  }\n\n  function commitShapeToLayer(){\n    const layer = getActiveLayer();\n    const lctx = layer.ctx;\n    lctx.save();\n    lctx.globalCompositeOperation = 'source-over';\n    lctx.strokeStyle = currentColor;\n    lctx.fillStyle = currentColor;\n    lctx.lineWidth = shapeWidth;\n    lctx.lineCap = 'round';\n    lctx.lineJoin = 'round';\n    lctx.beginPath();\n    lctx.moveTo(shapeStart.x, shapeStart.y);\n    if(shapeMode === 'line'){\n      lctx.lineTo(shapeCurrent.x, shapeCurrent.y);\n    } else {\n      let cx = (shapeStart.x + shapeCurrent.x) \/ 2 + (shapeCurrent.y - shapeStart.y) * 0.3;\n      let cy = (shapeStart.y + shapeCurrent.y) \/ 2 - (shapeCurrent.x - shapeStart.x) * 0.3;\n      lctx.quadraticCurveTo(cx, cy, shapeCurrent.x, shapeCurrent.y);\n    }\n    lctx.stroke();\n    lctx.restore();\n  }\n\n  \/\/ --- Lasso Tool ---\n  let lassoPoints = [];\n  let lassoActive = false;\n\n  function startLasso(e){\n    e.preventDefault();\n    if(tool !== 'lasso') return;\n    lassoActive = true;\n    const p = getPos(e);\n    lassoPoints = [p];\n  }\n  function moveLasso(e){\n    if(!lassoActive || tool !== 'lasso') return;\n    e.preventDefault();\n    const p = getPos(e);\n    lassoPoints.push(p);\n    drawLassoPreviewOverlay();\n  }\n  function endLasso(e){\n    if(!lassoActive || tool !== 'lasso') return;\n    lassoActive = false;\n    if(lassoPoints.length > 2){\n      pushHistory();\n      fillLasso();\n    }\n    lassoPoints = [];\n    redrawCanvas();\n  }\n\n  function drawLassoPreviewOverlay(){\n    redrawCanvas();\n    ctx.save();\n    ctx.translate(panX, panY);\n    ctx.scale(zoom, zoom);\n    ctx.setLineDash([4\/zoom, 4\/zoom]);\n    ctx.strokeStyle = '#888888';\n    ctx.lineWidth = 1.5 \/ zoom;\n    ctx.beginPath();\n    ctx.moveTo(lassoPoints[0].x, lassoPoints[0].y);\n    for(let i=1;i<lassoPoints.length;i++){\n      ctx.lineTo(lassoPoints[i].x, lassoPoints[i].y);\n    }\n    ctx.stroke();\n    ctx.restore();\n  }\n\n  function fillLasso(){\n    const layer = getActiveLayer();\n    const lctx = layer.ctx;\n    lctx.save();\n    lctx.globalCompositeOperation = 'source-over';\n    lctx.beginPath();\n    lctx.moveTo(lassoPoints[0].x, lassoPoints[0].y);\n    for(let i=1;i<lassoPoints.length;i++){\n      lctx.lineTo(lassoPoints[i].x, lassoPoints[i].y);\n    }\n    lctx.closePath();\n\n    if(fillMode === 'solid'){\n      lctx.fillStyle = currentColor;\n      lctx.fill();\n      lctx.lineWidth = 2;\n      lctx.strokeStyle = currentColor;\n      lctx.stroke();\n    } else {\n      lctx.save();\n      lctx.clip();\n\n      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n      lassoPoints.forEach(pt => {\n        if(pt.x < minX) minX = pt.x;\n        if(pt.y < minY) minY = pt.y;\n        if(pt.x > maxX) maxX = pt.x;\n        if(pt.y > maxY) maxY = pt.y;\n      });\n\n      lctx.fillStyle = currentColor;\n      for(let y = minY; y <= maxY; y += dotSpace){\n        for(let x = minX; x <= maxX; x += dotSpace){\n          lctx.beginPath();\n          lctx.arc(x, y, dotSize \/ 2, 0, Math.PI * 2);\n          lctx.fill();\n        }\n      }\n      lctx.restore();\n      lctx.lineWidth = 2;\n      lctx.strokeStyle = currentColor;\n      lctx.stroke();\n    }\n    lctx.restore();\n  }\n\n  canvas.addEventListener('pointerdown', (e)=>{\n    if(tool === 'draw' || tool === 'eraser' || tool === 'blend') startDraw(e);\n    else if(tool === 'shape') startShape(e);\n    else if(tool === 'lasso') startLasso(e);\n  });\n  canvas.addEventListener('pointermove', (e)=>{\n    if(tool === 'draw' || tool === 'eraser' || tool === 'blend') moveDraw(e);\n    else if(tool === 'shape') moveShape(e);\n    else if(tool === 'lasso') moveLasso(e);\n    else {\n      const p = getPos(e);\n      coords.textContent = Math.round(p.x) + ', ' + Math.round(p.y);\n    }\n  });\n  window.addEventListener('pointerup', (e)=>{\n    if(tool === 'draw' || tool === 'eraser' || tool === 'blend') endDraw(e);\n    else if(tool === 'shape') endShape(e);\n    else if(tool === 'lasso') endLasso(e);\n  });\n  window.addEventListener('pointercancel', (e)=>{\n    if(tool === 'draw' || tool === 'eraser' || tool === 'blend') endDraw(e);\n    else if(tool === 'shape') endShape(e);\n    else if(tool === 'lasso') endLasso(e);\n  });\n\n  const presetModal = document.getElementById('presetModal');\n  const presetButtons = document.querySelectorAll('.preset-btn');\n\n  presetButtons.forEach(btn => {\n    btn.addEventListener('click', () => {\n      const preset = btn.getAttribute('data-preset');\n      \n      if (preset === 'quality') {\n        RES_MULT = 2; \n        spacingValue = 10; \n      } else if (preset === 'normal') {\n        RES_MULT = 1.5; \n        spacingValue = 15;\n      } else if (preset === 'performance') {\n        RES_MULT = 1; \n        spacingValue = 20; \n      }\n\n      spaceInput.value = spacingValue;\n      spaceVal.textContent = spacingValue + '%';\n\n      dpr = Math.min(Math.max(window.devicePixelRatio || 1, 1), 2) * RES_MULT;\n      presetModal.style.display = 'none';\n      appInitialized = true;\n\n      if (isAndroid) {\n        document.getElementById('panel').classList.remove('collapsed');\n      }\n\n      resize();\n    });\n  });\n\n})();\n<\/script>\n<\/body>\n<\/html>\n\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n<title>RagBunnie Online Drawing Canvas<\/title>\n<style>\n  :root{\n    --bg: #141414;\n    --panel: #1e1e1e;\n    --panel-hover: #2a2a2a;\n    --accent: #ffffff;\n    --text: #c0c0c0;\n    --text-dim: #707070;\n    --border: #2c2c2c;\n    --panel-w: 240px;\n    --layers-w: 230px;\n  }\n  *{box-sizing:border-box;}\n  html,body{\n    margin:0; padding:0; height:100%; overflow:hidden;\n    background:var(--bg); color:var(--text);\n    font-family:-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n    user-select:none; -webkit-user-select:none;\n  }\n  #app{\n    position:relative; width:100vw; height:100vh;\n    display:flex; flex-direction:row;\n  }\n\n  \/* --- Preset Modal --- *\/\n  #presetModal {\n    position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;\n    background: rgba(0, 0, 0, 0.85); display: flex; align-items: center;\n    justify-content: center; z-index: 100; backdrop-filter: blur(5px);\n  }\n  .modal-content {\n    background: var(--panel); border: 1px solid var(--border);\n    padding: 30px; border-radius: 12px; width: 400px; max-width: 90vw;\n    text-align: center; box-shadow: 0 10px 30px rgba(0,0,0,0.5);\n  }\n  .modal-title {\n    font-size: 16px; font-weight: 600; color: var(--accent); margin-bottom: 8px;\n    text-transform: uppercase; letter-spacing: 1px;\n  }\n  .modal-desc {\n    font-size: 12px; color: var(--text-dim); margin-bottom: 24px;\n  }\n  .preset-options {\n    display: flex; flex-direction: column; gap: 12px;\n  }\n  .preset-btn {\n    padding: 14px; border-radius: 8px; border: 1px solid var(--border);\n    background: transparent; color: var(--text); cursor: pointer;\n    font-size: 13px; font-weight: 500; transition: all .2s;\n    display: flex; flex-direction: column; align-items: center; gap: 4px;\n  }\n  .preset-btn:hover {\n    background: var(--panel-hover); border-color: var(--accent); color: var(--accent);\n    transform: translateY(-2px);\n  }\n  .preset-btn span {\n    font-size: 10.5px; color: var(--text-dim); font-weight: normal;\n  }\n\n  \/* --- Monochrome Sidebar --- *\/\n  #panel{\n    position:relative; height:100vh; width:var(--panel-w);\n    min-width:var(--panel-w);\n    background:var(--panel); border-right:1px solid var(--border);\n    padding:16px; overflow-y:auto;\n    transition:margin-left .22s ease, opacity .18s ease;\n    z-index: 25;\n  }\n  #panel.collapsed{\n    margin-left:calc(var(--panel-w) * -1);\n    opacity:0;\n    pointer-events:none;\n  }\n\n  #menuToggle{\n    position:absolute; top:12px; left:12px; z-index:30;\n    width:36px; height:36px; border-radius:8px;\n    background:var(--panel); border:1px solid var(--border);\n    color:var(--text); cursor:pointer;\n    display:flex; align-items:center; justify-content:center;\n    transition:background .15s;\n  }\n  #menuToggle:hover{background:var(--panel-hover); color:var(--accent);}\n\n  \/* --- Layers Panel (Right) --- *\/\n  #layersPanel{\n    position:relative; height:100vh; width:var(--layers-w);\n    min-width:var(--layers-w);\n    background:var(--panel); border-left:1px solid var(--border);\n    padding:16px; overflow-y:auto;\n    transition:margin-right .22s ease, opacity .18s ease;\n    z-index: 25;\n  }\n  #layersPanel.collapsed{\n    margin-right:calc(var(--layers-w) * -1);\n    opacity:0;\n    pointer-events:none;\n  }\n  #layersToggle{\n    position:absolute; top:12px; right:12px; z-index:30;\n    width:36px; height:36px; border-radius:8px;\n    background:var(--panel); border:1px solid var(--border);\n    color:var(--text); cursor:pointer;\n    display:flex; align-items:center; justify-content:center;\n    transition:background .15s;\n  }\n  #layersToggle:hover{background:var(--panel-hover); color:var(--accent);}\n\n  #canvasWrap{\n    position:relative; flex:1 1 auto; height:100vh; overflow:hidden;\n  }\n  canvas{\n    position:absolute; top:0; left:0;\n    touch-action:none; background:#ffffff;\n    width:100%; height:100%;\n  }\n\n  .section{margin-bottom:20px;}\n  .section-title{\n    font-size:10px; text-transform:uppercase; letter-spacing:1px;\n    color:var(--text-dim); margin-bottom:8px; font-weight:600;\n  }\n  \n  .group{margin-bottom:12px;}\n  .group label{\n    display:flex; justify-content:space-between; font-size:11.5px;\n    margin-bottom:4px; color:var(--text);\n  }\n  .group span.val{color:var(--accent); font-weight:500;}\n\n  input[type=range]{\n    width:100%; appearance:none; height:4px; background:var(--border);\n    border-radius:2px; outline:none; accent-color:var(--accent); cursor:pointer;\n  }\n  \n  .color-row {\n    display: flex; gap: 6px; align-items: center;\n  }\n  input[type=color]{\n    -webkit-appearance:none; border:none; width:32px; height:32px;\n    border-radius:6px; cursor:pointer; background:none; padding:0;\n  }\n  input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }\n  input[type=color]::-webkit-color-swatch { border: 1px solid var(--border); border-radius: 6px; }\n\n  .row{display:flex; gap:6px;}\n  .row button{flex:1;}\n\n  button.tool{\n    padding:8px; border-radius:6px; border:1px solid var(--border);\n    background:transparent; color:var(--text-dim); cursor:pointer;\n    display:flex; align-items:center; justify-content:center;\n    transition:all .15s;\n  }\n  button.tool svg{ width:16px; height:16px; fill:currentColor; }\n  button.tool:hover{background:var(--panel-hover); color:var(--text); border-color:var(--text-dim);}\n  button.tool.active{\n    background:var(--accent); border-color:var(--accent); color:#000000;\n  }\n\n  .checkbox-group {\n    display: flex; align-items: center; gap: 8px; font-size: 11.5px; cursor: pointer; margin-top: 4px; color: var(--text);\n  }\n  .checkbox-group input { cursor: pointer; accent-color: var(--accent); }\n\n  hr{border:none; border-top:1px solid var(--border); margin:16px 0;}\n  \n  #coords{\n    position:absolute; bottom:10px; right:14px; z-index:5;\n    font-size:10px; color:var(--text-dim); background:rgba(20,20,20,.8);\n    padding:3px 6px; border-radius:4px; pointer-events:none;\n    border: 1px solid var(--border);\n  }\n\n  #layersList{ display:flex; flex-direction:column; gap:8px; }\n  .layer-item{\n    border:1px solid var(--border); border-radius:8px; padding:8px;\n    background:transparent; cursor:pointer; transition:border-color .15s, background .15s;\n  }\n  .layer-item:hover{ background:var(--panel-hover); }\n  .layer-item.active{ border-color:var(--accent); background:var(--panel-hover); }\n\n  .layer-row{ display:flex; align-items:center; gap:6px; margin-bottom:6px; }\n  .layer-row .minibtn{\n    width:22px; height:22px; flex:none; border-radius:5px; border:1px solid var(--border);\n    background:transparent; color:var(--text-dim); cursor:pointer;\n    display:flex; align-items:center; justify-content:center;\n  }\n  .layer-row .minibtn:hover{ background:var(--panel-hover); color:var(--text); }\n  .layer-row .minibtn svg{ width:12px; height:12px; fill:currentColor; }\n  .layer-row .minibtn.off{ color:var(--text-dim); opacity:.4; }\n\n  .layer-name{\n    flex:1; min-width:0; background:transparent; border:none; outline:none;\n    color:var(--text); font-size:11.5px; padding:2px 0; border-bottom:1px solid transparent;\n  }\n  .layer-name:focus{ border-bottom:1px solid var(--text-dim); }\n\n  .blend-select{\n    width:100%; background:#141414; color:var(--text); border:1px solid var(--border);\n    border-radius:5px; font-size:10.5px; padding:4px 6px; margin-bottom:6px; cursor:pointer;\n  }\n\n  .opacity-row{ display:flex; align-items:center; gap:6px; }\n  .opacity-row input[type=range]{ flex:1; }\n  .opacity-row span{ font-size:10px; color:var(--accent); width:30px; text-align:right; flex:none; }\n\n  #addLayerBtn{\n    width:100%; margin-top:4px; padding:8px; border-radius:6px; border:1px dashed var(--border);\n    background:transparent; color:var(--text-dim); cursor:pointer; font-size:11.5px;\n    display:flex; align-items:center; justify-content:center; gap:6px;\n  }\n  #addLayerBtn:hover{ background:var(--panel-hover); color:var(--text); border-color:var(--text-dim); }\n  #addLayerBtn svg{ width:13px; height:13px; fill:currentColor; }\n\n  @media (max-aspect-ratio: 1\/1){\n    #panel{\n      position:absolute; top:0; left:0; z-index:15;\n      box-shadow:4px 0 24px rgba(0,0,0,.6);\n    }\n    #panel.collapsed{ margin-left:calc(var(--panel-w) * -1); }\n    #layersPanel{\n      position:absolute; top:0; right:0; z-index:15;\n      box-shadow:-4px 0 24px rgba(0,0,0,.6);\n    }\n    #layersPanel.collapsed{ margin-right:calc(var(--layers-w) * -1); }\n  }\n<\/style>\n<\/head>\n<body>\n\n<div id=\"presetModal\">\n  <div class=\"modal-content\">\n    <div class=\"modal-title\">Initial Setup<\/div>\n    <div class=\"modal-desc\">Select a performance mode to begin drawing according to your device&#8217;s capabilities.<\/div>\n    <div class=\"preset-options\">\n      <button class=\"preset-btn\" data-preset=\"quality\">\n        Quality Preset\n        <span>High canvas resolution and dense stroke precision<\/span>\n      <\/button>\n      <button class=\"preset-btn\" data-preset=\"normal\">\n        Normal\n        <span>Balanced and standard configuration<\/span>\n      <\/button>\n      <button class=\"preset-btn\" data-preset=\"performance\">\n        Performance Preset (Android Recommended)\n        <span>Maximum fluidity and GPU optimization<\/span>\n      <\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<div id=\"app\">\n\n  <button id=\"menuToggle\" title=\"Menu\">\n    <svg viewBox=\"0 0 24 24\"><path d=\"M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z\"\/><\/svg>\n  <\/button>\n\n  <button id=\"layersToggle\" title=\"Layers\">\n    <svg viewBox=\"0 0 24 24\"><path d=\"M12 2 1 8l11 6 11-6-11-6zm0 8.53L4.24 8 12 3.47 19.76 8 12 10.53zM1 13.5l11 6 11-6-1.8-.98L12 17.4 2.8 12.52 1 13.5zm0 4.5 11 6 11-6-1.8-.98L12 21.9 2.8 17.02 1 18z\"\/><\/svg>\n  <\/button>\n\n  <div id=\"panel\" class=\"collapsed\">\n    <div style=\"height: 28px;\"><\/div>\n\n    <div class=\"section\" style=\"margin-top:12px;\">\n      <div class=\"section-title\">Tools<\/div>\n      <div class=\"row\" style=\"margin-bottom:6px;\">\n        <button class=\"tool active\" id=\"btnDraw\" data-tool=\"draw\" title=\"Brush\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z\"\/><\/svg>\n        <\/button>\n        <button class=\"tool\" id=\"btnBlend\" data-tool=\"blend\" title=\"Smudge Mixer\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z\"\/><\/svg>\n        <\/button>\n        <button class=\"tool\" id=\"btnEraser\" data-tool=\"eraser\" title=\"Eraser\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M16.24 3.56l4.95 4.94c.78.79.78 2.05 0 2.84L12 20.53a4.008 4.008 0 0 1-5.66 0L2.81 17c-.78-.79-.78-2.05 0-2.84l10.6-10.6c.79-.78 2.05-.78 2.83 0M4.25 15.58L9.5 20.83c.78.78 2.05.78 2.83 0L17 16.33 12.67 12l-8.42 3.58z\"\/><\/svg>\n        <\/button>\n      <\/div>\n      <div class=\"row\">\n        <button class=\"tool\" id=\"btnShape\" data-tool=\"shape\" title=\"Shapes (Straight &#038; Curved)\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM11 7h2v2h-2zm0 4h2v2h-2zm-4 0h2v2H7zm8 0h2v2h-2zm-4 4h2v2h-2z\"\/><\/svg>\n        <\/button>\n        <button class=\"tool\" id=\"btnLasso\" data-tool=\"lasso\" title=\"Area Fill \/ Lasso\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\/><\/svg>\n        <\/button>\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionColor\">\n      <div class=\"section-title\">Color<\/div>\n      <div class=\"color-row\">\n        <input type=\"color\" id=\"colorPicker\" value=\"#111111\">\n        <span class=\"val\" id=\"colorVal\" style=\"font-size: 11px; font-family: monospace;\">#111111<\/span>\n      <\/div>\n    <\/div>\n\n    <hr>\n\n    <div class=\"section\" id=\"sectionBrush\">\n      <div class=\"section-title\">Brush Settings<\/div>\n      \n      <div class=\"group\">\n        <label>Tip Shape \/ Pattern<\/label>\n        <div class=\"row\" style=\"margin-bottom:6px;\">\n          <button class=\"tool active\" data-brush=\"circle\" title=\"Circle\">\u25cf<\/button>\n          <button class=\"tool\" data-brush=\"square\" title=\"Square\">\u25a0<\/button>\n          <button class=\"tool\" data-brush=\"diamond\" title=\"Diamond\">\u25c6<\/button>\n          <button class=\"tool\" data-brush=\"star\" title=\"Star\">\u2605<\/button>\n        <\/div>\n        <button class=\"tool\" id=\"btnCustomBrush\" style=\"width:100%; font-size:11.5px;\" title=\"Load custom tip (Max 60x60px)\">\n          Load Custom Pattern&#8230;\n        <\/button>\n        <input type=\"file\" id=\"customBrushInput\" accept=\"image\/*\" style=\"display:none;\">\n      <\/div>\n\n      <div class=\"group\">\n        <label class=\"checkbox-group\">\n          <input type=\"checkbox\" id=\"chkRotate\"> Rotate pattern with direction\n        <\/label>\n      <\/div>\n\n      <div class=\"group\">\n        <label>Taper \/ Expand Ends <span class=\"val\" id=\"taperVal\">0%<\/span><\/label>\n        <input type=\"range\" id=\"taper\" min=\"-100\" max=\"100\" value=\"0\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Spacing <span class=\"val\" id=\"spaceVal\">15%<\/span><\/label>\n        <input type=\"range\" id=\"spacing\" min=\"5\" max=\"100\" value=\"15\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Smoothing <span class=\"val\" id=\"stabVal\">40%<\/span><\/label>\n        <input type=\"range\" id=\"stab\" min=\"0\" max=\"99\" value=\"40\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Base Size <span class=\"val\" id=\"widthVal\">6 px<\/span><\/label>\n        <input type=\"range\" id=\"width\" min=\"1\" max=\"150\" value=\"6\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Pressure Sensitivity <span class=\"val\" id=\"pressVal\">50%<\/span><\/label>\n        <input type=\"range\" id=\"pressureSens\" min=\"0\" max=\"100\" value=\"50\">\n      <\/div>\n\n      <div class=\"group\">\n        <label>Velocity Dynamics <span class=\"val\" id=\"velVal\">0<\/span><\/label>\n        <input type=\"range\" id=\"velocity\" min=\"-200\" max=\"200\" value=\"0\">\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionShape\" style=\"display:none;\">\n      <div class=\"section-title\">Shape Settings<\/div>\n      <div class=\"group\">\n        <label>Stroke Type<\/label>\n        <div class=\"row\">\n          <button class=\"tool active\" id=\"btnShapeLine\" title=\"Straight Line\">Line<\/button>\n          <button class=\"tool\" id=\"btnShapeCurve\" title=\"Rounded Curve\">Bezier Curve<\/button>\n        <\/div>\n      <\/div>\n      <div class=\"group\">\n        <label>Shape Thickness <span class=\"val\" id=\"shapeWidthVal\">6 px<\/span><\/label>\n        <input type=\"range\" id=\"shapeWidth\" min=\"1\" max=\"150\" value=\"6\">\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionBlend\" style=\"display:none;\">\n      <div class=\"section-title\">Blend Settings<\/div>\n      <div class=\"group\">\n        <label>Mix Intensity <span class=\"val\" id=\"blendStrengthVal\">50%<\/span><\/label>\n        <input type=\"range\" id=\"blendStrength\" min=\"1\" max=\"100\" value=\"50\">\n      <\/div>\n      <div class=\"group\">\n        <label>Mixer Size <span class=\"val\" id=\"blendWidthVal\">20 px<\/span><\/label>\n        <input type=\"range\" id=\"blendWidth\" min=\"1\" max=\"150\" value=\"20\">\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionEraser\" style=\"display:none;\">\n      <div class=\"section-title\">Eraser Settings<\/div>\n      <div class=\"group\">\n        <label>Eraser Size <span class=\"val\" id=\"eraserWidthVal\">20 px<\/span><\/label>\n        <input type=\"range\" id=\"eraserWidth\" min=\"1\" max=\"150\" value=\"20\">\n      <\/div>\n    <\/div>\n\n    <div class=\"section\" id=\"sectionLasso\" style=\"display:none;\">\n      <div class=\"section-title\">Lasso Settings<\/div>\n      <div class=\"group\">\n        <label>Fill Mode<\/label>\n        <div class=\"row\">\n          <button class=\"tool active\" id=\"btnFillSolid\" title=\"Solid\">Solid<\/button>\n          <button class=\"tool\" id=\"btnFillHalftone\" title=\"Halftone\">Halftone<\/button>\n        <\/div>\n      <\/div>\n      <div class=\"group\" id=\"halftoneSettings\" style=\"display:none; margin-top: 8px;\">\n        <label>Dot Size <span class=\"val\" id=\"dotSizeVal\">4 px<\/span><\/label>\n        <input type=\"range\" id=\"dotSize\" min=\"2\" max=\"15\" value=\"4\">\n        \n        <label style=\"margin-top:6px;\">Grid Spacing <span class=\"val\" id=\"dotSpaceVal\">12 px<\/span><\/label>\n        <input type=\"range\" id=\"dotSpace\" min=\"6\" max=\"30\" value=\"12\">\n      <\/div>\n    <\/div>\n\n    <hr>\n\n    <div class=\"section\">\n      <div class=\"section-title\">Actions<\/div>\n      <div class=\"row\">\n        <button class=\"tool\" id=\"btnUndo\" title=\"Undo\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z\"\/><\/svg>\n        <\/button>\n        <button class=\"tool\" id=\"btnClear\" title=\"Clear active layer\">\n          <svg viewBox=\"0 0 24 24\"><path d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\"\/><\/svg>\n        <\/button>\n      <\/div>\n      <div class=\"row\" style=\"margin-top:6px;\">\n        <button class=\"tool\" id=\"btnSave\" style=\"width:100%\" title=\"Save full composite image\">\n          <svg viewBox=\"0 0 24 24\" style=\"margin-right:6px;\"><path d=\"M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2z\"\/><\/svg>\n          <span style=\"font-size:11.5px\">Save Full PNG<\/span>\n        <\/button>\n      <\/div>\n      <div class=\"row\" style=\"margin-top:6px;\">\n        <button class=\"tool\" id=\"btnSaveLayers\" style=\"width:100%\" title=\"Export layers separately\">\n          <svg viewBox=\"0 0 24 24\" style=\"margin-right:6px;\"><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z\"\/><\/svg>\n          <span style=\"font-size:11.5px\">Export Separate Layers<\/span>\n        <\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div id=\"canvasWrap\">\n    <canvas id=\"canvas\"><\/canvas>\n    <div id=\"coords\"><\/div>\n  <\/div>\n\n  <div id=\"layersPanel\" class=\"collapsed\">\n    <div style=\"height: 28px;\"><\/div>\n    <div class=\"section\" style=\"margin-top:12px;\">\n      <div class=\"section-title\">Layers<\/div>\n      <div id=\"layersList\"><\/div>\n      <button id=\"addLayerBtn\" title=\"New layer\">\n        <svg viewBox=\"0 0 24 24\"><path d=\"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"\/><\/svg>\n        New Layer\n      <\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  const canvas = document.getElementById('canvas');\n  const ctx = canvas.getContext('2d', { alpha: false });\n  const wrap = document.getElementById('canvasWrap');\n\n  const isAndroid = \/android\/i.test(navigator.userAgent || navigator.vendor || window.opera);\n\n  let RES_MULT = isAndroid ? 1 : 2; \n  let dpr = Math.min(Math.max(window.devicePixelRatio || 1, 1), 2) * RES_MULT;\n\n  let zoom = 1;\n  let panX = 0;\n  let panY = 0;\n  let appInitialized = false;\n\n  let animFrameId = null;\n  let needsRedraw = false;\n\n  function requestRedraw() {\n    if (!needsRedraw) {\n      needsRedraw = true;\n      animFrameId = requestAnimationFrame(executeRedraw);\n    }\n  }\n\n  function executeRedraw() {\n    needsRedraw = false;\n    const cssW = wrap.clientWidth;\n    const cssH = wrap.clientHeight;\n\n    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    ctx.fillStyle = '#141414';\n    ctx.fillRect(0, 0, cssW, cssH);\n\n    ctx.save();\n    ctx.translate(panX, panY);\n    ctx.scale(zoom, zoom);\n\n    ctx.fillStyle = '#ffffff';\n    ctx.fillRect(0, 0, cssW, cssH);\n\n    layers.forEach(l => {\n      if(!l.visible) return;\n      ctx.globalAlpha = l.opacity;\n      ctx.globalCompositeOperation = l.blend;\n      ctx.drawImage(l.canvas, 0, 0, cssW, cssH);\n    });\n\n    ctx.restore();\n  }\n\n  window.redrawCanvas = requestRedraw;\n\n  const BLEND_MODES = [\n    ['source-over','Normal'],\n    ['multiply','Multiply'],\n    ['screen','Screen'],\n    ['overlay','Overlay'],\n    ['lighter','Lighter'],\n    ['darken','Darken'],\n    ['lighten','Lighten'],\n    ['color-dodge','Color Dodge'],\n    ['color-burn','Color Burn'],\n    ['hard-light','Hard Light'],\n    ['soft-light','Soft Light'],\n    ['difference','Difference'],\n    ['exclusion','Exclusion'],\n    ['hue','Hue'],\n    ['saturation','Saturation'],\n    ['color','Color'],\n    ['luminosity','Luminosity']\n  ];\n\n  let layers = [];\n  let activeLayerId = null;\n  let layerCounter = 0;\n\n  function getActiveLayer(){\n    return layers.find(l => l.id === activeLayerId) || layers[layers.length-1];\n  }\n\n  function makeLayerCanvas(cssW, cssH){\n    const c = document.createElement('canvas');\n    c.width = Math.round(cssW * dpr);\n    c.height = Math.round(cssH * dpr);\n    const lctx = c.getContext('2d', { willReadFrequently: true });\n    lctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    return {canvas:c, ctx:lctx};\n  }\n\n  function addLayer(name){\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    const {canvas:c, ctx:lctx} = makeLayerCanvas(cssW, cssH);\n    const layer = {\n      id: 'L' + (layerCounter++),\n      name: name || ('Layer ' + (layers.length + 1)),\n      canvas: c, ctx: lctx,\n      opacity: 1, blend: 'source-over', visible: true\n    };\n    layers.push(layer);\n    activeLayerId = layer.id;\n    renderLayers();\n    return layer;\n  }\n\n  function deleteLayer(id){\n    if(layers.length <= 1) return;\n    pushHistory();\n    const idx = layers.findIndex(l => l.id === id);\n    if(idx === -1) return;\n    layers.splice(idx, 1);\n    if(activeLayerId === id){\n      activeLayerId = layers[Math.max(0, idx-1)].id;\n    }\n    renderLayers();\n    redrawCanvas();\n  }\n\n  function moveLayer(id, dir){\n    const idx = layers.findIndex(l => l.id === id);\n    const newIdx = idx + dir;\n    if(newIdx < 0 || newIdx >= layers.length) return;\n    pushHistory();\n    const tmp = layers[idx];\n    layers[idx] = layers[newIdx];\n    layers[newIdx] = tmp;\n    renderLayers();\n    redrawCanvas();\n  }\n\n  function renderLayers(){\n    const list = document.getElementById('layersList');\n    list.innerHTML = '';\n    for(let i = layers.length - 1; i >= 0; i--){\n      const l = layers[i];\n      const item = document.createElement('div');\n      item.className = 'layer-item' + (l.id === activeLayerId ? ' active' : '');\n\n      const row = document.createElement('div');\n      row.className = 'layer-row';\n\n      const eyeBtn = document.createElement('button');\n      eyeBtn.className = 'minibtn' + (l.visible ? '' : ' off');\n      eyeBtn.title = 'Show \/ hide';\n      eyeBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M12 5c-5 0-9.27 3.11-11 7.5C2.73 16.89 7 20 12 20s9.27-3.11 11-7.5C21.27 8.11 17 5 12 5zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8a3 3 0 100 6 3 3 0 000-6z\"\/><\/svg>';\n      eyeBtn.addEventListener('click', (e)=>{\n        e.stopPropagation();\n        l.visible = !l.visible;\n        redrawCanvas();\n        renderLayers();\n      });\n\n      const nameInput = document.createElement('input');\n      nameInput.className = 'layer-name';\n      nameInput.value = l.name;\n      nameInput.addEventListener('click', e => e.stopPropagation());\n      nameInput.addEventListener('input', e => { l.name = e.target.value; });\n\n      const upBtn = document.createElement('button');\n      upBtn.className = 'minibtn';\n      upBtn.title = 'Move up';\n      upBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M7 14l5-5 5 5H7z\"\/><\/svg>';\n      upBtn.addEventListener('click', (e)=>{ e.stopPropagation(); moveLayer(l.id, 1); });\n\n      const downBtn = document.createElement('button');\n      downBtn.className = 'minibtn';\n      downBtn.title = 'Move down';\n      downBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M7 10l5 5 5-5H7z\"\/><\/svg>';\n      downBtn.addEventListener('click', (e)=>{ e.stopPropagation(); moveLayer(l.id, -1); });\n\n      const delBtn = document.createElement('button');\n      delBtn.className = 'minibtn';\n      delBtn.title = 'Delete layer';\n      delBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\"\/><\/svg>';\n      delBtn.addEventListener('click', (e)=>{ e.stopPropagation(); deleteLayer(l.id); });\n\n      row.appendChild(eyeBtn);\n      row.appendChild(nameInput);\n      row.appendChild(upBtn);\n      row.appendChild(downBtn);\n      if(layers.length > 1) row.appendChild(delBtn);\n\n      const blendSelect = document.createElement('select');\n      blendSelect.className = 'blend-select';\n      BLEND_MODES.forEach(([val, label])=>{\n        const opt = document.createElement('option');\n        opt.value = val; opt.textContent = label;\n        if(val === l.blend) opt.selected = true;\n        blendSelect.appendChild(opt);\n      });\n      blendSelect.addEventListener('click', e => e.stopPropagation());\n      blendSelect.addEventListener('change', e => {\n        l.blend = e.target.value;\n        redrawCanvas();\n      });\n\n      const opRow = document.createElement('div');\n      opRow.className = 'opacity-row';\n      const opRange = document.createElement('input');\n      opRange.type = 'range'; opRange.min = 0; opRange.max = 100;\n      opRange.value = Math.round(l.opacity * 100);\n      opRange.addEventListener('click', e => e.stopPropagation());\n      const opLabel = document.createElement('span');\n      opLabel.textContent = Math.round(l.opacity * 100) + '%';\n      opRange.addEventListener('input', e => {\n        l.opacity = (+e.target.value) \/ 100;\n        opLabel.textContent = e.target.value + '%';\n        redrawCanvas();\n      });\n      opRow.appendChild(opRange);\n      opRow.appendChild(opLabel);\n\n      item.addEventListener('click', ()=>{\n        activeLayerId = l.id;\n        renderLayers();\n      });\n\n      item.appendChild(row);\n      item.appendChild(blendSelect);\n      item.appendChild(opRow);\n      list.appendChild(item);\n    }\n  }\n\n  document.getElementById('addLayerBtn').addEventListener('click', ()=>{\n    pushHistory();\n    addLayer();\n    redrawCanvas();\n  });\n\n  function resizeLayerCanvas(layer, cssW, cssH){\n    const prevCanvas = layer.canvas;\n    const prevW = prevCanvas.width, prevH = prevCanvas.height;\n    const temp = document.createElement('canvas');\n    temp.width = prevW; temp.height = prevH;\n    temp.getContext('2d').drawImage(prevCanvas, 0, 0);\n\n    layer.canvas.width = Math.round(cssW * dpr);\n    layer.canvas.height = Math.round(cssH * dpr);\n    layer.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n    if(prevW > 0 && prevH > 0){\n      layer.ctx.drawImage(temp, 0, 0, prevW, prevH, 0, 0, cssW, cssH);\n    }\n  }\n\n  function resize(){\n    if(!appInitialized) return;\n    const cssW = wrap.clientWidth;\n    const cssH = wrap.clientHeight;\n\n    canvas.width = Math.round(cssW * dpr);\n    canvas.height = Math.round(cssH * dpr);\n    canvas.style.width = cssW + 'px';\n    canvas.style.height = cssH + 'px';\n\n    if(layers.length === 0){\n      addLayer('Layer 1');\n    } else {\n      layers.forEach(l => resizeLayerCanvas(l, cssW, cssH));\n    }\n    redrawCanvas();\n  }\n\n  window.addEventListener('resize', resize);\n\n  wrap.addEventListener('wheel', (e)=>{\n    e.preventDefault();\n    const rect = canvas.getBoundingClientRect();\n    const mouseX = e.clientX - rect.left;\n    const mouseY = e.clientY - rect.top;\n\n    const zoomFactor = e.deltaY < 0 ? 1.15 : 1\/1.15;\n    const newZoom = Math.max(0.1, Math.min(20, zoom * zoomFactor));\n\n    panX = mouseX - (mouseX - panX) * (newZoom \/ zoom);\n    panY = mouseY - (mouseY - panY) * (newZoom \/ zoom);\n    zoom = newZoom;\n\n    redrawCanvas();\n  }, {passive: false});\n\n  const menuToggle = document.getElementById('menuToggle');\n  const panel = document.getElementById('panel');\n  menuToggle.addEventListener('click', ()=>{\n    panel.classList.toggle('collapsed');\n    setTimeout(resize, 230);\n  });\n\n  const layersToggle = document.getElementById('layersToggle');\n  const layersPanel = document.getElementById('layersPanel');\n  layersToggle.addEventListener('click', ()=>{\n    layersPanel.classList.toggle('collapsed');\n    setTimeout(resize, 230);\n  });\n\n  const colorPicker = document.getElementById('colorPicker');\n  const colorVal = document.getElementById('colorVal');\n  const sectionColor = document.getElementById('sectionColor');\n  const stabInput = document.getElementById('stab');\n  const stabVal = document.getElementById('stabVal');\n  const widthInput = document.getElementById('width');\n  const widthVal = document.getElementById('widthVal');\n  const blendWidthInput = document.getElementById('blendWidth');\n  const blendWidthVal = document.getElementById('blendWidthVal');\n  const blendStrengthInput = document.getElementById('blendStrength');\n  const blendStrengthVal = document.getElementById('blendStrengthVal');\n  const eraserWidthInput = document.getElementById('eraserWidth');\n  const eraserWidthVal = document.getElementById('eraserWidthVal');\n  const pressInput = document.getElementById('pressureSens');\n  const pressVal = document.getElementById('pressVal');\n  const velInput = document.getElementById('velocity');\n  const velVal = document.getElementById('velVal');\n  const spaceInput = document.getElementById('spacing');\n  const spaceVal = document.getElementById('spaceVal');\n  const taperInput = document.getElementById('taper');\n  const taperVal = document.getElementById('taperVal');\n  \n  const chkRotate = document.getElementById('chkRotate');\n\n  const btnCustomBrush = document.getElementById('btnCustomBrush');\n  const customBrushInput = document.getElementById('customBrushInput');\n  let customBrushPattern = null;\n\n  const btnFillSolid = document.getElementById('btnFillSolid');\n  const btnFillHalftone = document.getElementById('btnFillHalftone');\n  const halftoneSettings = document.getElementById('halftoneSettings');\n  const dotSizeInput = document.getElementById('dotSize');\n  const dotSizeVal = document.getElementById('dotSizeVal');\n  const dotSpaceInput = document.getElementById('dotSpace');\n  const dotSpaceVal = document.getElementById('dotSpaceVal');\n\n  const btnShapeLine = document.getElementById('btnShapeLine');\n  const btnShapeCurve = document.getElementById('btnShapeCurve');\n  const shapeWidthInput = document.getElementById('shapeWidth');\n  const shapeWidthVal = document.getElementById('shapeWidthVal');\n\n  const sectionBrush = document.getElementById('sectionBrush');\n  const sectionShape = document.getElementById('sectionShape');\n  const sectionBlend = document.getElementById('sectionBlend');\n  const sectionEraser = document.getElementById('sectionEraser');\n  const sectionLasso = document.getElementById('sectionLasso');\n  const coords = document.getElementById('coords');\n\n  const btnDraw = document.getElementById('btnDraw');\n  const btnBlend = document.getElementById('btnBlend');\n  const btnEraser = document.getElementById('btnEraser');\n  const btnShape = document.getElementById('btnShape');\n  const btnLasso = document.getElementById('btnLasso');\n  const btnUndo = document.getElementById('btnUndo');\n  const btnClear = document.getElementById('btnClear');\n  const btnSave = document.getElementById('btnSave');\n  const btnSaveLayers = document.getElementById('btnSaveLayers');\n\n  let currentColor = colorPicker.value;\n  let stabilization = parseInt(stabInput.value,10);\n  let baseWidth = parseInt(widthInput.value,10);\n  let blendWidth = parseInt(blendWidthInput.value,10);\n  let blendStrength = parseInt(blendStrengthInput.value,10);\n  let eraserWidth = parseInt(eraserWidthInput.value,10);\n  let pressureSensitivity = parseInt(pressInput.value,10);\n  let velRelation = parseInt(velInput.value,10);\n  let spacingValue = parseInt(spaceInput.value,10);\n  let taperValue = parseInt(taperInput.value, 10);\n  let brushShape = 'circle';\n  let tool = 'draw';\n  let fillMode = 'solid';\n  let shapeMode = 'line';\n  let shapeWidth = parseInt(shapeWidthInput.value, 10);\n  let dotSize = parseInt(dotSizeInput.value, 10);\n  let dotSpace = parseInt(dotSpaceInput.value, 10);\n\n  const brushButtons = document.querySelectorAll('[data-brush]');\n  brushButtons.forEach(btn => {\n    btn.addEventListener('click', () => {\n      brushButtons.forEach(b => b.classList.remove('active'));\n      btn.classList.add('active');\n      brushShape = btn.getAttribute('data-brush');\n      customBrushPattern = null;\n      btnCustomBrush.style.borderColor = 'var(--border)';\n      btnCustomBrush.textContent = 'Load Custom Pattern...';\n    });\n  });\n\n  btnCustomBrush.addEventListener('click', () => customBrushInput.click());\n  customBrushInput.addEventListener('change', (e) => {\n    const file = e.target.files[0];\n    if(!file) return;\n    const reader = new FileReader();\n    reader.onload = function(event) {\n      const img = new Image();\n      img.onload = function() {\n        let w = img.width;\n        let h = img.height;\n        const maxLimit = 60;\n        if(w > maxLimit || h > maxLimit) {\n          if(w > h) {\n            h = Math.round((h * maxLimit) \/ w);\n            w = maxLimit;\n          } else {\n            w = Math.round((w * maxLimit) \/ h);\n            h = maxLimit;\n          }\n        }\n        const tempCanvas = document.createElement('canvas');\n        tempCanvas.width = w;\n        tempCanvas.height = h;\n        const tCtx = tempCanvas.getContext('2d');\n        tCtx.drawImage(img, 0, 0, w, h);\n\n        customBrushPattern = tempCanvas;\n        brushShape = 'custom';\n        brushButtons.forEach(b => b.classList.remove('active'));\n        btnCustomBrush.style.borderColor = 'var(--accent)';\n        btnCustomBrush.textContent = `Pattern Loaded (${w}x${h}px)`;\n      };\n      img.src = event.target.result;\n    };\n    reader.readAsDataURL(file);\n  });\n\n  colorVal.textContent = currentColor;\n  colorPicker.addEventListener('input', e=>{\n    currentColor = e.target.value;\n    colorVal.textContent = currentColor;\n  });\n  stabInput.addEventListener('input', e=>{\n    stabilization = parseInt(e.target.value,10);\n    stabVal.textContent = stabilization + '%';\n  });\n  widthInput.addEventListener('input', e=>{\n    baseWidth = parseInt(e.target.value,10);\n    widthVal.textContent = baseWidth + ' px';\n  });\n  blendWidthInput.addEventListener('input', e=>{\n    blendWidth = parseInt(e.target.value,10);\n    blendWidthVal.textContent = blendWidth + ' px';\n  });\n  blendStrengthInput.addEventListener('input', e=>{\n    blendStrength = parseInt(e.target.value,10);\n    blendStrengthVal.textContent = blendStrength + '%';\n  });\n  eraserWidthInput.addEventListener('input', e=>{\n    eraserWidth = parseInt(e.target.value,10);\n    eraserWidthVal.textContent = eraserWidth + ' px';\n  });\n  pressInput.addEventListener('input', e=>{\n    pressureSensitivity = parseInt(e.target.value,10);\n    pressVal.textContent = pressureSensitivity + '%';\n  });\n  velInput.addEventListener('input', e=>{\n    velRelation = parseInt(e.target.value,10);\n    velVal.textContent = velRelation;\n  });\n  spaceInput.addEventListener('input', e=>{\n    spacingValue = parseInt(e.target.value,10);\n    spaceVal.textContent = spacingValue + '%';\n  });\n  taperInput.addEventListener('input', e=>{\n    taperValue = parseInt(e.target.value, 10);\n    taperVal.textContent = taperValue + '%';\n  });\n  shapeWidthInput.addEventListener('input', e => {\n    shapeWidth = parseInt(e.target.value, 10);\n    shapeWidthVal.textContent = shapeWidth + ' px';\n  });\n  dotSizeInput.addEventListener('input', e=>{\n    dotSize = parseInt(e.target.value, 10);\n    dotSizeVal.textContent = dotSize + ' px';\n  });\n  dotSpaceInput.addEventListener('input', e=>{\n    dotSpace = parseInt(e.target.value, 10);\n    dotSpaceVal.textContent = dotSpace + ' px';\n  });\n\n  btnFillSolid.addEventListener('click', () => {\n    fillMode = 'solid';\n    btnFillSolid.classList.add('active');\n    btnFillHalftone.classList.remove('active');\n    halftoneSettings.style.display = 'none';\n  });\n  btnFillHalftone.addEventListener('click', () => {\n    fillMode = 'halftone';\n    btnFillHalftone.classList.add('active');\n    btnFillSolid.classList.remove('active');\n    halftoneSettings.style.display = 'block';\n  });\n\n  btnShapeLine.addEventListener('click', () => {\n    shapeMode = 'line';\n    btnShapeLine.classList.add('active');\n    btnShapeCurve.classList.remove('active');\n  });\n  btnShapeCurve.addEventListener('click', () => {\n    shapeMode = 'curve';\n    btnShapeCurve.classList.add('active');\n    btnShapeLine.classList.remove('active');\n  });\n\n  function setTool(t){\n    tool = t;\n    btnDraw.classList.toggle('active', t==='draw');\n    btnBlend.classList.toggle('active', t==='blend');\n    btnEraser.classList.toggle('active', t==='eraser');\n    btnShape.classList.toggle('active', t==='shape');\n    btnLasso.classList.toggle('active', t==='lasso');\n\n    sectionBrush.style.display = (t === 'draw') ? 'block' : 'none';\n    sectionShape.style.display = (t === 'shape') ? 'block' : 'none';\n    sectionBlend.style.display = (t === 'blend') ? 'block' : 'none';\n    sectionEraser.style.display = (t === 'eraser') ? 'block' : 'none';\n    sectionLasso.style.display = (t === 'lasso') ? 'block' : 'none';\n    sectionColor.style.display = (t === 'eraser' || t === 'blend') ? 'none' : 'block';\n  }\n  btnDraw.addEventListener('click', ()=> setTool('draw'));\n  btnBlend.addEventListener('click', ()=> setTool('blend'));\n  btnEraser.addEventListener('click', ()=> setTool('eraser'));\n  btnShape.addEventListener('click', ()=> setTool('shape'));\n  btnLasso.addEventListener('click', ()=> setTool('lasso'));\n\n  let history = [];\n  const MAX_HISTORY = 15;\n  function pushHistory(){\n    try{\n      const snap = {\n        activeLayerId,\n        layers: layers.map(l => ({\n          id:l.id, name:l.name, blend:l.blend, opacity:l.opacity, visible:l.visible,\n          dataURL: l.canvas.toDataURL('image\/png')\n        }))\n      };\n      history.push(snap);\n      if(history.length > MAX_HISTORY) history.shift();\n    }catch(e){}\n  }\n\n  function restoreSnapshot(snap){\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    const loaders = snap.layers.map(ld => new Promise(resolve=>{\n      const img = new Image();\n      img.onload = ()=>{\n        const {canvas:c, ctx:lctx} = makeLayerCanvas(cssW, cssH);\n        lctx.drawImage(img, 0, 0, cssW, cssH);\n        resolve({id:ld.id, name:ld.name, blend:ld.blend, opacity:ld.opacity, visible:ld.visible, canvas:c, ctx:lctx});\n      };\n      img.src = ld.dataURL;\n    }));\n    Promise.all(loaders).then(newLayers=>{\n      layers = newLayers;\n      activeLayerId = snap.activeLayerId;\n      renderLayers();\n      redrawCanvas();\n    });\n  }\n\n  btnUndo.addEventListener('click', ()=>{\n    if(history.length === 0) return;\n    restoreSnapshot(history.pop());\n  });\n\n  btnClear.addEventListener('click', ()=>{\n    pushHistory();\n    const layer = getActiveLayer();\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    layer.ctx.save();\n    layer.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    layer.ctx.clearRect(0, 0, cssW, cssH);\n    layer.ctx.restore();\n    redrawCanvas();\n  });\n\n  btnSave.addEventListener('click', ()=>{\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    const out = document.createElement('canvas');\n    out.width = Math.round(cssW * dpr);\n    out.height = Math.round(cssH * dpr);\n    const octx = out.getContext('2d');\n    octx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    octx.fillStyle = '#ffffff';\n    octx.fillRect(0, 0, cssW, cssH);\n    layers.forEach(l => {\n      if(!l.visible) return;\n      octx.globalAlpha = l.opacity;\n      octx.globalCompositeOperation = l.blend;\n      octx.drawImage(l.canvas, 0, 0, cssW, cssH);\n    });\n    const link = document.createElement('a');\n    link.download = 'full_drawing.png';\n    link.href = out.toDataURL('image\/png');\n    link.click();\n  });\n\n  btnSaveLayers.addEventListener('click', ()=>{\n    const cssW = wrap.clientWidth, cssH = wrap.clientHeight;\n    layers.forEach((l, index) => {\n      const out = document.createElement('canvas');\n      out.width = Math.round(cssW * dpr);\n      out.height = Math.round(cssH * dpr);\n      const octx = out.getContext('2d');\n      octx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      octx.drawImage(l.canvas, 0, 0, cssW, cssH);\n\n      const link = document.createElement('a');\n      link.download = `${l.name.replace(\/\\s+\/g, '_')}_${index + 1}.png`;\n      link.href = out.toDataURL('image\/png');\n      link.click();\n    });\n  });\n\n  function getPos(e){\n    const rect = canvas.getBoundingClientRect();\n    const clientX = e.clientX - rect.left;\n    const clientY = e.clientY - rect.top;\n    return {\n      x: (clientX - panX) \/ zoom,\n      y: (clientY - panY) \/ zoom\n    };\n  }\n  function dist(a,b){ return Math.hypot(b.x-a.x, b.y-a.y); }\n\n  let drawing = false;\n  let rawPoint = null;\n  let stablePoint = null;\n  let lastTime = 0;\n  let smoothedSpeed = 0;\n  let lastStampPos = null;\n  let distanceSinceLastStamp = 0;\n  let currentStrokePoints = [];\n  let smudgeColorSample = null;\n\n  let activePointers = new Map();\n  let isMultiTouch = false;\n  let initialPinchDist = 0;\n  let initialZoom = 1;\n  let initialPanCenter = {x: 0, y: 0};\n  let startPanOffset = {x: 0, y: 0};\n\n  function computeWidth(speed, pressure){\n    let activeWidth = baseWidth;\n    if(tool === 'eraser') activeWidth = eraserWidth;\n    if(tool === 'blend') activeWidth = blendWidth;\n\n    const normSpeed = Math.min(speed \/ 2.2, 1);\n    const relSpeed = velRelation \/ 100;\n    const speedFactor = 1 + relSpeed * normSpeed;\n    const rawPress = (pressure !== undefined && pressure > 0) ? pressure : 0.5;\n    const pressFactor = 1 + (rawPress - 0.5) * (pressureSensitivity \/ 50);\n    return Math.max(1, activeWidth * speedFactor * pressFactor);\n  }\n\n  function sampleColorAt(x, y) {\n    const layer = getActiveLayer();\n    const px = Math.round(x * dpr);\n    const py = Math.round(y * dpr);\n    if(px < 0 || py < 0 || px >= layer.canvas.width || py >= layer.canvas.height) return null;\n    const pData = layer.ctx.getImageData(px, py, 1, 1).data;\n    if(pData[3] === 0) return null;\n    return `rgb(${pData[0]},${pData[1]},${pData[2]})`;\n  }\n\n  function drawStamp(x, y, size, color, angle = 0){\n    const layer = getActiveLayer();\n    const lctx = layer.ctx;\n    lctx.save();\n\n    if(tool === 'eraser'){\n      lctx.globalCompositeOperation = 'destination-out';\n      lctx.fillStyle = 'rgba(0,0,0,1)';\n      lctx.strokeStyle = 'rgba(0,0,0,1)';\n    } else if(tool === 'blend'){\n      lctx.globalCompositeOperation = 'source-over';\n      if(!smudgeColorSample) {\n        let sampled = sampleColorAt(x, y);\n        if(sampled) smudgeColorSample = sampled;\n        else return;\n      }\n      lctx.fillStyle = smudgeColorSample;\n      lctx.strokeStyle = smudgeColorSample;\n      lctx.globalAlpha = (blendStrength \/ 100);\n    } else {\n      lctx.globalCompositeOperation = 'source-over';\n      lctx.fillStyle = color;\n      lctx.strokeStyle = color;\n    }\n\n    const r = size \/ 2;\n\n    if(chkRotate.checked && angle !== 0 && tool !== 'eraser'){\n      lctx.translate(x, y);\n      lctx.rotate(angle);\n      lctx.translate(-x, -y);\n    }\n\n    if(tool !== 'blend' && brushShape === 'custom' && customBrushPattern){\n      lctx.drawImage(customBrushPattern, x - r, y - r, size, size);\n    } else if(brushShape === 'circle' || tool === 'blend'){\n      lctx.beginPath();\n      lctx.arc(x, y, r, 0, Math.PI * 2);\n      lctx.fill();\n    } else if(brushShape === 'square'){\n      lctx.fillRect(x - r, y - r, size, size);\n    } else if(brushShape === 'diamond'){\n      lctx.beginPath();\n      lctx.moveTo(x, y - r);\n      lctx.lineTo(x + r, y);\n      lctx.lineTo(x, y + r);\n      lctx.lineTo(x - r, y);\n      lctx.closePath();\n      lctx.fill();\n    } else if(brushShape === 'star'){\n      lctx.beginPath();\n      let spikes = 5;\n      let outerRadius = r;\n      let innerRadius = r \/ 2;\n      let rot = Math.PI \/ 2 * 3;\n      let step = Math.PI \/ spikes;\n\n      lctx.moveTo(x, y - outerRadius);\n      for (let i = 0; i < spikes; i++) {\n        let xCoord = x + Math.cos(rot) * outerRadius;\n        let yCoord = y + Math.sin(rot) * outerRadius;\n        lctx.lineTo(xCoord, yCoord);\n        rot += step;\n\n        xCoord = x + Math.cos(rot) * innerRadius;\n        yCoord = y + Math.sin(rot) * innerRadius;\n        lctx.lineTo(xCoord, yCoord);\n        rot += step;\n      }\n      lctx.lineTo(x, y - outerRadius);\n      lctx.closePath();\n      lctx.fill();\n    }\n    lctx.restore();\n  }\n\n  function startDraw(e){\n    activePointers.set(e.pointerId, {clientX: e.clientX, clientY: e.clientY});\n\n    if(activePointers.size >= 2){\n      if(drawing){\n        endDraw(e);\n      }\n      isMultiTouch = true;\n      const pts = Array.from(activePointers.values());\n      initialPinchDist = Math.hypot(pts[1].clientX - pts[0].clientX, pts[1].clientY - pts[0].clientY);\n      initialZoom = zoom;\n      startPanOffset = {x: panX, y: panY};\n      initialPanCenter = {\n        x: (pts[0].clientX + pts[1].clientX) \/ 2,\n        y: (pts[0].clientY + pts[1].clientY) \/ 2\n      };\n      return;\n    }\n\n    if(isMultiTouch) return;\n    if(tool !== 'draw' && tool !== 'eraser' && tool !== 'blend') return;\n    \n    drawing = true;\n    pushHistory();\n    const p = getPos(e);\n    \n    rawPoint = {x: p.x, y: p.y};\n    stablePoint = {x: p.x, y: p.y};\n    \n    lastTime = performance.now();\n    smoothedSpeed = 0;\n    smudgeColorSample = (tool === 'blend') ? sampleColorAt(p.x, p.y) : null;\n    \n    const initialW = computeWidth(0, e.pressure);\n    lastStampPos = {x: p.x, y: p.y};\n    distanceSinceLastStamp = 0;\n    currentStrokePoints = [{x: p.x, y: p.y, w: initialW}];\n\n    const paintColor = (tool === 'eraser') ? '#ffffff' : currentColor;\n    drawStamp(p.x, p.y, initialW, paintColor, 0);\n    redrawCanvas();\n  }\n\n  function moveDraw(e){\n    if(activePointers.has(e.pointerId)){\n      activePointers.set(e.pointerId, {clientX: e.clientX, clientY: e.clientY});\n    }\n\n    if(isMultiTouch && activePointers.size >= 2){\n      e.preventDefault();\n      const pts = Array.from(activePointers.values());\n      const currentDist = Math.hypot(pts[1].clientX - pts[0].clientX, pts[1].clientY - pts[0].clientY);\n      const currentCenter = {\n        x: (pts[0].clientX + pts[1].clientX) \/ 2,\n        y: (pts[0].clientY + pts[1].clientY) \/ 2\n      };\n\n      const zoomFactor = currentDist \/ initialPinchDist;\n      const newZoom = Math.max(0.1, Math.min(20, initialZoom * zoomFactor));\n\n      const rect = canvas.getBoundingClientRect();\n      const centerX = initialPanCenter.x - rect.left;\n      const centerY = initialPanCenter.y - rect.top;\n\n      panX = currentCenter.x - rect.left - (centerX - startPanOffset.x) * (newZoom \/ initialZoom);\n      panY = currentCenter.y - rect.top - (centerY - startPanOffset.y) * (newZoom \/ initialZoom);\n      zoom = newZoom;\n\n      redrawCanvas();\n      return;\n    }\n\n    const p = getPos(e);\n    coords.textContent = Math.round(p.x) + ', ' + Math.round(p.y);\n    if(!drawing || (tool !== 'draw' && tool !== 'eraser' && tool !== 'blend')) return;\n    e.preventDefault();\n\n    rawPoint = p;\n    const now = performance.now();\n    const dt = Math.max(now - lastTime, 1);\n    lastTime = now;\n\n    const pull = 1 - (stabilization\/100)*0.95;\n    stablePoint.x += (rawPoint.x - stablePoint.x) * pull;\n    stablePoint.y += (rawPoint.y - stablePoint.y) * pull;\n\n    const d = dist(lastStampPos, stablePoint);\n    const instSpeed = d \/ dt;\n\n    smoothedSpeed += (instSpeed - smoothedSpeed) * pull;\n    const currentWidth = computeWidth(smoothedSpeed, e.pressure);\n\n    let angle = 0;\n    if(chkRotate.checked && tool !== 'eraser' && tool !== 'blend'){\n      angle = Math.atan2(stablePoint.y - lastStampPos.y, stablePoint.x - lastStampPos.x);\n    }\n\n    currentStrokePoints.push({x: stablePoint.x, y: stablePoint.y, w: currentWidth});\n\n    const spacingPx = Math.max(1, currentWidth * (spacingValue \/ 100));\n    distanceSinceLastStamp += d;\n\n    if (distanceSinceLastStamp >= spacingPx) {\n      const steps = Math.floor(distanceSinceLastStamp \/ spacingPx);\n      const paintColor = (tool === 'eraser') ? '#ffffff' : currentColor;\n      for (let i = 1; i <= steps; i++) {\n        const t = (i * spacingPx) \/ distanceSinceLastStamp;\n        const interpX = lastStampPos.x + (stablePoint.x - lastStampPos.x) * t;\n        const interpY = lastStampPos.y + (stablePoint.y - lastStampPos.y) * t;\n        \n        if(tool === 'blend') {\n          let sampled = sampleColorAt(interpX, interpY);\n          if(sampled) smudgeColorSample = sampled;\n        }\n\n        drawStamp(interpX, interpY, currentWidth, paintColor, angle);\n      }\n      lastStampPos = {x: stablePoint.x, y: stablePoint.y};\n      distanceSinceLastStamp %= spacingPx;\n      redrawCanvas();\n    }\n  }\n\n  function endDraw(e){\n    activePointers.delete(e.pointerId);\n\n    if(activePointers.size < 2){\n      isMultiTouch = false;\n    }\n\n    if(drawing &#038;&#038; (tool==='draw' || tool==='eraser' || tool==='blend')){\n      drawing = false;\n      \n      if(tool === 'draw' &#038;&#038; taperValue !== 0 &#038;&#038; currentStrokePoints.length > 2){\n        const totalPts = currentStrokePoints.length;\n        const taperWindow = Math.min(Math.floor(totalPts \/ 2), 25);\n        \n        for(let i = 0; i < totalPts; i++){\n          let pt = currentStrokePoints[i];\n          let factor = 1;\n          \n          if(i < taperWindow){\n            let pProg = i \/ taperWindow;\n            if(taperValue > 0) factor = pProg + (1 - pProg) * (1 - taperValue \/ 100);\n            else factor = pProg + (1 - pProg) * (1 + Math.abs(taperValue) \/ 100);\n          } else if(i > totalPts - taperWindow){\n            let pProg = (totalPts - i) \/ taperWindow;\n            if(taperValue > 0) factor = pProg + (1 - pProg) * (1 - taperValue \/ 100);\n            else factor = pProg + (1 - pProg) * (1 + Math.abs(taperValue) \/ 100);\n          }\n          \n          let modifiedWidth = Math.max(1, pt.w * factor);\n          drawStamp(pt.x, pt.y, modifiedWidth, currentColor, 0);\n        }\n        redrawCanvas();\n      }\n\n      rawPoint = null; \n      stablePoint = null; \n      lastStampPos = null;\n      currentStrokePoints = [];\n      smudgeColorSample = null;\n    }\n  }\n\n  \/\/ --- Shape Tool (Lines & Bezier Curves) ---\n  let shapeStart = null;\n  let shapeCurrent = null;\n  let shapeActive = false;\n\n  function startShape(e){\n    if(tool !== 'shape') return;\n    e.preventDefault();\n    shapeActive = true;\n    shapeStart = getPos(e);\n    shapeCurrent = {x: shapeStart.x, y: shapeStart.y};\n  }\n\n  function moveShape(e){\n    if(!shapeActive || tool !== 'shape') return;\n    e.preventDefault();\n    shapeCurrent = getPos(e);\n    drawShapePreviewOverlay();\n  }\n\n  function endShape(e){\n    if(!shapeActive || tool !== 'shape') return;\n    shapeActive = false;\n    pushHistory();\n    commitShapeToLayer();\n    shapeStart = null;\n    shapeCurrent = null;\n    redrawCanvas();\n  }\n\n  function drawShapePreviewOverlay(){\n    redrawCanvas();\n    ctx.save();\n    ctx.translate(panX, panY);\n    ctx.scale(zoom, zoom);\n    ctx.strokeStyle = currentColor;\n    ctx.lineWidth = shapeWidth \/ zoom;\n    ctx.lineCap = 'round';\n    ctx.lineJoin = 'round';\n    ctx.beginPath();\n    ctx.moveTo(shapeStart.x, shapeStart.y);\n    if(shapeMode === 'line'){\n      ctx.lineTo(shapeCurrent.x, shapeCurrent.y);\n    } else {\n      let cx = (shapeStart.x + shapeCurrent.x) \/ 2 + (shapeCurrent.y - shapeStart.y) * 0.3;\n      let cy = (shapeStart.y + shapeCurrent.y) \/ 2 - (shapeCurrent.x - shapeStart.x) * 0.3;\n      ctx.quadraticCurveTo(cx, cy, shapeCurrent.x, shapeCurrent.y);\n    }\n    ctx.stroke();\n    ctx.restore();\n  }\n\n  function commitShapeToLayer(){\n    const layer = getActiveLayer();\n    const lctx = layer.ctx;\n    lctx.save();\n    lctx.globalCompositeOperation = 'source-over';\n    lctx.strokeStyle = currentColor;\n    lctx.fillStyle = currentColor;\n    lctx.lineWidth = shapeWidth;\n    lctx.lineCap = 'round';\n    lctx.lineJoin = 'round';\n    lctx.beginPath();\n    lctx.moveTo(shapeStart.x, shapeStart.y);\n    if(shapeMode === 'line'){\n      lctx.lineTo(shapeCurrent.x, shapeCurrent.y);\n    } else {\n      let cx = (shapeStart.x + shapeCurrent.x) \/ 2 + (shapeCurrent.y - shapeStart.y) * 0.3;\n      let cy = (shapeStart.y + shapeCurrent.y) \/ 2 - (shapeCurrent.x - shapeStart.x) * 0.3;\n      lctx.quadraticCurveTo(cx, cy, shapeCurrent.x, shapeCurrent.y);\n    }\n    lctx.stroke();\n    lctx.restore();\n  }\n\n  \/\/ --- Lasso Tool ---\n  let lassoPoints = [];\n  let lassoActive = false;\n\n  function startLasso(e){\n    e.preventDefault();\n    if(tool !== 'lasso') return;\n    lassoActive = true;\n    const p = getPos(e);\n    lassoPoints = [p];\n  }\n  function moveLasso(e){\n    if(!lassoActive || tool !== 'lasso') return;\n    e.preventDefault();\n    const p = getPos(e);\n    lassoPoints.push(p);\n    drawLassoPreviewOverlay();\n  }\n  function endLasso(e){\n    if(!lassoActive || tool !== 'lasso') return;\n    lassoActive = false;\n    if(lassoPoints.length > 2){\n      pushHistory();\n      fillLasso();\n    }\n    lassoPoints = [];\n    redrawCanvas();\n  }\n\n  function drawLassoPreviewOverlay(){\n    redrawCanvas();\n    ctx.save();\n    ctx.translate(panX, panY);\n    ctx.scale(zoom, zoom);\n    ctx.setLineDash([4\/zoom, 4\/zoom]);\n    ctx.strokeStyle = '#888888';\n    ctx.lineWidth = 1.5 \/ zoom;\n    ctx.beginPath();\n    ctx.moveTo(lassoPoints[0].x, lassoPoints[0].y);\n    for(let i=1;i<lassoPoints.length;i++){\n      ctx.lineTo(lassoPoints[i].x, lassoPoints[i].y);\n    }\n    ctx.stroke();\n    ctx.restore();\n  }\n\n  function fillLasso(){\n    const layer = getActiveLayer();\n    const lctx = layer.ctx;\n    lctx.save();\n    lctx.globalCompositeOperation = 'source-over';\n    lctx.beginPath();\n    lctx.moveTo(lassoPoints[0].x, lassoPoints[0].y);\n    for(let i=1;i<lassoPoints.length;i++){\n      lctx.lineTo(lassoPoints[i].x, lassoPoints[i].y);\n    }\n    lctx.closePath();\n\n    if(fillMode === 'solid'){\n      lctx.fillStyle = currentColor;\n      lctx.fill();\n      lctx.lineWidth = 2;\n      lctx.strokeStyle = currentColor;\n      lctx.stroke();\n    } else {\n      lctx.save();\n      lctx.clip();\n\n      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n      lassoPoints.forEach(pt => {\n        if(pt.x < minX) minX = pt.x;\n        if(pt.y < minY) minY = pt.y;\n        if(pt.x > maxX) maxX = pt.x;\n        if(pt.y > maxY) maxY = pt.y;\n      });\n\n      lctx.fillStyle = currentColor;\n      for(let y = minY; y <= maxY; y += dotSpace){\n        for(let x = minX; x <= maxX; x += dotSpace){\n          lctx.beginPath();\n          lctx.arc(x, y, dotSize \/ 2, 0, Math.PI * 2);\n          lctx.fill();\n        }\n      }\n      lctx.restore();\n      lctx.lineWidth = 2;\n      lctx.strokeStyle = currentColor;\n      lctx.stroke();\n    }\n    lctx.restore();\n  }\n\n  canvas.addEventListener('pointerdown', (e)=>{\n    if(tool === 'draw' || tool === 'eraser' || tool === 'blend') startDraw(e);\n    else if(tool === 'shape') startShape(e);\n    else if(tool === 'lasso') startLasso(e);\n  });\n  canvas.addEventListener('pointermove', (e)=>{\n    if(tool === 'draw' || tool === 'eraser' || tool === 'blend') moveDraw(e);\n    else if(tool === 'shape') moveShape(e);\n    else if(tool === 'lasso') moveLasso(e);\n    else {\n      const p = getPos(e);\n      coords.textContent = Math.round(p.x) + ', ' + Math.round(p.y);\n    }\n  });\n  window.addEventListener('pointerup', (e)=>{\n    if(tool === 'draw' || tool === 'eraser' || tool === 'blend') endDraw(e);\n    else if(tool === 'shape') endShape(e);\n    else if(tool === 'lasso') endLasso(e);\n  });\n  window.addEventListener('pointercancel', (e)=>{\n    if(tool === 'draw' || tool === 'eraser' || tool === 'blend') endDraw(e);\n    else if(tool === 'shape') endShape(e);\n    else if(tool === 'lasso') endLasso(e);\n  });\n\n  const presetModal = document.getElementById('presetModal');\n  const presetButtons = document.querySelectorAll('.preset-btn');\n\n  presetButtons.forEach(btn => {\n    btn.addEventListener('click', () => {\n      const preset = btn.getAttribute('data-preset');\n      \n      if (preset === 'quality') {\n        RES_MULT = 2; \n        spacingValue = 10; \n      } else if (preset === 'normal') {\n        RES_MULT = 1.5; \n        spacingValue = 15;\n      } else if (preset === 'performance') {\n        RES_MULT = 1; \n        spacingValue = 20; \n      }\n\n      spaceInput.value = spacingValue;\n      spaceVal.textContent = spacingValue + '%';\n\n      dpr = Math.min(Math.max(window.devicePixelRatio || 1, 1), 2) * RES_MULT;\n      presetModal.style.display = 'none';\n      appInitialized = true;\n\n      if (isAndroid) {\n        document.getElementById('panel').classList.remove('collapsed');\n      }\n\n      resize();\n    });\n  });\n\n})();\n<\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>RagBunnie Online Drawing Canvas Initial Setup Select a performance mode to begin drawing according to your device&#8217;s capabilities. Quality Preset High canvas resolution and dense stroke precision Normal Balanced and standard configuration Performance Preset (Android Recommended) Maximum fluidity and GPU optimization Tools Color #111111 Brush Settings Tip Shape \/ Pattern \u25cf \u25a0 \u25c6 \u2605 Load [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-12","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ragbunnie.site\/index.php?rest_route=\/wp\/v2\/pages\/12","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ragbunnie.site\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/ragbunnie.site\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/ragbunnie.site\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ragbunnie.site\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=12"}],"version-history":[{"count":4,"href":"https:\/\/ragbunnie.site\/index.php?rest_route=\/wp\/v2\/pages\/12\/revisions"}],"predecessor-version":[{"id":16,"href":"https:\/\/ragbunnie.site\/index.php?rest_route=\/wp\/v2\/pages\/12\/revisions\/16"}],"wp:attachment":[{"href":"https:\/\/ragbunnie.site\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}