import type { GenPlan } from './openrouter'; // Offline heuristic fallback when no BYOK key: deterministic micro-breakdown // so timer/library/focus can be demoed without API. export function heuristicPlan(title: string, difficulty: 'easy' | 'medium' | 'hard'): GenPlan { const t = title.toLowerCase(); const isKitchen = /kitchen|clean|room| dishes|laundry|apartment/.test(t); const isFood = /cook|lunch|meal|egg|eat|dinner|breakfast/.test(t); const isWork = /report|review|q3|marketing|write|deck|email|analy/.test(t); if (isKitchen) { const steps = [ { emoji: '๐ŸŽง', text: 'Put on music, gloves, grab trash bag', minutes: 2 }, { emoji: '๐Ÿ—‘๏ธ', text: 'Tie trash, take to door, reliner bin', minutes: 3 }, { emoji: '๐Ÿ‘€', text: 'Perimeter walk: toss visible wrappers', minutes: 4 }, { emoji: 'โ˜•', text: 'Group mugs and glasses by sink', minutes: 3 }, { emoji: '๐Ÿฝ๏ธ', text: 'Stack plates and cutlery neatly', minutes: 3 }, { emoji: '๐Ÿงฝ', text: 'Scrape food, soak worst items', minutes: 4 }, { emoji: '๐Ÿซง', text: 'Wash glasses + cutlery to mat', minutes: 6 }, { emoji: '๐Ÿณ', text: 'Wash plates + big bowls to rack', minutes: 5 }, { emoji: 'โœจ', text: 'Spray + wipe counters and table', minutes: 4 }, { emoji: '๐Ÿ’ง', text: 'Gloves off, water, tap complete', minutes: 3 }, ]; const sliced = difficulty === 'easy' ? [steps[2], steps[6], steps[8], steps[9]] : difficulty === 'medium' ? steps.slice(1, 8) : steps; return { steps: sliced, totalMin: sliced.reduce((a, s) => a + s.minutes, 0) }; } if (isWork) { const steps = [ { emoji: '๐Ÿ“„', text: 'Open template, save file immediately', minutes: 3 }, { emoji: '๐Ÿ“Š', text: 'Export raw metrics to working folder', minutes: 10 }, { emoji: '๐ŸŽฏ', text: 'Extract top 3 metrics to notes', minutes: 12 }, { emoji: '๐Ÿ†', text: 'Draft 3 bullets: quarterly wins', minutes: 15 }, { emoji: '๐Ÿ”ง', text: 'Draft 2 bullets: fixes + next steps', minutes: 15 }, { emoji: '๐Ÿ“ˆ', text: 'Add clean charts to slides 2-3', minutes: 10 }, { emoji: '๐Ÿ“ค', text: 'Proof once, export PDF, draft email', minutes: 10 }, ]; const sliced = difficulty === 'hard' ? steps.flatMap((s) => s.minutes > 10 ? [ { ...s, text: s.text + ' โ€” part 1 (gather)', minutes: Math.ceil(s.minutes / 2) }, { ...s, text: s.text + ' โ€” part 2 (finish)', minutes: Math.floor(s.minutes / 2) }, ] : [s]) : difficulty === 'easy' ? [steps[0], { emoji: '๐Ÿงฑ', text: 'Draft all bullets in one pass', minutes: 20 }, steps[5], steps[6]] : steps; return { steps: sliced.slice(0, 12), totalMin: sliced.slice(0, 12).reduce((a, s) => a + s.minutes, 0) }; } if (isFood) { const steps = [ { emoji: '๐Ÿงน', text: 'Clear counter, set board + knife', minutes: 2 }, { emoji: '๐Ÿฅš', text: 'Get eggs, butter, bread, tomatoes', minutes: 2 }, { emoji: '๐Ÿณ', text: 'Crack + whisk eggs with salt', minutes: 2 }, { emoji: '๐Ÿ…', text: 'Halve tomatoes, set aside', minutes: 2 }, { emoji: '๐Ÿงˆ', text: 'Pan on medium-low + butter', minutes: 2 }, { emoji: '๐Ÿž', text: 'Start toast now', minutes: 1 }, { emoji: '๐Ÿ”ฅ', text: 'Pour eggs, fold gently till set', minutes: 4 }, { emoji: '๐Ÿฝ๏ธ', text: 'Plate eggs + toast + tomatoes', minutes: 2 }, { emoji: '๐Ÿšฐ', text: 'Burner off, pan to soak', minutes: 2 }, { emoji: '๐Ÿ˜Œ', text: 'Sit, water, eat calmly', minutes: 1 }, ]; const sliced = difficulty === 'easy' ? [steps[1], { emoji: '๐Ÿ‘จโ€๐Ÿณ', text: 'Cook everything in one go', minutes: 10 }, steps[7], steps[9]] : steps; return { steps: sliced, totalMin: sliced.reduce((a, s) => a + s.minutes, 0) }; } // generic const base = [ { emoji: '๐ŸŽฏ', text: `Set up space for: ${title.slice(0, 40)}`, minutes: 2 }, { emoji: '๐Ÿง ', text: 'Dump all sub-parts on paper quickly', minutes: 3 }, { emoji: 'โœ‚๏ธ', text: 'Do smallest visible piece first', minutes: 5 }, { emoji: '๐Ÿงฑ', text: 'Do main middle chunk, no polish', minutes: 10 }, { emoji: '๐Ÿงน', text: 'Tidy + finish last 10%', minutes: 5 }, { emoji: '๐Ÿ’ง', text: 'Water, breathe, mark complete', minutes: 2 }, ]; const steps = difficulty === 'easy' ? [base[0], base[3], base[5]] : difficulty === 'medium' ? base.slice(0, 5).concat(base[5]) : [ { emoji: '๐ŸŽง', text: 'Starter: music + clear 1 surface', minutes: 2 }, ...base.slice(1, 5).flatMap((s) => [{ ...s, minutes: Math.max(2, Math.ceil(s.minutes / 2)) }]), base[5], ]; return { steps, totalMin: steps.reduce((a, s) => a + s.minutes, 0) }; } export function speak(text: string, enabled: boolean) { try { if (!enabled || !('speechSynthesis' in window)) return; window.speechSynthesis.cancel(); const u = new SpeechSynthesisUtterance(text); u.rate = 1.02; window.speechSynthesis.speak(u); } catch { /* noop */ } } export function beep() { try { const Ctx = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext; const ctx = new Ctx(); const o = ctx.createOscillator(); const g = ctx.createGain(); o.connect(g); g.connect(ctx.destination); o.frequency.value = 880; g.gain.value = 0.08; o.start(); o.stop(ctx.currentTime + 0.25); setTimeout(() => ctx.close(), 400); } catch { /* noop */ } }