// Name: Сценарный план «Моё сердце — бабушка» // Description: Формирует сценарный план в виде Markdown и открывает его для правок и экспорта. // Author: AsiyaLyushanlo // GitHub: AsiyaLyushanlo import "@johnlindquist/kit" const storyboard = ` # Сценарный план видео «Моё сердце — бабушка» Общее настроение: теплое, ностальгическое, слегка зернистое (эффект старой плёнки), в золотистых и пастельных тонах. --- ## I. Вступление: Ощущение пустоты (0:00–0:30) - Кадр 1: Медленный наезд камеры на пустое кресло-качалку или стул у окна. На подлокотнике лежит аккуратно сложенный клетчатый плед. - Кадр 2: Крупный план: на столе стоит остывшая чашка чая и старые очки на раскрытой книге. - Кадр 3: Пылинки, танцующие в луче света, пробивающемся сквозь занавеску. _Смысл:_ Показать физическое отсутствие человека, о котором пойдёт речь. --- ## II. Куплет 1: Ожившие воспоминания (0:30–1:15) - Кадр 4: Переход через расфокус — те же предметы, но теперь «ожившие». Бабушка (лицо можно не показывать полностью, акцент на руки) поправляет плед. - Кадр 5: Руки бабушки перебирают старые фотографии или вяжут. - Кадр 6: Взаимодействие: маленькая детская ладошка ложится в морщинистую ладонь бабушки. Бабушка согревает её своим дыханием. _Смысл:_ Иллюстрация строк о «согретых ладонях» и «простых словах». --- ## III. Припев: Эмоциональный взрыв (1:15–1:50) - Кадр 7: Нарезка быстрых, светлых моментов: бабушка смеётся, обнимает внучку/внука на фоне цветущего сада (символ «вечной весны»). - Кадр 8: Лицо бабушки крупным планом, светящееся добротой. Камера фокусируется на морщинках вокруг глаз (символ «целого мира в морщинках»). - Кадр 9: Главный герой (взрослый) стоит на фоне красивого пейзажа или в той самой комнате, глядя в небо, как будто обращаясь к ней. --- ## IV. Куплет 2: Уроки жизни и грусть (1:50–2:35) - Кадр 10: Бабушка учит ребёнка чему-то простому: печь пироги, ухаживать за цветами или завязывать шнурки. - Кадр 11: Ребёнок падает, а бабушка помогает ему подняться и обнимает (иллюстрация строки «учила не бояться падать»). - Кадр 12: Кадр из настоящего времени: герой идёт по улице, вокруг суета, но он достаёт из кармана старую вещь (брошь, платок или фото) и улыбается сквозь слёзы. --- ## V. Кульминация и финал (2:35–конец) - Кадр 13: Самый мощный момент песни — кадры залитого светом поля или леса, символизирующие свободу и покой души. - Кадр 14: Возвращение в ту самую комнату из начала. Герой садится в это кресло, укрывается пледом и закрывает глаза. - Кадр 15: Финальный кадр: старое чёрно-белое фото бабушки в рамке, рядом — ваза с живыми весенними цветами. > Текст на экране (по желанию): «Той, кто научила меня любить...» --- ## Технические советы для монтажа - **Цветокоррекция:** Тёплые «солнечные» фильтры для воспоминаний; более холодные, спокойные тона — для настоящего времени. - **Синхронизация:** Самые важные кадры (объятия, улыбки) синхронизировать с ударами барабанов в припеве. - **Переходы:** Мягкие переходы (наплывы) для ощущения текучести памяти. --- ## Дополнительно (по желанию) - Добавить лёгкое зерно и виньетирование на сценах-воспоминаниях. - Использовать звук шагов, шелест страниц, дыхание — тихо, как атмосферные слои. - Тонкие шумы плёнки/ленты в моменты переходов. `.trim() await editor({ value: storyboard, scrollTo: 'top', shortcuts: [ { name: 'Сохранить как…', key: `${cmd}+s`, bar: 'right', onPress: async (input: string) => { let targetPath = await path({ hint: 'Укажите путь и имя .md файла (можно нового)', }) if (!targetPath.toLowerCase().endsWith('.md')) { targetPath = `${targetPath}.md` } await writeFile(targetPath, input) await revealFile(targetPath) await show() }, visible: true, }, { name: 'Скопировать всё', key: `${cmd}+shift+c`, bar: 'right', onPress: async (input: string) => { await copy(input) await toast('Скопировано в буфер обмена') }, visible: true, }, ], onEscape: async (input: string) => { submit(input) }, onAbandon: async (input: string) => { submit(input) }, })// Name: Focus Plan Generator // Description: Build a category-based focus plan, save it with a safe filename, optionally merge matching transcript drafts, and mirror the plan on a selected display. // Author: AsiyaLyushanlo // GitHub: AsiyaLyushanlo import "@johnlindquist/kit" // Persistent user prefs (save dir, last display) const cachePath = kenvPath("cache", "focus-plan.json") await ensureDir(path.dirname(cachePath)) type Cache = { saveDir?: string; lastDisplayName?: string } let prefs: Cache = {} try { if (await pathExists(cachePath)) { prefs = JSON.parse(await readFile(cachePath, "utf8")) } } catch { prefs = {} } const savePrefs = async () => writeFile(cachePath, JSON.stringify(prefs, null, 2)) // Categories and quick prompts (inspired by Example 1) const ALL = "All Categories" const topics = [ { name: "Deep Work", description: "Focused coding, writing, or design blocks", value: "Deep Work" }, { name: "Meetings", description: "Plan efficient agendas and outcomes", value: "Meetings" }, { name: "Learning", description: "Study notes and practice checkpoints", value: "Learning" }, { name: "Health", description: "Movement, breaks, hydration, rest", value: "Health" }, { name: "Household", description: "Light chores, declutter, maintenance", value: "Household" }, { name: ALL, description: "Combine tips from all categories", value: ALL }, ] const tipsMap: Record<string, string[]> = { "Deep Work": [ "Define a single, measurable goal for this session.", "Silence notifications and close unrelated tabs.", "Use a timer: 50 minutes focus + 10 minutes break.", "Keep a scratchpad for quick distractions; return after the session.", "Commit to a minimal viable outcome before polishing." ], "Meetings": [ "Write a 3-bullet agenda with expected decisions.", "Timebox each segment and assign a facilitator.", "Capture action items with owners and due dates.", "Invite only essential participants.", "End 5 minutes early to document outcomes." ], "Learning": [ "Pick a topic and define 2-3 learning questions.", "Practice with a small exercise or flashcards.", "Teach-back: summarize in your own words.", "Track one thing you learned and one you’ll practice next.", "Avoid passive bingeing—act after each section." ], "Health": [ "Drink a glass of water before you start.", "Stand and stretch for 2 minutes between tasks.", "Take a 10-minute walk at least once.", "Schedule a mindful breath break every hour.", "Plan a light, protein-focused snack." ], "Household": [ "Pick one micro-task (<10 min) to finish.", "Declutter a single surface or drawer.", "Group similar tasks and batch them.", "Prepare essentials for tomorrow.", "Put away items immediately after use." ], } // Safe filename (inspired by Example 2) const reservedWinRe = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/ function sanitizeToFilename(input: string, sep = "-") { const s = (input ?? "").trim() if (!s) return "untitled" let out = s.normalize("NFD").replace(/[\u0300-\u036f]/g, "") out = out.toLowerCase().replace(/\s+/g, sep) out = out.replace(new RegExp(`[^a-z0-9${escapeRegExp(sep)}]+`, "g"), "") out = out.replace(new RegExp(`${escapeRegExp(sep)}+`, "g"), sep).replace(new RegExp(`^${escapeRegExp(sep)}+|${escapeRegExp(sep)}+$`, "g"), "") if (!out) out = "untitled" if (reservedWinRe.test(out)) out = `_${out}` return out.slice(0, 200) } function escapeRegExp(str: string) { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") } // Pick category const selection = await arg<string>("Select a focus theme", topics) const chosenTips = selection === ALL ? Array.from(new Set(Object.values(tipsMap).flat())) : (tipsMap[selection] || []) // Build plan const today = formatDate(new Date(), "yyyy-MM-dd") const timeStr = formatDate(new Date(), "HH:mm") const title = `Focus Plan — ${selection} — ${today}` let contentMd = [ `# ${title}`, ``, `Start: ${timeStr}`, ``, `## Objectives`, `- Define success for this block`, `- Eliminate distractions`, ``, `## Quick Tips`, ...(chosenTips.length ? chosenTips.map(t => `- ${t}`) : ["- Add your own helpful nudges"]), ``, `## Tasks`, `- [ ] Task 1`, `- [ ] Task 2`, `- [ ] Task 3`, ``, `## Notes`, ``, ].join("\n") const base = sanitizeToFilename(`${selection}-${today}`) const fileName = `${base}.md` // Optional: show on a selected display (inspired by Example 5) let showOnDisplay = async () => { const disp = await selectDisplay(true) const bounds = disp?.bounds || disp await widget(md(contentMd), { x: bounds.x, y: bounds.y, width: Math.max(420, bounds.width * 0.42), height: Math.min(bounds.height, 640), alwaysOnTop: false, }) prefs.lastDisplayName = String(disp?.id ?? disp?.label ?? "display") await savePrefs() } // Merge transcripts into plan (inspired by Example 4) async function tryMergePair(dir: string, baseName: string) { try { const files = await readdir(dir) const transcript = files.find(f => f === `${baseName}-transcript.md`) const secondDraft = files.find(f => f === `${baseName}-second-draft.md`) if (!transcript || !secondDraft) return false const transcriptPath = path.join(dir, transcript) const secondDraftPath = path.join(dir, secondDraft) const transcriptContent = await readFile(transcriptPath, "utf8") const secondDraftContent = await readFile(secondDraftPath, "utf8") contentMd = [ contentMd.trim(), ``, `---`, ``, `## Attached Draft`, ``, secondDraftContent.trim(), ``, `---`, ``, `## Attached Transcript`, ``, transcriptContent.trim(), ``, ].join("\n") await setDiv(md(contentMd)) await toast("Attached matching transcript and second draft") return true } catch { return false } } // Save flow with cached directory (inspired by Example 3) async function resolveSaveDir() { if (prefs.saveDir && (await pathExists(prefs.saveDir))) return prefs.saveDir const dir = await path({ onlyDirs: true, hint: `Select a folder to store focus plans`, }) if (dir) { prefs.saveDir = dir await savePrefs() } return dir } await div( md(contentMd), [ { name: "Copy Plan", shortcut: `${cmd}+c`, onAction: async () => { await copy(contentMd) await toast("Copied plan to clipboard") }, }, { name: "Save…", shortcut: `${cmd}+s`, onAction: async () => { const dir = await resolveSaveDir() if (!dir) return // Optional pair merge before save await tryMergePair(dir, base) const out = path.join(dir, fileName) await writeFile(out, contentMd, "utf8") await revealFile(out) await toast(`Saved: ${fileName}`) }, }, { name: "Attach Draft Pair…", shortcut: `${cmd}+m`, onAction: async () => { const dir = await selectFolder("Pick a folder containing -transcript.md and -second-draft.md") if (!dir) return const attached = await tryMergePair(dir, base) if (!attached) await toast("No matching draft pair found") }, }, { name: "Show on Display", shortcut: `${cmd}+o`, onAction: async () => { await showOnDisplay() }, }, { name: "Close", shortcut: "esc", close: true, onAction: async () => submit(null), }, ] )