migrate web to svelte 5

This commit is contained in:
Rotimi Best
2025-12-25 05:37:21 +00:00
parent 50d6ee3111
commit 334c67f22a
63 changed files with 621 additions and 479 deletions
@@ -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" />
+2 -2
View File
@@ -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(() => {
-1
View File
@@ -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';
+10 -3
View File
@@ -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}
/>
+1 -11
View File
@@ -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
View File
@@ -1,4 +1,3 @@
// Re-export all components for convenient importing
// Usage: import { Footer, Navigation, Hero } from '$lib/components'
export * from './components';
+7 -7
View File
@@ -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.'
}
];