diff --git a/apps/site/e2e/auth-docs.spec.ts b/apps/site/e2e/auth-docs.spec.ts index c28202d..f5d67cf 100644 --- a/apps/site/e2e/auth-docs.spec.ts +++ b/apps/site/e2e/auth-docs.spec.ts @@ -40,7 +40,7 @@ test.describe("Auth and docs", () => { await page.goto("/docs"); await expect( - page.getByRole("heading", { name: "Guidance for creator teams" }), + page.getByRole("heading", { name: "Getting started" }), ).toBeVisible(); await expect( page.getByRole("heading", { name: "Authoring" }), @@ -49,7 +49,7 @@ test.describe("Auth and docs", () => { page.getByRole("heading", { name: "Operations" }), ).toBeVisible(); await expect( - page.getByRole("link", { name: "Open product docs" }), - ).toHaveAttribute("href", "/docs"); + page.getByRole("heading", { name: "Reference", exact: true }), + ).toBeVisible(); }); }); diff --git a/apps/site/e2e/home.spec.ts b/apps/site/e2e/home.spec.ts index e07c82f..9e99814 100644 --- a/apps/site/e2e/home.spec.ts +++ b/apps/site/e2e/home.spec.ts @@ -21,7 +21,7 @@ test.describe("Graspful site", () => { await page.goto("/"); await expect( - page.getByRole("link", { name: /start building free/i }).first(), + page.getByRole("link", { name: /create free account/i }).first(), ).toHaveAttribute("href", /sign-up/); }); diff --git a/apps/site/src/app/(marketing)/docs/page.tsx b/apps/site/src/app/(marketing)/docs/page.tsx index d6c0a31..b93fa2f 100644 --- a/apps/site/src/app/(marketing)/docs/page.tsx +++ b/apps/site/src/app/(marketing)/docs/page.tsx @@ -1,32 +1,91 @@ import Link from "next/link"; -import { PageShell } from "@/components/site/page-shell"; + +const sections = [ + { + title: "Authoring", + items: [ + "Define course structure with concepts, sections, and dependencies.", + "Review generated content before it reaches learners.", + "Publish changes without rebuilding your own delivery platform.", + ], + }, + { + title: "Operations", + items: [ + "Manage brands, API keys, and learner billing from the creator app.", + "Use one platform runtime for learner delivery and revenue operations.", + "Keep the flagship site separate from the white-label learner surfaces.", + ], + }, +]; + +const docLinks = [ + { title: "CLI Reference", href: "https://graspful.ai/docs/cli", description: "Install, authenticate, and run course commands from your terminal." }, + { title: "Course Schema", href: "https://graspful.ai/docs/course-schema", description: "YAML structure for courses, concepts, knowledge points, and problems." }, + { title: "Brand Schema", href: "https://graspful.ai/docs/brand-schema", description: "Configure your academy theme, domain, and landing page." }, + { title: "Glossary", href: "https://graspful.ai/docs/glossary", description: "Key terms: concepts, knowledge points, mastery, diagnostics, and more." }, +]; export default function DocsPage() { return ( - -
-

Authoring

- -
-
-

Operations

- - - Open product docs - -
-
+
+
+
+

+ Documentation +

+

+ Getting started +

+

+ Everything you need to build and publish adaptive courses with + Graspful. +

+
+
+
+
+
+ {sections.map((section) => ( +
+

+ {section.title} +

+
    + {section.items.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+ ))} +
+

+ Reference +

+
+ {docLinks.map((doc) => ( + +

+ {doc.title} → +

+

+ {doc.description} +

+ + ))} +
+
+
+
); } diff --git a/apps/site/src/app/(marketing)/how-graspful-works/page.tsx b/apps/site/src/app/(marketing)/how-graspful-works/page.tsx index 9b05d1f..4cb7218 100644 --- a/apps/site/src/app/(marketing)/how-graspful-works/page.tsx +++ b/apps/site/src/app/(marketing)/how-graspful-works/page.tsx @@ -1,39 +1,69 @@ -import { PageShell } from "@/components/site/page-shell"; +const steps = [ + { + number: "1", + title: "Find out what they already know", + description: + "Every course starts with a diagnostic. The platform figures out what the learner can prove they understand and skips the rest. No one sits through material they've already mastered.", + }, + { + number: "2", + title: "Teach what actually matters next", + description: + "The sequence changes based on gaps. If a learner is weak on prerequisites, the platform handles that first. If they're strong, it moves them forward. The path is different for everyone.", + }, + { + number: "3", + title: "Prove mastery before moving on", + description: + "Progress is gated on evidence. Learners solve problems that test real understanding. Clicking through slides doesn't count.", + }, + { + number: "4", + title: "Keep what you learned", + description: + "Spaced review brings knowledge back at the right time. It's built into the product, not a feature you have to turn on. Students retain what they learned instead of losing it.", + }, +]; export default function HowItWorksPage() { return ( - -
-

1. Find out what they already know

-

- Every course starts with a diagnostic. The platform figures out what - the learner can prove they understand and skips the rest. No one sits - through material they've already mastered. -

-

2. Teach what actually matters next

-

- The sequence changes based on gaps. If a learner is weak on - prerequisites, the platform handles that first. If they're strong, it - moves them forward. The path is different for everyone. -

-
-
-

3. Prove mastery before moving on

-

- Progress is gated on evidence. Learners solve problems that test real - understanding. Clicking through slides doesn't count. -

-

4. Keep what you learned

-

- Spaced review brings knowledge back at the right time. It's built into - the product, not a feature you have to turn on. Students retain what - they learned instead of losing it. -

-
-
+
+
+
+

+ How it works +

+

+ How Graspful works +

+

+ Diagnosis, mastery, and spaced review. Not a video player with a + progress bar. +

+
+
+
+
+
+ {steps.map((step) => ( +
+

+ Step {step.number} +

+

+ {step.title} +

+

+ {step.description} +

+
+ ))} +
+
+
+
); } diff --git a/apps/site/src/app/(marketing)/pricing/page.tsx b/apps/site/src/app/(marketing)/pricing/page.tsx index 3900180..450203f 100644 --- a/apps/site/src/app/(marketing)/pricing/page.tsx +++ b/apps/site/src/app/(marketing)/pricing/page.tsx @@ -10,7 +10,7 @@ export default function PricingPage() { >

What creators pay

-

+

No platform subscription. No setup fee. Graspful takes 30% of learner revenue and runs the infrastructure. You keep the rest.

@@ -29,19 +29,19 @@ export default function PricingPage() {

What learners pay

-

+

You set the price. Graspful handles checkout, access control, and subscription management through Stripe.

-
diff --git a/apps/site/src/components/site/home-page.tsx b/apps/site/src/components/site/home-page.tsx index dd2bc33..fefab18 100644 --- a/apps/site/src/components/site/home-page.tsx +++ b/apps/site/src/components/site/home-page.tsx @@ -1,6 +1,7 @@ "use client"; import Link from "next/link"; +import { useState } from "react"; import { faqItems } from "@/lib/site-config"; /* ─── Data ─── */ @@ -41,19 +42,22 @@ const features = [ const howSteps = [ { - title: "Describe What You Want to Teach", + step: "npx @graspful/cli init", + title: "Install the CLI", description: - "Tell us your subject and scope. Our AI builds the knowledge graph, diagnostics, and adaptive logic.", + "One command wires up MCP in Claude Code, Cursor, or Windsurf. Your AI agent gets course-building tools.", }, { - title: "Review and Customize", + step: '"Build me a course on X"', + title: "Prompt your agent", description: - "Refine the content, adjust difficulty, add your branding. Publish when it meets your standard.", + "Your agent calls scaffold, fill, validate, and review. You guide the content. It handles the structure.", }, { - title: "Publish and Earn", + step: "graspful import", + title: "Go live", description: - "Set your price, launch your academy. Students get adaptive learning. You get 70% of revenue.", + "Your academy launches on your domain with adaptive diagnostics, mastery gates, and spaced review from day one.", }, ]; @@ -66,6 +70,30 @@ const learnerBenefits = [ { title: "Built-In Reinforcement", desc: "Advanced topics naturally reinforce earlier material." }, ]; +const agentLogos = [ + { name: "Claude Code", icon: "claude" }, + { name: "Cursor", icon: "cursor" }, + { name: "Windsurf", icon: "windsurf" }, + { name: "Codex", icon: "codex" }, +]; + +function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = useState(false); + return ( + + ); +} + /* ─── Component ─── */ export function HomePage() { const headline = "Build courses where students actually"; @@ -105,17 +133,50 @@ export function HomePage() { className="animate-fade-up mx-auto mt-8 max-w-xl text-xl leading-relaxed text-muted-foreground md:text-2xl" style={{ animationDelay: "0.6s" }} > - Your expertise. AI's scaffolding. Every course gets adaptive - diagnostics, mastery tracking, and spaced review. Launch a live - product in minutes, not months. + Prompt AI to build your course. Adaptive diagnostics, mastery + gates, and spaced review - scaffolded for you. Go live in one + session.

-
+ + {/* CLI snippet */} +
+
+ $ + npx @graspful/cli init + +
+
+ +
- Start Building Free + Create Free Account + + See how it works → + +
+ + {/* Agent logo strip */} +
+

+ Works with any MCP-compatible agent +

+
+ {agentLogos.map((agent) => ( + + {agent.name} + + ))} +
@@ -156,9 +217,12 @@ export function HomePage() {
-

- How It Works +

+ Three commands. One session.

+

+ From empty repo to live academy with adaptive learning built in. +

@@ -172,6 +236,9 @@ export function HomePage() { > {i + 1}
+
+ {step.step} +

{step.title}

{step.description}

@@ -285,14 +352,22 @@ export function HomePage() { Free to create. You earn 70% when learners subscribe.

- Free to start. No credit card required. + No credit card required.

- - Start Building Free - +
+ + Create Free Account + + + Read the docs → + +
diff --git a/apps/site/src/lib/site-config.ts b/apps/site/src/lib/site-config.ts index 49349b0..c130fc6 100644 --- a/apps/site/src/lib/site-config.ts +++ b/apps/site/src/lib/site-config.ts @@ -73,9 +73,14 @@ export const platformPrinciples: PlatformPrinciple[] = [ export const faqItems = [ { - question: "Do I need to know how to code?", + question: "Do I need to know YAML?", answer: - "No. You describe what you want to teach, and Graspful's AI builds the course structure, questions, and adaptive logic. You review and customize.", + "No. Your AI agent writes the YAML for you. You prompt Claude Code or Cursor with what you want to teach, and the MCP tools handle scaffolding, filling, and validation. You review the output.", + }, + { + question: "What if I don't use Claude Code or Cursor?", + answer: + "Any MCP-compatible AI tool works. You can also use the raw CLI directly if you prefer.", }, { question: "How does the revenue share work?", @@ -97,6 +102,11 @@ export const faqItems = [ answer: "Those platforms host videos. Graspful actually teaches. Every course gets adaptive diagnostics, mastery gating, and spaced review built in. Students don't just watch — they prove they learned.", }, + { + question: "What's the 10-check quality gate?", + answer: + "Before publishing, every course runs through 10 automated checks: prerequisite graph integrity, question uniqueness, difficulty calibration, content coverage, and more. Your agent runs this with graspful review.", + }, { question: "What happens if I want to leave?", answer: @@ -106,13 +116,13 @@ export const faqItems = [ export const footerLinks = { product: [ - { title: "Agents", href: "/how-graspful-works" }, + { title: "How It Works", href: "/how-graspful-works" }, { title: "Pricing", href: "/pricing" }, { title: "Docs", href: "/docs" }, ], resources: [ { title: "Blog", href: "/blog" }, - { title: "CLI Reference", href: "/docs/cli" }, - { title: "Course Schema", href: "/docs/course-schema" }, + { title: "CLI Reference", href: "https://graspful.ai/docs/cli" }, + { title: "Course Schema", href: "https://graspful.ai/docs/course-schema" }, ], }; diff --git a/apps/web/src/app/(app)/browse/[courseId]/page.tsx b/apps/web/src/app/(app)/browse/[courseId]/page.tsx index 3fe6e28..57a30dd 100644 --- a/apps/web/src/app/(app)/browse/[courseId]/page.tsx +++ b/apps/web/src/app/(app)/browse/[courseId]/page.tsx @@ -100,7 +100,10 @@ export default async function CourseDetailPage({ masteryState: masteryMap.get(concept.id) ?? ("unstarted" as MasteryState), })); const courseUnlocked = !!profile && ( - profile.diagnosticCompleted || profile.completionPercent > 0 + profile.diagnosticCompleted || + profile.mastered > 0 || + profile.inProgress > 0 || + profile.needsReview > 0 ); const academyHref = graph.course.academyId ? getAcademyHref(graph.course.academyId) diff --git a/apps/web/src/app/(app)/dashboard/page.tsx b/apps/web/src/app/(app)/dashboard/page.tsx index b4f70f6..7b0e548 100644 --- a/apps/web/src/app/(app)/dashboard/page.tsx +++ b/apps/web/src/app/(app)/dashboard/page.tsx @@ -124,7 +124,7 @@ export default async function DashboardPage() { {courses.length > 0 && (() => { const firstCourse = courses[0]; const profile = profiles.get(firstCourse.id); - if (profile && profile.completionPercent > 0) { + if (profile && (profile.mastered > 0 || profile.inProgress > 0 || profile.needsReview > 0)) { return (
{ const [totalConcepts, masteredConcepts, enrollment] = await Promise.all([ this.prisma.concept.count({ - where: { course: { academyId } }, + where: activeConceptWhere({ course: { academyId } }), }), this.studentState.countMasteredConcepts(userId, { academyId }), this.prisma.academyEnrollment.findUnique({ @@ -82,7 +83,7 @@ export class CompletionEstimateService { async getEstimate(userId: string, courseId: string): Promise { const [totalConcepts, masteredConcepts, enrollment] = await Promise.all([ - this.prisma.concept.count({ where: { courseId } }), + this.prisma.concept.count({ where: activeConceptWhere({ courseId }) }), this.studentState.countMasteredConcepts(userId, { courseId }), this.prisma.courseEnrollment.findUnique({ where: { userId_courseId: { userId, courseId } }, diff --git a/backend/src/student-model/queries/student-state.queries.ts b/backend/src/student-model/queries/student-state.queries.ts index 2683e6c..502c73b 100644 --- a/backend/src/student-model/queries/student-state.queries.ts +++ b/backend/src/student-model/queries/student-state.queries.ts @@ -209,18 +209,18 @@ export async function countMasteredConcepts( userId: string, filter: { courseId?: string; academyId?: string }, ): Promise { - const conceptWhere: Record = {}; + const baseWhere: Record = {}; if (filter.courseId) { - conceptWhere.courseId = filter.courseId; + baseWhere.courseId = filter.courseId; } if (filter.academyId) { - conceptWhere.course = { academyId: filter.academyId }; + baseWhere.course = { academyId: filter.academyId }; } return prisma.studentConceptState.count({ where: { userId, - concept: conceptWhere, + concept: activeConceptWhere(baseWhere), masteryState: 'mastered', }, });