feat: introducing @classroomio/course-app (#490)
* feat: v1 templates * update nvm * working on the displaying the course content * working on the displaying the course content * displayed the content * finished with the logic for rendering the content for all the courses and lessons * post hog template UI is good to go * classic and minimalistic template UI is good to go * some refactoring * some refactoring * layout adjustments * update tsconfig * trying to get the filter to work well * this is working now * cleanup * cleanup * UI updates * course page UI for posthog * navbar and sidebar for course page in all templates complete * all templates course page showing lesson content from md file with mdsvex configured * wokring on sections * posthog lesson content done * all templates fetches the lesson content from the section * all templates properly typed * scaffolding the new template * finished the webflow-saas * done with the webflow saas UI * new calcom template * new calcom template * masonry layout for testitomonail section layout done * calcom UI done * UI all done * cleanup * trying to fix dark mode for the course page and home page * done with this * done with this * working on tests * migrated minimalistic template to svelte 5 * updated the syntax to svelte 5 for webflow * migrated classic to svelte 5 * migrated posthog to svelte 5 * used svelte 5 syntax in calcom * cli tool woks locally * rename to course-sites * use pnpm in root * calcom refactoring * remove unused files * chore: biggest refactoring in my life * add basic readme * refactoring on course page * restructure how courses are ordered * proper state propagation * add cf adapter * fix pnpm i issue * fix folder icon size * use 18.17 version * update npmrc * add engine to packagejson * pnpm install * add engine to cio-com * make it 18.x * remove sequence * move mdsvex to dependencies * update lock * mdsvex please work * replace fs with import from vite * add lock * styling improvements * fix styling * sidebar style improvements * fix: mobile concerns * fix spacing of course header on desktop * update header * top nav fix * feat: make cal template * setup another template * classic basic UI * fixed the /courses fetching wrong page content * fix breaking template cause of logo * adding some updates * improve styles theming * finished updates from reviews * updated the custom styling css * clean up posthog template * cli tool done, needs some cleaning though * moved components to home * improved the UI of the minimal template * Add CLI tool and course app site * prettier lint * improve CLI * ignore sveltekit folder * publish course app * fix tailwing theme + improve README * finish up course-app site * update README.md --------- Co-authored-by: emmanuel <[email protected]>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
|
||||
--destructive: 0 72.2% 50.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--ring: 240 10% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--ring: 240 4.9% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>Courseapp by ClassroomIO – Create your SAAS Academy in Minutes</title>
|
||||
<meta name="description" content="Courseapp" />
|
||||
<meta name="application-name" content="Courseapp" />
|
||||
<meta name="apple-mobile-web-app-title" content="Courseapp" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<!-- Facebook Meta Tags -->
|
||||
<meta property="og:url" content="https://www.courseapp.oncws.com/" />
|
||||
<meta property="og:image" itemprop="image" content="https://cdn.courseapp.oncws.com/courseapp-og-image.png" />
|
||||
<meta property="og:title" content="Courseapp by ClassroomIO – Create your SAAS Academy in Minutes" />
|
||||
<meta property="og:description"
|
||||
content="Series of templates available for you to clone, customise and launch your SAAS Academy. Scale your customer education effort today." />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1920" />
|
||||
<meta property="og:image:height" content="1080" />
|
||||
<meta property="og:image:secure_url" itemprop="image"
|
||||
content="https://cdn.courseapp.oncws.com/courseapp-og-image.png" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="courseapp.oncws.com" />
|
||||
<meta property="twitter:url" content="https://www.courseapp.oncws.com/" />
|
||||
<meta name="twitter:title" content="Courseapp by ClassroomIO – Create your SAAS Academy in Minutes" />
|
||||
<meta name="twitter:description"
|
||||
content="Series of templates available for you to clone, customise and launch your SAAS Academy. Scale your customer education effort today." />
|
||||
<meta name="twitter:image" content="https://cdn.courseapp.oncws.com/courseapp-og-image.png" />
|
||||
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('sum test', () => {
|
||||
it('adds 1 + 2 to equal 3', () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { Motion, AnimatePresence, useAnimation } from 'svelte-motion';
|
||||
import { inview } from 'svelte-inview';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
let {
|
||||
duration = 0.4,
|
||||
delay = 0,
|
||||
yOffset = 6,
|
||||
inViewMargin = '-50px',
|
||||
blur = '6px',
|
||||
once = false,
|
||||
class: className = ''
|
||||
} = $props();
|
||||
let id = $state(crypto.randomUUID().slice(0, 8));
|
||||
let isInView = $state('hidden');
|
||||
|
||||
let defaultVariants = {
|
||||
hidden: { opacity: 0, y: yOffset, filter: `blur(${blur})` },
|
||||
visible: { opacity: 1, y: 0, filter: `blur(0px)` }
|
||||
};
|
||||
</script>
|
||||
|
||||
<AnimatePresence let:item list={[{ key: id }]}>
|
||||
<Motion
|
||||
initial="hidden"
|
||||
animate={isInView}
|
||||
exit="hidden"
|
||||
variants={defaultVariants}
|
||||
transition={{
|
||||
delay: 0.04 + delay,
|
||||
duration,
|
||||
ease: 'easeOut'
|
||||
}}
|
||||
let:motion
|
||||
>
|
||||
<div
|
||||
use:inview={{ rootMargin: inViewMargin, unobserveOnEnter: once }}
|
||||
use:motion
|
||||
on:inview_change={({ detail }) => {
|
||||
isInView = detail.inView ? 'visible' : 'hidden';
|
||||
}}
|
||||
class={cn(className)}
|
||||
>
|
||||
<slot>Default</slot>
|
||||
</div>
|
||||
</Motion>
|
||||
</AnimatePresence>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as BlurFade } from './blurfade.svelte';
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { Motion } from "svelte-motion";
|
||||
|
||||
export let variant: {
|
||||
hidden: { filter: string; opacity: number };
|
||||
visible: { filter: string; opacity: number };
|
||||
} = {
|
||||
hidden: { filter: "blur(10px)", opacity: 0 },
|
||||
visible: { filter: "blur(0px)", opacity: 1 },
|
||||
};
|
||||
export let duration: number = 1;
|
||||
let className = "font-display text-4xl font-bold tracking-[-0.02em] drop-shadow-sm md:text-7xl md:leading-[5rem]";
|
||||
export { className as class };
|
||||
|
||||
let defaultVariants = {
|
||||
hidden: { filter: "blur(10px)", opacity: 0 },
|
||||
visible: { filter: "blur(0px)", opacity: 1 },
|
||||
};
|
||||
let combinedVariants = variant || defaultVariants;
|
||||
</script>
|
||||
|
||||
<Motion
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
transition={{ duration }}
|
||||
variants={combinedVariants}
|
||||
let:motion
|
||||
>
|
||||
<h1
|
||||
class={cn(className)}
|
||||
use:motion
|
||||
>
|
||||
<slot />
|
||||
</h1>
|
||||
</Motion>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as BlurIn } from './blurin.svelte';
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
let { children, className } = $props();
|
||||
|
||||
// let cardContent = {
|
||||
// title: 'Svelte is Vibe',
|
||||
// description: 'Svelte is a fun way to build web applications.'
|
||||
// };
|
||||
</script>
|
||||
|
||||
<div class={cn('p-4 text-left', className)}>
|
||||
<!-- <h3 class="mb-1 text-lg font-bold text-gray-900 dark:text-gray-100">
|
||||
{cardContent.title}
|
||||
</h3>
|
||||
<p class="text-gray-700 dark:text-gray-300">
|
||||
{cardContent.description}
|
||||
</p> -->
|
||||
|
||||
{@render children()}
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import Ellipses from './ellipses.svelte';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
let { className }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn(
|
||||
'relative mx-auto w-full border border-dashed border-zinc-300 px-8 shadow-sm sm:px-6 md:px-8 dark:border-zinc-800 dark:bg-[#09090B]',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
class="absolute left-0 top-4 -z-0 h-px w-full bg-zinc-400 sm:top-6 md:top-8 dark:bg-zinc-700"
|
||||
></div>
|
||||
<div
|
||||
class="absolute bottom-4 left-0 z-0 h-px w-full bg-zinc-400 sm:bottom-6 md:bottom-8 dark:bg-zinc-700"
|
||||
></div>
|
||||
<div class="relative w-full border-x border-zinc-400 dark:border-zinc-700">
|
||||
<Ellipses />
|
||||
<div class="relative z-20 mx-auto py-8">
|
||||
<slot>Default</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
let sharedClass =
|
||||
'rounded-full outline outline-8 dark:outline-gray-950 sm:my-6 md:my-8 size-1 my-4 outline-gray-50 bg-green-400';
|
||||
</script>
|
||||
|
||||
<div class="absolute z-0 grid h-full w-full items-center gap-8 lg:grid-cols-2">
|
||||
<section class="absolute z-0 grid h-full w-full grid-cols-2 place-content-between">
|
||||
<div class="{sharedClass} -mx-[2.5px]"></div>
|
||||
<div class="{sharedClass} -mx-[2px] place-self-end"></div>
|
||||
<div class="{sharedClass} -mx-[2.5px]"></div>
|
||||
<div class="{sharedClass} -mx-[2px] place-self-end"></div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as CardContainer } from './container.svelte';
|
||||
export { default as CardBody } from './body.svelte';
|
||||
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { AnimatePresence, Motion } from 'svelte-motion';
|
||||
|
||||
let className: any = 'Gradual Spacing';
|
||||
export { className as class };
|
||||
|
||||
export let words = 'Gradual Spacing';
|
||||
export let duration = 0.5;
|
||||
export let delayMultiple = 0.04;
|
||||
export let framerProps = {
|
||||
hidden: { opacity: 0, x: -20 },
|
||||
visible: { opacity: 1, x: 0 }
|
||||
};
|
||||
let wordsspilit = words.split('');
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center space-x-1">
|
||||
<AnimatePresence let:item list={[{ key: wordsspilit }]}>
|
||||
{#each wordsspilit as char, i}
|
||||
<Motion
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="hidden"
|
||||
variants={framerProps}
|
||||
transition={{
|
||||
duration: duration,
|
||||
delay: i * delayMultiple
|
||||
}}
|
||||
let:motion
|
||||
>
|
||||
<span use:motion class={cn('drop-shadow-sm', className)}>
|
||||
{#if char === ' '}
|
||||
<span> </span>
|
||||
{:else}
|
||||
{char}
|
||||
{/if}
|
||||
</span>
|
||||
</Motion>
|
||||
{/each}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as GradualSpacing } from './gradual-spacing.svelte';
|
||||
@@ -0,0 +1 @@
|
||||
export { default as MagicCard } from './magic-card.svelte';
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import { Motion, useMotionValue, useMotionTemplate } from 'svelte-motion';
|
||||
import { onMount } from 'svelte';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
export let gradientSize: number = 200;
|
||||
export let gradientColor: string = '#262626';
|
||||
export let gradientOpacity: number = 0.8;
|
||||
let className: string = '';
|
||||
export { className as class };
|
||||
|
||||
let gradSize = useMotionValue(gradientSize);
|
||||
let gradColor = useMotionValue(gradientColor);
|
||||
let mouseX = useMotionValue(-gradientSize);
|
||||
let mouseY = useMotionValue(-gradientSize);
|
||||
|
||||
function handleMouseMove(e: MouseEvent) {
|
||||
const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
|
||||
mouseX.set(e.clientX - rect.left);
|
||||
mouseY.set(e.clientY - rect.top);
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
mouseX.set(-gradientSize);
|
||||
mouseY.set(-gradientSize);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
mouseX.set(-gradientSize);
|
||||
mouseY.set(-gradientSize);
|
||||
});
|
||||
let bg = useMotionTemplate`radial-gradient(${gradSize}px circle at ${mouseX}px ${mouseY}px, ${gradColor}, transparent 100%)`;
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- I have added py-4 in below code, you can customize the component as per needs -->
|
||||
<div
|
||||
on:mousemove={handleMouseMove}
|
||||
on:mouseleave={handleMouseLeave}
|
||||
class={cn(
|
||||
'group relative flex size-full justify-center overflow-hidden rounded-xl border bg-neutral-100 py-4 text-black dark:bg-neutral-900 dark:text-white',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div class="relative z-10">
|
||||
<!-- Default -->
|
||||
<slot>
|
||||
<div class="flex h-full items-center justify-center text-center">
|
||||
<p class="text-2xl">Magic Card</p>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<Motion
|
||||
style={{
|
||||
background: bg,
|
||||
opacity: gradientOpacity
|
||||
}}
|
||||
let:motion
|
||||
>
|
||||
<div
|
||||
use:motion
|
||||
class="pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
/>
|
||||
</Motion>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.size-full {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Meteors } from './meteors.svelte';
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let number = 10;
|
||||
let meteorStyles: any = [];
|
||||
let changeMeteors = (num: number) => {
|
||||
meteorStyles = [];
|
||||
const styles = [...new Array(num)].map(() => ({
|
||||
top: -20,
|
||||
left: Math.floor(Math.random() * 700) + 'px',
|
||||
animationDelay: Math.random() * 1 + 0.2 + 's',
|
||||
animationDuration: Math.floor(Math.random() * 8 + 2.9) + 's'
|
||||
}));
|
||||
meteorStyles = styles;
|
||||
};
|
||||
onMount(() => {
|
||||
changeMeteors(number);
|
||||
});
|
||||
// $: changeMeteors(number);
|
||||
</script>
|
||||
|
||||
{#each meteorStyles as style, idx}
|
||||
<span
|
||||
id="meteor-{idx + 1}"
|
||||
class={cn(
|
||||
'animate-meteor pointer-events-none absolute left-1/2 top-1/2 size-[2.4px] rotate-[215deg] rounded-full bg-slate-500 shadow-[0_0_0_1px_#ffffff10]'
|
||||
)}
|
||||
style="top: {style.top}px; left: {style.left}; animation-delay: {style.animationDelay}; animation-duration: {style.animationDuration};"
|
||||
>
|
||||
<!-- Meteor Tail -->
|
||||
<div
|
||||
class="pointer-events-none absolute top-1/2 -z-10 h-px w-[50px] -translate-y-1/2 bg-gradient-to-r from-slate-500 via-blue-600/30 to-transparent"
|
||||
/>
|
||||
</span>
|
||||
{/each}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ShimmerButton } from './shimmerbutton.svelte';
|
||||
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
export let shimmerColor = '#ffffff';
|
||||
export let shimmerSize = '0.05em';
|
||||
export let shimmerDuration = '3s';
|
||||
export let borderRadius = '100px';
|
||||
export let background = 'rgba(0, 0, 0, 1)';
|
||||
export let onClick: () => void;
|
||||
let className: any = '';
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<button
|
||||
style:--spread="90deg"
|
||||
style:--shimmer-color={shimmerColor}
|
||||
style:--radius={borderRadius}
|
||||
style:--speed={shimmerDuration}
|
||||
style:--cut={shimmerSize}
|
||||
style:--bg={background}
|
||||
class={cn(
|
||||
'group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden whitespace-nowrap border border-white/10 px-6 py-3 text-white [background:var(--bg)] [border-radius:var(--radius)] dark:text-white',
|
||||
'transform-gpu transition-transform duration-300 ease-in-out active:translate-y-[1px]',
|
||||
className
|
||||
)}
|
||||
on:click={onClick}
|
||||
>
|
||||
<div class={cn('-z-30 blur-[2px]', 'absolute inset-0 overflow-visible [container-type:size]')}>
|
||||
<div
|
||||
class="animate-magicslide absolute inset-0 h-[100cqh] [aspect-ratio:1] [border-radius:0] [mask:none]"
|
||||
>
|
||||
<!-- spark before -->
|
||||
<div
|
||||
class="animate-spin-around absolute inset-[-100%] w-auto rotate-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))] [translate:0_0]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<slot>Shimmer Button</slot>
|
||||
<!-- Highlight -->
|
||||
<div
|
||||
class={cn(
|
||||
'insert-0 absolute h-full w-full',
|
||||
|
||||
'rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#ffffff1f]',
|
||||
|
||||
// transition
|
||||
'transform-gpu transition-all duration-300 ease-in-out',
|
||||
|
||||
// on hover
|
||||
'group-hover:shadow-[inset_0_-6px_10px_#ffffff3f]',
|
||||
|
||||
// on click
|
||||
'group-active:shadow-[inset_0_-10px_10px_#ffffff3f]'
|
||||
)}
|
||||
></div>
|
||||
<!-- backdrop -->
|
||||
<div
|
||||
class={cn(
|
||||
'absolute -z-20 [background:var(--bg)] [border-radius:var(--radius)] [inset:var(--cut)]'
|
||||
)}
|
||||
></div>
|
||||
</button>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ShinnyText } from './shinny-text.svelte';
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
export let shimmerWidth = 100;
|
||||
let className: any = '';
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<p
|
||||
style:--shimmer-width="{shimmerWidth}px"
|
||||
class={cn(
|
||||
'mx-auto max-w-md text-neutral-600/50 dark:text-neutral-400/50 ',
|
||||
|
||||
// Shimmer effect
|
||||
'animate-shimmer bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shimmer-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]',
|
||||
|
||||
// Shimmer gradient
|
||||
'bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80',
|
||||
|
||||
className
|
||||
)}
|
||||
>
|
||||
<slot>Shimmer Animation</slot>
|
||||
</p>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
let { children, className } = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn('text-left', className)}>
|
||||
{@render children()}
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
let className: any = '';
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
height={24}
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke="currentColor"
|
||||
class={cn('absolute text-black dark:text-white', className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
|
||||
</svg>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as SquareCardBody } from './body.svelte';
|
||||
export { default as SquareCardIcon } from './icon.svelte';
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Navigation } from './navigation.svelte';
|
||||
@@ -0,0 +1,106 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
|
||||
const getClass = (active: boolean) => (active ? 'text-foreground' : 'text-foreground/60');
|
||||
|
||||
let activeClass = $derived({
|
||||
template: getClass($page.url.href.includes('#templates')),
|
||||
setup: getClass($page.url.href.includes('#setup'))
|
||||
});
|
||||
</script>
|
||||
|
||||
<header
|
||||
class="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 w-full border-b backdrop-blur"
|
||||
>
|
||||
<p class="bg-blue-700 py-1.5 text-center text-sm font-medium text-white">
|
||||
brought to you by
|
||||
<a
|
||||
href="https://git.new/class"
|
||||
target="_blank"
|
||||
class="inline-flex items-center font-semibold underline-offset-2 hover:underline"
|
||||
>classroomio.com
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
class="lucide-icon lucide lucide-arrow-up-right ml-0.5 h-3 w-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><path d="M7 7h10v10"></path><path d="M7 17 17 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
<div class="container flex h-14 max-w-screen-2xl items-center">
|
||||
<div class="mr-4 flex">
|
||||
<a href="/" class="mr-6 flex items-center space-x-2">
|
||||
<img src="/logo-512.png" alt="Course App" class="h-8 w-8" />
|
||||
{' '}
|
||||
<span class="inline-block font-bold">courseapp</span>
|
||||
</a>{' '}
|
||||
<nav class="hidden items-center gap-6 text-sm md:flex">
|
||||
<a
|
||||
href="#templates"
|
||||
class="hover:text-foreground/80 {activeClass.template} transition-colors"
|
||||
>
|
||||
Templates
|
||||
</a>{' '}
|
||||
<a href="#setup" class="hover:text-foreground/80 {activeClass.setup} transition-colors">
|
||||
Setup
|
||||
</a>{' '}
|
||||
<!-- <a
|
||||
href="https://github.com/classroomio/classroomio"
|
||||
class="hover:text-foreground/80 text-foreground/60 transition-colors"
|
||||
>
|
||||
Github
|
||||
</a>{' '} -->
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 items-center justify-end space-x-2">
|
||||
<nav class="flex items-center">
|
||||
<a
|
||||
href="https://github.com/classroomio/classroomio"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div
|
||||
class="focus-visible:ring-ring hover:bg-accent hover:text-accent-foreground inline-flex h-8 w-8 items-center justify-center whitespace-nowrap rounded-md px-0 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
<svg viewBox="0 0 438.549 438.549" class="h-4 w-4">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
|
||||
></path>
|
||||
</svg>{' '}
|
||||
<span class="sr-only" data-svelte-h="svelte-11yow7k"> GitHub </span>
|
||||
</div>
|
||||
</a>{' '}
|
||||
<a href="https://twitter.com/classroomio" target="_blank" rel="noreferrer">
|
||||
<div
|
||||
class="focus-visible:ring-ring hover:bg-accent hover:text-accent-foreground inline-flex h-8 w-8 items-center justify-center whitespace-nowrap rounded-md px-0 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
height="23"
|
||||
viewBox="0 0 1200 1227"
|
||||
width="23"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
class="h-3 w-3 fill-current"
|
||||
>
|
||||
<path
|
||||
d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"
|
||||
></path>
|
||||
</svg>{' '}
|
||||
<span class="sr-only" data-svelte-h="svelte-13ytjzu">
|
||||
X (formerly known as Twitter)
|
||||
</span>
|
||||
</div>
|
||||
</a>{' '}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as TemplateCard } from './template-card.svelte';
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import ArrowUpRight from 'lucide-svelte/icons/arrow-up-right';
|
||||
|
||||
interface Props {
|
||||
template: {
|
||||
name: string;
|
||||
image: string;
|
||||
url: string;
|
||||
github: string;
|
||||
};
|
||||
}
|
||||
let { template }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="mb-5 w-full max-w-[340px]">
|
||||
<Button
|
||||
href={template.url}
|
||||
variant="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="border-stroke-primary relative h-80 min-w-60 overflow-hidden rounded-md border border-solid p-0 transition-all duration-500 lg:h-96"
|
||||
>
|
||||
<span
|
||||
class="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 transition-all duration-500 hover:opacity-80"
|
||||
>
|
||||
<ArrowUpRight class="h-14 w-14 text-white" />
|
||||
</span>
|
||||
<img
|
||||
src={template.image}
|
||||
class="h-full w-full object-contain hover:opacity-10"
|
||||
alt={template.name}
|
||||
/>
|
||||
</Button>
|
||||
<div>
|
||||
<h3 class="text-md font-bold">{template.name}</h3>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { Button as ButtonPrimitive } from "bits-ui";
|
||||
import { type Events, type Props, buttonVariants } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = Props;
|
||||
type $$Events = Events;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let variant: $$Props["variant"] = "default";
|
||||
export let size: $$Props["size"] = "default";
|
||||
export let builders: $$Props["builders"] = [];
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<ButtonPrimitive.Root
|
||||
{builders}
|
||||
class={cn(buttonVariants({ variant, size, className }))}
|
||||
type="button"
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
</ButtonPrimitive.Root>
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { Button as ButtonPrimitive } from "bits-ui";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
import Root from "./button.svelte";
|
||||
|
||||
const buttonVariants = tv({
|
||||
base: "focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm",
|
||||
outline:
|
||||
"border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
|
||||
type Variant = VariantProps<typeof buttonVariants>["variant"];
|
||||
type Size = VariantProps<typeof buttonVariants>["size"];
|
||||
|
||||
type Props = ButtonPrimitive.Props & {
|
||||
variant?: Variant;
|
||||
size?: Size;
|
||||
};
|
||||
|
||||
type Events = ButtonPrimitive.Events;
|
||||
|
||||
export {
|
||||
Root,
|
||||
type Props,
|
||||
type Events,
|
||||
//
|
||||
Root as Button,
|
||||
type Props as ButtonProps,
|
||||
type Events as ButtonEvents,
|
||||
buttonVariants,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import Root from "./separator.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SeparatorPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let orientation: $$Props["orientation"] = "horizontal";
|
||||
export let decorative: $$Props["decorative"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SeparatorPrimitive.Root
|
||||
class={cn(
|
||||
"bg-border shrink-0",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "min-h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{orientation}
|
||||
{decorative}
|
||||
{...$$restProps}
|
||||
/>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Toaster } from "./sonner.svelte";
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
|
||||
import { mode } from "mode-watcher";
|
||||
|
||||
type $$Props = SonnerProps;
|
||||
</script>
|
||||
|
||||
<Sonner
|
||||
theme={$mode}
|
||||
class="toaster group"
|
||||
toastOptions={{
|
||||
classes: {
|
||||
toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||
description: "group-[.toast]:text-muted-foreground",
|
||||
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||
},
|
||||
}}
|
||||
{...$$restProps}
|
||||
/>
|
||||
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
@@ -0,0 +1,62 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { cubicOut } from "svelte/easing";
|
||||
import type { TransitionConfig } from "svelte/transition";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
type FlyAndScaleParams = {
|
||||
y?: number;
|
||||
x?: number;
|
||||
start?: number;
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
export const flyAndScale = (
|
||||
node: Element,
|
||||
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
|
||||
): TransitionConfig => {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === "none" ? "" : style.transform;
|
||||
|
||||
const scaleConversion = (
|
||||
valueA: number,
|
||||
scaleA: [number, number],
|
||||
scaleB: [number, number]
|
||||
) => {
|
||||
const [minA, maxA] = scaleA;
|
||||
const [minB, maxB] = scaleB;
|
||||
|
||||
const percentage = (valueA - minA) / (maxA - minA);
|
||||
const valueB = percentage * (maxB - minB) + minB;
|
||||
|
||||
return valueB;
|
||||
};
|
||||
|
||||
const styleToString = (
|
||||
style: Record<string, number | string | undefined>
|
||||
): string => {
|
||||
return Object.keys(style).reduce((str, key) => {
|
||||
if (style[key] === undefined) return str;
|
||||
return str + `${key}:${style[key]};`;
|
||||
}, "");
|
||||
};
|
||||
|
||||
return {
|
||||
duration: params.duration ?? 200,
|
||||
delay: 0,
|
||||
css: (t) => {
|
||||
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
|
||||
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
|
||||
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
|
||||
|
||||
return styleToString({
|
||||
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
|
||||
opacity: t
|
||||
});
|
||||
},
|
||||
easing: cubicOut
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Toaster } from '$lib/components/ui/sonner';
|
||||
import { Navigation } from '$lib/components/navigation';
|
||||
|
||||
import '../app.css';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<Toaster />
|
||||
|
||||
<Navigation />
|
||||
|
||||
{@render children()}
|
||||
@@ -0,0 +1,185 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { CardBody, CardContainer } from '$lib/components/animation/card';
|
||||
import { BlurFade } from '$lib/components/animation/blurfade';
|
||||
import { BlurIn } from '$lib/components/animation/blurin';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { TemplateCard } from '$lib/components/template-card';
|
||||
import { Meteors } from '$lib/components/animation/meteors';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { ShinnyText } from '$lib/components/animation/shinny-text';
|
||||
import { ShimmerButton } from '$lib/components/animation/shimmerbutton';
|
||||
import Copy from 'lucide-svelte/icons/copy';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
const templates = [
|
||||
{
|
||||
name: 'Cal',
|
||||
image: 'https://cdn.courseapp.oncws.com/templates/cal-template.png',
|
||||
url: 'https://cal.courseapp.oncws.com',
|
||||
github: 'https://github.com/classroomio/classroomio'
|
||||
},
|
||||
{
|
||||
name: 'PostHog',
|
||||
image: 'https://cdn.courseapp.oncws.com/templates/posthog-template.png',
|
||||
url: 'https://posthog.courseapp.oncws.com',
|
||||
github: 'https://github.com/classroomio/classroomio'
|
||||
},
|
||||
{
|
||||
name: 'Classic',
|
||||
image: 'https://cdn.courseapp.oncws.com/templates/classic-template.png',
|
||||
url: 'https://classic.courseapp.oncws.com',
|
||||
github: 'https://github.com/classroomio/classroomio'
|
||||
},
|
||||
{
|
||||
name: 'Examprep',
|
||||
image: 'https://cdn.courseapp.oncws.com/templates/examprep-template.png',
|
||||
url: 'https://examprep.courseapp.oncws.com',
|
||||
github: 'https://github.com/classroomio/classroomio'
|
||||
},
|
||||
{
|
||||
name: 'Minimal',
|
||||
image: 'https://cdn.courseapp.oncws.com/templates/minimal.png',
|
||||
url: 'https://minimal.courseapp.oncws.com',
|
||||
github: 'https://github.com/classroomio/classroomio'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="my-5 w-full md:mb-20 md:mt-0">
|
||||
<div class="">
|
||||
<CardContainer className="md:px-20">
|
||||
<CardBody
|
||||
className="max-w-4xl min-h-[50vh] flex flex-col justify-center px-30 gap-2 md:gap-5"
|
||||
>
|
||||
<BlurIn
|
||||
class="pointer-events-none flex max-w-2xl flex-col items-start whitespace-pre-wrap bg-gradient-to-b from-black to-gray-300/80 bg-clip-text text-4xl font-semibold leading-none tracking-tighter text-transparent md:mb-4 md:block md:text-6xl"
|
||||
>
|
||||
Create a <span class="font-mono italic text-blue-700">UNIVERSITY</span>
|
||||
<span>Site for your SAAS in</span>
|
||||
<span class="ml-1 font-mono italic text-blue-700">MINUTES</span>
|
||||
</BlurIn>
|
||||
|
||||
<BlurFade delay={0.25 * 2}>
|
||||
<p class="text-md">
|
||||
With our prebuilt templates, you can setup a fully functional customer educational site
|
||||
for your product in minutes.<br />
|
||||
<span>
|
||||
Similar to the likes of
|
||||
|
||||
<Button variant="link" class="h-fit p-0" href="https://university.webflow.com/">
|
||||
Webflow,
|
||||
</Button>
|
||||
<Button variant="link" class="h-fit p-0" href="https://academy.hubspot.com/">
|
||||
Hubspot,
|
||||
</Button>
|
||||
<Button
|
||||
variant="link"
|
||||
class="h-fit p-0"
|
||||
href="https://university.atlassian.com/student/activity/2227111-home#/page/670e8ce7a1887954d13a856a"
|
||||
>
|
||||
Atlassian &
|
||||
</Button>
|
||||
<Button variant="link" class="h-fit p-0" href="https://framer.university/"
|
||||
>Framer</Button
|
||||
>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<button
|
||||
onclick={() => {
|
||||
navigator.clipboard.writeText('pnpx @classroomio/course-app');
|
||||
toast('Copied to clipboard 😉');
|
||||
}}
|
||||
class={cn(
|
||||
'group z-10 mt-5 rounded-full border border-white/50 bg-neutral-700 transition-all ease-in hover:cursor-pointer hover:bg-neutral-700 hover:text-white'
|
||||
)}
|
||||
>
|
||||
<ShinnyText
|
||||
class="animate-gradient inline-flex items-center justify-center bg-gradient-to-r from-[#ffaa40] via-[#9c40ff] to-[#ffaa40] bg-[length:var(--bg-size)_100%] bg-clip-text px-4 py-1 text-transparent text-white transition ease-out hover:text-neutral-400 hover:duration-300"
|
||||
>
|
||||
<span>pnpx @classroomio/course-app</span>
|
||||
|
||||
<Copy class="ml-2 size-3" />
|
||||
</ShinnyText>
|
||||
</button>
|
||||
</BlurFade>
|
||||
</CardBody>
|
||||
</CardContainer>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Hero End -->
|
||||
|
||||
<!-- Templates -->
|
||||
<section id="templates" class="my-5 w-full pt-0 md:my-10 md:pt-24">
|
||||
<div class="container">
|
||||
<h2 class="mb-5 text-center text-3xl font-semibold md:mb-20 md:text-start md:text-5xl">
|
||||
Browser Templates
|
||||
</h2>
|
||||
|
||||
<div class="grid max-w-6xl grid-cols-1 place-items-center gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{#each templates as template, index}
|
||||
<BlurFade delay={0.25 + index * 0.25}>
|
||||
<TemplateCard {template} />
|
||||
</BlurFade>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Templates End -->
|
||||
|
||||
<Separator class="my-20" />
|
||||
|
||||
<!-- Setup -->
|
||||
<section id="setup" class="mb-20 w-full">
|
||||
<BlurFade delay={0.25}>
|
||||
<div class="container">
|
||||
<!-- <h2 class="mb-10 text-center text-5xl font-semibold">
|
||||
Up & Running <span class="font-mono italic text-blue-700">MINUTES</span>
|
||||
</h2> -->
|
||||
|
||||
<div
|
||||
class="relative mx-auto h-fit w-full max-w-3xl overflow-hidden rounded-lg border bg-neutral-800 p-20 py-40 text-center shadow-2xl"
|
||||
>
|
||||
<Meteors number={30} />
|
||||
|
||||
<h1 class="mb-5 text-5xl font-semibold text-white">
|
||||
Start Customising Your University Site
|
||||
</h1>
|
||||
|
||||
<div class="z-10 flex items-center justify-center">
|
||||
<ShimmerButton
|
||||
onClick={() => {
|
||||
window.location.href =
|
||||
'https://github.com/classroomio/classroomio/blob/a6af412b2401a8dc3f871773b96a24b2fa764f52/packages/course-app/README.md';
|
||||
}}
|
||||
class="shadow-2xl"
|
||||
>
|
||||
<span
|
||||
class="whitespace-pre-wrap text-center text-sm font-medium leading-none tracking-tight text-white lg:text-lg dark:from-white dark:to-slate-900/10"
|
||||
>
|
||||
Go to Docs
|
||||
</span>
|
||||
</ShimmerButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
</section>
|
||||
|
||||
<!-- Setup End -->
|
||||
|
||||
<Separator class="mt-20" />
|
||||
|
||||
<!-- Footer -->
|
||||
<section class="my-10 w-full">
|
||||
<div class="container">
|
||||
<p class="text-center text-sm text-neutral-500">
|
||||
Made with ❤️ by <a href="https://git.new/class">classroomio</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Footer End -->
|
||||
Reference in New Issue
Block a user