From aa80e19d1da0c25fd27cade63d112c6df202923d Mon Sep 17 00:00:00 2001 From: brawliscool Date: Mon, 23 Feb 2026 18:51:25 -0600 Subject: [PATCH 1/2] Add files via upload --- FEATURES_RANKED.md | 132 ++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/FEATURES_RANKED.md b/FEATURES_RANKED.md index e345751..ad97f2e 100644 --- a/FEATURES_RANKED.md +++ b/FEATURES_RANKED.md @@ -1,66 +1,66 @@ -# OpenStep Feature Backlog (Easiest -> Hardest) - -This list ranks features by expected implementation effort for your current web app, from quickest wins to more advanced builds. - -1. **Waitlist + email capture on homepage** - Simple form + submit handler. -2. **Empty-state improvements** - Better placeholder text/visuals for no solves yet. -3. **Toast notifications** - Lightweight success/error messages for actions. -4. **Skeleton loading UI** - Loading placeholders while AI solve is pending. -5. **Drag-and-drop upload interaction** - Frontend upload UX polish. -6. **Photo crop/rotate before upload** - Basic image editing controls. -7. **Image quality warnings** - Detect blurry/cut-off photos before send. -8. **Re-solve button** - Retry failed/low-confidence solves. -9. **Saved favorites/bookmarks** - Mark and unmark important solves. -10. **Folders/tags for solves** - Organize solves by topic and date. -11. **Recent solves history with search/filter** - Better dashboard retrieval. -12. **Export solve to PDF/notes** - One-click study export. -13. **Keyboard shortcuts** - Fast actions like upload/search. -14. **Free tier + credit counter UI** - Show remaining solves. -15. **Light mode toggle** - Theme switch while keeping dark default. -16. **In-app onboarding checklist** - First-time user guidance. -17. **Share solve link (read-only)** - Generate public/private share URLs. -18. **Delete solves + auto-expire option** - Basic privacy controls. -19. **Usage analytics dashboard (basic)** - Track top metrics and usage. -20. **Subscription plans + billing pages** - Starter/Pro/School SaaS setup. -21. **Referral rewards system** - Credits for inviting users. -22. **Cloud sync across devices** - Persist and sync user data. -23. **Progress tracking + streaks** - Learning consistency stats. -24. **Confidence score per answer** - Show model certainty on outputs. -25. **Subject detection (math/science/etc.)** - Auto-classify problem type. -26. **Step-by-step mode vs final answer mode** - Output format controls. -27. **"Explain like I'm 12" toggle** - Simplified explanation pass. -28. **Hint mode first, full solution second** - Learning-first flow. -29. **Follow-up Q&A chat per solve** - Ask questions on each solution. -30. **Mistake checker vs student attempt** - Compare user work to AI steps. -31. **Alternative solving methods** - Multiple valid approaches. -32. **Practice generator (similar questions)** - Generate custom practice sets. -33. **Mini concept cards ("why this works")** - Contextual teaching snippets. -34. **Academic integrity mode** - Controlled answer reveal policies. -35. **Source/reference display for factual subjects** - Citations and references. -36. **Multi-question detection from one worksheet** - Segment and solve many items. -37. **Human review queue for premium** - Manual quality layer for edge cases. -38. **Teacher mode with class dashboard** - Multi-user classroom management. -39. **Assignment workspace for group study** - Shared collaboration flow. -40. **Commenting on steps** - Contextual discussion around each solution. -41. **LMS integrations (Google Classroom/Canvas)** - External platform integration. -42. **Parent/student reports** - Cross-role progress and activity reports. -43. **Team dashboard + API access for learning platforms** - Enterprise capability. -44. **Fast OCR + advanced reasoning pipeline at scale** - Production-grade AI infra. -45. **Secure cloud processing with compliance controls** - Advanced security and governance. - ---- - -## Suggested MVP (First 10 to build) - -If you want to move fast, start here: - -1. Waitlist + email capture -2. Drag-and-drop upload -3. Photo crop/rotate -4. Skeleton loading UI -5. Toast notifications -6. Recent solves history -7. Folders/tags -8. Confidence score -9. Step-by-step mode -10. Hint mode first +# OpenStep Feature Backlog (Easiest -> Hardest) + +This list ranks features by expected implementation effort for your current web app, from quickest wins to more advanced builds. + +1. **Waitlist + email capture on homepage** - Simple form + submit handler. +2. **Empty-state improvements** - Better placeholder text/visuals for no solves yet. +3. **Toast notifications** - Lightweight success/error messages for actions. +4. **Skeleton loading UI** - Loading placeholders while AI solve is pending. +5. **Drag-and-drop upload interaction** - Frontend upload UX polish. +6. **Photo crop/rotate before upload** - Basic image editing controls. +7. **Image quality warnings** - Detect blurry/cut-off photos before send. +8. **Re-solve button** - Retry failed/low-confidence solves. +9. **Saved favorites/bookmarks** - Mark and unmark important solves. +10. **Folders/tags for solves** - Organize solves by topic and date. +11. **Recent solves history with search/filter** - Better dashboard retrieval. +12. **Export solve to PDF/notes** - One-click study export. +13. **Keyboard shortcuts** - Fast actions like upload/search. +14. **Free tier + credit counter UI** - Show remaining solves. +15. **Light mode toggle** - Theme switch while keeping dark default. +16. **In-app onboarding checklist** - First-time user guidance. +17. **Share solve link (read-only)** - Generate public/private share URLs. +18. **Delete solves + auto-expire option** - Basic privacy controls. +19. **Usage analytics dashboard (basic)** - Track top metrics and usage. +20. **Subscription plans + billing pages** - Starter/Pro/School SaaS setup. +21. **Referral rewards system** - Credits for inviting users. +22. **Cloud sync across devices** - Persist and sync user data. +23. **Progress tracking + streaks** - Learning consistency stats. +24. **Confidence score per answer** - Show model certainty on outputs. +25. **Subject detection (math/science/etc.)** - Auto-classify problem type. +26. **Step-by-step mode vs final answer mode** - Output format controls. +27. **"Explain like I'm 12" toggle** - Simplified explanation pass. +28. **Hint mode first, full solution second** - Learning-first flow. +29. **Follow-up Q&A chat per solve** - Ask questions on each solution. +30. **Mistake checker vs student attempt** - Compare user work to AI steps. +31. **Alternative solving methods** - Multiple valid approaches. +32. **Practice generator (similar questions)** - Generate custom practice sets. +33. **Mini concept cards ("why this works")** - Contextual teaching snippets. +34. **Academic integrity mode** - Controlled answer reveal policies. +35. **Source/reference display for factual subjects** - Citations and references. +36. **Multi-question detection from one worksheet** - Segment and solve many items. +37. **Human review queue for premium** - Manual quality layer for edge cases. +38. **Teacher mode with class dashboard** - Multi-user classroom management. +39. **Assignment workspace for group study** - Shared collaboration flow. +40. **Commenting on steps** - Contextual discussion around each solution. +41. **LMS integrations (Google Classroom/Canvas)** - External platform integration. +42. **Parent/student reports** - Cross-role progress and activity reports. +43. **Team dashboard + API access for learning platforms** - Enterprise capability. +44. **Fast OCR + advanced reasoning pipeline at scale** - Production-grade AI infra. +45. **Secure cloud processing with compliance controls** - Advanced security and governance. + +--- + +## Suggested MVP (First 10 to build) + +If you want to move fast, start here: + +1. Waitlist + email capture +2. Drag-and-drop upload +3. Photo crop/rotate +4. Skeleton loading UI +5. Toast notifications +6. Recent solves history +7. Folders/tags +8. Confidence score +9. Step-by-step mode +10. Hint mode first From 7d4f86430712271dc9b27ac072b0938f99371189 Mon Sep 17 00:00:00 2001 From: brawliscool Date: Mon, 23 Feb 2026 18:56:44 -0600 Subject: [PATCH 2/2] refactor script structure for clearer feature organization --- script.js | 234 +++++++++++++++++++++++++++++------------------------- 1 file changed, 128 insertions(+), 106 deletions(-) diff --git a/script.js b/script.js index 975d5b6..ccd089d 100644 --- a/script.js +++ b/script.js @@ -63,6 +63,17 @@ document.addEventListener("DOMContentLoaded", () => { return; } + const setElementVisibility = (node, shouldShow, visibleClass = "flex") => { + if (!node) return; + if (shouldShow) { + node.classList.remove("hidden"); + node.classList.add(visibleClass); + return; + } + node.classList.remove(visibleClass); + node.classList.add("hidden"); + }; + const getCycleKey = (date = new Date()) => { const month = String(date.getMonth() + 1).padStart(2, "0"); return `${date.getFullYear()}-${month}`; @@ -122,6 +133,16 @@ document.addEventListener("DOMContentLoaded", () => { let solveTimeoutId = null; let solveStatusIntervalId = null; + const clearSolveTimeout = () => { + if (!solveTimeoutId) return; + clearTimeout(solveTimeoutId); + solveTimeoutId = null; + }; + + const updateUpgradeModalVisibility = (shouldOpen) => { + setElementVisibility(el.upgradeModal, shouldOpen, "flex"); + }; + const showToast = (message, isError = false) => { if (!el.toast) return; el.toast.textContent = message; @@ -274,35 +295,112 @@ document.addEventListener("DOMContentLoaded", () => { reader.readAsDataURL(file); }; - el.dropzone.addEventListener("click", () => { - el.fileInput.value = ""; - }); + const bindDropzoneEvents = () => { + ["dragenter", "dragover"].forEach((eventName) => { + el.dropzone.addEventListener(eventName, (event) => { + event.preventDefault(); + event.stopPropagation(); + el.dropzone.classList.add("dropzone-active"); + }); + }); - el.fileInput.addEventListener("change", async (event) => { - const file = event.target.files?.[0]; - await handleFile(file); - }); + ["dragleave", "drop"].forEach((eventName) => { + el.dropzone.addEventListener(eventName, (event) => { + event.preventDefault(); + event.stopPropagation(); + el.dropzone.classList.remove("dropzone-active"); + }); + }); - ["dragenter", "dragover"].forEach((eventName) => { - el.dropzone.addEventListener(eventName, (event) => { - event.preventDefault(); - event.stopPropagation(); - el.dropzone.classList.add("dropzone-active"); + el.dropzone.addEventListener("drop", async (event) => { + const file = event.dataTransfer?.files?.[0]; + await handleFile(file); }); + }; + + const getDemoSolution = () => ({ + problem: "Solve for x: 3x + 7 = 28", + steps: ["Subtract 7 from both sides: 3x = 21", "Divide both sides by 3: x = 7"], }); - ["dragleave", "drop"].forEach((eventName) => { - el.dropzone.addEventListener(eventName, (event) => { - event.preventDefault(); - event.stopPropagation(); - el.dropzone.classList.remove("dropzone-active"); - }); + const requireSolutionForExport = () => { + if (state.latestSolutionText) return true; + showToast("No solution to export yet.", true); + return false; + }; + + const triggerTextDownload = (content, fileName) => { + const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(url); + }; + + const solveCurrentImage = () => { + if (!state.currentImageDataUrl) { + showToast("Upload a homework photo first.", true); + return; + } + if (state.credits <= 0) { + showToast("No credits left. Upgrade your plan.", true); + return; + } + + state.credits -= 1; + updateCredits(); + const solveDurationMs = PLAN_CONFIG[state.plan].solveDurationMs; + setSolutionLoading(solveDurationMs); + + clearSolveTimeout(); + solveTimeoutId = setTimeout(() => { + setSolutionResult(getDemoSolution()); + showToast("Solution generated."); + }, solveDurationMs); + }; + + const closeUpgradeModal = () => updateUpgradeModalVisibility(false); + + const activatePlan = (plan) => { + if (!Object.prototype.hasOwnProperty.call(PLAN_CONFIG, plan)) return; + state.plan = plan; + state.credits = Math.max(state.credits, PLAN_CONFIG[plan].monthlyCredits); + updateCredits(); + }; + + const ensureStripeLinkConfigured = (plan) => { + const checkoutUrl = STRIPE_CHECKOUT_URLS[plan] || ""; + return checkoutUrl.startsWith("https://") && !checkoutUrl.includes("REPLACE_WITH_"); + }; + + const handleStripeCheckout = (plan) => { + if (!ensureStripeLinkConfigured(plan)) { + showToast(`Set your ${PLAN_CONFIG[plan].name} Stripe link in script.js first.`, true); + return; + } + window.location.href = STRIPE_CHECKOUT_URLS[plan]; + }; + + const consumeQueryParam = (params, key, expectedValue, onMatch) => { + const value = params.get(key); + if (value !== expectedValue) return; + onMatch(value); + params.delete(key); + }; + + el.dropzone.addEventListener("click", () => { + el.fileInput.value = ""; }); - el.dropzone.addEventListener("drop", async (event) => { - const file = event.dataTransfer?.files?.[0]; + el.fileInput.addEventListener("change", async (event) => { + const file = event.target.files?.[0]; await handleFile(file); }); + bindDropzoneEvents(); if (el.cropBtn) { el.cropBtn.addEventListener("click", async (event) => { @@ -323,83 +421,24 @@ document.addEventListener("DOMContentLoaded", () => { }); } - el.solveBtn.addEventListener("click", () => { - if (!state.currentImageDataUrl) { - showToast("Upload a homework photo first.", true); - return; - } - if (state.credits <= 0) { - showToast("No credits left. Upgrade your plan.", true); - return; - } - - state.credits -= 1; - updateCredits(); - const solveDurationMs = PLAN_CONFIG[state.plan].solveDurationMs; - setSolutionLoading(solveDurationMs); - - if (solveTimeoutId) clearTimeout(solveTimeoutId); - solveTimeoutId = setTimeout(() => { - const solution = { - problem: "Solve for x: 3x + 7 = 28", - steps: ["Subtract 7 from both sides: 3x = 21", "Divide both sides by 3: x = 7"], - }; - setSolutionResult(solution); - showToast("Solution generated."); - }, solveDurationMs); - }); + el.solveBtn.addEventListener("click", solveCurrentImage); if (el.exportNotesBtn) { el.exportNotesBtn.addEventListener("click", () => { - if (!state.latestSolutionText) { - showToast("No solution to export yet.", true); - return; - } - const blob = new Blob([state.latestSolutionText], { type: "text/plain;charset=utf-8" }); - const url = URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = "openstep-solution-notes.txt"; - document.body.appendChild(link); - link.click(); - link.remove(); - URL.revokeObjectURL(url); + if (!requireSolutionForExport()) return; + triggerTextDownload(state.latestSolutionText, "openstep-solution-notes.txt"); showToast("Notes exported."); }); } if (el.exportPdfBtn) { el.exportPdfBtn.addEventListener("click", () => { - if (!state.latestSolutionText) { - showToast("No solution to export yet.", true); - return; - } + if (!requireSolutionForExport()) return; window.print(); }); } - const openUpgradeModal = () => { - if (!el.upgradeModal) return; - el.upgradeModal.classList.remove("hidden"); - el.upgradeModal.classList.add("flex"); - }; - const closeUpgradeModal = () => { - if (!el.upgradeModal) return; - el.upgradeModal.classList.remove("flex"); - el.upgradeModal.classList.add("hidden"); - }; - const activatePlan = (plan) => { - if (!Object.prototype.hasOwnProperty.call(PLAN_CONFIG, plan)) return; - state.plan = plan; - state.credits = Math.max(state.credits, PLAN_CONFIG[plan].monthlyCredits); - updateCredits(); - }; - const ensureStripeLinkConfigured = (plan) => { - const checkoutUrl = STRIPE_CHECKOUT_URLS[plan] || ""; - return checkoutUrl.startsWith("https://") && !checkoutUrl.includes("REPLACE_WITH_"); - }; - - if (el.upgradeBtn) el.upgradeBtn.addEventListener("click", openUpgradeModal); + if (el.upgradeBtn) el.upgradeBtn.addEventListener("click", () => updateUpgradeModalVisibility(true)); if (el.closeUpgradeModalBtn) el.closeUpgradeModalBtn.addEventListener("click", closeUpgradeModal); if (el.upgradeModal) { el.upgradeModal.addEventListener("click", (event) => { @@ -408,23 +447,11 @@ document.addEventListener("DOMContentLoaded", () => { } if (el.stripeCheckoutPlusBtn) { - el.stripeCheckoutPlusBtn.addEventListener("click", () => { - if (!ensureStripeLinkConfigured("plus")) { - showToast("Set your Plus Stripe link in script.js first.", true); - return; - } - window.location.href = STRIPE_CHECKOUT_URLS.plus; - }); + el.stripeCheckoutPlusBtn.addEventListener("click", () => handleStripeCheckout("plus")); } if (el.stripeCheckoutProBtn) { - el.stripeCheckoutProBtn.addEventListener("click", () => { - if (!ensureStripeLinkConfigured("pro")) { - showToast("Set your Pro Stripe link in script.js first.", true); - return; - } - window.location.href = STRIPE_CHECKOUT_URLS.pro; - }); + el.stripeCheckoutProBtn.addEventListener("click", () => handleStripeCheckout("pro")); } const params = new URLSearchParams(window.location.search); @@ -434,15 +461,10 @@ document.addEventListener("DOMContentLoaded", () => { activatePlan(resolvedPlan); showToast(`${PLAN_CONFIG[resolvedPlan].name} plan activated.`); params.delete("upgraded"); - const newQuery = params.toString(); - window.history.replaceState({}, "", `${window.location.pathname}${newQuery ? `?${newQuery}` : ""}`); - } - if (params.get("canceled") === "1") { - showToast("Checkout canceled."); - params.delete("canceled"); - const newQuery = params.toString(); - window.history.replaceState({}, "", `${window.location.pathname}${newQuery ? `?${newQuery}` : ""}`); } + consumeQueryParam(params, "canceled", "1", () => showToast("Checkout canceled.")); + const newQuery = params.toString(); + window.history.replaceState({}, "", `${window.location.pathname}${newQuery ? `?${newQuery}` : ""}`); updateCredits(); });