Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b488164e0 | ||
|
|
aa60acb164 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "laude",
|
"name": "laude",
|
||||||
"version": "0.3.1",
|
"version": "0.3.3",
|
||||||
"description": "Exercise reminder — Windows desktop app",
|
"description": "Exercise reminder — Windows desktop app",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"author": "AnRil",
|
"author": "AnRil",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<title>Exercise Reminder</title>
|
<title>Exercise Reminder</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -139,22 +139,22 @@ function ExerciseReminder({
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="text-[12px] uppercase tracking-[0.18em] text-text/45 font-medium">
|
<div className="text-[13px] uppercase tracking-[0.18em] text-accent font-bold">
|
||||||
Время тренировки
|
Время тренировки
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-serif text-[32px] leading-tight tracking-tight mt-2 mb-3">
|
<h1 className="font-serif text-[28px] leading-tight tracking-tight mt-2 mb-3 font-medium">
|
||||||
{exercise.name}
|
{exercise.name}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="inline-flex items-baseline gap-2 font-mono-num">
|
<div className="inline-flex items-baseline gap-2 font-mono-num">
|
||||||
<span className="text-[64px] font-semibold tracking-tight text-text leading-none">
|
<span className="text-[56px] font-semibold tracking-tight text-text leading-none">
|
||||||
{exercise.reps}
|
{exercise.reps}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[15px] text-text/55">раз</span>
|
<span className="text-[15px] text-text/65 font-semibold">раз</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-[12px] text-text/45 mt-4 inline-flex items-center gap-1.5">
|
<div className="text-[13px] text-text/65 mt-4 inline-flex items-center gap-1.5 font-medium">
|
||||||
<Clock size={11} strokeWidth={2.4} />
|
<Clock size={12} strokeWidth={2.4} />
|
||||||
Следующее через {formatInterval(exercise.intervalMinutes)}
|
Следующее через {formatInterval(exercise.intervalMinutes)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,20 +163,20 @@ function ExerciseReminder({
|
|||||||
<div className="px-4 pb-4 space-y-2">
|
<div className="px-4 pb-4 space-y-2">
|
||||||
<button
|
<button
|
||||||
onClick={done}
|
onClick={done}
|
||||||
className="w-full h-12 rounded-2xl bg-accent text-white text-[15px] font-semibold inline-flex items-center justify-center gap-1.5 active:scale-[0.98] transition-transform"
|
className="w-full h-12 rounded-2xl bg-accent text-white text-[16px] font-bold inline-flex items-center justify-center gap-1.5 active:scale-[0.98] transition-transform"
|
||||||
>
|
>
|
||||||
<Check size={16} strokeWidth={2.5} /> Готово
|
<Check size={17} strokeWidth={2.5} /> Готово
|
||||||
</button>
|
</button>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={snooze}
|
onClick={snooze}
|
||||||
className="h-11 rounded-2xl bg-surface-2 text-text text-[14px] font-medium inline-flex items-center justify-center gap-1.5 active:scale-[0.98] transition-transform"
|
className="h-11 rounded-2xl bg-surface-2 text-text text-[15px] font-semibold inline-flex items-center justify-center gap-1.5 active:scale-[0.98] transition-transform"
|
||||||
>
|
>
|
||||||
<Clock size={14} strokeWidth={2.5} /> {snoozeMinutes} мин
|
<Clock size={15} strokeWidth={2.5} /> {snoozeMinutes} мин
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={skip}
|
onClick={skip}
|
||||||
className="h-11 rounded-2xl bg-surface-2 text-text/55 text-[14px] font-medium inline-flex items-center justify-center gap-1.5 active:scale-[0.98] transition-transform"
|
className="h-11 rounded-2xl bg-surface-2 text-text/65 text-[15px] font-semibold inline-flex items-center justify-center gap-1.5 active:scale-[0.98] transition-transform"
|
||||||
>
|
>
|
||||||
Пропустить
|
Пропустить
|
||||||
</button>
|
</button>
|
||||||
@@ -208,8 +208,8 @@ function MatchSummaryView({
|
|||||||
return (
|
return (
|
||||||
<div className="reminder-shell flex flex-col h-full">
|
<div className="reminder-shell flex flex-col h-full">
|
||||||
<div className="titlebar-drag h-9 px-2 flex items-center justify-between">
|
<div className="titlebar-drag h-9 px-2 flex items-center justify-between">
|
||||||
<div className="text-[11px] text-text/45 font-medium inline-flex items-center gap-1.5 px-2">
|
<div className="text-[12px] text-text/65 font-semibold inline-flex items-center gap-1.5 px-2">
|
||||||
<Gamepad2 size={11} strokeWidth={2.4} /> {summary.gameName}
|
<Gamepad2 size={12} strokeWidth={2.4} /> {summary.gameName}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -238,19 +238,19 @@ function MatchSummaryView({
|
|||||||
<Gamepad2 size={26} strokeWidth={2} />
|
<Gamepad2 size={26} strokeWidth={2} />
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<h1 className="font-serif text-[22px] tracking-tight">
|
<h1 className="font-serif text-[24px] tracking-tight font-medium">
|
||||||
{won ? 'Победа' : lost ? 'Поражение' : 'Матч завершён'}
|
{won ? 'Победа' : lost ? 'Поражение' : 'Матч завершён'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[12px] text-text/45 mt-1">
|
<p className="text-[13px] text-text/65 mt-1.5 font-medium">
|
||||||
<span className="font-mono-num font-medium text-text/65">
|
<span className="font-mono-num font-bold text-text">
|
||||||
{Math.floor(summary.durationMs / 60_000)}
|
{Math.floor(summary.durationMs / 60_000)}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
мин · {summary.results.length} челлендж
|
мин · {summary.results.length} челлендж
|
||||||
{summary.results.length === 1 ? '' : 'а'} ·{' '}
|
{summary.results.length === 1 ? '' : 'а'} ·{' '}
|
||||||
{allDone ? (
|
{allDone ? (
|
||||||
<span className="text-success font-medium">всё готово</span>
|
<span className="text-success font-bold">всё готово</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-accent font-mono-num font-semibold">
|
<span className="text-accent font-mono-num font-bold">
|
||||||
{remainingReps} осталось
|
{remainingReps} осталось
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -269,9 +269,9 @@ function MatchSummaryView({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-4 pb-4 pt-3 flex items-center gap-3">
|
<div className="px-4 pb-4 pt-3 flex items-center gap-3">
|
||||||
<div className="flex-1 text-[12px] text-text/55">
|
<div className="flex-1 text-[13px] text-text/65 font-medium">
|
||||||
Всего ·{' '}
|
Всего ·{' '}
|
||||||
<span className="text-text font-mono-num font-semibold text-[14px]">
|
<span className="text-text font-mono-num font-bold text-[16px]">
|
||||||
{totalReps}
|
{totalReps}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
повторов
|
повторов
|
||||||
@@ -326,14 +326,14 @@ function ChallengeRow({
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
'text-[14px] font-medium truncate',
|
'text-[15px] font-semibold truncate',
|
||||||
done ? 'line-through opacity-55' : ''
|
done ? 'line-through opacity-55' : ''
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
{result.exerciseName}
|
{result.exerciseName}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] text-text/55 mt-0.5">
|
<div className="text-[13px] text-text/65 mt-0.5 font-medium">
|
||||||
<span className="font-mono-num font-semibold text-text/75">
|
<span className="font-mono-num font-bold text-text">
|
||||||
{result.statValue}
|
{result.statValue}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
{result.statLabel} → <span>{result.name}</span>
|
{result.statLabel} → <span>{result.name}</span>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export function ExerciseCard({
|
|||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<h3 className="font-display text-[17px] font-semibold leading-tight truncate">
|
<h3 className="font-display text-[18px] font-bold leading-tight truncate">
|
||||||
{exercise.name}
|
{exercise.name}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -138,19 +138,19 @@ export function ExerciseCard({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[13px] text-text/55 mt-0.5">
|
<div className="text-[14px] text-text/65 mt-1 font-medium">
|
||||||
{exercise.reps} раз · каждые {formatInterval(exercise.intervalMinutes)}
|
{exercise.reps} раз · каждые {formatInterval(exercise.intervalMinutes)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Countdown + switch */}
|
{/* Countdown + switch */}
|
||||||
<div className="flex items-end justify-between mt-3.5">
|
<div className="flex items-end justify-between mt-3.5">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] text-text/45 uppercase tracking-wider font-medium">
|
<div className="text-[12px] text-text/60 uppercase tracking-wider font-semibold">
|
||||||
{isDue ? 'Сейчас' : 'Через'}
|
{isDue ? 'Сейчас' : 'Через'}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
'font-mono-num text-[22px] font-semibold leading-none mt-0.5 tracking-tight',
|
'font-mono-num text-[24px] font-bold leading-none mt-1 tracking-tight',
|
||||||
isDue ? 'text-accent' : 'text-text'
|
isDue ? 'text-accent' : 'text-text'
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
@@ -172,7 +172,7 @@ export function ExerciseCard({
|
|||||||
initial={{ opacity: 0, y: 4 }}
|
initial={{ opacity: 0, y: 4 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
onClick={onMarkDone}
|
onClick={onMarkDone}
|
||||||
className="mt-4 w-full h-10 rounded-xl bg-accent text-white text-[14px] font-semibold inline-flex items-center justify-center gap-1.5 active:scale-[0.98] transition-transform"
|
className="mt-4 w-full h-11 rounded-xl bg-accent text-white text-[15px] font-bold inline-flex items-center justify-center gap-1.5 active:scale-[0.98] transition-transform"
|
||||||
>
|
>
|
||||||
<Check size={15} strokeWidth={2.5} /> Готово
|
<Check size={15} strokeWidth={2.5} /> Готово
|
||||||
</motion.button>
|
</motion.button>
|
||||||
|
|||||||
@@ -1,38 +1,37 @@
|
|||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from 'react-router-dom'
|
||||||
import { AnimatePresence, motion } from 'framer-motion'
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
import {
|
import {
|
||||||
LayoutDashboard,
|
Sun,
|
||||||
ListChecks,
|
Dumbbell,
|
||||||
Gamepad2,
|
Joystick,
|
||||||
Target,
|
Flame,
|
||||||
Settings as SettingsIcon,
|
Settings2,
|
||||||
X
|
X
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
type Item = {
|
type Item = {
|
||||||
to: string
|
to: string
|
||||||
label: string
|
label: string
|
||||||
icon: typeof LayoutDashboard
|
icon: typeof Sun
|
||||||
end?: boolean
|
end?: boolean
|
||||||
tint?: string
|
tint?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each item gets a tinted icon-square reminiscent of iOS Settings rows.
|
// Tinted icon plaques á la iOS Settings rows.
|
||||||
const items: Item[] = [
|
const items: Item[] = [
|
||||||
|
{ to: '/', label: 'Сегодня', icon: Sun, end: true, tint: 'bg-accent' },
|
||||||
{
|
{
|
||||||
to: '/',
|
to: '/exercises',
|
||||||
label: 'Сегодня',
|
label: 'Упражнения',
|
||||||
icon: LayoutDashboard,
|
icon: Dumbbell,
|
||||||
end: true,
|
tint: 'bg-info'
|
||||||
tint: 'bg-accent'
|
|
||||||
},
|
},
|
||||||
{ to: '/exercises', label: 'Упражнения', icon: ListChecks, tint: 'bg-info' },
|
{ to: '/games', label: 'Игры', icon: Joystick, tint: 'bg-accent-2' },
|
||||||
{ to: '/games', label: 'Игры', icon: Gamepad2, tint: 'bg-accent-2' },
|
{ to: '/challenges', label: 'Челленджи', icon: Flame, tint: 'bg-warning' },
|
||||||
{ to: '/challenges', label: 'Челленджи', icon: Target, tint: 'bg-warning' },
|
|
||||||
{
|
{
|
||||||
to: '/settings',
|
to: '/settings',
|
||||||
label: 'Настройки',
|
label: 'Настройки',
|
||||||
icon: SettingsIcon,
|
icon: Settings2,
|
||||||
tint: 'bg-text/70'
|
tint: 'bg-text/70'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -98,16 +97,16 @@ function SidebarContent({ onNav }: { onNav?: () => void }): JSX.Element {
|
|||||||
<>
|
<>
|
||||||
{/* Brand */}
|
{/* Brand */}
|
||||||
<div className="px-5 pt-7 pb-6">
|
<div className="px-5 pt-7 pb-6">
|
||||||
<div className="font-serif text-[28px] leading-none tracking-tight">
|
<div className="font-serif text-[34px] leading-none tracking-tight font-medium">
|
||||||
Laude
|
Laude
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] text-text/45 mt-1.5">
|
<div className="text-[12px] text-text/45 mt-2 tracking-tight">
|
||||||
Move with intention
|
Двигайся осознанно
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Nav */}
|
{/* Nav */}
|
||||||
<nav className="px-2.5 flex flex-col gap-0.5">
|
<nav className="px-2.5 flex flex-col gap-1">
|
||||||
{items.map(({ to, label, icon: Icon, end, tint }) => (
|
{items.map(({ to, label, icon: Icon, end, tint }) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={to}
|
key={to}
|
||||||
@@ -127,15 +126,15 @@ function SidebarContent({ onNav }: { onNav?: () => void }): JSX.Element {
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
'w-7 h-7 rounded-lg grid place-items-center text-white shrink-0',
|
'w-8 h-8 rounded-[9px] grid place-items-center text-white shrink-0',
|
||||||
tint ?? 'bg-text/70'
|
tint ?? 'bg-text/70'
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
<Icon size={15} strokeWidth={2.4} />
|
<Icon size={17} strokeWidth={2.2} />
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={[
|
className={[
|
||||||
'text-[14px] truncate',
|
'text-[15px] truncate',
|
||||||
isActive
|
isActive
|
||||||
? 'text-text font-semibold'
|
? 'text-text font-semibold'
|
||||||
: 'text-text/85 font-medium'
|
: 'text-text/85 font-medium'
|
||||||
|
|||||||
@@ -126,19 +126,19 @@ function Body({
|
|||||||
return (
|
return (
|
||||||
<div className="px-4 py-4">
|
<div className="px-4 py-4">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div className="w-9 h-9 rounded-xl bg-accent/12 text-accent grid place-items-center">
|
<div className="w-10 h-10 rounded-xl bg-accent/12 text-accent grid place-items-center">
|
||||||
<Download size={16} strokeWidth={2.4} />
|
<Download size={17} strokeWidth={2.4} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[15px] font-medium leading-tight">
|
<div className="text-[15px] font-semibold leading-tight">
|
||||||
Загружаем обновление
|
Загружаем обновление
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] text-text/55 mt-0.5 font-mono-num">
|
<div className="text-[13px] text-text/65 mt-1 font-mono-num font-medium">
|
||||||
{mb(status.transferred)} / {mb(status.total)} МБ ·{' '}
|
{mb(status.transferred)} / {mb(status.total)} МБ ·{' '}
|
||||||
{(status.bytesPerSecond / 1024 / 1024).toFixed(2)} МБ/с
|
{(status.bytesPerSecond / 1024 / 1024).toFixed(2)} МБ/с
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="font-mono-num font-semibold text-[17px] text-accent">
|
<div className="font-mono-num font-bold text-[18px] text-accent">
|
||||||
{pct.toFixed(0)}%
|
{pct.toFixed(0)}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -218,19 +218,19 @@ function Cell({
|
|||||||
muted: 'bg-surface-2 text-text/55'
|
muted: 'bg-surface-2 text-text/55'
|
||||||
}[tone]
|
}[tone]
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 px-4 py-3.5">
|
<div className="flex items-center gap-3 px-4 py-4">
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
'w-9 h-9 rounded-xl grid place-items-center shrink-0',
|
'w-10 h-10 rounded-xl grid place-items-center shrink-0',
|
||||||
cls
|
cls
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[15px] font-medium leading-tight">{title}</div>
|
<div className="text-[15px] font-semibold leading-tight">{title}</div>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<div className="text-[12px] text-text/55 mt-0.5 truncate">
|
<div className="text-[13px] text-text/65 mt-1 truncate font-medium">
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -39,12 +39,16 @@ export function SectionHeader({
|
|||||||
action?: ReactNode
|
action?: ReactNode
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-end justify-between px-4 mb-2">
|
<div className="flex items-end justify-between px-4 mb-2.5">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[12px] font-medium uppercase tracking-[0.06em] text-text/45">
|
<div className="text-[13px] font-semibold uppercase tracking-[0.06em] text-text/60">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
{hint && <div className="text-[12px] text-text/45 mt-0.5">{hint}</div>}
|
{hint && (
|
||||||
|
<div className="text-[13px] text-text/55 mt-0.5 font-medium">
|
||||||
|
{hint}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{action}
|
{action}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ export default function ChallengesPage(): JSX.Element {
|
|||||||
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-10 pt-8 pb-12">
|
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-10 pt-8 pb-12">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[13px] text-text/45 font-medium">
|
<div className="text-[14px] text-text/65 font-semibold">
|
||||||
Правила за матч
|
Правила за матч
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-serif text-[40px] sm:text-[44px] leading-[1.05] tracking-tight mt-1">
|
<h1 className="font-serif text-[32px] sm:text-[36px] leading-[1.05] tracking-tight mt-1 font-medium">
|
||||||
Челленджи
|
Челленджи
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[14px] text-text/55 mt-2">
|
<p className="text-[15px] text-text/65 mt-2 font-medium">
|
||||||
Повторов = <span className="font-mono-num">статистика × коэффициент</span>
|
Повторов = <span className="font-mono-num font-semibold text-text">статистика × коэффициент</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -70,12 +70,12 @@ export default function ChallengesPage(): JSX.Element {
|
|||||||
|
|
||||||
{noGamesActive && (
|
{noGamesActive && (
|
||||||
<div className="mb-6 rounded-2xl bg-warning/12 p-4 flex items-start gap-3">
|
<div className="mb-6 rounded-2xl bg-warning/12 p-4 flex items-start gap-3">
|
||||||
<div className="w-9 h-9 rounded-xl bg-warning/15 text-warning grid place-items-center shrink-0">
|
<div className="w-10 h-10 rounded-xl bg-warning/18 text-warning grid place-items-center shrink-0">
|
||||||
<AlertTriangle size={16} strokeWidth={2.5} />
|
<AlertTriangle size={18} strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[13px] text-text/80 leading-relaxed">
|
<div className="text-[14px] text-text/85 leading-relaxed font-medium">
|
||||||
Челленджи срабатывают после матча. Подключи игру во вкладке{' '}
|
Челленджи срабатывают после матча. Подключи игру во вкладке{' '}
|
||||||
<span className="font-semibold">«Игры»</span>.
|
<span className="font-semibold text-text">«Игры»</span>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -104,13 +104,13 @@ export default function ChallengesPage(): JSX.Element {
|
|||||||
<Icon name={c.icon} size={18} strokeWidth={2.2} />
|
<Icon name={c.icon} size={18} strokeWidth={2.2} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[15px] font-medium truncate leading-tight">
|
<div className="text-[16px] font-semibold truncate leading-tight">
|
||||||
{c.name}
|
{c.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[13px] text-text/55 mt-0.5 inline-flex items-center gap-1.5">
|
<div className="text-[14px] text-text/65 mt-1 inline-flex items-center gap-1.5 font-medium">
|
||||||
<Gamepad2 size={11} />
|
<Gamepad2 size={12} strokeWidth={2.4} />
|
||||||
{GAME_NAMES[c.gameId]} ·{' '}
|
{GAME_NAMES[c.gameId]} ·{' '}
|
||||||
<span className="font-mono-num">
|
<span className="font-mono-num font-semibold text-text">
|
||||||
{STAT_LABELS[c.stat]} × {c.multiplier}
|
{STAT_LABELS[c.stat]} × {c.multiplier}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
→ {c.exerciseName}
|
→ {c.exerciseName}
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ export default function Dashboard(): JSX.Element {
|
|||||||
{/* Hero — iOS Large Title */}
|
{/* Hero — iOS Large Title */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="text-[13px] text-text/45 font-medium capitalize">
|
<div className="text-[14px] text-text/65 font-semibold capitalize">
|
||||||
{today}
|
{today}
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-serif text-[40px] sm:text-[44px] leading-[1.05] tracking-tight mt-1">
|
<h1 className="font-serif text-[32px] sm:text-[36px] leading-[1.05] tracking-tight mt-1 font-medium">
|
||||||
Сегодня
|
Сегодня
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,19 +139,19 @@ export default function Dashboard(): JSX.Element {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="mb-6 rounded-2xl bg-warning/12 p-4 flex items-center gap-3"
|
className="mb-6 rounded-2xl bg-warning/12 p-4 flex items-center gap-3"
|
||||||
>
|
>
|
||||||
<div className="w-9 h-9 rounded-xl bg-warning/15 text-warning grid place-items-center">
|
<div className="w-10 h-10 rounded-xl bg-warning/18 text-warning grid place-items-center shrink-0">
|
||||||
<Pause size={16} strokeWidth={2.5} />
|
<Pause size={18} strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[14px] font-semibold">
|
<div className="text-[16px] font-semibold leading-tight">
|
||||||
Напоминания на паузе
|
Напоминания на паузе
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] text-text/55 mt-0.5">
|
<div className="text-[14px] text-text/70 mt-1">
|
||||||
Возобнови, чтобы продолжить отсчёт
|
Возобнови, чтобы продолжить отсчёт
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="filled" size="sm" onClick={togglePause}>
|
<Button variant="filled" size="sm" onClick={togglePause}>
|
||||||
<Play size={13} strokeWidth={2.5} /> Старт
|
<Play size={14} strokeWidth={2.5} /> Старт
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@@ -225,19 +225,21 @@ function HeroStat({
|
|||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
'w-6 h-6 rounded-md grid place-items-center text-white',
|
'w-7 h-7 rounded-lg grid place-items-center text-white',
|
||||||
toneBg
|
toneBg
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] text-text/55 font-medium">{label}</div>
|
<div className="text-[14px] text-text/75 font-semibold">{label}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="font-display text-[30px] font-semibold tracking-tight leading-none">
|
<div className="font-display text-[28px] font-bold tracking-tight leading-none">
|
||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
{subvalue && (
|
{subvalue && (
|
||||||
<div className="text-[12px] text-text/45 mt-1.5">{subvalue}</div>
|
<div className="text-[13px] text-text/60 mt-2 font-medium">
|
||||||
|
{subvalue}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ export default function Exercises(): JSX.Element {
|
|||||||
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-10 pt-8 pb-12">
|
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-10 pt-8 pb-12">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[13px] text-text/45 font-medium">
|
<div className="text-[14px] text-text/65 font-semibold">
|
||||||
Программа
|
Программа
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-serif text-[40px] sm:text-[44px] leading-[1.05] tracking-tight mt-1">
|
<h1 className="font-serif text-[32px] sm:text-[36px] leading-[1.05] tracking-tight mt-1 font-medium">
|
||||||
Упражнения
|
Упражнения
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +79,7 @@ export default function Exercises(): JSX.Element {
|
|||||||
|
|
||||||
{exercises.length === 0 && (
|
{exercises.length === 0 && (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="px-5 py-12 text-center text-text/55 text-[14px]">
|
<div className="px-5 py-12 text-center text-text/65 text-[15px] font-medium">
|
||||||
Программа пуста — добавь первое упражнение
|
Программа пуста — добавь первое упражнение
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -126,10 +126,10 @@ function ExerciseRow({
|
|||||||
onClick={onEdit}
|
onClick={onEdit}
|
||||||
className="flex-1 min-w-0 text-left active:opacity-70 transition-opacity"
|
className="flex-1 min-w-0 text-left active:opacity-70 transition-opacity"
|
||||||
>
|
>
|
||||||
<div className="text-[15px] font-medium truncate leading-tight">
|
<div className="text-[16px] font-semibold truncate leading-tight">
|
||||||
{exercise.name}
|
{exercise.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[13px] text-text/55 mt-0.5">
|
<div className="text-[14px] text-text/65 mt-1 font-medium">
|
||||||
{exercise.reps} раз · {formatInterval(exercise.intervalMinutes)}
|
{exercise.reps} раз · {formatInterval(exercise.intervalMinutes)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -61,18 +61,18 @@ export default function GamesPage(): JSX.Element {
|
|||||||
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-10 pt-8 pb-12">
|
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-10 pt-8 pb-12">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[13px] text-text/45 font-medium">
|
<div className="text-[14px] text-text/65 font-semibold">
|
||||||
Трекинг матчей
|
Трекинг матчей
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-serif text-[40px] sm:text-[44px] leading-[1.05] tracking-tight mt-1">
|
<h1 className="font-serif text-[32px] sm:text-[36px] leading-[1.05] tracking-tight mt-1 font-medium">
|
||||||
Игры
|
Игры
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[14px] text-text/55 mt-2">
|
<p className="text-[15px] text-text/65 mt-2 font-medium leading-relaxed">
|
||||||
Подключи игру — челленджи сработают сразу после матча
|
Подключи игру — челленджи сработают сразу после матча
|
||||||
{liveCount > 0 && (
|
{liveCount > 0 && (
|
||||||
<>
|
<>
|
||||||
{' · '}
|
{' · '}
|
||||||
<span className="text-success font-mono-num font-semibold">
|
<span className="text-success font-mono-num font-bold">
|
||||||
{liveCount} live
|
{liveCount} live
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -154,13 +154,13 @@ function GameCard({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<h3 className="font-display text-[17px] font-semibold tracking-tight">
|
<h3 className="font-display text-[18px] font-bold tracking-tight">
|
||||||
{game.name}
|
{game.name}
|
||||||
</h3>
|
</h3>
|
||||||
<StatusBadge game={game} isLive={isLive} />
|
<StatusBadge game={game} isLive={isLive} />
|
||||||
</div>
|
</div>
|
||||||
{game.installPath && (
|
{game.installPath && (
|
||||||
<div className="text-[12px] text-text/45 mt-1 truncate font-mono-num">
|
<div className="text-[13px] text-text/55 mt-1.5 truncate font-mono-num font-medium">
|
||||||
{game.installPath}
|
{game.installPath}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -176,15 +176,15 @@ function GameCard({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{game.integrationActive && game.launchOptionStatus === 'queued' && (
|
{game.integrationActive && game.launchOptionStatus === 'queued' && (
|
||||||
<div className="mt-4 rounded-2xl bg-warning/12 p-3.5 text-[13px] leading-relaxed flex items-start gap-2.5">
|
<div className="mt-4 rounded-2xl bg-warning/12 p-4 text-[14px] leading-relaxed flex items-start gap-2.5 font-medium">
|
||||||
<Hourglass
|
<Hourglass
|
||||||
size={15}
|
size={17}
|
||||||
className="text-warning shrink-0 mt-0.5"
|
className="text-warning shrink-0 mt-0.5"
|
||||||
strokeWidth={2.4}
|
strokeWidth={2.4}
|
||||||
/>
|
/>
|
||||||
<div className="text-text/80">
|
<div className="text-text/85">
|
||||||
Steam запущен. Параметр{' '}
|
Steam запущен. Параметр{' '}
|
||||||
<code className="px-1.5 py-0.5 rounded-md bg-surface text-accent font-mono-num text-[12px]">
|
<code className="px-1.5 py-0.5 rounded-md bg-surface text-accent font-mono-num text-[13px] font-semibold">
|
||||||
{game.launchOption}
|
{game.launchOption}
|
||||||
</code>{' '}
|
</code>{' '}
|
||||||
пропишется автоматически при следующем закрытии Steam.
|
пропишется автоматически при следующем закрытии Steam.
|
||||||
@@ -193,15 +193,15 @@ function GameCard({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{game.integrationActive && game.launchOptionStatus === 'no_user' && (
|
{game.integrationActive && game.launchOptionStatus === 'no_user' && (
|
||||||
<div className="mt-4 rounded-2xl bg-destructive/10 p-3.5 text-[13px] leading-relaxed flex items-start gap-2.5">
|
<div className="mt-4 rounded-2xl bg-destructive/10 p-4 text-[14px] leading-relaxed flex items-start gap-2.5 font-medium">
|
||||||
<AlertTriangle
|
<AlertTriangle
|
||||||
size={15}
|
size={17}
|
||||||
className="text-destructive shrink-0 mt-0.5"
|
className="text-destructive shrink-0 mt-0.5"
|
||||||
strokeWidth={2.4}
|
strokeWidth={2.4}
|
||||||
/>
|
/>
|
||||||
<div className="text-text/80">
|
<div className="text-text/85">
|
||||||
В Steam нет залогиненного аккаунта (нет папки{' '}
|
В Steam нет залогиненного аккаунта (нет папки{' '}
|
||||||
<code className="font-mono-num text-[12px]">userdata</code>).
|
<code className="font-mono-num text-[13px] font-semibold">userdata</code>).
|
||||||
Запусти Steam один раз и нажми «Установить интеграцию».
|
Запусти Steam один раз и нажми «Установить интеграцию».
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -224,7 +224,7 @@ function GameCard({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!game.installed && (
|
{!game.installed && (
|
||||||
<div className="text-[13px] text-text/55">
|
<div className="text-[14px] text-text/65 font-medium">
|
||||||
Установи игру в Steam и нажми «Обновить»
|
Установи игру в Steam и нажми «Обновить»
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ export default function SettingsPage(): JSX.Element {
|
|||||||
<div className="h-full overflow-y-auto">
|
<div className="h-full overflow-y-auto">
|
||||||
<div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-10 pt-8 pb-12">
|
<div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-10 pt-8 pb-12">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="text-[13px] text-text/45 font-medium">
|
<div className="text-[14px] text-text/65 font-semibold">
|
||||||
Конфигурация
|
Конфигурация
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-serif text-[40px] sm:text-[44px] leading-[1.05] tracking-tight mt-1">
|
<h1 className="font-serif text-[32px] sm:text-[36px] leading-[1.05] tracking-tight mt-1 font-medium">
|
||||||
Настройки
|
Настройки
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,9 +132,11 @@ function ToggleRow({
|
|||||||
return (
|
return (
|
||||||
<Row last={last} className={disabled ? 'opacity-50' : ''}>
|
<Row last={last} className={disabled ? 'opacity-50' : ''}>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[15px] font-medium leading-tight">{label}</div>
|
<div className="text-[15px] font-semibold leading-tight">{label}</div>
|
||||||
{hint && (
|
{hint && (
|
||||||
<div className="text-[12px] text-text/55 mt-0.5">{hint}</div>
|
<div className="text-[13px] text-text/65 mt-1 leading-snug">
|
||||||
|
{hint}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Switch checked={checked} onChange={onChange} disabled={disabled} />
|
<Switch checked={checked} onChange={onChange} disabled={disabled} />
|
||||||
@@ -160,9 +162,11 @@ function SelectRow({
|
|||||||
return (
|
return (
|
||||||
<Row last={last}>
|
<Row last={last}>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[15px] font-medium leading-tight">{label}</div>
|
<div className="text-[15px] font-semibold leading-tight">{label}</div>
|
||||||
{hint && (
|
{hint && (
|
||||||
<div className="text-[12px] text-text/55 mt-0.5">{hint}</div>
|
<div className="text-[13px] text-text/65 mt-1 leading-snug">
|
||||||
|
{hint}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
|
|||||||
@@ -2,34 +2,33 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
/* ===== Design tokens — Apple Human Interface Guidelines ===== */
|
/* ===== Design tokens — Apple HIG with Manrope/Fraunces ===== */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Brand & semantic colors (iOS system palette) */
|
/* Brand & semantic colors (iOS system palette) */
|
||||||
--accent: 255 107 53; /* Apple Fitness Move orange */
|
--accent: 255 107 53; /* Apple Fitness Move orange */
|
||||||
--accent-2: 255 45 85; /* systemPink */
|
--accent-2: 255 45 85; /* systemPink */
|
||||||
--success: 52 199 89; /* systemGreen */
|
--success: 52 199 89; /* systemGreen */
|
||||||
--warning: 255 159 10; /* systemOrange-dark */
|
--warning: 255 159 10; /* systemOrange dark */
|
||||||
--destructive: 255 59 48; /* systemRed */
|
--destructive: 255 59 48; /* systemRed */
|
||||||
--info: 0 122 255; /* systemBlue */
|
--info: 0 122 255; /* systemBlue */
|
||||||
|
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Light — iOS groupedBackground feel */
|
/* Light — polished iOS groupedBackground with warm undertone */
|
||||||
:root {
|
:root {
|
||||||
--bg: 242 242 247; /* systemGroupedBackground */
|
--bg: 245 245 249; /* slightly warmer than 242,242,247 */
|
||||||
--surface: 255 255 255; /* secondarySystemGroupedBackground (cards) */
|
--surface: 255 255 255;
|
||||||
--surface-2: 242 242 247; /* tertiarySystemGroupedBackground */
|
--surface-2: 240 240 245; /* subtle separation for inputs/sections */
|
||||||
--text: 0 0 0;
|
--text: 17 17 19; /* not pure black — softer */
|
||||||
--text-secondary: 60 60 67; /* used with opacity 0.6 */
|
--text-secondary: 60 60 67;
|
||||||
--text-tertiary: 60 60 67; /* used with opacity 0.3 */
|
--text-tertiary: 60 60 67;
|
||||||
--hairline: 60 60 67; /* used with opacity 0.18 */
|
--hairline: 60 60 67;
|
||||||
--vibrancy: 255 255 255; /* sidebar translucent base */
|
--vibrancy: 255 255 255;
|
||||||
|
|
||||||
/* Legacy tokens (mapped to keep some old utility classes working) */
|
|
||||||
--accent-soft: 255 107 53;
|
--accent-soft: 255 107 53;
|
||||||
--bg-deep: 230 230 235;
|
--bg-deep: 232 232 238;
|
||||||
--surface-elevated: 255 255 255;
|
--surface-elevated: 255 255 255;
|
||||||
--border: 60 60 67;
|
--border: 60 60 67;
|
||||||
--muted: 60 60 67;
|
--muted: 60 60 67;
|
||||||
@@ -38,11 +37,11 @@
|
|||||||
--xp: 255 159 10;
|
--xp: 255 159 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark — iOS true black for OLED, elevation via grey steps */
|
/* Dark — true black with grey elevation */
|
||||||
.dark {
|
.dark {
|
||||||
--bg: 0 0 0; /* systemBackground */
|
--bg: 0 0 0;
|
||||||
--surface: 28 28 30; /* secondarySystemBackground */
|
--surface: 28 28 30;
|
||||||
--surface-2: 44 44 46; /* tertiarySystemBackground */
|
--surface-2: 44 44 46;
|
||||||
--text: 255 255 255;
|
--text: 255 255 255;
|
||||||
--text-secondary: 235 235 245;
|
--text-secondary: 235 235 245;
|
||||||
--text-tertiary: 235 235 245;
|
--text-tertiary: 235 235 245;
|
||||||
@@ -68,24 +67,26 @@ body,
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font-family:
|
font-family:
|
||||||
'Geist',
|
'Manrope',
|
||||||
-apple-system,
|
-apple-system,
|
||||||
'SF Pro Text',
|
'SF Pro Text',
|
||||||
'Segoe UI Variable Text',
|
'Segoe UI Variable Text',
|
||||||
'Segoe UI',
|
'Segoe UI',
|
||||||
system-ui,
|
system-ui,
|
||||||
sans-serif;
|
sans-serif;
|
||||||
font-feature-settings: 'cv11', 'ss01', 'ss03'; /* Geist stylistic alts */
|
|
||||||
background-color: rgb(var(--bg));
|
background-color: rgb(var(--bg));
|
||||||
color: rgb(var(--text));
|
color: rgb(var(--text));
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
letter-spacing: -0.01em;
|
font-size: 14px;
|
||||||
|
line-height: 1.45;
|
||||||
|
letter-spacing: -0.005em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Display — same Manrope but slightly tighter for headings */
|
||||||
.font-display {
|
.font-display {
|
||||||
font-family:
|
font-family:
|
||||||
'Geist',
|
'Manrope',
|
||||||
-apple-system,
|
-apple-system,
|
||||||
'SF Pro Display',
|
'SF Pro Display',
|
||||||
'Segoe UI Variable Display',
|
'Segoe UI Variable Display',
|
||||||
@@ -94,17 +95,20 @@ body {
|
|||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Serif — Fraunces for hero titles with optical-size axis */
|
||||||
.font-serif {
|
.font-serif {
|
||||||
font-family: 'Instrument Serif', 'Iowan Old Style', 'Apple Garamond', Georgia,
|
font-family: 'Fraunces', 'Iowan Old Style', 'New York', Georgia, serif;
|
||||||
serif;
|
font-optical-sizing: auto;
|
||||||
letter-spacing: -0.01em;
|
font-variation-settings: 'opsz' 144;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-mono-num {
|
.font-mono-num {
|
||||||
font-family: 'Geist Mono', ui-monospace, 'SF Mono', 'Cascadia Code', Menlo,
|
font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Code',
|
||||||
monospace;
|
Menlo, monospace;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-feature-settings: 'ss02';
|
font-feature-settings: 'ss02', 'ss19', 'zero';
|
||||||
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom titlebar drag region */
|
/* Custom titlebar drag region */
|
||||||
@@ -117,7 +121,7 @@ body {
|
|||||||
app-region: no-drag;
|
app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Thin iOS-style hairline (effectively 0.5px when device DPR allows) */
|
/* iOS 0.5px-style hairlines */
|
||||||
.hairline-b {
|
.hairline-b {
|
||||||
box-shadow: inset 0 -0.5px 0 rgb(var(--hairline) / 0.18);
|
box-shadow: inset 0 -0.5px 0 rgb(var(--hairline) / 0.18);
|
||||||
}
|
}
|
||||||
@@ -129,19 +133,19 @@ body {
|
|||||||
box-shadow: inset 0 -0.5px 0 rgb(var(--hairline) / 0.4);
|
box-shadow: inset 0 -0.5px 0 rgb(var(--hairline) / 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Vibrancy panel (macOS Big Sur+ sidebar feel) */
|
/* macOS vibrancy */
|
||||||
.vibrancy {
|
.vibrancy {
|
||||||
background-color: rgb(var(--vibrancy) / 0.72);
|
background-color: rgb(var(--vibrancy) / 0.72);
|
||||||
backdrop-filter: saturate(180%) blur(30px);
|
backdrop-filter: saturate(180%) blur(30px);
|
||||||
-webkit-backdrop-filter: saturate(180%) blur(30px);
|
-webkit-backdrop-filter: saturate(180%) blur(30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iOS-style soft card shadow */
|
/* Soft iOS card shadow with subtle warmth */
|
||||||
.shadow-card {
|
.shadow-card {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 0.5px 0 rgb(0 0 0 / 0.03),
|
0 0.5px 0 rgb(0 0 0 / 0.03),
|
||||||
0 1px 2px rgb(0 0 0 / 0.04),
|
0 1px 2px rgb(15 23 42 / 0.04),
|
||||||
0 4px 12px rgb(0 0 0 / 0.04);
|
0 6px 14px -4px rgb(15 23 42 / 0.05);
|
||||||
}
|
}
|
||||||
.dark .shadow-card {
|
.dark .shadow-card {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
@@ -149,7 +153,7 @@ body {
|
|||||||
0 1px 2px rgb(0 0 0 / 0.4);
|
0 1px 2px rgb(0 0 0 / 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar — thin, iOS-style */
|
/* Scrollbar */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
@@ -165,18 +169,15 @@ body {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Selection */
|
|
||||||
::selection {
|
::selection {
|
||||||
background: rgb(var(--accent) / 0.25);
|
background: rgb(var(--accent) / 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iOS focus ring */
|
|
||||||
*:focus-visible {
|
*:focus-visible {
|
||||||
outline: 2px solid rgb(var(--accent) / 0.55);
|
outline: 2px solid rgb(var(--accent) / 0.55);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reminder window root — iOS sheet */
|
|
||||||
.reminder-shell {
|
.reminder-shell {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 0.5px solid rgb(var(--hairline) / 0.25);
|
border: 0.5px solid rgb(var(--hairline) / 0.25);
|
||||||
@@ -189,7 +190,6 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Text helpers (semantic aliases) */
|
|
||||||
.text-secondary {
|
.text-secondary {
|
||||||
color: rgb(var(--text-secondary) / 0.6);
|
color: rgb(var(--text-secondary) / 0.6);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||||||
startWithWindows: false,
|
startWithWindows: false,
|
||||||
minimizeToTray: true,
|
minimizeToTray: true,
|
||||||
startMinimized: false,
|
startMinimized: false,
|
||||||
theme: 'system',
|
theme: 'light',
|
||||||
snoozeMinutes: 5
|
snoozeMinutes: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default {
|
|||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [
|
sans: [
|
||||||
'Geist',
|
'Manrope',
|
||||||
'-apple-system',
|
'-apple-system',
|
||||||
'SF Pro Text',
|
'SF Pro Text',
|
||||||
'Segoe UI Variable Text',
|
'Segoe UI Variable Text',
|
||||||
@@ -43,7 +43,7 @@ export default {
|
|||||||
'sans-serif'
|
'sans-serif'
|
||||||
],
|
],
|
||||||
display: [
|
display: [
|
||||||
'Geist',
|
'Manrope',
|
||||||
'-apple-system',
|
'-apple-system',
|
||||||
'SF Pro Display',
|
'SF Pro Display',
|
||||||
'Segoe UI Variable Display',
|
'Segoe UI Variable Display',
|
||||||
@@ -51,14 +51,14 @@ export default {
|
|||||||
'sans-serif'
|
'sans-serif'
|
||||||
],
|
],
|
||||||
serif: [
|
serif: [
|
||||||
'Instrument Serif',
|
'Fraunces',
|
||||||
'Iowan Old Style',
|
'Iowan Old Style',
|
||||||
'Apple Garamond',
|
'New York',
|
||||||
'Georgia',
|
'Georgia',
|
||||||
'serif'
|
'serif'
|
||||||
],
|
],
|
||||||
mono: [
|
mono: [
|
||||||
'Geist Mono',
|
'JetBrains Mono',
|
||||||
'ui-monospace',
|
'ui-monospace',
|
||||||
'SF Mono',
|
'SF Mono',
|
||||||
'Cascadia Code',
|
'Cascadia Code',
|
||||||
|
|||||||
Reference in New Issue
Block a user