diff --git a/packages/calendar/README.md b/packages/calendar/README.md
new file mode 100644
index 0000000..5fecd34
--- /dev/null
+++ b/packages/calendar/README.md
@@ -0,0 +1,620 @@
+# Calendar
+
+Professional TypeScript calendar component with offline-first architecture, drag-and-drop functionality, and real-time synchronization capabilities.
+
+## Features
+
+- **Multiple View Modes**: Date-based (day/week/month) and resource-based (people, rooms) views
+- **Drag & Drop**: Smooth event dragging with snap-to-grid, cross-column movement, and timed/all-day conversion
+- **Event Resizing**: Intuitive resize handles for adjusting event duration
+- **Offline-First**: IndexedDB storage with automatic background sync
+- **Event-Driven Architecture**: Decoupled components via centralized EventBus
+- **Dependency Injection**: Built on NovaDI for clean, testable architecture
+- **Extensions**: Modular extensions for teams, departments, bookings, customers, schedules, and audit logging
+
+## Installation
+
+```bash
+npm install calendar
+```
+
+## Quick Start (AI-Friendly Setup Guide)
+
+### Step 1: Create HTML Structure
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Step 2: Initialize Calendar
+
+```typescript
+import { Container } from '@novadi/core';
+import {
+ registerCoreServices,
+ CalendarApp,
+ IndexedDBContext,
+ SettingsService,
+ ViewConfigService,
+ EventService,
+ EventBus,
+ CalendarEvents
+} from 'calendar';
+
+async function init() {
+ // 1. Create DI container and register services
+ const container = new Container();
+ const builder = container.builder();
+ registerCoreServices(builder, {
+ dbConfig: { dbName: 'MyCalendarDB', dbVersion: 1 }
+ });
+ const app = builder.build();
+
+ // 2. Initialize IndexedDB
+ const dbContext = app.resolveType();
+ await dbContext.initialize();
+
+ // 3. Seed required settings (first time only)
+ const settingsService = app.resolveType();
+ const viewConfigService = app.resolveType();
+
+ await settingsService.save({
+ id: 'grid',
+ dayStartHour: 8,
+ dayEndHour: 17,
+ workStartHour: 9,
+ workEndHour: 16,
+ hourHeight: 64,
+ snapInterval: 15,
+ syncStatus: 'synced'
+ });
+
+ await settingsService.save({
+ id: 'workweek',
+ presets: {
+ standard: { id: 'standard', label: 'Standard', workDays: [1, 2, 3, 4, 5], periodDays: 7 }
+ },
+ defaultPreset: 'standard',
+ firstDayOfWeek: 1,
+ syncStatus: 'synced'
+ });
+
+ await viewConfigService.save({
+ id: 'simple',
+ groupings: [{ type: 'date', values: [], idProperty: 'date', derivedFrom: 'start' }],
+ syncStatus: 'synced'
+ });
+
+ // 4. Initialize CalendarApp
+ const calendarApp = app.resolveType();
+ const containerEl = document.querySelector('swp-calendar-container') as HTMLElement;
+ await calendarApp.init(containerEl);
+
+ // 5. Render a view
+ const eventBus = app.resolveType();
+ eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: 'simple' });
+}
+
+init().catch(console.error);
+```
+
+### Step 3: Add Events
+
+```typescript
+const eventService = app.resolveType();
+
+await eventService.save({
+ id: crypto.randomUUID(),
+ title: 'Meeting',
+ start: new Date('2024-01-15T09:00:00'),
+ end: new Date('2024-01-15T10:00:00'),
+ type: 'meeting',
+ allDay: false,
+ syncStatus: 'synced'
+});
+```
+
+---
+
+## Architecture
+
+### Core Components
+
+| Component | Description |
+|-----------|-------------|
+| `CalendarApp` | Main application entry point |
+| `CalendarOrchestrator` | Coordinates rendering pipeline |
+| `EventBus` | Central event dispatcher for all inter-component communication |
+| `DateService` | Date calculations and formatting |
+| `IndexedDBContext` | Offline storage infrastructure |
+
+### Managers
+
+| Manager | Description |
+|---------|-------------|
+| `DragDropManager` | Event drag-drop with smooth animations and snap-to-grid |
+| `EdgeScrollManager` | Automatic scrolling at viewport edges during drag |
+| `ResizeManager` | Event resizing with visual feedback |
+| `ScrollManager` | Scroll behavior and position management |
+| `HeaderDrawerManager` | All-day events drawer toggle |
+| `EventPersistenceManager` | Saves drag/resize changes to storage |
+
+### Renderers
+
+| Renderer | Description |
+|----------|-------------|
+| `DateRenderer` | Renders date-based column groupings |
+| `ResourceRenderer` | Renders resource-based column groupings |
+| `EventRenderer` | Renders timed events in columns |
+| `ScheduleRenderer` | Renders working hours backgrounds |
+| `HeaderDrawerRenderer` | Renders all-day events in header |
+| `TimeAxisRenderer` | Renders time labels on the left axis |
+
+### Storage
+
+| Service | Description |
+|---------|-------------|
+| `EventService` / `EventStore` | Calendar event CRUD |
+| `ResourceService` / `ResourceStore` | Resource management |
+| `SettingsService` / `SettingsStore` | Tenant settings |
+| `ViewConfigService` / `ViewConfigStore` | View configurations |
+
+---
+
+## Events Reference
+
+### Lifecycle Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `core:initialized` | `CoreEvents.INITIALIZED` | - | Calendar core initialized |
+| `core:ready` | `CoreEvents.READY` | - | Calendar ready for interaction |
+| `core:destroyed` | `CoreEvents.DESTROYED` | - | Calendar destroyed |
+
+### View Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `view:changed` | `CoreEvents.VIEW_CHANGED` | `{ viewId: string }` | View type changed |
+| `view:rendered` | `CoreEvents.VIEW_RENDERED` | - | View finished rendering |
+
+### Navigation Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `nav:date-changed` | `CoreEvents.DATE_CHANGED` | `{ date: Date }` | Current date changed |
+| `nav:navigation-completed` | `CoreEvents.NAVIGATION_COMPLETED` | - | Navigation animation completed |
+
+### Data Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `data:loading` | `CoreEvents.DATA_LOADING` | - | Data loading started |
+| `data:loaded` | `CoreEvents.DATA_LOADED` | - | Data loading completed |
+| `data:error` | `CoreEvents.DATA_ERROR` | `{ error: Error }` | Data loading error |
+
+### Grid Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `grid:rendered` | `CoreEvents.GRID_RENDERED` | - | Grid finished rendering |
+| `grid:clicked` | `CoreEvents.GRID_CLICKED` | `{ time: Date, columnKey: string }` | Grid area clicked |
+
+### Event Management
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `event:created` | `CoreEvents.EVENT_CREATED` | `ICalendarEvent` | Event created |
+| `event:updated` | `CoreEvents.EVENT_UPDATED` | `IEventUpdatedPayload` | Event updated |
+| `event:deleted` | `CoreEvents.EVENT_DELETED` | `{ eventId: string }` | Event deleted |
+| `event:selected` | `CoreEvents.EVENT_SELECTED` | `{ eventId: string }` | Event selected |
+
+### Drag-Drop Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `event:drag-start` | `CoreEvents.EVENT_DRAG_START` | `IDragStartPayload` | Drag started |
+| `event:drag-move` | `CoreEvents.EVENT_DRAG_MOVE` | `IDragMovePayload` | Dragging (throttled) |
+| `event:drag-end` | `CoreEvents.EVENT_DRAG_END` | `IDragEndPayload` | Drag completed |
+| `event:drag-cancel` | `CoreEvents.EVENT_DRAG_CANCEL` | `IDragCancelPayload` | Drag cancelled |
+| `event:drag-column-change` | `CoreEvents.EVENT_DRAG_COLUMN_CHANGE` | `IDragColumnChangePayload` | Moved to different column |
+
+### Header Drag Events (Timed to All-Day Conversion)
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `event:drag-enter-header` | `CoreEvents.EVENT_DRAG_ENTER_HEADER` | `IDragEnterHeaderPayload` | Entered header area |
+| `event:drag-move-header` | `CoreEvents.EVENT_DRAG_MOVE_HEADER` | `IDragMoveHeaderPayload` | Moving in header area |
+| `event:drag-leave-header` | `CoreEvents.EVENT_DRAG_LEAVE_HEADER` | `IDragLeaveHeaderPayload` | Left header area |
+
+### Resize Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `event:resize-start` | `CoreEvents.EVENT_RESIZE_START` | `IResizeStartPayload` | Resize started |
+| `event:resize-end` | `CoreEvents.EVENT_RESIZE_END` | `IResizeEndPayload` | Resize completed |
+
+### Edge Scroll Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `edge-scroll:tick` | `CoreEvents.EDGE_SCROLL_TICK` | `{ deltaY: number }` | Scroll tick during edge scroll |
+| `edge-scroll:started` | `CoreEvents.EDGE_SCROLL_STARTED` | - | Edge scrolling started |
+| `edge-scroll:stopped` | `CoreEvents.EDGE_SCROLL_STOPPED` | - | Edge scrolling stopped |
+
+### Sync Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `sync:started` | `CoreEvents.SYNC_STARTED` | - | Background sync started |
+| `sync:completed` | `CoreEvents.SYNC_COMPLETED` | - | Background sync completed |
+| `sync:failed` | `CoreEvents.SYNC_FAILED` | `{ error: Error }` | Background sync failed |
+
+### Entity Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `entity:saved` | `CoreEvents.ENTITY_SAVED` | `IEntitySavedPayload` | Entity saved to storage |
+| `entity:deleted` | `CoreEvents.ENTITY_DELETED` | `IEntityDeletedPayload` | Entity deleted from storage |
+
+### Audit Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `audit:logged` | `CoreEvents.AUDIT_LOGGED` | `IAuditLoggedPayload` | Audit entry logged |
+
+### Rendering Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `events:rendered` | `CoreEvents.EVENTS_RENDERED` | - | Events finished rendering |
+
+### System Events
+
+| Event | Constant | Payload | Description |
+|-------|----------|---------|-------------|
+| `system:error` | `CoreEvents.ERROR` | `{ error: Error, context?: string }` | System error occurred |
+
+---
+
+## Command Events (Host to Calendar)
+
+Use these to control the calendar from your application:
+
+```typescript
+import { EventBus, CalendarEvents } from 'calendar';
+
+const eventBus = app.resolveType();
+
+// Navigate
+eventBus.emit(CalendarEvents.CMD_NAVIGATE_PREV);
+eventBus.emit(CalendarEvents.CMD_NAVIGATE_NEXT);
+
+// Render a view
+eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: 'simple' });
+
+// Toggle header drawer
+eventBus.emit(CalendarEvents.CMD_DRAWER_TOGGLE);
+
+// Change workweek preset
+eventBus.emit(CalendarEvents.CMD_WORKWEEK_CHANGE, { presetId: 'standard' });
+
+// Update view grouping
+eventBus.emit(CalendarEvents.CMD_VIEW_UPDATE, { type: 'resource', values: ['r1', 'r2'] });
+```
+
+| Command | Constant | Payload | Description |
+|---------|----------|---------|-------------|
+| Navigate Previous | `CalendarEvents.CMD_NAVIGATE_PREV` | - | Go to previous period |
+| Navigate Next | `CalendarEvents.CMD_NAVIGATE_NEXT` | - | Go to next period |
+| Render View | `CalendarEvents.CMD_RENDER` | `{ viewId: string }` | Render specified view |
+| Toggle Drawer | `CalendarEvents.CMD_DRAWER_TOGGLE` | - | Toggle all-day drawer |
+| Change Workweek | `CalendarEvents.CMD_WORKWEEK_CHANGE` | `{ presetId: string }` | Change workweek preset |
+| Update View | `CalendarEvents.CMD_VIEW_UPDATE` | `{ type: string, values: string[] }` | Update grouping values |
+
+---
+
+## Types
+
+### Core Types
+
+```typescript
+// Event types
+type CalendarEventType = 'customer' | 'vacation' | 'break' | 'meeting' | 'blocked';
+
+interface ICalendarEvent {
+ id: string;
+ title: string;
+ description?: string;
+ start: Date;
+ end: Date;
+ type: CalendarEventType;
+ allDay: boolean;
+ bookingId?: string;
+ resourceId?: string;
+ customerId?: string;
+ recurringId?: string;
+ syncStatus: SyncStatus;
+ metadata?: Record;
+}
+
+// Resource types
+type ResourceType = 'person' | 'room' | 'equipment' | 'vehicle' | 'custom';
+
+interface IResource {
+ id: string;
+ name: string;
+ displayName: string;
+ type: ResourceType;
+ avatarUrl?: string;
+ color?: string;
+ isActive?: boolean;
+ defaultSchedule?: IWeekSchedule;
+ syncStatus: SyncStatus;
+}
+
+// Sync status
+type SyncStatus = 'synced' | 'pending' | 'error';
+```
+
+### Settings Types
+
+```typescript
+interface IGridSettings {
+ id: 'grid';
+ dayStartHour: number;
+ dayEndHour: number;
+ workStartHour: number;
+ workEndHour: number;
+ hourHeight: number;
+ snapInterval: number;
+}
+
+interface IWorkweekPreset {
+ id: string;
+ workDays: number[]; // ISO weekdays: 1=Monday, 7=Sunday
+ label: string;
+ periodDays: number; // Navigation step (1=day, 7=week)
+}
+
+interface IWeekSchedule {
+ [day: number]: ITimeSlot | null; // null = off that day
+}
+
+interface ITimeSlot {
+ start: string; // "HH:mm"
+ end: string; // "HH:mm"
+}
+```
+
+### Drag-Drop Payloads
+
+```typescript
+interface IDragStartPayload {
+ eventId: string;
+ element: HTMLElement;
+ ghostElement: HTMLElement;
+ startY: number;
+ mouseOffset: { x: number; y: number };
+ columnElement: HTMLElement;
+}
+
+interface IDragEndPayload {
+ swpEvent: SwpEvent;
+ sourceColumnKey: string;
+ target: 'grid' | 'header';
+}
+```
+
+---
+
+## Extensions
+
+Import extensions separately to keep bundle size minimal:
+
+```typescript
+// Teams extension
+import { registerTeams, TeamService, TeamStore, TeamRenderer } from 'calendar/teams';
+
+// Departments extension
+import { registerDepartments, DepartmentService, DepartmentStore } from 'calendar/departments';
+
+// Bookings extension
+import { registerBookings, BookingService, BookingStore } from 'calendar/bookings';
+
+// Customers extension
+import { registerCustomers, CustomerService, CustomerStore } from 'calendar/customers';
+
+// Schedules extension (working hours)
+import { registerSchedules, ResourceScheduleService, ScheduleOverrideService } from 'calendar/schedules';
+
+// Audit extension
+import { registerAudit, AuditService, AuditStore } from 'calendar/audit';
+
+// Register with container builder
+const builder = container.builder();
+registerCoreServices(builder);
+registerTeams(builder);
+registerSchedules(builder);
+// ... etc
+```
+
+---
+
+## Configuration
+
+### Calendar Options
+
+```typescript
+interface ICalendarOptions {
+ timeConfig?: ITimeFormatConfig;
+ gridConfig?: IGridConfig;
+ dbConfig?: IDBConfig;
+}
+
+// Time format configuration
+interface ITimeFormatConfig {
+ timezone: string; // e.g., 'Europe/Copenhagen'
+ use24HourFormat: boolean;
+ locale: string; // e.g., 'da-DK'
+ dateFormat: string;
+ showSeconds: boolean;
+}
+
+// Grid configuration
+interface IGridConfig {
+ hourHeight: number; // Pixels per hour (default: 64)
+ dayStartHour: number; // Grid start hour (default: 6)
+ dayEndHour: number; // Grid end hour (default: 18)
+ snapInterval: number; // Minutes to snap to (default: 15)
+ gridStartThresholdMinutes: number;
+}
+
+// Database configuration
+interface IDBConfig {
+ dbName: string; // IndexedDB database name
+ dbVersion: number; // Schema version
+}
+```
+
+### Default Configuration
+
+```typescript
+import {
+ defaultTimeFormatConfig,
+ defaultGridConfig,
+ defaultDBConfig
+} from 'calendar';
+
+// Defaults:
+// timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
+// use24HourFormat: true
+// locale: 'da-DK'
+// hourHeight: 64
+// dayStartHour: 6
+// dayEndHour: 18
+// snapInterval: 15
+```
+
+---
+
+## Utilities
+
+### Position Utilities
+
+```typescript
+import {
+ calculateEventPosition,
+ minutesToPixels,
+ pixelsToMinutes,
+ snapToGrid
+} from 'calendar';
+
+// Convert time to pixels
+const pixels = minutesToPixels(120, 64); // 120 mins at 64px/hour = 128px
+
+// Snap to 15-minute grid
+const snapped = snapToGrid(new Date(), 15);
+```
+
+### Event Layout Engine
+
+```typescript
+import { eventsOverlap, calculateColumnLayout } from 'calendar';
+
+// Check if two events overlap
+const overlap = eventsOverlap(event1, event2);
+
+// Calculate layout for overlapping events
+const layout = calculateColumnLayout(events);
+```
+
+---
+
+## Listening to Events
+
+```typescript
+import { EventBus, CoreEvents } from 'calendar';
+
+const eventBus = app.resolveType();
+
+// Subscribe to event updates
+eventBus.on(CoreEvents.EVENT_UPDATED, (e: Event) => {
+ const { eventId, sourceColumnKey, targetColumnKey } = (e as CustomEvent).detail;
+ console.log(`Event ${eventId} moved from ${sourceColumnKey} to ${targetColumnKey}`);
+});
+
+// Subscribe to drag events
+eventBus.on(CoreEvents.EVENT_DRAG_END, (e: Event) => {
+ const { swpEvent, target } = (e as CustomEvent).detail;
+ console.log(`Dropped on ${target}:`, swpEvent);
+});
+
+// One-time listener
+eventBus.once(CoreEvents.READY, () => {
+ console.log('Calendar is ready!');
+});
+```
+
+---
+
+## CSS Customization
+
+The calendar uses CSS custom properties for theming. Override these in your CSS:
+
+```css
+:root {
+ --calendar-hour-height: 64px;
+ --calendar-header-height: 48px;
+ --calendar-column-min-width: 120px;
+ --calendar-event-border-radius: 4px;
+}
+```
+
+---
+
+## Dependencies
+
+- `@novadi/core` - Dependency injection framework (peer dependency)
+- `dayjs` - Date manipulation and formatting
+
+---
+
+## License
+
+Proprietary - SWP
diff --git a/packages/calendar/package.json b/packages/calendar/package.json
index 8c83e0f..9a7374b 100644
--- a/packages/calendar/package.json
+++ b/packages/calendar/package.json
@@ -1,6 +1,6 @@
{
"name": "calendar",
- "version": "0.1.6",
+ "version": "0.1.7",
"description": "Calendar library",
"author": "SWP",
"type": "module",