feat: enhance API and middleware functionality: add public API routes, implement program and course member checks, and improve rate limiting for invite acceptance. Update OpenAPI spec generation to focus on public API endpoints and add new middleware for automation key scopes. Include new documentation for lesson AI features and public API tests.

This commit is contained in:
Rotimi Best
2026-04-12 08:58:41 +01:00
parent 5a8f50f67b
commit d6e812d08b
573 changed files with 31964 additions and 8708 deletions
-1
View File
@@ -2,7 +2,6 @@
"$schema": "https://shadcn-svelte.com/schema.json",
"style": "new-york",
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app.css",
"baseColor": "zinc"
},
+7 -7
View File
@@ -19,26 +19,26 @@
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.9.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"autoprefixer": "^10.4.20",
"bits-ui": "^0.21.16",
"@tailwindcss/postcss": "^4.2.2",
"bits-ui": "^2.16.1",
"clsx": "^2.1.1",
"postcss": "^8.5.6",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.5",
"prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"svelte-sonner": "^0.3.28",
"tailwind-merge": "^2.5.5",
"tailwind-variants": "^0.3.0",
"tailwindcss": "^3.4.9",
"tailwindcss": "^4.2.2",
"typescript": "^5.0.0",
"vite": "^6.0.0",
"vitest": "^2.0.4"
},
"dependencies": {
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@cio/ui": "workspace:*",
"@lucide/svelte": "^0.562.0",
"lucide-svelte": "^0.468.0",
"mode-watcher": "^1.1.0",
"posthog-js": "^1.180.1",
+1 -2
View File
@@ -1,6 +1,5 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
'@tailwindcss/postcss': {}
}
};
+63 -3
View File
@@ -1,6 +1,66 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
@import '@cio/ui/output.css';
@import '../../../packages/ui/src/custom/animation/marketing-layers.css';
@custom-variant dark (&:where(.dark, .dark *));
@source './**/*.{html,js,svelte,ts}';
@source '../../../packages/ui/src/custom/animation/**/*.svelte';
/* Theme tokens (colors, fonts, radii, motion) + 2xl breakpoint for layout (e.g. `max-w-(--breakpoint-2xl)`). */
@theme inline {
--breakpoint-2xl: 1400px;
--font-sans:
ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
--font-drawn:
Virgil, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--color-border: hsl(var(--border) / <alpha-value>);
--color-input: hsl(var(--input) / <alpha-value>);
--color-ring: hsl(var(--ring) / <alpha-value>);
--color-background: hsl(var(--background) / <alpha-value>);
--color-foreground: hsl(var(--foreground) / <alpha-value>);
--color-primary: hsl(var(--primary) / <alpha-value>);
--color-primary-foreground: hsl(var(--primary-foreground) / <alpha-value>);
--color-secondary: hsl(var(--secondary) / <alpha-value>);
--color-secondary-foreground: hsl(var(--secondary-foreground) / <alpha-value>);
--color-destructive: hsl(var(--destructive) / <alpha-value>);
--color-destructive-foreground: hsl(var(--destructive-foreground) / <alpha-value>);
--color-muted: hsl(var(--muted) / <alpha-value>);
--color-muted-foreground: hsl(var(--muted-foreground) / <alpha-value>);
--color-accent: hsl(var(--accent) / <alpha-value>);
--color-accent-foreground: hsl(var(--accent-foreground) / <alpha-value>);
--color-popover: hsl(var(--popover) / <alpha-value>);
--color-popover-foreground: hsl(var(--popover-foreground) / <alpha-value>);
--color-card: hsl(var(--card) / <alpha-value>);
--color-card-foreground: hsl(var(--card-foreground) / <alpha-value>);
--animate-border-beam: border-beam calc(var(--duration) * 1s) infinite linear;
--animate-meteor: meteor 5s linear infinite;
--animate-shimmer: shimmer 8s infinite;
--animate-spin-around: spin-around calc(var(--speed) * 2) infinite linear;
--animate-magicslide: magicslide var(--speed) ease-in-out infinite alternate;
}
/* Used by ShinnyText (`animate-shimmer`); marketing-layers uses `shimmer-marketing` for other UIs. */
@keyframes shimmer {
0%,
90%,
100% {
background-position: calc(-100% - var(--shimmer-width)) 0;
}
30%,
60% {
background-position: calc(100% + var(--shimmer-width)) 0;
}
}
@font-face {
font-family: 'Virgil';
@@ -1,48 +0,0 @@
<script lang="ts">
import { Motion, AnimatePresence } 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
oninview_change={({ detail }) => {
isInView = detail.inView ? 'visible' : 'hidden';
}}
class={cn(className)}
>
<slot>Default</slot>
</div>
</Motion>
</AnimatePresence>
@@ -1 +0,0 @@
export { default as BlurFade } from './blurfade.svelte';
@@ -1,36 +0,0 @@
<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>
@@ -1 +0,0 @@
export { default as BlurIn } from './blurin.svelte';
@@ -1,35 +0,0 @@
<script lang="ts">
import { cn } from '$lib/utils';
export let size = 200;
export let duration = 15;
export let anchor = 90;
export let borderWidth = 1.5;
export let colorFrom = '#ffaa40';
export let colorTo = '#9c40ff';
export let delay = 0;
let delaySec = delay + 's';
let className: any = '';
export { className as class };
</script>
<div
style:--border-width={borderWidth}
style:--size={size}
style:--color-from={colorFrom}
style:--color-to={colorTo}
style:--delay={delaySec}
style:--anchor={anchor}
style:--duration={duration}
class={cn(
'pointer-events-none absolute inset-[0] rounded-[inherit] [border:calc(var(--border-width)*1px)_solid_transparent]',
// mask styles
'![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(transparent,transparent),linear-gradient(white,white)]',
// pseudo styles
'after:animate-border-beam after:absolute after:aspect-square after:w-[calc(var(--size)*1px)] after:[animation-delay:var(--delay)] after:[background:linear-gradient(to_left,var(--color-from),var(--color-to),transparent)] after:[offset-anchor:calc(var(--anchor)*1%)_50%] after:[offset-path:rect(0_auto_auto_0_round_calc(var(--size)*1px))]',
className
)}
></div>
@@ -1 +0,0 @@
export { default as BorderBeam } from './border-beam.svelte';
@@ -1,21 +0,0 @@
<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>
@@ -1,30 +0,0 @@
<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>
@@ -1,13 +0,0 @@
<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>
@@ -1,2 +0,0 @@
export { default as CardContainer } from './container.svelte';
export { default as CardBody } from './body.svelte';
@@ -1,37 +0,0 @@
<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>
@@ -1 +0,0 @@
export { default as DotPattern } from './dot-pattern.svelte';
@@ -1,42 +0,0 @@
<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>&nbsp;</span>
{:else}
{char}
{/if}
</span>
</Motion>
{/each}
</AnimatePresence>
</div>
@@ -1 +0,0 @@
export { default as GradualSpacing } from './gradual-spacing.svelte';
@@ -1,193 +0,0 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { AnimatePresence, Motion } from 'svelte-motion';
import { Play, X } from 'lucide-svelte';
type AnimationStyle =
| 'from-bottom'
| 'from-center'
| 'from-top'
| 'from-left'
| 'from-right'
| 'fade'
| 'top-in-bottom-out'
| 'left-in-right-out';
// interface HeroVideoProps {
// animationStyle?: AnimationStyle;
// videoSrc: string;
// thumbnailSrc: string;
// thumbnailAlt?: string;
// }
export let animationStyle: AnimationStyle = 'from-center';
export let videoSrc: string;
export let thumbnailSrc: string;
export let thumbnailAlt: string = 'Video thumbnail';
export let iconColor: string = 'white';
const isVideoOpen = writable(false);
const isCloseHovered = writable(false);
const isPlayHovered = writable(false);
function openVideo() {
isVideoOpen.set(true);
}
function closeVideo() {
isVideoOpen.set(false);
}
let animationVariants = {
'from-bottom': {
initial: { y: 100, opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: 100, opacity: 0 }
},
'from-center': {
initial: { scale: 0.5, opacity: 0 },
animate: { scale: 1, opacity: 1 },
exit: { scale: 0.5, opacity: 0 }
},
'from-top': {
initial: { y: -100, opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: -100, opacity: 0 }
},
'from-left': {
initial: { x: -100, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: -100, opacity: 0 }
},
'from-right': {
initial: { x: 100, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: 100, opacity: 0 }
},
fade: {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 }
},
'top-in-bottom-out': {
initial: { y: -100, opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: 200, opacity: 0 }
},
'left-in-right-out': {
initial: { x: -100, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: 200, opacity: 0 }
}
};
$: selectedAnimation = animationVariants[animationStyle];
</script>
<div class="relative">
<!-- Thumbnail -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="relative cursor-pointer" on:click={openVideo}>
<img
src={thumbnailSrc}
alt={thumbnailAlt}
width={1920}
height={1080}
class="w-full rounded-2xl transition-all duration-200"
/>
<!-- {$isPlayHovered===true ? 'blur-[2px]' :'' } to make background blur put this in above img class -->
<div class="absolute inset-0 flex items-center justify-center">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="flex size-24 items-center justify-center rounded-full border border-neutral-400 bg-black backdrop-blur-md transition-transform duration-300 ease-out"
on:mouseenter={() => isPlayHovered.set(true)}
on:mouseleave={() => isPlayHovered.set(false)}
>
<div
class="relative flex size-20 items-center justify-center rounded-full border border-neutral-400 bg-black backdrop-blur-2xl transition-all duration-300 ease-out"
class:isPlayHovered={$isPlayHovered}
>
<Play
strokeWidth={1.7}
class="size-8"
style="transform: scale({$isPlayHovered ? 1.1 : 1}); transition: transform 0.3s ease;
color: {iconColor};
"
/>
</div>
</div>
</div>
</div>
<AnimatePresence let:item list={[{ key: $isVideoOpen }]}>
{#if item.key}
<Motion initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} let:motion>
<div
use:motion
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-md"
>
<!-- Modal Content -->
<Motion
initial={selectedAnimation.initial}
animate={selectedAnimation.animate}
exit={selectedAnimation.exit}
transition={{ type: 'spring', damping: 30, stiffness: 300 }}
let:motion
>
<div use:motion class="relative mx-4 aspect-video w-full max-w-4xl md:mx-0">
<Motion let:motion whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }}>
<button
use:motion
class="absolute -top-16 right-0 rounded-full bg-neutral-900/50 p-2 text-xl text-white ring-1 backdrop-blur-md"
on:click={closeVideo}
on:mouseenter={() => isCloseHovered.set(true)}
on:mouseleave={() => isCloseHovered.set(false)}
>
<X class="size-5" />
</button>
</Motion>
<Motion
animate={{ scale: $isCloseHovered ? 0.98 : 1 }}
transition={{ duration: 0.2 }}
let:motion
>
<div
use:motion
class="relative isolate z-[1] size-full overflow-hidden rounded-2xl border-2 border-white"
>
<!-- svelte-ignore a11y-missing-attribute -->
<iframe
src={videoSrc}
class="size-full rounded-2xl"
allowfullscreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
></iframe>
</div>
</Motion>
</div>
</Motion>
</div>
</Motion>
{/if}
</AnimatePresence>
</div>
<style>
.size-24 {
width: 6rem;
height: 6rem;
}
.size-20 {
width: 5rem;
height: 5rem;
}
.size-8 {
width: 2rem;
height: 2rem;
}
.size-full {
width: 100%;
height: 100%;
}
</style>
@@ -1 +0,0 @@
export { default as HeroVideoDialog } from './hero-video-dialog.svelte';
@@ -1 +0,0 @@
export { default as MagicCard } from './magic-card.svelte';
@@ -1,72 +0,0 @@
<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>
@@ -1 +0,0 @@
export { default as Meteors } from './meteors.svelte';
@@ -1,36 +0,0 @@
<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}
@@ -1 +0,0 @@
export { default as ShimmerButton } from './shimmerbutton.svelte';
@@ -1,67 +0,0 @@
<script lang="ts">
import { cn } from '$lib/utils';
export let href: string;
export let target: string = '';
export let rel: string = '';
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)';
let className: any = '';
export { className as class };
</script>
<a
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
)}
{href}
{target}
{rel}
data-rewardful
>
<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>
</a>
@@ -1 +0,0 @@
export { default as ShinnyText } from './shinny-text.svelte';
@@ -1,24 +0,0 @@
<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>
@@ -1 +0,0 @@
export { default as Sparkle } from './sparkle-text.svelte';
@@ -1,73 +0,0 @@
<script lang="ts">
interface TSparkle {
id: string;
x: string;
y: string;
color: string;
delay: number;
scale: number;
lifespan: number;
}
export let text: string = 'Hello World';
export let colors = {
first: '#9E7AFF',
second: '#FE8BBB'
};
export let sparklesCount = 5;
let className = '';
export { className as class };
let sparkles: TSparkle[] = [];
import { onMount, onDestroy } from 'svelte';
import Sparkle from './sparkle.svelte';
let generateStar = () => {
let starX = `${Math.random() * 100}%`;
let starY = `${Math.random() * 100}%`;
let color = Math.random() > 0.5 ? colors.first : colors.second;
let delay = Math.random() * 4;
let scale = Math.random() * 1 + 0.3;
let lifespan = Math.random() * 10 + 5;
let id = `${starX}-${starY}-${Date.now()}`;
return { id, x: starX, y: starY, color, delay, scale, lifespan };
};
let initializeStars = async () => {
let newSparkles = Array.from({ length: sparklesCount }, generateStar);
sparkles = newSparkles;
};
const updateStars = () => {
let temp = sparkles.map((star) => {
if (star.lifespan <= 0) {
return generateStar();
} else {
return { ...star, lifespan: star.lifespan - 0.1 };
}
});
sparkles = temp;
};
let interval: NodeJS.Timer;
onMount(async () => {
initializeStars();
interval = setInterval(updateStars, 100);
});
onDestroy(() => clearInterval(interval));
</script>
<div
style:--sparkles-first-color="{colors.first};"
style:--sparkles-second-color="{colors.second};"
class={className}
{...$$restProps}
>
<span class="relative inline-block">
{#each sparkles as item, _}
<Sparkle {...item} />
{/each}
<strong>{text}</strong>
</span>
</div>
@@ -1,35 +0,0 @@
<script lang="ts">
import { Motion } from "svelte-motion";
export let id;
export let x;
export let y;
export let color;
export let delay;
export let scale;
</script>
<Motion
initial={{ opacity: 0, left: x, top: y }}
animate={{
opacity: [0, 1, 0],
scale: [0, scale, 0],
rotate: [75, 120, 150],
}}
transition={{ duration: 0.8, repeat: Infinity, delay }}
let:motion
>
<svg
{id}
use:motion
class="pointer-events-none absolute z-20"
width="21"
height="21"
viewBox="0 0 21 21"
>
<path
d="M9.82531 0.843845C10.0553 0.215178 10.9446 0.215178 11.1746 0.843845L11.8618 2.72026C12.4006 4.19229 12.3916 6.39157 13.5 7.5C14.6084 8.60843 16.8077 8.59935 18.2797 9.13822L20.1561 9.82534C20.7858 10.0553 20.7858 10.9447 20.1561 11.1747L18.2797 11.8618C16.8077 12.4007 14.6084 12.3916 13.5 13.5C12.3916 14.6084 12.4006 16.8077 11.8618 18.2798L11.1746 20.1562C10.9446 20.7858 10.0553 20.7858 9.82531 20.1562L9.13819 18.2798C8.59932 16.8077 8.60843 14.6084 7.5 13.5C6.39157 12.3916 4.19225 12.4007 2.72023 11.8618L0.843814 11.1747C0.215148 10.9447 0.215148 10.0553 0.843814 9.82534L2.72023 9.13822C4.19225 8.59935 6.39157 8.60843 7.5 7.5C8.60843 6.39157 8.59932 4.19229 9.13819 2.72026L9.82531 0.843845Z"
fill={color}
/>
</svg>
</Motion>
@@ -1,9 +0,0 @@
<script lang="ts">
import { cn } from '$lib/utils';
let { children, className } = $props();
</script>
<div class={cn('text-left', className)}>
{@render children()}
</div>
@@ -1,21 +0,0 @@
<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>
@@ -1,2 +0,0 @@
export { default as SquareCardBody } from './body.svelte';
export { default as SquareCardIcon } from './icon.svelte';
@@ -1,7 +1,7 @@
<script lang="ts">
import { buttonVariants } from '$lib/components/ui/button';
import { ShimmerButton } from '$lib/components/animation/shimmerbutton';
import * as Dialog from '$lib/components/ui/dialog';
import { buttonVariants } from '@cio/ui/base/button';
import { ShimmerButton } from '@cio/ui/custom/animation';
import * as Dialog from '@cio/ui/base/dialog';
import { cn } from '$lib/utils';
import BadgeCheck from 'lucide-svelte/icons/badge-check';
@@ -19,10 +19,6 @@
</script>
<Dialog.Root>
<!-- <span
class="absolute inset-0 z-30 flex items-center justify-center bg-black/50 opacity-0 transition-all duration-500 hover:opacity-80"
>
</span> -->
<Dialog.Trigger class={cn(buttonVariants({ variant: 'default' }), `z-20 ${ctaClass}`)}>
{ctaLabel}
</Dialog.Trigger>
@@ -1,6 +1,6 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { ShinnyText } from '$lib/components/animation/shinny-text';
import { ShinnyText } from '@cio/ui/custom/animation';
import Copy from 'lucide-svelte/icons/copy';
import { cn } from '$lib/utils';
@@ -17,7 +17,7 @@
)}
>
<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"
class="animate-gradient inline-flex items-center justify-center bg-linear-to-r from-[#ffaa40] via-[#9c40ff] to-[#ffaa40] bg-size-[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>{command}</span>
@@ -11,7 +11,7 @@
</script>
<header
class="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
class="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60"
>
<p class="bg-blue-700 py-1.5 text-center text-sm font-medium text-white">
brought to you by
@@ -35,7 +35,7 @@
</svg>
</a>
</p>
<div class="container flex h-14 max-w-screen-2xl items-center">
<div class="flex h-14 max-w-5xl mx-auto items-center">
<div class="mr-4 flex">
<a href="/" class="mr-6 flex items-center">
<img src="/logo-512.png" alt="Course App" class="h-8 w-8" />
@@ -1,7 +1,6 @@
<script lang="ts">
import Santa from './santa.svg';
import { BorderBeam } from '$lib/components/animation/border-beam';
import { DotPattern } from '$lib/components/animation/dot-pattern';
import { BorderBeam, DotPattern } from '@cio/ui/custom/animation';
import { BuyTemplate } from '$lib/components/buy-template';
</script>
@@ -13,7 +13,7 @@
<SeparatorPrimitive.Root
class={cn(
"bg-border shrink-0",
orientation === "horizontal" ? "h-[1px] w-full" : "min-h-full w-[1px]",
orientation === "horizontal" ? "h-px w-full" : "min-h-full w-px",
className
)}
{orientation}
@@ -6,7 +6,7 @@
</script>
<Sonner
theme={$mode}
theme={mode.current}
class="toaster group"
toastOptions={{
classes: {
+13 -10
View File
@@ -1,16 +1,19 @@
<script lang="ts">
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, CustomTemplate } from '$lib/components/template-card';
import { Meteors } from '$lib/components/animation/meteors';
import { Separator } from '$lib/components/ui/separator';
import { BorderBeam } from '$lib/components/animation/border-beam';
import { ShimmerButton } from '$lib/components/animation/shimmerbutton';
import { BuyTemplate } from '$lib/components/buy-template';
import { Sparkle } from '$lib/components/animation/sparkle';
import { HeroVideoDialog } from '$lib/components/animation/hero-video-dialog';
import {
BlurFade,
BlurIn,
BorderBeam,
CardBody,
CardContainer,
HeroVideoDialog,
Meteors,
ShimmerButton,
Sparkle
} from '@cio/ui/custom/animation';
const templates = [
{
@@ -93,7 +96,7 @@
className="py-10 max-w-4xl mx-auto flex flex-col items-center justify-center px-30 gap-2 md:gap-5"
>
<BlurIn
class="pointer-events-none flex max-w-2xl flex-col items-center whitespace-pre-wrap bg-gradient-to-b from-black to-gray-300/80 bg-clip-text text-center text-2xl font-semibold leading-none tracking-tighter text-transparent md:mb-4 md:block md:text-4xl lg:text-6xl"
class="pointer-events-none flex max-w-2xl flex-col items-center whitespace-pre-wrap bg-linear-to-b from-black to-gray-300/80 bg-clip-text text-center text-2xl font-semibold leading-none tracking-tighter text-transparent md:mb-4 md:block md:text-4xl lg:text-6xl"
>
Create a <span class="font-mono italic text-blue-700">Course</span>
<span>Site for your Product in</span>
@@ -157,7 +160,7 @@
<!-- Templates -->
<section id="templates" class="my-5 w-full pt-0 md:my-0 md:pt-24">
<div class="px-4 md:container">
<div class="px-4 max-w- mx-auto">
<h2 class="mb-5 text-center text-3xl font-semibold md:mb-20 md:text-start md:text-5xl">
Browse Templates
</h2>
+7 -7
View File
@@ -1,23 +1,23 @@
import adapter from '@sveltejs/adapter-auto';
import path from 'path';
import { fileURLToPath } from 'url';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess({
script: true
}),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter(),
alias: {
$lib: './src/lib',
$lib: './src/lib/*'
$lib: path.resolve(__dirname, './src/lib'),
'@cio/ui': path.resolve(__dirname, './node_modules/@cio/ui/src'),
'@cio/ui/*': path.resolve(__dirname, './node_modules/@cio/ui/src/*')
}
}
};
-128
View File
@@ -1,128 +0,0 @@
import { fontFamily } from 'tailwindcss/defaultTheme';
import type { Config } from 'tailwindcss';
const config: Config = {
darkMode: ['class'],
content: ['./src/**/*.{html,js,svelte,ts}'],
safelist: ['dark'],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px'
}
},
extend: {
colors: {
border: 'hsl(var(--border) / <alpha-value>)',
input: 'hsl(var(--input) / <alpha-value>)',
ring: 'hsl(var(--ring) / <alpha-value>)',
background: 'hsl(var(--background) / <alpha-value>)',
foreground: 'hsl(var(--foreground) / <alpha-value>)',
primary: {
DEFAULT: 'hsl(var(--primary) / <alpha-value>)',
foreground: 'hsl(var(--primary-foreground) / <alpha-value>)'
},
secondary: {
DEFAULT: 'hsl(var(--secondary) / <alpha-value>)',
foreground: 'hsl(var(--secondary-foreground) / <alpha-value>)'
},
destructive: {
DEFAULT: 'hsl(var(--destructive) / <alpha-value>)',
foreground: 'hsl(var(--destructive-foreground) / <alpha-value>)'
},
muted: {
DEFAULT: 'hsl(var(--muted) / <alpha-value>)',
foreground: 'hsl(var(--muted-foreground) / <alpha-value>)'
},
accent: {
DEFAULT: 'hsl(var(--accent) / <alpha-value>)',
foreground: 'hsl(var(--accent-foreground) / <alpha-value>)'
},
popover: {
DEFAULT: 'hsl(var(--popover) / <alpha-value>)',
foreground: 'hsl(var(--popover-foreground) / <alpha-value>)'
},
card: {
DEFAULT: 'hsl(var(--card) / <alpha-value>)',
foreground: 'hsl(var(--card-foreground) / <alpha-value>)'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
fontFamily: {
sans: [...fontFamily.sans],
drawn: [
'Virgil',
'ui-sans-serif',
'system-ui',
'sans-serif',
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
'Noto Color Emoji'
]
},
animation: {
'border-beam': 'border-beam calc(var(--duration)*1s) infinite linear',
meteor: 'meteor 5s linear infinite',
shimmer: 'shimmer 8s infinite',
'spin-around': 'spin-around calc(var(--speed) * 2) infinite linear',
magicslide: 'magicslide var(--speed) ease-in-out infinite alternate'
},
keyframes: {
'border-beam': {
'100%': {
'offset-distance': '100%'
}
},
meteor: {
'0%': { transform: 'rotate(215deg) translateX(0)', opacity: '1' },
'70%': { opacity: '1' },
'100%': {
transform: 'rotate(215deg) translateX(-500px)',
opacity: '0'
}
},
shimmer: {
'0%, 90%, 100%': {
'background-position': 'calc(-100% - var(--shimmer-width)) 0'
},
'30%, 60%': {
'background-position': 'calc(100% + var(--shimmer-width)) 0'
}
},
'spin-around': {
'0%': {
transform: 'translateZ(0) rotate(0)'
},
'15%, 35%': {
transform: 'translateZ(0) rotate(90deg)'
},
'65%, 85%': {
transform: 'translateZ(0) rotate(270deg)'
},
'100%': {
transform: 'translateZ(0) rotate(360deg)'
}
},
magicslide: {
to: {
transform: 'translate(calc(100cqw - 100%), 0)'
}
}
}
}
}
};
export default config;