feat: "Heute" start page with one next step, clear task states and motion levels
Start page (AuDHD-oriented, per the agreed "Heute zuerst" layout): - One highlighted next step: started work first (easy re-entry after an interruption), then overdue, due today, then urgent undated tasks. Primary action "Erledigt", secondary "Beginnen", link to the related colony/apiary/queen. - "Außerdem heute": at most five further tasks with a large honey-cell checkbox; undated automatic suggestions are included so a queenless colony does not sit unseen in the full list. - Completing is the one authored motion moment: the cell fills, the tick draws, the row folds away and the next step arrives with a short focus pull. Every completion can be undone from the toast (10 s), restoring the previous status. - Status is shown as icon plus word (never colour alone); tasks created by automation carry a "Vorschlag" badge, recurring and inspection tasks their own - a suggestion never looks like work already done. - Friendly empty, loading and error states; key figures moved into a compact overview below; overdue tasks are no longer listed twice. Motion: per-device setting Voll / Dezent / Aus in the profile (default Voll); the OS reduce-motion setting always lowers Voll to Dezent. New 44 px "touch" button size for gloves at the apiary; themed text selection and caret. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
5c2ea7bf1e
commit
f9374a135a
14 files changed
+1104
-112
No files matched your search
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
/**
|
||||
* How much the interface moves. A per-device preference (localStorage), not
|
||||
* an account setting: the same person may want calm motion on the phone at
|
||||
* the apiary and full feedback on the desktop.
|
||||
*
|
||||
* - full: feedback, transitions and the task-completion moment.
|
||||
* - subtle: no spatial movement, only short fades and colour changes.
|
||||
* - off: state changes happen instantly.
|
||||
*
|
||||
* The operating system's "reduce motion" setting always wins over "full"
|
||||
* (see globals.css), so this only ever lowers motion further.
|
||||
*/
|
||||
export type MotionLevel = "full" | "subtle" | "off";
|
||||
|
||||
export const MOTION_LEVELS: MotionLevel[] = ["full", "subtle", "off"];
|
||||
export const DEFAULT_MOTION_LEVEL: MotionLevel = "full";
|
||||
|
||||
const STORAGE_KEY = "apicore.motion";
|
||||
|
||||
function isMotionLevel(value: unknown): value is MotionLevel {
|
||||
return value === "full" || value === "subtle" || value === "off";
|
||||
}
|
||||
|
||||
export function readMotionLevel(): MotionLevel {
|
||||
try {
|
||||
const stored = window.localStorage.getItem(STORAGE_KEY);
|
||||
return isMotionLevel(stored) ? stored : DEFAULT_MOTION_LEVEL;
|
||||
} catch {
|
||||
// Private mode / blocked storage: fall back to the default.
|
||||
return DEFAULT_MOTION_LEVEL;
|
||||
}
|
||||
}
|
||||
|
||||
export function applyMotionLevel(level: MotionLevel): void {
|
||||
document.documentElement.dataset.motion = level;
|
||||
}
|
||||
|
||||
/** Effective level, including the operating system's reduce-motion setting. */
|
||||
export function effectiveMotionLevel(level: MotionLevel = readMotionLevel()): MotionLevel {
|
||||
const systemReduces =
|
||||
typeof window.matchMedia === "function" &&
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
return systemReduces && level === "full" ? "subtle" : level;
|
||||
}
|
||||
|
||||
export function useMotionPreference(): [MotionLevel, (level: MotionLevel) => void] {
|
||||
// Lazy read: the app shell renders only on the client after login, and the
|
||||
// document attribute itself is applied once in Providers.
|
||||
const [level, setLevelState] = useState<MotionLevel>(() =>
|
||||
typeof window === "undefined" ? DEFAULT_MOTION_LEVEL : readMotionLevel(),
|
||||
);
|
||||
|
||||
const setLevel = useCallback((next: MotionLevel) => {
|
||||
setLevelState(next);
|
||||
applyMotionLevel(next);
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, next);
|
||||
} catch {
|
||||
// Not persisted - still applies for this visit.
|
||||
}
|
||||
}, []);
|
||||
|
||||
return [level, setLevel];
|
||||
}
|
||||
Reference in new issue
Block a user