diff --git a/app.js b/app.js
new file mode 100644
index 0000000..059933a
--- /dev/null
+++ b/app.js
@@ -0,0 +1,1178 @@
+const API_BASE =
+ "http://localhost:8000";
+
+
+
+function getToken(){
+
+ return localStorage.getItem("token");
+
+}
+
+
+function setToken(token){
+
+ localStorage.setItem("token", token);
+
+}
+
+
+function clearToken(){
+
+ localStorage.removeItem("token");
+
+}
+
+
+
+async function apiFetch(path, options = {}){
+
+ let headers =
+ options.headers
+ ? { ...options.headers }
+ : {};
+
+
+ headers["Content-Type"] = "application/json";
+
+
+ let token =
+ getToken();
+
+
+ if(token){
+
+ headers["Authorization"] = "Bearer " + token;
+
+ }
+
+
+ let response =
+ await fetch(
+ API_BASE + path,
+ {
+ ...options,
+ headers
+ }
+ );
+
+
+ if(response.status === 401){
+
+ clearToken();
+
+ showLogin();
+
+ throw new Error("not authenticated");
+
+ }
+
+
+ let data =
+ await response.json()
+ .catch(() => null);
+
+
+ if(!response.ok){
+
+ let message =
+ (data && data.detail)
+ ? data.detail
+ : "request failed";
+
+ throw new Error(message);
+
+ }
+
+
+ return data;
+
+}
+
+
+
+// ---------- auth ----------
+
+
+function showLogin(){
+
+ document.getElementById("app").style.display = "none";
+ document.getElementById("login-screen").style.display = "flex";
+
+}
+
+
+function showApp(){
+
+ document.getElementById("login-screen").style.display = "none";
+ document.getElementById("app").style.display = "grid";
+
+}
+
+
+async function handleLogin(event){
+
+ event.preventDefault();
+
+
+ let username =
+ document.getElementById("login-username").value;
+
+ let password =
+ document.getElementById("login-password").value;
+
+ let errorBox =
+ document.getElementById("login-error");
+
+
+ errorBox.textContent = "";
+
+
+ try{
+
+ let result =
+ await apiFetch(
+ "/login",
+ {
+ method: "POST",
+ body: JSON.stringify({ username, password })
+ }
+ );
+
+
+ if(result.status !== "success"){
+
+ errorBox.textContent = "invalid username or password";
+ return;
+
+ }
+
+
+ setToken(result.access_token);
+
+ showApp();
+
+ showPage("dashboard");
+
+ } catch(error){
+
+ errorBox.textContent = error.message;
+
+ }
+
+}
+
+
+async function handleRegister(event){
+
+ event.preventDefault();
+
+
+ let username =
+ document.getElementById("login-username").value;
+
+ let password =
+ document.getElementById("login-password").value;
+
+ let errorBox =
+ document.getElementById("login-error");
+
+
+ errorBox.textContent = "";
+
+
+ try{
+
+ await apiFetch(
+ "/create-user",
+ {
+ method: "POST",
+ body: JSON.stringify({ username, password })
+ }
+ );
+
+
+ errorBox.style.color = "green";
+ errorBox.textContent = "account created, you can log in now";
+
+ } catch(error){
+
+ errorBox.style.color = "";
+ errorBox.textContent = error.message;
+
+ }
+
+}
+
+
+function logout(){
+
+ clearToken();
+
+ showLogin();
+
+}
+
+
+
+// ---------- page router ----------
+
+
+function showPage(page){
+
+ let content =
+ document.getElementById("content");
+
+
+ document.querySelectorAll("nav a")
+ .forEach(link => link.classList.remove("active"));
+
+ let activeLink =
+ document.querySelector(`nav a[data-page="${page}"]`);
+
+ if(activeLink){
+
+ activeLink.classList.add("active");
+
+ }
+
+
+ if(page === "dashboard"){
+
+ loadDashboard();
+
+ }
+
+
+ if(page === "rooms"){
+
+ loadRooms();
+
+ }
+
+
+ if(page === "events"){
+
+ loadEvents();
+
+ }
+
+
+ if(page === "settings"){
+
+ content.innerHTML = `
+
Settings
+
+
+ Account preferences.
+
+
+
+ `;
+
+ }
+
+}
+
+
+
+// ---------- dashboard ----------
+
+
+let calendarWeekOffset = 0;
+
+let cachedDashboardEvents = [];
+
+let currentUserId = null;
+
+
+async function loadDashboard(){
+
+ let content =
+ document.getElementById("content");
+
+
+ content.innerHTML = `
+ Dashboard
+ Loading...
+ `;
+
+
+ calendarWeekOffset = 0;
+
+
+ try{
+
+ let me =
+ await apiFetch("/me");
+
+ currentUserId = me.id;
+
+ let roomsData =
+ await apiFetch("/rooms");
+
+ let rooms =
+ roomsData.rooms || [];
+
+
+ let eventsData =
+ await apiFetch("/events");
+
+ cachedDashboardEvents =
+ eventsData.events || [];
+
+
+ let roomsHtml =
+ rooms.length
+ ? rooms.map(room => `
+
+ ${room.room_name}
+
+ `).join("")
+ : `You have no rooms yet.
`;
+
+
+ content.innerHTML = `
+ Dashboard
+
+
+
Welcome back, ${me.username}
+
+ Overview of your calendar.
+
+
+
+
+ ${buildWeekCalendarHtml(cachedDashboardEvents, calendarWeekOffset)}
+
+
+
+
Your rooms
+ ${roomsHtml}
+
+
+
+
Create a room
+
+
+
+
+
+
+
+ `;
+
+ } catch(error){
+
+ content.innerHTML = `
+ Dashboard
+ Could not load dashboard: ${error.message}
+ `;
+
+ }
+
+}
+
+
+async function handleCreateRoom(event){
+
+ event.preventDefault();
+
+
+ let roomName =
+ document.getElementById("create-room-name").value;
+
+ let inviteCode =
+ document.getElementById("create-room-code").value;
+
+ let messageBox =
+ document.getElementById("create-room-message");
+
+
+ try{
+
+ await apiFetch(
+ "/create-room",
+ {
+ method: "POST",
+ body: JSON.stringify({
+ room_name: roomName,
+ invite_code: inviteCode
+ })
+ }
+ );
+
+
+ messageBox.textContent = "room created";
+
+ loadDashboard();
+
+ } catch(error){
+
+ messageBox.textContent = error.message;
+
+ }
+
+}
+
+
+async function handleJoinRoom(event){
+
+ event.preventDefault();
+
+
+ let inviteCode =
+ document.getElementById("join-room-code").value;
+
+ let messageBox =
+ document.getElementById("join-room-message");
+
+
+ try{
+
+ await apiFetch(
+ "/join-room",
+ {
+ method: "POST",
+ body: JSON.stringify({ invite_code: inviteCode })
+ }
+ );
+
+
+ messageBox.textContent = "joined room";
+
+ loadDashboard();
+
+ } catch(error){
+
+ messageBox.textContent = error.message;
+
+ }
+
+}
+
+
+
+// ---------- calendar (week view) ----------
+
+
+const CALENDAR_SLOT_HOURS = 2;
+const CALENDAR_SLOT_HEIGHT = 48;
+
+const CALENDAR_START_HOUR = 0;
+
+const CALENDAR_END_HOUR = 24;
+const PIXELS_PER_MINUTE =
+ CALENDAR_SLOT_HEIGHT / (CALENDAR_SLOT_HOURS * 60);
+
+function isEventCancelled(event){
+
+ let status =
+ (event.status || "").toLowerCase();
+
+ return (
+ status === "cancelled"
+ || status === "canceled"
+ || status === "declined"
+ );
+
+}
+
+
+function getEventColorClass(event, currentUserId){
+
+ let isOwnEvent =
+ event.creator_id === currentUserId;
+
+ let isPrivateOther =
+ event.visibility === "private" && !isOwnEvent;
+
+
+ if(isPrivateOther){
+
+ return "event-private";
+
+ }
+
+
+ if((event.status || "").toLowerCase() === "confirmed"){
+
+ return "event-confirmed";
+
+ }
+
+
+ return "event-busy";
+
+}
+
+
+function getWeekStart(offset){
+
+ let today =
+ new Date();
+
+ let base =
+ new Date(
+ today.getFullYear(),
+ today.getMonth(),
+ today.getDate() + (offset * 7)
+ );
+
+ let dayOfWeek =
+ base.getDay();
+
+ let mondayDiff =
+ (dayOfWeek === 0) ? -6 : (1 - dayOfWeek);
+
+
+ base.setDate(base.getDate() + mondayDiff);
+
+ return base;
+
+}
+
+
+function changeCalendarWeek(delta){
+
+ calendarWeekOffset += delta;
+
+
+ let calendarCard =
+ document.getElementById("calendar-card");
+
+ if(calendarCard){
+
+ calendarCard.innerHTML =
+ buildWeekCalendarHtml(cachedDashboardEvents, calendarWeekOffset);
+
+ }
+
+}
+
+
+function buildWeekCalendarHtml(events, offset){
+
+ let weekStart =
+ getWeekStart(offset);
+
+ let today =
+ new Date();
+
+ let dayNames =
+ ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
+
+ let days = [];
+
+
+ for(let i = 0; i < 7; i++){
+
+ let d =
+ new Date(weekStart);
+
+ d.setDate(weekStart.getDate() + i);
+
+ days.push(d);
+
+ }
+
+
+ let rangeLabel =
+ days[0].toLocaleDateString("default", { day: "numeric", month: "short" })
+ + " - "
+ + days[6].toLocaleDateString("default", { day: "numeric", month: "short", year: "numeric" });
+
+
+ let totalMinutes =
+ (CALENDAR_END_HOUR - CALENDAR_START_HOUR) * 60;
+
+ let trackHeight =
+ ((CALENDAR_END_HOUR - CALENDAR_START_HOUR) / CALENDAR_SLOT_HOURS)
+ * CALENDAR_SLOT_HEIGHT;
+
+ // time gutter
+
+ let hourRows = "";
+
+
+ for(
+ let h = CALENDAR_START_HOUR;
+ h < CALENDAR_END_HOUR;
+ h += CALENDAR_SLOT_HOURS
+ ){
+
+ hourRows += `
+
+ ${String(h).padStart(2, "0")}:00
+
+ `;
+
+ }
+
+ // day columns
+
+ let dayColumns =
+ days.map(day => {
+
+ let dayEvents =
+ events.filter(event => {
+
+ if(!event.start_time || isEventCancelled(event)){
+
+ return false;
+
+ }
+
+ let eventStart =
+ new Date(event.start_time);
+
+ return (
+ eventStart.getFullYear() === day.getFullYear()
+ && eventStart.getMonth() === day.getMonth()
+ && eventStart.getDate() === day.getDate()
+ );
+
+ })
+ .sort((a, b) => new Date(a.start_time) - new Date(b.start_time));
+
+
+ // assign overlap layers: an event gets the lowest layer
+ // whose most recent block has already ended
+
+ let layerEnds = [];
+
+ let placed =
+ dayEvents.map(event => {
+ function parseLocalDate(dateString){
+ let parts = dateString.split(/[- :]/);
+
+ return new Date(
+ parts[0],
+ parts[1] - 1,
+ parts[2],
+ parts[3],
+ parts[4]
+ );
+ }
+
+ let start = parseLocalDate(event.start_time);
+
+ let end =
+ event.end_time
+ ? parseLocalDate(event.end_time)
+ : new Date(start.getTime() + 30 * 60000);
+
+ let startMinutes =
+ start.getHours() * 60 + start.getMinutes();
+
+ let endMinutes =
+ Math.max(
+ startMinutes + 15,
+ end.getHours() * 60 + end.getMinutes()
+ );
+
+
+ let layer = 0;
+
+ while(layerEnds[layer] !== undefined && layerEnds[layer] > startMinutes){
+
+ layer++;
+
+ }
+
+ layerEnds[layer] = endMinutes;
+
+
+ return { event, startMinutes, endMinutes, layer };
+
+ });
+
+
+ let maxLayer =
+ placed.reduce((m, p) => Math.max(m, p.layer), 0);
+
+
+ let blocksHtml =
+ placed.map(p => {
+
+ let topPx =
+ (p.startMinutes - CALENDAR_START_HOUR * 60)
+ * PIXELS_PER_MINUTE;
+
+
+ let heightPx =
+ (p.endMinutes - p.startMinutes)
+ * PIXELS_PER_MINUTE;
+
+ let heightPct =
+ ((p.endMinutes - p.startMinutes) / totalMinutes) * 100;
+
+ let leftPct =
+ p.layer * 14;
+
+ let widthPct =
+ 100 - (maxLayer * 14);
+
+ let colorClass =
+ getEventColorClass(p.event, currentUserId);
+
+ let timeLabel =
+ new Date(p.event.start_time)
+ .toLocaleTimeString("nl-NL", {
+ hour: "2-digit",
+ minute: "2-digit",
+ hour12: false
+ });
+
+ console.log({
+ title: p.event.title,
+ startMinutes: p.startMinutes,
+ endMinutes: p.endMinutes,
+ topPx,
+ heightPx
+ });
+ return `
+
+ ${timeLabel}
+ ${p.event.title}
+
+ `;
+
+ }).join("");
+
+
+ let isToday =
+ (
+ day.getFullYear() === today.getFullYear()
+ && day.getMonth() === today.getMonth()
+ && day.getDate() === today.getDate()
+ );
+
+
+ return `
+
+
+
+
+
+ ${blocksHtml}
+
+
+
+ `;
+
+ }).join("");
+
+
+ return `
+
+
+
+
+
+ confirmed
+
+
+
+ busy
+
+
+
+ private (others)
+
+
+
+
+
+
+
+
+
+ ${dayColumns}
+
+
+
+ `;
+
+}
+
+
+
+// ---------- rooms page ----------
+
+
+async function loadRooms(){
+
+ let content =
+ document.getElementById("content");
+
+
+ content.innerHTML = `
+ Rooms
+ Loading...
+ `;
+
+
+ try{
+
+ let roomsData =
+ await apiFetch("/rooms");
+
+ let rooms =
+ roomsData.rooms || [];
+
+
+ if(rooms.length === 0){
+
+ content.innerHTML = `
+ Rooms
+
+ Your shared rooms appear here.
+
+ `;
+
+ return;
+
+ }
+
+
+ let roomsHtml =
+ rooms.map(room => `
+
+
${room.room_name}
+
invite code: ${room.invite_code || "-"}
+
+
+ `).join("");
+
+
+ content.innerHTML = `
+ Rooms
+ ${roomsHtml}
+ `;
+
+ } catch(error){
+
+ content.innerHTML = `
+ Rooms
+ Could not load rooms: ${error.message}
+ `;
+
+ }
+
+}
+
+
+async function loadRoomEvents(roomId){
+
+ let content =
+ document.getElementById("content");
+
+
+ content.innerHTML = `
+ Room events
+ Loading...
+ `;
+
+
+ try{
+
+ let data =
+ await apiFetch(`/rooms/${roomId}/events`);
+
+ let events =
+ data.events || [];
+
+
+ content.innerHTML = `
+ Room events
+ ${renderEventCards(events)}
+ `;
+
+ } catch(error){
+
+ content.innerHTML = `
+ Room events
+ Could not load events: ${error.message}
+ `;
+
+ }
+
+}
+
+
+
+// ---------- events page ----------
+
+
+async function loadEvents(){
+
+ let content =
+ document.getElementById("content");
+
+
+ content.innerHTML = `
+ Events
+ Loading...
+ `;
+
+
+ try{
+
+ let data =
+ await apiFetch("/events");
+
+ let events =
+ data.events || [];
+
+
+ content.innerHTML = `
+ Events
+ ${renderEventCards(events)}
+ `;
+
+ } catch(error){
+
+ content.innerHTML = `
+ Events
+ Could not load events: ${error.message}
+ `;
+
+ }
+
+}
+
+
+function renderEventCards(events){
+
+ if(events.length === 0){
+
+ return `No events scheduled.
`;
+
+ }
+
+
+ return events.map(event => `
+
+
+
+
${event.title}
+
+
${event.description || ""}
+
+
+ ${formatEventTime(event.start_time, event.end_time)}
+
+
+
+ status: ${event.status}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `).join("");
+
+}
+
+
+function formatEventTime(start, end){
+
+ if(!start){
+
+ return "time unknown";
+
+ }
+
+
+ let startDate =
+ new Date(start);
+
+ let endDate =
+ end
+ ? new Date(end)
+ : null;
+
+
+ let startText =
+ startDate.toLocaleString();
+
+ if(!endDate){
+
+ return startText;
+
+ }
+
+
+ return startText + " - " + endDate.toLocaleString();
+
+}
+
+
+async function handleRespond(eventId, status){
+
+ let messageBox =
+ document.getElementById(`event-response-${eventId}`);
+
+
+ try{
+
+ let result =
+ await apiFetch(
+ `/events/${eventId}/respond?status=${status}`,
+ { method: "POST" }
+ );
+
+
+ messageBox.textContent =
+ `you responded: ${status} (${result.going} going)`;
+
+ } catch(error){
+
+ messageBox.textContent = error.message;
+
+ }
+
+}
+
+
+
+// ---------- theme ----------
+
+
+function toggleTheme(){
+
+ document.body.classList.toggle("dark");
+
+
+ let theme =
+ document.body.classList.contains("dark")
+ ? "dark"
+ : "light";
+
+
+ localStorage.setItem(
+ "theme",
+ theme
+ );
+
+}
+
+
+
+// ---------- init ----------
+
+
+let savedTheme =
+ localStorage.getItem("theme");
+
+
+if(savedTheme === "dark"){
+
+ document.body.classList.add("dark");
+
+}
+
+
+if(getToken()){
+
+ showApp();
+
+ showPage("dashboard");
+
+} else {
+
+ showLogin();
+
+}
\ No newline at end of file
diff --git a/calender/index.html b/calender/index.html
new file mode 100644
index 0000000..d39ae1c
--- /dev/null
+++ b/calender/index.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+Dynamic HTML Calendar
+
+
+
+
+
+
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..b4f3f5b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,100 @@
+
+
+
+ Calendar Dashboard
+
+
+
+
+
+
+
+
+
+
Calendar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..ae9b186
--- /dev/null
+++ b/style.css
@@ -0,0 +1,547 @@
+:root {
+
+ --bg: #f4f5f7;
+ --panel: white;
+ --sidebar: #ffffff;
+ --text: #111;
+ --muted: #666;
+ --border: #ddd;
+ --accent: #2563eb;
+
+}
+
+
+
+.dark {
+
+ --bg: #080808;
+ --panel: #151515;
+ --sidebar: #101010;
+ --text: #eee;
+ --muted: #aaa;
+ --border: #333;
+ --accent: #38bdf8;
+
+}
+
+
+
+* {
+ box-sizing: border-box;
+}
+
+
+
+body {
+
+ margin: 0;
+
+ background: var(--bg);
+
+ color: var(--text);
+
+ font-family:
+ Inter,
+ system-ui,
+ sans-serif;
+
+ transition: 0.2s;
+
+}
+
+
+
+.layout {
+
+ display: grid;
+
+ grid-template-columns: 260px 1fr;
+
+ height: 100vh;
+
+ display: none;
+
+}
+
+
+.sidebar {
+
+ background: var(--sidebar);
+
+ border-right: 1px solid var(--border);
+
+ padding: 25px;
+
+ display: flex;
+
+ flex-direction: column;
+
+}
+
+
+
+.logo {
+
+ display: flex;
+
+ align-items: center;
+
+ gap: 10px;
+
+ margin-bottom: 40px;
+
+}
+
+
+
+.logo h2 {
+
+ margin: 0;
+
+}
+
+
+
+nav {
+
+ display: flex;
+
+ flex-direction: column;
+
+ gap: 8px;
+
+}
+
+
+
+nav a {
+
+ cursor: pointer;
+
+ padding: 12px 15px;
+
+ border-radius: 10px;
+
+ color: var(--muted);
+
+ transition: 0.2s;
+
+}
+
+
+
+nav a:hover,
+nav .active {
+
+ background: var(--accent);
+
+ color: white;
+
+}
+
+
+
+.sidebar-bottom {
+
+ margin-top: auto;
+
+}
+
+
+
+button {
+
+ width: 100%;
+
+ padding: 12px;
+
+ border-radius: 10px;
+
+ border: 1px solid var(--border);
+
+ background: var(--panel);
+
+ color: var(--text);
+
+ cursor: pointer;
+
+ margin-top: 8px;
+
+}
+
+
+
+/* Content */
+
+.content {
+
+ padding: 40px;
+
+ overflow-y: auto;
+
+}
+
+
+
+h1 {
+
+ margin-top: 0;
+
+}
+
+
+
+.card {
+
+ background: var(--panel);
+
+ border: 1px solid var(--border);
+
+ border-radius: 16px;
+
+ padding: 25px;
+
+ margin-top: 20px;
+
+ box-shadow:
+ 0 10px 30px rgba(0,0,0,0.05);
+
+}
+
+
+
+/* forms */
+
+input {
+
+ width: 100%;
+
+ padding: 12px;
+
+ border-radius: 10px;
+
+ border: 1px solid var(--border);
+
+ background: var(--bg);
+
+ color: var(--text);
+
+ margin-top: 8px;
+
+}
+
+
+
+form {
+
+ display: flex;
+
+ flex-direction: column;
+
+}
+
+
+
+.muted {
+
+ color: var(--muted);
+
+}
+
+
+
+.error {
+
+ color: #e11d48;
+
+}
+
+
+
+/* list items */
+
+.list-item {
+
+ padding: 10px 15px;
+
+ border: 1px solid var(--border);
+
+ border-radius: 10px;
+
+ margin-top: 10px;
+
+}
+
+
+
+/* event actions */
+
+.event-actions {
+
+ display: flex;
+
+ gap: 10px;
+
+ margin-top: 15px;
+
+}
+
+
+.event-actions button {
+
+ margin-top: 0;
+
+}
+
+
+
+/* calendar */
+
+.calendar-header {
+
+ display: flex;
+
+ align-items: center;
+
+ justify-content: space-between;
+
+}
+
+
+.calendar-header h3 {
+
+ margin: 0;
+
+ text-transform: capitalize;
+
+}
+
+
+.calendar-nav-btn {
+
+ width: auto;
+
+ padding: 8px 14px;
+
+ margin-top: 0;
+
+}
+
+
+.calendar-legend {
+
+ display: flex;
+
+ gap: 18px;
+
+ margin-top: 14px;
+
+ font-size: 12px;
+
+ color: var(--muted);
+
+}
+
+
+.legend-item {
+
+ display: flex;
+
+ align-items: center;
+
+ gap: 6px;
+
+}
+
+
+.legend-dot {
+
+ width: 10px;
+
+ height: 10px;
+
+ border-radius: 50%;
+
+ display: inline-block;
+
+}
+
+
+/* week grid */
+
+.calendar-week {
+ display: flex;
+ margin-top: 16px;
+ overflow-x: auto;
+}
+
+
+.calendar-gutter {
+ width: 55px;
+ flex-shrink: 0;
+ padding-top: 0px;
+}
+
+
+.calendar-hour-label {
+ height: 48px; /* 2 uur = 48px */
+ font-size: 11px;
+ color: var(--muted);
+ text-align: right;
+ padding-right: 8px;
+ display: flex;
+ justify-content: flex-end;
+ align-items: flex-start;
+ transform: translateY(-6px);
+}
+
+
+.calendar-days {
+ display: flex;
+ flex: 1;
+ min-width: 700px;
+}
+
+
+.calendar-day-column {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ border-left: 1px solid var(--border);
+}
+
+
+.calendar-day-header {
+ height: 32px;
+ text-align: center;
+ padding: 6px 0;
+ font-size: 12px;
+ color: var(--muted);
+}
+
+
+.calendar-day-track {
+ position: relative;
+
+ height: 576px; /* 12 uur zichtbaar × 48px */
+
+ background-image:
+ repeating-linear-gradient(
+ to bottom,
+ var(--border) 0,
+ var(--border) 1px,
+ transparent 1px,
+ transparent 48px
+ );
+}
+
+
+.calendar-event {
+ position: absolute;
+
+ left: 4px;
+ right: 4px;
+
+ border-radius: 6px;
+
+ padding: 3px 6px;
+
+ font-size: 11px;
+
+ color: white;
+
+ cursor: pointer;
+
+ overflow: hidden;
+
+ box-shadow:
+ 0 2px 6px rgba(0,0,0,0.25);
+
+ opacity: 0.92;
+
+ border: 1px solid rgba(255,255,255,0.4);
+}
+
+
+.calendar-event-time {
+ display: block;
+ font-size: 10px;
+ opacity: 0.85;
+}
+
+
+.calendar-event-title {
+ display: block;
+ font-weight: 600;
+
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.event-confirmed {
+
+ background: #16a34a;
+
+}
+
+
+.event-busy {
+
+ background: #f97316;
+
+}
+
+
+.event-private {
+
+ background: #6b7280;
+
+}
+
+
+
+/* login screen */
+
+.login-screen {
+
+ height: 100vh;
+
+ width: 100%;
+
+ display: flex;
+
+ align-items: center;
+
+ justify-content: center;
+
+}
+
+
+
+.login-box {
+
+ background: var(--panel);
+
+ border: 1px solid var(--border);
+
+ border-radius: 16px;
+
+ padding: 40px;
+
+ width: 320px;
+
+ box-shadow:
+ 0 10px 30px rgba(0,0,0,0.05);
+
+}
+
+
+
+.login-box h2 {
+
+ margin-top: 0;
+
+ text-align: center;
+
+}
\ No newline at end of file