feat: website release v2 (#616)

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

* feat: website navigation with nav menu from shadcn

* chore: refactor navigation menu storybook

* fix: responsiveness bug

* chore: remove tailwind config file

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

* refactor website

---------

Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Adeyemi Adetunji
2025-12-20 12:44:59 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 690cf147ff
commit e6a3cf9332
108 changed files with 2425 additions and 2169 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
"deno.importMap": "./supabase/functions/import_map.json", "deno.importMap": "./supabase/functions/import_map.json",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"[svelte]": { "[svelte]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "svelte.svelte-vscode"
}, },
"vitest.disableWorkspaceWarning": true, "vitest.disableWorkspaceWarning": true,
"typescript.tsserver.maxTsServerMemory": 4072, "typescript.tsserver.maxTsServerMemory": 4072,
+344
View File
@@ -0,0 +1,344 @@
# Folder Structure Documentation
This document describes the folder structure for the `apps/website` project, specifically focusing on the `src/lib` directory organization.
## Overview
The codebase follows a clear separation of concerns with three main categories:
- **Components**: Presentational UI components (dummy components)
- **Features**: Business logic and functional components
- **Utils**: Utility functions, constants, and shared types
## Directory Structure
```
src/lib/
├── components/ # UI Components (Presentational)
│ ├── index.ts # Centralized exports - use this for imports
│ ├── blog-list-item.svelte
│ ├── core-feature-section.svelte
│ ├── core-features.svelte
│ ├── custom-img.svelte
│ ├── faq.svelte
│ ├── footer.svelte
│ ├── hero.svelte
│ ├── modal.svelte
│ ├── more-features.svelte
│ ├── navigation.svelte
│ ├── not-found.svelte
│ ├── page-header.svelte
│ ├── page-signup-cta.svelte
│ ├── senja-embed.svelte
│ ├── student-dashboard.svelte
│ ├── success.svelte
│ ├── testimonial.svelte
│ ├── timeline.svelte
│ ├── tools-header.svelte
│ └── upload-widget.svelte
│
├── features/ # Features (Business Logic)
│ ├── contact/
│ │ ├── feedback-form.svelte
│ │ └── help-form.svelte
│ └── tools/
│ ├── progress/
│ │ ├── avatar.svelte
│ │ ├── background.svelte
│ │ ├── download-button.svelte
│ │ ├── full-view.svelte
│ │ ├── mood.svelte
│ │ ├── report.svelte
│ │ └── store.ts
│ ├── stopwatch/
│ │ └── timer.svelte
│ └── tic-tac-toe/
│ ├── board.svelte
│ ├── board.ts
│ ├── store.ts
│ └── winning-line.svelte
│
├── utils/ # Utilities
│ ├── constants/
│ │ └── tools.ts
│ ├── posthog/
│ │ └── index.ts
│ ├── faqs.ts
│ ├── format-date.ts
│ ├── is-form-valid.ts
│ ├── submit-form.ts
│ └── types.ts
│
└── index.ts # Root index - re-exports from components
│
└── emojis/ # Assets (Static files)
├── crossed-fingers.svg
├── folder.svg
├── globe.svg
├── index.js
├── left-fist.svg
├── printer.svg
├── right-fist.svg
├── robot-arm.svg
├── team.svg
├── thumbs-up.svg
└── ...
```
## Naming Conventions
### Files and Folders
- **Kebab-case**: All file and folder names use kebab-case (e.g., `blog-list-item.svelte`, `feedback-form.svelte`)
- **Single-file folders**: If a folder contains only one file, it should be flattened to a single file (e.g., `Footer/Footer.svelte` → `components/footer.svelte`)
### Components
- Component files use `.svelte` extension
- Component names match their file names in kebab-case
- Example: `blog-list-item.svelte` exports a component that can be imported as `BlogListItem`
## Directory Purposes
### `/components` - UI Components
**Purpose**: Presentational components with minimal or no business logic. These are "dummy" components that receive props and render UI.
**Characteristics**:
- No API calls or complex state management
- Primarily focused on rendering and styling
- May have simple local state for UI interactions (e.g., open/close modals)
- Reusable across different pages/features
**Examples**:
- `footer.svelte` - Site footer component
- `navigation.svelte` - Site navigation bar
- `hero.svelte` - Hero section component
- `modal.svelte` - Generic modal wrapper
**Import Pattern**:
```svelte
// ✅ CORRECT - Use named imports from the index
import { Footer, Navigation, Hero } from '$lib/components';
// ❌ INCORRECT - Direct file imports are not allowed
// import Footer from '$lib/components/footer.svelte';
```
**Note**: All components must be imported using named imports from `$lib/components`. Direct file imports are not allowed.
### `/features` - Features
**Purpose**: Components and modules that contain business logic, API calls, form handling, state management, or domain-specific functionality.
**Characteristics**:
- Contains business logic and data handling
- May include API calls, form submissions, or data transformations
- Often includes stores, utilities, or helper functions specific to the feature
- Organized by feature domain (e.g., `contact/`, `tools/`)
**Examples**:
- `features/contact/feedback-form.svelte` - Form with submission logic
- `features/tools/progress/store.ts` - State management for progress tool
- `features/tools/tic-tac-toe/board.ts` - Game logic
**Import Pattern**:
```svelte
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
import { htmlBody, openModal } from '$lib/features/tools/progress/store';
```
### `/utils` - Utilities
**Purpose**: Shared utility functions, constants, types, and helper modules used across the application.
**Characteristics**:
- Pure functions or utility modules
- No component-specific logic
- Reusable across different features/components
- May include TypeScript types and interfaces
**Examples**:
- `utils/format-date.ts` - Date formatting utility
- `utils/is-form-valid.ts` - Form validation helper
- `utils/submit-form.ts` - Generic form submission utility
- `utils/types.ts` - Shared TypeScript types
- `utils/constants/tools.ts` - Application constants
**Import Pattern**:
```typescript
import { formatDate } from '$lib/utils/format-date';
import { isFormValid } from '$lib/utils/is-form-valid';
import type { BlogPost } from '$lib/utils/types';
```
## Migration from Old Structure
### Old Structure (Before Refactoring)
```
src/lib/
├── Blog/BlogListItem.svelte
├── Contact/FeedbackForm.svelte, HelpForm.svelte, Sucess.svelte
├── Footer/Footer.svelte
├── Home/*.svelte, faqs.ts
├── Navigation/Navigation.svelte
├── Modal/Modal.svelte
├── PageHeader/PageHeader.svelte
├── PageSignupCTA/index.svelte
├── Senja/Embed.svelte
├── Timeline/Timeline.svelte
├── ToolsHeader/ToolsHeader.svelte
├── UploadWidget/UploadWidget.svelte
├── NotFound/NotFound.svelte
├── custom/img.svelte, index.ts
├── utils/*.ts
└── types.ts
```
### Key Changes
1. **Flattened single-file folders**: Folders with only one file were flattened (e.g., `Footer/Footer.svelte` → `components/footer.svelte`)
2. **Kebab-case conversion**: All PascalCase file names converted to kebab-case (e.g., `BlogListItem.svelte` → `blog-list-item.svelte`)
3. **Separation of concerns**: Components split into `components/` (UI) and `features/` (logic)
4. **Routes components moved**: Capitalized `components/` folders from `routes/tools/*/` moved to `features/tools/*/`
5. **Types moved**: `types.ts` moved to `utils/types.ts`
6. **Fixed typos**: `Sucess.svelte` → `success.svelte`
7. **Centralized component exports**: Created `components/index.ts` to export all components with PascalCase names
8. **Standardized imports**: All component imports now use named imports from `$lib/components` instead of direct file imports
## Import Examples
### Components
```svelte
<script>
// ✅ CORRECT - Named imports from centralized index
import { Footer, Navigation, Hero, Testimonial } from '$lib/components';
// ❌ INCORRECT - Direct file imports are not allowed
// import Footer from '$lib/components/footer.svelte';
</script>
```
**Important**: All components are exported from `components/index.ts` with PascalCase names. Always use named imports from `$lib/components`.
### Features
```svelte
<script>
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
import { htmlBody, openModal } from '$lib/features/tools/progress/store';
import Board from '$lib/features/tools/tic-tac-toe/board.svelte';
</script>
```
### Utils
```svelte
<script>
import { formatDate } from '$lib/utils/format-date';
import { isFormValid } from '$lib/utils/is-form-valid';
import { submitForm } from '$lib/utils/submit-form';
import type { BlogPost } from '$lib/utils/types';
import { tools } from '$lib/utils/constants/tools';
</script>
```
## Best Practices
1. **When to create a component vs feature**:
- **Component**: If it's purely presentational and reusable
- **Feature**: If it contains business logic, API calls, or domain-specific functionality
2. **File naming**:
- Always use kebab-case for file and folder names
- Match component file names with their import names (converted to PascalCase in imports)
3. **Component imports**:
- **Always** import components using named imports from `$lib/components`
- **Never** import directly from component files (e.g., `$lib/components/footer.svelte`)
- All components are exported from `components/index.ts` with PascalCase names
- Example: `import { Footer, Navigation } from '$lib/components'`
4. **Folder organization**:
- Group related features in subfolders (e.g., `features/contact/`, `features/tools/progress/`)
- Keep utility functions organized by purpose in `utils/`
5. **Avoid single-file folders**:
- If a folder contains only one file, flatten it to a single file
- Example: `Footer/Footer.svelte` → `components/footer.svelte`
6. **Type definitions**:
- Shared types go in `utils/types.ts`
- Feature-specific types can be co-located with the feature
7. **Adding new components**:
- Add the component file in kebab-case (e.g., `new-component.svelte`)
- Export it from `components/index.ts` with PascalCase name (e.g., `NewComponent`)
- Import using: `import { NewComponent } from '$lib/components'`
## Routes Structure
Routes that previously had capitalized `components/` folders have been refactored:
**Before**:
```
routes/tools/progress/components/Avatar.svelte
routes/tools/stopwatch/components/Timer.svelte
routes/tools/tic-tac-toe/components/Board.svelte
```
**After**:
```
lib/features/tools/progress/avatar.svelte
lib/features/tools/stopwatch/timer.svelte
lib/features/tools/tic-tac-toe/board.svelte
```
Routes now import from `$lib/features/tools/*/` instead of `./components/`.
## Assets
Static assets like images, SVGs, and other media files remain in their respective locations:
- `emojis/` - Emoji SVG files
- `public/` - Public static assets (images, fonts, etc.)
## Component Index File
All components are exported from `components/index.ts` with PascalCase names:
```typescript
// components/index.ts
export { default as BlogListItem } from './blog-list-item.svelte';
export { default as Footer } from './footer.svelte';
export { default as Navigation } from './navigation.svelte';
// ... etc
```
This allows for clean, consistent imports:
```svelte
import { Footer, Navigation, Hero } from '$lib/components';
```
**Rule**: Direct file imports (e.g., `import Footer from '$lib/components/footer.svelte'`) are **not allowed**. Always use named imports from `$lib/components`.
## Summary
This structure provides:
- ✅ Clear separation between UI and business logic
- ✅ Consistent naming conventions (kebab-case)
- ✅ Flattened structure (no unnecessary single-file folders)
- ✅ Centralized component exports via `index.ts`
- ✅ Standardized import pattern (named imports from `$lib/components`)
- ✅ Easy to navigate and maintain
- ✅ Scalable for future growth
For questions or suggestions about this structure, please refer to the project maintainers.
+16 -13
View File
@@ -12,18 +12,18 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0",
"autoprefixer": "^10.4.15",
"carbon-icons-svelte": "^12.3.0",
"mdsvex": "^0.11.0", "mdsvex": "^0.11.0",
"postcss": "^8.4.29", "postcss": "^8.5.6",
"svelte": "^4.1.2", "postcss-load-config": "^4.0.1",
"svelte-check": "^3.4.3", "svelte": "^5.0.0",
"svelte-sequential-preprocessor": "^2.0.1", "tailwindcss": "^4.1.14",
"tailwindcss": "^3.3.3", "@tailwindcss/typography": "^0.5.9",
"typescript": "^5.0.0", "typescript": "^5.5.0",
"vite": "^5.4.4" "vite": "^5.4.4",
"@cio/tsconfig": "workspace:*",
"@sveltejs/adapter-vercel": "^4.0.0"
}, },
"type": "module", "type": "module",
"engines": { "engines": {
@@ -36,8 +36,11 @@
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",
"remark-toc": "^9.0.0", "remark-toc": "^9.0.0",
"remark-unwrap-images": "^4.0.0", "remark-unwrap-images": "^4.0.0",
"@cio/utils": "workspace:*",
"shiki": "^0.14.7", "shiki": "^0.14.7",
"svelte-meta-tags": "^3.1.2" "svelte-meta-tags": "^3.1.2",
"@tailwindcss/postcss": "^4.1.17",
"@lucide/svelte": "^0.545.0",
"@cio/ui": "workspace:*",
"@cio/utils": "workspace:*"
} }
} }
+3 -4
View File
@@ -1,6 +1,5 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, '@tailwindcss/postcss': {}
autoprefixer: {}, }
}, };
}
+35 -6
View File
@@ -1,13 +1,17 @@
@tailwind base; @import url('https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap');
@tailwind components;
@tailwind utilities; @import 'tailwindcss';
@import '@cio/ui/output.css';
@custom-variant dark (&:where(.dark, .dark *));
body { body {
overflow-x: hidden; overflow-x: hidden;
} }
li { * {
font-family: 'Inter', sans-serif; font-family: 'Geist', sans-serif;
} }
.floating { .floating {
@@ -21,6 +25,7 @@ li {
-webkit-transform: translateY(0rem); -webkit-transform: translateY(0rem);
transform: translateY(0rem); transform: translateY(0rem);
} }
50% { 50% {
-webkit-transform: translateY(1rem); -webkit-transform: translateY(1rem);
transform: translateY(1rem); transform: translateY(1rem);
@@ -33,34 +38,42 @@ li {
-webkit-transform: translateY(0rem); -webkit-transform: translateY(0rem);
transform: translateY(0rem); transform: translateY(0rem);
} }
50% { 50% {
-webkit-transform: translateY(1rem); -webkit-transform: translateY(1rem);
transform: translateY(1rem); transform: translateY(1rem);
} }
} }
.prose { .prose {
font-family: 'Inter', sans-serif; font-family: 'Geist', sans-serif;
} }
.prose :is(h1, h2, h3, h4, h5, h6) { .prose :is(h1, h2, h3, h4, h5, h6) {
font-weight: 700; font-weight: 700;
margin-top: 20px; margin-top: 20px;
} }
.prose :is(h1) { .prose :is(h1) {
font-size: 2em; font-size: 2em;
} }
.prose :is(h2) { .prose :is(h2) {
font-size: 1.5em; font-size: 1.5em;
} }
.prose :is(h3) { .prose :is(h3) {
font-size: 1.17em; font-size: 1.17em;
} }
.prose :is(h4) { .prose :is(h4) {
font-size: 1em; font-size: 1em;
} }
.prose :is(h5) { .prose :is(h5) {
font-size: 0.83em; font-size: 0.83em;
} }
.prose :is(h6) { .prose :is(h6) {
font-size: 0.67em; font-size: 0.67em;
} }
@@ -82,6 +95,7 @@ li {
border-radius: 8px; border-radius: 8px;
tab-size: 2; tab-size: 2;
} }
.prose blockquote { .prose blockquote {
border-left: 4px solid gray; border-left: 4px solid gray;
font-style: italic; font-style: italic;
@@ -89,6 +103,7 @@ li {
padding-left: 16px; padding-left: 16px;
margin: 20px 0; margin: 20px 0;
} }
.prose figure + em { .prose figure + em {
display: block; display: block;
font-size: 14px; font-size: 14px;
@@ -105,11 +120,13 @@ li {
.tools-hero-bg { .tools-hero-bg {
background-image: url('https://assets.cdn.clsrio.com/www/tools-hero-bg.svg'); background-image: url('https://assets.cdn.clsrio.com/www/tools-hero-bg.svg');
} }
@media (min-width: 760px) { @media (min-width: 760px) {
.tools-hero-bg { .tools-hero-bg {
min-height: 350px; min-height: 350px;
} }
} }
.cio-bg-blue { .cio-bg-blue {
background: rgb(2, 51, 189); background: rgb(2, 51, 189);
background-size: cover; background-size: cover;
@@ -122,8 +139,20 @@ li {
border-radius: 0.375rem; border-radius: 0.375rem;
} }
svg.lucide-icon:not(.custom) {
color: #666666;
}
.dark svg.lucide-icon:not(.custom) {
color: #fff;
}
@media (min-width: 640px) { @media (min-width: 640px) {
.embed { .embed {
width: 560px; width: 560px;
} }
} }
body {
--primary: var(--color-blue-700);
}
-22
View File
@@ -1,22 +0,0 @@
<script>
import Renew from 'carbon-icons-svelte/lib/Renew.svelte';
export let label = '';
export let className = '';
export let isLoading = false;
export let isDisabled = false;
export let onClick = () => {};
</script>
<div class="w-full flex items-center justify-center pt-5">
<button
class="bg-[#0233BD] text-white font-medium text-sm py-4 {className}"
disabled={isDisabled}
on:click={onClick}
>
{#if isLoading}
<Renew size={24} class="animate-spin" />
{:else}
{label}
{/if}
</button>
</div>
-7
View File
@@ -1,7 +0,0 @@
<script lang="ts">
export let label: string;
</script>
<p class="whitespace-nowrap bg-gray-200 px-2 py-1 rounded-full text-xs font-normal text-black">
{label}
</p>
@@ -1,70 +0,0 @@
<script>
import Button from '$lib/Button/Button.svelte';
import TextArea from '$lib/Input/TextArea.svelte';
import TextField from '$lib/Input/TextField.svelte';
import { isFormValid } from '$lib/utils/isFormValid';
import { submitForm } from '$lib/utils/submitForm';
import Sucess from '$lib/Contact/Sucess.svelte';
let name = '';
let email = '';
let title = '';
let description = '';
let isLoading = false;
let errorMessage = '';
let sent = false;
const handleClick = async () => {
const data = { name, email, title, description };
if (!isFormValid(data)) {
errorMessage = 'Please fill out all required fields.';
return;
}
isLoading = true;
const formData = {
title: `[FEEDBACK] ${title}`,
description: `My name is ${JSON.stringify(name)}, email is ${JSON.stringify(
email
)}. \n\n${JSON.stringify(description)}.`,
replyTo: email
};
try {
await submitForm(formData);
sent = true;
} catch (error) {
console.log(error);
} finally {
isLoading = false;
}
};
</script>
<div>
{#if sent}
<Sucess />
{:else}
<div class="w-full my-10 space-y-5">
<div class="flex flex-col md:flex-row gap-10">
<TextField label="Your Name" isRequired className="w-full" bind:value={name} />
<TextField label="Your Email" isRequired className="w-full" bind:value={email} />
</div>
<TextField label="Subject" isRequired bind:value={title} />
<TextArea label="Message" isRequired bind:value={description} />
{#if !!errorMessage}
<p class="text-left text-sm text-red-500">{errorMessage}</p>
{/if}
<Button
label="Submit"
className="px-32"
onClick={handleClick}
isDisabled={isLoading}
{isLoading}
/>
</div>
{/if}
</div>
@@ -1,70 +0,0 @@
<script>
import Button from '$lib/Button/Button.svelte';
import TextArea from '$lib/Input/TextArea.svelte';
import TextField from '$lib/Input/TextField.svelte';
import { isFormValid } from '$lib/utils/isFormValid';
import { submitForm } from '$lib/utils/submitForm';
import Sucess from './Sucess.svelte';
let name = '';
let email = '';
let title = '';
let description = '';
let isLoading = false;
let errorMessage = '';
let sent = false;
async function handleClick() {
const data = { name, email, title, description };
if (!isFormValid(data)) {
errorMessage = 'Please fill out all required fields.';
return;
}
isLoading = true;
const formData = {
title: `[HELP] ${title}`,
description: `My name is ${JSON.stringify(name)}, email is ${JSON.stringify(
email
)}. \n\n${JSON.stringify(description)}.`,
replyTo: email
};
try {
await submitForm(formData);
sent = true;
} catch (error) {
console.log(error);
} finally {
isLoading = false;
}
}
</script>
<div>
{#if sent}
<Sucess />
{:else}
<div class="w-full my-10 space-y-5">
<div class="flex flex-col md:flex-row gap-10">
<TextField label="Your Name" isRequired className="w-full" bind:value={name} />
<TextField label="Your Email" isRequired className="w-full" bind:value={email} />
</div>
<TextField label="Subject" isRequired bind:value={title} />
<TextArea label="Message" isRequired bind:value={description} />
{#if !!errorMessage}
<p class="text-left text-sm text-red-500">{errorMessage}</p>
{/if}
<Button
label="Submit"
className="px-32"
onClick={handleClick}
isDisabled={isLoading}
{isLoading}
/>
</div>
{/if}
</div>
-213
View File
@@ -1,213 +0,0 @@
<script>
import LogoYoutube from 'carbon-icons-svelte/lib/LogoYoutube.svelte';
import LogoFacebook from 'carbon-icons-svelte/lib/LogoFacebook.svelte';
</script>
<div class="border-t-[1px] px-[2%] lg:px-[14%]">
<div class="flex flex-col items-center justify-between py-[10%] pl-5 lg:flex-row lg:pl-0">
<div class="w-full min-w-[200px] lg:w-3/12">
<img
width="32"
height="32"
loading="lazy"
src="/logo-192.png"
alt="classroomio logo"
class="mb-5"
/>
<p class="lg:text-base mb-5 mr-2 text-sm leading-7 tracking-wide text-gray-600 lg:leading-7">
The Open Source Learning Management System for Companies
<br />
ClassroomIO is the easiest way to grow your teaching business.
</p>
<!-- <a
href="https://www.producthunt.com/posts/classroomio?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-classroomio"
target="_blank"
><img
loading="lazy"
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=425236&theme=light"
alt="ClassroomIO - The&#0032;Open&#0032;Source&#0032;Platform&#0032;for&#0032;Tech&#0032;Bootcamps | Product Hunt"
style="width: 250px; height: 54px;"
width="250"
height="54"
/></a
> -->
<div class="mt-5 flex items-center gap-3 lg:mt-10 lg:gap-7">
<a href="https://www.facebook.com/classroomiohq" target="_blank" aria-label="Facebook Link">
<LogoFacebook
class="scale-90 cursor-pointer fill-gray-400 transition-all hover:scale-110 hover:fill-gray-500 lg:scale-150"
size={20}
/>
</a>
<a href="https://twitter.com/classroomio" target="_blank" aria-label="Twitter Link">
<img
width="20"
height="20"
loading="lazy"
src="/twitter.svg"
alt=""
class="scale-90 cursor-pointer transition-all hover:scale-110 hover:fill-gray-500 lg:scale-150"
/>
</a>
<a href="https://www.youtube.com/@ClassroomIO" target="_blank" aria-label="Youtube Link">
<LogoYoutube
class="scale-90 cursor-pointer fill-gray-400 transition-all hover:scale-110 hover:fill-gray-500 lg:scale-150"
size={20}
/>
</a>
</div>
</div>
<nav
class="mt-16 flex w-full min-w-[400px] flex-col items-start justify-end gap-12 space-y-6 lg:mt-0 lg:w-3/4 lg:flex-row lg:space-y-0"
>
<div class="max-w-[120px]">
<p class="lg:text-base mb-3 text-base font-semibold leading-6 text-gray-900 lg:mb-10">
Product
</p>
<ul class="space-y-2 lg:space-y-4">
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/#coursemanagement"> Course Management </a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/#customization"> Customizations </a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/#collaboration"> Forum </a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/#ai"> AI Lesson Planner </a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/#student-dashboard"> Student Dashboard </a>
</li>
</ul>
</div>
<div class="max-w-[120px]">
<p class="lg:text-base mb-3 text-base font-semibold leading-6 text-gray-900 lg:mb-10">
Solutions
</p>
<ul class="space-y-2 lg:space-y-4">
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/employee-training">Employee Training</a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/bootcamps">Bootcamps</a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/customer-education">Customer Education</a>
</li>
</ul>
</div>
<div class="max-w-[120px]">
<p class="lg:text-base mb-3 text-base font-semibold leading-6 text-gray-900 lg:mb-10">
Resources
</p>
<ul class="space-y-2 lg:space-y-4">
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/contact">Contact us</a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/teach">Teach with us</a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a target="_blank" href="https://classroomio.com/docs/"> Help center </a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a target="_blank" href="https://classroomio.com/docs/quickstart/signup">
Getting Started
</a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/oss-friends"> OSS Friends </a>
</li>
</ul>
</div>
<div class="max-w-[120px]">
<p class="lg:text-base mb-3 text-base font-semibold leading-6 text-gray-900 lg:mb-10">
Company
</p>
<ul class="space-y-2 lg:space-y-4">
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="/roadmap"> Roadmap </a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a href="mailto:[email protected]" target="_blank"> Support </a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a
href="https://app.enzuzo.com/policies/privacy/958fc978-5477-11ee-a03b-7b111830c594"
target="_blank"
>
Privacy policy
</a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a target="_blank" href="https://classroomio.com/docs/quickstart/signup">
Getting Started
</a>
</li>
<li
class="lg:text-base text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900"
>
<a
href="https://app.enzuzo.com/policies/tos/958fc978-5477-11ee-a03b-7b111830c594"
target="_blank"
>
Terms of use
</a>
</li>
</ul>
</div>
</nav>
</div>
<footer
class="lg:text-base border-t-[1px] py-[3%] text-center text-xs font-light leading-5 text-gray-500 lg:text-left"
>
<p>All rights reserved.</p>
<p class="flex items-center">
ClassroomIO © {new Date().getFullYear()} by
<a href="https://twitter.com/rotimi_best" target="_blank" class="mx-1 text-blue-700 underline"
>Rotimi Best</a
>
from
<img loading="lazy" alt="Nigerian Flag" src="/ng.png" class="ml-2 h-3 w-3" />
</p>
</footer>
</div>
-42
View File
@@ -1,42 +0,0 @@
<script>
import { faqs } from './faqs';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
</script>
<div class="mb-[15%] mt-[5%]">
<header class="mb-[5%] px-[8%] text-center">
<h1 class="mt-2 text-4xl font-medium tracking-tight text-gray-900 lg:text-4xl">
Questions & Answers
</h1>
<p
class="mt-4 text-lg font-normal tracking-normal text-gray-600 lg:px-[20%] lg:text-xl lg:tracking-wide"
>
Can’t find the answer you’re looking for?
<a class="font-semibold text-blue-700" href="mailto:[email protected]" target="_blank"
>Shoot us an email</a
> and we’ll get back to you ASAP.
</p>
</header>
<div class="mt-[20%] grid grid-cols-1 gap-10 lg:m-0 lg:grid-cols-3 lg:gap-20 lg:px-[13%]">
{#each faqs as faq}
<div class="max-w-[100%] px-6 lg:p-0">
<h1 class="text:md lg:text-base font-semibold leading-8 text-gray-800">
{faq.question}
</h1>
<p
class="text:md lg:text-base mt-2 font-light leading-7 tracking-wide text-gray-600 lg:leading-9"
>
{faq.answer}
</p>
</div>
{/each}
</div>
<PageSignupCTA
header="Your Custom Academy – Up & Running in Minutes!"
subText="Try before you buy. No credit card required."
btnLabel="Book a demo"
link="classroomio/demo"
/>
</div>
@@ -1,28 +0,0 @@
<script>
import SenjaEmbed from '$lib/Senja/Embed.svelte';
</script>
<section id="testimonial" class="flex flex-col items-center py-[5%] px-3 lg:px-0">
<h1
class="mx-auto text-3xl md:text-4xl lg:text-6xl font-normal leading-[1.5] flex flex-col items-center gap-4"
>
Hear from Happy ClassroomIO Users
</h1>
<p
class="mx-auto text-center my-5 lg:mt-6 px-4 lg:p-0 text-base tracking-wide leading-6 text-gray-400 lg:w-2/4"
>
See what excites them about building and managing bootcamps with ClassroomIO.
</p>
<a
class="md:text-base w-11/12 rounded-md bg-blue-700 px-6 py-3 text-sm font-medium text-white text-center transition-all delay-100 hover:bg-blue-800 hover:shadow-xl hover:scale-95 md:w-fit lg:px-6 lg:py-5 lg:font-semibold"
href="/signup"
rel="noopener noreferrer nofollow"
>
Sign Up for Free
</a>
<div class="relative my-5 md:my-10 w-[85%] lg:w-[70%]">
<SenjaEmbed id="3e847d7a-13d3-483e-8a71-5b35273dcc6c" />
</div>
</section>
@@ -1,17 +0,0 @@
<script>
export let color = 'currentColor';
</script>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="dark"
>
<path
d="M18 22.5H6C5.60232 22.4995 5.22105 22.3414 4.93984 22.0602C4.65864 21.7789 4.50046 21.3977 4.5 21V3C4.5004 2.6023 4.65856 2.221 4.93978 1.93978C5.221 1.65856 5.6023 1.5004 6 1.5H18C18.3977 1.50046 18.779 1.65864 19.0602 1.93984C19.3414 2.22105 19.4995 2.60232 19.5 3V15.4636L15.75 13.5886L12 15.4636V3H6V21H18V18H19.5V21C19.4995 21.3977 19.3413 21.7789 19.0601 22.0601C18.7789 22.3413 18.3977 22.4995 18 22.5ZM15.75 11.9114L18 13.0364V3H13.5V13.0364L15.75 11.9114Z"
fill={color}
/>
</svg>
@@ -1,56 +0,0 @@
<script lang="ts">
export let label = '';
export let disabled = false;
export let placeholder = '';
export let value = '';
export let rows = 3;
export let className = '';
export let labelClassName = '';
export let bgColor = 'bg-gray-50 focus:bg-primary-50 dark:bg-neutral-700 dark:text-white';
export let helperMessage = '';
export let errorMessage = '';
export let isRequired = false;
export let onChange = () => {}; // This is to know if element is 'dirty'
export let ref = null;
</script>
<label for="text-field" class="block w-full relative {className}">
{#if label}
<p class="dark:text-white text-left w-full flex items-center justify-between {labelClassName}">
<span>
{label}
{#if isRequired}
<span class="text-red-700">*</span>
{/if}
</span>
</p>
{/if}
<textarea
{disabled}
{rows}
{placeholder}
bind:value
bind:this={ref}
required={isRequired}
class="form-textarea p-2 border-l-0 border-r-0 border-t-0 border-b-2 border-gray-200 dark:border-neutral-600 focus:border-l-0 focus:border-r-0 rounded-t-md focus:border-t-0 focus:border-b-2 focus:border-primary-600 mt-1 block w-full
{bgColor} {!!errorMessage ? 'border-red-500' : 'border-gray-300'}"
on:change={onChange}
/>
{#if !!errorMessage}
<p class="text-sm text-red-500">
{errorMessage}
</p>
{:else if helperMessage}
<p class="text-sm text-gray-500 dark:text-white">
{helperMessage}
</p>
{/if}
<slot name="iconbutton" />
</label>
<style>
.form-textarea {
box-shadow: none !important;
}
</style>
@@ -1,81 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
export let label = '';
export let placeholder = '';
export let value: string | number | null = null;
export let name = '';
export let className = '';
export let inputClassName = '';
export let labelClassName = '';
export let bgColor = 'bg-gray-100 focus:bg-primary-50 dark:bg-neutral-700 dark:text-white';
export let autoFocus = false;
export let isRequired = false;
export let isDisabled = false;
export let errorMessage = '';
export let helperMessage = '';
export let autoComplete = true;
let ref: HTMLInputElement | undefined = undefined;
let focusClass = '';
onMount(() => {
if (autoFocus && ref) {
ref.focus();
}
});
</script>
<label class="block relative {className}">
{#if label}
<p class=" dark:text-white text-left m-0 {labelClassName}">
{label}
{#if isRequired}
<span class="text-red-700">*</span>
{/if}
</p>
{/if}
<input
class="form-input border-l-0 border-r-0 border-t-0 border-b-2 border-gray-200 dark:border-neutral-600 focus:border-l-0 focus:border-r-0 rounded-t-md focus:border-t-0 focus:border-b-2 {inputClassName} {focusClass} {isDisabled &&
'hover:cursor-not-allowed opacity-50'} dark:text-black p-2 mt-1 block w-full {bgColor} {errorMessage
? 'border-red-600'
: ''}"
{placeholder}
bind:value
bind:this={ref}
required={isRequired}
disabled={isDisabled}
autocomplete={autoComplete ? 'on' : 'off'}
on:blur={(e) => {
if (focusClass.includes('border-primary-600')) {
focusClass = focusClass.replace('border-primary-600', '');
}
}}
on:focus={() => (focusClass += ' border-primary-600')}
{name}
/>
{#if errorMessage}
<p class="text-sm text-red-500">{errorMessage}</p>
{:else if helperMessage}
<p class="dark:text-white text-sm">
{helperMessage}
</p>
{/if}
</label>
<style>
.form-input {
box-shadow: none !important;
}
.password-eye {
position: absolute;
top: 30px;
right: 6px;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
</style>
@@ -1,334 +0,0 @@
<script lang="ts">
import { page } from '$app/state';
import CourseIcon from '$lib/Icons/CourseIcon.svelte';
import ChevronDown from 'carbon-icons-svelte/lib/ChevronDown.svelte';
import CloseLarge from 'carbon-icons-svelte/lib/CloseLarge.svelte';
import ForumIcon from 'carbon-icons-svelte/lib/Forum.svelte';
import MapCenter from 'carbon-icons-svelte/lib/MapCenter.svelte';
import TextAlignJustify from 'carbon-icons-svelte/lib/TextAlignJustify.svelte';
import { onMount } from 'svelte';
import { fly } from 'svelte/transition';
export let stars: number;
let showDrawer = false;
let showSolutions = false;
let activeLink = '';
let closeTimeout: NodeJS.Timeout;
function closeSolutions() {
closeTimeout = setTimeout(() => {
showSolutions = false;
}, 100);
}
function resetCloseTimeout() {
clearTimeout(closeTimeout);
}
function handleShowDrawer() {
showDrawer = !showDrawer;
}
function handleShowSolutions() {
showSolutions = !showSolutions;
}
const solutions = [
{
key: 'employee-training',
title: 'Employee Training',
subtitle: 'Keep your team in sync.'
},
{
key: 'bootcamps',
title: 'Bootcamps',
subtitle: 'Drive student satisfaction.'
},
{
key: 'customer-education',
title: 'Customer Education',
subtitle: 'Teach customers your product.'
}
];
$: activeLink = page.url.pathname;
$: activeHash = page.url.hash;
$: isSolutionsActive = solutions.some((s) => activeHash.includes(s.key));
</script>
<div
class="flex w-full justify-between items-center py-6 border-b-[1px] md:px-12 px-5 fixed top-0 z-[3000] filter backdrop-blur-xl shadow-sm bg-white"
>
<a href="/" class="w-[10%]">
<div class="flex items-center w-full">
<img
loading="lazy"
width="28"
height="28"
src="/logo-512.png"
alt="classroomio logo"
class="w-6 md:w-10"
/>
<h1 class="text-xs md:text-lg font-medium ml-2">ClassroomIO</h1>
</div>
</a>
<nav class="w-[50%] hidden md:hidden lg:block">
<ul class="flex justify-center items-center w-full gap-5">
<li class="text-gray-800 font-semibold text-sm cursor-pointer relative">
<button
on:focus={() => (showSolutions = true)}
on:mouseenter={() => (showSolutions = true)}
on:mouseleave={closeSolutions}
class="flex items-center hover:bg-gray-100 {showSolutions &&
'bg-gray-100'} px-4 py-2 rounded-md transition-all duration-200"
on:click={() => (showSolutions = !showSolutions)}
class:active={isSolutionsActive}
>
<span class="mr-2">Solutions</span>
<ChevronDown size={16} />
</button>
{#if showSolutions}
<button
class="absolute w-[20rem] top-10 -left-10 border rounded-lg px-2 py-4 shadow-slate-700 z-[3001] bg-white flex flex-col gap-3"
on:mouseenter={resetCloseTimeout}
on:mouseleave={() => (showSolutions = false)}
>
{#each solutions as solution}
<a
class="flex justify-between items-center w-full rounded-lg hover:bg-gray-100 hover:cursor-pointer p-2 transition-colors duration-200"
href="/{solution.key}"
on:click={() => (showSolutions = false)}
>
{#if solution.key === 'employee-training'}
<CourseIcon />
{:else if solution.key === 'bootcamps'}
<MapCenter size={24} />
{:else if solution.key === 'customer-education'}
<ForumIcon size={24} />
{/if}
<div class="w-[84%] text-start">
<h3 class="font-semibold text-sm text-gray-700">
{solution.title}
</h3>
<p class="font-normal text-sm text-gray-600">
{solution.subtitle}
</p>
</div>
</a>
{/each}
</button>
{/if}
</li>
<a
href="/tools"
class="text-gray-800 font-semibold text-sm cursor-pointer"
class:active={activeLink.startsWith('/tools')}
>
<li class="hover:bg-gray-100 px-4 py-2 rounded-md transition-all duration-200">
Free Tools
</li>
</a>
<a
href="/blog"
class="text-gray-800 font-semibold text-sm cursor-pointer"
class:active={activeLink.startsWith('/blog')}
>
<li class="hover:bg-gray-100 px-4 py-2 rounded-md transition-all duration-200">Blog</li>
</a>
<a
href="/pricing"
class="text-gray-800 font-semibold text-sm cursor-pointer"
class:active={activeLink.startsWith('/pricing')}
>
<li class="hover:bg-gray-100 px-4 py-2 rounded-md transition-all duration-200">Pricing</li>
</a>
</ul>
</nav>
<div class="justify-between items-center flex-row hidden md:hidden lg:flex gap-3">
<a
href="/discord"
target="_blank"
class="flex items-center hover:opacity-80 transition-opacity duration-200"
>
<img
loading="lazy"
alt="discord logo"
src="/discord-blue.png"
class="w-6 h-5 cursor-pointer"
/>
</a>
<div class="flex items-center">
<a
href="/github"
target="_blank"
class="flex items-center gap-1.5 p-2 rounded-md transition-all duration-200 ease-in-out hover:bg-gray-100 group"
>
<img
loading="lazy"
alt="github logo"
src="/github-mark.png"
class="w-5 h-5 cursor-pointer transition-transform duration-200 group-hover:scale-110"
/>
<span
class="text-sm text-gray-600 font-medium leading-none transition-colors duration-200 group-hover:text-black"
>
{stars}
</span>
</a>
</div>
<a
class="font-medium text-sm after:content-['→'] after:ml-2 hover:opacity-80 transition-opacity duration-200"
href="https://app.classroomio.com"
>
Dashboard
</a>
</div>
<button
type="button"
aria-label="Hamburger Menu"
class="block md:block lg:hidden"
on:click={handleShowSolutions}
>
<TextAlignJustify size={24} />
</button>
{#if showSolutions}
<div
in:fly={{ x: 20, duration: 700 }}
out:fly={{ x: 20, duration: 400 }}
class="w-[60%] md:w-[40%] h-[100vh] border-2 sm:block lg:hidden px-3 pt-3 pb-2 md:p-7 absolute right-0 top-0 bg-white"
>
<div class="flex justify-between py-2 mb-5">
<img
loading="lazy"
width="20"
height="20"
src="/logo-512.png"
alt="classroomio logo"
class="w-[15%]"
/>
<button class="mr-5" on:click={handleShowSolutions}>
<CloseLarge size={24} />
</button>
</div>
<nav>
<ul class="flex items-center flex-col lg:flex-row justify-between w-full">
<li class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer w-full">
<button
class="w-full flex items-center justify-between hover:bg-gray-100 py-3 px-4 rounded-lg transition-all duration-200"
on:click={handleShowDrawer}
class:active={isSolutionsActive}
>
Our Superpowers <ChevronDown />
</button>
{#if showDrawer}
<div in:fly={{ y: -20, duration: 700 }} out:fly={{ y: 20, duration: 400 }}>
{#each solutions as solution}
<a
href="/{solution.key}"
on:click={() => {
handleShowSolutions();
}}
>
<p
class="font-normal text-xs text-gray-700 hover:bg-gray-100 rounded-lg py-2.5 pl-5 transition-colors duration-200"
>
{solution.title}
</p>
</a>
{/each}
</div>
{/if}
</li>
<a
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-xl w-full transition-all duration-200"
on:click={handleShowSolutions}
href="/tools"
>
<li>Free Tools</li>
</a>
<a
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-md w-full transition-all duration-200"
on:click={() => {
handleShowSolutions();
}}
class:active={activeLink.startsWith('/blog')}
href="/blog"
>
<li>Blog</li>
</a>
<a
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-xl w-full transition-all duration-200"
on:click={() => {
handleShowSolutions();
}}
href="/pricing"
class:active={activeLink.startsWith('/pricing')}
>
<li>Pricing</li>
</a>
<a
class="text-gray-800 font-semibold text-sm md:text-lg cursor-pointer hover:bg-gray-100 py-3 px-4 rounded-md w-full transition-all duration-200"
on:click={() => {
handleShowSolutions();
}}
class:active={activeHash.includes('morefeatures')}
href="/#morefeatures"
>
<li>More features</li>
</a>
</ul>
</nav>
<div class="flex items-start flex-col gap-y-2 mt-5 border-t-[1px] pt-5">
<a
href="/discord"
target="_blank"
class="flex items-center rounded-md w-full text-left py-4 px-4 hover:bg-gray-100 text-sm md:text-lg transition-all duration-200"
>
<img
loading="lazy"
alt="discord logo"
src="/discord-blue.png"
class="w-6 h-5 mr-2 cursor-pointer"
/>
<span>Discord</span>
</a>
<a
href="/github"
target="_blank"
class="flex items-center rounded-md w-full text-left py-4 px-4 hover:bg-gray-100 text-sm md:text-lg group transition-all duration-200"
>
<img
loading="lazy"
alt="github logo"
src="/github-mark.png"
class="w-5 h-5 cursor-pointer transition-transform duration-200 group-hover:scale-110"
/>
<span class="ml-3 transition-colors duration-200 group-hover:text-black">Github</span>
<span
class="text-sm text-gray-600 font-medium ml-1 transition-colors duration-200 group-hover:text-black"
>({stars})</span
>
</a>
<a
class="font-semibold after:content-['→'] rounded-md after:ml-2 w-full text-left py-4 px-4 hover:bg-gray-100 text-sm md:text-lg transition-all duration-200"
href="https://app.classroomio.com"
>
Dashboard
</a>
</div>
</div>
{/if}
</div>
<style>
.active {
background-color: #f3f4f6;
border-radius: 6px;
}
</style>
@@ -1,38 +0,0 @@
<script>
import { goto } from '$app/navigation';
export let isOrg = false;
export let className = '';
function handleClick() {
if (!isOrg) {
return goto('/');
}
return goto('https://classroomio.com');
}
</script>
<div class="{className} min-h-[80vh] flex items-center justify-center flex-col w-screen">
<img src="https://brand.cdn.clsrio.com/404.png" alt="classroomio_error_image" />
<div class="flex flex-col items-center justify-center dark:text-white gap-3 w-4/5 lg:w-2/5">
<p class="text-4xl dark:text-white font-semibold mb-5">
{#if isOrg}
Organization doesn't exist!
{:else}
Page not found
{/if}
</p>
<p class=" text-gray-700 text-lg text-center dark:text-white mb-5">
The page you are looking for doesn't exist or has been moved. Please go back to the homepage.
</p>
<button
class="text-base rounded-md bg-black transition-all delay-100 hover:shadow-xl hover:scale-95 px-6 py-4 font-semibold text-white lg:px-7 lg:py-3 lg:text-lg"
on:click={handleClick}
>
Go Home
</button>
</div>
</div>
@@ -1,9 +0,0 @@
<script>
export let className = '';
</script>
<header
class="hero-bg lg:min-h-[20rem] mt-[10%] md:mt-16 pt-24 pb-16 w-full border-b border-slate-100 {className}"
>
<slot />
</header>
@@ -1,52 +0,0 @@
<script>
export let header = '';
export let subText = '';
export let btnLabel = '';
export let link = '';
export let demo = true;
</script>
<div
class="floating grad relative mx-auto my-[5%] flex h-[61vh] w-[100%] flex-col items-center justify-center px-[6%] py-[12%] text-center shadow-2xl lg:h-[54vh] lg:w-[70%] lg:rounded-3xl max-w-4xl"
>
<div>
<h1
class="font-display mt-3 text-3xl font-bold tracking-tight text-white lg:mt-2 lg:text-4xl max-w-2xl"
>
{header}
</h1>
<p class="mt-6 text-xl text-gray-300 lg:text-lg">
{subText}
</p>
</div>
{#if demo}
<button
class="text-base mt-10 rounded-md bg-white px-6 py-4 font-semibold text-gray-900 lg:px-7 lg:py-3 lg:text-lg"
data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo"
>
{btnLabel}
</button>
{:else}
<a
class="text-base mt-10 rounded-md bg-white px-6 py-4 font-semibold text-gray-900 lg:px-7 lg:py-3 lg:text-lg"
href={link}
target="_blank"
rel="noopener noreferrer nofollow"
>
{btnLabel}
</a>
{/if}
</div>
<style>
.grad {
/* background: rgb(2, 51, 189);
background: radial-gradient(circle at 50% 100%, rgba(2, 51, 189, 1) 1%, rgb(4, 2, 56) 100%); */
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
background-image: url('https://brand.cdn.clsrio.com/cta-bg.png');
}
</style>
@@ -1,6 +1,6 @@
<script> <script>
import Chip from '$lib/Chip/Chip.svelte'; import { Badge } from '@cio/ui/base/badge';
import { formatDate } from '$lib/utils/formatDate'; import { formatDate } from '$lib/utils/format-date';
export let post; export let post;
export let isRecommended = false; export let isRecommended = false;
@@ -15,7 +15,7 @@
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{#each post.tags as tag} {#each post.tags as tag}
<Chip label={tag} /> <Badge variant="outline">{tag}</Badge>
{/each} {/each}
</div> </div>
</div> </div>
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import Section from './CoreFeatureSection.svelte'; import Section from './core-feature-section.svelte';
import { PUBLIC_ENABLE_STATS } from '$env/static/public'; import { PUBLIC_ENABLE_STATS } from '$env/static/public';
</script> </script>
@@ -0,0 +1,40 @@
<script>
import { faqs } from '$lib/utils/faqs';
import { PageSignupCTA } from '$lib/components';
import * as Accordion from '@cio/ui/base/accordion';
</script>
<div class="mb-20 mt-10 flex flex-col items-center justify-center">
<header class="text-center">
<h1 class="mt-2 text-xl md:text-4xl">
Questions & Answers
</h1>
<p
class="mt-4 text-sm md:text-lg font-normal tracking-normal ui:text-muted-foreground lg:px-[20%] lg:text-xl lg:tracking-wide"
>
Can't find the answer you're looking for?
<a class="font-semibold text-blue-700" href="mailto:[email protected]" target="_blank"
>Shoot us an email</a> and we'll get back to you ASAP.
</p>
</header>
<div class="mt-[20%] lg:m-0 max-w-3xl w-full mx-auto px-2">
<Accordion.Root type="single" class="w-full">
{#each faqs as faq, index}
<Accordion.Item value="faq-{index}">
<Accordion.Trigger>{faq.question}</Accordion.Trigger>
<Accordion.Content>
{faq.answer}
</Accordion.Content>
</Accordion.Item>
{/each}
</Accordion.Root>
</div>
<PageSignupCTA
header="Your Custom Academy – Up & Running in Minutes!"
subText="Try before you buy. No credit card required."
btnLabel="Book a demo"
link="classroomio/demo"
/>
</div>
@@ -0,0 +1,144 @@
<script>
import { Button } from '@cio/ui/base/button';
import Facebook from '@lucide/svelte/icons/facebook';
import Youtube from '@lucide/svelte/icons/youtube';
</script>
<div class="border-t-[1px] px-[2%] lg:px-[14%]">
<div class="flex flex-col items-center justify-between py-[10%] pl-5 lg:flex-row lg:pl-0">
<div class="w-full min-w-[200px] lg:w-3/12">
<img width="32" height="32" loading="lazy" src="/logo-192.png" alt="classroomio logo" class="mb-5" />
<p class="mb-5 mr-2 text-sm leading-7 tracking-wide text-gray-600 lg:text-base lg:leading-7">
The Open Source Learning Management System for Companies
<br />
ClassroomIO is the easiest way to grow your teaching business.
</p>
<!-- <a
href="https://www.producthunt.com/posts/classroomio?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-classroomio"
target="_blank"
><img
loading="lazy"
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=425236&theme=light"
alt="ClassroomIO - The&#0032;Open&#0032;Source&#0032;Platform&#0032;for&#0032;Tech&#0032;Bootcamps | Product Hunt"
style="width: 250px; height: 54px;"
width="250"
height="54"
/></a
> -->
<div class="mt-5 flex items-center gap-3 lg:mt-10 lg:gap-7">
<Button href="https://www.facebook.com/classroomiohq" target="_blank" variant="ghost" aria-label="Facebook Link">
<Facebook />
</Button>
<Button href="https://twitter.com/classroomio" target="_blank" variant="ghost" aria-label="Twitter Link">
<img
width="20"
height="20"
loading="lazy"
src="/twitter.svg"
alt=""
/>
</Button>
<Button href="https://www.youtube.com/@ClassroomIO" target="_blank" variant="ghost" aria-label="Youtube Link">
<Youtube />
</Button>
</div>
</div>
<nav
class="mt-16 flex w-full flex-col items-start justify-end gap-12 space-y-6 lg:mt-0 lg:w-3/4 lg:flex-row lg:space-y-0"
>
<div class="max-w-[120px]">
<p class="mb-3 text-base font-semibold leading-6 text-gray-900 lg:mb-10 lg:text-base">Product</p>
<ul class="space-y-2 lg:space-y-4">
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/#coursemanagement"> Course Management </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/#customization"> Customizations </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/#collaboration"> Forum </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/#ai"> AI Lesson Planner </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/#student-dashboard"> Student Dashboard </a>
</li>
</ul>
</div>
<div class="max-w-[120px]">
<p class="mb-3 text-base font-semibold leading-6 text-gray-900 lg:mb-10 lg:text-base">Solutions</p>
<ul class="space-y-2 lg:space-y-4">
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/employee-training">Employee Training</a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/bootcamps">Bootcamps</a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/customer-education">Customer Education</a>
</li>
</ul>
</div>
<div class="max-w-[120px]">
<p class="mb-3 text-base font-semibold leading-6 text-gray-900 lg:mb-10 lg:text-base">Resources</p>
<ul class="space-y-2 lg:space-y-4">
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/contact">Contact us</a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/teach">Teach with us</a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a target="_blank" href="https://classroomio.com/docs/"> Help center </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a target="_blank" href="https://classroomio.com/docs/quickstart/signup"> Getting Started </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/oss-friends"> OSS Friends </a>
</li>
</ul>
</div>
<div class="max-w-[120px]">
<p class="mb-3 text-base font-semibold leading-6 text-gray-900 lg:mb-10 lg:text-base">Company</p>
<ul class="space-y-2 lg:space-y-4">
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="/roadmap"> Roadmap </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="mailto:[email protected]" target="_blank"> Support </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="https://app.enzuzo.com/policies/privacy/958fc978-5477-11ee-a03b-7b111830c594" target="_blank">
Privacy policy
</a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a target="_blank" href="https://classroomio.com/docs/quickstart/signup"> Getting Started </a>
</li>
<li class="text-sm leading-6 tracking-wide text-gray-600 hover:text-gray-900 lg:text-base">
<a href="https://app.enzuzo.com/policies/tos/958fc978-5477-11ee-a03b-7b111830c594" target="_blank">
Terms of use
</a>
</li>
</ul>
</div>
</nav>
</div>
<footer
class="border-t py-3 text-center text-xs font-light leading-5 text-gray-500 lg:text-left lg:text-base"
>
<p>All rights reserved.</p>
<p class="flex items-center justify-center">
ClassroomIO © {new Date().getFullYear()} by
<a href="https://twitter.com/rotimi_best" target="_blank" class="mx-1 text-blue-700 underline">Rotimi Best</a>
from
<img loading="lazy" alt="Nigerian Flag" src="/ng.png" class="ml-2 h-3 w-3" />
</p>
</footer>
</div>
@@ -3,6 +3,7 @@
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing'; import { sineInOut } from 'svelte/easing';
import { PUBLIC_ENABLE_USERS_COMPANIES } from '$env/static/public'; import { PUBLIC_ENABLE_USERS_COMPANIES } from '$env/static/public';
import { Button } from '@cio/ui/base/button';
const names = [ const names = [
{ label: 'Audience', color: '#3ADFEC' }, { label: 'Audience', color: '#3ADFEC' },
@@ -30,32 +31,32 @@
}); });
</script> </script>
<div class="hero-bg relative border-b-2 text-center bg-[#F5F8FE] h-full mt-[5%] w-full"> <div class="hero-bg pt-18 relative h-full w-full border-b-2 bg-[#F5F8FE] text-center">
<div class="pt-24 w-full"> <div class="w-full pt-24">
<div class="mb-10 ml-[5%] lg:ml-0 flex w-full items-center justify-start lg:justify-center"> <div class="mb-10 ml-[5%] flex w-full items-center justify-start lg:ml-0 lg:justify-center">
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
href="/github" href="/github"
style="color: rgb(75, 85, 99);" style="color: rgb(75, 85, 99);"
class="rounded-full border-2 border-[#C2D2FF] px-4 py-1 text-sm font-medium bg-[#DCE5FF] text-[#4B5563] hover:underline" class="rounded-full border-2 border-[#C2D2FF] bg-[#DCE5FF] px-4 py-1 text-sm font-medium text-[#4B5563] hover:underline"
>We are <span class="text-blue-700">100%</span> open source</a >We are <span class="ui:text-primary">100%</span> open source</a
> >
</div> </div>
<div class="justify-between w-[90%] absolute top-[10%] left-[5%] hidden lg:flex"> <div class="absolute left-[5%] top-[10%] hidden w-[90%] justify-between lg:flex">
{#if animate} {#if animate}
<div <div
class="relative transition-all hover:scale-110 rotate-12" class="relative rotate-12 transition-all hover:scale-110"
transition:fly={{ y: 100, delay: 0, easing: sineInOut }} transition:fly={{ y: 100, delay: 0, easing: sineInOut }}
> >
<a <a
href="/#collaboration" href="/#collaboration"
class="px-6 py-2 border-[1.5px] border-[#3ADFEC] rounded-[4px] bg-white text-[#3ADFEC] text-lg font-bold" class="rounded-[4px] border-[1.5px] border-[#3ADFEC] bg-white px-6 py-2 text-lg font-bold text-[#3ADFEC]"
>Collaboration</a >Collaboration</a
> >
<svg <svg
class="absolute -right-1 -bottom-2.5" class="absolute -bottom-2.5 -right-1"
width="11" width="11"
height="11" height="11"
viewBox="0 0 11 11" viewBox="0 0 11 11"
@@ -67,17 +68,17 @@
</div> </div>
<div <div
class="relative transition-all hover:scale-110 -rotate-12" class="relative -rotate-12 transition-all hover:scale-110"
transition:fly={{ x: 100, delay: 50, easing: sineInOut }} transition:fly={{ x: 100, delay: 50, easing: sineInOut }}
> >
<a <a
href="/github" href="/github"
target="_blank" target="_blank"
class="px-6 py-2 border-[1.5px] border-[#BF0696] rounded-[4px] bg-white text-[#BF0696] text-lg font-bold" class="rounded-[4px] border-[1.5px] border-[#BF0696] bg-white px-6 py-2 text-lg font-bold text-[#BF0696]"
>Open Source</a >Open Source</a
> >
<svg <svg
class="absolute -right-1 -bottom-2.5" class="absolute -bottom-2.5 -right-1"
width="11" width="11"
height="11" height="11"
viewBox="0 0 11 11" viewBox="0 0 11 11"
@@ -91,21 +92,19 @@
</div> </div>
<div class="w-full"> <div class="w-full">
<div <div class="ml-6 text-left text-2xl font-semibold text-slate-900 lg:text-center lg:text-6xl">
class="font-bold md:leading-[1.5] text-left lg:text-center ml-6 text-slate-900 text-4xl lg:text-7xl"
>
<span>Educate Your</span> <span>Educate Your</span>
<div class="flex-wrap flex items-center justify-center lg:gap-3 mb-2"> <div class="mb-2 flex flex-wrap items-center justify-center lg:gap-3">
<span <span
class="w-full lg:w-[48%] h-[40px] md:h-[50px] lg:h-[75px] mb-0 mt-1 md:mt-0 lg:-mb-4 relative inline-block overflow-hidden lg:text-center" class="relative mb-0 mt-1 inline-block h-[40px] w-full overflow-hidden md:mt-0 md:h-[50px] lg:-mb-4 lg:h-[75px] lg:w-[48%] lg:text-center"
> >
{#key titleIndex} {#key titleIndex}
<span <span
class="absolute left-0 right-[unset] lg:right-0 lg:left-[unset] w-full" class="absolute left-0 right-[unset] w-full lg:left-[unset] lg:right-0"
style="color: {names[titleIndex].color}" style="color: {names[titleIndex].color}"
transition:fly={{ y: 100, delay: 0, easing: sineInOut }} transition:fly={{ y: 100, delay: 0, easing: sineInOut }}
> >
<span class="w-full text-center m-auto"> <span class="m-auto w-full text-center">
{names[titleIndex].label} {names[titleIndex].label}
</span> </span>
</span> </span>
@@ -115,27 +114,27 @@
<span>from one Platform</span> <span>from one Platform</span>
</div> </div>
<p class="text-base mx-auto mt-10 px-4 text-slate-700 md:text-lg lg:mt-6 lg:w-[42%] lg:p-0"> <p class="mx-auto mt-10 px-4 ui:text-muted-foreground md:text-lg lg:mt-6 lg:w-[42%] lg:p-0">
ClassroomIO is an open source learning management system for companies. We give you a suite ClassroomIO is an open source learning management system for companies. We give you a suite
of tools to help you achieve your training, onboarding and certification goals. of tools to help you achieve your training, onboarding and certification goals.
</p> </p>
</div> </div>
<div class="justify-between w-[90%] absolute top-[30%] left-[5%] hidden lg:flex"> <div class="absolute left-[5%] top-[30%] hidden w-[90%] justify-between lg:flex">
{#if animate} {#if animate}
<div <div
class="relative transition-all hover:scale-110 -rotate-12" class="relative -rotate-12 transition-all hover:scale-110"
transition:fly={{ x: -100, delay: 100, easing: sineInOut }} transition:fly={{ x: -100, delay: 100, easing: sineInOut }}
> >
<a <a
href="/#customization" href="/#customization"
class="px-6 py-2 border-[1.5px] border-[#0233BD] rounded-[4px] bg-white text-[#0233BD] text-lg font-bold" class="rounded-[4px] border-[1.5px] border-[#0233BD] bg-white px-6 py-2 text-lg font-bold text-[#0233BD]"
> >
Customizable LMS Customizable LMS
</a> </a>
<svg <svg
class="absolute -right-1 -bottom-2.5" class="absolute -bottom-2.5 -right-1"
width="11" width="11"
height="11" height="11"
viewBox="0 0 11 11" viewBox="0 0 11 11"
@@ -146,17 +145,17 @@
</svg> </svg>
</div> </div>
<div <div
class="relative transition-all hover:scale-110 rotate-12" class="relative rotate-12 transition-all hover:scale-110"
transition:fly={{ y: 100, delay: 150, easing: sineInOut }} transition:fly={{ y: 100, delay: 150, easing: sineInOut }}
> >
<a <a
href="/#ai" href="/#ai"
class="px-6 py-2 border-[1.5px] border-[#9747FF] rounded-[4px] bg-white text-[#9747FF] text-lg font-bold" class="rounded-[4px] border-[1.5px] border-[#9747FF] bg-white px-6 py-2 text-lg font-bold text-[#9747FF]"
>AI Support</a >AI Support</a
> >
<svg <svg
class="absolute -right-1 -bottom-2.5" class="absolute -bottom-2.5 -right-1"
width="11" width="11"
height="11" height="11"
viewBox="0 0 11 11" viewBox="0 0 11 11"
@@ -170,20 +169,12 @@
</div> </div>
<div class="my-10 flex flex-col items-center justify-center gap-4 md:flex-row"> <div class="my-10 flex flex-col items-center justify-center gap-4 md:flex-row">
<a <Button variant="outline" href="/demo" target="_blank" rel="noopener noreferrer nofollow">
class="md:text-base w-11/12 rounded-md border-gray-200 border-2 px-6 py-3 text-sm font-medium text-center transition-all delay-100 hover:bg-gray-200 hover:shadow-xl hover:scale-95 md:w-fit lg:px-6 lg:py-4 lg:font-semibold"
href="/demo"
rel="noopener noreferrer nofollow"
>
Book a demo Book a demo
</a> </Button>
<a <Button href="/signup" target="_blank" rel="noopener noreferrer nofollow">
class="md:text-base w-11/12 rounded-md bg-blue-700 px-6 py-3 text-sm font-medium text-white text-center transition-all delay-100 hover:bg-blue-800 hover:shadow-xl hover:scale-95 md:w-fit lg:px-6 lg:py-4 lg:font-semibold"
href="/signup"
rel="noopener noreferrer nofollow"
>
Sign Up for Free Sign Up for Free
</a> </Button>
<!-- <a <!-- <a
class="font-medium lg:font-semibold text-sm md:text-base w-11/12 md:w-fit border px-6 py-3 lg:px-10 lg:py-5 rounded-md" class="font-medium lg:font-semibold text-sm md:text-base w-11/12 md:w-fit border px-6 py-3 lg:px-10 lg:py-5 rounded-md"
href="/github" href="/github"
+22
View File
@@ -0,0 +1,22 @@
// UI Components - Named exports for easy importing
export { default as BlogListItem } from './blog-list-item.svelte';
export { default as CoreFeatureSection } from './core-feature-section.svelte';
export { default as CoreFeatures } from './core-features.svelte';
export { default as CustomImg } from './custom-img.svelte';
export { default as Faq } from './faq.svelte';
export { default as Footer } from './footer.svelte';
export { default as Hero } from './hero.svelte';
export { default as Modal } from './modal.svelte';
export { default as MoreFeatures } from './more-features.svelte';
export { default as Navigation } from './navigation.svelte';
export { default as NotFound } from './not-found.svelte';
export { default as PageHeader } from './page-header.svelte';
export { default as PageSignupCTA } from './page-signup-cta.svelte';
export { default as SenjaEmbed } from './senja-embed.svelte';
export { default as StudentDashboard } from './student-dashboard.svelte';
export { default as Success } from './success.svelte';
export { default as Testimonial } from './testimonial.svelte';
export { default as Timeline } from './timeline.svelte';
export { default as ToolsHeader } from './tools-header.svelte';
export { default as UploadWidget } from './upload-widget.svelte';
@@ -0,0 +1,379 @@
<script lang="ts">
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import Gamepad from '@lucide/svelte/icons/gamepad';
import Hourglass from '@lucide/svelte/icons/hourglass';
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
import LocateFixed from '@lucide/svelte/icons/locate-fixed';
import Menu from '@lucide/svelte/icons/menu';
import MessagesSquare from '@lucide/svelte/icons/messages-square';
import MousePointerClick from '@lucide/svelte/icons/mouse-pointer-click';
import Timer from '@lucide/svelte/icons/timer';
import X from '@lucide/svelte/icons/x';
import { page } from '$app/state';
import { fly } from 'svelte/transition';
import type { HTMLAttributes } from 'svelte/elements';
import * as NavigationMenu from '@cio/ui/base/navigation-menu';
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
export let stars: number;
let showDrawer = false;
let showSolutions = false;
let activeLink = '';
function handleShowDrawer() {
showDrawer = !showDrawer;
}
function handleShowSolutions() {
showSolutions = !showSolutions;
}
const solutions = [
{
key: 'employee-training',
title: 'Employee Training',
subtitle: 'Keep your team in sync.'
},
{
key: 'bootcamps',
title: 'Bootcamps',
subtitle: 'Drive student satisfaction.'
},
{
key: 'customer-education',
title: 'Customer Education',
subtitle: 'Teach customers your product.'
}
];
const freeTools = [
{
key: 'progress',
title: 'Progress Tracker',
subtitle: 'Monitor learning journeys.'
},
{
key: 'pomodoro',
title: 'Pomodoro Timer',
subtitle: 'Boost focus and productivity.'
},
{
key: 'name-picker',
title: 'Name Picker',
subtitle: 'Randomly select names.'
},
{
key: 'stopwatch',
title: 'Activity Stopwatch',
subtitle: 'Track time accurately.'
},
{
key: 'tic-tac-toe',
title: 'Tic Tac Toe',
subtitle: 'Play the classic game.'
}
];
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
title: string;
href: string;
subtitle: string;
key: string;
};
$: activeLink = page.url.pathname;
$: activeHash = page.url.hash;
$: isSolutionsActive = solutions.some((s) => activeHash.includes(s.key));
</script>
{#snippet list_item({ title, subtitle, href, key, class: className, ...restProps }: ListItemProps)}
<li>
<NavigationMenu.Link>
{#snippet child()}
<a
{href}
class="flex w-full flex-row items-center rounded-lg p-2 no-underline transition-colors duration-200 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none {className} [&_svg]:stroke-blue-600"
{...restProps}
>
<div class="shrink-0">
{#if key === 'employee-training'}
<LibraryBigIcon size={24} />
{:else if key === 'bootcamps'}
<LocateFixed size={24} />
{:else if key === 'customer-education'}
<MessagesSquare size={24} />
{:else if key === 'progress'}
<LoaderCircle size={24} />
{:else if key === 'pomodoro'}
<Hourglass size={24} />
{:else if key === 'name-picker'}
<MousePointerClick size={24} />
{:else if key === 'stopwatch'}
<Timer size={24} />
{:else if key === 'tic-tac-toe'}
<Gamepad size={24} />
{/if}
</div>
<div class="ml-3 text-start">
<p class="text-sm">
{title}
</p>
<p class="mt-1 text-xs ui:text-muted-foreground">
{subtitle}
</p>
</div>
</a>
{/snippet}
</NavigationMenu.Link>
</li>
{/snippet}
<section class="z-50 fixed top-0 w-full bg-white border-b">
<div class="mx-auto flex w-full items-center justify-between gap-20 py-1 lg:w-[90%] lg:px-0 px-4">
<a href="/" class="w-[10%]">
<div class="flex w-full items-center">
<img loading="lazy" width="28" height="28" src="/logo-512.png" alt="classroomio logo" class="w-6 md:w-7" />
<h1 class="ml-2 text-xs font-medium md:text-base">ClassroomIO</h1>
</div>
</a>
<section class="hidden lg:block">
<NavigationMenu.Root class="rounded-lg bg-white p-1">
<NavigationMenu.List class="flex w-full items-center justify-center gap-1">
<NavigationMenu.Item>
<NavigationMenu.Trigger
class="flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100"
>
<a href="/" class="no-underline"> Solutions </a>
</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="grid w-[560px] grid-cols-2 gap-2 p-2">
{#each solutions as solution}
{@render list_item({
href: `/${solution.key}`,
title: solution.title,
subtitle: solution.subtitle,
key: solution.key
})}
{/each}
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger
class="flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100"
>
<a href="/tools" class="no-underline"> Free Tools </a>
</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="grid w-[560px] grid-cols-2 gap-2 p-2">
{#each freeTools as tool}
{@render list_item({
href: `/tools/${tool.key}`,
title: tool.title,
subtitle: tool.subtitle,
key: tool.key
})}
{/each}
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link>
{#snippet child()}
<a
href="/blog"
class="cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100 {activeLink.startsWith(
'/blog'
)
? 'bg-gray-100'
: ''}"
>
Blog
</a>
{/snippet}
</NavigationMenu.Link>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link>
{#snippet child()}
<a
href="/pricing"
class="cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100 {activeLink.startsWith(
'/pricing'
)
? 'bg-gray-100'
: ''}"
>
Pricing
</a>
{/snippet}
</NavigationMenu.Link>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>
</section>
<div class="hidden flex-row items-center justify-between gap-3 md:hidden lg:flex">
<a href="/discord" target="_blank" class="flex items-center transition-opacity duration-200 hover:opacity-80">
<img loading="lazy" alt="discord logo" src="/discord-blue.png" class="h-5 w-6 cursor-pointer" />
</a>
<div class="flex items-center">
<a
href="/github"
target="_blank"
class="group flex items-center gap-1.5 rounded-md p-2 transition-all duration-200 ease-in-out hover:bg-gray-100"
>
<img
loading="lazy"
alt="github logo"
src="/github-mark.png"
class="h-5 w-5 cursor-pointer transition-transform duration-200 group-hover:scale-110"
/>
<span
class="text-sm font-medium leading-none text-gray-600 transition-colors duration-200 group-hover:text-black"
>
{stars}
</span>
</a>
</div>
<a
class="text-sm font-medium transition-opacity duration-200 after:ml-2 after:content-['→'] hover:opacity-80"
href="https://app.classroomio.com"
>
Dashboard
</a>
</div>
<button type="button" aria-label="Hamburger Menu" class="block md:block lg:hidden" on:click={handleShowSolutions}>
<Menu size={24} />
</button>
{#if showSolutions}
<div
in:fly={{ x: 20, duration: 700 }}
out:fly={{ x: 20, duration: 400 }}
class="absolute right-0 top-0 h-screen w-[60%] border-2 bg-white px-3 pb-2 pt-3 sm:block md:w-[40%] md:p-7 lg:hidden"
>
<div class="mb-5 flex justify-between py-2">
<img loading="lazy" width="20" height="20" src="/logo-512.png" alt="classroomio logo" class="w-[15%]" />
<button class="mr-5" on:click={handleShowSolutions}>
<X size={24} />
</button>
</div>
<nav>
<ul class="flex w-full flex-col items-center justify-between lg:flex-row">
<li class="w-full cursor-pointer text-sm font-semibold text-gray-800 md:text-lg">
<button
class="flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100"
on:click={handleShowDrawer}
class:active={isSolutionsActive}
>
Our Superpowers <ChevronDown />
</button>
{#if showDrawer}
<div in:fly={{ y: -20, duration: 700 }} out:fly={{ y: 20, duration: 400 }}>
{#each solutions as solution}
<a
href="/{solution.key}"
on:click={() => {
handleShowSolutions();
}}
>
<p
class="rounded-lg py-2.5 pl-5 text-xs font-normal text-gray-700 transition-colors duration-200 hover:bg-gray-100"
>
{solution.title}
</p>
</a>
{/each}
</div>
{/if}
</li>
<a
class="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
on:click={handleShowSolutions}
href="/tools"
>
<li>Free Tools</li>
</a>
<a
class="w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
on:click={() => {
handleShowSolutions();
}}
class:active={activeLink.startsWith('/blog')}
href="/blog"
>
<li>Blog</li>
</a>
<a
class="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
on:click={() => {
handleShowSolutions();
}}
href="/pricing"
class:active={activeLink.startsWith('/pricing')}
>
<li>Pricing</li>
</a>
<a
class="w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
on:click={() => {
handleShowSolutions();
}}
class:active={activeHash.includes('morefeatures')}
href="/#morefeatures"
>
<li>More features</li>
</a>
</ul>
</nav>
<div class="mt-5 flex flex-col items-start gap-y-2 border-t pt-5">
<a
href="/discord"
target="_blank"
class="flex w-full items-center rounded-md px-4 py-4 text-left text-sm transition-all duration-200 hover:bg-gray-100 md:text-lg"
>
<img loading="lazy" alt="discord logo" src="/discord-blue.png" class="mr-2 h-5 w-6 cursor-pointer" />
<span>Discord</span>
</a>
<a
href="/github"
target="_blank"
class="group flex w-full items-center rounded-md px-4 py-4 text-left text-sm transition-all duration-200 hover:bg-gray-100 md:text-lg"
>
<img
loading="lazy"
alt="github logo"
src="/github-mark.png"
class="h-5 w-5 cursor-pointer transition-transform duration-200 group-hover:scale-110"
/>
<span class="ml-3 transition-colors duration-200 group-hover:text-black">Github</span>
<span class="ml-1 text-sm font-medium text-gray-600 transition-colors duration-200 group-hover:text-black"
>({stars})</span
>
</a>
<a
class="w-full rounded-md px-4 py-4 text-left text-sm font-semibold transition-all duration-200 after:ml-2 after:content-['→'] hover:bg-gray-100 md:text-lg"
href="https://app.classroomio.com"
>
Dashboard
</a>
</div>
</div>
{/if}
</div>
</section>
<style>
.active {
background-color: #f3f4f6;
border-radius: 6px;
}
</style>
@@ -0,0 +1,33 @@
<script>
import { goto } from '$app/navigation';
import { Empty } from '@cio/ui/custom/empty';
import SearchXIcon from '@lucide/svelte/icons/search-x';
import { Button } from '@cio/ui/base/button';
export let isOrg = false;
export let className = '';
function handleClick() {
if (!isOrg) {
return goto('/');
}
return goto('https://classroomio.com');
}
</script>
<div class="{className} min-h-[80vh] w-screen flex items-center justify-center">
<Empty
title={isOrg ? 'Organization doesn\'t exist!' : 'Page not found'}
description="The page you are looking for doesn't exist or has been moved. Please go back to the homepage."
icon={SearchXIcon}
variant="page"
>
<Button
onclick={handleClick}
>
Go Home
</Button>
</Empty>
</div>
@@ -0,0 +1,7 @@
<script>
export let className = '';
</script>
<header class="hero-bg w-full border-b border-slate-100 pb-16 pt-32 lg:min-h-[20rem] {className}">
<slot />
</header>
@@ -0,0 +1,60 @@
<script lang="ts">
import { Button } from '@cio/ui/base/button';
interface Props {
header?: string;
subText?: string;
btnLabel?: string;
link?: string;
demo?: boolean;
}
let { header = '', subText = '', btnLabel = '', link = '', demo = true }: Props = $props();
</script>
<div
class="floating grad relative mx-auto my-5 flex h-[61vh] w-full flex-col items-center justify-center px-6 py-12 text-center shadow-2xl lg:h-[50vh] lg:w-[70%] lg:rounded-3xl max-w-4xl space-y-8"
>
<div>
<h3
class="font-display mt-3 text-xl font-semibold tracking-tight text-white lg:mt-2 lg:text-4xl max-w-2xl"
>
{header}
</h3>
<p class="mt-6 text-xl text-gray-300 lg:text-lg">
{subText}
</p>
</div>
{#if demo}
<Button
variant="ghost"
data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo"
class="bg-white!"
>
{btnLabel}
</Button>
{:else}
<Button
href={link}
variant="link"
target="_blank"
class="ui:bg-secondary ui:text-secondary-foreground"
rel="noopener noreferrer nofollow"
>
{btnLabel}
</Button>
{/if}
</div>
<style>
.grad {
/* background: rgb(2, 51, 189);
background: radial-gradient(circle at 50% 100%, rgba(2, 51, 189, 1) 1%, rgb(4, 2, 56) 100%); */
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
background-image: url('https://brand.cdn.clsrio.com/cta-bg.png');
}
</style>
@@ -1,11 +1,9 @@
<script>
import { Badge } from '@cio/ui/base/badge';
</script>
<section id="student-dashboard" class="text-center text-white py-[10%] px-3 lg:px-0 bg-black"> <section id="student-dashboard" class="text-center text-white py-[10%] px-3 lg:px-0 bg-black">
<div class="mb-10"> <Badge class="py-2! px-4! underline text-sm!">yourorg.classroomio.com</Badge>
<a
href="#student-dashboard"
class="rounded-full px-4 py-2 text-sm font-medium hover:underline bg-blue-700 underline"
><span class="">yourorg</span>.classroomio.com</a
>
</div>
<h1 <h1
class="mx-auto text-3xl md:text-4xl lg:text-6xl font-normal leading-[1.5] flex flex-col items-center gap-4" class="mx-auto text-3xl md:text-4xl lg:text-6xl font-normal leading-[1.5] flex flex-col items-center gap-4"
> >
@@ -1,7 +1,7 @@
<script> <script>
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Button from '$lib/Button/Button.svelte'; import { Button } from '@cio/ui/base/button';
</script> </script>
<div <div
@@ -13,5 +13,5 @@
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
website website
</p> </p>
<Button label="Go to Home" className="px-10 md:px-20" onClick={() => goto('/')} /> <Button class="px-10 md:px-20" onclick={() => goto('/')}>Go to Home</Button>
</div> </div>
@@ -0,0 +1,29 @@
<script>
import { SenjaEmbed } from '$lib/components';
import { Button } from '@cio/ui/base/button';
</script>
<section id="testimonial" class="flex flex-col items-center py-[5%] px-3 lg:px-0 space-y-2">
<h1
class="mx-auto text-3xl md:text-4xl lg:text-5xl font-normal leading-normal flex flex-col items-center gap-4 my-0"
>
Hear from Happy ClassroomIO Users
</h1>
<p
class="mx-auto text-center px-4 lg:p-0 text-base tracking-wide leading-6 ui:text-muted-foreground lg:w-2/4"
>
See what excites them about building and managing bootcamps with ClassroomIO.
</p>
<Button
href="/signup"
target="_blank"
rel="noopener noreferrer nofollow"
>
Sign Up for Free
</Button>
<div class="relative my-5 md:my-10 w-[85%] lg:w-[70%]">
<SenjaEmbed id="3e847d7a-13d3-483e-8a71-5b35273dcc6c" />
</div>
</section>
@@ -2,6 +2,6 @@
export let className = ''; export let className = '';
</script> </script>
<header class="tools-hero-bg text-center py-14 {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 /> <slot />
</header> </header>
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import ImageCopy from 'carbon-icons-svelte/lib/ImageCopy.svelte'; import Copy from '@lucide/svelte/icons/copy';
import TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte'; import Trash2 from '@lucide/svelte/icons/trash-2';
export let imageURL = ''; export let imageURL = '';
export let label = ''; export let label = '';
@@ -35,7 +35,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<div> <div>
{#if label} {#if label}
<p class="dark:text-white text-left w-full flex items-center justify-between {labelClassName}"> <p class="flex w-full items-center justify-between text-left dark:text-white {labelClassName}">
<span> <span>
{label} {label}
{#if isRequired} {#if isRequired}
@@ -45,13 +45,13 @@
{#if imageURL} {#if imageURL}
<span title="clear image" on:click={deleteImage}> <span title="clear image" on:click={deleteImage}>
<TrashCan class="fill-red-500 " /> <Trash2 class="fill-red-500 " />
</span> </span>
{/if} {/if}
</p> </p>
{/if} {/if}
<div <div
class="flex items-center justify-center w-full border rounded-md min-h-[200px] p-4" class="flex min-h-[200px] w-full items-center justify-center rounded-md border p-4"
on:click={() => fileInput.click()} on:click={() => fileInput.click()}
> >
<input <input
@@ -63,13 +63,11 @@
required={isRequired} required={isRequired}
/> />
{#if imageURL} {#if imageURL}
<img src={imageURL} alt="selected file" class="w-[30%] h-auto mt-4" /> <img src={imageURL} alt="selected file" class="mt-4 h-auto w-[30%]" />
{:else} {:else}
<div class="space-y-4"> <div class="space-y-4">
<ImageCopy size={24} class="mx-auto" /> <Copy size={24} class="mx-auto" />
<p class="text-center text-sm text-gray-500 my-2"> <p class="my-2 text-center text-sm text-gray-500">Max file size: 10MB, accepted: jpeg, jpg, png, gif</p>
Max file size: 10MB, accepted: jpeg, jpg, png, gif
</p>
</div> </div>
{/if} {/if}
</div> </div>
-2
View File
@@ -1,2 +0,0 @@
import img from './img.svelte';
export { img };
@@ -0,0 +1,87 @@
<script>
import { Button } from '@cio/ui/base/button';
import * as Field from '@cio/ui/base/field';
import { Input } from '@cio/ui/base/input';
import { Textarea } from '@cio/ui/base/textarea';
import { isFormValid } from '$lib/utils/is-form-valid';
import { submitForm } from '$lib/utils/submit-form';
import { Success } from '$lib/components';
let name = '';
let email = '';
let title = '';
let description = '';
let isLoading = false;
let errorMessage = '';
let sent = false;
const handleClick = async () => {
const data = { name, email, title, description };
if (!isFormValid(data)) {
errorMessage = 'Please fill out all required fields.';
return;
}
isLoading = true;
const formData = {
title: `[FEEDBACK] ${title}`,
description: `My name is ${JSON.stringify(name)}, email is ${JSON.stringify(
email
)}. \n\n${JSON.stringify(description)}.`,
replyTo: email
};
try {
await submitForm(formData);
sent = true;
} catch (error) {
console.log(error);
} finally {
isLoading = false;
}
};
</script>
<div>
{#if sent}
<Success />
{:else}
<div class="w-full my-10 space-y-5">
<Field.Group>
<div class="flex flex-col md:flex-row gap-10">
<Field.Field class="w-full">
<Field.Label for="feedback-name">Your Name</Field.Label>
<Input id="feedback-name" bind:value={name} required />
</Field.Field>
<Field.Field class="w-full">
<Field.Label for="feedback-email">Your Email</Field.Label>
<Input id="feedback-email" type="email" bind:value={email} required />
</Field.Field>
</div>
<Field.Field>
<Field.Label for="feedback-subject">Subject</Field.Label>
<Input id="feedback-subject" bind:value={title} required />
</Field.Field>
<Field.Field>
<Field.Label for="feedback-message">Message</Field.Label>
<Textarea id="feedback-message" bind:value={description} required />
</Field.Field>
{#if !!errorMessage}
<Field.Field>
<Field.Error>{errorMessage}</Field.Error>
</Field.Field>
{/if}
</Field.Group>
<Button
onclick={handleClick}
disabled={isLoading}
loading={isLoading}
>
Submit Feedback
</Button>
</div>
{/if}
</div>
@@ -0,0 +1,87 @@
<script>
import { Button } from '@cio/ui/base/button';
import * as Field from '@cio/ui/base/field';
import { Input } from '@cio/ui/base/input';
import { Textarea } from '@cio/ui/base/textarea';
import { isFormValid } from '$lib/utils/is-form-valid';
import { submitForm } from '$lib/utils/submit-form';
import { Success } from '$lib/components';
let name = '';
let email = '';
let title = '';
let description = '';
let isLoading = false;
let errorMessage = '';
let sent = false;
async function handleClick() {
const data = { name, email, title, description };
if (!isFormValid(data)) {
errorMessage = 'Please fill out all required fields.';
return;
}
isLoading = true;
const formData = {
title: `[HELP] ${title}`,
description: `My name is ${JSON.stringify(name)}, email is ${JSON.stringify(
email
)}. \n\n${JSON.stringify(description)}.`,
replyTo: email
};
try {
await submitForm(formData);
sent = true;
} catch (error) {
console.log(error);
} finally {
isLoading = false;
}
}
</script>
<div>
{#if sent}
<Success />
{:else}
<div class="w-full my-10 space-y-5">
<Field.Group>
<div class="flex flex-col md:flex-row gap-10">
<Field.Field class="w-full">
<Field.Label for="help-name">Your Name</Field.Label>
<Input id="help-name" bind:value={name} required />
</Field.Field>
<Field.Field class="w-full">
<Field.Label for="help-email">Your Email</Field.Label>
<Input id="help-email" type="email" bind:value={email} required />
</Field.Field>
</div>
<Field.Field>
<Field.Label for="help-subject">Subject</Field.Label>
<Input id="help-subject" bind:value={title} required />
</Field.Field>
<Field.Field>
<Field.Label for="help-message">Message</Field.Label>
<Textarea id="help-message" bind:value={description} required />
</Field.Field>
{#if !!errorMessage}
<Field.Field>
<Field.Error>{errorMessage}</Field.Error>
</Field.Field>
{/if}
</Field.Group>
<Button
onclick={handleClick}
disabled={isLoading}
loading={isLoading}
>
Submit Request
</Button>
</div>
{/if}
</div>
@@ -0,0 +1,82 @@
<script lang="ts">
import { openModal, htmlBody } from './store';
import * as Dialog from '@cio/ui/base/dialog';
interface Avatar {
src: string;
}
let isAddIcon = false;
let hoveredIndex: number | null = null;
let avatars: Avatar[] = [
{ src: 'avatar_a' },
{ src: 'avatar_b' },
{ src: 'avatar_c' },
{ src: 'avatar_d' },
{ src: 'avatar_e' },
{ src: 'avatar_f' },
{ src: 'avatar_g' },
{ src: 'avatar_h' },
{ src: 'avatar_i' },
{ src: 'avatar_j' },
{ src: 'avatar_k' },
{ src: 'avatar_l' },
{ src: 'avatar_m' },
{ src: 'avatar_n' },
{ src: 'avatar_o' },
{ src: 'avatar_p' },
{ src: 'avatar_q' },
{ src: 'avatar_r' },
{ src: 'avatar_s' },
{ src: 'avatar_t' }
];
function selectAvatar(avatar: Avatar) {
$htmlBody.avatar = avatar.src;
$openModal.avatar = false;
}
</script>
<Dialog.Root bind:open={$openModal.avatar}>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Choose your Avatars</Dialog.Title>
</Dialog.Header>
<!-- map for each avatar -->
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
{#each avatars as avatar, index}
<button
on:click={() => {
selectAvatar(avatar);
}}
on:mouseenter={() => {
hoveredIndex = index;
isAddIcon = true;
}}
on:mouseleave={() => {
hoveredIndex = null;
isAddIcon = false;
}}
class="relative w-[17%] flex justify-center items-center shadow-sm rounded-full hover:scale-110 transition-all duration-300"
>
<!-- + icon overlay -->
{#if isAddIcon && hoveredIndex === index}
<div
class="w-full h-full flex justify-center items-center bg-white border absolute rounded-full opacity-[0.7] blur-md"
></div>
<img src="/free-tools/progress-report/hover-plus-icon.svg" alt="" class="absolute" />
{/if}
<!-- avatar -->
<img
src="https://assets.cdn.clsrio.com/progress-report/avatar/{avatar.src}.svg"
alt=""
class="w-full"
/>
</button>
{/each}
</div>
<!-- -->
</Dialog.Content>
</Dialog.Root>
@@ -0,0 +1,81 @@
<script lang="ts">
import * as Dialog from '@cio/ui/base/dialog';
import { htmlBody, openModal } from './store';
interface Background {
src: string;
}
let backgrounds: Background[] = [
{
src: 'blue_tetiary_background'
},
{
src: 'blue_secondary_background'
},
{
src: 'blue_primary_background'
},
{
src: 'purple_tetiary_background'
},
{
src: 'purple_secondary_background'
},
{
src: 'purple_primary_background'
},
{
src: 'black_tetiary_background'
},
{
src: 'black_secondary_background'
},
{
src: 'black_primary_background'
},
{
src: 'yellow_tetiary_background'
},
{
src: 'yellow_secondary_background'
},
{
src: 'yellow_primary_background'
}
];
function selectBackground(background: Background) {
$htmlBody.background = background.src;
$openModal.background = false;
}
</script>
<Dialog.Root bind:open={$openModal.background}>
<Dialog.Content class="h-full max-h-[60vh]">
<Dialog.Header>
<Dialog.Title>Choose your Background</Dialog.Title>
</Dialog.Header>
<!-- -->
<div
class="flex flex-wrap justify-between gap-y-3 mt-3 overflow-y-auto overflow-x-hidden h-[85%]"
>
{#each backgrounds as background}
<button
on:click={() => {
selectBackground(background);
}}
class="flex w-[30%] items-center shadow-sm hover:scale-110 transition-all duration-300"
>
<img
src="https://assets.cdn.clsrio.com/progress-report/backgrounds/{background.src}.webp"
alt=""
class=""
/>
</button>
{/each}
</div>
<!-- -->
</Dialog.Content>
</Dialog.Root>
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { toPng } from 'html-to-image'; import { toPng } from 'html-to-image';
import { nodeStore, htmlBody, openModal } from './store'; import { nodeStore, htmlBody, openModal } from './store';
import { Button } from '@cio/ui/base/button';
export let isDisabled: boolean; export let isDisabled: boolean;
export let isDownloading: boolean; export let isDownloading: boolean;
@@ -39,14 +40,12 @@
} }
</script> </script>
<button <Button
type="button" type="button"
disabled={isDownloading || !isDisabled ? true : false} disabled={isDownloading || !isDisabled}
on:click={convertToPng} onclick={convertToPng}
class="{isDownloading || !isDisabled loading={isDownloading}
? 'bg-gray-200 cursor-not-allowed text-gray-600' class="{text === 'Generate Progress Report' ? 'block md:hidden' : ''} mt-5 w-full"
: 'bg-[#0233BD] text-white'} {text === 'Generate Progress Report'
? 'block md:hidden'
: ''} mt-5 transition-all delay-100 text-white text-xs font-semibold w-full py-3 rounded-md"
>{isDownloading ? 'Loading...' : text}</button
> >
{isDownloading ? 'Loading...' : text}
</Button>
@@ -0,0 +1,13 @@
<script lang="ts">
import * as Dialog from '@cio/ui/base/dialog';
import Report from './report.svelte';
import { openModal } from './store';
</script>
<Dialog.Root bind:open={$openModal.fullscreen}>
<Dialog.Content showCloseButton={false}>
<!-- -->
<Report />
<!-- -->
</Dialog.Content>
</Dialog.Root>
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import Modal from '$lib/Modal/Modal.svelte'; import * as Dialog from '@cio/ui/base/dialog';
import { htmlBody, openModal } from './store'; import { htmlBody, openModal } from './store';
interface Mood { interface Mood {
@@ -65,36 +65,30 @@
} }
</script> </script>
<Modal bind:open={$openModal.mood}> <Dialog.Root bind:open={$openModal.mood}>
<!-- header --> <Dialog.Content class="w-full md:w-[30%] md:min-w-[500px]">
<div class="flex justify-between"> <Dialog.Header>
<h1 class="text-sm font-semibold">Choose your Mood</h1> <Dialog.Title>Choose your Mood</Dialog.Title>
<button </Dialog.Header>
type="button"
on:click={() => ($openModal.mood = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<!-- mapping --> <!-- mapping -->
<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={() => { on:click={() => {
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"
> >
<p class="text-xs md:text-sm font-semibold">{mood.text}</p> <p class="text-xs md:text-sm font-semibold">{mood.text}</p>
<img <img
src="https://assets.cdn.clsrio.com/progress-report/emojis/{mood.iconSrc}.png" src="https://assets.cdn.clsrio.com/progress-report/emojis/{mood.iconSrc}.png"
alt={mood.text} alt={mood.text}
class="w-4" class="w-4"
/> />
</button> </button>
{/each} {/each}
</div> </div>
<!-- --> <!-- -->
</Modal> </Dialog.Content>
</Dialog.Root>
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { sineInOut } from 'svelte/easing'; import { sineInOut } from 'svelte/easing';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import WinningLine from './WinningLine.svelte'; import WinningLine from './winning-line.svelte';
import { changeToComputer } from './store'; import { changeToComputer } from './store';
interface Game { interface Game {
+4
View File
@@ -0,0 +1,4 @@
// Re-export all components for convenient importing
// Usage: import { Footer, Navigation, Hero } from '$lib/components'
export * from './components';
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import { img } from '$lib/custom/index'; import { CustomImg as img } from '$lib/components';
export { img }; export { img };
</script> </script>
@@ -1,6 +1,7 @@
<script> <script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte'; import { PageSignupCTA, PageHeader } from '$lib/components';
import PageHeader from '$lib/PageHeader/PageHeader.svelte'; import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [ const benefitDetails = [
{ {
@@ -77,20 +78,15 @@
<PageHeader <PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8" className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
> >
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center"> <Badge class="py-2! px-4!" variant="outline">Solution for Bootcamps</Badge>
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Solution for Bootcamps
</p>
</div>
<div <div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8" class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
> >
<div <div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4" class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
> >
<h1 <h1 class="mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
class="mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
The platform for fast The platform for fast
<span class="text-blue-700 relative"> growing bootcamps</span> <span class="text-blue-700 relative"> growing bootcamps</span>
</h1> </h1>
@@ -99,13 +95,9 @@
everything you need to run a cutting-edge, high-impact bootcamp. everything you need to run a cutting-edge, high-impact bootcamp.
</p> </p>
</div> </div>
<a <Button href="/signup" target="_blank" rel="noopener noreferrer nofollow">
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
Get Started for Free 🚀 Get Started for Free 🚀
</a> </Button>
</div> </div>
</PageHeader> </PageHeader>
@@ -115,11 +107,10 @@
<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"
> >
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center"> <Badge class="py-2! px-4!" variant="outline">Our benefits</Badge>
<p class=" text-sm md:text-base font-medium">Our benefits</p>
</div>
<div <div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4" class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
> >
<h1 class="text-3xl md:text-5xl font-bold">Inspire Student Success</h1> <h1 class="text-3xl md:text-5xl font-bold">Inspire Student Success</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]"> <p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -130,7 +121,7 @@
</div> </div>
<div <div
class="w-full max-w-screen-xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start" class="w-full max-w-7xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
> >
{#each benefitDetails as benefits} {#each benefitDetails as benefits}
<div class="w-full md:p-6 p-4 border border-[#D9E0F5] rounded-2xl flex flex-col gap-y-6"> <div class="w-full md:p-6 p-4 border border-[#D9E0F5] rounded-2xl flex flex-col gap-y-6">
@@ -164,11 +155,10 @@
class="w-full bg-[#F1F6FF] flex flex-col items-center justify-center gap-y-16 lg:py-24 py-16 lg:px-28 md:px-16 px-8" class="w-full bg-[#F1F6FF] flex flex-col items-center justify-center gap-y-16 lg:py-24 py-16 lg:px-28 md:px-16 px-8"
> >
<div <div
class="w-full max-w-screen-xl gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start" class="w-full max-w-7xl gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
> >
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center"> <Badge class="py-2! px-4!" variant="outline">Explore our features</Badge>
<p class=" text-sm md:text-base font-medium">Explore our features</p>
</div>
<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"
> >
@@ -181,9 +171,7 @@
</div> </div>
</div> </div>
<div <div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16">
class="w-full max-w-screen-xl 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 lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -206,21 +194,17 @@
</div> </div>
{#if feature.ctaLink} {#if feature.ctaLink}
<a <Button variant="outline" href={feature.ctaLink} target="_blank noopener noreferrer">
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
href={feature.ctaLink}
target="_blank noopener noreferrer"
>
{feature.ctaLabel} {feature.ctaLabel}
</a> </Button>
{:else} {:else}
<button <Button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold" variant="outline"
data-cal-config="'layout':'month_view'" data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo" data-cal-link="classroomio/demo"
> >
Book a Demo Book a Demo
</button> </Button>
{/if} {/if}
</div> </div>
@@ -1,6 +1,7 @@
<script> <script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte'; import { PageSignupCTA, PageHeader } from '$lib/components';
import PageHeader from '$lib/PageHeader/PageHeader.svelte'; import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [ const benefitDetails = [
{ {
@@ -61,20 +62,15 @@
<PageHeader <PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8" className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
> >
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center"> <Badge class="py-2! px-4!" variant="outline">Solution for Customer Education</Badge>
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Solution for Customer Education
</p>
</div>
<div <div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8" class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
> >
<div <div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4" class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
> >
<h1 <h1 class="w-[95%] mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
class="w-[95%] mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
Drive retention and revenue with Drive retention and revenue with
<span class="text-blue-700 relative">Customer Education</span> <span class="text-blue-700 relative">Customer Education</span>
</h1> </h1>
@@ -84,13 +80,9 @@
table. table.
</p> </p>
</div> </div>
<a <Button href="/signup" target="_blank" rel="noopener noreferrer nofollow">
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
Get Started for Free 🚀 Get Started for Free 🚀
</a> </Button>
</div> </div>
</PageHeader> </PageHeader>
@@ -100,11 +92,9 @@
<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"
> >
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center"> <Badge class="py-2! px-4!" variant="outline">Our benefits</Badge>
<p class="text-sm md:text-base font-medium">Our benefits</p>
</div>
<div <div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4" class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
> >
<h1 class="text-3xl md:text-5xl font-bold">The customer education toolkit</h1> <h1 class="text-3xl md:text-5xl font-bold">The customer education toolkit</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]"> <p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -115,7 +105,7 @@
</div> </div>
<div <div
class="w-full max-w-screen-xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start" class="w-full max-w-7xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
> >
{#each benefitDetails as benefits} {#each benefitDetails as benefits}
<div class="w-full md:p-6 p-4 border border-[#D9E0F5] rounded-2xl flex flex-col gap-y-6"> <div class="w-full md:p-6 p-4 border border-[#D9E0F5] rounded-2xl flex flex-col gap-y-6">
@@ -151,7 +141,7 @@
</p> </p>
</div> </div>
<div <div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4" class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
> >
<h1 class="text-3xl md:text-5xl font-bold">Why Choose Us For Education Needs</h1> <h1 class="text-3xl md:text-5xl font-bold">Why Choose Us For Education Needs</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]"> <p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -161,9 +151,7 @@
</div> </div>
</div> </div>
<div <div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16">
class="w-full max-w-screen-xl 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 lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -183,13 +171,13 @@
{feature.description} {feature.description}
</p> </p>
</div> </div>
<button <Button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold" variant="outline"
data-cal-config="'layout':'month_view'" data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo" data-cal-link="classroomio/demo"
> >
Book a Demo Book a Demo
</button> </Button>
</div> </div>
<div> <div>
@@ -1,6 +1,7 @@
<script> <script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte'; import { PageSignupCTA, PageHeader } from '$lib/components';
import PageHeader from '$lib/PageHeader/PageHeader.svelte'; import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [ const benefitDetails = [
{ {
@@ -57,20 +58,15 @@
<PageHeader <PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8" className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
> >
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center"> <Badge class="py-2! px-4!" variant="outline">Unlock your team's potential</Badge>
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Unlock your team's potential
</p>
</div>
<div <div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8" class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
> >
<div <div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4" class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
> >
<h1 <h1 class="mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
class="mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
Employee training software for Employee training software for
<br /><span class="text-blue-700 relative">modern teams</span> <br /><span class="text-blue-700 relative">modern teams</span>
</h1> </h1>
@@ -79,23 +75,15 @@
quickly. quickly.
</p> </p>
</div> </div>
<a <Button href="/signup" target="_blank">Get Started for Free 🚀</Button>
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
Get Started for Free 🚀
</a>
</div> </div>
</PageHeader> </PageHeader>
<section <section
class="w-full max-w-screen-xl lg:py-24 py-16 lg:px-28 md:px-16 px-8 flex lg:flex-row flex-col lg:items-center lg:justify-between justify-center gap-x-6 gap-y-8" class="w-full max-w-7xl lg:py-24 py-16 lg:px-28 md:px-16 px-8 flex lg:flex-row flex-col lg:items-center lg:justify-between justify-center gap-x-6 gap-y-8"
> >
<div class="w-full lg:w-[35%] gap-y-4 flex flex-col items-start justify-start"> <div class="w-full lg:w-[35%] gap-y-4 flex flex-col items-start justify-start">
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center"> <Badge variant="outline">Amazing benefits</Badge>
<p class=" text-sm md:text-base font-medium">Amazing benefits</p>
</div>
<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="font-bold text-4xl">Unlock your team's potential</h1>
@@ -134,13 +122,10 @@
<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"
> >
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center"> <Badge class="py-2! px-4!" variant="outline">Explore our features</Badge>
<p class="text-sm md:text-base text-[#4B5563] text-center font-medium">
Explore our features
</p>
</div>
<div <div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4" class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
> >
<h1 class="text-3xl md:text-5xl font-bold">What makes us the right choice</h1> <h1 class="text-3xl md:text-5xl font-bold">What makes us the right choice</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]"> <p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -150,9 +135,7 @@
</div> </div>
</div> </div>
<div <div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-16 p-2">
class="w-full max-w-screen-xl flex flex-col justify-start items-center lg:gap-y-24 gap-16 p-2"
>
{#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 lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -173,13 +156,13 @@
{feature.description} {feature.description}
</p> </p>
</div> </div>
<button <Button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold" variant="outline"
data-cal-config="'layout':'month_view'" data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo" data-cal-link="classroomio/demo"
> >
Book a Demo Book a Demo
</button> </Button>
</div> </div>
<div> <div>
@@ -198,7 +181,7 @@
class="w-full bg-[#040F2D] flex flex-col items-center justify-start gap-y-16 lg:py-24 py-16 lg:px-28 md:px-16 px-8" class="w-full bg-[#040F2D] flex flex-col items-center justify-start gap-y-16 lg:py-24 py-16 lg:px-28 md:px-16 px-8"
> >
<div <div
class="w-full max-w-screen-xl flex lg:flex-row-reverse flex-col-reverse lg:justify-between justify-start lg:items-center items-start gap-y-8" class="w-full max-w-7xl flex lg:flex-row-reverse flex-col-reverse lg:justify-between justify-start lg:items-center items-start gap-y-8"
> >
<div class="w-full lg:w-[40%] gap-y-4 flex flex-col items-start justify-start"> <div class="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">
@@ -209,13 +192,7 @@
Companies get the opportunity to request custom features from us. Companies get the opportunity to request custom features from us.
</p> </p>
</div> </div>
<a <Button href="/demo" target="_blank" variant="outline">Talk to sales</Button>
href="/demo"
target="_blank"
class="py-4 px-8 border border-tranparent outline-transparent bg-white rounded text-[#040F2D] text-sm font-semibold"
>
Talk to sales
</a>
</div> </div>
<img <img
+1 -3
View File
@@ -1,9 +1,7 @@
<script> <script>
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import { page } from '$app/state'; import { page } from '$app/state';
import Footer from '$lib/Footer/Footer.svelte'; import { Footer, Navigation, NotFound } from '$lib/components';
import Navigation from '$lib/Navigation/Navigation.svelte';
import NotFound from '$lib/NotFound/NotFound.svelte';
import extend from 'just-extend'; import extend from 'just-extend';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { MetaTags } from 'svelte-meta-tags'; import { MetaTags } from 'svelte-meta-tags';
+1 -6
View File
@@ -1,10 +1,5 @@
<script> <script>
import Hero from '$lib/Home/Hero.svelte'; import { Hero, CoreFeatures, Faq, StudentDashboard, MoreFeatures, Testimonial } from '$lib/components';
import CoreFeatures from '$lib/Home/CoreFeatures.svelte';
import Faq from '$lib/Home/Faq.svelte';
import StudentDashboard from '$lib/Home/StudentDashboard.svelte';
import MoreFeatures from '$lib/Home/MoreFeatures.svelte';
import Testimonial from '$lib/Home/Testimonial.svelte';
import { PUBLIC_ENABLE_FAQ } from '$env/static/public'; import { PUBLIC_ENABLE_FAQ } from '$env/static/public';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
+1 -1
View File
@@ -1,5 +1,5 @@
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import type { BlogPost } from '$lib/types'; import type { BlogPost } from '$lib/utils/types';
async function getBlogPosts() { async function getBlogPosts() {
let blogPosts: BlogPost[] = []; let blogPosts: BlogPost[] = [];
+28 -27
View File
@@ -1,38 +1,39 @@
import { PUBLIC_SERVER_URL } from '$env/static/public'; import { PUBLIC_SERVER_URL } from '$env/static/public';
const sendEmail = async (subject: string, content: string, replyTo?: string) => { const sendEmail =
try { (sFetch: typeof fetch) => async (subject: string, content: string, replyTo?: string) => {
const response = await fetch(`${PUBLIC_SERVER_URL}/sendEmail`, { try {
method: 'POST', const response = await sFetch(`${PUBLIC_SERVER_URL}/sendEmail`, {
headers: { method: 'POST',
'Content-Type': 'application/json' headers: {
}, 'Content-Type': 'application/json'
body: JSON.stringify([ },
{ body: JSON.stringify([
from: `"ClassroomIO Support" <[email protected]>`, {
to: 'help@classroomio.com', from: `"ClassroomIO Support" <notify@mail.classroomio.com>`,
subject, to: '[email protected]',
content, subject,
replyTo content,
} replyTo
]) }
}); ])
});
const responseData = await response.json(); const responseData = await response.json();
console.log('Emails sent successfully:', responseData); console.log('Emails sent successfully:', responseData);
return 'sent'; return 'sent';
} catch (error) { } catch (error) {
console.error('Error sending email', error); console.error('Error sending email', error);
return 'failed'; return 'failed';
} }
}; };
export async function POST({ request }) { export async function POST({ request, fetch }) {
try { try {
const { title, description, replyTo } = await request.json(); const { title, description, replyTo } = await request.json();
console.log(title, description, replyTo); console.log(title, description, replyTo);
const result = await sendEmail(title, description, replyTo); const result = await sendEmail(fetch)(title, description, replyTo);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
+2 -6
View File
@@ -1,7 +1,5 @@
<script> <script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte'; import { PageSignupCTA, PageHeader, BlogListItem } from '$lib/components';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import BlogListItem from '$lib/Blog/BlogListItem.svelte';
export let data; export let data;
</script> </script>
@@ -12,9 +10,7 @@
<section> <section>
<PageHeader className="flex flex-col items-center justify-center text-center"> <PageHeader className="flex flex-col items-center justify-center text-center">
<h1 <h1 class="mx-auto text-4xl md:text-6xl text-slate-900 flex flex-col items-center">
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<span>What's new on</span> <span>What's new on</span>
<span class="text-blue-700 relative">ClassroomIO?</span> <span class="text-blue-700 relative">ClassroomIO?</span>
</h1> </h1>
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BlogPost } from '$lib/types'; import type { BlogPost } from '$lib/utils/types';
/** @type {import('./$types').PageLoad} */ /** @type {import('./$types').PageLoad} */
export async function load({ fetch }) { export async function load({ fetch }) {
@@ -1,8 +1,10 @@
<script lang="ts"> <script lang="ts">
import { ChevronLeft } from 'carbon-icons-svelte'; import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import Chip from '$lib/Chip/Chip.svelte'; import { Badge } from '@cio/ui/base/badge';
import { formatDate } from '$lib/utils/formatDate'; import { formatDate } from '$lib/utils/format-date';
import BlogListItem from '$lib/Blog/BlogListItem.svelte'; import { Button } from '@cio/ui/base/button';
import { BlogListItem } from '$lib/components';
export let data; export let data;
</script> </script>
@@ -11,13 +13,13 @@
{#if data} {#if data}
<article class="py-16"> <article class="py-16">
<!-- Title --> <!-- Title -->
<hgroup class="flex flex-col items-center justify-center text-center w-full"> <hgroup class="flex w-full flex-col items-center justify-center text-center">
<p class="text-sm text-gray-500">{formatDate(data.meta.date)}</p> <p class="text-sm text-gray-500">{formatDate(data.meta.date)}</p>
<p class="font-bold text-3xl py-2 text-center md:w-[60%]">{@html data.meta.title}</p> <p class="py-2 text-center text-3xl font-bold md:w-[60%]">{@html data.meta.title}</p>
</hgroup> </hgroup>
<main class="mx-auto max-w-screen-md px-4 lg:px-8"> <main class="mx-auto max-w-3xl px-4 lg:px-8">
<div class="flex items-center justify-start gap-4 my-2 border-y border-gray-200 py-4"> <div class="my-2 flex items-center justify-start gap-4 border-y border-gray-200 py-4">
<img loading="lazy" src={data.meta.avatar} alt="avatar" class="w-10 h-10 rounded-full" /> <img loading="lazy" src={data.meta.avatar} alt="avatar" class="h-10 w-10 rounded-full" />
<span> <span>
<p class="font-semibold">{data.meta.author}</p> <p class="font-semibold">{data.meta.author}</p>
<p class="text-gray-500">{data.meta.role}</p> <p class="text-gray-500">{data.meta.role}</p>
@@ -25,12 +27,12 @@
</div> </div>
<!-- Post --> <!-- Post -->
<div class="prose pt-2 pb-4 border-b-2 border-gray-200"> <div class="prose border-b-2 border-gray-200 pb-4 pt-2">
<svelte:component this={data.content} /> <svelte:component this={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}
<Chip label={tag} /> <Badge variant="outline">{tag}</Badge>
{/each} {/each}
</div> </div>
</div> </div>
@@ -40,7 +42,7 @@
<p class="text-xl font-semibold">Related Posts</p> <p class="text-xl font-semibold">Related Posts</p>
<ul class="flex items-start justify-start gap-3 overflow-x-scroll"> <ul class="flex items-start justify-start gap-3 overflow-x-scroll">
{#each data.relatedPosts as post} {#each data.relatedPosts as post}
<li class="py-10 min-w-[80%] sm:w-80 sm:min-w-0"> <li class="min-w-[80%] py-10 sm:w-80 sm:min-w-0">
<BlogListItem {post} isRecommended /> <BlogListItem {post} isRecommended />
</li> </li>
{/each} {/each}
@@ -48,12 +50,9 @@
</section> </section>
{/if} {/if}
<a <Button href="/blog" variant="link">
href="/blog" <ChevronLeft /> Back to all posts
class="flex font-medium items-center gap-2 text-slate-700 text-lg underline my-10" </Button>
>
<ChevronLeft class=" w-6 h-6 font-medium" /> Back to all posts
</a>
</main> </main>
</article> </article>
{/if} {/if}
+1 -1
View File
@@ -1,5 +1,5 @@
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import type { BlogPost, RawBlogPost } from '$lib/types'; import type { BlogPost, RawBlogPost } from '$lib/utils/types';
import type { MetaTagsProps } from 'svelte-meta-tags'; import type { MetaTagsProps } from 'svelte-meta-tags';
/** @type {import('./$types').PageLoad} */ /** @type {import('./$types').PageLoad} */
+16 -17
View File
@@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import ArrowUpRight from 'carbon-icons-svelte/lib/ArrowUpRight.svelte'; import ArrowUpRight from '@lucide/svelte/icons/arrow-up-right';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import FeedbackForm from '$lib/Contact/FeedbackForm.svelte'; import { FORM_TYPE } from '$lib/utils/types';
import HelpForm from '$lib/Contact/HelpForm.svelte'; import type { FORM_TYPE_KEY } from '$lib/utils/types';
import { FORM_TYPE } from '$lib/types';
import type { FORM_TYPE_KEY } from '$lib/types'; import HelpForm from '$lib/features/contact/help-form.svelte';
import { PageHeader } from '$lib/components';
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
let currentForm: FORM_TYPE_KEY; let currentForm: FORM_TYPE_KEY;
@@ -45,40 +46,38 @@
<section> <section>
<PageHeader className="flex flex-col items-center justify-center bg-[#F5F8FE] text-center"> <PageHeader className="flex flex-col items-center justify-center bg-[#F5F8FE] text-center">
<div class="mb-2 ml-[5%] lg:ml-0 flex w-full items-center justify-center"> <div class="mb-2 ml-[5%] flex w-full items-center justify-center lg:ml-0">
<span <span
style="color: rgb(75, 85, 99);" style="color: rgb(75, 85, 99);"
class="rounded-full border-2 border-[#C2D2FF] px-4 py-1 text-sm font-medium bg-[#DCE5FF] text-blue-700" class="rounded-full border-2 border-[#C2D2FF] bg-[#DCE5FF] px-4 py-1 text-sm font-medium text-blue-700"
> >
Contact Us Contact Us
</span> </span>
</div> </div>
<h1 <h1 class="mx-auto flex flex-col items-center text-4xl text-slate-900 md:text-6xl">
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<span>Get in touch with <span class="text-blue-700">Us</span></span> <span>Get in touch with <span class="text-blue-700">Us</span></span>
</h1> </h1>
<p <p
class="w-[90%] md:w-[60%] max-w-3xl text-center font-normal text-lg text-slate-700 mt-10 lg:mt-7" class="mt-10 w-[90%] max-w-3xl text-center text-lg font-normal text-slate-700 md:w-[60%] lg:mt-7"
> >
Report any bugs you encounter, request new features you'd like to see, or ask any questions Report any bugs you encounter, request new features you'd like to see, or ask any questions
you have about ClassroomIO you have about ClassroomIO
</p> </p>
</PageHeader> </PageHeader>
<div class="my-20 mx-auto"> <div class="mx-auto my-20">
<!-- Content here --> <!-- Content here -->
<div <div
class="mx-auto w-[90%] lg:w-[60%] place-items-center grid grid-cols-1 md:grid-cols-2 gap-10" class="mx-auto grid w-[90%] grid-cols-1 place-items-center gap-10 md:grid-cols-2 lg:w-[60%]"
> >
{#each formList as list} {#each formList as list}
<button <button
on:click={() => onChange(list.id, list.to)} on:click={() => onChange(list.id, list.to)}
class="text-start border-[1.5px] w-full rounded-md p-3 {currentForm === list.id class="w-full rounded-md border-[1.5px] p-3 text-start {currentForm === list.id
? `border-[#0233BD]` ? `border-[#0233BD]`
: `border-gray-200`} flex items-center justify-between" : `border-gray-200`} flex items-center justify-between"
> >
<p class="text-lg text-slate-700 font-normal">{list.title}</p> <p class="text-lg font-normal text-slate-700">{list.title}</p>
{#if list.to} {#if list.to}
<ArrowUpRight size={16} /> <ArrowUpRight size={16} />
{/if} {/if}
@@ -87,7 +86,7 @@
</div> </div>
{#if currentForm === FORM_TYPE[currentForm]} {#if currentForm === FORM_TYPE[currentForm]}
<div class="mx-auto w-[90%] lg:w-[60%] px-5"> <div class="mx-auto w-[90%] px-5 lg:w-[60%]">
{#if currentForm === FORM_TYPE.HELP} {#if currentForm === FORM_TYPE.HELP}
<HelpForm /> <HelpForm />
{:else if currentForm === FORM_TYPE.FEEDBACK} {:else if currentForm === FORM_TYPE.FEEDBACK}
@@ -1,5 +1,6 @@
<script> <script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte'; import { PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
export let data; export let data;
</script> </script>
@@ -35,18 +36,14 @@
</p> </p>
</div> </div>
<div class="text-left px-6 mt-3"> <div class="text-left px-6 mt-3">
<a <Button
href={friend.href} href={friend.href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="inline-block w-full" variant="outline"
> >
<button Learn more
class="font-semibold text-base text-white bg-gray-800 hover:bg-gray-900 hover:shadow-xl transition-all delay-100 px-4 py-3 rounded-md" </Button>
>
Learn more
</button>
</a>
</div> </div>
</li> </li>
{/each} {/each}
+1 -1
View File
@@ -1,4 +1,4 @@
import type { OssFriend } from '$lib/types'; import type { OssFriend } from '$lib/utils/types';
export async function load() { export async function load() {
const friends: OssFriend[] = [ const friends: OssFriend[] = [
+17 -26
View File
@@ -1,9 +1,10 @@
<script> <script>
import Check from '@lucide/svelte/icons/check';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import Checkmark from 'carbon-icons-svelte/lib/Checkmark.svelte';
import { PLANS } from '@cio/utils/plans'; import { PLANS } from '@cio/utils/plans';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte'; import { PageSignupCTA, PageHeader } from '$lib/components';
let isYearlyPlan = false; let isYearlyPlan = false;
@@ -18,16 +19,14 @@
<section> <section>
<PageHeader className="flex flex-col items-center justify-center text-center"> <PageHeader className="flex flex-col items-center justify-center text-center">
<h1 <h1 class="mx-auto flex flex-col items-center text-4xl text-slate-900 md:text-6xl">
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<span>There is room for</span> <span>There is room for</span>
<span class="text-blue-700 relative">Everyone.</span> <span class="relative text-blue-700">Everyone.</span>
</h1> </h1>
<p class="w-[90%] md:w-[60%] text-center font-normal text-lg text-slate-700 mt-10 lg:mt-7"> <p class="mt-10 w-[90%] text-center text-lg font-normal text-slate-700 md:w-[60%] lg:mt-7">
You get a customizable LMS, AI integration for productive educators and many more... You get a customizable LMS, AI integration for productive educators and many more...
</p> </p>
<div class="relative mt-10 flex items-center rounded-[30px] border-[2px] p-[2px] lg:scale-100"> <div class="relative mt-10 flex items-center rounded-[30px] border-2 p-[2px] lg:scale-100">
<button <button
style="background-color: {isYearlyPlan ? 'initial' : '#1D4EE2'}; color: {isYearlyPlan style="background-color: {isYearlyPlan ? 'initial' : '#1D4EE2'}; color: {isYearlyPlan
? '#5e636b' ? '#5e636b'
@@ -71,7 +70,7 @@
{PLANS.BASIC.NAME} {PLANS.BASIC.NAME}
</p> </p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-gray-500 lg:leading-6"> <p class="mt-4 text-sm font-light leading-6 text-gray-500 lg:text-base lg:leading-6">
{PLANS.BASIC.DESCRIPTION} {PLANS.BASIC.DESCRIPTION}
</p> </p>
@@ -81,7 +80,7 @@
<span class="text-base">Free forever</span> <span class="text-base">Free forever</span>
<a <a
class="text-base mt-10 block w-full rounded-md bg-slate-900 py-3 text-center font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold" class="mt-10 block w-full rounded-md bg-slate-900 py-3 text-center text-base font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
href={PLANS.BASIC.CTA.LINK} href={PLANS.BASIC.CTA.LINK}
target="_blank" target="_blank"
> >
@@ -91,11 +90,7 @@
<ul class="mt-8 space-y-2 text-sm text-gray-600 lg:space-y-5 xl:mt-10"> <ul class="mt-8 space-y-2 text-sm text-gray-600 lg:space-y-5 xl:mt-10">
{#each PLANS.BASIC.FEATURES as feature} {#each PLANS.BASIC.FEATURES as feature}
<li class="flex items-center"> <li class="flex items-center">
<Checkmark <Check size={24} fill="#1D4EE2" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
size={24}
fill="#1D4EE2"
class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]"
/>
{feature} {feature}
</li> </li>
{/each} {/each}
@@ -108,7 +103,7 @@
{PLANS.EARLY_ADOPTER.NAME} {PLANS.EARLY_ADOPTER.NAME}
</p> </p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-white lg:leading-6"> <p class="mt-4 text-sm font-light leading-6 text-white lg:text-base lg:leading-6">
{PLANS.EARLY_ADOPTER.DESCRIPTION} {PLANS.EARLY_ADOPTER.DESCRIPTION}
</p> </p>
@@ -125,7 +120,7 @@
{/key} {/key}
<a <a
class="text-base mt-10 block w-full rounded-md bg-white py-3 text-center font-medium text-slate-900 hover:bg-indigo-50 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold" class="mt-10 block w-full rounded-md bg-white py-3 text-center text-base font-medium text-slate-900 hover:bg-indigo-50 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
href={PLANS.EARLY_ADOPTER.CTA.LINK} href={PLANS.EARLY_ADOPTER.CTA.LINK}
target="_blank" target="_blank"
> >
@@ -134,7 +129,7 @@
<ul class="mt-8 space-y-3 text-sm font-light text-white lg:space-y-5 xl:mt-10"> <ul class="mt-8 space-y-3 text-sm font-light text-white lg:space-y-5 xl:mt-10">
{#each PLANS.EARLY_ADOPTER.FEATURES as features} {#each PLANS.EARLY_ADOPTER.FEATURES as features}
<li class="flex items-center"> <li class="flex items-center">
<Checkmark size={24} fill="#fff" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" /> <Check size={24} fill="#fff" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
{features} {features}
</li> </li>
{/each} {/each}
@@ -146,14 +141,14 @@
<p class="text-lg font-semibold leading-8 text-gray-900 lg:text-xl"> <p class="text-lg font-semibold leading-8 text-gray-900 lg:text-xl">
{PLANS.ENTERPRISE.NAME} {PLANS.ENTERPRISE.NAME}
</p> </p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-gray-500 lg:leading-6"> <p class="mt-4 text-sm font-light leading-6 text-gray-500 lg:text-base lg:leading-6">
{PLANS.ENTERPRISE.DESCRIPTION} {PLANS.ENTERPRISE.DESCRIPTION}
</p> </p>
<p class="mt-6 flex items-baseline gap-x-1 text-xl font-medium lg:mt-6 lg:text-xl"> <p class="mt-6 flex items-baseline gap-x-1 text-xl font-medium lg:mt-6 lg:text-xl">
Request Pricing Request Pricing
</p> </p>
<button <button
class="text-base mt-10 w-full rounded-md bg-slate-900 py-3 font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold" class="mt-10 w-full rounded-md bg-slate-900 py-3 text-base font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
data-cal-config={"{'layout':'month_view'}"} data-cal-config={"{'layout':'month_view'}"}
data-cal-link="classroomio/enterprise" data-cal-link="classroomio/enterprise"
> >
@@ -163,11 +158,7 @@
<ul class="mt-8 space-y-2 text-sm text-gray-600 lg:space-y-5 xl:mt-10"> <ul class="mt-8 space-y-2 text-sm text-gray-600 lg:space-y-5 xl:mt-10">
{#each PLANS.ENTERPRISE.FEATURES as features} {#each PLANS.ENTERPRISE.FEATURES as features}
<li class="flex items-center"> <li class="flex items-center">
<Checkmark <Check size={24} fill="#1D4EE2" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
size={24}
fill="#1D4EE2"
class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]"
/>
{features} {features}
</li> </li>
{/each} {/each}
+3 -7
View File
@@ -1,7 +1,5 @@
<script> <script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte'; import { PageHeader, Timeline, PageSignupCTA } from '$lib/components';
import Timeline from '$lib/Timeline/Timeline.svelte';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
const newFeatures = [ const newFeatures = [
{ {
@@ -67,13 +65,11 @@
</svelte:head> </svelte:head>
<PageHeader className="flex flex-col items-center justify-center text-center"> <PageHeader className="flex flex-col items-center justify-center text-center">
<h1 class="text-start md:text-center text-5xl md:text-7xl font-bold text-slate-900"> <h1 class="text-start md:text-center text-4xl md:text-6xl text-slate-900">Product Roadmap</h1>
Product Roadmap
</h1>
<p class="text-start md:text-center w-[80%] md:w-[40%] text-slate-700 mt-10"> <p class="text-start md:text-center w-[80%] md:w-[40%] text-slate-700 mt-10">
This is a live document, and will be updated regularly. To submit an idea, or to view a list of This is a live document, and will be updated regularly. To submit an idea, or to view a list of
feature requests and ideas that we're considering, check out our <a feature requests and ideas that we're considering, check out our <a
href="https://github.com/rotimi-best/classroomio/discussion" href="https://github.com/classroomio/classroomio/discussion"
class="underline">GitHub Discussions</a class="underline">GitHub Discussions</a
> >
</p> </p>
+43 -57
View File
@@ -1,26 +1,23 @@
<script lang="ts"> <script lang="ts">
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte'; import { PageSignupCTA, PageHeader } from '$lib/components';
import PageHeader from '$lib/PageHeader/PageHeader.svelte'; import { Button } from '@cio/ui/base/button';
const whyStart = [ const whyStart = [
{ {
src: '/teach/teach-from-anywhere.svg', src: '/teach/teach-from-anywhere.svg',
header: 'Fully Online; Teach from anywhere', header: 'Fully Online; Teach from anywhere',
subText: subText: ' Facilitate a subject from the comfort of your space, engaging students from highly diverse background'
' Facilitate a subject from the comfort of your space, engaging students from highly diverse background'
}, },
{ {
src: '/teach/get-paid.svg', src: '/teach/get-paid.svg',
header: 'Get paid extra commission', header: 'Get paid extra commission',
subText: subText: 'Get paid for the course you design and earn extra comission based on course perfomance'
'Get paid for the course you design and earn extra comission based on course perfomance'
}, },
{ {
src: '/teach/inspire-learners.svg', src: '/teach/inspire-learners.svg',
header: 'Inspire learners', header: 'Inspire learners',
subText: subText: 'Your opportunity to inspire thousands of learners to achieve dreams and academic pursue exellence'
'Your opportunity to inspire thousands of learners to achieve dreams and academic pursue exellence'
} }
]; ];
@@ -79,22 +76,12 @@
<title>Teach | ClassroomIO</title> <title>Teach | ClassroomIO</title>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/teach.png" /> <meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/teach.png" />
<meta property="og:title" content="Teach on ClassroomIO" /> <meta property="og:title" content="Teach on ClassroomIO" />
<meta <meta property="og:description" content="On ClassroomIO you can make side income while doing what you love." />
property="og:description"
content="On ClassroomIO you can make side income while doing what you love."
/>
<meta <meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/teach.png" />
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/teach.png"
/>
<meta name="twitter:title" content="Teach on ClassroomIO" /> <meta name="twitter:title" content="Teach on ClassroomIO" />
<meta <meta name="twitter:description" content="On ClassroomIO you can make side income while doing what you love." />
name="twitter:description"
content="On ClassroomIO you can make side income while doing what you love."
/>
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/teach.png" /> <meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/teach.png" />
</svelte:head> </svelte:head>
@@ -102,17 +89,16 @@
<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-5xl font-bold">Teach with us</h1>
<p class="my-5 lg:w-[77%] lg:text-left text-center"> <p class="my-5 text-center lg:w-[77%] lg:text-left">
With ClassroomIO, you can make side income while teaching the subject you love With ClassroomIO, you can make side income while teaching the subject you love
</p> </p>
<a <Button
href="/teach/register" href="/teach/register"
target="_blank" target="_blank"
class="w-full block text-center lg:w-[30%] py-3 text-base text-white rounded-md bg-blue-700"
> >
Register Register
</a> </Button>
</div> </div>
<div class=" "> <div class=" ">
@@ -120,21 +106,21 @@
</div> </div>
</PageHeader> </PageHeader>
<div class="mt-10 mx-auto"> <div class="mx-auto mt-10">
<div class="px-[10%] pb-10 border-b border-slate-100"> <div class="border-b border-slate-100 px-[10%] pb-10">
<h1 class="text-4xl pb-28 text-center font-bold flex gap-3 justify-center items-center"> <h1 class="flex items-center justify-center gap-3 pb-28 text-center text-4xl font-bold">
Why you should teach with us? <img src="/teach/star.svg" alt="" /> Why you should teach with us? <img src="/teach/star.svg" alt="" />
</h1> </h1>
<div class="flex flex-wrap justify-center lg:justify-between mt-10"> <div class="mt-10 flex flex-wrap justify-center lg:justify-between">
{#each whyStart as why} {#each whyStart as why}
<figure <figure
class="flex gap-3 flex-col justify-center items-center px-2 w-full h-full max-w-[250px] max-h-[380px] text-center" class="flex h-full max-h-[380px] w-full max-w-[250px] flex-col items-center justify-center gap-3 px-2 text-center"
> >
<img src={why.src} alt="" class="w-[80px] h-[80px]" /> <img src={why.src} alt="" class="h-[80px] w-[80px]" />
<figcaption> <figcaption>
<h1 class="font-bold text-base">{why.header}</h1> <h1 class="text-base font-bold">{why.header}</h1>
<p class="text-sm mt-1"> <p class="mt-1 text-sm">
{why.subText} {why.subText}
</p> </p>
</figcaption> </figcaption>
@@ -143,66 +129,66 @@
</div> </div>
</div> </div>
<div class="px-[10%] mt-10 py-10"> <div class="mt-10 px-[10%] py-10">
<h1 class="text-4xl mb-28 text-center font-bold flex gap-3 justify-center items-center"> <h1 class="mb-28 flex items-center justify-center gap-3 text-center text-4xl font-bold">
How to get started <img src="/teach/thumbs-up.svg" alt="" /> How to get started <img src="/teach/thumbs-up.svg" alt="" />
</h1> </h1>
<div class="flex flex-wrap justify-between gap-y-10 mx-auto mt-5"> <div class="mx-auto mt-5 flex flex-wrap justify-between gap-y-10">
{#each steps as step, i} {#each steps as step, i}
<div class="lg:max-w-[40%] px-10 py-5"> <div class="px-10 py-5 lg:max-w-[40%]">
<h1 class="font-bold text-2xl"> <h1 class="text-2xl font-bold">
<span class="px-2 py-1 mr-3 bg-[#F7F7F7] rounded-md">{i + 1}</span> <span class="mr-3 rounded-md bg-[#F7F7F7] px-2 py-1">{i + 1}</span>
{step.title} {step.title}
</h1> </h1>
<p class="text-base text-gray-600 mt-5"> <p class="mt-5 text-base text-gray-600">
{step.subText} {step.subText}
</p> </p>
</div> </div>
{/each} {/each}
</div> </div>
<div class="flex justify-center px-[5%] mt-3"> <div class="mt-3 flex justify-center px-[5%]">
<a <Button
href="/teach/register" href="/teach/register"
target="_blank" target="_blank"
class="w-full lg:max-w-[20%] py-3 text-base text-white rounded-md bg-blue-700 text-center" rel="noopener noreferrer nofollow"
> >
Get Started Get Started
</a> </Button>
</div> </div>
</div> </div>
<div class="px-2 lg:px-[12%] bg-[#F5F8FE] my-10 py-12 h-[75%] lg:h-[80%]"> <div class="my-10 h-[75%] bg-[#F5F8FE] px-2 py-12 lg:h-[80%] lg:px-[12%]">
<h1 class="text-2xl text-center font-bold flex gap-3 justify-center items-center"> <h1 class="flex items-center justify-center gap-3 text-center text-2xl font-bold">
Word from our fufilled facilitators Word from our fufilled facilitators
</h1> </h1>
<div <div
class="mt-10 w-full md:w-2/4 mx-auto border shadow-sm bg-white rounded-md text-center py-6 relative before:w-[4.5%] before:absolute before:left-0 before:top-0 before:h-full before:bg-[#0233BD] before:rounded-tl-md before:rounded-bl-md" class="relative mx-auto mt-10 w-full rounded-md border bg-white py-6 text-center shadow-sm before:absolute before:left-0 before:top-0 before:h-full before:w-[4.5%] before:rounded-bl-md before:rounded-tl-md before:bg-[#0233BD] md:w-2/4"
> >
<h1 class="text-base font-medium leading-5 w-[77%] mx-auto"> <h1 class="mx-auto w-[77%] text-base font-medium leading-5">
With ClassroomIO, I am training over 100 students. I am making more money than expected With ClassroomIO, I am training over 100 students. I am making more money than expected with little effort and
with little effort and my students are happy. Love the product my students are happy. Love the product
</h1> </h1>
<div class="flex flex-row gap-4 justify-center items-center mt-3"> <div class="mt-3 flex flex-row items-center justify-center gap-4">
<img src="/teach/user-profile.png" alt="" class="w-8" /> <img src="/teach/user-profile.png" alt="" class="w-8" />
<p class="text-sm"> <p class="text-sm">
<span class="font-semibold mr-2"> Bilik Diana </span> English tutor <span class="mr-2 font-semibold"> Bilik Diana </span> English tutor
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div class="px-10 lg:px-[15%] lg:max-w-[65%] my-10 mt-10 mx-auto"> <div class="mx-auto my-10 mt-10 px-10 lg:max-w-[65%] lg:px-[15%]">
<h1 class="text-2xl text-center font-bold">Frequently asked questions</h1> <h1 class="text-center text-2xl font-bold">Frequently asked questions</h1>
<div class="mt-10"> <div class="mt-10">
{#each faqs as faq, index} {#each faqs as faq, index}
<div class="border-b border-black lg:px-3 pt-1 pb-2"> <div class="border-b border-black pb-2 pt-1 lg:px-3">
<div> <div>
<button <button
type="button" type="button"
class="w-full font-medium text-sm md:text-lg my-2 flex justify-between text-left md:text-center" class="my-2 flex w-full justify-between text-left text-sm font-medium md:text-center md:text-lg"
on:click={() => toggleAnswer(index)} on:click={() => toggleAnswer(index)}
> >
{faq.question} <span>{faq.showAnswer ? '-' : '+'}</span> {faq.question} <span>{faq.showAnswer ? '-' : '+'}</span>
+1 -1
View File
@@ -1,5 +1,5 @@
<script> <script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte'; import { PageSignupCTA } from '$lib/components';
</script> </script>
<svelte:head> <svelte:head>
+1 -1
View File
@@ -1,5 +1,5 @@
<script> <script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte'; import { PageHeader } from '$lib/components';
import { tools } from '$lib/utils/constants/tools'; import { tools } from '$lib/utils/constants/tools';
const freeTools = tools.filter((item) => item.show); const freeTools = tools.filter((item) => item.show);
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte'; import { ToolsHeader } from '$lib/components';
let namesInput = ''; let namesInput = '';
let numNames = 0; let numNames = 0;
@@ -103,22 +103,14 @@
<svelte:head> <svelte:head>
<title>Random Name Picker | ClassroomIO</title> <title>Random Name Picker | ClassroomIO</title>
<meta <meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:title" content="Random Name Picker | ClassroomIO" /> <meta property="og:title" content="Random Name Picker | ClassroomIO" />
<meta <meta
property="og:description" property="og:description"
content="Use this online name picker to draw a random name from a list of names for your online or physical classroom." content="Use this online name picker to draw a random name from a list of names for your online or physical classroom."
/> />
<meta <meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta name="twitter:title" content="Random Name Picker | ClassroomIO" /> <meta name="twitter:title" content="Random Name Picker | ClassroomIO" />
<meta <meta
@@ -128,41 +120,37 @@
<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>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white"> <section class="bg-white px-5 md:px-0">
<ToolsHeader> <ToolsHeader>
<img <img src="/free-tools/name-picker.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
src="/free-tools/name-picker.svg" <h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Random Name Picker</h1>
class="w-[15%] md:w-[5%] mx-auto border rounded-full" <p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
alt="" 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.
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Random Name Picker</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Use this online name picker to draw a random name from a list of names, or to draw several
names randomly out of a list. You can use it as a name randomizer for a class activities.
</p> </p>
</ToolsHeader> </ToolsHeader>
<div class="border rounded-md md:w-[60%] my-10 py-8 md:py-[3%] mx-auto shadow-md bg-white"> <div class="mx-auto my-10 rounded-md border bg-white py-8 shadow-md md:w-[60%] md:py-[3%]">
<div class="w-[85%] md:w-[70%] mx-auto bg-white"> <div class="mx-auto w-[85%] bg-white md:w-[70%]">
<h1 class="font-bold text-sm">List of names</h1> <h1 class="text-sm font-bold">List of names</h1>
<!-- container --> <!-- container -->
<div <div
class="relative focus-within:border-[#0233BD] border-2 mt-5 h-[12rem] pl-3 pt-2 overflow-hidden rounded-sm bg-[#F1F2F4]" class="relative mt-5 h-[12rem] overflow-hidden rounded-sm border-2 bg-[#F1F2F4] pl-3 pt-2 focus-within:border-[#0233BD]"
> >
<!-- sidebar --> <!-- sidebar -->
<div class="flex flex-col gap-y-3 absolute right-2 md:right-7 top-3"> <div class="absolute right-2 top-3 flex flex-col gap-y-3 md:right-7">
<button <button
type="button" type="button"
on:click={sortRandom} on:click={sortRandom}
class="p-1.5 rounded-full bg-[#D9E0F5] flex justify-center items-center" class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
> >
<img src="/sort-icon.svg" class="w-2.5" alt="" /> <img src="/sort-icon.svg" class="w-2.5" alt="" />
</button> </button>
<button <button
type="button" type="button"
on:click={unsortRandom} on:click={unsortRandom}
class="p-1.5 rounded-full bg-[#D9E0F5] flex justify-center items-center" class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
> >
<img src="/unsort-icon.svg" class="w-2.5" alt="" /> <img src="/unsort-icon.svg" class="w-2.5" alt="" />
</button> </button>
@@ -175,48 +163,46 @@
tabindex={0} tabindex={0}
contenteditable contenteditable
bind:textContent={namesInput} bind:textContent={namesInput}
class="w-full pr-9 md:pr-14 overflow-y-auto text-xs block border-0 outline-none h-full" class="block h-full w-full overflow-y-auto border-0 pr-9 text-xs outline-none md:pr-14"
></span> ></span>
</div> </div>
<!-- word count --> <!-- word count -->
<p <p class="mt-1 w-[80px] rounded-xl bg-[#D9E0F5] py-1 text-center text-[10px] font-semibold text-[#0F62FE]">
class="rounded-xl w-[80px] mt-1 py-1 bg-[#D9E0F5] text-center text-[#0F62FE] text-[10px] font-semibold"
>
name count: {wordCount} name count: {wordCount}
</p> </p>
</div> </div>
<p class="py-4 border-b text-xs">Each name must be separated by a comma</p> <p class="border-b py-4 text-xs">Each name must be separated by a comma</p>
<div class="mt-10"> <div class="mt-10">
<h1 class="font-bold text-sm">Number of Names</h1> <h1 class="text-sm font-bold">Number of Names</h1>
<!-- number input & button --> <!-- number input & button -->
<div class="flex flex-wrap gap-y-3 justify-between w-full mt-5"> <div class="mt-5 flex w-full flex-wrap justify-between gap-y-3">
<input <input
type="number" type="number"
bind:value={numNames} bind:value={numNames}
class="px-3 py-2 text-sm w-full md:w-2/4 border-2 rounded-sm outline focus:border-[#0233BD] bg-[#F1F2F4] outline-none" class="w-full rounded-sm border-2 bg-[#F1F2F4] px-3 py-2 text-sm outline-none outline focus:border-[#0233BD] md:w-2/4"
/> />
<button <button
type="button" type="button"
on:click={selectName} on:click={selectName}
class="bg-[#0F62FE] rounded-md text-sm py-3 md:py-0 text-white w-[70%] md:w-[35%]" class="w-[70%] rounded-md bg-[#0F62FE] py-3 text-sm text-white md:w-[35%] md:py-0"
>Select name(s) at random</button >Select name(s) at random</button
> >
</div> </div>
</div> </div>
<!-- footer logo --> <!-- footer logo -->
<div class="py-3 mt-10 mb-5 bg-[#F1F6FF] flex justify-center items-center gap-3"> <div class="mb-5 mt-10 flex items-center justify-center gap-3 bg-[#F1F6FF] py-3">
<img src="/free-tools/name-picker.svg" class="w-[12%] md:w-[9%]" alt="" /> <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-[#0233BD] text-xl font-bold">Selected Names</h1> <h1 class="text-xl font-bold text-[#0233BD]">Selected Names</h1>
{/if} {/if}
</div> </div>
<!-- loading state --> <!-- loading state -->
{#if selectedNames.length > 0 && !avatarUrlsFetched} {#if selectedNames.length > 0 && !avatarUrlsFetched}
<div class="w-full flex justify-center items-center"> <div class="flex w-full items-center justify-center">
<p class="font-bold">Loading...</p> <p class="font-bold">Loading...</p>
</div> </div>
{/if} {/if}
@@ -226,10 +212,10 @@
<div> <div>
<ul> <ul>
{#each selectedNames as name, index} {#each selectedNames as name, index}
<li class="py-2 border-b"> <li class="border-b py-2">
<div class="flex item-center gap-3 md:gap-5 justify-center"> <div class="item-center flex justify-center gap-3 md:gap-5">
<img src={avatarUrls[index]} alt="" class="w-7" /> <img src={avatarUrls[index]} alt="" class="w-7" />
<p class="font-medium text-sm md:text-base flex justify-center items-center"> <p class="flex items-center justify-center text-sm font-medium md:text-base">
{name} {name}
</p> </p>
</div> </div>
@@ -237,17 +223,16 @@
{/each} {/each}
</ul> </ul>
<div class="flex flex-wrap justify-between md:justify-center items-center md:gap-3 mt-3"> <div class="mt-3 flex flex-wrap items-center justify-between md:justify-center md:gap-3">
<button <button
type="button" type="button"
on:click={resetEntry} on:click={resetEntry}
class="py-2 border text-xs md:text-sm rounded-md w-[45%] md:w-[20%]" class="w-[45%] rounded-md border py-2 text-xs md:w-[20%] md:text-sm">Reset Entries</button
>Reset Entries</button
> >
<button <button
type="button" type="button"
on:click={selectName} on:click={selectName}
class="py-2 bg-[#0F62FE] rounded-md text-xs md:text-sm text-white w-[45%] md:w-[27%]" class="w-[45%] rounded-md bg-[#0F62FE] py-2 text-xs text-white md:w-[27%] md:text-sm"
>Select other names</button >Select other names</button
> >
</div> </div>
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte'; import { ToolsHeader } from '$lib/components';
interface TodoItem { interface TodoItem {
id: number; id: number;
@@ -167,15 +167,15 @@
<meta name="twitter:image" content="" /> <meta name="twitter:image" content="" />
</svelte:head> </svelte:head>
<section class="mt-[10%] md:mt-16 px-1 md:px-0 w-full md:w-full bg-white"> <section class=" w-full bg-white px-1 md:w-full md:px-0">
<ToolsHeader> <ToolsHeader>
<img <img
src="/free-tools/pomodoro.svg" src="/free-tools/pomodoro.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full" class="mx-auto w-[15%] rounded-full border md:w-[5%]"
alt="" alt=""
/> />
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Pomodoro Timer</h1> <h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Pomodoro Timer</h1>
<p class="text-sm mt-10 text-[#656565] font-light md:font-normal md:w-[45%] mx-auto"> <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
break. break.
@@ -183,20 +183,20 @@
</ToolsHeader> </ToolsHeader>
<div <div
class="body w-full md:h-[60vh] flex flex-col gap-y-10 md:flex-row justify-evenly px-5 md:px-10 my-10 text-white text-center" class="body my-10 flex w-full flex-col justify-evenly gap-y-10 px-5 text-center text-white md:h-[60vh] md:flex-row md:px-10"
> >
<!-- left side --> <!-- left side -->
<div <div
class="w-full md:w-[50%] bg-[#0D4CFF] rounded-lg md:rounded-2xl py-7 px-5 md:p-10 flex flex-col gap-y-16 md:gap-y-0 justify-between" class="flex w-full flex-col justify-between gap-y-16 rounded-lg bg-[#0D4CFF] px-5 py-7 md:w-[50%] md:gap-y-0 md:rounded-2xl md:p-10"
> >
<!-- types --> <!-- types -->
<div class="flex item-center justify-evenly"> <div class="item-center flex justify-evenly">
<button <button
type="button" type="button"
on:click={() => setTimerState('pomodoro')} on:click={() => setTimerState('pomodoro')}
class:bg-[#3ADFECED]={timerState === 'pomodoro'} class:bg-[#3ADFECED]={timerState === 'pomodoro'}
class:bg-[#0233BD]={timerState !== 'pomodoro'} class:bg-[#0233BD]={timerState !== 'pomodoro'}
class="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]" class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Pomodoro</button >Pomodoro</button
> >
<button <button
@@ -204,7 +204,7 @@
on:click={() => setTimerState('long-break')} on:click={() => 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="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]" class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Short Break >Short Break
</button> </button>
<button <button
@@ -212,13 +212,13 @@
on:click={() => setTimerState('short-break')} on:click={() => 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="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]" class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Long Break >Long Break
</button> </button>
</div> </div>
<!-- countdown --> <!-- countdown -->
<h1 class="text-7xl md:text-9xl font-bold">{countdownDisplay}</h1> <h1 class="text-7xl font-bold md:text-9xl">{countdownDisplay}</h1>
<!-- controls --> <!-- controls -->
<div class="flex items-center justify-center gap-7"> <div class="flex items-center justify-center gap-7">
@@ -227,7 +227,7 @@
<img <img
src="/free-tools/pomodoro/restart-icon.svg" src="/free-tools/pomodoro/restart-icon.svg"
alt="Restart Icon" alt="Restart Icon"
class="w-7 hover:scale-110 transition-all duration-300" class="w-7 transition-all duration-300 hover:scale-110"
/> />
</button> </button>
@@ -235,7 +235,7 @@
<button <button
type="button" type="button"
on:click={isPaused ? startCountdown : pauseCountdown} on:click={isPaused ? startCountdown : pauseCountdown}
class="bg-white text-[#0542CC] border text-base font-bold uppercase py-3 px-14 rounded-md hover:bg-transparent hover:text-white hover:border-white hover:border transition-all duration-300" class="rounded-md border bg-white px-14 py-3 text-base font-bold uppercase text-[#0542CC] transition-all duration-300 hover:border hover:border-white hover:bg-transparent hover:text-white"
> >
{isPaused ? 'Start' : 'Pause'} {isPaused ? 'Start' : 'Pause'}
</button> </button>
@@ -245,16 +245,16 @@
<img <img
src="/free-tools/pomodoro/timer-play-icon.svg" src="/free-tools/pomodoro/timer-play-icon.svg"
alt="Play Icon" alt="Play Icon"
class="w-7 hover:scale-110 transition-all duration-300" class="w-7 transition-all duration-300 hover:scale-110"
/> />
</button> </button>
</div> </div>
</div> </div>
<!-- right side --> <!-- right side -->
<div class="w-full md:w-[36%] relative"> <div class="relative w-full md:w-[36%]">
<h1 <h1
class="bg-[#040F2D] uppercase text-xl font-bold py-4" class="bg-[#040F2D] py-4 text-xl font-bold uppercase"
style="clip-path: polygon(0 0, 100% 1%, 100% 65%, 0% 100%);" style="clip-path: polygon(0 0, 100% 1%, 100% 65%, 0% 100%);"
> >
To do list To do list
@@ -262,28 +262,28 @@
<div class="overflow-hidden"> <div class="overflow-hidden">
<!-- todos --> <!-- todos -->
<div class="overflow-y-auto mt-3 max-h-[40vh] md:max-h-[50vh]"> <div class="mt-3 max-h-[40vh] overflow-y-auto md:max-h-[50vh]">
{#each todoList as todo, i} {#each todoList as todo, i}
<div class="border text-black p-5"> <div class="border p-5 text-black">
{#if todo.isEditing} {#if todo.isEditing}
<form on:submit|preventDefault={() => setEditing(i, false)}> <form on:submit|preventDefault={() => setEditing(i, false)}>
<p class="text-xs text-left text-[#656565] font-semibold">Pomodoro name</p> <p class="text-left text-xs font-semibold text-[#656565]">Pomodoro name</p>
<input <input
type="text" type="text"
bind:this={addInput} bind:this={addInput}
bind:value={todo.content} bind:value={todo.content}
class="bg-[#F1F2F4] border-[#EAEAEA] w-full border mt-2 px-3 py-2 text-base outline-none" class="mt-2 w-full border border-[#EAEAEA] bg-[#F1F2F4] px-3 py-2 text-base outline-none"
/> />
<div class="flex justify-end items-center gap-5 mt-5"> <div class="mt-5 flex items-center justify-end gap-5">
<button <button
type="button" type="button"
on:click={() => deleteTodo(i)} on:click={() => deleteTodo(i)}
class="rounded-sm py-2 px-5 text-xs font-semibold bg-[#F7F7F7]" class="rounded-sm bg-[#F7F7F7] px-5 py-2 text-xs font-semibold"
>Discard</button >Discard</button
> >
<button <button
type="submit" type="submit"
class="rounded-sm py-2 px-5 text-xs font-semibold bg-[#0D4CFF] text-white" class="rounded-sm bg-[#0D4CFF] px-5 py-2 text-xs font-semibold text-white"
>Save</button >Save</button
> >
</div> </div>
@@ -294,7 +294,7 @@
<p class="text-sm font-medium">{todo.content}</p> <p class="text-sm font-medium">{todo.content}</p>
<!-- pen and menu icon --> <!-- pen and menu icon -->
<div class="relative flex justify-between w-[15%]"> <div class="relative flex w-[15%] justify-between">
<button type="button" on:click={() => setEditing(i, true)}> <button type="button" on:click={() => 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>
@@ -305,19 +305,19 @@
{#if todo.isVisible} {#if todo.isVisible}
<ul <ul
class="z-40 absolute top-6 -right-3 text-xs w-[8rem] bg-white border shadow-md rounded-[5px]" class="absolute -right-3 top-6 z-40 w-[8rem] rounded-[5px] border bg-white text-xs shadow-md"
> >
<button <button
type="button" type="button"
on:click={() => markTodoAsDone(i)} on:click={() => markTodoAsDone(i)}
class="w-full font-medium text-left py-3 px-5" class="w-full px-5 py-3 text-left font-medium"
> >
Mark as done Mark as done
</button> </button>
<button <button
type="button" type="button"
on:click={() => deleteTodo(i)} on:click={() => deleteTodo(i)}
class="w-full font-medium text-left py-3 px-5 bg-red-600 text-white rounded-b-[5px]" class="w-full rounded-b-[5px] bg-red-600 px-5 py-3 text-left font-medium text-white"
> >
Delete Delete
</button> </button>
@@ -326,9 +326,9 @@
</div> </div>
</div> </div>
<div class="flex justify-between items-center mt-10"> <div class="mt-10 flex items-center justify-between">
<p <p
class="rounded-[3px] text-xs font-medium text-[#656565] bg-[#EAEAEA] py-0.5 px-1.5" class="rounded-[3px] bg-[#EAEAEA] px-1.5 py-0.5 text-xs font-medium text-[#656565]"
> >
{todo.time} {todo.time}
</p> </p>
@@ -345,12 +345,12 @@
todo.isPaused = !todo.isPaused; todo.isPaused = !todo.isPaused;
}} }}
class="{todo.isDone class="{todo.isDone
? 'bg-[#00D06C] border-none text-white' ? 'border-none bg-[#00D06C] text-white'
: todo.isPaused : todo.isPaused
? 'bg-white text-[#0D4CFF] border-[#0D4CFF]' ? 'border-[#0D4CFF] bg-white text-[#0D4CFF]'
: 'bg-[#0D4CFF] text-white border-none'} : 'border-none bg-[#0D4CFF] text-white'}
flex items-center border-2 justify-center gap-2 py-1.5 px-4 rounded-[4px] text-xs font-bold uppercase" flex items-center justify-center gap-2 rounded-[4px] border-2 px-4 py-1.5 text-xs font-bold uppercase"
> >
{#if todo.isDone} {#if todo.isDone}
<img src="/free-tools/pomodoro/done-icon.svg" alt="Done icon" /> <img src="/free-tools/pomodoro/done-icon.svg" alt="Done icon" />
@@ -374,7 +374,7 @@
<button <button
type="button" type="button"
on:click={addTodo} on:click={addTodo}
class="bg-[#1D4ED8] rounded-md text-center font-medium mt-10 py-3 w-full flex justify-center items-center gap-3" class="mt-10 flex w-full items-center justify-center gap-3 rounded-md bg-[#1D4ED8] py-3 text-center font-medium"
> >
Add new item Add new item
<img src="/free-tools/pomodoro/add-icon.svg" alt="Add icon" class="w-3" /> <img src="/free-tools/pomodoro/add-icon.svg" alt="Add icon" class="w-3" />
@@ -385,9 +385,9 @@
</section> </section>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap');
.body { .body {
font-family: 'Inter', sans-serif; font-family: 'Geist', sans-serif;
} }
</style> </style>
@@ -1,16 +1,19 @@
<script lang="ts"> <script lang="ts">
import { LogoFacebook, LogoLinkedin } from 'carbon-icons-svelte';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing'; import { sineInOut } from 'svelte/easing';
import Mood from './components/Mood.svelte'; import Facebook from '@lucide/svelte/icons/facebook';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte'; import Linkedin from '@lucide/svelte/icons/linkedin';
import Avatar from './components/Avatar.svelte';
import Background from './components/Background.svelte'; import { htmlBody, type HtmlBody, openModal, isFormComplete } from '$lib/features/tools/progress/store';
import Report from './components/Report.svelte';
import FullView from './components/FullView.svelte'; import Mood from '$lib/features/tools/progress/mood.svelte';
import DownloadButton from './components/DownloadButton.svelte'; import Avatar from '$lib/features/tools/progress/avatar.svelte';
import { htmlBody, type HtmlBody, openModal, isFormComplete } from './components/store'; import Report from '$lib/features/tools/progress/report.svelte';
import FullView from '$lib/features/tools/progress/full-view.svelte';
import Background from '$lib/features/tools/progress/background.svelte';
import { ToolsHeader } from '$lib/components';
import DownloadButton from '$lib/features/tools/progress/download-button.svelte';
const MAX_CHARS = 160; const MAX_CHARS = 160;
let remainingChars = MAX_CHARS; let remainingChars = MAX_CHARS;
@@ -65,22 +68,14 @@
<svelte:head> <svelte:head>
<title>Random Name Picker | ClassroomIO</title> <title>Random Name Picker | ClassroomIO</title>
<meta <meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:title" content="Random Name Picker | ClassroomIO" /> <meta property="og:title" content="Random Name Picker | ClassroomIO" />
<meta <meta
property="og:description" property="og:description"
content="Use this online name picker to draw a random name from a list of names for your online or physical classroom." content="Use this online name picker to draw a random name from a list of names for your online or physical classroom."
/> />
<meta <meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta name="twitter:title" content="Random Name Picker | ClassroomIO" /> <meta name="twitter:title" content="Random Name Picker | ClassroomIO" />
<meta <meta
@@ -90,17 +85,12 @@
<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>
<section class="mt-[10%] md:mt-16 px-1 md:px-0 w-full md:w-full"> <section class=" w-full px-1 md:w-full md:px-0">
<ToolsHeader> <ToolsHeader>
<img <img src="/free-tools/progress-report.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
src="/free-tools/progress-report.svg" <h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Progress Report</h1>
class="w-[15%] md:w-[5%] mx-auto border rounded-full" <p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
alt="" Generate cool reports of your learning progress. Share reports with your network for collaborative learning
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Progress Report</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Generate cool reports of your learning progress. Share reports with your network for
collaborative learning
</p> </p>
</ToolsHeader> </ToolsHeader>
@@ -111,29 +101,25 @@
<FullView /> <FullView />
<div <div
class="border rounded-md w-full md:w-11/12 lg:w-[80%] my-10 mx-auto shadow-md flex md:flex-row flex-col justify-evenly" class="mx-auto my-10 flex w-full flex-col justify-evenly rounded-md border shadow-md md:w-11/12 md:flex-row lg:w-[80%]"
> >
<!-- left side --> <!-- left side -->
{#if showSetter} {#if showSetter}
<div <div
transition:fly={{ y: 100, easing: sineInOut }} transition:fly={{ y: 100, easing: sineInOut }}
class="w-full mx-auto md:w-[48%] md:max-w-[500px] p-5 md:border-r" class="mx-auto w-full p-5 md:w-[48%] md:max-w-[500px] md:border-r"
> >
<!-- preview button --> <!-- preview button -->
<div class="flex md:hidden justify-end mb-5"> <div class="mb-5 flex justify-end md:hidden">
<button <button
type="button" type="button"
on:click={() => { on:click={() => {
showSetter = false; showSetter = false;
showReport = true; showReport = true;
}} }}
class="flex justify-between items-center gap-2 px-3 py-1 rounded-full text-white text-xs bg-[#0233BD]" class="flex items-center justify-between gap-2 rounded-full bg-[#0233BD] px-3 py-1 text-xs text-white"
> >
<img <img src="/free-tools/progress-report/preview-icon.svg" alt="preview icon" class="w-4" />
src="/free-tools/progress-report/preview-icon.svg"
alt="preview icon"
class="w-4"
/>
Preview Preview
</button> </button>
</div> </div>
@@ -146,7 +132,7 @@
bind:value={$htmlBody.name} bind:value={$htmlBody.name}
on:input={(event) => handleInputChange(event, 'name')} on:input={(event) => handleInputChange(event, 'name')}
placeholder="Enter your name here" placeholder="Enter your name here"
class="w-full border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-xs placeholder:text-sm placeholder:text-[#ADADAD]" class="my-3 w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 text-xs outline-none placeholder:text-sm placeholder:text-[#ADADAD]"
/> />
</div> </div>
@@ -156,18 +142,17 @@
<button <button
type="button" type="button"
on:click={() => ($openModal.mood = true)} on:click={() => ($openModal.mood = true)}
class="w-full flex justify-between items-center border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-gray-400 text-sm" class="my-3 flex w-full items-center justify-between rounded-sm border bg-[#F1F2F4] px-3 py-2 text-sm text-gray-400 outline-none"
> >
{#if $htmlBody.mood.text} {#if $htmlBody.mood.text}
<div <div
class="px-3 py-1 border border-[#EDEDED] text-white bg-blue-500 rounded-2xl font-semibold text-[7px] md:text-xs flex items-center justify-between" class="flex items-center justify-between rounded-2xl border border-[#EDEDED] bg-blue-500 px-3 py-1 text-[7px] font-semibold text-white md:text-xs"
> >
{#if $htmlBody.mood.text} {#if $htmlBody.mood.text}
<span class="flex gap-1 items-center"> <span class="flex items-center gap-1">
<p class="font-semibold text-[9px] md:text-xs">is {$htmlBody.mood.text}</p> <p class="text-[9px] font-semibold md:text-xs">is {$htmlBody.mood.text}</p>
<img <img
src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood.iconSrc}.png"
.iconSrc}.png"
alt={$htmlBody.mood.text} alt={$htmlBody.mood.text}
class="w-3" class="w-3"
/> />
@@ -176,11 +161,7 @@
</div> </div>
{:else} {:else}
What feeling describes you? What feeling describes you?
<img <img src="/free-tools/progress-report/smiley-face-icon.svg" alt="smiley face" class="w-3" />
src="/free-tools/progress-report/smiley-face-icon.svg"
alt="smiley face"
class="w-3"
/>
{/if} {/if}
</button> </button>
</div> </div>
@@ -193,15 +174,15 @@
maxlength={MAX_CHARS} maxlength={MAX_CHARS}
on:input={(event) => handleInputChange(event, 'learning')} on:input={(event) => handleInputChange(event, 'learning')}
placeholder="Tell us what you are learning" placeholder="Tell us what you are learning"
class="w-full h-[17vh] border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-sm placeholder:text-sm placeholder:text-[#ADADAD]" class="my-3 h-[17vh] w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 text-sm outline-none placeholder:text-sm placeholder:text-[#ADADAD]"
></textarea> ></textarea>
<p class="text-xs text-right text-[#656565]">{remainingChars} characters remaining</p> <p class="text-right text-xs text-[#656565]">{remainingChars} characters remaining</p>
</div> </div>
<!-- range input --> <!-- range input -->
<div class="mt-3"> <div class="mt-3">
<p class="text-sm text-[#656565] pb-4">Estimate your progress</p> <p class="pb-4 text-sm text-[#656565]">Estimate your progress</p>
<div class="flex justify-between items-center"> <div class="flex items-center justify-between">
<input <input
type="range" type="range"
min="0" min="0"
@@ -216,18 +197,16 @@
</div> </div>
<!-- avatar button --> <!-- avatar button -->
<div class="flex flex-wrap justify-between items-center mt-3"> <div class="mt-3 flex flex-wrap items-center justify-between">
<div class="w-full md:w-2/4"> <div class="w-full md:w-2/4">
<p class="text-[12px] md:text-sm text-[#656565]">Choose your avatar</p> <p class="text-[12px] text-[#656565] md:text-sm">Choose your avatar</p>
<button <button
type="button" type="button"
on:click={() => ($openModal.avatar = true)} on:click={() => ($openModal.avatar = true)}
class="bg-[#F7F7F7] w-full py-1.5 px-5 md:px-0 mt-3 flex md:w-[65%] items-center justify-between md:justify-center gap-2" class="mt-3 flex w-full items-center justify-between gap-2 bg-[#F7F7F7] px-5 py-1.5 md:w-[65%] md:justify-center md:px-0"
> >
<img <img
src={`https://assets.cdn.clsrio.com/progress-report/avatars/${ src={`https://assets.cdn.clsrio.com/progress-report/avatars/${$htmlBody.avatar || 'avatar_m'}.svg`}
$htmlBody.avatar || 'avatar_m'
}.svg`}
alt="" alt=""
class="w-[15%] md:w-[30%]" class="w-[15%] md:w-[30%]"
/> />
@@ -241,11 +220,11 @@
<!-- & background --> <!-- & background -->
<div class="w-full md:w-2/4"> <div class="w-full md:w-2/4">
<p class="text-[12px] md:text-sm text-[#656565]">Choose your background</p> <p class="text-[12px] text-[#656565] md:text-sm">Choose your background</p>
<button <button
type="button" type="button"
on:click={() => ($openModal.background = true)} on:click={() => ($openModal.background = true)}
class="bg-[#F7F7F7] w-full py-1.5 px-5 md:px-0 mt-3 flex md:w-[65%] items-center justify-between md:justify-center gap-2" class="mt-3 flex w-full items-center justify-between gap-2 bg-[#F7F7F7] px-5 py-1.5 md:w-[65%] md:justify-center md:px-0"
> >
<img <img
src={`https://assets.cdn.clsrio.com/progress-report/backgrounds/${ src={`https://assets.cdn.clsrio.com/progress-report/backgrounds/${
@@ -270,41 +249,35 @@
<div <div
transition:fly={{ y: 100, easing: sineInOut }} transition:fly={{ y: 100, easing: sineInOut }}
class="w-full mx-auto md:w-[48%] md:min-w-[400px] md:max-w-[500px] px-5 md:px-1 md:mt-0 mt-5 md:p-5" class="mx-auto mt-5 w-full px-5 md:mt-0 md:w-[48%] md:min-w-[400px] md:max-w-[500px] md:p-5 md:px-1"
> >
<div class="hidden md:block"> <div class="hidden md:block">
<Report /> <Report />
<!-- download & share button --> <!-- download & share button -->
<div class="mt-9 pt-8 px-2 h-auto border-t"> <div class="mt-9 h-auto border-t px-2 pt-8">
<DownloadButton {isDisabled} {isDownloading} text="Download Image" /> <DownloadButton {isDisabled} {isDownloading} text="Download Image" />
<!-- share button --> <!-- share button -->
<div class="w-full md:w-[70%] mx-auto my-5"> <div class="mx-auto my-5 w-full md:w-[70%]">
<h1 class="text-sm font-semibold text-center">Share on image on social media:</h1> <h1 class="text-center text-sm font-semibold">Share on image on social media:</h1>
<div <div class="mx-auto mt-2 flex w-[60%] justify-evenly gap-5 rounded-xl border px-5 py-2 md:w-[80%]">
class="flex justify-evenly border rounded-xl gap-5 w-[60%] md:w-[80%] px-5 py-2 mt-2 mx-auto"
>
<button <button
type="button" type="button"
on:click={shareOnFacebook} on:click={shareOnFacebook}
class="w-10 hover:scale-[1.2] transition-all duration-300" class="w-10 transition-all duration-300 hover:scale-[1.2]"
> >
<LogoFacebook size={32} /> <Facebook size={32} />
</button> </button>
<button <button
type="button" type="button"
on:click={shareOnLinkedIn} on:click={shareOnLinkedIn}
class="w-10 hover:scale-[1.2] transition-all duration-300" class="w-10 transition-all duration-300 hover:scale-[1.2]"
> >
<LogoLinkedin size={32} /> <Linkedin size={32} />
</button> </button>
<button <button type="button" on:click={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
type="button"
on:click={shareOnTwitter}
class="w-5 hover:scale-[1.2] transition-all duration-300"
>
<img src="/twitter_logo.png" alt="X.com" /> <img src="/twitter_logo.png" alt="X.com" />
</button> </button>
</div> </div>
@@ -314,14 +287,14 @@
{#if showReport} {#if showReport}
<!-- hide button --> <!-- hide button -->
<div class="flex justify-end mb-5"> <div class="mb-5 flex justify-end">
<button <button
type="button" type="button"
on:click={() => { on:click={() => {
showReport = false; showReport = false;
showSetter = true; showSetter = true;
}} }}
class="flex justify-between items-center gap-2 px-3 py-1 rounded-full text-white text-xs bg-[#0233BD]" class="flex items-center justify-between gap-2 rounded-full bg-[#0233BD] px-3 py-1 text-xs text-white"
> >
<img src="/free-tools/progress-report/hide-icon.svg" alt="preview icon" class="w-4" /> <img src="/free-tools/progress-report/hide-icon.svg" alt="preview icon" class="w-4" />
Close Close
@@ -330,35 +303,29 @@
<Report /> <Report />
<!-- download & share button --> <!-- download & share button -->
<div class="mt-9 pt-8 px-2 h-auto border-t"> <div class="mt-9 h-auto border-t px-2 pt-8">
<DownloadButton {isDisabled} {isDownloading} /> <DownloadButton {isDisabled} {isDownloading} />
<!-- share button --> <!-- share button -->
<div class="w-full md:w-[70%] mx-auto my-5"> <div class="mx-auto my-5 w-full md:w-[70%]">
<h1 class="text-sm font-semibold text-center">Share on image on social media:</h1> <h1 class="text-center text-sm font-semibold">Share on image on social media:</h1>
<div <div class="mx-auto mt-2 flex w-[60%] justify-evenly gap-5 rounded-xl border px-5 py-2 md:w-[80%]">
class="flex justify-evenly border rounded-xl gap-5 w-[60%] md:w-[80%] px-5 py-2 mt-2 mx-auto"
>
<button <button
type="button" type="button"
on:click={shareOnFacebook} on:click={shareOnFacebook}
class="w-10 hover:scale-[1.2] transition-all duration-300" class="w-10 transition-all duration-300 hover:scale-[1.2]"
> >
<LogoFacebook size={32} /> <Facebook size={32} />
</button> </button>
<button <button
type="button" type="button"
on:click={shareOnLinkedIn} on:click={shareOnLinkedIn}
class="w-10 hover:scale-[1.2] transition-all duration-300" class="w-10 transition-all duration-300 hover:scale-[1.2]"
> >
<LogoLinkedin size={32} /> <Linkedin size={32} />
</button> </button>
<button <button type="button" on:click={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
type="button"
on:click={shareOnTwitter}
class="w-5 hover:scale-[1.2] transition-all duration-300"
>
<img src="/twitter_logo.png" alt="X.com" /> <img src="/twitter_logo.png" alt="X.com" />
</button> </button>
</div> </div>
@@ -1,88 +0,0 @@
<script lang="ts">
import { openModal, htmlBody } from './store';
import Modal from '$lib/Modal/Modal.svelte';
interface Avatar {
src: string;
}
let isAddIcon = false;
let hoveredIndex: number | null = null;
let avatars: Avatar[] = [
{ src: 'avatar_a' },
{ src: 'avatar_b' },
{ src: 'avatar_c' },
{ src: 'avatar_d' },
{ src: 'avatar_e' },
{ src: 'avatar_f' },
{ src: 'avatar_g' },
{ src: 'avatar_h' },
{ src: 'avatar_i' },
{ src: 'avatar_j' },
{ src: 'avatar_k' },
{ src: 'avatar_l' },
{ src: 'avatar_m' },
{ src: 'avatar_n' },
{ src: 'avatar_o' },
{ src: 'avatar_p' },
{ src: 'avatar_q' },
{ src: 'avatar_r' },
{ src: 'avatar_s' },
{ src: 'avatar_t' }
];
function selectAvatar(avatar: Avatar) {
$htmlBody.avatar = avatar.src;
$openModal.avatar = false;
}
</script>
<Modal bind:open={$openModal.avatar}>
<!-- heading -->
<div class="flex justify-between">
<h1 class="text-sm font-semibold">Choose your Avatars</h1>
<button
type="button"
on:click={() => ($openModal.avatar = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<!-- map for each avatar -->
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
{#each avatars as avatar, index}
<button
on:click={() => {
selectAvatar(avatar);
}}
on:mouseenter={() => {
hoveredIndex = index;
isAddIcon = true;
}}
on:mouseleave={() => {
hoveredIndex = null;
isAddIcon = false;
}}
class="relative w-[17%] flex justify-center items-center shadow-sm rounded-full hover:scale-110 transition-all duration-300"
>
<!-- + icon overlay -->
{#if isAddIcon && hoveredIndex === index}
<div
class="w-full h-full flex justify-center items-center bg-white border absolute rounded-full opacity-[0.7] blur-md"
></div>
<img src="/free-tools/progress-report/hover-plus-icon.svg" alt="" class="absolute" />
{/if}
<!-- avatar -->
<img
src="https://assets.cdn.clsrio.com/progress-report/avatar/{avatar.src}.svg"
alt=""
class="w-full"
/>
</button>
{/each}
</div>
<!-- -->
</Modal>
@@ -1,87 +0,0 @@
<script lang="ts">
import Modal from '$lib/Modal/Modal.svelte';
import { htmlBody, openModal } from './store';
interface Background {
src: string;
}
let backgrounds: Background[] = [
{
src: 'blue_tetiary_background'
},
{
src: 'blue_secondary_background'
},
{
src: 'blue_primary_background'
},
{
src: 'purple_tetiary_background'
},
{
src: 'purple_secondary_background'
},
{
src: 'purple_primary_background'
},
{
src: 'black_tetiary_background'
},
{
src: 'black_secondary_background'
},
{
src: 'black_primary_background'
},
{
src: 'yellow_tetiary_background'
},
{
src: 'yellow_secondary_background'
},
{
src: 'yellow_primary_background'
}
];
function selectBackground(background: Background) {
$htmlBody.background = background.src;
$openModal.background = false;
}
</script>
<Modal className="h-full max-h-[60vh]" bind:open={$openModal.background}>
<!-- -->
<div class="flex justify-between">
<h1 class="text-sm font-semibold">Choose your Background</h1>
<button
type="button"
on:click={() => ($openModal.background = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<!-- -->
<div
class="flex flex-wrap justify-between gap-y-3 mt-3 overflow-y-auto overflow-x-hidden h-[85%]"
>
{#each backgrounds as background}
<button
on:click={() => {
selectBackground(background);
}}
class="flex w-[30%] items-center shadow-sm hover:scale-110 transition-all duration-300"
>
<img
src="https://assets.cdn.clsrio.com/progress-report/backgrounds/{background.src}.webp"
alt=""
class=""
/>
</button>
{/each}
</div>
<!-- -->
</Modal>
@@ -1,11 +0,0 @@
<script lang="ts">
import Modal from '$lib/Modal/Modal.svelte';
import Report from './Report.svelte';
import { openModal } from './store';
</script>
<Modal bind:open={$openModal.fullscreen}>
<!-- -->
<Report />
<!-- -->
</Modal>
@@ -2,8 +2,8 @@
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';
import Timer from './components/Timer.svelte'; import Timer from '$lib/features/tools/stopwatch/timer.svelte';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte'; import { ToolsHeader } from '$lib/components';
let timer: NodeJS.Timeout; let timer: NodeJS.Timeout;
let buzzSound: HTMLAudioElement; let buzzSound: HTMLAudioElement;
@@ -66,9 +66,7 @@
const hrs = Math.floor(totalSeconds / 3600); const hrs = Math.floor(totalSeconds / 3600);
const mins = Math.floor((totalSeconds % 3600) / 60); const mins = Math.floor((totalSeconds % 3600) / 60);
const secs = totalSeconds % 60; const secs = totalSeconds % 60;
displayTime = `${String(hrs).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String( displayTime = `${String(hrs).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
secs
).padStart(2, '0')}`;
if (formattedTime === '00:00') { if (formattedTime === '00:00') {
formattedTime = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; formattedTime = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
@@ -117,22 +115,14 @@
<svelte:head> <svelte:head>
<title>Activity Stopwatch | ClassroomIO</title> <title>Activity Stopwatch | ClassroomIO</title>
<meta <meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:title" content="Activity Stopwatch | ClassroomIO" /> <meta property="og:title" content="Activity Stopwatch | ClassroomIO" />
<meta <meta
property="og:description" property="og:description"
content="Stay on track with a customizable stopwatch for timed tasks, quizzes, and study sessions" content="Stay on track with a customizable stopwatch for timed tasks, quizzes, and study sessions"
/> />
<meta <meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta name="twitter:title" content="Activity Stopwatch | ClassroomIO" /> <meta name="twitter:title" content="Activity Stopwatch | ClassroomIO" />
<meta <meta
@@ -142,76 +132,72 @@
<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>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white"> <section class=" bg-white px-5 md:px-0">
<ToolsHeader> <ToolsHeader>
<img <img src="/free-tools/name-picker.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
src="/free-tools/name-picker.svg" <h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Activity Stopwatch</h1>
class="w-[15%] md:w-[5%] mx-auto border rounded-full" <p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
alt="" Stay on track and enhance productivity with our customizable stopwatch for timed tasks, quizzes, and study
/> sessions.
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Activity Stopwatch</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Stay on track and enhance productivity with our customizable stopwatch for timed tasks,
quizzes, and study sessions.
</p> </p>
</ToolsHeader> </ToolsHeader>
<div class="w-full md:w-2/4 my-10 mx-auto border rounded-md overflow-hidden bg-white"> <div class="mx-auto my-10 w-full overflow-hidden rounded-md border bg-white md:w-2/4">
<!-- countdown setter --> <!-- countdown setter -->
{#if !isNextStep} {#if !isNextStep}
<div transition:fly={{ y: -300, delay: 0, easing: sineInOut }} class="px-6 py-8 bg-white"> <div transition:fly={{ y: -300, delay: 0, easing: sineInOut }} class="bg-white px-6 py-8">
<form on:submit|preventDefault={startTimer}> <form on:submit|preventDefault={startTimer}>
<div> <div>
<h1 class="font-bold text-sm">Name of activity</h1> <h1 class="text-sm font-bold">Name of activity</h1>
<input <input
type="text" type="text"
bind:value={activityName} bind:value={activityName}
class="w-full rounded-sm border py-2 mt-3 bg-[#F1F2F4] outline-none px-3 focus-within:border-[#0233BD] focus-within:border" class="mt-3 w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 outline-none focus-within:border focus-within:border-[#0233BD]"
required required
/> />
</div> </div>
<div class="mt-10 bg-white"> <div class="mt-10 bg-white">
<h1 class="font-bold text-sm">Set an Activity</h1> <h1 class="text-sm font-bold">Set an Activity</h1>
<div class="flex flex-wrap justify-between gap-y-5 w-full mt-3 uppercase font-bold"> <div class="mt-3 flex w-full flex-wrap justify-between gap-y-5 font-bold uppercase">
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5"> <div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2"> <div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input <input
type="number" type="number"
bind:value={hours} bind:value={hours}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black" class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/> />
<p class="text-[10px]">hrs</p> <p class="text-[10px]">hrs</p>
</div> </div>
</div> </div>
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5"> <div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2"> <div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input <input
type="number" type="number"
bind:value={minutes} bind:value={minutes}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black" class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/> />
<p class="text-[10px]">mins</p> <p class="text-[10px]">mins</p>
</div> </div>
</div> </div>
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5"> <div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2"> <div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input <input
type="number" type="number"
bind:value={seconds} bind:value={seconds}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black" class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/> />
<p class="text-[10px]">sec</p> <p class="text-[10px]">sec</p>
</div> </div>
</div> </div>
</div> </div>
<div class="flex justify-end mt-10"> <div class="mt-10 flex justify-end">
<button <button
type="submit" type="submit"
class="bg-[#0F62FE] text-white text-sm px-5 py-3 rounded-md flex gap-3 items-center" class="flex items-center gap-3 rounded-md bg-[#0F62FE] px-5 py-3 text-sm text-white"
> >
<img src="/free-tools/stopwatch/loading-timer.svg" alt="" /> <img src="/free-tools/stopwatch/loading-timer.svg" alt="" />
Start Timer</button Start Timer</button
@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing'; import { sineInOut } from 'svelte/easing';
import Board from './components/Board.svelte'; import Board from '$lib/features/tools/tic-tac-toe/board.svelte';
import { TicTacToe } from './components/board'; import { TicTacToe } from '$lib/features/tools/tic-tac-toe/board';
import { scores, changeToComputer } from './components/store'; import { scores, changeToComputer } from '$lib/features/tools/tic-tac-toe/store';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte'; import { ToolsHeader } from '$lib/components';
let startGame = false; let startGame = false;
let message: string | null = null; let message: string | null = null;
@@ -78,22 +78,14 @@
<svelte:head> <svelte:head>
<title>Tic Tac Toe | ClassroomIO</title> <title>Tic Tac Toe | ClassroomIO</title>
<meta <meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:title" content="Tic Tac Toe | ClassroomIO" /> <meta property="og:title" content="Tic Tac Toe | ClassroomIO" />
<meta <meta
property="og:description" property="og:description"
content="A simple and free tic tac toe game to play to your friends in class or outside the classroom." content="A simple and free tic tac toe game to play to your friends in class or outside the classroom."
/> />
<meta <meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta name="twitter:title" content="Tic Tac Toe | ClassroomIO" /> <meta name="twitter:title" content="Tic Tac Toe | ClassroomIO" />
<meta <meta
@@ -103,42 +95,37 @@
<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>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white"> <section class=" bg-white px-5 md:px-0">
<ToolsHeader className="mb-10"> <ToolsHeader className="mb-10">
<img <img src="/free-tools/tic-tac.svg" class="mx-auto w-14 rounded-full border md:w-[5%]" alt="Tic Tac Icon" />
src="/free-tools/tic-tac.svg" <h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Tic-Tac game</h1>
class="w-14 md:w-[5%] mx-auto border rounded-full" <p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
alt="Tic Tac Icon" More than just a game; it's an educational tool that teaches pattern recognition, and decision-making.
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Tic-Tac game</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
More than just a game; it's an educational tool that teaches pattern recognition, and
decision-making.
</p> </p>
</ToolsHeader> </ToolsHeader>
<div <div
class="overflow-hidden relative tic-tac-bg text-white w-full h-[55vh] md:w-[59%] md:h-[82vh] border-8 border-black mx-auto flex justify-center items-center bg-white" class="tic-tac-bg relative mx-auto flex h-[55vh] w-full items-center justify-center overflow-hidden border-8 border-black bg-white text-white md:h-[82vh] md:w-[59%]"
> >
<!-- congratulatory message --> <!-- congratulatory message -->
{#if message} {#if message}
<div <div
transition:fly={{ x: -100, delay: 600, easing: sineInOut }} transition:fly={{ x: -100, delay: 600, easing: sineInOut }}
class="absolute w-full top-[43%] flex flex-col justify-center items-center z-[150]" class="absolute top-[43%] z-[150] flex w-full flex-col items-center justify-center"
> >
<div <div
class="flex gap-10 justify-center py-1 h-[8vh] md:h-[13vh] w-full items-center bg-white font-bold text-[#0233BD]" class="flex h-[8vh] w-full items-center justify-center gap-10 bg-white py-1 font-bold text-[#0233BD] md:h-[13vh]"
> >
{#if message != 'Tie game!'} {#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" />
{/if} {/if}
<h2 class="md:text-base text-xs">{message}</h2> <h2 class="text-xs md:text-base">{message}</h2>
</div> </div>
<button <button
type="button" type="button"
on:click={playAgain} on:click={playAgain}
class="px-16 pt-1 pb-1.5 md:text-base text-xs border border-white font-bold rounded-md mt-10 bg-[#0F62FE]" class="mt-10 rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-xs font-bold md:text-base"
>Play Again</button >Play Again</button
> >
</div> </div>
@@ -146,17 +133,13 @@
<!-- this condition renders the game --> <!-- this condition renders the game -->
{#if startGame} {#if startGame}
<div class="w-full h-full p-5 flex flex-col border justify-between items-center"> <div class="flex h-full w-full flex-col items-center justify-between border p-5">
<header class="flex w-full items-center justify-between"> <header class="flex w-full items-center justify-between">
<button <button
on:click={changePlayer} on:click={changePlayer}
class="text-[10px] md:text-xs w-[35%] gap-2 md:w-28 flex flex-row items-center leading-3 font-medium" class="flex w-[35%] flex-row items-center gap-2 text-[10px] font-medium leading-3 md:w-28 md:text-xs"
> >
<img <img src="/free-tools/tic-tac/computer-icon.svg" alt="Computer icon" class="w-4 md:w-6" />
src="/free-tools/tic-tac/computer-icon.svg"
alt="Computer icon"
class="w-4 md:w-6"
/>
{#if $changeToComputer.change} {#if $changeToComputer.change}
Play two players Play two players
{:else} {:else}
@@ -165,7 +148,7 @@
</button> </button>
<button <button
on:click={newGame} on:click={newGame}
class="text-[10px] w-24 flex flex-col gap-1 p-2 shadow-md rounded-md items-center leading-3 font-medium border text-[#0542CC] bg-white" class="flex w-24 flex-col items-center gap-1 rounded-md border bg-white p-2 text-[10px] font-medium leading-3 text-[#0542CC] shadow-md"
> >
<img src="/free-tools/tic-tac/restart-icon.svg" alt="Restart icon" class="w-3 md:w-4" /> <img src="/free-tools/tic-tac/restart-icon.svg" alt="Restart icon" class="w-3 md:w-4" />
Reset Score</button Reset Score</button
@@ -176,19 +159,17 @@
<Board {game} {winner} /> <Board {game} {winner} />
<!-- scores --> <!-- scores -->
<div <div class="flex w-full items-center justify-between gap-10 font-semibold text-white md:justify-center">
class="flex justify-between md:justify-center items-center gap-10 w-full text-white font-semibold" <div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
> <span class="bg-[#FE28CB99] px-1 text-[10px] md:p-1 md:text-sm">Player 1</span>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2">
<span class="px-1 md:p-1 bg-[#FE28CB99] md:text-sm text-[10px]">Player 1</span>
{$scores.X} {$scores.X}
</div> </div>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2"> <div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
<span class="px-1 md:p-1 bg-[#0F62FE] md:text-sm text-[10px]">Draw</span> <span class="bg-[#0F62FE] px-1 text-[10px] md:p-1 md:text-sm">Draw</span>
{$scores.draw} {$scores.draw}
</div> </div>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2"> <div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
<span class="px-1 md:p-1 bg-[#94F7FFB2] md:text-sm text-[10px]">Player 2</span> <span class="bg-[#94F7FFB2] px-1 text-[10px] md:p-1 md:text-sm">Player 2</span>
{$scores.O} {$scores.O}
</div> </div>
</div> </div>
@@ -197,11 +178,7 @@
<!-- this condition renders the homepage --> <!-- this condition renders the homepage -->
{:else} {:else}
<div class="flex flex-col items-center gap-5"> <div class="flex flex-col items-center gap-5">
<img <img src="/free-tools/tic-tac/tic-tac-start-icon.svg" alt="Tic tac toe game icon" class="w-[50%] md:w-[70%]" />
src="/free-tools/tic-tac/tic-tac-start-icon.svg"
alt="Tic tac toe game icon"
class="w-[50%] md:w-[70%]"
/>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<button <button
type="button" type="button"
@@ -209,7 +186,7 @@
changePlayer(); changePlayer();
startTicTac(); startTicTac();
}} }}
class="mt-5 px-[3.3rem] pt-1 pb-1.5 border border-white text-sm font-bold rounded-md bg-[#0F62FE]" class="mt-5 rounded-md border border-white bg-[#0F62FE] px-[3.3rem] pb-1.5 pt-1 text-sm font-bold"
> >
Play with computer</button Play with computer</button
> >
@@ -218,7 +195,7 @@
on:click={() => { on:click={() => {
startTicTac(); startTicTac();
}} }}
class="px-16 pt-1 pb-1.5 border border-white text-sm font-bold rounded-md bg-[#0F62FE]" class="rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-sm font-bold"
>Play two players</button >Play two players</button
> >
</div> </div>
+16 -4
View File
@@ -1,14 +1,17 @@
import sequence from 'svelte-sequential-preprocessor'; import adapterNode from '@sveltejs/adapter-auto';
import adapter from '@sveltejs/adapter-auto'; import adapterVercel from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { mdsvex, escapeSvelte } from 'mdsvex'; import { mdsvex, escapeSvelte } from 'mdsvex';
import path from 'path';
import shiki from 'shiki'; import shiki from 'shiki';
import remarkUnwrapImages from 'remark-unwrap-images'; import remarkUnwrapImages from 'remark-unwrap-images';
import remarkToc from 'remark-toc'; import remarkToc from 'remark-toc';
import rehypeSlug from 'rehype-slug'; import rehypeSlug from 'rehype-slug';
const useNodeAdapter = process.env.PUBLIC_IS_SELFHOSTED === 'true';
/** @type {import('@sveltejs/kit').Config}*/ /** @type {import('@sveltejs/kit').Config}*/
/** @type {import('mdsvex').MdsvexOptions} */ /** @type {import('mdsvex').MdsvexOptions} */
@@ -30,9 +33,18 @@ const mdsvexOptions = {
const config = { const config = {
kit: { kit: {
adapter: adapter() adapter: useNodeAdapter ? adapterNode() : adapterVercel(),
alias: {
$lib: path.resolve('./src/lib'),
'$src/tools': path.resolve('./node_modules/@cio/ui/src/tools/index.ts'),
'$src/base/*': path.resolve('./node_modules/@cio/ui/src/base/*'),
'@cio/ui': path.resolve('./node_modules/@cio/ui/src'),
'@cio/ui/*': path.resolve('./node_modules/@cio/ui/src/*'),
'@cio/utils': path.resolve('./node_modules/@cio/utils/dist'),
'@cio/utils/*': path.resolve('./node_modules/@cio/utils/dist/*')
}
}, },
extensions: ['.svelte', '.md'], extensions: ['.svelte', '.md'],
preprocess: sequence([vitePreprocess(), mdsvex(mdsvexOptions)]) preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)]
}; };
export default config; export default config;
-8
View File
@@ -1,8 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: []
};
+13
View File
@@ -2,11 +2,24 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
css: {
preprocessorOptions: {
scss: {
silenceDeprecations: ['legacy-js-api']
}
}
},
plugins: [sveltekit()], plugins: [sveltekit()],
build: { build: {
sourcemap: true sourcemap: true
}, },
optimizeDeps: { optimizeDeps: {
entries: ['src/routes/**/+*.{js,ts,svelte}'] entries: ['src/routes/**/+*.{js,ts,svelte}']
},
ssr: {
noExternal: ['bits-ui', 'svelte-toolbelt', '@lucide/svelte', 'runed']
},
resolve: {
mainFields: ['browser']
} }
}); });
@@ -0,0 +1 @@
export const FIELDS = ['value', 'onValueChange', 'dir', 'orientation', 'viewport', 'disabled'] as string[];
@@ -0,0 +1,155 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import * as NavigationMenu from '@cio/ui/base/navigation-menu';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/NavigationMenu',
component: NavigationMenu.Root,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
</script>
<Story name="Default">
{#snippet template()}
<NavigationMenu.Root class="ui:relative">
<NavigationMenu.List class="ui:flex ui:gap-2">
<NavigationMenu.Item>
<NavigationMenu.Trigger>Getting started</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="ui:grid ui:gap-3 ui:p-6 ui:md:w-[400px] ui:lg:w-[500px] ui:lg:grid-cols-[.75fr_1fr]">
<li class="ui:row-span-3">
<NavigationMenu.Link
href="/"
class="ui:from-muted/50 ui:to-muted ui:flex ui:h-full ui:w-full ui:select-none ui:flex-col ui:justify-end ui:rounded-md ui:bg-gradient-to-b ui:p-6 ui:no-underline ui:outline-none ui:focus:shadow-md"
>
<div class="ui:mb-2 ui:mt-4 ui:text-lg ui:font-medium">ClassroomIO</div>
<p class="ui:text-muted-foreground ui:text-sm ui:leading-tight">
A powerful learning management system for creating and managing online courses.
</p>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link
href="/docs"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Introduction</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
Learn how to get started with ClassroomIO.
</p>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link
href="/docs/installation"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Installation</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
How to install and configure your instance.
</p>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link
href="/docs/components"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Components</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
Explore the available UI components.
</p>
</NavigationMenu.Link>
</li>
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Features</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="ui:grid ui:w-[400px] ui:gap-3 ui:p-4 md:ui:w-[500px] md:ui:grid-cols-2 lg:ui:w-[600px]">
<li>
<NavigationMenu.Link
href="/courses"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Course Management</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
Create and organize your courses with ease.
</p>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link
href="/students"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Student Tracking</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
Monitor student progress and performance.
</p>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link
href="/assessments"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Assessments</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
Create quizzes, assignments, and exams.
</p>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link
href="/analytics"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Analytics</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
View detailed analytics and reports.
</p>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link
href="/collaboration"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Collaboration</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
Enable team collaboration and discussions.
</p>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link
href="/integrations"
class="ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:block ui:select-none ui:space-y-1 ui:rounded-md ui:p-3 ui:leading-none ui:no-underline ui:outline-none ui:transition-colors"
>
<div class="ui:text-sm ui:font-medium ui:leading-none">Integrations</div>
<p class="ui:text-muted-foreground ui:line-clamp-2 ui:text-sm ui:leading-snug">
Connect with your favorite tools.
</p>
</NavigationMenu.Link>
</li>
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link href="/docs">Documentation</NavigationMenu.Link>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>
{/snippet}
</Story>
+1 -1
View File
@@ -12,7 +12,7 @@
}, },
"devDependencies": { "devDependencies": {
"@internationalized/date": "^3.8.1", "@internationalized/date": "^3.8.1",
"@lucide/svelte": "^0.554.0", "@lucide/svelte": "^0.544.0",
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.5.27", "@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^6.2.1", "@sveltejs/vite-plugin-svelte": "^6.2.1",
@@ -36,7 +36,7 @@
class="ui:ring-offset-background ui:focus:ring-ring ui:rounded-xs ui:focus:outline-hidden ui:absolute ui:top-4 ui:opacity-70 ui:transition-opacity ui:hover:opacity-100 ui:focus:ring-2 ui:focus:ring-offset-2 ui:disabled:pointer-events-none ui:[&_svg:not([class*='size-'])]:size-4 ui:[&_svg]:pointer-events-none ui:[&_svg]:shrink-0 ui:end-4 ui:cursor-pointer" class="ui:ring-offset-background ui:focus:ring-ring ui:rounded-xs ui:focus:outline-hidden ui:absolute ui:top-4 ui:opacity-70 ui:transition-opacity ui:hover:opacity-100 ui:focus:ring-2 ui:focus:ring-offset-2 ui:disabled:pointer-events-none ui:[&_svg:not([class*='size-'])]:size-4 ui:[&_svg]:pointer-events-none ui:[&_svg]:shrink-0 ui:end-4 ui:cursor-pointer"
> >
<XIcon /> <XIcon />
<span class="sr-only">Close</span> <span class="ui:sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
{/if} {/if}
</DialogPrimitive.Content> </DialogPrimitive.Content>
@@ -0,0 +1,28 @@
import Root from "./navigation-menu.svelte";
import Content from "./navigation-menu-content.svelte";
import Indicator from "./navigation-menu-indicator.svelte";
import Item from "./navigation-menu-item.svelte";
import Link from "./navigation-menu-link.svelte";
import List from "./navigation-menu-list.svelte";
import Trigger from "./navigation-menu-trigger.svelte";
import Viewport from "./navigation-menu-viewport.svelte";
export {
Root,
Content,
Indicator,
Item,
Link,
List,
Trigger,
Viewport,
//
Root as NavigationMenuRoot,
Content as NavigationMenuContent,
Indicator as NavigationMenuIndicator,
Item as NavigationMenuItem,
Link as NavigationMenuLink,
List as NavigationMenuList,
Trigger as NavigationMenuTrigger,
Viewport as NavigationMenuViewport,
};
@@ -0,0 +1,17 @@
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '../../tools';
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.ContentProps = $props();
</script>
<NavigationMenuPrimitive.Content
bind:ref
data-slot="navigation-menu-content"
class={cn(
'ui:data-[motion^=from-]:animate-in ui:data-[motion^=to-]:animate-out ui:data-[motion^=from-]:fade-in ui:data-[motion^=to-]:fade-out ui:data-[motion=from-end]:slide-in-from-end-52 ui:data-[motion=from-start]:slide-in-from-start-52 ui:data-[motion=to-end]:slide-out-to-end-52 ui:data-[motion=to-start]:slide-out-to-start-52 ui:start-0 ui:top-0 ui:w-full md:ui:absolute md:ui:w-auto',
'ui:group-data-[viewport=false]/navigation-menu:bg-popover ui:group-data-[viewport=false]/navigation-menu:text-popover-foreground ui:group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in ui:group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out ui:group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 ui:group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 ui:group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 ui:group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 ui:**:data-[slot=navigation-menu-link]:focus:ring-0 ui:**:data-[slot=navigation-menu-link]:focus:outline-none ui:group-data-[viewport=false]/navigation-menu:top-full ui:group-data-[viewport=false]/navigation-menu:mt-1.5 ui:group-data-[viewport=false]/navigation-menu:overflow-hidden ui:group-data-[viewport=false]/navigation-menu:rounded-md ui:group-data-[viewport=false]/navigation-menu:border ui:group-data-[viewport=false]/navigation-menu:shadow ui:group-data-[viewport=false]/navigation-menu:duration-200',
className
)}
{...restProps}
/>
@@ -0,0 +1,18 @@
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '../../tools';
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.IndicatorProps = $props();
</script>
<NavigationMenuPrimitive.Indicator
bind:ref
data-slot="navigation-menu-indicator"
class={cn(
'ui:data-[state=visible]:animate-in ui:data-[state=hidden]:animate-out ui:data-[state=hidden]:fade-out ui:data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden',
className
)}
{...restProps}
>
<div class="ui:bg-border ui:rounded-ts-sm ui:relative ui:top-[60%] ui:h-2 ui:w-2 ui:rotate-45 ui:shadow-md"></div>
</NavigationMenuPrimitive.Indicator>
@@ -0,0 +1,13 @@
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '../../tools';
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.ItemProps = $props();
</script>
<NavigationMenuPrimitive.Item
bind:ref
data-slot="navigation-menu-item"
class={cn('ui:relative', className)}
{...restProps}
/>

Some files were not shown because too many files have changed in this diff Show More