migrate web to svelte 5
This commit is contained in:
@@ -5,6 +5,7 @@ This document describes the folder structure for the `apps/website` project, spe
|
||||
## Overview
|
||||
|
||||
The codebase follows a clear separation of concerns with three main categories:
|
||||
|
||||
- **Components**: Presentational UI components (dummy components)
|
||||
- **Features**: Business logic and functional components
|
||||
- **Utils**: Utility functions, constants, and shared types
|
||||
@@ -87,10 +88,12 @@ src/lib/
|
||||
## Naming Conventions
|
||||
|
||||
### Files and Folders
|
||||
|
||||
- **Kebab-case**: All file and folder names use kebab-case (e.g., `blog-list-item.svelte`, `feedback-form.svelte`)
|
||||
- **Single-file folders**: If a folder contains only one file, it should be flattened to a single file (e.g., `Footer/Footer.svelte` → `components/footer.svelte`)
|
||||
|
||||
### Components
|
||||
|
||||
- Component files use `.svelte` extension
|
||||
- Component names match their file names in kebab-case
|
||||
- Example: `blog-list-item.svelte` exports a component that can be imported as `BlogListItem`
|
||||
@@ -102,18 +105,21 @@ src/lib/
|
||||
**Purpose**: Presentational components with minimal or no business logic. These are "dummy" components that receive props and render UI.
|
||||
|
||||
**Characteristics**:
|
||||
|
||||
- No API calls or complex state management
|
||||
- Primarily focused on rendering and styling
|
||||
- May have simple local state for UI interactions (e.g., open/close modals)
|
||||
- Reusable across different pages/features
|
||||
|
||||
**Examples**:
|
||||
|
||||
- `footer.svelte` - Site footer component
|
||||
- `navigation.svelte` - Site navigation bar
|
||||
- `hero.svelte` - Hero section component
|
||||
- `modal.svelte` - Generic modal wrapper
|
||||
|
||||
**Import Pattern**:
|
||||
|
||||
```svelte
|
||||
// ✅ CORRECT - Use named imports from the index
|
||||
import { Footer, Navigation, Hero } from '$lib/components';
|
||||
@@ -129,17 +135,20 @@ import { Footer, Navigation, Hero } from '$lib/components';
|
||||
**Purpose**: Components and modules that contain business logic, API calls, form handling, state management, or domain-specific functionality.
|
||||
|
||||
**Characteristics**:
|
||||
|
||||
- Contains business logic and data handling
|
||||
- May include API calls, form submissions, or data transformations
|
||||
- Often includes stores, utilities, or helper functions specific to the feature
|
||||
- Organized by feature domain (e.g., `contact/`, `tools/`)
|
||||
|
||||
**Examples**:
|
||||
|
||||
- `features/contact/feedback-form.svelte` - Form with submission logic
|
||||
- `features/tools/progress/store.ts` - State management for progress tool
|
||||
- `features/tools/tic-tac-toe/board.ts` - Game logic
|
||||
|
||||
**Import Pattern**:
|
||||
|
||||
```svelte
|
||||
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
|
||||
import { htmlBody, openModal } from '$lib/features/tools/progress/store';
|
||||
@@ -150,12 +159,14 @@ import { htmlBody, openModal } from '$lib/features/tools/progress/store';
|
||||
**Purpose**: Shared utility functions, constants, types, and helper modules used across the application.
|
||||
|
||||
**Characteristics**:
|
||||
|
||||
- Pure functions or utility modules
|
||||
- No component-specific logic
|
||||
- Reusable across different features/components
|
||||
- May include TypeScript types and interfaces
|
||||
|
||||
**Examples**:
|
||||
|
||||
- `utils/format-date.ts` - Date formatting utility
|
||||
- `utils/is-form-valid.ts` - Form validation helper
|
||||
- `utils/submit-form.ts` - Generic form submission utility
|
||||
@@ -163,6 +174,7 @@ import { htmlBody, openModal } from '$lib/features/tools/progress/store';
|
||||
- `utils/constants/tools.ts` - Application constants
|
||||
|
||||
**Import Pattern**:
|
||||
|
||||
```typescript
|
||||
import { formatDate } from '$lib/utils/format-date';
|
||||
import { isFormValid } from '$lib/utils/is-form-valid';
|
||||
@@ -219,7 +231,7 @@ src/lib/
|
||||
<script>
|
||||
// ✅ CORRECT - Named imports from centralized index
|
||||
import { Footer, Navigation, Hero, Testimonial } from '$lib/components';
|
||||
|
||||
|
||||
// ❌ INCORRECT - Direct file imports are not allowed
|
||||
// import Footer from '$lib/components/footer.svelte';
|
||||
</script>
|
||||
@@ -307,6 +319,7 @@ Routes now import from `$lib/features/tools/*/` instead of `./components/`.
|
||||
## Assets
|
||||
|
||||
Static assets like images, SVGs, and other media files remain in their respective locations:
|
||||
|
||||
- `emojis/` - Emoji SVG files
|
||||
- `public/` - Public static assets (images, fonts, etc.)
|
||||
|
||||
@@ -323,6 +336,7 @@ export { default as Navigation } from './navigation.svelte';
|
||||
```
|
||||
|
||||
This allows for clean, consistent imports:
|
||||
|
||||
```svelte
|
||||
import { Footer, Navigation, Hero } from '$lib/components';
|
||||
```
|
||||
@@ -332,6 +346,7 @@ import { Footer, Navigation, Hero } from '$lib/components';
|
||||
## Summary
|
||||
|
||||
This structure provides:
|
||||
|
||||
- ✅ Clear separation between UI and business logic
|
||||
- ✅ Consistent naming conventions (kebab-case)
|
||||
- ✅ Flattened structure (no unnecessary single-file folders)
|
||||
@@ -341,4 +356,3 @@ This structure provides:
|
||||
- ✅ Scalable for future growth
|
||||
|
||||
For questions or suggestions about this structure, please refer to the project maintainers.
|
||||
|
||||
|
||||
+15
-15
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
|
||||
Vendored
+6
-6
@@ -1,12 +1,12 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
+73
-73
@@ -1,20 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#333333" />
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#333333" />
|
||||
|
||||
<!-- <title>ClassroomIO – The Open Source Learning Management System for Companies</title>
|
||||
<!-- <title>ClassroomIO – The Open Source Learning Management System for Companies</title>
|
||||
<meta name="description" content="ClassroomIO" />
|
||||
<meta name="application-name" content="ClassroomIO" />
|
||||
<meta name="apple-mobile-web-app-title" content="ClassroomIO" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" /> -->
|
||||
|
||||
<!-- Facebook Meta Tags -->
|
||||
<!-- <meta property="og:url" content="https://www.classroomio.com/" />
|
||||
<!-- Facebook Meta Tags -->
|
||||
<!-- <meta property="og:url" content="https://www.classroomio.com/" />
|
||||
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/classroomio-og.png" />
|
||||
<meta property="og:title" content="ClassroomIO – The Open Source Learning Management System for Companies" />
|
||||
<meta property="og:description"
|
||||
@@ -34,75 +33,76 @@
|
||||
content="Launch your bootcamp quichttps://brand.cdn.clsrio.com/og/classroomio-og.pngnline teaching platform." />
|
||||
<meta name="twitter:image" content="https://classroomio.com/classroomio-opengraph-image.png" /> -->
|
||||
|
||||
<!-- <link rel="stylesheet" href="global.css"> -->
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
|
||||
<!-- <link rel="stylesheet" href="global.css"> -->
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
|
||||
|
||||
<!-- <link rel="stylesheet" href="global.css"> -->
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
|
||||
|
||||
<!-- <link rel="stylesheet" href="global.css"> -->
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
|
||||
<script
|
||||
defer
|
||||
src="https://umami.hz.oncws.com/script.js"
|
||||
data-website-id="5321d227-751a-4d43-8556-f516fba60891"
|
||||
></script>
|
||||
|
||||
<script defer src="https://umami.hz.oncws.com/script.js"
|
||||
data-website-id="5321d227-751a-4d43-8556-f516fba60891"></script>
|
||||
<script defer type="text/javascript">
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = '69176b47-5f63-42cc-bb9b-68d4927b6276';
|
||||
(function () {
|
||||
d = document;
|
||||
s = d.createElement('script');
|
||||
s.src = 'https://client.crisp.chat/l.js';
|
||||
s.async = 1;
|
||||
d.getElementsByTagName('head')[0].appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script defer type="text/javascript">
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = '69176b47-5f63-42cc-bb9b-68d4927b6276';
|
||||
(function () {
|
||||
d = document;
|
||||
s = d.createElement('script');
|
||||
s.src = 'https://client.crisp.chat/l.js';
|
||||
s.async = 1;
|
||||
d.getElementsByTagName('head')[0].appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script defer type="text/javascript">
|
||||
(function (C, A, L) {
|
||||
let p = function (a, ar) {
|
||||
a.q.push(ar);
|
||||
};
|
||||
let d = C.document;
|
||||
C.Cal =
|
||||
C.Cal ||
|
||||
function () {
|
||||
let cal = C.Cal;
|
||||
let ar = arguments;
|
||||
if (!cal.loaded) {
|
||||
cal.ns = {};
|
||||
cal.q = cal.q || [];
|
||||
d.head.appendChild(d.createElement('script')).src = A;
|
||||
cal.loaded = true;
|
||||
}
|
||||
if (ar[0] === L) {
|
||||
const api = function () {
|
||||
p(api, arguments);
|
||||
};
|
||||
const namespace = ar[1];
|
||||
api.q = api.q || [];
|
||||
typeof namespace === 'string' ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar);
|
||||
return;
|
||||
}
|
||||
p(cal, ar);
|
||||
<script defer type="text/javascript">
|
||||
(function (C, A, L) {
|
||||
let p = function (a, ar) {
|
||||
a.q.push(ar);
|
||||
};
|
||||
})(window, 'https://app.cal.com/embed/embed.js', 'init');
|
||||
Cal('init', { origin: 'https://cal.com' });
|
||||
let d = C.document;
|
||||
C.Cal =
|
||||
C.Cal ||
|
||||
function () {
|
||||
let cal = C.Cal;
|
||||
let ar = arguments;
|
||||
if (!cal.loaded) {
|
||||
cal.ns = {};
|
||||
cal.q = cal.q || [];
|
||||
d.head.appendChild(d.createElement('script')).src = A;
|
||||
cal.loaded = true;
|
||||
}
|
||||
if (ar[0] === L) {
|
||||
const api = function () {
|
||||
p(api, arguments);
|
||||
};
|
||||
const namespace = ar[1];
|
||||
api.q = api.q || [];
|
||||
typeof namespace === 'string' ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar);
|
||||
return;
|
||||
}
|
||||
p(cal, ar);
|
||||
};
|
||||
})(window, 'https://app.cal.com/embed/embed.js', 'init');
|
||||
Cal('init', { origin: 'https://cal.com' });
|
||||
|
||||
Cal('ui', {
|
||||
theme: 'light',
|
||||
styles: { branding: { brandColor: '#1D4EE2' } },
|
||||
hideEventTypeDetails: false,
|
||||
layout: 'month_view'
|
||||
});
|
||||
</script>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
Cal('ui', {
|
||||
theme: 'light',
|
||||
styles: { branding: { brandColor: '#1D4EE2' } },
|
||||
hideEventTypeDetails: false,
|
||||
layout: 'month_view'
|
||||
});
|
||||
</script>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -13,7 +13,7 @@ published: true
|
||||

|
||||
_ClassroomIO Brand Customisations_
|
||||
|
||||
We are excited to announce more customisations coming into our learning management system (LMS). In this blog post, we will like to share with you how we've made *YOUR* *STUDENT DASHBOARD* much more customisable than before.
|
||||
We are excited to announce more customisations coming into our learning management system (LMS). In this blog post, we will like to share with you how we've made _YOUR_ _STUDENT DASHBOARD_ much more customisable than before.
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -40,7 +40,7 @@ However that is not the UI that your audience will see when they consume your co
|
||||

|
||||
_The LMS aka Student dashboard - yourorg.classroomio.com_
|
||||
|
||||
Typically this page can be found at *yourorg.classroomio.com*, where *yourorg* is a slug you set when you first set up your account. For example, we've built an internal bootcamp called ClassroomIO Developers, in this bootcamp we train other developers on some of the key tools we use internally. You can checkout our page here: [ciodevs.classroomio.com](https://ciodevs.classroomio.com), in this case you can see that our slug is *ciodevs* and you should be able to easily see yours when you log into the dashboard `/org/{yourorg}`.
|
||||
Typically this page can be found at _yourorg.classroomio.com_, where _yourorg_ is a slug you set when you first set up your account. For example, we've built an internal bootcamp called ClassroomIO Developers, in this bootcamp we train other developers on some of the key tools we use internally. You can checkout our page here: [ciodevs.classroomio.com](https://ciodevs.classroomio.com), in this case you can see that our slug is _ciodevs_ and you should be able to easily see yours when you log into the dashboard `/org/{yourorg}`.
|
||||
|
||||
In your student dashboard, your audience can:
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ A bit about EduAI Hub before I jump into my experience at the summit:
|
||||
|
||||
### My experience
|
||||
|
||||
Now to my experience - __it was nothing less than amazing__.
|
||||
Now to my experience - **it was nothing less than amazing**.
|
||||
|
||||
I could see how the eyes of some of the educators lite up when I said we can allow them build a customisable alternative to Google Classroom without writing a single line of code. I spoke about the importance of a customisable no-code elearning platform that anyone could use to deploy their own learning management system that suits their brand. I also gave an analogy using Shopify and Wordpress, how Shopify is allowing businesses create ecommerce stores in minutes or Wordpress allowing writters and businesses create robust websites in minutes - that's exactly what we want to do in the educational space starting with bootcamps.
|
||||
|
||||
@@ -33,7 +33,7 @@ You can find my slides [here](https://docs.google.com/presentation/d/1yU9EFmYA5O
|
||||
|
||||
This was my first actual event speaking about ClassroomIO and I was amazed by the amount of people that wanted to chat about how they could use the product for their online courses.
|
||||
|
||||
Apart from the talk, I met amazing people working on other edtech related products and how they also use AI to empower their users.
|
||||
Apart from the talk, I met amazing people working on other edtech related products and how they also use AI to empower their users.
|
||||
|
||||
### Unexpected learning
|
||||
|
||||
|
||||
@@ -48,6 +48,6 @@ Oh we even hooked up our AI content generator with it as well, you can switch to
|
||||
|
||||
## Next steps
|
||||
|
||||
All translations in our app are stored in a JSON file which we generate and keep up to date using Google translate. This means that it might not be 100% accurate and there could be some mistakes when use our dashboard in other languages. If you do find any issues with our translations, please do open an [issue on Github](https://github.com/classroomio/classroomio/issues/new/choose) with the correct translation and we'd be so happy to fix it.
|
||||
All translations in our app are stored in a JSON file which we generate and keep up to date using Google translate. This means that it might not be 100% accurate and there could be some mistakes when use our dashboard in other languages. If you do find any issues with our translations, please do open an [issue on Github](https://github.com/classroomio/classroomio/issues/new/choose) with the correct translation and we'd be so happy to fix it.
|
||||
|
||||
If you are a native speaker of any of the 7 languages and want to contribute, please feel free to nitpick into our [translations](https://github.com/classroomio/classroomio/tree/main/apps/dashboard/src/lib/utils/translations) and open a PR with the fixes. Every contribution is valuable and will be gladly welcomed 😊
|
||||
If you are a native speaker of any of the 7 languages and want to contribute, please feel free to nitpick into our [translations](https://github.com/classroomio/classroomio/tree/main/apps/dashboard/src/lib/utils/translations) and open a PR with the fixes. Every contribution is valuable and will be gladly welcomed 😊
|
||||
|
||||
@@ -17,7 +17,7 @@ Today we are happy to share that you can now easily self-host classroomio and us
|
||||
|
||||
If you are interested in just trying out the product, it is advisable to use the [cloud version](https://app.classroomio.com). You can use almost all the features for free on the cloud version unless you need more students. Right now the main reason you'd have to upgrade is because your students are growing. If you don't want to upgrade and/or face this, then you'd be fine self hosting the whole application.
|
||||
|
||||
For self-hosting, we have written up a [guide that uses Railway](https://classroomio.com/docs/quickstart/self-hosting) to help deploy our stack and you get to manage the servers on Railway as you wish. We hope this is a good step in helping you educate your audience at ease.
|
||||
For self-hosting, we have written up a [guide that uses Railway](https://classroomio.com/docs/quickstart/self-hosting) to help deploy our stack and you get to manage the servers on Railway as you wish. We hope this is a good step in helping you educate your audience at ease.
|
||||
|
||||
Below is a demo showing you step by step how to self host the project
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ The audience page as you already know allows you to view the students in your **
|
||||
<br />
|
||||
|
||||
<video
|
||||
loop
|
||||
muted
|
||||
autoplay
|
||||
preload="auto"
|
||||
autoPictureInPicture
|
||||
class="rounded-md" src="https://assets.cdn.clsrio.com/blog/organisation-student-analytics.mp4" controls
|
||||
>
|
||||
</video>
|
||||
loop
|
||||
muted
|
||||
autoplay
|
||||
preload="auto"
|
||||
autoPictureInPicture
|
||||
class="rounded-md" src="https://assets.cdn.clsrio.com/blog/organisation-student-analytics.mp4" controls
|
||||
|
||||
> </video>
|
||||
|
||||
## Course Student Analytics
|
||||
|
||||
@@ -43,14 +43,14 @@ Further more, when you go into any course and go to the people tab, you can view
|
||||
<br />
|
||||
|
||||
<video loop
|
||||
muted
|
||||
autoplay
|
||||
preload="auto"
|
||||
autoPictureInPicture
|
||||
class="rounded-md"
|
||||
src="https://assets.cdn.clsrio.com/blog/course-students-analytics.mp4" controls
|
||||
>
|
||||
</video>
|
||||
muted
|
||||
autoplay
|
||||
preload="auto"
|
||||
autoPictureInPicture
|
||||
class="rounded-md"
|
||||
src="https://assets.cdn.clsrio.com/blog/course-students-analytics.mp4" controls
|
||||
|
||||
> </video>
|
||||
|
||||
## What's Next?
|
||||
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
import { Badge } from '@cio/ui/base/badge';
|
||||
import { formatDate } from '$lib/utils/format-date';
|
||||
|
||||
export let post;
|
||||
export let isRecommended = false;
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {any} post
|
||||
* @property {boolean} [isRecommended]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { post, isRecommended = false } = $props();
|
||||
</script>
|
||||
|
||||
{#if !isRecommended}
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
<script>
|
||||
import { flexible, leftFist, rightFist, robotArm, thumbsUp } from '$lib/emojis';
|
||||
|
||||
export let id = '';
|
||||
export let rightToLeft = false;
|
||||
export let tagline = '';
|
||||
export let title = '';
|
||||
export let description = '';
|
||||
export let video = '';
|
||||
export let taglineIcon = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [id]
|
||||
* @property {boolean} [rightToLeft]
|
||||
* @property {string} [tagline]
|
||||
* @property {string} [title]
|
||||
* @property {string} [description]
|
||||
* @property {string} [video]
|
||||
* @property {string} [taglineIcon]
|
||||
* @property {import('svelte').Snippet} [more]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let {
|
||||
id = '',
|
||||
rightToLeft = false,
|
||||
tagline = '',
|
||||
title = '',
|
||||
description = '',
|
||||
video = '',
|
||||
taglineIcon = '',
|
||||
more
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<section {id} class="w-full py-32 border-b border-x-0 border-t-0 border-gray-200">
|
||||
@@ -32,15 +48,15 @@
|
||||
{/if}
|
||||
<p class="font-medium text-base">{tagline}</p>
|
||||
</div>
|
||||
<h2 class="font-bold text-4xl mb-3 lg:mb-6">{title}</h2>
|
||||
<h2 class="text-3xl mb-3 lg:mb-6">{title}</h2>
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
{description}
|
||||
</p>
|
||||
{#if $$slots.more}
|
||||
{#if more}
|
||||
<br />
|
||||
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
<slot name="more" />
|
||||
{@render more?.()}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let src: string;
|
||||
export let alt: string;
|
||||
interface Props {
|
||||
src: string;
|
||||
alt: string;
|
||||
}
|
||||
|
||||
let { src, alt }: Props = $props();
|
||||
</script>
|
||||
|
||||
<figure>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import Youtube from '@lucide/svelte/icons/youtube';
|
||||
</script>
|
||||
|
||||
<div class="border-t-[1px] px-[2%] lg:px-[14%]">
|
||||
<div class="border-t px-[2%] lg:px-[14%]">
|
||||
<div class="flex flex-col items-center justify-between py-[10%] pl-5 lg:flex-row lg:pl-0">
|
||||
<div class="w-full min-w-[200px] lg:w-3/12">
|
||||
<img width="32" height="32" loading="lazy" src="/logo-192.png" alt="classroomio logo" class="mb-5" />
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
{ label: 'Customers', color: '#0233BD' },
|
||||
{ label: 'Students', color: '#9747FF' }
|
||||
];
|
||||
let animate = false;
|
||||
let titleIndex = 0;
|
||||
let animate = $state(false);
|
||||
let titleIndex = $state(0);
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -19,4 +19,3 @@ export { default as Testimonial } from './testimonial.svelte';
|
||||
export { default as Timeline } from './timeline.svelte';
|
||||
export { default as ToolsHeader } from './tools-header.svelte';
|
||||
export { default as UploadWidget } from './upload-widget.svelte';
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
export let open = false;
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {boolean} [open]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', open = false, children } = $props();
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
@@ -10,7 +17,7 @@
|
||||
<div
|
||||
class="fixed z-[3001] {className} w-full md:w-[30%] md:min-w-[500px] bg-white rounded-md mx-auto py-6 px-7"
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import { folder, globe, team, transcription } from '$lib/emojis';
|
||||
import { tools } from '$lib/utils/constants/tools';
|
||||
|
||||
let selected = 0;
|
||||
let interval: NodeJS.Timer;
|
||||
let selected = $state(0);
|
||||
let interval: NodeJS.Timer = $state();
|
||||
|
||||
const moreFeatures = [
|
||||
{
|
||||
@@ -22,13 +22,13 @@
|
||||
'Extend invitations and manage your teaching institution seamlessly through ClassroomIO.',
|
||||
image: '/team-management.png'
|
||||
},
|
||||
{
|
||||
icon: transcription,
|
||||
title: 'Auto Video Transcription',
|
||||
description:
|
||||
'AI automatically transcribes videos, making content searchable for seamless student access.',
|
||||
image: '/video-transcription.png'
|
||||
},
|
||||
// {
|
||||
// icon: transcription,
|
||||
// title: 'Auto Video Transcription',
|
||||
// description:
|
||||
// 'AI automatically transcribes videos, making content searchable for seamless student access.',
|
||||
// image: '/video-transcription.png'
|
||||
// },
|
||||
{
|
||||
icon: folder,
|
||||
title: 'Downloadable Lesson PDF',
|
||||
@@ -52,7 +52,7 @@
|
||||
<section id="morefeatures" class="py-[10%] px-3 lg:px-0 bg-gray-50">
|
||||
<div class="mx-0 lg:mx-[12%]">
|
||||
<div class="w-full lg:w-4/5 mx-4 lg:mx-0 mb-14">
|
||||
<h2 class="mt-2 text-3xl font-bold font-display tracking-tight lg:text-4xl">
|
||||
<h2 class="mt-2 text-3xl tracking-tight">
|
||||
There is More...
|
||||
</h2>
|
||||
<p class="mt-4 text-lg text-gray-500 w-[94%] lg:w-4/5">
|
||||
@@ -67,8 +67,8 @@
|
||||
{#each moreFeatures as moreFeature, i}
|
||||
<button
|
||||
class="p-4 rounded-md {i === selected &&
|
||||
'selected'} mb-3 text-start min-w-[75vw] lg:min-w-[unset]"
|
||||
on:mouseenter={() => {
|
||||
'selected'} mb-3 text-start min-w-[75vw] lg:min-w-[unset] cursor-pointer"
|
||||
onclick={() => {
|
||||
selected = i;
|
||||
clearInterval(interval);
|
||||
}}
|
||||
@@ -82,11 +82,11 @@
|
||||
alt=""
|
||||
class="w-8"
|
||||
/>
|
||||
<p class="text-lg font-semibold">
|
||||
<p class="text-lg">
|
||||
{moreFeature.title}
|
||||
</p>
|
||||
</div>
|
||||
<p>{moreFeature.description}</p>
|
||||
<p class="text-sm">{moreFeature.description}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -99,7 +99,7 @@
|
||||
height="100%"
|
||||
loading="lazy"
|
||||
id="image"
|
||||
in:fly={{ y: 100, duration: 1000 }}
|
||||
in:fly={{ x: 100, duration: 800 }}
|
||||
class="w-full flex flex-col items-center"
|
||||
src={moreFeatures[selected].image}
|
||||
alt={moreFeatures[selected].title}
|
||||
|
||||
@@ -14,13 +14,28 @@
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as NavigationMenu from '@cio/ui/base/navigation-menu';
|
||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
||||
import { cn } from '@cio/ui/tools';
|
||||
|
||||
|
||||
export let stars: number;
|
||||
let { stars }: { stars: number } = $props();
|
||||
|
||||
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
|
||||
title: string;
|
||||
href: string;
|
||||
subtitle: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
let showDrawer = false;
|
||||
let showSolutions = false;
|
||||
let activeLink = '';
|
||||
let activeLink = $derived(page.url.pathname);
|
||||
let activeHash = $derived(page.url.hash);
|
||||
|
||||
$effect(() => {
|
||||
console.log('activeLink', activeLink);
|
||||
console.log('activeHash', activeHash);
|
||||
console.log('isSolutionsActive', isSolutionsActive);
|
||||
});
|
||||
|
||||
function handleShowDrawer() {
|
||||
showDrawer = !showDrawer;
|
||||
@@ -76,16 +91,7 @@
|
||||
}
|
||||
];
|
||||
|
||||
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
|
||||
title: string;
|
||||
href: string;
|
||||
subtitle: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
$: activeLink = page.url.pathname;
|
||||
$: activeHash = page.url.hash;
|
||||
$: isSolutionsActive = solutions.some((s) => activeHash.includes(s.key));
|
||||
let isSolutionsActive = $derived(!!solutions.some((s) => activeLink.includes(s.key)));
|
||||
</script>
|
||||
|
||||
{#snippet list_item({ title, subtitle, href, key, class: className, ...restProps }: ListItemProps)}
|
||||
@@ -140,11 +146,14 @@
|
||||
</a>
|
||||
|
||||
<section class="hidden lg:block">
|
||||
<NavigationMenu.Root class="rounded-lg bg-white p-1">
|
||||
<NavigationMenu.Root class="p-1">
|
||||
<NavigationMenu.List class="flex w-full items-center justify-center gap-1">
|
||||
<NavigationMenu.Item>
|
||||
<NavigationMenu.Trigger
|
||||
class="flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100"
|
||||
class={cn(
|
||||
"flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100",
|
||||
isSolutionsActive && 'bg-gray-100!'
|
||||
)}
|
||||
>
|
||||
<a href="/" class="no-underline"> Solutions </a>
|
||||
</NavigationMenu.Trigger>
|
||||
@@ -164,7 +173,7 @@
|
||||
|
||||
<NavigationMenu.Item>
|
||||
<NavigationMenu.Trigger
|
||||
class="flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100"
|
||||
class={cn("flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100", activeLink.startsWith('/tools') && 'bg-gray-100!')}
|
||||
>
|
||||
<a href="/tools" class="no-underline"> Free Tools </a>
|
||||
</NavigationMenu.Trigger>
|
||||
@@ -187,11 +196,10 @@
|
||||
{#snippet child()}
|
||||
<a
|
||||
href="/blog"
|
||||
class="cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100 {activeLink.startsWith(
|
||||
'/blog'
|
||||
)
|
||||
? 'bg-gray-100'
|
||||
: ''}"
|
||||
class={cn(
|
||||
'cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100',
|
||||
activeLink.startsWith('/blog') && 'bg-gray-100'
|
||||
)}
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
@@ -204,11 +212,10 @@
|
||||
{#snippet child()}
|
||||
<a
|
||||
href="/pricing"
|
||||
class="cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100 {activeLink.startsWith(
|
||||
'/pricing'
|
||||
)
|
||||
? 'bg-gray-100'
|
||||
: ''}"
|
||||
class={cn(
|
||||
'cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100',
|
||||
activeLink.startsWith('/pricing') && 'bg-gray-100'
|
||||
)}
|
||||
>
|
||||
Pricing
|
||||
</a>
|
||||
@@ -250,7 +257,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button type="button" aria-label="Hamburger Menu" class="block md:block lg:hidden" on:click={handleShowSolutions}>
|
||||
<button type="button" aria-label="Hamburger Menu" class="block md:block lg:hidden" onclick={handleShowSolutions}>
|
||||
<Menu size={24} />
|
||||
</button>
|
||||
|
||||
@@ -262,7 +269,7 @@
|
||||
>
|
||||
<div class="mb-5 flex justify-between py-2">
|
||||
<img loading="lazy" width="20" height="20" src="/logo-512.png" alt="classroomio logo" class="w-[15%]" />
|
||||
<button class="mr-5" on:click={handleShowSolutions}>
|
||||
<button class="mr-5" onclick={handleShowSolutions}>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -270,9 +277,11 @@
|
||||
<ul class="flex w-full flex-col items-center justify-between lg:flex-row">
|
||||
<li class="w-full cursor-pointer text-sm font-semibold text-gray-800 md:text-lg">
|
||||
<button
|
||||
class="flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100"
|
||||
on:click={handleShowDrawer}
|
||||
class:active={isSolutionsActive}
|
||||
class={cn(
|
||||
"flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100",
|
||||
isSolutionsActive && 'bg-gray-100'
|
||||
)}
|
||||
onclick={handleShowDrawer}
|
||||
>
|
||||
Our Superpowers <ChevronDown />
|
||||
</button>
|
||||
@@ -281,7 +290,7 @@
|
||||
{#each solutions as solution}
|
||||
<a
|
||||
href="/{solution.key}"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
>
|
||||
@@ -297,37 +306,43 @@
|
||||
</li>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={handleShowSolutions}
|
||||
onclick={handleShowSolutions}
|
||||
href="/tools"
|
||||
>
|
||||
<li>Free Tools</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeLink.startsWith('/morefeatures') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
class:active={activeLink.startsWith('/blog')}
|
||||
href="/blog"
|
||||
>
|
||||
<li>Blog</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeLink.startsWith('/pricing') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
href="/pricing"
|
||||
class:active={activeLink.startsWith('/pricing')}
|
||||
>
|
||||
<li>Pricing</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeHash.includes('morefeatures') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
class:active={activeHash.includes('morefeatures')}
|
||||
href="/#morefeatures"
|
||||
>
|
||||
<li>More features</li>
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
import SearchXIcon from '@lucide/svelte/icons/search-x';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
|
||||
export let isOrg = false;
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {boolean} [isOrg]
|
||||
* @property {string} [className]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { isOrg = false, className = '' } = $props();
|
||||
|
||||
function handleClick() {
|
||||
if (!isOrg) {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', children } = $props();
|
||||
</script>
|
||||
|
||||
<header class="hero-bg w-full border-b border-slate-100 pb-16 pt-32 lg:min-h-[20rem] {className}">
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</header>
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
>
|
||||
<div>
|
||||
<h3
|
||||
class="font-display mt-3 text-xl font-semibold tracking-tight text-white lg:mt-2 lg:text-4xl max-w-2xl"
|
||||
class="font-display mt-3 text-xl tracking-tight text-white lg:mt-2 lg:text-4xl max-w-2xl"
|
||||
>
|
||||
{header}
|
||||
</h3>
|
||||
<p class="mt-6 text-xl text-gray-300 lg:text-lg">
|
||||
<p class="mt-6 text-md text-gray-300">
|
||||
{subText}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
import { embedSenjaWidget } from '@cio/utils/senja';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let id = '';
|
||||
interface Props {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
let { id = '' }: Props = $props();
|
||||
|
||||
onMount(() => {
|
||||
embedSenjaWidget(id);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div
|
||||
class="relative overflow-hidden mt-10 flex space-y-3 flex-col text-center items-center border border-[#D9E0F5] justify-center w-full rounded-md min-h-[300px] p-4 bg-[#F5F8FE]"
|
||||
>
|
||||
<div class="absolute w-20 h-20 bg-[#0233BD] -top-14 -left-9 rotate-[50deg]" />
|
||||
<div class="absolute w-20 h-20 bg-[#0233BD] -top-14 -left-9 rotate-[50deg]"></div>
|
||||
<h1 class="font-bold text-3xl">Your message has been sent 🎉</h1>
|
||||
<p class="w-[70%] font-normal text-sm">
|
||||
We'll get back to you as soon as possible. In the meantime, Explore and enjoy features on our
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
<script lang="ts">
|
||||
export let items: {
|
||||
interface Props {
|
||||
items?: {
|
||||
title: string;
|
||||
desc: string;
|
||||
}[] = [];
|
||||
}[];
|
||||
}
|
||||
|
||||
let { items = [] }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="relative w-fit flex justify-center space-x-8 md:space-x-10">
|
||||
<div
|
||||
class="w-[3px] md:w-[2px] bg-blue-600 mt-10 before:bg-gradient-to-t before:from-white before:to-transparent before:w-[4px] before:h-[9rem] before:absolute before:bottom-0"
|
||||
/>
|
||||
class="w-[3px] md:w-[2px] bg-blue-600 mt-10 before:bg-linear-to-t before:from-white before:to-transparent before:w-[4px] before:h-36 before:absolute before:bottom-0"
|
||||
></div>
|
||||
<div class="space-y-8">
|
||||
{#each items as card, index}
|
||||
<div class="md:w-[35rem] w-full border-2 rounded-lg px-4 py-8 space-y-4 bg-white">
|
||||
<div class="md:w-140 w-full border-2 rounded-lg px-4 py-8 space-y-4 bg-white">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class={`z-50 absolute -left-3 ${
|
||||
index > 0 &&
|
||||
'before:bg-gradient-to-t before:from-white before:to-transparent before:w-[4px] before:h-[9rem] before:absolute before:-top-36'
|
||||
'before:bg-linear-to-t before:from-white before:to-transparent before:w-[4px] before:h-36 before:absolute before:-top-36'
|
||||
} flex items-center justify-center w-7 h-7 bg-blue-100 rounded-full`}
|
||||
>
|
||||
<div class="w-4 h-4 bg-blue-800 rounded-full z-50" />
|
||||
<div class="w-4 h-4 bg-blue-800 rounded-full z-50"></div>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-blue-800">{card.title}</h1>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', children } = $props();
|
||||
</script>
|
||||
|
||||
<header class="tools-hero-bg py-24 text-center {className} border-b border-slate-100 bg-white">
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</header>
|
||||
|
||||
@@ -3,12 +3,21 @@
|
||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||
import { ALLOWED_IMAGE_TYPES, validateImageUpload } from '@cio/utils/functions/fileValidation';
|
||||
|
||||
export let imageURL = '';
|
||||
export let label = '';
|
||||
export let isRequired = false;
|
||||
export let labelClassName = '';
|
||||
interface Props {
|
||||
imageURL?: string;
|
||||
label?: string;
|
||||
isRequired?: boolean;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
imageURL = $bindable(''),
|
||||
label = '',
|
||||
isRequired = false,
|
||||
labelClassName = ''
|
||||
}: Props = $props();
|
||||
let isUploading = false;
|
||||
let fileInput: HTMLInputElement;
|
||||
let fileInput: HTMLInputElement = $state();
|
||||
|
||||
const onFileSelected = () => {
|
||||
const file = fileInput?.files?.[0];
|
||||
@@ -52,8 +61,8 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div>
|
||||
{#if label}
|
||||
<p class="flex w-full items-center justify-between text-left dark:text-white {labelClassName}">
|
||||
@@ -65,7 +74,7 @@
|
||||
</span>
|
||||
|
||||
{#if imageURL}
|
||||
<span title="clear image" on:click={deleteImage}>
|
||||
<span title="clear image" onclick={deleteImage}>
|
||||
<Trash2 class="fill-red-500 " />
|
||||
</span>
|
||||
{/if}
|
||||
@@ -73,14 +82,14 @@
|
||||
{/if}
|
||||
<div
|
||||
class="flex min-h-[200px] w-full items-center justify-center rounded-md border p-4"
|
||||
on:click={() => fileInput.click()}
|
||||
onclick={() => fileInput.click()}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png,.gif,.webp,image/jpeg,image/png,image/gif,image/webp"
|
||||
style="display: none;"
|
||||
bind:this={fileInput}
|
||||
on:change={onFileSelected}
|
||||
onchange={onFileSelected}
|
||||
disabled={isUploading}
|
||||
required={isRequired}
|
||||
/>
|
||||
|
||||
@@ -9,14 +9,4 @@ import team from './team.svg';
|
||||
import transcription from './printer.svg';
|
||||
import folder from './folder.svg';
|
||||
|
||||
export {
|
||||
flexible,
|
||||
robotArm,
|
||||
thumbsUp,
|
||||
leftFist,
|
||||
rightFist,
|
||||
globe,
|
||||
team,
|
||||
transcription,
|
||||
folder,
|
||||
};
|
||||
export { flexible, robotArm, thumbsUp, leftFist, rightFist, globe, team, transcription, folder };
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
import { submitForm } from '$lib/utils/submit-form';
|
||||
import { Success } from '$lib/components';
|
||||
|
||||
let name = '';
|
||||
let email = '';
|
||||
let title = '';
|
||||
let description = '';
|
||||
let name = $state('');
|
||||
let email = $state('');
|
||||
let title = $state('');
|
||||
let description = $state('');
|
||||
|
||||
let isLoading = false;
|
||||
let errorMessage = '';
|
||||
let sent = false;
|
||||
let isLoading = $state(false);
|
||||
let errorMessage = $state('');
|
||||
let sent = $state(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
const data = { name, email, title, description };
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
import { submitForm } from '$lib/utils/submit-form';
|
||||
import { Success } from '$lib/components';
|
||||
|
||||
let name = '';
|
||||
let email = '';
|
||||
let title = '';
|
||||
let description = '';
|
||||
let name = $state('');
|
||||
let email = $state('');
|
||||
let title = $state('');
|
||||
let description = $state('');
|
||||
|
||||
let isLoading = false;
|
||||
let errorMessage = '';
|
||||
let sent = false;
|
||||
let isLoading = $state(false);
|
||||
let errorMessage = $state('');
|
||||
let sent = $state(false);
|
||||
|
||||
async function handleClick() {
|
||||
const data = { name, email, title, description };
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
src: string;
|
||||
}
|
||||
|
||||
let isAddIcon = false;
|
||||
let hoveredIndex: number | null = null;
|
||||
let isAddIcon = $state(false);
|
||||
let hoveredIndex: number | null = $state(null);
|
||||
|
||||
let avatars: Avatar[] = [
|
||||
{ src: 'avatar_a' },
|
||||
@@ -48,14 +48,14 @@
|
||||
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
|
||||
{#each avatars as avatar, index}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectAvatar(avatar);
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
onmouseenter={() => {
|
||||
hoveredIndex = index;
|
||||
isAddIcon = true;
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
onmouseleave={() => {
|
||||
hoveredIndex = null;
|
||||
isAddIcon = false;
|
||||
}}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
>
|
||||
{#each backgrounds as background}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectBackground(background);
|
||||
}}
|
||||
class="flex w-[30%] items-center shadow-sm hover:scale-110 transition-all duration-300"
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
import { nodeStore, htmlBody, openModal } from './store';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
|
||||
export let isDisabled: boolean;
|
||||
export let isDownloading: boolean;
|
||||
export let text: string = 'Generate Progress Report';
|
||||
interface Props {
|
||||
isDisabled: boolean;
|
||||
isDownloading: boolean;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
let { isDisabled, isDownloading = $bindable(), text = 'Generate Progress Report' }: Props = $props();
|
||||
|
||||
function convertToPng() {
|
||||
isDownloading = true;
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<div class="flex flex-wrap justify-between gap-y-3 mt-3">
|
||||
{#each moods as mood}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectMood(mood);
|
||||
}}
|
||||
class="flex gap-3 items-center shadow-sm rounded-full px-4 py-1 bg-[#F1F6FF] hover:bg-[#0233BD] hover:text-white transition-all duration-700"
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
|
||||
let node: any;
|
||||
|
||||
$: nodeStore.set(node);
|
||||
$effect(() => {
|
||||
nodeStore.set(node);
|
||||
});
|
||||
|
||||
$: rootBgImage = `https://assets.cdn.clsrio.com/progress-report/backgrounds/${
|
||||
let rootBgImage = $derived(`https://assets.cdn.clsrio.com/progress-report/backgrounds/${
|
||||
$htmlBody.background || 'blue_tetiary_background'
|
||||
}.webp`;
|
||||
}.webp`);
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -43,7 +45,7 @@
|
||||
{#if $openModal.fullscreen}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
onclick={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
class="absolute top-4 right-4 p-1.5 rounded-full hover:scale-90 transition-all duration-300 bg-white"
|
||||
>
|
||||
<img src="/free-tools/progress-report/close-icon.svg" alt="" class="" />
|
||||
@@ -51,7 +53,7 @@
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
onclick={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
class="absolute top-4 right-4 w-7 hover:scale-90 transition-all duration-300"
|
||||
>
|
||||
<img src="/free-tools/progress-report/full-screen-icon.svg" alt="" class="" />
|
||||
@@ -118,7 +120,7 @@
|
||||
<div
|
||||
class="progress-range rounded-full w-full h-full"
|
||||
style="background: {$backgroundColor}; width: {$htmlBody.progress}%"
|
||||
/>
|
||||
></div>
|
||||
</div>
|
||||
<p class="text-[12px] font-semibold mt-1">Progress Achieved: {$htmlBody.progress}%</p>
|
||||
</div>
|
||||
|
||||
@@ -2,22 +2,36 @@
|
||||
import { sineInOut } from 'svelte/easing';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
export let activityName: string;
|
||||
export let formattedTime: string;
|
||||
export let displayTime: string;
|
||||
export let resetTimer: () => void;
|
||||
export let toggleSound: () => void;
|
||||
export let toggleTimer: () => void;
|
||||
export let restartTimer: () => void;
|
||||
export let isPaused: boolean;
|
||||
export let soundOn: boolean;
|
||||
interface Props {
|
||||
activityName: string;
|
||||
formattedTime: string;
|
||||
displayTime: string;
|
||||
resetTimer: () => void;
|
||||
toggleSound: () => void;
|
||||
toggleTimer: () => void;
|
||||
restartTimer: () => void;
|
||||
isPaused: boolean;
|
||||
soundOn: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
activityName,
|
||||
formattedTime,
|
||||
displayTime,
|
||||
resetTimer,
|
||||
toggleSound,
|
||||
toggleTimer,
|
||||
restartTimer,
|
||||
isPaused,
|
||||
soundOn
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div transition:fly={{ y: 100, delay: 0, easing: sineInOut }}>
|
||||
<div class="px-6 pt-8 text-center">
|
||||
<button
|
||||
type="button"
|
||||
on:click={resetTimer}
|
||||
onclick={resetTimer}
|
||||
class="text-[10px] text-[#656565] font-semibold uppercase underline"
|
||||
>set timer for another activity</button
|
||||
>
|
||||
@@ -36,7 +50,7 @@
|
||||
<div class="flex">
|
||||
<button
|
||||
type="button"
|
||||
on:click={toggleSound}
|
||||
onclick={toggleSound}
|
||||
class="bg-[#F7F7F7] w-[34%] flex flex-col gap-1 items-center uppercase text-[10px] py-5 md:py-10 rounded-bl-md hover:bg-[#F1F6FF] transition-all duration-500"
|
||||
>
|
||||
{#if soundOn}
|
||||
@@ -48,7 +62,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={toggleTimer}
|
||||
onclick={toggleTimer}
|
||||
class="bg-[#0F62FE] text-white w-[34%] flex flex-col gap-1 items-center uppercase text-[10px] py-5 md:py-10"
|
||||
>
|
||||
{#if isPaused}
|
||||
@@ -60,7 +74,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={restartTimer}
|
||||
onclick={restartTimer}
|
||||
class="bg-[#F7F7F7] w-[34%] flex flex-col gap-1 items-center uppercase text-[10px] py-5 md:py-10 rounded-br-md hover:bg-[#F1F6FF] transition-all duration-500"
|
||||
>
|
||||
<img src="/free-tools/stopwatch/reset-icon.svg" alt="" class="w-7 md:w-12" />
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { sineInOut } from 'svelte/easing';
|
||||
import { fly } from 'svelte/transition';
|
||||
import WinningLine from './winning-line.svelte';
|
||||
@@ -9,18 +11,22 @@
|
||||
click: (rowIndex: number, columnIndex: number) => Game;
|
||||
}
|
||||
|
||||
export let game: Game;
|
||||
export let winner = null;
|
||||
interface Props {
|
||||
game: Game;
|
||||
winner?: any;
|
||||
}
|
||||
|
||||
let customDelay: number;
|
||||
let { game = $bindable(), winner = null }: Props = $props();
|
||||
|
||||
$: {
|
||||
let customDelay: number = $state();
|
||||
|
||||
run(() => {
|
||||
if ($changeToComputer.change) {
|
||||
customDelay = 400;
|
||||
} else {
|
||||
customDelay = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
@@ -31,7 +37,7 @@
|
||||
{#each row as column, columnIndex}
|
||||
<td
|
||||
class="cursor-pointer text-center h-25 w-[1.5em] border-r-10 border-b-10 border-white last:border-r-0"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
game = game.click(rowIndex, columnIndex);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,7 @@ changeToComputer.subscribe((value) => {
|
||||
}
|
||||
});
|
||||
|
||||
export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTie: () => void }) {
|
||||
export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void; onTie: () => void }) {
|
||||
const board = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
@@ -23,7 +23,7 @@ export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTi
|
||||
return {
|
||||
board,
|
||||
|
||||
click(rowIndex:number, columnIndex:number) {
|
||||
click(rowIndex: number, columnIndex: number) {
|
||||
if (!done && !board[rowIndex][columnIndex]) {
|
||||
board[rowIndex][columnIndex] = turn;
|
||||
turn = turn === 'X' ? 'O' : 'X';
|
||||
@@ -43,25 +43,24 @@ export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTi
|
||||
if (activateComp) {
|
||||
const aiMove = getBestMove(board);
|
||||
if (aiMove) {
|
||||
board[aiMove.row][aiMove.column] = 'O';
|
||||
turn = 'X';
|
||||
|
||||
const aiWinner = getWinner(board);
|
||||
if (aiWinner) {
|
||||
onWin(aiWinner);
|
||||
done = true;
|
||||
} else if (isBoardFull(board)) {
|
||||
onTie();
|
||||
scores.update((currentScores) => {
|
||||
return { ...currentScores, draw: currentScores.draw + 1 };
|
||||
});
|
||||
done = true;
|
||||
}
|
||||
board[aiMove.row][aiMove.column] = 'O';
|
||||
turn = 'X';
|
||||
|
||||
const aiWinner = getWinner(board);
|
||||
if (aiWinner) {
|
||||
onWin(aiWinner);
|
||||
done = true;
|
||||
} else if (isBoardFull(board)) {
|
||||
onTie();
|
||||
scores.update((currentScores) => {
|
||||
return { ...currentScores, draw: currentScores.draw + 1 };
|
||||
});
|
||||
done = true;
|
||||
}
|
||||
} else {
|
||||
console.error("AI could not find a valid move.");
|
||||
console.error('AI could not find a valid move.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type {Writable} from 'svelte/store'
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
interface Scores {
|
||||
X: number;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { fade } from 'svelte/transition';
|
||||
export let winner: Winner | null = null;
|
||||
interface Props {
|
||||
winner?: Winner | null;
|
||||
}
|
||||
|
||||
let { winner = null }: Props = $props();
|
||||
|
||||
interface Winner {
|
||||
direction: 'horizontal' | 'vertical' | 'diagonal';
|
||||
@@ -9,13 +15,15 @@
|
||||
diagonal?: 'top-left-to-bottom-right' | 'top-right-to-bottom-left';
|
||||
}
|
||||
|
||||
$: if (winner) {
|
||||
if (winner.direction === 'vertical' && winner.column !== undefined) {
|
||||
document.documentElement.style.setProperty('--col-index', winner.column.toString());
|
||||
} else if (winner.direction === 'horizontal' && winner.row !== undefined) {
|
||||
document.documentElement.style.setProperty('--row-index', winner.row.toString());
|
||||
run(() => {
|
||||
if (winner) {
|
||||
if (winner.direction === 'vertical' && winner.column !== undefined) {
|
||||
document.documentElement.style.setProperty('--col-index', winner.column.toString());
|
||||
} else if (winner.direction === 'horizontal' && winner.row !== undefined) {
|
||||
document.documentElement.style.setProperty('--row-index', winner.row.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if winner}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Re-export all components for convenient importing
|
||||
// Usage: import { Footer, Navigation, Hero } from '$lib/components'
|
||||
export * from './components';
|
||||
|
||||
|
||||
@@ -2,31 +2,31 @@ export const faqs = [
|
||||
{
|
||||
question: 'Who can benefit from ClassroomIO?',
|
||||
answer:
|
||||
'We built ClassroomIO from the ground up for Educators, meaning if you teach online or in a physical classroom you are good to go. It works best for Educators who already have students and want an efficient way to manage their learning experience.',
|
||||
'We built ClassroomIO from the ground up for Educators, meaning if you teach online or in a physical classroom you are good to go. It works best for Educators who already have students and want an efficient way to manage their learning experience.'
|
||||
},
|
||||
{
|
||||
question: 'Will I be able to set up ClassroomIO without technical skills?',
|
||||
answer:
|
||||
'Most definitely. You can use ClassroomIO as a no-code tool to build your own LMS platform. We provide an intuitive user interface and documentation to help you give your students the best learning experience.',
|
||||
'Most definitely. You can use ClassroomIO as a no-code tool to build your own LMS platform. We provide an intuitive user interface and documentation to help you give your students the best learning experience.'
|
||||
},
|
||||
{
|
||||
question: 'How much customization can I do with ClassroomIO?',
|
||||
answer:
|
||||
'Things like theming, organization name, landing pages, content that your students see and many more customization options to come.',
|
||||
'Things like theming, organization name, landing pages, content that your students see and many more customization options to come.'
|
||||
},
|
||||
{
|
||||
question: 'Is ClassroomIO suitable for a large number of students?',
|
||||
answer:
|
||||
'Absolutely. ClassroomIO is built for a large number of students in any course. You can have multiple courses with as many students as you need.',
|
||||
'Absolutely. ClassroomIO is built for a large number of students in any course. You can have multiple courses with as many students as you need.'
|
||||
},
|
||||
{
|
||||
question: 'What kind of team plans does ClassroomIO offer?',
|
||||
answer:
|
||||
'ClassroomIO offers a Team plan designed to cater to organizations of any size. Depending on the plan you select, you can invite other teachers into your organization.',
|
||||
'ClassroomIO offers a Team plan designed to cater to organizations of any size. Depending on the plan you select, you can invite other teachers into your organization.'
|
||||
},
|
||||
{
|
||||
question: 'Can I try ClassroomIO before I buy it?',
|
||||
answer:
|
||||
'Definitely. We’ve got a generous free plan to help you get started with the platform. As your requirements grow, we’ve got other plans that can grow with you.',
|
||||
},
|
||||
'Definitely. We’ve got a generous free plan to help you get started with the platform. As your requirements grow, we’ve got other plans that can grow with you.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
<script lang="ts" context="module">
|
||||
<script>
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<script lang="ts" module>
|
||||
import { CustomImg as img } from '$lib/components';
|
||||
export { img };
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
<div
|
||||
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>
|
||||
<h1 class="text-3xl">Inspire Student Success</h1>
|
||||
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
|
||||
Scale your bootcamp with ClassroomIO's comprehensive toolkit. Create engaging content,
|
||||
foster community, and drive student achievement.
|
||||
@@ -132,7 +132,7 @@
|
||||
/>
|
||||
|
||||
<div class="w-full flex flex-col justify-start align-start gap-y-2">
|
||||
<h1 class="text-xl font-medium">{benefits.title}</h1>
|
||||
<h1 class="text-md">{benefits.title}</h1>
|
||||
|
||||
{#each benefits.points as point}
|
||||
<div class="flex flex-row justify-start items-start gap-x-3">
|
||||
@@ -162,7 +162,7 @@
|
||||
<div
|
||||
class="w-full 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 We're the Perfect Fit</h1>
|
||||
<h1 class="text-3xl">Why We're the Perfect Fit</h1>
|
||||
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
|
||||
Discover why we’re the ideal partner for your bootcamp training needs. Experience
|
||||
innovative solutions designed to elevate student engagement and maximize learning
|
||||
@@ -187,7 +187,7 @@
|
||||
alt="team icon"
|
||||
/>
|
||||
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
|
||||
<h1 class="font-bold text-4xl">{feature.title}</h1>
|
||||
<h1 class="text-2xl">{feature.title}</h1>
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
{feature.description}
|
||||
</p>
|
||||
|
||||
@@ -92,11 +92,11 @@
|
||||
<div
|
||||
class="w-full gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
|
||||
>
|
||||
<Badge class="py-2! px-4!" variant="outline">Our benefits</Badge>
|
||||
<Badge class="py-1! px-4!" variant="outline">Our benefits</Badge>
|
||||
<div
|
||||
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>
|
||||
<h1 class="text-3xl">The customer education toolkit</h1>
|
||||
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
|
||||
Create beautiful, branded landing pages to showcase your courses and get customers up to
|
||||
speed quickly
|
||||
@@ -116,7 +116,7 @@
|
||||
/>
|
||||
|
||||
<div class="w-full flex flex-col justify-start align-start gap-y-2">
|
||||
<h1 class="text-xl font-medium">{benefits.title}</h1>
|
||||
<p class="text-lg">{benefits.title}</p>
|
||||
|
||||
<div class="flex flex-row justify-start items-start gap-x-3">
|
||||
<p class="leading-8 text-gray-500 font-[#656565] font-light">
|
||||
@@ -136,14 +136,14 @@
|
||||
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">
|
||||
<p class="text-sm text-[#4B5563] text-center">
|
||||
Explore our features
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
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>
|
||||
<h1 class="text-3xl">Why Choose Us For Education Needs</h1>
|
||||
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
|
||||
Discover how our innovative solutions can reduce stress, enhance productivity, and achieve
|
||||
your training goals.
|
||||
@@ -154,9 +154,9 @@
|
||||
<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
|
||||
class="w-full flex lg:flex-row {i % 2 !== 0
|
||||
? 'lg:flex-row-reverse'
|
||||
: ''} flex-col lg:justify-between justify-start lg:items-center items-start gap-10"
|
||||
: ''} flex-col lg:justify-evenly justify-start lg:items-center items-start gap-10"
|
||||
>
|
||||
<div
|
||||
class="w-full max-w-sm lg:w-[40%] lg:gap-y-6 gap-y-3 flex flex-col items-start justify-start"
|
||||
@@ -166,7 +166,7 @@
|
||||
alt="team icon"
|
||||
/>
|
||||
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
|
||||
<h1 class="font-bold text-4xl">{feature.title}</h1>
|
||||
<h1 class="text-2xl">{feature.title}</h1>
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
{feature.description}
|
||||
</p>
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<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>
|
||||
<h1 class="text-3xl">Unlock your team's potential</h1>
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
Modern teams require solutions that are easy to use and allows them move quickly.
|
||||
</p>
|
||||
@@ -106,8 +106,8 @@
|
||||
/>
|
||||
|
||||
<div class="w-full flex flex-col justify-start align-start gap-y-2">
|
||||
<h1 class="text-xl font-medium">{benefits.title}</h1>
|
||||
<p class="leading-8 text-gray-500 font-[#656565] font-light">
|
||||
<h1 class="text-lg">{benefits.title}</h1>
|
||||
<p class="text-base leading-8 text-gray-500 font-[#656565] font-light">
|
||||
{benefits.description}
|
||||
</p>
|
||||
</div>
|
||||
@@ -127,7 +127,7 @@
|
||||
<div
|
||||
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>
|
||||
<h1 class="text-3xl">What makes us the right choice</h1>
|
||||
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
|
||||
Discover why we’re the perfect partner for your employee training needs. Experience
|
||||
innovative solutions designed to reduce stress and enhance productivity.
|
||||
@@ -151,7 +151,7 @@
|
||||
alt="team icon"
|
||||
/>
|
||||
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
|
||||
<h1 class="font-bold text-4xl">{feature.title}</h1>
|
||||
<h1 class="text-2xl">{feature.title}</h1>
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
{feature.description}
|
||||
</p>
|
||||
@@ -185,10 +185,10 @@
|
||||
>
|
||||
<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">
|
||||
<h1 class="font-bold text-4xl text-white">
|
||||
<h1 class="text-2xl text-white">
|
||||
Did you know you can request for custom features?
|
||||
</h1>
|
||||
<p class="leading-8 text-[#DEDEDE] text-xl font-light">
|
||||
<p class="leading-8 text-[#DEDEDE] text-base">
|
||||
Companies get the opportunity to request custom features from us.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load = async () => {
|
||||
redirect(307, 'https://docs.google.com/presentation/d/1eqvNPNk167ZcP21gZGVAatdDpWAFL9lfmWJHHrzsVFc');
|
||||
redirect(
|
||||
307,
|
||||
'https://docs.google.com/presentation/d/1eqvNPNk167ZcP21gZGVAatdDpWAFL9lfmWJHHrzsVFc'
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,8 +7,5 @@ export const load = async ({ request }) => {
|
||||
event: 'privacy page visited'
|
||||
});
|
||||
|
||||
redirect(
|
||||
307,
|
||||
'https://app.enzuzo.com/policies/privacy/958fc978-5477-11ee-a03b-7b111830c594'
|
||||
);
|
||||
redirect(307, 'https://app.enzuzo.com/policies/privacy/958fc978-5477-11ee-a03b-7b111830c594');
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import '../app.css';
|
||||
|
||||
export let data;
|
||||
let { children, data } = $props();
|
||||
|
||||
onMount(() => {
|
||||
if (dev) {
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
$: metaTags = extend(true, {}, data.baseMetaTags, page.data.pageMetaTags);
|
||||
let metaTags = $derived(extend(true, {}, data.baseMetaTags, page.data.pageMetaTags));
|
||||
</script>
|
||||
|
||||
<MetaTags {...metaTags} />
|
||||
@@ -29,7 +29,7 @@
|
||||
{#if page.status === 404}
|
||||
<NotFound className="mt-5" />
|
||||
{:else}
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
{/if}
|
||||
</PageTransition>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { PageSignupCTA, PageHeader, BlogListItem } from '$lib/components';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { BlogListItem } from '$lib/components';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<div class=" mt-[10%] md:mt-16">
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<!-- Post -->
|
||||
<div class="prose border-b-2 border-gray-200 pb-4 pt-2">
|
||||
<svelte:component this={data.content} />
|
||||
<data.content />
|
||||
<!-- Tags -->
|
||||
<div class="flex gap-2 py-4">
|
||||
{#each data.meta.tags as tag}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { PageHeader } from '$lib/components';
|
||||
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
|
||||
|
||||
let currentForm: FORM_TYPE_KEY;
|
||||
let currentForm: FORM_TYPE_KEY = $state();
|
||||
|
||||
const formList = [
|
||||
{
|
||||
@@ -72,7 +72,7 @@
|
||||
>
|
||||
{#each formList as list}
|
||||
<button
|
||||
on:click={() => onChange(list.id, list.to)}
|
||||
onclick={() => onChange(list.id, list.to)}
|
||||
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"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { PageHeader } from '$lib/components';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { PageSignupCTA, PageHeader } from '$lib/components';
|
||||
|
||||
let isYearlyPlan = false;
|
||||
let isYearlyPlan = $state(false);
|
||||
|
||||
function toggleIsYearlyPlan() {
|
||||
isYearlyPlan = !isYearlyPlan;
|
||||
@@ -32,14 +32,14 @@
|
||||
? '#5e636b'
|
||||
: '#fff'}"
|
||||
class="rounded-[30px] bg-blue-700 px-3 py-1 text-xs text-white lg:px-4 lg:py-2"
|
||||
on:click={toggleIsYearlyPlan}>Monthly</button
|
||||
onclick={toggleIsYearlyPlan}>Monthly</button
|
||||
>
|
||||
<button
|
||||
style="background-color: {isYearlyPlan ? '#1D4EE2' : ''}; color: {isYearlyPlan
|
||||
? '#fff'
|
||||
: '#5e636b'}"
|
||||
class="rounded-[30px] px-3 py-1 text-xs text-white lg:px-4 lg:py-2"
|
||||
on:click={toggleIsYearlyPlan}>Annually</button
|
||||
onclick={toggleIsYearlyPlan}>Annually</button
|
||||
>
|
||||
<div
|
||||
class="absolute right-[-40%] top-[-85%] scale-[90%] rounded-full bg-[#006600] px-3.5 py-1.5 text-xs text-white lg:right-[-43%] lg:top-[-75%] lg:scale-100"
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<img src="/roadmap-two.svg" alt="" class="hidden lg:block absolute top-0 left-0" />
|
||||
<div class="flex flex-col items-start space-y-10 px-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Latest Features</h1>
|
||||
<h1 class="text-3xl">Latest Features</h1>
|
||||
</div>
|
||||
|
||||
<Timeline items={newFeatures} />
|
||||
@@ -89,7 +89,7 @@
|
||||
<section class="flex flex-col items-center w-full px-4 py-20 border-b-2">
|
||||
<div class="flex flex-col items-start space-y-10 px-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Upcoming Features</h1>
|
||||
<h1 class="text-3xl">Upcoming Features</h1>
|
||||
</div>
|
||||
|
||||
<Timeline items={upcomingFeatures} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { slide } from 'svelte/transition';
|
||||
import { PageSignupCTA, PageHeader } from '$lib/components';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import * as Accordion from '@cio/ui/base/accordion';
|
||||
|
||||
const whyStart = [
|
||||
{
|
||||
@@ -41,35 +41,31 @@
|
||||
}
|
||||
];
|
||||
|
||||
let faqs = [
|
||||
const faqs = [
|
||||
{
|
||||
value: 'tutors',
|
||||
question: 'What kind of tutors does ClassroomIO look for?',
|
||||
answer:
|
||||
'We are looking for all kinds of tutors. The most important thing is that you have some experience teaching and you have proven track record of excellent communication',
|
||||
showAnswer: false
|
||||
'We are looking for all kinds of tutors. The most important thing is that you have some experience teaching and you have proven track record of excellent communication'
|
||||
},
|
||||
{
|
||||
value: 'equipment',
|
||||
question: 'What equipment do I need to teach on ClassroomIO?',
|
||||
answer: 'You just need internet access and a laptop.',
|
||||
showAnswer: false
|
||||
answer: 'You just need internet access and a laptop.'
|
||||
},
|
||||
{
|
||||
value: 'apply',
|
||||
question: 'What happens after I apply?',
|
||||
answer:
|
||||
'We will get your application and review it. You should get a response from us within 2-5 working days depending on the volume of applications',
|
||||
showAnswer: false
|
||||
'We will get your application and review it. You should get a response from us within 2-5 working days depending on the volume of applications'
|
||||
},
|
||||
{
|
||||
value: 'earn',
|
||||
question: 'How much can I earn?',
|
||||
answer:
|
||||
'That depends on how much your course sells for and how many students buy your course. Your income is limitless, you can keep making money while you sleep.',
|
||||
showAnswer: false
|
||||
'That depends on how much your course sells for and how many students buy your course. Your income is limitless, you can keep making money while you sleep.'
|
||||
}
|
||||
];
|
||||
|
||||
function toggleAnswer(index: number) {
|
||||
faqs[index].showAnswer = !faqs[index].showAnswer;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -88,7 +84,7 @@
|
||||
<section>
|
||||
<PageHeader className="flex items-center justify-evenly px-5 flex-wrap text-left">
|
||||
<div>
|
||||
<h1 class="text-5xl font-bold">Teach with us</h1>
|
||||
<h1 class="text-3xl lg:text-5xl">Teach with us</h1>
|
||||
<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>
|
||||
@@ -108,7 +104,7 @@
|
||||
|
||||
<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">
|
||||
<h1 class="flex items-center justify-center gap-3 pb-28 text-center text-3xl">
|
||||
Why you should teach with us? <img src="/teach/star.svg" alt="" />
|
||||
</h1>
|
||||
|
||||
@@ -119,7 +115,7 @@
|
||||
>
|
||||
<img src={why.src} alt="" class="h-[80px] w-[80px]" />
|
||||
<figcaption>
|
||||
<h1 class="text-base font-bold">{why.header}</h1>
|
||||
<h1 class="text-base">{why.header}</h1>
|
||||
<p class="mt-1 text-sm">
|
||||
{why.subText}
|
||||
</p>
|
||||
@@ -130,14 +126,14 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<h1 class="mb-28 flex items-center justify-center gap-3 text-center text-3xl">
|
||||
How to get started <img src="/teach/thumbs-up.svg" alt="" />
|
||||
</h1>
|
||||
|
||||
<div class="mx-auto mt-5 flex flex-wrap justify-between gap-y-10">
|
||||
{#each steps as step, i}
|
||||
<div class="px-10 py-5 lg:max-w-[40%]">
|
||||
<h1 class="text-2xl font-bold">
|
||||
<h1 class="text-2xl">
|
||||
<span class="mr-3 rounded-md bg-[#F7F7F7] px-2 py-1">{i + 1}</span>
|
||||
{step.title}
|
||||
</h1>
|
||||
@@ -159,7 +155,7 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<h1 class="flex items-center justify-center gap-3 text-center text-2xl">
|
||||
Word from our fufilled facilitators
|
||||
</h1>
|
||||
|
||||
@@ -180,25 +176,21 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<h1 class="text-center text-xl">Frequently asked questions</h1>
|
||||
|
||||
<div class="mt-10">
|
||||
{#each faqs as faq, index}
|
||||
<div class="border-b border-black pb-2 pt-1 lg:px-3">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
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>
|
||||
</button>
|
||||
</div>
|
||||
{#if faq.showAnswer}
|
||||
<div transition:slide class="text-base font-light leading-5">{faq.answer}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<Accordion.Root type="single" class="w-full">
|
||||
{#each faqs as faq}
|
||||
<Accordion.Item value={faq.value}>
|
||||
<Accordion.Trigger class="text-left md:text-center">
|
||||
{faq.question}
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
{faq.answer}
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
{/each}
|
||||
</Accordion.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script>
|
||||
import { PageSignupCTA } from '$lib/components';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -29,7 +36,7 @@
|
||||
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
|
||||
</svelte:head>
|
||||
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
|
||||
<PageSignupCTA
|
||||
header="Scale Your Bootcamp Today with ClassroomIO"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<PageHeader>
|
||||
<!-- added bg-white to keep the background white on devices that are on darkmode -->
|
||||
<div class="bg-white text-left md:text-center md:w-2/4 px-7 md:p-0 mx-auto">
|
||||
<h1 class="text-5xl md:text-6xl font-bold">
|
||||
<h1 class="text-3xl md:text-5xl">
|
||||
Explore our free <span class="text-blue-700">tools</span>
|
||||
</h1>
|
||||
<p class="my-4 md:my-8">
|
||||
@@ -58,7 +58,7 @@
|
||||
</PageHeader>
|
||||
|
||||
<div class="p-10 px-2 md:px-0 bg-white">
|
||||
<h1 class="text-center font-bold text-3xl">Featured Tools</h1>
|
||||
<h1 class="text-center text-2xl">Featured Tools</h1>
|
||||
|
||||
<div class="flex justify-evenly gap-y-8 flex-wrap w-full md:w-[70%] mt-10 mx-auto bg-white">
|
||||
{#each freeTools as tool}
|
||||
@@ -68,7 +68,7 @@
|
||||
>
|
||||
<img class="w-[30%] rounded-l-md" src={tool.src} alt="" />
|
||||
<div class="bg-[#F5F8FE] p-5 rounded-r-md w-[70%]">
|
||||
<h1 class="font-bold text-sm md:text-base">{tool.title}</h1>
|
||||
<h1 class="text-sm md:text-base">{tool.title}</h1>
|
||||
<p class="text-xs text-gray-600 mt-1.5">
|
||||
{tool.subText}
|
||||
</p>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { ToolsHeader } from '$lib/components';
|
||||
|
||||
let namesInput = '';
|
||||
let numNames = 0;
|
||||
let wordCount = 0;
|
||||
let selectedNames: string[] = [];
|
||||
let avatarUrls: string[] = [];
|
||||
let avatarUrlsFetched = false;
|
||||
let namesInput = $state('');
|
||||
let numNames = $state(0);
|
||||
let wordCount = $state(0);
|
||||
let selectedNames: string[] = $state([]);
|
||||
let avatarUrls: string[] = $state([]);
|
||||
let avatarUrlsFetched = $state(false);
|
||||
|
||||
// function to shuffle an array
|
||||
const shuffleArray = (array: string[]) => {
|
||||
@@ -98,7 +100,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: wordCount = namesInput?.split(',').filter((name) => name.trim())?.length ?? 0;
|
||||
run(() => {
|
||||
wordCount = namesInput?.split(',').filter((name) => name.trim())?.length ?? 0;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -123,7 +127,7 @@
|
||||
<section class="bg-white px-5 md:px-0">
|
||||
<ToolsHeader>
|
||||
<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>
|
||||
<h1 class="my-3 text-3xl text-[#040F2D] md:text-5xl">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.
|
||||
@@ -132,24 +136,24 @@
|
||||
|
||||
<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>
|
||||
<h1 class="text-sm">List of names</h1>
|
||||
|
||||
<!-- container -->
|
||||
<div
|
||||
class="relative mt-5 h-[12rem] overflow-hidden rounded-sm border-2 bg-[#F1F2F4] pl-3 pt-2 focus-within:border-[#0233BD]"
|
||||
class="relative mt-5 h-48 overflow-hidden rounded-sm border-2 bg-[#F1F2F4] pl-3 pt-2 focus-within:border-[#0233BD]"
|
||||
>
|
||||
<!-- sidebar -->
|
||||
<div class="absolute right-2 top-3 flex flex-col gap-y-3 md:right-7">
|
||||
<button
|
||||
type="button"
|
||||
on:click={sortRandom}
|
||||
onclick={sortRandom}
|
||||
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}
|
||||
onclick={unsortRandom}
|
||||
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
|
||||
>
|
||||
<img src="/unsort-icon.svg" class="w-2.5" alt="" />
|
||||
@@ -175,7 +179,7 @@
|
||||
<p class="border-b py-4 text-xs">Each name must be separated by a comma</p>
|
||||
|
||||
<div class="mt-10">
|
||||
<h1 class="text-sm font-bold">Number of Names</h1>
|
||||
<h1 class="text-sm">Number of Names</h1>
|
||||
<!-- number input & button -->
|
||||
<div class="mt-5 flex w-full flex-wrap justify-between gap-y-3">
|
||||
<input
|
||||
@@ -185,7 +189,7 @@
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
on:click={selectName}
|
||||
onclick={selectName}
|
||||
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
|
||||
>
|
||||
@@ -196,7 +200,7 @@
|
||||
<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-xl font-bold text-[#0233BD]">Selected Names</h1>
|
||||
<h1 class="text-xl text-[#0233BD]">Selected Names</h1>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -226,12 +230,12 @@
|
||||
<div class="mt-3 flex flex-wrap items-center justify-between md:justify-center md:gap-3">
|
||||
<button
|
||||
type="button"
|
||||
on:click={resetEntry}
|
||||
onclick={resetEntry}
|
||||
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}
|
||||
onclick={selectName}
|
||||
class="w-[45%] rounded-md bg-[#0F62FE] py-2 text-xs text-white md:w-[27%] md:text-sm"
|
||||
>Select other names</button
|
||||
>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { ToolsHeader } from '$lib/components';
|
||||
|
||||
@@ -12,15 +14,15 @@
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
let addInput: HTMLInputElement;
|
||||
let addInput: HTMLInputElement = $state();
|
||||
let buzzSound: HTMLAudioElement;
|
||||
let todoList: TodoItem[] = [];
|
||||
let todoList: TodoItem[] = $state([]);
|
||||
|
||||
let countdownTime: number = 25 * 60;
|
||||
let countdownDisplay: string = '25:00';
|
||||
let timerState: string = 'pomodoro';
|
||||
let countdownDisplay: string = $state('25:00');
|
||||
let timerState: string = $state('pomodoro');
|
||||
|
||||
let isPaused: boolean = true;
|
||||
let isPaused: boolean = $state(true);
|
||||
let timerInterval: any = null;
|
||||
let isVisible: boolean = false;
|
||||
|
||||
@@ -174,7 +176,7 @@
|
||||
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">Pomodoro Timer</h1>
|
||||
<h1 class="my-3 text-3xl text-[#040F2D] md:text-5xl">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
|
||||
@@ -193,7 +195,7 @@
|
||||
<div class="item-center flex justify-evenly">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => setTimerState('pomodoro')}
|
||||
onclick={() => setTimerState('pomodoro')}
|
||||
class:bg-[#3ADFECED]={timerState === 'pomodoro'}
|
||||
class:bg-[#0233BD]={timerState !== 'pomodoro'}
|
||||
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"
|
||||
@@ -201,7 +203,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => setTimerState('long-break')}
|
||||
onclick={() => setTimerState('long-break')}
|
||||
class:bg-[#3ADFECED]={timerState === 'long-break'}
|
||||
class:bg-[#0233BD]={timerState !== 'long-break'}
|
||||
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"
|
||||
@@ -209,7 +211,7 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => setTimerState('short-break')}
|
||||
onclick={() => setTimerState('short-break')}
|
||||
class:bg-[#3ADFECED]={timerState === 'short-break'}
|
||||
class:bg-[#0233BD]={timerState !== 'short-break'}
|
||||
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"
|
||||
@@ -223,7 +225,7 @@
|
||||
<!-- controls -->
|
||||
<div class="flex items-center justify-center gap-7">
|
||||
<!-- reset -->
|
||||
<button type="button" on:click={resetCountdown}>
|
||||
<button type="button" onclick={resetCountdown}>
|
||||
<img
|
||||
src="/free-tools/pomodoro/restart-icon.svg"
|
||||
alt="Restart Icon"
|
||||
@@ -234,14 +236,14 @@
|
||||
<!-- start (i intentionally disabled this button once the user clicks start so they can use the pause button to actually pause the countdown) -->
|
||||
<button
|
||||
type="button"
|
||||
on:click={isPaused ? startCountdown : pauseCountdown}
|
||||
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"
|
||||
onclick={isPaused ? startCountdown : pauseCountdown}
|
||||
class="rounded-md border bg-white px-14 py-3 text-base uppercase text-[#0542CC] transition-all duration-300 hover:border hover:border-white hover:bg-transparent hover:text-white"
|
||||
>
|
||||
{isPaused ? 'Start' : 'Pause'}
|
||||
</button>
|
||||
|
||||
<!-- next -->
|
||||
<button type="button" on:click={nextTimerState}>
|
||||
<button type="button" onclick={nextTimerState}>
|
||||
<img
|
||||
src="/free-tools/pomodoro/timer-play-icon.svg"
|
||||
alt="Play Icon"
|
||||
@@ -266,7 +268,7 @@
|
||||
{#each todoList as todo, i}
|
||||
<div class="border p-5 text-black">
|
||||
{#if todo.isEditing}
|
||||
<form on:submit|preventDefault={() => setEditing(i, false)}>
|
||||
<form onsubmit={preventDefault(() => setEditing(i, false))}>
|
||||
<p class="text-left text-xs font-semibold text-[#656565]">Pomodoro name</p>
|
||||
<input
|
||||
type="text"
|
||||
@@ -277,7 +279,7 @@
|
||||
<div class="mt-5 flex items-center justify-end gap-5">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => deleteTodo(i)}
|
||||
onclick={() => deleteTodo(i)}
|
||||
class="rounded-sm bg-[#F7F7F7] px-5 py-2 text-xs font-semibold"
|
||||
>Discard</button
|
||||
>
|
||||
@@ -295,11 +297,11 @@
|
||||
|
||||
<!-- pen and menu icon -->
|
||||
<div class="relative flex w-[15%] justify-between">
|
||||
<button type="button" on:click={() => setEditing(i, true)}>
|
||||
<button type="button" onclick={() => setEditing(i, true)}>
|
||||
<img src="/free-tools/pomodoro/pen-icon.svg" alt="Pen icon" class="w-5" />
|
||||
</button>
|
||||
|
||||
<button type="button" on:click={() => (todo.isVisible = !todo.isVisible)}>
|
||||
<button type="button" onclick={() => (todo.isVisible = !todo.isVisible)}>
|
||||
<img src="/free-tools/pomodoro/menu-icon.svg" alt="Menu icon" class="w-5" />
|
||||
</button>
|
||||
|
||||
@@ -309,14 +311,14 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => markTodoAsDone(i)}
|
||||
onclick={() => markTodoAsDone(i)}
|
||||
class="w-full px-5 py-3 text-left font-medium"
|
||||
>
|
||||
Mark as done
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => deleteTodo(i)}
|
||||
onclick={() => deleteTodo(i)}
|
||||
class="w-full rounded-b-[5px] bg-red-600 px-5 py-3 text-left font-medium text-white"
|
||||
>
|
||||
Delete
|
||||
@@ -335,7 +337,7 @@
|
||||
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (todo.isPaused) {
|
||||
startCountdown();
|
||||
} else {
|
||||
@@ -373,7 +375,7 @@
|
||||
<!-- todo button -->
|
||||
<button
|
||||
type="button"
|
||||
on:click={addTodo}
|
||||
onclick={addTodo}
|
||||
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
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
import DownloadButton from '$lib/features/tools/progress/download-button.svelte';
|
||||
|
||||
const MAX_CHARS = 160;
|
||||
let remainingChars = MAX_CHARS;
|
||||
let showReport: boolean = false;
|
||||
let showSetter: boolean = true;
|
||||
let remainingChars = $state(MAX_CHARS);
|
||||
let showReport: boolean = $state(false);
|
||||
let showSetter: boolean = $state(true);
|
||||
let isDownloading: boolean = false;
|
||||
let isDisabled: boolean;
|
||||
let isDisabled: boolean = $state();
|
||||
|
||||
// sets the result of the mini validation for the htmlBody store
|
||||
const unsubscribe = isFormComplete.subscribe((value) => {
|
||||
@@ -88,7 +88,7 @@
|
||||
<section class=" w-full px-1 md:w-full md:px-0">
|
||||
<ToolsHeader>
|
||||
<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>
|
||||
<h1 class="my-3 text-3xl text-[#040F2D] md:text-5xl">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>
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="mb-5 flex justify-end md:hidden">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
showSetter = false;
|
||||
showReport = true;
|
||||
}}
|
||||
@@ -130,7 +130,7 @@
|
||||
<input
|
||||
type="text"
|
||||
bind:value={$htmlBody.name}
|
||||
on:input={(event) => handleInputChange(event, 'name')}
|
||||
oninput={(event) => handleInputChange(event, 'name')}
|
||||
placeholder="Enter your name here"
|
||||
class="my-3 w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 text-xs outline-none placeholder:text-sm placeholder:text-[#ADADAD]"
|
||||
/>
|
||||
@@ -141,7 +141,7 @@
|
||||
<p class="text-sm text-[#656565]">Select your mood</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.mood = true)}
|
||||
onclick={() => ($openModal.mood = true)}
|
||||
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}
|
||||
@@ -172,7 +172,7 @@
|
||||
<textarea
|
||||
bind:value={$htmlBody.learning}
|
||||
maxlength={MAX_CHARS}
|
||||
on:input={(event) => handleInputChange(event, 'learning')}
|
||||
oninput={(event) => handleInputChange(event, 'learning')}
|
||||
placeholder="Tell us what you are learning"
|
||||
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>
|
||||
@@ -189,7 +189,7 @@
|
||||
max="100"
|
||||
bind:value={$htmlBody.progress}
|
||||
class="range-input"
|
||||
on:input={(event) => handleInputChange(event, 'progress')}
|
||||
oninput={(event) => handleInputChange(event, 'progress')}
|
||||
style="background: linear-gradient(to right, #0F62FE {$htmlBody.progress}%, #ccc {$htmlBody.progress}%);"
|
||||
/>
|
||||
<p class="text-sm font-semibold">{$htmlBody.progress}%</p>
|
||||
@@ -202,7 +202,7 @@
|
||||
<p class="text-[12px] text-[#656565] md:text-sm">Choose your avatar</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.avatar = true)}
|
||||
onclick={() => ($openModal.avatar = true)}
|
||||
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
|
||||
@@ -223,7 +223,7 @@
|
||||
<p class="text-[12px] text-[#656565] md:text-sm">Choose your background</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.background = true)}
|
||||
onclick={() => ($openModal.background = true)}
|
||||
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
|
||||
@@ -265,19 +265,19 @@
|
||||
<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}
|
||||
onclick={shareOnFacebook}
|
||||
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
||||
>
|
||||
<Facebook size={32} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={shareOnLinkedIn}
|
||||
onclick={shareOnLinkedIn}
|
||||
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
||||
>
|
||||
<Linkedin size={32} />
|
||||
</button>
|
||||
<button type="button" on:click={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
|
||||
<button type="button" onclick={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
|
||||
<img src="/twitter_logo.png" alt="X.com" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -290,7 +290,7 @@
|
||||
<div class="mb-5 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
showReport = false;
|
||||
showSetter = true;
|
||||
}}
|
||||
@@ -313,19 +313,19 @@
|
||||
<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}
|
||||
onclick={shareOnFacebook}
|
||||
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
||||
>
|
||||
<Facebook size={32} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={shareOnLinkedIn}
|
||||
onclick={shareOnLinkedIn}
|
||||
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
||||
>
|
||||
<Linkedin size={32} />
|
||||
</button>
|
||||
<button type="button" on:click={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
|
||||
<button type="button" onclick={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
|
||||
<img src="/twitter_logo.png" alt="X.com" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { sineInOut } from 'svelte/easing';
|
||||
@@ -7,18 +9,18 @@
|
||||
|
||||
let timer: NodeJS.Timeout;
|
||||
let buzzSound: HTMLAudioElement;
|
||||
let isNextStep: boolean = false;
|
||||
let isPaused: boolean = false;
|
||||
let soundOn: boolean = true;
|
||||
let isNextStep: boolean = $state(false);
|
||||
let isPaused: boolean = $state(false);
|
||||
let soundOn: boolean = $state(true);
|
||||
|
||||
let hours: number = 0;
|
||||
let minutes: number = 0;
|
||||
let seconds: number = 0;
|
||||
let hours: number = $state(0);
|
||||
let minutes: number = $state(0);
|
||||
let seconds: number = $state(0);
|
||||
|
||||
let activityName: string = '';
|
||||
let displayTime: string = '00:00:00';
|
||||
let activityName: string = $state('');
|
||||
let displayTime: string = $state('00:00:00');
|
||||
let totalSeconds: number = 0;
|
||||
let formattedTime: string = '00:00';
|
||||
let formattedTime: string = $state('00:00');
|
||||
|
||||
export function toggleTimer() {
|
||||
if (isPaused) {
|
||||
@@ -146,7 +148,7 @@
|
||||
<!-- countdown setter -->
|
||||
{#if !isNextStep}
|
||||
<div transition:fly={{ y: -300, delay: 0, easing: sineInOut }} class="bg-white px-6 py-8">
|
||||
<form on:submit|preventDefault={startTimer}>
|
||||
<form onsubmit={preventDefault(startTimer)}>
|
||||
<div>
|
||||
<h1 class="text-sm font-bold">Name of activity</h1>
|
||||
<input
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
import { scores, changeToComputer } from '$lib/features/tools/tic-tac-toe/store';
|
||||
import { ToolsHeader } from '$lib/components';
|
||||
|
||||
let startGame = false;
|
||||
let message: string | null = null;
|
||||
let winner: Winner | null = null;
|
||||
let game = createGame();
|
||||
let startGame = $state(false);
|
||||
let message: string | null = $state(null);
|
||||
let winner: Winner | null = $state(null);
|
||||
let game = $state(createGame());
|
||||
|
||||
interface Winner {
|
||||
symbol?: string;
|
||||
@@ -114,7 +114,7 @@
|
||||
class="absolute top-[43%] z-[150] flex w-full flex-col items-center justify-center"
|
||||
>
|
||||
<div
|
||||
class="flex h-[8vh] w-full items-center justify-center gap-10 bg-white py-1 font-bold text-[#0233BD] md:h-[13vh]"
|
||||
class="flex h-[8vh] w-full items-center justify-center gap-10 bg-white py-1 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" />
|
||||
@@ -124,8 +124,8 @@
|
||||
|
||||
<button
|
||||
type="button"
|
||||
on:click={playAgain}
|
||||
class="mt-10 rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-xs font-bold md:text-base"
|
||||
onclick={playAgain}
|
||||
class="mt-10 rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-xs md:text-base"
|
||||
>Play Again</button
|
||||
>
|
||||
</div>
|
||||
@@ -136,7 +136,7 @@
|
||||
<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}
|
||||
onclick={changePlayer}
|
||||
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" />
|
||||
@@ -147,7 +147,7 @@
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
on:click={newGame}
|
||||
onclick={newGame}
|
||||
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" />
|
||||
@@ -182,7 +182,7 @@
|
||||
<div class="flex flex-col gap-3">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
changePlayer();
|
||||
startTicTac();
|
||||
}}
|
||||
@@ -192,7 +192,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
startTicTac();
|
||||
}}
|
||||
class="rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-sm font-bold"
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
export let url: string;
|
||||
interface Props {
|
||||
url: string;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { url, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#key url}
|
||||
<div class="transition" in:fade>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/key}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user