feat: HLS streaming, transcripts, agent course generation & landing-page course previews (#663)

New features:
- HLS video encoding/streaming (hls routes, hls-encoder) and a media
  transcripts pipeline (transcripts route + media-transcript queries)
- AI assistant chat vs. run model: agent-course-generation worker/queue,
  run-monitor pane, chat-changes card, content ask-ai bar
- Per-theme course-page previews across org landing-page themes (bold,
  classic, corporate, editorial, etc.) with a shared course token/component set
- View-as-student mode and login-link / link-invite invite flows

Refactor:
- Extract packages/core consolidating config (env, storage, dashboard-url),
  services and utils previously duplicated under apps/api/src/config
- Remove legacy agent service modules (chat-tools, chat-context,
  context-window, document, fetch-url, lesson-content)
- Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding
- Add format script to tenant-router so turbo run format covers it
This commit is contained in:
Rotimi Best
2026-06-07 18:43:05 +01:00
committed by GitHub
parent a2e70779a0
commit d5b07d356e
496 changed files with 19050 additions and 8312 deletions
@@ -23,10 +23,10 @@
];
const agents = [
{ name: 'Cursor', logo: '/cursor-logo.png' },
{ name: 'Claude Code', logo: '/claude-logo.svg' },
{ name: 'Codex', logo: '/codex-logo.png' },
{ name: 'OpenCode', logo: '/opencode-logo.svg' }
{ name: 'Cursor', logo: 'https://assets.cdn.clsrio.com/www/home/cursor-logo.png' },
{ name: 'Claude Code', logo: 'https://assets.cdn.clsrio.com/www/home/claude-logo.svg' },
{ name: 'Codex', logo: 'https://assets.cdn.clsrio.com/www/home/codex-logo.png' },
{ name: 'OpenCode', logo: 'https://assets.cdn.clsrio.com/www/home/opencode-logo.svg' }
];
</script>
@@ -47,7 +47,7 @@
<div class="mx-auto mt-10 w-full max-w-[1100px]">
<div class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-200">
<img
src="/ai-course-builder.jpeg"
src="https://assets.cdn.clsrio.com/www/home/ai-course-builder.jpeg"
alt="Course builder — plan, approve, implement. Templates, in-editor chat, and MCP."
class="block h-auto w-full"
loading="lazy"
@@ -152,7 +152,7 @@
<div class="mx-auto mt-10 w-full max-w-[960px] overflow-hidden rounded-lg">
<div style="padding:{videoAspect} 0 0 0;position:relative;">
<iframe
src="https://player.vimeo.com/video/{vimeoVideoId}?badge=0&autopause=0&player_id=0&app_id=58479&autoplay=1&muted=1&loop=1"
src="https://player.vimeo.com/video/{vimeoVideoId}?badge=0&autopause=0&player_id=0&app_id=58479&loop=1"
frameborder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share"
referrerpolicy="strict-origin-when-cross-origin"
@@ -145,6 +145,12 @@
>Docs vs Company Academy</a
>
</li>
<li>
<a
href="/public-courses"
class="ui:text-muted-foreground ui:hover:text-foreground text-sm transition-colors">Public Courses</a
>
</li>
<li>
<a href="/teach" class="ui:text-muted-foreground ui:hover:text-foreground text-sm transition-colors"
>Teach with us</a
@@ -177,7 +183,7 @@
<ul class="space-y-3">
<li>
<a
href="https://feedback.classroomio.com"
href="https://feedback.classroomio.com/roadmap"
target="_blank"
rel="noopener"
class="ui:text-muted-foreground ui:hover:text-foreground text-sm transition-colors">Roadmap</a
+1 -1
View File
@@ -109,7 +109,7 @@
<div class="relative z-30 mx-auto mt-14 w-full max-w-[1100px]">
<div class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-200">
<img
src="/hero-academy.jpeg"
src="https://assets.cdn.clsrio.com/www/home/hero-academy.jpeg"
alt="Branded customer academy on a custom domain — catalog, programs, AI tutor"
width="2560"
height="1440"
-1
View File
@@ -27,7 +27,6 @@ export { default as ImagePlaceholder } from './image-placeholder.svelte';
export { default as LanguageReel } from './language-reel.svelte';
export { default as LearnerSection } from './learner-section.svelte';
export { default as Modal } from './modal.svelte';
export { default as MoreFeatures } from './more-features.svelte';
export { default as McpServerSection } from './mcp-server-section.svelte';
export { default as MultiAcademySection } from './multi-academy-section.svelte';
export { default as Navigation } from './navigation.svelte';
@@ -1,104 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import { fly } from 'svelte/transition';
import { folder, globe, team, transcription } from '$lib/emojis';
import { tools } from '$lib/utils/constants/tools';
let selected = $state(0);
let interval: NodeJS.Timer = $state();
const moreFeatures = [
{
icon: globe,
title: 'Customizable Landing Page',
description: 'A minimalistic landing page template out of the box to share your courses with the world',
image: '/landing-page-builder.webp'
},
{
icon: team,
title: 'Team management',
description: 'Extend invitations and manage your teaching institution seamlessly through ClassroomIO.',
image: '/team-management.png'
},
// {
// icon: transcription,
// title: 'Auto Video Transcription',
// description:
// 'AI automatically transcribes videos, making content searchable for seamless student access.',
// image: '/video-transcription.png'
// },
{
icon: folder,
title: 'Downloadable Lesson PDF',
description: 'Students can download lessons automatically for offline access.',
image: '/download-pdf.png'
}
];
const freeTools = tools.filter((item) => item.showFeature);
onMount(() => {
if (window.innerWidth > 768) {
interval = setInterval(() => {
selected = moreFeatures[selected + 1] ? selected + 1 : 0;
}, 3000);
}
});
</script>
<!-- More Features -->
<section id="morefeatures" class="bg-gray-50 px-3 py-[10%] lg:px-0">
<div class="mx-0 lg:mx-[12%]">
<div class="mx-4 mb-14 w-full lg:mx-0 lg:w-4/5">
<h2 class="mt-2 text-3xl tracking-tight">There is More...</h2>
<p class="mt-4 w-[94%] text-lg text-gray-500 lg:w-4/5">
ClassroomIO is packed with useful features while we try to make it easy to use.
</p>
</div>
<div class="flex flex-col-reverse gap-3 md:gap-10 lg:flex-row" id="image-container">
<div class="mt-5 flex w-[95%] overflow-auto p-3 lg:mt-0 lg:block lg:w-[45%] lg:overflow-hidden">
{#each moreFeatures as moreFeature, i}
<button
class="rounded-md p-4 {i === selected &&
'selected'} mb-3 min-w-[75vw] cursor-pointer text-start lg:min-w-[unset]"
onclick={() => {
selected = i;
clearInterval(interval);
}}
>
<div class="flex items-center gap-1">
<img loading="lazy" width="32" height="32" src={moreFeature.icon} alt="" class="w-8" />
<p class="text-lg">
{moreFeature.title}
</p>
</div>
<p class="text-sm">{moreFeature.description}</p>
</button>
{/each}
</div>
<!-- Video -->
<div class="h-full w-full p-3 lg:w-2/3 lg:p-0">
{#key selected}
<img
width="100%"
height="100%"
loading="lazy"
id="image"
in:fly={{ x: 100, duration: 800 }}
class="flex w-full flex-col items-center"
src={moreFeatures[selected].image}
alt={moreFeatures[selected].title}
/>
{/key}
</div>
</div>
</div>
</section>
<style>
#morefeatures .selected {
background-image: linear-gradient(45deg, rgb(253, 239, 132), rgb(247, 198, 169), rgb(21, 186, 196));
}
</style>
@@ -105,10 +105,10 @@
href: '/mcp-recipes'
},
{
key: 'github',
title: 'GitHub',
subtitle: 'Star, read the source, fork.',
href: '/github'
key: 'storybook',
title: 'Storybook',
subtitle: 'Browse and reuse UI components.',
href: 'https://ui.classroomio.dev'
}
];
@@ -305,7 +305,12 @@
<div class="hidden! flex-row items-center justify-between gap-3 lg:flex!">
<a href="/discord" target="_blank" class="flex items-center transition-opacity duration-200 hover:opacity-80">
<img loading="lazy" alt="discord logo" src="/discord-blue.png" class="h-5 w-6 cursor-pointer" />
<img
loading="lazy"
alt="discord logo"
src="https://assets.cdn.clsrio.com/www/home/discord-blue.png"
class="h-5 w-6 cursor-pointer"
/>
</a>
<div class="flex items-center">
<a
@@ -316,7 +321,7 @@
<img
loading="lazy"
alt="github logo"
src="/github-mark.png"
src="https://assets.cdn.clsrio.com/www/home/github-mark.png"
class="h-5 w-5 cursor-pointer transition-transform duration-200 group-hover:scale-110"
/>
<span
@@ -402,7 +407,12 @@
target="_blank"
class="flex w-full items-center rounded-md px-4 py-4 text-left text-sm transition-all duration-200 hover:bg-gray-100 md:text-lg"
>
<img loading="lazy" alt="discord logo" src="/discord-blue.png" class="mr-2 h-5 w-6 cursor-pointer" />
<img
loading="lazy"
alt="discord logo"
src="https://assets.cdn.clsrio.com/www/home/discord-blue.png"
class="mr-2 h-5 w-6 cursor-pointer"
/>
<span>Discord</span>
</a>
<a
@@ -413,7 +423,7 @@
<img
loading="lazy"
alt="github logo"
src="/github-mark.png"
src="https://assets.cdn.clsrio.com/www/home/github-mark.png"
class="h-5 w-5 cursor-pointer transition-transform duration-200 group-hover:scale-110"
/>
<span class="ml-3 transition-colors duration-200 group-hover:text-black">Github</span>
@@ -0,0 +1,44 @@
import { dev } from '$app/environment';
const USERJOT_APP_ID = 'cm4a6vcmp00jpmdb5n66rmkzz';
let isInitialized = false;
function isWidgetAllowed(): boolean {
return !dev;
}
function ensureSdkLoaded(): void {
if (window.uj) return;
window.$ujq = window.$ujq || [];
window.uj =
window.uj ||
(new Proxy({} as Window['uj'], {
get:
(_, prop) =>
(...args: unknown[]) =>
window.$ujq.push([prop, ...args])
}) as Window['uj']);
const script = document.createElement('script');
script.type = 'module';
script.async = true;
script.src = 'https://cdn.userjot.com/sdk/v2/uj.js';
document.head.appendChild(script);
}
export function initUserJot(): void {
if (isInitialized) return;
if (!isWidgetAllowed()) return;
ensureSdkLoaded();
window.uj.init(USERJOT_APP_ID, {
trigger: 'custom',
position: 'right',
theme: 'auto'
});
isInitialized = true;
}
+19
View File
@@ -0,0 +1,19 @@
declare global {
interface Window {
$ujq: unknown[];
uj: {
init: (
appId: string,
options?: {
widget?: boolean;
position?: 'left' | 'right';
theme?: 'auto' | 'light' | 'dark';
}
) => void;
identify: (user: { id: string; email?: string; firstName?: string; lastName?: string; avatar?: string }) => void;
[method: string]: (...args: unknown[]) => void;
};
}
}
export {};
@@ -187,7 +187,7 @@
<div class="mx-auto mt-14 w-full max-w-[1100px]">
<div class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-200">
<img
src="/org-compliance.jpeg"
src="https://assets.cdn.clsrio.com/www/compliance-training/org-compliance.jpeg"
alt="Compliance dashboard — required learners grouped by compliant, expiring soon, and non-compliant"
class="block h-auto w-full"
loading="eager"
@@ -215,7 +215,7 @@
eyebrow="Inside a compliance program"
title="A look inside a compliance program."
description="From importing your team to issuing a certificate — the whole flow at a glance."
imageSrc="/compliance-dashboard.jpeg"
imageSrc="https://assets.cdn.clsrio.com/www/compliance-training/compliance-dashboard.jpeg"
imageAlt="Compliance dashboard — required learners, status, and certificates"
/>
@@ -234,7 +234,7 @@
eyebrow="Renewals on autopilot"
title="Training that re-runs itself."
description="Set a retake interval once. ClassroomIO automatically moves learners back into “expiring soon”, sends reminders, holds them in a grace period, and flags non-compliance — all without admin intervention."
imageSrc="/course-compliance.jpeg"
imageSrc="https://assets.cdn.clsrio.com/www/compliance-training/course-compliance.jpeg"
imageAlt="Compliance settings panel — retake interval, grace period, reminders, waiver rules"
>
{#snippet bullets()}
@@ -317,7 +317,7 @@
accent="In your dashboard"
title="A personal course-authoring assistant."
description="Drop in a policy doc or paste a URL. The assistant plans the compliance course, drafts each lesson, writes the quiz questions, and stops for your approval before publishing — so building required training drops from a week to an afternoon."
imageSrc="/ai-course-builder.jpeg"
imageSrc="https://assets.cdn.clsrio.com/www/compliance-training/ai-course-builder.jpeg"
imageAlt="Course builder — plan, approve, implement. Templates, in-editor chat, and MCP."
/>
@@ -239,7 +239,7 @@
<div class="mx-auto mt-14 w-full max-w-[1100px]">
<div class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-200">
<img
src="/beautiful-academy-site.jpeg"
src="https://assets.cdn.clsrio.com/www/customer-education/beautiful-academy-site.jpeg"
alt="Branded customer academy on a custom domain — hero, catalog, themed branding"
class="block h-auto w-full"
loading="eager"
@@ -269,7 +269,7 @@
eyebrow="Make it yours"
title="Customize how your customer academy looks like."
description="Theme the catalog, swap fonts and colors to match your brand, and build landing pages that feel like the rest of your product — without losing the LMS underneath."
imageSrc="/customizable-academy.jpeg"
imageSrc="https://assets.cdn.clsrio.com/www/customer-education/customizable-academy.jpeg"
imageAlt="Customizable customer academy — catalog, course page, certificate"
/>
@@ -318,7 +318,7 @@
]}
statusBar="connected · org: yourcompany · widget: getting-started"
previewUrl="help.yourcompany.com / getting-started"
previewImageSrc="/embeddable-widget.jpeg"
previewImageSrc="https://assets.cdn.clsrio.com/www/customer-education/embeddable-widget.jpeg"
previewAlt="Lesson widget embedded inside a help-center page"
capabilities={[
{ label: 'One script tag', sub: 'Drop-in install, no SDK required' },
@@ -192,7 +192,7 @@
<div class="mx-auto mt-14 w-full max-w-[1100px]">
<div class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-200">
<img
src="/docs-vs-customer-site.jpeg"
src="https://assets.cdn.clsrio.com/www/docs-vs-company-academy/docs-vs-customer-site.jpeg"
alt="Docs answering questions, customer academy building capability — side by side"
class="block h-auto w-full"
loading="eager"
@@ -221,7 +221,7 @@
<div class="mx-auto mt-14 w-full max-w-[1100px]">
<div class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-200">
<img
src="/latest-courses.jpeg"
src="https://assets.cdn.clsrio.com/www/partner-training/latest-courses.jpeg"
alt="Partner academy — latest courses, program structure, and learner state"
class="block h-auto w-full"
loading="eager"
@@ -254,7 +254,7 @@
eyebrow="Public course pages"
title="Open a few courses to the public, gate the rest."
description="Pick the courses you want anyone to see. Partners (and the curious ones who aren’t partners yet) can jump in without signing up, watch a few lessons, and if they’re hooked they create an account to unlock the rest of your catalog."
imageSrc="/public-courses.jpeg"
imageSrc="https://assets.cdn.clsrio.com/www/partner-training/public-courses.jpeg"
imageAlt="Public partner course pages — open courses up front, sign-up gates the rest"
/>
@@ -281,7 +281,7 @@
]}
statusBar="connected · org: yourcompany · widget: partner-onboarding"
previewUrl="partners.yourcompany.com / academy"
previewImageSrc="/embeddable-widget.jpeg"
previewImageSrc="https://assets.cdn.clsrio.com/www/partner-training/embeddable-widget.jpeg"
previewAlt="Course widget embedded inside a partner portal"
capabilities={[
{ label: 'One script tag', sub: 'Drop-in install, no SDK required' },
+5 -1
View File
@@ -1,7 +1,8 @@
<script>
<script lang="ts">
import { dev } from '$app/environment';
import { page } from '$app/state';
import { Footer, Navigation, NotFound } from '$lib/components';
import { initUserJot } from '$lib/utils/userjot';
import extend from 'just-extend';
import { onMount } from 'svelte';
import { MetaTags } from 'svelte-meta-tags';
@@ -13,7 +14,10 @@
onMount(() => {
if (dev) {
localStorage.setItem('umami.disabled', '1');
return;
}
initUserJot();
});
let metaTags = $derived(extend(true, {}, data.baseMetaTags, page.data.pageMetaTags));
+22 -18
View File
@@ -15,6 +15,11 @@ export async function load({ url }) {
const starsCache = new Map<string, { stars: number; lastUpdated: number }>();
const CACHE_TIME = 1000 * 60 * 60 * 48; // 48 hours
const CACHE_KEY = 'github-stars';
const DEFAULT_META_TITLE = 'ClassroomIO | The Most Customizable Platform for Customer, Partner, and Employee Education';
const DEFAULT_META_DESCRIPTION =
'One platform for customer academies, partner certification, and employee training. Build courses with AI, publish under your domain, and track completions.';
const DEFAULT_OG_IMAGE_URL = 'https://brand.cdn.clsrio.com/og/classroomio-opengraph.png';
const DEFAULT_OG_IMAGE_ALT = 'ClassroomIO platform for customer, partner, and employee education';
async function getStars() {
const now = Date.now();
@@ -43,27 +48,27 @@ async function getStars() {
}
function getBaseMetaTags(url: URL) {
const canonicalUrl = new URL(url.pathname, url.origin).href;
const metatags = Object.freeze({
title: 'ClassroomIO | The Open Source Learning Management System for Companies',
description:
'A flexible, user-friendly platform for creating, managing, and delivering courses for companies and training organisations',
canonical: new URL(url.pathname, url.origin).href,
title: DEFAULT_META_TITLE,
description: DEFAULT_META_DESCRIPTION,
canonical: canonicalUrl,
openGraph: {
type: 'website',
url: new URL(url.pathname, url.origin).href,
locale: 'en_IE',
title: 'ClassroomIO | The Open Source Learning Management System for Companies',
description:
'A flexible, user-friendly platform for creating, managing, and delivering courses for companies and training organisations',
url: canonicalUrl,
locale: 'en_US',
title: DEFAULT_META_TITLE,
description: DEFAULT_META_DESCRIPTION,
siteName: 'ClassroomIO',
images: [
{
url: 'https://brand.cdn.clsrio.com/og/classroomio-og.png',
alt: 'ClassroomIO OG Image',
url: DEFAULT_OG_IMAGE_URL,
alt: DEFAULT_OG_IMAGE_ALT,
width: 1920,
height: 1080,
secureUrl: 'https://brand.cdn.clsrio.com/og/classroomio-og.png',
type: 'image/jpeg'
secureUrl: DEFAULT_OG_IMAGE_URL,
type: 'image/png'
}
]
},
@@ -71,11 +76,10 @@ function getBaseMetaTags(url: URL) {
handle: '@classroomio',
site: '@classroomio',
cardType: 'summary_large_image' as const,
title: 'ClassroomIO | The Open Source Learning Management System for Companies',
description:
'A flexible, user-friendly platform for creating, managing, and delivering courses for companies and training organisations',
image: 'https://brand.cdn.clsrio.com/og/classroomio-og.png',
imageAlt: 'ClassroomIO OG Image'
title: DEFAULT_META_TITLE,
description: DEFAULT_META_DESCRIPTION,
image: DEFAULT_OG_IMAGE_URL,
imageAlt: DEFAULT_OG_IMAGE_ALT
}
}) satisfies MetaTagsProps;
@@ -0,0 +1,347 @@
<script lang="ts">
import { Faq, FeatureGrid, PageHeader, PageSignupCTA, TrustBand, WorkflowSteps } from '$lib/components';
import { Badge } from '@cio/ui/base/badge';
import { Button } from '@cio/ui/base/button';
import { BlurFade } from '@cio/ui/custom/animation/blurfade';
import Check from '@lucide/svelte/icons/check';
import Globe from '@lucide/svelte/icons/globe';
import GraduationCap from '@lucide/svelte/icons/graduation-cap';
import MoonStar from '@lucide/svelte/icons/moon-star';
import Palette from '@lucide/svelte/icons/palette';
import Sparkles from '@lucide/svelte/icons/sparkles';
import Users from '@lucide/svelte/icons/users';
import Zap from '@lucide/svelte/icons/zap';
const creatorUseCases = [
'A free intro course that leads into a paid program',
'A public sample course people can review before they buy',
'A short onboarding or community course that does not need login friction',
'Partner or reseller training you can share with a link',
'A simple home for short lessons and self-graded modules'
];
const learnerUseCases = [
'Browse the full catalog without creating an account',
'Read in light mode or dark mode',
'Get immediate quiz feedback',
'Start on mobile and continue on desktop',
'Use a course page that matches the creator brand they came for'
];
const keyFeatures = [
{
Icon: Palette,
title: 'Branding controls',
description:
'Choose a landing theme, add your logo, set your site name, and connect a custom domain without custom development.'
},
{
Icon: MoonStar,
title: 'Light and dark mode',
description:
'The lesson viewer includes a built-in mode switcher, and the learner preference stays in place between visits.'
},
{
Icon: Sparkles,
title: 'Clean lesson pages',
description:
'Public course pages use the same design system as the rest of ClassroomIO, so they feel like part of your product instead of a separate microsite.'
},
{
Icon: Zap,
title: 'Auto-graded quizzes',
description:
'Support for 15 auto-gradable question types, including multiple choice, ordering, hotspot, short answer, and fill-in-the-blank, with instant feedback in the lesson.'
}
];
const conversionMechanics = [
{
title: 'Inline callout cards',
description:
'Add a callout card inside any lesson with a title, description, button label, and link to your paid course, checkout, or community.'
},
{
title: 'Locked lesson previews',
description:
'Leave part of the course open, then replace locked lesson content with a callout that points to the next step.'
},
{
title: 'Payment, discount, and invite gating',
description: 'Route learners into paid enrollment with payment links, discount codes, or invite-based access.'
},
{
title: 'Auto-enroll after sign-up',
description: 'Learners who sign in can return to the lesson they were already viewing instead of starting over.'
}
];
const brandingItems = ['Custom domain', 'Your logo', 'Your site name', 'Dark mode built-in', 'SEO-friendly pages'];
const workflow = [
{
title: 'Build the course',
description:
'Use the same course editor you already use for paid courses. Public courses support all 15 auto-gradable question types.',
label: 'Step 01'
},
{
title: 'Switch type to PUBLIC',
description:
'Change the course type to `PUBLIC`. If a question type is not supported, ClassroomIO flags it before publish.',
label: 'Step 02'
},
{
title: 'Set the page up',
description: 'Choose a theme, add your logo and site name, and connect a custom domain if you want one.',
label: 'Step 03'
},
{
title: 'Publish',
description:
'Publish the course on your public site, then add callouts or locked lessons where you want to turn readers into enrollments.',
label: 'Step 04'
}
];
const publicCoursesFaqs = [
{
question: 'Does hosting a public course cost extra?',
answer:
'No. Public courses are part of your existing ClassroomIO workspace. Every paid plan can publish them, and learners do not count against your seat limits until they enroll in a paid course.'
},
{
question: 'Can I require payment, or capture leads on a public course?',
answer:
'Yes. You can attach a payment link, a discount code, or an invite token to the enrollment flow. You can also place callout cards inside lessons that link to a paid course, a community, or any URL you choose.'
},
{
question: 'Do I need a custom domain?',
answer:
'No. Every public course is available on your ClassroomIO public site by default. A custom domain is optional if you want the course to live at your own URL.'
},
{
question: 'Can search engines index a public course?',
answer:
'Yes. Public course landing pages and lesson previews are server-rendered and include the metadata search engines need, so courses are discoverable.'
},
{
question: 'How are quizzes graded?',
answer:
'Instantly in the browser. Public courses support 15 auto-gradable question types, including multiple choice, ordering, matching, hotspot, fill-in-the-blank, numeric, short answer, and file upload.'
},
{
question: 'Can I gate some lessons and leave the rest public?',
answer:
'Yes. Lock the lessons you want to gate and the viewer will show a callout card in place of the content. You can use that card to send people to the paid version of the course.'
},
{
question: 'Can I match the course page to my brand?',
answer:
'Yes. You can choose a landing theme, upload your logo, set a site name, and optionally connect a custom domain. Dark mode is built in for learners.'
}
];
</script>
<svelte:head>
<title>Public Courses | ClassroomIO</title>
<meta
name="description"
content="Publish public courses on your own branded page, with built-in quizzes, lesson callouts, and optional custom domains."
/>
<meta property="og:title" content="Public Courses | ClassroomIO" />
<meta
property="og:description"
content="A branded public course page for sharing free lessons, quizzes, and previews before someone enrolls."
/>
</svelte:head>
<section class="bg-white">
<PageHeader className="px-6 lg:px-12">
<div class="mx-auto flex max-w-[820px] flex-col items-center text-center">
<Badge variant="outline" class="mb-6 gap-2! bg-white px-3.5! py-1.5!">
<Globe size={14} class="text-blue-700" />
Public Courses
</Badge>
<h1 class="text-[clamp(2.4rem,4vw,3.6rem)] leading-[1.08] font-medium tracking-tight text-gray-950">
Publish free courses that lead to paid enrollments.
</h1>
<p class="mt-6 text-sm leading-relaxed lg:text-base">
Public courses let you share lessons without asking people to create an account first. You can publish free
course content, add quizzes, customize the page, and guide interested learners into a paid course or community.
</p>
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
<Button data-cal-config="'layout':'month_view'" data-cal-link="classroomio/demo">Book a Demo</Button>
<Button href="/signup" variant="outline">Start free</Button>
</div>
</div>
<div class="mx-auto mt-14 w-full max-w-[1100px]">
<img
src="https://assets.cdn.clsrio.com/www/public-courses/public-courses-hero.jpeg"
alt="Public course page on desktop"
width="1920"
height="1481"
loading="eager"
class="w-full rounded-2xl border border-gray-200 shadow-sm"
/>
</div>
</PageHeader>
<!-- Use cases: two-column split -->
<section class="bg-gray-50 px-6 py-16 lg:px-12 lg:py-24">
<div class="mx-auto max-w-[1120px]">
<div class="mb-10 max-w-[720px]">
<div class="mb-3 text-xs font-medium tracking-widest text-blue-700 uppercase">Why teams use it</div>
<h2 class="text-[clamp(2rem,3vw,2.7rem)] leading-[1.15] font-medium tracking-tight text-gray-950">
Public courses are useful before someone is ready to enroll.
</h2>
<p class="mt-5 text-base leading-relaxed text-gray-500">
Teams use them to share sample lessons, open training, and course previews. Learners get a low-friction way to
read or try the material before deciding what to do next.
</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 lg:divide-x lg:divide-gray-200">
<div class="rounded-2xl border border-gray-200 bg-white p-8 lg:rounded-r-none lg:border-r-0">
<div class="text-[11px] font-medium tracking-widest text-blue-700 uppercase">For creators</div>
<h3 class="mt-4 text-[clamp(1.4rem,2.2vw,1.9rem)] leading-[1.15] font-medium tracking-tight text-gray-950">
A practical way to publish useful course content in public.
</h3>
<div class="my-7 border-t border-gray-200"></div>
<ul class="space-y-4">
{#each creatorUseCases as useCase}
<li class="flex items-start gap-3 text-sm text-gray-700">
<span
class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-50 text-blue-700"
>
<Check size={12} strokeWidth={2.5} />
</span>
{useCase}
</li>
{/each}
</ul>
<div class="mt-8">
<span
class="inline-flex items-center gap-2 rounded-full bg-blue-50 px-3 py-1.5 text-xs font-medium text-blue-700"
>
<GraduationCap size={13} strokeWidth={2} />
Shareable by default
</span>
</div>
</div>
<div class="mt-6 rounded-2xl border border-gray-200 bg-white p-8 lg:mt-0 lg:rounded-l-none lg:border-l-0">
<div class="text-[11px] font-medium tracking-widest text-emerald-700 uppercase">For learners</div>
<h3 class="mt-4 text-[clamp(1.4rem,2.2vw,1.9rem)] leading-[1.15] font-medium tracking-tight text-gray-950">
A simple course page that is easy to browse and easy to read.
</h3>
<div class="my-7 border-t border-gray-200"></div>
<ul class="space-y-4">
{#each learnerUseCases as useCase}
<li class="flex items-start gap-3 text-sm text-gray-700">
<span
class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-emerald-50 text-emerald-700"
>
<Check size={12} strokeWidth={2.5} />
</span>
{useCase}
</li>
{/each}
</ul>
<div class="mt-8">
<span
class="inline-flex items-center gap-2 rounded-full bg-emerald-50 px-3 py-1.5 text-xs font-medium text-emerald-700"
>
<Users size={13} strokeWidth={2} />
No login wall
</span>
</div>
</div>
</div>
</div>
</section>
<!-- Key features -->
<FeatureGrid
eyebrow="What you get"
title="The essentials are already built in."
description="You do not need a separate website project to publish public course pages that look finished and work well."
features={keyFeatures}
columns={4}
/>
<!-- Drive-to-paid story -->
<section class="bg-white px-6 py-12 lg:px-12 lg:py-16">
<div class="mx-auto grid max-w-[1100px] grid-cols-1 items-center gap-12 lg:grid-cols-2 lg:gap-20">
<div>
<div class="mb-2 text-xs font-medium tracking-widest text-blue-700 uppercase">Moving people forward</div>
<h2 class="text-[clamp(1.75rem,2.6vw,2.2rem)] leading-[1.15] font-medium tracking-tight">
If someone wants more, you can give them a clear next step.
</h2>
<p class="mt-5 text-base leading-relaxed text-gray-500">
Public courses are useful on their own, but they can also lead into a paid course, a cohort, or a community.
Add callouts inside lessons, lock selected content, and guide people into enrollment when it makes sense.
</p>
<ul class="mt-6 space-y-3 text-sm text-gray-700">
{#each conversionMechanics as item, i}
<BlurFade delay={0.06 * i} once>
<li class="flex gap-3">
<span class="mt-1.5 inline-flex h-1.5 w-1.5 shrink-0 rounded-full bg-blue-700"></span>
<span>
<span class="font-medium text-gray-950">{item.title}.</span>
<span class="ml-1 text-gray-500">{item.description}</span>
</span>
</li>
</BlurFade>
{/each}
</ul>
</div>
<img
src="https://assets.cdn.clsrio.com/www/public-courses/public-course-empty-state.jpeg"
alt="Public course empty state in the dashboard"
width="1920"
height="1410"
loading="lazy"
class="w-full rounded-2xl border border-gray-200 shadow-sm"
/>
</div>
</section>
<!-- Custom domain + branding trust band -->
<TrustBand caption="Branding options" items={brandingItems} />
<!-- How it works -->
<WorkflowSteps
eyebrow="How it works"
title="Set it up in the same workflow you already use."
description="You do not need a second publishing tool. Public courses use the same course editor and publishing flow as the rest of ClassroomIO."
steps={workflow}
/>
<!-- FAQ -->
<section class="bg-white px-6 lg:px-12">
<Faq
heading="Public courses, answered"
subheading="A few common questions before teams publish their first public course."
items={publicCoursesFaqs}
hideCta
/>
</section>
<!-- Final CTA -->
<PageSignupCTA
header="Publish a public course without spinning up a separate site."
subText="Use the same ClassroomIO workspace to share free lessons, add quizzes, and point interested learners toward the next step."
btnLabel="Book a Demo"
/>
</section>
@@ -0,0 +1,7 @@
import { redirect } from '@sveltejs/kit';
export const prerender = false;
export const load = () => {
redirect(307, 'https://feedback.classroomio.com/roadmap');
};
@@ -1,107 +0,0 @@
<script>
import { PageHeader, Timeline, PageSignupCTA } from '$lib/components';
const newFeatures = [
{
title: 'LMS Customization',
desc: 'Right in your settings you can customize what you students see in their dashboard in few clicks.'
},
{
title: 'Course Newsfeed',
desc: 'You can now send a broadcast message to all the students participating in your course right from our dashboard.'
},
{
title: 'Tags',
desc: 'You can now group courses into organization-specific categories and filter course catalogs using tags.'
},
{
title: 'Multi language support',
desc: 'Our dashboard now supports 7 new languages. In addition, we allow you create course content in those any of those languages.'
},
{
title: 'Security RLS',
desc: "All the tables we've created now have role level security enabled by default"
},
{
title: 'Increased video limit',
desc: 'You can now upload a single video of about 500mb without interruption.'
},
{
title: 'More certificate templates',
desc: 'We have shipped 4 more certificate templates in which you can choose from right within your course dashboard'
}
];
const upcomingFeatures = [
{
title: 'Pathways',
desc: 'This feature gives you the ability to group courses into folder which we are calling - Pathways. Students can complete a series of courses before getting a certificate'
},
{
title: 'Course Batches',
desc: 'A course can be taken in batches, meaning that you can create different cohorts of students within one course. The benefit of this is that you can teach a course multiple times to different groups of students. This is useful mainly for bootcamps and teaching organizations.'
},
{
title: 'API',
desc: 'We are working on our public API which allows you to perform CRUD operations against your classroomio organization.'
},
{
title: 'Embedable Course',
desc: 'You can embed selected course form ClassroomIO on any website of your choice'
},
{
title: 'Landing page builder',
desc: 'With more templates and pages, we will allow educators create simple web pages using our platform to showcase their programs and offerings.'
},
{
title: 'Analytics',
desc: 'Educators can easily track students course completion rating, understand what topics are not clearly understood and much more.'
}
];
</script>
<svelte:head>
<title>Roadmap | ClassroomIO</title>
</svelte:head>
<PageHeader className="flex flex-col items-center justify-center text-center">
<h1 class="text-start text-4xl text-slate-900 md:text-center md:text-6xl">Product Roadmap</h1>
<p class="mt-10 w-[80%] text-start text-slate-700 md:w-[40%] md:text-center">
This is a live document, and will be updated regularly. To submit an idea, or to view a list of feature requests and
ideas that we're considering, check out our <a
href="https://feedback.classroomio.com"
target="_blank"
rel="noopener"
class="underline">feedback portal</a
>
</p>
</PageHeader>
<section class="relative flex w-full flex-col items-center border-b-2 px-4 py-20">
<img src="/roadmap-one.svg" alt="" class="absolute right-0 bottom-0 w-60 lg:w-96" />
<img src="/roadmap-two.svg" alt="" class="absolute top-0 left-0 hidden lg:block" />
<div class="flex flex-col items-start space-y-10 px-4">
<div>
<h1 class="text-3xl">Latest Features</h1>
</div>
<Timeline items={newFeatures} />
</div>
</section>
<section class="flex w-full flex-col items-center border-b-2 px-4 py-20">
<div class="flex flex-col items-start space-y-10 px-4">
<div>
<h1 class="text-3xl">Upcoming Features</h1>
</div>
<Timeline items={upcomingFeatures} />
</div>
</section>
<PageSignupCTA
header="Share Your First Course With Your Audience In Minutes"
subText="In about 4 Clicks you can already start creating courses."
btnLabel="Get started for free"
link="/signup"
demo={false}
/>
+7 -7
View File
@@ -5,17 +5,17 @@
const whyStart = [
{
src: '/teach/teach-from-anywhere.svg',
src: 'https://assets.cdn.clsrio.com/www/teach/teach-from-anywhere.svg',
header: 'Fully Online; Teach from anywhere',
subText: ' Facilitate a subject from the comfort of your space, engaging students from highly diverse background'
},
{
src: '/teach/get-paid.svg',
src: 'https://assets.cdn.clsrio.com/www/teach/get-paid.svg',
header: 'Get paid extra commission',
subText: 'Get paid for the course you design and earn extra comission based on course perfomance'
},
{
src: '/teach/inspire-learners.svg',
src: 'https://assets.cdn.clsrio.com/www/teach/inspire-learners.svg',
header: 'Inspire learners',
subText: 'Your opportunity to inspire thousands of learners to achieve dreams and academic pursue exellence'
}
@@ -93,14 +93,14 @@
</div>
<div class=" ">
<img src="/teach/teacher-hero.png" alt="" class="w-full" />
<img src="https://assets.cdn.clsrio.com/www/teach/teacher-hero.png" alt="" class="w-full" />
</div>
</PageHeader>
<div class="mx-auto mt-10">
<div class="border-b border-slate-100 px-[10%] pb-10">
<h1 class="flex items-center justify-center gap-3 pb-28 text-center text-3xl">
Why you should teach with us? <img src="/teach/star.svg" alt="" />
Why you should teach with us? <img src="https://assets.cdn.clsrio.com/www/teach/star.svg" alt="" />
</h1>
<div class="mt-10 flex flex-wrap justify-center lg:justify-between">
@@ -122,7 +122,7 @@
<div class="mt-10 px-[10%] py-10">
<h1 class="mb-28 flex items-center justify-center gap-3 text-center text-3xl">
How to get started <img src="/teach/thumbs-up.svg" alt="" />
How to get started <img src="https://assets.cdn.clsrio.com/www/teach/thumbs-up.svg" alt="" />
</h1>
<div class="mx-auto mt-5 flex flex-wrap justify-between gap-y-10">
@@ -154,7 +154,7 @@
my students are happy. Love the product
</h1>
<div class="mt-3 flex flex-row items-center justify-center gap-4">
<img src="/teach/user-profile.png" alt="" class="w-8" />
<img src="https://assets.cdn.clsrio.com/www/teach/user-profile.png" alt="" class="w-8" />
<p class="text-sm">
<span class="mr-2 font-semibold"> Bilik Diana </span> English tutor
</p>