feat: website release v2 (#616)

* feat: upgrade to svelte 5 & tailwind v4 + add navigation menu to ui package

* feat: website navigation with nav menu from shadcn

* chore: refactor navigation menu storybook

* fix: responsiveness bug

* chore: remove tailwind config file

* chore:
- remove carbon icons from repo
- add Geist font family

* refactor website

---------

Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Adeyemi Adetunji
2025-12-20 12:44:59 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 690cf147ff
commit e6a3cf9332
108 changed files with 2425 additions and 2169 deletions
@@ -1,6 +1,7 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [
{
@@ -77,20 +78,15 @@
<PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
>
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Solution for Bootcamps
</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Solution for Bootcamps</Badge>
<div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
>
<div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1
class="mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
<h1 class="mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
The platform for fast
<span class="text-blue-700 relative"> growing bootcamps</span>
</h1>
@@ -99,13 +95,9 @@
everything you need to run a cutting-edge, high-impact bootcamp.
</p>
</div>
<a
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
<Button href="/signup" target="_blank" rel="noopener noreferrer nofollow">
Get Started for Free 🚀
</a>
</Button>
</div>
</PageHeader>
@@ -115,11 +107,10 @@
<div
class="w-full gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
>
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base font-medium">Our benefits</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Our benefits</Badge>
<div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1 class="text-3xl md:text-5xl font-bold">Inspire Student Success</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -130,7 +121,7 @@
</div>
<div
class="w-full max-w-screen-xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
class="w-full max-w-7xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
>
{#each benefitDetails as benefits}
<div class="w-full md:p-6 p-4 border border-[#D9E0F5] rounded-2xl flex flex-col gap-y-6">
@@ -164,11 +155,10 @@
class="w-full bg-[#F1F6FF] flex flex-col items-center justify-center gap-y-16 lg:py-24 py-16 lg:px-28 md:px-16 px-8"
>
<div
class="w-full max-w-screen-xl gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
class="w-full max-w-7xl gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
>
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base font-medium">Explore our features</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Explore our features</Badge>
<div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
@@ -181,9 +171,7 @@
</div>
</div>
<div
class="w-full max-w-screen-xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16"
>
<div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16">
{#each featureDetails as feature, i}
<div
class="w-full lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -206,21 +194,17 @@
</div>
{#if feature.ctaLink}
<a
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
href={feature.ctaLink}
target="_blank noopener noreferrer"
>
<Button variant="outline" href={feature.ctaLink} target="_blank noopener noreferrer">
{feature.ctaLabel}
</a>
</Button>
{:else}
<button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
<Button
variant="outline"
data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo"
>
Book a Demo
</button>
</Button>
{/if}
</div>
@@ -1,6 +1,7 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [
{
@@ -61,20 +62,15 @@
<PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
>
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Solution for Customer Education
</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Solution for Customer Education</Badge>
<div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
>
<div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1
class="w-[95%] mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
<h1 class="w-[95%] mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
Drive retention and revenue with
<span class="text-blue-700 relative">Customer Education</span>
</h1>
@@ -84,13 +80,9 @@
table.
</p>
</div>
<a
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
<Button href="/signup" target="_blank" rel="noopener noreferrer nofollow">
Get Started for Free 🚀
</a>
</Button>
</div>
</PageHeader>
@@ -100,11 +92,9 @@
<div
class="w-full gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
>
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class="text-sm md:text-base font-medium">Our benefits</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Our benefits</Badge>
<div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1 class="text-3xl md:text-5xl font-bold">The customer education toolkit</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -115,7 +105,7 @@
</div>
<div
class="w-full max-w-screen-xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
class="w-full max-w-7xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
>
{#each benefitDetails as benefits}
<div class="w-full md:p-6 p-4 border border-[#D9E0F5] rounded-2xl flex flex-col gap-y-6">
@@ -151,7 +141,7 @@
</p>
</div>
<div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1 class="text-3xl md:text-5xl font-bold">Why Choose Us For Education Needs</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -161,9 +151,7 @@
</div>
</div>
<div
class="w-full max-w-screen-xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16"
>
<div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16">
{#each featureDetails as feature, i}
<div
class="w-full lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -183,13 +171,13 @@
{feature.description}
</p>
</div>
<button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
<Button
variant="outline"
data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo"
>
Book a Demo
</button>
</Button>
</div>
<div>
@@ -1,6 +1,7 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [
{
@@ -57,20 +58,15 @@
<PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
>
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Unlock your team's potential
</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Unlock your team's potential</Badge>
<div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
>
<div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1
class="mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
<h1 class="mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
Employee training software for
<br /><span class="text-blue-700 relative">modern teams</span>
</h1>
@@ -79,23 +75,15 @@
quickly.
</p>
</div>
<a
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
Get Started for Free 🚀
</a>
<Button href="/signup" target="_blank">Get Started for Free 🚀</Button>
</div>
</PageHeader>
<section
class="w-full max-w-screen-xl lg:py-24 py-16 lg:px-28 md:px-16 px-8 flex lg:flex-row flex-col lg:items-center lg:justify-between justify-center gap-x-6 gap-y-8"
class="w-full max-w-7xl lg:py-24 py-16 lg:px-28 md:px-16 px-8 flex lg:flex-row flex-col lg:items-center lg:justify-between justify-center gap-x-6 gap-y-8"
>
<div class="w-full lg:w-[35%] gap-y-4 flex flex-col items-start justify-start">
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base font-medium">Amazing benefits</p>
</div>
<Badge variant="outline">Amazing benefits</Badge>
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
<h1 class="font-bold text-4xl">Unlock your team's potential</h1>
@@ -134,13 +122,10 @@
<div
class="w-full gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
>
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class="text-sm md:text-base text-[#4B5563] text-center font-medium">
Explore our features
</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Explore our features</Badge>
<div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1 class="text-3xl md:text-5xl font-bold">What makes us the right choice</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -150,9 +135,7 @@
</div>
</div>
<div
class="w-full max-w-screen-xl flex flex-col justify-start items-center lg:gap-y-24 gap-16 p-2"
>
<div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-16 p-2">
{#each featureDetails as feature, i}
<div
class="w-full lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -173,13 +156,13 @@
{feature.description}
</p>
</div>
<button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
<Button
variant="outline"
data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo"
>
Book a Demo
</button>
</Button>
</div>
<div>
@@ -198,7 +181,7 @@
class="w-full bg-[#040F2D] flex flex-col items-center justify-start gap-y-16 lg:py-24 py-16 lg:px-28 md:px-16 px-8"
>
<div
class="w-full max-w-screen-xl flex lg:flex-row-reverse flex-col-reverse lg:justify-between justify-start lg:items-center items-start gap-y-8"
class="w-full max-w-7xl flex lg:flex-row-reverse flex-col-reverse lg:justify-between justify-start lg:items-center items-start gap-y-8"
>
<div class="w-full lg:w-[40%] gap-y-4 flex flex-col items-start justify-start">
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
@@ -209,13 +192,7 @@
Companies get the opportunity to request custom features from us.
</p>
</div>
<a
href="/demo"
target="_blank"
class="py-4 px-8 border border-tranparent outline-transparent bg-white rounded text-[#040F2D] text-sm font-semibold"
>
Talk to sales
</a>
<Button href="/demo" target="_blank" variant="outline">Talk to sales</Button>
</div>
<img
+1 -3
View File
@@ -1,9 +1,7 @@
<script>
import { dev } from '$app/environment';
import { page } from '$app/state';
import Footer from '$lib/Footer/Footer.svelte';
import Navigation from '$lib/Navigation/Navigation.svelte';
import NotFound from '$lib/NotFound/NotFound.svelte';
import { Footer, Navigation, NotFound } from '$lib/components';
import extend from 'just-extend';
import { onMount } from 'svelte';
import { MetaTags } from 'svelte-meta-tags';
+1 -6
View File
@@ -1,10 +1,5 @@
<script>
import Hero from '$lib/Home/Hero.svelte';
import CoreFeatures from '$lib/Home/CoreFeatures.svelte';
import Faq from '$lib/Home/Faq.svelte';
import StudentDashboard from '$lib/Home/StudentDashboard.svelte';
import MoreFeatures from '$lib/Home/MoreFeatures.svelte';
import Testimonial from '$lib/Home/Testimonial.svelte';
import { Hero, CoreFeatures, Faq, StudentDashboard, MoreFeatures, Testimonial } from '$lib/components';
import { PUBLIC_ENABLE_FAQ } from '$env/static/public';
import { onMount } from 'svelte';
+1 -1
View File
@@ -1,5 +1,5 @@
import { json } from '@sveltejs/kit';
import type { BlogPost } from '$lib/types';
import type { BlogPost } from '$lib/utils/types';
async function getBlogPosts() {
let blogPosts: BlogPost[] = [];
+28 -27
View File
@@ -1,38 +1,39 @@
import { PUBLIC_SERVER_URL } from '$env/static/public';
const sendEmail = async (subject: string, content: string, replyTo?: string) => {
try {
const response = await fetch(`${PUBLIC_SERVER_URL}/sendEmail`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify([
{
from: `"ClassroomIO Support" <[email protected]>`,
to: 'help@classroomio.com',
subject,
content,
replyTo
}
])
});
const sendEmail =
(sFetch: typeof fetch) => async (subject: string, content: string, replyTo?: string) => {
try {
const response = await sFetch(`${PUBLIC_SERVER_URL}/sendEmail`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify([
{
from: `"ClassroomIO Support" <notify@mail.classroomio.com>`,
to: '[email protected]',
subject,
content,
replyTo
}
])
});
const responseData = await response.json();
console.log('Emails sent successfully:', responseData);
const responseData = await response.json();
console.log('Emails sent successfully:', responseData);
return 'sent';
} catch (error) {
console.error('Error sending email', error);
return 'failed';
}
};
return 'sent';
} catch (error) {
console.error('Error sending email', error);
return 'failed';
}
};
export async function POST({ request }) {
export async function POST({ request, fetch }) {
try {
const { title, description, replyTo } = await request.json();
console.log(title, description, replyTo);
const result = await sendEmail(title, description, replyTo);
const result = await sendEmail(fetch)(title, description, replyTo);
return new Response(
JSON.stringify({
+2 -6
View File
@@ -1,7 +1,5 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import BlogListItem from '$lib/Blog/BlogListItem.svelte';
import { PageSignupCTA, PageHeader, BlogListItem } from '$lib/components';
export let data;
</script>
@@ -12,9 +10,7 @@
<section>
<PageHeader className="flex flex-col items-center justify-center text-center">
<h1
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<h1 class="mx-auto text-4xl md:text-6xl text-slate-900 flex flex-col items-center">
<span>What's new on</span>
<span class="text-blue-700 relative">ClassroomIO?</span>
</h1>
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BlogPost } from '$lib/types';
import type { BlogPost } from '$lib/utils/types';
/** @type {import('./$types').PageLoad} */
export async function load({ fetch }) {
@@ -1,8 +1,10 @@
<script lang="ts">
import { ChevronLeft } from 'carbon-icons-svelte';
import Chip from '$lib/Chip/Chip.svelte';
import { formatDate } from '$lib/utils/formatDate';
import BlogListItem from '$lib/Blog/BlogListItem.svelte';
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import { Badge } from '@cio/ui/base/badge';
import { formatDate } from '$lib/utils/format-date';
import { Button } from '@cio/ui/base/button';
import { BlogListItem } from '$lib/components';
export let data;
</script>
@@ -11,13 +13,13 @@
{#if data}
<article class="py-16">
<!-- Title -->
<hgroup class="flex flex-col items-center justify-center text-center w-full">
<hgroup class="flex w-full flex-col items-center justify-center text-center">
<p class="text-sm text-gray-500">{formatDate(data.meta.date)}</p>
<p class="font-bold text-3xl py-2 text-center md:w-[60%]">{@html data.meta.title}</p>
<p class="py-2 text-center text-3xl font-bold md:w-[60%]">{@html data.meta.title}</p>
</hgroup>
<main class="mx-auto max-w-screen-md px-4 lg:px-8">
<div class="flex items-center justify-start gap-4 my-2 border-y border-gray-200 py-4">
<img loading="lazy" src={data.meta.avatar} alt="avatar" class="w-10 h-10 rounded-full" />
<main class="mx-auto max-w-3xl px-4 lg:px-8">
<div class="my-2 flex items-center justify-start gap-4 border-y border-gray-200 py-4">
<img loading="lazy" src={data.meta.avatar} alt="avatar" class="h-10 w-10 rounded-full" />
<span>
<p class="font-semibold">{data.meta.author}</p>
<p class="text-gray-500">{data.meta.role}</p>
@@ -25,12 +27,12 @@
</div>
<!-- Post -->
<div class="prose pt-2 pb-4 border-b-2 border-gray-200">
<div class="prose border-b-2 border-gray-200 pb-4 pt-2">
<svelte:component this={data.content} />
<!-- Tags -->
<div class="flex gap-2 py-4">
{#each data.meta.tags as tag}
<Chip label={tag} />
<Badge variant="outline">{tag}</Badge>
{/each}
</div>
</div>
@@ -40,7 +42,7 @@
<p class="text-xl font-semibold">Related Posts</p>
<ul class="flex items-start justify-start gap-3 overflow-x-scroll">
{#each data.relatedPosts as post}
<li class="py-10 min-w-[80%] sm:w-80 sm:min-w-0">
<li class="min-w-[80%] py-10 sm:w-80 sm:min-w-0">
<BlogListItem {post} isRecommended />
</li>
{/each}
@@ -48,12 +50,9 @@
</section>
{/if}
<a
href="/blog"
class="flex font-medium items-center gap-2 text-slate-700 text-lg underline my-10"
>
<ChevronLeft class=" w-6 h-6 font-medium" /> Back to all posts
</a>
<Button href="/blog" variant="link">
<ChevronLeft /> Back to all posts
</Button>
</main>
</article>
{/if}
+1 -1
View File
@@ -1,5 +1,5 @@
import { error } from '@sveltejs/kit';
import type { BlogPost, RawBlogPost } from '$lib/types';
import type { BlogPost, RawBlogPost } from '$lib/utils/types';
import type { MetaTagsProps } from 'svelte-meta-tags';
/** @type {import('./$types').PageLoad} */
+16 -17
View File
@@ -1,11 +1,12 @@
<script lang="ts">
import ArrowUpRight from 'carbon-icons-svelte/lib/ArrowUpRight.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import ArrowUpRight from '@lucide/svelte/icons/arrow-up-right';
import FeedbackForm from '$lib/Contact/FeedbackForm.svelte';
import HelpForm from '$lib/Contact/HelpForm.svelte';
import { FORM_TYPE } from '$lib/types';
import type { FORM_TYPE_KEY } from '$lib/types';
import { FORM_TYPE } from '$lib/utils/types';
import type { FORM_TYPE_KEY } from '$lib/utils/types';
import HelpForm from '$lib/features/contact/help-form.svelte';
import { PageHeader } from '$lib/components';
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
let currentForm: FORM_TYPE_KEY;
@@ -45,40 +46,38 @@
<section>
<PageHeader className="flex flex-col items-center justify-center bg-[#F5F8FE] text-center">
<div class="mb-2 ml-[5%] lg:ml-0 flex w-full items-center justify-center">
<div class="mb-2 ml-[5%] flex w-full items-center justify-center lg:ml-0">
<span
style="color: rgb(75, 85, 99);"
class="rounded-full border-2 border-[#C2D2FF] px-4 py-1 text-sm font-medium bg-[#DCE5FF] text-blue-700"
class="rounded-full border-2 border-[#C2D2FF] bg-[#DCE5FF] px-4 py-1 text-sm font-medium text-blue-700"
>
Contact Us
</span>
</div>
<h1
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<h1 class="mx-auto flex flex-col items-center text-4xl text-slate-900 md:text-6xl">
<span>Get in touch with <span class="text-blue-700">Us</span></span>
</h1>
<p
class="w-[90%] md:w-[60%] max-w-3xl text-center font-normal text-lg text-slate-700 mt-10 lg:mt-7"
class="mt-10 w-[90%] max-w-3xl text-center text-lg font-normal text-slate-700 md:w-[60%] lg:mt-7"
>
Report any bugs you encounter, request new features you'd like to see, or ask any questions
you have about ClassroomIO
</p>
</PageHeader>
<div class="my-20 mx-auto">
<div class="mx-auto my-20">
<!-- Content here -->
<div
class="mx-auto w-[90%] lg:w-[60%] place-items-center grid grid-cols-1 md:grid-cols-2 gap-10"
class="mx-auto grid w-[90%] grid-cols-1 place-items-center gap-10 md:grid-cols-2 lg:w-[60%]"
>
{#each formList as list}
<button
on:click={() => onChange(list.id, list.to)}
class="text-start border-[1.5px] w-full rounded-md p-3 {currentForm === list.id
class="w-full rounded-md border-[1.5px] p-3 text-start {currentForm === list.id
? `border-[#0233BD]`
: `border-gray-200`} flex items-center justify-between"
>
<p class="text-lg text-slate-700 font-normal">{list.title}</p>
<p class="text-lg font-normal text-slate-700">{list.title}</p>
{#if list.to}
<ArrowUpRight size={16} />
{/if}
@@ -87,7 +86,7 @@
</div>
{#if currentForm === FORM_TYPE[currentForm]}
<div class="mx-auto w-[90%] lg:w-[60%] px-5">
<div class="mx-auto w-[90%] px-5 lg:w-[60%]">
{#if currentForm === FORM_TYPE.HELP}
<HelpForm />
{:else if currentForm === FORM_TYPE.FEEDBACK}
@@ -1,5 +1,6 @@
<script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
export let data;
</script>
@@ -35,18 +36,14 @@
</p>
</div>
<div class="text-left px-6 mt-3">
<a
<Button
href={friend.href}
target="_blank"
rel="noopener noreferrer"
class="inline-block w-full"
variant="outline"
>
<button
class="font-semibold text-base text-white bg-gray-800 hover:bg-gray-900 hover:shadow-xl transition-all delay-100 px-4 py-3 rounded-md"
>
Learn more
</button>
</a>
Learn more
</Button>
</div>
</li>
{/each}
+1 -1
View File
@@ -1,4 +1,4 @@
import type { OssFriend } from '$lib/types';
import type { OssFriend } from '$lib/utils/types';
export async function load() {
const friends: OssFriend[] = [
+17 -26
View File
@@ -1,9 +1,10 @@
<script>
import Check from '@lucide/svelte/icons/check';
import { fade } from 'svelte/transition';
import Checkmark from 'carbon-icons-svelte/lib/Checkmark.svelte';
import { PLANS } from '@cio/utils/plans';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
let isYearlyPlan = false;
@@ -18,16 +19,14 @@
<section>
<PageHeader className="flex flex-col items-center justify-center text-center">
<h1
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<h1 class="mx-auto flex flex-col items-center text-4xl text-slate-900 md:text-6xl">
<span>There is room for</span>
<span class="text-blue-700 relative">Everyone.</span>
<span class="relative text-blue-700">Everyone.</span>
</h1>
<p class="w-[90%] md:w-[60%] text-center font-normal text-lg text-slate-700 mt-10 lg:mt-7">
<p class="mt-10 w-[90%] text-center text-lg font-normal text-slate-700 md:w-[60%] lg:mt-7">
You get a customizable LMS, AI integration for productive educators and many more...
</p>
<div class="relative mt-10 flex items-center rounded-[30px] border-[2px] p-[2px] lg:scale-100">
<div class="relative mt-10 flex items-center rounded-[30px] border-2 p-[2px] lg:scale-100">
<button
style="background-color: {isYearlyPlan ? 'initial' : '#1D4EE2'}; color: {isYearlyPlan
? '#5e636b'
@@ -71,7 +70,7 @@
{PLANS.BASIC.NAME}
</p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-gray-500 lg:leading-6">
<p class="mt-4 text-sm font-light leading-6 text-gray-500 lg:text-base lg:leading-6">
{PLANS.BASIC.DESCRIPTION}
</p>
@@ -81,7 +80,7 @@
<span class="text-base">Free forever</span>
<a
class="text-base mt-10 block w-full rounded-md bg-slate-900 py-3 text-center font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
class="mt-10 block w-full rounded-md bg-slate-900 py-3 text-center text-base font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
href={PLANS.BASIC.CTA.LINK}
target="_blank"
>
@@ -91,11 +90,7 @@
<ul class="mt-8 space-y-2 text-sm text-gray-600 lg:space-y-5 xl:mt-10">
{#each PLANS.BASIC.FEATURES as feature}
<li class="flex items-center">
<Checkmark
size={24}
fill="#1D4EE2"
class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]"
/>
<Check size={24} fill="#1D4EE2" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
{feature}
</li>
{/each}
@@ -108,7 +103,7 @@
{PLANS.EARLY_ADOPTER.NAME}
</p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-white lg:leading-6">
<p class="mt-4 text-sm font-light leading-6 text-white lg:text-base lg:leading-6">
{PLANS.EARLY_ADOPTER.DESCRIPTION}
</p>
@@ -125,7 +120,7 @@
{/key}
<a
class="text-base mt-10 block w-full rounded-md bg-white py-3 text-center font-medium text-slate-900 hover:bg-indigo-50 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
class="mt-10 block w-full rounded-md bg-white py-3 text-center text-base font-medium text-slate-900 hover:bg-indigo-50 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
href={PLANS.EARLY_ADOPTER.CTA.LINK}
target="_blank"
>
@@ -134,7 +129,7 @@
<ul class="mt-8 space-y-3 text-sm font-light text-white lg:space-y-5 xl:mt-10">
{#each PLANS.EARLY_ADOPTER.FEATURES as features}
<li class="flex items-center">
<Checkmark size={24} fill="#fff" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
<Check size={24} fill="#fff" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
{features}
</li>
{/each}
@@ -146,14 +141,14 @@
<p class="text-lg font-semibold leading-8 text-gray-900 lg:text-xl">
{PLANS.ENTERPRISE.NAME}
</p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-gray-500 lg:leading-6">
<p class="mt-4 text-sm font-light leading-6 text-gray-500 lg:text-base lg:leading-6">
{PLANS.ENTERPRISE.DESCRIPTION}
</p>
<p class="mt-6 flex items-baseline gap-x-1 text-xl font-medium lg:mt-6 lg:text-xl">
Request Pricing
</p>
<button
class="text-base mt-10 w-full rounded-md bg-slate-900 py-3 font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
class="mt-10 w-full rounded-md bg-slate-900 py-3 text-base font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
data-cal-config={"{'layout':'month_view'}"}
data-cal-link="classroomio/enterprise"
>
@@ -163,11 +158,7 @@
<ul class="mt-8 space-y-2 text-sm text-gray-600 lg:space-y-5 xl:mt-10">
{#each PLANS.ENTERPRISE.FEATURES as features}
<li class="flex items-center">
<Checkmark
size={24}
fill="#1D4EE2"
class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]"
/>
<Check size={24} fill="#1D4EE2" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
{features}
</li>
{/each}
+3 -7
View File
@@ -1,7 +1,5 @@
<script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import Timeline from '$lib/Timeline/Timeline.svelte';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import { PageHeader, Timeline, PageSignupCTA } from '$lib/components';
const newFeatures = [
{
@@ -67,13 +65,11 @@
</svelte:head>
<PageHeader className="flex flex-col items-center justify-center text-center">
<h1 class="text-start md:text-center text-5xl md:text-7xl font-bold text-slate-900">
Product Roadmap
</h1>
<h1 class="text-start md:text-center text-4xl md:text-6xl text-slate-900">Product Roadmap</h1>
<p class="text-start md:text-center w-[80%] md:w-[40%] text-slate-700 mt-10">
This is a live document, and will be updated regularly. To submit an idea, or to view a list of
feature requests and ideas that we're considering, check out our <a
href="https://github.com/rotimi-best/classroomio/discussion"
href="https://github.com/classroomio/classroomio/discussion"
class="underline">GitHub Discussions</a
>
</p>
+43 -57
View File
@@ -1,26 +1,23 @@
<script lang="ts">
import { slide } from 'svelte/transition';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
const whyStart = [
{
src: '/teach/teach-from-anywhere.svg',
header: 'Fully Online; Teach from anywhere',
subText:
' Facilitate a subject from the comfort of your space, engaging students from highly diverse background'
subText: ' Facilitate a subject from the comfort of your space, engaging students from highly diverse background'
},
{
src: '/teach/get-paid.svg',
header: 'Get paid extra commission',
subText:
'Get paid for the course you design and earn extra comission based on course perfomance'
subText: 'Get paid for the course you design and earn extra comission based on course perfomance'
},
{
src: '/teach/inspire-learners.svg',
header: 'Inspire learners',
subText:
'Your opportunity to inspire thousands of learners to achieve dreams and academic pursue exellence'
subText: 'Your opportunity to inspire thousands of learners to achieve dreams and academic pursue exellence'
}
];
@@ -79,22 +76,12 @@
<title>Teach | ClassroomIO</title>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/teach.png" />
<meta property="og:title" content="Teach on ClassroomIO" />
<meta
property="og:description"
content="On ClassroomIO you can make side income while doing what you love."
/>
<meta property="og:description" content="On ClassroomIO you can make side income while doing what you love." />
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/teach.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/teach.png" />
<meta name="twitter:title" content="Teach on ClassroomIO" />
<meta
name="twitter:description"
content="On ClassroomIO you can make side income while doing what you love."
/>
<meta name="twitter:description" content="On ClassroomIO you can make side income while doing what you love." />
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/teach.png" />
</svelte:head>
@@ -102,17 +89,16 @@
<PageHeader className="flex items-center justify-evenly px-5 flex-wrap text-left">
<div>
<h1 class="text-5xl font-bold">Teach with us</h1>
<p class="my-5 lg:w-[77%] lg:text-left text-center">
<p class="my-5 text-center lg:w-[77%] lg:text-left">
With ClassroomIO, you can make side income while teaching the subject you love
</p>
<a
<Button
href="/teach/register"
target="_blank"
class="w-full block text-center lg:w-[30%] py-3 text-base text-white rounded-md bg-blue-700"
>
Register
</a>
</Button>
</div>
<div class=" ">
@@ -120,21 +106,21 @@
</div>
</PageHeader>
<div class="mt-10 mx-auto">
<div class="px-[10%] pb-10 border-b border-slate-100">
<h1 class="text-4xl pb-28 text-center font-bold flex gap-3 justify-center items-center">
<div class="mx-auto mt-10">
<div class="border-b border-slate-100 px-[10%] pb-10">
<h1 class="flex items-center justify-center gap-3 pb-28 text-center text-4xl font-bold">
Why you should teach with us? <img src="/teach/star.svg" alt="" />
</h1>
<div class="flex flex-wrap justify-center lg:justify-between mt-10">
<div class="mt-10 flex flex-wrap justify-center lg:justify-between">
{#each whyStart as why}
<figure
class="flex gap-3 flex-col justify-center items-center px-2 w-full h-full max-w-[250px] max-h-[380px] text-center"
class="flex h-full max-h-[380px] w-full max-w-[250px] flex-col items-center justify-center gap-3 px-2 text-center"
>
<img src={why.src} alt="" class="w-[80px] h-[80px]" />
<img src={why.src} alt="" class="h-[80px] w-[80px]" />
<figcaption>
<h1 class="font-bold text-base">{why.header}</h1>
<p class="text-sm mt-1">
<h1 class="text-base font-bold">{why.header}</h1>
<p class="mt-1 text-sm">
{why.subText}
</p>
</figcaption>
@@ -143,66 +129,66 @@
</div>
</div>
<div class="px-[10%] mt-10 py-10">
<h1 class="text-4xl mb-28 text-center font-bold flex gap-3 justify-center items-center">
<div class="mt-10 px-[10%] py-10">
<h1 class="mb-28 flex items-center justify-center gap-3 text-center text-4xl font-bold">
How to get started <img src="/teach/thumbs-up.svg" alt="" />
</h1>
<div class="flex flex-wrap justify-between gap-y-10 mx-auto mt-5">
<div class="mx-auto mt-5 flex flex-wrap justify-between gap-y-10">
{#each steps as step, i}
<div class="lg:max-w-[40%] px-10 py-5">
<h1 class="font-bold text-2xl">
<span class="px-2 py-1 mr-3 bg-[#F7F7F7] rounded-md">{i + 1}</span>
<div class="px-10 py-5 lg:max-w-[40%]">
<h1 class="text-2xl font-bold">
<span class="mr-3 rounded-md bg-[#F7F7F7] px-2 py-1">{i + 1}</span>
{step.title}
</h1>
<p class="text-base text-gray-600 mt-5">
<p class="mt-5 text-base text-gray-600">
{step.subText}
</p>
</div>
{/each}
</div>
<div class="flex justify-center px-[5%] mt-3">
<a
<div class="mt-3 flex justify-center px-[5%]">
<Button
href="/teach/register"
target="_blank"
class="w-full lg:max-w-[20%] py-3 text-base text-white rounded-md bg-blue-700 text-center"
>
rel="noopener noreferrer nofollow"
>
Get Started
</a>
</Button>
</div>
</div>
<div class="px-2 lg:px-[12%] bg-[#F5F8FE] my-10 py-12 h-[75%] lg:h-[80%]">
<h1 class="text-2xl text-center font-bold flex gap-3 justify-center items-center">
<div class="my-10 h-[75%] bg-[#F5F8FE] px-2 py-12 lg:h-[80%] lg:px-[12%]">
<h1 class="flex items-center justify-center gap-3 text-center text-2xl font-bold">
Word from our fufilled facilitators
</h1>
<div
class="mt-10 w-full md:w-2/4 mx-auto border shadow-sm bg-white rounded-md text-center py-6 relative before:w-[4.5%] before:absolute before:left-0 before:top-0 before:h-full before:bg-[#0233BD] before:rounded-tl-md before:rounded-bl-md"
class="relative mx-auto mt-10 w-full rounded-md border bg-white py-6 text-center shadow-sm before:absolute before:left-0 before:top-0 before:h-full before:w-[4.5%] before:rounded-bl-md before:rounded-tl-md before:bg-[#0233BD] md:w-2/4"
>
<h1 class="text-base font-medium leading-5 w-[77%] mx-auto">
With ClassroomIO, I am training over 100 students. I am making more money than expected
with little effort and my students are happy. Love the product
<h1 class="mx-auto w-[77%] text-base font-medium leading-5">
With ClassroomIO, I am training over 100 students. I am making more money than expected with little effort and
my students are happy. Love the product
</h1>
<div class="flex flex-row gap-4 justify-center items-center mt-3">
<div class="mt-3 flex flex-row items-center justify-center gap-4">
<img src="/teach/user-profile.png" alt="" class="w-8" />
<p class="text-sm">
<span class="font-semibold mr-2"> Bilik Diana </span> English tutor
<span class="mr-2 font-semibold"> Bilik Diana </span> English tutor
</p>
</div>
</div>
</div>
<div class="px-10 lg:px-[15%] lg:max-w-[65%] my-10 mt-10 mx-auto">
<h1 class="text-2xl text-center font-bold">Frequently asked questions</h1>
<div class="mx-auto my-10 mt-10 px-10 lg:max-w-[65%] lg:px-[15%]">
<h1 class="text-center text-2xl font-bold">Frequently asked questions</h1>
<div class="mt-10">
{#each faqs as faq, index}
<div class="border-b border-black lg:px-3 pt-1 pb-2">
<div class="border-b border-black pb-2 pt-1 lg:px-3">
<div>
<button
type="button"
class="w-full font-medium text-sm md:text-lg my-2 flex justify-between text-left md:text-center"
class="my-2 flex w-full justify-between text-left text-sm font-medium md:text-center md:text-lg"
on:click={() => toggleAnswer(index)}
>
{faq.question} <span>{faq.showAnswer ? '-' : '+'}</span>
+1 -1
View File
@@ -1,5 +1,5 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import { PageSignupCTA } from '$lib/components';
</script>
<svelte:head>
+1 -1
View File
@@ -1,5 +1,5 @@
<script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageHeader } from '$lib/components';
import { tools } from '$lib/utils/constants/tools';
const freeTools = tools.filter((item) => item.show);
@@ -1,5 +1,5 @@
<script lang="ts">
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import { ToolsHeader } from '$lib/components';
let namesInput = '';
let numNames = 0;
@@ -103,22 +103,14 @@
<svelte:head>
<title>Random Name Picker | ClassroomIO</title>
<meta
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta property="og:title" content="Random Name Picker | ClassroomIO" />
<meta
property="og:description"
content="Use this online name picker to draw a random name from a list of names for your online or physical classroom."
/>
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta name="twitter:title" content="Random Name Picker | ClassroomIO" />
<meta
@@ -128,41 +120,37 @@
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white">
<section class="bg-white px-5 md:px-0">
<ToolsHeader>
<img
src="/free-tools/name-picker.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
alt=""
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Random Name Picker</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Use this online name picker to draw a random name from a list of names, or to draw several
names randomly out of a list. You can use it as a name randomizer for a class activities.
<img src="/free-tools/name-picker.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Random Name Picker</h1>
<p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
Use this online name picker to draw a random name from a list of names, or to draw several names randomly out of a
list. You can use it as a name randomizer for a class activities.
</p>
</ToolsHeader>
<div class="border rounded-md md:w-[60%] my-10 py-8 md:py-[3%] mx-auto shadow-md bg-white">
<div class="w-[85%] md:w-[70%] mx-auto bg-white">
<h1 class="font-bold text-sm">List of names</h1>
<div class="mx-auto my-10 rounded-md border bg-white py-8 shadow-md md:w-[60%] md:py-[3%]">
<div class="mx-auto w-[85%] bg-white md:w-[70%]">
<h1 class="text-sm font-bold">List of names</h1>
<!-- container -->
<div
class="relative focus-within:border-[#0233BD] border-2 mt-5 h-[12rem] pl-3 pt-2 overflow-hidden rounded-sm bg-[#F1F2F4]"
class="relative mt-5 h-[12rem] overflow-hidden rounded-sm border-2 bg-[#F1F2F4] pl-3 pt-2 focus-within:border-[#0233BD]"
>
<!-- sidebar -->
<div class="flex flex-col gap-y-3 absolute right-2 md:right-7 top-3">
<div class="absolute right-2 top-3 flex flex-col gap-y-3 md:right-7">
<button
type="button"
on:click={sortRandom}
class="p-1.5 rounded-full bg-[#D9E0F5] flex justify-center items-center"
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
>
<img src="/sort-icon.svg" class="w-2.5" alt="" />
</button>
<button
type="button"
on:click={unsortRandom}
class="p-1.5 rounded-full bg-[#D9E0F5] flex justify-center items-center"
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
>
<img src="/unsort-icon.svg" class="w-2.5" alt="" />
</button>
@@ -175,48 +163,46 @@
tabindex={0}
contenteditable
bind:textContent={namesInput}
class="w-full pr-9 md:pr-14 overflow-y-auto text-xs block border-0 outline-none h-full"
class="block h-full w-full overflow-y-auto border-0 pr-9 text-xs outline-none md:pr-14"
></span>
</div>
<!-- word count -->
<p
class="rounded-xl w-[80px] mt-1 py-1 bg-[#D9E0F5] text-center text-[#0F62FE] text-[10px] font-semibold"
>
<p class="mt-1 w-[80px] rounded-xl bg-[#D9E0F5] py-1 text-center text-[10px] font-semibold text-[#0F62FE]">
name count: {wordCount}
</p>
</div>
<p class="py-4 border-b text-xs">Each name must be separated by a comma</p>
<p class="border-b py-4 text-xs">Each name must be separated by a comma</p>
<div class="mt-10">
<h1 class="font-bold text-sm">Number of Names</h1>
<h1 class="text-sm font-bold">Number of Names</h1>
<!-- number input & button -->
<div class="flex flex-wrap gap-y-3 justify-between w-full mt-5">
<div class="mt-5 flex w-full flex-wrap justify-between gap-y-3">
<input
type="number"
bind:value={numNames}
class="px-3 py-2 text-sm w-full md:w-2/4 border-2 rounded-sm outline focus:border-[#0233BD] bg-[#F1F2F4] outline-none"
class="w-full rounded-sm border-2 bg-[#F1F2F4] px-3 py-2 text-sm outline-none outline focus:border-[#0233BD] md:w-2/4"
/>
<button
type="button"
on:click={selectName}
class="bg-[#0F62FE] rounded-md text-sm py-3 md:py-0 text-white w-[70%] md:w-[35%]"
class="w-[70%] rounded-md bg-[#0F62FE] py-3 text-sm text-white md:w-[35%] md:py-0"
>Select name(s) at random</button
>
</div>
</div>
<!-- footer logo -->
<div class="py-3 mt-10 mb-5 bg-[#F1F6FF] flex justify-center items-center gap-3">
<div class="mb-5 mt-10 flex items-center justify-center gap-3 bg-[#F1F6FF] py-3">
<img src="/free-tools/name-picker.svg" class="w-[12%] md:w-[9%]" alt="" />
{#if selectedNames.length > 0}
<h1 class="text-[#0233BD] text-xl font-bold">Selected Names</h1>
<h1 class="text-xl font-bold text-[#0233BD]">Selected Names</h1>
{/if}
</div>
<!-- loading state -->
{#if selectedNames.length > 0 && !avatarUrlsFetched}
<div class="w-full flex justify-center items-center">
<div class="flex w-full items-center justify-center">
<p class="font-bold">Loading...</p>
</div>
{/if}
@@ -226,10 +212,10 @@
<div>
<ul>
{#each selectedNames as name, index}
<li class="py-2 border-b">
<div class="flex item-center gap-3 md:gap-5 justify-center">
<li class="border-b py-2">
<div class="item-center flex justify-center gap-3 md:gap-5">
<img src={avatarUrls[index]} alt="" class="w-7" />
<p class="font-medium text-sm md:text-base flex justify-center items-center">
<p class="flex items-center justify-center text-sm font-medium md:text-base">
{name}
</p>
</div>
@@ -237,17 +223,16 @@
{/each}
</ul>
<div class="flex flex-wrap justify-between md:justify-center items-center md:gap-3 mt-3">
<div class="mt-3 flex flex-wrap items-center justify-between md:justify-center md:gap-3">
<button
type="button"
on:click={resetEntry}
class="py-2 border text-xs md:text-sm rounded-md w-[45%] md:w-[20%]"
>Reset Entries</button
class="w-[45%] rounded-md border py-2 text-xs md:w-[20%] md:text-sm">Reset Entries</button
>
<button
type="button"
on:click={selectName}
class="py-2 bg-[#0F62FE] rounded-md text-xs md:text-sm text-white w-[45%] md:w-[27%]"
class="w-[45%] rounded-md bg-[#0F62FE] py-2 text-xs text-white md:w-[27%] md:text-sm"
>Select other names</button
>
</div>
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import { ToolsHeader } from '$lib/components';
interface TodoItem {
id: number;
@@ -167,15 +167,15 @@
<meta name="twitter:image" content="" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-1 md:px-0 w-full md:w-full bg-white">
<section class=" w-full bg-white px-1 md:w-full md:px-0">
<ToolsHeader>
<img
src="/free-tools/pomodoro.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
class="mx-auto w-[15%] rounded-full border md:w-[5%]"
alt=""
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Pomodoro Timer</h1>
<p class="text-sm mt-10 text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Pomodoro Timer</h1>
<p class="mx-auto mt-10 text-sm font-light text-[#656565] md:w-[45%] md:font-normal">
Make your workday more engaging and effective with the Pomodoro timer, break work into
25-minute focused intervals called "pomodoros," followed by 5-minute breaks. take a longer
break.
@@ -183,20 +183,20 @@
</ToolsHeader>
<div
class="body w-full md:h-[60vh] flex flex-col gap-y-10 md:flex-row justify-evenly px-5 md:px-10 my-10 text-white text-center"
class="body my-10 flex w-full flex-col justify-evenly gap-y-10 px-5 text-center text-white md:h-[60vh] md:flex-row md:px-10"
>
<!-- left side -->
<div
class="w-full md:w-[50%] bg-[#0D4CFF] rounded-lg md:rounded-2xl py-7 px-5 md:p-10 flex flex-col gap-y-16 md:gap-y-0 justify-between"
class="flex w-full flex-col justify-between gap-y-16 rounded-lg bg-[#0D4CFF] px-5 py-7 md:w-[50%] md:gap-y-0 md:rounded-2xl md:p-10"
>
<!-- types -->
<div class="flex item-center justify-evenly">
<div class="item-center flex justify-evenly">
<button
type="button"
on:click={() => setTimerState('pomodoro')}
class:bg-[#3ADFECED]={timerState === 'pomodoro'}
class:bg-[#0233BD]={timerState !== 'pomodoro'}
class="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]"
class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Pomodoro</button
>
<button
@@ -204,7 +204,7 @@
on:click={() => setTimerState('long-break')}
class:bg-[#3ADFECED]={timerState === 'long-break'}
class:bg-[#0233BD]={timerState !== 'long-break'}
class="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]"
class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Short Break
</button>
<button
@@ -212,13 +212,13 @@
on:click={() => setTimerState('short-break')}
class:bg-[#3ADFECED]={timerState === 'short-break'}
class:bg-[#0233BD]={timerState !== 'short-break'}
class="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]"
class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Long Break
</button>
</div>
<!-- countdown -->
<h1 class="text-7xl md:text-9xl font-bold">{countdownDisplay}</h1>
<h1 class="text-7xl font-bold md:text-9xl">{countdownDisplay}</h1>
<!-- controls -->
<div class="flex items-center justify-center gap-7">
@@ -227,7 +227,7 @@
<img
src="/free-tools/pomodoro/restart-icon.svg"
alt="Restart Icon"
class="w-7 hover:scale-110 transition-all duration-300"
class="w-7 transition-all duration-300 hover:scale-110"
/>
</button>
@@ -235,7 +235,7 @@
<button
type="button"
on:click={isPaused ? startCountdown : pauseCountdown}
class="bg-white text-[#0542CC] border text-base font-bold uppercase py-3 px-14 rounded-md hover:bg-transparent hover:text-white hover:border-white hover:border transition-all duration-300"
class="rounded-md border bg-white px-14 py-3 text-base font-bold uppercase text-[#0542CC] transition-all duration-300 hover:border hover:border-white hover:bg-transparent hover:text-white"
>
{isPaused ? 'Start' : 'Pause'}
</button>
@@ -245,16 +245,16 @@
<img
src="/free-tools/pomodoro/timer-play-icon.svg"
alt="Play Icon"
class="w-7 hover:scale-110 transition-all duration-300"
class="w-7 transition-all duration-300 hover:scale-110"
/>
</button>
</div>
</div>
<!-- right side -->
<div class="w-full md:w-[36%] relative">
<div class="relative w-full md:w-[36%]">
<h1
class="bg-[#040F2D] uppercase text-xl font-bold py-4"
class="bg-[#040F2D] py-4 text-xl font-bold uppercase"
style="clip-path: polygon(0 0, 100% 1%, 100% 65%, 0% 100%);"
>
To do list
@@ -262,28 +262,28 @@
<div class="overflow-hidden">
<!-- todos -->
<div class="overflow-y-auto mt-3 max-h-[40vh] md:max-h-[50vh]">
<div class="mt-3 max-h-[40vh] overflow-y-auto md:max-h-[50vh]">
{#each todoList as todo, i}
<div class="border text-black p-5">
<div class="border p-5 text-black">
{#if todo.isEditing}
<form on:submit|preventDefault={() => setEditing(i, false)}>
<p class="text-xs text-left text-[#656565] font-semibold">Pomodoro name</p>
<p class="text-left text-xs font-semibold text-[#656565]">Pomodoro name</p>
<input
type="text"
bind:this={addInput}
bind:value={todo.content}
class="bg-[#F1F2F4] border-[#EAEAEA] w-full border mt-2 px-3 py-2 text-base outline-none"
class="mt-2 w-full border border-[#EAEAEA] bg-[#F1F2F4] px-3 py-2 text-base outline-none"
/>
<div class="flex justify-end items-center gap-5 mt-5">
<div class="mt-5 flex items-center justify-end gap-5">
<button
type="button"
on:click={() => deleteTodo(i)}
class="rounded-sm py-2 px-5 text-xs font-semibold bg-[#F7F7F7]"
class="rounded-sm bg-[#F7F7F7] px-5 py-2 text-xs font-semibold"
>Discard</button
>
<button
type="submit"
class="rounded-sm py-2 px-5 text-xs font-semibold bg-[#0D4CFF] text-white"
class="rounded-sm bg-[#0D4CFF] px-5 py-2 text-xs font-semibold text-white"
>Save</button
>
</div>
@@ -294,7 +294,7 @@
<p class="text-sm font-medium">{todo.content}</p>
<!-- pen and menu icon -->
<div class="relative flex justify-between w-[15%]">
<div class="relative flex w-[15%] justify-between">
<button type="button" on:click={() => setEditing(i, true)}>
<img src="/free-tools/pomodoro/pen-icon.svg" alt="Pen icon" class="w-5" />
</button>
@@ -305,19 +305,19 @@
{#if todo.isVisible}
<ul
class="z-40 absolute top-6 -right-3 text-xs w-[8rem] bg-white border shadow-md rounded-[5px]"
class="absolute -right-3 top-6 z-40 w-[8rem] rounded-[5px] border bg-white text-xs shadow-md"
>
<button
type="button"
on:click={() => markTodoAsDone(i)}
class="w-full font-medium text-left py-3 px-5"
class="w-full px-5 py-3 text-left font-medium"
>
Mark as done
</button>
<button
type="button"
on:click={() => deleteTodo(i)}
class="w-full font-medium text-left py-3 px-5 bg-red-600 text-white rounded-b-[5px]"
class="w-full rounded-b-[5px] bg-red-600 px-5 py-3 text-left font-medium text-white"
>
Delete
</button>
@@ -326,9 +326,9 @@
</div>
</div>
<div class="flex justify-between items-center mt-10">
<div class="mt-10 flex items-center justify-between">
<p
class="rounded-[3px] text-xs font-medium text-[#656565] bg-[#EAEAEA] py-0.5 px-1.5"
class="rounded-[3px] bg-[#EAEAEA] px-1.5 py-0.5 text-xs font-medium text-[#656565]"
>
{todo.time}
</p>
@@ -345,12 +345,12 @@
todo.isPaused = !todo.isPaused;
}}
class="{todo.isDone
? 'bg-[#00D06C] border-none text-white'
? 'border-none bg-[#00D06C] text-white'
: todo.isPaused
? 'bg-white text-[#0D4CFF] border-[#0D4CFF]'
: 'bg-[#0D4CFF] text-white border-none'}
? 'border-[#0D4CFF] bg-white text-[#0D4CFF]'
: 'border-none bg-[#0D4CFF] text-white'}
flex items-center border-2 justify-center gap-2 py-1.5 px-4 rounded-[4px] text-xs font-bold uppercase"
flex items-center justify-center gap-2 rounded-[4px] border-2 px-4 py-1.5 text-xs font-bold uppercase"
>
{#if todo.isDone}
<img src="/free-tools/pomodoro/done-icon.svg" alt="Done icon" />
@@ -374,7 +374,7 @@
<button
type="button"
on:click={addTodo}
class="bg-[#1D4ED8] rounded-md text-center font-medium mt-10 py-3 w-full flex justify-center items-center gap-3"
class="mt-10 flex w-full items-center justify-center gap-3 rounded-md bg-[#1D4ED8] py-3 text-center font-medium"
>
Add new item
<img src="/free-tools/pomodoro/add-icon.svg" alt="Add icon" class="w-3" />
@@ -385,9 +385,9 @@
</section>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap');
.body {
font-family: 'Inter', sans-serif;
font-family: 'Geist', sans-serif;
}
</style>
@@ -1,16 +1,19 @@
<script lang="ts">
import { LogoFacebook, LogoLinkedin } from 'carbon-icons-svelte';
import { onDestroy } from 'svelte';
import { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing';
import Mood from './components/Mood.svelte';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import Avatar from './components/Avatar.svelte';
import Background from './components/Background.svelte';
import Report from './components/Report.svelte';
import FullView from './components/FullView.svelte';
import DownloadButton from './components/DownloadButton.svelte';
import { htmlBody, type HtmlBody, openModal, isFormComplete } from './components/store';
import Facebook from '@lucide/svelte/icons/facebook';
import Linkedin from '@lucide/svelte/icons/linkedin';
import { htmlBody, type HtmlBody, openModal, isFormComplete } from '$lib/features/tools/progress/store';
import Mood from '$lib/features/tools/progress/mood.svelte';
import Avatar from '$lib/features/tools/progress/avatar.svelte';
import Report from '$lib/features/tools/progress/report.svelte';
import FullView from '$lib/features/tools/progress/full-view.svelte';
import Background from '$lib/features/tools/progress/background.svelte';
import { ToolsHeader } from '$lib/components';
import DownloadButton from '$lib/features/tools/progress/download-button.svelte';
const MAX_CHARS = 160;
let remainingChars = MAX_CHARS;
@@ -65,22 +68,14 @@
<svelte:head>
<title>Random Name Picker | ClassroomIO</title>
<meta
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta property="og:title" content="Random Name Picker | ClassroomIO" />
<meta
property="og:description"
content="Use this online name picker to draw a random name from a list of names for your online or physical classroom."
/>
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta name="twitter:title" content="Random Name Picker | ClassroomIO" />
<meta
@@ -90,17 +85,12 @@
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-1 md:px-0 w-full md:w-full">
<section class=" w-full px-1 md:w-full md:px-0">
<ToolsHeader>
<img
src="/free-tools/progress-report.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
alt=""
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Progress Report</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Generate cool reports of your learning progress. Share reports with your network for
collaborative learning
<img src="/free-tools/progress-report.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Progress Report</h1>
<p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
Generate cool reports of your learning progress. Share reports with your network for collaborative learning
</p>
</ToolsHeader>
@@ -111,29 +101,25 @@
<FullView />
<div
class="border rounded-md w-full md:w-11/12 lg:w-[80%] my-10 mx-auto shadow-md flex md:flex-row flex-col justify-evenly"
class="mx-auto my-10 flex w-full flex-col justify-evenly rounded-md border shadow-md md:w-11/12 md:flex-row lg:w-[80%]"
>
<!-- left side -->
{#if showSetter}
<div
transition:fly={{ y: 100, easing: sineInOut }}
class="w-full mx-auto md:w-[48%] md:max-w-[500px] p-5 md:border-r"
class="mx-auto w-full p-5 md:w-[48%] md:max-w-[500px] md:border-r"
>
<!-- preview button -->
<div class="flex md:hidden justify-end mb-5">
<div class="mb-5 flex justify-end md:hidden">
<button
type="button"
on:click={() => {
showSetter = false;
showReport = true;
}}
class="flex justify-between items-center gap-2 px-3 py-1 rounded-full text-white text-xs bg-[#0233BD]"
class="flex items-center justify-between gap-2 rounded-full bg-[#0233BD] px-3 py-1 text-xs text-white"
>
<img
src="/free-tools/progress-report/preview-icon.svg"
alt="preview icon"
class="w-4"
/>
<img src="/free-tools/progress-report/preview-icon.svg" alt="preview icon" class="w-4" />
Preview
</button>
</div>
@@ -146,7 +132,7 @@
bind:value={$htmlBody.name}
on:input={(event) => handleInputChange(event, 'name')}
placeholder="Enter your name here"
class="w-full border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-xs placeholder:text-sm placeholder:text-[#ADADAD]"
class="my-3 w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 text-xs outline-none placeholder:text-sm placeholder:text-[#ADADAD]"
/>
</div>
@@ -156,18 +142,17 @@
<button
type="button"
on:click={() => ($openModal.mood = true)}
class="w-full flex justify-between items-center border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-gray-400 text-sm"
class="my-3 flex w-full items-center justify-between rounded-sm border bg-[#F1F2F4] px-3 py-2 text-sm text-gray-400 outline-none"
>
{#if $htmlBody.mood.text}
<div
class="px-3 py-1 border border-[#EDEDED] text-white bg-blue-500 rounded-2xl font-semibold text-[7px] md:text-xs flex items-center justify-between"
class="flex items-center justify-between rounded-2xl border border-[#EDEDED] bg-blue-500 px-3 py-1 text-[7px] font-semibold text-white md:text-xs"
>
{#if $htmlBody.mood.text}
<span class="flex gap-1 items-center">
<p class="font-semibold text-[9px] md:text-xs">is {$htmlBody.mood.text}</p>
<span class="flex items-center gap-1">
<p class="text-[9px] font-semibold md:text-xs">is {$htmlBody.mood.text}</p>
<img
src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood
.iconSrc}.png"
src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood.iconSrc}.png"
alt={$htmlBody.mood.text}
class="w-3"
/>
@@ -176,11 +161,7 @@
</div>
{:else}
What feeling describes you?
<img
src="/free-tools/progress-report/smiley-face-icon.svg"
alt="smiley face"
class="w-3"
/>
<img src="/free-tools/progress-report/smiley-face-icon.svg" alt="smiley face" class="w-3" />
{/if}
</button>
</div>
@@ -193,15 +174,15 @@
maxlength={MAX_CHARS}
on:input={(event) => handleInputChange(event, 'learning')}
placeholder="Tell us what you are learning"
class="w-full h-[17vh] border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-sm placeholder:text-sm placeholder:text-[#ADADAD]"
class="my-3 h-[17vh] w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 text-sm outline-none placeholder:text-sm placeholder:text-[#ADADAD]"
></textarea>
<p class="text-xs text-right text-[#656565]">{remainingChars} characters remaining</p>
<p class="text-right text-xs text-[#656565]">{remainingChars} characters remaining</p>
</div>
<!-- range input -->
<div class="mt-3">
<p class="text-sm text-[#656565] pb-4">Estimate your progress</p>
<div class="flex justify-between items-center">
<p class="pb-4 text-sm text-[#656565]">Estimate your progress</p>
<div class="flex items-center justify-between">
<input
type="range"
min="0"
@@ -216,18 +197,16 @@
</div>
<!-- avatar button -->
<div class="flex flex-wrap justify-between items-center mt-3">
<div class="mt-3 flex flex-wrap items-center justify-between">
<div class="w-full md:w-2/4">
<p class="text-[12px] md:text-sm text-[#656565]">Choose your avatar</p>
<p class="text-[12px] text-[#656565] md:text-sm">Choose your avatar</p>
<button
type="button"
on:click={() => ($openModal.avatar = true)}
class="bg-[#F7F7F7] w-full py-1.5 px-5 md:px-0 mt-3 flex md:w-[65%] items-center justify-between md:justify-center gap-2"
class="mt-3 flex w-full items-center justify-between gap-2 bg-[#F7F7F7] px-5 py-1.5 md:w-[65%] md:justify-center md:px-0"
>
<img
src={`https://assets.cdn.clsrio.com/progress-report/avatars/${
$htmlBody.avatar || 'avatar_m'
}.svg`}
src={`https://assets.cdn.clsrio.com/progress-report/avatars/${$htmlBody.avatar || 'avatar_m'}.svg`}
alt=""
class="w-[15%] md:w-[30%]"
/>
@@ -241,11 +220,11 @@
<!-- & background -->
<div class="w-full md:w-2/4">
<p class="text-[12px] md:text-sm text-[#656565]">Choose your background</p>
<p class="text-[12px] text-[#656565] md:text-sm">Choose your background</p>
<button
type="button"
on:click={() => ($openModal.background = true)}
class="bg-[#F7F7F7] w-full py-1.5 px-5 md:px-0 mt-3 flex md:w-[65%] items-center justify-between md:justify-center gap-2"
class="mt-3 flex w-full items-center justify-between gap-2 bg-[#F7F7F7] px-5 py-1.5 md:w-[65%] md:justify-center md:px-0"
>
<img
src={`https://assets.cdn.clsrio.com/progress-report/backgrounds/${
@@ -270,41 +249,35 @@
<div
transition:fly={{ y: 100, easing: sineInOut }}
class="w-full mx-auto md:w-[48%] md:min-w-[400px] md:max-w-[500px] px-5 md:px-1 md:mt-0 mt-5 md:p-5"
class="mx-auto mt-5 w-full px-5 md:mt-0 md:w-[48%] md:min-w-[400px] md:max-w-[500px] md:p-5 md:px-1"
>
<div class="hidden md:block">
<Report />
<!-- download & share button -->
<div class="mt-9 pt-8 px-2 h-auto border-t">
<div class="mt-9 h-auto border-t px-2 pt-8">
<DownloadButton {isDisabled} {isDownloading} text="Download Image" />
<!-- share button -->
<div class="w-full md:w-[70%] mx-auto my-5">
<h1 class="text-sm font-semibold text-center">Share on image on social media:</h1>
<div class="mx-auto my-5 w-full md:w-[70%]">
<h1 class="text-center text-sm font-semibold">Share on image on social media:</h1>
<div
class="flex justify-evenly border rounded-xl gap-5 w-[60%] md:w-[80%] px-5 py-2 mt-2 mx-auto"
>
<div class="mx-auto mt-2 flex w-[60%] justify-evenly gap-5 rounded-xl border px-5 py-2 md:w-[80%]">
<button
type="button"
on:click={shareOnFacebook}
class="w-10 hover:scale-[1.2] transition-all duration-300"
class="w-10 transition-all duration-300 hover:scale-[1.2]"
>
<LogoFacebook size={32} />
<Facebook size={32} />
</button>
<button
type="button"
on:click={shareOnLinkedIn}
class="w-10 hover:scale-[1.2] transition-all duration-300"
class="w-10 transition-all duration-300 hover:scale-[1.2]"
>
<LogoLinkedin size={32} />
<Linkedin size={32} />
</button>
<button
type="button"
on:click={shareOnTwitter}
class="w-5 hover:scale-[1.2] transition-all duration-300"
>
<button type="button" on:click={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
<img src="/twitter_logo.png" alt="X.com" />
</button>
</div>
@@ -314,14 +287,14 @@
{#if showReport}
<!-- hide button -->
<div class="flex justify-end mb-5">
<div class="mb-5 flex justify-end">
<button
type="button"
on:click={() => {
showReport = false;
showSetter = true;
}}
class="flex justify-between items-center gap-2 px-3 py-1 rounded-full text-white text-xs bg-[#0233BD]"
class="flex items-center justify-between gap-2 rounded-full bg-[#0233BD] px-3 py-1 text-xs text-white"
>
<img src="/free-tools/progress-report/hide-icon.svg" alt="preview icon" class="w-4" />
Close
@@ -330,35 +303,29 @@
<Report />
<!-- download & share button -->
<div class="mt-9 pt-8 px-2 h-auto border-t">
<div class="mt-9 h-auto border-t px-2 pt-8">
<DownloadButton {isDisabled} {isDownloading} />
<!-- share button -->
<div class="w-full md:w-[70%] mx-auto my-5">
<h1 class="text-sm font-semibold text-center">Share on image on social media:</h1>
<div class="mx-auto my-5 w-full md:w-[70%]">
<h1 class="text-center text-sm font-semibold">Share on image on social media:</h1>
<div
class="flex justify-evenly border rounded-xl gap-5 w-[60%] md:w-[80%] px-5 py-2 mt-2 mx-auto"
>
<div class="mx-auto mt-2 flex w-[60%] justify-evenly gap-5 rounded-xl border px-5 py-2 md:w-[80%]">
<button
type="button"
on:click={shareOnFacebook}
class="w-10 hover:scale-[1.2] transition-all duration-300"
class="w-10 transition-all duration-300 hover:scale-[1.2]"
>
<LogoFacebook size={32} />
<Facebook size={32} />
</button>
<button
type="button"
on:click={shareOnLinkedIn}
class="w-10 hover:scale-[1.2] transition-all duration-300"
class="w-10 transition-all duration-300 hover:scale-[1.2]"
>
<LogoLinkedin size={32} />
<Linkedin size={32} />
</button>
<button
type="button"
on:click={shareOnTwitter}
class="w-5 hover:scale-[1.2] transition-all duration-300"
>
<button type="button" on:click={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
<img src="/twitter_logo.png" alt="X.com" />
</button>
</div>
@@ -1,88 +0,0 @@
<script lang="ts">
import { openModal, htmlBody } from './store';
import Modal from '$lib/Modal/Modal.svelte';
interface Avatar {
src: string;
}
let isAddIcon = false;
let hoveredIndex: number | null = null;
let avatars: Avatar[] = [
{ src: 'avatar_a' },
{ src: 'avatar_b' },
{ src: 'avatar_c' },
{ src: 'avatar_d' },
{ src: 'avatar_e' },
{ src: 'avatar_f' },
{ src: 'avatar_g' },
{ src: 'avatar_h' },
{ src: 'avatar_i' },
{ src: 'avatar_j' },
{ src: 'avatar_k' },
{ src: 'avatar_l' },
{ src: 'avatar_m' },
{ src: 'avatar_n' },
{ src: 'avatar_o' },
{ src: 'avatar_p' },
{ src: 'avatar_q' },
{ src: 'avatar_r' },
{ src: 'avatar_s' },
{ src: 'avatar_t' }
];
function selectAvatar(avatar: Avatar) {
$htmlBody.avatar = avatar.src;
$openModal.avatar = false;
}
</script>
<Modal bind:open={$openModal.avatar}>
<!-- heading -->
<div class="flex justify-between">
<h1 class="text-sm font-semibold">Choose your Avatars</h1>
<button
type="button"
on:click={() => ($openModal.avatar = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<!-- map for each avatar -->
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
{#each avatars as avatar, index}
<button
on:click={() => {
selectAvatar(avatar);
}}
on:mouseenter={() => {
hoveredIndex = index;
isAddIcon = true;
}}
on:mouseleave={() => {
hoveredIndex = null;
isAddIcon = false;
}}
class="relative w-[17%] flex justify-center items-center shadow-sm rounded-full hover:scale-110 transition-all duration-300"
>
<!-- + icon overlay -->
{#if isAddIcon && hoveredIndex === index}
<div
class="w-full h-full flex justify-center items-center bg-white border absolute rounded-full opacity-[0.7] blur-md"
></div>
<img src="/free-tools/progress-report/hover-plus-icon.svg" alt="" class="absolute" />
{/if}
<!-- avatar -->
<img
src="https://assets.cdn.clsrio.com/progress-report/avatar/{avatar.src}.svg"
alt=""
class="w-full"
/>
</button>
{/each}
</div>
<!-- -->
</Modal>
@@ -1,87 +0,0 @@
<script lang="ts">
import Modal from '$lib/Modal/Modal.svelte';
import { htmlBody, openModal } from './store';
interface Background {
src: string;
}
let backgrounds: Background[] = [
{
src: 'blue_tetiary_background'
},
{
src: 'blue_secondary_background'
},
{
src: 'blue_primary_background'
},
{
src: 'purple_tetiary_background'
},
{
src: 'purple_secondary_background'
},
{
src: 'purple_primary_background'
},
{
src: 'black_tetiary_background'
},
{
src: 'black_secondary_background'
},
{
src: 'black_primary_background'
},
{
src: 'yellow_tetiary_background'
},
{
src: 'yellow_secondary_background'
},
{
src: 'yellow_primary_background'
}
];
function selectBackground(background: Background) {
$htmlBody.background = background.src;
$openModal.background = false;
}
</script>
<Modal className="h-full max-h-[60vh]" bind:open={$openModal.background}>
<!-- -->
<div class="flex justify-between">
<h1 class="text-sm font-semibold">Choose your Background</h1>
<button
type="button"
on:click={() => ($openModal.background = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<!-- -->
<div
class="flex flex-wrap justify-between gap-y-3 mt-3 overflow-y-auto overflow-x-hidden h-[85%]"
>
{#each backgrounds as background}
<button
on:click={() => {
selectBackground(background);
}}
class="flex w-[30%] items-center shadow-sm hover:scale-110 transition-all duration-300"
>
<img
src="https://assets.cdn.clsrio.com/progress-report/backgrounds/{background.src}.webp"
alt=""
class=""
/>
</button>
{/each}
</div>
<!-- -->
</Modal>
@@ -1,52 +0,0 @@
<script lang="ts">
import { toPng } from 'html-to-image';
import { nodeStore, htmlBody, openModal } from './store';
export let isDisabled: boolean;
export let isDownloading: boolean;
export let text: string = 'Generate Progress Report';
function convertToPng() {
isDownloading = true;
$openModal.showFullscreenButton = false;
const fileName = `${$htmlBody.name} - ${$htmlBody.mood.text} (ClassroomIO_com)`;
if ($nodeStore) {
setTimeout(() => {
toPng($nodeStore, {
width: 500,
height: 500,
quality: 1
})
.then((dataUrl) => {
const link = document.createElement('a');
link.download = fileName;
link.href = dataUrl;
link.click();
})
.catch((error) => {
console.error('Oops, something went wrong!', error);
})
.finally(() => {
isDownloading = false;
$openModal.showFullscreenButton = false;
});
}, 300);
} else {
console.error('Node is not defined');
}
}
</script>
<button
type="button"
disabled={isDownloading || !isDisabled ? true : false}
on:click={convertToPng}
class="{isDownloading || !isDisabled
? 'bg-gray-200 cursor-not-allowed text-gray-600'
: 'bg-[#0233BD] text-white'} {text === 'Generate Progress Report'
? 'block md:hidden'
: ''} mt-5 transition-all delay-100 text-white text-xs font-semibold w-full py-3 rounded-md"
>{isDownloading ? 'Loading...' : text}</button
>
@@ -1,11 +0,0 @@
<script lang="ts">
import Modal from '$lib/Modal/Modal.svelte';
import Report from './Report.svelte';
import { openModal } from './store';
</script>
<Modal bind:open={$openModal.fullscreen}>
<!-- -->
<Report />
<!-- -->
</Modal>
@@ -1,100 +0,0 @@
<script lang="ts">
import Modal from '$lib/Modal/Modal.svelte';
import { htmlBody, openModal } from './store';
interface Mood {
text: string;
iconSrc: string;
}
let moods: Mood[] = [
{
text: 'Unmotivated',
iconSrc: 'unmotivated'
},
{
text: 'Feeling Enlightened',
iconSrc: 'feeling-enlightened'
},
{
text: 'Enthusiastic',
iconSrc: 'enthusiastic'
},
{
text: 'Confused',
iconSrc: 'confused'
},
{
text: 'Feeling stuck',
iconSrc: 'feeling-stuck'
},
// {
// text: 'Believing in the process',
// iconSrc: 'believing-in-the-process'
// },
{
text: 'Curious',
iconSrc: 'curious'
},
{
text: 'Achieving Mastery',
iconSrc: 'achieving-mastery'
},
{
text: 'Feeling Positive',
iconSrc: 'feeling-positive'
},
{
text: 'Reflecting',
iconSrc: 'reflecting'
},
{
text: 'Making progress',
iconSrc: 'progress'
},
{
text: 'Feeling Sad',
iconSrc: 'sad'
}
];
function selectMood(mood: Mood) {
$htmlBody.mood.text = mood.text;
$htmlBody.mood.iconSrc = mood.iconSrc;
$openModal.mood = false;
}
</script>
<Modal bind:open={$openModal.mood}>
<!-- header -->
<div class="flex justify-between">
<h1 class="text-sm font-semibold">Choose your Mood</h1>
<button
type="button"
on:click={() => ($openModal.mood = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<!-- mapping -->
<div class="flex flex-wrap justify-between gap-y-3 mt-3">
{#each moods as mood}
<button
on:click={() => {
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"
>
<p class="text-xs md:text-sm font-semibold">{mood.text}</p>
<img
src="https://assets.cdn.clsrio.com/progress-report/emojis/{mood.iconSrc}.png"
alt={mood.text}
class="w-4"
/>
</button>
{/each}
</div>
<!-- -->
</Modal>
@@ -1,142 +0,0 @@
<script lang="ts">
import { htmlBody, nodeStore, openModal } from './store';
import { derived } from 'svelte/store';
const colorMap = {
blue_tetiary_background: '#0542CC',
blue_secondary_background: '#0542CC',
blue_primary_background: '#0542CC',
purple_tetiary_background: '#9D4EDD',
purple_secondary_background: '#9D4EDD',
purple_primary_background: '#9D4EDD',
black_tetiary_background: '#252525',
black_secondary_background: '#252525',
black_primary_background: '#252525',
yellow_tetiary_background: '#FFBC42',
yellow_secondary_background: '#FFBC42',
yellow_primary_background: '#FFBC42'
};
type ColorMapKey = keyof typeof colorMap;
const backgroundColor = derived(htmlBody, ($htmlBody) => {
return colorMap[$htmlBody.background as ColorMapKey] || '#1e5ed2';
});
let node: any;
$: nodeStore.set(node);
$: rootBgImage = `https://assets.cdn.clsrio.com/progress-report/backgrounds/${
$htmlBody.background || 'blue_tetiary_background'
}.webp`;
</script>
<div
class="h-full min-h-[500px] max-h-[500px] md:h-[60vh] w-full border relative flex items-center justify-center"
style={`font-family: "Space Grotesk", sans-serif; background-image: url('${rootBgImage}'); background-size: cover; background-repeat: no-repeat`}
bind:this={node}
>
<!-- fullscreen button -->
{#if $openModal.showFullscreenButton}
{#if $openModal.fullscreen}
<button
type="button"
on:click={() => ($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="" />
</button>
{:else}
<button
type="button"
on:click={() => ($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="" />
</button>
{/if}
{/if}
<div class="mt-20">
<!-- using both md & lg for the width here because the lib doesn't consider md to be medium screens and above, using md alone will make the output break on desktop so i had to also specify for large screens -->
<div
class="rounded-lg shadow-md bg-white border-4 border-gray-100 z-20 mx-auto flex justify-center items-center w-[16rem] h-[16rem]"
>
<div class="relative w-full h-full flex flex-col justify-center gap-3 items-center px-2">
<!-- avatar -->
<img
src={`https://assets.cdn.clsrio.com/progress-report/avatar/${
$htmlBody.avatar || 'avatar_a'
}.svg`}
alt=""
class="absolute top-0 w-[25%] h-[25%] ml-2 -mt-[2.2rem]"
/>
<!-- mood -->
{#if $htmlBody.name || $htmlBody.mood.text}
<div
style="background-color: {$backgroundColor}; color: white; display: flex;"
class="px-3 py-0.5 border border-[#EDEDED] rounded-2xl mt-6 md:mt-3 font-semibold text-[10px] md:text-[0.7rem] flex items-center justify-between"
>
<span class="mr-0.5">{$htmlBody.name}</span>
{#if $htmlBody.mood.text}
<span class="flex gap-1 items-center">
<p class="font-semibold text-[10px] md:text-[0.7rem]">
is {$htmlBody.mood.text}
</p>
<img
src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood
.iconSrc}.png"
alt={$htmlBody.mood.text}
class="w-3"
/>
</span>
{/if}
</div>
{:else}
<p
style="background-color: {$backgroundColor}"
class="px-3 py-0.5 border border-[#EDEDED] text-white rounded-2xl mt-3 font-medium text-[9px]"
>
Add your name
</p>
{/if}
<!-- learning -->
<span
class="w-full md:my-2 p-1 overflow-hidden text-[0.7rem] md:text-[0.8rem] font-normal text-center outline-none h-[10vh] md:h-[12vh]"
>
{$htmlBody.learning}
</span>
<!-- progress -->
<div class="flex flex-col justify-center items-center mb-2 md:mb-0">
<div class="border border-[#d9e0f5] w-full h-4 rounded-full p-0.5 bg-[#f1f6ff]">
<div
class="progress-range rounded-full w-full h-full"
style="background: {$backgroundColor}; width: {$htmlBody.progress}%"
/>
</div>
<p class="text-[12px] font-semibold mt-1">Progress Achieved: {$htmlBody.progress}%</p>
</div>
</div>
</div>
<div
style="display: flex;"
class="border mt-20 bottom-5 w-[50%] mx-auto flex gap-1 items-center justify-center bg-white rounded-md px-2 py-1"
>
<img src="/logo-192.png" alt="classroomio logo" class="w-4" />
<p class="text-[10px] font-bold">ClassroomIO.com</p>
</div>
<!-- -->
</div>
</div>
<!-- importing font family -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:[email protected]&display=swap');
</style>
@@ -1,57 +0,0 @@
import { derived, writable } from 'svelte/store';
import type { Writable } from 'svelte/store';
interface OpenModal {
avatar: boolean;
background: boolean;
fullscreen: boolean;
showFullscreenButton: boolean;
mood: boolean;
}
export interface HtmlBody {
name: string;
learning: string;
progress: number;
avatar: string;
background: string;
mood: {
text: string;
iconSrc: string;
};
}
// store for the node binded to the report component for the component to image conversion
export const nodeStore = writable(null);
export const openModal: Writable<OpenModal> = writable({
avatar: false,
background: false,
fullscreen: false,
showFullscreenButton: false,
mood: false
});
// report html
export const htmlBody: Writable<HtmlBody> = writable({
name: '',
learning: '',
progress: 10,
avatar: '',
background: '',
mood: {
text: '',
iconSrc: ''
}
});
// a mini validation for the htmlBody store that ensures all fields are filled
export const isFormComplete = derived(htmlBody, ($htmlBody) => {
return !!(
$htmlBody.name.trim() &&
$htmlBody.learning.trim() &&
$htmlBody.progress > 0 &&
$htmlBody.mood.text.trim() &&
$htmlBody.mood.iconSrc.trim()
);
});
@@ -2,8 +2,8 @@
import { onMount } from 'svelte';
import { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing';
import Timer from './components/Timer.svelte';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import Timer from '$lib/features/tools/stopwatch/timer.svelte';
import { ToolsHeader } from '$lib/components';
let timer: NodeJS.Timeout;
let buzzSound: HTMLAudioElement;
@@ -66,9 +66,7 @@
const hrs = Math.floor(totalSeconds / 3600);
const mins = Math.floor((totalSeconds % 3600) / 60);
const secs = totalSeconds % 60;
displayTime = `${String(hrs).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(
secs
).padStart(2, '0')}`;
displayTime = `${String(hrs).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
if (formattedTime === '00:00') {
formattedTime = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
@@ -117,22 +115,14 @@
<svelte:head>
<title>Activity Stopwatch | ClassroomIO</title>
<meta
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta property="og:title" content="Activity Stopwatch | ClassroomIO" />
<meta
property="og:description"
content="Stay on track with a customizable stopwatch for timed tasks, quizzes, and study sessions"
/>
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta name="twitter:title" content="Activity Stopwatch | ClassroomIO" />
<meta
@@ -142,76 +132,72 @@
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white">
<section class=" bg-white px-5 md:px-0">
<ToolsHeader>
<img
src="/free-tools/name-picker.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
alt=""
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Activity Stopwatch</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Stay on track and enhance productivity with our customizable stopwatch for timed tasks,
quizzes, and study sessions.
<img src="/free-tools/name-picker.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Activity Stopwatch</h1>
<p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
Stay on track and enhance productivity with our customizable stopwatch for timed tasks, quizzes, and study
sessions.
</p>
</ToolsHeader>
<div class="w-full md:w-2/4 my-10 mx-auto border rounded-md overflow-hidden bg-white">
<div class="mx-auto my-10 w-full overflow-hidden rounded-md border bg-white md:w-2/4">
<!-- countdown setter -->
{#if !isNextStep}
<div transition:fly={{ y: -300, delay: 0, easing: sineInOut }} class="px-6 py-8 bg-white">
<div transition:fly={{ y: -300, delay: 0, easing: sineInOut }} class="bg-white px-6 py-8">
<form on:submit|preventDefault={startTimer}>
<div>
<h1 class="font-bold text-sm">Name of activity</h1>
<h1 class="text-sm font-bold">Name of activity</h1>
<input
type="text"
bind:value={activityName}
class="w-full rounded-sm border py-2 mt-3 bg-[#F1F2F4] outline-none px-3 focus-within:border-[#0233BD] focus-within:border"
class="mt-3 w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 outline-none focus-within:border focus-within:border-[#0233BD]"
required
/>
</div>
<div class="mt-10 bg-white">
<h1 class="font-bold text-sm">Set an Activity</h1>
<div class="flex flex-wrap justify-between gap-y-5 w-full mt-3 uppercase font-bold">
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2">
<h1 class="text-sm font-bold">Set an Activity</h1>
<div class="mt-3 flex w-full flex-wrap justify-between gap-y-5 font-bold uppercase">
<div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input
type="number"
bind:value={hours}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black"
class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/>
<p class="text-[10px]">hrs</p>
</div>
</div>
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2">
<div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input
type="number"
bind:value={minutes}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black"
class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/>
<p class="text-[10px]">mins</p>
</div>
</div>
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2">
<div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input
type="number"
bind:value={seconds}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black"
class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/>
<p class="text-[10px]">sec</p>
</div>
</div>
</div>
<div class="flex justify-end mt-10">
<div class="mt-10 flex justify-end">
<button
type="submit"
class="bg-[#0F62FE] text-white text-sm px-5 py-3 rounded-md flex gap-3 items-center"
class="flex items-center gap-3 rounded-md bg-[#0F62FE] px-5 py-3 text-sm text-white"
>
<img src="/free-tools/stopwatch/loading-timer.svg" alt="" />
Start Timer</button
@@ -1,70 +0,0 @@
<script lang="ts">
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;
</script>
<div transition:fly={{ y: 100, delay: 0, easing: sineInOut }}>
<div class="px-6 pt-8 text-center">
<button
type="button"
on:click={resetTimer}
class="text-[10px] text-[#656565] font-semibold uppercase underline"
>set timer for another activity</button
>
<button
class="border mx-auto mt-7 mb-3 px-2 py-0.5 rounded-full flex items-center gap-1 text-sm text-[#656565] font-semibold border-[#C2D2FF] bg-[#DCE5FF]"
>
{activityName}:
<span class="text-[#0233BD]">{formattedTime}</span>
</button>
<!-- countdown -->
<h1 class="text-[#040F2D] mb-14 text-6xl font-bold">{displayTime}</h1>
</div>
<div class="flex">
<button
type="button"
on:click={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}
<img src="/free-tools/stopwatch/speaker-icon.svg" alt="" class="w-7 md:w-12" />
{:else}
<img src="/free-tools/stopwatch/no-speaker-icon.svg" alt="" class="w-7 md:w-12" />
{/if}
buzzer</button
>
<button
type="button"
on:click={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}
<img src="/free-tools/stopwatch/play-icon.svg" alt="" class="w-7 md:w-12" />
{:else}
<img src="/free-tools/stopwatch/pause-icon.svg" alt="" class="w-7 md:w-12" />
{/if}
{isPaused ? 'play' : 'pause'}</button
>
<button
type="button"
on:click={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" />
restart</button
>
</div>
</div>
@@ -1,10 +1,10 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing';
import Board from './components/Board.svelte';
import { TicTacToe } from './components/board';
import { scores, changeToComputer } from './components/store';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import Board from '$lib/features/tools/tic-tac-toe/board.svelte';
import { TicTacToe } from '$lib/features/tools/tic-tac-toe/board';
import { scores, changeToComputer } from '$lib/features/tools/tic-tac-toe/store';
import { ToolsHeader } from '$lib/components';
let startGame = false;
let message: string | null = null;
@@ -78,22 +78,14 @@
<svelte:head>
<title>Tic Tac Toe | ClassroomIO</title>
<meta
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta property="og:title" content="Tic Tac Toe | ClassroomIO" />
<meta
property="og:description"
content="A simple and free tic tac toe game to play to your friends in class or outside the classroom."
/>
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta name="twitter:title" content="Tic Tac Toe | ClassroomIO" />
<meta
@@ -103,42 +95,37 @@
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white">
<section class=" bg-white px-5 md:px-0">
<ToolsHeader className="mb-10">
<img
src="/free-tools/tic-tac.svg"
class="w-14 md:w-[5%] mx-auto border rounded-full"
alt="Tic Tac Icon"
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Tic-Tac game</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
More than just a game; it's an educational tool that teaches pattern recognition, and
decision-making.
<img src="/free-tools/tic-tac.svg" class="mx-auto w-14 rounded-full border md:w-[5%]" alt="Tic Tac Icon" />
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Tic-Tac game</h1>
<p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
More than just a game; it's an educational tool that teaches pattern recognition, and decision-making.
</p>
</ToolsHeader>
<div
class="overflow-hidden relative tic-tac-bg text-white w-full h-[55vh] md:w-[59%] md:h-[82vh] border-8 border-black mx-auto flex justify-center items-center bg-white"
class="tic-tac-bg relative mx-auto flex h-[55vh] w-full items-center justify-center overflow-hidden border-8 border-black bg-white text-white md:h-[82vh] md:w-[59%]"
>
<!-- congratulatory message -->
{#if message}
<div
transition:fly={{ x: -100, delay: 600, easing: sineInOut }}
class="absolute w-full top-[43%] flex flex-col justify-center items-center z-[150]"
class="absolute top-[43%] z-[150] flex w-full flex-col items-center justify-center"
>
<div
class="flex gap-10 justify-center py-1 h-[8vh] md:h-[13vh] w-full items-center bg-white font-bold text-[#0233BD]"
class="flex h-[8vh] w-full items-center justify-center gap-10 bg-white py-1 font-bold text-[#0233BD] md:h-[13vh]"
>
{#if message != 'Tie game!'}
<img src="/free-tools/tic-tac/winner-star-icon.svg" alt="Star icon" class="h-full" />
{/if}
<h2 class="md:text-base text-xs">{message}</h2>
<h2 class="text-xs md:text-base">{message}</h2>
</div>
<button
type="button"
on:click={playAgain}
class="px-16 pt-1 pb-1.5 md:text-base text-xs border border-white font-bold rounded-md mt-10 bg-[#0F62FE]"
class="mt-10 rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-xs font-bold md:text-base"
>Play Again</button
>
</div>
@@ -146,17 +133,13 @@
<!-- this condition renders the game -->
{#if startGame}
<div class="w-full h-full p-5 flex flex-col border justify-between items-center">
<div class="flex h-full w-full flex-col items-center justify-between border p-5">
<header class="flex w-full items-center justify-between">
<button
on:click={changePlayer}
class="text-[10px] md:text-xs w-[35%] gap-2 md:w-28 flex flex-row items-center leading-3 font-medium"
class="flex w-[35%] flex-row items-center gap-2 text-[10px] font-medium leading-3 md:w-28 md:text-xs"
>
<img
src="/free-tools/tic-tac/computer-icon.svg"
alt="Computer icon"
class="w-4 md:w-6"
/>
<img src="/free-tools/tic-tac/computer-icon.svg" alt="Computer icon" class="w-4 md:w-6" />
{#if $changeToComputer.change}
Play two players
{:else}
@@ -165,7 +148,7 @@
</button>
<button
on:click={newGame}
class="text-[10px] w-24 flex flex-col gap-1 p-2 shadow-md rounded-md items-center leading-3 font-medium border text-[#0542CC] bg-white"
class="flex w-24 flex-col items-center gap-1 rounded-md border bg-white p-2 text-[10px] font-medium leading-3 text-[#0542CC] shadow-md"
>
<img src="/free-tools/tic-tac/restart-icon.svg" alt="Restart icon" class="w-3 md:w-4" />
Reset Score</button
@@ -176,19 +159,17 @@
<Board {game} {winner} />
<!-- scores -->
<div
class="flex justify-between md:justify-center items-center gap-10 w-full text-white font-semibold"
>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2">
<span class="px-1 md:p-1 bg-[#FE28CB99] md:text-sm text-[10px]">Player 1</span>
<div class="flex w-full items-center justify-between gap-10 font-semibold text-white md:justify-center">
<div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
<span class="bg-[#FE28CB99] px-1 text-[10px] md:p-1 md:text-sm">Player 1</span>
{$scores.X}
</div>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2">
<span class="px-1 md:p-1 bg-[#0F62FE] md:text-sm text-[10px]">Draw</span>
<div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
<span class="bg-[#0F62FE] px-1 text-[10px] md:p-1 md:text-sm">Draw</span>
{$scores.draw}
</div>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2">
<span class="px-1 md:p-1 bg-[#94F7FFB2] md:text-sm text-[10px]">Player 2</span>
<div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
<span class="bg-[#94F7FFB2] px-1 text-[10px] md:p-1 md:text-sm">Player 2</span>
{$scores.O}
</div>
</div>
@@ -197,11 +178,7 @@
<!-- this condition renders the homepage -->
{:else}
<div class="flex flex-col items-center gap-5">
<img
src="/free-tools/tic-tac/tic-tac-start-icon.svg"
alt="Tic tac toe game icon"
class="w-[50%] md:w-[70%]"
/>
<img src="/free-tools/tic-tac/tic-tac-start-icon.svg" alt="Tic tac toe game icon" class="w-[50%] md:w-[70%]" />
<div class="flex flex-col gap-3">
<button
type="button"
@@ -209,7 +186,7 @@
changePlayer();
startTicTac();
}}
class="mt-5 px-[3.3rem] pt-1 pb-1.5 border border-white text-sm font-bold rounded-md bg-[#0F62FE]"
class="mt-5 rounded-md border border-white bg-[#0F62FE] px-[3.3rem] pb-1.5 pt-1 text-sm font-bold"
>
Play with computer</button
>
@@ -218,7 +195,7 @@
on:click={() => {
startTicTac();
}}
class="px-16 pt-1 pb-1.5 border border-white text-sm font-bold rounded-md bg-[#0F62FE]"
class="rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-sm font-bold"
>Play two players</button
>
</div>
@@ -1,99 +0,0 @@
<script lang="ts">
import { sineInOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
import WinningLine from './WinningLine.svelte';
import { changeToComputer } from './store';
interface Game {
board: Array<Array<string>>;
click: (rowIndex: number, columnIndex: number) => Game;
}
export let game: Game;
export let winner = null;
let customDelay: number;
$: {
if ($changeToComputer.change) {
customDelay = 400;
} else {
customDelay = 0;
}
}
</script>
<div class="relative">
<table>
<tbody>
{#each game.board as row, rowIndex}
<tr>
{#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={() => {
game = game.click(rowIndex, columnIndex);
}}
>
{#if column === 'X'}
<img
transition:fly={{ y: 100, delay: 0, easing: sineInOut }}
src="/free-tools/tic-tac/tic-tac-x.svg"
alt="X icon"
/>
{:else if column === 'O'}
<img
transition:fly={{ y: 100, delay: customDelay, easing: sineInOut }}
src="/free-tools/tic-tac/tic-tac-o.svg"
alt="O icon"
/>
{:else}
<!-- empty cell -->
{/if}
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
<WinningLine {winner} />
</div>
<style>
table {
overflow: hidden;
border-collapse: collapse;
font-size: 4em;
color: white;
border: 0px;
z-index: 10;
position: relative;
}
td {
overflow: hidden;
text-align: center;
height: 100px;
width: 100px;
border-right: 10px solid white;
border-bottom: 10px solid white;
position: relative;
z-index: 10;
}
td:last-child {
border-right: none;
}
tr:last-child td {
border-bottom: none;
}
@media (max-width: 600px) {
td {
width: 16vw;
height: 16vw;
}
}
</style>
@@ -1,80 +0,0 @@
<script lang="ts">
import { fade } from 'svelte/transition';
export let winner: Winner | null = null;
interface Winner {
direction: 'horizontal' | 'vertical' | 'diagonal';
row?: number;
column?: number;
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());
}
}
</script>
{#if winner}
<div
transition:fade={{ delay: 500, duration: 2 }}
class="winning-line absolute bg-[#ffc300] z-[100]"
class:horizontal-line={winner.direction === 'horizontal'}
class:vertical-line={winner.direction === 'vertical'}
class:diagonal-line-1={winner.direction === 'diagonal' &&
winner.diagonal === 'top-left-to-bottom-right'}
class:diagonal-line-2={winner.direction === 'diagonal' &&
winner.diagonal === 'top-right-to-bottom-left'}
></div>
{/if}
<style>
.horizontal-line {
top: calc(var(--row-index) * 100px + 50px - 5px);
left: 0;
width: 100%;
height: 10px;
}
.vertical-line {
top: 0;
left: calc(var(--col-index) * 100px + 50px - 5px);
width: 10px;
height: 100%;
}
.diagonal-line-1 {
top: 0;
left: 0;
width: 140%;
height: 100%;
background: none;
border-top: 10px solid #ffc300;
transform: rotate(45deg);
transform-origin: top left;
}
.diagonal-line-2 {
top: 0;
right: 0;
width: 140%;
height: 100%;
background: none;
border-top: 10px solid #ffc300;
transform: rotate(-45deg);
transform-origin: top right;
}
@media (max-width: 600px) {
.horizontal-line {
top: calc(var(--row-index) * 16vw + 8vw - 5px);
}
.vertical-line {
left: calc(var(--col-index) * 16vw + 8vw - 5px);
}
}
</style>
@@ -1,178 +0,0 @@
import { scores, changeToComputer } from './store';
let activateComp = false;
changeToComputer.subscribe((value) => {
if (value && value.change === true) {
activateComp = true;
} else {
activateComp = false;
}
});
export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTie: () => void }) {
const board = [
['', '', ''],
['', '', ''],
['', '', '']
];
let turn = 'X';
let done = false;
return {
board,
click(rowIndex:number, columnIndex:number) {
if (!done && !board[rowIndex][columnIndex]) {
board[rowIndex][columnIndex] = turn;
turn = turn === 'X' ? 'O' : 'X';
const winner = getWinner(board);
if (winner) {
onWin(winner);
done = true;
} else if (isBoardFull(board)) {
onTie();
scores.update((currentScores) => {
return { ...currentScores, draw: currentScores.draw + 1 };
});
done = true;
} else {
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;
}
} else {
console.error("AI could not find a valid move.");
}
}
}
}
return this;
}
};
}
function getWinner(board: string[][]) {
// check rows
for (let row = 0; row < 3; row++) {
if (board[row][0] === board[row][1] && board[row][0] === board[row][2] && board[row][0]) {
return { symbol: board[row][0], direction: 'horizontal', row };
}
}
// check columns
for (let column = 0; column < 3; column++) {
if (
board[0][column] === board[1][column] &&
board[0][column] === board[2][column] &&
board[0][column]
) {
return { symbol: board[0][column], direction: 'vertical', column };
}
}
// check diagonals
if (board[0][0] === board[1][1] && board[0][0] == board[2][2] && board[0][0]) {
return { symbol: board[0][0], direction: 'diagonal', diagonal: 'top-left-to-bottom-right' };
}
if (board[0][2] === board[1][1] && board[0][2] == board[2][0] && board[0][2]) {
return { symbol: board[0][2], direction: 'diagonal', diagonal: 'top-right-to-bottom-left' };
}
// no winner
return null;
}
function isBoardFull(board: string[][]) {
for (let row = 0; row < 3; row++) {
for (let column = 0; column < 3; column++) {
if (!board[row][column]) {
return false;
}
}
}
return true;
}
function getBestMove(board: string[][]) {
let bestScore = -Infinity;
let move;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
// check if cell is empty
if (board[i][j] === '') {
board[i][j] = 'O'; // make the move
let score = minimax(board, 0, false); // call minimax on the new board
board[i][j] = ''; // undo the move
if (score > bestScore) {
bestScore = score;
move = { row: i, column: j };
}
}
}
}
return move;
}
function minimax(board: string[][], depth: number, isMaximizing: boolean) {
const result = getWinner(board);
if (result !== null) {
if (result.symbol === 'O') {
return 10 - depth; // ai wins
} else if (result.symbol === 'X') {
return depth - 10; // player wins
} else {
return 0; // draw
}
}
if (isMaximizing) {
let bestScore = -Infinity;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[i][j] === '') {
board[i][j] = 'O';
let score = minimax(board, depth + 1, false);
board[i][j] = '';
bestScore = Math.max(score, bestScore);
}
}
}
return bestScore;
} else {
let bestScore = Infinity;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[i][j] === '') {
board[i][j] = 'X';
let score = minimax(board, depth + 1, true);
board[i][j] = '';
bestScore = Math.min(score, bestScore);
}
}
}
return bestScore;
}
}
@@ -1,22 +0,0 @@
import { writable } from 'svelte/store';
import type {Writable} from 'svelte/store'
interface Scores {
X: number;
O: number;
draw: number;
}
interface ChangeToComputer {
change: boolean;
}
export const scores: Writable<Scores> = writable({
X: 0,
O: 0,
draw: 0
});
export const changeToComputer: Writable<ChangeToComputer> = writable({
change: false
});