migrate web to svelte 5
This commit is contained in:
@@ -2,8 +2,14 @@
|
||||
import { Badge } from '@cio/ui/base/badge';
|
||||
import { formatDate } from '$lib/utils/format-date';
|
||||
|
||||
export let post;
|
||||
export let isRecommended = false;
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {any} post
|
||||
* @property {boolean} [isRecommended]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { post, isRecommended = false } = $props();
|
||||
</script>
|
||||
|
||||
{#if !isRecommended}
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
<script>
|
||||
import { flexible, leftFist, rightFist, robotArm, thumbsUp } from '$lib/emojis';
|
||||
|
||||
export let id = '';
|
||||
export let rightToLeft = false;
|
||||
export let tagline = '';
|
||||
export let title = '';
|
||||
export let description = '';
|
||||
export let video = '';
|
||||
export let taglineIcon = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [id]
|
||||
* @property {boolean} [rightToLeft]
|
||||
* @property {string} [tagline]
|
||||
* @property {string} [title]
|
||||
* @property {string} [description]
|
||||
* @property {string} [video]
|
||||
* @property {string} [taglineIcon]
|
||||
* @property {import('svelte').Snippet} [more]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let {
|
||||
id = '',
|
||||
rightToLeft = false,
|
||||
tagline = '',
|
||||
title = '',
|
||||
description = '',
|
||||
video = '',
|
||||
taglineIcon = '',
|
||||
more
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<section {id} class="w-full py-32 border-b border-x-0 border-t-0 border-gray-200">
|
||||
@@ -32,15 +48,15 @@
|
||||
{/if}
|
||||
<p class="font-medium text-base">{tagline}</p>
|
||||
</div>
|
||||
<h2 class="font-bold text-4xl mb-3 lg:mb-6">{title}</h2>
|
||||
<h2 class="text-3xl mb-3 lg:mb-6">{title}</h2>
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
{description}
|
||||
</p>
|
||||
{#if $$slots.more}
|
||||
{#if more}
|
||||
<br />
|
||||
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
<slot name="more" />
|
||||
{@render more?.()}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let src: string;
|
||||
export let alt: string;
|
||||
interface Props {
|
||||
src: string;
|
||||
alt: string;
|
||||
}
|
||||
|
||||
let { src, alt }: Props = $props();
|
||||
</script>
|
||||
|
||||
<figure>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import Youtube from '@lucide/svelte/icons/youtube';
|
||||
</script>
|
||||
|
||||
<div class="border-t-[1px] px-[2%] lg:px-[14%]">
|
||||
<div class="border-t px-[2%] lg:px-[14%]">
|
||||
<div class="flex flex-col items-center justify-between py-[10%] pl-5 lg:flex-row lg:pl-0">
|
||||
<div class="w-full min-w-[200px] lg:w-3/12">
|
||||
<img width="32" height="32" loading="lazy" src="/logo-192.png" alt="classroomio logo" class="mb-5" />
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
{ label: 'Customers', color: '#0233BD' },
|
||||
{ label: 'Students', color: '#9747FF' }
|
||||
];
|
||||
let animate = false;
|
||||
let titleIndex = 0;
|
||||
let animate = $state(false);
|
||||
let titleIndex = $state(0);
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -19,4 +19,3 @@ export { default as Testimonial } from './testimonial.svelte';
|
||||
export { default as Timeline } from './timeline.svelte';
|
||||
export { default as ToolsHeader } from './tools-header.svelte';
|
||||
export { default as UploadWidget } from './upload-widget.svelte';
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
export let open = false;
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {boolean} [open]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', open = false, children } = $props();
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
@@ -10,7 +17,7 @@
|
||||
<div
|
||||
class="fixed z-[3001] {className} w-full md:w-[30%] md:min-w-[500px] bg-white rounded-md mx-auto py-6 px-7"
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import { folder, globe, team, transcription } from '$lib/emojis';
|
||||
import { tools } from '$lib/utils/constants/tools';
|
||||
|
||||
let selected = 0;
|
||||
let interval: NodeJS.Timer;
|
||||
let selected = $state(0);
|
||||
let interval: NodeJS.Timer = $state();
|
||||
|
||||
const moreFeatures = [
|
||||
{
|
||||
@@ -22,13 +22,13 @@
|
||||
'Extend invitations and manage your teaching institution seamlessly through ClassroomIO.',
|
||||
image: '/team-management.png'
|
||||
},
|
||||
{
|
||||
icon: transcription,
|
||||
title: 'Auto Video Transcription',
|
||||
description:
|
||||
'AI automatically transcribes videos, making content searchable for seamless student access.',
|
||||
image: '/video-transcription.png'
|
||||
},
|
||||
// {
|
||||
// icon: transcription,
|
||||
// title: 'Auto Video Transcription',
|
||||
// description:
|
||||
// 'AI automatically transcribes videos, making content searchable for seamless student access.',
|
||||
// image: '/video-transcription.png'
|
||||
// },
|
||||
{
|
||||
icon: folder,
|
||||
title: 'Downloadable Lesson PDF',
|
||||
@@ -52,7 +52,7 @@
|
||||
<section id="morefeatures" class="py-[10%] px-3 lg:px-0 bg-gray-50">
|
||||
<div class="mx-0 lg:mx-[12%]">
|
||||
<div class="w-full lg:w-4/5 mx-4 lg:mx-0 mb-14">
|
||||
<h2 class="mt-2 text-3xl font-bold font-display tracking-tight lg:text-4xl">
|
||||
<h2 class="mt-2 text-3xl tracking-tight">
|
||||
There is More...
|
||||
</h2>
|
||||
<p class="mt-4 text-lg text-gray-500 w-[94%] lg:w-4/5">
|
||||
@@ -67,8 +67,8 @@
|
||||
{#each moreFeatures as moreFeature, i}
|
||||
<button
|
||||
class="p-4 rounded-md {i === selected &&
|
||||
'selected'} mb-3 text-start min-w-[75vw] lg:min-w-[unset]"
|
||||
on:mouseenter={() => {
|
||||
'selected'} mb-3 text-start min-w-[75vw] lg:min-w-[unset] cursor-pointer"
|
||||
onclick={() => {
|
||||
selected = i;
|
||||
clearInterval(interval);
|
||||
}}
|
||||
@@ -82,11 +82,11 @@
|
||||
alt=""
|
||||
class="w-8"
|
||||
/>
|
||||
<p class="text-lg font-semibold">
|
||||
<p class="text-lg">
|
||||
{moreFeature.title}
|
||||
</p>
|
||||
</div>
|
||||
<p>{moreFeature.description}</p>
|
||||
<p class="text-sm">{moreFeature.description}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -99,7 +99,7 @@
|
||||
height="100%"
|
||||
loading="lazy"
|
||||
id="image"
|
||||
in:fly={{ y: 100, duration: 1000 }}
|
||||
in:fly={{ x: 100, duration: 800 }}
|
||||
class="w-full flex flex-col items-center"
|
||||
src={moreFeatures[selected].image}
|
||||
alt={moreFeatures[selected].title}
|
||||
|
||||
@@ -14,13 +14,28 @@
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as NavigationMenu from '@cio/ui/base/navigation-menu';
|
||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
||||
import { cn } from '@cio/ui/tools';
|
||||
|
||||
|
||||
export let stars: number;
|
||||
let { stars }: { stars: number } = $props();
|
||||
|
||||
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
|
||||
title: string;
|
||||
href: string;
|
||||
subtitle: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
let showDrawer = false;
|
||||
let showSolutions = false;
|
||||
let activeLink = '';
|
||||
let activeLink = $derived(page.url.pathname);
|
||||
let activeHash = $derived(page.url.hash);
|
||||
|
||||
$effect(() => {
|
||||
console.log('activeLink', activeLink);
|
||||
console.log('activeHash', activeHash);
|
||||
console.log('isSolutionsActive', isSolutionsActive);
|
||||
});
|
||||
|
||||
function handleShowDrawer() {
|
||||
showDrawer = !showDrawer;
|
||||
@@ -76,16 +91,7 @@
|
||||
}
|
||||
];
|
||||
|
||||
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
|
||||
title: string;
|
||||
href: string;
|
||||
subtitle: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
$: activeLink = page.url.pathname;
|
||||
$: activeHash = page.url.hash;
|
||||
$: isSolutionsActive = solutions.some((s) => activeHash.includes(s.key));
|
||||
let isSolutionsActive = $derived(!!solutions.some((s) => activeLink.includes(s.key)));
|
||||
</script>
|
||||
|
||||
{#snippet list_item({ title, subtitle, href, key, class: className, ...restProps }: ListItemProps)}
|
||||
@@ -140,11 +146,14 @@
|
||||
</a>
|
||||
|
||||
<section class="hidden lg:block">
|
||||
<NavigationMenu.Root class="rounded-lg bg-white p-1">
|
||||
<NavigationMenu.Root class="p-1">
|
||||
<NavigationMenu.List class="flex w-full items-center justify-center gap-1">
|
||||
<NavigationMenu.Item>
|
||||
<NavigationMenu.Trigger
|
||||
class="flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100"
|
||||
class={cn(
|
||||
"flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100",
|
||||
isSolutionsActive && 'bg-gray-100!'
|
||||
)}
|
||||
>
|
||||
<a href="/" class="no-underline"> Solutions </a>
|
||||
</NavigationMenu.Trigger>
|
||||
@@ -164,7 +173,7 @@
|
||||
|
||||
<NavigationMenu.Item>
|
||||
<NavigationMenu.Trigger
|
||||
class="flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100"
|
||||
class={cn("flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100", activeLink.startsWith('/tools') && 'bg-gray-100!')}
|
||||
>
|
||||
<a href="/tools" class="no-underline"> Free Tools </a>
|
||||
</NavigationMenu.Trigger>
|
||||
@@ -187,11 +196,10 @@
|
||||
{#snippet child()}
|
||||
<a
|
||||
href="/blog"
|
||||
class="cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100 {activeLink.startsWith(
|
||||
'/blog'
|
||||
)
|
||||
? 'bg-gray-100'
|
||||
: ''}"
|
||||
class={cn(
|
||||
'cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100',
|
||||
activeLink.startsWith('/blog') && 'bg-gray-100'
|
||||
)}
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
@@ -204,11 +212,10 @@
|
||||
{#snippet child()}
|
||||
<a
|
||||
href="/pricing"
|
||||
class="cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100 {activeLink.startsWith(
|
||||
'/pricing'
|
||||
)
|
||||
? 'bg-gray-100'
|
||||
: ''}"
|
||||
class={cn(
|
||||
'cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100',
|
||||
activeLink.startsWith('/pricing') && 'bg-gray-100'
|
||||
)}
|
||||
>
|
||||
Pricing
|
||||
</a>
|
||||
@@ -250,7 +257,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button type="button" aria-label="Hamburger Menu" class="block md:block lg:hidden" on:click={handleShowSolutions}>
|
||||
<button type="button" aria-label="Hamburger Menu" class="block md:block lg:hidden" onclick={handleShowSolutions}>
|
||||
<Menu size={24} />
|
||||
</button>
|
||||
|
||||
@@ -262,7 +269,7 @@
|
||||
>
|
||||
<div class="mb-5 flex justify-between py-2">
|
||||
<img loading="lazy" width="20" height="20" src="/logo-512.png" alt="classroomio logo" class="w-[15%]" />
|
||||
<button class="mr-5" on:click={handleShowSolutions}>
|
||||
<button class="mr-5" onclick={handleShowSolutions}>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -270,9 +277,11 @@
|
||||
<ul class="flex w-full flex-col items-center justify-between lg:flex-row">
|
||||
<li class="w-full cursor-pointer text-sm font-semibold text-gray-800 md:text-lg">
|
||||
<button
|
||||
class="flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100"
|
||||
on:click={handleShowDrawer}
|
||||
class:active={isSolutionsActive}
|
||||
class={cn(
|
||||
"flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100",
|
||||
isSolutionsActive && 'bg-gray-100'
|
||||
)}
|
||||
onclick={handleShowDrawer}
|
||||
>
|
||||
Our Superpowers <ChevronDown />
|
||||
</button>
|
||||
@@ -281,7 +290,7 @@
|
||||
{#each solutions as solution}
|
||||
<a
|
||||
href="/{solution.key}"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
>
|
||||
@@ -297,37 +306,43 @@
|
||||
</li>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={handleShowSolutions}
|
||||
onclick={handleShowSolutions}
|
||||
href="/tools"
|
||||
>
|
||||
<li>Free Tools</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeLink.startsWith('/morefeatures') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
class:active={activeLink.startsWith('/blog')}
|
||||
href="/blog"
|
||||
>
|
||||
<li>Blog</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeLink.startsWith('/pricing') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
href="/pricing"
|
||||
class:active={activeLink.startsWith('/pricing')}
|
||||
>
|
||||
<li>Pricing</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeHash.includes('morefeatures') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
class:active={activeHash.includes('morefeatures')}
|
||||
href="/#morefeatures"
|
||||
>
|
||||
<li>More features</li>
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
import SearchXIcon from '@lucide/svelte/icons/search-x';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
|
||||
export let isOrg = false;
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {boolean} [isOrg]
|
||||
* @property {string} [className]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { isOrg = false, className = '' } = $props();
|
||||
|
||||
function handleClick() {
|
||||
if (!isOrg) {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', children } = $props();
|
||||
</script>
|
||||
|
||||
<header class="hero-bg w-full border-b border-slate-100 pb-16 pt-32 lg:min-h-[20rem] {className}">
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</header>
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
>
|
||||
<div>
|
||||
<h3
|
||||
class="font-display mt-3 text-xl font-semibold tracking-tight text-white lg:mt-2 lg:text-4xl max-w-2xl"
|
||||
class="font-display mt-3 text-xl tracking-tight text-white lg:mt-2 lg:text-4xl max-w-2xl"
|
||||
>
|
||||
{header}
|
||||
</h3>
|
||||
<p class="mt-6 text-xl text-gray-300 lg:text-lg">
|
||||
<p class="mt-6 text-md text-gray-300">
|
||||
{subText}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
import { embedSenjaWidget } from '@cio/utils/senja';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let id = '';
|
||||
interface Props {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
let { id = '' }: Props = $props();
|
||||
|
||||
onMount(() => {
|
||||
embedSenjaWidget(id);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div
|
||||
class="relative overflow-hidden mt-10 flex space-y-3 flex-col text-center items-center border border-[#D9E0F5] justify-center w-full rounded-md min-h-[300px] p-4 bg-[#F5F8FE]"
|
||||
>
|
||||
<div class="absolute w-20 h-20 bg-[#0233BD] -top-14 -left-9 rotate-[50deg]" />
|
||||
<div class="absolute w-20 h-20 bg-[#0233BD] -top-14 -left-9 rotate-[50deg]"></div>
|
||||
<h1 class="font-bold text-3xl">Your message has been sent 🎉</h1>
|
||||
<p class="w-[70%] font-normal text-sm">
|
||||
We'll get back to you as soon as possible. In the meantime, Explore and enjoy features on our
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
<script lang="ts">
|
||||
export let items: {
|
||||
interface Props {
|
||||
items?: {
|
||||
title: string;
|
||||
desc: string;
|
||||
}[] = [];
|
||||
}[];
|
||||
}
|
||||
|
||||
let { items = [] }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="relative w-fit flex justify-center space-x-8 md:space-x-10">
|
||||
<div
|
||||
class="w-[3px] md:w-[2px] bg-blue-600 mt-10 before:bg-gradient-to-t before:from-white before:to-transparent before:w-[4px] before:h-[9rem] before:absolute before:bottom-0"
|
||||
/>
|
||||
class="w-[3px] md:w-[2px] bg-blue-600 mt-10 before:bg-linear-to-t before:from-white before:to-transparent before:w-[4px] before:h-36 before:absolute before:bottom-0"
|
||||
></div>
|
||||
<div class="space-y-8">
|
||||
{#each items as card, index}
|
||||
<div class="md:w-[35rem] w-full border-2 rounded-lg px-4 py-8 space-y-4 bg-white">
|
||||
<div class="md:w-140 w-full border-2 rounded-lg px-4 py-8 space-y-4 bg-white">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class={`z-50 absolute -left-3 ${
|
||||
index > 0 &&
|
||||
'before:bg-gradient-to-t before:from-white before:to-transparent before:w-[4px] before:h-[9rem] before:absolute before:-top-36'
|
||||
'before:bg-linear-to-t before:from-white before:to-transparent before:w-[4px] before:h-36 before:absolute before:-top-36'
|
||||
} flex items-center justify-center w-7 h-7 bg-blue-100 rounded-full`}
|
||||
>
|
||||
<div class="w-4 h-4 bg-blue-800 rounded-full z-50" />
|
||||
<div class="w-4 h-4 bg-blue-800 rounded-full z-50"></div>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-blue-800">{card.title}</h1>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', children } = $props();
|
||||
</script>
|
||||
|
||||
<header class="tools-hero-bg py-24 text-center {className} border-b border-slate-100 bg-white">
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</header>
|
||||
|
||||
@@ -3,12 +3,21 @@
|
||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||
import { ALLOWED_IMAGE_TYPES, validateImageUpload } from '@cio/utils/functions/fileValidation';
|
||||
|
||||
export let imageURL = '';
|
||||
export let label = '';
|
||||
export let isRequired = false;
|
||||
export let labelClassName = '';
|
||||
interface Props {
|
||||
imageURL?: string;
|
||||
label?: string;
|
||||
isRequired?: boolean;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
imageURL = $bindable(''),
|
||||
label = '',
|
||||
isRequired = false,
|
||||
labelClassName = ''
|
||||
}: Props = $props();
|
||||
let isUploading = false;
|
||||
let fileInput: HTMLInputElement;
|
||||
let fileInput: HTMLInputElement = $state();
|
||||
|
||||
const onFileSelected = () => {
|
||||
const file = fileInput?.files?.[0];
|
||||
@@ -52,8 +61,8 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div>
|
||||
{#if label}
|
||||
<p class="flex w-full items-center justify-between text-left dark:text-white {labelClassName}">
|
||||
@@ -65,7 +74,7 @@
|
||||
</span>
|
||||
|
||||
{#if imageURL}
|
||||
<span title="clear image" on:click={deleteImage}>
|
||||
<span title="clear image" onclick={deleteImage}>
|
||||
<Trash2 class="fill-red-500 " />
|
||||
</span>
|
||||
{/if}
|
||||
@@ -73,14 +82,14 @@
|
||||
{/if}
|
||||
<div
|
||||
class="flex min-h-[200px] w-full items-center justify-center rounded-md border p-4"
|
||||
on:click={() => fileInput.click()}
|
||||
onclick={() => fileInput.click()}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png,.gif,.webp,image/jpeg,image/png,image/gif,image/webp"
|
||||
style="display: none;"
|
||||
bind:this={fileInput}
|
||||
on:change={onFileSelected}
|
||||
onchange={onFileSelected}
|
||||
disabled={isUploading}
|
||||
required={isRequired}
|
||||
/>
|
||||
|
||||
@@ -9,14 +9,4 @@ import team from './team.svg';
|
||||
import transcription from './printer.svg';
|
||||
import folder from './folder.svg';
|
||||
|
||||
export {
|
||||
flexible,
|
||||
robotArm,
|
||||
thumbsUp,
|
||||
leftFist,
|
||||
rightFist,
|
||||
globe,
|
||||
team,
|
||||
transcription,
|
||||
folder,
|
||||
};
|
||||
export { flexible, robotArm, thumbsUp, leftFist, rightFist, globe, team, transcription, folder };
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
import { submitForm } from '$lib/utils/submit-form';
|
||||
import { Success } from '$lib/components';
|
||||
|
||||
let name = '';
|
||||
let email = '';
|
||||
let title = '';
|
||||
let description = '';
|
||||
let name = $state('');
|
||||
let email = $state('');
|
||||
let title = $state('');
|
||||
let description = $state('');
|
||||
|
||||
let isLoading = false;
|
||||
let errorMessage = '';
|
||||
let sent = false;
|
||||
let isLoading = $state(false);
|
||||
let errorMessage = $state('');
|
||||
let sent = $state(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
const data = { name, email, title, description };
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
import { submitForm } from '$lib/utils/submit-form';
|
||||
import { Success } from '$lib/components';
|
||||
|
||||
let name = '';
|
||||
let email = '';
|
||||
let title = '';
|
||||
let description = '';
|
||||
let name = $state('');
|
||||
let email = $state('');
|
||||
let title = $state('');
|
||||
let description = $state('');
|
||||
|
||||
let isLoading = false;
|
||||
let errorMessage = '';
|
||||
let sent = false;
|
||||
let isLoading = $state(false);
|
||||
let errorMessage = $state('');
|
||||
let sent = $state(false);
|
||||
|
||||
async function handleClick() {
|
||||
const data = { name, email, title, description };
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
src: string;
|
||||
}
|
||||
|
||||
let isAddIcon = false;
|
||||
let hoveredIndex: number | null = null;
|
||||
let isAddIcon = $state(false);
|
||||
let hoveredIndex: number | null = $state(null);
|
||||
|
||||
let avatars: Avatar[] = [
|
||||
{ src: 'avatar_a' },
|
||||
@@ -48,14 +48,14 @@
|
||||
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
|
||||
{#each avatars as avatar, index}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectAvatar(avatar);
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
onmouseenter={() => {
|
||||
hoveredIndex = index;
|
||||
isAddIcon = true;
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
onmouseleave={() => {
|
||||
hoveredIndex = null;
|
||||
isAddIcon = false;
|
||||
}}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
>
|
||||
{#each backgrounds as background}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectBackground(background);
|
||||
}}
|
||||
class="flex w-[30%] items-center shadow-sm hover:scale-110 transition-all duration-300"
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
import { nodeStore, htmlBody, openModal } from './store';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
|
||||
export let isDisabled: boolean;
|
||||
export let isDownloading: boolean;
|
||||
export let text: string = 'Generate Progress Report';
|
||||
interface Props {
|
||||
isDisabled: boolean;
|
||||
isDownloading: boolean;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
let { isDisabled, isDownloading = $bindable(), text = 'Generate Progress Report' }: Props = $props();
|
||||
|
||||
function convertToPng() {
|
||||
isDownloading = true;
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<div class="flex flex-wrap justify-between gap-y-3 mt-3">
|
||||
{#each moods as mood}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectMood(mood);
|
||||
}}
|
||||
class="flex gap-3 items-center shadow-sm rounded-full px-4 py-1 bg-[#F1F6FF] hover:bg-[#0233BD] hover:text-white transition-all duration-700"
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
|
||||
let node: any;
|
||||
|
||||
$: nodeStore.set(node);
|
||||
$effect(() => {
|
||||
nodeStore.set(node);
|
||||
});
|
||||
|
||||
$: rootBgImage = `https://assets.cdn.clsrio.com/progress-report/backgrounds/${
|
||||
let rootBgImage = $derived(`https://assets.cdn.clsrio.com/progress-report/backgrounds/${
|
||||
$htmlBody.background || 'blue_tetiary_background'
|
||||
}.webp`;
|
||||
}.webp`);
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -43,7 +45,7 @@
|
||||
{#if $openModal.fullscreen}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
onclick={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
class="absolute top-4 right-4 p-1.5 rounded-full hover:scale-90 transition-all duration-300 bg-white"
|
||||
>
|
||||
<img src="/free-tools/progress-report/close-icon.svg" alt="" class="" />
|
||||
@@ -51,7 +53,7 @@
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
onclick={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
class="absolute top-4 right-4 w-7 hover:scale-90 transition-all duration-300"
|
||||
>
|
||||
<img src="/free-tools/progress-report/full-screen-icon.svg" alt="" class="" />
|
||||
@@ -118,7 +120,7 @@
|
||||
<div
|
||||
class="progress-range rounded-full w-full h-full"
|
||||
style="background: {$backgroundColor}; width: {$htmlBody.progress}%"
|
||||
/>
|
||||
></div>
|
||||
</div>
|
||||
<p class="text-[12px] font-semibold mt-1">Progress Achieved: {$htmlBody.progress}%</p>
|
||||
</div>
|
||||
|
||||
@@ -2,22 +2,36 @@
|
||||
import { sineInOut } from 'svelte/easing';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
export let activityName: string;
|
||||
export let formattedTime: string;
|
||||
export let displayTime: string;
|
||||
export let resetTimer: () => void;
|
||||
export let toggleSound: () => void;
|
||||
export let toggleTimer: () => void;
|
||||
export let restartTimer: () => void;
|
||||
export let isPaused: boolean;
|
||||
export let soundOn: boolean;
|
||||
interface Props {
|
||||
activityName: string;
|
||||
formattedTime: string;
|
||||
displayTime: string;
|
||||
resetTimer: () => void;
|
||||
toggleSound: () => void;
|
||||
toggleTimer: () => void;
|
||||
restartTimer: () => void;
|
||||
isPaused: boolean;
|
||||
soundOn: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
activityName,
|
||||
formattedTime,
|
||||
displayTime,
|
||||
resetTimer,
|
||||
toggleSound,
|
||||
toggleTimer,
|
||||
restartTimer,
|
||||
isPaused,
|
||||
soundOn
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div transition:fly={{ y: 100, delay: 0, easing: sineInOut }}>
|
||||
<div class="px-6 pt-8 text-center">
|
||||
<button
|
||||
type="button"
|
||||
on:click={resetTimer}
|
||||
onclick={resetTimer}
|
||||
class="text-[10px] text-[#656565] font-semibold uppercase underline"
|
||||
>set timer for another activity</button
|
||||
>
|
||||
@@ -36,7 +50,7 @@
|
||||
<div class="flex">
|
||||
<button
|
||||
type="button"
|
||||
on:click={toggleSound}
|
||||
onclick={toggleSound}
|
||||
class="bg-[#F7F7F7] w-[34%] flex flex-col gap-1 items-center uppercase text-[10px] py-5 md:py-10 rounded-bl-md hover:bg-[#F1F6FF] transition-all duration-500"
|
||||
>
|
||||
{#if soundOn}
|
||||
@@ -48,7 +62,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={toggleTimer}
|
||||
onclick={toggleTimer}
|
||||
class="bg-[#0F62FE] text-white w-[34%] flex flex-col gap-1 items-center uppercase text-[10px] py-5 md:py-10"
|
||||
>
|
||||
{#if isPaused}
|
||||
@@ -60,7 +74,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={restartTimer}
|
||||
onclick={restartTimer}
|
||||
class="bg-[#F7F7F7] w-[34%] flex flex-col gap-1 items-center uppercase text-[10px] py-5 md:py-10 rounded-br-md hover:bg-[#F1F6FF] transition-all duration-500"
|
||||
>
|
||||
<img src="/free-tools/stopwatch/reset-icon.svg" alt="" class="w-7 md:w-12" />
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { sineInOut } from 'svelte/easing';
|
||||
import { fly } from 'svelte/transition';
|
||||
import WinningLine from './winning-line.svelte';
|
||||
@@ -9,18 +11,22 @@
|
||||
click: (rowIndex: number, columnIndex: number) => Game;
|
||||
}
|
||||
|
||||
export let game: Game;
|
||||
export let winner = null;
|
||||
interface Props {
|
||||
game: Game;
|
||||
winner?: any;
|
||||
}
|
||||
|
||||
let customDelay: number;
|
||||
let { game = $bindable(), winner = null }: Props = $props();
|
||||
|
||||
$: {
|
||||
let customDelay: number = $state();
|
||||
|
||||
run(() => {
|
||||
if ($changeToComputer.change) {
|
||||
customDelay = 400;
|
||||
} else {
|
||||
customDelay = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
@@ -31,7 +37,7 @@
|
||||
{#each row as column, columnIndex}
|
||||
<td
|
||||
class="cursor-pointer text-center h-25 w-[1.5em] border-r-10 border-b-10 border-white last:border-r-0"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
game = game.click(rowIndex, columnIndex);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,7 @@ changeToComputer.subscribe((value) => {
|
||||
}
|
||||
});
|
||||
|
||||
export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTie: () => void }) {
|
||||
export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void; onTie: () => void }) {
|
||||
const board = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
@@ -23,7 +23,7 @@ export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTi
|
||||
return {
|
||||
board,
|
||||
|
||||
click(rowIndex:number, columnIndex:number) {
|
||||
click(rowIndex: number, columnIndex: number) {
|
||||
if (!done && !board[rowIndex][columnIndex]) {
|
||||
board[rowIndex][columnIndex] = turn;
|
||||
turn = turn === 'X' ? 'O' : 'X';
|
||||
@@ -43,25 +43,24 @@ export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTi
|
||||
if (activateComp) {
|
||||
const aiMove = getBestMove(board);
|
||||
if (aiMove) {
|
||||
board[aiMove.row][aiMove.column] = 'O';
|
||||
turn = 'X';
|
||||
|
||||
const aiWinner = getWinner(board);
|
||||
if (aiWinner) {
|
||||
onWin(aiWinner);
|
||||
done = true;
|
||||
} else if (isBoardFull(board)) {
|
||||
onTie();
|
||||
scores.update((currentScores) => {
|
||||
return { ...currentScores, draw: currentScores.draw + 1 };
|
||||
});
|
||||
done = true;
|
||||
}
|
||||
board[aiMove.row][aiMove.column] = 'O';
|
||||
turn = 'X';
|
||||
|
||||
const aiWinner = getWinner(board);
|
||||
if (aiWinner) {
|
||||
onWin(aiWinner);
|
||||
done = true;
|
||||
} else if (isBoardFull(board)) {
|
||||
onTie();
|
||||
scores.update((currentScores) => {
|
||||
return { ...currentScores, draw: currentScores.draw + 1 };
|
||||
});
|
||||
done = true;
|
||||
}
|
||||
} else {
|
||||
console.error("AI could not find a valid move.");
|
||||
console.error('AI could not find a valid move.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type {Writable} from 'svelte/store'
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
interface Scores {
|
||||
X: number;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { fade } from 'svelte/transition';
|
||||
export let winner: Winner | null = null;
|
||||
interface Props {
|
||||
winner?: Winner | null;
|
||||
}
|
||||
|
||||
let { winner = null }: Props = $props();
|
||||
|
||||
interface Winner {
|
||||
direction: 'horizontal' | 'vertical' | 'diagonal';
|
||||
@@ -9,13 +15,15 @@
|
||||
diagonal?: 'top-left-to-bottom-right' | 'top-right-to-bottom-left';
|
||||
}
|
||||
|
||||
$: if (winner) {
|
||||
if (winner.direction === 'vertical' && winner.column !== undefined) {
|
||||
document.documentElement.style.setProperty('--col-index', winner.column.toString());
|
||||
} else if (winner.direction === 'horizontal' && winner.row !== undefined) {
|
||||
document.documentElement.style.setProperty('--row-index', winner.row.toString());
|
||||
run(() => {
|
||||
if (winner) {
|
||||
if (winner.direction === 'vertical' && winner.column !== undefined) {
|
||||
document.documentElement.style.setProperty('--col-index', winner.column.toString());
|
||||
} else if (winner.direction === 'horizontal' && winner.row !== undefined) {
|
||||
document.documentElement.style.setProperty('--row-index', winner.row.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if winner}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Re-export all components for convenient importing
|
||||
// Usage: import { Footer, Navigation, Hero } from '$lib/components'
|
||||
export * from './components';
|
||||
|
||||
|
||||
@@ -2,31 +2,31 @@ export const faqs = [
|
||||
{
|
||||
question: 'Who can benefit from ClassroomIO?',
|
||||
answer:
|
||||
'We built ClassroomIO from the ground up for Educators, meaning if you teach online or in a physical classroom you are good to go. It works best for Educators who already have students and want an efficient way to manage their learning experience.',
|
||||
'We built ClassroomIO from the ground up for Educators, meaning if you teach online or in a physical classroom you are good to go. It works best for Educators who already have students and want an efficient way to manage their learning experience.'
|
||||
},
|
||||
{
|
||||
question: 'Will I be able to set up ClassroomIO without technical skills?',
|
||||
answer:
|
||||
'Most definitely. You can use ClassroomIO as a no-code tool to build your own LMS platform. We provide an intuitive user interface and documentation to help you give your students the best learning experience.',
|
||||
'Most definitely. You can use ClassroomIO as a no-code tool to build your own LMS platform. We provide an intuitive user interface and documentation to help you give your students the best learning experience.'
|
||||
},
|
||||
{
|
||||
question: 'How much customization can I do with ClassroomIO?',
|
||||
answer:
|
||||
'Things like theming, organization name, landing pages, content that your students see and many more customization options to come.',
|
||||
'Things like theming, organization name, landing pages, content that your students see and many more customization options to come.'
|
||||
},
|
||||
{
|
||||
question: 'Is ClassroomIO suitable for a large number of students?',
|
||||
answer:
|
||||
'Absolutely. ClassroomIO is built for a large number of students in any course. You can have multiple courses with as many students as you need.',
|
||||
'Absolutely. ClassroomIO is built for a large number of students in any course. You can have multiple courses with as many students as you need.'
|
||||
},
|
||||
{
|
||||
question: 'What kind of team plans does ClassroomIO offer?',
|
||||
answer:
|
||||
'ClassroomIO offers a Team plan designed to cater to organizations of any size. Depending on the plan you select, you can invite other teachers into your organization.',
|
||||
'ClassroomIO offers a Team plan designed to cater to organizations of any size. Depending on the plan you select, you can invite other teachers into your organization.'
|
||||
},
|
||||
{
|
||||
question: 'Can I try ClassroomIO before I buy it?',
|
||||
answer:
|
||||
'Definitely. We’ve got a generous free plan to help you get started with the platform. As your requirements grow, we’ve got other plans that can grow with you.',
|
||||
},
|
||||
'Definitely. We’ve got a generous free plan to help you get started with the platform. As your requirements grow, we’ve got other plans that can grow with you.'
|
||||
}
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user