export function localDayKey(d = new Date()) { return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` } export function localShichenIndex(d = new Date()) { return ((d.getHours() + 1) % 24) >> 1 } function nextShichenEnd(d = new Date()) { const shi = localShichenIndex(d) const startHour = (shi * 2 + 23) % 24 const start = new Date(d) start.setHours(startHour, 0, 0, 0) if (start.getTime() > d.getTime()) start.setDate(start.getDate() - 1) return new Date(start.getTime() + 2 * 3600 * 1000) } export function localFallbackTips() { const n = new Date() const day = Math.floor(Date.UTC(n.getFullYear(), n.getMonth(), n.getDate()) / 86400000) const shi = localShichenIndex(n) const seed = day * 13 + shi * 19 const palettes = [ [{ name: '米白', hex: '#F5F0E8' }, { name: '雾霾蓝', hex: '#A8C4D8' }], [{ name: '燕麦色', hex: '#E8DCC8' }, { name: '浅杏', hex: '#F0C9A8' }], [{ name: '浅灰', hex: '#D8D6D4' }, { name: '雾粉', hex: '#E8B8C4' }], [{ name: '天青', hex: '#9BB8D4' }, { name: '陶土', hex: '#C4A484' }], ] const clothing = [ '轻薄透气更舒服,外套可备一件薄衫应付温差。', '今日宜层搭:薄内搭+透气外衫,方便随时增减。', '选柔软面料贴身,活动一整天也不易紧绷。', '宽松剪裁更自在,适合慢节奏出门与散步。', ] const notes = ['清爽干净,不抢戏。', '今日偏清透,层次更轻。', '温柔耐看,适合日常。', '柔和提气色,不显沉。'] const well = [ '午后泡一杯温茶,给身心一点缓冲。', '今日做三次深呼吸,拉长呼气更易放松。', '今晚早点放下屏幕,让眼睛歇一会儿。', '走路时把肩膀放松,呼吸会顺很多。', ] const i = seed % palettes.length return { clothingIndex: 55 + (seed % 41), clothing: clothing[i], colorNote: notes[i], palette: palettes[i], wellness: well[i], source: 'fallback', needBirth: true, asOf: localDayKey(n), shichen: shi, validUntil: nextShichenEnd(n).toISOString(), } } export function tipsFromApi(t) { return { clothingIndex: t.clothing_index, clothing: t.clothing, colorNote: t.color_note, palette: (t.palette || []).map((p) => ({ name: p.name, hex: p.hex })), wellness: t.wellness, needBirth: !!t.need_birth, guaName: t.gua_name, source: t.source, asOf: t.as_of || localDayKey(), shichen: t.shichen, shichenName: t.shichen_name, validUntil: t.valid_until, } } export function ringOffset(index) { const c = 2 * Math.PI * 30 const pct = Math.min(100, Math.max(0, Number(index) || 0)) / 100 return c * (1 - pct) }