Seed Starting Companion

A friendly seasonal planner that tells you what to do next, and makes you want to come back.

Season mood
Getting ready

Your plan, in three easy steps

You bring your frost dates and a few crops. This tool brings the timeline and the gentle nudges.

  • Step 1, Your dates
  • Step 2, Pick crops
  • Step 3, Get your timeline
Tip: If you only know one date, start with your average last spring frost date.



This anchors your indoor start and transplant timing.


This helps the seasonal progress view feel real.



Your crop picks and timeline

Pick a few crops, then this becomes your personalized seed starting calendar.

Crop Start indoors Transplant Direct sow Estimated harvest
Pick crops and build your timeline.

(function () { const statusEl = document.getElementById("liveStatus"); const timerEl = document.getElementById("countdownTimer"); const widgetEl = document.getElementById("liveWidget"); if (!statusEl || !timerEl || !widgetEl) return; const MST_OFFSET_MINUTES = -7 * 60; function getMstDateParts(date = new Date()) { const utcTime = date.getTime() + (date.getTimezoneOffset() * 60000); const mstTime = new Date(utcTime + (MST_OFFSET_MINUTES * 60000)); return { year: mstTime.getUTCFullYear(), month: mstTime.getUTCMonth(), day: mstTime.getUTCDate(), weekday: mstTime.getUTCDay(), hour: mstTime.getUTCHours(), minute: mstTime.getUTCMinutes(), second: mstTime.getUTCSeconds() }; } function createUtcFromMst(year, month, day, hour = 0, minute = 0, second = 0) { return new Date(Date.UTC(year, month, day, hour + 7, minute, second)); } function getNextSaturdayStartUtc(now = new Date()) { const mst = getMstDateParts(now); const todayStartUtc = createUtcFromMst(mst.year, mst.month, mst.day, 0, 0, 0); const daysUntilSaturday = (6 - mst.weekday + 7) % 7; let targetDayUtc = new Date(todayStartUtc.getTime() + (daysUntilSaturday * 86400000)); let targetStartUtc = createUtcFromMst( targetDayUtc.getUTCFullYear(), targetDayUtc.getUTCMonth(), targetDayUtc.getUTCDate(), 7, 0, 0 ); const isSaturday = mst.weekday === 6; const isAfterLiveWindow = isSaturday && mst.hour >= 9; const isBeforeStartToday = isSaturday && mst.hour < 7; if (daysUntilSaturday === 0 && !isBeforeStartToday && isAfterLiveWindow) { targetDayUtc = new Date(targetDayUtc.getTime() + (7 * 86400000)); targetStartUtc = createUtcFromMst( targetDayUtc.getUTCFullYear(), targetDayUtc.getUTCMonth(), targetDayUtc.getUTCDate(), 7, 0, 0 ); } return targetStartUtc; } function isLiveWindow(now = new Date()) { const mst = getMstDateParts(now); return mst.weekday === 6 && mst.hour >= 7 && mst.hour < 9; } function formatCountdown(ms) { const totalSeconds = Math.max(0, Math.floor(ms / 1000)); const days = Math.floor(totalSeconds / 86400); const hours = Math.floor((totalSeconds % 86400) / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); if (days > 0) { return `${days}d ${String(hours).padStart(2, "0")}h ${String(minutes).padStart(2, "0")}m`; } return `${String(hours).padStart(2, "0")}h ${String(minutes).padStart(2, "0")}m`; } function updateCountdown() { const now = new Date(); if (isLiveWindow(now)) { statusEl.textContent = ""; timerEl.textContent = "LIVE NOW"; widgetEl.classList.add("live-active"); return; } widgetEl.classList.remove("live-active"); statusEl.textContent = "Next live in"; const nextStartUtc = getNextSaturdayStartUtc(now); const diff = nextStartUtc.getTime() - now.getTime(); timerEl.textContent = formatCountdown(diff); } updateCountdown(); setInterval(updateCountdown, 1000); })();