/* ============================================================
   Work Desktop — app.js
   ============================================================ */
'use strict';

(function () {

  const API = 'api.php';
  const $ = id => document.getElementById(id);

  /* ---- CLOCK ---- */
  const clockEl = $('clock');
  function tickClock() {
    clockEl.textContent = new Date().toLocaleTimeString('en-NZ', {
      hour: '2-digit', minute: '2-digit', second: '2-digit'
    });
  }
  tickClock();
  setInterval(tickClock, 1000);

  /* ---- AUDIO ---- */
  const AudioCtx = window.AudioContext || window.webkitAudioContext;
  let audioCtx = null;
  function getAudio() {
    if (!audioCtx) { try { audioCtx = new AudioCtx(); } catch (e) {} }
    return audioCtx;
  }
  function playTone(freq1, freq2, dur, vol) {
    try {
      const ac = getAudio(); if (!ac) return;
      const o = ac.createOscillator(), g = ac.createGain();
      o.connect(g); g.connect(ac.destination);
      o.type = 'sine';
      o.frequency.setValueAtTime(freq1, ac.currentTime);
      o.frequency.exponentialRampToValueAtTime(freq2, ac.currentTime + dur);
      g.gain.setValueAtTime(vol, ac.currentTime);
      g.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + dur + 0.02);
      o.start(ac.currentTime);
      o.stop(ac.currentTime + dur + 0.03);
    } catch (e) {}
  }
  const playClick = () => playTone(880, 660, 0.07, 0.10);
  const playOpen  = () => playTone(440, 880, 0.12, 0.09);
  const playClose = () => playTone(660, 330, 0.10, 0.08);

  /* ---- NOTIFICATIONS ---- */
  function notify(msg, type = 'info', dur = 3000) {
    const tray = $('notifTray');
    const n = document.createElement('div');
    n.className = `notif ${type}`;
    n.textContent = msg;
    tray.appendChild(n);
    setTimeout(() => {
      n.style.animation = 'notif-out .3s forwards';
      setTimeout(() => n.remove(), 300);
    }, dur);
  }

  /* ---- SECTIONS CONFIG ---- */
  const SECTION = {
    main:     { file: 'main.json',     mount: 'icons-main',     dock: true,  defaultGrid: { rows: 1, cols: 6 } },
    general:  { file: 'general.json',  mount: 'icons-general',  dock: false, defaultGrid: { rows: 2, cols: 3 } },
    security: { file: 'security.json', mount: 'icons-security', dock: false, defaultGrid: { rows: 2, cols: 3 } },
    parking:  { file: 'parking.json',  mount: 'icons-parking',  dock: false, defaultGrid: { rows: 2, cols: 3 } },
    admin:    { file: 'admin.json',    mount: 'icons-admin',    dock: false, defaultGrid: { rows: 2, cols: 3 } },
    tools:    { file: 'tools.json',    mount: 'icons-tools',    dock: false, defaultGrid: { rows: 2, cols: 3 } },
  };

  let editMode  = false;
  let dataCache = {};
  let saveTimer = null;

  const uid   = () => crypto?.randomUUID ? crypto.randomUUID() : 'id-' + Math.random().toString(16).slice(2);
  const clamp = (n, a, b) => Math.max(a, Math.min(b, n));
  const escHtml = s => String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');

  /* ---- SAVE STATE ---- */
  const saveStateEl = $('saveState');
  function setSaveState(txt, type = '') {
    saveStateEl.textContent = txt;
    saveStateEl.className = 'tag ' + type;
  }

  /* ---- EDIT MODE ---- */
  const btnEdit = $('btnEdit');
  function setEditMode(on) {
    editMode = !!on;
    document.body.classList.toggle('edit-mode', editMode);
    btnEdit.classList.toggle('on', editMode);
    document.querySelectorAll('.icon-btn').forEach(el => {
      if (el.dataset.action !== 'collapse') {
        el.classList.toggle('hidden', !editMode);
      }
    });
    setSaveState(editMode ? 'Edit ON' : 'Ready', editMode ? 'saving' : '');
    if (editMode) notify('Edit mode on — hover tiles to edit or remove', 'info', 2500);
  }
  btnEdit.addEventListener('click', () => { playClick(); setEditMode(!editMode); });

  /* ---- KEYBOARD SHORTCUTS ---- */
  document.addEventListener('keydown', e => {
    if (e.key === 'e' && !e.target.matches('input,textarea,select')) setEditMode(!editMode);
    if (e.key === 'Escape') { closeOverlay(); closeModal(); }
  });

  /* ---- SEARCH ---- */
  const searchInput = $('searchInput');
  searchInput.addEventListener('input', () => {
    const q = searchInput.value.toLowerCase().trim();
    document.querySelectorAll('.app').forEach(el => {
      const name = (el.querySelector('.lbl')?.textContent || '').toLowerCase();
      el.style.display = (!q || name.includes(q)) ? '' : 'none';
    });
  });

  /* ---- COLLAPSE ---- */
  document.addEventListener('click', e => {
    const btn = e.target.closest('[data-action="collapse"]');
    if (!btn) return;
    const win = btn.closest('.win');
    if (win) { playClick(); win.classList.toggle('collapsed'); }
  });

  /* ---- ICON FALLBACK ---- */
  function iconFallback(name) {
    return (name || '').trim()[0]?.toUpperCase() || '•';
  }

  /* ---- RIPPLE ---- */
  function addRipple(el, ev) {
    const rect = el.getBoundingClientRect();
    const r = document.createElement('span');
    r.className = 'ripple';
    const size = Math.max(rect.width, rect.height);
    r.style.cssText = `width:${size}px;height:${size}px;left:${ev.clientX - rect.left - size / 2}px;top:${ev.clientY - rect.top - size / 2}px`;
    el.appendChild(r);
    setTimeout(() => r.remove(), 600);
  }

  /* ---- OVERLAY ---- */
  const overlayBack    = $('overlayBack');
  const overlayFrame   = $('overlayFrame');
  const overlayTitle   = $('overlayTitle');
  const overlayUrl     = $('overlayUrl');
  const overlayLoading = $('overlayLoading');
  let currentOverlayUrl = '';

  function openOverlay(name, url) {
    if (!url || url === '#') { notify('No URL set for this app', 'error'); return; }
    playOpen();
    overlayTitle.textContent = name;
    overlayUrl.textContent = url;
    currentOverlayUrl = url;
    overlayLoading.style.display = 'flex';
    overlayFrame.src = '';
    overlayBack.classList.add('show');
    setTimeout(() => { overlayFrame.src = url; }, 80);
  }
  overlayFrame.addEventListener('load', () => { overlayLoading.style.display = 'none'; });

  function closeOverlay() {
    if (!overlayBack.classList.contains('show')) return;
    playClose();
    overlayBack.classList.remove('show');
    overlayFrame.src = '';
  }
  $('overlayClose').addEventListener('click', closeOverlay);
  $('overlayRefresh').addEventListener('click', () => {
    overlayFrame.src = overlayFrame.src;
    overlayLoading.style.display = 'flex';
  });
  $('overlayOpenExternal').addEventListener('click', () => {
    if (currentOverlayUrl) window.open(currentOverlayUrl, '_blank');
  });

  /* ---- MAKE APP ELEMENT ---- */
  function makeAppEl(sectionKey, app) {
    const el = document.createElement('div');
    el.className = 'app';
    el.dataset.id = app.id;
    el.dataset.section = sectionKey;
    if (app.overlay) el.dataset.overlay = 'true';

    // Drag handle
    const dh = document.createElement('div');
    dh.className = 'drag-handle';
    dh.textContent = '⠿';

    // Icon
    const ico = document.createElement('div');
    ico.className = 'ico';
    if (app.tint) ico.style.background = app.tint;
    if (app.icon && app.icon.trim()) {
      const img = document.createElement('img');
      img.alt = app.name || 'App';
      img.src = app.icon;
      img.loading = 'lazy';
      img.onerror = () => { img.remove(); ico.textContent = iconFallback(app.name); };
      ico.appendChild(img);
    } else {
      ico.textContent = iconFallback(app.name);
    }

    // Label
    const lbl = document.createElement('div');
    lbl.className = 'lbl';
    lbl.textContent = app.name || 'App';

    // Edit overlay (shown on hover in edit mode)
    const eo = document.createElement('div');
    eo.className = 'edit-overlay';

    const editBtn = document.createElement('div');
    editBtn.className = 'edit-overlay-btn';
    editBtn.textContent = '✎ Edit';
    editBtn.addEventListener('click', e => {
      e.stopPropagation();
      openAppEditor(sectionKey, app.id);
    });

    const delBtn = document.createElement('div');
    delBtn.className = 'edit-overlay-btn del';
    delBtn.textContent = '✕ Remove';
    delBtn.addEventListener('click', e => {
      e.stopPropagation();
      if (confirm(`Remove "${app.name}"?`)) deleteApp(sectionKey, app.id);
    });

    eo.appendChild(editBtn);
    eo.appendChild(delBtn);

    el.appendChild(dh);
    el.appendChild(ico);
    el.appendChild(lbl);
    el.appendChild(eo);

    if (app.hint) el.title = app.hint;

    // Click — open app or overlay
    el.addEventListener('click', e => {
      if (editMode) return;
      addRipple(el, e);
      playClick();
      const url = app.url || '#';
      if (app.overlay || app.target === '_self') {
        openOverlay(app.name, url);
      } else {
        if (url && url !== '#') window.open(url, '_blank', 'noopener,noreferrer');
        else notify('No URL configured for this app', 'error');
      }
    });

    // HTML5 drag
    el.draggable = true;
    el.addEventListener('dragstart', e => {
      if (!editMode) { e.preventDefault(); return; }
      e.dataTransfer.setData('text/plain', JSON.stringify({ section: sectionKey, id: app.id }));
      e.dataTransfer.effectAllowed = 'move';
      setTimeout(() => el.style.opacity = '.4', 0);
    });
    el.addEventListener('dragend', () => { el.style.opacity = ''; });

    // Pointer/touch drag on handle
    dh.addEventListener('pointerdown', e => {
      if (!editMode) return;
      e.preventDefault();
      e.stopPropagation();
      startPointerDrag(e, el, sectionKey);
    });

    return el;
  }

  /* ---- GRID / RENDER ---- */
  function applyGrid(sectionKey) {
    const cfg   = SECTION[sectionKey];
    const mount = $(cfg.mount);
    const d     = dataCache[sectionKey] || { grid: cfg.defaultGrid, apps: [] };
    const rows  = clamp(parseInt(d.grid?.rows || cfg.defaultGrid.rows), 1, 20);
    const cols  = clamp(parseInt(d.grid?.cols || cfg.defaultGrid.cols), 1, 20);
    if (cfg.dock) {
      mount.style.gridTemplateColumns = `repeat(${Math.max(cols, 4)}, minmax(80px, 1fr))`;
      mount.style.gridTemplateRows    = `repeat(${rows}, auto)`;
    } else {
      mount.style.gridTemplateColumns = `repeat(${cols}, minmax(0, 1fr))`;
      mount.style.gridTemplateRows    = `repeat(${rows}, auto)`;
    }
    mount.dataset.rows = rows;
    mount.dataset.cols = cols;
  }

  function renderSection(sectionKey) {
    const cfg   = SECTION[sectionKey];
    const mount = $(cfg.mount);
    const d     = dataCache[sectionKey] || { grid: cfg.defaultGrid, apps: [] };

    d.apps.forEach(a => { if (!a.id) a.id = uid(); });

    const rows = clamp(parseInt(d.grid?.rows || cfg.defaultGrid.rows), 1, 20);
    const cols = clamp(parseInt(d.grid?.cols || cfg.defaultGrid.cols), 1, 20);
    if (!cfg.dock && d.apps.length > rows * cols) d.apps = d.apps.slice(0, rows * cols);

    dataCache[sectionKey] = d;
    applyGrid(sectionKey);
    mount.innerHTML = '';
    d.apps.forEach(app => mount.appendChild(makeAppEl(sectionKey, app)));

    // HTML5 drop target
    mount.addEventListener('dragover', e => {
      if (!editMode) return;
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
      mount.closest('.win').classList.add('drag-over');
    });
    mount.addEventListener('dragleave', e => {
      if (!e.currentTarget.contains(e.relatedTarget)) {
        mount.closest('.win').classList.remove('drag-over');
      }
    });
    mount.addEventListener('drop', e => {
      if (!editMode) return;
      e.preventDefault();
      mount.closest('.win').classList.remove('drag-over');
      let payload;
      try { payload = JSON.parse(e.dataTransfer.getData('text/plain')); } catch { return; }
      const target = e.target.closest('.app');
      if (!target) return;
      const targetId = target.dataset.id;
      if (!targetId || targetId === payload.id) return;
      if (payload.section === sectionKey) {
        reorderApps(sectionKey, payload.id, targetId);
      } else {
        moveAppCrossSection(payload.section, sectionKey, payload.id, targetId);
      }
    });
  }

  function reorderApps(sectionKey, movedId, beforeId) {
    const apps = dataCache[sectionKey].apps;
    const from = apps.findIndex(a => a.id === movedId);
    const to   = apps.findIndex(a => a.id === beforeId);
    if (from < 0 || to < 0) return;
    const [moved] = apps.splice(from, 1);
    apps.splice(to, 0, moved);
    renderSection(sectionKey);
    queueSave(sectionKey);
  }

  function moveAppCrossSection(fromKey, toKey, id, beforeId) {
    const fromApps = dataCache[fromKey]?.apps;
    const toApps   = dataCache[toKey]?.apps;
    if (!fromApps || !toApps) return;
    const idx = fromApps.findIndex(a => a.id === id);
    if (idx < 0) return;
    const [app] = fromApps.splice(idx, 1);
    app.id = uid();
    const toIdx = toApps.findIndex(a => a.id === beforeId);
    toApps.splice(toIdx >= 0 ? toIdx : toApps.length, 0, app);
    renderSection(fromKey);
    renderSection(toKey);
    queueSave(fromKey);
    queueSave(toKey);
    notify(`Moved "${app.name}" to ${toKey}`, 'success');
  }

  function deleteApp(sectionKey, id) {
    const d   = dataCache[sectionKey];
    const idx = d.apps.findIndex(a => a.id === id);
    if (idx < 0) return;
    const name = d.apps[idx].name;
    d.apps.splice(idx, 1);
    renderSection(sectionKey);
    queueSave(sectionKey);
    notify(`Removed "${name}"`, 'info');
  }

  /* ---- POINTER/TOUCH DRAG ---- */
  let drag = null;
  function startPointerDrag(ev, appEl, sectionKey) {
    const id   = appEl.dataset.id;
    const rect = appEl.getBoundingClientRect();
    const ghost = appEl.cloneNode(true);
    ghost.style.cssText = `position:fixed;left:${rect.left}px;top:${rect.top}px;width:${rect.width}px;height:${rect.height}px;opacity:.8;z-index:9999;pointer-events:none;transform:scale(1.05) rotate(2deg);transition:none;border:2px solid rgba(79,143,255,.6)`;
    document.body.appendChild(ghost);
    drag = { sectionKey, id, ghost, offsetX: ev.clientX - rect.left, offsetY: ev.clientY - rect.top };

    const onMove = e => {
      if (!drag) return;
      drag.ghost.style.left = (e.clientX - drag.offsetX) + 'px';
      drag.ghost.style.top  = (e.clientY - drag.offsetY) + 'px';
      const el = document.elementFromPoint(e.clientX, e.clientY);
      const targetApp = el?.closest?.('.app');
      if (targetApp && targetApp.dataset.id && targetApp.dataset.id !== drag.id) {
        const ts = targetApp.dataset.section;
        if (ts === drag.sectionKey) reorderApps(drag.sectionKey, drag.id, targetApp.dataset.id);
        else moveAppCrossSection(drag.sectionKey, ts, drag.id, targetApp.dataset.id);
      }
    };
    const onUp = () => {
      drag?.ghost.remove();
      drag = null;
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
    };
    window.addEventListener('pointermove', onMove, { passive: false });
    window.addEventListener('pointerup', onUp);
  }

  /* ---- API ---- */
  async function loadSection(sectionKey) {
    const cfg = SECTION[sectionKey];
    try {
      const r = await fetch(`${API}?file=${encodeURIComponent(cfg.file)}`, { cache: 'no-store' });
      const j = await r.json();
      if (!j.ok) throw new Error(j.error);
      dataCache[sectionKey] = j.data || { grid: cfg.defaultGrid, apps: [] };
    } catch {
      dataCache[sectionKey] = { grid: cfg.defaultGrid, apps: [] };
    }
    renderSection(sectionKey);
  }

  function queueSave(sectionKey) {
    setSaveState('Saving…', 'saving');
    clearTimeout(saveTimer);
    saveTimer = setTimeout(() => saveSection(sectionKey), 500);
  }

  async function saveSection(sectionKey) {
    const cfg = SECTION[sectionKey];
    try {
      const r = await fetch(API, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'save', file: cfg.file, data: dataCache[sectionKey] })
      });
      const j = await r.json();
      if (!j.ok) throw new Error(j.error);
      setSaveState('Saved ✓', 'saved');
      setTimeout(() => setSaveState('Ready', ''), 2500);
    } catch (e) {
      setSaveState('Error!', 'error');
      notify('Save failed: ' + e.message, 'error');
    }
  }

  /* ---- MODAL ---- */
  const modalBack    = $('modalBack');
  const modalTitle   = $('modalTitle');
  const modalContent = $('modalContent');
  const modalActions = $('modalActions');

  function openModal(title, content, actions) {
    modalTitle.textContent = title;
    modalContent.innerHTML = content;
    modalActions.innerHTML = actions;
    modalBack.classList.add('show');
    modalBack.setAttribute('aria-hidden', 'false');
  }
  function closeModal() {
    modalBack.classList.remove('show');
    modalBack.setAttribute('aria-hidden', 'true');
  }
  $('modalClose').addEventListener('click', closeModal);
  modalBack.addEventListener('click', e => { if (e.target === modalBack) closeModal(); });

  /* ---- APP EDITOR ---- */
  function openAppEditor(sectionKey, appId) {
    const d     = dataCache[sectionKey];
    let app     = appId ? d.apps.find(a => a.id === appId) : null;
    const isNew = !app;
    if (isNew) app = { id: uid(), name: '', url: '', icon: '', tint: '', hint: '', target: '_blank', overlay: false };

    openModal(
      isNew ? `Add App — ${sectionKey}` : `Edit "${app.name}"`,
      `<div class="field" style="grid-column:1/-1">
         <label>Name</label>
         <input id="ea_name" value="${escHtml(app.name)}" placeholder="My App" />
       </div>
       <div class="field" style="grid-column:1/-1">
         <label>URL</label>
         <input id="ea_url" type="url" value="${escHtml(app.url)}" placeholder="https://…" />
       </div>
       <div class="field" style="grid-column:1/-1">
         <label>Icon URL</label>
         <input id="ea_icon" value="${escHtml(app.icon)}" placeholder="https://…/icon.png" />
       </div>
       <div class="field">
         <label>Tooltip / Hint</label>
         <input id="ea_hint" value="${escHtml(app.hint || '')}" placeholder="Short description" />
       </div>
       <div class="field">
         <label>Tint colour</label>
         <input id="ea_tint" value="${escHtml(app.tint || '')}" placeholder="#334455 or rgba(…)" />
       </div>
       <div class="field" style="grid-column:1/-1; flex-direction:row; align-items:center; gap:10px">
         <input type="checkbox" id="ea_overlay" ${app.overlay ? 'checked' : ''} style="width:18px;height:18px;cursor:pointer" />
         <label for="ea_overlay" style="cursor:pointer;font-size:12px;opacity:1">Open in overlay (stays on this page)</label>
       </div>`,
      `${!isNew ? `<button class="btn danger" id="ea_del">✕ Remove</button>` : ''}
       <button class="btn secondary" id="ea_cancel">Cancel</button>
       <button class="btn primary" id="ea_save">💾 Save</button>`
    );

    $('ea_cancel').onclick = closeModal;
    $('ea_save').onclick = () => {
      const name = $('ea_name').value.trim();
      if (!name) { notify('Name is required', 'error'); return; }
      app.name    = name;
      app.url     = $('ea_url').value.trim();
      app.icon    = $('ea_icon').value.trim();
      app.hint    = $('ea_hint').value.trim();
      app.tint    = $('ea_tint').value.trim();
      app.overlay = $('ea_overlay').checked;
      app.target  = app.overlay ? '_self' : '_blank';
      if (isNew) {
        dataCache[sectionKey].apps.push(app);
      } else {
        const idx = d.apps.findIndex(a => a.id === app.id);
        if (idx >= 0) d.apps[idx] = app;
      }
      renderSection(sectionKey);
      queueSave(sectionKey);
      closeModal();
      notify(isNew ? `Added "${app.name}"` : `Updated "${app.name}"`, 'success');
    };
    if ($('ea_del')) {
      $('ea_del').onclick = () => {
        if (confirm(`Remove "${app.name}"?`)) {
          deleteApp(sectionKey, app.id);
          closeModal();
        }
      };
    }
  }

  /* ---- GRID EDITOR ---- */
  function openGridEditor(sectionKey) {
    const g = dataCache[sectionKey]?.grid || SECTION[sectionKey].defaultGrid;
    openModal(
      `Grid: ${sectionKey}`,
      `<div class="field">
         <label>Rows</label>
         <input id="g_rows" type="number" min="1" max="20" value="${g.rows}" />
       </div>
       <div class="field">
         <label>Columns</label>
         <input id="g_cols" type="number" min="1" max="20" value="${g.cols}" />
       </div>`,
      `<button class="btn secondary" id="g_cancel">Cancel</button>
       <button class="btn primary" id="g_save">Save</button>`
    );
    $('g_cancel').onclick = closeModal;
    $('g_save').onclick = () => {
      const rows = clamp(parseInt($('g_rows').value || '1'), 1, 20);
      const cols = clamp(parseInt($('g_cols').value || '4'), 1, 20);
      dataCache[sectionKey].grid = { rows, cols };
      renderSection(sectionKey);
      queueSave(sectionKey);
      closeModal();
    };
  }

  /* ---- TITLEBAR BUTTONS ---- */
  document.querySelectorAll('.win[data-section]').forEach(win => {
    const sk = win.dataset.section;
    if (!SECTION[sk]) return;
    win.querySelectorAll('.icon-btn').forEach(btn => {
      btn.addEventListener('click', e => {
        e.stopPropagation();
        if (!editMode && btn.dataset.action !== 'collapse') return;
        if (btn.dataset.action === 'add')  { playClick(); openAppEditor(sk, null); }
        if (btn.dataset.action === 'grid') { playClick(); openGridEditor(sk); }
      });
    });
  });

  /* ---- INIT ---- */
  const statusText = $('statusText');
  async function init() {
    statusText.textContent = 'Loading…';
    for (const key of Object.keys(SECTION)) {
      await loadSection(key);
    }
    const dateStr = new Date().toLocaleDateString('en-NZ', {
      weekday: 'long', day: 'numeric', month: 'long', year: 'numeric'
    });
    statusText.textContent = dateStr;
    setEditMode(false);
  }
  init();

  /* ---- NOTEPAD ---- */
  const pad = $('pad');
  const ctx = pad.getContext('2d', { willReadFrequently: true });
  const SAVE_KEY = 'workdesktop.pad.v2';
  let npMode  = 'brush';
  let drawing = false;
  let lastPt  = null;
  let undoStack = [], redoStack = [];

  function setTool(m) {
    npMode = m;
    $('pad-status').textContent = m === 'text'
      ? 'Text tool: click canvas to place text.'
      : 'Notes auto-save in your browser.';
  }
  $('tool-brush').onclick  = () => setTool('brush');
  $('tool-eraser').onclick = () => setTool('eraser');
  $('tool-text').onclick   = () => setTool('text');

  function fitCanvas() {
    const rect = pad.getBoundingClientRect();
    const dpr  = Math.max(1, window.devicePixelRatio || 1);
    const w    = Math.floor(rect.width  * dpr);
    const h    = Math.floor(rect.height * dpr);
    if (pad.width !== w || pad.height !== h) {
      const snap = pad.toDataURL();
      pad.width = w; pad.height = h;
      const img = new Image();
      img.onload = () => ctx.drawImage(img, 0, 0, w, h);
      img.src = snap;
    }
  }

  function autoSavePad() {
    try { localStorage.setItem(SAVE_KEY, pad.toDataURL()); } catch (e) {}
  }
  function loadPad() {
    try {
      const d = localStorage.getItem(SAVE_KEY);
      if (!d) return;
      const img = new Image();
      img.onload = () => { ctx.clearRect(0, 0, pad.width, pad.height); ctx.drawImage(img, 0, 0, pad.width, pad.height); };
      img.src = d;
    } catch (e) {}
  }

  function pushUndo() {
    try {
      undoStack.push(ctx.getImageData(0, 0, pad.width, pad.height));
      if (undoStack.length > 40) undoStack.shift();
      redoStack = [];
    } catch (e) {}
  }

  function getPt(e) {
    const r = pad.getBoundingClientRect();
    return {
      x: (e.clientX - r.left) * (pad.width  / r.width),
      y: (e.clientY - r.top)  * (pad.height / r.height)
    };
  }

  function stroke(a, b) {
    ctx.lineCap = 'round'; ctx.lineJoin = 'round';
    if (npMode === 'eraser') {
      ctx.globalCompositeOperation = 'destination-out';
      ctx.strokeStyle = 'rgba(0,0,0,1)';
      ctx.lineWidth = parseFloat($('eraser-size').value);
    } else {
      ctx.globalCompositeOperation = 'source-over';
      ctx.strokeStyle = $('brush-color').value;
      ctx.lineWidth   = parseFloat($('brush-size').value);
    }
    ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
  }

  pad.addEventListener('pointerdown', e => {
    const p = getPt(e);
    if (npMode === 'text') {
      const txt = ($('text-input').value || '').trim();
      if (!txt) return;
      pushUndo();
      ctx.globalCompositeOperation = 'source-over';
      ctx.fillStyle = $('text-color').value;
      ctx.font = `700 ${clamp(parseInt($('text-size').value || '20'), 10, 120)}px 'DM Sans', system-ui`;
      ctx.textBaseline = 'top';
      ctx.fillText(txt, p.x, p.y);
      autoSavePad();
      return;
    }
    drawing = true; lastPt = p; pushUndo();
  });
  pad.addEventListener('pointermove', e => {
    if (!drawing) return;
    const p = getPt(e); stroke(lastPt, p); lastPt = p;
  });
  const endDraw = () => {
    if (!drawing) return;
    drawing = false; lastPt = null;
    ctx.globalCompositeOperation = 'source-over';
    autoSavePad();
  };
  pad.addEventListener('pointerup',     endDraw);
  pad.addEventListener('pointercancel', endDraw);
  pad.addEventListener('pointerleave',  endDraw);

  $('undo').onclick = () => {
    if (!undoStack.length) return;
    redoStack.push(ctx.getImageData(0, 0, pad.width, pad.height));
    ctx.putImageData(undoStack.pop(), 0, 0);
    autoSavePad();
  };
  $('redo').onclick = () => {
    if (!redoStack.length) return;
    undoStack.push(ctx.getImageData(0, 0, pad.width, pad.height));
    ctx.putImageData(redoStack.pop(), 0, 0);
    autoSavePad();
  };
  $('clear').onclick   = () => { if (confirm('Clear notepad?')) { pushUndo(); ctx.clearRect(0, 0, pad.width, pad.height); autoSavePad(); } };
  $('np-save').onclick = () => { const a = document.createElement('a'); a.download = 'notes.png'; a.href = pad.toDataURL(); a.click(); };

  new ResizeObserver(() => { fitCanvas(); loadPad(); }).observe(pad);
  setTimeout(() => { fitCanvas(); loadPad(); setTool('brush'); }, 100);

})();
