<?php
/* ═══════════════════════════════════════════════════════════════
   PetBlip · Gallery Photo Editor
   ─────────────────────────────────────────────────────────────
   SETUP:
   1. Set BGBUSTER_KEY below
   2. Set DB credentials below
   3. Upload alongside your gallery pages
   4. chmod 755 /uploads/editor/

   CALL FROM GALLERY:
   gallery-editor.php?image_id=123&image=/uploads/gallery/photo.jpg&return=/gallery.php
   ═══════════════════════════════════════════════════════════════ */

session_start();

define('BGBUSTER_KEY', 'LlYuinpMHieaNejGfRwiqoDptECgqcjxpMUxIXBiclXzBNiMtNdzvGiFPhRBxiua');
define('DB_HOST',      'localhost');
define('DB_USER',      'qxgxnceu_photou');
define('DB_PASS',      '#Bullshit124');
define('DB_NAME',      'qxgxnceu_photo');
define('UPLOAD_DIR',   __DIR__ . '/uploads/editor/');
define('UPLOAD_URL',   '/uploads/editor/');
define('MOCKUP_URL',   '/mockups/');
define('ALLOWED_MIME', ['image/jpeg','image/png','image/webp']);
define('MAX_BYTES',    20 * 1024 * 1024);

if (!is_dir(UPLOAD_DIR)) mkdir(UPLOAD_DIR, 0755, true);

function siteUrl(): string {
    $s = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    return $s . '://' . $_SERVER['HTTP_HOST'];
}

/* ── AJAX ──────────────────────────────────────────────────── */
$action = $_POST['action'] ?? $_GET['action'] ?? '';

if ($action === 'upload') {
    header('Content-Type: application/json');
    $f = $_FILES['image'] ?? null;
    if (!$f || !in_array(mime_content_type($f['tmp_name']), ALLOWED_MIME))
        die(json_encode(['error' => 'Invalid file type']));
    if ($f['size'] > MAX_BYTES)
        die(json_encode(['error' => 'File too large (max 20 MB)']));
    $ext  = strtolower(pathinfo($f['name'], PATHINFO_EXTENSION)) ?: 'jpg';
    $name = 'up_' . uniqid() . '.' . $ext;
    $path = UPLOAD_DIR . $name;
    move_uploaded_file($f['tmp_name'], $path);
    $bytes = file_get_contents($path);
    $mime  = mime_content_type($path);
    echo json_encode(['ok'=>true,'b64'=>'data:'.$mime.';base64,'.base64_encode($bytes),'filename'=>$name,'size'=>$f['size'],'w'=>0,'h'=>0]);
    exit;
}

if ($action === 'remove_bg') {
    header('Content-Type: application/json');
    $filename = basename($_POST['filename'] ?? '');
    $path     = UPLOAD_DIR . $filename;
    if (!$filename || !file_exists($path)) die(json_encode(['error' => 'File not found']));
    $mime = mime_content_type($path);

    $ch = curl_init('https://api.bgbuster.com/images/remove-background');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => ['input' => new CURLFile($path,$mime,$filename),'output'=>'raw','format'=>'png'],
        CURLOPT_HTTPHEADER     => ['X-Api-Key: '.trim(BGBUSTER_KEY)],
        CURLOPT_TIMEOUT        => 60,
    ]);
    $res  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $ct   = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);

    if ($code !== 200) {
        $e = json_decode($res, true);
        die(json_encode(['error' => $e['message'] ?? 'BGBuster error '.$code]));
    }
    $out = 'rmbg_'.uniqid().'.png';
    file_put_contents(UPLOAD_DIR.$out, $res);
    echo json_encode(['ok'=>true,'b64'=>'data:image/png;base64,'.base64_encode($res),'filename'=>$out]);
    exit;
}

if ($action === 'save_to_gallery') {
    header('Content-Type: application/json');
    $dataUrl  = $_POST['dataUrl'] ?? '';
    $imageId  = (int)($_POST['image_id'] ?? 0);
    $saveMode = $_POST['save_mode'] ?? 'new'; // 'new' or 'replace'

    if (!$dataUrl || !str_starts_with($dataUrl,'data:image/'))
        die(json_encode(['error' => 'No image data']));

    $bytes = base64_decode(explode(',', $dataUrl, 2)[1] ?? '');
    if (!$bytes) die(json_encode(['error' => 'Invalid image data']));

    $name   = 'edited_'.uniqid().'.png';
    $path   = UPLOAD_DIR . $name;
    file_put_contents($path, $bytes);
    $pubUrl = siteUrl() . UPLOAD_URL . $name;

    try {
        $db  = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
        $uid = $_SESSION['user_id'] ?? $_SESSION['uid'] ?? 0;

        if ($saveMode === 'replace' && $imageId > 0) {
            // Update existing gallery record
            $stmt = $db->prepare("UPDATE pet_edited_images SET file_path=?,file_url=?,updated_at=NOW() WHERE id=? AND user_id=?");
            if ($stmt) { $stmt->bind_param('ssii',$path,$pubUrl,$imageId,$uid); $stmt->execute(); $stmt->close(); }
        } else {
            // Insert new gallery record
            $stmt = $db->prepare("INSERT INTO pet_edited_images (user_id,source_id,file_path,file_url,type,created_at) VALUES (?,?,?,?,'edited',NOW())");
            if ($stmt) { $stmt->bind_param('iiss',$uid,$imageId,$path,$pubUrl); $stmt->execute(); $imageId=$db->insert_id; $stmt->close(); }
        }
        $db->close();
    } catch (\Exception $e) { /* DB not wired yet — still return file URL */ }

    echo json_encode(['ok'=>true,'url'=>$pubUrl,'image_id'=>$imageId]);
    exit;
}

// Pre-load from gallery
$preloadImage    = $_GET['image']    ?? '';
$preloadImageId  = (int)($_GET['image_id'] ?? 0);
$returnUrl       = $_GET['return']   ?? '';
$preloadB64      = '';
$preloadFilename = '';
if ($preloadImage) {
    $localPath = ltrim($preloadImage, '/');
    if (file_exists($localPath)) {
        $bytes = file_get_contents($localPath);
        $mime  = mime_content_type($localPath);
        $preloadB64 = 'data:'.$mime.';base64,'.base64_encode($bytes);
        $ext  = pathinfo($localPath, PATHINFO_EXTENSION) ?: 'jpg';
        $preloadFilename = 'gal_'.uniqid().'.'.$ext;
        copy($localPath, UPLOAD_DIR.$preloadFilename);
    }
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Gallery Editor · PetBlip</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@600;700;800&family=DM+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<style>
:root{
  --ink:#0A0F1E;--ink2:#354060;--ink3:#7B8BAA;
  --line:#E4E9F4;--line2:#C9D3E8;--surf:#F5F7FC;--white:#FFF;
  --blue:#1A56FF;--blue-d:#1040D6;--blue-l:#EEF2FF;--blue-r:rgba(26,86,255,.16);
  --green:#00C896;--green-l:#E4FBF5;
  --red:#FF3B4E;--red-l:#FFF0F1;
  --amber:#FF9500;--amber-l:#FFF4E0;
  --purple:#7C3AED;--purple-l:#F5F3FF;
  --nav-h:52px;--lw:262px;--rw:268px;
  --r:10px;--rs:7px;--rxs:5px;
  --sh-s:0 1px 3px rgba(10,15,30,.07),0 1px 2px rgba(10,15,30,.04);
  --sh-m:0 4px 16px rgba(10,15,30,.10);
  --sh-l:0 20px 60px rgba(10,15,30,.20),0 4px 20px rgba(10,15,30,.10);
  --fd:'Syne',sans-serif;--fb:'DM Sans',sans-serif;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;font-family:var(--fb);font-size:13px;color:var(--ink);background:var(--surf);-webkit-font-smoothing:antialiased}

/* ── NAV ──────────────────────────────────────────────────── */
.nav{height:var(--nav-h);background:var(--ink);display:flex;align-items:center;padding:0 14px;gap:8px;position:sticky;top:0;z-index:300;border-bottom:1px solid rgba(255,255,255,.05);box-shadow:0 1px 0 rgba(0,0,0,.3)}
.nav-logo{display:flex;align-items:center;gap:8px;text-decoration:none;flex-shrink:0}
.nav-mark{width:28px;height:28px;background:var(--blue);border-radius:7px;display:grid;place-items:center;flex-shrink:0}
.nav-mark svg{width:15px;height:15px}
.nav-name{font-family:var(--fd);font-size:14.5px;color:#fff;letter-spacing:-.015em}
.nav-pipe{width:1px;height:16px;background:rgba(255,255,255,.1);margin:0 2px}
.nav-sub{font-size:11.5px;font-weight:500;color:rgba(255,255,255,.38)}
.nav-badge{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;background:rgba(0,200,150,.12);border:1px solid rgba(0,200,150,.25);border-radius:20px;font-size:10.5px;font-weight:700;color:var(--green)}
.nav-dot{width:5px;height:5px;border-radius:50%;background:var(--green);animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
.nav-r{margin-left:auto;display:flex;align-items:center;gap:5px}
.nb{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:6px;font-size:12px;font-weight:600;font-family:var(--fb);cursor:pointer;border:none;transition:all .12s;line-height:1}
.nb-ico{padding:6px}.nb-ghost{background:rgba(255,255,255,.07);color:rgba(255,255,255,.6)}
.nb-ghost:hover{background:rgba(255,255,255,.14);color:#fff}
.nb-ghost:disabled{opacity:.3;cursor:not-allowed}
.nb-primary{background:var(--blue);color:#fff}.nb-primary:hover{background:var(--blue-d)}
.nb-primary:disabled{opacity:.4;cursor:not-allowed}
.nb-save{background:var(--green);color:#fff}.nb-save:hover{filter:brightness(.9)}
.nb-save:disabled{opacity:.4;cursor:not-allowed}
.nav-sep{width:1px;height:16px;background:rgba(255,255,255,.08)}

/* ── SHELL ────────────────────────────────────────────────── */
.shell{display:grid;grid-template-columns:var(--lw) 1fr var(--rw);height:calc(100vh - var(--nav-h));overflow:hidden}

/* ── PANELS ───────────────────────────────────────────────── */
.panel{background:var(--white);display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--line)}
.panel.right{border-right:none;border-left:1px solid var(--line)}
.pscroll{flex:1;overflow-y:auto;padding:8px 8px 28px;display:flex;flex-direction:column;gap:1px}
.pscroll::-webkit-scrollbar{width:3px}
.pscroll::-webkit-scrollbar-thumb{background:var(--line2);border-radius:3px}

/* section */
.shd{display:flex;align-items:center;gap:7px;padding:8px 7px 6px;cursor:pointer;border-radius:var(--rs);transition:background .1s;user-select:none}
.shd:hover{background:var(--surf)}
.shd-ic{color:var(--ink3);display:flex;flex-shrink:0}
.shd-lb{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ink3);flex:1}
.schev{color:var(--ink3);display:flex;transition:transform .15s}
.schev.dn{transform:rotate(180deg)}
.sbd{padding:2px 4px 10px;display:flex;flex-direction:column;gap:7px}
.sbd.hidden{display:none}
.sdiv{height:1px;background:var(--line);margin:2px 0}

/* form */
.f{display:flex;flex-direction:column;gap:3px}
.flb{font-size:11px;font-weight:600;color:var(--ink2)}
.inp{width:100%;padding:6px 8px;border:1.5px solid var(--line2);border-radius:var(--rs);font-size:12.5px;font-family:var(--fb);color:var(--ink);background:#fff;outline:none;transition:border-color .12s,box-shadow .12s;appearance:none}
.inp:focus{border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-r)}
.inp::placeholder{color:var(--ink3)}
.inp2{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.inp3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px}
.csw{width:100%;height:32px;padding:2px;border:1.5px solid var(--line2);border-radius:var(--rs);cursor:pointer;background:none}

/* swatches */
.sws{display:flex;flex-wrap:wrap;gap:4px}
.sw{width:20px;height:20px;border-radius:50%;cursor:pointer;flex-shrink:0;border:2.5px solid transparent;transition:transform .1s,box-shadow .1s}
.sw:hover{transform:scale(1.18)}
.sw.on{box-shadow:0 0 0 2px #fff,0 0 0 4px var(--blue)}
.sw-tp{background:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%),linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%);background-size:8px 8px;background-position:0 0,4px 4px;background-color:#e8e8e8}

/* buttons */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:6px 11px;border-radius:var(--rs);font-size:12px;font-weight:600;cursor:pointer;border:1.5px solid transparent;font-family:var(--fb);transition:all .13s;line-height:1;white-space:nowrap}
.btn:disabled{opacity:.36;cursor:not-allowed}
.btn svg{flex-shrink:0}
.btn-p{background:var(--blue);color:#fff;border-color:var(--blue)}
.btn-p:hover:not(:disabled){background:var(--blue-d)}
.btn-s{background:#fff;color:var(--ink);border-color:var(--line2)}
.btn-s:hover:not(:disabled){border-color:var(--blue);color:var(--blue);background:var(--blue-l)}
.btn-g{background:transparent;color:var(--ink2);border-color:transparent}
.btn-g:hover:not(:disabled){background:var(--surf);color:var(--ink)}
.btn-dng{background:#fff;color:var(--red);border-color:#FECDD3}
.btn-dng:hover:not(:disabled){background:var(--red);color:#fff}
.btn-grn{background:var(--green);color:#fff;border-color:var(--green)}
.btn-grn:hover:not(:disabled){filter:brightness(.9)}
.btn-pur{background:var(--purple);color:#fff;border-color:var(--purple)}
.btn-pur:hover:not(:disabled){filter:brightness(.9)}
.btn-w{width:100%}
.btn-sm{padding:4px 8px;font-size:11px}
.btn-ic{padding:5px}

/* upload zone */
.upzone{border:1.5px dashed var(--line2);border-radius:var(--r);padding:16px 10px;text-align:center;cursor:pointer;background:var(--surf);transition:all .15s}
.upzone input{display:none}
.upzone:hover,.upzone.drag{border-color:var(--blue);background:var(--blue-l)}
.upzone:hover .upz-ic,.upzone.drag .upz-ic{color:var(--blue)}
.upz-ic{display:flex;justify-content:center;color:var(--ink3);margin-bottom:6px;transition:color .12s}
.upz-t{font-size:12px;font-weight:600;color:var(--ink2);margin-bottom:2px}
.upz-h{font-size:11px;color:var(--ink3)}

/* status */
.st{display:none;align-items:center;gap:6px;padding:7px 9px;border-radius:var(--rs);font-size:11.5px;font-weight:500}
.st.show{display:flex}
.st.ld{background:var(--blue-l);color:var(--blue)}
.st.ok{background:var(--green-l);color:#065F46}
.st.er{background:var(--red-l);color:#991B1B}
.st.warn{background:var(--amber-l);color:#92400E}
.spin{width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:spin .5s linear infinite;flex-shrink:0}
@keyframes spin{to{transform:rotate(360deg)}}

/* sliders */
.slr{display:flex;align-items:center;gap:7px}
.sllb{font-size:11px;font-weight:600;color:var(--ink2);width:66px;flex-shrink:0}
.slr input[type=range]{flex:1;accent-color:var(--blue);cursor:pointer;height:4px}
.slv{font-size:11px;color:var(--ink3);width:30px;text-align:right;flex-shrink:0;font-variant-numeric:tabular-nums}

/* toggles */
.togrow{display:flex;gap:3px}
.tog{flex:1;padding:5px 0;border:1.5px solid var(--line2);border-radius:var(--rxs);background:#fff;color:var(--ink2);cursor:pointer;display:grid;place-items:center;transition:all .1s}
.tog.on{background:var(--blue-l);border-color:var(--blue);color:var(--blue)}
.tog:hover:not(.on){border-color:var(--ink3)}

/* ── FILTERS GRID ─────────────────────────────────────────── */
.filter-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}
.filter-chip{border:1.5px solid var(--line2);border-radius:var(--rs);overflow:hidden;cursor:pointer;transition:all .13s;background:#fff;text-align:center}
.filter-chip:hover{border-color:var(--blue);transform:translateY(-1px);box-shadow:var(--sh-s)}
.filter-chip.on{border-color:var(--blue);box-shadow:0 0 0 2px var(--blue-l)}
.filter-thumb{height:44px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:rgba(255,255,255,.9);text-shadow:0 1px 2px rgba(0,0,0,.5)}
.filter-thumb.f-normal{background:linear-gradient(135deg,#e8ecf4,#d0d8e8);color:var(--ink3)}
.filter-thumb.f-grayscale{background:linear-gradient(135deg,#888,#444)}
.filter-thumb.f-sepia{background:linear-gradient(135deg,#c8a87a,#8b6a3e)}
.filter-thumb.f-blackwhite{background:linear-gradient(135deg,#fff 50%,#000 50%);color:#666}
.filter-thumb.f-vintage{background:linear-gradient(135deg,#d4a56a,#7a5c3a)}
.filter-thumb.f-kodachrome{background:linear-gradient(135deg,#e8b84b,#c44a2a)}
.filter-thumb.f-polaroid{background:linear-gradient(135deg,#e8e0d4,#b4a090)}
.filter-thumb.f-brownie{background:linear-gradient(135deg,#a07850,#5c3820)}
.filter-thumb.f-technicolor{background:linear-gradient(135deg,#e84848,#48b448,#4848e8)}
.filter-name{font-size:9.5px;font-weight:700;color:var(--ink2);padding:3px 0;letter-spacing:.02em}
.filter-chip.on .filter-name{color:var(--blue)}

/* ── SHAPE CROP ───────────────────────────────────────────── */
.shape-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}
.shape-chip{border:1.5px solid var(--line2);border-radius:var(--rs);padding:8px 4px;cursor:pointer;text-align:center;background:#fff;transition:all .13s;display:flex;flex-direction:column;align-items:center;gap:4px}
.shape-chip:hover{border-color:var(--blue);background:var(--blue-l)}
.shape-chip.on{border-color:var(--blue);background:var(--blue-l)}
.shape-chip svg{width:22px;height:22px;color:var(--ink2)}
.shape-chip.on svg{color:var(--blue)}
.shape-chip-lb{font-size:9px;font-weight:700;color:var(--ink3);text-transform:uppercase;letter-spacing:.04em}

/* ── CLIPART / STICKERS ───────────────────────────────────── */
.sticker-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}
.sticker-btn{border:1.5px solid var(--line2);border-radius:var(--rs);padding:8px 4px;cursor:pointer;text-align:center;background:#fff;transition:all .13s;display:flex;align-items:center;justify-content:center}
.sticker-btn svg,.sticker-btn img{width:32px;height:32px}
.sticker-btn:hover{border-color:var(--blue);background:var(--blue-l);transform:scale(1.05)}

/* ── TEXT TEMPLATES ───────────────────────────────────────── */
.tmpl-list{display:flex;flex-direction:column;gap:4px}
.tmpl-btn{padding:7px 10px;border:1.5px solid var(--line2);border-radius:var(--rs);background:#fff;cursor:pointer;font-family:var(--fb);font-size:12px;font-weight:600;color:var(--ink2);text-align:left;transition:all .13s;display:flex;align-items:center;justify-content:space-between}
.tmpl-btn:hover{border-color:var(--blue);background:var(--blue-l);color:var(--blue)}
.tmpl-btn small{font-size:10px;font-weight:500;color:var(--ink3);font-family:var(--fb)}
.tmpl-btn:hover small{color:var(--blue-d)}

/* ── QR ────────────────────────────────────────────────────── */
.qr-preview{width:80px;height:80px;border-radius:var(--rs);border:1.5px solid var(--line2);overflow:hidden;background:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0}

/* ── CANVAS STAGE ─────────────────────────────────────────── */
.stage{background:#BEC6D4;display:flex;flex-direction:column;overflow:hidden}
.ctb{display:flex;align-items:center;gap:3px;padding:5px 12px;background:rgba(255,255,255,.9);backdrop-filter:blur(12px) saturate(180%);border-bottom:1px solid rgba(0,0,0,.07);flex-shrink:0;min-height:44px;flex-wrap:wrap}
.ctb-sep{width:1px;height:18px;background:var(--line);margin:0 3px;flex-shrink:0}
.ctb-r{margin-left:auto;display:flex;align-items:center;gap:3px}
.tb{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;border-radius:var(--rxs);background:transparent;border:1px solid transparent;color:var(--ink2);cursor:pointer;font-size:11px;font-weight:500;font-family:var(--fb);transition:all .11s}
.tb:hover{background:rgba(255,255,255,.95);border-color:var(--line);color:var(--ink);box-shadow:var(--sh-s)}
.tb:disabled{opacity:.25;cursor:not-allowed;box-shadow:none;background:transparent;border-color:transparent}
.tb.dng:hover{background:var(--red-l);color:var(--red);border-color:#FECDD3}
.tb.active{background:var(--blue-l);border-color:var(--blue);color:var(--blue)}
.tb svg{flex-shrink:0}

/* canvas wrap */
.cwrap{flex:1;display:flex;align-items:center;justify-content:center;padding:24px;overflow:auto;position:relative}
.cempty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;pointer-events:none;transition:opacity .2s}
.cempty.gone{opacity:0;pointer-events:none}
.cempty-ic{color:rgba(0,0,0,.15)}
.cempty-t{font-family:var(--fd);font-size:15px;color:rgba(0,0,0,.2)}
.cempty-h{font-size:12px;color:rgba(0,0,0,.18)}

/* canvas host */
.chost{position:relative;border-radius:8px;overflow:hidden;box-shadow:var(--sh-l);display:none}
.cchk{position:absolute;inset:0;pointer-events:none;z-index:0;background-image:linear-gradient(45deg,#a8b0be 25%,transparent 25%),linear-gradient(-45deg,#a8b0be 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#a8b0be 75%),linear-gradient(-45deg,transparent 75%,#a8b0be 75%);background-size:20px 20px;background-position:0 0,0 10px,10px -10px,-10px 0;background-color:#bec7d4}
#fc{display:block;position:relative;z-index:1}

/* smart guide lines */
.guide-h,.guide-v{position:absolute;pointer-events:none;z-index:10;opacity:0;transition:opacity .1s}
.guide-h{height:1px;left:0;right:0;background:#FF3B4E}
.guide-v{width:1px;top:0;bottom:0;background:#FF3B4E}
.guide-h.show,.guide-v.show{opacity:1}

/* DPI warning badge */
.dpi-warn{display:none;align-items:center;gap:6px;padding:6px 10px;background:var(--amber-l);border:1.5px solid var(--amber);border-radius:var(--rs);font-size:11px;font-weight:600;color:#92400E}
.dpi-warn.show{display:flex}

/* ── LAYERS ───────────────────────────────────────────────── */
.ly-item{display:flex;align-items:center;gap:6px;padding:5px 7px;border:1.5px solid var(--line2);border-radius:var(--rs);background:#fff;cursor:pointer;font-size:12px;font-weight:500;color:var(--ink2);transition:all .11s}
.ly-item:hover{border-color:var(--blue);color:var(--ink)}
.ly-item.on{border-color:var(--blue);background:var(--blue-l);color:var(--blue)}
.ly-ic{width:24px;height:24px;border-radius:4px;background:var(--surf);border:1px solid var(--line);display:grid;place-items:center;color:var(--ink3);flex-shrink:0}
.ly-nm{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px}
.ly-mt{text-align:center;padding:12px 0;font-size:11.5px;color:var(--ink3)}
.ly-btns{display:flex;gap:2px;flex-shrink:0}

/* ── EXPORT BOX ───────────────────────────────────────────── */
.expbox{background:linear-gradient(135deg,#EEF2FF 0%,#F5F7FF 100%);border:1.5px solid rgba(26,86,255,.13);border-radius:var(--r);padding:11px;display:flex;flex-direction:column;gap:8px}
.exp-ttl{font-family:var(--fd);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--blue)}
.fmt-row{display:flex;gap:4px}
.fmt{flex:1;padding:5px 0;border:1.5px solid var(--line2);border-radius:var(--rxs);background:#fff;text-align:center;font-size:11px;font-weight:700;cursor:pointer;color:var(--ink2);transition:all .11s}
.fmt:hover{border-color:var(--blue);color:var(--blue)}
.fmt.on{border-color:var(--blue);background:var(--blue);color:#fff}

/* prop panel */
.prop-g{background:var(--surf);border-radius:var(--rs);padding:8px 10px;display:flex;flex-direction:column;gap:7px}

/* ── MODAL ────────────────────────────────────────────────── */
.mbg{position:fixed;inset:0;background:rgba(10,15,30,.55);backdrop-filter:blur(5px);z-index:500;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .2s}
.mbg.open{opacity:1;pointer-events:auto}
.mbox{background:#fff;border-radius:16px;padding:26px;width:420px;max-width:92vw;box-shadow:var(--sh-l);transform:translateY(14px);transition:transform .2s}
.mbg.open .mbox{transform:translateY(0)}
.m-title{font-family:var(--fd);font-size:18px;color:var(--ink);margin-bottom:3px}
.m-sub{font-size:12.5px;color:var(--ink3);margin-bottom:18px}
.m-preview{width:100%;aspect-ratio:1;border-radius:var(--rs);overflow:hidden;background:#e0e4ed;margin-bottom:14px;position:relative;border:1.5px solid var(--line)}
.m-preview-chk{position:absolute;inset:0;background-image:linear-gradient(45deg,#ccc 25%,transparent 25%,transparent 75%,#ccc 75%),linear-gradient(45deg,#ccc 25%,transparent 25%,transparent 75%,#ccc 75%);background-size:14px 14px;background-position:0 0,7px 7px;background-color:#f0f0f0;z-index:0}
.m-preview img{position:relative;z-index:1;width:100%;height:100%;object-fit:contain}
.m-save-opts{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px}
.m-save-opt{padding:10px;border:2px solid var(--line2);border-radius:var(--rs);cursor:pointer;transition:all .13s;text-align:center}
.m-save-opt:hover{border-color:var(--blue);background:var(--blue-l)}
.m-save-opt.on{border-color:var(--blue);background:var(--blue-l)}
.m-save-opt-icon{font-size:20px;margin-bottom:4px}
.m-save-opt-title{font-size:12px;font-weight:700;color:var(--ink)}
.m-save-opt-desc{font-size:10.5px;color:var(--ink3)}
.m-actions{display:flex;gap:8px}
.m-actions .btn{flex:1}

/* canvas size tabs */
.sz-tabs{display:flex;gap:3px;background:var(--surf);border-radius:var(--rxs);padding:3px}
.sz-tab{flex:1;padding:5px 4px;border-radius:4px;border:none;cursor:pointer;font-size:10.5px;font-weight:700;font-family:var(--fb);background:transparent;color:var(--ink3);transition:all .12s}
.sz-tab.on{background:#fff;color:var(--blue);box-shadow:var(--sh-s)}
</style>
</head>
<body>

<!-- ═══ NAV ════════════════════════════════════════════════════ -->
<nav class="nav">
  <a href="/" class="nav-logo">
    <div class="nav-mark">
      <svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
        <circle cx="11" cy="4" r="2"/><circle cx="18" cy="8" r="2"/><circle cx="20" cy="16" r="2"/>
        <path d="M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z"/>
      </svg>
    </div>
    <span class="nav-name">PetBlip</span>
  </a>
  <div class="nav-pipe"></div>
  <span class="nav-sub">Gallery Editor</span>
  <div class="nav-badge"><span class="nav-dot"></span>BGBuster AI</div>

  <div class="nav-r">
    <button class="nb nb-ghost nb-ico" id="btnUndo" disabled title="Undo Ctrl+Z">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>
    </button>
    <button class="nb nb-ghost nb-ico" id="btnRedo" disabled title="Redo Ctrl+Y">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13"/></svg>
    </button>
    <div class="nav-sep"></div>
    <button class="nb nb-ghost" id="btnDownload" disabled>
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
      Download
    </button>
    <button class="nb nb-save" id="btnSave" disabled>
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
      Save to Gallery
    </button>
  </div>
</nav>

<!-- ═══ SHELL ════════════════════════════════════════════════════ -->
<div class="shell">

<!-- ══ LEFT PANEL ═════════════════════════════════════════════════ -->
<aside class="panel">
<div class="pscroll">

  <!-- 1. PHOTO UPLOAD + BG REMOVAL -->
  <div>
    <div class="shd" data-sec="ph">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg></span>
      <span class="shd-lb">Photo</span>
      <span class="schev" id="cv-ph"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-ph">
      <div class="upzone" id="upZone"><input type="file" id="fileIn" accept="image/*">
        <div class="upz-ic"><svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg></div>
        <div class="upz-t">Click or drop photo</div>
        <div class="upz-h">JPG · PNG · WEBP · max 20 MB</div>
      </div>
      <div id="stUp" class="st"></div>
      <div id="dpiWarn" class="dpi-warn">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
        <span id="dpiWarnText">Low resolution — may print blurry</span>
      </div>
      <button class="btn btn-p btn-w" id="btnRmBg" disabled>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22C6.5 22 2 17.5 2 12S6.5 2 12 2s10 4.5 10 10"/><circle cx="17" cy="17" r="4"/><path d="m19 15-4 4"/><path d="m15 15 4 4"/></svg>
        Remove Background
      </button>
      <button class="btn btn-s btn-w btn-sm" id="btnRestore" disabled>
        <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
        Restore Original
      </button>
      <div id="stBg" class="st"></div>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- 2. PHOTO FILTERS -->
  <div>
    <div class="shd" data-sec="fi">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M3 9c0-1.1.9-2 2-2h1l1-2h6l1 2h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9z"/></svg></span>
      <span class="shd-lb">Photo Filters</span>
      <span class="schev" id="cv-fi"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-fi">
      <div class="filter-grid" id="filterGrid">
        <!-- built dynamically by JS -->
      </div>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- 3. ADJUSTMENTS -->
  <div>
    <div class="shd" data-sec="aj">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></svg></span>
      <span class="shd-lb">Adjustments</span>
      <span class="schev" id="cv-aj"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-aj">
      <div class="slr"><span class="sllb">Brightness</span><input type="range" id="sBr" min="-1" max="1" step=".05" value="0"><span class="slv" id="vBr">0</span></div>
      <div class="slr"><span class="sllb">Contrast</span><input type="range" id="sCo" min="-1" max="1" step=".05" value="0"><span class="slv" id="vCo">0</span></div>
      <div class="slr"><span class="sllb">Saturation</span><input type="range" id="sSa" min="-1" max="1" step=".05" value="0"><span class="slv" id="vSa">0</span></div>
      <div class="slr"><span class="sllb">Opacity</span><input type="range" id="sOp" min="0" max="1" step=".05" value="1"><span class="slv" id="vOp">1.0</span></div>
      <button class="btn btn-g btn-sm" id="btnRstAdj" style="align-self:flex-start">
        <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
        Reset
      </button>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- 4. CROP TO SHAPE -->
  <div>
    <div class="shd" data-sec="sh">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/></svg></span>
      <span class="shd-lb">Crop to Shape</span>
      <span class="schev" id="cv-sh"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-sh">
      <div class="shape-grid" id="shapeGrid">
        <div class="shape-chip on" data-shape="none">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/></svg>
          <span class="shape-chip-lb">None</span>
        </div>
        <div class="shape-chip" data-shape="circle">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/></svg>
          <span class="shape-chip-lb">Circle</span>
        </div>
        <div class="shape-chip" data-shape="rounded">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="6"/></svg>
          <span class="shape-chip-lb">Rounded</span>
        </div>
        <div class="shape-chip" data-shape="heart">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
          <span class="shape-chip-lb">Heart</span>
        </div>
        <div class="shape-chip" data-shape="star">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
          <span class="shape-chip-lb">Star</span>
        </div>
        <div class="shape-chip" data-shape="diamond">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 22 12 12 22 2 12"/></svg>
          <span class="shape-chip-lb">Diamond</span>
        </div>
        <div class="shape-chip" data-shape="hexagon">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 22 7 22 17 12 22 2 17 2 7"/></svg>
          <span class="shape-chip-lb">Hex</span>
        </div>
        <div class="shape-chip" data-shape="octagon">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86"/></svg>
          <span class="shape-chip-lb">Oct</span>
        </div>
      </div>
      <p style="font-size:10.5px;color:var(--ink3)">Applies to the selected photo layer</p>
    </div>
  </div>

</div>
</aside>

<!-- ══ CANVAS ════════════════════════════════════════════════════ -->
<main class="stage">
  <!-- toolbar -->
  <div class="ctb">
    <button class="tb" id="tbFH" disabled><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h3"/><path d="M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3"/><line x1="12" y1="20" x2="12" y2="4"/></svg> Flip H</button>
    <button class="tb" id="tbFV" disabled><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v3"/><path d="M21 16v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3"/><line x1="4" y1="12" x2="20" y2="12"/></svg> Flip V</button>
    <div class="ctb-sep"></div>
    <button class="tb" id="tbRL" disabled><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg> −15°</button>
    <button class="tb" id="tbRR" disabled><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg> +15°</button>
    <div class="ctb-sep"></div>
    <button class="tb" id="tbDup" disabled title="Duplicate layer">
      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="8" y="8" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Dup
    </button>
    <button class="tb" id="tbCenter" disabled title="Center on canvas">
      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="22" y1="12" x2="2" y2="12"/><line x1="12" y1="22" x2="12" y2="2"/></svg> Center
    </button>
    <button class="tb" id="tbFill" disabled title="Fill canvas">
      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1.5"/></svg> Fill
    </button>
    <div class="ctb-sep"></div>
    <!-- smart guides toggle -->
    <button class="tb active" id="tbGuides" title="Smart guides">
      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"/><line x1="12" y1="3" x2="12" y2="21"/></svg> Guides
    </button>
    <div class="ctb-r">
      <button class="tb dng" id="tbDel" disabled>
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg> Delete
      </button>
    </div>
  </div>

  <!-- canvas area -->
  <div class="cwrap" id="cwrap">
    <div class="cempty" id="cEmpty">
      <div class="cempty-ic"><svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg></div>
      <div class="cempty-t">Upload your pet photo to begin</div>
      <div class="cempty-h">Remove background · Add text · Apply filters · Save to gallery</div>
    </div>
    <div class="chost" id="cHost">
      <div class="cchk"></div>
      <canvas id="fc"></canvas>
      <div class="guide-h" id="guideH"></div>
      <div class="guide-v" id="guideV"></div>
    </div>
  </div>
</main>

<!-- ══ RIGHT PANEL ════════════════════════════════════════════════ -->
<aside class="panel right">
<div class="pscroll">

  <!-- CANVAS SIZE -->
  <div>
    <div class="shd" data-sec="sz">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1.5"/></svg></span>
      <span class="shd-lb">Canvas</span>
      <span class="schev" id="cv-sz"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-sz">
      <div class="sz-tabs">
        <button class="sz-tab on" data-w="600" data-h="600">1:1</button>
        <button class="sz-tab" data-w="600" data-h="750">4:5</button>
        <button class="sz-tab" data-w="750" data-h="600">5:4</button>
        <button class="sz-tab" data-w="600" data-h="900">2:3</button>
        <button class="sz-tab" data-w="900" data-h="600">3:2</button>
      </div>
      <div class="inp2">
        <div class="f"><label class="flb">W (px)</label><input class="inp" type="number" id="cwW" value="600" min="100" max="1200"></div>
        <div class="f"><label class="flb">H (px)</label><input class="inp" type="number" id="cwH" value="600" min="100" max="1200"></div>
      </div>
      <button class="btn btn-s btn-w btn-sm" id="btnApSz">Apply Custom Size</button>
      <!-- BG color -->
      <div class="f">
        <label class="flb">Canvas Background</label>
        <div class="sws" id="bgSws">
          <div class="sw sw-tp on" data-c="transparent" title="Transparent"></div>
          <?php foreach(['#FFFFFF','#F5F5F5','#0A0F1E','#1A56FF','#00C896','#FF3B4E','#FF9500','#7C3AED','#FFB6C1','#FFF9C4'] as $c): ?>
          <div class="sw" style="background:<?=$c?>" data-c="<?=$c?>"></div>
          <?php endforeach; ?>
        </div>
        <input type="color" class="csw" id="bgPicker" value="#ffffff">
      </div>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- TEXT -->
  <div>
    <div class="shd" data-sec="tx">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" y1="20" x2="15" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/></svg></span>
      <span class="shd-lb">Text</span>
      <span class="schev" id="cv-tx"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-tx">
      <div class="f"><label class="flb">Content</label><input class="inp" type="text" id="txCt" placeholder="Max · Good Boy · 2020–2025"></div>
      <div class="f">
        <label class="flb">Font</label>
        <select class="inp" id="txFn">
          <option value="Syne">Syne (Bold Display)</option>
          <option value="Georgia">Georgia (Classic)</option>
          <option value="Impact">Impact (Strong)</option>
          <option value="Verdana">Verdana (Clean)</option>
          <option value="DM Sans">DM Sans (Modern)</option>
          <option value="Courier New">Courier New (Retro)</option>
          <option value="Arial Black">Arial Black (Heavy)</option>
          <option value="Comic Sans MS">Comic Sans (Fun)</option>
        </select>
      </div>
      <div class="inp2">
        <div class="f"><label class="flb">Size</label><input class="inp" type="number" id="txSz" value="48" min="6" max="400"></div>
        <div class="f"><label class="flb">Color</label><input type="color" class="csw" id="txCol" value="#0A0F1E"></div>
      </div>
      <!-- text style toggles -->
      <div class="togrow">
        <button class="tog" id="togB" title="Bold"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg></button>
        <button class="tog" id="togI" title="Italic"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg></button>
        <button class="tog on" id="togAC" title="Center"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="21" y1="6" x2="3" y2="6"/><line x1="17" y1="12" x2="7" y2="12"/><line x1="19" y1="18" x2="5" y2="18"/></svg></button>
        <button class="tog" id="togAL" title="Left"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="21" y1="6" x2="3" y2="6"/><line x1="15" y1="12" x2="3" y2="12"/><line x1="17" y1="18" x2="3" y2="18"/></svg></button>
        <button class="tog" id="togAR" title="Right"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="12" x2="9" y2="12"/><line x1="21" y1="18" x2="7" y2="18"/></svg></button>
      </div>
      <!-- letter spacing + line height -->
      <div class="slr"><span class="sllb">Spacing</span><input type="range" id="txLs" min="-200" max="800" step="10" value="0"><span class="slv" id="vLs">0</span></div>
      <div class="slr"><span class="sllb">Line H</span><input type="range" id="txLh" min="0.5" max="3" step="0.1" value="1.2"><span class="slv" id="vLh">1.2</span></div>
      <!-- stroke / outline -->
      <div class="f">
        <label class="flb">Outline</label>
        <div class="inp2">
          <input type="color" class="csw" id="txStrokeCol" value="#FFFFFF">
          <div class="slr" style="flex:1"><input type="range" id="txStrokeW" min="0" max="20" step="1" value="0" style="flex:1"><span class="slv" id="vSw">0</span></div>
        </div>
      </div>
      <!-- curved text -->
      <div class="f">
        <label class="flb">Curve Radius (0 = straight)</label>
        <div class="slr"><input type="range" id="txCurve" min="0" max="500" step="10" value="0"><span class="slv" id="vCurve">0</span></div>
      </div>
      <button class="btn btn-p btn-w" id="btnAddTx">
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
        Add Text Layer
      </button>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- TEXT SHADOW (shown when text selected) -->
  <div id="secShadow" style="display:none">
    <div>
      <div class="shd" data-sec="sd">
        <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 21v-4a2 2 0 0 1 2-2h6"/><path d="M11 7h6v6"/></svg></span>
        <span class="shd-lb">Shadow</span>
        <span class="schev" id="cv-sd"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
      </div>
      <div class="sbd" id="sb-sd">
        <div class="f">
          <label class="flb">Shadow Color</label>
          <input type="color" class="csw" id="sdCol" value="#000000">
        </div>
        <div class="slr"><span class="sllb">Blur</span><input type="range" id="sdBlur" min="0" max="30" step="1" value="0"><span class="slv" id="vSdB">0</span></div>
        <div class="slr"><span class="sllb">Offset X</span><input type="range" id="sdX" min="-20" max="20" step="1" value="0"><span class="slv" id="vSdX">0</span></div>
        <div class="slr"><span class="sllb">Offset Y</span><input type="range" id="sdY" min="-20" max="20" step="1" value="0"><span class="slv" id="vSdY">0</span></div>
      </div>
    </div>
    <div class="sdiv"></div>
  </div>

  <!-- TEXT TEMPLATES -->
  <div>
    <div class="shd" data-sec="tmpl">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg></span>
      <span class="shd-lb">Quick Text</span>
      <span class="schev" id="cv-tmpl"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-tmpl">
      <div class="tmpl-list" id="tmplList">
        <?php
        $templates = [
          ['text'=>'Good Boy','font'=>'Syne','size'=>52,'color'=>'#1A56FF'],
          ['text'=>'Good Girl','font'=>'Syne','size'=>52,'color'=>'#DB2777'],
          ['text'=>'Best Dog Mom','font'=>'Georgia','size'=>38,'color'=>'#0A0F1E'],
          ['text'=>'Rescue Dog','font'=>'Impact','size'=>48,'color'=>'#FF3B4E'],
          ['text'=>'Dog Dad','font'=>'Syne','size'=>52,'color'=>'#7C3AED'],
          ['text'=>'Forever in My Heart','font'=>'Georgia','size'=>32,'color'=>'#354060'],
          ['text'=>"Rachel's Pet Supply",'font'=>'Syne','size'=>36,'color'=>'#1A56FF'],
          ['text'=>'PetBlip Community','font'=>'DM Sans','size'=>36,'color'=>'#00C896'],
          ['text'=>'Adopt Don\'t Shop','font'=>'Impact','size'=>40,'color'=>'#FF9500'],
          ['text'=>'✦ Good Pup ✦','font'=>'Syne','size'=>46,'color'=>'#0A0F1E'],
        ];
        foreach($templates as $t):
        ?>
        <button class="tmpl-btn" data-text="<?=htmlspecialchars($t['text'])?>" data-font="<?=$t['font']?>" data-size="<?=$t['size']?>" data-color="<?=$t['color']?>">
          <?=htmlspecialchars($t['text'])?>
          <small><?=$t['font']?></small>
        </button>
        <?php endforeach; ?>
      </div>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- CLIPART / STICKERS -->
  <div>
    <div class="shd" data-sec="cl">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg></span>
      <span class="shd-lb">Clipart & Stickers</span>
      <span class="schev" id="cv-cl"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-cl">
      <div class="sticker-grid" id="stickerGrid">
        <!-- built by JS from SVG library -->
      </div>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- QR CODE -->
  <div>
    <div class="shd" data-sec="qr">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="5" y="5" width="3" height="3" fill="currentColor"/><rect x="16" y="5" width="3" height="3" fill="currentColor"/><rect x="16" y="16" width="3" height="3" fill="currentColor"/><rect x="5" y="16" width="3" height="3" fill="currentColor"/></svg></span>
      <span class="shd-lb">QR Code</span>
      <span class="schev" id="cv-qr"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-qr">
      <div class="f"><label class="flb">URL or Text</label><input class="inp" type="text" id="qrUrl" placeholder="https://petblip.com/..."></div>
      <div class="inp2">
        <div class="f"><label class="flb">Foreground</label><input type="color" class="csw" id="qrFg" value="#0A0F1E"></div>
        <div class="f"><label class="flb">Background</label><input type="color" class="csw" id="qrBg" value="#FFFFFF"></div>
      </div>
      <div style="display:flex;gap:8px;align-items:center">
        <div class="qr-preview" id="qrPreview"></div>
        <button class="btn btn-p btn-sm" id="btnGenQr" style="flex:1">Generate & Add</button>
      </div>
      <div id="stQr" class="st"></div>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- LAYERS -->
  <div>
    <div class="shd" data-sec="ly">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg></span>
      <span class="shd-lb">Layers</span>
      <span class="schev" id="cv-ly"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-ly">
      <div id="lyList"><div class="ly-mt">No layers yet</div></div>
      <div style="display:flex;gap:4px">
        <button class="btn btn-g btn-sm" style="flex:1" id="btnLyUp"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>Up</button>
        <button class="btn btn-g btn-sm" style="flex:1" id="btnLyDn"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>Down</button>
      </div>
    </div>
  </div>

  <div class="sdiv"></div>

  <!-- LAYER PROPERTIES (selected) -->
  <div id="secProp" style="display:none">
    <div>
      <div class="shd" data-sec="pr">
        <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93 17.66 6.34"/><path d="M10.5 2h3"/><path d="M10.5 22h3"/><path d="M4.93 4.93l1.41 1.41"/><path d="M2 10.5v3"/><path d="M22 10.5v3"/><path d="M6.34 17.66l-1.41 1.41"/><path d="M19.07 19.07l-1.41-1.41"/></svg></span>
        <span class="shd-lb">Properties</span>
        <span class="schev" id="cv-pr"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
      </div>
      <div class="sbd prop-g" id="sb-pr">
        <div class="slr"><span class="sllb">Scale X</span><input type="range" id="pSX" min=".05" max="4" step=".05" value="1"><span class="slv" id="vpSX">1.0</span></div>
        <div class="slr"><span class="sllb">Scale Y</span><input type="range" id="pSY" min=".05" max="4" step=".05" value="1"><span class="slv" id="vpSY">1.0</span></div>
        <div class="slr"><span class="sllb">Rotation</span><input type="range" id="pRt" min="-180" max="180" step="1" value="0"><span class="slv" id="vpRt">0°</span></div>
      </div>
    </div>
    <div class="sdiv"></div>
  </div>

  <!-- EXPORT / DOWNLOAD -->
  <div>
    <div class="shd" data-sec="ex">
      <span class="shd-ic"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg></span>
      <span class="shd-lb">Export</span>
      <span class="schev" id="cv-ex"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
    </div>
    <div class="sbd" id="sb-ex">
      <div class="expbox">
        <div class="exp-ttl">Download Settings</div>
        <div class="f">
          <label class="flb">Resolution</label>
          <select class="inp" id="exSc">
            <option value="1">1× Screen size</option>
            <option value="2" selected>2× High res</option>
            <option value="3">3× Ultra HD</option>
          </select>
        </div>
        <div class="f">
          <label class="flb">Format</label>
          <div class="fmt-row">
            <div class="fmt on" data-fmt="png">PNG</div>
            <div class="fmt" data-fmt="jpeg">JPEG</div>
            <div class="fmt" data-fmt="webp">WebP</div>
          </div>
        </div>
        <div class="f" id="qField" style="display:none">
          <label class="flb">Quality</label>
          <div class="slr"><input type="range" id="exQ" min=".5" max="1" step=".05" value=".92" style="flex:1"><span class="slv" id="vExQ">92%</span></div>
        </div>
        <button class="btn btn-p btn-w" id="btnDl" disabled>
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
          Download
        </button>
        <div id="stEx" class="st"></div>
      </div>
    </div>
  </div>

</div>
</aside>
</div><!-- /shell -->

<!-- ═══ SAVE MODAL ════════════════════════════════════════════════ -->
<div class="mbg" id="saveModal">
  <div class="mbox">
    <div class="m-title">Save to Gallery</div>
    <div class="m-sub">Choose how to save your edited photo.</div>
    <div class="m-preview"><div class="m-preview-chk"></div><img id="mPreviewImg" src="" alt=""></div>
    <div class="m-save-opts">
      <div class="m-save-opt on" id="mOptNew" data-mode="new">
        <div class="m-save-opt-icon">✦</div>
        <div class="m-save-opt-title">Save as New</div>
        <div class="m-save-opt-desc">Keep original + add edited copy</div>
      </div>
      <div class="m-save-opt" id="mOptReplace" data-mode="replace">
        <div class="m-save-opt-icon">↺</div>
        <div class="m-save-opt-title">Replace Original</div>
        <div class="m-save-opt-desc">Overwrite the gallery photo</div>
      </div>
    </div>
    <div class="m-actions">
      <button class="btn btn-s" id="btnMCancel">Cancel</button>
      <button class="btn btn-grn" id="btnMSave">
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
        Save to Gallery
      </button>
    </div>
    <div id="stSave" class="st" style="margin-top:8px"></div>
  </div>
</div>

<!-- hidden QR generator div -->
<div id="qrHidden" style="position:absolute;left:-9999px;top:-9999px;width:200px;height:200px"></div>

<script>
'use strict';

/* ══════════════════════════════════════════════════════════════
   PHOTO FILTERS — Fabric.js ColorMatrix definitions
   ══════════════════════════════════════════════════════════════ */
const FILTERS = {
  Normal:     null,
  Grayscale:  () => new fabric.Image.filters.Grayscale(),
  Sepia:      () => new fabric.Image.filters.Sepia(),
  BlackWhite: () => new fabric.Image.filters.BlackWhite({threshold:0.5}),
  Vintage:    () => new fabric.Image.filters.Vintage(),
  Kodachrome: () => new fabric.Image.filters.Kodachrome(),
  Polaroid:   () => new fabric.Image.filters.Polaroid(),
  Brownie:    () => new fabric.Image.filters.Brownie(),
  Technicolor:() => new fabric.Image.filters.Technicolor(),
};

/* ══════════════════════════════════════════════════════════════
   SVG CLIPART LIBRARY
   ══════════════════════════════════════════════════════════════ */
const CLIPART = [
  {name:'Paw',svg:`<svg viewBox="0 0 100 100" fill="#1A56FF" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="65" rx="22" ry="20"/><ellipse cx="28" cy="42" rx="10" ry="12"/><ellipse cx="72" cy="42" rx="10" ry="12"/><ellipse cx="18" cy="28" rx="8" ry="9"/><ellipse cx="82" cy="28" rx="8" ry="9"/></svg>`},
  {name:'Bone',svg:`<svg viewBox="0 0 100 40" fill="#FF9500" xmlns="http://www.w3.org/2000/svg"><rect x="20" y="14" width="60" height="12" rx="4"/><circle cx="16" cy="12" r="9"/><circle cx="16" cy="28" r="9"/><circle cx="84" cy="12" r="9"/><circle cx="84" cy="28" r="9"/></svg>`},
  {name:'Heart',svg:`<svg viewBox="0 0 100 90" fill="#FF3B4E" xmlns="http://www.w3.org/2000/svg"><path d="M50 80 C50 80 5 50 5 25 C5 10 18 2 30 8 C40 13 50 25 50 25 C50 25 60 13 70 8 C82 2 95 10 95 25 C95 50 50 80 50 80Z"/></svg>`},
  {name:'Star',svg:`<svg viewBox="0 0 100 100" fill="#FF9500" xmlns="http://www.w3.org/2000/svg"><polygon points="50,5 61,35 95,35 68,57 79,91 50,70 21,91 32,57 5,35 39,35"/></svg>`},
  {name:'Crown',svg:`<svg viewBox="0 0 100 70" fill="#FF9500" xmlns="http://www.w3.org/2000/svg"><polygon points="5,65 20,20 50,45 80,20 95,65"/><circle cx="5" cy="65" r="6"/><circle cx="95" cy="65" r="6"/><circle cx="20" cy="20" r="6"/><circle cx="50" cy="45" r="6"/><circle cx="80" cy="20" r="6"/><rect x="5" y="60" width="90" height="10" rx="3"/></svg>`},
  {name:'Ribbon',svg:`<svg viewBox="0 0 100 100" fill="#DB2777" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="18"/><path d="M50 32 Q30 10 15 20 Q5 30 30 48Z"/><path d="M50 32 Q70 10 85 20 Q95 30 70 48Z"/><path d="M50 68 Q30 90 15 80 Q5 70 30 52Z"/><path d="M50 68 Q70 90 85 80 Q95 70 70 52Z"/></svg>`},
  {name:'Banner',svg:`<svg viewBox="0 0 120 40" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 5 L115 5 L115 30 L60 38 L5 30 Z" fill="#1A56FF"/><path d="M5 5 L5 30 L0 35 L5 5Z" fill="#1040D6"/><path d="M115 5 L115 30 L120 35 L115 5Z" fill="#1040D6"/></svg>`},
  {name:'Spark',svg:`<svg viewBox="0 0 100 100" fill="#FF9500" xmlns="http://www.w3.org/2000/svg"><polygon points="50,5 53,44 92,47 53,50 56,89 50,52 8,55 47,50 44,11"/></svg>`},
  {name:'Shield',svg:`<svg viewBox="0 0 100 110" fill="#7C3AED" xmlns="http://www.w3.org/2000/svg"><path d="M50 5 L95 20 L95 55 C95 78 74 97 50 105 C26 97 5 78 5 55 L5 20 Z"/></svg>`},
  {name:'Circle',svg:`<svg viewBox="0 0 100 100" fill="none" stroke="#1A56FF" stroke-width="8" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="42"/></svg>`},
  {name:'Dog',svg:`<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="58" rx="30" ry="28" fill="#FF9500"/><ellipse cx="50" cy="40" rx="22" ry="20" fill="#FF9500"/><ellipse cx="30" cy="28" rx="10" ry="14" fill="#FF9500" transform="rotate(-15,30,28)"/><ellipse cx="70" cy="28" rx="10" ry="14" fill="#FF9500" transform="rotate(15,70,28)"/><circle cx="43" cy="38" r="4" fill="#0A0F1E"/><circle cx="57" cy="38" r="4" fill="#0A0F1E"/><ellipse cx="50" cy="48" rx="8" ry="5" fill="#D4742A"/></svg>`},
  {name:'Cat',svg:`<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="58" rx="28" ry="26" fill="#9AA5B4"/><ellipse cx="50" cy="40" rx="20" ry="18" fill="#9AA5B4"/><polygon points="32,28 26,10 42,22" fill="#9AA5B4"/><polygon points="68,28 74,10 58,22" fill="#9AA5B4"/><circle cx="43" cy="38" r="4" fill="#0A0F1E"/><circle cx="57" cy="38" r="4" fill="#0A0F1E"/><ellipse cx="50" cy="47" rx="6" ry="4" fill="#FFB6C1"/></svg>`},
  {name:''+String.fromCodePoint(10024),svg:`<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><text x="50" y="70" text-anchor="middle" font-size="70">✨</text></svg>`},
  {name:'PB',svg:`<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="48" fill="#1A56FF"/><text x="50" y="65" text-anchor="middle" font-family="Syne,sans-serif" font-weight="800" font-size="30" fill="#fff">PB</text></svg>`},
  {name:'Footprint',svg:`<svg viewBox="0 0 80 100" fill="#0A0F1E" xmlns="http://www.w3.org/2000/svg"><ellipse cx="35" cy="70" rx="15" ry="20"/><ellipse cx="18" cy="50" rx="8" ry="10"/><ellipse cx="52" cy="50" rx="8" ry="10"/><ellipse cx="10" cy="38" rx="7" ry="8"/><ellipse cx="60" cy="38" rx="7" ry="8"/></svg>`},
];

/* ══════════════════════════════════════════════════════════════
   STATE
   ══════════════════════════════════════════════════════════════ */
const S = {
  cv: null, mainImg: null,
  origB64: null, origFilename: null,
  rmBgB64: null, rmBgFilename: null,
  busy: false,
  hist: [], histIdx: -1,
  fmt: 'png',
  bold: false, italic: false, align: 'center',
  activeFilter: 'Normal',
  smartGuides: true,
  currentShape: 'none',
  imgW: 0, imgH: 0,         // uploaded image natural dimensions
  imageId: <?= $preloadImageId ?>,
  returnUrl: <?= json_encode($returnUrl) ?>,
  saveMode: 'new',
};

/* ══════════════════════════════════════════════════════════════
   CANVAS INIT
   ══════════════════════════════════════════════════════════════ */
function mkCv(lw=600,lh=600) {
  if (S.cv) S.cv.dispose();
  const wrap=document.getElementById('cwrap');
  const sc=Math.min((wrap.clientWidth-48)/lw,(wrap.clientHeight-48)/lh,1);
  const dW=Math.round(lw*sc),dH=Math.round(lh*sc);
  S.cv=new fabric.Canvas('fc',{width:dW,height:dH,backgroundColor:null,preserveObjectStacking:true});
  S.cv._lW=lw;S.cv._lH=lh;S.cv._sc=sc;
  const host=document.getElementById('cHost');
  host.style.width=dW+'px';host.style.height=dH+'px';
  S.cv.on('object:modified',saveH);
  S.cv.on('object:added',()=>{mkLy();saveH();});
  S.cv.on('object:removed',()=>{mkLy();saveH();});
  S.cv.on('selection:created',onSel);
  S.cv.on('selection:updated',onSel);
  S.cv.on('selection:cleared',onDesel);
  if (S.smartGuides) attachGuides();
  saveH();
}

/* ── SMART GUIDES ─────────────────────────────────────────── */
function attachGuides() {
  const guideH=document.getElementById('guideH');
  const guideV=document.getElementById('guideV');
  const THRESH=8;

  S.cv.on('object:moving',({target:obj})=>{
    const cx=S.cv.width/2, cy=S.cv.height/2;
    const ob=obj.getBoundingRect(true);
    const ox=ob.left+ob.width/2, oy=ob.top+ob.height/2;
    let snapX=false,snapY=false;

    if (Math.abs(ox-cx)<THRESH) { obj.left+=cx-ox; snapX=true; }
    if (Math.abs(oy-cy)<THRESH) { obj.top+=cy-oy; snapY=true; }
    if (Math.abs(ob.left)<THRESH) { obj.left=obj.left-(ob.left); snapX=true; }
    if (Math.abs(ob.top)<THRESH) { obj.top=obj.top-(ob.top); snapY=true; }

    guideV.style.left=(S.cv.width/2)+'px'; guideV.classList.toggle('show',snapX);
    guideH.style.top=(S.cv.height/2)+'px'; guideH.classList.toggle('show',snapY);
    S.cv.renderAll();
  });
  S.cv.on('object:modified',()=>{
    guideH.classList.remove('show');guideV.classList.remove('show');
  });
}

/* ══════════════════════════════════════════════════════════════
   UPLOAD
   ══════════════════════════════════════════════════════════════ */
const fileIn=document.getElementById('fileIn'),upZone=document.getElementById('upZone');
upZone.addEventListener('click',()=>fileIn.click());
upZone.addEventListener('dragover',e=>{e.preventDefault();upZone.classList.add('drag');});
upZone.addEventListener('dragleave',()=>upZone.classList.remove('drag'));
upZone.addEventListener('drop',e=>{e.preventDefault();upZone.classList.remove('drag');if(e.dataTransfer.files[0])upload(e.dataTransfer.files[0]);});
fileIn.addEventListener('change',e=>{if(e.target.files[0])upload(e.target.files[0]);});

function upload(file) {
  if(file.size>20*1024*1024){st('stUp','er','File too large (max 20 MB)');return;}
  st('stUp','ld','Uploading…');
  const fd=new FormData();fd.append('image',file);fd.append('action','upload');
  fetch(location.pathname,{method:'POST',body:fd}).then(r=>r.json()).then(d=>{
    if(d.error){st('stUp','er',d.error);return;}
    S.origB64=d.b64;S.origFilename=d.filename;S.rmBgB64=null;
    loadImg(d.b64,false,()=>{
      // check DPI after loading
      checkDPI();
    });
    hideSt('stUp');showEditor();
    document.getElementById('btnRmBg').disabled=false;
  }).catch(()=>st('stUp','er','Upload failed'));
}

function checkDPI() {
  if (!S.mainImg) return;
  // natural image size vs canvas logical size
  const natW=S.mainImg.getOriginalSize().width;
  const natH=S.mainImg.getOriginalSize().height;
  S.imgW=natW;S.imgH=natH;
  // assume 300 DPI target; warn if < 150 DPI equivalent
  // at 1× print: width in px / DPI = width in inches
  // if the image is less than 150px per inch at its displayed size, warn
  const minPx=300; // minimum px per canvas dimension for decent print
  const lW=S.cv._lW,lH=S.cv._lH;
  const warn=(natW<lW*0.5||natH<lH*0.5);
  const warnEl=document.getElementById('dpiWarn');
  warnEl.classList.toggle('show',warn);
  if(warn){
    const eff=Math.round(natW/lW*300);
    document.getElementById('dpiWarnText').textContent=`Low resolution (~${eff} DPI) — may print blurry`;
  }
}

function showEditor() {
  document.getElementById('cEmpty').classList.add('gone');
  document.getElementById('cHost').style.display='block';
  document.getElementById('btnDl').disabled=false;
  document.getElementById('btnDownload').disabled=false;
  document.getElementById('btnSave').disabled=false;
  setObjBtns(false);
}

function loadImg(src,keepRest=false,cb=null) {
  fabric.Image.fromURL(src,img=>{
    if(!keepRest)S.cv.clear();
    else if(S.mainImg)S.cv.remove(S.mainImg);
    const cw=S.cv.width,ch=S.cv.height;
    const sc=Math.min(cw/img.width,ch/img.height)*.9;
    img.set({left:cw/2,top:ch/2,originX:'center',originY:'center',scaleX:sc,scaleY:sc});
    img.applyFilters();
    S.cv.insertAt(img,0);S.cv.setActiveObject(img);
    S.mainImg=img;S.cv.renderAll();rstSliders();mkLy();
    if(cb)cb();
  },{crossOrigin:'anonymous'});
}

/* ══════════════════════════════════════════════════════════════
   REMOVE BACKGROUND
   ══════════════════════════════════════════════════════════════ */
document.getElementById('btnRmBg').addEventListener('click',()=>{
  if(!S.origFilename||S.busy)return;
  S.busy=true;st('stBg','ld','Removing background…');
  document.getElementById('btnRmBg').disabled=true;
  const fd=new FormData();fd.append('action','remove_bg');fd.append('filename',S.origFilename);
  fetch(location.pathname,{method:'POST',body:fd}).then(r=>r.json()).then(d=>{
    S.busy=false;document.getElementById('btnRmBg').disabled=false;
    if(d.error){st('stBg','er',d.error);return;}
    S.rmBgB64=d.b64;S.rmBgFilename=d.filename;
    loadImg(d.b64,true);
    st('stBg','ok','Background removed!');
    document.getElementById('btnRestore').disabled=false;
    setTimeout(()=>hideSt('stBg'),4000);
  }).catch(()=>{S.busy=false;document.getElementById('btnRmBg').disabled=false;st('stBg','er','Request failed');});
});
document.getElementById('btnRestore').addEventListener('click',()=>{
  if(S.origB64){loadImg(S.origB64,true);S.rmBgB64=null;S.currentShape='none';document.querySelectorAll('.shape-chip').forEach(c=>c.classList.remove('on'));document.querySelector('.shape-chip[data-shape="none"]').classList.add('on');}
});

/* ══════════════════════════════════════════════════════════════
   PHOTO FILTERS
   ══════════════════════════════════════════════════════════════ */
const FILTER_CLASSES = {
  'Normal':'f-normal','Grayscale':'f-grayscale','Sepia':'f-sepia',
  'BlackWhite':'f-blackwhite','Vintage':'f-vintage','Kodachrome':'f-kodachrome',
  'Polaroid':'f-polaroid','Brownie':'f-brownie','Technicolor':'f-technicolor'
};
function buildFilterGrid() {
  const grid=document.getElementById('filterGrid');
  Object.keys(FILTERS).forEach(name=>{
    const cls=FILTER_CLASSES[name]||'f-normal';
    const chip=document.createElement('div');
    chip.className='filter-chip'+(name==='Normal'?' on':'');
    chip.dataset.filter=name;
    chip.innerHTML=`<div class="filter-thumb ${cls}"></div><div class="filter-name">${name}</div>`;
    grid.appendChild(chip);
    chip.addEventListener('click',()=>applyPhotoFilter(name));
  });
}
buildFilterGrid();

function applyPhotoFilter(name) {
  const obj=S.mainImg;if(!obj)return;
  S.activeFilter=name;
  document.querySelectorAll('.filter-chip').forEach(c=>c.classList.toggle('on',c.dataset.filter===name));

  // Keep adjustment filters too
  const adjFilters=[];
  const b=+document.getElementById('sBr').value;
  const c=+document.getElementById('sCo').value;
  const sat=+document.getElementById('sSa').value;
  if(b!==0)adjFilters.push(new fabric.Image.filters.Brightness({brightness:b}));
  if(c!==0)adjFilters.push(new fabric.Image.filters.Contrast({contrast:c}));
  if(sat!==0)adjFilters.push(new fabric.Image.filters.Saturation({saturation:sat}));

  const photoFilter=name!=='Normal'?FILTERS[name]():null;
  obj.filters=photoFilter?[photoFilter,...adjFilters]:adjFilters;
  obj.applyFilters();S.cv.renderAll();
}

// Build filter thumbnails after main image loads (called in loadImg cb or here lazily)
function updateFilterThumbs() {
  if(!S.mainImg)return;
  const tmpCanvas=document.createElement('canvas');
  tmpCanvas.width=60;tmpCanvas.height=44;

  Object.entries(FILTERS).forEach(([name,fn])=>{
    const thumbCanvas=document.getElementById('ft_'+name);
    if(!thumbCanvas)return;
    const ctx=thumbCanvas.getContext('2d');

    // Draw from main image
    fabric.Image.fromURL(S.mainImg.getSrc(),img=>{
      img.scaleToWidth(60);img.scaleToHeight(44);
      if(fn){img.filters=[fn()];img.applyFilters();}
      img.set({left:0,top:0});
      const tmpFab=new fabric.Canvas(null,{width:60,height:44});
      tmpFab.add(img);tmpFab.renderAll();
      ctx.drawImage(tmpFab.getElement(),0,0,60,44);
      tmpFab.dispose();
    },{crossOrigin:'anonymous'});
  });
}

/* ══════════════════════════════════════════════════════════════
   ADJUSTMENTS
   ══════════════════════════════════════════════════════════════ */
[['sBr','vBr'],['sCo','vCo'],['sSa','vSa'],['sOp','vOp']].forEach(([s,v])=>{
  document.getElementById(s).addEventListener('input',e=>{
    const n=parseFloat(e.target.value);
    document.getElementById(v).textContent=Number.isInteger(n)?n:n.toFixed(2).replace(/\.?0+$/,'');
    applyAdj();
  });
});
function applyAdj(){
  const o=S.cv.getActiveObject()||S.mainImg;if(!o||o.type!=='image')return;
  const photoFilter=S.activeFilter!=='Normal'?[FILTERS[S.activeFilter]()]:[];
  const b=+document.getElementById('sBr').value;
  const c=+document.getElementById('sCo').value;
  const sat=+document.getElementById('sSa').value;
  const adjF=[];
  if(b!==0)adjF.push(new fabric.Image.filters.Brightness({brightness:b}));
  if(c!==0)adjF.push(new fabric.Image.filters.Contrast({contrast:c}));
  if(sat!==0)adjF.push(new fabric.Image.filters.Saturation({saturation:sat}));
  o.filters=[...photoFilter,...adjF];
  o.opacity=+document.getElementById('sOp').value;
  o.applyFilters();S.cv.renderAll();
}
document.getElementById('btnRstAdj').addEventListener('click',()=>{
  [['sBr','vBr',0],['sCo','vCo',0],['sSa','vSa',0],['sOp','vOp',1]].forEach(([s,v,n])=>{
    document.getElementById(s).value=n;document.getElementById(v).textContent=n;
  });applyAdj();
});
function rstSliders(){
  [['sBr','vBr',0],['sCo','vCo',0],['sSa','vSa',0],['sOp','vOp',1]].forEach(([s,v,n])=>{
    document.getElementById(s).value=n;document.getElementById(v).textContent=n;
  });
}

/* ══════════════════════════════════════════════════════════════
   CANVAS BG
   ══════════════════════════════════════════════════════════════ */
const bgSws=document.getElementById('bgSws');
bgSws.addEventListener('click',e=>{
  const sw=e.target.closest('.sw');if(!sw)return;
  bgSws.querySelectorAll('.sw').forEach(s=>s.classList.remove('on'));
  sw.classList.add('on');
  S.cv.backgroundColor=sw.dataset.c==='transparent'?null:sw.dataset.c;
  S.cv.renderAll();
});
document.getElementById('bgPicker').addEventListener('input',e=>{
  bgSws.querySelectorAll('.sw').forEach(s=>s.classList.remove('on'));
  S.cv.backgroundColor=e.target.value;S.cv.renderAll();
});

/* ══════════════════════════════════════════════════════════════
   CANVAS SIZE
   ══════════════════════════════════════════════════════════════ */
document.querySelectorAll('.sz-tab').forEach(t=>{
  t.addEventListener('click',()=>{
    document.querySelectorAll('.sz-tab').forEach(x=>x.classList.remove('on'));
    t.classList.add('on');
    document.getElementById('cwW').value=t.dataset.w;
    document.getElementById('cwH').value=t.dataset.h;
    rzCv(+t.dataset.w,+t.dataset.h);
  });
});
document.getElementById('btnApSz').addEventListener('click',()=>{
  const w=+document.getElementById('cwW').value,h=+document.getElementById('cwH').value;
  document.querySelectorAll('.sz-tab').forEach(x=>x.classList.remove('on'));
  rzCv(w,h);
});
function rzCv(lw,lh){
  const wrap=document.getElementById('cwrap');
  const sc=Math.min((wrap.clientWidth-48)/lw,(wrap.clientHeight-48)/lh,1);
  const dW=Math.round(lw*sc),dH=Math.round(lh*sc);
  S.cv._lW=lw;S.cv._lH=lh;S.cv._sc=sc;
  S.cv.setWidth(dW);S.cv.setHeight(dH);
  document.getElementById('cHost').style.width=dW+'px';
  document.getElementById('cHost').style.height=dH+'px';
  S.cv.renderAll();checkDPI();
}

/* ══════════════════════════════════════════════════════════════
   CROP TO SHAPE — applies clipPath to selected image
   ══════════════════════════════════════════════════════════════ */
function makeShapeClip(shape,w,h){
  const hw=w/2,hh=h/2;
  switch(shape){
    case 'circle':
      return new fabric.Circle({radius:Math.min(hw,hh),originX:'center',originY:'center'});
    case 'rounded':
      return new fabric.Rect({width:w,height:h,rx:w*0.15,ry:h*0.15,originX:'center',originY:'center'});
    case 'heart':{
      // heart via SVG path scaled
      const s=Math.min(w,h)/100;
      const path=`M ${50*s} ${80*s} C ${50*s} ${80*s} ${5*s} ${50*s} ${5*s} ${25*s} C ${5*s} ${10*s} ${18*s} ${2*s} ${30*s} ${8*s} C ${40*s} ${13*s} ${50*s} ${25*s} ${50*s} ${25*s} C ${50*s} ${25*s} ${60*s} ${13*s} ${70*s} ${8*s} C ${82*s} ${2*s} ${95*s} ${10*s} ${95*s} ${25*s} C ${95*s} ${50*s} ${50*s} ${80*s} ${50*s} ${80*s} Z`;
      return new fabric.Path(path,{originX:'center',originY:'center',scaleX:1,scaleY:1,left:0,top:s*40-hh});
    }
    case 'star':{
      const pts=[];const r1=Math.min(hw,hh),r2=r1*0.4;
      for(let i=0;i<10;i++){
        const a=((i*36)-90)*Math.PI/180;
        const r=i%2===0?r1:r2;
        pts.push({x:Math.cos(a)*r,y:Math.sin(a)*r});
      }
      return new fabric.Polygon(pts,{originX:'center',originY:'center'});
    }
    case 'diamond':{
      const pts=[{x:0,y:-hh},{x:hw,y:0},{x:0,y:hh},{x:-hw,y:0}];
      return new fabric.Polygon(pts,{originX:'center',originY:'center'});
    }
    case 'hexagon':{
      const r=Math.min(hw,hh);const pts=[];
      for(let i=0;i<6;i++){const a=(i*60-30)*Math.PI/180;pts.push({x:Math.cos(a)*r,y:Math.sin(a)*r});}
      return new fabric.Polygon(pts,{originX:'center',originY:'center'});
    }
    case 'octagon':{
      const r=Math.min(hw,hh);const pts=[];
      for(let i=0;i<8;i++){const a=(i*45)*Math.PI/180;pts.push({x:Math.cos(a)*r,y:Math.sin(a)*r});}
      return new fabric.Polygon(pts,{originX:'center',originY:'center'});
    }
    default:return null;
  }
}

document.getElementById('shapeGrid').addEventListener('click',e=>{
  const chip=e.target.closest('.shape-chip');if(!chip)return;
  const shape=chip.dataset.shape;
  const obj=S.cv.getActiveObject()||S.mainImg;
  if(!obj||obj.type!=='image'){st('stBg','warn','Select a photo layer first');return;}

  document.querySelectorAll('.shape-chip').forEach(c=>c.classList.remove('on'));
  chip.classList.add('on');S.currentShape=shape;

  if(shape==='none'){obj.clipPath=null;}
  else{
    const bw=obj.getScaledWidth(),bh=obj.getScaledHeight();
    const clip=makeShapeClip(shape,bw,bh);
    if(clip){clip.absolutePositioned=false;obj.clipPath=clip;}
  }
  S.cv.renderAll();saveH();
});

/* ══════════════════════════════════════════════════════════════
   TEXT
   ══════════════════════════════════════════════════════════════ */
// Live update selected text when color/font/size changes
['txCol','txFn','txSz'].forEach(id=>{
  ['input','change'].forEach(evt=>{
    document.getElementById(id)?.addEventListener(evt,()=>{
      const o=S.cv.getActiveObject();
      if(!o||o.type!=='text')return;
      o.set({
        fill:document.getElementById('txCol').value,
        fontFamily:document.getElementById('txFn').value,
        fontSize:Math.round((+document.getElementById('txSz').value||48)*S.cv._sc),
      });
      S.cv.renderAll();
    });
  });
});

document.getElementById('togB').addEventListener('click',()=>{
  S.bold=!S.bold;document.getElementById('togB').classList.toggle('on',S.bold);
  const o=S.cv.getActiveObject();if(o&&o.type==='text'){o.set('fontWeight',S.bold?'bold':'normal');S.cv.renderAll();}
});
document.getElementById('togI').addEventListener('click',()=>{
  S.italic=!S.italic;document.getElementById('togI').classList.toggle('on',S.italic);
  const o=S.cv.getActiveObject();if(o&&o.type==='text'){o.set('fontStyle',S.italic?'italic':'normal');S.cv.renderAll();}
});
[['togAL','left'],['togAC','center'],['togAR','right']].forEach(([id,a])=>{
  document.getElementById(id).addEventListener('click',()=>{
    ['togAL','togAC','togAR'].forEach(i=>document.getElementById(i).classList.remove('on'));
    document.getElementById(id).classList.add('on');S.align=a;
    const o=S.cv.getActiveObject();if(o&&o.type==='text'){o.set('textAlign',a);S.cv.renderAll();}
  });
});

// letter spacing + line height live update on selected text
['txLs','txLh','txStrokeW','txStrokeCol'].forEach(id=>{
  document.getElementById(id).addEventListener('input',()=>applyTextProps());
});
function applyTextProps(){
  const o=S.cv.getActiveObject();if(!o||o.type!=='text')return;
  o.set({
    charSpacing:+document.getElementById('txLs').value,
    lineHeight:+document.getElementById('txLh').value,
    stroke:document.getElementById('txStrokeCol').value,
    strokeWidth:+document.getElementById('txStrokeW').value,
  });
  document.getElementById('vLs').textContent=document.getElementById('txLs').value;
  document.getElementById('vLh').textContent=parseFloat(document.getElementById('txLh').value).toFixed(1);
  document.getElementById('vSw').textContent=document.getElementById('txStrokeW').value;
  S.cv.renderAll();
}

// shadow live update
['sdCol','sdBlur','sdX','sdY'].forEach(id=>{
  document.getElementById(id).addEventListener('input',()=>applyTextShadow());
});
function applyTextShadow(){
  const o=S.cv.getActiveObject();if(!o)return;
  const col=document.getElementById('sdCol').value;
  const blur=+document.getElementById('sdBlur').value;
  const ox=+document.getElementById('sdX').value;
  const oy=+document.getElementById('sdY').value;
  document.getElementById('vSdB').textContent=blur;
  document.getElementById('vSdX').textContent=ox;
  document.getElementById('vSdY').textContent=oy;
  o.set('shadow',new fabric.Shadow({color:col,blur,offsetX:ox,offsetY:oy}));
  S.cv.renderAll();
}

// curved text
document.getElementById('txCurve').addEventListener('input',e=>{
  const v=+e.target.value;document.getElementById('vCurve').textContent=v;
  const o=S.cv.getActiveObject();
  if(o&&o.type==='text'&&v>0){applyCurvedText(o,v);}
  else if(o&&o.type==='text'){o.set('path',null);S.cv.renderAll();}
});

function applyCurvedText(obj,radius){
  // Create a circular path for the text to follow
  const pathData=`M ${-Math.PI*radius} 0 A ${radius} ${radius} 0 1 1 ${Math.PI*radius} 0`;
  const path=new fabric.Path(pathData,{visible:false,fill:'',stroke:''});
  obj.set('path',path);
  S.cv.renderAll();
}

document.getElementById('btnAddTx').addEventListener('click',()=>{
  const txt=document.getElementById('txCt').value.trim()||'My Pet';
  const size=+document.getElementById('txSz').value||48;
  const curveR=+document.getElementById('txCurve').value;
  const sw=+document.getElementById('txStrokeW').value;

  const t=new fabric.Text(txt,{
    left:S.cv.width/2,top:S.cv.height/2,
    originX:'center',originY:'center',
    fontSize:Math.round(size*S.cv._sc),
    fill:document.getElementById('txCol').value,
    fontFamily:document.getElementById('txFn').value,
    fontWeight:S.bold?'bold':'normal',
    fontStyle:S.italic?'italic':'normal',
    textAlign:S.align,
    charSpacing:+document.getElementById('txLs').value,
    lineHeight:+document.getElementById('txLh').value,
    stroke:sw>0?document.getElementById('txStrokeCol').value:'',
    strokeWidth:sw,
    paintFirst:'stroke',
  });
  if(curveR>0)applyCurvedText(t,curveR);
  S.cv.add(t);S.cv.setActiveObject(t);S.cv.renderAll();
});

/* ── TEXT TEMPLATES ──────────────────────────────────────── */
document.getElementById('tmplList').querySelectorAll('.tmpl-btn').forEach(btn=>{
  btn.addEventListener('click',()=>{
    const t=new fabric.Text(btn.dataset.text,{
      left:S.cv.width/2,top:S.cv.height/2,
      originX:'center',originY:'center',
      fontSize:Math.round(+btn.dataset.size*S.cv._sc),
      fill:btn.dataset.color,
      fontFamily:btn.dataset.font,
      fontWeight:'bold',textAlign:'center',
    });
    S.cv.add(t);S.cv.setActiveObject(t);S.cv.renderAll();
  });
});

/* ══════════════════════════════════════════════════════════════
   CLIPART / STICKERS
   ══════════════════════════════════════════════════════════════ */
function buildStickerGrid(){
  const grid=document.getElementById('stickerGrid');
  CLIPART.forEach(item=>{
    const btn=document.createElement('div');
    btn.className='sticker-btn';
    btn.title=item.name;
    btn.innerHTML=item.svg;
    btn.addEventListener('click',()=>addSvgSticker(item.svg,item.name));
    grid.appendChild(btn);
  });
}
buildStickerGrid();

function addSvgSticker(svgStr,name){
  const blob=new Blob([svgStr],{type:'image/svg+xml'});
  const url=URL.createObjectURL(blob);
  fabric.Image.fromURL(url,img=>{
    const sz=Math.round(Math.min(S.cv.width,S.cv.height)*0.25);
    img.scaleToWidth(sz);
    img.set({
      left:S.cv.width/2+Math.random()*40-20,
      top:S.cv.height/2+Math.random()*40-20,
      originX:'center',originY:'center',
    });
    img.data={type:'sticker',name};
    S.cv.add(img);S.cv.setActiveObject(img);S.cv.renderAll();
    URL.revokeObjectURL(url);
  });
}

/* ══════════════════════════════════════════════════════════════
   QR CODE
   ══════════════════════════════════════════════════════════════ */
document.getElementById('btnGenQr').addEventListener('click',()=>{
  const url=document.getElementById('qrUrl').value.trim();
  if(!url){st('stQr','er','Enter a URL or text first');return;}
  st('stQr','ld','Generating QR code…');
  const fg=document.getElementById('qrFg').value;
  const bg=document.getElementById('qrBg').value;
  const qrDiv=document.getElementById('qrHidden');
  qrDiv.innerHTML='';
  try{
    new QRCode(qrDiv,{text:url,width:200,height:200,colorDark:fg,colorLight:bg,correctLevel:QRCode.CorrectLevel.H});
    setTimeout(()=>{
      const canvas=qrDiv.querySelector('canvas')||qrDiv.querySelector('img');
      if(!canvas){st('stQr','er','QR generation failed');return;}
      const src=canvas.tagName==='CANVAS'?canvas.toDataURL('image/png'):canvas.src;
      // Add preview
      const prev=document.getElementById('qrPreview');
      prev.innerHTML=`<img src="${src}" style="width:100%;height:100%;object-fit:contain">`;
      // Add to canvas
      fabric.Image.fromURL(src,img=>{
        const sz=Math.round(Math.min(S.cv.width,S.cv.height)*0.28);
        img.scaleToWidth(sz);
        img.set({left:S.cv.width/2,top:S.cv.height/2,originX:'center',originY:'center'});
        img.data={name:'QR Code',isQR:true};S.cv.add(img);S.cv.setActiveObject(img);S.cv.renderAll();
        st('stQr','ok','QR code added!');
        setTimeout(()=>hideSt('stQr'),3000);
      });
    },200);
  }catch(e){st('stQr','er','QR generation error');}
});

/* ══════════════════════════════════════════════════════════════
   TOOLBAR
   ══════════════════════════════════════════════════════════════ */
document.getElementById('tbFH').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){o.set('flipX',!o.flipX);S.cv.renderAll();}});
document.getElementById('tbFV').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){o.set('flipY',!o.flipY);S.cv.renderAll();}});
document.getElementById('tbRL').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){o.rotate((o.angle||0)-15);S.cv.renderAll();}});
document.getElementById('tbRR').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){o.rotate((o.angle||0)+15);S.cv.renderAll();}});
document.getElementById('tbDel').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){if(o===S.mainImg)S.mainImg=null;S.cv.remove(o);S.cv.renderAll();}});
document.getElementById('tbCenter').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){o.set({left:S.cv.width/2,top:S.cv.height/2,originX:'center',originY:'center'});S.cv.renderAll();}});
document.getElementById('tbFill').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){const sc=Math.min(S.cv.width/(o.width||1),S.cv.height/(o.height||1));o.set({scaleX:sc,scaleY:sc,left:S.cv.width/2,top:S.cv.height/2,originX:'center',originY:'center'});S.cv.renderAll();}});

// Duplicate
document.getElementById('tbDup').addEventListener('click',()=>{
  const o=S.cv.getActiveObject();if(!o)return;
  o.clone(cloned=>{
    cloned.set({left:(o.left||0)+20,top:(o.top||0)+20});
    S.cv.add(cloned);S.cv.setActiveObject(cloned);S.cv.renderAll();
  });
});

// Smart guides toggle
document.getElementById('tbGuides').addEventListener('click',()=>{
  S.smartGuides=!S.smartGuides;
  document.getElementById('tbGuides').classList.toggle('active',S.smartGuides);
  if(S.smartGuides)attachGuides();
  else S.cv.off('object:moving');
  document.getElementById('guideH').classList.remove('show');
  document.getElementById('guideV').classList.remove('show');
});

function setObjBtns(on){['tbFH','tbFV','tbRL','tbRR','tbDel','tbDup','tbCenter','tbFill'].forEach(id=>document.getElementById(id).disabled=!on);}

/* ══════════════════════════════════════════════════════════════
   SELECTION
   ══════════════════════════════════════════════════════════════ */
function onSel(e){
  const o=e.selected?.[0]||S.cv.getActiveObject();if(!o)return;
  setObjBtns(true);
  document.getElementById('secProp').style.display='';
  document.getElementById('pSX').value=(o.scaleX||1).toFixed(2);document.getElementById('vpSX').textContent=(o.scaleX||1).toFixed(1);
  document.getElementById('pSY').value=(o.scaleY||1).toFixed(2);document.getElementById('vpSY').textContent=(o.scaleY||1).toFixed(1);
  document.getElementById('pRt').value=Math.round(o.angle||0);document.getElementById('vpRt').textContent=Math.round(o.angle||0)+'°';

  // Show shadow panel only for text/image
  document.getElementById('secShadow').style.display='';

  // Sync text controls
  if(o.type==='text'){
    document.getElementById('txLs').value=o.charSpacing||0;document.getElementById('vLs').textContent=o.charSpacing||0;
    document.getElementById('txLh').value=o.lineHeight||1.2;document.getElementById('vLh').textContent=(o.lineHeight||1.2).toFixed(1);
    document.getElementById('txStrokeW').value=o.strokeWidth||0;document.getElementById('vSw').textContent=o.strokeWidth||0;
    if(o.shadow){
      document.getElementById('sdCol').value=o.shadow.color||'#000000';
      document.getElementById('sdBlur').value=o.shadow.blur||0;document.getElementById('vSdB').textContent=o.shadow.blur||0;
      document.getElementById('sdX').value=o.shadow.offsetX||0;document.getElementById('vSdX').textContent=o.shadow.offsetX||0;
      document.getElementById('sdY').value=o.shadow.offsetY||0;document.getElementById('vSdY').textContent=o.shadow.offsetY||0;
    }
  }
  mkLy();
}
function onDesel(){setObjBtns(false);document.getElementById('secProp').style.display='none';document.getElementById('secShadow').style.display='none';mkLy();}

[['pSX','vpSX'],['pSY','vpSY'],['pRt','vpRt']].forEach(([id,vid])=>{
  document.getElementById(id).addEventListener('input',e=>{
    const o=S.cv.getActiveObject();if(!o)return;const v=+e.target.value;
    if(id==='pSX'){o.set('scaleX',v);document.getElementById(vid).textContent=v.toFixed(1);}
    if(id==='pSY'){o.set('scaleY',v);document.getElementById(vid).textContent=v.toFixed(1);}
    if(id==='pRt'){o.rotate(v);document.getElementById(vid).textContent=v+'°';}
    S.cv.renderAll();
  });
});

/* ══════════════════════════════════════════════════════════════
   LAYERS
   ══════════════════════════════════════════════════════════════ */
const IMG_IC=`<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`;
const TXT_IC=`<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" y1="20" x2="15" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/></svg>`;
const X_IC=`<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;

function mkLy(){
  const list=document.getElementById('lyList');
  const objs=S.cv.getObjects();
  if(!objs.length){list.innerHTML='<div class="ly-mt">No layers yet</div>';return;}
  const act=S.cv.getActiveObject();
  list.innerHTML=[...objs].reverse().map((obj,ri)=>{
    const idx=objs.length-1-ri;
    const isTx=obj.type==='text';
    const lbl=isTx?(obj.text||'').slice(0,18):obj.data?.name||'Photo';
    return `<div class="ly-item${obj===act?' on':''}" data-i="${idx}">
      <div class="ly-ic">${isTx?TXT_IC:IMG_IC}</div>
      <span class="ly-nm">${lbl}</span>
      <div class="ly-btns">
        <button class="btn btn-g btn-ic btn-sm ly-del" data-i="${idx}" title="Delete">${X_IC}</button>
      </div>
    </div>`;
  }).join('');
  list.querySelectorAll('.ly-item').forEach(item=>{
    item.addEventListener('click',e=>{if(e.target.closest('.ly-del'))return;
      const o=S.cv.getObjects()[+item.dataset.i];if(o){S.cv.setActiveObject(o);S.cv.renderAll();mkLy();}
    });
  });
  list.querySelectorAll('.ly-del').forEach(btn=>{
    btn.addEventListener('click',()=>{
      const o=S.cv.getObjects()[+btn.dataset.i];
      if(o){if(o===S.mainImg)S.mainImg=null;S.cv.remove(o);S.cv.renderAll();}
    });
  });
}
document.getElementById('btnLyUp').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){S.cv.bringForward(o);mkLy();}});
document.getElementById('btnLyDn').addEventListener('click',()=>{const o=S.cv.getActiveObject();if(o){S.cv.sendBackwards(o);mkLy();}});

/* ══════════════════════════════════════════════════════════════
   HISTORY
   ══════════════════════════════════════════════════════════════ */
function saveH(){
  if(!S.cv)return;
  const j=JSON.stringify(S.cv.toJSON(['data','clipPath']));
  S.hist=S.hist.slice(0,S.histIdx+1);S.hist.push(j);S.histIdx=S.hist.length-1;
  document.getElementById('btnUndo').disabled=S.histIdx<=0;
  document.getElementById('btnRedo').disabled=true;
}
function undo(){if(S.histIdx<=0)return;S.histIdx--;loadH();document.getElementById('btnUndo').disabled=S.histIdx<=0;document.getElementById('btnRedo').disabled=false;}
function redo(){if(S.histIdx>=S.hist.length-1)return;S.histIdx++;loadH();document.getElementById('btnRedo').disabled=S.histIdx>=S.hist.length-1;document.getElementById('btnUndo').disabled=false;}
function loadH(){S.cv.loadFromJSON(S.hist[S.histIdx],()=>{S.cv.renderAll();mkLy();});}
document.getElementById('btnUndo').addEventListener('click',undo);
document.getElementById('btnRedo').addEventListener('click',redo);
document.addEventListener('keydown',e=>{
  const tag=document.activeElement.tagName;
  if(tag==='INPUT'||tag==='TEXTAREA'||tag==='SELECT')return;
  if((e.ctrlKey||e.metaKey)&&!e.shiftKey&&e.key.toLowerCase()==='z'){e.preventDefault();undo();}
  if((e.ctrlKey||e.metaKey)&&(e.key.toLowerCase()==='y'||(e.shiftKey&&e.key.toLowerCase()==='z'))){e.preventDefault();redo();}
  if(e.key==='Delete'||e.key==='Backspace'){const o=S.cv.getActiveObject();if(o){if(o===S.mainImg)S.mainImg=null;S.cv.remove(o);S.cv.renderAll();}}
});

/* ══════════════════════════════════════════════════════════════
   EXPORT / DOWNLOAD
   ══════════════════════════════════════════════════════════════ */
document.querySelectorAll('.fmt').forEach(f=>{
  f.addEventListener('click',()=>{
    document.querySelectorAll('.fmt').forEach(x=>x.classList.remove('on'));
    f.classList.add('on');S.fmt=f.dataset.fmt;
    document.getElementById('qField').style.display=S.fmt==='jpeg'?'':'none';
  });
});
document.getElementById('exQ').addEventListener('input',e=>{document.getElementById('vExQ').textContent=Math.round(+e.target.value*100)+'%';});

function getExportDataUrl(){
  const sc=+document.getElementById('exSc').value;
  const mult=(S.cv._lW/S.cv.width)*sc;
  const q=S.fmt==='jpeg'?+document.getElementById('exQ').value:1;
  return S.cv.toDataURL({format:S.fmt==='jpeg'?'jpeg':'png',quality:q,multiplier:mult});
}

function doDownload(){
  st('stEx','ld','Generating…');
  setTimeout(()=>{
    const url=getExportDataUrl();
    const sc=+document.getElementById('exSc').value;
    const px=Math.round(S.cv._lW*sc),py=Math.round(S.cv._lH*sc);
    const a=document.createElement('a');a.href=url;a.download=`petblip-photo-${px}x${py}.${S.fmt}`;a.click();
    st('stEx','ok',`Downloaded · ${px}×${py}px`);setTimeout(()=>hideSt('stEx'),5000);
  },60);
}
document.getElementById('btnDl').addEventListener('click',doDownload);
document.getElementById('btnDownload').addEventListener('click',doDownload);

/* ══════════════════════════════════════════════════════════════
   SAVE TO GALLERY MODAL
   ══════════════════════════════════════════════════════════════ */
const saveModal=document.getElementById('saveModal');
document.getElementById('btnSave').addEventListener('click',()=>{
  const prev=getExportDataUrl();
  document.getElementById('mPreviewImg').src=prev;
  saveModal.classList.add('open');
});

// save mode selection
document.querySelectorAll('.m-save-opt').forEach(opt=>{
  opt.addEventListener('click',()=>{
    document.querySelectorAll('.m-save-opt').forEach(x=>x.classList.remove('on'));
    opt.classList.add('on');S.saveMode=opt.dataset.mode;
  });
});

document.getElementById('btnMCancel').addEventListener('click',()=>saveModal.classList.remove('open'));
saveModal.addEventListener('click',e=>{if(e.target===saveModal)saveModal.classList.remove('open');});

document.getElementById('btnMSave').addEventListener('click',()=>{
  st('stSave','ld','Saving…');
  document.getElementById('btnMSave').disabled=true;
  const dataUrl=getExportDataUrl();
  const fd=new FormData();
  fd.append('action','save_to_gallery');
  fd.append('dataUrl',dataUrl);
  fd.append('image_id',S.imageId);
  fd.append('save_mode',S.saveMode);
  fetch(location.pathname,{method:'POST',body:fd}).then(r=>r.json()).then(d=>{
    document.getElementById('btnMSave').disabled=false;
    if(d.error){st('stSave','er',d.error);return;}
    st('stSave','ok','Saved to gallery!');
    setTimeout(()=>{
      saveModal.classList.remove('open');
      if(S.returnUrl)window.location.href=S.returnUrl;
    },1800);
  }).catch(()=>{document.getElementById('btnMSave').disabled=false;st('stSave','er','Save failed');});
});

/* ══════════════════════════════════════════════════════════════
   COLLAPSIBLE SECTIONS
   ══════════════════════════════════════════════════════════════ */
document.querySelectorAll('.shd').forEach(hd=>{
  hd.addEventListener('click',()=>{
    const id=hd.dataset.sec;
    const bd=document.getElementById('sb-'+id);
    const cv=document.getElementById('cv-'+id);
    if(!bd)return;
    const c=bd.classList.toggle('hidden');
    if(cv)cv.classList.toggle('dn',c);
  });
});

/* ══════════════════════════════════════════════════════════════
   STATUS HELPERS
   ══════════════════════════════════════════════════════════════ */
function st(id,type,msg){const el=document.getElementById(id);if(!el)return;el.className='st show '+type;el.innerHTML=type==='ld'?`<div class="spin"></div><span>${msg}</span>`:`<span>${msg}</span>`;}
function hideSt(id){const el=document.getElementById(id);if(el)el.className='st';}

/* ══════════════════════════════════════════════════════════════
   BOOT
   ══════════════════════════════════════════════════════════════ */
mkCv(600,600);
setObjBtns(false);
document.getElementById('btnRestore').disabled=true;

// Pre-load gallery image
<?php if($preloadB64 && $preloadFilename): ?>
(()=>{
  S.origB64 = <?= json_encode($preloadB64) ?>;
  S.origFilename = <?= json_encode($preloadFilename) ?>;
  loadImg(S.origB64,false,()=>{
    checkDPI();
    updateFilterThumbs();
  });
  showEditor();
  document.getElementById('btnRmBg').disabled=false;
  // Auto trigger BG removal
  setTimeout(()=>document.getElementById('btnRmBg').click(),700);
})();
<?php else: ?>
// Update filter thumbs when first image is loaded manually
const origUpload=upload;
<?php endif; ?>
</script>
</body>
</html>