Calendar
GPT 5.6 terra · typescript

Browser check: Artifact runtime error. This is a technical check, not a quality review.
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 33,659 bytes · SHA-256 67acb5f40f37
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Daymark — Calendar & Planner</title>
<style>
/* ── css: app ── */
*{box-sizing:border-box}body{margin:0;background:#f7f8fc;color:#202434;font:14px/1.35 Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}button,input,textarea,select{font:inherit}button{cursor:pointer;border:0}.topbar{height:76px;background:#fff;border-bottom:1px solid #e9eaf0;display:flex;align-items:center;gap:28px;padding:0 28px;position:sticky;top:0;z-index:5}.brand{font-weight:750;font-size:20px;letter-spacing:-.8px;display:flex;align-items:center;gap:9px}.brand-mark{display:grid;place-items:center;width:29px;height:29px;border-radius:9px;background:#625bf6;color:#fff;font-size:20px}.nav{display:flex;align-items:center;gap:7px;flex:1}.nav h1{font-size:20px;letter-spacing:-.5px;margin:0 0 0 9px;min-width:185px}.top-actions{display:flex;align-items:center;gap:12px}.button,.icon-btn,.year-btn{border-radius:8px;padding:8px 12px;color:#303548;background:white;border:1px solid #dde0e9;font-weight:650}.button:hover,.icon-btn:hover,.year-btn:hover{background:#f4f5fa}.button.primary{background:#625bf6;color:#fff;border-color:#625bf6;box-shadow:0 4px 10px #625bf633}.button.danger{color:#d94357}.icon-btn{font-size:27px;line-height:18px;width:34px;padding:4px}.year-btn{font-size:11px;padding:7px}.view-switch{display:flex;background:#f0f1f6;border-radius:9px;padding:3px}.view-switch button{padding:6px 10px;border-radius:6px;background:transparent;color:#676b7b;font-size:12px;font-weight:700}.view-switch button.active{background:#fff;color:#3f3bcd;box-shadow:0 1px 4px #17234b19}#content{padding:24px 28px 40px;max-width:1600px;margin:auto}.calendar-shell{background:#fff;border:1px solid #e5e7ef;border-radius:14px;overflow:hidden;box-shadow:0 10px 26px #2c35670a}.weekdays,.month-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr))}.weekdays{background:#fbfbfd;border-bottom:1px solid #e8e9f0}.weekdays div{padding:11px 12px;color:#7a7f90;font-size:11px;font-weight:800;letter-spacing:.5px;text-transform:uppercase}.month-grid{grid-template-rows:repeat(6,128px)}.day-cell{padding:8px;border-right:1px solid #eceef4;border-bottom:1px solid #eceef4;min-width:0;position:relative;transition:background .15s}.day-cell:nth-child(7n){border-right:0}.day-cell:hover{background:#fafaff}.day-cell.outside{background:#fcfcfe}.day-cell.outside .day-num{color:#b9bcc7}.day-cell.drop-target{background:#f0efff}.day-num{width:25px;height:25px;display:grid;place-items:center;border-radius:50%;font-size:12px;font-weight:700;margin-bottom:4px}.today .day-num{background:#625bf6;color:#fff}.event-chip{display:flex;align-items:center;gap:5px;border-left:3px solid var(--c);background:var(--bg);color:#313545;margin:2px 0;padding:3px 5px;border-radius:4px;font-size:11px;line-height:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab}.event-chip:hover{filter:brightness(.98)}.more-link{background:transparent;color:#5a56db;font-weight:750;font-size:11px;padding:2px 0}.empty-note{padding:30px;color:#8a8fa0}.week-shell{background:#fff;border:1px solid #e5e7ef;border-radius:14px;overflow:auto;box-shadow:0 10px 26px #2c35670a}.week-head,.week-body{min-width:820px}.week-head{display:grid;grid-template-columns:62px repeat(7,1fr);border-bottom:1px solid #e5e7ef}.week-head>div{padding:10px;text-align:center;border-left:1px solid #eef0f4;font-size:12px;font-weight:700;color:#606577}.week-head .head-today{color:#514bd3}.week-head strong{display:block;font-size:20px;color:#262a3a}.all-day-row{display:grid;grid-template-columns:62px repeat(7,1fr);min-width:820px;border-bottom:1px solid #e5e7ef;min-height:36px}.all-day-label{font-size:10px;color:#8e93a2;padding:11px 7px}.allday-col{border-left:1px solid #eef0f4;padding:3px}.week-body{display:grid;grid-template-columns:62px repeat(7,1fr);height:768px;position:relative;background:repeating-linear-gradient(to bottom,transparent 0,transparent 47px,#edf0f4 48px)}.time-labels{position:relative}.time-label{height:48px;text-align:right;padding:0 9px;color:#989cac;font-size:10px;transform:translateY(-7px)}.day-lane{position:relative;border-left:1px solid #e9ebf1}.week-event{position:absolute;left:4px;right:4px;border-left:3px solid var(--c);background:var(--bg);border-radius:5px;padding:4px 6px;overflow:hidden;font-size:11px;line-height:14px;color:#303344;cursor:grab;z-index:2}.week-event b{display:block;font-size:11px}.time-now{position:absolute;left:62px;right:0;height:2px;background:#ed5969;z-index:4;pointer-events:none}.time-now:before{content:"";display:block;width:7px;height:7px;background:#ed5969;border-radius:50%;transform:translate(-3px,-3px)}.agenda{max-width:900px;margin:auto}.agenda-day{margin-bottom:22px}.agenda-date{display:flex;gap:12px;align-items:baseline;margin:0 0 8px 4px}.agenda-date b{font-size:14px}.agenda-date span{font-size:11px;color:#848999;text-transform:uppercase;letter-spacing:.5px}.agenda-item{background:#fff;border:1px solid #e5e7ef;border-left:4px solid var(--c);padding:12px 15px;border-radius:8px;margin:6px 0;display:grid;grid-template-columns:110px 1fr auto;gap:12px;align-items:center;cursor:pointer}.agenda-time{font-size:12px;color:#767b8c}.agenda-title{font-weight:750}.agenda-notes{font-size:12px;color:#818696;margin-top:2px}.hidden{display:none!important}.popover{position:fixed;width:285px;max-height:350px;overflow:auto;background:#fff;border:1px solid #e1e3ed;border-radius:10px;padding:10px;box-shadow:0 14px 35px #1b235133;z-index:10}.popover h3{font-size:13px;margin:2px 3px 8px}.modal{position:fixed;inset:0;background:#20233770;display:grid;place-items:center;z-index:20;padding:16px}.dialog{background:#fff;width:min(550px,100%);max-height:92vh;overflow:auto;border-radius:14px;padding:24px;box-shadow:0 20px 55px #11152b55}.dialog.small{width:min(430px,100%)}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start}.dialog h2{margin:2px 0 18px;font-size:21px;letter-spacing:-.5px}.eyebrow{color:#7973ed;font-size:10px;font-weight:850;letter-spacing:1.1px;margin:0}.close{font-size:26px;background:transparent;color:#777c8d}.dialog label{display:flex;flex-direction:column;gap:5px;font-size:12px;font-weight:750;color:#505568;margin:12px 0}.dialog input:not([type=checkbox]),.dialog textarea,.dialog select{width:100%;border:1px solid #dcdfea;border-radius:7px;padding:9px;color:#2b2f40;background:#fff}.form-grid{display:grid;grid-template-columns:1.5fr 1fr 1fr;gap:10px}.check{flex-direction:row!important;align-items:center}.form-row{display:flex;align-items:center;gap:12px;margin:10px 0;font-weight:700;font-size:12px}.palette{display:flex;gap:8px}.color-dot{width:20px;height:20px;border-radius:50%;background:var(--c);outline:2px solid transparent;outline-offset:2px}.color-dot.selected{outline-color:#25283a}details{margin-top:10px;border-top:1px solid #eceef4;padding-top:10px}summary{cursor:pointer;font-weight:750;font-size:12px}.repeat-fields{display:flex;gap:12px;align-items:center}.repeat-fields label{flex:1}.weekday-pick{display:flex;gap:4px;align-self:flex-end;margin-bottom:12px}.weekday-pick button{border:1px solid #dfe1e9;border-radius:5px;background:#fff;color:#676b7c;font-size:10px;padding:5px}.weekday-pick button.selected{background:#625bf6;color:#fff;border-color:#625bf6}.form-error{color:#d63d51;font-size:12px;min-height:16px;margin:6px 0}.dialog-actions{display:grid;grid-template-columns:auto 1fr auto auto;gap:8px;align-items:center;margin-top:18px}.scope-copy{color:#727789}.scope-actions{display:grid;gap:8px;margin-top:18px}.toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);background:#252938;color:#fff;padding:11px 16px;border-radius:8px;box-shadow:0 8px 20px #0003;z-index:30;font-size:13px}.toast button{margin-left:12px;background:transparent;color:#bdb8ff;text-decoration:underline;font-weight:700}@media(max-width:950px){.topbar{padding:0 16px;gap:13px}.brand{font-size:0}.brand-mark{font-size:20px}.year-btn{display:none}#content{padding:15px}.month-grid{grid-template-rows:repeat(6,112px)}.top-actions{gap:6px}.view-switch button{padding:6px 7px}}
</style>
</head>
<body>
<div id="planner"><header class="topbar"><div class="brand"><span class="brand-mark">▦</span><span>daymark</span></div><div class="nav"><button id="todayBtn" class="button soft">Today</button><button id="prevBtn" class="icon-btn" aria-label="Previous">‹</button><button id="nextBtn" class="icon-btn" aria-label="Next">›</button><button id="yearBack" class="year-btn" title="Previous year">−1y</button><button id="yearNext" class="year-btn" title="Next year">+1y</button><h1 id="periodLabel"></h1></div><div class="top-actions"><div class="view-switch" role="group"><button data-view="month" class="active">Month</button><button data-view="week">Week</button><button data-view="agenda">Agenda</button></div><button id="weekStartBtn" class="button soft">Mon start</button><button id="newBtn" class="button primary">+ Create</button></div></header><main id="content"></main></div><div id="popover" class="popover hidden"></div><div id="modal" class="modal hidden"><div class="dialog"><div class="dialog-head"><div><p class="eyebrow" id="modalKicker">NEW EVENT</p><h2 id="modalTitle">Create event</h2></div><button id="closeModal" class="close">×</button></div><form id="eventForm"><label>Title<input id="titleInput" required="" maxlength="80" placeholder="Add title"></label><div class="form-grid"><label>Date<input id="dateInput" type="date" required=""></label><label class="time-field">Start<input id="startInput" type="time" value="09:00"></label><label class="time-field">End<input id="endInput" type="time" value="10:00"></label></div><label class="check"><input id="allDayInput" type="checkbox"> All-day event</label><label>Notes<textarea id="notesInput" rows="3" placeholder="Optional details"></textarea></label><div class="form-row"><span>Color</span><div id="palette" class="palette"></div></div><details id="repeatDetails"><summary>Repeat</summary><div class="repeat-fields"><label>Frequency<select id="freqInput"><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></label><div id="weekdayPick" class="weekday-pick hidden"></div></div></details><p id="formError" class="form-error"></p><div class="dialog-actions"><button type="button" id="deleteBtn" class="button danger hidden">Delete</button><span></span><button type="button" id="cancelBtn" class="button soft">Cancel</button><button class="button primary" type="submit">Save event</button></div></form></div></div><div id="scopeModal" class="modal hidden"><div class="dialog small"><p class="eyebrow">RECURRING EVENT</p><h2>Apply changes to…</h2><p class="scope-copy">Choose how this edit should affect the series.</p><div class="scope-actions"><button data-scope="one" class="button soft">This event only</button><button data-scope="following" class="button soft">This and following</button><button data-scope="series" class="button primary">Entire series</button></div></div></div><div id="toast" class="toast hidden"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./date-utils":"src/date-utils.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";
Object.defineProperty(exports, "__esModule", { value: true });
const date_utils_1 = require("./date-utils");
// ── Tunable constants ────────────────────────────────────────────────────────
const COLORS = [['#776ff2', '#efefff'], ['#38a98d', '#e6f7f2'], ['#e18545', '#fff1e5'], ['#df5d79', '#ffeaf0'], ['#4d91d9', '#e9f3ff']], HOUR_START = 6, HOUR_END = 22, SNAP = 15, DEFAULT_WEEK_START = 1;
const $ = (s) => document.querySelector(s);
let state, editing = null, selectedColor = 0, toastTimer = 0, monthAnchor = 1;
function load() { try {
const s = JSON.parse(localStorage.daymark || 'null');
if (s?.events)
return { ...s, cursor: s.cursor || (0, date_utils_1.today)(), weekStart: s.weekStart ?? DEFAULT_WEEK_START };
}
catch { } return { view: 'month', cursor: (0, date_utils_1.today)(), weekStart: DEFAULT_WEEK_START, events: [] }; }
function save() { localStorage.daymark = JSON.stringify(state); }
function uid() { return crypto.randomUUID?.() || Math.random().toString(36).slice(2); }
const ymd = date_utils_1.parseKey;
function fmtDate(x) { return new Intl.DateTimeFormat(undefined, { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' }).format(new Date(x.y, x.m - 1, x.d)); }
function timeLabel(t) { const [h, m] = t.split(':').map(Number); return `${h % 12 || 12}:${(0, date_utils_1.pad)(m)} ${h < 12 ? 'AM' : 'PM'}`; }
function timeMin(t) { const [a, b] = t.split(':').map(Number); return a * 60 + b; }
function inRange(x, a, b) { return (0, date_utils_1.compare)(x, a) >= 0 && (0, date_utils_1.compare)(x, b) <= 0; }
// ── Visible-range recurrence expansion ──────────────────────────────────────
function occurrences(a, b) { const out = []; for (const e of state.events) {
const origin = ymd(e.date);
for (const d of (0, date_utils_1.rangeDays)(a, b)) {
if (e.rule ? !(0, date_utils_1.matchesRule)(origin, e.rule, d) : !(0, date_utils_1.same)(origin, d))
continue;
if (e.rule?.until && (0, date_utils_1.compare)(d, ymd(e.rule.until)) > 0)
continue;
const k = (0, date_utils_1.key)(d), ex = e.exceptions?.[k];
if (ex === null)
continue;
out.push({ ...e, ...(ex || {}), occDate: k, masterId: e.id, isInstance: !!e.rule });
}
} return out; }
function eventStyle(e) { const c = COLORS[e.color] || COLORS[0]; return `--c:${c[0]};--bg:${c[1]}`; }
function heading() { const c = state.cursor; let text = ''; if (state.view === 'month')
text = new Intl.DateTimeFormat(undefined, { month: 'long', year: 'numeric' }).format(new Date(c.y, c.m - 1, 1));
else if (state.view === 'week') {
const a = (0, date_utils_1.weekStart)(c, state.weekStart), b = (0, date_utils_1.addDays)(a, 6);
text = `${fmtDate(a).replace(/.*, /, '')} – ${fmtDate(b)}`;
}
else
text = 'Upcoming agenda'; $('#periodLabel').textContent = text; }
function chip(e) { const el = document.createElement('div'); el.className = 'event-chip'; el.style.cssText = eventStyle(e); el.draggable = true; el.dataset.id = e.masterId; el.dataset.date = e.occDate; el.textContent = `${e.allDay ? '' : e.start + ' '}${e.title}`; el.onclick = v => { v.stopPropagation(); openEditor(e, e.occDate); }; el.ondragstart = v => v.dataTransfer?.setData('text/plain', JSON.stringify({ id: e.masterId, date: e.occDate })); return el; }
function renderMonth() { const grid = (0, date_utils_1.monthGrid)(state.cursor.y, state.cursor.m, state.weekStart), all = occurrences(grid[0], grid[41]), shell = document.createElement('section'); shell.className = 'calendar-shell'; const names = state.weekStart === 1 ? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; shell.innerHTML = `<div class="weekdays">${names.map(n => `<div>${n}</div>`).join('')}</div><div class="month-grid"></div>`; const host = shell.querySelector('.month-grid'); for (const d of grid) {
const dk = (0, date_utils_1.key)(d), cell = document.createElement('div');
cell.className = 'day-cell' + (d.m !== state.cursor.m ? ' outside' : '') + ((0, date_utils_1.same)(d, (0, date_utils_1.today)()) ? ' today' : '');
cell.dataset.date = dk;
cell.onclick = () => openEditor(null, dk);
cell.ondragover = v => { v.preventDefault(); cell.classList.add('drop-target'); };
cell.ondragleave = () => cell.classList.remove('drop-target');
cell.ondrop = v => { v.preventDefault(); cell.classList.remove('drop-target'); const raw = v.dataTransfer?.getData('text/plain'); if (raw)
moveDate(JSON.parse(raw), dk); };
const n = document.createElement('div');
n.className = 'day-num';
n.textContent = String(d.d);
cell.append(n);
const es = all.filter(e => e.occDate === dk);
es.slice(0, 3).forEach(e => cell.append(chip(e)));
if (es.length > 3) {
const more = document.createElement('button');
more.className = 'more-link';
more.textContent = `+${es.length - 3} more`;
more.onclick = v => { v.stopPropagation(); showPop(cell, es); };
cell.append(more);
}
host.append(cell);
} return shell; }
function renderWeek() { const a = (0, date_utils_1.weekStart)(state.cursor, state.weekStart), b = (0, date_utils_1.addDays)(a, 6), all = occurrences(a, b), shell = document.createElement('section'); shell.className = 'week-shell'; const names = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; shell.innerHTML = `<div class="week-head"><div></div>${(0, date_utils_1.rangeDays)(a, b).map(d => `<div class="${(0, date_utils_1.same)(d, (0, date_utils_1.today)()) ? 'head-today' : ''}">${names[(0, date_utils_1.weekday)(d)]}<strong>${d.d}</strong></div>`).join('')}</div><div class="all-day-row"><div class="all-day-label">ALL DAY</div>${(0, date_utils_1.rangeDays)(a, b).map(d => `<div class="allday-col" data-date="${(0, date_utils_1.key)(d)}"></div>`).join('')}</div><div class="week-body"><div class="time-labels">${Array.from({ length: HOUR_END - HOUR_START + 1 }, (_, i) => `<div class="time-label">${timeLabel(`${(0, date_utils_1.pad)(HOUR_START + i)}:00`)}</div>`).join('')}</div>${(0, date_utils_1.rangeDays)(a, b).map(d => `<div class="day-lane" data-date="${(0, date_utils_1.key)(d)}"></div>`).join('')}</div>`; all.filter(e => e.allDay).forEach(e => shell.querySelector(`.allday-col[data-date="${e.occDate}"]`).append(chip(e))); all.filter(e => !e.allDay).forEach(e => { const lane = shell.querySelector(`.day-lane[data-date="${e.occDate}"]`), el = document.createElement('div'); el.className = 'week-event'; el.style.cssText = eventStyle(e) + `;top:${(timeMin(e.start) - HOUR_START * 60) / 60 * 48}px;height:${Math.max(22, (timeMin(e.end) - timeMin(e.start)) / 60 * 48)}px`; el.innerHTML = `<b>${e.title}</b><span>${timeLabel(e.start)} – ${timeLabel(e.end)}</span>`; el.draggable = true; el.onclick = v => { v.stopPropagation(); openEditor(e, e.occDate); }; el.ondragstart = v => v.dataTransfer?.setData('text/plain', JSON.stringify({ id: e.masterId, date: e.occDate, start: e.start })); lane.append(el); }); shell.querySelectorAll('.day-lane').forEach(lane => { lane.ondragover = v => v.preventDefault(); lane.ondrop = v => { v.preventDefault(); const raw = v.dataTransfer?.getData('text/plain'); if (!raw)
return; const dat = JSON.parse(raw); if (!dat.start)
return moveDate(dat, lane.dataset.date); const r = lane.getBoundingClientRect(), mins = Math.max(HOUR_START * 60, Math.min((HOUR_END - 1) * 60, Math.round(((v.clientY - r.top) / 48 * 60) / SNAP) * SNAP)); moveTime(dat, lane.dataset.date, `${(0, date_utils_1.pad)(Math.floor(mins / 60))}:${(0, date_utils_1.pad)(mins % 60)}`); }; }); if (inRange((0, date_utils_1.today)(), a, b)) {
const n = new Date(), min = n.getHours() * 60 + n.getMinutes();
if (min >= HOUR_START * 60 && min <= HOUR_END * 60) {
const line = document.createElement('div');
line.className = 'time-now';
line.style.top = `${(min - HOUR_START * 60) / 60 * 48}px`;
shell.querySelector('.week-body').append(line);
}
} return shell; }
function renderAgenda() { const a = (0, date_utils_1.today)(), b = (0, date_utils_1.addDays)(a, 90), all = occurrences(a, b), el = document.createElement('section'); el.className = 'agenda'; if (!all.length)
el.innerHTML = '<div class="empty-note">Nothing planned yet. Create an event to get started.</div>'; for (const d of (0, date_utils_1.rangeDays)(a, b)) {
const es = all.filter(e => e.occDate === (0, date_utils_1.key)(d));
if (!es.length)
continue;
const day = document.createElement('div');
day.className = 'agenda-day';
day.innerHTML = `<div class="agenda-date"><b>${fmtDate(d)}</b><span>${(0, date_utils_1.same)(d, (0, date_utils_1.today)()) ? 'Today' : ''}</span></div>`;
es.sort((x, y) => x.start.localeCompare(y.start)).forEach(e => { const r = document.createElement('div'); r.className = 'agenda-item'; r.style.cssText = eventStyle(e); r.innerHTML = `<span class="agenda-time">${e.allDay ? 'All day' : `${timeLabel(e.start)} – ${timeLabel(e.end)}`}</span><div><div class="agenda-title">${e.title}</div><div class="agenda-notes">${e.notes || ''}</div></div><span>›</span>`; r.onclick = () => openEditor(e, e.occDate); day.append(r); });
el.append(day);
} return el; }
function render() { heading(); document.querySelectorAll('.view-switch button').forEach(x => x.classList.toggle('active', x.dataset.view === state.view)); $('#weekStartBtn').textContent = state.weekStart === 1 ? 'Mon start' : 'Sun start'; const c = $('#content'); c.innerHTML = ''; c.append(state.view === 'month' ? renderMonth() : state.view === 'week' ? renderWeek() : renderAgenda()); save(); }
// ── Editor / CRUD / recurring scopes ────────────────────────────────────────
function palette() { const p = $('#palette'); p.innerHTML = ''; COLORS.forEach((c, i) => { const b = document.createElement('button'); b.type = 'button'; b.className = 'color-dot' + (i === selectedColor ? ' selected' : ''); b.style.setProperty('--c', c[0]); b.onclick = () => { selectedColor = i; palette(); }; p.append(b); }); }
function toggleTime() { const off = $('#allDayInput'); document.querySelectorAll('.time-field').forEach(e => e.classList.toggle('hidden', off.checked)); }
function openEditor(e, date) { editing = e ? { event: state.events.find(x => x.id === e.masterId), occDate: date } : null; $('#modal').classList.remove('hidden'); $('#modalTitle').textContent = e ? 'Edit event' : 'Create event'; $('#modalKicker').textContent = e?.isInstance ? 'RECURRING EVENT' : 'EVENT DETAILS'; $('#deleteBtn').classList.toggle('hidden', !e); const base = e || { date: date || (0, date_utils_1.key)(state.cursor), start: '09:00', end: '10:00', allDay: false, color: 0, title: '', notes: '' }; $('#titleInput').value = base.title; $('#dateInput').value = date || base.date; $('#startInput').value = base.start; $('#endInput').value = base.end; $('#allDayInput').checked = base.allDay; $('#notesInput').value = base.notes; selectedColor = base.color; $('#freqInput').value = base.rule?.freq || 'none'; buildWeekdays(base.rule?.weekdays || [(0, date_utils_1.weekday)(ymd(date || base.date))]); $('#repeatDetails').open = !!base.rule; toggleTime(); palette(); $('#formError').textContent = ''; }
function closeEditor() { $('#modal').classList.add('hidden'); editing = null; }
function buildWeekdays(selected) { const p = $('#weekdayPick'); p.innerHTML = ''; ['S', 'M', 'T', 'W', 'T', 'F', 'S'].forEach((n, i) => { const b = document.createElement('button'); b.type = 'button'; b.textContent = n; b.dataset.day = String(i); b.classList.toggle('selected', selected.includes(i)); b.onclick = () => b.classList.toggle('selected'); p.append(b); }); p.classList.toggle('hidden', $('#freqInput').value !== 'weekly'); }
function formEvent() { const title = $('#titleInput').value.trim(), date = $('#dateInput').value, start = $('#startInput').value, end = $('#endInput').value, allDay = $('#allDayInput').checked, freq = $('#freqInput').value; if (!title || !date)
return null; if (!allDay && timeMin(end) <= timeMin(start))
return null; const weekdays = Array.from(document.querySelectorAll('#weekdayPick .selected')).map(x => Number(x.dataset.day)); const rule = freq === 'none' ? undefined : { freq: freq, ...(freq === 'weekly' ? { weekdays } : {}) }; return { title, date, start, end, allDay, color: selectedColor, notes: $('#notesInput').value.trim(), rule }; }
function applyEdit(scope, data) { if (!data || !editing)
return; const master = editing.event, occ = editing.occDate; if (scope === 'series') {
Object.assign(master, data);
}
else if (scope === 'one') {
master.exceptions ?? (master.exceptions = {});
master.exceptions[occ] = { ...data, rule: undefined };
}
else {
master.rule = { ...master.rule, until: (0, date_utils_1.key)((0, date_utils_1.addDays)(ymd(occ), -1)) };
state.events.push({ id: uid(), ...data, exceptions: {} });
} save(); closeEditor(); render(); }
function askScope(data) { if (editing?.event.rule) {
$('#scopeModal').classList.remove('hidden');
document.querySelectorAll('#scopeModal [data-scope]').forEach(b => b.onclick = () => { $('#scopeModal').classList.add('hidden'); applyEdit(b.dataset.scope, data); });
}
else if (editing) {
Object.assign(editing.event, data);
save();
closeEditor();
render();
}
else {
state.events.push({ id: uid(), ...data, exceptions: {} });
save();
closeEditor();
render();
} }
function deleteEvent() { if (!editing)
return; const e = editing.event; if (e.rule && editing.occDate) {
e.exceptions ?? (e.exceptions = {});
e.exceptions[editing.occDate] = null;
}
else
state.events = state.events.filter(x => x.id !== e.id); save(); closeEditor(); render(); toast('Event deleted'); }
function moveDate(dat, to) { const e = state.events.find(x => x.id === dat.id); if (!e)
return; if (e.rule) {
e.exceptions ?? (e.exceptions = {});
e.exceptions[dat.date] = { date: to, rule: undefined };
}
else
e.date = to; save(); render(); }
function moveTime(dat, to, start) { const e = state.events.find(x => x.id === dat.id); if (!e)
return; const duration = timeMin(e.end) - timeMin(e.start), endMin = Math.min(24 * 60 - 1, timeMin(start) + duration), patch = { date: to, start, end: `${(0, date_utils_1.pad)(Math.floor(endMin / 60))}:${(0, date_utils_1.pad)(endMin % 60)}`, rule: undefined }; if (e.rule) {
e.exceptions ?? (e.exceptions = {});
e.exceptions[dat.date] = patch;
}
else
Object.assign(e, patch); save(); render(); }
function showPop(cell, es) { const p = $('#popover'); p.innerHTML = `<h3>${fmtDate(ymd(cell.dataset.date))}</h3>`; es.forEach(e => p.append(chip(e))); const r = cell.getBoundingClientRect(); p.style.left = `${Math.min(innerWidth - 300, r.left)}px`; p.style.top = `${Math.min(innerHeight - 360, r.bottom)}px`; p.classList.remove('hidden'); }
function toast(s) { const t = $('#toast'); t.textContent = s; t.classList.remove('hidden'); clearTimeout(toastTimer); toastTimer = window.setTimeout(() => t.classList.add('hidden'), 3000); }
// ── Wiring ──────────────────────────────────────────────────────────────────
state = load();
monthAnchor = state.cursor.d;
function shiftMonth(n) { const raw = (0, date_utils_1.addMonths)({ ...state.cursor, d: 1 }, n); state.cursor = { ...raw, d: Math.min(monthAnchor, (0, date_utils_1.daysInMonth)(raw.y, raw.m)) }; }
$('#todayBtn').onclick = () => { state.cursor = (0, date_utils_1.today)(); monthAnchor = state.cursor.d; render(); };
$('#prevBtn').onclick = () => { state.view === 'month' ? shiftMonth(-1) : state.cursor = (0, date_utils_1.addDays)(state.cursor, -7); render(); };
$('#nextBtn').onclick = () => { state.view === 'month' ? shiftMonth(1) : state.cursor = (0, date_utils_1.addDays)(state.cursor, 7); render(); };
$('#yearBack').onclick = () => { shiftMonth(-12); render(); };
$('#yearNext').onclick = () => { shiftMonth(12); render(); };
document.querySelectorAll('.view-switch button').forEach(b => b.onclick = () => { state.view = b.dataset.view; render(); });
$('#weekStartBtn').onclick = () => { state.weekStart = state.weekStart === 1 ? 0 : 1; render(); };
$('#newBtn').onclick = () => openEditor(null, (0, date_utils_1.key)(state.cursor));
$('#closeModal').onclick = $('#cancelBtn').onclick = closeEditor;
$('#allDayInput').onchange = toggleTime;
$('#freqInput').onchange = () => buildWeekdays([]);
$('#eventForm').onsubmit = e => { e.preventDefault(); const data = formEvent(); if (!data) {
$('#formError').textContent = 'Add a title and ensure the end time is after the start time.';
return;
} askScope(data); };
$('#deleteBtn').onclick = () => { if (confirm('Delete this event?'))
deleteEvent(); };
document.addEventListener('click', e => { if (!e.target.closest('#popover') && !(e.target.closest('.more-link')))
$('#popover').classList.add('hidden'); });
render();
// Test surface for date-engine checks.
Object.assign(window, { daymark: { daysInMonth: (y, m) => { const { daysInMonth } = requireDate(); return daysInMonth(y, m); }, weekday: date_utils_1.weekday, monthGrid: date_utils_1.monthGrid, state: () => state, occurrences } });
function requireDate() { return { daysInMonth: (y, m) => m === 2 ? (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28) : [4, 6, 9, 11].includes(m) ? 30 : 31 }; }
};
// ── module: src/date-utils.ts ──
__mods["src/date-utils.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.rangeDays = exports.matchesRule = exports.weekNumber = exports.monthGrid = exports.weekStart = exports.weekday = exports.addMonths = exports.addDays = exports.same = exports.compare = exports.daysInMonth = exports.isLeap = exports.today = exports.parseKey = exports.key = exports.pad = void 0;
const pad = (n) => String(n).padStart(2, '0');
exports.pad = pad;
const key = (x) => `${x.y}-${(0, exports.pad)(x.m)}-${(0, exports.pad)(x.d)}`;
exports.key = key;
const parseKey = (s) => { const [y, m, d] = s.split('-').map(Number); return { y, m, d }; };
exports.parseKey = parseKey;
const today = () => { const n = new Date(); return { y: n.getFullYear(), m: n.getMonth() + 1, d: n.getDate() }; };
exports.today = today;
const isLeap = (y) => y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0);
exports.isLeap = isLeap;
const daysInMonth = (y, m) => m === 2 ? ((0, exports.isLeap)(y) ? 29 : 28) : [4, 6, 9, 11].includes(m) ? 30 : 31;
exports.daysInMonth = daysInMonth;
const compare = (a, b) => a.y - b.y || a.m - b.m || a.d - b.d;
exports.compare = compare;
const same = (a, b) => (0, exports.compare)(a, b) === 0;
exports.same = same;
const addDays = (x, n) => { let { y, m, d } = x; d += n; while (d > (0, exports.daysInMonth)(y, m)) {
d -= (0, exports.daysInMonth)(y, m);
if (++m > 12) {
m = 1;
y++;
}
} while (d < 1) {
if (--m < 1) {
m = 12;
y--;
}
d += (0, exports.daysInMonth)(y, m);
} return { y, m, d }; };
exports.addDays = addDays;
const addMonths = (x, n) => { let m = x.m + n, y = x.y; while (m > 12) {
m -= 12;
y++;
} while (m < 1) {
m += 12;
y--;
} return { y, m, d: Math.min(x.d, (0, exports.daysInMonth)(y, m)) }; };
exports.addMonths = addMonths;
// Sakamoto: 0 Sunday through 6 Saturday, Gregorian calendar.
const weekday = ({ y, m, d }) => { const t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]; if (m < 3)
y--; return (y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) + t[m - 1] + d) % 7; };
exports.weekday = weekday;
const weekStart = (x, start) => (0, exports.addDays)(x, -(((0, exports.weekday)(x) - start + 7) % 7));
exports.weekStart = weekStart;
const monthGrid = (y, m, start) => { const first = { y, m, d: 1 }; const base = (0, exports.addDays)(first, -(((0, exports.weekday)(first) - start + 7) % 7)); return Array.from({ length: 42 }, (_, i) => (0, exports.addDays)(base, i)); };
exports.monthGrid = monthGrid;
const weekNumber = (x, start) => { const jan = { y: x.y, m: 1, d: 1 }; let days = 0; for (let p = jan; (0, exports.compare)(p, x) < 0; p = (0, exports.addDays)(p, 1))
days++; return Math.floor((days + (((0, exports.weekday)(jan) - start + 7) % 7)) / 7) + 1; };
exports.weekNumber = weekNumber;
const matchesRule = (origin, rule, at) => { if ((0, exports.compare)(at, origin) < 0)
return false; if (rule.freq === 'daily')
return true; if (rule.freq === 'weekly') {
const ds = rule.weekdays?.length ? rule.weekdays : [(0, exports.weekday)(origin)];
return ds.includes((0, exports.weekday)(at));
} if (rule.freq === 'monthly')
return at.d === origin.d; if (rule.freq === 'yearly')
return at.m === origin.m && at.d === origin.d; return false; };
exports.matchesRule = matchesRule;
const rangeDays = (a, b) => { const out = []; for (let x = a; (0, exports.compare)(x, b) <= 0; x = (0, exports.addDays)(x, 1))
out.push(x); return out; };
exports.rangeDays = rangeDays;
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-terra","provider":"openrouter","persona":"typescript","sessionId":"97880cb1-a8c9-49ef-a28f-14197d05608b","tokensIn":410885,"tokensOut":14445,"tokensTotal":425330,"turns":27,"toolCalls":26,"failedToolCalls":2,"timestamp":"2026-07-31T16:10:28.492Z"} -->