add2586cb1
flatten themes to 3 colors without categories, rewrite css in vanilla layers, simplify onboarding flow, tighten greeting punctuation, adjust viewport meta
119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
import type { LimitCheckResult } from "../types";
|
|
import { formatStopTimeLabel } from "./userLimits";
|
|
import { groupByFlavour } from "./metrics";
|
|
|
|
type GreetingInput = {
|
|
name: string;
|
|
todayCans: number;
|
|
favouriteFlavour: string;
|
|
currentStreak: number;
|
|
todayCaffeineMg: number;
|
|
allTimeCans: number;
|
|
dailyCanLimit?: number;
|
|
limitCheck?: LimitCheckResult;
|
|
};
|
|
|
|
type GreetingResult = {
|
|
badge: string;
|
|
headline: string;
|
|
subline: string;
|
|
};
|
|
|
|
export function getBstHour(date = new Date()) {
|
|
const hour = new Intl.DateTimeFormat("en-GB", {
|
|
timeZone: "Europe/London",
|
|
hour: "numeric",
|
|
hour12: false,
|
|
}).format(date);
|
|
return Number.parseInt(hour, 10);
|
|
}
|
|
|
|
export function buildDynamicGreeting(input: GreetingInput): GreetingResult {
|
|
const hour = getBstHour();
|
|
const timeLabel = timeOfDayLabel(hour);
|
|
const cans = input.todayCans;
|
|
const favourite =
|
|
input.favouriteFlavour === "None yet" ? null : input.favouriteFlavour;
|
|
const streak = input.currentStreak;
|
|
|
|
const badge = cans === 0 ? `${timeLabel} · clear slate` : `${timeLabel} · ${cans} today`;
|
|
|
|
let headline: string;
|
|
if (cans === 0) {
|
|
headline =
|
|
streak > 0
|
|
? `${input.name}, nothing logged yet today. ${streak}-day streak still alive.`
|
|
: `${input.name}, no Red Bulls logged yet this ${hour < 12 ? "morning" : hour < 17 ? "afternoon" : "evening"}.`;
|
|
} else if (cans === 1) {
|
|
headline = `${input.name}, one Red Bull in so far today.`;
|
|
} else if (input.dailyCanLimit != null) {
|
|
if (cans >= input.dailyCanLimit) {
|
|
headline = `${input.name}, you're at your ${input.dailyCanLimit}-can daily limit.`;
|
|
} else if (cans >= input.dailyCanLimit - 1) {
|
|
headline = `${input.name}, ${cans} Red Bulls today. One under your limit.`;
|
|
} else {
|
|
headline = `${input.name}, ${cans} Red Bulls today. Steady pace.`;
|
|
}
|
|
} else if (cans <= 3) {
|
|
headline = `${input.name}, ${cans} Red Bulls today. Steady pace.`;
|
|
} else {
|
|
headline = `${input.name}, ${cans} Red Bulls today. Worth watching the caffeine curve.`;
|
|
}
|
|
|
|
const flavourLine = favourite
|
|
? cans > 0
|
|
? `Today's top pick looks like ${favourite}.`
|
|
: `All-time favourite: ${favourite} (${input.allTimeCans} cans logged).`
|
|
: "Your flavour story is just getting started.";
|
|
|
|
const stopLine =
|
|
input.limitCheck?.pastStopTime && input.limitCheck?.violations.includes("stopTime")
|
|
? "You're past your stop time for today."
|
|
: null;
|
|
|
|
const caffeineLine =
|
|
stopLine ??
|
|
(cans > 0 && input.todayCaffeineMg > 0
|
|
? `~${Math.round(input.todayCaffeineMg)}mg caffeine so far.`
|
|
: hour >= 17 && cans === 0
|
|
? "Evening reset. Clean slate if you want it."
|
|
: hour >= 22
|
|
? "Late night. Pace yourself if you're still going."
|
|
: "Log an intake to unlock today's signals.");
|
|
|
|
const limitLine =
|
|
input.dailyCanLimit != null && cans > 0
|
|
? `${cans}/${input.dailyCanLimit} cans toward your daily limit.`
|
|
: null;
|
|
|
|
return {
|
|
badge,
|
|
headline,
|
|
subline: [flavourLine, limitLine ?? caffeineLine].filter(Boolean).join(" "),
|
|
};
|
|
}
|
|
|
|
export function stopTimeGreetingHint(stopTime?: string, pastStopTime?: boolean) {
|
|
if (!stopTime || !pastStopTime) return null;
|
|
return `Past your ${formatStopTimeLabel(stopTime)} stop time.`;
|
|
}
|
|
|
|
export function buildFlavourHistorySummary(entries: Parameters<typeof groupByFlavour>[0]) {
|
|
const breakdown = groupByFlavour(entries);
|
|
if (!breakdown.length) return "No flavour history yet.";
|
|
|
|
return breakdown
|
|
.map((item, index) => {
|
|
const rank = index === 0 ? " (all-time favourite)" : "";
|
|
return `- ${item.name}: ${item.value} cans${rank}`;
|
|
})
|
|
.join("\n");
|
|
}
|
|
|
|
function timeOfDayLabel(hour: number) {
|
|
if (hour >= 5 && hour < 12) return "morning";
|
|
if (hour >= 12 && hour < 17) return "afternoon";
|
|
if (hour >= 17 && hour < 22) return "evening";
|
|
return "night";
|
|
}
|