Add new styles for home and landing pages
- Created home.css with comprehensive styles for the home page layout, including typography, buttons, and responsive design adjustments. - Created landing.css to style the landing page, focusing on typography, layout, and responsive behavior for various screen sizes.
This commit is contained in:
139
src/components/design-system/calendar-model.ts
Normal file
139
src/components/design-system/calendar-model.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
export const PROGRESS_SHADES = [
|
||||
"#eeeeee",
|
||||
"#d8d8d8",
|
||||
"#bfbfbf",
|
||||
"#a5a5a5",
|
||||
"#8a8a8a",
|
||||
"#707070",
|
||||
"#555555",
|
||||
"#363636",
|
||||
"#111111",
|
||||
] as const;
|
||||
export type CalendarDay = {
|
||||
color?: string;
|
||||
unit?: string;
|
||||
date: string;
|
||||
value: number;
|
||||
target: number;
|
||||
state: "due" | "not-due" | "future";
|
||||
};
|
||||
|
||||
export const MONTH_VIEWS = [3, 4, 6, 12] as const;
|
||||
export type MonthView = (typeof MONTH_VIEWS)[number];
|
||||
|
||||
/** Whole calendar months, including the month containing the anchor date. */
|
||||
export function monthWindow(anchor: string, months: MonthView) {
|
||||
const date = new Date(`${anchor}T12:00:00Z`);
|
||||
const first = new Date(
|
||||
Date.UTC(date.getUTCFullYear(), date.getUTCMonth() - months + 1, 1),
|
||||
);
|
||||
const last = new Date(
|
||||
Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0),
|
||||
);
|
||||
return {
|
||||
from: first.toISOString().slice(0, 10),
|
||||
to: last.toISOString().slice(0, 10),
|
||||
};
|
||||
}
|
||||
|
||||
export function visibleCalendarDays(
|
||||
days: CalendarDay[],
|
||||
months: MonthView,
|
||||
anchor: string,
|
||||
) {
|
||||
const range = monthWindow(anchor, months);
|
||||
return days.filter((day) => day.date >= range.from && day.date <= range.to);
|
||||
}
|
||||
|
||||
export const HABIT_COLORS = {
|
||||
water: "#426582",
|
||||
reading: "#977344",
|
||||
tasks: "#79618d",
|
||||
movement: "#58765b",
|
||||
} as const;
|
||||
|
||||
export function progressShade(day: CalendarDay, color = "#111111") {
|
||||
if (day.color) return day.color;
|
||||
if (day.state === "not-due") return "#ffffff";
|
||||
if (day.state === "future") return PROGRESS_SHADES[0];
|
||||
if (day.target <= 0 || day.value <= 0) return PROGRESS_SHADES[0];
|
||||
const step = Math.max(
|
||||
1,
|
||||
Math.min(8, Math.round((day.value / day.target) * 8)),
|
||||
);
|
||||
if (color === "#111111" || !/^#[0-9a-f]{6}$/i.test(color))
|
||||
return PROGRESS_SHADES[step]!;
|
||||
const weight = step / 8;
|
||||
return `#${[1, 3, 5]
|
||||
.map((offset) =>
|
||||
Math.round(
|
||||
238 + (parseInt(color.slice(offset, offset + 2), 16) - 238) * weight,
|
||||
)
|
||||
.toString(16)
|
||||
.padStart(2, "0"),
|
||||
)
|
||||
.join("")}`;
|
||||
}
|
||||
|
||||
export function describeDay(day: CalendarDay, unit: string) {
|
||||
if (day.state === "future") return "Upcoming · Logging is not available yet";
|
||||
if (day.state === "not-due")
|
||||
return "Nothing scheduled · Not included in the score";
|
||||
return `${day.value} of ${day.target} ${day.unit ?? unit} · ${day.value >= day.target ? "Complete" : "Incomplete"}`;
|
||||
}
|
||||
|
||||
/** Only show shades that the chart's completion targets can produce. */
|
||||
export function legendShades(days: CalendarDay[], color = "#111111") {
|
||||
const levels = new Set<number>();
|
||||
for (const target of new Set(days.map((day) => day.target))) {
|
||||
if (!Number.isFinite(target) || target <= 0) continue;
|
||||
for (let value = 1; value <= Math.min(target, 8); value++) {
|
||||
levels.add(
|
||||
target > 8 ? value : Math.max(1, Math.round((value / target) * 8)),
|
||||
);
|
||||
}
|
||||
}
|
||||
return [
|
||||
PROGRESS_SHADES[0],
|
||||
...[...levels]
|
||||
.sort((a, b) => a - b)
|
||||
.map((value) =>
|
||||
progressShade({ date: "", value, target: 8, state: "due" }, color),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
export function combinedProgress(
|
||||
habits: { value: number; target: number; due: boolean }[],
|
||||
) {
|
||||
const due = habits.filter((habit) => habit.due && habit.target > 0);
|
||||
return {
|
||||
completed: due.filter((habit) => habit.value >= habit.target).length,
|
||||
due: due.length,
|
||||
};
|
||||
}
|
||||
|
||||
/** A full 12 calendar months of stable demo history, including future dates. */
|
||||
export function demoCalendar(value: number, target: number): CalendarDay[] {
|
||||
return Array.from({ length: 365 }, (_, index) => {
|
||||
const timestamp = Date.UTC(2025, 9, 1 + index);
|
||||
// Keep the previously displayed dates' fixture values unchanged.
|
||||
const fixtureIndex = (timestamp - Date.UTC(2026, 2, 8)) / 86_400_000;
|
||||
const rawValue = fixtureIndex * 17 + Math.floor(fixtureIndex / 7) * 3;
|
||||
const date = new Date(timestamp).toISOString().slice(0, 10);
|
||||
return {
|
||||
date,
|
||||
target,
|
||||
value:
|
||||
date === "2026-09-04"
|
||||
? value
|
||||
: ((rawValue % (target + 1)) + target + 1) % (target + 1),
|
||||
state:
|
||||
date > "2026-09-04"
|
||||
? "future"
|
||||
: fixtureIndex % 19 === 0
|
||||
? "not-due"
|
||||
: "due",
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user