feat: add mobile font scale setting

New setting in Settings > Styling that scales all fonts on mobile
devices (75% / 85% / 100% / 115% / 130%). Applies to day names,
dates, tasks, events, and the mobile header date. Also scales the
CW/date display in the mobile header via scaleRem().

New DB field: mobileFontScale (Float, default 1.0)

v1.38.0
This commit is contained in:
mARTin 2026-03-16 22:42:09 +01:00
parent 2ad55fe1bd
commit 43169c0bfc
4 changed files with 58 additions and 4 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.37.4",
"version": "1.38.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js",
"scripts": {

View File

@ -100,6 +100,7 @@ model User {
weekdayFormat String? @default("long")
weekdayCase String? @default("capitalize")
customWeekdayNames String? @default("")
mobileFontScale Float @default(1.0)
startDayOffset Int @default(-1)
quoteSourceUrls String[] @default([])
quoteLanguages String[] @default(["en", "de"])

View File

@ -96,6 +96,7 @@ export async function GET(request: NextRequest) {
lightTheme: true,
darkTheme: true,
notificationsEnabled: true,
mobileFontScale: true,
createdAt: true
}
});
@ -138,7 +139,7 @@ export async function PATCH(request: NextRequest) {
yearFontFamily, yearFontSize, yearFontWeight, yearColor,
showTaskCheckboxes, dayHeaderGap,
showCompletedTasks, showLines, startDayOffset, weekdayFormat, weekdayCase, customWeekdayNames, quoteSourceUrls, quoteLanguages,
kanbanStages, lightTheme, darkTheme, notificationsEnabled
kanbanStages, lightTheme, darkTheme, notificationsEnabled, mobileFontScale
} = body;
const updateData: any = {
@ -223,6 +224,7 @@ export async function PATCH(request: NextRequest) {
...(lightTheme !== undefined && { lightTheme }),
...(darkTheme !== undefined && { darkTheme }),
...(notificationsEnabled !== undefined && { notificationsEnabled }),
...(mobileFontScale !== undefined && { mobileFontScale: parseFloat(mobileFontScale) || 1.0 }),
};
if (password && password.trim() !== "") {
updateData.passwordHash = await bcrypt.hash(password, 10);
@ -316,6 +318,7 @@ export async function PATCH(request: NextRequest) {
lightTheme: true,
darkTheme: true,
notificationsEnabled: true,
mobileFontScale: true,
}
});

View File

@ -4980,10 +4980,11 @@ export default function WeeklyView() {
);
const fontSizeScale = fontSize === "S" ? 0.85 : fontSize === "L" ? 1.15 : 1;
const mobileScale = isMobile ? (profile.mobileFontScale || 1.0) : 1.0;
const fontVal = (v: string | undefined) => v && v !== "__custom__" ? v : "";
const scaleRem = (base: string) => {
const num = parseFloat(base);
return `${(num * fontSizeScale).toFixed(3)}rem`;
return `${(num * fontSizeScale * mobileScale).toFixed(3)}rem`;
};
const activeTheme = (darkMode ? profile.darkTheme : profile.lightTheme) as Record<string, string> | null;
@ -5548,7 +5549,7 @@ export default function WeeklyView() {
{/* Center: Date display (tap to jump to date) */}
<button className="mobile-header-date" onClick={() => setShowDatePicker(true)} style={{
fontFamily: profile.cwFontFamily || "Inter",
fontSize: profile.cwFontSize || "0.9rem",
fontSize: scaleRem(profile.cwFontSize || "0.9rem"),
fontWeight: Number(profile.cwFontWeight || "700"),
color: adjustColorForDarkMode(profile.cwColor || (darkMode ? "#e5e7eb" : "#333333"), darkMode),
}}>
@ -11914,6 +11915,55 @@ function SettingsSidebar({
<div
style={{ display: "flex", flexDirection: "column", gap: "24px" }}
>
{/* Mobile Font Scale */}
<div style={{
background: "var(--weekly-settings-item-bg)",
padding: "12px",
borderRadius: "8px",
}}>
<label style={{
display: "block", fontSize: "0.85rem", fontWeight: 600,
marginBottom: "8px", color: "var(--weekly-settings-label)",
}}>
{(profile.language || "en") === "de" ? "Mobile Schriftgröße" : "Mobile Font Scale"}
</label>
<div style={{ display: "flex", gap: "6px" }}>
{[
{ label: "75%", value: 0.75 },
{ label: "85%", value: 0.85 },
{ label: "100%", value: 1.0 },
{ label: "115%", value: 1.15 },
{ label: "130%", value: 1.3 },
].map(opt => (
<button
key={opt.value}
onClick={() => {
setProfile({ ...profile, mobileFontScale: opt.value });
saveSetting("mobileFontScale", opt.value);
}}
style={{
flex: 1, padding: "6px 4px", borderRadius: "6px",
border: (profile.mobileFontScale || 1.0) === opt.value
? "2px solid var(--weekly-teal, #0ea5e9)"
: "1px solid var(--weekly-border, #ddd)",
background: (profile.mobileFontScale || 1.0) === opt.value
? "var(--weekly-teal, #0ea5e9)" : "transparent",
color: (profile.mobileFontScale || 1.0) === opt.value
? "white" : "var(--weekly-text)",
fontSize: "0.8rem", fontWeight: 600, cursor: "pointer",
}}
>
{opt.label}
</button>
))}
</div>
<span style={{ fontSize: "0.7rem", color: "var(--weekly-text-light)", marginTop: "4px", display: "block" }}>
{(profile.language || "en") === "de"
? "Skaliert alle Schriften auf Mobilgeräten (< 768px)"
: "Scales all fonts on mobile devices (< 768px)"}
</span>
</div>
{/* Typography Settings */}
<div
style={{