← Kanban results

Kanban

Qwen 3.6 27b MTP · typescript

Initial view of Kanban

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 76,419 bytes · SHA-256 d5a3a23e42c7
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kanban Board</title>
<style>
/* ── css: layout ── */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  background: #f4f5f7;
  color: #172b4d;
  font-size: 14px;
  line-height: 1.5;
}

#app {
  height: 100%;
  display: flex;
  flex-direction: column;
}

#board-container {
  flex: 1;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 20px;
  scroll-behavior: smooth;
}

#toolbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 24px;
  background: #fff;
  border-bottom: 1px solid #dfe1e6;
  z-index: 10;
}

#board {
  display: flex;
  gap: 12px;
  height: calc(100vh - 57px);
  align-items: flex-start;
  padding-bottom: 20px;
}

/* Column wrapper */
.column-wrapper {
  min-width: 280px;
  max-width: 280px;
  background: #ebecf0;
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 97px);
}

.column-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 12px 8px;
  cursor: grab;
  user-select: none;
}

.column-header:active {
  cursor: grabbing;
}

.column-title {
  font-weight: 600;
  font-size: 14px;
  color: #172b4d;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-count {
  background: rgba(0,0,0,0.08);
  border-radius: 10px;
  padding: 2px 8px;
  font-size: 12px;
  color: #5e6c84;
  margin-left: 8px;
}

.column-menu-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 8px;
  font-size: 16px;
  color: #5e6c84;
  border-radius: 4px;
}

.column-menu-btn:hover {
  background: rgba(0,0,0,0.08);
}

/* Card list */
.card-list {
  padding: 4px 8px;
  overflow-y: auto;
  flex: 1;
  min-height: 40px;
  scroll-behavior: smooth;
}

.card-list::-webkit-scrollbar {
  width: 6px;
}

.card-list::-webkit-scrollbar-track {
  background: transparent;
}

.card-list::-webkit-scrollbar-thumb {
  background: rgba(0,0,0,0.15);
  border-radius: 3px;
}

/* Add card button */
.add-card-btn {
  margin: 4px 8px 8px;
  padding: 8px 12px;
  background: none;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  text-align: left;
  color: #5e6c84;
  font-size: 13px;
}

.add-card-btn:hover {
  background: rgba(0,0,0,0.06);
  color: #172b4d;
}

/* Add column button */
.add-column-btn {
  min-width: 280px;
  max-width: 280px;
}

.add-column-btn button {
  width: 100%;
  padding: 12px;
  background: rgba(255,255,255,0.25);
  border: none;
  border-radius: 12px;
  cursor: pointer;
  text-align: left;
  color: #5e6c84;
  font-size: 14px;
}

.add-column-btn button:hover {
  background: rgba(255,255,255,0.5);
}

/* Empty column zone */
.empty-column-zone {
  border: 2px dashed #c4c9cc;
  border-radius: 8px;
  padding: 20px;
  text-align: center;
  color: #a5adba;
  font-size: 13px;
  margin: 4px 0;
}

/* WIP exceeded */
.card-list.wip-exceeded {
  outline: 2px solid rgba(235, 90, 70, 0.4);
  outline-offset: -2px;
  border-radius: 8px;
}
/* ── css: cards ── */
/* Card */
.card {
  background: #fff;
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 8px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.1);
  cursor: grab;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  position: relative;
}

.card:hover {
  box-shadow: 0 2px 8px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}

.card:active {
  cursor: grabbing;
}

.card-hidden {
  display: none;
}

.drag-source-hidden {
  opacity: 0.3 !important;
  pointer-events: none !important;
}

/* Card labels */
.card-labels {
  display: flex;
  gap: 4px;
  margin-bottom: 6px;
  flex-wrap: wrap;
}

.card-label-chip {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 3px;
  border-width: 1px;
  border-style: solid;
  font-weight: 500;
}

/* Card title */
.card-title {
  font-size: 14px;
  color: #172b4d;
  margin-bottom: 4px;
  word-break: break-word;
}

/* Card description */
.card-desc {
  font-size: 12px;
  color: #5e6c84;
  margin-bottom: 4px;
}

/* Card due date */
.card-due-date {
  font-size: 12px;
  color: #5e6c84;
}

.card-due-date.overdue {
  color: #eb5a46;
  font-weight: 600;
}

/* Card actions */
.card-actions {
  position: absolute;
  top: 6px;
  right: 6px;
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.card:hover .card-actions {
  opacity: 1;
}

.card-action-btn {
  background: rgba(0,0,0,0.06);
  border: none;
  cursor: pointer;
  padding: 2px 6px;
  font-size: 12px;
  border-radius: 4px;
  color: #5e6c84;
}

.card-action-btn:hover {
  background: rgba(0,0,0,0.12);
  color: #172b4d;
}

/* Drag ghost */
.drag-ghost {
  position: fixed !important;
  z-index: 1000 !important;
  pointer-events: none !important;
  background: #fff !important;
  border-radius: 8px !important;
  box-shadow: 0 8px 25px rgba(0,0,0,0.15), 0 4px 10px rgba(0,0,0,0.1) !important;
}

.drag-column-ghost {
  background: #ebecf0 !important;
  border-radius: 8px !important;
  padding: 8px 12px !important;
  display: flex !important;
  align-items: center !important;
}

/* Drop placeholder */
.drop-placeholder {
  background: rgba(0, 121, 191, 0.1);
  border: 2px dashed #0079bf;
  border-radius: 8px;
  margin-bottom: 8px;
  min-height: 40px;
  transition: height 0.15s ease;
}

.column-placeholder {
  width: 4px;
  background: #0079bf;
  border-radius: 2px;
  margin-right: 8px;
  min-height: 60px;
}

/* Column drop target highlight */
.column-drop-target .card-list {
  outline: 2px solid rgba(0, 121, 191, 0.3);
  border-radius: 8px;
}
/* ── css: ui-elements ── */
/* ─── Toolbar ──────────────────────────────────────────────── */

.toolbar-search input {
  padding: 8px 12px;
  border: 2px solid #dfe1e6;
  border-radius: 8px;
  font-size: 14px;
  width: 250px;
  outline: none;
  transition: border-color 0.15s ease;
}

.toolbar-search input:focus {
  border-color: #0079bf;
}

.toolbar-labels {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
}

.label-chip {
  padding: 4px 12px;
  border-radius: 20px;
  border: none;
  cursor: pointer;
  font-size: 12px;
  font-weight: 600;
  transition: transform 0.1s ease, opacity 0.15s ease;
}

.label-chip:hover {
  transform: scale(1.05);
}

.label-chip.active {
  box-shadow: 0 0 0 3px rgba(0,0,0,0.15);
}

.clear-filters-btn {
  padding: 4px 12px;
  border-radius: 20px;
  border: 1px solid #dfe1e6;
  background: #fff;
  cursor: pointer;
  font-size: 12px;
  color: #5e6c84;
}

.clear-filters-btn:hover {
  background: #f4f5f7;
}

/* ─── Column Menu ──────────────────────────────────────────── */

.column-menu {
  position: fixed;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.2);
  z-index: 100;
  min-width: 180px;
  padding: 6px;
}

.menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 12px;
  border: none;
  background: none;
  cursor: pointer;
  font-size: 13px;
  color: #172b4d;
  border-radius: 4px;
}

.menu-item:hover {
  background: #f4f5f7;
}

.menu-danger {
  color: #eb5a46 !important;
}

/* ─── Dialog / Modal ──────────────────────────────────────── */

#modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
}

#modal-overlay.hidden {
  display: none !important;
}

.card-dialog {
  background: #fff;
  border-radius: 12px;
  padding: 24px;
  width: 400px;
  max-width: 90vw;
  box-shadow: 0 8px 30px rgba(0,0,0,0.2);
}

.dialog-input {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 2px solid #dfe1e6;
  border-radius: 8px;
  font-size: 14px;
  margin-bottom: 12px;
  outline: none;
  transition: border-color 0.15s ease;
  font-family: inherit;
}

.dialog-input:focus {
  border-color: #0079bf;
}

.dialog-textarea {
  min-height: 80px;
  resize: vertical;
}

.dialog-labels-section {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.dialog-label-header {
  font-size: 13px;
  color: #5e6c84;
  font-weight: 600;
  margin-right: 4px;
}

.dialog-label-chip {
  padding: 4px 12px;
  border-radius: 20px;
  border: 2px solid transparent;
  cursor: pointer;
  font-size: 12px;
  font-weight: 600;
}

.dialog-label-chip.active {
  box-shadow: 0 0 0 2px rgba(0,0,0,0.15);
}

.dialog-btn-row {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

.dialog-save-btn {
  padding: 8px 20px;
  background: #0079bf;
  color: #fff;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
}

.dialog-save-btn:hover {
  background: #026aa7;
}

.dialog-cancel-btn {
  padding: 8px 20px;
  background: none;
  border: 1px solid #dfe1e6;
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
  color: #5e6c84;
}

.dialog-cancel-btn:hover {
  background: #f4f5f7;
}

/* ─── Toast Notifications ─────────────────────────────────── */

#toast-container {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.toast {
  background: #172b4d;
  color: #fff;
  padding: 12px 20px;
  border-radius: 8px;
  box-shadow: 0 4px 15px rgba(0,0,0,0.2);
  display: flex;
  align-items: center;
  gap: 16px;
  animation: toast-in 0.3s ease-out;
}

@keyframes toast-in {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

.toast-message {
  font-size: 14px;
}

.toast-undo-btn {
  background: none;
  border: none;
  color: #fff;
  cursor: pointer;
  font-weight: 600;
  text-decoration: underline;
  font-size: 13px;
}

.toast-undo-btn:hover {
  opacity: 0.8;
}
/* ── css: polish ── */
/* ─── Scrollbar Styling ───────────────────────────────────── */

#board-container::-webkit-scrollbar {
  height: 8px;
}

#board-container::-webkit-scrollbar-track {
  background: #f4f5f7;
}

#board-container::-webkit-scrollbar-thumb {
  background: #c4c9cc;
  border-radius: 4px;
}

#board-container::-webkit-scrollbar-thumb:hover {
  background: #a5adba;
}

/* ─── Card Micro-interactions ─────────────────────────────── */

.card {
  transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.15s ease;
}

.card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

/* ─── Column Header Hover ────────────────────────────────── */

.column-header:hover {
  background: rgba(0,0,0,0.03);
  border-radius: 8px 8px 0 0;
}

/* ─── Add Column Button Polish ───────────────────────────── */

.add-column-btn button {
  padding: 12px 16px;
  background: rgba(255,255,255,0.3);
  border: none;
  border-radius: 12px;
  cursor: pointer;
  text-align: left;
  color: #5e6c84;
  font-size: 14px;
  transition: background 0.15s ease;
}

.add-column-btn button:hover {
  background: rgba(255,255,255,0.6);
  color: #172b4d;
}

/* ─── Empty Column Zone Polish ───────────────────────────── */

.empty-column-zone {
  border: 2px dashed #c4c9cc;
  border-radius: 8px;
  padding: 24px 16px;
  text-align: center;
  color: #a5adba;
  font-size: 13px;
  margin: 4px 0;
  transition: border-color 0.15s ease, background 0.15s ease;
}

/* ─── Drop Placeholder Animation ─────────────────────────── */

.drop-placeholder {
  animation: placeholder-pulse 1s ease-in-out infinite;
}

@keyframes placeholder-pulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

/* ─── Card Dialog Polish ─────────────────────────────────── */

.card-dialog {
  animation: dialog-in 0.2s ease-out;
}

@keyframes dialog-in {
  from { transform: scale(0.95); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* ─── Toast Animation Polish ─────────────────────────────── */

.toast {
  animation: toast-slide-up 0.3s ease-out;
}

@keyframes toast-slide-up {
  from { transform: translateY(20px) scale(0.95); opacity: 0; }
  to { transform: translateY(0) scale(1); opacity: 1; }
}

/* ─── Selection Styling ──────────────────────────────────── */

::selection {
  background: rgba(0, 121, 191, 0.2);
}
</style>
</head>
<body>
<div id="board-container">
  <div id="toolbar"></div>
  <div id="board"></div>
</div>
<div id="toast-container"></div>
<div id="modal-overlay" class="hidden"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./state":"src/state.ts","./rendering":"src/rendering.ts","./dragdrop":"src/dragdrop.ts","./persistence":"src/persistence.ts"},"src/state.ts":{"./types":"src/types.ts"},"src/rendering.ts":{"./types":"src/types.ts","./state":"src/state.ts","./persistence":"src/persistence.ts"},"src/dragdrop.ts":{"./types":"src/types.ts","./state":"src/state.ts","./rendering":"src/rendering.ts","./persistence":"src/persistence.ts"},"src/persistence.ts":{"./state":"src/state.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";
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 State = __importStar(require("./state"));
const rendering_1 = require("./rendering");
const dragdrop_1 = require("./dragdrop");
const persistence_1 = require("./persistence");
// ─── Entry Point ──────────────────────────────────────────────────────
function main() {
    // Load persisted state or create default board
    let loaded = (0, persistence_1.load)();
    // Check if saved data has cards - if not, use sample data
    const savedModel = State.getBoard();
    const totalCards = savedModel.columns.reduce((sum, c) => sum + c.cardIds.length, 0);
    if (!loaded || totalCards === 0) {
        State.initDefaultBoard();
        (0, persistence_1.save)();
        loaded = true;
    }
    // Initial render
    (0, rendering_1.render)();
    // Initialize drag & drop system
    (0, dragdrop_1.initDragDrop)();
    // Add column button
    setupAddColumnButton();
    // Auto-save on state changes (debounced)
    // We hook into rendering to detect changes
    const origRender = rendering_1.render;
    // Actually, we save after each user action. The scheduleSave is called from interactions.
    // Save periodically as well
    setInterval(persistence_1.save, 5000);
}
// ─── Add Column Button ────────────────────────────────────────────────
function setupAddColumnButton() {
    const board = document.getElementById('board');
    const addColBtn = document.createElement('div');
    addColBtn.className = 'add-column-btn';
    addColBtn.innerHTML = '<button>+ Add another column</button>';
    addColBtn.querySelector('button').addEventListener('click', () => {
        const title = prompt('Column name:');
        if (title && title.trim()) {
            State.addColumn(title.trim());
            (0, persistence_1.scheduleSave)();
            (0, rendering_1.render)();
        }
    });
    board.appendChild(addColBtn);
}
// ─── Start ────────────────────────────────────────────────────────────
main();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ─── Tunable Constants ────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.TOAST_DURATION = exports.DROP_SETTLE_DURATION = exports.CARD_LIFT_DURATION = exports.AUTO_SCROLL_MARGIN = exports.AUTO_SCROLL_SPEED = exports.DRAG_THRESHOLD = exports.DEFAULT_COLUMNS = exports.LABEL_PALETTE = exports.COLORS = void 0;
exports.COLORS = {
    bg: '#f4f5f7',
    columnBg: '#ebecf0',
    cardBg: '#ffffff',
    textPrimary: '#172b4d',
    textSecondary: '#5e6c84',
    accent: '#0079bf',
    accentHover: '#026aa7',
    danger: '#eb5a46',
    success: '#61bd4f',
    labelBorder: 'rgba(0,0,0,0.08)',
    placeholderBg: 'rgba(0,121,191,0.12)',
    dropIndicatorColor: '#0079bf',
    wipExceeded: '#eb5a46',
};
exports.LABEL_PALETTE = [
    { name: 'Bug', color: '#eb5a46' },
    { name: 'Feature', color: '#61bd4f' },
    { name: 'Enhancement', color: '#0079bf' },
    { name: 'Urgent', color: '#c377e0' },
    { name: 'Design', color: '#f2d600' },
    { name: 'Documentation', color: '#00c2e0' },
];
exports.DEFAULT_COLUMNS = [
    'Backlog', 'To Do', 'In Progress', 'Review', 'Done',
];
// Drag thresholds & animation timings (ms)
exports.DRAG_THRESHOLD = 5; // px before drag starts
exports.AUTO_SCROLL_SPEED = 12; // px per frame near edges
exports.AUTO_SCROLL_MARGIN = 60; // px from edge to trigger scroll
exports.CARD_LIFT_DURATION = 150; // ms for card lift animation
exports.DROP_SETTLE_DURATION = 200; // ms for drop settle animation
exports.TOAST_DURATION = 3000; // ms before toast auto-dismisses
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBoard = getBoard;
exports.getFilter = getFilter;
exports.initDefaultBoard = initDefaultBoard;
exports.addColumn = addColumn;
exports.renameColumn = renameColumn;
exports.deleteColumn = deleteColumn;
exports.setWipLimit = setWipLimit;
exports.addCard = addCard;
exports.updateCard = updateCard;
exports.deleteCard = deleteCard;
exports.moveCard = moveCard;
exports.reorderCardInColumn = reorderCardInColumn;
exports.reorderColumns = reorderColumns;
exports.setSearchQuery = setSearchQuery;
exports.toggleLabelFilter = toggleLabelFilter;
exports.clearFilters = clearFilters;
exports.isCardVisible = isCardVisible;
exports.serialize = serialize;
exports.deserialize = deserialize;
// ─── State (single source of truth) ──────────────────────────────────
let board = { columns: [], cards: {} };
let filter = { searchQuery: '', activeLabels: new Set() };
function getBoard() { return JSON.parse(JSON.stringify(board)); }
function getFilter() { return { ...filter, activeLabels: new Set(filter.activeLabels) }; }
// ─── Initialization ──────────────────────────────────────────────────
function initDefaultBoard() {
    const col1Id = uid();
    const col2Id = uid();
    const col3Id = uid();
    const col4Id = uid();
    const col5Id = uid();
    // Create sample cards
    const c1 = uid(), c2 = uid(), c3 = uid(), c4 = uid(), c5 = uid(), c6 = uid(), c7 = uid(), c8 = uid();
    board = {
        columns: [
            { id: col1Id, title: 'Backlog', cardIds: [c1, c2] },
            { id: col2Id, title: 'To Do', cardIds: [c3, c4] },
            { id: col3Id, title: 'In Progress', cardIds: [c5] },
            { id: col4Id, title: 'Review', cardIds: [] },
            { id: col5Id, title: 'Done', cardIds: [c6, c7, c8] },
        ],
        cards: {
            [c1]: { id: c1, title: 'Research competitors', description: 'Analyze top 3 competitor products and document findings', labels: ['Feature'], dueDate: '' },
            [c2]: { id: c2, title: 'Define user personas', description: '', labels: ['Design'], dueDate: '' },
            [c3]: { id: c3, title: 'Set up CI/CD pipeline', description: 'Configure GitHub Actions for automated testing and deployment', labels: ['Enhancement'], dueDate: '' },
            [c4]: { id: c4, title: 'Fix login redirect bug', description: 'Users are redirected to 404 after OAuth login on mobile', labels: ['Bug', 'Urgent'], dueDate: '' },
            [c5]: { id: c5, title: 'Implement drag & drop', description: 'Card reordering within and across columns', labels: ['Feature'], dueDate: '' },
            [c6]: { id: c6, title: 'Project kickoff meeting', description: '', labels: [], dueDate: '' },
            [c7]: { id: c7, title: 'Design system v1', description: 'Color palette, typography, spacing tokens', labels: ['Design'], dueDate: '' },
            [c8]: { id: c8, title: 'Write API docs', description: '', labels: ['Documentation'], dueDate: '' },
        },
    };
}
// ─── Column CRUD ─────────────────────────────────────────────────────
function addColumn(title) {
    board.columns.push({ id: uid(), title, cardIds: [] });
}
function renameColumn(columnId, newTitle) {
    const col = board.columns.find(c => c.id === columnId);
    if (col)
        col.title = newTitle;
}
function deleteColumn(columnId) {
    if (board.columns.length <= 1)
        return; // keep at least one
    const col = board.columns.find(c => c.id === columnId);
    if (!col)
        return;
    // Remove all cards in this column
    for (const cardId of col.cardIds) {
        delete board.cards[cardId];
    }
    board.columns = board.columns.filter(c => c.id !== columnId);
}
function setWipLimit(columnId, limit) {
    const col = board.columns.find(c => c.id === columnId);
    if (col)
        col.wipLimit = limit;
}
// ─── Card CRUD ───────────────────────────────────────────────────────
function addCard(columnId, card) {
    const id = uid();
    board.cards[id] = { ...card, id };
    const col = board.columns.find(c => c.id === columnId);
    if (col)
        col.cardIds.push(id);
    return id;
}
function updateCard(cardId, updates) {
    const card = board.cards[cardId];
    if (card)
        Object.assign(card, updates);
}
function deleteCard(cardId) {
    // Remove from column
    for (const col of board.columns) {
        col.cardIds = col.cardIds.filter(id => id !== cardId);
    }
    delete board.cards[cardId];
}
// ─── Card Reordering (drag & drop result) ────────────────────────────
function moveCard(cardId, fromColumnId, toColumnId, toIndex) {
    const fromCol = board.columns.find(c => c.id === fromColumnId);
    if (!fromCol)
        return;
    // Remove from source column
    fromCol.cardIds = fromCol.cardIds.filter(id => id !== cardId);
    // Insert into target column at index
    const toCol = board.columns.find(c => c.id === toColumnId);
    if (toCol) {
        toCol.cardIds.splice(toIndex, 0, cardId);
    }
}
function reorderCardInColumn(columnId, fromIndex, toIndex) {
    const col = board.columns.find(c => c.id === columnId);
    if (!col)
        return;
    const [cardId] = col.cardIds.splice(fromIndex, 1);
    col.cardIds.splice(toIndex, 0, cardId);
}
// ─── Column Reordering ──────────────────────────────────────────────
function reorderColumns(fromIndex, toIndex) {
    const [col] = board.columns.splice(fromIndex, 1);
    board.columns.splice(toIndex, 0, col);
}
// ─── Filter ──────────────────────────────────────────────────────────
function setSearchQuery(query) { filter.searchQuery = query; }
function toggleLabelFilter(labelName) {
    if (filter.activeLabels.has(labelName))
        filter.activeLabels.delete(labelName);
    else
        filter.activeLabels.add(labelName);
}
function clearFilters() {
    filter.searchQuery = '';
    filter.activeLabels.clear();
}
// ─── Visibility Check ────────────────────────────────────────────────
function isCardVisible(cardId) {
    const card = board.cards[cardId];
    if (!card)
        return false;
    // Search filter
    if (filter.searchQuery.trim()) {
        const q = filter.searchQuery.toLowerCase();
        if (!card.title.toLowerCase().includes(q))
            return false;
    }
    // Label filter: show cards that have at least one active label
    if (filter.activeLabels.size > 0) {
        const hasActiveLabel = card.labels.some(l => filter.activeLabels.has(l));
        if (!hasActiveLabel)
            return false;
    }
    return true;
}
// ─── Serialization ──────────────────────────────────────────────────
function serialize() {
    return JSON.stringify({ board, filter: { searchQuery: filter.searchQuery, activeLabels: [...filter.activeLabels] } });
}
function deserialize(json) {
    try {
        const data = JSON.parse(json);
        board = data.board;
        filter = {
            searchQuery: data.filter?.searchQuery ?? '',
            activeLabels: new Set(data.filter?.activeLabels ?? [])
        };
    }
    catch { /* ignore corrupt data */ }
}
// ─── Helpers ─────────────────────────────────────────────────────────
let _uidCounter = 0;
function uid() {
    return 'id_' + Date.now().toString(36) + '_' + (++_uidCounter).toString(36);
}
};

// ── module: src/rendering.ts ──
__mods["src/rendering.ts"] = function (exports, require, module) {
"use strict";
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 });
exports.render = render;
exports.deleteCardWithUndo = deleteCardWithUndo;
exports.deleteColumnWithUndo = deleteColumnWithUndo;
const types_1 = require("./types");
const State = __importStar(require("./state"));
const persistence_1 = require("./persistence");
// ─── DOM References ──────────────────────────────────────────────────
const boardEl = document.getElementById('board');
const toolbarEl = document.getElementById('toolbar');
// ─── Render Everything ───────────────────────────────────────────────
function render() {
    renderToolbar();
    renderBoard();
}
// ─── Toolbar (search, label filters) ─────────────────────────────────
function renderToolbar() {
    const filter = State.getFilter();
    toolbarEl.innerHTML = '';
    // Search input
    const searchWrap = h('div', { class: 'toolbar-search' });
    const searchInput = h('input', { type: 'text', placeholder: 'Search cards…', value: filter.searchQuery, id: 'search-input' });
    searchInput.addEventListener('input', () => { State.setSearchQuery(searchInput.value); (0, persistence_1.scheduleSave)(); render(); });
    searchWrap.appendChild(searchInput);
    // Label filters
    const labelBar = h('div', { class: 'toolbar-labels' });
    for (const label of types_1.LABEL_PALETTE) {
        const isActive = filter.activeLabels.has(label.name);
        const chip = h('button', {
            class: `label-chip ${isActive ? 'active' : ''}`,
            style: `background:${label.color};color:#fff;`,
            title: label.name,
            'data-label': label.name,
        });
        chip.textContent = label.name;
        chip.addEventListener('click', () => { State.toggleLabelFilter(label.name); (0, persistence_1.scheduleSave)(); render(); });
        labelBar.appendChild(chip);
    }
    // Clear filters button (only show if filters active)
    if (filter.searchQuery || filter.activeLabels.size > 0) {
        const clearBtn = h('button', { class: 'clear-filters-btn' });
        clearBtn.textContent = '✕ Clear';
        clearBtn.addEventListener('click', () => { State.clearFilters(); render(); });
        labelBar.appendChild(clearBtn);
    }
    toolbarEl.appendChild(searchWrap);
    toolbarEl.appendChild(labelBar);
}
// ─── Board (columns) ────────────────────────────────────────────────
function renderBoard() {
    boardEl.innerHTML = '';
    const model = State.getBoard();
    for (const col of model.columns) {
        boardEl.appendChild(createColumnElement(col));
    }
}
// ─── Column Element ──────────────────────────────────────────────────
function createColumnElement(col) {
    const wrapper = h('div', { class: 'column-wrapper' });
    // Header
    const header = h('div', { class: 'column-header', 'data-column-id': col.id, 'draggable-type': 'column' });
    const titleSpan = h('span', { class: 'column-title' });
    titleSpan.textContent = col.title;
    // Card count
    const visibleCount = col.cardIds.filter(id => State.isCardVisible(id)).length;
    const totalCount = col.cardIds.length;
    const countBadge = h('span', { class: 'card-count' });
    countBadge.textContent = `${visibleCount}${totalCount !== visibleCount ? `/${totalCount}` : ''}`;
    header.appendChild(titleSpan);
    header.appendChild(countBadge);
    // Column menu button
    const menuBtn = h('button', { class: 'column-menu-btn' });
    menuBtn.textContent = '⋯';
    menuBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        showColumnMenu(col.id, menuBtn);
    });
    header.appendChild(menuBtn);
    wrapper.appendChild(header);
    // Card list
    const cardList = h('div', {
        class: 'card-list',
        'data-column-id': col.id,
        style: `min-height:${col.cardIds.length === 0 ? '40px' : 'auto'}`,
    });
    // WIP exceeded styling
    const wipExceeded = col.wipLimit !== undefined && col.wipLimit > 0 && col.cardIds.length > col.wipLimit;
    if (wipExceeded) {
        cardList.classList.add('wip-exceeded');
    }
    // Render cards
    for (const cardId of col.cardIds) {
        const card = State.getBoard().cards[cardId];
        if (!card)
            continue;
        const isVisible = State.isCardVisible(cardId);
        const cardEl = createCardElement(card, col.id, !isVisible);
        cardList.appendChild(cardEl);
    }
    // Empty column drop zone indicator
    if (col.cardIds.length === 0) {
        const emptyZone = h('div', { class: 'empty-column-zone' });
        emptyZone.textContent = 'Drop cards here';
        cardList.appendChild(emptyZone);
    }
    wrapper.appendChild(cardList);
    // Add column button (at bottom of last column, or always visible)
    const addCardBtn = h('button', { class: 'add-card-btn' });
    addCardBtn.textContent = '+ Add a card';
    addCardBtn.addEventListener('click', () => showAddCardDialog(col.id));
    wrapper.appendChild(addCardBtn);
    return wrapper;
}
// ─── Card Element ────────────────────────────────────────────────────
function createCardElement(card, columnId, hidden) {
    const cardEl = h('div', {
        class: `card ${hidden ? 'card-hidden' : ''}`,
        'data-card-id': card.id,
        'draggable-type': 'card',
    });
    // Labels row
    if (card.labels.length > 0) {
        const labelRow = h('div', { class: 'card-labels' });
        for (const labelName of card.labels) {
            const labelDef = types_1.LABEL_PALETTE.find(l => l.name === labelName);
            if (!labelDef)
                continue;
            const chip = h('span', {
                class: 'card-label-chip',
                style: `background:${labelDef.color}22;color:${labelDef.color};border-color:${labelDef.color}44`,
            });
            chip.textContent = labelName;
            labelRow.appendChild(chip);
        }
        cardEl.appendChild(labelRow);
    }
    // Title
    const titleEl = h('div', { class: 'card-title' });
    titleEl.textContent = card.title;
    cardEl.appendChild(titleEl);
    // Description preview
    if (card.description) {
        const descEl = h('div', { class: 'card-desc' });
        descEl.textContent = card.description.length > 80 ? card.description.slice(0, 80) + '…' : card.description;
        cardEl.appendChild(descEl);
    }
    // Due date
    if (card.dueDate) {
        const dueEl = h('div', { class: 'card-due-date' });
        const d = new Date(card.dueDate + 'T00:00:00');
        const today = new Date();
        today.setHours(0, 0, 0, 0);
        const isOverdue = d < today;
        dueEl.textContent = `📅 ${card.dueDate}`;
        if (isOverdue)
            dueEl.classList.add('overdue');
        cardEl.appendChild(dueEl);
    }
    // Card actions (hover-visible)
    const actionsRow = h('div', { class: 'card-actions' });
    const editBtn = h('button', { class: 'card-action-btn' });
    editBtn.textContent = '✎';
    editBtn.title = 'Edit card';
    editBtn.addEventListener('click', (e) => { e.stopPropagation(); showEditCardDialog(card.id, columnId); });
    const deleteBtn = h('button', { class: 'card-action-btn' });
    deleteBtn.textContent = '✕';
    deleteBtn.title = 'Delete card';
    deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); deleteCardWithUndo(card.id, card.title); });
    actionsRow.appendChild(editBtn);
    actionsRow.appendChild(deleteBtn);
    cardEl.appendChild(actionsRow);
    return cardEl;
}
// ─── Column Menu ─────────────────────────────────────────────────────
function showColumnMenu(columnId, btn) {
    // Remove existing menus
    document.querySelectorAll('.column-menu').forEach(m => m.remove());
    const menu = h('div', { class: 'column-menu' });
    const rect = btn.getBoundingClientRect();
    menu.style.top = `${rect.bottom + 4}px`;
    menu.style.left = `${rect.left}px`;
    // Rename option
    const renameItem = h('button', { class: 'menu-item' });
    renameItem.textContent = 'Rename column';
    renameItem.addEventListener('click', () => {
        menu.remove();
        const col = State.getBoard().columns.find(c => c.id === columnId);
        if (!col)
            return;
        const newTitle = prompt('Column name:', col.title);
        if (newTitle && newTitle.trim()) {
            State.renameColumn(columnId, newTitle.trim());
            (0, persistence_1.scheduleSave)();
            render();
        }
    });
    // WIP limit option
    const wipItem = h('button', { class: 'menu-item' });
    const col = State.getBoard().columns.find(c => c.id === columnId);
    wipItem.textContent = col?.wipLimit ? `WIP limit: ${col.wipLimit}` : 'Set WIP limit';
    wipItem.addEventListener('click', () => {
        menu.remove();
        const current = col?.wipLimit ?? 0;
        const val = prompt(`Current WIP limit: ${current || 'none'}\nEnter new limit (0 to remove):`, String(current));
        if (val !== null) {
            const num = parseInt(val, 10);
            if (!isNaN(num)) {
                State.setWipLimit(columnId, num > 0 ? num : undefined);
                (0, persistence_1.scheduleSave)();
                render();
            }
        }
    });
    // Delete option
    const deleteItem = h('button', { class: 'menu-item menu-danger' });
    deleteItem.textContent = 'Delete column';
    deleteItem.addEventListener('click', () => {
        menu.remove();
        if (State.getBoard().columns.length <= 1)
            return;
        const col2 = State.getBoard().columns.find(c => c.id === columnId);
        deleteColumnWithUndo(columnId, col2?.title ?? 'column');
    });
    menu.appendChild(renameItem);
    menu.appendChild(wipItem);
    menu.appendChild(deleteItem);
    document.body.appendChild(menu);
    // Close on outside click
    const closeMenu = (e) => {
        if (!menu.contains(e.target)) {
            menu.remove();
            document.removeEventListener('click', closeMenu);
        }
    };
    setTimeout(() => document.addEventListener('click', closeMenu), 0);
}
// ─── Add Card Dialog ────────────────────────────────────────────────
function showAddCardDialog(columnId) {
    const overlay = document.getElementById('modal-overlay');
    overlay.innerHTML = '';
    overlay.classList.remove('hidden');
    const dialog = h('div', { class: 'card-dialog' });
    // Title input
    const titleInput = h('input', {
        type: 'text', placeholder: 'Card title…', id: 'dialog-title-input', class: 'dialog-input'
    });
    // Description textarea
    const descTextarea = h('textarea', {
        placeholder: 'Description (optional)…', id: 'dialog-desc-textarea', class: 'dialog-input dialog-textarea'
    });
    // Due date input
    const dueDateInput = h('input', {
        type: 'date', id: 'dialog-due-date', class: 'dialog-input'
    });
    // Labels selection
    const labelSection = h('div', { class: 'dialog-labels-section' });
    const labelHeader = h('span', { class: 'dialog-label-header' });
    labelHeader.textContent = 'Labels';
    labelSection.appendChild(labelHeader);
    const selectedLabels = new Set();
    for (const label of types_1.LABEL_PALETTE) {
        const chip = h('button', {
            class: `dialog-label-chip ${selectedLabels.has(label.name) ? 'active' : ''}`,
            style: `background:${label.color}22;color:${label.color};border-color:${label.color}44`,
            'data-label': label.name,
        });
        chip.textContent = label.name;
        chip.addEventListener('click', () => {
            if (selectedLabels.has(label.name))
                selectedLabels.delete(label.name);
            else
                selectedLabels.add(label.name);
            chip.classList.toggle('active');
        });
        labelSection.appendChild(chip);
    }
    // Buttons
    const btnRow = h('div', { class: 'dialog-btn-row' });
    const saveBtn = h('button', { class: 'dialog-save-btn' });
    saveBtn.textContent = 'Add Card';
    saveBtn.addEventListener('click', () => {
        const tv = titleInput.value;
        if (tv.trim()) {
            State.addCard(columnId, {
                title: tv.trim(),
                description: descTextarea.value.trim(),
                labels: [...selectedLabels],
                dueDate: dueDateInput.value || undefined,
            });
            (0, persistence_1.scheduleSave)();
            closeDialog();
        }
    });
    const cancelBtn = h('button', { class: 'dialog-cancel-btn' });
    cancelBtn.textContent = 'Cancel';
    cancelBtn.addEventListener('click', closeDialog);
    btnRow.appendChild(saveBtn);
    btnRow.appendChild(cancelBtn);
    dialog.appendChild(titleInput);
    dialog.appendChild(descTextarea);
    dialog.appendChild(dueDateInput);
    dialog.appendChild(labelSection);
    dialog.appendChild(btnRow);
    overlay.appendChild(dialog);
    // Focus title input and handle Enter/Escape
    setTimeout(() => {
        titleInput.focus();
        const handler = (e) => {
            if (e.key === 'Enter' && !e.shiftKey && document.activeElement !== descTextarea) {
                e.preventDefault();
                saveBtn.click();
            }
            if (e.key === 'Escape')
                closeDialog();
        };
        overlay.addEventListener('keydown', handler);
    }, 50);
}
function showEditCardDialog(cardId, _columnId) {
    const card = State.getBoard().cards[cardId];
    if (!card)
        return;
    const overlay = document.getElementById('modal-overlay');
    overlay.innerHTML = '';
    overlay.classList.remove('hidden');
    const dialog = h('div', { class: 'card-dialog' });
    const titleInput = h('input', {
        type: 'text', value: card.title, id: 'dialog-title-input', class: 'dialog-input'
    });
    const descTextarea = h('textarea', {
        value: card.description, id: 'dialog-desc-textarea', class: 'dialog-input dialog-textarea'
    });
    const dueDateInput = h('input', {
        type: 'date', value: card.dueDate || '', id: 'dialog-due-date', class: 'dialog-input'
    });
    const labelSection = h('div', { class: 'dialog-labels-section' });
    const labelHeader = h('span', { class: 'dialog-label-header' });
    labelHeader.textContent = 'Labels';
    labelSection.appendChild(labelHeader);
    for (const label of types_1.LABEL_PALETTE) {
        const isActive = card.labels.includes(label.name);
        const chip = h('button', {
            class: `dialog-label-chip ${isActive ? 'active' : ''}`,
            style: `background:${label.color}22;color:${label.color};border-color:${label.color}44`,
            'data-label': label.name,
        });
        chip.textContent = label.name;
        chip.addEventListener('click', () => {
            chip.classList.toggle('active');
        });
        labelSection.appendChild(chip);
    }
    const btnRow = h('div', { class: 'dialog-btn-row' });
    const saveBtn = h('button', { class: 'dialog-save-btn' });
    saveBtn.textContent = 'Save';
    saveBtn.addEventListener('click', () => {
        const activeLabels = [...labelSection.querySelectorAll('.dialog-label-chip.active')].map((b) => b.dataset.label).filter(Boolean);
        State.updateCard(cardId, {
            title: (titleInput.value.trim() || card.title),
            description: descTextarea.value.trim(),
            labels: activeLabels,
            dueDate: dueDateInput.value || undefined,
        });
        (0, persistence_1.scheduleSave)();
        closeDialog();
    });
    const cancelBtn = h('button', { class: 'dialog-cancel-btn' });
    cancelBtn.textContent = 'Cancel';
    cancelBtn.addEventListener('click', closeDialog);
    btnRow.appendChild(saveBtn);
    btnRow.appendChild(cancelBtn);
    dialog.appendChild(titleInput);
    dialog.appendChild(descTextarea);
    dialog.appendChild(dueDateInput);
    dialog.appendChild(labelSection);
    dialog.appendChild(btnRow);
    overlay.appendChild(dialog);
    setTimeout(() => {
        titleInput.focus();
        const handler = (e) => {
            if (e.key === 'Enter' && !e.shiftKey && document.activeElement !== descTextarea) {
                e.preventDefault();
                saveBtn.click();
            }
            if (e.key === 'Escape')
                closeDialog();
        };
        overlay.addEventListener('keydown', handler);
    }, 50);
}
function closeDialog() {
    const overlay = document.getElementById('modal-overlay');
    overlay.classList.add('hidden');
    overlay.innerHTML = '';
    render();
}
// ─── Undo Helpers ────────────────────────────────────────────────────
function deleteCardWithUndo(cardId, cardTitle) {
    const snapshot = State.getBoard();
    State.deleteCard(cardId);
    showUndoToast(`Deleted "${cardTitle}"`, () => restoreSnapshot(snapshot));
    (0, persistence_1.scheduleSave)();
    render();
}
function deleteColumnWithUndo(columnId, columnTitle) {
    const snapshot = State.getBoard();
    State.deleteColumn(columnId);
    showUndoToast(`Deleted column "${columnTitle}"`, () => restoreSnapshot(snapshot));
    (0, persistence_1.scheduleSave)();
    render();
}
function restoreSnapshot(snapshot) {
    State.deserialize(JSON.stringify({ board: snapshot }));
    render();
}
// ─── Toast System ────────────────────────────────────────────────────
function showUndoToast(message, onUndo) {
    const container = document.getElementById('toast-container');
    const toast = h('div', { class: 'toast' });
    const msgEl = h('span', { class: 'toast-message' });
    msgEl.textContent = message;
    const undoBtn = h('button', { class: 'toast-undo-btn' });
    undoBtn.textContent = 'Undo';
    undoBtn.addEventListener('click', () => {
        onUndo();
        toast.remove();
    });
    toast.appendChild(msgEl);
    toast.appendChild(undoBtn);
    container.appendChild(toast);
    // Auto-remove after delay
    setTimeout(() => { if (toast.parentNode)
        toast.remove(); }, 4000);
}
// ─── DOM Helpers ─────────────────────────────────────────────────────
function h(tag, attrs) {
    const element = document.createElement(tag);
    if (attrs) {
        for (const [key, value] of Object.entries(attrs)) {
            if (value === undefined || value === null)
                continue;
            if (key === 'style') {
                element.style.cssText = String(value);
            }
            else if (key === 'class') {
                element.className = String(value);
            }
            else if (key.startsWith('data-')) {
                const dataKey = key.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
                element.dataset[dataKey] = String(value);
            }
            else if (key.includes('-') || !(key in element)) {
                // Custom attribute or non-standard property - use setAttribute
                element.setAttribute(key, String(value));
            }
            else {
                try {
                    element[key] = value;
                }
                catch { }
            }
        }
    }
    return element;
}
};

// ── module: src/dragdrop.ts ──
__mods["src/dragdrop.ts"] = function (exports, require, module) {
"use strict";
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 });
exports.initDragDrop = initDragDrop;
const types_1 = require("./types");
const State = __importStar(require("./state"));
const rendering_1 = require("./rendering");
const persistence_1 = require("./persistence");
// ─── Drag Drop System ────────────────────────────────────────────────
let dragState = null;
let placeholderEl = null;
let ghostEl = null;
let autoScrollRAF = 0;
// ─── Initialization ──────────────────────────────────────────────────
function initDragDrop() {
    const board = document.getElementById('board');
    board.addEventListener('mousedown', onBoardMouseDown);
    window.addEventListener('mousemove', onMouseMove, true); // capture for reliability
    window.addEventListener('mouseup', onMouseUp, true);
    window.addEventListener('keydown', onKeyDown);
}
// ─── Mouse Down ──────────────────────────────────────────────────────
function onBoardMouseDown(e) {
    if (e.button !== 0)
        return; // only left click
    const target = e.target;
    // Check for card drag
    const cardEl = target.closest('[draggable-type="card"]') || null;
    if (cardEl && !(target.closest('.card-action-btn'))) {
        startCardDrag(e, cardEl);
        return;
    }
    // Check for column header drag
    const headerEl = target.closest('[draggable-type="column"]') || null;
    if (headerEl && !target.closest('.column-menu-btn')) {
        startColumnDrag(e, headerEl);
        return;
    }
}
// ─── Card Drag ───────────────────────────────────────────────────────
function startCardDrag(e, cardEl) {
    const cardId = cardEl.dataset.cardId;
    if (!cardId)
        return;
    const columnList = cardEl.closest('.card-list');
    const columnId = columnList?.dataset.columnId;
    if (!columnId)
        return;
    // Find index in model
    const model = State.getBoard();
    const col = model.columns.find(c => c.id === columnId);
    if (!col)
        return;
    const sourceIndex = col.cardIds.indexOf(cardId);
    if (sourceIndex < 0)
        return;
    // Store initial state for cancel
    dragState = { type: 'card', sourceColumnId: columnId, sourceIndex, cardId };
    e.preventDefault();
    const rect = cardEl.getBoundingClientRect();
    const offsetX = e.clientX - rect.left;
    const offsetY = e.clientY - rect.top;
    // Create ghost element (follows cursor)
    ghostEl = cardEl.cloneNode(true);
    ghostEl.classList.add('drag-ghost');
    ghostEl.style.width = `${rect.width}px`;
    ghostEl.style.position = 'fixed';
    ghostEl.style.left = `${rect.left}px`;
    ghostEl.style.top = `${rect.top}px`;
    ghostEl.style.zIndex = '1000';
    ghostEl.style.pointerEvents = 'none';
    ghostEl.style.transformOrigin = `${offsetX}px ${offsetY}px`;
    ghostEl.style.transition = `transform ${types_1.CARD_LIFT_DURATION}ms ease-out`;
    // Hide original card (show placeholder instead)
    cardEl.classList.add('drag-source-hidden');
    document.body.appendChild(ghostEl);
    // Create placeholder at source position
    placeholderEl = createPlaceholder(cardEl);
    const parent = cardEl.parentNode;
    if (parent) {
        parent.insertBefore(placeholderEl, cardEl.nextSibling);
    }
    // Animate ghost lift after a frame
    requestAnimationFrame(() => {
        if (ghostEl) {
            ghostEl.style.transform = 'scale(1.03) rotate(-1deg)';
            ghostEl.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15), 0 4px 10px rgba(0,0,0,0.1)';
        }
    });
    // Store offset for cursor tracking
    ghostEl._dragOffsetX = offsetX;
    ghostEl._dragOffsetY = offsetY;
}
// ─── Column Drag ─────────────────────────────────────────────────────
function startColumnDrag(e, headerEl) {
    const columnId = headerEl.dataset.columnId;
    if (!columnId)
        return;
    const model = State.getBoard();
    const sourceIndex = model.columns.findIndex(c => c.id === columnId);
    if (sourceIndex < 0)
        return;
    dragState = { type: 'column', sourceColumnId: columnId, sourceIndex };
    e.preventDefault();
    // Create ghost for column header
    const wrapper = headerEl.closest('.column-wrapper');
    if (!wrapper)
        return;
    const rect = wrapper.getBoundingClientRect();
    ghostEl = h('div', { class: 'drag-ghost drag-column-ghost' });
    ghostEl.style.width = `${rect.width}px`;
    ghostEl.style.height = `${headerEl.offsetHeight}px`;
    ghostEl.style.position = 'fixed';
    ghostEl.style.left = `${rect.left}px`;
    ghostEl.style.top = `${e.clientY - headerEl.offsetTop + wrapper.offsetTop - rect.top}px`;
    ghostEl.style.zIndex = '1000';
    ghostEl.style.pointerEvents = 'none';
    ghostEl.style.background = types_1.COLORS.columnBg;
    ghostEl.style.borderRadius = '8px';
    ghostEl.style.opacity = '0.7';
    const titleClone = h('span', { class: 'column-title' });
    titleClone.textContent = headerEl.querySelector('.column-title')?.textContent ?? '';
    ghostEl.appendChild(titleClone);
    document.body.appendChild(ghostEl);
    // Dim the original column
    wrapper.classList.add('drag-source-hidden');
    ghostEl._dragOffsetX = e.clientX - rect.left;
    ghostEl._dragOffsetY = e.clientY - rect.top;
}
// ─── Mouse Move ──────────────────────────────────────────────────────
function onMouseMove(e) {
    if (!dragState || !ghostEl)
        return;
    // Update ghost position to follow cursor
    const offsetX = ghostEl._dragOffsetX ?? 0;
    const offsetY = ghostEl._dragOffsetY ?? 0;
    ghostEl.style.left = `${e.clientX - offsetX}px`;
    ghostEl.style.top = `${e.clientY - offsetY}px`;
    // Auto-scroll
    handleAutoScroll(e);
    if (dragState.type === 'card') {
        updateCardDropPosition(e);
    }
    else {
        updateColumnDropPosition(e);
    }
}
// ─── Card Drop Position Update ───────────────────────────────────────
function updateCardDropPosition(e) {
    if (!placeholderEl || !dragState)
        return;
    // Hide empty zone indicators temporarily for hit testing
    const allLists = document.querySelectorAll('.card-list');
    let targetColumnId = undefined;
    let targetIndex = -1;
    for (const list of allLists) {
        const rect = list.getBoundingClientRect();
        // Check if cursor is within this column's bounds (with some padding)
        if (e.clientX >= rect.left - 20 && e.clientX <= rect.right + 20 &&
            e.clientY >= rect.top - 10 && e.clientY <= rect.bottom + 10) {
            targetColumnId = list.dataset.columnId;
            // Find visible cards in this column (excluding hidden ones and the dragged card)
            const cards = [...list.querySelectorAll('.card:not(.drag-source-hidden):not(.card-hidden)')];
            if (cards.length === 0) {
                // Empty column or all filtered - append at end
                targetIndex = getColumnCardCount(targetColumnId);
            }
            else {
                // Find the nearest card to determine insertion point
                let bestIdx = cards.length; // default: after last
                for (let i = 0; i < cards.length; i++) {
                    const cardRect = cards[i].getBoundingClientRect();
                    const midY = cardRect.top + cardRect.height / 2;
                    if (e.clientY < midY) {
                        bestIdx = i;
                        break;
                    }
                }
                // Map visible index to model index
                targetIndex = mapVisibleToModelIndex(targetColumnId, bestIdx);
            }
            break;
        }
    }
    if (!targetColumnId)
        return;
    // Move placeholder to the right position
    const targetList = document.querySelector(`.card-list[data-column-id="${targetColumnId}"]`);
    if (!targetList)
        return;
    // Get all visible cards in this list (excluding hidden and drag source)
    const visibleCards = [...targetList.querySelectorAll('.card:not(.drag-source-hidden):not(.card-hidden)')];
    let insertBefore = null;
    if (targetIndex < visibleCards.length && targetIndex >= 0) {
        // Find the card at this model index among visible cards
        const model = State.getBoard();
        const col = model.columns.find(c => c.id === targetColumnId);
        if (col) {
            let visCount = 0;
            for (const vc of visibleCards) {
                const vcCardId = vc.dataset.cardId;
                const modelIdx = col.cardIds.indexOf(vcCardId);
                if (modelIdx === targetIndex) {
                    insertBefore = vc;
                    break;
                }
                visCount++;
            }
        }
    }
    // If we couldn't find a specific card, use the visible index approach
    if (!insertBefore && targetIndex < visibleCards.length) {
        insertBefore = visibleCards[targetIndex];
    }
    else if (!insertBefore) {
        insertBefore = null; // append at end
    }
    // Move placeholder
    const emptyZone = targetList.querySelector('.empty-column-zone');
    if (visibleCards.length === 0 && !insertBefore) {
        // Empty column - place before empty zone
        if (emptyZone) {
            targetList.insertBefore(placeholderEl, emptyZone);
        }
        else {
            targetList.appendChild(placeholderEl);
        }
    }
    else if (insertBefore) {
        targetList.insertBefore(placeholderEl, insertBefore);
    }
    else {
        // After last card but before empty zone
        if (emptyZone) {
            targetList.insertBefore(placeholderEl, emptyZone);
        }
        else {
            targetList.appendChild(placeholderEl);
        }
    }
}
function getColumnCardCount(columnId) {
    const model = State.getBoard();
    const col = model.columns.find(c => c.id === columnId);
    return col ? col.cardIds.length : 0;
}
function mapVisibleToModelIndex(columnId, visibleIndex) {
    const model = State.getBoard();
    const col = model.columns.find(c => c.id === columnId);
    if (!col)
        return visibleIndex;
    let visCount = 0;
    for (const cardId of col.cardIds) {
        // Skip the dragged card itself
        if (dragState?.cardId === cardId && dragState.sourceColumnId === columnId)
            continue;
        const card = model.cards[cardId];
        if (!card)
            continue;
        // Check visibility based on filters
        if (!isCardVisibleByFilter(card))
            continue;
        if (visCount === visibleIndex)
            return col.cardIds.indexOf(cardId);
        visCount++;
    }
    return Math.min(visibleIndex, col.cardIds.length - 1);
}
function isCardVisibleByFilter(card) {
    const filter = State.getFilter();
    if (filter.searchQuery.trim()) {
        const q = filter.searchQuery.toLowerCase();
        if (!card.title.toLowerCase().includes(q))
            return false;
    }
    if (filter.activeLabels.size > 0) {
        const hasActiveLabel = card.labels.some(l => filter.activeLabels.has(l));
        if (!hasActiveLabel)
            return false;
    }
    return true;
}
// ─── Column Drop Position Update ─────────────────────────────────────
function updateColumnDropPosition(e) {
    const wrappers = document.querySelectorAll('.column-wrapper:not(.drag-source-hidden)');
    for (const wrapper of wrappers) {
        const rect = wrapper.getBoundingClientRect();
        const midX = rect.left + rect.width / 2;
        if (Math.abs(e.clientX - midX) < rect.width / 2) {
            // Highlight this column as drop target
            wrapper.classList.add('column-drop-target');
            // Move placeholder to show insertion point
            if (!placeholderEl) {
                placeholderEl = h('div', { class: 'column-placeholder' });
            }
            const board = document.getElementById('board');
            if (e.clientX < midX) {
                board.insertBefore(placeholderEl, wrapper);
            }
            else {
                const next = wrapper.nextElementSibling;
                if (next && next.classList.contains('column-wrapper')) {
                    board.insertBefore(placeholderEl, next);
                }
                else {
                    board.appendChild(placeholderEl);
                }
            }
            // Remove highlight from others
            wrappers.forEach(w => {
                if (w !== wrapper)
                    w.classList.remove('column-drop-target');
            });
            break;
        }
    }
}
// ─── Mouse Up ────────────────────────────────────────────────────────
function onMouseUp(e) {
    if (!dragState)
        return;
    // Clean up auto-scroll
    if (autoScrollRAF) {
        cancelAnimationFrame(autoScrollRAF);
        autoScrollRAF = 0;
    }
    if (dragState.type === 'card') {
        commitCardDrop(e);
    }
    else {
        commitColumnDrop(e);
    }
    cleanupDrag();
}
// ─── Commit Card Drop ────────────────────────────────────────────────
function commitCardDrop(_e) {
    if (!dragState || !placeholderEl)
        return;
    const cardId = dragState.cardId;
    const sourceColumnId = dragState.sourceColumnId;
    const sourceIndex = dragState.sourceIndex;
    // Find where placeholder is now
    const targetList = placeholderEl.parentNode;
    if (!targetList) {
        // No valid drop target - cancel
        return;
    }
    const targetColumnId = targetList.dataset.columnId;
    if (!targetColumnId)
        return;
    // Calculate target index based on placeholder position among visible cards
    const allCardsInTarget = [...targetList.querySelectorAll('.card:not(.drag-source-hidden):not(.card-hidden)')];
    let targetIndex = allCardsInTarget.length; // default: append at end
    for (let i = 0; i < allCardsInTarget.length; i++) {
        if (allCardsInTarget[i] === placeholderEl ||
            (placeholderEl && allCardsInTarget[i].previousElementSibling === placeholderEl)) {
            targetIndex = i;
            break;
        }
    }
    // Map visible index to model index
    const model = State.getBoard();
    const targetCol = model.columns.find(c => c.id === targetColumnId);
    if (!targetCol)
        return;
    let modelTargetIndex = targetCol.cardIds.length;
    let visCount = 0;
    for (let i = 0; i < targetCol.cardIds.length; i++) {
        const cid = targetCol.cardIds[i];
        if (cid === cardId && sourceColumnId === targetColumnId)
            continue; // skip self in same column
        const card = model.cards[cid];
        if (!card || !isCardVisibleByFilter(card))
            continue;
        if (visCount === targetIndex) {
            modelTargetIndex = i;
            break;
        }
        visCount++;
    }
    // Adjust for same-column drag: if moving after current position, account for removal
    if (sourceColumnId === targetColumnId && sourceIndex < modelTargetIndex) {
        modelTargetIndex--;
    }
    // Clamp index
    modelTargetIndex = Math.max(0, Math.min(modelTargetIndex, targetCol.cardIds.length));
    // Execute the move in state
    State.moveCard(cardId, sourceColumnId, targetColumnId, modelTargetIndex);
    (0, persistence_1.scheduleSave)();
    (0, rendering_1.render)();
}
// ─── Commit Column Drop ──────────────────────────────────────────────
function commitColumnDrop(_e) {
    if (!dragState || !placeholderEl)
        return;
    const sourceIndex = dragState.sourceIndex;
    // Find where placeholder is in the board
    const board = document.getElementById('board');
    const children = [...board.querySelectorAll('.column-wrapper:not(.drag-source-hidden)')];
    let targetIndex = children.length;
    for (let i = 0; i < children.length; i++) {
        if (children[i].previousElementSibling === placeholderEl ||
            (i > 0 && children[i - 1] === placeholderEl)) {
            targetIndex = i;
            break;
        }
    }
    // Adjust for removal of source column
    let modelTargetIndex = targetIndex;
    if (sourceIndex < modelTargetIndex) {
        modelTargetIndex--;
    }
    modelTargetIndex = Math.max(0, Math.min(modelTargetIndex, State.getBoard().columns.length - 1));
    State.reorderColumns(sourceIndex, modelTargetIndex);
    (0, persistence_1.scheduleSave)();
    (0, rendering_1.render)();
}
// ─── Cancel Drag (Escape key) ────────────────────────────────────────
function onKeyDown(e) {
    if (e.key === 'Escape' && dragState) {
        cancelDrag();
    }
}
function cancelDrag() {
    // Remove ghost and placeholder - original card stays hidden, render will restore it
    cleanupDrag();
    (0, rendering_1.render)();
}
// ─── Cleanup ─────────────────────────────────────────────────────────
function cleanupDrag() {
    if (ghostEl) {
        ghostEl.remove();
        ghostEl = null;
    }
    if (placeholderEl) {
        placeholderEl.remove();
        placeholderEl = null;
    }
    // Remove drag-source-hidden from all elements
    document.querySelectorAll('.drag-source-hidden').forEach(el => el.classList.remove('drag-source-hidden'));
    document.querySelectorAll('.column-drop-target').forEach(el => el.classList.remove('column-drop-target'));
    if (autoScrollRAF) {
        cancelAnimationFrame(autoScrollRAF);
        autoScrollRAF = 0;
    }
    dragState = null;
}
// ─── Auto-Scroll ─────────────────────────────────────────────────────
function handleAutoScroll(e) {
    if (autoScrollRAF)
        return; // already scrolling
    const margin = types_1.AUTO_SCROLL_MARGIN;
    let scrollX = 0, scrollY = 0;
    // Horizontal board scroll
    if (e.clientX < margin)
        scrollX = -types_1.AUTO_SCROLL_SPEED;
    else if (e.clientX > window.innerWidth - margin)
        scrollX = types_1.AUTO_SCROLL_SPEED;
    // Vertical column scroll
    const lists = document.querySelectorAll('.card-list');
    for (const list of lists) {
        const rect = list.getBoundingClientRect();
        if (e.clientX >= rect.left && e.clientX <= rect.right) {
            if (e.clientY < rect.top + margin && list.scrollTop > 0) {
                scrollY = -types_1.AUTO_SCROLL_SPEED;
            }
            else if (e.clientY > rect.bottom - margin &&
                list.scrollTop < list.scrollHeight - list.clientHeight) {
                scrollY = types_1.AUTO_SCROLL_SPEED;
            }
        }
    }
    // Board horizontal scroll
    const boardContainer = document.getElementById('board-container');
    if (scrollX !== 0) {
        boardContainer.scrollLeft += scrollX;
    }
    // Column vertical scroll
    for (const list of lists) {
        const rect = list.getBoundingClientRect();
        if (e.clientX >= rect.left && e.clientX <= rect.right) {
            if (e.clientY < rect.top + margin && list.scrollTop > 0) {
                list.scrollTop -= types_1.AUTO_SCROLL_SPEED;
            }
            else if (e.clientY > rect.bottom - margin &&
                list.scrollTop < list.scrollHeight - list.clientHeight) {
                list.scrollTop += types_1.AUTO_SCROLL_SPEED;
            }
        }
    }
    // Continue scrolling via rAF
    autoScrollRAF = requestAnimationFrame(() => {
        autoScrollRAF = 0;
        handleAutoScroll(e);
    });
}
// ─── Placeholder Element ─────────────────────────────────────────────
function createPlaceholder(cardEl) {
    const placeholder = h('div', { class: 'drop-placeholder' });
    placeholder.style.height = `${cardEl.offsetHeight}px`;
    return placeholder;
}
// ─── DOM Helper ──────────────────────────────────────────────────────
function h(tag, attrs) {
    const element = document.createElement(tag);
    if (attrs) {
        for (const [key, value] of Object.entries(attrs)) {
            if (value === undefined || value === null)
                continue;
            if (key === 'style') {
                element.style.cssText = String(value);
            }
            else if (key === 'class') {
                element.className = String(value);
            }
            else if (key.startsWith('data-')) {
                const dataKey = key.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
                element.dataset[dataKey] = String(value);
            }
            else if (key.includes('-') || !(key in element)) {
                element.setAttribute(key, String(value));
            }
            else {
                try {
                    element[key] = value;
                }
                catch { }
            }
        }
    }
    return element;
}
};

// ── module: src/persistence.ts ──
__mods["src/persistence.ts"] = function (exports, require, module) {
"use strict";
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 });
exports.save = save;
exports.load = load;
exports.scheduleSave = scheduleSave;
const State = __importStar(require("./state"));
const STORAGE_KEY = 'kanban-board-state';
// ─── Persistence ──────────────────────────────────────────────────────
function save() {
    try {
        localStorage.setItem(STORAGE_KEY, State.serialize());
    }
    catch { /* quota exceeded or private mode */ }
}
function load() {
    try {
        const data = localStorage.getItem(STORAGE_KEY);
        if (data) {
            State.deserialize(data);
            return true;
        }
    }
    catch { /* ignore corrupt data */ }
    return false;
}
// Auto-save on state changes
let saveTimeout = null;
function scheduleSave() {
    if (saveTimeout)
        clearTimeout(saveTimeout);
    saveTimeout = window.setTimeout(save, 200);
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"typescript","sessionId":"b8a80ccd-f68a-444c-adde-48d5f4c864af","tokensIn":5874634,"tokensOut":53138,"tokensTotal":5927772,"turns":106,"toolCalls":105,"failedToolCalls":3,"timestamp":"2026-07-30T23:19:29.247Z"} -->