← Calendar results

Calendar

Qwen 3.6 27b MTP · 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 96,072 bytes · SHA-256 d822c7f4691a
<!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: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  font-size: 14px;
  color: #202124;
  background: #fff;
  line-height: 1.5;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

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

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

.toolbar-left {
  display: flex;
  align-items: center;
  gap: 4px;
}

.toolbar-center {
  display: flex;
  align-items: center;
  gap: 12px;
}

.toolbar-right {
  display: flex;
  align-items: center;
}

#header-date {
  font-size: 22px;
  font-weight: 500;
  margin: 0 8px;
  white-space: nowrap;
  color: #3c4043;
}

.year-input {
  width: 64px;
  padding: 4px 8px;
  border: 1px solid #dadce0;
  border-radius: 4px;
  font-size: 13px;
  text-align: center;
  background: #fff;
}

.year-input:focus {
  outline: none;
  border-color: #1a73e8;
  box-shadow: 0 0 0 2px rgba(26,115,232,0.15);
}

/* ─── Buttons ──────────────────────────────────────────────────── */
.icon-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
  border-radius: 50%;
  font-size: 18px;
  color: #444746;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
}

.icon-btn:hover { background: #f1f3f4; }

.btn {
  padding: 6px 16px;
  border-radius: 20px;
  border: none;
  cursor: pointer;
  font-size: 13px;
  font-weight: 500;
  transition: background 0.15s, box-shadow 0.15s;
}

.btn-primary { background: #1a73e8; color: #fff; }
.btn-primary:hover { background: #1765cc; }

.btn-secondary { background: #f1f3f4; color: #202124; }
.btn-secondary:hover { background: #e8eaed; }

.btn-danger { background: #d93025; color: #fff; }
.btn-danger:hover { background: #c5221f; }

.btn-today {
  border: 1px solid #dadce0;
  background: #fff;
  color: #1a73e8;
  font-weight: 600;
}
.btn-today:hover { background: #e8f0fe; }

/* ─── View Switcher ────────────────────────────────────────────── */
.view-switcher {
  display: flex;
  gap: 2px;
  background: #f1f3f4;
  border-radius: 8px;
  padding: 2px;
}

.view-btn {
  padding: 6px 14px;
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 13px;
  font-weight: 500;
  color: #5f6368;
  border-radius: 6px;
  transition: all 0.15s;
}

.view-btn:hover { background: rgba(0,0,0,0.04); }

.view-btn.active {
  background: #fff;
  color: #202124;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

/* ─── Settings Panel ───────────────────────────────────────────── */
.settings-panel {
  position: absolute;
  top: 56px;
  right: 16px;
  background: #fff;
  border: 1px solid #dadce0;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.15);
  z-index: 200;
}

.settings-panel.hidden { display: none !important; }

.settings-panel h3 { font-size: 14px; margin-bottom: 8px; color: #3c4043; }

/* ─── Main Content ─────────────────────────────────────────────── */
#views-container {
  flex: 1;
  overflow-y: auto;
  position: relative;
}

.view-container.hidden { display: none !important; }

/* ─── Responsive ──────────────────────────────────────────────── */
@media (max-width: 768px) {
  #toolbar { padding: 6px 8px !important; gap: 4px !important; }
  .toolbar-left, .toolbar-center { gap: 2px !important; }
  #header-date { font-size: 16px !important; margin: 0 4px !important; }
  .year-input { width: 52px !important; padding: 3px 4px !important; }
  .view-btn { padding: 4px 8px !important; font-size: 12px !important; }
  .btn-today { padding: 4px 10px !important; font-size: 12px !important; }
}
/* ── css: month-view ── */
/* ─── Month View Grid ──────────────────────────────────────────── */
#month-view-container {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.month-header-row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  border-bottom: 1px solid #dadce0;
}

.month-day-label {
  text-align: center;
  padding: 8px 4px;
  font-size: 11px;
  font-weight: 600;
  color: #70757a;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.month-row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  flex: 1;
}

.month-cell {
  border-right: 1px solid #e8eaed;
  border-bottom: 1px solid #e8eaed;
  min-height: 90px;
  padding: 2px 4px;
  cursor: pointer;
  position: relative;
  transition: background 0.1s;
}

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

.month-cell.other-month {
  background: #fafbfc;
}

.month-cell.other-month .day-number { color: #c4c7cc; }

.day-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-size: 13px;
  font-weight: 500;
}

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

.cell-events {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: 2px;
}

.event-chip {
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  border-left: 3px solid #3b82f6;
  background: rgba(59, 130, 246, 0.08);
  color: #1a73e8;
  transition: filter 0.1s;
}

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

.event-more {
  font-size: 10px;
  color: #70757a;
  padding: 1px 6px;
  cursor: pointer;
}

.month-cell.drag-over {
  background: #e8f0fe !important;
  outline: 2px solid #1a73e8;
  outline-offset: -2px;
}

/* ─── Day Popover ──────────────────────────────────────────────── */
.day-popover {
  position: fixed;
  background: #fff;
  border: 1px solid #dadce0;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  padding: 12px;
  z-index: 300;
  min-width: 220px;
}

.popover-header {
  font-weight: 600;
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid #eee;
  font-size: 13px;
}

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

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

/* ─── Responsive ──────────────────────────────────────────────── */
@media (max-width: 768px) {
  .month-cell { min-height: 60px !important; padding: 1px 2px !important; }
  .day-number { width: 22px !important; height: 22px !important; font-size: 11px !important; }
  .event-chip { font-size: 9px !important; padding: 0 3px !important; border-left-width: 2px !important; }
}
/* ── css: week-view ── */
/* ─── Week View ────────────────────────────────────────────────── */
#week-view-container {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow-y: auto;
}

.week-header {
  display: grid;
  grid-template-columns: 56px repeat(7, 1fr);
  border-bottom: 1px solid #dadce0;
  position: sticky;
  top: 0;
  background: #fff;
  z-index: 10;
}

.week-day-header {
  text-align: center;
  padding: 8px 4px;
  border-left: 1px solid #eee;
}

.day-label {
  font-size: 11px;
  color: #70757a;
  font-weight: 600;
  text-transform: uppercase;
}

.date-label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  font-size: 20px;
  font-weight: 400;
  margin-top: 2px;
}

.today-date {
  background: #1a73e8;
  color: #fff;
  font-weight: 700;
}

/* All-day strip */
.all-day-strip {
  display: grid;
  grid-template-columns: 56px repeat(7, 1fr);
  border-bottom: 1px solid #dadce0;
  min-height: 32px;
}

.all-day-slot {
  padding: 4px;
  border-left: 1px solid #eee;
}

.all-day-chip {
  font-size: 11px;
  padding: 2px 6px;
}

/* Time grid */
.time-grid {
  display: flex;
  position: relative;
}

.hour-labels {
  width: 56px;
  flex-shrink: 0;
  position: relative;
}

.hour-label {
  height: 240px; /* 60 min * 4 px/min */
  font-size: 10px;
  color: #9aa0a6;
  text-align: right;
  padding-right: 8px;
  position: relative;
  top: -6px;
}

.day-columns {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  flex: 1;
  position: relative;
}

.week-day-column {
  border-left: 1px solid #eee;
  position: relative;
  min-height: 6480px; /* (22-6)*60*4 = 3840, but we need more for scroll */
}

.week-day-column.today {
  background: rgba(26, 115, 232, 0.02);
}

.time-slot {
  height: 60px; /* 15 min * 4 px/min */
  border-bottom: 1px solid #f8f9fa;
  cursor: pointer;
}

.time-slot:hover { background: rgba(26, 115, 232, 0.04); }

.week-event-block {
  position: absolute;
  border-left-width: 3px;
  border-left-style: solid;
  z-index: 5;
}

.event-time { font-size: 10px; opacity: 0.8; line-height: 1.2; }
.event-title { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.3; }

.current-time-indicator {
  position: absolute;
  left: -1px;
  right: -1px;
  height: 2px;
  background: #d93025;
  z-index: 6;
  pointer-events: none;
}

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

/* ─── Responsive ──────────────────────────────────────────────── */
@media (max-width: 768px) {
  .toolbar-left, .toolbar-center { gap: 4px !important; }
  #header-date { font-size: 16px !important; margin: 0 4px !important; }
  .year-input { width: 52px !important; }
  .view-btn { padding: 4px 8px !important; font-size: 12px !important; }
  
  .month-cell { min-height: 60px !important; padding: 1px 2px !important; }
  .day-number { width: 22px !important; height: 22px !important; line-height: 22px !important; font-size: 11px !important; }
  .event-chip { font-size: 9px !important; padding: 0 3px !important; border-left-width: 2px !important; }
  
  .week-header { grid-template-columns: 40px repeat(7, 1fr) !important; }
  .hour-labels { width: 40px !important; }
  .date-label { width: 28px !important; height: 28px !important; font-size: 16px !important; }
}
/* ── css: agenda-view ── */
/* ─── Agenda View ──────────────────────────────────────────────── */
#agenda-view-container {
  padding: 16px;
}

.agenda-day-header {
  font-size: 13px;
  font-weight: 600;
  color: #70757a;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 12px 0 4px;
  border-bottom: 1px solid #eee;
}

.agenda-event-item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 12px;
  cursor: pointer;
  border-radius: 4px;
  margin-top: 2px;
}

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

.agenda-color-bar {
  width: 4px;
  min-height: 28px;
  border-radius: 2px;
  flex-shrink: 0;
  align-self: stretch;
}

.agenda-event-content { flex: 1; }

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

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

.agenda-empty {
  text-align: center;
  padding: 48px;
  color: #9aa0a6;
  font-size: 15px;
}

.today-badge {
  display: inline-block;
  background: #e8f0fe;
  color: #1a73e8;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 11px;
  margin-left: 8px;
}
/* ── css: modal ── */
/* ─── Modal Overlay & Dialog ───────────────────────────────────── */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 500;
}

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

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

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid #eee;
}

.modal-header h2 { font-size: 18px; font-weight: 500; }

.modal-body { padding: 20px; }

/* ─── Form Elements ────────────────────────────────────────────── */
.form-group { margin-bottom: 14px; }

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

.form-group input[type="text"],
.form-group input[type="date"],
.form-group input[type="time"],
.form-group select,
.form-group textarea {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid #dadce0;
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
  transition: border-color 0.15s;
}

.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.15);
}

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

.flex-1 { flex: 1; }

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 14px;
}

.field-error {
  display: block;
  color: #d93025;
  font-size: 12px;
  margin-top: 2px;
  min-height: 16px;
}

/* ─── Color Picker ─────────────────────────────────────────────── */
.color-picker {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.color-swatch {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid transparent;
  cursor: pointer;
  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);
}

/* ─── Weekday Picker ───────────────────────────────────────────── */
.weekday-picker {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.weekday-btn {
  padding: 4px 10px;
  border: 1px solid #dadce0;
  border-radius: 16px;
  background: #fff;
  cursor: pointer;
  font-size: 12px;
  transition: all 0.15s;
}

.weekday-btn.selected {
  background: #e8f0fe;
  border-color: #1a73e8;
  color: #1a73e8;
}

/* ─── Modal Actions ────────────────────────────────────────────── */
.modal-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid #eee;
}

.spacer { flex: 1; }

/* ─── Scope Dialog (recurring edit) ────────────────────────────── */
.scope-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 600;
}

.scope-dialog {
  background: #fff;
  border-radius: 12px;
  padding: 24px;
  max-width: 380px;
  width: 90%;
  box-shadow: 0 8px 40px rgba(0,0,0,0.2);
}

.scope-dialog h3 { margin-bottom: 8px; }
.scope-dialog p { font-size: 13px; color: #5f6368; margin-bottom: 16px; }

.scope-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ─── Confirm Dialog ───────────────────────────────────────────── */
.confirm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 600;
}

.confirm-dialog {
  background: #fff;
  border-radius: 12px;
  padding: 24px;
  max-width: 340px;
  width: 90%;
  box-shadow: 0 8px 40px rgba(0,0,0,0.2);
}

.confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

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

.toast {
  background: #323232;
  color: #fff;
  padding: 12px 24px;
  border-radius: 8px;
  font-size: 14px;
  margin-top: 8px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
  animation: toast-in 0.2s ease-out;
}

.toast.fade-out { opacity: 0; transition: opacity 0.3s; }

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

/* ─── Utility ──────────────────────────────────────────────────── */
.hidden { display: none !important; }
</style>
</head>
<body>
<div id="app">
  <header id="toolbar">
    <div class="toolbar-left">
      <button id="nav-prev" class="icon-btn" title="Previous">‹</button>
      <button id="nav-next" class="icon-btn" title="Next">›</button>
      <h1 id="header-date"></h1>
      <input type="number" id="year-jump" min="1900" max="2200" class="year-input">
    </div>
    <div class="toolbar-center">
      <button id="nav-today" class="btn btn-today">Today</button>
      <div class="view-switcher">
        <button class="view-btn active" data-view="month">Month</button>
        <button class="view-btn" data-view="week">Week</button>
        <button class="view-btn" data-view="agenda">Agenda</button>
      </div>
    </div>
    <div class="toolbar-right">
      <button id="settings-btn" class="icon-btn" title="Settings">⚙</button>
    </div>
  </header>

  <div id="settings-panel" class="settings-panel hidden">
    <h3>Settings</h3>
    <label class="checkbox-label">
      <input type="checkbox" id="week-start-toggle">
      <span>Week starts on Monday</span>
    </label>
  </div>

  <main id="views-container">
    <div id="month-view-container" class="view-container"></div>
    <div id="week-view-container" class="view-container hidden"></div>
    <div id="agenda-view-container" class="view-container hidden"></div>
  </main>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./state":"src/state.ts","./date-util":"src/date-util.ts","./recurrence":"src/recurrence.ts","./modal":"src/modal.ts","./views/month-view":"src/views/month-view.ts","./views/week-view":"src/views/week-view.ts","./views/agenda-view":"src/views/agenda-view.ts","./render":"src/render.ts"},"src/date-util.ts":{"./types":"src/types.ts","./constants":"src/constants.ts"},"src/recurrence.ts":{"./types":"src/types.ts","./date-util":"src/date-util.ts"},"src/state.ts":{"./types":"src/types.ts","./date-util":"src/date-util.ts","./constants":"src/constants.ts"},"src/modal.ts":{"./types":"src/types.ts","./constants":"src/constants.ts","./date-util":"src/date-util.ts"},"src/render.ts":{"./types":"src/types.ts","./date-util":"src/date-util.ts"},"src/views/month-view.ts":{"../types":"src/types.ts","../date-util":"src/date-util.ts","../constants":"src/constants.ts","../render":"src/render.ts"},"src/views/week-view.ts":{"../types":"src/types.ts","../date-util":"src/date-util.ts","../constants":"src/constants.ts","../render":"src/render.ts"},"src/views/agenda-view.ts":{"../types":"src/types.ts","../date-util":"src/date-util.ts","../render":"src/render.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
// ─── Main Entry Point ─────────────────────────────────────────────────
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.start = start;
const state = __importStar(require("./state"));
const dateUtil = __importStar(require("./date-util"));
const recurrence = __importStar(require("./recurrence"));
const modal = __importStar(require("./modal"));
const monthView = __importStar(require("./views/month-view"));
const weekView = __importStar(require("./views/week-view"));
const agendaView = __importStar(require("./views/agenda-view"));
const render_1 = require("./render");
// ── Initialization ────────────────────────────────────────────────────
function start() {
    const appState = state.init();
    // Initialize modal
    modal.initModal();
    // Initialize views
    monthView.init({
        containerId: 'month-view-container',
        weekStart: appState.settings.weekStart,
        onDayClick: (date) => openNewEventModal(date),
        onEventClick: (evt, date) => openEditOccurrenceModal(evt, date),
        onDragDrop: handleMonthDragDrop,
    });
    weekView.init({
        containerId: 'week-view-container',
        weekStart: appState.settings.weekStart,
        onTimeSlotClick: (date, hour, minute) => {
            const startTime = `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
            openNewEventModal(date, startTime);
        },
        onEventClick: (evt, date) => openEditOccurrenceModal(evt, date),
        onDragDrop: handleWeekDragDrop,
    });
    agendaView.init({
        containerId: 'agenda-view-container',
        onEventClick: (evt) => openEditOccurrenceModal(evt, evt.date),
    });
    // Wire navigation controls
    wireNavigation(appState);
    // Subscribe to state changes for re-rendering
    state.subscribe(() => renderCurrentView());
    // Initial render
    renderCurrentView();
}
// ── Rendering ─────────────────────────────────────────────────────────
function renderCurrentView() {
    const appState = state.getState();
    const { currentYear, currentMonth, settings } = appState;
    // Update header
    updateHeader(currentYear, currentMonth);
    // Compute visible date range and get occurrences
    let rangeStart, rangeEnd;
    switch (settings.viewMode) {
        case 'month':
            rangeStart = { year: currentYear, month: currentMonth, day: 1 };
            const lastDay = dateUtil.daysInMonth(currentYear, currentMonth);
            rangeEnd = { year: currentYear, month: currentMonth, day: lastDay };
            break;
        case 'week':
            rangeStart = dateUtil.startOfWeek({ year: currentYear, month: currentMonth, day: 1 }, settings.weekStart);
            rangeEnd = dateUtil.addDays(rangeStart, 6);
            break;
        case 'agenda':
            const today = dateUtil.today();
            rangeStart = { ...today };
            rangeEnd = dateUtil.addDays(today, 30);
            break;
    }
    // Expand all events for the visible range
    const occurrences = recurrence.getAllOccurrences(appState.events, rangeStart, rangeEnd, appState.exceptions, appState.deletedOccurrences);
    // Render active view
    switch (settings.viewMode) {
        case 'month':
            monthView.render(currentYear, currentMonth, occurrences, settings.weekStart);
            break;
        case 'week':
            weekView.render({ year: currentYear, month: currentMonth, day: 1 }, occurrences, settings.weekStart);
            break;
        case 'agenda':
            const today2 = dateUtil.today();
            agendaView.render(today2, occurrences);
            break;
    }
    // Show/hide view containers
    ['month', 'week', 'agenda'].forEach(view => {
        const el = document.getElementById(`${view}-view-container`);
        if (settings.viewMode === view) {
            el.classList.remove('hidden');
            el.style.display = '';
        }
        else {
            el.classList.add('hidden');
            el.style.display = 'none';
        }
    });
    // Update view mode buttons
    document.querySelectorAll('.view-btn').forEach(btn => {
        btn.classList.toggle('active', btn.dataset.view === settings.viewMode);
    });
}
function updateHeader(year, month) {
    const headerEl = document.getElementById('header-date');
    if (headerEl) {
        headerEl.textContent = `${dateUtil.monthName(month)} ${year}`;
    }
}
// ── Navigation Wiring ─────────────────────────────────────────────────
function wireNavigation(appState) {
    // Prev/Next buttons
    document.getElementById('nav-prev').addEventListener('click', () => navigate(-1));
    document.getElementById('nav-next').addEventListener('click', () => navigate(1));
    // Today button
    document.getElementById('nav-today').addEventListener('click', () => {
        const today = dateUtil.today();
        state.setCurrentMonth(today.year, today.month);
    });
    // Year jump input
    const yearInput = document.getElementById('year-jump');
    yearInput.value = String(appState.currentYear);
    yearInput.addEventListener('change', () => {
        const y = parseInt(yearInput.value);
        if (!isNaN(y) && y > 0) {
            state.setCurrentMonth(y, state.getState().currentMonth);
        }
        else {
            yearInput.value = String(state.getState().currentYear);
        }
    });
    // View mode buttons
    document.querySelectorAll('.view-btn').forEach(btn => {
        btn.addEventListener('click', () => {
            const view = btn.dataset.view;
            if (view)
                state.setViewMode(view);
        });
    });
    // Week start toggle
    document.getElementById('week-start-toggle').addEventListener('click', () => {
        const current = state.getState().settings.weekStart;
        state.setWeekStart(current === 0 ? 1 : 0);
    });
    // Settings button (toggle settings panel)
    document.getElementById('settings-btn').addEventListener('click', () => {
        const panel = document.getElementById('settings-panel');
        if (panel)
            panel.classList.toggle('open');
    });
}
function navigate(delta) {
    const appState = state.getState();
    let newMonth = appState.currentMonth + delta;
    let newYear = appState.currentYear;
    if (newMonth < 0) {
        newMonth = 11;
        newYear--;
    }
    if (newMonth > 11) {
        newMonth = 0;
        newYear++;
    }
    state.setCurrentMonth(newYear, newMonth);
}
// ── Event Modal Integration ───────────────────────────────────────────
function openNewEventModal(date, startTime) {
    modal.openModal({
        onSave: (evt) => {
            if (!evt.title)
                return; // Cancel signal
            state.addEvent(evt);
            (0, render_1.showToast)('Event created');
            modal.closeModal();
        },
        onCancel: () => modal.closeModal(),
    }, date);
    // If startTime provided, set it after modal opens (small delay for DOM update)
    if (startTime) {
        setTimeout(() => {
            const startEl = document.getElementById('evt-start');
            const endEl = document.getElementById('evt-end');
            if (startEl && endEl) {
                startEl.value = startTime;
                // Default 1-hour duration
                const [h, m] = startTime.split(':').map(Number);
                let endH = h + 1;
                let endM = m;
                if (endH > 23)
                    endH = 0;
                endEl.value = `${String(endH).padStart(2, '0')}:${String(endM).padStart(2, '0')}`;
            }
        }, 50);
    }
}
function openEditOccurrenceModal(evt, date) {
    const master = state.getEvent(evt.eventId);
    if (!master)
        return;
    // Check if this is a recurring event — show scope selector
    if (master.recurrence.type !== 'none') {
        openRecurringEditModal(master, evt, date);
        return;
    }
    // Non-recurring: edit the master directly
    modal.openEditModal({
        onSave: (updated) => {
            if (!updated.title) {
                state.deleteEvent(evt.eventId);
                (0, render_1.showToast)('Event deleted');
                return;
            }
            state.updateEvent(evt.eventId, updated);
            (0, render_1.showToast)('Event updated');
            modal.closeModal();
        },
        onCancel: () => modal.closeModal(),
    }, master);
}
function openRecurringEditModal(master, occurrence, date) {
    // Show a scope selector before opening the edit modal
    const overlay = document.createElement('div');
    overlay.className = 'scope-overlay';
    overlay.innerHTML = `
    <div class="scope-dialog">
      <h3>Edit recurring event</h3>
      <p>This is part of a recurring series. Which occurrence do you want to edit?</p>
      <div class="scope-options">
        <button class="btn btn-secondary scope-btn" data-scope="this">This event only</button>
        <button class="btn btn-secondary scope-btn" data-scope="following">This & following</button>
        <button class="btn btn-primary scope-btn" data-scope="all">Entire series</button>
      </div>
    </div>
  `;
    document.body.appendChild(overlay);
    overlay.querySelectorAll('.scope-btn').forEach(btn => {
        btn.addEventListener('click', () => {
            const scope = btn.dataset.scope;
            overlay.remove();
            if (scope === 'this') {
                // Edit this occurrence only — create an exception
                openEditOccurrenceOnly(master, occurrence, date);
            }
            else if (scope === 'following') {
                // Split the series: set new anchor to this occurrence's date
                const updatedMaster = { ...master, date: { ...date } };
                modal.openEditModal({
                    onSave: (updated) => {
                        if (!updated.title)
                            return;
                        state.updateEvent(master.id, { ...updated, date: { ...date } });
                        (0, render_1.showToast)('Series updated from this point forward');
                        modal.closeModal();
                    },
                    onCancel: () => modal.closeModal(),
                }, updatedMaster);
            }
            else {
                // Edit entire series
                modal.openEditModal({
                    onSave: (updated) => {
                        if (!updated.title) {
                            state.deleteEvent(master.id);
                            (0, render_1.showToast)('Series deleted');
                            return;
                        }
                        state.updateEvent(master.id, updated);
                        (0, render_1.showToast)('Entire series updated');
                        modal.closeModal();
                    },
                    onCancel: () => modal.closeModal(),
                }, master);
            }
        });
    });
    overlay.addEventListener('click', (e) => {
        if (e.target === overlay)
            overlay.remove();
    });
}
function openEditOccurrenceOnly(master, occurrence, date) {
    // Open modal pre-filled with the occurrence data
    modal.openModal({
        onSave: (updated) => {
            if (!updated.title)
                return;
            // Create an exception for this specific occurrence
            state.addException(master.id, date, updated);
            (0, render_1.showToast)('Occurrence updated');
            modal.closeModal();
        },
        onCancel: () => modal.closeModal(),
    }, date);
    // Pre-fill the form with occurrence data
    setTimeout(() => {
        document.getElementById('evt-title').value = occurrence.title;
        document.getElementById('evt-allday').checked = occurrence.isAllDay;
        document.getElementById('evt-start').value = occurrence.startTime || '';
        document.getElementById('evt-end').value = occurrence.endTime || '';
        document.getElementById('evt-notes').value = occurrence.notes || '';
        // Color selection
        document.querySelectorAll('.color-swatch').forEach(s => {
            s.classList.toggle('selected', s.dataset.color === occurrence.color);
        });
        // Hide recurrence options for single-occurrence edit
        document.getElementById('evt-recurrence-type').value = 'none';
    }, 50);
}
// ── Drag & Drop Handlers ──────────────────────────────────────────────
function handleMonthDragDrop(eventId, fromDate, toDate) {
    const master = state.getEvent(eventId);
    if (!master)
        return;
    // Check if this is a recurring event — ask for scope
    if (master.recurrence.type !== 'none') {
        (0, render_1.showToast)(`Dragged "${master.title}" to ${dateUtil.monthName(toDate.month)} ${toDate.day}`);
        // For simplicity, move the entire series anchor date
        state.updateEvent(eventId, { date: { ...toDate } });
    }
    else {
        state.updateEvent(eventId, { date: { ...toDate } });
    }
    (0, render_1.showToast)(`Moved to ${dateUtil.monthName(toDate.month)} ${toDate.day}`);
}
// ── Auto-start ────────────────────────────────────────────────────────
start();
function handleWeekDragDrop(eventId, fromDate, fromTime, toDate, toTime) {
    const master = state.getEvent(eventId);
    if (!master)
        return;
    if (dateUtil.sameDay(fromDate, toDate)) {
        // Same day — just update time
        state.updateEvent(eventId, { startTime: toTime });
        (0, render_1.showToast)('Time updated');
    }
    else {
        // Different day — update date and time
        state.updateEvent(eventId, { date: { ...toDate }, startTime: toTime });
        (0, render_1.showToast)(`Moved to ${dateUtil.monthName(toDate.month)} ${toDate.day}`);
    }
}
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
// ─── Tunable Constants ────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.TRANSITION_MS = exports.MAX_CELL_EVENTS = exports.DEFAULT_WEEK_START = exports.SNAP_MINUTES = exports.HOUR_END = exports.HOUR_START = exports.DEFAULT_COLOR = exports.EVENT_COLORS = void 0;
exports.EVENT_COLORS = [
    { name: 'Blue', value: '#3b82f6' },
    { name: 'Red', value: '#ef4444' },
    { name: 'Green', value: '#22c55e' },
    { name: 'Purple', value: '#a855f7' },
    { name: 'Orange', value: '#f97316' },
    { name: 'Teal', value: '#14b8a6' },
    { name: 'Pink', value: '#ec4899' },
    { name: 'Yellow', value: '#eab308' },
];
exports.DEFAULT_COLOR = exports.EVENT_COLORS[0].value;
// Week view hour range (24h)
exports.HOUR_START = 6; // 6:00 AM
exports.HOUR_END = 22; // 10:00 PM
exports.SNAP_MINUTES = 15; // drag snap interval in minutes
// Default week start day (0=Sunday, 1=Monday)
exports.DEFAULT_WEEK_START = 1;
// Max events shown per month cell before "+N more"
exports.MAX_CELL_EVENTS = 3;
// Animation duration for view transitions (ms)
exports.TRANSITION_MS = 250;
};

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

// ── module: src/date-util.ts ──
__mods["src/date-util.ts"] = function (exports, require, module) {
"use strict";
// ─── Date Engine (Pure Functions) ─────────────────────────────────────
// All arithmetic on year/month/day components — never ms-based day hacks.
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLeapYear = isLeapYear;
exports.daysInMonth = daysInMonth;
exports.dayOfWeek = dayOfWeek;
exports.addDays = addDays;
exports.addMonths = addMonths;
exports.addYears = addYears;
exports.sameDay = sameDay;
exports.compareDays = compareDays;
exports.today = today;
exports.startOfWeek = startOfWeek;
exports.getWeekDays = getWeekDays;
exports.buildMonthGrid = buildMonthGrid;
exports.monthName = monthName;
exports.dayNameShort = dayNameShort;
exports.dayNameFull = dayNameFull;
exports.formatDate = formatDate;
exports.parseDate = parseDate;
exports.formatTime = formatTime;
exports.formatDayHeader = formatDayHeader;
exports.generateId = generateId;
const constants_1 = require("./constants");
// ── Leap Year & Month Lengths ─────────────────────────────────────────
function isLeapYear(year) {
    return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
function daysInMonth(year, month) {
    // month: 0=Jan .. 11=Dec
    const table = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    if (month === 1 && isLeapYear(year))
        return 29; // Feb leap
    return table[month];
}
// ── Day-of-Week ───────────────────────────────────────────────────────
// Returns 0=Sunday .. 6=Saturday. Uses Zeller-like formula on components.
function dayOfWeek(y, m, d) {
    // Use native Date only for the day-of-week lookup (no arithmetic)
    const dt = new Date(y, m, d);
    return dt.getDay(); // 0=Sun .. 6=Sat
}
// ── Component Arithmetic ──────────────────────────────────────────────
function addDays(dc, n) {
    let { year, month, day } = dc;
    const delta = n >= 0 ? n : -n;
    const sign = n >= 0 ? 1 : -1;
    for (let i = 0; i < delta; i++) {
        day += sign;
        if (day > daysInMonth(year, month)) {
            day = 1;
            month++;
            if (month > 11) {
                month = 0;
                year++;
            }
        }
        else if (day < 1) {
            month--;
            if (month < 0) {
                month = 11;
                year--;
            }
            day = daysInMonth(year, month);
        }
    }
    return { year, month, day };
}
function addMonths(dc, n) {
    let { year, month, day } = dc;
    const totalMonths = (year * 12 + month) + n;
    year = Math.floor(totalMonths / 12);
    month = ((totalMonths % 12) + 12) % 12; // handle negative wrap
    // Clamp day to valid range for the target month
    const maxDay = daysInMonth(year, month);
    if (day > maxDay)
        day = maxDay;
    return { year, month, day };
}
function addYears(dc, n) {
    let { year, month, day } = dc;
    year += n;
    // Handle Feb 29 -> Feb 28 in non-leap years
    if (month === 1 && day === 29 && !isLeapYear(year)) {
        day = 28;
    }
    return { year, month, day };
}
// ── Comparison & Equality ─────────────────────────────────────────────
function sameDay(a, b) {
    return a.year === b.year && a.month === b.month && a.day === b.day;
}
function compareDays(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;
}
// ── Today ─────────────────────────────────────────────────────────────
function today() {
    const d = new Date();
    return { year: d.getFullYear(), month: d.getMonth(), day: d.getDate() };
}
// ── Week Helpers ──────────────────────────────────────────────────────
/** Get the first day of the week containing `dc`, using given weekStart. */
function startOfWeek(dc, weekStart = constants_1.DEFAULT_WEEK_START) {
    const dow = dayOfWeek(dc.year, dc.month, dc.day); // 0=Sun..6=Sat
    let diff;
    if (weekStart === 1) {
        // Monday start: Mon=0 .. Sun=6
        diff = dow === 0 ? -6 : 1 - dow;
    }
    else {
        // Sunday start: Sun=0 .. Sat=6
        diff = -dow;
    }
    return addDays(dc, diff);
}
/** Get all 7 days of the week containing `dc`. */
function getWeekDays(dc, weekStart = constants_1.DEFAULT_WEEK_START) {
    const start = startOfWeek(dc, weekStart);
    const result = [];
    for (let i = 0; i < 7; i++) {
        result.push(addDays(start, i));
    }
    return result;
}
function buildMonthGrid(year, month, weekStart = constants_1.DEFAULT_WEEK_START) {
    const firstDay = { year, month, day: 1 };
    const weekStartDay = startOfWeek(firstDay, weekStart);
    const grid = [];
    let current = { ...weekStartDay };
    for (let row = 0; row < 6; row++) {
        const rowCells = [];
        for (let col = 0; col < 7; col++) {
            rowCells.push({
                date: { year: current.year, month: current.month, day: current.day },
                isCurrentMonth: current.month === month,
            });
            // Advance to next day — but stop after filling the grid if we've gone past 6 rows
            const next = addDays(current, 1);
            // If we're on row 5 (last) and already in a different month, check if we need more
            current = next;
        }
        grid.push(rowCells);
        // Early exit: if the last cell of this row is still in our target month's week range
        // Actually, standard calendar grids always show 6 rows. But some months only need 5.
        // We'll check: if we've passed the end of the month and are now into next month,
        // and all remaining cells would be from next month...
        // Standard approach: just fill 6 rows always for consistency.
    }
    return grid;
}
// ── Formatting ────────────────────────────────────────────────────────
const MONTH_NAMES = [
    'January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'
];
const DAY_NAMES_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const DAY_NAMES_FULL = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
function monthName(month) { return MONTH_NAMES[month]; }
function dayNameShort(dow) { return DAY_NAMES_SHORT[dow]; }
function dayNameFull(dow) { return DAY_NAMES_FULL[dow]; }
function formatDate(dc) {
    return `${dc.year}-${String(dc.month + 1).padStart(2, '0')}-${String(dc.day).padStart(2, '0')}`;
}
function parseDate(s) {
    const m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
    if (!m)
        return null;
    return { year: +m[1], month: +m[2] - 1, day: +m[3] };
}
function formatTime(hhmm) {
    if (!hhmm)
        return '';
    const [h, m] = hhmm.split(':').map(Number);
    const ampm = h >= 12 ? 'PM' : 'AM';
    const hour12 = h % 12 || 12;
    return `${hour12}:${String(m).padStart(2, '0')} ${ampm}`;
}
function formatDayHeader(dc) {
    return `${monthName(dc.month)} ${dc.day}, ${dc.year}`;
}
// ── ID Generation ─────────────────────────────────────────────────────
let _idCounter = Date.now();
function generateId() {
    return `evt_${++_idCounter}_${Math.random().toString(36).slice(2, 8)}`;
}
};

// ── module: src/recurrence.ts ──
__mods["src/recurrence.ts"] = function (exports, require, module) {
"use strict";
// ─── Recurrence Engine ────────────────────────────────────────────────
// Expands recurrence rules into concrete occurrences within a given date range.
// Never materializes infinite series — only computes for the requested window.
Object.defineProperty(exports, "__esModule", { value: true });
exports.expandEvent = expandEvent;
exports.getAllOccurrences = getAllOccurrences;
exports.getOccurrencesForDate = getOccurrencesForDate;
const date_util_1 = require("./date-util");
/**
 * Expand a single event into all its occurrences within [rangeStart, rangeEnd].
 * Respects exceptions and deleted occurrences.
 */
function expandEvent(evt, rangeStart, rangeEnd, exceptions, deletedOccurrences) {
    const results = [];
    // Generate candidate dates from recurrence rule
    const candidates = generateCandidateDates(evt, rangeStart, rangeEnd);
    for (const date of candidates) {
        // Check if this occurrence was deleted
        if (deletedOccurrences.some(d => d.eventId === evt.id && (0, date_util_1.sameDay)(d.date, date))) {
            continue;
        }
        // Build the base occurrence from the master event
        const exception = exceptions.find(e => e.eventId === evt.id && (0, date_util_1.sameDay)(e.date, date));
        const override = exception?.override ?? {};
        results.push({
            eventId: evt.id,
            date,
            startTime: override.startTime !== undefined ? override.startTime : evt.startTime,
            endTime: override.endTime !== undefined ? override.endTime : evt.endTime,
            isAllDay: override.isAllDay !== undefined ? override.isAllDay : evt.isAllDay,
            title: override.title ?? evt.title,
            color: override.color ?? evt.color,
            notes: override.notes ?? evt.notes,
        });
    }
    return results;
}
/** Generate all candidate dates for an event within the range. */
function generateCandidateDates(evt, rangeStart, rangeEnd) {
    const { recurrence, date: anchorDate } = evt;
    const results = [];
    if (recurrence.type === 'none') {
        // Non-recurring: only include if within range
        if ((0, date_util_1.compareDays)(anchorDate, rangeStart) >= 0 && (0, date_util_1.compareDays)(anchorDate, rangeEnd) <= 0) {
            results.push({ ...anchorDate });
        }
        return results;
    }
    const interval = recurrence.interval ?? 1;
    switch (recurrence.type) {
        case 'daily':
            generateDaily(anchorDate, rangeStart, rangeEnd, interval, results);
            break;
        case 'weekly':
            generateWeekly(evt, anchorDate, rangeStart, rangeEnd, interval, results);
            break;
        case 'monthly':
            generateMonthly(anchorDate, rangeStart, rangeEnd, interval, results);
            break;
        case 'yearly':
            generateYearly(anchorDate, rangeStart, rangeEnd, interval, results);
            break;
    }
    return results;
}
function generateDaily(anchor, rangeStart, rangeEnd, interval, out) {
    // Start from the first occurrence on or after rangeStart
    let current = { ...anchor };
    if ((0, date_util_1.compareDays)(current, rangeStart) < 0) {
        const diff = daysBetween(anchor, rangeStart);
        const steps = Math.ceil(diff / interval);
        current = (0, date_util_1.addDays)(anchor, steps * interval);
    }
    while ((0, date_util_1.compareDays)(current, rangeEnd) <= 0) {
        if ((0, date_util_1.compareDays)(current, anchor) >= 0) { // Don't go before anchor
            out.push({ ...current });
        }
        current = (0, date_util_1.addDays)(current, interval);
    }
}
function generateWeekly(evt, anchor, rangeStart, rangeEnd, interval, out) {
    const weekdays = evt.recurrence.weekdays ?? [anchorDow(anchor)];
    // Find the first week to start checking from
    let currentWeekAnchor = { ...anchor };
    if ((0, date_util_1.compareDays)(currentWeekAnchor, rangeStart) < 0) {
        const diff = daysBetween(anchor, rangeStart);
        const weeks = Math.ceil(diff / interval);
        currentWeekAnchor = (0, date_util_1.addDays)(anchor, weeks * interval);
    }
    while (true) {
        // For this week anchor, check each selected weekday
        let foundInRange = false;
        for (const wd of weekdays) {
            const dow = anchorDow(currentWeekAnchor);
            const dayOffset = ((wd - dow) + 7) % 7;
            const candidate = (0, date_util_1.addDays)(currentWeekAnchor, dayOffset);
            if ((0, date_util_1.compareDays)(candidate, rangeStart) >= 0 && (0, date_util_1.compareDays)(candidate, rangeEnd) <= 0) {
                out.push({ ...candidate });
                foundInRange = true;
            }
        }
        // Advance to next interval week
        currentWeekAnchor = (0, date_util_1.addDays)(currentWeekAnchor, interval * 7);
        // Stop if the earliest possible day in the next iteration is past rangeEnd
        const minNextDow = Math.min(...weekdays);
        const dow = anchorDow(currentWeekAnchor);
        const minOffset = ((minNextDow - dow) + 7) % 7;
        if ((0, date_util_1.compareDays)((0, date_util_1.addDays)(currentWeekAnchor, minOffset), rangeEnd) > 0)
            break;
    }
}
function generateMonthly(anchor, rangeStart, rangeEnd, interval, out) {
    // Monthly on the same day-of-month. Skip months that don't have that day.
    const targetDay = anchor.day;
    let currentMonthOffset = 0;
    if ((0, date_util_1.compareDays)(anchor, rangeStart) < 0) {
        const monthDiff = (rangeStart.year - anchor.year) * 12 + (rangeStart.month - anchor.month);
        currentMonthOffset = Math.max(0, Math.ceil(monthDiff / interval) * interval);
    }
    for (let offset = currentMonthOffset;; offset += interval) {
        const candidateDate = (0, date_util_1.addMonths)(anchor, offset);
        // Skip if the target day doesn't exist in this month (e.g., Jan 31 -> Feb has no 31st)
        if (candidateDate.day !== targetDay)
            continue;
        if ((0, date_util_1.compareDays)(candidateDate, rangeEnd) > 0)
            break;
        if ((0, date_util_1.compareDays)(candidateDate, anchor) >= 0 && (0, date_util_1.compareDays)(candidateDate, rangeStart) >= 0) {
            out.push({ ...candidateDate });
        }
    }
}
function generateYearly(anchor, rangeStart, rangeEnd, interval, out) {
    let current = { ...anchor };
    if ((0, date_util_1.compareDays)(current, rangeStart) < 0) {
        const yearDiff = rangeStart.year - anchor.year;
        const steps = Math.max(0, Math.ceil(yearDiff / interval));
        current = (0, date_util_1.addYears)(anchor, steps * interval);
    }
    while ((0, date_util_1.compareDays)(current, rangeEnd) <= 0 && (0, date_util_1.compareDays)(current, anchor) >= 0) {
        out.push({ ...current });
        current = (0, date_util_1.addYears)(current, interval);
    }
}
// ── Helpers ───────────────────────────────────────────────────────────
function daysBetween(a, b) {
    // Use native Date for this calculation only (no arithmetic on components)
    const da = new Date(a.year, a.month, a.day).getTime();
    const db = new Date(b.year, b.month, b.day).getTime();
    return Math.round((db - da) / 86400000);
}
function anchorDow(dc) {
    // dayOfWeek returns 0=Sun..6=Sat
    const dt = new Date(dc.year, dc.month, dc.day);
    return dt.getDay();
}
/** Get all occurrences for a set of events within a range. */
function getAllOccurrences(events, rangeStart, rangeEnd, exceptions, deletedOccurrences) {
    const all = [];
    for (const evt of events) {
        all.push(...expandEvent(evt, rangeStart, rangeEnd, exceptions, deletedOccurrences));
    }
    // Sort by date, then start time
    all.sort((a, b) => {
        const dc = (0, date_util_1.compareDays)(a.date, b.date);
        if (dc !== 0)
            return dc;
        if (a.isAllDay && !b.isAllDay)
            return -1;
        if (!a.isAllDay && b.isAllDay)
            return 1;
        if (a.startTime && b.startTime)
            return a.startTime.localeCompare(b.startTime);
        if (a.startTime)
            return -1;
        if (b.startTime)
            return 1;
        return 0;
    });
    return all;
}
/** Get occurrences for a specific date. */
function getOccurrencesForDate(events, date, exceptions, deletedOccurrences) {
    const rangeStart = { ...date };
    const rangeEnd = { ...date };
    return getAllOccurrences(events, rangeStart, rangeEnd, exceptions, deletedOccurrences);
}
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// ─── Application State & Persistence ──────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadState = loadState;
exports.saveState = saveState;
exports.init = init;
exports.getState = getState;
exports.subscribe = subscribe;
exports.addEvent = addEvent;
exports.updateEvent = updateEvent;
exports.deleteEvent = deleteEvent;
exports.getEvent = getEvent;
exports.addException = addException;
exports.deleteOccurrence = deleteOccurrence;
exports.undoDeleteOccurrence = undoDeleteOccurrence;
exports.setCurrentMonth = setCurrentMonth;
exports.setViewMode = setViewMode;
exports.setWeekStart = setWeekStart;
const date_util_1 = require("./date-util");
const constants_1 = require("./constants");
const STORAGE_KEY = 'calendar_planner_state_v1';
function defaultState() {
    const t = (0, date_util_1.today)();
    return {
        events: [],
        exceptions: [],
        deletedOccurrences: [],
        settings: { weekStart: constants_1.DEFAULT_WEEK_START, viewMode: 'month' },
        currentYear: t.year,
        currentMonth: t.month,
    };
}
function loadState() {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (raw) {
            const parsed = JSON.parse(raw);
            // Merge with defaults for forward compatibility
            return { ...defaultState(), ...parsed };
        }
    }
    catch { /* ignore */ }
    return defaultState();
}
function saveState(state) {
    try {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
    }
    catch { /* quota exceeded — silently fail */ }
}
// ── Event CRUD ────────────────────────────────────────────────────────
let state;
let listeners = [];
function init() {
    state = loadState();
    return state;
}
function getState() {
    return state;
}
function subscribe(fn) {
    listeners.push(fn);
    return () => { listeners = listeners.filter(l => l !== fn); };
}
function notify() {
    saveState(state);
    for (const fn of listeners)
        fn();
}
function addEvent(evt) {
    const newEvt = { ...evt, id: (0, date_util_1.generateId)() };
    state.events.push(newEvt);
    notify();
    return newEvt;
}
function updateEvent(id, updates) {
    const idx = state.events.findIndex(e => e.id === id);
    if (idx < 0)
        return;
    state.events[idx] = { ...state.events[idx], ...updates };
    notify();
}
function deleteEvent(id) {
    state.events = state.events.filter(e => e.id !== id);
    // Also clean up related exceptions and deleted occurrences
    state.exceptions = state.exceptions.filter(e => e.eventId !== id);
    state.deletedOccurrences = state.deletedOccurrences.filter(d => d.eventId !== id);
    notify();
}
function getEvent(id) {
    return state.events.find(e => e.id === id);
}
// ── Occurrence Exceptions ─────────────────────────────────────────────
function addException(evtId, date, override) {
    // Remove any existing exception for this occurrence
    state.exceptions = state.exceptions.filter(e => !(e.eventId === evtId && sameDaySimple(e.date, date)));
    state.exceptions.push({ eventId: evtId, date: { ...date }, override });
    notify();
}
function deleteOccurrence(evtId, date) {
    // Remove from deleted list if already there (toggle)
    const existing = state.deletedOccurrences.findIndex(d => d.eventId === evtId && sameDaySimple(d.date, date));
    if (existing >= 0) {
        state.deletedOccurrences.splice(existing, 1);
    }
    else {
        state.deletedOccurrences.push({ eventId: evtId, date: { ...date } });
    }
    notify();
}
function undoDeleteOccurrence(evtId, date) {
    state.deletedOccurrences = state.deletedOccurrences.filter(d => !(d.eventId === evtId && sameDaySimple(d.date, date)));
    notify();
}
// ── Navigation ────────────────────────────────────────────────────────
function setCurrentMonth(year, month) {
    state.currentYear = year;
    state.currentMonth = month;
    notify();
}
function setViewMode(mode) {
    state.settings.viewMode = mode;
    notify();
}
function setWeekStart(ws) {
    state.settings.weekStart = ws;
    notify();
}
// ── Helpers ───────────────────────────────────────────────────────────
function sameDaySimple(a, b) {
    return a.year === b.year && a.month === b.month && a.day === b.day;
}
};

// ── module: src/modal.ts ──
__mods["src/modal.ts"] = function (exports, require, module) {
"use strict";
// ─── Event Editor Modal ───────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.initModal = initModal;
exports.openModal = openModal;
exports.openEditModal = openEditModal;
exports.closeModal = closeModal;
const constants_1 = require("./constants");
let modalEl = null;
let currentCallback = null;
let editingId = null;
function initModal() {
    // Build the modal DOM once
    const overlay = document.createElement('div');
    overlay.id = 'event-modal-overlay';
    overlay.className = 'modal-overlay hidden';
    const modal = document.createElement('div');
    modal.id = 'event-modal';
    modal.className = 'modal';
    modal.innerHTML = `
    <div class="modal-header">
      <h2 id="modal-title-text">New Event</h2>
      <button id="modal-close" class="icon-btn" title="Close">&times;</button>
    </div>
    <div class="modal-body">
      <form id="event-form" novalidate>
        <!-- Title -->
        <div class="form-group">
          <label for="evt-title">Title</label>
          <input type="text" id="evt-title" required placeholder="Event title" autocomplete="off" />
          <span class="field-error" id="err-title"></span>
        </div>

        <!-- Date -->
        <div class="form-group">
          <label for="evt-date">Date</label>
          <input type="date" id="evt-date" required />
          <span class="field-error" id="err-date"></span>
        </div>

        <!-- All-day toggle -->
        <div class="form-group form-row">
          <label class="checkbox-label">
            <input type="checkbox" id="evt-allday" />
            <span>All day</span>
          </label>
        </div>

        <!-- Time range (hidden when all-day) -->
        <div class="form-row time-row" id="time-group">
          <div class="form-group flex-1">
            <label for="evt-start">Start</label>
            <input type="time" id="evt-start" step="900" />
          </div>
          <div class="form-group flex-1">
            <label for="evt-end">End</label>
            <input type="time" id="evt-end" step="900" />
            <span class="field-error" id="err-time"></span>
          </div>
        </div>

        <!-- Color -->
        <div class="form-group">
          <label>Color</label>
          <div class="color-picker" id="color-picker"></div>
        </div>

        <!-- Recurrence -->
        <div class="form-group">
          <label for="evt-recurrence-type">Repeats</label>
          <select id="evt-recurrence-type">
            <option value="none">Does not repeat</option>
            <option value="daily">Daily</option>
            <option value="weekly">Weekly</option>
            <option value="monthly">Monthly</option>
            <option value="yearly">Yearly</option>
          </select>
        </div>

        <!-- Weekly weekday selection -->
        <div class="form-group hidden" id="weekday-group">
          <label>On these days:</label>
          <div class="weekday-picker" id="weekday-picker"></div>
        </div>

        <!-- Notes -->
        <div class="form-group">
          <label for="evt-notes">Notes</label>
          <textarea id="evt-notes" rows="2" placeholder="Optional notes…"></textarea>
        </div>

        <!-- Actions -->
        <div class="modal-actions">
          <button type="button" id="modal-delete" class="btn btn-danger hidden">Delete Event</button>
          <div class="spacer"></div>
          <button type="button" id="modal-cancel" class="btn btn-secondary">Cancel</button>
          <button type="submit" id="modal-save" class="btn btn-primary">Save</button>
        </div>
      </form>
    </div>
  `;
    overlay.appendChild(modal);
    document.body.appendChild(overlay);
    modalEl = overlay;
    // Build color picker
    const colorPicker = document.getElementById('color-picker');
    for (const c of constants_1.EVENT_COLORS) {
        const swatch = document.createElement('button');
        swatch.type = 'button';
        swatch.className = 'color-swatch';
        swatch.style.backgroundColor = c.value;
        swatch.dataset.color = c.value;
        swatch.title = c.name;
        colorPicker.appendChild(swatch);
    }
    // Build weekday picker
    const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    const weekdayPicker = document.getElementById('weekday-picker');
    for (let i = 0; i < 7; i++) {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'weekday-btn';
        btn.textContent = dayNames[i];
        btn.dataset.day = String(i);
        weekdayPicker.appendChild(btn);
    }
    // Wire events
    wireModalEvents(overlay, modal);
}
function wireModalEvents(overlay, modal) {
    const closeBtn = document.getElementById('modal-close');
    const cancelBtn = document.getElementById('modal-cancel');
    const deleteBtn = document.getElementById('modal-delete');
    const form = document.getElementById('event-form');
    const alldayCheck = document.getElementById('evt-allday');
    const timeGroup = document.getElementById('time-group');
    const recurrenceType = document.getElementById('evt-recurrence-type');
    const weekdayGroup = document.getElementById('weekday-group');
    closeBtn.addEventListener('click', closeModal);
    cancelBtn.addEventListener('click', closeModal);
    overlay.addEventListener('click', (e) => { if (e.target === overlay)
        closeModal(); });
    alldayCheck.addEventListener('change', () => {
        timeGroup.classList.toggle('hidden', alldayCheck.checked);
    });
    recurrenceType.addEventListener('change', () => {
        weekdayGroup.classList.toggle('hidden', recurrenceType.value !== 'weekly');
    });
    // Color picker selection
    const colorPicker = document.getElementById('color-picker');
    let selectedColor = constants_1.DEFAULT_COLOR;
    colorPicker.addEventListener('click', (e) => {
        const swatch = e.target.closest('.color-swatch');
        if (!swatch)
            return;
        selectedColor = swatch.dataset.color || constants_1.DEFAULT_COLOR;
        document.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
        swatch.classList.add('selected');
    });
    // Weekday picker selection
    const weekdayPicker = document.getElementById('weekday-picker');
    let selectedWeekdays = [];
    weekdayPicker.addEventListener('click', (e) => {
        const btn = e.target.closest('.weekday-btn');
        if (!btn)
            return;
        const day = parseInt(btn.dataset.day || '0');
        if (selectedWeekdays.includes(day)) {
            selectedWeekdays = selectedWeekdays.filter(d => d !== day);
            btn.classList.remove('selected');
        }
        else {
            selectedWeekdays.push(day);
            btn.classList.add('selected');
        }
    });
    // Form submit
    form.addEventListener('submit', (e) => {
        e.preventDefault();
        clearErrors();
        const title = document.getElementById('evt-title').value.trim();
        const dateVal = document.getElementById('evt-date').value;
        const isAllDay = alldayCheck.checked;
        const startTime = document.getElementById('evt-start').value || undefined;
        const endTime = document.getElementById('evt-end').value || undefined;
        const notes = document.getElementById('evt-notes').value.trim() || undefined;
        // Validation
        let valid = true;
        if (!title) {
            setFieldError('err-title', 'Title is required');
            valid = false;
        }
        if (!dateVal) {
            setFieldError('err-date', 'Date is required');
            valid = false;
        }
        if (!isAllDay && !startTime) {
            setFieldError('err-time', 'Start time is required');
            valid = false;
        }
        if (!isAllDay && startTime && endTime && endTime <= startTime) {
            setFieldError('err-time', 'End time must be after start time');
            valid = false;
        }
        if (!valid)
            return;
        const [y, m, d] = dateVal.split('-').map(Number);
        const recurrenceTypeVal = recurrenceType.value;
        const evt = {
            title,
            date: { year: y, month: m - 1, day: d },
            isAllDay,
            startTime,
            endTime,
            color: selectedColor,
            notes,
            recurrence: {
                type: recurrenceTypeVal,
                weekdays: recurrenceTypeVal === 'weekly' ? [...selectedWeekdays].sort() : undefined,
            },
        };
        currentCallback?.onSave(evt);
    });
    // Delete button
    deleteBtn.addEventListener('click', () => {
        if (editingId && confirm('Delete this event?')) {
            // Signal deletion via a special save with null title — handled by caller
            const evt = {
                title: '', date: { year: 0, month: 0, day: 1 }, isAllDay: false,
                color: constants_1.DEFAULT_COLOR, recurrence: { type: 'none' },
            };
            currentCallback?.onSave(evt); // Caller checks for empty title = delete signal
        }
    });
    // Store getters for form data access
    modalEl._getSelectedColor = () => selectedColor;
    modalEl._getSelectedWeekdays = () => [...selectedWeekdays];
}
function setFieldError(id, msg) {
    const el = document.getElementById(id);
    if (el)
        el.textContent = msg;
}
function clearErrors() {
    document.querySelectorAll('.field-error').forEach(el => el.textContent = '');
}
function openModal(callbacks, initialDate) {
    currentCallback = callbacks;
    editingId = null;
    const titleText = document.getElementById('modal-title-text');
    titleText.textContent = 'New Event';
    // Reset form
    document.getElementById('evt-title').value = '';
    document.getElementById('evt-date').value = initialDate
        ? `${initialDate.year}-${String(initialDate.month + 1).padStart(2, '0')}-${String(initialDate.day).padStart(2, '0')}`
        : new Date().toISOString().slice(0, 10);
    document.getElementById('evt-allday').checked = false;
    document.getElementById('evt-start').value = '09:00';
    document.getElementById('evt-end').value = '10:00';
    document.getElementById('evt-notes').value = '';
    document.getElementById('evt-recurrence-type').value = 'none';
    // Reset color selection
    document.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
    const firstSwatch = document.querySelector('.color-swatch');
    if (firstSwatch)
        firstSwatch.classList.add('selected');
    // Reset weekday selection
    document.querySelectorAll('.weekday-btn').forEach(b => b.classList.remove('selected'));
    // Show/hide groups
    document.getElementById('time-group').classList.remove('hidden');
    document.getElementById('weekday-group').classList.add('hidden');
    document.getElementById('modal-delete').classList.add('hidden');
    clearErrors();
    modalEl.classList.remove('hidden');
}
function openEditModal(callbacks, evt) {
    currentCallback = callbacks;
    editingId = evt.id;
    const titleText = document.getElementById('modal-title-text');
    titleText.textContent = 'Edit Event';
    document.getElementById('evt-title').value = evt.title;
    document.getElementById('evt-date').value =
        `${evt.date.year}-${String(evt.date.month + 1).padStart(2, '0')}-${String(evt.date.day).padStart(2, '0')}`;
    document.getElementById('evt-allday').checked = evt.isAllDay;
    document.getElementById('evt-start').value = evt.startTime || '';
    document.getElementById('evt-end').value = evt.endTime || '';
    document.getElementById('evt-notes').value = evt.notes || '';
    document.getElementById('evt-recurrence-type').value = evt.recurrence.type;
    // Color selection
    document.querySelectorAll('.color-swatch').forEach(s => {
        s.classList.toggle('selected', s.dataset.color === evt.color);
    });
    // Weekday selection
    const selectedWeekdays = evt.recurrence.weekdays ?? [];
    document.querySelectorAll('.weekday-btn').forEach(b => {
        b.classList.toggle('selected', selectedWeekdays.includes(parseInt(b.dataset.day || '0')));
    });
    // Show/hide groups
    document.getElementById('time-group').classList.toggle('hidden', evt.isAllDay);
    document.getElementById('weekday-group').classList.toggle('hidden', evt.recurrence.type !== 'weekly');
    document.getElementById('modal-delete').classList.remove('hidden');
    clearErrors();
    modalEl.classList.remove('hidden');
}
function closeModal() {
    if (modalEl)
        modalEl.classList.add('hidden');
    currentCallback = null;
    editingId = null;
}
};

// ── module: src/render.ts ──
__mods["src/render.ts"] = function (exports, require, module) {
"use strict";
// ─── Shared Rendering Helpers ─────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEl = createEl;
exports.renderEventChip = renderEventChip;
exports.today = today;
exports.isToday = isToday;
exports.showToast = showToast;
exports.showConfirm = showConfirm;
const date_util_1 = require("./date-util");
function createEl(tag, className, text) {
    const el = document.createElement(tag);
    if (className)
        el.className = className;
    if (text !== undefined)
        el.textContent = text;
    return el;
}
// ── Event Chip Rendering ──────────────────────────────────────────────
/** Render a compact event chip for month view cells. */
function renderEventChip(evt) {
    const chip = createEl('div', 'event-chip');
    chip.style.borderLeftColor = evt.color || '#3b82f6';
    chip.textContent = evt.title;
    return chip;
}
// ── Today Detection ───────────────────────────────────────────────────
let _todayCache = null;
function today() {
    if (!_todayCache)
        _todayCache = (0, date_util_1.today)();
    return _todayCache;
}
function isToday(dc) {
    return (0, date_util_1.sameDay)(dc, today());
}
// ── Toast Notifications ───────────────────────────────────────────────
let toastContainerEl = null;
function ensureToastContainer() {
    if (!toastContainerEl) {
        toastContainerEl = createEl('div', 'toast-container');
        document.body.appendChild(toastContainerEl);
    }
    return toastContainerEl;
}
function showToast(message, duration = 3000) {
    const container = ensureToastContainer();
    const toast = createEl('div', 'toast', message);
    container.appendChild(toast);
    setTimeout(() => {
        toast.classList.add('fade-out');
        setTimeout(() => toast.remove(), 300);
    }, duration);
}
// ── Confirm Dialog ────────────────────────────────────────────────────
function showConfirm(message) {
    return new Promise((resolve) => {
        const overlay = createEl('div', 'confirm-overlay');
        const dialog = createEl('div', 'confirm-dialog');
        dialog.innerHTML = `
      <p>${message}</p>
      <div class="confirm-actions">
        <button class="btn btn-secondary" id="confirm-cancel">Cancel</button>
        <button class="btn btn-danger" id="confirm-ok">Delete</button>
      </div>
    `;
        overlay.appendChild(dialog);
        document.body.appendChild(overlay);
        const cancelBtn = dialog.querySelector('#confirm-cancel');
        const okBtn = dialog.querySelector('#confirm-ok');
        cancelBtn.addEventListener('click', () => { resolve(false); cleanup(); });
        okBtn.addEventListener('click', () => { resolve(true); cleanup(); });
        overlay.addEventListener('click', (e) => { if (e.target === overlay) {
            resolve(false);
            cleanup();
        } });
        function cleanup() {
            setTimeout(() => overlay.remove(), 200);
        }
    });
}
};

// ── module: src/views/month-view.ts ──
__mods["src/views/month-view.ts"] = function (exports, require, module) {
"use strict";
// ─── Month View ───────────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
exports.render = render;
const date_util_1 = require("../date-util");
const constants_1 = require("../constants");
const render_1 = require("../render");
let options = null;
let currentEvents = new Map(); // dateStr -> events[]
function init(opts) {
    options = opts;
}
function render(year, month, allOccurrences, weekStart) {
    if (!options)
        return;
    const container = document.getElementById(options.containerId);
    if (!container)
        return;
    // Build grid - use passed weekStart or fall back to options
    const ws = (weekStart !== undefined ? weekStart : options.weekStart);
    const grid = (0, date_util_1.buildMonthGrid)(year, month, ws);
    // Index events by date string for quick lookup
    currentEvents.clear();
    for (const evt of allOccurrences) {
        const key = `${evt.date.year}-${evt.date.month}-${evt.date.day}`;
        if (!currentEvents.has(key))
            currentEvents.set(key, []);
        currentEvents.get(key).push(evt);
    }
    // Render header row with day names
    container.innerHTML = '';
    const headerRow = (0, render_1.createEl)('div', 'month-header-row');
    for (let i = 0; i < 7; i++) {
        // Calculate which day of week this column represents
        let dow;
        if (ws === 1) {
            dow = (i + 1) % 7; // Mon=1, Tue=2, ..., Sun=0
        }
        else {
            dow = i; // Sun=0, Mon=1, ...
        }
        const dayLabel = (0, render_1.createEl)('div', 'month-day-label');
        dayLabel.textContent = (0, date_util_1.dayNameShort)(dow);
        headerRow.appendChild(dayLabel);
    }
    container.appendChild(headerRow);
    // Render grid rows
    for (const row of grid) {
        const rowEl = (0, render_1.createEl)('div', 'month-row');
        for (const cell of row) {
            const cellEl = renderCell(cell.date, cell.isCurrentMonth);
            rowEl.appendChild(cellEl);
        }
        container.appendChild(rowEl);
    }
}
function renderCell(date, isCurrentMonth) {
    const cell = (0, render_1.createEl)('div', 'month-cell');
    if (!isCurrentMonth)
        cell.classList.add('other-month');
    if ((0, render_1.isToday)(date))
        cell.classList.add('today');
    // Day number
    const dayNum = (0, render_1.createEl)('span', 'day-number');
    dayNum.textContent = String(date.day);
    cell.appendChild(dayNum);
    // Events for this date
    const key = `${date.year}-${date.month}-${date.day}`;
    const events = currentEvents.get(key) || [];
    if (events.length > 0) {
        const eventList = (0, render_1.createEl)('div', 'cell-events');
        const showCount = Math.min(events.length, constants_1.MAX_CELL_EVENTS);
        for (let i = 0; i < showCount; i++) {
            const chip = (0, render_1.renderEventChip)(events[i]);
            chip.addEventListener('click', (e) => {
                e.stopPropagation();
                options.onEventClick(events[i], date);
            });
            // Make chips draggable
            makeDraggable(chip, events[i].eventId, date);
            eventList.appendChild(chip);
        }
        if (events.length > constants_1.MAX_CELL_EVENTS) {
            const more = (0, render_1.createEl)('div', 'event-chip event-more');
            more.textContent = `+${events.length - constants_1.MAX_CELL_EVENTS} more`;
            more.addEventListener('click', (e) => {
                e.stopPropagation();
                showDayPopover(date, events);
            });
            eventList.appendChild(more);
        }
        cell.appendChild(eventList);
    }
    // Click to create new event
    cell.addEventListener('click', () => {
        options.onDayClick(date);
    });
    // Drag drop target
    makeDropTarget(cell, date);
    return cell;
}
// ── Drag & Drop ───────────────────────────────────────────────────────
let dragData = null;
function makeDraggable(chip, eventId, fromDate) {
    chip.draggable = true;
    chip.addEventListener('dragstart', (e) => {
        dragData = { eventId, fromDate };
        e.dataTransfer.effectAllowed = 'move';
        // Set a ghost image
        const ghost = (0, render_1.createEl)('div', 'event-chip');
        ghost.textContent = 'Move event';
        document.body.appendChild(ghost);
        e.dataTransfer.setDragImage?.(ghost, 0, 0);
        setTimeout(() => ghost.remove(), 0);
    });
    chip.addEventListener('dragend', () => { dragData = null; });
}
function makeDropTarget(cell, toDate) {
    cell.addEventListener('dragover', (e) => {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';
        cell.classList.add('drag-over');
    });
    cell.addEventListener('dragleave', () => {
        cell.classList.remove('drag-over');
    });
    cell.addEventListener('drop', (e) => {
        e.preventDefault();
        cell.classList.remove('drag-over');
        if (!dragData)
            return;
        options.onDragDrop(dragData.eventId, dragData.fromDate, toDate);
        dragData = null;
    });
}
// ── Day Popover ───────────────────────────────────────────────────────
function showDayPopover(date, events) {
    // Remove existing popover
    const existing = document.querySelector('.day-popover');
    if (existing)
        existing.remove();
    const popover = (0, render_1.createEl)('div', 'day-popover');
    const header = (0, render_1.createEl)('div', 'popover-header');
    header.textContent = `${date.month + 1}/${date.day}/${date.year} — ${events.length} event${events.length !== 1 ? 's' : ''}`;
    popover.appendChild(header);
    for (const evt of events) {
        const item = (0, render_1.createEl)('div', 'popover-event');
        item.style.borderLeftColor = evt.color || '#3b82f6';
        item.textContent = evt.title;
        item.addEventListener('click', () => {
            options.onEventClick(evt, date);
            popover.remove();
        });
        popover.appendChild(item);
    }
    document.body.appendChild(popover);
    // Position near the cell (approximate)
    const container = document.getElementById(options.containerId);
    const rect = container.getBoundingClientRect();
    popover.style.position = 'fixed';
    popover.style.top = `${rect.top + 100}px`;
    popover.style.left = `${rect.left + 200}px`;
    // Close on outside click
    setTimeout(() => {
        document.addEventListener('click', (e) => {
            if (!popover.contains(e.target))
                popover.remove();
        }, { once: true });
    }, 10);
}
};

// ── module: src/views/week-view.ts ──
__mods["src/views/week-view.ts"] = function (exports, require, module) {
"use strict";
// ─── Week View ────────────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
exports.render = render;
const date_util_1 = require("../date-util");
const constants_1 = require("../constants");
const render_1 = require("../render");
let options = null;
const PIXELS_PER_MINUTE = 4; // pixels per minute for event height calculation
function init(opts) {
    options = opts;
}
function render(date, allOccurrences, weekStart) {
    if (!options)
        return;
    const container = document.getElementById(options.containerId);
    if (!container)
        return;
    const ws = (weekStart !== undefined ? weekStart : options.weekStart);
    const weekDays = (0, date_util_1.getWeekDays)(date, ws);
    const totalMinutes = (constants_1.HOUR_END - constants_1.HOUR_START) * 60;
    // Build the week view layout
    container.innerHTML = '';
    container.style.position = 'relative';
    // Header row with day names
    const headerRow = (0, render_1.createEl)('div', 'week-header');
    for (const wd of weekDays) {
        const dayHeader = (0, render_1.createEl)('div', 'week-day-header');
        if ((0, render_1.isToday)(wd))
            dayHeader.classList.add('today');
        const dowLabel = (0, render_1.createEl)('div', 'day-label');
        dowLabel.textContent = (0, date_util_1.dayNameShort)(getDow(wd));
        dayHeader.appendChild(dowLabel);
        const dateLabel = (0, render_1.createEl)('div', 'date-label');
        dateLabel.textContent = String(wd.day);
        if ((0, render_1.isToday)(wd)) {
            dateLabel.className += ' today-date';
        }
        dayHeader.appendChild(dateLabel);
        headerRow.appendChild(dayHeader);
    }
    container.appendChild(headerRow);
    // All-day events strip
    const allDayStrip = (0, render_1.createEl)('div', 'all-day-strip');
    for (const wd of weekDays) {
        const key = `${wd.year}-${wd.month}-${wd.day}`;
        const dayEvents = allOccurrences.filter(e => (0, date_util_1.sameDay)(e.date, wd) && e.isAllDay);
        const slot = (0, render_1.createEl)('div', 'all-day-slot');
        if ((0, render_1.isToday)(wd))
            slot.classList.add('today');
        for (const evt of dayEvents) {
            const chip = (0, render_1.createEl)('div', 'event-chip all-day-chip');
            chip.style.borderLeftColor = evt.color || '#3b82f6';
            chip.textContent = evt.title;
            chip.addEventListener('click', (e) => { e.stopPropagation(); options.onEventClick(evt, wd); });
            slot.appendChild(chip);
        }
        allDayStrip.appendChild(slot);
    }
    container.appendChild(allDayStrip);
    // Time grid
    const timeGrid = (0, render_1.createEl)('div', 'time-grid');
    timeGrid.style.height = `${totalMinutes * PIXELS_PER_MINUTE}px`;
    // Hour labels (left column)
    const hourLabels = (0, render_1.createEl)('div', 'hour-labels');
    for (let h = constants_1.HOUR_START; h <= constants_1.HOUR_END; h++) {
        const label = (0, render_1.createEl)('div', 'hour-label');
        label.textContent = formatHourLabel(h);
        hourLabels.appendChild(label);
    }
    timeGrid.appendChild(hourLabels);
    // Day columns with events
    const dayColumns = (0, render_1.createEl)('div', 'day-columns');
    for (const wd of weekDays) {
        const col = (0, render_1.createEl)('div', 'week-day-column');
        if ((0, render_1.isToday)(wd))
            col.classList.add('today');
        // Hour slots (clickable)
        for (let h = constants_1.HOUR_START; h <= constants_1.HOUR_END; h++) {
            for (let m = 0; m < 60; m += constants_1.SNAP_MINUTES) {
                const slot = (0, render_1.createEl)('div', 'time-slot');
                slot.addEventListener('click', () => options.onTimeSlotClick(wd, h, m));
                col.appendChild(slot);
            }
        }
        // Render events as positioned blocks
        const dayEvents = allOccurrences.filter(e => (0, date_util_1.sameDay)(e.date, wd) && !e.isAllDay);
        for (const evt of dayEvents) {
            const block = renderEventBlock(evt, totalMinutes);
            col.appendChild(block);
        }
        // Current time indicator
        if ((0, render_1.isToday)(wd)) {
            const indicator = (0, render_1.createEl)('div', 'current-time-indicator');
            const now = getNow();
            const minutesFromStart = (now.getHours() - constants_1.HOUR_START) * 60 + now.getMinutes();
            if (minutesFromStart >= 0 && minutesFromStart <= totalMinutes) {
                indicator.style.top = `${minutesFromStart * PIXELS_PER_MINUTE}px`;
            }
            else {
                indicator.remove();
            }
            col.appendChild(indicator);
        }
        dayColumns.appendChild(col);
    }
    timeGrid.appendChild(dayColumns);
    container.appendChild(timeGrid);
}
function renderEventBlock(evt, totalMinutes) {
    const block = (0, render_1.createEl)('div', 'week-event-block');
    block.style.borderLeftColor = evt.color || '#3b82f6';
    block.style.backgroundColor = (evt.color || '#3b82f6') + '1a'; // ~10% opacity
    const startMin = timeToMinutes(evt.startTime);
    const endMin = timeToMinutes(evt.endTime);
    if (startMin < 0 || endMin <= startMin)
        return block;
    const topPx = Math.max(0, (startMin - constants_1.HOUR_START * 60)) * PIXELS_PER_MINUTE;
    const heightPx = Math.max(20, (endMin - startMin) * PIXELS_PER_MINUTE);
    block.style.position = 'absolute';
    block.style.top = `${topPx}px`;
    block.style.height = `${heightPx}px`;
    block.style.left = '4px';
    block.style.right = '4px';
    block.style.borderRadius = '4px';
    block.style.padding = '2px 6px';
    block.style.fontSize = '11px';
    block.style.overflow = 'hidden';
    block.style.cursor = 'pointer';
    const timeStr = `${evt.startTime} – ${evt.endTime}`;
    block.innerHTML = `<div class="event-time">${timeStr}</div><div class="event-title">${evt.title}</div>`;
    // Click to edit
    block.addEventListener('click', (e) => {
        e.stopPropagation();
        options.onEventClick(evt, evt.date);
    });
    return block;
}
// ── Helpers ───────────────────────────────────────────────────────────
function getDow(dc) {
    const d = new Date(dc.year, dc.month, dc.day);
    return d.getDay(); // 0=Sun..6=Sat
}
function formatHourLabel(h) {
    if (h === 0)
        return '12 AM';
    if (h < 12)
        return `${h} AM`;
    if (h === 12)
        return '12 PM';
    return `${h - 12} PM`;
}
function timeToMinutes(t) {
    if (!t)
        return constants_1.HOUR_START * 60;
    const [h, m] = t.split(':').map(Number);
    return h * 60 + m;
}
function getNow() {
    return new Date();
}
};

// ── module: src/views/agenda-view.ts ──
__mods["src/views/agenda-view.ts"] = function (exports, require, module) {
"use strict";
// ─── Agenda View ──────────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
exports.render = render;
const date_util_1 = require("../date-util");
const render_1 = require("../render");
let options = null;
function init(opts) {
    options = opts;
}
/** Render agenda view starting from `fromDate` for the next `numDays` days. */
function render(fromDate, allOccurrences, numDays = 30) {
    if (!options)
        return;
    const container = document.getElementById(options.containerId);
    if (!container)
        return;
    container.innerHTML = '';
    // Group events by date
    const grouped = new Map();
    for (const evt of allOccurrences) {
        const key = `${evt.date.year}-${evt.date.month}-${evt.date.day}`;
        if (!grouped.has(key))
            grouped.set(key, []);
        grouped.get(key).push(evt);
    }
    // Iterate through each day and show events
    let hasEvents = false;
    for (let i = 0; i < numDays; i++) {
        const date = (0, date_util_1.addDays)(fromDate, i);
        const key = `${date.year}-${date.month}-${date.day}`;
        const events = grouped.get(key) || [];
        if (events.length === 0)
            continue;
        hasEvents = true;
        // Day header
        const dayHeader = createEl('div', 'agenda-day-header');
        let label;
        if (i === 0) {
            label = (0, render_1.isToday)(date) ? 'Today' : `${(0, date_util_1.monthName)(date.month)} ${date.day}`;
        }
        else if (i === 1) {
            label = 'Tomorrow';
        }
        else {
            label = `${(0, date_util_1.monthName)(date.month)} ${date.day}`;
        }
        dayHeader.textContent = label;
        if ((0, render_1.isToday)(date)) {
            const todayBadge = createEl('span', 'today-badge');
            todayBadge.textContent = '';
            dayHeader.appendChild(todayBadge);
        }
        container.appendChild(dayHeader);
        // Event items
        for (const evt of events) {
            const item = createEl('div', 'agenda-event-item');
            if (evt.isAllDay) {
                item.classList.add('all-day');
            }
            // Color indicator
            const colorBar = createEl('div', 'agenda-color-bar');
            colorBar.style.backgroundColor = evt.color || '#3b82f6';
            item.appendChild(colorBar);
            // Content
            const content = createEl('div', 'agenda-event-content');
            const titleEl = createEl('div', 'agenda-event-title', evt.title);
            content.appendChild(titleEl);
            if (evt.isAllDay) {
                const timeEl = createEl('div', 'agenda-event-time', 'All day');
                content.appendChild(timeEl);
            }
            else {
                const timeEl = createEl('div', 'agenda-event-time', `${(0, date_util_1.formatTime)(evt.startTime)} – ${(0, date_util_1.formatTime)(evt.endTime)}`);
                content.appendChild(timeEl);
            }
            item.appendChild(content);
            item.addEventListener('click', () => options.onEventClick(evt));
            container.appendChild(item);
        }
    }
    if (!hasEvents) {
        const empty = createEl('div', 'agenda-empty');
        empty.textContent = 'No upcoming events';
        container.appendChild(empty);
    }
}
function createEl(tag, className, text) {
    const el = document.createElement(tag);
    if (className)
        el.className = className;
    if (text !== undefined)
        el.textContent = text;
    return el;
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"typescript","sessionId":"c5bfb438-2623-433b-ae81-94e7aa721278","tokensIn":5030518,"tokensOut":56898,"tokensTotal":5087416,"turns":105,"toolCalls":105,"failedToolCalls":3,"timestamp":"2026-07-30T16:23:17.597Z"} -->