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

|

|
||||||
_ClassroomIO Brand Customisations_
|
_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
|
## 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_
|
_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:
|
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
|
### 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.
|
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.
|
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
|
### 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
|
## 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.
|
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
|
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 />
|
<br />
|
||||||
|
|
||||||
<video
|
<video
|
||||||
loop
|
loop
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
preload="auto"
|
preload="auto"
|
||||||
autoPictureInPicture
|
autoPictureInPicture
|
||||||
class="rounded-md" src="https://assets.cdn.clsrio.com/blog/organisation-student-analytics.mp4" controls
|
class="rounded-md" src="https://assets.cdn.clsrio.com/blog/organisation-student-analytics.mp4" controls
|
||||||
>
|
|
||||||
</video>
|
> </video>
|
||||||
|
|
||||||
## Course Student Analytics
|
## 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 />
|
<br />
|
||||||
|
|
||||||
<video loop
|
<video loop
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
preload="auto"
|
preload="auto"
|
||||||
autoPictureInPicture
|
autoPictureInPicture
|
||||||
class="rounded-md"
|
class="rounded-md"
|
||||||
src="https://assets.cdn.clsrio.com/blog/course-students-analytics.mp4" controls
|
src="https://assets.cdn.clsrio.com/blog/course-students-analytics.mp4" controls
|
||||||
>
|
|
||||||
</video>
|
> </video>
|
||||||
|
|
||||||
## What's Next?
|
## What's Next?
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,14 @@
|
|||||||
import { Badge } from '@cio/ui/base/badge';
|
import { Badge } from '@cio/ui/base/badge';
|
||||||
import { formatDate } from '$lib/utils/format-date';
|
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>
|
</script>
|
||||||
|
|
||||||
{#if !isRecommended}
|
{#if !isRecommended}
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
<script>
|
<script>
|
||||||
import { flexible, leftFist, rightFist, robotArm, thumbsUp } from '$lib/emojis';
|
import { flexible, leftFist, rightFist, robotArm, thumbsUp } from '$lib/emojis';
|
||||||
|
|
||||||
export let id = '';
|
/**
|
||||||
export let rightToLeft = false;
|
* @typedef {Object} Props
|
||||||
export let tagline = '';
|
* @property {string} [id]
|
||||||
export let title = '';
|
* @property {boolean} [rightToLeft]
|
||||||
export let description = '';
|
* @property {string} [tagline]
|
||||||
export let video = '';
|
* @property {string} [title]
|
||||||
export let taglineIcon = '';
|
* @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>
|
</script>
|
||||||
|
|
||||||
<section {id} class="w-full py-32 border-b border-x-0 border-t-0 border-gray-200">
|
<section {id} class="w-full py-32 border-b border-x-0 border-t-0 border-gray-200">
|
||||||
@@ -32,15 +48,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<p class="font-medium text-base">{tagline}</p>
|
<p class="font-medium text-base">{tagline}</p>
|
||||||
</div>
|
</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">
|
<p class="leading-8 text-lg text-gray-500">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
{#if $$slots.more}
|
{#if more}
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<p class="leading-8 text-lg text-gray-500">
|
<p class="leading-8 text-lg text-gray-500">
|
||||||
<slot name="more" />
|
{@render more?.()}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let src: string;
|
interface Props {
|
||||||
export let alt: string;
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { src, alt }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import Youtube from '@lucide/svelte/icons/youtube';
|
import Youtube from '@lucide/svelte/icons/youtube';
|
||||||
</script>
|
</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="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">
|
<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" />
|
<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: 'Customers', color: '#0233BD' },
|
||||||
{ label: 'Students', color: '#9747FF' }
|
{ label: 'Students', color: '#9747FF' }
|
||||||
];
|
];
|
||||||
let animate = false;
|
let animate = $state(false);
|
||||||
let titleIndex = 0;
|
let titleIndex = $state(0);
|
||||||
let timeout: NodeJS.Timeout;
|
let timeout: NodeJS.Timeout;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|||||||
@@ -19,4 +19,3 @@ export { default as Testimonial } from './testimonial.svelte';
|
|||||||
export { default as Timeline } from './timeline.svelte';
|
export { default as Timeline } from './timeline.svelte';
|
||||||
export { default as ToolsHeader } from './tools-header.svelte';
|
export { default as ToolsHeader } from './tools-header.svelte';
|
||||||
export { default as UploadWidget } from './upload-widget.svelte';
|
export { default as UploadWidget } from './upload-widget.svelte';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
<script>
|
<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>
|
</script>
|
||||||
|
|
||||||
{#if open}
|
{#if open}
|
||||||
@@ -10,7 +17,7 @@
|
|||||||
<div
|
<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"
|
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>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import { folder, globe, team, transcription } from '$lib/emojis';
|
import { folder, globe, team, transcription } from '$lib/emojis';
|
||||||
import { tools } from '$lib/utils/constants/tools';
|
import { tools } from '$lib/utils/constants/tools';
|
||||||
|
|
||||||
let selected = 0;
|
let selected = $state(0);
|
||||||
let interval: NodeJS.Timer;
|
let interval: NodeJS.Timer = $state();
|
||||||
|
|
||||||
const moreFeatures = [
|
const moreFeatures = [
|
||||||
{
|
{
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
'Extend invitations and manage your teaching institution seamlessly through ClassroomIO.',
|
'Extend invitations and manage your teaching institution seamlessly through ClassroomIO.',
|
||||||
image: '/team-management.png'
|
image: '/team-management.png'
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
icon: transcription,
|
// icon: transcription,
|
||||||
title: 'Auto Video Transcription',
|
// title: 'Auto Video Transcription',
|
||||||
description:
|
// description:
|
||||||
'AI automatically transcribes videos, making content searchable for seamless student access.',
|
// 'AI automatically transcribes videos, making content searchable for seamless student access.',
|
||||||
image: '/video-transcription.png'
|
// image: '/video-transcription.png'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
icon: folder,
|
icon: folder,
|
||||||
title: 'Downloadable Lesson PDF',
|
title: 'Downloadable Lesson PDF',
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<section id="morefeatures" class="py-[10%] px-3 lg:px-0 bg-gray-50">
|
<section id="morefeatures" class="py-[10%] px-3 lg:px-0 bg-gray-50">
|
||||||
<div class="mx-0 lg:mx-[12%]">
|
<div class="mx-0 lg:mx-[12%]">
|
||||||
<div class="w-full lg:w-4/5 mx-4 lg:mx-0 mb-14">
|
<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...
|
There is More...
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-4 text-lg text-gray-500 w-[94%] lg:w-4/5">
|
<p class="mt-4 text-lg text-gray-500 w-[94%] lg:w-4/5">
|
||||||
@@ -67,8 +67,8 @@
|
|||||||
{#each moreFeatures as moreFeature, i}
|
{#each moreFeatures as moreFeature, i}
|
||||||
<button
|
<button
|
||||||
class="p-4 rounded-md {i === selected &&
|
class="p-4 rounded-md {i === selected &&
|
||||||
'selected'} mb-3 text-start min-w-[75vw] lg:min-w-[unset]"
|
'selected'} mb-3 text-start min-w-[75vw] lg:min-w-[unset] cursor-pointer"
|
||||||
on:mouseenter={() => {
|
onclick={() => {
|
||||||
selected = i;
|
selected = i;
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}}
|
}}
|
||||||
@@ -82,11 +82,11 @@
|
|||||||
alt=""
|
alt=""
|
||||||
class="w-8"
|
class="w-8"
|
||||||
/>
|
/>
|
||||||
<p class="text-lg font-semibold">
|
<p class="text-lg">
|
||||||
{moreFeature.title}
|
{moreFeature.title}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p>{moreFeature.description}</p>
|
<p class="text-sm">{moreFeature.description}</p>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
height="100%"
|
height="100%"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
id="image"
|
id="image"
|
||||||
in:fly={{ y: 100, duration: 1000 }}
|
in:fly={{ x: 100, duration: 800 }}
|
||||||
class="w-full flex flex-col items-center"
|
class="w-full flex flex-col items-center"
|
||||||
src={moreFeatures[selected].image}
|
src={moreFeatures[selected].image}
|
||||||
alt={moreFeatures[selected].title}
|
alt={moreFeatures[selected].title}
|
||||||
|
|||||||
@@ -14,13 +14,28 @@
|
|||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
import * as NavigationMenu from '@cio/ui/base/navigation-menu';
|
import * as NavigationMenu from '@cio/ui/base/navigation-menu';
|
||||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
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 showDrawer = false;
|
||||||
let showSolutions = 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() {
|
function handleShowDrawer() {
|
||||||
showDrawer = !showDrawer;
|
showDrawer = !showDrawer;
|
||||||
@@ -76,16 +91,7 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
|
let isSolutionsActive = $derived(!!solutions.some((s) => activeLink.includes(s.key)));
|
||||||
title: string;
|
|
||||||
href: string;
|
|
||||||
subtitle: string;
|
|
||||||
key: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
$: activeLink = page.url.pathname;
|
|
||||||
$: activeHash = page.url.hash;
|
|
||||||
$: isSolutionsActive = solutions.some((s) => activeHash.includes(s.key));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet list_item({ title, subtitle, href, key, class: className, ...restProps }: ListItemProps)}
|
{#snippet list_item({ title, subtitle, href, key, class: className, ...restProps }: ListItemProps)}
|
||||||
@@ -140,11 +146,14 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<section class="hidden lg:block">
|
<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.List class="flex w-full items-center justify-center gap-1">
|
||||||
<NavigationMenu.Item>
|
<NavigationMenu.Item>
|
||||||
<NavigationMenu.Trigger
|
<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>
|
<a href="/" class="no-underline"> Solutions </a>
|
||||||
</NavigationMenu.Trigger>
|
</NavigationMenu.Trigger>
|
||||||
@@ -164,7 +173,7 @@
|
|||||||
|
|
||||||
<NavigationMenu.Item>
|
<NavigationMenu.Item>
|
||||||
<NavigationMenu.Trigger
|
<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>
|
<a href="/tools" class="no-underline"> Free Tools </a>
|
||||||
</NavigationMenu.Trigger>
|
</NavigationMenu.Trigger>
|
||||||
@@ -187,11 +196,10 @@
|
|||||||
{#snippet child()}
|
{#snippet child()}
|
||||||
<a
|
<a
|
||||||
href="/blog"
|
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(
|
class={cn(
|
||||||
'/blog'
|
'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'
|
||||||
? 'bg-gray-100'
|
)}
|
||||||
: ''}"
|
|
||||||
>
|
>
|
||||||
Blog
|
Blog
|
||||||
</a>
|
</a>
|
||||||
@@ -204,11 +212,10 @@
|
|||||||
{#snippet child()}
|
{#snippet child()}
|
||||||
<a
|
<a
|
||||||
href="/pricing"
|
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(
|
class={cn(
|
||||||
'/pricing'
|
'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'
|
||||||
? 'bg-gray-100'
|
)}
|
||||||
: ''}"
|
|
||||||
>
|
>
|
||||||
Pricing
|
Pricing
|
||||||
</a>
|
</a>
|
||||||
@@ -250,7 +257,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</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} />
|
<Menu size={24} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -262,7 +269,7 @@
|
|||||||
>
|
>
|
||||||
<div class="mb-5 flex justify-between py-2">
|
<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%]" />
|
<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} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -270,9 +277,11 @@
|
|||||||
<ul class="flex w-full flex-col items-center justify-between lg:flex-row">
|
<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">
|
<li class="w-full cursor-pointer text-sm font-semibold text-gray-800 md:text-lg">
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100"
|
class={cn(
|
||||||
on:click={handleShowDrawer}
|
"flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100",
|
||||||
class:active={isSolutionsActive}
|
isSolutionsActive && 'bg-gray-100'
|
||||||
|
)}
|
||||||
|
onclick={handleShowDrawer}
|
||||||
>
|
>
|
||||||
Our Superpowers <ChevronDown />
|
Our Superpowers <ChevronDown />
|
||||||
</button>
|
</button>
|
||||||
@@ -281,7 +290,7 @@
|
|||||||
{#each solutions as solution}
|
{#each solutions as solution}
|
||||||
<a
|
<a
|
||||||
href="/{solution.key}"
|
href="/{solution.key}"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
handleShowSolutions();
|
handleShowSolutions();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -297,37 +306,43 @@
|
|||||||
</li>
|
</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"
|
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"
|
href="/tools"
|
||||||
>
|
>
|
||||||
<li>Free Tools</li>
|
<li>Free Tools</li>
|
||||||
</a>
|
</a>
|
||||||
<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"
|
class={cn(
|
||||||
on:click={() => {
|
"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();
|
handleShowSolutions();
|
||||||
}}
|
}}
|
||||||
class:active={activeLink.startsWith('/blog')}
|
|
||||||
href="/blog"
|
href="/blog"
|
||||||
>
|
>
|
||||||
<li>Blog</li>
|
<li>Blog</li>
|
||||||
</a>
|
</a>
|
||||||
<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"
|
class={cn(
|
||||||
on:click={() => {
|
"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();
|
handleShowSolutions();
|
||||||
}}
|
}}
|
||||||
href="/pricing"
|
href="/pricing"
|
||||||
class:active={activeLink.startsWith('/pricing')}
|
|
||||||
>
|
>
|
||||||
<li>Pricing</li>
|
<li>Pricing</li>
|
||||||
</a>
|
</a>
|
||||||
<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"
|
class={cn(
|
||||||
on:click={() => {
|
"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();
|
handleShowSolutions();
|
||||||
}}
|
}}
|
||||||
class:active={activeHash.includes('morefeatures')}
|
|
||||||
href="/#morefeatures"
|
href="/#morefeatures"
|
||||||
>
|
>
|
||||||
<li>More features</li>
|
<li>More features</li>
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
import SearchXIcon from '@lucide/svelte/icons/search-x';
|
import SearchXIcon from '@lucide/svelte/icons/search-x';
|
||||||
import { Button } from '@cio/ui/base/button';
|
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() {
|
function handleClick() {
|
||||||
if (!isOrg) {
|
if (!isOrg) {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
export let className = '';
|
/**
|
||||||
|
* @typedef {Object} Props
|
||||||
|
* @property {string} [className]
|
||||||
|
* @property {import('svelte').Snippet} [children]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Props} */
|
||||||
|
let { className = '', children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="hero-bg w-full border-b border-slate-100 pb-16 pt-32 lg:min-h-[20rem] {className}">
|
<header class="hero-bg w-full border-b border-slate-100 pb-16 pt-32 lg:min-h-[20rem] {className}">
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h3
|
<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}
|
{header}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-6 text-xl text-gray-300 lg:text-lg">
|
<p class="mt-6 text-md text-gray-300">
|
||||||
{subText}
|
{subText}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
import { embedSenjaWidget } from '@cio/utils/senja';
|
import { embedSenjaWidget } from '@cio/utils/senja';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let id = '';
|
interface Props {
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id = '' }: Props = $props();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
embedSenjaWidget(id);
|
embedSenjaWidget(id);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div
|
<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]"
|
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>
|
<h1 class="font-bold text-3xl">Your message has been sent 🎉</h1>
|
||||||
<p class="w-[70%] font-normal text-sm">
|
<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
|
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">
|
<script lang="ts">
|
||||||
export let items: {
|
interface Props {
|
||||||
|
items?: {
|
||||||
title: string;
|
title: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
}[] = [];
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let { items = [] }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative w-fit flex justify-center space-x-8 md:space-x-10">
|
<div class="relative w-fit flex justify-center space-x-8 md:space-x-10">
|
||||||
<div
|
<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">
|
<div class="space-y-8">
|
||||||
{#each items as card, index}
|
{#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="flex items-center">
|
||||||
<div
|
<div
|
||||||
class={`z-50 absolute -left-3 ${
|
class={`z-50 absolute -left-3 ${
|
||||||
index > 0 &&
|
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`}
|
} 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>
|
</div>
|
||||||
<h1 class="text-2xl font-bold text-blue-800">{card.title}</h1>
|
<h1 class="text-2xl font-bold text-blue-800">{card.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
export let className = '';
|
/**
|
||||||
|
* @typedef {Object} Props
|
||||||
|
* @property {string} [className]
|
||||||
|
* @property {import('svelte').Snippet} [children]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Props} */
|
||||||
|
let { className = '', children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="tools-hero-bg py-24 text-center {className} border-b border-slate-100 bg-white">
|
<header class="tools-hero-bg py-24 text-center {className} border-b border-slate-100 bg-white">
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -3,12 +3,21 @@
|
|||||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
import { ALLOWED_IMAGE_TYPES, validateImageUpload } from '@cio/utils/functions/fileValidation';
|
import { ALLOWED_IMAGE_TYPES, validateImageUpload } from '@cio/utils/functions/fileValidation';
|
||||||
|
|
||||||
export let imageURL = '';
|
interface Props {
|
||||||
export let label = '';
|
imageURL?: string;
|
||||||
export let isRequired = false;
|
label?: string;
|
||||||
export let labelClassName = '';
|
isRequired?: boolean;
|
||||||
|
labelClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
imageURL = $bindable(''),
|
||||||
|
label = '',
|
||||||
|
isRequired = false,
|
||||||
|
labelClassName = ''
|
||||||
|
}: Props = $props();
|
||||||
let isUploading = false;
|
let isUploading = false;
|
||||||
let fileInput: HTMLInputElement;
|
let fileInput: HTMLInputElement = $state();
|
||||||
|
|
||||||
const onFileSelected = () => {
|
const onFileSelected = () => {
|
||||||
const file = fileInput?.files?.[0];
|
const file = fileInput?.files?.[0];
|
||||||
@@ -52,8 +61,8 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div>
|
<div>
|
||||||
{#if label}
|
{#if label}
|
||||||
<p class="flex w-full items-center justify-between text-left dark:text-white {labelClassName}">
|
<p class="flex w-full items-center justify-between text-left dark:text-white {labelClassName}">
|
||||||
@@ -65,7 +74,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{#if imageURL}
|
{#if imageURL}
|
||||||
<span title="clear image" on:click={deleteImage}>
|
<span title="clear image" onclick={deleteImage}>
|
||||||
<Trash2 class="fill-red-500 " />
|
<Trash2 class="fill-red-500 " />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -73,14 +82,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div
|
<div
|
||||||
class="flex min-h-[200px] w-full items-center justify-center rounded-md border p-4"
|
class="flex min-h-[200px] w-full items-center justify-center rounded-md border p-4"
|
||||||
on:click={() => fileInput.click()}
|
onclick={() => fileInput.click()}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept=".jpg,.jpeg,.png,.gif,.webp,image/jpeg,image/png,image/gif,image/webp"
|
accept=".jpg,.jpeg,.png,.gif,.webp,image/jpeg,image/png,image/gif,image/webp"
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
bind:this={fileInput}
|
bind:this={fileInput}
|
||||||
on:change={onFileSelected}
|
onchange={onFileSelected}
|
||||||
disabled={isUploading}
|
disabled={isUploading}
|
||||||
required={isRequired}
|
required={isRequired}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,14 +9,4 @@ import team from './team.svg';
|
|||||||
import transcription from './printer.svg';
|
import transcription from './printer.svg';
|
||||||
import folder from './folder.svg';
|
import folder from './folder.svg';
|
||||||
|
|
||||||
export {
|
export { flexible, robotArm, thumbsUp, leftFist, rightFist, globe, team, transcription, folder };
|
||||||
flexible,
|
|
||||||
robotArm,
|
|
||||||
thumbsUp,
|
|
||||||
leftFist,
|
|
||||||
rightFist,
|
|
||||||
globe,
|
|
||||||
team,
|
|
||||||
transcription,
|
|
||||||
folder,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
import { submitForm } from '$lib/utils/submit-form';
|
import { submitForm } from '$lib/utils/submit-form';
|
||||||
import { Success } from '$lib/components';
|
import { Success } from '$lib/components';
|
||||||
|
|
||||||
let name = '';
|
let name = $state('');
|
||||||
let email = '';
|
let email = $state('');
|
||||||
let title = '';
|
let title = $state('');
|
||||||
let description = '';
|
let description = $state('');
|
||||||
|
|
||||||
let isLoading = false;
|
let isLoading = $state(false);
|
||||||
let errorMessage = '';
|
let errorMessage = $state('');
|
||||||
let sent = false;
|
let sent = $state(false);
|
||||||
|
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
const data = { name, email, title, description };
|
const data = { name, email, title, description };
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
import { submitForm } from '$lib/utils/submit-form';
|
import { submitForm } from '$lib/utils/submit-form';
|
||||||
import { Success } from '$lib/components';
|
import { Success } from '$lib/components';
|
||||||
|
|
||||||
let name = '';
|
let name = $state('');
|
||||||
let email = '';
|
let email = $state('');
|
||||||
let title = '';
|
let title = $state('');
|
||||||
let description = '';
|
let description = $state('');
|
||||||
|
|
||||||
let isLoading = false;
|
let isLoading = $state(false);
|
||||||
let errorMessage = '';
|
let errorMessage = $state('');
|
||||||
let sent = false;
|
let sent = $state(false);
|
||||||
|
|
||||||
async function handleClick() {
|
async function handleClick() {
|
||||||
const data = { name, email, title, description };
|
const data = { name, email, title, description };
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
src: string;
|
src: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let isAddIcon = false;
|
let isAddIcon = $state(false);
|
||||||
let hoveredIndex: number | null = null;
|
let hoveredIndex: number | null = $state(null);
|
||||||
|
|
||||||
let avatars: Avatar[] = [
|
let avatars: Avatar[] = [
|
||||||
{ src: 'avatar_a' },
|
{ src: 'avatar_a' },
|
||||||
@@ -48,14 +48,14 @@
|
|||||||
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
|
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
|
||||||
{#each avatars as avatar, index}
|
{#each avatars as avatar, index}
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
selectAvatar(avatar);
|
selectAvatar(avatar);
|
||||||
}}
|
}}
|
||||||
on:mouseenter={() => {
|
onmouseenter={() => {
|
||||||
hoveredIndex = index;
|
hoveredIndex = index;
|
||||||
isAddIcon = true;
|
isAddIcon = true;
|
||||||
}}
|
}}
|
||||||
on:mouseleave={() => {
|
onmouseleave={() => {
|
||||||
hoveredIndex = null;
|
hoveredIndex = null;
|
||||||
isAddIcon = false;
|
isAddIcon = false;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
>
|
>
|
||||||
{#each backgrounds as background}
|
{#each backgrounds as background}
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
selectBackground(background);
|
selectBackground(background);
|
||||||
}}
|
}}
|
||||||
class="flex w-[30%] items-center shadow-sm hover:scale-110 transition-all duration-300"
|
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 { nodeStore, htmlBody, openModal } from './store';
|
||||||
import { Button } from '@cio/ui/base/button';
|
import { Button } from '@cio/ui/base/button';
|
||||||
|
|
||||||
export let isDisabled: boolean;
|
interface Props {
|
||||||
export let isDownloading: boolean;
|
isDisabled: boolean;
|
||||||
export let text: string = 'Generate Progress Report';
|
isDownloading: boolean;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { isDisabled, isDownloading = $bindable(), text = 'Generate Progress Report' }: Props = $props();
|
||||||
|
|
||||||
function convertToPng() {
|
function convertToPng() {
|
||||||
isDownloading = true;
|
isDownloading = true;
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
<div class="flex flex-wrap justify-between gap-y-3 mt-3">
|
<div class="flex flex-wrap justify-between gap-y-3 mt-3">
|
||||||
{#each moods as mood}
|
{#each moods as mood}
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
selectMood(mood);
|
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"
|
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;
|
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'
|
$htmlBody.background || 'blue_tetiary_background'
|
||||||
}.webp`;
|
}.webp`);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -43,7 +45,7 @@
|
|||||||
{#if $openModal.fullscreen}
|
{#if $openModal.fullscreen}
|
||||||
<button
|
<button
|
||||||
type="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"
|
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="" />
|
<img src="/free-tools/progress-report/close-icon.svg" alt="" class="" />
|
||||||
@@ -51,7 +53,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
type="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"
|
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="" />
|
<img src="/free-tools/progress-report/full-screen-icon.svg" alt="" class="" />
|
||||||
@@ -118,7 +120,7 @@
|
|||||||
<div
|
<div
|
||||||
class="progress-range rounded-full w-full h-full"
|
class="progress-range rounded-full w-full h-full"
|
||||||
style="background: {$backgroundColor}; width: {$htmlBody.progress}%"
|
style="background: {$backgroundColor}; width: {$htmlBody.progress}%"
|
||||||
/>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[12px] font-semibold mt-1">Progress Achieved: {$htmlBody.progress}%</p>
|
<p class="text-[12px] font-semibold mt-1">Progress Achieved: {$htmlBody.progress}%</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,22 +2,36 @@
|
|||||||
import { sineInOut } from 'svelte/easing';
|
import { sineInOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
|
|
||||||
export let activityName: string;
|
interface Props {
|
||||||
export let formattedTime: string;
|
activityName: string;
|
||||||
export let displayTime: string;
|
formattedTime: string;
|
||||||
export let resetTimer: () => void;
|
displayTime: string;
|
||||||
export let toggleSound: () => void;
|
resetTimer: () => void;
|
||||||
export let toggleTimer: () => void;
|
toggleSound: () => void;
|
||||||
export let restartTimer: () => void;
|
toggleTimer: () => void;
|
||||||
export let isPaused: boolean;
|
restartTimer: () => void;
|
||||||
export let soundOn: boolean;
|
isPaused: boolean;
|
||||||
|
soundOn: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
activityName,
|
||||||
|
formattedTime,
|
||||||
|
displayTime,
|
||||||
|
resetTimer,
|
||||||
|
toggleSound,
|
||||||
|
toggleTimer,
|
||||||
|
restartTimer,
|
||||||
|
isPaused,
|
||||||
|
soundOn
|
||||||
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div transition:fly={{ y: 100, delay: 0, easing: sineInOut }}>
|
<div transition:fly={{ y: 100, delay: 0, easing: sineInOut }}>
|
||||||
<div class="px-6 pt-8 text-center">
|
<div class="px-6 pt-8 text-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={resetTimer}
|
onclick={resetTimer}
|
||||||
class="text-[10px] text-[#656565] font-semibold uppercase underline"
|
class="text-[10px] text-[#656565] font-semibold uppercase underline"
|
||||||
>set timer for another activity</button
|
>set timer for another activity</button
|
||||||
>
|
>
|
||||||
@@ -36,7 +50,7 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button
|
<button
|
||||||
type="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"
|
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}
|
{#if soundOn}
|
||||||
@@ -48,7 +62,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="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"
|
class="bg-[#0F62FE] text-white w-[34%] flex flex-col gap-1 items-center uppercase text-[10px] py-5 md:py-10"
|
||||||
>
|
>
|
||||||
{#if isPaused}
|
{#if isPaused}
|
||||||
@@ -60,7 +74,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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" />
|
<img src="/free-tools/stopwatch/reset-icon.svg" alt="" class="w-7 md:w-12" />
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { run } from 'svelte/legacy';
|
||||||
|
|
||||||
import { sineInOut } from 'svelte/easing';
|
import { sineInOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import WinningLine from './winning-line.svelte';
|
import WinningLine from './winning-line.svelte';
|
||||||
@@ -9,18 +11,22 @@
|
|||||||
click: (rowIndex: number, columnIndex: number) => Game;
|
click: (rowIndex: number, columnIndex: number) => Game;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let game: Game;
|
interface Props {
|
||||||
export let winner = null;
|
game: Game;
|
||||||
|
winner?: any;
|
||||||
|
}
|
||||||
|
|
||||||
let customDelay: number;
|
let { game = $bindable(), winner = null }: Props = $props();
|
||||||
|
|
||||||
$: {
|
let customDelay: number = $state();
|
||||||
|
|
||||||
|
run(() => {
|
||||||
if ($changeToComputer.change) {
|
if ($changeToComputer.change) {
|
||||||
customDelay = 400;
|
customDelay = 400;
|
||||||
} else {
|
} else {
|
||||||
customDelay = 0;
|
customDelay = 0;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -31,7 +37,7 @@
|
|||||||
{#each row as column, columnIndex}
|
{#each row as column, columnIndex}
|
||||||
<td
|
<td
|
||||||
class="cursor-pointer text-center h-25 w-[1.5em] border-r-10 border-b-10 border-white last:border-r-0"
|
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);
|
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 = [
|
const board = [
|
||||||
['', '', ''],
|
['', '', ''],
|
||||||
['', '', ''],
|
['', '', ''],
|
||||||
@@ -23,7 +23,7 @@ export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTi
|
|||||||
return {
|
return {
|
||||||
board,
|
board,
|
||||||
|
|
||||||
click(rowIndex:number, columnIndex:number) {
|
click(rowIndex: number, columnIndex: number) {
|
||||||
if (!done && !board[rowIndex][columnIndex]) {
|
if (!done && !board[rowIndex][columnIndex]) {
|
||||||
board[rowIndex][columnIndex] = turn;
|
board[rowIndex][columnIndex] = turn;
|
||||||
turn = turn === 'X' ? 'O' : 'X';
|
turn = turn === 'X' ? 'O' : 'X';
|
||||||
@@ -43,25 +43,24 @@ export function TicTacToe({ onWin, onTie }: { onWin: (winner: any) => void, onTi
|
|||||||
if (activateComp) {
|
if (activateComp) {
|
||||||
const aiMove = getBestMove(board);
|
const aiMove = getBestMove(board);
|
||||||
if (aiMove) {
|
if (aiMove) {
|
||||||
board[aiMove.row][aiMove.column] = 'O';
|
board[aiMove.row][aiMove.column] = 'O';
|
||||||
turn = 'X';
|
turn = 'X';
|
||||||
|
|
||||||
const aiWinner = getWinner(board);
|
const aiWinner = getWinner(board);
|
||||||
if (aiWinner) {
|
if (aiWinner) {
|
||||||
onWin(aiWinner);
|
onWin(aiWinner);
|
||||||
done = true;
|
done = true;
|
||||||
} else if (isBoardFull(board)) {
|
} else if (isBoardFull(board)) {
|
||||||
onTie();
|
onTie();
|
||||||
scores.update((currentScores) => {
|
scores.update((currentScores) => {
|
||||||
return { ...currentScores, draw: currentScores.draw + 1 };
|
return { ...currentScores, draw: currentScores.draw + 1 };
|
||||||
});
|
});
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
} else {
|
} 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 { writable } from 'svelte/store';
|
||||||
import type {Writable} from 'svelte/store'
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
interface Scores {
|
interface Scores {
|
||||||
X: number;
|
X: number;
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { run } from 'svelte/legacy';
|
||||||
|
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
export let winner: Winner | null = null;
|
interface Props {
|
||||||
|
winner?: Winner | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { winner = null }: Props = $props();
|
||||||
|
|
||||||
interface Winner {
|
interface Winner {
|
||||||
direction: 'horizontal' | 'vertical' | 'diagonal';
|
direction: 'horizontal' | 'vertical' | 'diagonal';
|
||||||
@@ -9,13 +15,15 @@
|
|||||||
diagonal?: 'top-left-to-bottom-right' | 'top-right-to-bottom-left';
|
diagonal?: 'top-left-to-bottom-right' | 'top-right-to-bottom-left';
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (winner) {
|
run(() => {
|
||||||
if (winner.direction === 'vertical' && winner.column !== undefined) {
|
if (winner) {
|
||||||
document.documentElement.style.setProperty('--col-index', winner.column.toString());
|
if (winner.direction === 'vertical' && winner.column !== undefined) {
|
||||||
} else if (winner.direction === 'horizontal' && winner.row !== undefined) {
|
document.documentElement.style.setProperty('--col-index', winner.column.toString());
|
||||||
document.documentElement.style.setProperty('--row-index', winner.row.toString());
|
} else if (winner.direction === 'horizontal' && winner.row !== undefined) {
|
||||||
|
document.documentElement.style.setProperty('--row-index', winner.row.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if winner}
|
{#if winner}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// Re-export all components for convenient importing
|
// Re-export all components for convenient importing
|
||||||
// Usage: import { Footer, Navigation, Hero } from '$lib/components'
|
// Usage: import { Footer, Navigation, Hero } from '$lib/components'
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
|
||||||
|
|||||||
@@ -2,31 +2,31 @@ export const faqs = [
|
|||||||
{
|
{
|
||||||
question: 'Who can benefit from ClassroomIO?',
|
question: 'Who can benefit from ClassroomIO?',
|
||||||
answer:
|
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?',
|
question: 'Will I be able to set up ClassroomIO without technical skills?',
|
||||||
answer:
|
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?',
|
question: 'How much customization can I do with ClassroomIO?',
|
||||||
answer:
|
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?',
|
question: 'Is ClassroomIO suitable for a large number of students?',
|
||||||
answer:
|
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?',
|
question: 'What kind of team plans does ClassroomIO offer?',
|
||||||
answer:
|
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?',
|
question: 'Can I try ClassroomIO before I buy it?',
|
||||||
answer:
|
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';
|
import { CustomImg as img } from '$lib/components';
|
||||||
export { img };
|
export { img };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot />
|
{@render children?.()}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
<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"
|
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%]">
|
<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,
|
Scale your bootcamp with ClassroomIO's comprehensive toolkit. Create engaging content,
|
||||||
foster community, and drive student achievement.
|
foster community, and drive student achievement.
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="w-full flex flex-col justify-start align-start gap-y-2">
|
<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}
|
{#each benefits.points as point}
|
||||||
<div class="flex flex-row justify-start items-start gap-x-3">
|
<div class="flex flex-row justify-start items-start gap-x-3">
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
<div
|
<div
|
||||||
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
|
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%]">
|
<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
|
Discover why we’re the ideal partner for your bootcamp training needs. Experience
|
||||||
innovative solutions designed to elevate student engagement and maximize learning
|
innovative solutions designed to elevate student engagement and maximize learning
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
alt="team icon"
|
alt="team icon"
|
||||||
/>
|
/>
|
||||||
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
|
<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">
|
<p class="leading-8 text-lg text-gray-500">
|
||||||
{feature.description}
|
{feature.description}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -92,11 +92,11 @@
|
|||||||
<div
|
<div
|
||||||
class="w-full gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
|
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
|
<div
|
||||||
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
|
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
|
||||||
>
|
>
|
||||||
<h1 class="text-3xl md:text-5xl font-bold">The customer education toolkit</h1>
|
<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%]">
|
<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
|
Create beautiful, branded landing pages to showcase your courses and get customers up to
|
||||||
speed quickly
|
speed quickly
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="w-full flex flex-col justify-start align-start gap-y-2">
|
<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">
|
<div class="flex flex-row justify-start items-start gap-x-3">
|
||||||
<p class="leading-8 text-gray-500 font-[#656565] font-light">
|
<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"
|
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">
|
<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
|
Explore our features
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<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"
|
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%]">
|
<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
|
Discover how our innovative solutions can reduce stress, enhance productivity, and achieve
|
||||||
your training goals.
|
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">
|
<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}
|
{#each featureDetails as feature, i}
|
||||||
<div
|
<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'
|
? '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
|
<div
|
||||||
class="w-full max-w-sm lg:w-[40%] lg:gap-y-6 gap-y-3 flex flex-col items-start justify-start"
|
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"
|
alt="team icon"
|
||||||
/>
|
/>
|
||||||
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
|
<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">
|
<p class="leading-8 text-lg text-gray-500">
|
||||||
{feature.description}
|
{feature.description}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
<Badge variant="outline">Amazing benefits</Badge>
|
<Badge variant="outline">Amazing benefits</Badge>
|
||||||
|
|
||||||
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
|
<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">
|
<p class="leading-8 text-lg text-gray-500">
|
||||||
Modern teams require solutions that are easy to use and allows them move quickly.
|
Modern teams require solutions that are easy to use and allows them move quickly.
|
||||||
</p>
|
</p>
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="w-full flex flex-col justify-start align-start gap-y-2">
|
<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-lg">{benefits.title}</h1>
|
||||||
<p class="leading-8 text-gray-500 font-[#656565] font-light">
|
<p class="text-base leading-8 text-gray-500 font-[#656565] font-light">
|
||||||
{benefits.description}
|
{benefits.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
<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"
|
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%]">
|
<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
|
Discover why we’re the perfect partner for your employee training needs. Experience
|
||||||
innovative solutions designed to reduce stress and enhance productivity.
|
innovative solutions designed to reduce stress and enhance productivity.
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
alt="team icon"
|
alt="team icon"
|
||||||
/>
|
/>
|
||||||
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
|
<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">
|
<p class="leading-8 text-lg text-gray-500">
|
||||||
{feature.description}
|
{feature.description}
|
||||||
</p>
|
</p>
|
||||||
@@ -185,10 +185,10 @@
|
|||||||
>
|
>
|
||||||
<div class="w-full lg:w-[40%] gap-y-4 flex flex-col items-start justify-start">
|
<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">
|
<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?
|
Did you know you can request for custom features?
|
||||||
</h1>
|
</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.
|
Companies get the opportunity to request custom features from us.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const load = async () => {
|
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'
|
event: 'privacy page visited'
|
||||||
});
|
});
|
||||||
|
|
||||||
redirect(
|
redirect(307, 'https://app.enzuzo.com/policies/privacy/958fc978-5477-11ee-a03b-7b111830c594');
|
||||||
307,
|
|
||||||
'https://app.enzuzo.com/policies/privacy/958fc978-5477-11ee-a03b-7b111830c594'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
|
||||||
export let data;
|
let { children, data } = $props();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (dev) {
|
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>
|
</script>
|
||||||
|
|
||||||
<MetaTags {...metaTags} />
|
<MetaTags {...metaTags} />
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
{#if page.status === 404}
|
{#if page.status === 404}
|
||||||
<NotFound className="mt-5" />
|
<NotFound className="mt-5" />
|
||||||
{:else}
|
{:else}
|
||||||
<slot />
|
{@render children?.()}
|
||||||
{/if}
|
{/if}
|
||||||
</PageTransition>
|
</PageTransition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { PageSignupCTA, PageHeader, BlogListItem } from '$lib/components';
|
import { PageSignupCTA, PageHeader, BlogListItem } from '$lib/components';
|
||||||
|
|
||||||
export let data;
|
let { data } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import { BlogListItem } from '$lib/components';
|
import { BlogListItem } from '$lib/components';
|
||||||
|
|
||||||
export let data;
|
let { data } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class=" mt-[10%] md:mt-16">
|
<div class=" mt-[10%] md:mt-16">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
<!-- Post -->
|
<!-- Post -->
|
||||||
<div class="prose border-b-2 border-gray-200 pb-4 pt-2">
|
<div class="prose border-b-2 border-gray-200 pb-4 pt-2">
|
||||||
<svelte:component this={data.content} />
|
<data.content />
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
<div class="flex gap-2 py-4">
|
<div class="flex gap-2 py-4">
|
||||||
{#each data.meta.tags as tag}
|
{#each data.meta.tags as tag}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import { PageHeader } from '$lib/components';
|
import { PageHeader } from '$lib/components';
|
||||||
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
|
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
|
||||||
|
|
||||||
let currentForm: FORM_TYPE_KEY;
|
let currentForm: FORM_TYPE_KEY = $state();
|
||||||
|
|
||||||
const formList = [
|
const formList = [
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
>
|
>
|
||||||
{#each formList as list}
|
{#each formList as list}
|
||||||
<button
|
<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
|
class="w-full rounded-md border-[1.5px] p-3 text-start {currentForm === list.id
|
||||||
? `border-[#0233BD]`
|
? `border-[#0233BD]`
|
||||||
: `border-gray-200`} flex items-center justify-between"
|
: `border-gray-200`} flex items-center justify-between"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { PageHeader } from '$lib/components';
|
import { PageHeader } from '$lib/components';
|
||||||
import { Button } from '@cio/ui/base/button';
|
import { Button } from '@cio/ui/base/button';
|
||||||
|
|
||||||
export let data;
|
let { data } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import { PageSignupCTA, PageHeader } from '$lib/components';
|
import { PageSignupCTA, PageHeader } from '$lib/components';
|
||||||
|
|
||||||
let isYearlyPlan = false;
|
let isYearlyPlan = $state(false);
|
||||||
|
|
||||||
function toggleIsYearlyPlan() {
|
function toggleIsYearlyPlan() {
|
||||||
isYearlyPlan = !isYearlyPlan;
|
isYearlyPlan = !isYearlyPlan;
|
||||||
@@ -32,14 +32,14 @@
|
|||||||
? '#5e636b'
|
? '#5e636b'
|
||||||
: '#fff'}"
|
: '#fff'}"
|
||||||
class="rounded-[30px] bg-blue-700 px-3 py-1 text-xs text-white lg:px-4 lg:py-2"
|
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
|
<button
|
||||||
style="background-color: {isYearlyPlan ? '#1D4EE2' : ''}; color: {isYearlyPlan
|
style="background-color: {isYearlyPlan ? '#1D4EE2' : ''}; color: {isYearlyPlan
|
||||||
? '#fff'
|
? '#fff'
|
||||||
: '#5e636b'}"
|
: '#5e636b'}"
|
||||||
class="rounded-[30px] px-3 py-1 text-xs text-white lg:px-4 lg:py-2"
|
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
|
<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"
|
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" />
|
<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 class="flex flex-col items-start space-y-10 px-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Latest Features</h1>
|
<h1 class="text-3xl">Latest Features</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Timeline items={newFeatures} />
|
<Timeline items={newFeatures} />
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<section class="flex flex-col items-center w-full px-4 py-20 border-b-2">
|
<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 class="flex flex-col items-start space-y-10 px-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Upcoming Features</h1>
|
<h1 class="text-3xl">Upcoming Features</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Timeline items={upcomingFeatures} />
|
<Timeline items={upcomingFeatures} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { slide } from 'svelte/transition';
|
|
||||||
import { PageSignupCTA, PageHeader } from '$lib/components';
|
import { PageSignupCTA, PageHeader } from '$lib/components';
|
||||||
import { Button } from '@cio/ui/base/button';
|
import { Button } from '@cio/ui/base/button';
|
||||||
|
import * as Accordion from '@cio/ui/base/accordion';
|
||||||
|
|
||||||
const whyStart = [
|
const whyStart = [
|
||||||
{
|
{
|
||||||
@@ -41,35 +41,31 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
let faqs = [
|
const faqs = [
|
||||||
{
|
{
|
||||||
|
value: 'tutors',
|
||||||
question: 'What kind of tutors does ClassroomIO look for?',
|
question: 'What kind of tutors does ClassroomIO look for?',
|
||||||
answer:
|
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',
|
'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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
value: 'equipment',
|
||||||
question: 'What equipment do I need to teach on ClassroomIO?',
|
question: 'What equipment do I need to teach on ClassroomIO?',
|
||||||
answer: 'You just need internet access and a laptop.',
|
answer: 'You just need internet access and a laptop.'
|
||||||
showAnswer: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
value: 'apply',
|
||||||
question: 'What happens after I apply?',
|
question: 'What happens after I apply?',
|
||||||
answer:
|
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',
|
'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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
value: 'earn',
|
||||||
question: 'How much can I earn?',
|
question: 'How much can I earn?',
|
||||||
answer:
|
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.',
|
'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
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function toggleAnswer(index: number) {
|
|
||||||
faqs[index].showAnswer = !faqs[index].showAnswer;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -88,7 +84,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<PageHeader className="flex items-center justify-evenly px-5 flex-wrap text-left">
|
<PageHeader className="flex items-center justify-evenly px-5 flex-wrap text-left">
|
||||||
<div>
|
<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">
|
<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
|
With ClassroomIO, you can make side income while teaching the subject you love
|
||||||
</p>
|
</p>
|
||||||
@@ -108,7 +104,7 @@
|
|||||||
|
|
||||||
<div class="mx-auto mt-10">
|
<div class="mx-auto mt-10">
|
||||||
<div class="border-b border-slate-100 px-[10%] pb-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="" />
|
Why you should teach with us? <img src="/teach/star.svg" alt="" />
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -119,7 +115,7 @@
|
|||||||
>
|
>
|
||||||
<img src={why.src} alt="" class="h-[80px] w-[80px]" />
|
<img src={why.src} alt="" class="h-[80px] w-[80px]" />
|
||||||
<figcaption>
|
<figcaption>
|
||||||
<h1 class="text-base font-bold">{why.header}</h1>
|
<h1 class="text-base">{why.header}</h1>
|
||||||
<p class="mt-1 text-sm">
|
<p class="mt-1 text-sm">
|
||||||
{why.subText}
|
{why.subText}
|
||||||
</p>
|
</p>
|
||||||
@@ -130,14 +126,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-10 px-[10%] py-10">
|
<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="" />
|
How to get started <img src="/teach/thumbs-up.svg" alt="" />
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="mx-auto mt-5 flex flex-wrap justify-between gap-y-10">
|
<div class="mx-auto mt-5 flex flex-wrap justify-between gap-y-10">
|
||||||
{#each steps as step, i}
|
{#each steps as step, i}
|
||||||
<div class="px-10 py-5 lg:max-w-[40%]">
|
<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>
|
<span class="mr-3 rounded-md bg-[#F7F7F7] px-2 py-1">{i + 1}</span>
|
||||||
{step.title}
|
{step.title}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -159,7 +155,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-10 h-[75%] bg-[#F5F8FE] px-2 py-12 lg:h-[80%] lg:px-[12%]">
|
<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
|
Word from our fufilled facilitators
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -180,25 +176,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-auto my-10 mt-10 px-10 lg:max-w-[65%] lg:px-[15%]">
|
<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">
|
<div class="mt-10">
|
||||||
{#each faqs as faq, index}
|
<Accordion.Root type="single" class="w-full">
|
||||||
<div class="border-b border-black pb-2 pt-1 lg:px-3">
|
{#each faqs as faq}
|
||||||
<div>
|
<Accordion.Item value={faq.value}>
|
||||||
<button
|
<Accordion.Trigger class="text-left md:text-center">
|
||||||
type="button"
|
{faq.question}
|
||||||
class="my-2 flex w-full justify-between text-left text-sm font-medium md:text-center md:text-lg"
|
</Accordion.Trigger>
|
||||||
on:click={() => toggleAnswer(index)}
|
<Accordion.Content>
|
||||||
>
|
{faq.answer}
|
||||||
{faq.question} <span>{faq.showAnswer ? '-' : '+'}</span>
|
</Accordion.Content>
|
||||||
</button>
|
</Accordion.Item>
|
||||||
</div>
|
{/each}
|
||||||
{#if faq.showAnswer}
|
</Accordion.Root>
|
||||||
<div transition:slide class="text-base font-light leading-5">{faq.answer}</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import { PageSignupCTA } from '$lib/components';
|
import { PageSignupCTA } from '$lib/components';
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Props
|
||||||
|
* @property {import('svelte').Snippet} [children]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Props} */
|
||||||
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -29,7 +36,7 @@
|
|||||||
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
|
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<slot />
|
{@render children?.()}
|
||||||
|
|
||||||
<PageSignupCTA
|
<PageSignupCTA
|
||||||
header="Scale Your Bootcamp Today with ClassroomIO"
|
header="Scale Your Bootcamp Today with ClassroomIO"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<PageHeader>
|
<PageHeader>
|
||||||
<!-- added bg-white to keep the background white on devices that are on darkmode -->
|
<!-- 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">
|
<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>
|
Explore our free <span class="text-blue-700">tools</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="my-4 md:my-8">
|
<p class="my-4 md:my-8">
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<div class="p-10 px-2 md:px-0 bg-white">
|
<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">
|
<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}
|
{#each freeTools as tool}
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
>
|
>
|
||||||
<img class="w-[30%] rounded-l-md" src={tool.src} alt="" />
|
<img class="w-[30%] rounded-l-md" src={tool.src} alt="" />
|
||||||
<div class="bg-[#F5F8FE] p-5 rounded-r-md w-[70%]">
|
<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">
|
<p class="text-xs text-gray-600 mt-1.5">
|
||||||
{tool.subText}
|
{tool.subText}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { run } from 'svelte/legacy';
|
||||||
|
|
||||||
import { ToolsHeader } from '$lib/components';
|
import { ToolsHeader } from '$lib/components';
|
||||||
|
|
||||||
let namesInput = '';
|
let namesInput = $state('');
|
||||||
let numNames = 0;
|
let numNames = $state(0);
|
||||||
let wordCount = 0;
|
let wordCount = $state(0);
|
||||||
let selectedNames: string[] = [];
|
let selectedNames: string[] = $state([]);
|
||||||
let avatarUrls: string[] = [];
|
let avatarUrls: string[] = $state([]);
|
||||||
let avatarUrlsFetched = false;
|
let avatarUrlsFetched = $state(false);
|
||||||
|
|
||||||
// function to shuffle an array
|
// function to shuffle an array
|
||||||
const shuffleArray = (array: string[]) => {
|
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>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -123,7 +127,7 @@
|
|||||||
<section class="bg-white px-5 md:px-0">
|
<section class="bg-white px-5 md:px-0">
|
||||||
<ToolsHeader>
|
<ToolsHeader>
|
||||||
<img src="/free-tools/name-picker.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
|
<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">
|
<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
|
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.
|
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 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%]">
|
<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 -->
|
<!-- container -->
|
||||||
<div
|
<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 -->
|
<!-- sidebar -->
|
||||||
<div class="absolute right-2 top-3 flex flex-col gap-y-3 md:right-7">
|
<div class="absolute right-2 top-3 flex flex-col gap-y-3 md:right-7">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={sortRandom}
|
onclick={sortRandom}
|
||||||
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
|
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
|
||||||
>
|
>
|
||||||
<img src="/sort-icon.svg" class="w-2.5" alt="" />
|
<img src="/sort-icon.svg" class="w-2.5" alt="" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={unsortRandom}
|
onclick={unsortRandom}
|
||||||
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
|
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
|
||||||
>
|
>
|
||||||
<img src="/unsort-icon.svg" class="w-2.5" alt="" />
|
<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>
|
<p class="border-b py-4 text-xs">Each name must be separated by a comma</p>
|
||||||
|
|
||||||
<div class="mt-10">
|
<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 -->
|
<!-- number input & button -->
|
||||||
<div class="mt-5 flex w-full flex-wrap justify-between gap-y-3">
|
<div class="mt-5 flex w-full flex-wrap justify-between gap-y-3">
|
||||||
<input
|
<input
|
||||||
@@ -185,7 +189,7 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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
|
>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">
|
<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="" />
|
<img src="/free-tools/name-picker.svg" class="w-[12%] md:w-[9%]" alt="" />
|
||||||
{#if selectedNames.length > 0}
|
{#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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -226,12 +230,12 @@
|
|||||||
<div class="mt-3 flex flex-wrap items-center justify-between md:justify-center md:gap-3">
|
<div class="mt-3 flex flex-wrap items-center justify-between md:justify-center md:gap-3">
|
||||||
<button
|
<button
|
||||||
type="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
|
class="w-[45%] rounded-md border py-2 text-xs md:w-[20%] md:text-sm">Reset Entries</button
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="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"
|
class="w-[45%] rounded-md bg-[#0F62FE] py-2 text-xs text-white md:w-[27%] md:text-sm"
|
||||||
>Select other names</button
|
>Select other names</button
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { ToolsHeader } from '$lib/components';
|
import { ToolsHeader } from '$lib/components';
|
||||||
|
|
||||||
@@ -12,15 +14,15 @@
|
|||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let addInput: HTMLInputElement;
|
let addInput: HTMLInputElement = $state();
|
||||||
let buzzSound: HTMLAudioElement;
|
let buzzSound: HTMLAudioElement;
|
||||||
let todoList: TodoItem[] = [];
|
let todoList: TodoItem[] = $state([]);
|
||||||
|
|
||||||
let countdownTime: number = 25 * 60;
|
let countdownTime: number = 25 * 60;
|
||||||
let countdownDisplay: string = '25:00';
|
let countdownDisplay: string = $state('25:00');
|
||||||
let timerState: string = 'pomodoro';
|
let timerState: string = $state('pomodoro');
|
||||||
|
|
||||||
let isPaused: boolean = true;
|
let isPaused: boolean = $state(true);
|
||||||
let timerInterval: any = null;
|
let timerInterval: any = null;
|
||||||
let isVisible: boolean = false;
|
let isVisible: boolean = false;
|
||||||
|
|
||||||
@@ -174,7 +176,7 @@
|
|||||||
class="mx-auto w-[15%] rounded-full border md:w-[5%]"
|
class="mx-auto w-[15%] rounded-full border md:w-[5%]"
|
||||||
alt=""
|
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">
|
<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
|
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
|
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">
|
<div class="item-center flex justify-evenly">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => setTimerState('pomodoro')}
|
onclick={() => setTimerState('pomodoro')}
|
||||||
class:bg-[#3ADFECED]={timerState === 'pomodoro'}
|
class:bg-[#3ADFECED]={timerState === 'pomodoro'}
|
||||||
class:bg-[#0233BD]={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"
|
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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => setTimerState('long-break')}
|
onclick={() => setTimerState('long-break')}
|
||||||
class:bg-[#3ADFECED]={timerState === 'long-break'}
|
class:bg-[#3ADFECED]={timerState === 'long-break'}
|
||||||
class:bg-[#0233BD]={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"
|
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>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => setTimerState('short-break')}
|
onclick={() => setTimerState('short-break')}
|
||||||
class:bg-[#3ADFECED]={timerState === 'short-break'}
|
class:bg-[#3ADFECED]={timerState === 'short-break'}
|
||||||
class:bg-[#0233BD]={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"
|
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 -->
|
<!-- controls -->
|
||||||
<div class="flex items-center justify-center gap-7">
|
<div class="flex items-center justify-center gap-7">
|
||||||
<!-- reset -->
|
<!-- reset -->
|
||||||
<button type="button" on:click={resetCountdown}>
|
<button type="button" onclick={resetCountdown}>
|
||||||
<img
|
<img
|
||||||
src="/free-tools/pomodoro/restart-icon.svg"
|
src="/free-tools/pomodoro/restart-icon.svg"
|
||||||
alt="Restart Icon"
|
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) -->
|
<!-- start (i intentionally disabled this button once the user clicks start so they can use the pause button to actually pause the countdown) -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={isPaused ? startCountdown : pauseCountdown}
|
onclick={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"
|
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'}
|
{isPaused ? 'Start' : 'Pause'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- next -->
|
<!-- next -->
|
||||||
<button type="button" on:click={nextTimerState}>
|
<button type="button" onclick={nextTimerState}>
|
||||||
<img
|
<img
|
||||||
src="/free-tools/pomodoro/timer-play-icon.svg"
|
src="/free-tools/pomodoro/timer-play-icon.svg"
|
||||||
alt="Play Icon"
|
alt="Play Icon"
|
||||||
@@ -266,7 +268,7 @@
|
|||||||
{#each todoList as todo, i}
|
{#each todoList as todo, i}
|
||||||
<div class="border p-5 text-black">
|
<div class="border p-5 text-black">
|
||||||
{#if todo.isEditing}
|
{#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>
|
<p class="text-left text-xs font-semibold text-[#656565]">Pomodoro name</p>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -277,7 +279,7 @@
|
|||||||
<div class="mt-5 flex items-center justify-end gap-5">
|
<div class="mt-5 flex items-center justify-end gap-5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => deleteTodo(i)}
|
onclick={() => deleteTodo(i)}
|
||||||
class="rounded-sm bg-[#F7F7F7] px-5 py-2 text-xs font-semibold"
|
class="rounded-sm bg-[#F7F7F7] px-5 py-2 text-xs font-semibold"
|
||||||
>Discard</button
|
>Discard</button
|
||||||
>
|
>
|
||||||
@@ -295,11 +297,11 @@
|
|||||||
|
|
||||||
<!-- pen and menu icon -->
|
<!-- pen and menu icon -->
|
||||||
<div class="relative flex w-[15%] justify-between">
|
<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" />
|
<img src="/free-tools/pomodoro/pen-icon.svg" alt="Pen icon" class="w-5" />
|
||||||
</button>
|
</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" />
|
<img src="/free-tools/pomodoro/menu-icon.svg" alt="Menu icon" class="w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -309,14 +311,14 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => markTodoAsDone(i)}
|
onclick={() => markTodoAsDone(i)}
|
||||||
class="w-full px-5 py-3 text-left font-medium"
|
class="w-full px-5 py-3 text-left font-medium"
|
||||||
>
|
>
|
||||||
Mark as done
|
Mark as done
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="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"
|
class="w-full rounded-b-[5px] bg-red-600 px-5 py-3 text-left font-medium text-white"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -335,7 +337,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
if (todo.isPaused) {
|
if (todo.isPaused) {
|
||||||
startCountdown();
|
startCountdown();
|
||||||
} else {
|
} else {
|
||||||
@@ -373,7 +375,7 @@
|
|||||||
<!-- todo button -->
|
<!-- todo button -->
|
||||||
<button
|
<button
|
||||||
type="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"
|
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
|
Add new item
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
import DownloadButton from '$lib/features/tools/progress/download-button.svelte';
|
import DownloadButton from '$lib/features/tools/progress/download-button.svelte';
|
||||||
|
|
||||||
const MAX_CHARS = 160;
|
const MAX_CHARS = 160;
|
||||||
let remainingChars = MAX_CHARS;
|
let remainingChars = $state(MAX_CHARS);
|
||||||
let showReport: boolean = false;
|
let showReport: boolean = $state(false);
|
||||||
let showSetter: boolean = true;
|
let showSetter: boolean = $state(true);
|
||||||
let isDownloading: boolean = false;
|
let isDownloading: boolean = false;
|
||||||
let isDisabled: boolean;
|
let isDisabled: boolean = $state();
|
||||||
|
|
||||||
// sets the result of the mini validation for the htmlBody store
|
// sets the result of the mini validation for the htmlBody store
|
||||||
const unsubscribe = isFormComplete.subscribe((value) => {
|
const unsubscribe = isFormComplete.subscribe((value) => {
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
<section class=" w-full px-1 md:w-full md:px-0">
|
<section class=" w-full px-1 md:w-full md:px-0">
|
||||||
<ToolsHeader>
|
<ToolsHeader>
|
||||||
<img src="/free-tools/progress-report.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
|
<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">
|
<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
|
Generate cool reports of your learning progress. Share reports with your network for collaborative learning
|
||||||
</p>
|
</p>
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
<div class="mb-5 flex justify-end md:hidden">
|
<div class="mb-5 flex justify-end md:hidden">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
showSetter = false;
|
showSetter = false;
|
||||||
showReport = true;
|
showReport = true;
|
||||||
}}
|
}}
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={$htmlBody.name}
|
bind:value={$htmlBody.name}
|
||||||
on:input={(event) => handleInputChange(event, 'name')}
|
oninput={(event) => handleInputChange(event, 'name')}
|
||||||
placeholder="Enter your name here"
|
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]"
|
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>
|
<p class="text-sm text-[#656565]">Select your mood</p>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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}
|
{#if $htmlBody.mood.text}
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
<textarea
|
<textarea
|
||||||
bind:value={$htmlBody.learning}
|
bind:value={$htmlBody.learning}
|
||||||
maxlength={MAX_CHARS}
|
maxlength={MAX_CHARS}
|
||||||
on:input={(event) => handleInputChange(event, 'learning')}
|
oninput={(event) => handleInputChange(event, 'learning')}
|
||||||
placeholder="Tell us what you are 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]"
|
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>
|
></textarea>
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
max="100"
|
max="100"
|
||||||
bind:value={$htmlBody.progress}
|
bind:value={$htmlBody.progress}
|
||||||
class="range-input"
|
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}%);"
|
style="background: linear-gradient(to right, #0F62FE {$htmlBody.progress}%, #ccc {$htmlBody.progress}%);"
|
||||||
/>
|
/>
|
||||||
<p class="text-sm font-semibold">{$htmlBody.progress}%</p>
|
<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>
|
<p class="text-[12px] text-[#656565] md:text-sm">Choose your avatar</p>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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
|
<img
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
<p class="text-[12px] text-[#656565] md:text-sm">Choose your background</p>
|
<p class="text-[12px] text-[#656565] md:text-sm">Choose your background</p>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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
|
<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%]">
|
<div class="mx-auto mt-2 flex w-[60%] justify-evenly gap-5 rounded-xl border px-5 py-2 md:w-[80%]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={shareOnFacebook}
|
onclick={shareOnFacebook}
|
||||||
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
||||||
>
|
>
|
||||||
<Facebook size={32} />
|
<Facebook size={32} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={shareOnLinkedIn}
|
onclick={shareOnLinkedIn}
|
||||||
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
||||||
>
|
>
|
||||||
<Linkedin size={32} />
|
<Linkedin size={32} />
|
||||||
</button>
|
</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" />
|
<img src="/twitter_logo.png" alt="X.com" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -290,7 +290,7 @@
|
|||||||
<div class="mb-5 flex justify-end">
|
<div class="mb-5 flex justify-end">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
showReport = false;
|
showReport = false;
|
||||||
showSetter = true;
|
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%]">
|
<div class="mx-auto mt-2 flex w-[60%] justify-evenly gap-5 rounded-xl border px-5 py-2 md:w-[80%]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={shareOnFacebook}
|
onclick={shareOnFacebook}
|
||||||
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
||||||
>
|
>
|
||||||
<Facebook size={32} />
|
<Facebook size={32} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={shareOnLinkedIn}
|
onclick={shareOnLinkedIn}
|
||||||
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
class="w-10 transition-all duration-300 hover:scale-[1.2]"
|
||||||
>
|
>
|
||||||
<Linkedin size={32} />
|
<Linkedin size={32} />
|
||||||
</button>
|
</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" />
|
<img src="/twitter_logo.png" alt="X.com" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { sineInOut } from 'svelte/easing';
|
import { sineInOut } from 'svelte/easing';
|
||||||
@@ -7,18 +9,18 @@
|
|||||||
|
|
||||||
let timer: NodeJS.Timeout;
|
let timer: NodeJS.Timeout;
|
||||||
let buzzSound: HTMLAudioElement;
|
let buzzSound: HTMLAudioElement;
|
||||||
let isNextStep: boolean = false;
|
let isNextStep: boolean = $state(false);
|
||||||
let isPaused: boolean = false;
|
let isPaused: boolean = $state(false);
|
||||||
let soundOn: boolean = true;
|
let soundOn: boolean = $state(true);
|
||||||
|
|
||||||
let hours: number = 0;
|
let hours: number = $state(0);
|
||||||
let minutes: number = 0;
|
let minutes: number = $state(0);
|
||||||
let seconds: number = 0;
|
let seconds: number = $state(0);
|
||||||
|
|
||||||
let activityName: string = '';
|
let activityName: string = $state('');
|
||||||
let displayTime: string = '00:00:00';
|
let displayTime: string = $state('00:00:00');
|
||||||
let totalSeconds: number = 0;
|
let totalSeconds: number = 0;
|
||||||
let formattedTime: string = '00:00';
|
let formattedTime: string = $state('00:00');
|
||||||
|
|
||||||
export function toggleTimer() {
|
export function toggleTimer() {
|
||||||
if (isPaused) {
|
if (isPaused) {
|
||||||
@@ -146,7 +148,7 @@
|
|||||||
<!-- countdown setter -->
|
<!-- countdown setter -->
|
||||||
{#if !isNextStep}
|
{#if !isNextStep}
|
||||||
<div transition:fly={{ y: -300, delay: 0, easing: sineInOut }} class="bg-white px-6 py-8">
|
<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>
|
<div>
|
||||||
<h1 class="text-sm font-bold">Name of activity</h1>
|
<h1 class="text-sm font-bold">Name of activity</h1>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
import { scores, changeToComputer } from '$lib/features/tools/tic-tac-toe/store';
|
import { scores, changeToComputer } from '$lib/features/tools/tic-tac-toe/store';
|
||||||
import { ToolsHeader } from '$lib/components';
|
import { ToolsHeader } from '$lib/components';
|
||||||
|
|
||||||
let startGame = false;
|
let startGame = $state(false);
|
||||||
let message: string | null = null;
|
let message: string | null = $state(null);
|
||||||
let winner: Winner | null = null;
|
let winner: Winner | null = $state(null);
|
||||||
let game = createGame();
|
let game = $state(createGame());
|
||||||
|
|
||||||
interface Winner {
|
interface Winner {
|
||||||
symbol?: string;
|
symbol?: string;
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
class="absolute top-[43%] z-[150] flex w-full flex-col items-center justify-center"
|
class="absolute top-[43%] z-[150] flex w-full flex-col items-center justify-center"
|
||||||
>
|
>
|
||||||
<div
|
<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!'}
|
{#if message != 'Tie game!'}
|
||||||
<img src="/free-tools/tic-tac/winner-star-icon.svg" alt="Star icon" class="h-full" />
|
<img src="/free-tools/tic-tac/winner-star-icon.svg" alt="Star icon" class="h-full" />
|
||||||
@@ -124,8 +124,8 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={playAgain}
|
onclick={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"
|
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
|
>Play Again</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
<div class="flex h-full w-full flex-col items-center justify-between border p-5">
|
<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">
|
<header class="flex w-full items-center justify-between">
|
||||||
<button
|
<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"
|
class="flex w-[35%] flex-row items-center gap-2 text-[10px] font-medium leading-3 md:w-28 md:text-xs"
|
||||||
>
|
>
|
||||||
<img src="/free-tools/tic-tac/computer-icon.svg" alt="Computer icon" class="w-4 md:w-6" />
|
<img src="/free-tools/tic-tac/computer-icon.svg" alt="Computer icon" class="w-4 md:w-6" />
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
<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"
|
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" />
|
<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">
|
<div class="flex flex-col gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
changePlayer();
|
changePlayer();
|
||||||
startTicTac();
|
startTicTac();
|
||||||
}}
|
}}
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
startTicTac();
|
startTicTac();
|
||||||
}}
|
}}
|
||||||
class="rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-sm font-bold"
|
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">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
export let url: string;
|
interface Props {
|
||||||
|
url: string;
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { url, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key url}
|
{#key url}
|
||||||
<div class="transition" in:fade>
|
<div class="transition" in:fade>
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user