feat(landing): секции по DESIGN.md
Шрифты Golos Text и Martian Mono через next/font, токены @theme, все блоки по плану: первый экран со штампом даты ближайшей обжарки, лента недели, этикетки сортов, подписка, кто мы, доставка, форма, подвал. Три фото Unsplash локально, priority только у первого экрана. LeadForm перекрашена под токены, логика и data-атрибуты те же. В site.ts и form.ts добавлены служебные подписи для вёрстки.
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatShortDate, getRoastWeek } from './roastWeek';
|
||||
|
||||
describe('getRoastWeek', () => {
|
||||
it('в субботу ведёт на следующий четверг', () => {
|
||||
const week = getRoastWeek(new Date('2026-10-17T12:00:00Z'));
|
||||
expect(week.roast.iso).toBe('2026-10-22');
|
||||
expect(week.delivery.iso).toBe('2026-10-23');
|
||||
expect(week.days.map((d) => d.letter).join(' ')).toBe('ПН ВТ СР ЧТ ПТ СБ ВС');
|
||||
expect(week.days[0]?.iso).toBe('2026-10-19');
|
||||
expect(formatShortDate(week.roast)).toBe('22.10');
|
||||
});
|
||||
|
||||
it('в четверг обжарка сегодня', () => {
|
||||
expect(getRoastWeek(new Date('2026-10-22T08:00:00Z')).roast.iso).toBe('2026-10-22');
|
||||
});
|
||||
|
||||
it('считает по Минску: ночь среды по UTC — уже четверг в Минске', () => {
|
||||
expect(getRoastWeek(new Date('2026-10-21T22:30:00Z')).roast.iso).toBe('2026-10-22');
|
||||
});
|
||||
|
||||
it('переход через месяц', () => {
|
||||
const week = getRoastWeek(new Date('2026-10-30T10:00:00Z'));
|
||||
expect(week.roast.iso).toBe('2026-11-05');
|
||||
expect(formatShortDate(week.roast)).toBe('05.11');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
// Неделя ближайшей обжарки по минскому времени: обжарка по четвергам, доставка в пятницу.
|
||||
// Сегодня четверг — обжарка сегодня, иначе следующий четверг.
|
||||
|
||||
export type WeekDay = {
|
||||
letter: string;
|
||||
/** число месяца */
|
||||
date: number;
|
||||
/** YYYY-MM-DD */
|
||||
iso: string;
|
||||
};
|
||||
|
||||
export type RoastWeek = {
|
||||
/** пн…вс недели обжарки */
|
||||
days: WeekDay[];
|
||||
roast: WeekDay;
|
||||
delivery: WeekDay;
|
||||
};
|
||||
|
||||
const LETTERS = ['ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ', 'ВС'] as const;
|
||||
const DAY_MS = 86_400_000;
|
||||
|
||||
/** Календарная дата в Минске как полночь UTC того же числа — удобно складывать дни. */
|
||||
function minskDay(now: Date): Date {
|
||||
const parts = new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: 'Europe/Minsk',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
}).formatToParts(now);
|
||||
const get = (type: string) => Number(parts.find((p) => p.type === type)?.value);
|
||||
return new Date(Date.UTC(get('year'), get('month') - 1, get('day')));
|
||||
}
|
||||
|
||||
function toDay(d: Date, index: number): WeekDay {
|
||||
return { letter: LETTERS[index] ?? '', date: d.getUTCDate(), iso: d.toISOString().slice(0, 10) };
|
||||
}
|
||||
|
||||
export function getRoastWeek(now: Date = new Date()): RoastWeek {
|
||||
const today = minskDay(now);
|
||||
const weekday = (today.getUTCDay() + 6) % 7; // пн = 0
|
||||
const untilThursday = (3 - weekday + 7) % 7;
|
||||
const roast = new Date(today.getTime() + untilThursday * DAY_MS);
|
||||
const monday = new Date(roast.getTime() - 3 * DAY_MS);
|
||||
const day = (i: number) => toDay(new Date(monday.getTime() + i * DAY_MS), i);
|
||||
return { days: LETTERS.map((_, i) => day(i)), roast: day(3), delivery: day(4) };
|
||||
}
|
||||
|
||||
/** «22.10» */
|
||||
export function formatShortDate(day: WeekDay): string {
|
||||
const [, month, date] = day.iso.split('-');
|
||||
return `${date}.${month}`;
|
||||
}
|
||||
Reference in New Issue
Block a user