diff --git a/src/app/globals.css b/src/app/globals.css
index e6742e2..255a5aa 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -3270,26 +3270,29 @@ h3 {
/* Resize handle for draggable section borders */
.resize-handle {
- height: 7px;
+ height: 2px;
+ border: 1px solid #ccc;
cursor: ns-resize;
display: flex;
align-items: center;
justify-content: center;
+ -webkit-user-select: none;
+ -moz-user-select: none;
user-select: none;
touch-action: none;
position: relative;
- z-index: 10;
+ z-index: 30;
flex-shrink: 0;
- border-top: 1px solid var(--weekly-border, #e5e7eb);
- border-bottom: 1px solid var(--weekly-border, #e5e7eb);
+ overflow: visible;
+ background-color: #ddd;
}
.resize-handle:hover {
- background: rgba(59, 130, 246, 0.08);
- border-color: var(--weekly-accent, #3b82f6);
+ background-color: #d7d7d7;
+ border-color: #b8b8b8;
}
.resize-handle:active {
- background: rgba(59, 130, 246, 0.12);
- border-color: var(--weekly-accent, #3b82f6);
+ background-color: #d1d1d1;
+ border-color: #aaa;
}
.resize-handle-bar {
width: 32px;
@@ -3299,10 +3302,50 @@ h3 {
opacity: 0.6;
transition: background 0.15s, opacity 0.15s;
}
+.resize-handle-anyday {
+ height: 2px;
+}
+.resize-handle-allday {
+ height: 2px;
+}
+.resize-handle-anyday .resize-handle-bar {
+ width: 23px;
+ height: 9px;
+ border-radius: 4px;
+ background: radial-gradient(circle, color-mix(in srgb, var(--weekly-text, #333) 48%, transparent) 1.25px, transparent 1.35px) 2px 1.5px / 6px 5px repeat;
+ background-color: #ddd;
+ border: 1px solid #ccc;
+ opacity: 1;
+ position: relative;
+ bottom: 0;
+ overflow: visible;
+ z-index: 50;
+}
+.resize-handle-allday .resize-handle-bar {
+ width: 23px;
+ height: 9px;
+ border-radius: 4px;
+ background: radial-gradient(circle, color-mix(in srgb, var(--weekly-text, #333) 48%, transparent) 1.25px, transparent 1.35px) 2px 1.5px / 6px 5px repeat;
+ background-color: #ddd;
+ border: 1px solid #ccc;
+ opacity: 1;
+ position: relative;
+ bottom: 0;
+ overflow: visible;
+ z-index: 50;
+}
.resize-handle:hover .resize-handle-bar,
.resize-handle:active .resize-handle-bar {
- background: var(--weekly-accent, #3b82f6);
- opacity: 0.5;
+ opacity: 1;
+}
+.resize-handle-anyday:hover .resize-handle-bar,
+.resize-handle-anyday:active .resize-handle-bar,
+.resize-handle-allday:hover .resize-handle-bar,
+.resize-handle-allday:active .resize-handle-bar {
+ background: radial-gradient(circle, var(--weekly-accent, #3b82f6) 1.25px, transparent 1.35px) 2px 1.5px / 6px 5px repeat;
+ background-color: #d7d7d7;
+ border-color: #b8b8b8;
+ opacity: 1;
}
.all-day-events-header {
diff --git a/src/components/SettingsSidebar.tsx b/src/components/SettingsSidebar.tsx
index 62b40cb..01e6958 100644
--- a/src/components/SettingsSidebar.tsx
+++ b/src/components/SettingsSidebar.tsx
@@ -1045,6 +1045,41 @@ function SettingsSidebar({
))}
+
+
+
+ {[
+ { value: true, label: profile.language === "de" ? "Anzeigen" : "Show" },
+ { value: false, label: profile.language === "de" ? "Ausblenden" : "Hide" },
+ ].map(({ value, label }) => {
+ const isActive = (profile.showHeaderControls !== false) === value;
+ return (
+
+ );
+ })}
+
+
{/* Header Display */}
diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx
index 0524497..91667b1 100644
--- a/src/components/WeeklyView.tsx
+++ b/src/components/WeeklyView.tsx
@@ -128,6 +128,7 @@ type DeviceViewSettingKey = typeof DEVICE_VIEW_SETTINGS_KEYS[number];
// Settings that save to DB (cross-device default) AND to cookie (device override wins on load)
const DEVICE_ALSO_COOKIE_KEYS = ["viewStyle", "showTimeGrid", "startDayOffset"];
+const VIEW_SETTINGS_PROFILE_KEYS = ["menuPosition", "showHeaderControls"];
function getCookie(name: string): string | null {
if (typeof document === "undefined") return null;
@@ -918,6 +919,7 @@ export default function WeeklyView() {
// Mobile detection
const [isMobile, setIsMobile] = useState(false);
const [isPortrait, setIsPortrait] = useState(false);
+ const [isCompactHeight, setIsCompactHeight] = useState(false);
// Tracks the last day column the user interacted with (for "selected day" header display)
const [selectedDay, setSelectedDay] = useState
(null);
@@ -1037,6 +1039,8 @@ export default function WeeklyView() {
weatherLon: null,
weatherLocation: "",
showCalendarProviderIcon: false,
+ menuPosition: "left",
+ showHeaderControls: true,
});
const [motivationalQuote, setMotivationalQuote] = useState("");
const [showSummary, setShowSummary] = useState(false);
@@ -1400,6 +1404,7 @@ export default function WeeklyView() {
const check = () => {
setIsMobile(window.innerWidth <= 768);
setIsPortrait(window.innerHeight > window.innerWidth);
+ setIsCompactHeight(window.innerHeight <= 720);
};
check();
window.addEventListener("resize", check);
@@ -1416,7 +1421,12 @@ export default function WeeklyView() {
if (res.ok) {
const data = await res.json();
if (data && data.user) {
- const profileData = data.user;
+ const viewPrefs = (data.user.viewSettings || {}) as any;
+ const profileData = {
+ ...data.user,
+ menuPosition: viewPrefs.menuPosition ?? data.user.menuPosition ?? "left",
+ showHeaderControls: viewPrefs.showHeaderControls ?? data.user.showHeaderControls ?? true,
+ };
setProfile(profileData);
// Sync individual states to profile data
@@ -2403,6 +2413,22 @@ export default function WeeklyView() {
somedayWheelCleanup.current = () => node.removeEventListener("wheel", handler);
}, []);
const saveSetting = async (key: string, value: any) => {
+ if (VIEW_SETTINGS_PROFILE_KEYS.includes(key)) {
+ const updated = { ...(viewSettingsRef.current as any), [key]: value };
+ viewSettingsRef.current = updated;
+ setViewSettings(updated as any);
+ setProfile((p: any) => ({ ...p, [key]: value }));
+ try {
+ await fetch("/api/user/profile", {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ viewSettings: updated }),
+ });
+ } catch (err) {
+ console.error(`Failed to save setting ${key}:`, err);
+ }
+ return;
+ }
// Per-device settings: save to cookie ONLY (not DB) so each device keeps its own value
if (DEVICE_SETTINGS_KEYS.includes(key)) {
setCookie(`setting_${key}`, String(value));
@@ -5383,6 +5409,10 @@ export default function WeeklyView() {
};
const activeTheme = (darkMode ? profile.darkTheme : profile.lightTheme) as Record | null;
const useLeftRail = !isMobile && profile.menuPosition !== "top";
+ const showHeaderControls = profile.showHeaderControls !== false;
+ const compactRail = useLeftRail && isCompactHeight;
+ const collapsedRailWidth = compactRail ? 38 : 44;
+ const expandedRailWidth = compactRail ? 220 : 240;
const containerStyle = {
...(activeTheme ? {
@@ -5461,7 +5491,7 @@ export default function WeeklyView() {
"--weekly-past-color": activeTheme?.color8 || (darkMode
? invertColor(profile.pastDayColor || "#a6a6a7")
: profile.pastDayColor || "#a6a6a7"),
- ...(useLeftRail ? { paddingLeft: leftRailExpanded ? "240px" : "44px", transition: "padding-left 0.2s ease" } : {}),
+ ...(useLeftRail ? { paddingLeft: leftRailExpanded ? `${expandedRailWidth}px` : `${collapsedRailWidth}px`, transition: "padding-left 0.2s ease" } : {}),
} as React.CSSProperties;
if (isLoading) {
@@ -5492,12 +5522,12 @@ export default function WeeklyView() {
});
const railIconBtnStyle: React.CSSProperties = {
- width: "36px", height: "36px", display: "flex", alignItems: "center", justifyContent: "center",
- background: "none", border: "none", cursor: "pointer", borderRadius: "8px",
+ width: compactRail ? "30px" : "36px", height: compactRail ? "30px" : "36px", display: "flex", alignItems: "center", justifyContent: "center",
+ background: "none", border: "none", cursor: "pointer", borderRadius: compactRail ? "7px" : "8px",
color: darkMode ? "#9ca3af" : "#6b7280", flexShrink: 0,
};
const flyoutPanelStyle: React.CSSProperties = {
- position: "fixed", left: "48px",
+ position: "fixed", left: `${collapsedRailWidth + 4}px`,
top: `${Math.min(flyoutY, (typeof window !== "undefined" ? window.innerHeight : 800) - 260)}px`,
background: darkMode ? "#1a1a2e" : "var(--paper)",
border: "1px solid var(--line)", borderRadius: "10px", padding: "12px 14px",
@@ -5521,7 +5551,7 @@ export default function WeeklyView() {
if (flyoutTimerRef.current) clearTimeout(flyoutTimerRef.current);
};
const railSep = (
-
+
);
// All-Day Events Section (reusable for above/below positioning)
@@ -5535,7 +5565,7 @@ export default function WeeklyView() {
const handleOnTop = effectiveAllDayPosition === "below";
const resizeHandle = isAllDayExpanded ? (
startResize(e, 'allday', handleOnTop)}
onTouchStart={(e) => startResize(e, 'allday', handleOnTop)}
>
@@ -5830,10 +5860,10 @@ export default function WeeklyView() {
{useLeftRail && (
<>
{/* The rail panel */}
-
+
{leftRailExpanded ? (
// ── EXPANDED MODE ──
-
+
{profile.language === "de" ? "Einstellungen" : "Preferences"}
) : (
// ── COLLAPSED MODE — icon strip ──
-
+
{/* Expand toggle */}
{railSep}
@@ -6243,7 +6273,7 @@ export default function WeeklyView() {
)}
{/* Desktop Header: Left, Center, Right */}
-