feat: course-app v0.0.11 (#523)

* chore: remove bin

* fix: restore bin

* change dist to bin

* publish only dist folder

* publish with src folder.

* fix ci

* merge steps in CI

* update workflow

* deploy with src

* update to v9

* delete unused code

* complete landing page

* complete landing page

* feat: webflow template (#518)

* created a new branch for webflow from debug/course-app

* added webflow and reverted neccessary changes

* fixed commit issues

* clean up posthog

* clean up classic

* clean up minimal

* clean up posthog

* clean up webflow

* v0.0.10

* markdown lint

* feat: add prep template (#522)

* working on the ielts template

* working on the ielts template

* finished the ielts template

---------

Co-authored-by: emmanuel <[email protected]>

* clean up examprep

* v0.0.11

---------

Co-authored-by: Em_Dev <[email protected]>
This commit is contained in:
Rotimi Best
2024-12-17 04:06:03 +01:00
committed by GitHub
co-authored by emmanuel
parent 5c83034a01
commit 3ee7cf10e3
208 changed files with 7211 additions and 7003 deletions
+167 -17
View File
@@ -1,27 +1,177 @@
# @classroomio/course-app
<a href="https://courseapp.oncws.com/">
<img alt="Create beautiful course websites to educate your customers" src="https://cdn.courseapp.oncws.com/courseapp-og.png" />
<h1 align="center">CourseApp by ClassroomIO</h1>
<p align="center">
Create beautiful course websites to educate your customers.
<br />
<a href="https://courseapp.oncws.com">Website</a> | <a href="https://dub.sh/ciodiscord">Join Discord community</a>
</p>
</a>
Create a university site for your SAAS in seconds, ready to go. Just add your content.
# CourseApp
## Usage
Create a beautiful course website in seconds with our SvelteKit-powered CLI tool. Choose from multiple templates and customize your content through a simple file structure.
To create a template run:
## Quick Start
```bash
npx @cio/course-app@latest name-of-your-project -t name-of-template --course
pnpx @classroomio/course-app@latest
```
### Commands
The CLI accepts the following arguments
- argument 1 : `name-of-project` this argument is the name of your project
- argument 2 : `--template name-of-template` this argument is the name of the template you wish to use
- argument 3 : `--courses` an option to use our demo courses
You'll be prompted to:
1. Enter a project name (or use `.` for current directory)
2. Select a template
3. Choose whether to include demo courses
## Available Templates
- cal
- classic
- minimalistic
- posthog
- webflow
- `cal` - Cal.com inspired design
- `classic` - Traditional course layout
- `minimal` - Clean, minimalist design
- `posthog` - PostHog-style interface
- `webflow` - Webflow-inspired template
## Project Structure
```
your-project/
├── src/
│ ├── courses/ # Your course content
│ │ ├── course-slug/
│ │ │ ├── metadata.json # Course details
│ │ │ ├── section-1/
│ │ │ │ ├── metadata.json # Section details
│ │ │ │ ├── lesson-1.md # Lesson content
│ │ │ │ └── lesson-2.md
│ │ │ └── section-2/
│ │ └── another-course/
│ └── lib/
│ └── components/ # Template-specific components
```
## Course Configuration
### Course Metadata
Each course needs a `metadata.json` file:
```json
{
"title": "Your Course Title",
"description": "Course description",
"banner": "https://path-to-banner-image.jpg",
"created_at": "24-11-2024",
"cost": 0,
"currency": "USD",
"type": "paced"
}
```
### Section Metadata
Each section folder requires a `metadata.json`:
```json
{
"title": "Section Title",
"unlocked": true
}
```
### Lesson Content
Lessons are written in Markdown (`.md`) files:
```markdown
---
title: 'Lesson Title'
---
Your lesson content here...
## Supports Markdown Features
- Lists
- Code blocks
- Images
- And more!
```
## Customization
### Templates
Each template has its own component set in `src/lib/components/[template-name]/`. The active template is determined by the `VITE_TEMPLATE` environment variable.
### Navigation & Site Settings
Update `src/lib/data/pages.json` to customize your site settings. The file contains an array of three main page configurations:
```json
[
{
"id": "shared-page",
"title": "Shared",
"slug": "#",
"sections": [
// Global components like SEO, navigation, and footer
]
},
{
"id": "home-page",
"title": "Home Page",
"slug": "/",
"sections": [
// Home page specific sections like hero, features, testimonials
]
},
{
"id": "courses-page",
"title": "Courses Page",
"slug": "/courses",
"sections": [
// Course page specific sections
]
}
]
```
Each page configuration includes:
1. **Shared Page** (`shared-page`):
- Contains global components used across all pages
- Configures SEO settings, main navigation, and footer
- Always present regardless of the current route
2. **Home Page** (`home-page`):
- Landing page configuration (`/` route)
- Includes sections like header, about, featured courses, instructors, FAQ, testimonials, and CTAs
- Configurable marketing sections to showcase your platform
3. **Courses Page** (`courses-page`):
- Course listing page configuration (`/courses` route)
- Contains course-specific header and layout settings
- Manages how courses are displayed and filtered
Each section can be toggled with the `show` property and customized through its `settings` object.
## API Routes
The project includes built-in API routes:
- `/api/courses` - Returns all courses with their metadata
- Individual course data is dynamically loaded based on the URL structure
## Development
After creating your project:
```bash
cd your-project
pnpm install
pnpm run dev
```
## Next up
- More templates ;)
- Create quizzes
- Authentication
- Learning paths
- And more!
+6 -5
View File
@@ -61,20 +61,21 @@ program
]);
courses = answer.course;
}
spinner.start('Scaffolding project....');
try {
console.log('\n');
await scaffoldTemplate({ template, courses, projectPath, projectName });
spinner.succeed(`\n\n${chalk.green('Done! Your project is ready.')}\n\n`);
spinner.succeed(`${chalk.green('Done! Your template is ready.')}`);
console.log(
`To get started:\n\n${chalk.blue(`cd ${projectName}`)}\n${chalk.blue(
`\nTo get started:\n\n${chalk.green(`cd ${projectName}`)}\n${chalk.green(
'pnpm i'
)}\n${chalk.blue('pnpm run dev')}`
)}\n${chalk.green('pnpm run dev')}`
);
console.log('\n\n', chalk.blue('Happy hacking ;)'));
console.log(`\nDM me on X if you face any issues: https://x.com/rotimi_best`);
} catch (error) {
spinner.fail(chalk.red(`Failed to scaffold the project. ${error}`));
process.exit(1);
+23 -18
View File
@@ -20,11 +20,20 @@ const __dirname = path.dirname(__filename);
const packageRoot = path.join(__dirname, '..');
const spinner = ora();
async function rewriteComponentsFile(filePath: string, template: string): Promise<void> {
const newContent = `export { components } from './${template}';`;
try {
fs.writeFileSync(filePath, newContent);
} catch (error) {
console.error(`Error writing to file: ${error}`);
}
}
export async function scaffoldTemplate({
template,
courses,
projectPath,
projectName
projectPath
}: ScaffoldTemplateParams): Promise<void> {
spinner.text = chalk.yellow('Getting project...');
@@ -69,23 +78,19 @@ export async function scaffoldTemplate({
excludePath: coursePathToExclude
});
// Update root import of components to reflect only selected template
const componentsIndex = path.join(projectPath, 'src', 'lib', 'components', 'index.ts');
await rewriteComponentsFile(componentsIndex, template);
// If the user wants demo courses, copy the courses folder
spinner.text = chalk.yellow('Setting up courses folder...');
await copyCoursesFolder({ templatePath: templateDirPath, projectPath, courses });
console.log(
chalk.yellow(
`Project ${projectName} created using template "${template}" ${
courses ? 'with' : 'without'
} demo courses.`
)
);
console.log(
chalk.yellow(
`You can ${
courses
? 'edit or create new courses in the courses folder'
: 'create new courses in the courses folder'
}`
)
);
// console.log(
// chalk.yellow(
// `Project ${projectName} created using template "${template}" ${
// courses ? 'with' : 'without'
// } demo courses.`
// )
// );
}
+2 -2
View File
@@ -1,10 +1,10 @@
{
"name": "@classroomio/course-app",
"version": "0.0.1",
"version": "0.0.11",
"private": false,
"main": "dist/create-template.js",
"scripts": {
"create": "node dist/create-template.js",
"dev:run": "pnpm build && node dist/create-template.js",
"build": "vite build && node scripts/postbuild.js"
},
"bin": {
@@ -0,0 +1,3 @@
{
"MD013": false
}
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,5 @@
{
"name": "calcom",
"name": "courseapp",
"version": "0.0.1",
"type": "module",
"scripts": {
@@ -36,12 +36,12 @@
"vitest": "^2.1.4"
},
"dependencies": {
"@sveltejs/adapter-cloudflare": "^4.7.4",
"carbon-icons-svelte": "^12.13.0",
"rehype-slug": "^6.0.0",
"rehype-unwrap-images": "^1.0.0",
"remark-toc": "^9.0.0",
"shiki": "^1.23.1",
"svelte-meta-tags": "^4.0.4"
"svelte-meta-tags": "^4.0.4",
"svelte-motion": "^0.12.2"
}
}
+6 -2
View File
@@ -40,11 +40,15 @@
--posthog-background: 70 16% 93%;
--posthog-border: 68 8% 80%;
--minimal-primary: 214 33% 9%;
--minimal-secondary: 212 34% 15% 1;
--minimal-primary: 222 95% 41%;
--minimal-secondary: 214 33% 9%;
--classic-primary: 300 98% 41%;
--classic-secondary: 300 100% 95%;
--examprep-primary: 224 98% 37%;
--webflow-primary: 224 93% 39%;
--webflow-secondary: 224 93% 39%;
}
.dark {
@@ -7,7 +7,6 @@ title: 'Why Analytics Matters'
const increment = () => count++;
</script>
## Code
```ts
@@ -1,35 +0,0 @@
<script lang="ts">
import { CheckmarkFilled } from 'carbon-icons-svelte';
import { getPageSection } from '$lib/utils/helpers/page';
import { homePage } from '$lib/utils/stores/pages';
const content = $derived(getPageSection($homePage, 'about'));
</script>
{#if content?.show}
<section id="about">
<div class="relative px-4 md:px-10 py-10 h-full w-full mx-auto xl:w-[80%]">
<h1 class="text-center text-5xl mb-2 font-bold md:w-[60%] mx-auto">
{content.settings.title}
</h1>
<p class="text-center dark:text-white text-lg font-semibold md:w-[70%] mb-8 mx-auto">
{content.settings.subtitle}
</p>
<div>
<section class="flex flex-wrap items-start justify-center w-full gap-6 py-4">
{#each content.settings.benefits.list as item}
<div
class="flex flex-col gap-2 p-4 bg-white border border-[#282828] h-[130px] max-h-[180px] w-full max-w-full md:max-w-[320px] lg:max-w-[450px] rounded-lg overflow-hidden"
>
<span class="flex items-center gap-2">
<CheckmarkFilled size={24} />
<p class="text-lg capitalize line-clamp-1">{item.title}</p>
</span>
<p class="text-sm text-[#878787] line-clamp-3">{item.subtitle}</p>
</div>
{/each}
</section>
</div>
</div>
</section>
{/if}
@@ -9,38 +9,28 @@
description: string;
}
let {
className = '',
bannerImage = '',
slug = '',
title = '',
description = ''
}: Props = $props();
let { className = '', bannerImage, slug = '', title = '', description = '' }: Props = $props();
</script>
<div
class="bg-white border-2 border-black rounded-xl overflow-hidden w-full h-full pb-2 min-w-[80%] md:max-w-[350px] md:min-w-[300px] max-w-full space-y-4"
class="h-full w-full min-w-[80%] max-w-full space-y-4 overflow-hidden rounded-xl border-2 border-black bg-white pb-2 md:min-w-[300px] md:max-w-[350px]"
>
<div class="relative space-y-2 text-black {className}">
<div class="border-b-2 border-black border-dashed">
<img
src={bannerImage ? bannerImage : '/classroomio-course-img-template.jpg'}
alt=""
class="w-full h-44"
/>
<div class="border-b-2 border-dashed border-black">
<img src={bannerImage || '/course-banner.jpg'} alt="" class="h-44 w-full" />
</div>
<div>
<div class="px-3 py-2 overflow-hidden">
<p class="text-lg font-semibold overflow-ellipsis line-clamp-1">
<div class="overflow-hidden px-3 py-2">
<p class="line-clamp-1 overflow-ellipsis text-lg font-semibold">
{title}
</p>
</div>
<p class="text-sm text-[#656565] px-3 overflow-ellipsis line-clamp-3 text-justify">
<p class="line-clamp-3 overflow-ellipsis px-3 text-justify text-sm text-[#656565]">
{description}
</p>
</div>
<div class=" w-full py-4 px-6">
<div class=" w-full px-6 py-4">
<PrimaryButton href={`/course/${slug}`} label="Go to Course" />
</div>
</div>
@@ -1,27 +0,0 @@
<script lang="ts">
import { getPageSection } from '$lib/utils/helpers/page';
import { homePage } from '$lib/utils/stores/pages';
import Accordion from './Accordion.svelte';
const content = $derived(getPageSection($homePage, 'faq'));
</script>
{#if content?.show}
<section id="faq" class="relative px-2 pt-4 pb-20 h-full">
<div class="absolute inset-0 bg-[#E5E7E0] opacity-60"></div>
<div class="relative">
<h1 class="text-center text-5xl font-bold mb-4">
{content.settings.title}
</h1>
<h1 class="text-center text-lg font-semibold mb-4 w-full md:w-[80%] lg:w-[60%] mx-auto">
{content.settings.subtitle}
</h1>
<section class="py-4 px-2 space-y-10 w-full md:w-[80%] mx-auto">
{#each content.settings.questions as faq}
<Accordion title={faq.title} content={faq.content} />
{/each}
</section>
</div>
</section>
{/if}
@@ -1,4 +1,5 @@
<script lang="ts">
import Logo from '$lib/components/ui/_custom/Logo.svelte';
import { getPageSection } from '$lib/utils/helpers/page';
import { sharedPage } from '$lib/utils/stores/pages';
@@ -7,20 +8,18 @@
</script>
{#if content?.show}
<nav class="w-full py-4 px-4 lg:px-14 bg-[#F4F4F4]">
<nav class="w-full bg-[#F4F4F4] px-4 py-4 lg:px-14">
<div
class="bg-[#2E2E2F] rounded-lg p-4 text-white w-full flex flex-col md:flex-row items-start md:items-center gap-4 md:justify-between"
class="flex w-full flex-col items-start gap-4 rounded-lg bg-[#2E2E2F] p-4 text-white md:flex-row md:items-center md:justify-between"
>
<a href="/#" class="p-2 bg-white rounded-lg">
<img
src={seo?.settings.logo}
alt={`${seo?.settings.title} logo`}
class="rounded bg-white inline-block mx-auto"
data-atf="1"
/>
</a>
<Logo
src={seo?.settings.logo}
alt={seo?.settings.title}
className="bg-white rounded-lg p-2 w-28"
/>
<ul
class="flex flex-col md:flex-row items-start md:items-center gap-4 -md:gap-8 font-semibold capitalize underline"
class="-md:gap-8 flex flex-col items-start gap-4 font-semibold capitalize underline md:flex-row md:items-center"
>
{#if content.settings.twitter}
<a href={content.settings.twitter} target="_blank" title="twitter">Twitter</a>
@@ -38,7 +37,7 @@
<a
href="https://git.new/cio-template"
target="_blank noopener noreferrer"
class="flex w-full md:w-fit items-center justify-center lg:justify-start hover:underline text-white gap-1"
class="flex w-full items-center justify-center gap-1 text-white hover:underline md:w-fit lg:justify-start"
>
<p class="text-base font-bold">Built with ❤️ by ClassroomIO</p>
</a>
@@ -1,70 +0,0 @@
<script lang="ts">
import { goto } from '$app/navigation';
import PrimaryButton from './PrimaryButton.svelte';
import { getPageSection } from '$lib/utils/helpers/page';
import { homePage } from '$lib/utils/stores/pages';
</script>
{#if $homePage}
{@const content = getPageSection($homePage, 'header')}
{#if content?.show}
<section
class="font-matter flex items-start md:items-center justify-center py-10 lg:py-20 px-5 lg:px-20 h-full bg-[#F4F4F4]"
>
<section
class="flex flex-col-reverse lg:flex-row items-center justify-center lg:justify-between"
>
<div class="text-center lg:text-start space-y-6 w-full mt-4 lg:mt-0">
<div
class="bg-[#E5E7E0] py-1 px-4 md:border-b border-black rounded-sm w-fit mx-auto lg:mx-0"
>
<p class="uppercase font-bold text-xs lg:text-base text-[#0F163F]">
{content.settings.title}
</p>
</div>
<p
class="text-4xl md:text-6xl font-black w-full mx-auto lg:mx-0 md:w-[90%] lg:w-[80%] capitalize"
>
{content.settings.titleHighlight}
</p>
<p class="font-semibold text-xl w-full md:w-[80%] mx-auto lg:mx-0 lg:w-[70%]">
{content.settings.subtitle}
</p>
<PrimaryButton
onClick={() => {
goto(content.settings.action.link);
}}
label={content.settings.action.label}
/>
</div>
<div
class="relative w-[250px] h-[250px] lg:w-[500px] lg:h-[350px] flex items-center justify-center"
>
<svg
class="absolute -left-[15px] lg:-left-[60px] xl:-left-[100px] w-[270px] h-[270px] lg:w-[400px] xl:w-[520px] lg:h-[370px]"
viewBox="0 0 360 360"
>
<circle
cx="180"
cy="180"
r="170"
fill="none"
stroke="#282828"
stroke-width="2"
stroke-dasharray="15, 10"
/>
</svg>
<img
alt="landing page banner"
src={content.settings.banner?.image
? content.settings.banner?.image
: '/calcom-banner-img.jpeg'}
class="object-cover rounded-full w-[250px] h-[250px] lg:w-[500px] lg:h-[350px]"
/>
</div>
</section>
</section>
{/if}
{/if}
@@ -5,6 +5,7 @@
import { getPageSection } from '$lib/utils/helpers/page';
import { Button } from '$lib/components/ui/button';
import type { Section } from '$lib/utils/types/page';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
let open = $state(false);
@@ -17,22 +18,13 @@
</script>
<nav
class="relative md:sticky top-0 z-[3000] w-full flex items-center justify-between bg-[#F4F4F4] border-b border-[#D0D1C9] py-4 px-6"
class="relative top-0 z-[3000] flex w-full items-center justify-between border-b border-[#D0D1C9] bg-[#F4F4F4] px-6 py-4 md:sticky"
>
<!-- Logo Section -->
<a href="/" class="flex items-center flex-col" title={`${seo?.settings?.title}`} id="logo">
{#if seo?.settings?.logo}
<img
src={seo?.settings?.logo || ''}
alt={`${seo?.settings?.title || ''} logo`}
class="rounded inline-block mx-auto"
/>
{/if}
<h1 class="text-xl font-semibold">University</h1>
</a>
<Logo src={seo?.settings.logo} alt={seo?.settings.title} className="w-24" addAcademy />
<ul
class="hidden lg:flex items-center gap-14 text-base font-semibold text-[#1F2937] list-none hover:no-underline"
class="hidden list-none items-center gap-14 text-base font-semibold text-[#1F2937] hover:no-underline lg:flex"
>
{#each content?.settings.navItems as navItem}
<li>
@@ -52,7 +44,7 @@
<Button
href="/courses"
class="bg-[#141414] rounded-full hidden lg:flex items-center gap-1 font-semibold text-white text-base"
class="hidden items-center gap-1 rounded-full bg-[#141414] text-base font-semibold text-white lg:flex"
>
Explore Courses
</Button>
@@ -64,17 +56,17 @@
<!-- Mobile Sidebar Menu (Visible only on mobile) -->
<ul
class={`fixed top-0 left-0 pt-10 h-full bg-white w-full transform hover:no-underline ${
class={`fixed left-0 top-0 h-full w-full transform bg-white pt-10 hover:no-underline ${
open ? 'translate-y-0 ' : '-translate-y-full'
} transition-transform duration-300 ease-in-out lg:hidden text-base font-semibold text-[#1F2937] list-none cursor-pointer`}
} cursor-pointer list-none text-base font-semibold text-[#1F2937] transition-transform duration-300 ease-in-out lg:hidden`}
>
<button onclick={toggleMenu} class="absolute right-4 top-4 lg:hidden z-20">
<button onclick={toggleMenu} class="absolute right-4 top-4 z-20 lg:hidden">
<Close size={24} />
</button>
<div class="divide-y divide-solid border-b">
{#each content?.settings.navItems as navItem}
<li class="py-4 px-6">
<li class="px-6 py-4">
<a href={navItem.link} onclick={toggleMenu}>{navItem.title}</a>
</li>
{/each}
@@ -82,7 +74,7 @@
<Button
href="/courses"
class="flex items-center mx-auto justify-center gap-1 py-4 px-6 bg-[#141414] w-fit my-4 font-semibold rounded-full text-white text-base"
class="mx-auto my-4 flex w-fit items-center justify-center gap-1 rounded-full bg-[#141414] px-6 py-4 text-base font-semibold text-white"
on:click={toggleMenu}
>
Explore Courses

Before

Width:  |  Height:  |  Size: 3.4 MiB

After

Width:  |  Height:  |  Size: 3.4 MiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

@@ -6,6 +6,7 @@
import { COURSE_TYPE } from '$lib/utils/constants/course';
import type { CourseFilterItem, Course } from '$lib/utils/types/course';
import type { Page } from '$lib/utils/types/page';
import { SECTION } from '@/utils/constants/page';
interface Props {
data: {
@@ -41,8 +42,8 @@
* Constants
*/
const section = $derived({
header: getPageSection(data.page, 'header'),
courses: getPageSection(data.page, 'courses')
header: getPageSection(data.page, SECTION.HERO),
courses: getPageSection(data.page, SECTION.COURSE)
});
/**
@@ -66,20 +67,20 @@
<div class="w-full">
{#if section.header?.show}
<section
style="background-image: url('calcom-background.svg');"
class="bg-cover bg-center flex items-center justify-center py-10 px-2 lg:px-14 min-h-[30vh]"
id="courses-header"
class="flex min-h-[30vh] items-center justify-center bg-cover bg-center px-2 py-10 lg:px-14"
>
<section class="flex flex-col text-center items-center gap-5 justify-center">
<div class="space-y-6 w-full mb-4">
<div class="bg-[#E5E7E0] py-1 px-4 md:border-b border-black rounded-sm w-fit mx-auto">
<p class="uppercase font-bold text-xs lg:text-base text-[#0F163F]">
<section class="flex flex-col items-center justify-center gap-5 text-center">
<div class="mb-4 w-full space-y-6">
<div class="mx-auto w-fit rounded-sm border-black bg-[#E5E7E0] px-4 py-1 md:border-b">
<p class="text-xs font-bold uppercase text-[#0F163F] lg:text-base">
{section.header.settings.title}
</p>
</div>
<p class="w-full text-5xl font-bold md:w-[70%] mx-auto">
<p class="mx-auto w-full text-5xl font-bold md:w-[70%]">
{section.header.settings.titleHighlight}
</p>
<p class="w-full font-semibold text-lg md:w-[70%] mx-auto">
<p class="mx-auto w-full text-lg font-semibold md:w-[70%]">
{section.header.settings.subtitle}
</p>
</div>
@@ -88,15 +89,15 @@
{/if}
{#if section.courses?.show}
<section id="course" class="px-2 pt-4 pb-20 h-full lg:px-16">
<div class="relative w-full flex flex-row items-start justify-start lg:gap-2 mt-10">
<section id="course" class="h-full px-2 pb-20 pt-4 lg:px-16">
<div class="relative mt-10 flex w-full flex-row items-start justify-start lg:gap-2">
<!-- filter start -->
<div class="hidden lg:block sticky top-0 min-w-max">
<p class="font-semibold mb-4">Filter by</p>
<div class="sticky top-0 hidden min-w-max lg:block">
<p class="mb-4 font-semibold">Filter by</p>
<div class="w-full space-y-2">
{#each filter as item}
<form
class="space-x-2 text-[#3C4043] font-medium border border-[#D0D1C9] bg-[#F4F4F4] rounded-md pl-4 pr-8 py-4"
class="space-x-2 rounded-md border border-[#D0D1C9] bg-[#F4F4F4] py-4 pl-4 pr-8 font-medium text-[#3C4043]"
>
<input
type="checkbox"
@@ -113,11 +114,11 @@
</div>
<!-- filter end -->
<div class="text-white rounded-3xl w-full h-full mx-auto">
<div class="mx-auto h-full w-full rounded-3xl text-white">
<!-- courses start -->
<div class="w-full max-h-screen p-4 md:p-8">
<div class="max-h-screen w-full p-4 md:p-8">
{#if data.courses.length > 0}
<section class="flex flex-wrap items-center justify-center gap-4 w-full">
<section class="flex w-full flex-wrap items-center justify-center gap-4">
{#each filteredCourses.slice(0, viewAll ? filteredCourses.length : 3) as course}
<CourseCard
slug={course.slug}
@@ -128,12 +129,12 @@
{/each}
</section>
{#if filteredCourses.length == 0}
<div class="px-4 w-full mx-auto">
<div class="mx-auto w-full px-4">
<EmptyState />
</div>
{/if}
{:else}
<div class="px-4 w-full mx-auto">
<div class="mx-auto w-full px-4">
<EmptyState />
</div>
{/if}
@@ -141,10 +142,10 @@
<!-- courses end -->
{#if filteredCourses.length > 2}
<div class="w-full flex items-center justify-center py-8">
<div class="flex w-full items-center justify-center py-8">
<Button
on:click={() => (viewAll = !viewAll)}
class="uppercase rounded-2xl py-6 hover:bg-white hover:text-black border-[1.5px] border-white text-white shadow-[0px_3px_white]"
class="rounded-2xl border-[1.5px] border-white py-6 uppercase text-white shadow-[0px_3px_white] hover:bg-white hover:text-black"
>
{viewAll === true ? 'Show less' : 'View more courses'}
</Button>
@@ -156,3 +157,9 @@
{/if}
<!-- course end -->
</div>
<style>
#courses-header {
background-image: url('$lib/components/cal/assets/calcom-background.svg');
}
</style>
@@ -11,6 +11,8 @@
import { courses } from '$lib/utils/stores/course';
import { CheckmarkFilled } from 'carbon-icons-svelte';
import Accordion from '../Accordion.svelte';
import bannerImg from '../assets/banner.jpeg';
import { SECTION } from '@/utils/constants/page';
/**
* State
@@ -21,11 +23,11 @@
/**
* Constants
*/
const coursesSection = $derived(getPageSection($homePage, 'courses'));
const aboutSection = $derived(getPageSection($homePage, 'about'));
const faqSection = $derived(getPageSection($homePage, 'faq'));
const testimonialSection = $derived(getPageSection($homePage, 'testimonial'));
const footerNoteSection = $derived(getPageSection($homePage, 'cta'));
const coursesSection = $derived(getPageSection($homePage, SECTION.COURSE));
const aboutSection = $derived(getPageSection($homePage, SECTION.ABOUT));
const faqSection = $derived(getPageSection($homePage, SECTION.FAQ));
const testimonialSection = $derived(getPageSection($homePage, SECTION.TESTIMONIAL));
const ctaSection = $derived(getPageSection($homePage, SECTION.CTA));
/**
* Functions
@@ -41,28 +43,28 @@
<!-- hero -->
{#if $homePage}
{@const content = getPageSection($homePage, 'header')}
{@const content = getPageSection($homePage, SECTION.HERO)}
{#if content?.show}
<section
class="font-matter flex items-start md:items-center justify-center py-10 lg:py-20 px-5 lg:px-20 h-full bg-[#F4F4F4]"
class="font-matter flex h-full items-start justify-center bg-[#F4F4F4] px-5 py-10 md:items-center lg:px-20 lg:py-20"
>
<section
class="flex flex-col-reverse lg:flex-row items-center justify-center lg:justify-between"
class="flex flex-col-reverse items-center justify-center lg:flex-row lg:justify-between"
>
<div class="text-center lg:text-start space-y-6 w-full mt-4 lg:mt-0">
<div class="mt-4 w-full space-y-6 text-center lg:mt-0 lg:text-start">
<div
class="bg-[#E5E7E0] py-1 px-4 md:border-b border-black rounded-sm w-fit mx-auto lg:mx-0"
class="mx-auto w-fit rounded-sm border-black bg-[#E5E7E0] px-4 py-1 md:border-b lg:mx-0"
>
<p class="uppercase font-bold text-xs lg:text-base text-[#0F163F]">
<p class="text-xs font-bold uppercase text-[#0F163F] lg:text-base">
{content.settings.title}
</p>
</div>
<p
class="text-4xl md:text-6xl font-black w-full mx-auto lg:mx-0 md:w-[90%] lg:w-[80%] capitalize"
class="mx-auto w-full text-4xl font-black capitalize md:w-[90%] md:text-6xl lg:mx-0 lg:w-[80%]"
>
{content.settings.titleHighlight}
</p>
<p class="font-semibold text-xl w-full md:w-[80%] mx-auto lg:mx-0 lg:w-[70%]">
<p class="mx-auto w-full text-xl font-semibold md:w-[80%] lg:mx-0 lg:w-[70%]">
{content.settings.subtitle}
</p>
@@ -75,10 +77,10 @@
</div>
<div
class="relative w-[250px] h-[250px] lg:w-[500px] lg:h-[350px] flex items-center justify-center"
class="relative flex h-[250px] w-[250px] items-center justify-center lg:h-[350px] lg:w-[500px]"
>
<svg
class="absolute -left-[15px] lg:-left-[60px] xl:-left-[100px] w-[270px] h-[270px] lg:w-[400px] xl:w-[520px] lg:h-[370px]"
class="absolute -left-[15px] h-[270px] w-[270px] lg:-left-[60px] lg:h-[370px] lg:w-[400px] xl:-left-[100px] xl:w-[520px]"
viewBox="0 0 360 360"
>
<circle
@@ -93,10 +95,8 @@
</svg>
<img
alt="landing page banner"
src={content.settings.banner?.image
? content.settings.banner?.image
: '/calcom-banner-img.jpeg'}
class="object-cover rounded-full w-[250px] h-[250px] lg:w-[500px] lg:h-[350px]"
src={content.settings.banner?.image || bannerImg}
class="h-[250px] w-[250px] rounded-full object-cover lg:h-[350px] lg:w-[500px]"
/>
</div>
</section>
@@ -106,26 +106,26 @@
<!-- courses -->
{#if coursesSection?.show}
<section id="course" class="flex items-center px-2 md:px-8 py-12 h-full bg-[#E5E7E0]">
<div class="text-white py-8 bg-black rounded-3xl w-full lg:w-[90%] mx-auto">
<section id="course" class="flex h-full items-center bg-[#E5E7E0] px-2 py-12 md:px-8">
<div class="mx-auto w-full rounded-3xl bg-black py-8 text-white lg:w-[90%]">
<div
class="flex flex-col md:flex-row items-center justify-center md:items-start gap-2 md:justify-between px-4 lg:px-8"
class="flex flex-col items-center justify-center gap-2 px-4 md:flex-row md:items-start md:justify-between lg:px-8"
>
<h1
class="text-white text-start text-2xl md:text-3xl lg:text-5xl mb-2 font-bold w-full md:w-[80%] lg:w-[60%]"
class="mb-2 w-full text-start text-2xl font-bold text-white md:w-[80%] md:text-3xl lg:w-[60%] lg:text-5xl"
>
{coursesSection.settings.title}
</h1>
<div class="hidden md:flex gap-2 items-center">
<div class="hidden items-center gap-2 md:flex">
<button
class="w-fit flex items-center justify-center border border-white rounded-full p-2 bg-transparent hover:bg-white hover:text-black"
class="flex w-fit items-center justify-center rounded-full border border-white bg-transparent p-2 hover:bg-white hover:text-black"
onclick={scrollLeft}
>
<ArrowLeft />
</button>
<button
class="w-fit flex items-center justify-center border border-white rounded-full p-2 bg-transparent hover:bg-white hover:text-black"
class="flex w-fit items-center justify-center rounded-full border border-white bg-transparent p-2 hover:bg-white hover:text-black"
onclick={scrollRight}
>
<ArrowRight />
@@ -133,11 +133,11 @@
</div>
</div>
<div class="w-full pl-4 md:pl-8 overflow-x-hidden">
<div class="w-full overflow-x-hidden pl-4 md:pl-8">
{#if $courses.length > 0}
<section
bind:this={scrollContainer}
class="flex items-center overflow-x-auto gap-4 py-4 pr-2 w-full scrollbar-hide"
class="scrollbar-hide flex w-full items-center gap-4 overflow-x-auto py-4 pr-2"
>
{#each $courses as course}
<CourseCard
@@ -160,15 +160,15 @@
{/each} -->
</section>
{:else}
<div class="w-full mx-auto">
<div class="mx-auto w-full">
<EmptyState className="dark:bg-[#232429] dark:border-[#EAEAEA]" />
</div>
{/if}
</div>
<div class="flex items-center justify-center w-full pt-4">
<div class="flex w-full items-center justify-center pt-4">
<Button
class="uppercase rounded-2xl py-6 px-10 bg-transparent border-[1.5px] border-white text-white shadow-[-1px_3px_#FFFFFF]"
class="rounded-2xl border-[1.5px] border-white bg-transparent px-10 py-6 uppercase text-white shadow-[-1px_3px_#FFFFFF]"
href="/courses"
>
Explore courses
@@ -180,24 +180,24 @@
<!-- about -->
{#if aboutSection?.show}
<section id="about">
<div class="relative px-4 md:px-10 py-10 h-full w-full mx-auto xl:w-[80%]">
<h1 class="text-center text-5xl mb-2 font-bold md:w-[60%] mx-auto">
<div class="relative mx-auto h-full w-full px-4 py-10 md:px-10 xl:w-[80%]">
<h1 class="mx-auto mb-2 text-center text-5xl font-bold md:w-[60%]">
{aboutSection.settings.title}
</h1>
<p class="text-center dark:text-white text-lg font-semibold md:w-[70%] mb-8 mx-auto">
<p class="mx-auto mb-8 text-center text-lg font-semibold dark:text-white md:w-[70%]">
{aboutSection.settings.subtitle}
</p>
<div>
<section class="flex flex-wrap items-start justify-center w-full gap-6 py-4">
<section class="flex w-full flex-wrap items-start justify-center gap-6 py-4">
{#each aboutSection.settings.benefits.list as item}
<div
class="flex flex-col gap-2 p-4 bg-white border border-[#282828] h-[130px] max-h-[180px] w-full max-w-full md:max-w-[320px] lg:max-w-[450px] rounded-lg overflow-hidden"
class="flex h-[130px] max-h-[180px] w-full max-w-full flex-col gap-2 overflow-hidden rounded-lg border border-[#282828] bg-white p-4 md:max-w-[320px] lg:max-w-[450px]"
>
<span class="flex items-center gap-2">
<CheckmarkFilled size={24} />
<p class="text-lg capitalize line-clamp-1">{item.title}</p>
<p class="line-clamp-1 text-lg capitalize">{item.title}</p>
</span>
<p class="text-sm text-[#878787] line-clamp-3">{item.subtitle}</p>
<p class="line-clamp-3 text-sm text-[#878787]">{item.subtitle}</p>
</div>
{/each}
</section>
@@ -208,17 +208,17 @@
<!-- faq -->
{#if faqSection?.show}
<section id="faq" class="relative px-2 pt-4 pb-20 h-full">
<section id="faq" class="relative h-full px-2 pb-20 pt-4">
<div class="absolute inset-0 bg-[#E5E7E0] opacity-60"></div>
<div class="relative">
<h1 class="text-center text-5xl font-bold mb-4">
<h1 class="mb-4 text-center text-5xl font-bold">
{faqSection.settings.title}
</h1>
<h1 class="text-center text-lg font-semibold mb-4 w-full md:w-[80%] lg:w-[60%] mx-auto">
<h1 class="mx-auto mb-4 w-full text-center text-lg font-semibold md:w-[80%] lg:w-[60%]">
{faqSection.settings.subtitle}
</h1>
<section class="py-4 px-2 space-y-10 w-full md:w-[80%] mx-auto">
<section class="mx-auto w-full space-y-10 px-2 py-4 md:w-[80%]">
{#each faqSection.settings.questions as faq}
<Accordion title={faq.title} content={faq.content} />
{/each}
@@ -229,18 +229,18 @@
<!-- testimonial -->
{#if testimonialSection?.show}
<section id="testimonial" class="bg-[#F4F4F4] px-4 lg:p-14 h-fit">
<section class="coolumns-1 md:columns-3 space-y-6 overflow-visible">
<section id="testimonial" class="h-fit bg-[#F4F4F4] px-4 lg:p-14">
<section class="coolumns-1 space-y-6 overflow-visible md:columns-3">
{#each testimonialSection.settings.list as item}
<div class="break-inside-avoid overflow-visible">
<section
class="cursor-pointer flex flex-col relative overflow-visible px-6 pt-6 gap-4 p-4 bg-white border border-[#D7D7D7] rounded-3xl w-full h-fit max-w-full sm:max-w-[320px] xl:max-w-[400px]"
class="relative flex h-fit w-full max-w-full cursor-pointer flex-col gap-4 overflow-visible rounded-3xl border border-[#D7D7D7] bg-white p-4 px-6 pt-6 sm:max-w-[320px] xl:max-w-[400px]"
>
<div class="flex items-center gap-2">
<img
src={item.banner ?? '/classroomio-course-img-template.jpg'}
src={item.banner ?? '/course-banner.jpg'}
alt=""
class="w-10 h-10 rounded-full"
class="h-10 w-10 rounded-full"
/>
<span class="flex flex-col items-start">
<p class="text-sm font-bold">{item.name}</p>
@@ -259,30 +259,31 @@
</section>
{/if}
<!-- footernote -->
{#if footerNoteSection?.show}
<section class="w-full bg-[#F4F4F4] py-16 px-4 lg:px-10">
<!-- CTA -->
{#if ctaSection?.show}
<section class="w-full bg-[#F4F4F4] px-4 py-16 lg:px-10">
<div
class="relative flex flex-col md:flex-row min-h-40 justify-center border-2 border-[#141414] rounded-xl divide-x divide-transparent w-full lg:w-[80%] mx-auto"
class="min-h-40 relative mx-auto flex w-full flex-col justify-center divide-x divide-transparent rounded-xl border-2 border-[#141414] md:flex-row lg:w-[80%]"
>
<div
class="bg-[#E5E7E0] text-center md:text-start rounded-t-xl md:rounded-l-xl flex justify-center capitalize items-center py-20 px-4 lg:px-10 w-full md:w-[65%] min-h-40 text-4xl font-bold"
class="min-h-40 flex w-full items-center justify-center rounded-t-xl bg-[#E5E7E0] px-4 py-20 text-center text-4xl font-bold capitalize md:w-[65%] md:rounded-l-xl md:text-start lg:px-10"
>
<h1 class="leading-normal">
{footerNoteSection.settings.title}<br />{footerNoteSection.settings.titleHighlight}
{ctaSection.settings.title}<br />{ctaSection.settings.titleHighlight}
</h1>
</div>
<div
class="rounded-b-xl md:rounded-r-xl flex items-center justify-center py-16 px-4 bg-white w-full md:w-[35%] min-h-40 text-lg font-semibold"
class="min-h-40 flex w-full items-center justify-center rounded-b-xl bg-white px-4 py-16 text-lg font-semibold md:w-[35%] md:rounded-r-xl"
>
<p>
{footerNoteSection.settings.subtitle}
{ctaSection.settings.subtitle}
</p>
<Button
class="absolute -bottom-5 uppercase rounded-2xl py-6 bg-white hover:bg-white hover:text-black border-[1.5px] border-[#141414] text-[#141414] shadow-[0px_3px_#141414]"
class="absolute -bottom-5 rounded-2xl border-[1.5px] border-[#141414] bg-white py-6 uppercase text-[#141414] shadow-[0px_3px_#141414] hover:bg-white hover:text-black"
href={ctaSection.settings?.button?.link}
>
{footerNoteSection.settings.buttonLabel}
{ctaSection.settings?.button?.label}
</Button>
</div>
</div>
@@ -3,11 +3,11 @@
</script>
<div
class="hover:scale-95 border border-[#EAEAEA] bg-[#FCFCFC] dark:border-neutral-600 rounded w-full max-w-[300px] space-y-4 p-4"
class="w-full max-w-[300px] space-y-4 rounded border border-[#EAEAEA] bg-[#FCFCFC] p-4 hover:scale-95 dark:border-neutral-600"
>
<img src="/classroomio-course-img-template.jpg" alt="tutor" class="object-cover w-full h-full" />
<img src="/course-banner.jpg" alt="tutor" class="h-full w-full object-cover" />
<div class="w-full space-y-2">
<p class="text-lg font-semibold line-clamp-2">
<p class="line-clamp-2 text-lg font-semibold">
How to Choose the Right Online Course to Boost Your Career
</p>
<span class="inline-flex items-center gap-1 text-[#282828]">
@@ -1,13 +1,4 @@
<script lang="ts">
import RadioButtonChecked from 'carbon-icons-svelte/lib/RadioButtonChecked.svelte';
import GrowthIcon from 'carbon-icons-svelte/lib/Growth.svelte';
import UserProfileIcon from 'carbon-icons-svelte/lib/UserProfile.svelte';
const COURSE_TYPE = {
LIVE_CLASS: 'live',
SELF_PACED: 'paced'
};
interface Props {
bannerImage: string | undefined;
slug?: string;
@@ -26,76 +17,37 @@
description = '',
cost = 0,
totalLessons = 0,
currency = 'USD',
type
currency = 'USD'
}: Props = $props();
let target: any = $state();
function getCourseUrl() {
return `/course/${slug}`;
}
const COURSE_TAG: Record<
string,
{
style: string;
label: string;
icon: any;
iconStyle?: string;
}
> = {
[COURSE_TYPE.LIVE_CLASS]: {
style: '',
label: 'live',
icon: RadioButtonChecked,
iconStyle: 'text-red-700'
},
[COURSE_TYPE.SELF_PACED]: {
style: '',
label: 'paced',
icon: UserProfileIcon,
iconStyle: 'text-primary-700'
},
SPECIALIZATION: {
style: '',
label: 'specialized',
icon: GrowthIcon
}
};
</script>
<a
rel="prefetch"
bind:this={target}
href={getCourseUrl()}
class="text-black border border-gray dark:border-neutral-600 rounded w-full h-fit md:h-[350px] min-w-[250px] max-w-[300px] relative hover:scale-95 transition-all ease-in-out"
class="border-gray relative h-fit w-full min-w-[250px] max-w-[300px] rounded border text-black transition-all ease-in-out hover:scale-95 dark:border-neutral-600 md:h-[350px]"
>
<div class="p-4">
<div class="relative mb-5">
<img
src={bannerImage || '/classroomio-course-img-template.jpg'}
src={bannerImage || '/course-banner.jpg'}
alt="Course Logo"
class="h-[170px] w-full rounded dark:border dark:border-neutral-600 relative"
class="relative h-[170px] w-full rounded dark:border dark:border-neutral-600"
/>
{#if type}
{@const tag = COURSE_TAG[type]}
<span
class="absolute bottom-2 left-2 z-10 text-xs capitalize bg-primary-50 rounded-sm p-1 flex items-center gap-1 font-mono"
>
<tag.icon size={16} class={tag.iconStyle} />
{tag.label}
</span>
{/if}
</div>
<h3 class="text-xl dark:text-white font-semibold line-clamp-1">{title}</h3>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-300 description">
<h3 class="line-clamp-1 text-xl font-semibold dark:text-white">{title}</h3>
<p class="description mt-2 text-sm text-gray-500 dark:text-gray-300">
{description}
</p>
</div>
<div class="py-4 mx-2 border-t border-gray dark:border-neutral-600 flex justify-between">
<div class="border-gray mx-2 flex justify-between border-t py-4 dark:border-neutral-600">
<div>
<p class="text-xs pl-2 dark:text-white">
<p class="pl-2 text-xs dark:text-white">
{totalLessons}
lessons
</p>
@@ -5,65 +5,71 @@
import LogoLinkedin from 'carbon-icons-svelte/lib/LogoLinkedin.svelte';
import LogoInstagram from 'carbon-icons-svelte/lib/LogoInstagram.svelte';
import { LogoYoutube } from 'carbon-icons-svelte';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
import { getPageSection } from '@/utils/helpers/page';
import { sharedPage } from '@/utils/stores/pages';
import { SECTION } from '@/utils/constants/page';
const content = $derived(getPageSection($sharedPage, 'footer'));
const seo = $derived(getPageSection($sharedPage, 'seo'));
const content = $derived(getPageSection($sharedPage, SECTION.FOOTER));
const seo = $derived(getPageSection($sharedPage, SECTION.SEO));
</script>
{#if content?.show}
<nav
class="bg-white w-full flex flex-col md:flex-row items-start md:items-center gap-4 md:justify-between py-4 px-6"
class="flex w-full flex-col items-start gap-4 bg-white px-6 py-4 md:flex-row md:items-center md:justify-between"
>
<div class="logo w-full md:w-fit flex items-center justify-between">
<a href="/" title={`Go to ${seo?.settings.title} Home`} id="logo" data-hveid="8">
<img
src={seo?.settings.logo}
alt={`${seo?.settings.title} logo`}
class="rounded w-9 inline-block mx-auto"
data-atf="1"
/>
</a>
<div class="logo flex w-full items-center justify-between md:w-fit">
<Logo src={seo?.settings.logo} alt={seo?.settings.title} className="" />
<a href="/#" class="md:hidden flex items-center gap-1">
<p class=" text-[#0233BD] text-base underline font-semibold">Built on ClassroomIO</p>
<a
href="https://classroomio.com"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-1 md:hidden"
>
<p class=" text-base font-semibold text-[#0233BD] underline">Built on ClassroomIO</p>
</a>
</div>
<ul
class="my-4 md:my-0 mx-auto flex flex-row items-center justify-center gap-8 underline border rounded-full px-6 py-2"
class="mx-auto my-4 flex flex-row items-center justify-center gap-8 rounded-full border px-6 py-2 underline md:my-0"
>
{#if content.settings.instagram}
<a href={content.settings.instagram} target="_blank" title="instragram"
><LogoInstagram class="fill-red-500 " size={24} /></a
>
<a href={content.settings.instagram} target="_blank" title="instragram">
<LogoInstagram class="fill-red-500 " size={24} />
</a>
{/if}
{#if content.settings.twitter}
<a href={content.settings.twitter} target="_blank" title="twitter"><LogoX /></a>
{/if}
{#if content.settings?.youtube}
<a href={content.settings?.youtube} target="_blank"
><LogoYoutube class="fill-red-700" size={24} /></a
>{/if}
<a href={content.settings?.youtube} target="_blank">
<LogoYoutube class="fill-red-700" size={24} />
</a>
{/if}
{#if content.settings?.discord}
<a href={content.settings.discord} target="_blank" title="discord"
><LogoDiscord class="fill-blue-800" size={24} /></a
>
<a href={content.settings.discord} target="_blank" title="discord">
<LogoDiscord class="fill-blue-800" size={24} />
</a>
{/if}
{#if content.settings.linkedin}
<a href={content.settings.linkedin} target="_blank" title="linkedin"
><LogoLinkedin class="fill-blue-800" size={24} /></a
>
<a href={content.settings.linkedin} target="_blank" title="linkedin">
<LogoLinkedin class="fill-blue-800" size={24} />
</a>
{/if}
{#if content.settings.facebook}
<a href={content.settings.facebook} target="_blank" title="facebook"
><LogoFacebook class="fill-blue-800" size={24} /></a
>
<a href={content.settings.facebook} target="_blank" title="facebook">
<LogoFacebook class="fill-blue-800" size={24} />
</a>
{/if}
</ul>
<a href="/#" class="hidden md:flex items-center gap-1">
<p class=" text-[#0233BD] text-base underline font-semibold">Built on ClassroomIO</p>
<a
href="https://classroomio.com"
target="_blank"
rel="noopener noreferrer"
class="hidden items-center gap-1 md:flex"
>
<p class=" text-base font-semibold text-[#0233BD] underline">Built on ClassroomIO</p>
</a>
</nav>
{/if}
@@ -12,19 +12,19 @@
</script>
<div
class="flex flex-col md:flex-row items-start gap-3 border max-h-full max-w-[300px] md:max-h-[250px] md:max-w-[500px] lg:max-w-[400px] xl:max-w-[550px] rounded-md p-3"
class="flex max-h-full max-w-[300px] flex-col items-start gap-3 rounded-md border p-3 md:max-h-[250px] md:max-w-[500px] md:flex-row lg:max-w-[400px] xl:max-w-[550px]"
>
<div class="bg-[#FFE8FF] w-full h-full rounded-md p-2">
<div class="h-full w-full rounded-md bg-[#FFE8FF] p-2">
<img
src={banner ? banner : './classroomio-course-img-template.jpg'}
src={banner ? banner : './course-banner.jpg'}
alt="tutor"
class="object-cover w-full h-full rounded"
class="h-full w-full rounded object-cover"
/>
</div>
<div class="w-full space-y-1">
<p class="text-xl font-semibold">{name}</p>
<RatingComponent {rating} />
<p class="text-[#656565] text-sm line-clamp-5 md:line-clamp-4 xl:line-clamp-6">
<p class="line-clamp-5 text-sm text-[#656565] md:line-clamp-4 xl:line-clamp-6">
{description}
</p>
</div>
@@ -1,7 +1,6 @@
<script lang="ts">
import { ArrowRight, ArrowUpRight, Close } from 'carbon-icons-svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
import Menu from 'carbon-icons-svelte/lib/Menu.svelte';
import { getPageSection } from '@/utils/helpers/page';
import { sharedPage } from '@/utils/stores/pages';
@@ -18,19 +17,11 @@
}
</script>
<nav class={`relative w-full flex items-center justify-between py-4 px-6 bg-white dark:bg-black`}>
<nav
class="relative flex w-full items-center justify-between border-b-2 bg-white px-6 py-4 dark:bg-black"
>
<!-- Logo Section -->
<div class="logo">
<a href="/" title={`${seo?.settings?.title}`} id="logo">
{#if seo?.settings?.logo}
<img
src={seo?.settings?.logo}
alt={`${seo.settings.title} logo`}
class="rounded w-9 inline-block mx-auto"
/>
{/if}
</a>
</div>
<Logo src={seo?.settings.logo} alt={seo?.settings.title} className="w-24" addAcademy />
<!-- Mobile Menu Button (Visible only on mobile and when logged in) -->
@@ -39,16 +30,17 @@
</button>
<!-- Desktop Navigation Menu (Hidden on mobile) -->
<ul class="hidden lg:flex items-center space-x-8 text-base font-bold text-[#1F2937] list-none">
<ul class="hidden list-none items-center space-x-8 text-base font-bold text-[#1F2937] lg:flex">
{#each content?.settings.navItems as navItem}
<li class="hover:text-[#CE02CE]">
<a
href={navItem.link}
target={navItem.redirect ? '_blank' : undefined}
class="flex items-center gap-1"
>{navItem.title}
>
{navItem.title}
{#if navItem.redirect}
<ArrowUpRight size={12} />
<ArrowUpRight />
{/if}
</a>
</li>
@@ -57,34 +49,34 @@
<!-- Mobile Sidebar Menu (Visible only on mobile) -->
<ul
class={`fixed top-0 left-0 pt-10 h-full bg-white w-full transform hover:no-underline ${
class={`fixed left-0 top-0 h-full w-full transform bg-white pt-10 hover:no-underline ${
open ? 'translate-y-0' : '-translate-y-full'
} transition-transform duration-300 ease-in-out lg:hidden text-base font-bold text-[#1F2937] list-none cursor-pointer`}
} cursor-pointer list-none text-base font-bold text-[#1F2937] transition-transform duration-300 ease-in-out lg:hidden`}
>
<button onclick={toggleMenu} class="absolute right-4 top-4 lg:hidden z-20">
<button onclick={toggleMenu} class="absolute right-4 top-4 z-20 lg:hidden">
<Close size={24} />
</button>
{#each content?.settings.navItems as navItem}
<li class="py-4 px-6 border-b">
<li class="border-b px-6 py-4">
<a href={navItem.link} onclick={toggleMenu} class="flex items-center gap-1"
>{navItem.title}
{#if navItem.redirect}
<ArrowUpRight size={12} />
<ArrowUpRight />
{/if}</a
>
</li>
{/each}
<a href="/courses" class="flex items-center gap-1 py-4 px-6 border-b" onclick={toggleMenu}>
<p class="font-bold text-[#1F2937] text-base">Start Learning</p>
<a href="/courses" class="flex items-center gap-1 border-b px-6 py-4" onclick={toggleMenu}>
<p class="text-base font-bold text-[#1F2937]">Start Learning</p>
<ArrowRight size={16} class="fill-[#1F2937] font-bold" />
</a>
</ul>
<!-- Learn with Me Button (Visible on desktop when logged in) -->
<a href="/courses" class="hidden lg:flex items-center gap-1">
<p class="font-bold text-[#1F2937] text-base">Start Learning</p>
<a href="/courses" class="hidden items-center gap-1 lg:flex">
<p class="text-base font-bold text-[#1F2937]">Start Learning</p>
<ArrowRight size={16} class="fill-[#1F2937] font-bold" />
</a>
</nav>
@@ -1,6 +1,6 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { cn } from '$lib/utils.js';
import { cn } from '$lib/utils';
interface Props {
onClick?: () => void;
@@ -15,7 +15,7 @@
<Button
{href}
class={cn(
'bg-classic rounded text-white font-semibold p-2 hover:scale-90 hover:bg-classic',
'bg-classic rounded text-white font-semibold p-2 hover:bg-classic/90 hover:scale-95 transition-all duration-300',
className
)}
onclick={onClick}
@@ -9,20 +9,16 @@
</script>
<section
class="flex flex-col justify-between relative border border-[#EAEAEA] px-6 pt-6 space-y-4 bg-[#FDFDFD] rounded w-full h-full lg:h-[280px] max-w-[400px] xl:max-w-[500px]"
class="relative flex h-full w-full max-w-[400px] flex-col justify-between space-y-4 rounded border border-[#EAEAEA] bg-[#FDFDFD] px-6 pt-6 lg:h-[280px] xl:max-w-[500px]"
>
<img src="/quote.svg" alt="" class="w-6 h-6 rounded-full absolute top-6 left-8" />
<img src="/quote.svg" alt="" class="absolute left-8 top-6 h-6 w-6 rounded-full" />
<div>
<p class="my-6 font-bold line-clamp-5">
<p class="my-6 line-clamp-5 font-bold">
{description}
</p>
</div>
<div class="flex items-center p-4 gap-4 border-t border-[#EAEAEA]">
<img
src={banner ? banner : '/classroomio-course-img-template.jpg'}
alt=""
class="w-6 h-6 rounded-full"
/>
<div class="flex items-center gap-4 border-t border-[#EAEAEA] p-4">
<img src={banner ? banner : '/course-banner.jpg'} alt="" class="h-6 w-6 rounded-full" />
<span>
<p class="text-sm font-bold">{name}</p>
</span>
@@ -6,6 +6,8 @@
import { getPageSection } from '@/utils/helpers/page';
import type { Course, CourseFilterItem } from '@/utils/types/course';
import type { Page } from '$lib/utils/types/page';
import PrimaryButton from '../PrimaryButton.svelte';
import { SECTION } from '@/utils/constants/page';
interface Props {
data: {
@@ -41,8 +43,8 @@
* Constants
*/
const section = $derived({
header: getPageSection(data.page, 'header'),
courses: getPageSection(data.page, 'courses')
header: getPageSection(data.page, SECTION.HERO),
courses: getPageSection(data.page, SECTION.COURSE)
});
/**
@@ -65,31 +67,33 @@
<section class="overflow-x-hidden">
{#if section.header?.show}
<div
class="h-full pt-20 pb-36 flex flex-col items-center justify-center text-center w-full bg-gradient-to-r from-[#f0a5f11a] via-white to-[#f6e7f61a]"
class="flex h-full w-full flex-col items-center justify-center bg-gradient-to-r from-[#F785F9] via-white to-[#fff] pb-36 pt-20 text-center"
>
<h1 class="font-bold text-5xl mb-4 w-full md:w-[50%] text-center">
<h1 class="mb-4 w-full text-center text-5xl font-bold md:w-[50%]">
{section.header.settings.title}
<span class="text-[#CE02CE]/80 font-bold text-5xl">
<span class="text-classic/80 text-5xl font-bold">
{section.header.settings.titleHighlight}</span
>
</h1>
<p class="md:text-xl text-[#878787] w-full md:w-[60%] lg:w-[40%] text-center">
<p class="w-full text-center text-[#878787] md:w-[60%] md:text-xl lg:w-[40%]">
{section.header.settings.subtitle}
</p>
</div>
{/if}
{#if section.courses?.show}
<section id="course" class="px-4 pt-4 pb-20 h-full bg-white">
<h1 class="text-center text-3xl text-[#3F3F3F] font-bold mb-6 w-full md:w-[60%] mx-auto">
<section id="course" class="h-full bg-white px-4 pb-20 pt-4">
<h1
class="mx-auto my-10 mb-20 w-full text-center text-3xl font-bold text-[#3F3F3F] md:w-[60%]"
>
{section.courses.settings.title}
</h1>
<div class="w-full md:w-[90%] mx-auto">
<div class="mx-auto w-full md:w-[90%]">
{#if data.courses.length > 0}
<div class="w-full flex gap-4 lg:ml-[5%]">
<div class="hidden lg:block w-fit space-y-2">
<p class="font-medium text-[#3C4043] uppercase">Filter</p>
<div class="w-max border border-[#EAEAEA] p-6 space-y-8">
<div class="flex w-full gap-4 lg:ml-[5%]">
<div class="hidden w-fit space-y-2 lg:block">
<p class="font-medium uppercase text-[#3C4043]">Filter</p>
<div class="w-max space-y-8 border border-[#EAEAEA] p-6">
{#each filter as item}
<form class="space-x-2 text-[#3C4043]">
<input
@@ -104,7 +108,7 @@
</div>
</div>
<section
class="grid place-items-center grid-cols-1 md:grid-cols-2 xl:grid-cols-3 w-full"
class="grid w-full grid-cols-1 place-items-center md:grid-cols-2 xl:grid-cols-3"
>
{#each filteredCourses.slice(0, viewAll ? filteredCourses.length : 3) as courseData}
<CourseCard
@@ -119,22 +123,23 @@
/>
{/each}
{#if filteredCourses.length == 0}
<div class="px-4 w-full mx-auto">
<div class="mx-auto w-full px-4">
<EmptyState headerClassName="text-[#CE02CE]" />
</div>
{/if}
</section>
{#if filteredCourses.length > 3}
<div class="w-full flex items-center justify-center my-5">
<Button
class="text-lg font-semibold text-white !bg-[#CE02CE]"
on:click={() => (viewAll = !viewAll)}>View more programs</Button
>
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
class="text-lg font-semibold text-white "
label="View more programs"
onClick={() => (viewAll = !viewAll)}
/>
</div>
{/if}
</div>
{:else}
<div class="px-4 w-full lg:w-[70%] mx-auto">
<div class="mx-auto w-full px-4 lg:w-[70%]">
<EmptyState headerClassName="text-[#CE02CE]" />
</div>
{/if}
@@ -11,55 +11,49 @@
import Accordion from '../Accordion.svelte';
import TestimonialCard from '../TestimonialCard.svelte';
import BlogCard from '../BlogCard.svelte';
import { SECTION } from '@/utils/constants/page';
const faqSection = $derived(getPageSection($homePage, 'faq'));
const testimonialSection = $derived(getPageSection($homePage, 'testimonial'));
const aboutSection = $derived(getPageSection($homePage, 'about'));
const courseSection = $derived(getPageSection($homePage, 'courses'));
const instructorSection = $derived(getPageSection($homePage, 'instructors'));
const footerNoteSection = $derived(getPageSection($homePage, 'cta'));
const faqSection = $derived(getPageSection($homePage, SECTION.FAQ));
const testimonialSection = $derived(getPageSection($homePage, SECTION.TESTIMONIAL));
const aboutSection = $derived(getPageSection($homePage, SECTION.ABOUT));
const courseSection = $derived(getPageSection($homePage, SECTION.COURSE));
const instructorSection = $derived(getPageSection($homePage, SECTION.INSTRUCTORS));
const ctaSection = $derived(getPageSection($homePage, SECTION.CTA));
const DISPLAY_COURSE = {
ALL: 'all',
COURSE: 'course',
PATH: 'path'
};
let viewAll = $state(false);
let type = DISPLAY_COURSE.ALL;
</script>
<main>
<!-- <Hero /> -->
{#if $homePage}
{@const content = getPageSection($homePage, 'header')}
{@const content = getPageSection($homePage, SECTION.HERO)}
{#if content?.show}
<section class="flex items-start justify-center pb-20 lg:pt-10 pt-4 px-6 md:px-14 h-full">
<section class="flex flex-col-reverse md:flex-row items-start gap-10 md:justify-between">
<div class="space-y-6 w-full">
<p class="text-4xl xl:text-6xl font-bold w-full xl:w-[90%]">
<section class="flex h-full items-start justify-center px-6 pb-20 md:px-14">
<section class="flex flex-col-reverse items-center gap-10 md:flex-row md:justify-between">
<div class="flex h-full w-full flex-col gap-4 pt-4">
<p class="w-full text-4xl font-bold xl:w-[90%] xl:text-6xl">
{content.settings.title}
<span class="text-[#CE02CE]">
{content.settings.titleHighlight}
</span>
</p>
<p class="w-full lg:w-[70%] text-[#878787] xl:text-lg">
<p class="w-full text-[#878787] lg:w-[70%] xl:text-lg">
{content.settings.subtitle}
</p>
<PrimaryButton
onClick={() => {
goto(content.settings.action.link);
}}
href={content.settings.action.link}
label={content.settings.action.label}
class="w-fit"
/>
</div>
<div
class="rounded-md md:h-[300px] xl:h-[500px] max-h-full w-full md:w-1/2 lg:w-4/5 xl:w-[800px] md:max-w-[800px] flex"
class="flex max-h-full w-full md:w-1/2 md:max-w-[800px] lg:w-4/5 xl:h-[500px] xl:w-[800px]"
>
<img
style="min-width:280px; min-height:200px"
alt="landing page banner"
src={content.settings.banner.image ? content.settings.banner.image : banner}
class="object-cover mt-2 h-full w-full rounded-md md:mt-0"
src={content.settings.banner.image || banner}
class="mt-2 h-full min-h-[200px] w-full min-w-[280px] object-cover md:mt-0"
/>
</div>
</section>
@@ -72,20 +66,20 @@
{#if aboutSection?.show}
<section
id="about"
class="flex items-start justify-center px-6 md:px-10 lg:px-14 h-full bg-white border-b-2 pb-20 lg:pt-20 pt-4"
class="flex h-full items-start justify-center border-b-2 bg-white px-6 pb-20 pt-4 md:px-10 lg:px-14 lg:pt-20"
>
<section class="flex flex-col lg:flex-row gap-4 items-center lg:items-start justify-center">
<div class="w-full lg:w-[60%] space-y-4">
<section class="flex flex-col items-center justify-center gap-4 lg:flex-row lg:items-start">
<div class="w-full space-y-4 lg:w-[60%]">
<p class="text-4xl font-bold text-[#3F3F3F]">{aboutSection.settings.title}</p>
<p class="w-full lg:w-[80%] text-base leading-7 text-[#878787]">
<p class="w-full text-base leading-7 text-[#878787] lg:w-[80%]">
{aboutSection.settings.content}
</p>
</div>
{#if aboutSection.settings.benefits}
<div class="max-w-[400px] min-h-fit">
<div class="min-h-fit max-w-[400px]">
{#each aboutSection.settings.benefits.list as item, index}
<div
class="benefit-card w-full text-center max-w-[200px] p-4 font-semibold mb-9 border-b-4 border-[#CE02CE] bg-white rounded-b-lg shadow-lg"
class="benefit-card mb-9 w-full max-w-[200px] rounded-b-lg border-b-4 border-[#CE02CE] bg-white p-4 text-center font-semibold shadow-lg"
class:left={index % 2 === 0}
class:right={index % 2 !== 0}
>
@@ -97,7 +91,7 @@
<img
src={aboutSection.settings.imageUrl}
alt="Our Story"
class="rounded-2xl max-h-[450px]"
class="max-h-[450px] rounded-2xl"
/>
{/if}
</section>
@@ -106,17 +100,17 @@
<!-- courses -->
{#if courseSection?.show}
<section id="course" class="px-4 py-6 pb-20 h-full bg-white">
<h1 class="text-center text-3xl text-[#3F3F3F] font-bold mb-4">
<section id="course" class="h-full bg-white px-4 py-6 pb-20">
<h1 class="mb-4 text-center text-3xl font-bold text-[#3F3F3F]">
{courseSection.settings.title}
</h1>
<div class="w-full md:w-[90%] mx-auto">
<div class="mx-auto w-full md:w-[90%]">
{#if $courses.length > 0}
<section class="flex flex-wrap items-center mx-auto w-fit gap-4">
<section class="mx-auto flex w-fit flex-wrap items-center gap-4">
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<CourseCard
slug={courseData.slug}
bannerImage={courseData.banner || '/classroomio-course-img-template.jpg'}
bannerImage={courseData.banner || '/course-banner.jpg'}
title={courseData.title}
type={courseData.type}
description={courseData.description}
@@ -127,16 +121,16 @@
{/each}
</section>
{#if $courses.length > 3}
<div class="w-full flex items-center justify-center my-5">
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
class="text-lg font-semibold text-white bg-[#CE02CE]"
class="bg-[#CE02CE] text-lg font-semibold text-white"
onClick={() => (viewAll = !viewAll)}
label="View more programs"
/>
</div>
{/if}
{:else}
<div class="px-4 w-full lg:w-[70%] mx-auto">
<div class="mx-auto w-full px-4 lg:w-[70%]">
<EmptyState headerClassName="text-[#CE02CE]" />
</div>
{/if}
@@ -145,19 +139,19 @@
{/if}
<!-- instructors -->
{#if instructorSection?.show}
<section class="px-4 lg:px-14 pt-4 pb-20 h-full bg-white">
<div class="w-full xl:w-[90%] mx-auto">
<h1 class=" text-center lg:text-start text-3xl text-[#3F3F3F] font-bold mb-4">
<section class="h-full bg-white px-4 pb-20 pt-4 lg:px-14">
<div class="mx-auto w-full xl:w-[90%]">
<h1 class=" mb-4 text-center text-3xl font-bold text-[#3F3F3F] lg:text-start">
Meet some of our Instructors
</h1>
<section class="grid place-items-center grid-cols-1 md:grid-cols-2 gap-2 w-full">
<section class="grid w-full grid-cols-1 place-items-center gap-2 md:grid-cols-2">
{#each instructorSection.settings.list as item}
<InstructorCard name={item.name} description={item.description} rating={item.rating} />
{/each}
</section>
<div class="w-full flex justify-center md:justify-end px-4 mt-6">
<div class="mt-6 flex w-full justify-center px-4 md:justify-end">
<PrimaryButton
class="text-lg font-semibold text-white p-6 rounded"
class="rounded p-6 text-lg font-semibold text-white"
label="Start learning & Explore courses"
/>
</div>
@@ -166,11 +160,11 @@
{/if}
<!-- faq -->
{#if faqSection?.show}
<section class="px-4 py-6 pb-20 h-full bg-[#F9F9F9]">
<h1 class="text-center text-3xl text-[#3F3F3F] font-bold mb-4">
<section class="h-full bg-[#F9F9F9] px-4 py-6 pb-20">
<h1 class="mb-4 text-center text-3xl font-bold text-[#3F3F3F]">
{faqSection.settings.title}
</h1>
<section class="p-2 space-y-10 w-full md:w-[80%] mx-auto">
<section class="mx-auto w-full space-y-10 p-2 md:w-[80%]">
{#each faqSection.settings.questions as faq}
<Accordion title={faq.title} content={faq.content} />
{/each}
@@ -179,39 +173,39 @@
{/if}
<!-- testimonial -->
{#if testimonialSection?.show}
<section id="testimonial" class="px-4 lg:px-14 pt-4 pb-20 h-full bg-white">
<h1 class="text-center text-3xl text-[#3F3F3F] font-bold mb-4">
<section id="testimonial" class="h-full bg-white px-4 pb-20 pt-4 lg:px-14">
<h1 class="mb-4 text-center text-3xl font-bold text-[#3F3F3F]">
Words from our past learners
</h1>
<section class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 w-full">
<section class="grid w-full grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{#each testimonialSection.settings.list as item}
<TestimonialCard description={item.description} name={item.name} />
{/each}
</section>
</section>
{/if}
<!-- footerNote -->
{#if footerNoteSection?.show}
<section class="flex flex-col items-center justify-between px-6 lg:px-10 py-20 bg-classic">
<div class="flex items-center justify-center w-full">
<p class="text-4xl text-white text-center font-bold w-full">
{footerNoteSection.settings.title}
<!-- cta -->
{#if ctaSection?.show}
<section class="bg-classic flex flex-col items-center justify-between px-6 py-20 lg:px-10">
<div class="flex w-full items-center justify-center">
<p class="w-full text-center text-4xl font-bold text-white">
{ctaSection.settings.title}
</p>
</div>
<div class="w-full flex items-center justify-center my-5">
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
onClick={() => goto('/courses')}
class="rounded-sm font-bold text-lg bg-classic-secondary border hover:scale-95 hover:bg-classic-secondary border-gray-100"
label={footerNoteSection.settings.buttonLabel}
href={ctaSection.settings?.button?.link}
class="bg-classic-secondary hover:bg-classic-secondary text-classic rounded-sm border border-gray-100 text-lg font-bold hover:scale-95"
label={ctaSection.settings?.button?.label}
/>
</div>
</section>
{/if}
<!-- blog -->
<section id="course" class="px-4 py-6 pb-20 h-full bg-white">
<h1 class="text-center text-3xl text-[#3F3F3F] font-bold mb-4">Latest blog Post</h1>
<section class="flex flex-wrap items-center mx-auto gap-2 w-full">
<section id="course" class="hidden h-full bg-white px-4 py-6 pb-20">
<h1 class="mb-4 text-center text-3xl font-bold text-[#3F3F3F]">Latest blog Post</h1>
<section class="mx-auto flex w-full flex-wrap items-center gap-2">
<BlogCard />
<BlogCard />
<BlogCard />
@@ -0,0 +1,53 @@
<script lang="ts">
import { Add, Subtract } from 'carbon-icons-svelte';
interface Props {
title?: string;
content?: string;
}
let { title = '', content = '' }: Props = $props();
let isOpen = $state(false);
let contentHeight = $state('0px'); // Track the height of the content
let contentRef: HTMLDivElement = $state(); // Reference to the content div
function toggleAccordion() {
isOpen = !isOpen;
if (isOpen) {
// Measure and set the height when opening
contentHeight = `${contentRef?.scrollHeight}px`;
} else {
// Set to 0 height when closing
contentHeight = '0px';
}
}
</script>
<div class="space-y-2 bg-white shadow-md">
<!-- Accordion Header -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="flex cursor-pointer items-center justify-between rounded bg-white p-4"
onclick={toggleAccordion}
>
<p class="text-lg font-semibold">{title}</p>
{#if isOpen}
<Subtract class="text-xl transition-transform duration-200 ease-in" />
{:else}
<Add class="text-xl transition-transform duration-200 ease-in" />
{/if}
</div>
<!-- Accordion Content -->
<div
bind:this={contentRef}
style="max-height: {contentHeight}; transition: max-height 0.3s ease-in-out; overflow: hidden;"
>
<div class="rounded-b bg-white p-4">
<p>{content}</p>
</div>
</div>
</div>
@@ -0,0 +1,33 @@
<script>
export let className = '';
export let buttonClass = '';
export let slug = '';
export let title = '';
export let description = '';
function getCourseUrl() {
return `/course/${slug}`;
}
</script>
<!-- only custom colors work for shadow -->
<div
class="border-examprep w-full rounded-lg border-2 p-4 shadow-[-12px_12px_0px_#0233BD] md:h-[300px] md:w-[350px] {className}"
>
<div class="flex h-full w-full flex-col justify-between gap-4 rounded-lg">
<div class="flex-1 space-y-2">
<p class="text-examprep line-clamp-2 font-serif text-xl font-semibold uppercase">{title}</p>
<p class="font-inter line-clamp-4 text-gray-600">
{description}
</p>
</div>
<a href={getCourseUrl()} class="hover:no-underline">
<div
class="bg-examprep w-full cursor-pointer p-3 text-start font-medium uppercase text-white transition-all duration-200 hover:scale-90 hover:no-underline {buttonClass}"
>
Start course
</div>
</a>
</div>
</div>
@@ -0,0 +1,26 @@
<script lang="ts">
import emptyBanner from './assets/expert-empty.svg';
interface Props {
className?: string;
headerClassName?: string;
subtitleClassName?: string;
}
let { className = '', headerClassName = '', subtitleClassName = '' }: Props = $props();
</script>
<section
class="{className} my-2 flex w-full items-center justify-center rounded-md border p-2 py-4"
>
<div
class="flex flex-col items-center justify-center gap-6 p-2 text-center md:flex-row md:text-start"
>
<img src={emptyBanner} alt="empty" />
<div>
<h3 class="font-semibold {headerClassName}">"No Course yet"</h3>
<p class="text-sm md:text-base {subtitleClassName}">
Looks like courses have not been added yet.<br /> Kindly check back later
</p>
</div>
</div>
</section>
@@ -0,0 +1,39 @@
<script>
import { getPageSection } from '@/utils/helpers/page';
import { sharedPage } from '@/utils/stores/pages';
import Logo from '../ui/_custom/Logo.svelte';
import { SECTION } from '@/utils/constants/page';
const content = $derived(getPageSection($sharedPage, SECTION.FOOTER));
const seo = $derived(getPageSection($sharedPage, SECTION.SEO));
</script>
{#if content?.show}
<nav
class="flex w-full flex-col items-start gap-4 bg-white px-6 py-4 md:flex-row md:items-center md:justify-between"
>
<Logo src={seo?.settings.logo} alt={seo?.settings.title} />
<ul class="-md:gap-8 flex flex-col items-start gap-4 underline md:flex-row md:items-center">
{#if content.settings.twitter}
<a href={content.settings.twitter} target="_blank" title="twitter">Twitter</a>
{/if}
{#if content.settings?.youtube}
<a href={content.settings?.youtube} target="_blank">Youtube</a>{/if}
{#if content.settings.linkedin}
<a href={content.settings.linkedin} target="_blank" title="linkedin">LinkedIn</a>
{/if}
{#if content.settings.facebook}
<a href={content.settings.facebook} target="_blank" title="facebook">facebook</a>
{/if}
</ul>
<a
href="https://classroomio.com"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-1"
>
<p class=" text-examprep text-base font-semibold underline">Built on ClassroomIO</p>
</a>
</nav>
{/if}
@@ -0,0 +1,97 @@
<script>
import { ArrowRight, ArrowUpRight, Close } from 'carbon-icons-svelte';
import Menu from 'carbon-icons-svelte/lib/Menu.svelte';
import { getPageSection } from '@/utils/helpers/page';
import { sharedPage } from '@/utils/stores/pages';
import PrimaryButton from './PrimaryButton.svelte';
import Logo from '../ui/_custom/Logo.svelte';
let open = $state(false);
const seo = $derived(getPageSection($sharedPage, 'seo'));
const content = $derived(getPageSection($sharedPage, 'navigation'));
// Function to toggle the mobile menu
function toggleMenu() {
open = !open;
}
</script>
<nav class="relative flex h-16 w-full items-center justify-between border-b pl-6">
<!-- Logo Section -->
<Logo src={seo?.settings.logo} alt={seo?.settings.title} addAcademy />
<!-- Mobile Menu Button (Visible only on mobile) -->
<button onclick={toggleMenu} class="px-6 py-4 lg:hidden">
<Menu size={24} />
</button>
<!-- Desktop Navigation Menu (Hidden on mobile) -->
<ul
class="hidden list-none items-center space-x-8 text-base font-bold text-gray-700 hover:no-underline lg:flex"
>
{#each content?.settings.navItems as navItem}
<li>
<a
href={navItem.link}
target={navItem.redirect ? '_blank' : undefined}
class="flex items-center gap-1"
>{navItem.title}
{#if navItem.redirect}
<ArrowUpRight />
{/if}</a
>
</li>
{/each}
</ul>
<!-- Mobile Sidebar Menu (Visible only on mobile) -->
<ul
class={`fixed left-0 top-0 h-full w-full transform bg-white hover:no-underline ${
open ? 'translate-y-0' : '-translate-y-full'
} cursor-pointer list-none text-base font-bold text-gray-700 transition-transform duration-300 ease-in-out lg:hidden`}
>
<button onclick={toggleMenu} class="absolute right-4 top-4 z-20 lg:hidden">
<Close size={24} />
</button>
{#each content?.settings.navItems as navItem}
<li class="border-b px-6 py-4">
<a
href={navItem.link}
target={navItem.redirect ? '_blank' : undefined}
class="flex items-center gap-1"
onclick={toggleMenu}
>{navItem.title}{#if navItem.redirect}
<ArrowUpRight />
{/if}</a
>
</li>
{/each}
<PrimaryButton
href="/courses"
class="flex items-center gap-1 border-b px-6 py-4 text-base font-bold text-gray-700"
onClick={toggleMenu}
label="Learn with us"
>
<ArrowRight size={16} class="fill-gray-700 font-bold" />
</PrimaryButton>
</ul>
<PrimaryButton
href="/courses"
label="Learn with us"
class="hidden h-full w-fit px-6 py-6 text-base font-normal text-white hover:no-underline lg:flex"
>
<ArrowRight size={20} class="fill-white" />
</PrimaryButton>
</nav>
<style>
nav {
position: relative;
}
ul {
z-index: 10;
}
</style>
@@ -0,0 +1,29 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { cn } from '$lib/utils.js';
interface Props {
onClick?: () => void;
label: string;
href?: string | undefined;
class?: string | undefined;
children?: any;
}
const { onClick, children, label, href, class: className, ...restProps }: Props = $props();
</script>
<Button
{href}
class={cn(
'bg-examprep text-white font-semibold p-2 hover:bg-examprep rounded-none hover:bg-examprep/90 hover:scale-95 transition-all duration-300',
className
)}
onclick={onClick}
{...restProps}
>
{label}
{#if children}
{@render children?.()}
{/if}
</Button>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

@@ -0,0 +1,15 @@
import type { Component } from 'svelte';
import CoursesPage from './pages/Course.svelte';
import HomePage from './pages/Home.svelte';
import Navigation from './Navigation.svelte';
import Footer from './Footer.svelte';
import Button from './PrimaryButton.svelte';
import type { TemplateComponents } from '$lib/utils/types/template';
export const components: TemplateComponents = {
navigation: Navigation as unknown as Component,
footer: Footer as unknown as Component,
button: Button as unknown as Component,
coursespage: CoursesPage as unknown as Component,
homepage: HomePage as unknown as Component
};
@@ -0,0 +1,141 @@
<script lang="ts">
import EmptyState from '../EmptyState.svelte';
import PrimaryButton from '../PrimaryButton.svelte';
import { courses } from '@/utils/stores/course';
import CourseCard from '../CourseCard.svelte';
import type { Course, CourseFilterItem } from '@/utils/types/course';
import type { Page } from '$lib/utils/types/page';
import { COURSE_TYPE } from '@/utils/constants/course';
import { getPageSection } from '@/utils/helpers/page';
import { SECTION } from '@/utils/constants/page';
interface Props {
data: {
page: Page;
sharedPage: Page;
courses: Course[];
};
}
let { data }: Props = $props();
/**
* State
*/
let viewAll = $state(false);
let filter: CourseFilterItem[] = $state([
{
title: 'Self Paced',
type: COURSE_TYPE.PACED,
checked: false
},
{
title: 'Live Sessions',
type: COURSE_TYPE.LIVE,
checked: false
}
]);
let filteredCourses = $state([...data.courses]);
/**
* Constants
*/
const section = $derived({
header: getPageSection(data.page, SECTION.HERO),
courses: getPageSection(data.page, SECTION.COURSE)
});
/**
* Functions
*/
function applyFilter() {
const activeFilters = new Set(
filter
.filter((filterItem) => filterItem.checked)
.map((filterItem) => filterItem.type.toLowerCase())
);
filteredCourses =
activeFilters.size === 0
? data.courses
: data.courses.filter((course) => activeFilters.has(course.type.toLowerCase()));
}
</script>
<main class="overflow-x-hidden">
{#if section.header?.show}
<section class="flex h-[50vh] items-center justify-center py-20">
<section class="flex flex-col items-start gap-10 p-4 text-center md:items-center">
<div class="w-full space-y-6">
<p
class="mx-auto w-full font-serif text-3xl font-medium text-gray-800 md:w-[60%] md:text-4xl"
>
{section.header.settings.title}
</p>
<p class="mx-auto w-full font-normal text-gray-500 md:w-[70%]">
{section.header.settings.subtitle}
</p>
</div>
</section>
</section>
{/if}
{#if section.courses?.show}
<div class="border-b bg-white px-2 py-10 md:px-10 lg:px-20">
<div class="mb-4 flex flex-col items-start justify-between md:flex-row md:items-center">
<span class="mb-4 space-y-2 px-4 text-start lg:mb-0">
<p class="mb-4 font-serif text-2xl md:text-3xl">{section.courses.settings.title}</p>
<p class=" w-full text-sm font-medium text-gray-500 md:w-[80%]">
{section.courses.settings.subtitle}
</p>
</span>
<div class=" w-full md:w-fit">
<div class="flex w-full items-center gap-1 bg-slate-100 p-2 md:gap-4">
<p class="whitespace-nowrap text-sm font-medium md:text-base">Filter by</p>
<div class="flex w-full flex-row items-center bg-white p-1 md:w-fit">
{#each filter as item}
<label
for={item.title}
class="flex flex-row items-center whitespace-nowrap rounded-md bg-white p-2 text-xs font-medium text-gray-800"
>
<input type="checkbox" name={item.title} class="text-examprep mr-2" />
{item.title}
</label>
{/each}
</div>
</div>
</div>
</div>
<div>
{#if $courses.length > 0}
<section class="flex flex-wrap items-center justify-center gap-5 p-4 md:justify-start">
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<CourseCard
slug={courseData.slug}
title={courseData.title}
description={courseData.description}
/>
{/each}
</section>
{#if $courses.length > 3}
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
label="VIEW MORE PREPCOURSES"
class="rounded-none text-lg font-semibold"
onClick={() => (viewAll = !viewAll)}
/>
</div>
{/if}
{:else}
<div class="px-10">
<EmptyState />
</div>
{/if}
</div>
</div>
{/if}
</main>
@@ -0,0 +1,250 @@
<script>
import { getPageSection } from '@/utils/helpers/page';
import { homePage } from '@/utils/stores/pages';
import { courses } from '$lib/utils/stores/course';
import { quintOut } from 'svelte/easing';
import { slide } from 'svelte/transition';
import { CheckmarkFilled } from 'carbon-icons-svelte';
import defaultBanner from '../assets/org-banner.png';
import defaultAvatar from '../assets/examprep-user.png';
import PrimaryButton from '../PrimaryButton.svelte';
import { goto } from '$app/navigation';
import CourseCard from '../CourseCard.svelte';
import EmptyState from '../EmptyState.svelte';
import Accordion from '../Accordion.svelte';
const coursesSection = $derived(getPageSection($homePage, 'courses'));
const aboutSection = $derived(getPageSection($homePage, 'about'));
const faqSection = $derived(getPageSection($homePage, 'faq'));
const testimonialSection = $derived(getPageSection($homePage, 'testimonial'));
const ctaSection = $derived(getPageSection($homePage, 'cta'));
let currentIndex = $state(0);
let viewAll = $state(false);
/**
* @param {number} index
*/
function goToSlide(index) {
currentIndex = index;
}
</script>
<main>
<!-- hero -->
{#if $homePage}
{@const content = getPageSection($homePage, 'header')}
{#if content?.show}
<section
class="flex h-full items-start justify-center bg-slate-100 px-10 pb-20 pt-4 lg:px-20 lg:pt-20"
>
<section
class="flex flex-col-reverse items-start gap-5 md:flex-row md:items-center md:justify-between"
>
<div class="w-full space-y-6">
<p
class="text-2xl font-semibold text-gray-700 first-line:w-full xl:w-[80%] xl:text-5xl"
>
{content.settings.title}
</p>
<p class="font-inter w-full text-gray-500 lg:w-[70%] xl:text-lg">
{content.settings.subtitle}
</p>
<PrimaryButton
class="px-6 font-semibold uppercase"
label={content.settings.action.label}
onClick={() => {
goto(content.settings.action.link);
}}
/>
</div>
<div
class="flex max-h-full w-full rounded-md shadow-[-12px_12px_0px_#0233BD] md:h-[400px] md:w-full lg:max-w-[800px] xl:w-1/2"
>
<img
style="min-width:280px; min-height:200px"
alt="landing page banner"
src={content.settings?.banner?.image
? content.settings?.banner?.image
: defaultBanner}
class="mt-2 h-full max-h-[550px] w-full max-w-[800px] rounded-md object-cover md:mt-0"
/>
</div>
</section>
</section>
{/if}
{/if}
<!-- testimonial -->
{#if testimonialSection?.show}
<section class="overflow-x-hidden bg-slate-100 px-4 py-6">
<div
class="bg-examprep relative mx-auto flex h-[220px] w-full items-center text-center text-white md:w-[80%] lg:w-[70%]"
>
<!-- Pointer at the top -->
<div
class="bg-examprep absolute left-[50px] top-[-25px] h-10 w-16 -translate-x-1/2 transform"
style="clip-path: polygon(50% 0%, 0% 100%, 100% 100%);"
></div>
<!-- Testimonial content with fly transition (from right) -->
<div
class="mx-auto flex h-full w-full flex-row overflow-x-hidden p-4 md:w-[90%] lg:w-[80%]"
>
{#each testimonialSection.settings.list as testimonial, index}
{#if currentIndex === index}
<div
transition:slide={{ duration: 300, easing: quintOut, axis: 'x' }}
class="flex min-w-full flex-1 flex-col items-center justify-between md:h-full"
>
<p class=" line-clamp-4 text-base font-semibold italic md:line-clamp-3 md:text-lg">
"{testimonial.description}"
</p>
<div class="flex items-center justify-center">
<img
src={testimonial.image ? testimonial.image : defaultAvatar}
alt="profile"
class="mr-4 h-14 w-14 rounded-full object-cover"
/>
<div class="font-inter text-start">
<strong>{testimonial.name}</strong><br />
<span class="text-sm uppercase text-gray-300">{testimonial.role}</span>
</div>
</div>
</div>
{/if}
{/each}
</div>
</div>
<!-- Dots for testimonial navigation -->
<div class="mt-4 flex justify-center space-x-2">
{#each testimonialSection.settings.list as _, index}
<button
class="h-3 w-3 cursor-pointer rounded-full transition-opacity duration-300 {currentIndex ===
index
? 'bg-blue-900'
: 'bg-blue-900 opacity-50'}"
onclick={() => goToSlide(index)}
aria-label={`Go to slide ${index + 1}`}
></button>
{/each}
</div>
</section>
{/if}
<!-- courses -->
{#if coursesSection?.show}
<section id="course" class="h-full bg-white px-4 pb-20 pt-4 md:px-20">
<h1 class="text-center font-serif text-3xl">{coursesSection.settings.title}</h1>
<p class="font-inter mx-auto mb-8 w-full text-center font-medium md:w-[50%]">
{coursesSection.settings.subtitle}
</p>
{#if $courses.length > 0}
<section class="mx-auto flex w-fit flex-wrap items-center justify-center gap-5 p-4">
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<CourseCard
slug={courseData.slug}
title={courseData.title}
description={courseData.description}
/>
{/each}
</section>
{#if $courses.length > 3}
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
label="VIEW MORE PREPCOURSES"
class="rounded-none text-lg font-semibold text-white"
onClick={() => (viewAll = !viewAll)}
/>
</div>
{/if}
{:else}
<div class="px-10">
<EmptyState />
</div>
{/if}
</section>
{/if}
<!-- about -->
{#if aboutSection?.show}
<section id="course" class="h-full bg-white px-4 pb-20 pt-4 md:px-10">
<h1 class="mx-auto w-full text-center font-serif text-3xl text-gray-800 md:w-[50%]">
{aboutSection.settings.title}
</h1>
<p class="text-inter mx-auto mb-8 w-[60%] text-center font-medium text-gray-500">
{aboutSection.settings.subtitle}
</p>
<section class="flex flex-wrap items-center justify-center gap-8 p-4">
{#each aboutSection.settings.benefits.list as item}
<div
class="flex h-fit max-h-[300px] max-w-full flex-col justify-start gap-4 rounded-lg border-2 border-gray-200 bg-white p-4 md:h-[150px] md:max-h-[200px] md:max-w-[500px]"
>
<span class="flex items-center gap-2">
<CheckmarkFilled size={24} class="fill-blue-400" />
<p class="font-serif text-xl">{item.title}</p>
</span>
<p class="font-inter text-base text-gray-400">{item.subtitle}</p>
</div>
{/each}
</section>
</section>
{/if}
<!-- learningpath -->
<!-- <LearningPath /> -->
<!-- faq -->
{#if faqSection?.show}
<section class="h-full space-y-5 bg-slate-100 px-4 pb-20 pt-8">
<h1 class="text-center font-serif text-3xl text-gray-700">{faqSection.settings.title}</h1>
<section class="mx-auto w-full space-y-10 p-4 md:w-[80%]">
{#each faqSection.settings.questions as faq}
<Accordion title={faq.title} content={faq.content} />
{/each}
</section>
</section>
{/if}
<!-- cta -->
{#if ctaSection?.show}
<section
class="flex flex-col items-center justify-between space-y-4 bg-blue-800 px-6 py-20 lg:px-10"
>
<div class="flex w-full items-center justify-center">
<p
class="w-full text-center font-serif text-4xl font-bold capitalize text-white lg:w-[70%]"
>
{ctaSection.settings.title}
</p>
</div>
<p class="w-full text-center text-white lg:w-[50%]">
{ctaSection.settings.subtitle}
</p>
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
label={ctaSection.settings.button.label}
href={ctaSection.settings.button.redirect && ctaSection.settings.button.link}
class=" text-examprep bg-white text-lg font-bold uppercase hover:bg-white"
/>
</div>
</section>
{/if}
</main>
<style>
/* Optional smooth transition effect for text */
:global(.testimonial-text-enter-active, .testimonial-text-leave-active) {
transition: opacity 0.5s;
}
:global(.testimonial-text-enter, .testimonial-text-leave-to) {
opacity: 0;
}
</style>
@@ -2,6 +2,8 @@ import { components as CalComponents } from './cal';
import { components as PosthogComponents } from './posthog';
import { components as ClassicComponents } from './classic';
import { components as MinimalComponents } from './minimal';
import { components as ExamprepComponents } from './examprep';
import { components as WebflowComponents } from './webflow';
function getComponents() {
switch (import.meta.env.VITE_TEMPLATE) {
@@ -11,6 +13,10 @@ function getComponents() {
return ClassicComponents;
case 'minimal':
return MinimalComponents;
case 'examprep':
return ExamprepComponents;
case 'webflow':
return WebflowComponents;
default:
return CalComponents;
}
@@ -28,18 +28,18 @@
</script>
<div class="h-fit w-full min-w-[250px] max-w-[300px] space-y-4">
<div class="border border-[#EAEAEA] rounded-sm space-y-4 px-4 {className}">
<div class="py-4 border-b overflow-hidden">
<p class="text-xl font-semibold p border-[#EAEAEA] overflow-ellipsis line-clamp-1">
<div class="space-y-4 rounded-sm border border-[#EAEAEA] px-4 {className}">
<div class="overflow-hidden border-b py-4">
<p class="p line-clamp-1 overflow-ellipsis border-[#EAEAEA] text-xl font-semibold">
{title}
</p>
</div>
<div class="rounded-md overflow-hidden">
<p class=" text-[#878787] overflow-ellipsis line-clamp-3 text-justify">
<div class="overflow-hidden rounded-md">
<p class=" line-clamp-3 overflow-ellipsis text-justify text-[#878787]">
{description}
</p>
</div>
<span class="flex justify-between items-center py-4">
<span class="flex items-center justify-between py-4">
<p>{lessons} Lessons</p>
<p class="font-bold text-[#0233BD]">
{!cost ? 'Free' : currency == 'USD' ? `$ ${cost}` : `N ${cost}`}
@@ -48,9 +48,9 @@
</div>
<a
href={getCourseUrl()}
class="flex items-center justify-between w-full border border-[#EAEAEA] rounded-sm p-3 cursor-pointer hover:no-underline hover:scale-90 transition-all duration-200 {buttonClass}"
class="flex w-full cursor-pointer items-center justify-between rounded-sm border border-[#EAEAEA] p-3 transition-all duration-200 hover:scale-95 hover:no-underline {buttonClass}"
>
<p class="font-bold text-[#0233BD] uppercase">Learn More</p>
<p class="font-bold uppercase text-[#0233BD]">Learn More</p>
<ArrowRight class="text-[#0233BD]" />
</a>
</div>
@@ -1,27 +1,20 @@
<script lang="ts">
import { getPageSection } from '@/utils/helpers/page';
import { sharedPage } from '@/utils/stores/pages';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
import { SECTION } from '@/utils/constants/page';
const content = $derived(getPageSection($sharedPage, 'footer'));
const seo = $derived(getPageSection($sharedPage, 'seo'));
const content = $derived(getPageSection($sharedPage, SECTION.FOOTER));
const seo = $derived(getPageSection($sharedPage, SECTION.SEO));
</script>
{#if content?.show}
<nav
class="bg-white w-full flex flex-col md:flex-row items-start md:items-center gap-4 md:justify-between py-4 px-6"
class="flex w-full flex-col items-start gap-4 bg-white px-6 py-4 md:flex-row md:items-center md:justify-between"
>
<div class="logo">
<a href="/" title={`Go to ${seo?.settings.title} Home`} id="logo" data-hveid="8">
<img
src={seo?.settings.logo}
alt={`${seo?.settings.title} logo`}
class="rounded w-9 inline-block mx-auto"
data-atf="1"
/>
</a>
</div>
<Logo src={seo?.settings.logo} alt={seo?.settings.title} />
<ul
class="flex flex-col md:flex-row items-start md:items-center gap-4 -md:gap-8 font-semibold capitalize underline"
class="-md:gap-8 flex flex-col items-start gap-4 font-semibold capitalize underline md:flex-row md:items-center"
>
{#if content.settings.twitter}
<a href={content.settings.twitter} target="_blank" title="twitter">Twitter</a>
@@ -36,7 +29,12 @@
<a href={content.settings.facebook} target="_blank" title="facebook">facebook</a>
{/if}
</ul>
<a href="/#" class="flex items-center hover:underline text-[#0233BD] dark:text-white gap-1">
<a
href="https://classroomio.com"
target="_blank"
rel="noopener noreferrer"
class="text-minimal flex items-center gap-1 hover:underline dark:text-white"
>
<p class="text-base font-semibold">Built on ClassroomIO</p>
</a>
</nav>
@@ -1,7 +1,6 @@
<script lang="ts">
import { ArrowRight, ArrowUpRight, Close } from 'carbon-icons-svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
import { getPageSection } from '@/utils/helpers/page';
import { sharedPage } from '@/utils/stores/pages';
@@ -17,17 +16,9 @@
const content = $derived(getPageSection($sharedPage, 'navigation'));
</script>
<nav class={`relative w-full flex items-center justify-between py-4 px-6 bg-white dark:bg-black`}>
<nav class={`relative flex w-full items-center justify-between bg-white px-6 py-4 dark:bg-black`}>
<!-- Logo Section -->
<div class="logo">
<a href="/" title={`${seo?.settings.title} home`} id="logo">
<img
src={seo?.settings.logo}
alt={`${seo?.settings.title} logo`}
class="rounded w-9 inline-block mx-auto"
/>
</a>
</div>
<Logo src={seo?.settings.logo} alt={seo?.settings.title} className="w-24" addAcademy />
<!-- Mobile Menu Button (Visible only on mobile and when logged in) -->
@@ -37,14 +28,14 @@
<!-- Desktop Navigation Menu (Hidden on mobile) -->
<ul
class="hidden lg:flex items-center space-x-8 text-base font-bold text-[#1F2937] list-none hover:no-underline"
class="hidden list-none items-center space-x-8 text-base font-bold text-[#1F2937] hover:no-underline lg:flex"
>
{#each content?.settings.navItems as navItem}
<li>
<a href={navItem.link} class="flex items-center gap-1">
{navItem.title}
{#if navItem.redirect}
<ArrowUpRight size={12} />
<ArrowUpRight />
{/if}
</a>
</li>
@@ -53,34 +44,34 @@
<!-- Mobile Sidebar Menu (Visible only on mobile) -->
<ul
class={`fixed top-0 left-0 pt-10 h-full bg-white w-full transform hover:no-underline ${
class={`fixed left-0 top-0 h-full w-full transform bg-white pt-10 hover:no-underline ${
open ? 'translate-y-0' : '-translate-y-full'
} transition-transform duration-300 ease-in-out lg:hidden text-base font-bold text-[#1F2937] list-none cursor-pointer`}
} cursor-pointer list-none text-base font-bold text-[#1F2937] transition-transform duration-300 ease-in-out lg:hidden`}
>
<button onclick={toggleMenu} class="absolute right-4 top-4 lg:hidden z-20">
<button onclick={toggleMenu} class="absolute right-4 top-4 z-20 lg:hidden">
<Close size={24} />
</button>
{#each content?.settings.navItem as navItem}
<li class="py-4 px-6 border-b">
<a href={navItem.link} onclick={toggleMenu} class="flex item-center gap-1">
<li class="border-b px-6 py-4">
<a href={navItem.link} onclick={toggleMenu} class="item-center flex gap-1">
{navItem.title}
{#if navItem.redirect}
<ArrowUpRight size={12} />
<ArrowUpRight />
{/if}
</a>
</li>
{/each}
<a href="/courses" class="flex items-center gap-1 py-4 px-6 border-b" onclick={toggleMenu}>
<p class="font-bold text-[#1F2937] text-base">Learn with me</p>
<a href="/courses" class="flex items-center gap-1 border-b px-6 py-4" onclick={toggleMenu}>
<p class="text-base font-bold text-[#1F2937]">Learn with me</p>
<ArrowRight size={16} class="fill-[#1F2937] font-bold" />
</a>
</ul>
<!-- Learn with Me Button (Visible on desktop when logged in) -->
<a href="/courses" class="hidden lg:flex items-center gap-1">
<p class="font-bold text-[#1F2937] text-base">Learn with me</p>
<a href="/courses" class="hidden items-center gap-1 lg:flex">
<p class="text-base font-bold text-[#1F2937]">Learn with me</p>
<ArrowRight size={16} class="fill-[#1F2937] font-bold" />
</a>
</nav>
@@ -1,6 +1,6 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { cn } from '$lib/utils.js';
import { cn } from '$lib/utils';
interface Props {
onClick?: () => void;
@@ -16,7 +16,7 @@
{href}
onclick={onClick}
class={cn(
'rounded-none uppercase bg-minimal hover:bg-minimal p-3 hover:scale-95 font-bold',
'rounded-none uppercase bg-minimal hover:bg-minimal p-3 font-bold hover:bg-minimal/90 hover:scale-95 transition-all duration-300',
className
)}
{...restProps}
Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

@@ -8,6 +8,7 @@
import PrimaryButton from '../PrimaryButton.svelte';
import { courses } from '@/utils/stores/course';
import CourseCard from '../CourseCard.svelte';
import { SECTION } from '@/utils/constants/page';
interface Props {
data: {
@@ -43,8 +44,8 @@
* Constants
*/
const section = $derived({
header: getPageSection(data.page, 'header'),
courses: getPageSection(data.page, 'courses')
header: getPageSection(data.page, SECTION.HERO),
courses: getPageSection(data.page, SECTION.COURSE)
});
/**
@@ -64,26 +65,19 @@
}
</script>
<main class="bg-[#101720] font-ibm overflow-hidden">
<main class="font-ibm">
{#if section.header?.show}
<div class="relative h-full p-2 md:p-10">
<div
class="absolute inset-0 bg-cover bg-center"
style="background-image: url('/classroomio-course-img-template.jpg');"
></div>
<div
class="absolute inset-0 bg-gradient-to-r from-[#0233BD99] via-[#00000033] to-[#FFFFFF00]"
></div>
<div class="relative flex items-center justify-center h-full text-white px-4 py-20 md:px-10">
<div class="text-white space-y-6 w-full">
<p class="text-2xl md:text-4xl font-mono font-semibold w-full md:w-[60%] lg:w-[40%]">
<div id="course-banner" class="h-full bg-cover bg-center md:h-[600px] md:p-10">
<div class="relative flex h-full items-center justify-center px-4 py-20 text-white md:px-10">
<div class="w-full space-y-6 text-white">
<h1 class="w-full font-mono text-2xl font-semibold md:w-[60%] md:text-6xl">
{section.header.settings.title}
</p>
</h1>
<p class="w-full md:w-[60%] lg:w-[40%]">{section.header.settings.subtitle}</p>
<div class="flex items-center gap-2 md:gap-4">
<PrimaryButton
onClick={() => goto(section.header?.settings.action.link)}
class="uppercase bg-blue-900 px-6 py-3 font-semibold hover:bg-blue-900 hover:scale-95 rounded-none"
class="rounded-none bg-blue-900 px-6 py-3 font-semibold uppercase hover:scale-95 hover:bg-blue-900"
label={section.header.settings.action.label}
/>
</div>
@@ -93,19 +87,19 @@
{/if}
{#if section.courses?.show}
<div class="bg-white py-10 px-2 md:px-8">
<p class="dark:text-white text-3xl text-center font-semibold pb-10 w-full md:w-[70%] mx-auto">
<div class="w-full border-b px-2 py-10 md:px-8">
<p class="mx-auto w-full pb-10 text-center text-3xl font-semibold dark:text-white md:w-2/4">
{section.courses.settings.title}
</p>
<div>
<div class="py-10">
{#if $courses.length > 0}
<div class="w-full lg:flex items-start gap-4 lg:ml-[5%]">
<div class="hidden lg:block w-max">
<p class="font-medium mb-2">Filter by</p>
<div class="mx-auto max-w-5xl items-start gap-8 lg:flex">
<div class="hidden w-max lg:block">
<p class="mb-2 font-medium">Filter by</p>
<div class="w-max space-y-2">
{#each filter as item}
<form
class="space-x-2 text-[#3C4043] font-medium border border-[#EAEAEA] bg-[#FDFDFD] rounded-md px-4 py-4"
class="space-x-2 rounded-md border border-[#EAEAEA] bg-[#FDFDFD] px-4 py-4 font-medium text-[#3C4043]"
>
<input
type="checkbox"
@@ -119,7 +113,7 @@
</div>
</div>
<section
class="grid place-items-center grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
class="grid grid-cols-1 place-items-center gap-4 md:grid-cols-2 lg:grid-cols-3"
>
{#each filteredCourses.slice(0, viewAllCourses ? filteredCourses.length : 3) as courseData}
<CourseCard
@@ -135,23 +129,23 @@
</section>
{#if filteredCourses.length == 0}
<div class="px-4 w-full mx-auto">
<div class="mx-auto w-full px-4">
<EmptyState />
</div>
{/if}
</div>
{#if filteredCourses.length > 3}
<div class="w-full flex items-center justify-center my-5">
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
class="uppercase bg-blue-900 p-2"
class="bg-blue-900 p-2 uppercase"
onClick={() => (viewAllCourses = !viewAllCourses)}
label="VIEW COURSES"
/>
</div>
{/if}
{:else}
<div class="px-4 w-full lg:w-[70%] mx-auto">
<div class="mx-auto w-full px-4 lg:w-[70%]">
<EmptyState />
</div>
{/if}
@@ -159,3 +153,9 @@
</div>
{/if}
</main>
<style>
#course-banner {
background-image: url('$lib/components/minimal/assets/courses-pg-bg.png');
}
</style>
@@ -3,59 +3,58 @@
import { getPageSection } from '@/utils/helpers/page';
import PrimaryButton from '../PrimaryButton.svelte';
import { goto } from '$app/navigation';
import defaultBanner from '../assets/classroomio-course-img-template.jpg';
import { courses } from '@/utils/stores/course';
import CourseCard from '../CourseCard.svelte';
import EmptyState from '../EmptyState.svelte';
import TestimonialCard from '../TestimonialCard.svelte';
import Accordion from '../Accordion.svelte';
import testimonial from '../assets/testimonial.svg';
import { SECTION } from '@/utils/constants/page';
const courseSection = $derived(getPageSection($homePage, 'about'));
const learnSection = $derived(getPageSection($homePage, 'about'));
const testimonialSection = $derived(getPageSection($homePage, 'testimonial'));
const faqSection = $derived(getPageSection($homePage, 'faq'));
const footerNoteSection = $derived(getPageSection($homePage, 'cta'));
const courseSection = $derived(getPageSection($homePage, SECTION.COURSE));
const learnSection = $derived(getPageSection($homePage, SECTION.ABOUT));
const testimonialSection = $derived(getPageSection($homePage, SECTION.TESTIMONIAL));
const faqSection = $derived(getPageSection($homePage, SECTION.FAQ));
const ctaSection = $derived(getPageSection($homePage, SECTION.CTA));
let viewAll = $state(false);
</script>
<!-- for some reason the custom color doesnt work for this template -->
<main class="bg-[#101720]">
<main class="bg-minimal-secondary">
<!-- hero -->
{#if $homePage}
{@const content = getPageSection($homePage, 'header')}
{@const content = getPageSection($homePage, SECTION.HERO)}
{#if content?.show}
<section class="flex items-center justify-center py-5 px-5 md:py-14 md:px-14 max-h-full">
<section class="flex items-center justify-between gap-4">
<div class="text-white space-y-6 w-full">
<div class="bg-[#DCFCFFED] py-1 px-3 md:border rounded-sm w-[90%]">
<p class="text-center uppercase font-bold text-sm text-[#0F163F]">
<section class="flex max-h-full items-center justify-center px-5 py-5 md:px-14 md:py-14">
<section class="mx-auto flex max-w-6xl items-center justify-between gap-4">
<div class="w-full max-w-[50%] space-y-6 text-white">
<div class="w-fit rounded-sm bg-emerald-100 px-3 py-1 md:border">
<p class="text-minimal text-center text-sm font-bold uppercase">
{content.settings?.title}
</p>
</div>
<p class="text-4xl font-bold w-full md:w-[70%] capitalize">
<h1 class="w-full text-5xl font-bold capitalize">
{content.settings?.titleHighlight}
</p>
<p class="w-full md:w-[70%]">
</h1>
<p class="w-full">
{content.settings?.subtitle}
</p>
<PrimaryButton
class="bg-[#0542CC]"
class="bg-minimal"
onClick={() => {
goto(content.settings?.action.link);
}}
label={content.settings?.action.label}
/>
</div>
<div class="hidden rounded-md h-[280px] max-h-[400px] w-5/6 md:w-full lg:block">
<div class="hidden h-[280px] max-h-[400px] w-5/6 rounded-md md:w-full lg:block">
<img
style="min-width:280px; min-height:200px"
alt="landing page banner"
src={content.settings?.banner?.image
? content.settings?.banner?.image
: defaultBanner}
class="mt-2 h-full max-h-[400px] w-full max-w-[500px] rounded-md md:mt-0 object-cover"
src={content.settings?.banner?.image || '/course-banner.jpg'}
class="mt-2 h-full max-h-[400px] w-full max-w-[500px] rounded-md object-cover md:mt-0"
/>
</div>
</section>
@@ -63,29 +62,30 @@
{/if}
{/if}
<!-- learn -->
<!-- Learn -->
{#if learnSection?.show}
<section
id="about"
class="flex items-start justify-center px-5 md:px-14 h-full bg-white py-20 mx-auto"
class="mx-auto flex h-full items-start justify-center border-b bg-white px-5 py-20 md:px-14"
>
<section class="flex flex-col lg:flex-row gap-8 items-start justify-center">
<div class="w-full lg:w-[60%] space-y-4">
<p class="text-4xl font-bold">{learnSection.settings.title}</p>
<p class="w-full lg:w-[80%] text-base leading-7 text-[#878787]">
<section class="flex flex-col items-start justify-center gap-8 lg:flex-row">
<div class="w-full space-y-4 lg:w-2/4">
<p class="max-w-[500px] text-4xl font-bold">{learnSection.settings.title}</p>
<p class="w-full text-base leading-7 text-gray-500 lg:w-[80%]">
{learnSection.settings.subtitle}
</p>
</div>
{#if learnSection.settings.benefits}
<div class="w-full lg:w-fit space-y-8">
<div class="w-full space-y-8 lg:w-fit">
<span
class="bg-[#DCFCFF] py-1 px-3 uppercase border border-[#0233BD] rounded-sm text-center font-bold text-base text-[#0233BD]"
>{learnSection.settings.benefits.title}</span
class="border-minimal text-minimal bg-minimal/10 rounded-sm border px-3 py-1 text-center text-base font-bold uppercase"
>
<ul class="space-y-6 text-base text-[#696969] font-medium">
{learnSection.settings.benefits.title}
</span>
<ul class="space-y-6 text-base font-medium text-gray-600">
{#each learnSection.settings.benefits.list as items}
<li class="border-l border-[#0233BD] pl-3">{items.title}</li>
<li class="border-minimal border-l pl-3">{items.title}</li>
{/each}
</ul>
</div>
@@ -93,26 +93,26 @@
<img
src={learnSection.settings.imageUrl}
alt="Our Story"
class="rounded-2xl max-h-[450px]"
class="max-h-[450px] rounded-2xl"
/>
{/if}
</section>
</section>
{/if}
<!-- courses -->
<!-- Courses -->
{#if courseSection?.show}
<section id="course" class="px-5 pt-10 space-y-10 py-36 h-full bg-white">
<h1 class="text-center text-3xl font-bold mb-4 w-full md:w-[70%] mx-auto">
<section id="course" class="h-full space-y-10 bg-white px-5 pb-36 pt-10">
<h2 class="mx-auto mb-4 w-full text-center text-4xl font-bold md:w-[70%]">
{courseSection.settings.title}
</h1>
<div class="w-full md:w-[90%] mx-auto">
</h2>
<div class="mx-auto w-full md:w-[90%]">
{#if $courses.length > 0}
<section class="flex flex-wrap items-center w-fit mx-auto gap-4 p-4">
<section class="mx-auto flex w-fit flex-wrap items-center justify-center gap-4 p-4">
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<CourseCard
className="bg-[#FDFDFD]"
slug={courseData.slug}
title={courseData.title}
description={courseData.description}
@@ -123,7 +123,7 @@
{/each}
</section>
{#if $courses.length > 3}
<div class="w-full flex items-center justify-center my-5">
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
class="p-2"
onClick={() => (viewAll = !viewAll)}
@@ -132,7 +132,7 @@
</div>
{/if}
{:else}
<div class="px-4 w-full lg:w-[70%] mx-auto">
<div class="mx-auto w-full px-4 lg:w-[70%]">
<EmptyState />
</div>
{/if}
@@ -140,16 +140,17 @@
</section>
{/if}
<!-- testimonial -->
<!-- Testimonial -->
{#if testimonialSection?.show}
<section id="testimonial" class="relative pt-10 h-full text-white">
<div class="absolute -top-20 md:-top-32 left-[5%] w-[90%]">
<img src={testimonial} alt="testimonial" class="w-full max-h-[500px] md:max-h-[300px]" />
<section class="relative h-full pt-10 text-white">
<div class="absolute -top-20 left-[5%] w-[90%] md:-top-32">
<img src={testimonial} alt="testimonial" class="max-h-[500px] w-full md:max-h-[300px]" />
</div>
<div class="px-5 py-20 md:py-40 space-y-10">
<h1 class="text-center text-3xl font-bold mb-4">Student testimonial</h1>
<div class="w-full md:w-[90%] mx-auto">
<section class="grid place-items-center grid-cols-1 md:grid-cols-3 w-fit mx-auto gap-4">
<div class="space-y-10 px-5 py-20 md:py-40">
<h1 class="mb-4 text-center text-3xl font-bold">Student testimonial</h1>
<div class="mx-auto w-full md:w-[90%]">
<section class="mx-auto grid w-fit grid-cols-1 place-items-center gap-4 md:grid-cols-3">
{#each testimonialSection.settings.list as item}
<TestimonialCard name={item.name} description={item.description} role={item.role} />
{/each}
@@ -159,11 +160,11 @@
</section>
{/if}
<!-- faq -->
<!-- FAQ -->
{#if faqSection?.show}
<section class="px-5 py-20 space-y-10 h-full bg-[#F9F9F9]">
<h1 class="text-center text-3xl font-bold mb-4">{faqSection.settings.title}</h1>
<section class="p-4 space-y-10 w-full md:w-[80%] mx-auto">
<section class="h-full space-y-10 bg-gray-100 px-5 py-20">
<h1 class="mb-4 text-center text-3xl font-bold">{faqSection.settings.title}</h1>
<section class="mx-auto w-full space-y-10 p-4 md:w-[80%]">
{#each faqSection.settings.questions as faq}
<Accordion title={faq.title} content={faq.content} />
{/each}
@@ -171,23 +172,23 @@
</section>
{/if}
<!-- footerNote -->
{#if footerNoteSection?.show}
<!-- cta -->
{#if ctaSection?.show}
<section
class="flex flex-col md:flex-row items-center justify-between px-6 lg:px-10 py-20 bg-blue-800"
class="flex flex-col items-center justify-between bg-blue-800 px-6 py-20 md:flex-row lg:px-10"
>
<div class="flex items-center justify-center w-full">
<div class="flex w-full items-center justify-center">
<p
class="text-4xl text-white text-center md:text-start font-bold w-full lg:w-[70%] capitalize"
class="w-full text-center text-4xl font-bold capitalize text-white md:text-start lg:w-[70%]"
>
{footerNoteSection.settings.title}
{ctaSection.settings.title}
</p>
</div>
<div class="w-full flex items-center justify-center my-5">
<div class="my-5 flex w-full items-center justify-center">
<PrimaryButton
onClick={() => goto('/courses')}
class="py-2 bg-white text-blue-700 hover:bg-white"
label={footerNoteSection.settings.buttonLabel}
href={ctaSection.settings?.button?.link}
class="bg-white py-2 text-blue-700 hover:bg-white"
label={ctaSection.settings?.button?.label}
/>
</div>
</section>
@@ -1,5 +1,5 @@
<script lang="ts">
import FaqMark from './static/icons/FaqMark.svelte';
import FaqMark from './assets/icons/FaqMark.svelte';
interface Props {
faq: any;
@@ -33,26 +33,26 @@
</script>
<div
class="z-10 min-w-60 max-w-72 h-72 perspective group"
class="min-w-60 max-w-72 perspective group z-10 h-72"
bind:this={card}
onmousemove={handleMouseMove}
onmouseleave={handleMouseLeave}
role="none"
>
<div class={`inner-card ${index % 2 === 0 ? 'rotate-3 ' : '-rotate-3 '} `}>
<span class="z-10 absolute w-2 h-2 rounded-full {iconColor} top-4 left-4"></span>
<span class="z-10 absolute w-[2px] h-[2px] rounded-full {iconColor} top-8 left-8"></span>
<span class="z-10 absolute w-2 h-2 rounded-full {iconColor} bottom-4 right-4"></span>
<span class="z-10 absolute w-[2px] h-[2px] rounded-full {iconColor} bottom-8 right-8"></span>
<span class="absolute z-10 h-2 w-2 rounded-full {iconColor} left-4 top-4"></span>
<span class="absolute z-10 h-[2px] w-[2px] rounded-full {iconColor} left-8 top-8"></span>
<span class="absolute z-10 h-2 w-2 rounded-full {iconColor} bottom-4 right-4"></span>
<span class="absolute z-10 h-[2px] w-[2px] rounded-full {iconColor} bottom-8 right-8"></span>
<div
class="front text-black shadow-md flex flex-col items-center justify-center p-4 {background}"
class="front flex flex-col items-center justify-center p-4 text-black shadow-md {background}"
>
<FaqMark color="#3B82F6" />
<h3 class="text-lg font-bold text-center">{faq.title}</h3>
<h3 class="text-center text-lg font-bold">{faq.title}</h3>
</div>
<div
class="back text-black text-center text-sm shadow-md flex items-center justify-center p-4 {background}"
class="back flex items-center justify-center p-4 text-center text-sm text-black shadow-md {background}"
>
<p class="line-clamp-5">{faq.content}</p>
</div>
@@ -1,31 +1,20 @@
<script lang="ts">
import Logo from '$lib/components/ui/_custom/Logo.svelte';
import { getPageSection } from '$lib/utils/helpers/page';
import { sharedPage } from '$lib/utils/stores/pages';
import { SECTION } from '@/utils/constants/page';
const content = $derived(getPageSection($sharedPage, 'footer'));
const seo = $derived(getPageSection($sharedPage, 'seo'));
/**
* Constants
*/
const content = $derived(getPageSection($sharedPage, SECTION.FOOTER));
const seo = $derived(getPageSection($sharedPage, SECTION.SEO));
</script>
{#if content?.show}
<nav
class="w-full h-40 bg-posthog-background border-t border-posthog-border flex flex-col md:flex-row items-start md:items-center gap-4 md:justify-between py-4 px-6"
class="bg-posthog-background border-posthog-border flex h-40 w-full flex-col items-start gap-4 border-t px-6 py-4 md:flex-row md:items-center md:justify-between"
>
<div class="logo">
<a href="/" title={`Go to ${seo?.settings.title} Home`} id="logo" data-hveid="8">
<img
src={seo?.settings.logo}
alt={`${seo?.settings.title} logo`}
class="w-24"
data-atf="1"
/>
</a>
</div>
<Logo src={seo?.settings.logo} alt={seo?.settings.title} className="w-24" />
<ul
class="flex flex-col md:flex-row items-start md:items-center gap-4 -md:gap-8 font-semibold capitalize underline"
class="-md:gap-8 flex flex-col items-start gap-4 font-semibold capitalize underline md:flex-row md:items-center"
>
{#if content.settings.twitter}
<a href={content.settings.twitter} target="_blank" title="twitter">Twitter</a>
@@ -42,7 +31,7 @@
</ul>
<a
href="https://classroomio.com"
class="flex items-center hover:underline text-[#0233BD] dark:text-white gap-1"
class="flex items-center gap-1 text-blue-700 hover:underline dark:text-white"
target="_blank"
rel="noopener noreferrer"
>
@@ -8,6 +8,7 @@
import type { Section } from '@/utils/types/page';
import PrimaryButton from './PrimaryButton.svelte';
import { ArrowUpRight } from 'carbon-icons-svelte';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
let open = $state(false);
@@ -20,19 +21,10 @@
</script>
<nav
class={`relative w-full flex items-center justify-between border-b border-posthog-border py-3 px-6`}
class={`border-posthog-border relative flex w-full items-center justify-between border-b px-6 py-3`}
>
<!-- Logo Section -->
<a href="/" class="flex items-center flex-col" title={`${seo?.settings?.title}`} id="logo">
{#if seo?.settings?.logo}
<img
src={seo?.settings?.logo || ''}
alt={`${seo?.settings?.title || ''} logo`}
class="w-24"
/>
{/if}
<h1 class="text-lg font-semibold">University</h1>
</a>
<Logo src={seo?.settings.logo} alt={seo?.settings.title} className="w-24" addAcademy />
<!-- Mobile Menu Button (Visible only on mobile and when logged in) -->
<button onclick={toggleMenu} class="lg:hidden">
@@ -40,14 +32,14 @@
</button>
<!-- Desktop Navigation Menu (Hidden on mobile) -->
<ul class="hidden lg:flex items-center space-x-8 list-none hover:no-underline">
<ul class="hidden list-none items-center space-x-8 hover:no-underline lg:flex">
{#each content?.settings.navItems as navItem}
<a
href={navItem.link}
target={navItem.redirect ? '_blank' : undefined}
class="text-sm text-gray-500 hover:text-black"
>
<li class="hover:no-underline flex items-center gap-1">
<li class="flex items-center gap-1 hover:no-underline">
{navItem.title}
{#if navItem.redirect}
<ArrowUpRight size={16} />
@@ -62,17 +54,17 @@
<!-- Mobile Sidebar Menu (Visible only on mobile) -->
<ul
class={`fixed top-0 left-0 pt-10 h-full bg-white dark:bg-black w-full transform hover:no-underline ${
class={`fixed left-0 top-0 h-full w-full transform bg-white pt-10 hover:no-underline dark:bg-black ${
open ? 'translate-y-0' : '-translate-y-full'
} transition-transform duration-300 ease-in-out lg:hidden text-base font-bold text-[#1F2937] dark:text-white list-none cursor-pointer`}
} cursor-pointer list-none text-base font-bold text-[#1F2937] transition-transform duration-300 ease-in-out dark:text-white lg:hidden`}
>
<button onclick={toggleMenu} class="absolute right-4 top-4 lg:hidden z-20">
<button onclick={toggleMenu} class="absolute right-4 top-4 z-20 lg:hidden">
<Close size={24} />
</button>
{#each content?.settings.navItems as navItem}
<li class="py-4 px-6 border-b dark:border-gray-200">
<a href={navItem.link} onclick={toggleMenu} class="flex item-center gap-1"
<li class="border-b px-6 py-4 dark:border-gray-200">
<a href={navItem.link} onclick={toggleMenu} class="item-center flex gap-1"
>{navItem.title}
{#if navItem.redirect}
<ArrowUpRight size={16} />
@@ -81,13 +73,13 @@
</li>
{/each}
<div class="border-b dark:border-gray-200 py-4 px-6">
<div class="border-b px-6 py-4 dark:border-gray-200">
<PrimaryButton
onClick={() => {
goto('/courses');
toggleMenu;
}}
class="bg-[#F7A50180] hover:bg-[#f7a501ad] dark:bg-[#F7A501] hover:no-underline rounded-lg flex items-center py-2 px-3 border w-fit border-[#4A4C524D] font-bold text-black text-base"
class="flex w-fit items-center rounded-lg border border-[#4A4C524D] bg-[#F7A50180] px-3 py-2 text-base font-bold text-black hover:bg-[#f7a501ad] hover:no-underline dark:bg-[#F7A501]"
label="Explore Course"
/>
</div>
@@ -95,7 +87,7 @@
<!-- Learn with Me Button (Visible on desktop when logged in) -->
<div class="hidden lg:flex items-center gap-2">
<div class="hidden items-center gap-2 lg:flex">
<PrimaryButton onClick={() => goto('/courses')} label="Explore Course" />
</div>
</nav>
@@ -1,6 +1,6 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { cn } from '$lib/utils.js';
import { cn } from '$lib/utils';
interface Props {
onClick?: () => void;
@@ -14,12 +14,12 @@
<div class="relative inline-block group">
<span
class="absolute inset-0 -bottom-1 ring-1 ring-posthog bg-posthog rounded-md transition-transform duration-200 ease-in-out"
class="absolute inset-0 -bottom-1 ring-1 ring-posthog-secondary bg-posthog-secondary rounded-md transition-transform duration-200 ease-in-out"
></span>
<Button
{href}
class={cn(
'bg-white group-hover:bg-white text-black ring-1 ring-posthog font-bold p-2 rounded-md relative z-10 transition-transform duration-200 ease-in-out hover:-translate-y-[2px]',
'bg-white group-hover:bg-white text-black ring-1 ring-posthog-secondary font-bold p-2 rounded-md relative z-10 transition-transform duration-200 ease-in-out hover:-translate-y-[2px]',
className
)}
onclick={onClick}
@@ -6,6 +6,7 @@
import type { CourseFilterItem, Course } from '$lib/utils/types/course';
import type { Page } from '$lib/utils/types/page';
import { COURSE_TYPE } from '@/utils/constants/course';
import { SECTION } from '@/utils/constants/page';
interface Props {
data: {
@@ -41,8 +42,8 @@
* Constants
*/
const section = $derived({
header: getPageSection(data.page, 'header'),
courses: getPageSection(data.page, 'courses')
header: getPageSection(data.page, SECTION.HERO),
courses: getPageSection(data.page, SECTION.COURSE)
});
/**
@@ -3,18 +3,19 @@
import { getPageSection } from '@/utils/helpers/page';
import { homePage } from '@/utils/stores/pages';
import PrimaryButton from '../PrimaryButton.svelte';
import analytics from '../static/analytics.png';
import analytics from '../assets/analytics.png';
import { Button } from '@/components/ui/button';
import { courses } from '@/utils/stores/course';
import CourseCard from '../CourseCard.svelte';
import EmptyState from '../EmptyState.svelte';
import { CheckmarkFilled } from 'carbon-icons-svelte';
import FaqCard from '../FaqCard.svelte';
import { SECTION } from '@/utils/constants/page';
const coursesSection = $derived(getPageSection($homePage, 'courses'));
const aboutSection = $derived(getPageSection($homePage, 'about'));
const faqSection = $derived(getPageSection($homePage, 'faq'));
const footerNoteSection = $derived(getPageSection($homePage, 'cta'));
const coursesSection = $derived(getPageSection($homePage, SECTION.COURSE));
const aboutSection = $derived(getPageSection($homePage, SECTION.ABOUT));
const faqSection = $derived(getPageSection($homePage, SECTION.FAQ));
const ctaSection = $derived(getPageSection($homePage, SECTION.CTA));
function getCourseUrl() {
return '/courses';
@@ -26,20 +27,20 @@
<div class="bg-posthog-background">
<!-- hero -->
{#if $homePage}
{@const content = getPageSection($homePage, 'header')}
{@const content = getPageSection($homePage, SECTION.HERO)}
{#if content?.show}
<section
class="font-matter flex items-center justify-center py-14 px-10 md:px-14 min-h-full overflow-hidden"
class="font-matter flex min-h-full items-center justify-center overflow-hidden px-10 py-14 md:px-14"
>
<section
class="flex flex-col-reverse md:flex-col text-center items-center gap-5 justify-center"
class="flex flex-col-reverse items-center justify-center gap-5 text-center md:flex-col"
>
<div class="space-y-6 w-full mb-4">
<p class="text-3xl md:text-5xl font-bold w-full md:w-[80%] lg:w-[70%] mx-auto">
<div class="mb-4 w-full space-y-6">
<p class="mx-auto w-full text-3xl font-bold md:w-[80%] md:text-5xl lg:w-[70%]">
{content.settings.title}
</p>
<p class="w-full text-lg md:w-[80%] lg:w-[70%] mx-auto">
<p class="mx-auto w-full text-lg md:w-[80%] lg:w-[70%]">
{content.settings.subtitle}
</p>
<PrimaryButton
@@ -50,13 +51,13 @@
/>
</div>
<div class=" relative rounded-lg w-full md:w-[800px] md:max-w-[80vw] lg:max-w-[80%] flex">
<span class="absolute w-2 h-2 rounded-full bg-red-500 -top-14 left-3"></span>
<span class="absolute w-2 h-2 rounded-full bg-white top-10 -left-10"></span>
<span class="absolute w-2 h-2 rounded-full bg-yellow-500 -top-10 -right-3"></span>
<span class="absolute w-2 h-2 rounded-full bg-blue-800 top-14 -right-10"></span>
<div class=" relative flex w-full rounded-lg md:w-[800px] md:max-w-[80vw] lg:max-w-[80%]">
<span class="absolute -top-14 left-3 h-2 w-2 rounded-full bg-red-500"></span>
<span class="absolute -left-10 top-10 h-2 w-2 rounded-full bg-white"></span>
<span class="absolute -right-3 -top-10 h-2 w-2 rounded-full bg-yellow-500"></span>
<span class="absolute -right-10 top-14 h-2 w-2 rounded-full bg-blue-800"></span>
<div class="w-full h-full flex items-center justify-center">
<div class="flex h-full w-full items-center justify-center">
<img alt="Hero Banner" src={analytics} class="w-full rounded-lg object-cover" />
</div>
</div>
@@ -67,15 +68,15 @@
<!-- courses -->
{#if coursesSection?.show}
<section id="course" class="px-4 pt-4 pb-20 h-full md:px-10">
<h1 class="text-center text-3xl mb-8 font-bold w-full md:w-[90%] lg:w-[70%] mx-auto">
<section id="course" class="h-full px-4 pb-20 pt-4 md:px-10">
<h1 class="mx-auto mb-8 w-full text-center text-3xl font-bold md:w-[90%] lg:w-[70%]">
{coursesSection.settings.title}
<span class="text-[#F54E00] dark:text-[#EB9D2A]"
>{coursesSection.settings.titleHighlight}</span
>
</h1>
{#if $courses.length > 0}
<section class="flex flex-wrap items-center justify-center mx-auto gap-4">
<section class="mx-auto flex flex-wrap items-center justify-center gap-4">
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<CourseCard
slug={courseData.slug}
@@ -85,17 +86,17 @@
{/each}
</section>
{#if $courses.length > 3}
<div class="w-full flex items-center justify-center my-5">
<div class="my-5 flex w-full items-center justify-center">
<Button
on:click={() => (viewAll = !viewAll)}
class="dark:text-black bg-white text-black capitalize ring-1 ring-[#B17816] dark:bg-[#EB9D2A] hover:scale-95 hover:bg-[#EB9D2A] shadow-[0px_3px_#B17816] font-bold mb-4"
class="mb-4 bg-white font-bold capitalize text-black shadow-[0px_3px_#B17816] ring-1 ring-[#B17816] hover:scale-95 hover:bg-[#EB9D2A] dark:bg-[#EB9D2A] dark:text-black"
>
{viewAll === true ? 'Show less' : 'View more courses'}
</Button>
</div>
{/if}
{:else}
<div class="w-full lg:w-[70%] mx-auto">
<div class="mx-auto w-full lg:w-[70%]">
<EmptyState className="dark:bg-[#232429] dark:border-[#EAEAEA]" />
</div>
{/if}
@@ -106,24 +107,24 @@
{#if aboutSection?.show}
<section
id="about"
class="px-4 md:px-10 pt-4 pb-20 h-full w-full mx-auto md:w-[90%] lg:w-[80%]"
class="mx-auto h-full w-full px-4 pb-20 pt-4 md:w-[90%] md:px-10 lg:w-[80%]"
>
<h1 class="text-start text-3xl mb-2 font-bold md:w-[50%]">{aboutSection.settings.title}</h1>
<p class="text-start text-[#3D3D3D] dark:text-white text-sm font-semibold md:w-[70%] mb-8">
<h1 class="mb-2 text-start text-3xl font-bold md:w-[50%]">{aboutSection.settings.title}</h1>
<p class="mb-8 text-start text-sm font-semibold text-[#3D3D3D] dark:text-white md:w-[70%]">
{aboutSection.settings.subtitle}
</p>
<div>
<p class="text-xl font-bold capitalize mb-6">{aboutSection.settings.benefits.title}</p>
<section class="grid place-items-center grid-cols-1 md:grid-cols-2 gap-4">
<p class="mb-6 text-xl font-bold capitalize">{aboutSection.settings.benefits.title}</p>
<section class="grid grid-cols-1 place-items-center gap-4 md:grid-cols-2">
{#each aboutSection.settings.benefits.list as item}
<div
class="flex flex-col gap-2 p-4 bg-[#E5E7E0] dark:bg-[#232429] border border-[#D0D1C9] h-[150px] max-h-[200px] w-full max-w-full md:max-w-[320px] lg:max-w-[450px] rounded-lg overflow-hidden"
class="flex h-[150px] max-h-[200px] w-full max-w-full flex-col gap-2 overflow-hidden rounded-lg border border-[#D0D1C9] bg-[#E5E7E0] p-4 dark:bg-[#232429] md:max-w-[320px] lg:max-w-[450px]"
>
<span class="flex items-center gap-2">
<CheckmarkFilled size={24} class=" fill-[#F54E00] dark:fill-[#EB9D2A]" />
<p class="text-lg font-bold capitalize line-clamp-1">{item.title}</p>
<p class="line-clamp-1 text-lg font-bold capitalize">{item.title}</p>
</span>
<p class="text-sm line-clamp-4">{item.subtitle}</p>
<p class="line-clamp-4 text-sm">{item.subtitle}</p>
</div>
{/each}
</section>
@@ -134,8 +135,8 @@
<!-- faq -->
{#if faqSection?.show}
<div id="faq">
<h1 class="text-center text-3xl mb-8 font-bold">{faqSection.settings.title}</h1>
<div class="flex flex-wrap items-center justify-center mx-auto gap-8 p-4">
<h1 class="mb-8 text-center text-3xl font-bold">{faqSection.settings.title}</h1>
<div class="mx-auto flex flex-wrap items-center justify-center gap-8 p-4">
{#each faqSection.settings.questions as faq, index}
<FaqCard {index} {faq} />
{/each}
@@ -143,33 +144,33 @@
</div>
{/if}
<!-- footerNote -->
{#if footerNoteSection?.show}
<section class="w-full h-full p-4 md:p-20">
<!-- cta -->
{#if ctaSection?.show}
<section class="h-full w-full p-4 md:p-20">
<section
class="mx-auto w-fit md:w-[80%] flex flex-col items-center justify-between px-2 lg:px-5 py-20 space-y-4 bg-[#E5E7E0] dark:bg-[#232429] rounded-md"
class="mx-auto flex w-fit flex-col items-center justify-between space-y-4 rounded-md bg-[#E5E7E0] px-2 py-20 dark:bg-[#232429] md:w-[80%] lg:px-5"
>
<div class="flex items-center justify-center w-full">
<div class="flex w-full items-center justify-center">
<p
class="text-4xl text-center xl:text-start font-bold w-full mx-auto lg:w-[90%] capitalize"
class="mx-auto w-full text-center text-4xl font-bold capitalize lg:w-[90%] xl:text-start"
>
{footerNoteSection.settings.title}
{ctaSection.settings.title}
<span class="text-[#F54E00]">{footerNoteSection.settings.titleHighlight}</span>
<span class="text-[#F54E00]">{ctaSection.settings.titleHighlight}</span>
</p>
</div>
<div
class="w-full lg:w-[90%] flex flex-col lg:flex-row items-center lg:items-end gap-4 lg:gap-0 lg:justify-between"
class="flex w-full flex-col items-center gap-4 lg:w-[90%] lg:flex-row lg:items-end lg:justify-between lg:gap-0"
>
<p class="text-center lg:text-start text-sm font-semibold w-full lg:w-[50%]">
{footerNoteSection.settings.subtitle}
<p class="w-full text-center text-sm font-semibold lg:w-[50%] lg:text-start">
{ctaSection.settings.subtitle}
</p>
<div class="w-fit">
<PrimaryButton
href={getCourseUrl()}
class="px-8 transition bg-white hover:bg-white text-black"
label={footerNoteSection.settings.buttonLabel}
href={ctaSection.settings?.button?.link}
class="bg-white px-8 text-black transition hover:bg-white"
label={ctaSection.settings?.button?.label}
/>
</div>
</div>
@@ -0,0 +1,24 @@
<script lang="ts">
import { cn } from '$lib/utils';
let { src, alt, className = '', addAcademy = false } = $props();
</script>
{#snippet logo(src = '/logo.png', alt = 'logo', className = '')}
<a href="/#" class={cn('w-20', className)}>
<img
src={src}
alt={alt}
class="mx-auto inline-block rounded text-white"
/>
</a>
{/snippet}
{#if addAcademy}
<div class="flex flex-row items-center gap-1">
{@render logo(src, alt, className)}
<span class="mt-1 text-lg font-semibold italic">Academy</span>
</div>
{:else}
{@render logo(src, alt, className)}
{/if}
@@ -0,0 +1,37 @@
<script lang="ts">
import { cn } from '$lib/utils';
export let width: string | number = 16;
export let height: string | number = 16;
export let x = 0;
export let y = 0;
export let cx = 1;
export let cy = 1;
export let cr = 1;
export let fillColor = 'rgb(163 163 163 / 0.8)';
let className: any = '';
export { className as class };
let id = crypto.randomUUID().toString().slice(0, 10); // generating a unique ID for Components
</script>
<svg
aria-hidden="true"
class={cn('pointer-events-none absolute inset-0 h-full w-full ', className)}
{...$$restProps}
fill={fillColor}
>
<defs>
<pattern
{id}
{width}
{height}
patternUnits="userSpaceOnUse"
patternContentUnits="userSpaceOnUse"
{x}
{y}
>
<circle id="pattern-circle" {cx} {cy} r={cr} />
</pattern>
</defs>
<rect width="100%" height="100%" stroke-width={0} fill={`url(#${id})`} />
</svg>
@@ -0,0 +1 @@
export { default as DotPattern } from './dot-pattern.svelte';
@@ -38,7 +38,7 @@
</script>
<script lang="ts">
import { cn } from '$lib/utils.js';
import { cn } from '$lib/utils';
let {
class: className,
@@ -17,12 +17,12 @@
>
<span>
<span
class="spark mask-gradient animate-flip before:animate-kitrotate absolute inset-0 h-full w-full overflow-hidden rounded dark:[mask:linear-gradient(white,_transparent_50%)] before:absolute before:aspect-square before:w-[200%] before:rotate-[-90deg] before:dark:bg-[conic-gradient(from_0deg,transparent_0_340deg,white_360deg)] before:bg-[conic-gradient(from_0deg,black_360deg,transparent_0_340deg)] before:content-[''] before:[inset:0_auto_auto_50%] before:[translate:-50%_-15%]"
></span>
class="spark mask-gradient animate-flip before:animate-kitrotate absolute inset-0 h-full w-full overflow-hidden rounded [mask:linear-gradient(white,_transparent_50%)] before:absolute before:aspect-square before:w-[200%] before:rotate-[-90deg] before:dark:bg-[conic-gradient(from_0deg,transparent_0_340deg,white_360deg)] before:bg-[conic-gradient(from_0deg,black_360deg,transparent_0_340deg)] before:content-[''] before:[inset:0_auto_auto_50%] before:[translate:-50%_-15%]"
></span>
</span>
<span
class="backdrop absolute inset-px rounded bg-neutral-950 transition-colors"
style="--duration: {duration}; --delay: {delay};"
></span>
></span>
{@render children?.()}
</div>
@@ -2,7 +2,7 @@
import { Checkbox as CheckboxPrimitive } from "bits-ui";
import Check from "lucide-svelte/icons/check";
import Minus from "lucide-svelte/icons/minus";
import { cn } from "$lib/utils.js";
import { cn } from "$lib/utils";
type $$Props = CheckboxPrimitive.Props;
type $$Events = CheckboxPrimitive.Events;
@@ -1,6 +1,6 @@
<script lang="ts">
import { Label as LabelPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
import { cn } from "$lib/utils";
type $$Props = LabelPrimitive.Props;
type $$Events = LabelPrimitive.Events;
@@ -21,7 +21,6 @@
class: className = '',
children
}: Props = $props();
let gradSize = useMotionValue(gradientSize);
let gradColor = useMotionValue(gradientColor);
@@ -46,32 +45,31 @@
let bg = useMotionTemplate`radial-gradient(${gradSize}px circle at ${mouseX}px ${mouseY}px, ${gradColor}, transparent 100%)`;
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
onmousemove={handleMouseMove}
onmouseleave={handleMouseLeave}
class={cn(
'group relative flex w-fit rounded bg-neutral-100 dark:bg-neutral-900 justify-center',
className
)}
onmousemove={handleMouseMove}
onmouseleave={handleMouseLeave}
class={cn('group relative flex w-fit justify-center rounded bg-neutral-900', className)}
>
<CardAnimatedBorder index={index + 1}>
<div class="relative z-10">
<CardAnimatedBorder index={index + 1}>
<div class="relative z-10">
{@render children?.()}
</div>
</div>
<Motion
style={{
background: bg,
opacity: gradientOpacity
}}
let:motion
>
{#snippet children({ motion })}
<div
use:motion
class="pointer-events-none absolute inset-px rounded opacity-0 transition-opacity duration-300 group-hover:opacity-100"
></div>
{/snippet}
</Motion>
<div
use:motion
class="pointer-events-none absolute inset-px rounded opacity-0 transition-opacity duration-300 group-hover:opacity-100"
></div>
</Motion>
</CardAnimatedBorder>
</div>
</div>
@@ -1,16 +1,15 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils';
type $$Props = HTMLAttributes<HTMLDivElement>;
type $$Props = HTMLAttributes<HTMLDivElement>;
interface Props {
class?: $$Props["class"];
[key: string]: any
}
interface Props {
class?: $$Props['class'];
[key: string]: any;
}
let { class: className = undefined, ...rest }: Props = $props();
let { class: className = undefined, ...rest }: Props = $props();
</script>
<div class={cn("bg-muted animate-pulse rounded-md", className)} {...rest}></div>
<div class={cn('bg-muted animate-pulse rounded-md', className)} {...rest}></div>
@@ -24,16 +24,20 @@
</script>
<div
class="hover:dark:bg-[#282828] space-y-2 shadow-md bg-white dark:bg-inherit border dark:border-[#282828] rounded"
class="hover:bg-[#282828] text-white space-y-2 shadow-md bg-[#282828] {isOpen
? 'bg-[#282828]'
: 'bg-inherit'} border border-[#282828] rounded"
>
<!-- Accordion Header -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="flex justify-between items-center cursor-pointer p-4 bg-white dark:bg-inherit rounded"
class="flex justify-between items-center cursor-pointer p-4 bg-inherit rounded"
onclick={toggleAccordion}
>
<p class="font-semibold text-lg">{title}</p>
<!-- Icon changes based on the state -->
<div class="flex items-center justify-center p-[2px] dark:bg-[#232429]">
<div class="flex items-center justify-center p-[2px] bg-[#232429]">
{#if isOpen}
<Subtract class="text-xl transition-transform duration-200 ease-in" />
{:else}
@@ -47,7 +51,7 @@
bind:this={contentRef}
style="max-height: {contentHeight}; transition: max-height 0.3s ease-in-out; overflow: hidden;"
>
<div class="p-4 bg-white dark:bg-inherit rounded-b">
<div class="p-4 bg-inherit rounded-b">
<p>
{content}
</p>
@@ -0,0 +1,46 @@
<script lang="ts">
import PrimaryButton from './PrimaryButton.svelte';
interface Props {
className?: string;
bannerImage: string | undefined;
slug?: string;
title?: string;
description?: string;
}
let { className = '', bannerImage, slug = '', title = '', description = '' }: Props = $props();
function getCourseUrl() {
return `/course/${slug}`;
}
</script>
<div
class="h-fit w-full min-w-[250px] max-w-[320px] cursor-pointer space-y-4 md:min-w-[200px] md:max-w-[300px]"
>
<div
class="relative space-y-4 rounded-sm border border-[#282828] px-2 py-2 hover:bg-[#282828] {className} shadow"
>
<div class="overflow-hidden rounded-md">
<img
src={bannerImage ? bannerImage : '/course-banner.jpg'}
alt=""
class="h-44 w-full object-cover"
/>
<div class="overflow-hidden py-4">
<p class="line-clamp-1 overflow-ellipsis text-lg font-semibold">
{title}
</p>
</div>
<p class=" line-clamp-3 overflow-ellipsis text-justify text-[#ABABAB]">
{description}
</p>
</div>
<PrimaryButton
href={getCourseUrl()}
class="w-full rounded border border-white bg-transparent text-white hover:bg-white hover:text-black"
label="Go to Course"
/>
</div>
</div>
@@ -0,0 +1,43 @@
<script lang="ts">
import { getPageSection } from '$lib/utils/helpers/page';
import { sharedPage } from '$lib/utils/stores/pages';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
import { SECTION } from '@/utils/constants/page';
const content = $derived(getPageSection($sharedPage, SECTION.FOOTER));
const seo = $derived(getPageSection($sharedPage, SECTION.SEO));
</script>
{#if content?.show}
<nav
class="flex w-full flex-col items-start gap-4 bg-black px-6 py-4 text-white md:flex-row-reverse md:items-center md:justify-between"
>
<div class="w-fit rounded-md bg-white p-2">
<Logo src={seo?.settings.logo} alt={seo?.settings.title} />
</div>
<ul
class="-md:gap-8 flex flex-col items-start gap-4 font-semibold capitalize underline md:flex-row md:items-center"
>
{#if content.settings.twitter}
<a href={content.settings.twitter} target="_blank" title="twitter">Twitter</a>
{/if}
{#if content.settings?.youtube}
<a href={content.settings?.youtube} target="_blank">Youtube</a>{/if}
{#if content.settings.linkedin}
<a href={content.settings.linkedin} target="_blank" title="linkedin">LinkedIn</a>
{/if}
{#if content.settings.facebook}
<a href={content.settings.facebook} target="_blank" title="facebook">facebook</a>
{/if}
</ul>
<a
href="https://classroomio.com"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-1 text-blue-600 hover:underline dark:text-white"
>
<p class="text-base font-semibold">Built on ClassroomIO</p>
</a>
</nav>
{/if}
@@ -0,0 +1,100 @@
<script lang="ts">
import Close from 'carbon-icons-svelte/lib/Close.svelte';
import Menu from 'carbon-icons-svelte/lib/Menu.svelte';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
import DirectionStraightRight from 'carbon-icons-svelte/lib/DirectionStraightRight.svelte';
import { ArrowUpRight, Demo } from 'carbon-icons-svelte';
import { goto } from '$app/navigation';
import { getPageSection } from '@/utils/helpers/page';
import { sharedPage } from '@/utils/stores/pages';
import PrimaryButton from './PrimaryButton.svelte';
import { SECTION } from '@/utils/constants/page';
let open = $state(false);
function toggleMenu() {
open = !open;
}
const seo = $derived(getPageSection($sharedPage, SECTION.SEO));
const content = $derived(getPageSection($sharedPage, SECTION.NAVIGATION));
</script>
<!-- testing this out for now -->
<section class="fixed left-[5%] top-2 z-50 mx-auto w-[90%] lg:left-[2%] lg:w-fit">
<div class="flex flex-row-reverse items-start justify-between">
<div class="z-50 flex items-center p-1 lg:hidden">
<button
onclick={toggleMenu}
class="flex w-fit items-center gap-2 bg-gray-900 p-2 text-sm text-white transition"
>
{#if open}
<Close size={24} />
<p class="font-semibold uppercase">close</p>
{:else}
<Menu size={24} />
<p class="font-semibold uppercase">Menu</p>
{/if}
</button>
</div>
<nav
class="absolute w-full py-2 transition lg:relative {open
? 'translate-x-0 lg:translate-x-0 '
: '-translate-x-full lg:translate-x-0 '}"
>
<!-- Desktop nav -->
<div class="relative h-80 w-52 rounded bg-neutral-900 py-2 text-sm text-white transition">
<div class="ml-2 w-fit rounded-md bg-white p-2">
<Logo
src={seo?.settings.logo}
alt={`${seo?.settings.title} logo`}
className=" flex-col bg-white"
/>
</div>
<div class="flex flex-col gap-2 space-y-2 border-b px-4 py-4">
{#each content?.settings.navItems.slice(0, 2) as navItem}
<a href={navItem.link} onclick={toggleMenu} class="flex items-center gap-2">
{#if navItem.redirect}
<ArrowUpRight />
{:else}
<Demo />
{/if}
<p>{navItem.title}</p>
</a>
{/each}
</div>
<div class="flex flex-col gap-2 space-y-2 border-b px-4 py-4">
{#each content?.settings.navItems.slice(2, content?.settings.navItems.length) as navItem}
<a href={navItem.link} onclick={toggleMenu} class="flex items-center gap-2">
{#if navItem.redirect}
<ArrowUpRight />
{:else}
<Demo />
{/if}
<p>{navItem.title}</p>
</a>
{/each}
</div>
<div class="px-2 py-6">
<PrimaryButton
class="bg-webflow hover:bg-webflow-secondary group flex h-fit w-full items-center gap-6 rounded px-2 py-2 text-xs text-white"
onClick={() => {
goto('/courses');
toggleMenu();
}}
label="Learn with us"
>
<DirectionStraightRight
size={16}
class="transition-transform duration-300 group-hover:translate-x-1"
/>
</PrimaryButton>
</div>
</div>
</nav>
</div>
</section>
@@ -0,0 +1,25 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { cn } from '$lib/utils';
interface Props {
onClick?: () => void;
label?: string;
href?: string;
class?: string;
children?: any;
}
const { onClick, children, label, href, class: className, ...restProps }: Props = $props();
</script>
<Button
{href}
onclick={onClick}
class={cn('group bg-[#0737BE] hover:bg-[#0737BE] rounded-none text-white gap-6', className)}
{...restProps}
>
{label}
{#if children}
{@render children?.()}
{/if}
</Button>
@@ -1,5 +1,5 @@
<script lang="ts">
import MagicCard from '$lib/components/magiccard/MagicCard.svelte';
import MagicCard from '$lib/components/ui/magiccard/MagicCard.svelte';
interface Props {
index?: number;
@@ -8,27 +8,22 @@
banner?: string;
}
let {
index = 1,
description = '',
name = '',
banner = ''
}: Props = $props();
let { index = 1, description = '', name = '', banner = '' }: Props = $props();
</script>
<MagicCard {index}>
<section
class="cursor-pointer flex flex-col justify-between relative border-[#EAEAEA] px-6 pt-6 space-y-4 bg-white dark:bg-inherit border dark:border-[#282828] rounded w-full h-full max-w-full lg:h-[280px]"
class="cursor-pointer flex flex-col justify-between relative px-6 pt-6 space-y-4 hover:bg-[#282828] bg-inherit border border-[#282828] rounded w-full h-full max-w-full lg:h-[280px]"
>
<img src="/quote.svg" alt="" class="w-6 h-6 absolute top-6 left-8" />
<div>
<p class="my-6 text-[#282828] dark:text-[#ABABAB] line-clamp-5">
<p class="my-6 text-[#ABABAB] line-clamp-5">
{description}
</p>
</div>
<div class="flex items-center p-4 gap-4 border-[#EAEAEA]">
<img
src={banner ? banner : '/classroomio-course-img-template.jpg'}
src={banner ? banner : '/course-banner.jpg'}
alt=""
class="w-6 h-6 rounded-full"
/>
@@ -0,0 +1,81 @@
<svg
width="168"
height="191"
viewBox="0 0 168 191"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M146.744 71.4805H83.6106L78.5507 59.1652C77.772 57.396 76.4872 55.8967 74.858 54.8562C73.2289 53.8157 71.3283 53.2806 69.3956 53.3183H33.7102C31.7775 53.2806 29.8769 53.8157 28.2478 54.8562C26.6186 55.8967 25.3338 57.396 24.5551 59.1652L21.1108 70.3265C20.4728 72.4035 20.1438 74.563 20.1344 76.7357L19.5071 158.404C19.4897 160.913 20.4691 163.327 22.2301 165.114C23.9911 166.902 26.3897 167.917 28.8989 167.938H156.135V81.0203C156.154 78.51 155.176 76.095 153.414 74.3061C151.653 72.5172 149.254 71.5009 146.744 71.4805Z"
fill="#032587"
/>
<path
d="M146.744 71.4805H83.6108L78.5509 59.1652C77.7723 57.396 76.4874 55.8967 74.8583 54.8562C73.2291 53.8157 71.3285 53.2806 69.3958 53.3183H33.7105C32.4474 53.3178 31.1946 53.5443 30.0117 53.987H64.7561C68.8218 53.987 74.3847 56.3187 75.8287 59.834L80.8886 72.1552H142.104C147.288 72.1552 153.413 76.422 153.413 81.689V167.92H156.136V81.0203C156.155 78.51 155.176 76.095 153.415 74.3061C151.654 72.5172 149.254 71.5009 146.744 71.4805Z"
fill="white"
/>
<path
d="M146.744 71.4805H83.6106L78.5507 59.1652C77.772 57.396 76.4872 55.8967 74.858 54.8562C73.2289 53.8157 71.3283 53.2806 69.3956 53.3183H33.7102C31.7775 53.2806 29.8769 53.8157 28.2478 54.8562C26.6186 55.8967 25.3338 57.396 24.5551 59.1652L21.1108 70.3265C20.4728 72.4035 20.1438 74.563 20.1344 76.7357L19.5071 158.404C19.4897 160.913 20.4691 163.327 22.2301 165.114C23.9911 166.902 26.3897 167.917 28.8989 167.938H156.135V81.0203C156.154 78.51 155.176 76.095 153.414 74.3061C151.653 72.5172 149.254 71.5009 146.744 71.4805Z"
stroke="#263238"
stroke-width="1.47949"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M29.6983 167.917H156.136L134.464 96.9016C133.911 95.0497 132.777 93.4248 131.23 92.2665C129.683 91.1082 127.804 90.478 125.871 90.4688H10.0092C8.58813 90.485 7.19062 90.8334 5.92823 91.4861C4.66584 92.1387 3.57356 93.0776 2.73865 94.2276C1.90375 95.3776 1.34935 96.7069 1.11974 98.1093C0.890134 99.5118 0.991673 100.948 1.41626 102.305L18.9927 159.898C19.6833 162.205 21.0962 164.229 23.0234 165.673C24.9507 167.116 27.2904 167.903 29.6983 167.917Z"
fill="#0233BD"
/>
<path
d="M7.42288 93.6171H123.291C125.223 93.6273 127.102 94.2579 128.649 95.416C130.196 96.5742 131.33 98.1986 131.884 100.05L152.597 167.917H156.148L134.476 96.9016C133.923 95.0497 132.789 93.4248 131.242 92.2665C129.694 91.1082 127.816 90.478 125.883 90.4688H10.009C8.33774 90.4722 6.70093 90.9443 5.2845 91.8314C3.86807 92.7185 2.7288 93.9851 1.99609 95.4872C3.5445 94.2736 5.45555 93.6151 7.42288 93.6171Z"
fill="white"
/>
<path
d="M29.6983 167.917H156.136L134.464 96.9016C133.911 95.0497 132.777 93.4248 131.23 92.2665C129.683 91.1082 127.804 90.478 125.871 90.4688H10.0092C8.58813 90.485 7.19062 90.8334 5.92823 91.4861C4.66584 92.1387 3.57356 93.0776 2.73865 94.2276C1.90375 95.3776 1.34935 96.7069 1.11974 98.1093C0.890134 99.5118 0.991673 100.948 1.41626 102.305L18.9927 159.898C19.6833 162.205 21.0962 164.229 23.0234 165.673C24.9507 167.116 27.2904 167.903 29.6983 167.917Z"
stroke="#263238"
stroke-width="1.47949"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M71.3727 145.165C79.0338 145.165 85.2444 138.955 85.2444 131.294C85.2444 123.632 79.0338 117.422 71.3727 117.422C63.7115 117.422 57.501 123.632 57.501 131.294C57.501 138.955 63.7115 145.165 71.3727 145.165Z"
fill="#263238"
stroke="#263238"
stroke-width="1.47949"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M64.6855 137.982L78.0661 124.578"
stroke="#F2F2F2"
stroke-width="1.47949"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M64.6855 124.578L78.0661 137.959"
stroke="#F2F2F2"
stroke-width="1.47949"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M163.982 9.00001H126.415C125.774 9.00001 125.16 9.2544 124.707 9.70721C124.254 10.16 124 10.7742 124 11.4145V40.1286C124 40.7689 124.254 41.3831 124.707 41.8359C125.16 42.2887 125.774 42.5431 126.415 42.5431H135.735V51.7396C135.724 51.9767 135.785 52.2116 135.909 52.4139C136.033 52.6162 136.214 52.7765 136.431 52.8741C136.647 52.9718 136.888 53.0021 137.121 52.9613C137.355 52.9204 137.571 52.8103 137.742 52.6451L149.128 42.5431H163.982C164.622 42.5431 165.236 42.2887 165.689 41.8359C166.142 41.3831 166.396 40.7689 166.396 40.1286V11.4145C166.397 11.0972 166.335 10.7829 166.214 10.4896C166.093 10.1963 165.915 9.92983 165.691 9.70547C165.467 9.4811 165.2 9.30328 164.907 9.18221C164.613 9.06115 164.299 8.99923 163.982 9.00001Z"
fill="white"
stroke="#263238"
stroke-width="1.47949"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M144.719 29.8483C144.477 29.8525 144.239 29.7971 144.024 29.687C143.809 29.5768 143.624 29.4154 143.487 29.217C143.349 29.0186 143.263 28.7893 143.235 28.5495C143.207 28.3096 143.239 28.0667 143.328 27.8421C143.577 27.2749 143.911 26.7482 144.316 26.2798C144.706 25.8248 145.127 25.3977 145.577 25.0015C146.021 24.6109 146.441 24.2203 146.837 23.8179C147.212 23.4683 147.538 23.0702 147.808 22.6343C148.118 22.0995 148.237 21.4754 148.147 20.8638C148.057 20.2523 147.762 19.6894 147.311 19.267C146.44 18.5913 145.351 18.2586 144.251 18.3319C143.243 18.3061 142.241 18.5084 141.322 18.9237C140.861 19.1388 140.432 19.4177 140.049 19.7522C139.763 19.9972 139.404 20.1414 139.028 20.1628C138.651 20.1842 138.279 20.0815 137.966 19.8706C137.738 19.7172 137.549 19.5131 137.414 19.2743C137.278 19.0356 137.2 18.7686 137.185 18.4945C137.17 18.2203 137.219 17.9465 137.328 17.6944C137.437 17.4424 137.603 17.219 137.812 17.0418C138.359 16.6036 138.955 16.2302 139.588 15.9292C141.154 15.2226 142.859 14.8768 144.577 14.9172C145.948 14.8869 147.313 15.1137 148.601 15.586C149.677 15.9803 150.619 16.6719 151.317 17.5803C151.97 18.4556 152.31 19.5248 152.282 20.6163C152.302 21.377 152.176 22.1345 151.909 22.8473C151.673 23.4382 151.341 23.986 150.927 24.4689C150.522 24.9353 150.085 25.3723 149.619 25.7767C149.145 26.1851 148.713 26.5816 148.305 26.9603C147.912 27.3411 147.577 27.7778 147.311 28.2564C147.209 28.4443 147.127 28.6427 147.068 28.8482C146.975 29.1491 146.788 29.4125 146.535 29.5998C146.281 29.7871 145.975 29.8887 145.66 29.8897L144.719 29.8483ZM144.943 37.299C144.621 37.3054 144.3 37.247 144.001 37.1274C143.701 37.0078 143.429 36.8293 143.199 36.6026C142.97 36.3759 142.788 36.1054 142.665 35.8073C142.541 35.5091 142.479 35.1893 142.482 34.8667C142.476 34.551 142.535 34.2374 142.655 33.9453C142.775 33.6532 142.954 33.3887 143.18 33.1683C143.665 32.7241 144.298 32.4777 144.955 32.4777C145.613 32.4777 146.246 32.7241 146.731 33.1683C146.954 33.3902 147.13 33.6551 147.248 33.9471C147.366 34.239 147.424 34.5519 147.417 34.8667C147.422 35.1926 147.361 35.5161 147.237 35.8174C147.113 36.1187 146.928 36.3915 146.695 36.6192C146.462 36.8468 146.185 37.0245 145.88 37.1414C145.576 37.2583 145.251 37.3119 144.926 37.299H144.943Z"
fill="#EC644E"
stroke="#263238"
stroke-width="1.47949"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M89.0291 190.495C131.502 190.495 165.933 188.569 165.933 186.193C165.933 183.817 131.502 181.891 89.0291 181.891C46.5561 181.891 12.125 183.817 12.125 186.193C12.125 188.569 46.5561 190.495 89.0291 190.495Z"
fill="#8192C3"
fill-opacity="0.2"
/>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

Before

Width:  |  Height:  |  Size: 495 KiB

After

Width:  |  Height:  |  Size: 495 KiB

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,15 @@
import type { Component } from 'svelte';
import CoursesPage from './pages/Courses.svelte';
import HomePage from './pages/Home.svelte';
import Navigation from './Navigation.svelte';
import Footer from './Footer.svelte';
import Button from './PrimaryButton.svelte';
import type { TemplateComponents } from '$lib/utils/types/template';
export const components: TemplateComponents = {
navigation: Navigation as unknown as Component,
footer: Footer as unknown as Component,
button: Button as unknown as Component,
coursespage: CoursesPage as unknown as Component,
homepage: HomePage as unknown as Component
};
@@ -0,0 +1,165 @@
<script lang="ts">
import { Button } from '@/components/ui/button';
import { COURSE_TYPE } from '@/utils/constants/course';
import { getPageSection } from '@/utils/helpers/page';
import type { Course, CourseFilterItem } from '@/utils/types/course';
import { DotPattern } from '$lib/components/ui/animation/dotpattern';
import CourseCard from '../CourseCard.svelte';
import EmptyState from '../EmptyState.svelte';
import type { Page } from '@/utils/types/page';
import { SECTION } from '@/utils/constants/page';
interface Props {
data: {
page: Page;
sharedPage: Page;
courses: Course[];
};
}
let { data }: Props = $props();
/**
* State
*/
let viewAll = $state(false);
let filter: CourseFilterItem[] = $state([
{
title: 'Self Paced',
type: COURSE_TYPE.PACED,
checked: false
},
{
title: 'Live Sessions',
type: COURSE_TYPE.LIVE,
checked: false
}
]);
let filteredCourses = $state([...data.courses]);
/**
* Constants
*/
const section = $derived({
header: getPageSection(data.page, SECTION.HERO),
courses: getPageSection(data.page, SECTION.COURSE)
});
/**
* Functions
*/
function applyFilter() {
const activeFilters = new Set(
filter
.filter((filterItem) => filterItem.checked)
.map((filterItem) => filterItem.type.toLowerCase())
);
filteredCourses =
activeFilters.size === 0
? data.courses
: data.courses.filter((course) => activeFilters.has(course.type.toLowerCase()));
}
</script>
<section class="bg-black text-white">
<div class="flex items-start lg:gap-4">
<div class="w-full">
{#if section.header?.show}
<section class="flex min-h-[50vh] items-center justify-center px-2 py-20 lg:px-14">
<section
class="relative flex w-full flex-col-reverse items-center justify-center gap-5 text-center md:flex-col lg:ml-auto lg:w-[80%]"
>
<DotPattern
class="[mask-image:radial-gradient(30vw_circle_at_center,white,transparent)]"
/>
<div class="z-10 mb-4 w-full space-y-6">
<p class="mx-auto w-full text-3xl font-bold md:w-[90%] md:text-6xl">
{section.header.settings.title}
</p>
<p class="mx-auto w-full text-lg text-gray-400 md:w-[70%]">
{section.header.settings.subtitle}
</p>
</div>
</section>
</section>
{/if}
<div class="w-full lg:ml-auto lg:w-[80%]">
{#if section.courses?.show}
<section id="course" class="h-full px-2 pb-20 pt-4 xl:px-6">
<div class="max-w-2xl">
<h1 class="mb-2 pl-2 text-start text-3xl font-bold">
{section.courses.settings.title}
</h1>
<p class="mb-2 pl-2 text-start text-gray-400">
{section.courses.settings.subtitle}
</p>
</div>
<div class="mx-auto max-w-6xl">
{#if data.courses.length > 0}
<div class="mt-10 flex w-full items-start justify-start lg:gap-2">
<div class="hidden min-w-max">
<p class="mb-4 font-semibold">Filter by</p>
<div class="w-full space-y-2">
{#each filter as item}
<form
class="space-x-2 rounded-md border border-[#232429] bg-[#232429] px-4 py-4 font-medium text-white"
>
<input
type="checkbox"
checked={item.checked}
name={item.title}
onchange={applyFilter}
class="focus:ring-0"
/>
<label for={item.title} class="whitespace-nowrap">{item.title}</label>
</form>
{/each}
</div>
</div>
<section class="flex w-fit flex-wrap items-start gap-4">
{#each filteredCourses.slice(0, viewAll ? filteredCourses.length : 3) as courseData}
<CourseCard
slug={courseData.slug}
title={courseData.title}
bannerImage={courseData.banner}
description={courseData.description}
/>
{/each}
</section>
{#if filteredCourses.length == 0}
<div class="mx-auto w-full px-4">
<EmptyState className="dark:bg-[#232429] dark:border-[#EAEAEA]" />
</div>
{/if}
</div>
{#if filteredCourses.length > 3}
<div class="my-5 flex w-full items-center justify-center">
<Button
on:click={() => (viewAll = !viewAll)}
class="w-full gap-6 rounded-none bg-[#0737BE] text-white hover:bg-[#0737BE]"
>{viewAll === true ? 'Show less' : 'View more courses'}</Button
>
</div>
{/if}
{:else}
<div class="mx-auto w-full px-4 lg:w-[70%]">
<EmptyState className="dark:bg-[#232429] dark:border-[#EAEAEA]" />
</div>
{/if}
</div>
</section>
{/if}
</div>
</div>
<!-- <Footer data={org} /> -->
</div>
</section>
@@ -0,0 +1,167 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { getPageSection } from '@/utils/helpers/page';
import { homePage } from '@/utils/stores/pages';
import DirectionStraightRight from 'carbon-icons-svelte/lib/DirectionStraightRight.svelte';
import { onMount } from 'svelte';
import EmptyState from '../EmptyState.svelte';
import { DotPattern } from '$lib/components/ui/animation/dotpattern';
import CourseCard from '../CourseCard.svelte';
import { courses } from '@/utils/stores/course';
import Accordion from '../Accordion.svelte';
import TestimonialCard from '../TestimonialCard.svelte';
import PrimaryButton from '../PrimaryButton.svelte';
import { SECTION } from '@/utils/constants/page';
const heroSection = $derived(getPageSection($homePage, SECTION.HERO));
const courseSection = $derived(getPageSection($homePage, SECTION.COURSE));
const faqSection = $derived(getPageSection($homePage, SECTION.FAQ));
const testimonialSection = $derived(getPageSection($homePage, SECTION.TESTIMONIAL));
let viewAll = false;
let scrollPosition = $state(0);
let letters = $derived(heroSection?.settings.title.split(''));
let subtitleLetters = $derived(heroSection?.settings.subtitle.split(''));
const subtitleFadeOffset = $derived(letters.length * 3);
onMount(() => {
const handleScroll = () => {
scrollPosition = window.scrollY;
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
});
</script>
<section class="bg-black text-white">
<div class="flex items-start lg:gap-4">
<div class="w-full">
{#if heroSection?.show}
<section
class="flex min-h-[50vh] items-center justify-center overflow-hidden px-2 py-20 lg:px-14"
>
<div
class="relative flex w-full flex-col-reverse items-center justify-center gap-5 text-center md:flex-col lg:ml-auto lg:w-[80%]"
>
<DotPattern
class="[mask-image:radial-gradient(30vw_circle_at_center,white,transparent)]"
/>
<div class="z-10 mb-4 w-full space-y-6">
<p class="mx-auto w-full text-3xl font-bold md:w-[90%] md:text-6xl">
{#each letters as letter, index}
<span>
{letter}
</span>
{/each}
</p>
<p class="mx-auto w-full text-lg md:w-[70%]">
{#each subtitleLetters as letter, index}
<span
style="opacity: {Math.max(
0,
1 - (scrollPosition - (index * 10 + subtitleFadeOffset)) / 150
)}"
class="transition-opacity duration-300"
>
{letter}
</span>
{/each}
</p>
<PrimaryButton
href={heroSection?.settings.action.link}
label={heroSection?.settings.action.label}
>
<DirectionStraightRight
class="transition-transform duration-300 group-hover:translate-x-1"
/>
</PrimaryButton>
</div>
</div>
</section>
{/if}
<!-- <div class="fixed lg:sticky top-2 left-[5%] lg:left-[2%] w-[90%] mx-auto lg:w-fit z-50">
<Navigation />
</div> -->
<!-- courses -->
<div class="w-full lg:ml-auto lg:w-[80%]">
{#if courseSection?.show}
<section id="course" class="h-full px-2 pb-20 pt-4 md:px-5">
<h1 class="mb-2 pl-2 text-start text-3xl font-bold">
{courseSection.settings.title}
</h1>
<p class="mb-2 pl-2 text-start text-[#878787]">{courseSection.settings.subtitle}</p>
{#if $courses.length > 0}
<section class="grid w-full grid-cols-1 place-items-center gap-4 p-2 md:grid-cols-3">
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<CourseCard
slug={courseData.slug}
title={courseData.title}
bannerImage={courseData.banner}
description={courseData.description}
/>
{/each}
</section>
{#if $courses.length > 3}
<div class="my-5 flex w-full items-center justify-center px-2">
<PrimaryButton
onClick={() => {
goto('/courses');
}}
class="group w-full gap-6 rounded-none text-white"
label="View all courses"
>
<DirectionStraightRight
class="transition-transform duration-300 group-hover:translate-x-1"
/>
</PrimaryButton>
</div>
{/if}
{:else}
<div class="mx-auto w-full">
<EmptyState className="dark:bg-[#232429] dark:border-[#EAEAEA]" />
</div>
{/if}
</section>
{/if}
<!-- faq -->
{#if faqSection?.show}
<section id="faq" class="h-full px-2 pb-20 pt-4">
<h1 class="mb-4 text-center text-3xl font-bold">{faqSection.settings.title}</h1>
<section class="mx-auto w-full space-y-2 px-2 py-5">
{#each faqSection.settings.questions as faq}
<Accordion title={faq.title} content={faq.content} />
{/each}
</section>
</section>
{/if}
<!-- testimonial -->
{#if testimonialSection?.show}
<section id="testimonial" class="h-full px-4 pb-20 pt-4 lg:px-5">
<h1 class="mb-4 text-start text-3xl font-bold">Testimonial</h1>
<section
class="grid grid-cols-1 place-items-center gap-4 md:grid-cols-2 xl:grid-cols-3"
>
{#each testimonialSection.settings.list as item, i}
<TestimonialCard description={item.description} name={item.name} index={i} />
{/each}
</section>
</section>
{/if}
</div>
</div>
<!-- <Footer data={org} /> -->
</div>
</section>
@@ -261,10 +261,14 @@
{
"type": "cta",
"settings": {
"title": "programs that you will love.",
"title": "Programs that you will love.",
"titleHighlight": "100% guaranteed",
"subtitle": "Unlock skills you'll use for life with courses designed to inspire, empower, and elevate your expertise.",
"buttonLabel": "Learn More"
"button": {
"label": "Learn More",
"link": "/courses",
"redirect": false
}
},
"show": true
},
@@ -273,7 +277,11 @@
"settings": {
"title": "Join our mailing list",
"subtitle": "We are constantly releasing new courses and sharing them with our email list. Subscribe to get notified once we release a new course.",
"buttonLabel": "Subscribe",
"button": {
"label": "Subscribe",
"link": "/#",
"redirect": false
},
"show": true
}
}
@@ -309,133 +317,22 @@
"type": "courses",
"show": true,
"settings": {
"title": "Unlock your potential with Engaging and innovative online courses",
"title": "Unlock your potential with engaging and innovative online courses",
"titleHighlight": "",
"subtitle": "Explore creative and tech courses that will help you own you skill set"
}
},
{
"type": "pathway",
"show": true,
"settings": {
"title": "Become an expert with our \"Learning Path\" Programs",
"titleHighlight": "",
"subtitle": "With over 5 years of extensive coding experience, I am equipped to help you achieve success in your choice of coding career."
}
},
{
"type": "faq",
"show": true,
"settings": {
"title": "Frequently Asked Questions.",
"subtitle": "Our courses will help you make sense of the numbers, drive improvements, and create products that stick.",
"questions": [
{
"id": 1,
"title": "What sets your platform apart from others?",
"content": "Our platform is focused on providing practical, project-based learning. We aim to equip learners with skills that are directly applicable to their career paths. From real-world projects to experienced instructors, we focus on a hands-on approach to help you master each topic efficiently."
},
{
"id": 2,
"title": "Are the courses beginner-friendly?",
"content": "Absolutely! Whether you're a complete beginner or looking to advance your skills, we have courses designed for every level. Our step-by-step approach ensures that even the most complex topics are broken down into easy-to-understand lessons."
},
{
"id": 3,
"title": "How long does it take to complete a course?",
"content": "Course lengths vary depending on the subject. However, most of our courses are designed to be completed at your own pace, allowing you to take the time you need to fully understand the material before moving on."
}
]
}
},
{
"type": "testimonial",
"show": true,
"settings": {
"list": [
{
"name": "Zulaike Elebute",
"role": "Web Developer",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Zulaike Elebute",
"role": "Content Writer",
"description": "The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Oluwaseun olaniyan",
"role": "Frontend Developement",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Zulaike Elebute",
"role": "Web Developer",
"description": "The feedback and real-world projects were invaluable—I’m now confident in delivering high-quality work that connects and engages"
},
{
"name": "Oluwaseun olaniyan",
"role": "Content Writer",
"description": "The lessons were comprehensive, covering everything from HTML to advanced JavaScript. Working on interactive projects transformed my approach, and I’m now ready to create polished, user-friendly interfaces!"
},
{
"name": "Oluwaseun olaniyan",
"role": "Web Developer",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Oluwaseun olaniyan",
"role": "Content Writer",
"description": " revelation. I learned techniques to craft compelling, audience-focused content that tells a story. The feedback and real-world projects were invaluable—I’m now confident in delivering high-quality work that connects and engages"
},
{
"name": "Oluwaseun olaniyan",
"role": "Frontend Developement",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Oluwaseun olaniyan",
"role": "Web Developer",
"description": "Through hands-on projects, I learned to build responsive, secure websites from scratch. I now feel equipped to tackle complex challenges with confidence"
},
{
"name": "Oluwaseun olaniyan",
"role": "Web Developer",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
}
]
}
},
{
"type": "contact",
"show": true,
"settings": {
"title": "Have you got any",
"titleHighlight": "Questions?",
"subtitle": "Reach out to us with any questions you might have.",
"address": "Ashwoodfield House Farm, Kidderminster Rd, Stourbridge, Kingswinford DY6 0AA",
"phone": "+4407339904995",
"email": "[email protected]"
}
},
{
"type": "cta",
"settings": {
"title": "programs that you will love.",
"title": "Programs that you will love.",
"titleHighlight": "100% guaranteed",
"subtitle": "Unlock skills you'll use for life with courses designed to inspire, empower, and elevate your expertise.",
"buttonLabel": "Learn More",
"show": true
}
},
{
"type": "mailinglist",
"settings": {
"title": "Join our mailing list",
"subtitle": "We are constantly releasing new courses and sharing them with our email list. Subscribe to get notified once we release a new course.",
"buttonLabel": "Subscribe",
"button": {
"label": "Learn More",
"link": "/courses",
"redirect": false
},
"show": true
}
}
@@ -1,418 +0,0 @@
[
{
"id": "shared-page",
"title": "Shared",
"slug": "#",
"status": "published",
"sections": [
{
"type": "seo",
"show": true,
"settings": {
"title": "Posthog University",
"description": "The one place to learn everything about Posthog",
"url": "https://posthog.com",
"appUrl": "https://app.posthog.com",
"keywords": ["posthog", "saas university"],
"logo": "/posthog-logo.svg"
}
},
{
"type": "navigation",
"show": true,
"settings": {
"navItems": [
{
"title": "Courses",
"link": "/courses"
},
{
"title": "Blog",
"link": "https://posthog.com/blog/all",
"redirect": true
},
{
"title": "API",
"link": "https://posthog.com/docs/api",
"redirect": true
},
{
"title": "Docs",
"link": "https://posthog.com/docs",
"redirect": true
}
]
}
},
{
"type": "footer",
"show": true,
"settings": {
"links": [
{ "label": "Privacy Policy", "url": "/privacy" },
{ "label": "Terms of Service", "url": "/terms" }
],
"facebook": "#",
"instagram": "#",
"twitter": "#",
"linkedin": "#"
}
}
]
},
{
"id": "home-page",
"title": "Home Page",
"slug": "/",
"status": "published",
"sections": [
{
"type": "header",
"show": true,
"settings": {
"title": "How Product People Master PostHog: Track, Test, and Transform Your Product",
"titleHighlight": "The Posthog University - Learn All About Analytics",
"subtitle": "PostHog university is the one stop shop for learning everything about analytics with PostHog. Save countless hours and improve your team's productivity.",
"action": {
"label": "Explore courses",
"link": "/courses",
"redirect": false
},
"banner": {
"video": "https://www.youtube.com/watch?v=qqAYW7uxErI",
"image": "",
"type": "video",
"show": true
}
}
},
{
"type": "about",
"show": true,
"settings": {
"title": "Learn with us: To unlock your potential and fuel your Expertise",
"subtitle": "Our courses will help you make sense of the numbers, drive improvements, and create products that stick.",
"benefits": {
"title": "What you will gain",
"list": [
{
"title": "Real-world coding challenges.",
"subtitle": "Work through projects that mirror real-world tasks, helping you build a portfolio that showcases your skills to potential employers."
},
{
"title": "Experienced Instructors",
"subtitle": "Learn from industry experts who bring their hands-on experience to the classroom, guiding you every step of the way."
},
{
"title": "Comprehensive Materials",
"subtitle": "Get access to up-to-date resources that cover all key topics and methodologies, ensuring a complete learning experience."
},
{
"title": "Proven Success",
"subtitle": "With over 20 years of combined teaching experience, our methods have been proven to lead to better results and a deep understanding of the subject matter."
}
]
}
}
},
{
"type": "courses",
"show": true,
"settings": {
"title": "Our Course Lineup: Build Skills",
"titleHighlight": "That Drive Results",
"subtitle": "Explore our most popular courses that will help you master the art of scheduling with Cal.com"
}
},
{
"type": "pathway",
"show": true,
"settings": {
"title": "Become an expert with our \"Learning Path\" Programs",
"titleHighlight": "",
"subtitle": "With over 5 years of extensive coding experience, I am equipped to help you achieve success in your choice of coding career."
}
},
{
"type": "instructors",
"show": true,
"settings": {
"list": [
{
"rating": 4,
"name": "Adewale",
"description": "Adewale Adebayo is a seasoned professional with a wealth of experience in software development. He is passionate about coding and teaching, and his classes are designed to be engaging and practical, ensuring that students gain hands-on skills and knowledge."
},
{
"rating": 4,
"name": "Folakemi Adeyemi",
"description": "Folake Adeyemi has extensive expertise in project management and leadership. She combines theoretical knowledge with practical case studies, ensuring that students are well-prepared for real-world challenges. "
},
{
"rating": 4,
"name": "Ifedayo Olabisi",
"description": "Ifedayo Olabisi is a distinguished instructor in web development. With a deep understanding of both front-end and back-end technologies, he provides comprehensive training that covers all aspects of web development."
},
{
"rating": 4,
"name": "Kemi Adediran",
"description": "Kemi Adediran is a skilled digital marketer with a passion for sharing her knowledge. Her courses cover the latest trends and techniques in digital marketing, from SEO to social media strategy."
}
]
}
},
{
"type": "faq",
"show": true,
"settings": {
"title": "Frequently Asked Questions.",
"subtitle": "Our courses will help you make sense of the numbers, drive improvements, and create products that stick.",
"questions": [
{
"id": 1,
"title": "What sets your platform apart from others?",
"content": "Our platform is focused on providing practical, project-based learning. We aim to equip learners with skills that are directly applicable to their career paths. From real-world projects to experienced instructors, we focus on a hands-on approach to help you master each topic efficiently."
},
{
"id": 2,
"title": "Are the courses beginner-friendly?",
"content": "Absolutely! Whether you're a complete beginner or looking to advance your skills, we have courses designed for every level. Our step-by-step approach ensures that even the most complex topics are broken down into easy-to-understand lessons."
},
{
"id": 3,
"title": "How long does it take to complete a course?",
"content": "Course lengths vary depending on the subject. However, most of our courses are designed to be completed at your own pace, allowing you to take the time you need to fully understand the material before moving on."
}
]
}
},
{
"type": "testimonial",
"show": true,
"settings": {
"list": [
{
"name": "Zulaike Elebute",
"role": "Web Developer",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Zulaike Elebute",
"role": "Content Writer",
"description": "The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Oluwaseun olaniyan",
"role": "Frontend Developement",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Zulaike Elebute",
"role": "Web Developer",
"description": "The feedback and real-world projects were invaluable—I’m now confident in delivering high-quality work that connects and engages"
},
{
"name": "Oluwaseun olaniyan",
"role": "Content Writer",
"description": "The lessons were comprehensive, covering everything from HTML to advanced JavaScript. Working on interactive projects transformed my approach, and I’m now ready to create polished, user-friendly interfaces!"
}
]
}
},
{
"type": "contact",
"show": true,
"settings": {
"title": "Have you got any",
"titleHighlight": "Questions?",
"subtitle": "Reach out to us with any questions you might have.",
"address": "Ashwoodfield House Farm, Kidderminster Rd, Stourbridge, Kingswinford DY6 0AA",
"phone": "+4407339904995",
"email": "[email protected]"
}
},
{
"type": "cta",
"settings": {
"title": "programs that you will love.",
"titleHighlight": "100% guaranteed",
"subtitle": "Unlock skills you'll use for life with courses designed to inspire, empower, and elevate your expertise.",
"buttonLabel": "Learn More"
},
"show": true
},
{
"type": "mailinglist",
"settings": {
"title": "Join our mailing list",
"subtitle": "We are constantly releasing new courses and sharing them with our email list. Subscribe to get notified once we release a new course.",
"buttonLabel": "Subscribe",
"show": true
}
}
]
},
{
"id": "courses-page",
"title": "Courses Page",
"slug": "/courses",
"status": "published",
"sections": [
{
"type": "header",
"show": true,
"settings": {
"title": "You are closest to mastering PostHog!",
"titleHighlight": "Explore our Courses",
"subtitle": "Courses are tailored to help you master PostHog’s tools and make impactful data-driven choices.",
"action": {
"label": "Explore Courses",
"link": "/courses",
"redirect": false
},
"banner": {
"video": "https://www.youtube.com/watch?v=qqAYW7uxErI",
"image": "",
"type": "video",
"show": true
}
}
},
{
"type": "courses",
"show": true,
"settings": {
"title": "Unlock your potential with Engaging and innovative online courses",
"titleHighlight": "",
"subtitle": "Explore creative and tech courses that will help you own you skill set"
}
},
{
"type": "pathway",
"show": true,
"settings": {
"title": "Become an expert with our \"Learning Path\" Programs",
"titleHighlight": "",
"subtitle": "With over 5 years of extensive coding experience, I am equipped to help you achieve success in your choice of coding career."
}
},
{
"type": "faq",
"show": true,
"settings": {
"title": "Frequently Asked Questions.",
"subtitle": "Our courses will help you make sense of the numbers, drive improvements, and create products that stick.",
"questions": [
{
"id": 1,
"title": "What sets your platform apart from others?",
"content": "Our platform is focused on providing practical, project-based learning. We aim to equip learners with skills that are directly applicable to their career paths. From real-world projects to experienced instructors, we focus on a hands-on approach to help you master each topic efficiently."
},
{
"id": 2,
"title": "Are the courses beginner-friendly?",
"content": "Absolutely! Whether you're a complete beginner or looking to advance your skills, we have courses designed for every level. Our step-by-step approach ensures that even the most complex topics are broken down into easy-to-understand lessons."
},
{
"id": 3,
"title": "How long does it take to complete a course?",
"content": "Course lengths vary depending on the subject. However, most of our courses are designed to be completed at your own pace, allowing you to take the time you need to fully understand the material before moving on."
}
]
}
},
{
"type": "testimonial",
"show": true,
"settings": {
"list": [
{
"name": "Zulaike Elebute",
"role": "Web Developer",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Zulaike Elebute",
"role": "Content Writer",
"description": "The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Oluwaseun olaniyan",
"role": "Frontend Developement",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Zulaike Elebute",
"role": "Web Developer",
"description": "The feedback and real-world projects were invaluable—I’m now confident in delivering high-quality work that connects and engages"
},
{
"name": "Oluwaseun olaniyan",
"role": "Content Writer",
"description": "The lessons were comprehensive, covering everything from HTML to advanced JavaScript. Working on interactive projects transformed my approach, and I’m now ready to create polished, user-friendly interfaces!"
},
{
"name": "Oluwaseun olaniyan",
"role": "Web Developer",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Oluwaseun olaniyan",
"role": "Content Writer",
"description": " revelation. I learned techniques to craft compelling, audience-focused content that tells a story. The feedback and real-world projects were invaluable—I’m now confident in delivering high-quality work that connects and engages"
},
{
"name": "Oluwaseun olaniyan",
"role": "Frontend Developement",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
},
{
"name": "Oluwaseun olaniyan",
"role": "Web Developer",
"description": "Through hands-on projects, I learned to build responsive, secure websites from scratch. I now feel equipped to tackle complex challenges with confidence"
},
{
"name": "Oluwaseun olaniyan",
"role": "Web Developer",
"description": "I can't recommend the Mobile App Development bootcamp enough. The course was challenging, but the support from my peers and instructors was incredible. I learned so much about both iOS and Android development, and I'm now developing apps for a tech startup."
}
]
}
},
{
"type": "contact",
"show": true,
"settings": {
"title": "Have you got any",
"titleHighlight": "Questions?",
"subtitle": "Reach out to us with any questions you might have.",
"address": "Ashwoodfield House Farm, Kidderminster Rd, Stourbridge, Kingswinford DY6 0AA",
"phone": "+4407339904995",
"email": "[email protected]"
}
},
{
"type": "cta",
"settings": {
"title": "programs that you will love.",
"titleHighlight": "100% guaranteed",
"subtitle": "Unlock skills you'll use for life with courses designed to inspire, empower, and elevate your expertise.",
"buttonLabel": "Learn More",
"show": true
}
},
{
"type": "mailinglist",
"settings": {
"title": "Join our mailing list",
"subtitle": "We are constantly releasing new courses and sharing them with our email list. Subscribe to get notified once we release a new course.",
"buttonLabel": "Subscribe",
"show": true
}
}
]
}
]
@@ -3,3 +3,16 @@ export const PAGE = {
COURSES: 'courses-page',
SHARED: 'shared-page'
};
export const SECTION = {
HERO: 'header',
COURSE: 'courses',
ABOUT: 'about',
FAQ: 'faq',
TESTIMONIAL: 'testimonial',
CTA: 'cta',
INSTRUCTORS: 'instructors',
SEO: 'seo',
FOOTER: 'footer',
NAVIGATION: 'navigation'
};
@@ -1,5 +1,4 @@
// import pages from '$lib/data/pages.json';
import posthogPages from '$lib/data/posthog-pages.json';
import pages from '$lib/data/pages.json';
import type { Page } from '$lib/utils/types/page';
/**
@@ -8,7 +7,7 @@ import type { Page } from '$lib/utils/types/page';
* @returns type Page
*/
export function getPage(pageId: string) {
return posthogPages.find((page) => page.id === pageId) as Page;
return pages.find((page) => page.id === pageId) as Page;
}
/**
@@ -6,7 +6,7 @@
import { getPageSection } from '$lib/utils/helpers/page';
import { sharedPage } from '$lib/utils/stores/pages';
import { components } from '$lib/components';
import Button from '@/components/ui/button/button.svelte';
import Logo from '$lib/components/ui/_custom/Logo.svelte';
interface Props {
children: import('svelte').Snippet;
@@ -39,18 +39,9 @@
{#if seo?.settings?.logo}
<div class="flex items-center gap-2 md:gap-3" in:blur>
<a
href="/"
class="flex items-center flex-col min-w-[50px]"
title={`${seo?.settings?.title}`}
id="logo"
>
<img
src={seo?.settings?.logo || ''}
alt={`${seo?.settings?.title || ''} logo`}
class="rounded inline-block mx-auto w-16"
/>
</a>
<Logo src={seo?.settings?.logo} alt={seo?.settings?.title} className="w-16 min-w-[50px]" />
<span class="h-5 w-1 bg-[#141414] rounded-full"></span>
<p class="text-start line-clamp-1 font-semibold text-lg capitalize" title={data.title}>
{data.title}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

@@ -2,7 +2,7 @@ import { mdsvex, escapeSvelte } from 'mdsvex';
import rehypeUnwrapImages from 'rehype-unwrap-images';
import remarkToc from 'remark-toc';
import rehypeSlug from 'rehype-slug';
import adapter from '@sveltejs/adapter-cloudflare';
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { createHighlighter } from 'shiki';
@@ -40,6 +40,14 @@ const config: Config = {
DEFAULT: 'hsl(var(--classic-primary) / <alpha-value>)',
secondary: 'hsl(var(--classic-secondary) / <alpha-value>)'
},
examprep: {
DEFAULT: 'hsl(var(--examprep-primary) / <alpha-value>)',
secondary: 'hsl(var(--examprep-secondary) / <alpha-value>)'
},
webflow: {
DEFAULT: 'hsl(var(--webflow-primary) / <alpha-value>)',
secondary: 'hsl(var(--webflow-secondary) / <alpha-value>)'
},
secondary: {
DEFAULT: 'hsl(var(--secondary) / <alpha-value>)',
foreground: 'hsl(var(--secondary-foreground) / <alpha-value>)'
-1
View File
@@ -1 +0,0 @@
engine-strict=true
-38
View File
@@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

Some files were not shown because too many files have changed in this diff Show More