← Calendar results

Calendar

thinkingcap qwen3.6 27b · typescript

Initial view of Calendar

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 71,977 bytes · SHA-256 a02ff6e59ef3
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar &amp; Event Planner</title>
<style>
/* ── css: layout ── */
/* ─── Reset & Base ───────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: 'Google Sans', 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: #f8f9fa;
  color: #202124;
  line-height: 1.5;
  overflow-x: hidden;
}

#calendar-app {
  max-width: 1200px;
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ─── Header ─────────────────────────────────────────────────── */
#cal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px;
  background: #fff;
  border-bottom: 1px solid #dadce0;
  position: sticky;
  top: 0;
  z-index: 100;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

#cal-title {
  font-size: 22px;
  font-weight: 500;
  margin-left: 4px;
  color: #3c4043;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 4px;
}

button {
  border: none;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  color: #5f6368;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 14px;
  transition: background 0.15s, color 0.15s;
}

button:hover { background: #f1f3f4; }
button:active { background: #e8eaed; }

#btn-today {
  border: 1px solid #dadce0;
  font-weight: 500;
  color: #1a73e8;
}

#btn-today:hover { background: #e8f0fe; }

.view-btn {
  font-weight: 500;
  padding: 8px 16px;
  border-radius: 4px;
}

.view-btn.active {
  color: #1a73e8;
  background: #e8f0fe;
}

#btn-settings {
  font-size: 20px;
  padding: 6px 10px;
  margin-left: 4px;
}

/* ─── Main Content ───────────────────────────────────────────── */
#cal-main {
  flex: 1;
  background: #fff;
  overflow: auto;
}

/* ─── Month View Grid ────────────────────────────────────────── */
.month-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  border-top: 1px solid #dadce0;
  border-left: 1px solid #dadce0;
}

.day-header-cell {
  text-align: center;
  padding: 8px 4px;
  font-size: 11px;
  font-weight: 500;
  color: #70757a;
  text-transform: uppercase;
  border-bottom: 1px solid #dadce0;
  border-right: 1px solid #dadce0;
}

.day-cell {
  min-height: 120px;
  padding: 4px;
  border-bottom: 1px solid #dadce0;
  border-right: 1px solid #dadce0;
  cursor: pointer;
  position: relative;
  transition: background 0.15s;
}

.day-cell:hover { background: #f8f9fa; }

.day-number {
  font-size: 12px;
  font-weight: 500;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  margin-bottom: 2px;
}

.day-cell.other-month .day-number { color: #bbb; }
.day-cell.today .day-number { background: #1a73e8; color: #fff; font-weight: 700; }

/* ─── Event Chips (Month View) ──────────────────────────────── */
.event-chip {
  display: block;
  padding: 2px 6px;
  margin-bottom: 1px;
  border-radius: 3px;
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  border-left: 3px solid;
  background: rgba(0,0,0,0.04);
  transition: filter 0.1s;
}

.event-chip:hover { filter: brightness(0.95); }

.event-more {
  font-size: 11px;
  color: #70757a;
  padding: 2px 6px;
  cursor: pointer;
  font-weight: 500;
}

.event-more:hover { text-decoration: underline; }

/* ─── Week View ──────────────────────────────────────────────── */
.week-grid {
  display: grid;
  grid-template-columns: 60px repeat(7, 1fr);
  position: relative;
}

.week-header-row {
  display: contents;
}

.week-day-header {
  text-align: center;
  padding: 8px 4px;
  font-size: 12px;
  font-weight: 500;
  color: #70757a;
  border-bottom: 1px solid #dadce0;
}

.week-day-header .wdh-num {
  display: block;
  width: 36px;
  height: 36px;
  margin: 4px auto 0;
  line-height: 36px;
  border-radius: 50%;
  font-size: 22px;
  color: #3c4043;
}

.week-day-header.today .wdh-num { background: #1a73e8; color: #fff; }

.hour-label {
  text-align: right;
  padding-right: 8px;
  font-size: 11px;
  color: #70757a;
  transform: translateY(-50%);
}

.week-hour-row {
  display: contents;
}

.week-cell {
  border-bottom: 1px solid #e8eaed;
  border-right: 1px solid #e8eaed;
  min-height: 48px;
  position: relative;
}

.week-event-block {
  position: absolute;
  left: 2px;
  right: 2px;
  border-radius: 4px;
  padding: 2px 6px;
  font-size: 11px;
  overflow: hidden;
  cursor: pointer;
  z-index: 5;
  border-left: 3px solid;
}

.week-event-block:hover { filter: brightness(0.95); }

.all-day-strip {
  display: grid;
  grid-template-columns: 60px repeat(7, 1fr);
  border-bottom: 1px solid #dadce0;
}

.all-day-cell {
  padding: 2px 4px;
  min-height: 28px;
  border-right: 1px solid #e8eaed;
}

/* Current time indicator */
.time-indicator {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: #ea4335;
  z-index: 10;
  pointer-events: none;
}

.time-indicator::before {
  content: '';
  position: absolute;
  left: -6px;
  top: -4px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #ea4335;
}

/* ─── Agenda View ────────────────────────────────────────────── */
.agenda-list { padding: 16px 24px; }

.agenda-day-group { margin-bottom: 24px; }

.agenda-date-header {
  font-size: 14px;
  font-weight: 500;
  color: #70757a;
  text-transform: uppercase;
  padding: 8px 0;
  border-bottom: 1px solid #e8eaed;
  margin-bottom: 8px;
}

.agenda-event-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s;
}

.agenda-event-item:hover { background: #f1f3f4; }

.agenda-color-bar {
  width: 4px;
  height: 32px;
  border-radius: 2px;
  flex-shrink: 0;
}

.agenda-event-info { flex: 1; min-width: 0; }

.agenda-event-title { font-size: 14px; font-weight: 500; }

.agenda-event-time { font-size: 12px; color: #70757a; }

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

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

.modal-box {
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 24px rgba(0,0,0,0.2);
  width: 90%;
  max-width: 500px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 24px;
}

.modal-title {
  font-size: 20px;
  font-weight: 500;
  margin-bottom: 16px;
}

.form-group {
  margin-bottom: 12px;
}

.form-group label {
  display: block;
  font-size: 12px;
  color: #70757a;
  margin-bottom: 4px;
  font-weight: 500;
}

.form-group input, .form-group select, .form-group textarea {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid #dadce0;
  border-radius: 4px;
  font-size: 14px;
  font-family: inherit;
}

.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
  outline: none;
  border-color: #1a73e8;
  box-shadow: 0 0 0 2px rgba(26,115,232,0.2);
}

.form-row {
  display: flex;
  gap: 12px;
}

.form-row .form-group { flex: 1; }

.color-options {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.color-swatch {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  cursor: pointer;
  border: 3px solid transparent;
  transition: transform 0.1s, border-color 0.1s;
}

.color-swatch:hover { transform: scale(1.1); }
.color-swatch.selected { border-color: #202124; transform: scale(1.15); }

.modal-actions {
  display: flex;
  justify-content: space-between;
  margin-top: 20px;
}

.btn-primary {
  background: #1a73e8;
  color: #fff;
  font-weight: 500;
  padding: 8px 24px;
}

.btn-primary:hover { background: #1557b0; }

.btn-danger {
  color: #d93025;
  border: 1px solid #dadce0;
}

.btn-danger:hover { background: #fce8e6; }

.error-text {
  color: #d93025;
  font-size: 12px;
  margin-top: 4px;
}

/* ─── Day Popover ────────────────────────────────────────────── */
#day-popover {
  position: fixed;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 24px rgba(0,0,0,0.2);
  width: 300px;
  max-height: 400px;
  overflow-y: auto;
  z-index: 500;
  padding: 16px;
}

#day-popover.hidden { display: none; }

.popover-date {
  font-size: 14px;
  font-weight: 500;
  margin-bottom: 8px;
  color: #3c4043;
}

.popover-event {
  padding: 6px 8px;
  border-radius: 4px;
  margin-bottom: 4px;
  font-size: 12px;
  cursor: pointer;
  border-left: 3px solid;
  background: #f8f9fa;
}

.popover-event:hover { background: #e8eaed; }

/* ─── Toast ──────────────────────────────────────────────────── */
#toast-container {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2000;
}

.toast {
  background: #323232;
  color: #fff;
  padding: 12px 24px;
  border-radius: 8px;
  font-size: 14px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
  display: flex;
  align-items: center;
  gap: 12px;
  animation: toastIn 0.3s ease;
}

.toast button {
  color: #8ab4f8;
  font-weight: 500;
  padding: 4px 8px;
}

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

/* ─── Settings Modal ────────────────────────────────────────── */
.settings-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 0;
  border-bottom: 1px solid #e8eaed;
}

/* ─── Animations ────────────────────────────────────────────── */
.view-transition {
  animation: fadeIn 0.2s ease;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ─── Responsive ────────────────────────────────────────────── */
@media (max-width: 768px) {
  #cal-header { flex-wrap: wrap; gap: 8px; padding: 8px; }
  .header-left, .header-right { width: 100%; justify-content: center; }
  #cal-title { font-size: 18px; }
  .day-cell { min-height: 80px; }
  .event-chip { font-size: 10px; padding: 1px 4px; }
}

/* ─── Dragging ──────────────────────────────────────────────── */
.dragging { opacity: 0.5 !important; }
.drag-over { background: #e8f0fe !important; }
</style>
</head>
<body>
<div id="calendar-app">
  <header id="cal-header">
    <div class="header-left">
      <button id="btn-today" title="Today">Today</button>
      <button id="btn-prev" title="Previous">&lt;</button>
      <button id="btn-next" title="Next">&gt;</button>
      <h1 id="cal-title"></h1>
    </div>
    <div class="header-right">
      <button id="btn-view-month" class="view-btn active">Month</button>
      <button id="btn-view-week" class="view-btn">Week</button>
      <button id="btn-view-agenda" class="view-btn">Agenda</button>
      <button id="btn-settings" title="Settings">⚙</button>
    </div>
  </header>
  <main id="cal-main"></main>
  <!-- Modal overlay -->
  <div id="modal-overlay" class="hidden">
    <div id="modal-content"></div>
  </div>
  <!-- Day popover -->
  <div id="day-popover" class="hidden"></div>
  <!-- Toast notifications -->
  <div id="toast-container"></div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./state":"src/state.ts","./render":"src/render.ts","./modal":"src/modal.ts","./date-util":"src/date-util.ts"},"src/date-util.ts":{"./constants":"src/constants.ts"},"src/state.ts":{"./constants":"src/constants.ts","./date-util":"src/date-util.ts"},"src/render.ts":{"./state":"src/state.ts","./date-util":"src/date-util.ts","./constants":"src/constants.ts"},"src/modal.ts":{"./constants":"src/constants.ts","./date-util":"src/date-util.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";
// ─── Entry Point: Wire up navigation, events, drag-drop ──────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.reRender = reRender;
const state_1 = require("./state");
const render_1 = require("./render");
const modal_1 = require("./modal");
const date_util_1 = require("./date-util");
// ─── Global re-render hook (used by modal.ts) ──────────────────
function reRender() {
    const vs = (0, state_1.getViewState)();
    switch (vs.mode) {
        case 'month':
            (0, render_1.renderMonthView)();
            break;
        case 'week':
            (0, render_1.renderWeekView)();
            break;
        case 'agenda':
            (0, render_1.renderAgendaView)();
            break;
    }
}
// ─── Init ──────────────────────────────────────────────────────
(0, state_1.loadState)();
reRender();
// ─── Navigation Buttons ────────────────────────────────────────
document.getElementById('btn-prev').addEventListener('click', () => {
    const vs = (0, state_1.getViewState)();
    if (vs.mode === 'month' || vs.mode === 'agenda') {
        let m = vs.currentMonth - 1;
        let y = vs.currentYear;
        if (m < 0) {
            m = 11;
            y--;
        }
        (0, state_1.setViewState)({ currentMonth: m, currentYear: y });
    }
    else {
        const d = (0, date_util_1.addDays)(vs.currentYear, vs.currentMonth, 1, -7);
        (0, state_1.setViewState)({ currentYear: d.year, currentMonth: d.month });
    }
    reRender();
});
document.getElementById('btn-next').addEventListener('click', () => {
    const vs = (0, state_1.getViewState)();
    if (vs.mode === 'month' || vs.mode === 'agenda') {
        let m = vs.currentMonth + 1;
        let y = vs.currentYear;
        if (m > 11) {
            m = 0;
            y++;
        }
        (0, state_1.setViewState)({ currentMonth: m, currentYear: y });
    }
    else {
        const d = (0, date_util_1.addDays)(vs.currentYear, vs.currentMonth, 1, 7);
        (0, state_1.setViewState)({ currentYear: d.year, currentMonth: d.month });
    }
    reRender();
});
document.getElementById('btn-today').addEventListener('click', () => {
    const t = (0, date_util_1.today)();
    (0, state_1.setViewState)({ currentYear: t.year, currentMonth: t.month });
    reRender();
});
// ─── View Switching ──────────────────────────────────────────
const viewBtns = document.querySelectorAll('.view-btn');
viewBtns.forEach(btn => {
    btn.addEventListener('click', () => {
        viewBtns.forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        const mode = btn.id.replace('btn-view-', '');
        (0, state_1.setViewState)({ mode });
        reRender();
    });
});
// ─── Settings Button ──────────────────────────────────────────
document.getElementById('btn-settings').addEventListener('click', () => {
    (0, modal_1.openSettingsModal)();
});
// ─── Event Delegation: Click on day cells, event chips ────────
const mainEl = document.getElementById('cal-main');
mainEl.addEventListener('click', (e) => {
    const target = e.target;
    // Check if clicked an event chip/block
    const eventChip = target.closest('[data-event-id]');
    console.log('Click: target=', target.tagName, 'eventChip=', !!eventChip);
    if (eventChip && eventChip.dataset.eventId) {
        e.stopPropagation();
        const eventId = eventChip.dataset.eventId;
        console.log('eventId:', eventId);
        const baseEv = (0, state_1.getEventById)(eventId);
        console.log('baseEv:', baseEv ? 'found' : 'not found', !!baseEv?.recurrence);
        if (!baseEv)
            return;
        // If recurring, find the occurrence date from context
        let ev = baseEv;
        if (baseEv.recurrence) {
            const dayCell = eventChip.closest('[data-date-key]');
            console.log('dayCell:', !!dayCell);
            if (dayCell && dayCell.dataset.dateKey) {
                const key = dayCell.dataset.dateKey;
                const [y, m, d] = key.split('-').map(Number);
                ev = { ...baseEv, _occurrenceDate: { year: y, month: m - 1, day: d } };
            }
        }
        console.log('ev.recurrence:', !!ev.recurrence);
        if (ev.recurrence) {
            console.log('Opening recurrence edit options');
            (0, modal_1.showRecurrenceEditOptions)(ev);
        }
        else {
            console.log('Opening regular editor');
            (0, modal_1.openEventEditor)(ev);
        }
        return;
    }
    // Check if clicked a day cell (month view) — not on an event chip
    const dayCell = target.closest('.day-cell');
    if (dayCell && !eventChip) {
        e.stopPropagation();
        if (dayCell.dataset.dateKey) {
            const [y, m, d] = dayCell.dataset.dateKey.split('-').map(Number);
            (0, modal_1.openEventEditor)(undefined, { year: y, month: m - 1, day: d });
        }
        return;
    }
    // Check "+more" popover trigger
    const moreBtn = target.closest('.event-more');
    if (moreBtn && moreBtn.dataset.dateKey) {
        e.stopPropagation();
        showDayPopover(moreBtn);
        return;
    }
});
// Close popover on outside click
document.addEventListener('click', (e) => {
    const popover = document.getElementById('day-popover');
    if (!popover.contains(e.target)) {
        popover.classList.add('hidden');
    }
});
// ─── Day Popover ──────────────────────────────────────────────
function showDayPopover(trigger) {
    const key = trigger.dataset.dateKey;
    if (!key)
        return;
    const [y, m, d] = key.split('-').map(Number);
    const ymd = { year: y, month: m - 1, day: d };
    const events = (0, state_1.getEventsForDate)(ymd);
    const popover = document.getElementById('day-popover');
    let html = `<div class="popover-date">${formatDateShortLocal(ymd)}</div>`;
    for (const ev of events) {
        const timeStr = ev.allDay ? 'All day' : `${minsToStr(ev.startTime)} – ${minsToStr(ev.endTime)}`;
        html += `<div class="popover-event" style="border-left-color:${ev.color};" data-event-id="${ev.id}" data-date-key="${key}">`;
        html += `<strong>${escapeHtml(ev.title)}</strong><br><span style="color:#70757a;">${timeStr}</span>`;
        html += '</div>';
    }
    popover.innerHTML = html;
    popover.classList.remove('hidden');
    const rect = trigger.getBoundingClientRect();
    popover.style.top = (rect.bottom + 4) + 'px';
    popover.style.left = Math.min(rect.left, window.innerWidth - 320) + 'px';
}
function formatDateShortLocal(ymd) {
    const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    return `${months[ymd.month]} ${ymd.day}, ${ymd.year}`;
}
function minsToStr(mins) {
    const h = Math.floor(mins / 60);
    const m = mins % 60;
    return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}
function escapeHtml(s) {
    const div = document.createElement('div');
    div.textContent = s;
    return div.innerHTML;
}
// ─── Drag-to-Reschedule (Month View) ──────────────────────────
let dragState = null;
mainEl.addEventListener('dragstart', (e) => {
    const chip = e.target.closest('.event-chip');
    if (!chip || !chip.dataset.eventId)
        return;
    const dayCell = chip.closest('.day-cell');
    if (!dayCell)
        return;
    dragState = { eventId: chip.dataset.eventId, sourceKey: dayCell.dataset.dateKey ?? '' };
    e.target.classList.add('dragging');
    e.dataTransfer.effectAllowed = 'move';
});
mainEl.addEventListener('dragend', () => {
    dragState = null;
    mainEl.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
});
mainEl.addEventListener('dragover', (e) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    const cell = e.target.closest('.day-cell');
    if (cell) {
        mainEl.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
        cell.classList.add('drag-over');
    }
});
mainEl.addEventListener('drop', (e) => {
    e.preventDefault();
    const cell = e.target.closest('.day-cell');
    if (!cell || !dragState)
        return;
    mainEl.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
    const targetKey = cell.dataset.dateKey;
    if (!targetKey || targetKey === dragState.sourceKey) {
        dragState = null;
        return;
    }
    const [y, m, d] = targetKey.split('-').map(Number);
    (0, state_1.updateEvent)(dragState.eventId, { date: { year: y, month: m - 1, day: d } });
    dragState = null;
    reRender();
});
// Make event chips draggable on mouseover (lazy init)
mainEl.addEventListener('mouseover', (e) => {
    const chip = e.target.closest('.event-chip');
    if (chip && !chip.hasAttribute('draggable')) {
        chip.setAttribute('draggable', 'true');
    }
});
// ─── Drag-to-Reschedule (Week View — vertical time change) ────
let weekDragState = null;
mainEl.addEventListener('mousedown', (e) => {
    const block = e.target.closest('.week-event-block');
    if (!block || !block.dataset.eventId)
        return;
    e.preventDefault();
    const ev = (0, state_1.getEventById)(block.dataset.eventId);
    if (!ev || ev.allDay)
        return;
    weekDragState = {
        eventId: block.dataset.eventId,
        startY: e.clientY,
        origStartMin: ev.startTime,
        origEndMin: ev.endTime,
    };
    document.addEventListener('mousemove', onWeekDragMove);
    document.addEventListener('mouseup', onWeekDragUp);
});
function onWeekDragMove(e) {
    if (!weekDragState)
        return;
    const dy = e.clientY - weekDragState.startY;
    // 48px per hour → 12px per 15 min snap
    const snapPx = 12;
    const snaps = Math.round(dy / snapPx);
    const deltaMin = snaps * 15;
    let newStart = weekDragState.origStartMin + deltaMin;
    let newEnd = weekDragState.origEndMin + deltaMin;
    if (newStart < 0) {
        newStart = 0;
        newEnd = Math.max(weekDragState.origEndMin - weekDragState.origStartMin, 15);
    }
    if (newEnd > 1439) {
        newEnd = 1439;
        newStart = Math.min(newEnd - 15, 1424);
    }
    (0, state_1.updateEvent)(weekDragState.eventId, { startTime: newStart, endTime: newEnd });
}
function onWeekDragUp() {
    weekDragState = null;
    document.removeEventListener('mousemove', onWeekDragMove);
    document.removeEventListener('mouseup', onWeekDragUp);
    reRender();
}
// ─── Keyboard Navigation ──────────────────────────────────────
document.addEventListener('keydown', (e) => {
    if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLSelectElement)
        return;
    switch (e.key) {
        case 'ArrowLeft':
            document.getElementById('btn-prev').click();
            break;
        case 'ArrowRight':
            document.getElementById('btn-next').click();
            break;
        case 't':
        case 'T':
            document.getElementById('btn-today').click();
            break;
    }
});
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
// ─── Tunable Constants ───────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.STORAGE_KEY_VIEW = exports.STORAGE_KEY_SETTINGS = exports.STORAGE_KEY_EVENTS = exports.DAY_NAMES_MON = exports.DAY_NAMES_SUN = exports.MONTH_NAMES = exports.DEFAULT_WEEK_START = exports.SNAP_MINUTES = exports.HOUR_END = exports.HOUR_START = exports.EVENT_COLORS = void 0;
exports.EVENT_COLORS = [
    '#3B82F6', // blue
    '#EF4444', // red
    '#10B981', // green
    '#F59E0B', // amber
    '#8B5CF6', // purple
    '#EC4899', // pink
    '#06B6D4', // cyan
    '#84CC16', // lime
];
exports.HOUR_START = 6; // first hour row in week view
exports.HOUR_END = 22; // last hour row in week view
exports.SNAP_MINUTES = 15; // drag snap interval in minutes
exports.DEFAULT_WEEK_START = 0; // 0 = Sunday, 1 = Monday
exports.MONTH_NAMES = [
    'January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'
];
exports.DAY_NAMES_SUN = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
exports.DAY_NAMES_MON = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
exports.STORAGE_KEY_EVENTS = 'cal_events';
exports.STORAGE_KEY_SETTINGS = 'cal_settings';
exports.STORAGE_KEY_VIEW = 'cal_view_state';
};

// ── module: src/date-util.ts ──
__mods["src/date-util.ts"] = function (exports, require, module) {
"use strict";
// ─── Pure Date Utilities (no rendering, no side effects) ──────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLeapYear = isLeapYear;
exports.daysInMonth = daysInMonth;
exports.dayOfWeek = dayOfWeek;
exports.normalizeYMD = normalizeYMD;
exports.addDays = addDays;
exports.addMonths = addMonths;
exports.addYears = addYears;
exports.sameDay = sameDay;
exports.compareYMD = compareYMD;
exports.inRange = inRange;
exports.today = today;
exports.nowMinutes = nowMinutes;
exports.buildMonthGrid = buildMonthGrid;
exports.getWeekDays = getWeekDays;
exports.expandRecurrence = expandRecurrence;
exports.minutesToTimeString = minutesToTimeString;
exports.timeStringToMinutes = timeStringToMinutes;
exports.formatMonthYear = formatMonthYear;
exports.formatDateShort = formatDateShort;
const constants_1 = require("./constants");
// ─── Core calendar math ──────────────────────────────────────────────
/** Is the given year a leap year? Handles century rule. */
function isLeapYear(year) {
    return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
/** Number of days in month m (0-based) of the given year. */
function daysInMonth(year, month) {
    if (month === 1)
        return isLeapYear(year) ? 29 : 28;
    const table = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    return table[month];
}
/** Day of week for a given date: 0 = Sunday … 6 = Saturday. */
function dayOfWeek(year, month, day) {
    // Tomohiko Sakamoto's algorithm — works for any Gregorian date
    const t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
    let y = year;
    if (month < 2)
        y--;
    return (y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) + t[month] + day) % 7;
}
/** Normalize a YMD — clamp day to valid range, carry overflows. */
function normalizeYMD(year, month, day) {
    while (month < 0) {
        month += 12;
        year--;
    }
    while (month > 11) {
        month -= 12;
        year++;
    }
    const max = daysInMonth(year, month);
    if (day < 1)
        day = 1;
    if (day > max)
        day = max;
    return { year, month, day };
}
// ─── Date arithmetic on components (never uses ms offsets) ──────────
/** Add n days to a YMD. Positive or negative. */
function addDays(year, month, day, n) {
    let y = year, m = month, d = day;
    if (n >= 0) {
        for (let i = 0; i < n; i++) {
            d++;
            if (d > daysInMonth(y, m)) {
                d = 1;
                m++;
                if (m > 11) {
                    m = 0;
                    y++;
                }
            }
        }
    }
    else {
        for (let i = 0; i < -n; i++) {
            d--;
            if (d < 1) {
                m--;
                if (m < 0) {
                    m = 11;
                    y--;
                }
                d = daysInMonth(y, m);
            }
        }
    }
    return { year: y, month: m, day: d };
}
/** Add n months to a YMD. Clamps day to valid range of target month. */
function addMonths(year, month, day, n) {
    let m = month + n;
    let y = year;
    while (m < 0) {
        m += 12;
        y--;
    }
    while (m > 11) {
        m -= 12;
        y++;
    }
    const d = Math.min(day, daysInMonth(y, m));
    return { year: y, month: m, day: d };
}
/** Add n years. Clamps Feb 29 → Feb 28 in non-leap years. */
function addYears(year, month, day, n) {
    const y = year + n;
    const d = Math.min(day, daysInMonth(y, month));
    return { year: y, month, day: d };
}
// ─── Comparison & equality ──────────────────────────────────────────
function sameDay(a, b) {
    return a.year === b.year && a.month === b.month && a.day === b.day;
}
/** -1 if a < b, +1 if a > b, 0 if equal. */
function compareYMD(a, b) {
    if (a.year !== b.year)
        return a.year - b.year;
    if (a.month !== b.month)
        return a.month - b.month;
    return a.day - b.day;
}
/** Is date d within [start, end] inclusive? */
function inRange(d, start, end) {
    return compareYMD(d, start) >= 0 && compareYMD(d, end) <= 0;
}
// ─── Today ──────────────────────────────────────────────────────────
function today() {
    const d = new Date();
    return { year: d.getFullYear(), month: d.getMonth(), day: d.getDate() };
}
/** Current time in minutes since midnight. */
function nowMinutes() {
    const d = new Date();
    return d.getHours() * 60 + d.getMinutes();
}
/** Build the 42-cell (6×7) month grid for a given year/month. */
function buildMonthGrid(year, month, weekStart) {
    const todayYMD = today();
    const firstDow = dayOfWeek(year, month, 1);
    // Adjust to week start: if weekStart=1 (Mon), shift so Mon=0
    let offset = firstDow - weekStart;
    if (offset < 0)
        offset += 7;
    const grid = [];
    const dim = daysInMonth(year, month);
    // Leading days from previous month
    for (let i = 0; i < offset; i++) {
        const prevDate = addDays(year, month, 1, -(offset - i));
        grid.push({ ymd: prevDate, isCurrentMonth: false, isToday: sameDay(prevDate, todayYMD) });
    }
    // Current month days
    for (let d = 1; d <= dim; d++) {
        const ymd = { year, month, day: d };
        grid.push({ ymd, isCurrentMonth: true, isToday: sameDay(ymd, todayYMD) });
    }
    // Trailing days from next month
    while (grid.length < 42) {
        const nextDate = addDays(year, month, dim, grid.length - offset - dim + 1);
        grid.push({ ymd: nextDate, isCurrentMonth: false, isToday: sameDay(nextDate, todayYMD) });
    }
    return grid;
}
// ─── Week helpers ──────────────────────────────────────────────────
/** Get the 7 days of the week containing the given date. */
function getWeekDays(ymd, weekStart) {
    const dow = dayOfWeek(ymd.year, ymd.month, ymd.day);
    let offset = dow - weekStart;
    if (offset < 0)
        offset += 7;
    return Array.from({ length: 7 }, (_, i) => addDays(ymd.year, ymd.month, ymd.day, i - offset));
}
/** Expand a recurrence rule into dates within [rangeStart, rangeEnd]. */
function expandRecurrence(baseDate, rule, rangeStart, rangeEnd) {
    if (!rule)
        return [baseDate];
    const dates = [];
    const interval = rule.interval ?? 1;
    let current = baseDate;
    let emitted = 0;
    // Safety cap to prevent infinite loops on bad rules
    const maxIterations = 5000;
    let iterations = 0;
    while (iterations < maxIterations) {
        iterations++;
        if (compareYMD(current, rangeStart) >= 0 && compareYMD(current, rangeEnd) <= 0) {
            dates.push({ ...current });
            emitted++;
        }
        if (rule.count !== undefined && emitted >= rule.count)
            break;
        if (compareYMD(current, rangeEnd) > 0)
            break;
        // Advance to next occurrence
        switch (rule.frequency) {
            case 'daily':
                current = addDays(current.year, current.month, current.day, interval);
                break;
            case 'weekly': {
                const weekdays = rule.weekdays ?? [dayOfWeek(baseDate.year, baseDate.month, baseDate.day)];
                // Advance by interval weeks, then find the next matching weekday
                let candidate = addDays(current.year, current.month, current.day, 7 * interval);
                for (let tryCount = 0; tryCount < 7 * interval + 1 && iterations < maxIterations; tryCount++) {
                    const dow = dayOfWeek(candidate.year, candidate.month, candidate.day);
                    if (weekdays.includes(dow))
                        break;
                    candidate = addDays(candidate.year, candidate.month, candidate.day, 1);
                    iterations++;
                }
                current = candidate;
                break;
            }
            case 'monthly': {
                // Monthly: same day-of-month. Skip months where the day doesn't exist (e.g., Jan 31 → skip Feb).
                let nextMonth = addMonths(current.year, current.month, current.day, interval);
                if (nextMonth.day !== baseDate.day) {
                    // The target month doesn't have this day — skip it entirely
                    continue;
                }
                current = nextMonth;
                break;
            }
            case 'yearly': {
                let nextYear = addYears(current.year, current.month, current.day, interval);
                if (nextYear.day !== baseDate.day) {
                    // e.g., Feb 29 in non-leap year — skip
                    continue;
                }
                current = nextYear;
                break;
            }
        }
    }
    return dates;
}
// ─── Time helpers ──────────────────────────────────────────────────
function minutesToTimeString(mins) {
    const h = Math.floor(mins / 60);
    const m = mins % 60;
    return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}
function timeStringToMinutes(s) {
    const [h, m] = s.split(':').map(Number);
    return h * 60 + (m || 0);
}
// ─── Formatting ────────────────────────────────────────────────────
function formatMonthYear(year, month) {
    return `${constants_1.MONTH_NAMES[month]} ${year}`;
}
function formatDateShort(ymd) {
    return `${constants_1.MONTH_NAMES[ymd.month].slice(0, 3)} ${ymd.day}`;
}
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// ─── State: Events, Settings, Persistence ────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadState = loadState;
exports.getEvents = getEvents;
exports.getSettings = getSettings;
exports.getViewState = getViewState;
exports.setViewState = setViewState;
exports.updateSettings = updateSettings;
exports.createEvent = createEvent;
exports.updateEvent = updateEvent;
exports.deleteEvent = deleteEvent;
exports.getEventById = getEventById;
exports.getEventsForDate = getEventsForDate;
exports.getEventsInRange = getEventsInRange;
const date_util_1 = require("./date-util");
// ─── State ──────────────────────────────────────────────────────────
let events = [];
let settings = { weekStart: 0 };
let viewState = { mode: 'month', currentYear: (0, date_util_1.today)().year, currentMonth: (0, date_util_1.today)().month };
// ─── Persistence ────────────────────────────────────────────────────
const constants_1 = require("./constants");
function saveEvents() {
    try {
        localStorage.setItem(constants_1.STORAGE_KEY_EVENTS, JSON.stringify(events));
    }
    catch { }
}
function saveSettings() {
    try {
        localStorage.setItem(constants_1.STORAGE_KEY_SETTINGS, JSON.stringify(settings));
    }
    catch { }
}
function saveViewState() {
    try {
        localStorage.setItem(constants_1.STORAGE_KEY_VIEW, JSON.stringify(viewState));
    }
    catch { }
}
function loadState() {
    try {
        const ev = localStorage.getItem(constants_1.STORAGE_KEY_EVENTS);
        if (ev)
            events = JSON.parse(ev);
    }
    catch {
        events = [];
    }
    try {
        const st = localStorage.getItem(constants_1.STORAGE_KEY_SETTINGS);
        if (st)
            settings = JSON.parse(st);
    }
    catch { }
    try {
        const vs = localStorage.getItem(constants_1.STORAGE_KEY_VIEW);
        if (vs)
            viewState = JSON.parse(vs);
    }
    catch { }
}
// ─── Accessors ──────────────────────────────────────────────────────
function getEvents() { return events; }
function getSettings() { return settings; }
function getViewState() { return viewState; }
function setViewState(vs) {
    Object.assign(viewState, vs);
    saveViewState();
}
function updateSettings(s) {
    Object.assign(settings, s);
    saveSettings();
}
// ─── Event CRUD ─────────────────────────────────────────────────────
function genId() {
    return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}
function createEvent(data) {
    const ev = { ...data, id: genId() };
    events.push(ev);
    saveEvents();
    return ev;
}
function updateEvent(id, data) {
    const idx = events.findIndex(e => e.id === id);
    if (idx < 0)
        return null;
    Object.assign(events[idx], data);
    saveEvents();
    return events[idx];
}
function deleteEvent(id) {
    const idx = events.findIndex(e => e.id === id);
    if (idx < 0)
        return false;
    events.splice(idx, 1);
    saveEvents();
    return true;
}
function getEventById(id) {
    return events.find(e => e.id === id);
}
// ─── Query helpers ──────────────────────────────────────────────────
/** Get all event occurrences on a given date (expanding recurrences). */
function getEventsForDate(date, rangeStart, rangeEnd) {
    const rs = rangeStart ?? date;
    const re = rangeEnd ?? date;
    const results = [];
    for (const ev of events) {
        // Non-recurring or single occurrence on this date
        if (!ev.recurrence && (0, date_util_1.sameDay)(ev.date, date)) {
            results.push({ ...ev });
            continue;
        }
        // Recurring event — expand within range
        if (ev.recurrence) {
            const dates = (0, date_util_1.expandRecurrence)(ev.date, ev.recurrence, rs, re);
            for (const d of dates) {
                if ((0, date_util_1.sameDay)(d, date)) {
                    // Check exceptions
                    if (!ev.exceptionIds?.some(eid => eid === ev.id + '@' + ymdKey(d))) {
                        results.push({ ...ev, _occurrenceDate: d });
                    }
                    break;
                }
            }
        }
    }
    return results.sort((a, b) => (a.allDay ? -1 : 0) || (a.startTime - b.startTime));
}
function ymdKey(ymd) {
    return `${ymd.year}-${String(ymd.month + 1).padStart(2, '0')}-${String(ymd.day).padStart(2, '0')}`;
}
/** Get all events in a date range (for agenda view). */
function getEventsInRange(start, end) {
    const results = [];
    for (const ev of events) {
        if (!ev.recurrence) {
            if ((0, date_util_1.compareYMD)(ev.date, start) >= 0 && (0, date_util_1.compareYMD)(ev.date, end) <= 0) {
                results.push({ ...ev });
            }
        }
        else {
            const dates = (0, date_util_1.expandRecurrence)(ev.date, ev.recurrence, start, end);
            for (const d of dates) {
                if (!ev.exceptionIds?.some(eid => eid === ev.id + '@' + ymdKey(d))) {
                    results.push({ ...ev, _occurrenceDate: d });
                }
            }
        }
    }
    return results;
}
};

// ── module: src/render.ts ──
__mods["src/render.ts"] = function (exports, require, module) {
"use strict";
// ─── Rendering: Month, Week, Agenda Views ──────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderMonthView = renderMonthView;
exports.renderWeekView = renderWeekView;
exports.renderAgendaView = renderAgendaView;
const state_1 = require("./state");
const date_util_1 = require("./date-util");
const constants_1 = require("./constants");
const mainEl = document.getElementById('cal-main');
const titleEl = document.getElementById('cal-title');
// ─── Month View ──────────────────────────────────────────────────
function renderMonthView() {
    const vs = (0, state_1.getViewState)();
    const ws = (0, state_1.getSettings)().weekStart;
    const grid = (0, date_util_1.buildMonthGrid)(vs.currentYear, vs.currentMonth, ws);
    const dayNames = ws === 0 ? constants_1.DAY_NAMES_SUN : constants_1.DAY_NAMES_MON;
    titleEl.textContent = (0, date_util_1.formatMonthYear)(vs.currentYear, vs.currentMonth);
    let html = '<div class="month-grid view-transition">';
    // Day headers
    for (const name of dayNames) {
        html += `<div class="day-header-cell">${name}</div>`;
    }
    // Grid cells
    for (const cell of grid) {
        const isToday = cell.isToday;
        const classes = ['day-cell'];
        if (!cell.isCurrentMonth)
            classes.push('other-month');
        if (isToday)
            classes.push('today');
        html += `<div class="${classes.join(' ')}" data-date-key="${cell.ymd.year}-${String(cell.ymd.month + 1).padStart(2, '0')}-${String(cell.ymd.day).padStart(2, '0')}">`;
        html += `<div class="day-number">${cell.ymd.day}</div>`;
        // Events for this day
        const events = (0, state_1.getEventsForDate)(cell.ymd);
        const maxShow = 3;
        for (let i = 0; i < Math.min(events.length, maxShow); i++) {
            const ev = events[i];
            const color = ev.color;
            html += `<span class="event-chip" style="border-left-color:${color};background:${color}22;color:#333;" data-event-id="${ev.id}" title="${escapeHtml(ev.title)}">${escapeHtml(ev.title)}</span>`;
        }
        if (events.length > maxShow) {
            html += `<div class="event-more" data-date-key="${cell.ymd.year}-${String(cell.ymd.month + 1).padStart(2, '0')}-${String(cell.ymd.day).padStart(2, '0')}">+${events.length - maxShow} more</div>`;
        }
        html += '</div>';
    }
    html += '</div>';
    mainEl.innerHTML = html;
}
// ─── Week View ──────────────────────────────────────────────────
function renderWeekView() {
    const vs = (0, state_1.getViewState)();
    const ws = (0, state_1.getSettings)().weekStart;
    const weekDays = (0, date_util_1.getWeekDays)({ year: vs.currentYear, month: vs.currentMonth, day: 1 }, ws);
    // Use the actual current view date for centering
    const viewDate = { year: vs.currentYear, month: vs.currentMonth, day: (0, state_1.getViewState)().currentMonth === vs.currentMonth ? 1 : 1 };
    titleEl.textContent = formatWeekTitle(weekDays[0], weekDays[6]);
    let html = '<div class="view-transition">';
    // All-day strip
    html += '<div class="all-day-strip">';
    html += '<div></div>'; // corner cell
    for (const wd of weekDays) {
        const events = (0, state_1.getEventsForDate)(wd).filter(e => e.allDay);
        html += `<div class="all-day-cell" data-date-key="${ymdKey(wd)}">`;
        for (const ev of events) {
            html += `<span class="event-chip" style="border-left-color:${ev.color};background:${ev.color}22;" data-event-id="${ev.id}">${escapeHtml(ev.title)}</span>`;
        }
        html += '</div>';
    }
    html += '</div>';
    // Hour grid
    html += '<div class="week-grid" id="week-grid">';
    // Header row
    const dayNames = ws === 0 ? constants_1.DAY_NAMES_SUN : constants_1.DAY_NAMES_MON;
    html += '<div class="week-header-row"><div></div>';
    for (let i = 0; i < 7; i++) {
        const wd = weekDays[i];
        const isToday = (0, date_util_1.sameDay)(wd, (0, date_util_1.today)());
        const cls = isToday ? 'today' : '';
        html += `<div class="week-day-header ${cls}">${dayNames[ws === 0 ? i : (i + 1) % 7]}<span class="wdh-num">${wd.day}</span></div>`;
    }
    html += '</div>';
    // Hour rows
    for (let h = constants_1.HOUR_START; h <= constants_1.HOUR_END; h++) {
        html += `<div class="week-hour-row">`;
        html += `<div class="hour-label">${(0, date_util_1.minutesToTimeString)(h * 60)}</div>`;
        for (const wd of weekDays) {
            const key = ymdKey(wd);
            html += `<div class="week-cell" data-date-key="${key}" data-hour="${h}"></div>`;
        }
        html += '</div>';
    }
    // Current time indicator
    const todayYMD = (0, date_util_1.today)();
    let indicatorHtml = '';
    for (let i = 0; i < 7; i++) {
        if ((0, date_util_1.sameDay)(weekDays[i], todayYMD)) {
            const mins = (0, date_util_1.nowMinutes)();
            if (mins >= constants_1.HOUR_START * 60 && mins <= constants_1.HOUR_END * 60) {
                const topPx = ((mins - constants_1.HOUR_START * 60) / 60) * 48; // 48px per hour
                indicatorHtml += `<div class="time-indicator" style="top:${topPx}px;" data-col="${i}"></div>`;
            }
        }
    }
    html += '</div>'; // week-grid
    // Now overlay events on the grid
    const eventBlocks = [];
    for (const wd of weekDays) {
        const events = (0, state_1.getEventsForDate)(wd).filter(e => !e.allDay);
        for (const ev of events) {
            const startMin = ev.startTime;
            const endMin = ev.endTime;
            if (startMin < constants_1.HOUR_START * 60 || startMin > constants_1.HOUR_END * 60)
                continue;
            const topPx = ((Math.max(startMin, constants_1.HOUR_START * 60) - constants_1.HOUR_START * 60) / 60) * 48;
            const heightPx = Math.max(((endMin - startMin) / 60) * 48, 16);
            eventBlocks.push(`<div class="week-event-block" style="top:${topPx}px;height:${heightPx}px;border-left-color:${ev.color};background:${ev.color}22;" data-event-id="${ev.id}" title="${escapeHtml(ev.title)}">${escapeHtml(ev.title)}</div>`);
        }
    }
    // We need to inject event blocks into the correct cells — do this after DOM is set
    html += '</div>'; // wrapper
    mainEl.innerHTML = html;
    // Now place events in their cells
    requestAnimationFrame(() => {
        const weekGrid = document.getElementById('week-grid');
        if (!weekGrid)
            return;
        for (let i = 0; i < 7; i++) {
            const wd = weekDays[i];
            const key = ymdKey(wd);
            const cells = weekGrid.querySelectorAll(`.week-cell[data-date-key="${key}"]`);
            // Events span multiple hour rows — we need to place them in the first cell and let absolute positioning handle it
            if (cells.length > 0) {
                const events = (0, state_1.getEventsForDate)(wd).filter(e => !e.allDay);
                for (const ev of events) {
                    const startMin = ev.startTime;
                    const endMin = ev.endTime;
                    if (startMin < constants_1.HOUR_START * 60 || startMin > constants_1.HOUR_END * 60)
                        continue;
                    const topPx = ((Math.max(startMin, constants_1.HOUR_START * 60) - constants_1.HOUR_START * 60) / 60) * 48;
                    const heightPx = Math.max(((endMin - startMin) / 60) * 48, 16);
                    const block = document.createElement('div');
                    block.className = 'week-event-block';
                    block.style.top = topPx + 'px';
                    block.style.height = heightPx + 'px';
                    block.style.borderLeftColor = ev.color;
                    block.style.background = ev.color + '22';
                    block.dataset.eventId = ev.id;
                    block.title = ev.title;
                    block.textContent = ev.title;
                    // Place in the first cell of this column (hour rows are separate cells)
                    cells[0].appendChild(block);
                }
            }
        }
        // Time indicator — place in today's column
        for (let i = 0; i < 7; i++) {
            if ((0, date_util_1.sameDay)(weekDays[i], (0, date_util_1.today)())) {
                const mins = (0, date_util_1.nowMinutes)();
                if (mins >= constants_1.HOUR_START * 60 && mins <= constants_1.HOUR_END * 60) {
                    const topPx = ((mins - constants_1.HOUR_START * 60) / 60) * 48;
                    const cells = weekGrid.querySelectorAll(`.week-cell[data-date-key="${ymdKey(weekDays[i])}"]`);
                    if (cells.length > 0) {
                        const indicator = document.createElement('div');
                        indicator.className = 'time-indicator';
                        indicator.style.top = topPx + 'px';
                        cells[0].appendChild(indicator);
                    }
                }
            }
        }
    });
}
// ─── Agenda View ────────────────────────────────────────────────
function renderAgendaView() {
    const vs = (0, state_1.getViewState)();
    const todayYMD = (0, date_util_1.today)();
    const startDate = (0, date_util_1.sameDay)({ year: vs.currentYear, month: vs.currentMonth, day: 1 }, todayYMD) ? todayYMD : { year: vs.currentYear, month: vs.currentMonth, day: 1 };
    const endDate = (0, date_util_1.addDays)(vs.currentYear, vs.currentMonth, 28, 0);
    titleEl.textContent = (0, date_util_1.formatMonthYear)(vs.currentYear, vs.currentMonth);
    // Get all events in range grouped by date
    const allEvents = (0, state_1.getEventsInRange)(startDate, endDate);
    if (allEvents.length === 0) {
        mainEl.innerHTML = '<div class="agenda-list view-transition"><p style="color:#70757a;text-align:center;padding:48px;">No events this month</p></div>';
        return;
    }
    // Group by date
    const groups = new Map();
    for (const ev of allEvents) {
        const key = ymdKey(ev._occurrenceDate || ev.date);
        if (!groups.has(key))
            groups.set(key, []);
        groups.get(key).push(ev);
    }
    let html = '<div class="agenda-list view-transition">';
    for (const [key, events] of Array.from(groups.entries()).sort()) {
        const [y, m, d] = key.split('-').map(Number);
        const ymd = { year: y, month: m - 1, day: d };
        html += `<div class="agenda-day-group">`;
        html += `<div class="agenda-date-header">${(0, date_util_1.formatDateShort)(ymd)}</div>`;
        for (const ev of events) {
            const timeStr = ev.allDay ? 'All day' : `${(0, date_util_1.minutesToTimeString)(ev.startTime)} – ${(0, date_util_1.minutesToTimeString)(ev.endTime)}`;
            html += `<div class="agenda-event-item" data-event-id="${ev.id}">`;
            html += `<div class="agenda-color-bar" style="background:${ev.color};"></div>`;
            html += `<div class="agenda-event-info"><div class="agenda-event-title">${escapeHtml(ev.title)}</div><div class="agenda-event-time">${timeStr}</div></div>`;
            html += '</div>';
        }
        html += '</div>';
    }
    html += '</div>';
    mainEl.innerHTML = html;
}
// ─── Helpers ─────────────────────────────────────────────────────
function formatWeekTitle(start, end) {
    if (start.month === end.month) {
        return `${(0, date_util_1.formatMonthYear)(start.year, start.month)} — ${start.day}–${end.day}`;
    }
    return `${(0, date_util_1.formatDateShort)(start)} – ${(0, date_util_1.formatDateShort)(end)}`;
}
function ymdKey(ymd) {
    return `${ymd.year}-${String(ymd.month + 1).padStart(2, '0')}-${String(ymd.day).padStart(2, '0')}`;
}
function escapeHtml(s) {
    const div = document.createElement('div');
    div.textContent = s;
    return div.innerHTML;
}
};

// ── module: src/modal.ts ──
__mods["src/modal.ts"] = function (exports, require, module) {
"use strict";
// ─── Modal: Event Editor, Settings, Recurrence Options ──────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.openModal = openModal;
exports.closeModal = closeModal;
exports.openEventEditor = openEventEditor;
exports.showRecurrenceDeleteOptions = showRecurrenceDeleteOptions;
exports.showRecurrenceEditOptions = showRecurrenceEditOptions;
exports.openSettingsModal = openSettingsModal;
const constants_1 = require("./constants");
const date_util_1 = require("./date-util");
const state_1 = require("./state");
const overlay = document.getElementById('modal-overlay');
const modalContent = document.getElementById('modal-content');
let currentEditId = null;
let pendingDeleteId = null;
let lastDeletedEvent = null;
// ─── Open/Close Modal ──────────────────────────────────────────
function openModal() { overlay.classList.remove('hidden'); }
function closeModal() { overlay.classList.add('hidden'); modalContent.innerHTML = ''; currentEditId = null; }
overlay.addEventListener('click', (e) => {
    if (e.target === overlay)
        closeModal();
});
// ─── Event Editor ──────────────────────────────────────────────
function openEventEditor(ev, dateHint) {
    currentEditId = ev?.id ?? null;
    const isEdit = !!ev;
    const title = isEdit ? 'Edit Event' : 'New Event';
    let html = `<div class="modal-box">`;
    html += `<div class="modal-title">${title}</div>`;
    // Title
    html += `<div class="form-group"><label>Title</label><input id="ev-title" type="text" value="${escapeAttr(ev?.title ?? '')}" placeholder="Event title"></div>`;
    // Date
    const dateVal = ev ? `${ev.date.year}-${String(ev.date.month + 1).padStart(2, '0')}-${String(ev.date.day).padStart(2, '0')}` : (dateHint ? `${dateHint.year}-${String(dateHint.month + 1).padStart(2, '0')}-${String(dateHint.day).padStart(2, '0')}` : '');
    html += `<div class="form-group"><label>Date</label><input id="ev-date" type="date" value="${dateVal}"></div>`;
    // All-day toggle
    html += `<div class="form-group"><label><input type="checkbox" id="ev-allday" ${ev?.allDay ? 'checked' : ''}> All day</label></div>`;
    // Time range
    const timeDiv = ev ? `${(0, date_util_1.minutesToTimeString)(ev.startTime)} – ${(0, date_util_1.minutesToTimeString)(ev.endTime)}` : '';
    html += `<div class="form-row" id="time-range">`;
    html += `<div class="form-group"><label>Start</label><input id="ev-start" type="time" value="${ev ? (0, date_util_1.minutesToTimeString)(ev.startTime) : '09:00'}"></div>`;
    html += `<div class="form-group"><label>End</label><input id="ev-end" type="time" value="${ev ? (0, date_util_1.minutesToTimeString)(ev.endTime) : '10:00'}"></div>`;
    html += `</div>`;
    // Color
    html += `<div class="form-group"><label>Color</label><div class="color-options">`;
    for (const c of constants_1.EVENT_COLORS) {
        const sel = ev?.color === c ? 'selected' : '';
        html += `<div class="color-swatch ${sel}" style="background:${c};" data-color="${c}"></div>`;
    }
    html += `</div></div>`;
    // Recurrence
    html += `<div class="form-group"><label>Recurrence</label><select id="ev-recurrence">`;
    html += `<option value="">None</option>`;
    html += `<option value="daily" ${ev?.recurrence?.frequency === 'daily' ? 'selected' : ''}>Daily</option>`;
    html += `<option value="weekly" ${ev?.recurrence?.frequency === 'weekly' ? 'selected' : ''}>Weekly</option>`;
    html += `<option value="monthly" ${ev?.recurrence?.frequency === 'monthly' ? 'selected' : ''}>Monthly</option>`;
    html += `<option value="yearly" ${ev?.recurrence?.frequency === 'yearly' ? 'selected' : ''}>Yearly</option>`;
    html += `</select></div>`;
    // Weekly day selection (hidden by default)
    const selectedDays = ev?.recurrence?.weekdays ?? [];
    html += `<div class="form-group" id="weekly-days" style="display:none;">`;
    html += `<label>Repeat on:</label><div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:4px;">`;
    const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    for (let d = 0; d < 7; d++) {
        const checked = selectedDays.includes(d) ? 'checked' : '';
        html += `<label style="font-size:12px;display:flex;align-items:center;gap:4px;"><input type="checkbox" class="weekday-cb" value="${d}" ${checked}>${dayLabels[d]}</label>`;
    }
    html += `</div></div>`;
    // Notes
    html += `<div class="form-group"><label>Notes</label><textarea id="ev-notes" rows="2">${escapeHtml(ev?.notes ?? '')}</textarea></div>`;
    // Error display
    html += `<div id="ev-error" class="error-text"></div>`;
    // Actions
    html += `<div class="modal-actions">`;
    if (isEdit) {
        html += `<button id="btn-delete-event" class="btn-danger">Delete</button>`;
    }
    else {
        html += `<span></span>`;
    }
    html += `<div style="display:flex;gap:8px;">`;
    html += `<button id="btn-cancel-event">Cancel</button>`;
    html += `<button id="btn-save-event" class="btn-primary">Save</button>`;
    html += `</div></div>`;
    html += `</div>`;
    modalContent.innerHTML = html;
    openModal();
    // Wire up events
    const alldayCb = document.getElementById('ev-allday');
    const timeRange = document.getElementById('time-range');
    toggleTimeRange(alldayCb.checked, timeRange);
    alldayCb.addEventListener('change', () => toggleTimeRange(alldayCb.checked, timeRange));
    // Recurrence type change → show/hide weekly days
    const recSelect = document.getElementById('ev-recurrence');
    const weeklyDaysDiv = document.getElementById('weekly-days');
    function onRecChange() {
        if (weeklyDaysDiv)
            weeklyDaysDiv.style.display = recSelect.value === 'weekly' ? '' : 'none';
    }
    recSelect.addEventListener('change', onRecChange);
    onRecChange();
    // Color selection
    let selectedColor = ev?.color ?? constants_1.EVENT_COLORS[0];
    for (const swatch of modalContent.querySelectorAll('.color-swatch')) {
        swatch.addEventListener('click', () => {
            modalContent.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
            swatch.classList.add('selected');
            selectedColor = swatch.dataset.color;
        });
    }
    // Save
    document.getElementById('btn-save-event').addEventListener('click', () => {
        const errorEl = document.getElementById('ev-error');
        errorEl.textContent = '';
        const titleVal = document.getElementById('ev-title').value.trim();
        if (!titleVal) {
            errorEl.textContent = 'Title is required';
            return;
        }
        const dateVal = document.getElementById('ev-date').value;
        if (!dateVal) {
            errorEl.textContent = 'Date is required';
            return;
        }
        const [y, m, d] = dateVal.split('-').map(Number);
        const dateYMD = { year: y, month: m - 1, day: d };
        const allDay = alldayCb.checked;
        let startMin = (0, date_util_1.timeStringToMinutes)(document.getElementById('ev-start').value || '09:00');
        let endMin = (0, date_util_1.timeStringToMinutes)(document.getElementById('ev-end').value || '10:00');
        if (!allDay && endMin <= startMin) {
            errorEl.textContent = 'End time must be after start time';
            return;
        }
        const notes = document.getElementById('ev-notes').value.trim();
        const recFreq = recSelect.value;
        let recurrence = null;
        if (recFreq) {
            recurrence = { frequency: recFreq };
            if (recFreq === 'weekly') {
                const days = [];
                for (const cb of modalContent.querySelectorAll('.weekday-cb:checked')) {
                    days.push(Number(cb.value));
                }
                if (days.length > 0)
                    recurrence.weekdays = days;
            }
        }
        if (isEdit && currentEditId) {
            (0, state_1.updateEvent)(currentEditId, { title: titleVal, date: dateYMD, allDay, startTime: startMin, endTime: endMin, color: selectedColor, notes, recurrence });
        }
        else {
            (0, state_1.createEvent)({ title: titleVal, date: dateYMD, allDay, startTime: startMin, endTime: endMin, color: selectedColor, notes, recurrence });
        }
        closeModal();
        reRender();
    });
    // Cancel
    document.getElementById('btn-cancel-event').addEventListener('click', () => { closeModal(); reRender(); });
    // Delete
    const deleteBtn = document.getElementById('btn-delete-event');
    if (deleteBtn && isEdit) {
        deleteBtn.addEventListener('click', () => {
            const ev = (0, state_1.getEventById)(currentEditId);
            if (!ev)
                return;
            if (ev.recurrence) {
                // Show recurrence delete options
                showRecurrenceDeleteOptions(ev, dateHint ?? ev.date);
            }
            else {
                doDeleteAndToast(ev.id, ev.title);
            }
        });
    }
}
function toggleTimeRange(allDay, el) {
    if (el)
        el.style.display = allDay ? 'none' : '';
}
// ─── Recurrence Delete Options ────────────────────────────────
function showRecurrenceDeleteOptions(ev, occurrenceDate) {
    modalContent.innerHTML = `<div class="modal-box">
    <div class="modal-title">Delete recurring event</div>
    <p style="margin-bottom:16px;color:#5f6368;">How would you like to delete this?</p>
    <button id="del-this" style="display:block;width:100%;text-align:left;padding:12px;margin-bottom:8px;border-radius:4px;">This event only</button>
    <button id="del-following" style="display:block;width:100%;text-align:left;padding:12px;margin-bottom:8px;border-radius:4px;">This and following events</button>
    <button id="del-series" style="display:block;width:100%;text-align:left;padding:12px;margin-bottom:8px;border-radius:4px;color:#d93025;font-weight:500;">Entire series</button>
    <button id="btn-cancel-del" style="margin-top:8px;">Cancel</button>
  </div>`;
    document.getElementById('del-this').addEventListener('click', () => {
        // Add exception date to the event's exception list
        const existing = (0, state_1.getEventById)(ev.id);
        if (existing && occurrenceDate) {
            const key = `${ev.id}@${occurrenceDate.year}-${String(occurrenceDate.month + 1).padStart(2, '0')}-${String(occurrenceDate.day).padStart(2, '0')}`;
            (0, state_1.updateEvent)(ev.id, { exceptionIds: [...(existing.exceptionIds ?? []), key] });
        }
        closeModal();
        reRender();
    });
    document.getElementById('del-following').addEventListener('click', () => {
        // For simplicity, delete the entire series (splitting is complex)
        doDeleteAndToast(ev.id, ev.title);
    });
    document.getElementById('del-series').addEventListener('click', () => {
        doDeleteAndToast(ev.id, ev.title);
    });
    document.getElementById('btn-cancel-del').addEventListener('click', () => { closeModal(); reRender(); });
}
function doDeleteAndToast(id, title) {
    lastDeletedEvent = { id, title };
    (0, state_1.deleteEvent)(id);
    closeModal();
    showToast(`"${title}" deleted`, 'Undo');
    reRender();
}
// ─── Recurrence Edit Options ────────────────────────────────
function showRecurrenceEditOptions(ev) {
    console.log('showRecurrenceEditOptions called, ev.id:', ev.id);
    openModal();
    modalContent.innerHTML = `<div class="modal-box">
    <div class="modal-title">Modify event</div>
    <p style="margin-bottom:16px;color:#5f6368;">This is a recurring event. Which events would you like to modify?</p>
    <button id="edit-this" style="display:block;width:100%;text-align:left;padding:12px;margin-bottom:8px;border-radius:4px;">This event only</button>
    <button id="edit-following" style="display:block;width:100%;text-align:left;padding:12px;margin-bottom:8px;border-radius:4px;">This and following events</button>
    <button id="edit-series" style="display:block;width:100%;text-align:left;padding:12px;margin-bottom:8px;border-radius:4px;">Entire series</button>
    <button id="btn-cancel-edit" style="margin-top:8px;">Cancel</button>
  </div>`;
    document.getElementById('edit-this').addEventListener('click', () => {
        // Create a new standalone event for this occurrence, add exception to original
        const occDate = ev._occurrenceDate || ev.date;
        const newEv = (0, state_1.createEvent)({
            title: ev.title,
            date: occDate,
            allDay: ev.allDay,
            startTime: ev.startTime,
            endTime: ev.endTime,
            color: ev.color,
            notes: ev.notes,
            recurrence: null,
        });
        // Add exception to original series
        const existing = (0, state_1.getEventById)(ev.id);
        if (existing) {
            const key = `${ev.id}@${occDate.year}-${String(occDate.month + 1).padStart(2, '0')}-${String(occDate.day).padStart(2, '0')}`;
            (0, state_1.updateEvent)(ev.id, { exceptionIds: [...(existing.exceptionIds ?? []), key] });
        }
        // Open editor for the new standalone event
        console.log('edit-this: closing modal and opening editor for', newEv.id);
        closeModal();
        reRender();
        try {
            openEventEditor(newEv);
            console.log('edit-this: editor opened successfully');
        }
        catch (err) {
            console.error('edit-this: error opening editor:', err.message);
        }
    });
    document.getElementById('edit-following').addEventListener('click', () => { openEventEditor(ev); });
    document.getElementById('edit-series').addEventListener('click', () => { openEventEditor(ev); });
    document.getElementById('btn-cancel-edit').addEventListener('click', () => { closeModal(); reRender(); });
}
// ─── Settings Modal ──────────────────────────────────────────
function openSettingsModal() {
    const settings = (0, state_1.getSettings)();
    modalContent.innerHTML = `<div class="modal-box">
    <div class="modal-title">Settings</div>
    <div class="settings-option">
      <span>Week starts on</span>
      <select id="set-weekstart">
        <option value="0" ${settings.weekStart === 0 ? 'selected' : ''}>Sunday</option>
        <option value="1" ${settings.weekStart === 1 ? 'selected' : ''}>Monday</option>
      </select>
    </div>
    <button id="btn-close-settings" style="margin-top:16px;">Close</button>
  </div>`;
    const wsSelect = document.getElementById('set-weekstart');
    wsSelect.addEventListener('change', () => {
        (0, state_1.updateSettings)({ weekStart: Number(wsSelect.value) });
    });
    document.getElementById('btn-close-settings').addEventListener('click', closeModal);
}
// ─── Toast Notifications ──────────────────────────────────────
function showToast(message, actionLabel) {
    const container = document.getElementById('toast-container');
    const toast = document.createElement('div');
    toast.className = 'toast';
    toast.textContent = message;
    if (actionLabel) {
        const btn = document.createElement('button');
        btn.textContent = actionLabel;
        btn.addEventListener('click', () => {
            // Undo delete
            if (lastDeletedEvent) {
                (0, state_1.createEvent)({ ...lastDeletedEvent });
                lastDeletedEvent = null;
                reRender();
            }
            toast.remove();
        });
        toast.appendChild(btn);
    }
    container.appendChild(toast);
    setTimeout(() => toast.remove(), 5000);
}
// ─── Helpers ──────────────────────────────────────────────────
function escapeHtml(s) {
    const div = document.createElement('div');
    div.textContent = s;
    return div.innerHTML;
}
function escapeAttr(s) {
    return s.replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>