← Whiteboard results

Whiteboard

Qwen 3.6 27B MTP GGUF · typescript

Initial view of Whiteboard

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 69,228 bytes · SHA-256 88fccda89aba
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Whiteboard</title>
<style>
/* ── css: layout ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
  overflow: hidden;
  background: #e8eaed;
  color: #1e1e1e;
  user-select: none;
  -webkit-user-select: none;
}

#canvas-container {
  position: fixed;
  top: 0; left: 0;
  width: 100vw; height: 100vh;
}

#whiteboard {
  display: block;
  width: 100%; height: 100%;
}

/* Toolbar */
.floating-toolbar {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 6px 10px;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.05);
  z-index: 100;
  white-space: nowrap;
}

.tool-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: #5f6368;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.tool-btn:hover { background: #f1f3f4; color: #1e1e1e; }
.tool-btn.active { background: #e8f0fe; color: #1967d2; }
.tool-btn svg { pointer-events: none; }

.tool-sep {
  width: 1px;
  height: 24px;
  background: #e2e5e9;
  margin: 0 4px;
}

/* Color pickers */
#color-pick {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 4px;
}
.color-label {
  font-size: 9px;
  font-weight: 600;
  color: #999;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.color-row {
  display: flex;
  gap: 3px;
}
.color-dot {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  transition: border-color 0.15s, transform 0.15s;
}
.color-dot:hover { transform: scale(1.2); }
.color-dot.active { border-color: #1967d2; transform: scale(1.15); }

/* Stroke widths */
.sw-row {
  display: flex;
  gap: 4px;
  align-items: center;
}
.sw-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
  transition: border-color 0.15s;
}
.sw-btn:hover { border-color: #ddd; }
.sw-btn.active { border-color: #1967d2; background: #e8f0fe; }
.sw-btn .dot {
  border-radius: 50%;
  background: #1e1e1e;
}

/* Zoom controls */
#zoom-controls {
  position: fixed;
  bottom: 16px;
  right: 16px;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  background: #ffffff;
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.05);
  z-index: 100;
}
#zoom-controls button {
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: #5f6368;
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
}
#zoom-controls button:hover { background: #f1f3f4; color: #1e1e1e; }
#zoom-level {
  font-size: 12px;
  font-weight: 600;
  color: #5f6368;
  min-width: 42px;
  text-align: center;
}

/* Hint bar */
#hint-bar {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  color: #aaa;
  background: rgba(255,255,255,0.85);
  padding: 6px 14px;
  border-radius: 8px;
  backdrop-filter: blur(4px);
  z-index: 100;
  pointer-events: none;
  transition: opacity 0.5s;
}
</style>
</head>
<body>
<div id="toolbar" class="floating-toolbar">
  <button data-tool="select" class="tool-btn active" title="Select (V)">
    <svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M4 4l8 16 3-7 7-3z"></path></svg>
  </button>
  <button data-tool="pen" class="tool-btn" title="Pen (P)">
    <svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25z"></path></svg>
  </button>
  <button data-tool="rect" class="tool-btn" title="Rectangle (R)">
    <svg viewBox="0 0 24 24" width="20" height="20"><rect x="3" y="3" width="18" height="18" rx="2" fill="none" stroke="currentColor" stroke-width="2"></rect></svg>
  </button>
  <button data-tool="ellipse" class="tool-btn" title="Ellipse (O)">
    <svg viewBox="0 0 24 24" width="20" height="20"><ellipse cx="12" cy="12" rx="9" ry="9" fill="none" stroke="currentColor" stroke-width="2"></ellipse></svg>
  </button>
  <button data-tool="line" class="tool-btn" title="Line (L)">
    <svg viewBox="0 0 24 24" width="20" height="20"><line x1="4" y1="20" x2="20" y2="4" stroke="currentColor" stroke-width="2" stroke-linecap="round"></line></svg>
  </button>
  <button data-tool="text" class="tool-btn" title="Text (T)">
    <svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M5 4v3h5.5v12h3V7H19V4z"></path></svg>
  </button>
  <button data-tool="eraser" class="tool-btn" title="Eraser (E)">
    <svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M16.24 3.56l4.95 4.94c.78.79.78 2.05 0 2.84L12 20.41a4.01 4.01 0 01-5.66 0L2.81 16.88c-.78-.78-.78-2.05 0-2.83l8.49-8.49c.78-.78 2.05-.78 2.84 0l2.1 2.1z"></path></svg>
  </button>
  <div class="tool-sep"></div>
  <button id="undo-btn" title="Undo (Ctrl+Z)">
    <svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12.5 8c-2.65 0-5.05 1.04-6.83 2.73L2 7.14V16h8.86l-3.67-3.67A7.46 7.46 0 0112.5 10c3.04 0 5.64 1.85 6.67 4.48l1.95-.83C20.09 10.25 16.6 8 12.5 8z"></path></svg>
  </button>
  <button id="redo-btn" title="Redo (Ctrl+Shift+Z)">
    <svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M18.4 10.03C16.57 8.36 14.18 7.19 11.5 7.19V4.16l5.71 5.71-.81.16zM12.5 8c-3.04 0-5.64 1.85-6.67 4.48l-1.95-.83C4.91 8.25 8.4 6 12.5 6c2.65 0 5.05 1.04 6.83 2.73l2.67-2.67V16h-8.86l3.67-3.67A7.46 7.46 0 0112.5 10z"></path></svg>
  </button>
  <div class="tool-sep"></div>
  <div id="color-pick">
    <label class="color-label" title="Stroke color">Stroke</label>
    <div id="stroke-colors" class="color-row"></div>
    <label class="color-label" title="Fill color">Fill</label>
    <div id="fill-colors" class="color-row"></div>
  </div>
  <div class="tool-sep"></div>
  <div id="stroke-widths" class="sw-row"></div>
</div>
<div id="canvas-container">
  <canvas id="whiteboard"></canvas>
</div>
<div id="zoom-controls">
  <button id="zoom-out-btn" title="Zoom Out">−</button>
  <span id="zoom-level">100%</span>
  <button id="zoom-in-btn" title="Zoom In">+</button>
  <button id="reset-view-btn" title="Reset View">⌂</button>
</div>
<div id="hint-bar">Space+drag to pan · Scroll to zoom · Delete to remove · V/P/R/O/L/T/E for tools</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./constants":"src/constants.ts","./state":"src/state.ts","./renderer":"src/renderer.ts","./interactions":"src/interactions.ts","./types":"src/types.ts"},"src/state.ts":{"./constants":"src/constants.ts","./types":"src/types.ts"},"src/hitTest.ts":{"./constants":"src/constants.ts","./types":"src/types.ts","./state":"src/state.ts"},"src/renderer.ts":{"./constants":"src/constants.ts","./types":"src/types.ts","./state":"src/state.ts","./hitTest":"src/hitTest.ts"},"src/interactions.ts":{"./constants":"src/constants.ts","./types":"src/types.ts","./hitTest":"src/hitTest.ts","./state":"src/state.ts","./renderer":"src/renderer.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
// ===== Main Entry =====
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("./constants");
const state_1 = require("./state");
const renderer_1 = require("./renderer");
const interactions_1 = require("./interactions");
function start() {
    (0, state_1.loadState)();
    const canvas = document.getElementById('whiteboard');
    (0, renderer_1.initRenderer)(canvas);
    (0, interactions_1.initInteractions)(canvas);
    // Center viewport
    state_1.viewport.x = canvas.clientWidth / 2;
    state_1.viewport.y = canvas.clientHeight / 2;
    buildColorPalette('stroke-colors', constants_1.STROKE_COLORS, constants_1.DEFAULT_STROKE_COLOR, (color) => {
        (0, state_1.setStrokeColor)(color);
        updateActiveColorDots('stroke-colors', color);
    });
    buildColorPalette('fill-colors', constants_1.FILL_COLORS, constants_1.DEFAULT_FILL_COLOR, (color) => {
        (0, state_1.setFillColor)(color);
        updateActiveColorDots('fill-colors', color);
    });
    buildStrokeWidths(constants_1.DEFAULT_STROKE_WIDTH);
    // Tool buttons
    document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
        btn.addEventListener('click', () => {
            const tool = btn.dataset.tool;
            (0, interactions_1.setTool)(tool);
            updateActiveToolBtn(tool);
        });
    });
    // Undo / Redo
    document.getElementById('undo-btn')?.addEventListener('click', () => {
        Promise.resolve().then(() => __importStar(require('./state'))).then(m => { m.undo(); (0, renderer_1.render)(null); });
    });
    document.getElementById('redo-btn')?.addEventListener('click', () => {
        Promise.resolve().then(() => __importStar(require('./state'))).then(m => { m.redo(); (0, renderer_1.render)(null); });
    });
    // Zoom
    document.getElementById('zoom-in-btn')?.addEventListener('click', () => zoomBy(1.25));
    document.getElementById('zoom-out-btn')?.addEventListener('click', () => zoomBy(0.8));
    document.getElementById('reset-view-btn')?.addEventListener('click', interactions_1.resetView);
    window.addEventListener('resize', () => { (0, renderer_1.resize)(); (0, renderer_1.render)((0, interactions_1.getPreview)()); });
    // Hide hint
    setTimeout(() => {
        const hint = document.getElementById('hint-bar');
        if (hint)
            hint.style.opacity = '0';
    }, 5000);
    (0, interactions_1.updateZoomIndicator)();
    (0, renderer_1.render)(null);
}
function buildColorPalette(containerId, colors, defaultColor, onPick) {
    const container = document.getElementById(containerId);
    if (!container)
        return;
    for (const color of colors) {
        const dot = document.createElement('div');
        dot.className = 'color-dot' + (color === defaultColor ? ' active' : '');
        dot.dataset.color = color;
        if (color === 'none') {
            dot.style.background = 'transparent';
            dot.style.border = '2px solid #ccc';
            dot.style.position = 'relative';
            dot.innerHTML = '<svg style="position:absolute;top:2px;left:3px" width="10" height="10" viewBox="0 0 10 10"><line x1="1" y1="1" x2="9" y2="9" stroke="#999" stroke-width="1.5"/></svg>';
        }
        else {
            dot.style.background = color;
        }
        dot.addEventListener('click', () => onPick(color));
        container.appendChild(dot);
    }
}
function updateActiveColorDots(containerId, activeColor) {
    const container = document.getElementById(containerId);
    if (!container)
        return;
    container.querySelectorAll('.color-dot').forEach(dot => {
        dot.classList.toggle('active', dot.dataset.color === activeColor);
    });
}
function buildStrokeWidths(defaultWidth) {
    const container = document.getElementById('stroke-widths');
    if (!container)
        return;
    for (const w of constants_1.STROKE_WIDTHS) {
        const btn = document.createElement('button');
        btn.className = 'sw-btn' + (w === defaultWidth ? ' active' : '');
        btn.dataset.width = String(w);
        const dot = document.createElement('span');
        dot.className = 'dot';
        const size = Math.max(4, w * 2);
        dot.style.width = `${size}px`;
        dot.style.height = `${size}px`;
        btn.appendChild(dot);
        btn.addEventListener('click', () => {
            (0, state_1.setStrokeWidth)(w);
            container.querySelectorAll('.sw-btn').forEach(b => b.classList.remove('active'));
            btn.classList.add('active');
        });
        container.appendChild(btn);
    }
}
function updateActiveToolBtn(tool) {
    document.querySelectorAll('.tool-btn').forEach(btn => {
        btn.classList.toggle('active', btn.dataset.tool === tool);
    });
}
function zoomBy(factor) {
    const canvas = document.getElementById('whiteboard');
    const cx = canvas.clientWidth / 2;
    const cy = canvas.clientHeight / 2;
    const oldZoom = state_1.viewport.zoom;
    let newZoom = oldZoom * factor;
    newZoom = Math.max(0.25, Math.min(4, newZoom));
    const worldX = (cx - state_1.viewport.x) / oldZoom;
    const worldY = (cy - state_1.viewport.y) / oldZoom;
    state_1.viewport.x = cx - worldX * newZoom;
    state_1.viewport.y = cy - worldY * newZoom;
    state_1.viewport.zoom = newZoom;
    (0, renderer_1.render)((0, interactions_1.getPreview)());
    (0, interactions_1.updateZoomIndicator)();
}
start();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
// ===== Tunable Constants =====
Object.defineProperty(exports, "__esModule", { value: true });
exports.LOCAL_STORAGE_KEY = exports.DEFAULT_STROKE_WIDTH = exports.DEFAULT_FILL_COLOR = exports.DEFAULT_STROKE_COLOR = exports.NUDGE_SHIFT_AMOUNT = exports.NUDGE_AMOUNT = exports.SELECTION_BORDER = exports.HANDLE_SIZE = exports.HIT_TOLERANCE = exports.ZOOM_STEP = exports.ZOOM_FACTOR = exports.ZOOM_MAX = exports.ZOOM_MIN = exports.STROKE_WIDTHS = exports.FILL_COLORS = exports.STROKE_COLORS = exports.COLORS = void 0;
exports.COLORS = {
    canvasBg: '#f8f9fa',
    chromeBg: '#ffffff',
    chromeBorder: '#e2e5e9',
    toolbarShadow: 'rgba(0,0,0,0.12)',
    gridDot: '#d4d7dc',
    selectionBox: '#2970ff',
    selectionHandle: '#ffffff',
    previewShape: '#2970ff',
    activeToolBg: '#e8f0fe',
    activeToolBorder: '#2970ff',
    textOverlay: '#ffffff',
    textCaret: '#2970ff',
    rubberBand: 'rgba(41,112,255,0.1)',
    rubberBandBorder: '#2970ff',
    eraserCursor: '#ff6b6b',
};
exports.STROKE_COLORS = [
    '#1e1e1e', '#e74c3c', '#e67e22', '#f1c40f', '#27ae60',
    '#2980b9', '#8e44ad', '#2c3e50', '#7f8c8d', '#ecf0f1',
];
exports.FILL_COLORS = [
    'none', '#fee2e2', '#fef3c7', '#d1fae5', '#dbeafe',
    '#ede9fe', '#fce7f3', '#e0e7ff', '#f3e8ff', '#fff7ed',
];
exports.STROKE_WIDTHS = [1, 2, 3, 4, 6, 8];
exports.ZOOM_MIN = 0.25;
exports.ZOOM_MAX = 4;
exports.ZOOM_FACTOR = 1.001; // per scroll tick multiplier
exports.ZOOM_STEP = 1.25; // for buttons
exports.HIT_TOLERANCE = 8; // px for hit testing strokes
exports.HANDLE_SIZE = 8; // corner handle size in world coords
exports.SELECTION_BORDER = 2;
exports.NUDGE_AMOUNT = 1;
exports.NUDGE_SHIFT_AMOUNT = 10;
exports.DEFAULT_STROKE_COLOR = '#1e1e1e';
exports.DEFAULT_FILL_COLOR = 'none';
exports.DEFAULT_STROKE_WIDTH = 2;
exports.LOCAL_STORAGE_KEY = 'whiteboard-data';
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ===== Types & Data Model =====
Object.defineProperty(exports, "__esModule", { value: true });
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// ===== State Management: scene, viewport, tools, selection, history =====
Object.defineProperty(exports, "__esModule", { value: true });
exports.textInputObjectId = exports.textInputActive = exports.currentTool = exports.viewport = exports.selectedIds = exports.scene = void 0;
exports.nextId = nextId;
exports.getStrokeColor = getStrokeColor;
exports.getFillColor = getFillColor;
exports.getStrokeWidth = getStrokeWidth;
exports.setStrokeColor = setStrokeColor;
exports.setFillColor = setFillColor;
exports.setStrokeWidth = setStrokeWidth;
exports.pushHistory = pushHistory;
exports.undo = undo;
exports.redo = redo;
exports.selectObject = selectObject;
exports.deselectAll = deselectAll;
exports.getSelectedObjects = getSelectedObjects;
exports.isSelected = isSelected;
exports.saveState = saveState;
exports.loadState = loadState;
exports.addObject = addObject;
exports.removeObject = removeObject;
exports.removeSelected = removeSelected;
exports.modifyObjects = modifyObjects;
exports.moveObjects = moveObjects;
const constants_1 = require("./constants");
let _idCounter = 0;
function nextId() {
    return `obj_${++_idCounter}_${Date.now()}`;
}
// --- Scene ---
exports.scene = [];
exports.selectedIds = [];
// --- Viewport ---
exports.viewport = { x: 0, y: 0, zoom: 1 };
// --- Tools & Style ---
exports.currentTool = 'select';
let _strokeColor = constants_1.DEFAULT_STROKE_COLOR;
let _fillColor = constants_1.DEFAULT_FILL_COLOR;
let _strokeWidth = constants_1.DEFAULT_STROKE_WIDTH;
function getStrokeColor() { return _strokeColor; }
function getFillColor() { return _fillColor; }
function getStrokeWidth() { return _strokeWidth; }
function setStrokeColor(c) { _strokeColor = c; }
function setFillColor(c) { _fillColor = c; }
function setStrokeWidth(w) { _strokeWidth = w; }
// --- Text input ---
exports.textInputActive = false;
exports.textInputObjectId = null;
// --- History ---
const past = [];
const future = [];
function pushHistory(entry) {
    past.push(entry);
    future.length = 0; // discard future on new action
}
function undo() {
    if (past.length === 0)
        return;
    const entry = past.pop();
    // Restore before state
    for (const obj of entry.before) {
        const idx = exports.scene.findIndex(o => o.id === obj.id);
        if (idx >= 0) {
            exports.scene[idx] = obj;
        }
        else {
            exports.scene.push(obj);
        }
    }
    // Remove objects that weren't in before state but are in scene
    const beforeIds = new Set(entry.before.map(o => o.id));
    for (let i = exports.scene.length - 1; i >= 0; i--) {
        if (entry.objectIds.includes(exports.scene[i].id) && !beforeIds.has(exports.scene[i].id)) {
            exports.scene.splice(i, 1);
        }
    }
    future.push(entry);
    exports.selectedIds.length = 0;
}
function redo() {
    if (future.length === 0)
        return;
    const entry = future.pop();
    // Apply after state
    for (const obj of entry.after) {
        const idx = exports.scene.findIndex(o => o.id === obj.id);
        if (idx >= 0) {
            exports.scene[idx] = obj;
        }
        else {
            exports.scene.push(obj);
        }
    }
    const afterIds = new Set(entry.after.map(o => o.id));
    for (let i = exports.scene.length - 1; i >= 0; i--) {
        if (entry.objectIds.includes(exports.scene[i].id) && !afterIds.has(exports.scene[i].id)) {
            exports.scene.splice(i, 1);
        }
    }
    past.push(entry);
    exports.selectedIds.length = 0;
}
// --- Selection helpers ---
function selectObject(id, additive) {
    if (additive) {
        if (!exports.selectedIds.includes(id))
            exports.selectedIds.push(id);
    }
    else {
        exports.selectedIds.length = 0;
        exports.selectedIds.push(id);
    }
}
function deselectAll() {
    exports.selectedIds.length = 0;
}
function getSelectedObjects() {
    return exports.scene.filter(o => exports.selectedIds.includes(o.id));
}
function isSelected(id) {
    return exports.selectedIds.includes(id);
}
// --- Persistence ---
function saveState() {
    const data = {
        objects: exports.scene,
        idCounter: _idCounter,
    };
    try {
        localStorage.setItem(constants_1.LOCAL_STORAGE_KEY, JSON.stringify(data));
    }
    catch (_e) { /* quota exceeded */ }
}
function loadState() {
    try {
        const raw = localStorage.getItem(constants_1.LOCAL_STORAGE_KEY);
        if (!raw)
            return false;
        const data = JSON.parse(raw);
        exports.scene.length = 0;
        if (Array.isArray(data.objects)) {
            exports.scene.push(...data.objects);
        }
        if (typeof data.idCounter === 'number') {
            _idCounter = data.idCounter;
        }
        exports.selectedIds.length = 0;
        return true;
    }
    catch (_e) {
        return false;
    }
}
// --- History-aware scene mutations ---
function addObject(obj) {
    exports.scene.push(obj);
    pushHistory({ action: 'create', objectIds: [obj.id], before: [], after: [obj] });
    saveState();
}
function removeObject(id) {
    const idx = exports.scene.findIndex(o => o.id === id);
    if (idx < 0)
        return;
    const obj = exports.scene[idx];
    exports.scene.splice(idx, 1);
    pushHistory({ action: 'delete', objectIds: [id], before: [obj], after: [] });
    saveState();
}
function removeSelected() {
    if (exports.selectedIds.length === 0)
        return;
    const objects = getSelectedObjects();
    const ids = [...exports.selectedIds];
    for (const obj of objects) {
        const idx = exports.scene.findIndex(o => o.id === obj.id);
        if (idx >= 0)
            exports.scene.splice(idx, 1);
    }
    pushHistory({ action: 'delete', objectIds: ids, before: objects, after: [] });
    exports.selectedIds.length = 0;
    saveState();
}
function modifyObjects(before, after) {
    const ids = after.map(o => o.id);
    pushHistory({ action: 'modify', objectIds: ids, before, after });
    // Update scene
    for (const obj of after) {
        const idx = exports.scene.findIndex(o => o.id === obj.id);
        if (idx >= 0)
            exports.scene[idx] = obj;
    }
    saveState();
}
function moveObjects(before, after) {
    const ids = after.map(o => o.id);
    pushHistory({ action: 'move', objectIds: ids, before, after });
    for (const obj of after) {
        const idx = exports.scene.findIndex(o => o.id === obj.id);
        if (idx >= 0)
            exports.scene[idx] = obj;
    }
    saveState();
}
};

// ── module: src/hitTest.ts ──
__mods["src/hitTest.ts"] = function (exports, require, module) {
"use strict";
// ===== Hit Testing =====
Object.defineProperty(exports, "__esModule", { value: true });
exports.screenToWorld = screenToWorld;
exports.worldToScreen = worldToScreen;
exports.hitTestObject = hitTestObject;
exports.hitTestAll = hitTestAll;
exports.getSelectionBounds = getSelectionBounds;
exports.hitTestHandle = hitTestHandle;
exports.rubberBandSelect = rubberBandSelect;
const constants_1 = require("./constants");
const state_1 = require("./state");
// Transform screen coordinates to world coordinates
function screenToWorld(sx, sy) {
    return {
        x: (sx - state_1.viewport.x) / state_1.viewport.zoom,
        y: (sy - state_1.viewport.y) / state_1.viewport.zoom,
    };
}
// Transform world coordinates to screen coordinates
function worldToScreen(wx, wy) {
    return {
        x: wx * state_1.viewport.zoom + state_1.viewport.x,
        y: wy * state_1.viewport.zoom + state_1.viewport.y,
    };
}
// --- Point-to-segment distance ---
function pointToSegDist(px, py, ax, ay, bx, by) {
    const dx = bx - ax, dy = by - ay;
    const lenSq = dx * dx + dy * dy;
    if (lenSq === 0)
        return Math.hypot(px - ax, py - ay);
    let t = ((px - ax) * dx + (py - ay) * dy) / lenSq;
    t = Math.max(0, Math.min(1, t));
    return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
}
// --- Hit test a single object ---
function hitTestObject(obj, wx, wy) {
    const tol = constants_1.HIT_TOLERANCE / state_1.viewport.zoom;
    switch (obj.type) {
        case 'rect': {
            // Check if point is inside rect (filled) or near border
            const x = Math.min(obj.x, obj.x + obj.w);
            const y = Math.min(obj.y, obj.y + obj.h);
            const bw = Math.abs(obj.w);
            const bh = Math.abs(obj.h);
            const inside = wx >= x - tol && wx <= x + bw + tol && wy >= y - tol && wy <= y + bh + tol;
            if (inside)
                return true;
            // Border proximity
            const borderDist = Math.min(pointToSegDist(wx, wy, x, y, x + bw, y), pointToSegDist(wx, wy, x + bw, y, x + bw, y + bh), pointToSegDist(wx, wy, x + bw, y + bh, x, y + bh), pointToSegDist(wx, wy, x, y + bh, x, y));
            return borderDist <= tol + obj.strokeWidth / 2;
        }
        case 'ellipse': {
            // Point inside ellipse: ((x-cx)/rx)^2 + ((y-cy)/ry)^2 <= 1
            const rx = Math.abs(obj.rx) || 0.01;
            const ry = Math.abs(obj.ry) || 0.01;
            const dx = (wx - obj.cx) / rx;
            const dy = (wy - obj.cy) / ry;
            const inside = (dx * dx + dy * dy) <= 1 + (tol / Math.max(rx, ry));
            if (inside)
                return true;
            // Border check
            const dist = Math.sqrt(dx * dx + dy * dy) - 1;
            return dist * Math.max(rx, ry) <= tol + obj.strokeWidth / 2;
        }
        case 'line':
        case 'arrow': {
            return pointToSegDist(wx, wy, obj.x1, obj.y1, obj.x2, obj.y2) <= tol + obj.strokeWidth / 2;
        }
        case 'freehand': {
            if (!obj.points || obj.points.length < 2)
                return false;
            for (let i = 1; i < obj.points.length; i++) {
                if (pointToSegDist(wx, wy, obj.points[i - 1].x, obj.points[i - 1].y, obj.points[i].x, obj.points[i].y) <= tol + obj.strokeWidth / 2) {
                    return true;
                }
            }
            return false;
        }
        case 'text': {
            const fs = obj.fontSize || 20;
            // Approximate text bounding box
            const textW = (obj.text || '').length * fs * 0.55;
            const textH = fs * 1.2;
            return wx >= obj.x - tol && wx <= obj.x + textW + tol && wy >= obj.y - textH - tol && wy <= obj.y + tol;
        }
        default:
            return false;
    }
}
// --- Hit test all objects (top-to-bottom order) ---
function hitTestAll(objs, wx, wy) {
    // Test in reverse order (topmost first)
    for (let i = objs.length - 1; i >= 0; i--) {
        if (hitTestObject(objs[i], wx, wy))
            return objs[i];
    }
    return null;
}
// --- Get bounding box of objects ---
function getSelectionBounds(objs) {
    if (objs.length === 0)
        return null;
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    for (const obj of objs) {
        const b = getBounds(obj);
        if (b) {
            minX = Math.min(minX, b.x);
            minY = Math.min(minY, b.y);
            maxX = Math.max(maxX, b.x + b.w);
            maxY = Math.max(maxY, b.y + b.h);
        }
    }
    if (minX > maxX)
        return null;
    return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
}
function getBounds(obj) {
    switch (obj.type) {
        case 'rect':
            return {
                x: obj.w >= 0 ? obj.x : obj.x + obj.w,
                y: obj.h >= 0 ? obj.y : obj.y + obj.h,
                w: Math.abs(obj.w),
                h: Math.abs(obj.h),
            };
        case 'ellipse':
            return { x: obj.cx - Math.abs(obj.rx), y: obj.cy - Math.abs(obj.ry), w: Math.abs(obj.rx) * 2, h: Math.abs(obj.ry) * 2 };
        case 'line':
        case 'arrow':
            return {
                x: Math.min(obj.x1, obj.x2), y: Math.min(obj.y1, obj.y2),
                w: Math.abs(obj.x2 - obj.x1), h: Math.abs(obj.y2 - obj.y1),
            };
        case 'freehand': {
            if (!obj.points || obj.points.length === 0)
                return null;
            let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
            for (const p of obj.points) {
                minX = Math.min(minX, p.x);
                minY = Math.min(minY, p.y);
                maxX = Math.max(maxX, p.x);
                maxY = Math.max(maxY, p.y);
            }
            return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
        }
        case 'text': {
            const fs = obj.fontSize || 20;
            return { x: obj.x, y: obj.y - fs * 1.2, w: (obj.text || '').length * fs * 0.55, h: fs * 1.2 };
        }
    }
}
function hitTestHandle(objs, sx, sy) {
    const bounds = getSelectionBounds(objs);
    if (!bounds)
        return null;
    const hs = constants_1.HANDLE_SIZE / state_1.viewport.zoom;
    const handles = [
        { h: 'nw', wx: bounds.x, wy: bounds.y },
        { h: 'ne', wx: bounds.x + bounds.w, wy: bounds.y },
        { h: 'sw', wx: bounds.x, wy: bounds.y + bounds.h },
        { h: 'se', wx: bounds.x + bounds.w, wy: bounds.y + bounds.h },
    ];
    const world = screenToWorld(sx, sy);
    for (const { h, wx, wy } of handles) {
        if (Math.abs(world.x - wx) <= hs && Math.abs(world.y - wy) <= hs) {
            // Return the first selected object (for single-object, this is the object itself)
            return { obj: objs[0], handle: h };
        }
    }
    return null;
}
// --- Rubber-band selection ---
function rubberBandSelect(objects, start, end) {
    const sx = Math.min(start.x, end.x);
    const sy = Math.min(start.y, end.y);
    const sw = Math.abs(end.x - start.x);
    const sh = Math.abs(end.y - start.y);
    const result = [];
    for (const obj of objects) {
        const b = getBounds(obj);
        if (!b)
            continue;
        // Check if bounding box overlaps with rubber band
        if (b.x + b.w > sx && b.x < sx + sw && b.y + b.h > sy && b.y < sy + sh) {
            result.push(obj.id);
        }
    }
    return result;
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
// ===== Renderer: draws grid, shapes, selection, previews =====
Object.defineProperty(exports, "__esModule", { value: true });
exports.initRenderer = initRenderer;
exports.resize = resize;
exports.drawPreview = drawPreview;
exports.render = render;
const constants_1 = require("./constants");
const state_1 = require("./state");
const hitTest_1 = require("./hitTest");
let canvas;
let ctx;
function initRenderer(c) {
    canvas = c;
    ctx = c.getContext('2d');
    resize();
}
function resize() {
    const dpr = window.devicePixelRatio || 1;
    canvas.width = canvas.clientWidth * dpr;
    canvas.height = canvas.clientHeight * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
// --- Grid ---
function drawGrid() {
    const gridSize = 24;
    // Only draw grid at reasonable zoom levels (too dense below 0.5, too sparse above 3)
    if (state_1.viewport.zoom < 0.3 || state_1.viewport.zoom > 3)
        return;
    // Calculate visible world bounds
    const topLeft = { x: -state_1.viewport.x / state_1.viewport.zoom, y: -state_1.viewport.y / state_1.viewport.zoom };
    const botRight = {
        x: (canvas.clientWidth - state_1.viewport.x) / state_1.viewport.zoom,
        y: (canvas.clientHeight - state_1.viewport.y) / state_1.viewport.zoom,
    };
    // Snap to grid
    const startX = Math.floor(topLeft.x / gridSize) * gridSize;
    const startY = Math.floor(topLeft.y / gridSize) * gridSize;
    // Estimate number of dots - skip if too many
    const estCols = Math.ceil((botRight.x - topLeft.x) / gridSize);
    const estRows = Math.ceil((botRight.y - topLeft.y) / gridSize);
    if (estCols * estRows > 20000)
        return; // bail out to avoid perf hit
    ctx.save();
    ctx.fillStyle = constants_1.COLORS.gridDot;
    ctx.lineWidth = 1;
    const dotRadius = Math.max(1, Math.min(2, 1.5 / state_1.viewport.zoom));
    for (let x = startX; x <= botRight.x; x += gridSize) {
        for (let y = startY; y <= botRight.y; y += gridSize) {
            const screen = (0, hitTest_1.worldToScreen)(x, y);
            // Only draw if in viewport
            if (screen.x < 0 || screen.x > canvas.clientWidth || screen.y < 0 || screen.y > canvas.clientHeight)
                continue;
            ctx.beginPath();
            ctx.arc(screen.x, screen.y, dotRadius, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    ctx.restore();
}
// --- Shape Rendering ---
function drawRect(obj) {
    if (obj.type !== 'rect')
        return;
    const { x, y, w, h, strokeColor, fillColor, strokeWidth } = obj;
    ctx.lineWidth = strokeWidth;
    ctx.strokeStyle = strokeColor;
    if (fillColor && fillColor !== 'none') {
        ctx.fillStyle = fillColor;
        ctx.fillRect(x, y, w, h);
    }
    ctx.strokeRect(x, y, w, h);
}
function drawEllipse(obj) {
    if (obj.type !== 'ellipse')
        return;
    const { cx, cy, rx, ry, strokeColor, fillColor, strokeWidth } = obj;
    ctx.lineWidth = strokeWidth;
    ctx.strokeStyle = strokeColor;
    ctx.beginPath();
    ctx.ellipse(cx, cy, Math.abs(rx), Math.abs(ry), 0, 0, Math.PI * 2);
    if (fillColor && fillColor !== 'none') {
        ctx.fillStyle = fillColor;
        ctx.fill();
    }
    ctx.stroke();
}
function drawLine(obj) {
    if (obj.type !== 'line' && obj.type !== 'arrow')
        return;
    const { x1, y1, x2, y2, strokeColor, strokeWidth } = obj;
    ctx.lineWidth = strokeWidth;
    ctx.strokeStyle = strokeColor;
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
    // Arrowhead
    if (obj.type === 'arrow') {
        const angle = Math.atan2(y2 - y1, x2 - x1);
        const headLen = 12;
        ctx.fillStyle = strokeColor;
        ctx.beginPath();
        ctx.moveTo(x2, y2);
        ctx.lineTo(x2 - headLen * Math.cos(angle - Math.PI / 6), y2 - headLen * Math.sin(angle - Math.PI / 6));
        ctx.lineTo(x2 - headLen * Math.cos(angle + Math.PI / 6), y2 - headLen * Math.sin(angle + Math.PI / 6));
        ctx.closePath();
        ctx.fill();
    }
}
function drawFreehand(obj) {
    if (obj.type !== 'freehand' || !obj.points || obj.points.length < 2)
        return;
    const { points, strokeColor, strokeWidth } = obj;
    ctx.lineWidth = strokeWidth;
    ctx.strokeStyle = strokeColor;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);
    // Simple smoothing: use quadratic curves through midpoints
    if (points.length === 2) {
        ctx.lineTo(points[1].x, points[1].y);
    }
    else {
        for (let i = 1; i < points.length - 1; i++) {
            const mx = (points[i].x + points[i + 1].x) / 2;
            const my = (points[i].y + points[i + 1].y) / 2;
            ctx.quadraticCurveTo(points[i].x, points[i].y, mx, my);
        }
        const last = points[points.length - 1];
        const prev = points[points.length - 2];
        const mx = (prev.x + last.x) / 2;
        const my = (prev.y + last.y) / 2;
        ctx.quadraticCurveTo(prev.x, prev.y, last.x, last.y);
    }
    ctx.stroke();
}
function drawText(obj) {
    if (obj.type !== 'text')
        return;
    const { x, y, text, strokeColor, fontSize } = obj;
    ctx.font = `${fontSize}px "Inter", "Segoe UI", system-ui, sans-serif`;
    ctx.fillStyle = strokeColor;
    ctx.textBaseline = 'alphabetic';
    ctx.fillText(text || '', x, y);
}
// --- Selection Rendering ---
function drawSelection() {
    const selected = state_1.scene.filter(o => state_1.selectedIds.includes(o.id));
    if (selected.length === 0)
        return;
    // Compute bounds
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    for (const obj of selected) {
        // Quick bounding
        const pad = obj.strokeWidth / 2;
        switch (obj.type) {
            case 'rect': {
                const x = Math.min(obj.x, obj.x + obj.w);
                const y = Math.min(obj.y, obj.y + obj.h);
                minX = Math.min(minX, x - pad);
                minY = Math.min(minY, y - pad);
                maxX = Math.max(maxX, x + Math.abs(obj.w) + pad);
                maxY = Math.max(maxY, y + Math.abs(obj.h) + pad);
                break;
            }
            case 'ellipse': {
                minX = Math.min(minX, obj.cx - Math.abs(obj.rx) - pad);
                minY = Math.min(minY, obj.cy - Math.abs(obj.ry) - pad);
                maxX = Math.max(maxX, obj.cx + Math.abs(obj.rx) + pad);
                maxY = Math.max(maxY, obj.cy + Math.abs(obj.ry) + pad);
                break;
            }
            case 'line':
            case 'arrow': {
                minX = Math.min(minX, obj.x1 - pad, obj.x2 - pad);
                minY = Math.min(minY, obj.y1 - pad, obj.y2 - pad);
                maxX = Math.max(maxX, obj.x1 + pad, obj.x2 + pad);
                maxY = Math.max(maxY, obj.y1 + pad, obj.y2 + pad);
                break;
            }
            case 'freehand': {
                if (obj.points) {
                    for (const p of obj.points) {
                        minX = Math.min(minX, p.x - pad);
                        minY = Math.min(minY, p.y - pad);
                        maxX = Math.max(maxX, p.x + pad);
                        maxY = Math.max(maxY, p.y + pad);
                    }
                }
                break;
            }
            case 'text': {
                const fs = obj.fontSize || 20;
                const tw = (obj.text || '').length * fs * 0.55;
                minX = Math.min(minX, obj.x - pad);
                minY = Math.min(minY, obj.y - fs * 1.2 - pad);
                maxX = Math.max(maxX, obj.x + tw + pad);
                maxY = Math.max(maxY, obj.y + pad);
                break;
            }
        }
    }
    if (minX === Infinity)
        return;
    // Draw bounding box
    const bw = 2; // screen-space border width
    ctx.strokeStyle = constants_1.COLORS.selectionBox;
    ctx.lineWidth = bw;
    ctx.setLineDash([4, 4]);
    ctx.strokeRect(minX, minY, maxX - minX, maxY - minY);
    ctx.setLineDash([]);
    // Draw handles
    const hs = 6 / state_1.viewport.zoom; // handle half-size in world coords
    const handleCorners = [
        { x: minX, y: minY },
        { x: maxX, y: minY },
        { x: minX, y: maxY },
        { x: maxX, y: maxY },
    ];
    for (const c of handleCorners) {
        ctx.fillStyle = constants_1.COLORS.selectionHandle;
        ctx.strokeStyle = constants_1.COLORS.selectionBox;
        ctx.lineWidth = 1.5;
        ctx.fillRect(c.x - hs, c.y - hs, hs * 2, hs * 2);
        ctx.strokeRect(c.x - hs, c.y - hs, hs * 2, hs * 2);
    }
}
function drawPreview(preview) {
    if (!preview)
        return;
    const alpha = 0.5;
    switch (preview.type) {
        case 'rect': {
            ctx.globalAlpha = alpha;
            ctx.strokeStyle = constants_1.COLORS.previewShape;
            ctx.lineWidth = 2;
            if (preview.w && preview.h) {
                ctx.strokeRect(preview.x, preview.y, preview.w, preview.h);
            }
            ctx.globalAlpha = 1;
            break;
        }
        case 'ellipse': {
            ctx.globalAlpha = alpha;
            ctx.strokeStyle = constants_1.COLORS.previewShape;
            ctx.lineWidth = 2;
            ctx.beginPath();
            ctx.ellipse(preview.cx, preview.cy, Math.abs(preview.rx), Math.abs(preview.ry), 0, 0, Math.PI * 2);
            ctx.stroke();
            ctx.globalAlpha = 1;
            break;
        }
        case 'line':
        case 'arrow': {
            ctx.globalAlpha = alpha;
            ctx.strokeStyle = constants_1.COLORS.previewShape;
            ctx.lineWidth = 2;
            ctx.lineCap = 'round';
            ctx.beginPath();
            ctx.moveTo(preview.x1, preview.y1);
            ctx.lineTo(preview.x2, preview.y2);
            ctx.stroke();
            ctx.globalAlpha = 1;
            break;
        }
        case 'freehand': {
            if (!preview.points || preview.points.length < 2)
                return;
            ctx.strokeStyle = constants_1.COLORS.previewShape;
            ctx.lineWidth = 2;
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            ctx.beginPath();
            ctx.moveTo(preview.points[0].x, preview.points[0].y);
            for (let i = 1; i < preview.points.length; i++) {
                ctx.lineTo(preview.points[i].x, preview.points[i].y);
            }
            ctx.stroke();
            break;
        }
        case 'rubberband': {
            ctx.globalAlpha = 0.1;
            ctx.fillStyle = constants_1.COLORS.rubberBand;
            ctx.fillRect(preview.x, preview.y, preview.w, preview.h);
            ctx.globalAlpha = 1;
            ctx.strokeStyle = constants_1.COLORS.rubberBandBorder;
            ctx.lineWidth = 1;
            ctx.setLineDash([4, 4]);
            ctx.strokeRect(preview.x, preview.y, preview.w, preview.h);
            ctx.setLineDash([]);
            break;
        }
        case 'text': {
            ctx.font = '20px "Inter", "Segoe UI", system-ui, sans-serif';
            ctx.fillStyle = constants_1.COLORS.previewShape;
            ctx.globalAlpha = alpha;
            ctx.fillText(preview.text || 'Type here...', preview.x, preview.y);
            ctx.globalAlpha = 1;
            break;
        }
    }
}
// --- Main render ---
function render(preview = null) {
    // Clear
    ctx.fillStyle = constants_1.COLORS.canvasBg;
    ctx.fillRect(0, 0, canvas.clientWidth, canvas.clientHeight);
    // Draw grid
    drawGrid();
    // Apply viewport transform
    ctx.save();
    ctx.translate(state_1.viewport.x, state_1.viewport.y);
    ctx.scale(state_1.viewport.zoom, state_1.viewport.zoom);
    // Draw all objects
    for (const obj of state_1.scene) {
        switch (obj.type) {
            case 'rect':
                drawRect(obj);
                break;
            case 'ellipse':
                drawEllipse(obj);
                break;
            case 'line':
            case 'arrow':
                drawLine(obj);
                break;
            case 'freehand':
                drawFreehand(obj);
                break;
            case 'text':
                drawText(obj);
                break;
        }
    }
    ctx.restore();
    // Draw preview (in world coords)
    ctx.save();
    ctx.translate(state_1.viewport.x, state_1.viewport.y);
    ctx.scale(state_1.viewport.zoom, state_1.viewport.zoom);
    drawPreview(preview);
    ctx.restore();
    // Draw selection (in world coords)
    ctx.save();
    ctx.translate(state_1.viewport.x, state_1.viewport.y);
    ctx.scale(state_1.viewport.zoom, state_1.viewport.zoom);
    drawSelection();
    ctx.restore();
}
};

// ── module: src/interactions.ts ──
__mods["src/interactions.ts"] = function (exports, require, module) {
"use strict";
// ===== Interactions: input handling for all tools =====
Object.defineProperty(exports, "__esModule", { value: true });
exports.initInteractions = initInteractions;
exports.getPreview = getPreview;
exports.setTool = setTool;
exports.getCurrentTool = getCurrentTool;
exports.updateZoomIndicator = updateZoomIndicator;
exports.resetView = resetView;
const constants_1 = require("./constants");
const state_1 = require("./state");
const hitTest_1 = require("./hitTest");
const renderer_1 = require("./renderer");
let canvas;
let preview = null;
// --- Deep clone helper ---
function cloneObj(o) {
    if (o.type === 'freehand') {
        return { ...o, points: o.points.map(p => ({ ...p })) };
    }
    return { ...o };
}
function cloneSelected() {
    return (0, state_1.getSelectedObjects)().map(cloneObj);
}
// Interaction state
let isPanning = false;
let panStart = { x: 0, y: 0 };
let spaceHeld = false;
// Drawing state
let isDrawing = false;
let drawStart = { x: 0, y: 0 };
let freehandPoints = [];
// Selection/move state
let isDragging = false;
let dragStartWorld = { x: 0, y: 0 };
let dragSnapshotBefore = [];
// Resize state
let isResizing = false;
let resizeHandle = 'se';
let resizeStartWorld = { x: 0, y: 0 };
let resizeSnapshotBefore = [];
// Rubber-band state
let isRubberBand = false;
let rubberStart = { x: 0, y: 0 };
// Eraser
let erasing = false;
function initInteractions(c) {
    canvas = c;
    canvas.addEventListener('mousedown', onMouseDown);
    canvas.addEventListener('mousemove', onMouseMove);
    canvas.addEventListener('mouseup', onMouseUp);
    canvas.addEventListener('mouseleave', onMouseUp);
    canvas.addEventListener('wheel', onWheel, { passive: false });
    canvas.addEventListener('contextmenu', e => e.preventDefault());
    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
}
function getPreview() {
    return preview;
}
function getMouseWorld(e) {
    const rect = canvas.getBoundingClientRect();
    return (0, hitTest_1.screenToWorld)(e.clientX - rect.left, e.clientY - rect.top);
}
function getMouseScreen(e) {
    const rect = canvas.getBoundingClientRect();
    return { x: e.clientX - rect.left, y: e.clientY - rect.top };
}
// ===== Mouse Events =====
function onMouseDown(e) {
    if (textInputActive)
        return;
    const world = getMouseWorld(e);
    const screen = getMouseScreen(e);
    const shift = e.shiftKey;
    // Middle mouse or space+left = pan
    if (e.button === 1 || (e.button === 0 && spaceHeld)) {
        isPanning = true;
        panStart = { x: e.clientX - state_1.viewport.x, y: e.clientY - state_1.viewport.y };
        canvas.style.cursor = 'grabbing';
        e.preventDefault();
        return;
    }
    if (e.button !== 0)
        return;
    switch (currentTool) {
        case 'select':
            handleSelectDown(world, screen, shift);
            break;
        case 'pen':
            handlePenDown(world);
            break;
        case 'rect':
        case 'ellipse':
        case 'line':
            handleShapeDown(world);
            break;
        case 'text':
            handleTextDown(world);
            break;
        case 'eraser':
            handleEraserDown(world);
            break;
    }
}
let currentTool = 'select';
function handleSelectDown(world, screen, shift) {
    const selected = (0, state_1.getSelectedObjects)();
    // Check handles first
    if (selected.length > 0) {
        const handleHit = (0, hitTest_1.hitTestHandle)(selected, screen.x, screen.y);
        if (handleHit) {
            isResizing = true;
            resizeHandle = handleHit.handle;
            resizeStartWorld = world;
            resizeSnapshotBefore = cloneSelected();
            return;
        }
    }
    // Hit test objects
    const hit = (0, hitTest_1.hitTestAll)(state_1.scene, world.x, world.y);
    if (hit) {
        (0, state_1.selectObject)(hit.id, shift);
        isDragging = true;
        dragStartWorld = world;
        dragSnapshotBefore = cloneSelected();
        return;
    }
    // Click empty space
    if (!shift)
        (0, state_1.deselectAll)();
    isRubberBand = true;
    rubberStart = world;
}
function handlePenDown(world) {
    isDrawing = true;
    freehandPoints = [world];
}
function handleShapeDown(world) {
    isDrawing = true;
    drawStart = world;
}
function handleTextDown(world) {
    const screen = {
        x: world.x * state_1.viewport.zoom + state_1.viewport.x,
        y: world.y * state_1.viewport.zoom + state_1.viewport.y,
    };
    showTextInput(screen.x, screen.y, world.x, world.y);
}
function handleEraserDown(world) {
    erasing = true;
    eraseAt(world);
}
function eraseAt(world) {
    const hit = (0, hitTest_1.hitTestAll)(state_1.scene, world.x, world.y);
    if (hit) {
        const idx = state_1.scene.findIndex(o => o.id === hit.id);
        if (idx >= 0) {
            const obj = state_1.scene[idx];
            const before = [cloneObj(obj)];
            state_1.scene.splice(idx, 1);
            state_1.selectedIds.splice(state_1.selectedIds.indexOf(obj.id), 1);
            (0, state_1.pushHistory)({ action: 'delete', objectIds: [obj.id], before, after: [] });
            (0, state_1.saveState)();
        }
    }
}
function onMouseMove(e) {
    if (textInputActive)
        return;
    const world = getMouseWorld(e);
    const screen = getMouseScreen(e);
    // Cursor updates (when not in an action)
    if (!isPanning && !isDrawing && !isDragging && !isResizing && !isRubberBand) {
        updateCursorFromState(world, screen);
    }
    if (isPanning) {
        state_1.viewport.x = e.clientX - panStart.x;
        state_1.viewport.y = e.clientY - panStart.y;
        (0, renderer_1.render)(preview);
        updateZoomIndicator();
        return;
    }
    if (currentTool === 'eraser' && erasing) {
        eraseAt(world);
        (0, renderer_1.render)(preview);
        return;
    }
    switch (currentTool) {
        case 'select':
            handleSelectMove(world);
            break;
        case 'pen':
            handlePenMove(world);
            break;
        case 'rect':
        case 'ellipse':
        case 'line':
            handleShapeMove(world);
            break;
    }
}
function handleSelectMove(world) {
    if (isDragging) {
        const dx = world.x - dragStartWorld.x;
        const dy = world.y - dragStartWorld.y;
        if (Math.abs(dx) > 0.1 || Math.abs(dy) > 0.1) {
            moveObjectsInPlace((0, state_1.getSelectedObjects)(), dx, dy);
            (0, renderer_1.render)(preview);
        }
        return;
    }
    if (isResizing) {
        const dx = world.x - resizeStartWorld.x;
        const dy = world.y - resizeStartWorld.y;
        resizeSelected(dx, dy);
        (0, renderer_1.render)(preview);
        return;
    }
    if (isRubberBand) {
        const sx = Math.min(rubberStart.x, world.x);
        const sy = Math.min(rubberStart.y, world.y);
        preview = { type: 'rubberband', x: sx, y: sy, w: Math.abs(world.x - rubberStart.x), h: Math.abs(world.y - rubberStart.y) };
        (0, renderer_1.render)(preview);
    }
}
function handlePenMove(world) {
    if (!isDrawing || freehandPoints.length === 0)
        return;
    const last = freehandPoints[freehandPoints.length - 1];
    if (Math.hypot(world.x - last.x, world.y - last.y) > 1 / state_1.viewport.zoom) {
        freehandPoints.push(world);
    }
    preview = { type: 'freehand', points: [...freehandPoints] };
    (0, renderer_1.render)(preview);
}
function handleShapeMove(world) {
    if (!isDrawing)
        return;
    const dx = world.x - drawStart.x;
    const dy = world.y - drawStart.y;
    switch (currentTool) {
        case 'rect':
            preview = { type: 'rect', x: dx >= 0 ? drawStart.x : world.x, y: dy >= 0 ? drawStart.y : world.y, w: Math.abs(dx), h: Math.abs(dy) };
            break;
        case 'ellipse':
            preview = { type: 'ellipse', cx: drawStart.x + dx / 2, cy: drawStart.y + dy / 2, rx: dx / 2, ry: dy / 2 };
            break;
        case 'line':
            preview = { type: 'line', x1: drawStart.x, y1: drawStart.y, x2: world.x, y2: world.y };
            break;
    }
    (0, renderer_1.render)(preview);
}
function onMouseUp(e) {
    if (textInputActive)
        return;
    const world = getMouseWorld(e);
    if (isPanning) {
        isPanning = false;
        updateCursorFromState(world, { x: 0, y: 0 });
        (0, renderer_1.render)(preview);
        return;
    }
    if (currentTool === 'eraser') {
        erasing = false;
        (0, renderer_1.render)(preview);
        return;
    }
    switch (currentTool) {
        case 'select':
            handleSelectUp(world);
            break;
        case 'pen':
            handlePenUp();
            break;
        case 'rect':
        case 'ellipse':
        case 'line':
            handleShapeUp(world);
            break;
    }
}
function handleSelectUp(world) {
    if (isDragging) {
        isDragging = false;
        const after = cloneSelected();
        (0, state_1.moveObjects)(dragSnapshotBefore, after);
        dragSnapshotBefore = [];
    }
    if (isResizing) {
        isResizing = false;
        const after = cloneSelected();
        (0, state_1.modifyObjects)(resizeSnapshotBefore, after);
        resizeSnapshotBefore = [];
    }
    if (isRubberBand) {
        isRubberBand = false;
        const ids = (0, hitTest_1.rubberBandSelect)(state_1.scene, rubberStart, world);
        state_1.selectedIds.length = 0;
        state_1.selectedIds.push(...ids);
        preview = null;
        (0, renderer_1.render)(null);
    }
}
function handlePenUp() {
    if (!isDrawing || freehandPoints.length < 2) {
        isDrawing = false;
        freehandPoints = [];
        preview = null;
        (0, renderer_1.render)(null);
        return;
    }
    const obj = {
        id: generateId(),
        type: 'freehand',
        strokeColor: (0, state_1.getStrokeColor)(),
        fillColor: 'none',
        strokeWidth: (0, state_1.getStrokeWidth)(),
        points: [...freehandPoints],
    };
    state_1.scene.push(obj);
    (0, state_1.pushHistory)({ action: 'create', objectIds: [obj.id], before: [], after: [cloneObj(obj)] });
    (0, state_1.saveState)();
    isDrawing = false;
    freehandPoints = [];
    preview = null;
    (0, renderer_1.render)(null);
}
function handleShapeUp(world) {
    if (!isDrawing)
        return;
    isDrawing = false;
    preview = null;
    const dx = world.x - drawStart.x;
    const dy = world.y - drawStart.y;
    if (Math.hypot(dx, dy) < 2 / state_1.viewport.zoom) {
        (0, renderer_1.render)(null);
        return;
    }
    let obj;
    switch (currentTool) {
        case 'rect':
            obj = { id: generateId(), type: 'rect', x: dx >= 0 ? drawStart.x : world.x, y: dy >= 0 ? drawStart.y : world.y, w: Math.abs(dx), h: Math.abs(dy), strokeColor: (0, state_1.getStrokeColor)(), fillColor: (0, state_1.getFillColor)(), strokeWidth: (0, state_1.getStrokeWidth)() };
            break;
        case 'ellipse':
            obj = { id: generateId(), type: 'ellipse', cx: drawStart.x + dx / 2, cy: drawStart.y + dy / 2, rx: dx / 2, ry: dy / 2, strokeColor: (0, state_1.getStrokeColor)(), fillColor: (0, state_1.getFillColor)(), strokeWidth: (0, state_1.getStrokeWidth)() };
            break;
        case 'line':
            obj = { id: generateId(), type: 'line', x1: drawStart.x, y1: drawStart.y, x2: world.x, y2: world.y, strokeColor: (0, state_1.getStrokeColor)(), fillColor: 'none', strokeWidth: (0, state_1.getStrokeWidth)() };
            break;
        default:
            (0, renderer_1.render)(null);
            return;
    }
    state_1.scene.push(obj);
    (0, state_1.pushHistory)({ action: 'create', objectIds: [obj.id], before: [], after: [cloneObj(obj)] });
    (0, state_1.saveState)();
    (0, renderer_1.render)(null);
}
// ===== Wheel =====
let zoomRaf = 0;
function onWheel(e) {
    if (e.cancelable)
        e.preventDefault();
    const rect = canvas.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const my = e.clientY - rect.top;
    const oldZoom = state_1.viewport.zoom;
    // Scale zoom by delta magnitude; deltaMode 1 = lines (~100)
    const delta = e.deltaY * 0.002; // tune: ~200 delta → factor ~1.45
    const factor = Math.exp(-delta);
    let newZoom = oldZoom * factor;
    newZoom = Math.max(constants_1.ZOOM_MIN, Math.min(constants_1.ZOOM_MAX, newZoom));
    // Zoom toward cursor
    const worldX = (mx - state_1.viewport.x) / oldZoom;
    const worldY = (my - state_1.viewport.y) / oldZoom;
    state_1.viewport.x = mx - worldX * newZoom;
    state_1.viewport.y = my - worldY * newZoom;
    state_1.viewport.zoom = newZoom;
    (0, renderer_1.render)(preview);
    updateZoomIndicator();
}
// ===== Keyboard =====
function onKeyDown(e) {
    if (textInputActive)
        return;
    if (e.code === 'Space' && !e.repeat) {
        spaceHeld = true;
        canvas.style.cursor = 'grab';
        e.preventDefault();
    }
    if (e.key === 'Delete' || e.key === 'Backspace') {
        if (state_1.selectedIds.length > 0) {
            (0, state_1.removeSelected)();
            (0, renderer_1.render)(null);
            e.preventDefault();
        }
    }
    if (e.ctrlKey || e.metaKey) {
        if (e.key === 'z' && !e.shiftKey) {
            (0, state_1.undo)();
            (0, renderer_1.render)(null);
            e.preventDefault();
        }
        if ((e.key === 'z' && e.shiftKey) || e.key === 'y') {
            (0, state_1.redo)();
            (0, renderer_1.render)(null);
            e.preventDefault();
        }
        return;
    }
    // Nudge
    if (state_1.selectedIds.length > 0 && !e.ctrlKey && !e.metaKey) {
        const amount = e.shiftKey ? constants_1.NUDGE_SHIFT_AMOUNT : constants_1.NUDGE_AMOUNT;
        let dx = 0, dy = 0;
        switch (e.key) {
            case 'ArrowLeft':
                dx = -amount;
                break;
            case 'ArrowRight':
                dx = amount;
                break;
            case 'ArrowUp':
                dy = -amount;
                break;
            case 'ArrowDown':
                dy = amount;
                break;
        }
        if (dx !== 0 || dy !== 0) {
            const selected = (0, state_1.getSelectedObjects)();
            const before = cloneSelected();
            moveObjectsInPlace(selected, dx, dy);
            (0, state_1.moveObjects)(before, cloneSelected());
            (0, renderer_1.render)(preview);
            e.preventDefault();
        }
    }
    // Tool shortcuts
    if (!e.ctrlKey && !e.metaKey && e.key.length === 1) {
        const k = e.key.toLowerCase();
        const toolMap = {
            v: 'select', p: 'pen', r: 'rect', o: 'ellipse', l: 'line', t: 'text', e: 'eraser',
        };
        if (toolMap[k]) {
            const newTool = toolMap[k];
            if (newTool !== currentTool) {
                setTool(newTool);
            }
        }
    }
}
function onKeyUp(e) {
    if (e.code === 'Space') {
        spaceHeld = false;
        updateCursor();
        (0, renderer_1.render)(preview);
    }
}
// ===== Helpers =====
function moveObjectsInPlace(objs, dx, dy) {
    for (const obj of objs) {
        switch (obj.type) {
            case 'rect':
                obj.x += dx;
                obj.y += dy;
                break;
            case 'ellipse':
                obj.cx += dx;
                obj.cy += dy;
                break;
            case 'line':
            case 'arrow':
                obj.x1 += dx;
                obj.y1 += dy;
                obj.x2 += dx;
                obj.y2 += dy;
                break;
            case 'freehand':
                if (obj.points)
                    for (const p of obj.points) {
                        p.x += dx;
                        p.y += dy;
                    }
                break;
            case 'text':
                obj.x += dx;
                obj.y += dy;
                break;
        }
    }
}
function getObjectBounds(obj) {
    switch (obj.type) {
        case 'rect': return { x: Math.min(obj.x, obj.x + obj.w), y: Math.min(obj.y, obj.y + obj.h), w: Math.abs(obj.w), h: Math.abs(obj.h) };
        case 'ellipse': return { x: obj.cx - Math.abs(obj.rx), y: obj.cy - Math.abs(obj.ry), w: Math.abs(obj.rx) * 2, h: Math.abs(obj.ry) * 2 };
        case 'line':
        case 'arrow': return { x: Math.min(obj.x1, obj.x2), y: Math.min(obj.y1, obj.y2), w: Math.abs(obj.x2 - obj.x1), h: Math.abs(obj.y2 - obj.y1) };
        case 'freehand': {
            if (!obj.points || obj.points.length === 0)
                return null;
            let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
            for (const p of obj.points) {
                minX = Math.min(minX, p.x);
                minY = Math.min(minY, p.y);
                maxX = Math.max(maxX, p.x);
                maxY = Math.max(maxY, p.y);
            }
            return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
        }
        case 'text': {
            const fs = obj.fontSize || 20;
            return { x: obj.x, y: obj.y - fs * 1.2, w: (obj.text || '').length * fs * 0.55, h: fs * 1.2 };
        }
    }
}
function computeMultiBounds(objs) {
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    for (const obj of objs) {
        const b = getObjectBounds(obj);
        if (b) {
            minX = Math.min(minX, b.x);
            minY = Math.min(minY, b.y);
            maxX = Math.max(maxX, b.x + b.w);
            maxY = Math.max(maxY, b.y + b.h);
        }
    }
    if (minX > maxX)
        return null;
    return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
}
function resizeSelected(dx, dy) {
    const selected = (0, state_1.getSelectedObjects)();
    if (selected.length === 0)
        return;
    const bounds = computeMultiBounds(selected);
    if (!bounds || bounds.w === 0 || bounds.h === 0)
        return;
    // Determine the anchor corner (opposite of handle)
    const originX = resizeHandle.includes('w') ? bounds.x : bounds.x + bounds.w;
    const originY = resizeHandle.includes('n') ? bounds.y : bounds.y + bounds.h;
    // New dimension from drag
    const newW = bounds.w + (resizeHandle.includes('w') ? -dx : dx);
    const newH = bounds.h + (resizeHandle.includes('n') ? -dy : dy);
    const scaleX = newW / bounds.w;
    const scaleY = newH / bounds.h;
    for (const obj of selected) {
        const ob = getObjectBounds(obj);
        if (!ob)
            continue;
        // Relative position from origin
        const relLeft = (ob.x - originX) / bounds.w;
        const relTop = (ob.y - originY) / bounds.h;
        const newOx = resizeHandle.includes('w') ? originX + newW : originX;
        const newOy = resizeHandle.includes('n') ? originY + newH : originY;
        const newX = originX + relLeft * newW;
        const newY = originY + relTop * newH;
        const newObW = ob.w * scaleX;
        const newObH = ob.h * scaleY;
        switch (obj.type) {
            case 'rect':
                obj.x = newX;
                obj.y = newY;
                obj.w = newObW;
                obj.h = newObH;
                break;
            case 'ellipse':
                obj.rx = (newObW / 2) * Math.sign(obj.rx || 1);
                obj.ry = (newObH / 2) * Math.sign(obj.ry || 1);
                obj.cx = newX + Math.abs(obj.rx);
                obj.cy = newY + Math.abs(obj.ry);
                break;
            case 'line':
            case 'arrow': {
                const rX1 = (obj.x1 - originX) / bounds.w;
                const rY1 = (obj.y1 - originY) / bounds.h;
                const rX2 = (obj.x2 - originX) / bounds.w;
                const rY2 = (obj.y2 - originY) / bounds.h;
                obj.x1 = originX + rX1 * newW;
                obj.y1 = originY + rY1 * newH;
                obj.x2 = originX + rX2 * newW;
                obj.y2 = originY + rY2 * newH;
                break;
            }
            case 'freehand':
                if (obj.points) {
                    for (const p of obj.points) {
                        const rx = (p.x - originX) / bounds.w;
                        const ry = (p.y - originY) / bounds.h;
                        p.x = originX + rx * newW;
                        p.y = originY + ry * newH;
                    }
                }
                break;
            case 'text':
                obj.x = newX;
                obj.y = newY;
                break;
        }
    }
}
// ===== Text Input =====
let textInputActive = false;
function showTextInput(screenX, screenY, worldX, worldY) {
    textInputActive = true;
    const textarea = document.createElement('textarea');
    textarea.id = 'text-input';
    Object.assign(textarea.style, {
        position: 'fixed', left: `${screenX}px`, top: `${screenY - 20}px`,
        fontSize: '20px', fontFamily: '"Inter","Segoe UI",system-ui,sans-serif',
        border: '2px solid #2970ff', outline: 'none', padding: '4px 8px',
        minWidth: '120px', backgroundColor: '#fff', zIndex: '1000',
        color: (0, state_1.getStrokeColor)(), resize: 'none', boxSizing: 'border-box', lineHeight: '1.2',
    });
    textarea.value = '';
    document.body.appendChild(textarea);
    textarea.focus();
    // Auto-resize
    const measure = document.createElement('div');
    measure.style.cssText = 'position:absolute;visibility:hidden;font-size:20px;font-family:"Inter","Segoe UI",system-ui,sans-serif;white-space:pre;pointer-events:none;';
    document.body.appendChild(measure);
    textarea.addEventListener('input', () => {
        measure.textContent = textarea.value || 'W';
        textarea.style.width = `${measure.offsetWidth + 24}px`;
    });
    const commit = () => {
        const text = textarea.value || '';
        textarea.remove();
        measure.remove();
        textInputActive = false;
        if (text) {
            const obj = {
                id: generateId(), type: 'text', x: worldX, y: worldY,
                text, fontSize: 20, strokeColor: (0, state_1.getStrokeColor)(), fillColor: 'none', strokeWidth: 0,
            };
            state_1.scene.push(obj);
            (0, state_1.pushHistory)({ action: 'create', objectIds: [obj.id], before: [], after: [cloneObj(obj)] });
            (0, state_1.saveState)();
        }
        (0, renderer_1.render)(null);
    };
    textarea.addEventListener('blur', commit);
    textarea.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            textarea.blur();
        }
        if (e.key === 'Escape') {
            textarea.value = '';
            textarea.blur();
        }
    });
}
// ===== Tool / Cursor =====
function updateCursorFromState(world, screen) {
    if (spaceHeld) {
        canvas.style.cursor = 'grab';
        return;
    }
    switch (currentTool) {
        case 'select': {
            const selected = (0, state_1.getSelectedObjects)();
            if (selected.length > 0) {
                const h = (0, hitTest_1.hitTestHandle)(selected, screen.x, screen.y);
                if (h) {
                    canvas.style.cursor = (h.handle === 'nw' || h.handle === 'se') ? 'nwse-resize' : 'nesw-resize';
                    return;
                }
            }
            canvas.style.cursor = (0, hitTest_1.hitTestAll)(state_1.scene, world.x, world.y) ? 'pointer' : 'default';
            break;
        }
        case 'text':
            canvas.style.cursor = 'text';
            break;
        case 'eraser':
            canvas.style.cursor = 'crosshair';
            break;
        default:
            canvas.style.cursor = 'crosshair';
            break;
    }
}
function updateCursor() {
    updateCursorFromState(getMouseWorld({ clientX: 0, clientY: 0 }), { x: 0, y: 0 });
}
function setTool(tool) {
    currentTool = tool;
    updateToolButtons(tool);
    updateCursor();
    (0, renderer_1.render)(preview);
}
function getCurrentTool() {
    return currentTool;
}
function updateToolButtons(tool) {
    document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
        btn.classList.toggle('active', btn.dataset.tool === tool);
    });
}
// ===== Zoom =====
function updateZoomIndicator() {
    const el = document.getElementById('zoom-level');
    if (el)
        el.textContent = `${Math.round(state_1.viewport.zoom * 100)}%`;
}
function resetView() {
    state_1.viewport.x = canvas.clientWidth / 2;
    state_1.viewport.y = canvas.clientHeight / 2;
    state_1.viewport.zoom = 1;
    (0, renderer_1.render)(preview);
    updateZoomIndicator();
}
// ===== ID =====
let _idSeq = 0;
function generateId() {
    return `w_${++_idSeq}_${Date.now()}`;
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"9b25e508-436f-4843-97ef-5f03cca09e6b","tokensIn":3571646,"tokensOut":46121,"tokensTotal":3617767,"cost":0,"turns":68,"toolCalls":68,"failedToolCalls":0,"timestamp":"2026-08-02T07:26:17.798Z"} -->