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",
"editor.formatOnSave": true,
"[svelte]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "svelte.svelte-vscode"
},
"vitest.disableWorkspaceWarning": true,
"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": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"autoprefixer": "^10.4.15",
"carbon-icons-svelte": "^12.3.0",
"@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"mdsvex": "^0.11.0",
"postcss": "^8.4.29",
"svelte": "^4.1.2",
"svelte-check": "^3.4.3",
"svelte-sequential-preprocessor": "^2.0.1",
"tailwindcss": "^3.3.3",
"typescript": "^5.0.0",
"vite": "^5.4.4"
"postcss": "^8.5.6",
"postcss-load-config": "^4.0.1",
"svelte": "^5.0.0",
"tailwindcss": "^4.1.14",
"@tailwindcss/typography": "^0.5.9",
"typescript": "^5.5.0",
"vite": "^5.4.4",
"@cio/tsconfig": "workspace:*",
"@sveltejs/adapter-vercel": "^4.0.0"
},
"type": "module",
"engines": {
@@ -36,8 +36,11 @@
"rehype-slug": "^6.0.0",
"remark-toc": "^9.0.0",
"remark-unwrap-images": "^4.0.0",
"@cio/utils": "workspace:*",
"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 {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
'@tailwindcss/postcss': {}
}
};
+35 -6
View File
@@ -1,13 +1,17 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap');
@import 'tailwindcss';
@import '@cio/ui/output.css';
@custom-variant dark (&:where(.dark, .dark *));
body {
overflow-x: hidden;
}
li {
font-family: 'Inter', sans-serif;
* {
font-family: 'Geist', sans-serif;
}
.floating {
@@ -21,6 +25,7 @@ li {
-webkit-transform: translateY(0rem);
transform: translateY(0rem);
}
50% {
-webkit-transform: translateY(1rem);
transform: translateY(1rem);
@@ -33,34 +38,42 @@ li {
-webkit-transform: translateY(0rem);
transform: translateY(0rem);
}
50% {
-webkit-transform: translateY(1rem);
transform: translateY(1rem);
}
}
.prose {
font-family: 'Inter', sans-serif;
font-family: 'Geist', sans-serif;
}
.prose :is(h1, h2, h3, h4, h5, h6) {
font-weight: 700;
margin-top: 20px;
}
.prose :is(h1) {
font-size: 2em;
}
.prose :is(h2) {
font-size: 1.5em;
}
.prose :is(h3) {
font-size: 1.17em;
}
.prose :is(h4) {
font-size: 1em;
}
.prose :is(h5) {
font-size: 0.83em;
}
.prose :is(h6) {
font-size: 0.67em;
}
@@ -82,6 +95,7 @@ li {
border-radius: 8px;
tab-size: 2;
}
.prose blockquote {
border-left: 4px solid gray;
font-style: italic;
@@ -89,6 +103,7 @@ li {
padding-left: 16px;
margin: 20px 0;
}
.prose figure + em {
display: block;
font-size: 14px;
@@ -105,11 +120,13 @@ li {
.tools-hero-bg {
background-image: url('https://assets.cdn.clsrio.com/www/tools-hero-bg.svg');
}
@media (min-width: 760px) {
.tools-hero-bg {
min-height: 350px;
}
}
.cio-bg-blue {
background: rgb(2, 51, 189);
background-size: cover;
@@ -122,8 +139,20 @@ li {
border-radius: 0.375rem;
}
svg.lucide-icon:not(.custom) {
color: #666666;
}
.dark svg.lucide-icon:not(.custom) {
color: #fff;
}
@media (min-width: 640px) {
.embed {
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>
import Chip from '$lib/Chip/Chip.svelte';
import { formatDate } from '$lib/utils/formatDate';
import { Badge } from '@cio/ui/base/badge';
import { formatDate } from '$lib/utils/format-date';
export let post;
export let isRecommended = false;
@@ -15,7 +15,7 @@
<div class="flex flex-col">
<div class="flex flex-wrap gap-2">
{#each post.tags as tag}
<Chip label={tag} />
<Badge variant="outline">{tag}</Badge>
{/each}
</div>
</div>
@@ -1,5 +1,5 @@
<script lang="ts">
import Section from './CoreFeatureSection.svelte';
import Section from './core-feature-section.svelte';
import { PUBLIC_ENABLE_STATS } from '$env/static/public';
</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 { sineInOut } from 'svelte/easing';
import { PUBLIC_ENABLE_USERS_COMPANIES } from '$env/static/public';
import { Button } from '@cio/ui/base/button';
const names = [
{ label: 'Audience', color: '#3ADFEC' },
@@ -30,32 +31,32 @@
});
</script>
<div class="hero-bg relative border-b-2 text-center bg-[#F5F8FE] h-full mt-[5%] w-full">
<div class="pt-24 w-full">
<div class="mb-10 ml-[5%] lg:ml-0 flex w-full items-center justify-start lg:justify-center">
<div class="hero-bg pt-18 relative h-full w-full border-b-2 bg-[#F5F8FE] text-center">
<div class="w-full pt-24">
<div class="mb-10 ml-[5%] flex w-full items-center justify-start lg:ml-0 lg:justify-center">
<a
target="_blank"
rel="noopener noreferrer nofollow"
href="/github"
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"
>We are <span class="text-blue-700">100%</span> open source</a
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="ui:text-primary">100%</span> open source</a
>
</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}
<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 }}
>
<a
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
>
<svg
class="absolute -right-1 -bottom-2.5"
class="absolute -bottom-2.5 -right-1"
width="11"
height="11"
viewBox="0 0 11 11"
@@ -67,17 +68,17 @@
</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 }}
>
<a
href="/github"
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
>
<svg
class="absolute -right-1 -bottom-2.5"
class="absolute -bottom-2.5 -right-1"
width="11"
height="11"
viewBox="0 0 11 11"
@@ -91,21 +92,19 @@
</div>
<div class="w-full">
<div
class="font-bold md:leading-[1.5] text-left lg:text-center ml-6 text-slate-900 text-4xl lg:text-7xl"
>
<div class="ml-6 text-left text-2xl font-semibold text-slate-900 lg:text-center lg:text-6xl">
<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
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}
<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}"
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}
</span>
</span>
@@ -115,27 +114,27 @@
<span>from one Platform</span>
</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
of tools to help you achieve your training, onboarding and certification goals.
</p>
</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}
<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 }}
>
<a
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
</a>
<svg
class="absolute -right-1 -bottom-2.5"
class="absolute -bottom-2.5 -right-1"
width="11"
height="11"
viewBox="0 0 11 11"
@@ -146,17 +145,17 @@
</svg>
</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 }}
>
<a
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
>
<svg
class="absolute -right-1 -bottom-2.5"
class="absolute -bottom-2.5 -right-1"
width="11"
height="11"
viewBox="0 0 11 11"
@@ -170,20 +169,12 @@
</div>
<div class="my-10 flex flex-col items-center justify-center gap-4 md:flex-row">
<a
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"
>
<Button variant="outline" href="/demo" target="_blank" rel="noopener noreferrer nofollow">
Book a demo
</a>
<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-4 lg:font-semibold"
href="/signup"
rel="noopener noreferrer nofollow"
>
</Button>
<Button href="/signup" target="_blank" rel="noopener noreferrer nofollow">
Sign Up for Free
</a>
</Button>
<!-- <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"
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">
<div class="mb-10">
<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>
<Badge class="py-2! px-4! underline text-sm!">yourorg.classroomio.com</Badge>
<h1
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>
import { goto } from '$app/navigation';
import Button from '$lib/Button/Button.svelte';
import { Button } from '@cio/ui/base/button';
</script>
<div
@@ -13,5 +13,5 @@
We'll get back to you as soon as possible. In the meantime, Explore and enjoy features on our
website
</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>
@@ -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 = '';
</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 />
</header>
@@ -1,6 +1,6 @@
<script lang="ts">
import ImageCopy from 'carbon-icons-svelte/lib/ImageCopy.svelte';
import TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte';
import Copy from '@lucide/svelte/icons/copy';
import Trash2 from '@lucide/svelte/icons/trash-2';
export let imageURL = '';
export let label = '';
@@ -35,7 +35,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div>
{#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>
{label}
{#if isRequired}
@@ -45,13 +45,13 @@
{#if imageURL}
<span title="clear image" on:click={deleteImage}>
<TrashCan class="fill-red-500 " />
<Trash2 class="fill-red-500 " />
</span>
{/if}
</p>
{/if}
<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()}
>
<input
@@ -63,13 +63,11 @@
required={isRequired}
/>
{#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}
<div class="space-y-4">
<ImageCopy size={24} class="mx-auto" />
<p class="text-center text-sm text-gray-500 my-2">
Max file size: 10MB, accepted: jpeg, jpg, png, gif
</p>
<Copy size={24} class="mx-auto" />
<p class="my-2 text-center text-sm text-gray-500">Max file size: 10MB, accepted: jpeg, jpg, png, gif</p>
</div>
{/if}
</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">
import { toPng } from 'html-to-image';
import { nodeStore, htmlBody, openModal } from './store';
import { Button } from '@cio/ui/base/button';
export let isDisabled: boolean;
export let isDownloading: boolean;
@@ -39,14 +40,12 @@
}
</script>
<button
<Button
type="button"
disabled={isDownloading || !isDisabled ? true : false}
on:click={convertToPng}
class="{isDownloading || !isDisabled
? 'bg-gray-200 cursor-not-allowed text-gray-600'
: 'bg-[#0233BD] text-white'} {text === 'Generate Progress Report'
? 'block md:hidden'
: ''} mt-5 transition-all delay-100 text-white text-xs font-semibold w-full py-3 rounded-md"
>{isDownloading ? 'Loading...' : text}</button
disabled={isDownloading || !isDisabled}
onclick={convertToPng}
loading={isDownloading}
class="{text === 'Generate Progress Report' ? 'block md:hidden' : ''} mt-5 w-full"
>
{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">
import Modal from '$lib/Modal/Modal.svelte';
import * as Dialog from '@cio/ui/base/dialog';
import { htmlBody, openModal } from './store';
interface Mood {
@@ -65,36 +65,30 @@
}
</script>
<Modal bind:open={$openModal.mood}>
<!-- header -->
<div class="flex justify-between">
<h1 class="text-sm font-semibold">Choose your Mood</h1>
<button
type="button"
on:click={() => ($openModal.mood = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<Dialog.Root bind:open={$openModal.mood}>
<Dialog.Content class="w-full md:w-[30%] md:min-w-[500px]">
<Dialog.Header>
<Dialog.Title>Choose your Mood</Dialog.Title>
</Dialog.Header>
<!-- mapping -->
<div class="flex flex-wrap justify-between gap-y-3 mt-3">
{#each moods as mood}
<button
on:click={() => {
selectMood(mood);
}}
class="flex gap-3 items-center shadow-sm rounded-full px-4 py-1 bg-[#F1F6FF] hover:bg-[#0233BD] hover:text-white transition-all duration-700"
>
<p class="text-xs md:text-sm font-semibold">{mood.text}</p>
<img
src="https://assets.cdn.clsrio.com/progress-report/emojis/{mood.iconSrc}.png"
alt={mood.text}
class="w-4"
/>
</button>
{/each}
</div>
<!-- -->
</Modal>
<!-- mapping -->
<div class="flex flex-wrap justify-between gap-y-3 mt-3">
{#each moods as mood}
<button
on:click={() => {
selectMood(mood);
}}
class="flex gap-3 items-center shadow-sm rounded-full px-4 py-1 bg-[#F1F6FF] hover:bg-[#0233BD] hover:text-white transition-all duration-700"
>
<p class="text-xs md:text-sm font-semibold">{mood.text}</p>
<img
src="https://assets.cdn.clsrio.com/progress-report/emojis/{mood.iconSrc}.png"
alt={mood.text}
class="w-4"
/>
</button>
{/each}
</div>
<!-- -->
</Dialog.Content>
</Dialog.Root>
@@ -1,7 +1,7 @@
<script lang="ts">
import { sineInOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
import WinningLine from './WinningLine.svelte';
import WinningLine from './winning-line.svelte';
import { changeToComputer } from './store';
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">
import { img } from '$lib/custom/index';
import { CustomImg as img } from '$lib/components';
export { img };
</script>
@@ -1,6 +1,7 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [
{
@@ -77,20 +78,15 @@
<PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
>
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Solution for Bootcamps
</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Solution for Bootcamps</Badge>
<div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
>
<div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1
class="mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
<h1 class="mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
The platform for fast
<span class="text-blue-700 relative"> growing bootcamps</span>
</h1>
@@ -99,13 +95,9 @@
everything you need to run a cutting-edge, high-impact bootcamp.
</p>
</div>
<a
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
<Button href="/signup" target="_blank" rel="noopener noreferrer nofollow">
Get Started for Free 🚀
</a>
</Button>
</div>
</PageHeader>
@@ -115,11 +107,10 @@
<div
class="w-full gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
>
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base font-medium">Our benefits</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Our benefits</Badge>
<div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1 class="text-3xl md:text-5xl font-bold">Inspire Student Success</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -130,7 +121,7 @@
</div>
<div
class="w-full max-w-screen-xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
class="w-full max-w-7xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
>
{#each benefitDetails as benefits}
<div class="w-full md:p-6 p-4 border border-[#D9E0F5] rounded-2xl flex flex-col gap-y-6">
@@ -164,11 +155,10 @@
class="w-full bg-[#F1F6FF] flex flex-col items-center justify-center gap-y-16 lg:py-24 py-16 lg:px-28 md:px-16 px-8"
>
<div
class="w-full max-w-screen-xl gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
class="w-full max-w-7xl gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
>
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base font-medium">Explore our features</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Explore our features</Badge>
<div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
@@ -181,9 +171,7 @@
</div>
</div>
<div
class="w-full max-w-screen-xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16"
>
<div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16">
{#each featureDetails as feature, i}
<div
class="w-full lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -206,21 +194,17 @@
</div>
{#if feature.ctaLink}
<a
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
href={feature.ctaLink}
target="_blank noopener noreferrer"
>
<Button variant="outline" href={feature.ctaLink} target="_blank noopener noreferrer">
{feature.ctaLabel}
</a>
</Button>
{:else}
<button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
<Button
variant="outline"
data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo"
>
Book a Demo
</button>
</Button>
{/if}
</div>
@@ -1,6 +1,7 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [
{
@@ -61,20 +62,15 @@
<PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
>
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Solution for Customer Education
</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Solution for Customer Education</Badge>
<div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
>
<div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1
class="w-[95%] mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
<h1 class="w-[95%] mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
Drive retention and revenue with
<span class="text-blue-700 relative">Customer Education</span>
</h1>
@@ -84,13 +80,9 @@
table.
</p>
</div>
<a
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
<Button href="/signup" target="_blank" rel="noopener noreferrer nofollow">
Get Started for Free 🚀
</a>
</Button>
</div>
</PageHeader>
@@ -100,11 +92,9 @@
<div
class="w-full gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
>
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class="text-sm md:text-base font-medium">Our benefits</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Our benefits</Badge>
<div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1 class="text-3xl md:text-5xl font-bold">The customer education toolkit</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -115,7 +105,7 @@
</div>
<div
class="w-full max-w-screen-xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
class="w-full max-w-7xl flex lg:flex-row flex-col lg:justify-between justify-start gap-6 lg:items-center items-start"
>
{#each benefitDetails as benefits}
<div class="w-full md:p-6 p-4 border border-[#D9E0F5] rounded-2xl flex flex-col gap-y-6">
@@ -151,7 +141,7 @@
</p>
</div>
<div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1 class="text-3xl md:text-5xl font-bold">Why Choose Us For Education Needs</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -161,9 +151,7 @@
</div>
</div>
<div
class="w-full max-w-screen-xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16"
>
<div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-y-16">
{#each featureDetails as feature, i}
<div
class="w-full lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -183,13 +171,13 @@
{feature.description}
</p>
</div>
<button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
<Button
variant="outline"
data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo"
>
Book a Demo
</button>
</Button>
</div>
<div>
@@ -1,6 +1,7 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
import { Badge } from '@cio/ui/base/badge';
const benefitDetails = [
{
@@ -57,20 +58,15 @@
<PageHeader
className="flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-6 lg:px-28 md:px-16 px-8"
>
<div class="bg-[#DCE5FF] py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base text-[#4B5563] text-center font-medium">
Unlock your team's potential
</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Unlock your team's potential</Badge>
<div
class="w-full flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-8"
>
<div
class="w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1
class="mx-auto m-0 text-4xl lg:text-7xl md:text-6xl font-bold text-slate-900 lg:text-center"
>
<h1 class="mx-auto m-0 text-4xl md:text-6xl text-slate-900 lg:text-center">
Employee training software for
<br /><span class="text-blue-700 relative">modern teams</span>
</h1>
@@ -79,23 +75,15 @@
quickly.
</p>
</div>
<a
href="/signup"
target="_blank"
class="py-5 px-6 bg-[#1D4ED8] border-transparent outline-transparent rounded-lg text-white text-lg font-semibold"
>
Get Started for Free 🚀
</a>
<Button href="/signup" target="_blank">Get Started for Free 🚀</Button>
</div>
</PageHeader>
<section
class="w-full max-w-screen-xl lg:py-24 py-16 lg:px-28 md:px-16 px-8 flex lg:flex-row flex-col lg:items-center lg:justify-between justify-center gap-x-6 gap-y-8"
class="w-full max-w-7xl lg:py-24 py-16 lg:px-28 md:px-16 px-8 flex lg:flex-row flex-col lg:items-center lg:justify-between justify-center gap-x-6 gap-y-8"
>
<div class="w-full lg:w-[35%] gap-y-4 flex flex-col items-start justify-start">
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class=" text-sm md:text-base font-medium">Amazing benefits</p>
</div>
<Badge variant="outline">Amazing benefits</Badge>
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
<h1 class="font-bold text-4xl">Unlock your team's potential</h1>
@@ -134,13 +122,10 @@
<div
class="w-full gap-y-4 flex flex-col lg:items-center items-start lg:justify-center justify-start"
>
<div class="w-fit py-2 px-4 rounded-full border-[#C2D2FF] border-2 text-center">
<p class="text-sm md:text-base text-[#4B5563] text-center font-medium">
Explore our features
</p>
</div>
<Badge class="py-2! px-4!" variant="outline">Explore our features</Badge>
<div
class="lg:w-full max-w-screen-xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
class="lg:w-full max-w-7xl flex flex-col lg:items-center items-start lg:justify-center justify-start gap-y-4"
>
<h1 class="text-3xl md:text-5xl font-bold">What makes us the right choice</h1>
<p class="text-[#334155] text-sm md:text-base font-light lg:text-center md:w-[80%]">
@@ -150,9 +135,7 @@
</div>
</div>
<div
class="w-full max-w-screen-xl flex flex-col justify-start items-center lg:gap-y-24 gap-16 p-2"
>
<div class="w-full max-w-7xl flex flex-col justify-start items-center lg:gap-y-24 gap-16 p-2">
{#each featureDetails as feature, i}
<div
class="w-full lg:w-[70%] flex lg:flex-row {i % 2 !== 0
@@ -173,13 +156,13 @@
{feature.description}
</p>
</div>
<button
class="py-4 px-8 border border-[#1D4ED8] outline-transparent rounded text-[#1D4ED8] text-sm font-semibold"
<Button
variant="outline"
data-cal-config="'layout':'month_view'"
data-cal-link="classroomio/demo"
>
Book a Demo
</button>
</Button>
</div>
<div>
@@ -198,7 +181,7 @@
class="w-full bg-[#040F2D] flex flex-col items-center justify-start gap-y-16 lg:py-24 py-16 lg:px-28 md:px-16 px-8"
>
<div
class="w-full max-w-screen-xl flex lg:flex-row-reverse flex-col-reverse lg:justify-between justify-start lg:items-center items-start gap-y-8"
class="w-full max-w-7xl flex lg:flex-row-reverse flex-col-reverse lg:justify-between justify-start lg:items-center items-start gap-y-8"
>
<div class="w-full lg:w-[40%] gap-y-4 flex flex-col items-start justify-start">
<div class="md:w-full flex flex-col items-start justify-start gap-y-4">
@@ -209,13 +192,7 @@
Companies get the opportunity to request custom features from us.
</p>
</div>
<a
href="/demo"
target="_blank"
class="py-4 px-8 border border-tranparent outline-transparent bg-white rounded text-[#040F2D] text-sm font-semibold"
>
Talk to sales
</a>
<Button href="/demo" target="_blank" variant="outline">Talk to sales</Button>
</div>
<img
+1 -3
View File
@@ -1,9 +1,7 @@
<script>
import { dev } from '$app/environment';
import { page } from '$app/state';
import Footer from '$lib/Footer/Footer.svelte';
import Navigation from '$lib/Navigation/Navigation.svelte';
import NotFound from '$lib/NotFound/NotFound.svelte';
import { Footer, Navigation, NotFound } from '$lib/components';
import extend from 'just-extend';
import { onMount } from 'svelte';
import { MetaTags } from 'svelte-meta-tags';
+1 -6
View File
@@ -1,10 +1,5 @@
<script>
import Hero from '$lib/Home/Hero.svelte';
import CoreFeatures from '$lib/Home/CoreFeatures.svelte';
import Faq from '$lib/Home/Faq.svelte';
import StudentDashboard from '$lib/Home/StudentDashboard.svelte';
import MoreFeatures from '$lib/Home/MoreFeatures.svelte';
import Testimonial from '$lib/Home/Testimonial.svelte';
import { Hero, CoreFeatures, Faq, StudentDashboard, MoreFeatures, Testimonial } from '$lib/components';
import { PUBLIC_ENABLE_FAQ } from '$env/static/public';
import { onMount } from 'svelte';
+1 -1
View File
@@ -1,5 +1,5 @@
import { json } from '@sveltejs/kit';
import type { BlogPost } from '$lib/types';
import type { BlogPost } from '$lib/utils/types';
async function getBlogPosts() {
let blogPosts: BlogPost[] = [];
+28 -27
View File
@@ -1,38 +1,39 @@
import { PUBLIC_SERVER_URL } from '$env/static/public';
const sendEmail = async (subject: string, content: string, replyTo?: string) => {
try {
const response = await fetch(`${PUBLIC_SERVER_URL}/sendEmail`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify([
{
from: `"ClassroomIO Support" <[email protected]>`,
to: 'help@classroomio.com',
subject,
content,
replyTo
}
])
});
const sendEmail =
(sFetch: typeof fetch) => async (subject: string, content: string, replyTo?: string) => {
try {
const response = await sFetch(`${PUBLIC_SERVER_URL}/sendEmail`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify([
{
from: `"ClassroomIO Support" <notify@mail.classroomio.com>`,
to: '[email protected]',
subject,
content,
replyTo
}
])
});
const responseData = await response.json();
console.log('Emails sent successfully:', responseData);
const responseData = await response.json();
console.log('Emails sent successfully:', responseData);
return 'sent';
} catch (error) {
console.error('Error sending email', error);
return 'failed';
}
};
return 'sent';
} catch (error) {
console.error('Error sending email', error);
return 'failed';
}
};
export async function POST({ request }) {
export async function POST({ request, fetch }) {
try {
const { title, description, replyTo } = await request.json();
console.log(title, description, replyTo);
const result = await sendEmail(title, description, replyTo);
const result = await sendEmail(fetch)(title, description, replyTo);
return new Response(
JSON.stringify({
+2 -6
View File
@@ -1,7 +1,5 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import BlogListItem from '$lib/Blog/BlogListItem.svelte';
import { PageSignupCTA, PageHeader, BlogListItem } from '$lib/components';
export let data;
</script>
@@ -12,9 +10,7 @@
<section>
<PageHeader className="flex flex-col items-center justify-center text-center">
<h1
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<h1 class="mx-auto text-4xl md:text-6xl text-slate-900 flex flex-col items-center">
<span>What's new on</span>
<span class="text-blue-700 relative">ClassroomIO?</span>
</h1>
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BlogPost } from '$lib/types';
import type { BlogPost } from '$lib/utils/types';
/** @type {import('./$types').PageLoad} */
export async function load({ fetch }) {
@@ -1,8 +1,10 @@
<script lang="ts">
import { ChevronLeft } from 'carbon-icons-svelte';
import Chip from '$lib/Chip/Chip.svelte';
import { formatDate } from '$lib/utils/formatDate';
import BlogListItem from '$lib/Blog/BlogListItem.svelte';
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import { Badge } from '@cio/ui/base/badge';
import { formatDate } from '$lib/utils/format-date';
import { Button } from '@cio/ui/base/button';
import { BlogListItem } from '$lib/components';
export let data;
</script>
@@ -11,13 +13,13 @@
{#if data}
<article class="py-16">
<!-- Title -->
<hgroup class="flex flex-col items-center justify-center text-center w-full">
<hgroup class="flex w-full flex-col items-center justify-center text-center">
<p class="text-sm text-gray-500">{formatDate(data.meta.date)}</p>
<p class="font-bold text-3xl py-2 text-center md:w-[60%]">{@html data.meta.title}</p>
<p class="py-2 text-center text-3xl font-bold md:w-[60%]">{@html data.meta.title}</p>
</hgroup>
<main class="mx-auto max-w-screen-md px-4 lg:px-8">
<div class="flex items-center justify-start gap-4 my-2 border-y border-gray-200 py-4">
<img loading="lazy" src={data.meta.avatar} alt="avatar" class="w-10 h-10 rounded-full" />
<main class="mx-auto max-w-3xl px-4 lg:px-8">
<div class="my-2 flex items-center justify-start gap-4 border-y border-gray-200 py-4">
<img loading="lazy" src={data.meta.avatar} alt="avatar" class="h-10 w-10 rounded-full" />
<span>
<p class="font-semibold">{data.meta.author}</p>
<p class="text-gray-500">{data.meta.role}</p>
@@ -25,12 +27,12 @@
</div>
<!-- Post -->
<div class="prose pt-2 pb-4 border-b-2 border-gray-200">
<div class="prose border-b-2 border-gray-200 pb-4 pt-2">
<svelte:component this={data.content} />
<!-- Tags -->
<div class="flex gap-2 py-4">
{#each data.meta.tags as tag}
<Chip label={tag} />
<Badge variant="outline">{tag}</Badge>
{/each}
</div>
</div>
@@ -40,7 +42,7 @@
<p class="text-xl font-semibold">Related Posts</p>
<ul class="flex items-start justify-start gap-3 overflow-x-scroll">
{#each data.relatedPosts as post}
<li class="py-10 min-w-[80%] sm:w-80 sm:min-w-0">
<li class="min-w-[80%] py-10 sm:w-80 sm:min-w-0">
<BlogListItem {post} isRecommended />
</li>
{/each}
@@ -48,12 +50,9 @@
</section>
{/if}
<a
href="/blog"
class="flex font-medium items-center gap-2 text-slate-700 text-lg underline my-10"
>
<ChevronLeft class=" w-6 h-6 font-medium" /> Back to all posts
</a>
<Button href="/blog" variant="link">
<ChevronLeft /> Back to all posts
</Button>
</main>
</article>
{/if}
+1 -1
View File
@@ -1,5 +1,5 @@
import { error } from '@sveltejs/kit';
import type { BlogPost, RawBlogPost } from '$lib/types';
import type { BlogPost, RawBlogPost } from '$lib/utils/types';
import type { MetaTagsProps } from 'svelte-meta-tags';
/** @type {import('./$types').PageLoad} */
+16 -17
View File
@@ -1,11 +1,12 @@
<script lang="ts">
import ArrowUpRight from 'carbon-icons-svelte/lib/ArrowUpRight.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import ArrowUpRight from '@lucide/svelte/icons/arrow-up-right';
import FeedbackForm from '$lib/Contact/FeedbackForm.svelte';
import HelpForm from '$lib/Contact/HelpForm.svelte';
import { FORM_TYPE } from '$lib/types';
import type { FORM_TYPE_KEY } from '$lib/types';
import { FORM_TYPE } from '$lib/utils/types';
import type { FORM_TYPE_KEY } from '$lib/utils/types';
import HelpForm from '$lib/features/contact/help-form.svelte';
import { PageHeader } from '$lib/components';
import FeedbackForm from '$lib/features/contact/feedback-form.svelte';
let currentForm: FORM_TYPE_KEY;
@@ -45,40 +46,38 @@
<section>
<PageHeader className="flex flex-col items-center justify-center bg-[#F5F8FE] text-center">
<div class="mb-2 ml-[5%] lg:ml-0 flex w-full items-center justify-center">
<div class="mb-2 ml-[5%] flex w-full items-center justify-center lg:ml-0">
<span
style="color: rgb(75, 85, 99);"
class="rounded-full border-2 border-[#C2D2FF] px-4 py-1 text-sm font-medium bg-[#DCE5FF] text-blue-700"
class="rounded-full border-2 border-[#C2D2FF] bg-[#DCE5FF] px-4 py-1 text-sm font-medium text-blue-700"
>
Contact Us
</span>
</div>
<h1
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<h1 class="mx-auto flex flex-col items-center text-4xl text-slate-900 md:text-6xl">
<span>Get in touch with <span class="text-blue-700">Us</span></span>
</h1>
<p
class="w-[90%] md:w-[60%] max-w-3xl text-center font-normal text-lg text-slate-700 mt-10 lg:mt-7"
class="mt-10 w-[90%] max-w-3xl text-center text-lg font-normal text-slate-700 md:w-[60%] lg:mt-7"
>
Report any bugs you encounter, request new features you'd like to see, or ask any questions
you have about ClassroomIO
</p>
</PageHeader>
<div class="my-20 mx-auto">
<div class="mx-auto my-20">
<!-- Content here -->
<div
class="mx-auto w-[90%] lg:w-[60%] place-items-center grid grid-cols-1 md:grid-cols-2 gap-10"
class="mx-auto grid w-[90%] grid-cols-1 place-items-center gap-10 md:grid-cols-2 lg:w-[60%]"
>
{#each formList as list}
<button
on:click={() => onChange(list.id, list.to)}
class="text-start border-[1.5px] w-full rounded-md p-3 {currentForm === list.id
class="w-full rounded-md border-[1.5px] p-3 text-start {currentForm === list.id
? `border-[#0233BD]`
: `border-gray-200`} flex items-center justify-between"
>
<p class="text-lg text-slate-700 font-normal">{list.title}</p>
<p class="text-lg font-normal text-slate-700">{list.title}</p>
{#if list.to}
<ArrowUpRight size={16} />
{/if}
@@ -87,7 +86,7 @@
</div>
{#if currentForm === FORM_TYPE[currentForm]}
<div class="mx-auto w-[90%] lg:w-[60%] px-5">
<div class="mx-auto w-[90%] px-5 lg:w-[60%]">
{#if currentForm === FORM_TYPE.HELP}
<HelpForm />
{:else if currentForm === FORM_TYPE.FEEDBACK}
@@ -1,5 +1,6 @@
<script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
export let data;
</script>
@@ -35,18 +36,14 @@
</p>
</div>
<div class="text-left px-6 mt-3">
<a
<Button
href={friend.href}
target="_blank"
rel="noopener noreferrer"
class="inline-block w-full"
variant="outline"
>
<button
class="font-semibold text-base text-white bg-gray-800 hover:bg-gray-900 hover:shadow-xl transition-all delay-100 px-4 py-3 rounded-md"
>
Learn more
</button>
</a>
Learn more
</Button>
</div>
</li>
{/each}
+1 -1
View File
@@ -1,4 +1,4 @@
import type { OssFriend } from '$lib/types';
import type { OssFriend } from '$lib/utils/types';
export async function load() {
const friends: OssFriend[] = [
+17 -26
View File
@@ -1,9 +1,10 @@
<script>
import Check from '@lucide/svelte/icons/check';
import { fade } from 'svelte/transition';
import Checkmark from 'carbon-icons-svelte/lib/Checkmark.svelte';
import { PLANS } from '@cio/utils/plans';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
let isYearlyPlan = false;
@@ -18,16 +19,14 @@
<section>
<PageHeader className="flex flex-col items-center justify-center text-center">
<h1
class="mx-auto text-4xl md:text-7xl lg:text-6xl font-bold text-slate-900 flex flex-col items-center"
>
<h1 class="mx-auto flex flex-col items-center text-4xl text-slate-900 md:text-6xl">
<span>There is room for</span>
<span class="text-blue-700 relative">Everyone.</span>
<span class="relative text-blue-700">Everyone.</span>
</h1>
<p class="w-[90%] md:w-[60%] text-center font-normal text-lg text-slate-700 mt-10 lg:mt-7">
<p class="mt-10 w-[90%] text-center text-lg font-normal text-slate-700 md:w-[60%] lg:mt-7">
You get a customizable LMS, AI integration for productive educators and many more...
</p>
<div class="relative mt-10 flex items-center rounded-[30px] border-[2px] p-[2px] lg:scale-100">
<div class="relative mt-10 flex items-center rounded-[30px] border-2 p-[2px] lg:scale-100">
<button
style="background-color: {isYearlyPlan ? 'initial' : '#1D4EE2'}; color: {isYearlyPlan
? '#5e636b'
@@ -71,7 +70,7 @@
{PLANS.BASIC.NAME}
</p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-gray-500 lg:leading-6">
<p class="mt-4 text-sm font-light leading-6 text-gray-500 lg:text-base lg:leading-6">
{PLANS.BASIC.DESCRIPTION}
</p>
@@ -81,7 +80,7 @@
<span class="text-base">Free forever</span>
<a
class="text-base mt-10 block w-full rounded-md bg-slate-900 py-3 text-center font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
class="mt-10 block w-full rounded-md bg-slate-900 py-3 text-center text-base font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
href={PLANS.BASIC.CTA.LINK}
target="_blank"
>
@@ -91,11 +90,7 @@
<ul class="mt-8 space-y-2 text-sm text-gray-600 lg:space-y-5 xl:mt-10">
{#each PLANS.BASIC.FEATURES as feature}
<li class="flex items-center">
<Checkmark
size={24}
fill="#1D4EE2"
class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]"
/>
<Check size={24} fill="#1D4EE2" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
{feature}
</li>
{/each}
@@ -108,7 +103,7 @@
{PLANS.EARLY_ADOPTER.NAME}
</p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-white lg:leading-6">
<p class="mt-4 text-sm font-light leading-6 text-white lg:text-base lg:leading-6">
{PLANS.EARLY_ADOPTER.DESCRIPTION}
</p>
@@ -125,7 +120,7 @@
{/key}
<a
class="text-base mt-10 block w-full rounded-md bg-white py-3 text-center font-medium text-slate-900 hover:bg-indigo-50 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
class="mt-10 block w-full rounded-md bg-white py-3 text-center text-base font-medium text-slate-900 hover:bg-indigo-50 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
href={PLANS.EARLY_ADOPTER.CTA.LINK}
target="_blank"
>
@@ -134,7 +129,7 @@
<ul class="mt-8 space-y-3 text-sm font-light text-white lg:space-y-5 xl:mt-10">
{#each PLANS.EARLY_ADOPTER.FEATURES as features}
<li class="flex items-center">
<Checkmark size={24} fill="#fff" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
<Check size={24} fill="#fff" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
{features}
</li>
{/each}
@@ -146,14 +141,14 @@
<p class="text-lg font-semibold leading-8 text-gray-900 lg:text-xl">
{PLANS.ENTERPRISE.NAME}
</p>
<p class="lg:text-base mt-4 text-sm font-light leading-6 text-gray-500 lg:leading-6">
<p class="mt-4 text-sm font-light leading-6 text-gray-500 lg:text-base lg:leading-6">
{PLANS.ENTERPRISE.DESCRIPTION}
</p>
<p class="mt-6 flex items-baseline gap-x-1 text-xl font-medium lg:mt-6 lg:text-xl">
Request Pricing
</p>
<button
class="text-base mt-10 w-full rounded-md bg-slate-900 py-3 font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
class="mt-10 w-full rounded-md bg-slate-900 py-3 text-base font-medium text-white hover:bg-slate-700 lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold"
data-cal-config={"{'layout':'month_view'}"}
data-cal-link="classroomio/enterprise"
>
@@ -163,11 +158,7 @@
<ul class="mt-8 space-y-2 text-sm text-gray-600 lg:space-y-5 xl:mt-10">
{#each PLANS.ENTERPRISE.FEATURES as features}
<li class="flex items-center">
<Checkmark
size={24}
fill="#1D4EE2"
class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]"
/>
<Check size={24} fill="#1D4EE2" class="mr-2 scale-[70%] lg:mr-3 lg:scale-[100%]" />
{features}
</li>
{/each}
+3 -7
View File
@@ -1,7 +1,5 @@
<script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import Timeline from '$lib/Timeline/Timeline.svelte';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import { PageHeader, Timeline, PageSignupCTA } from '$lib/components';
const newFeatures = [
{
@@ -67,13 +65,11 @@
</svelte:head>
<PageHeader className="flex flex-col items-center justify-center text-center">
<h1 class="text-start md:text-center text-5xl md:text-7xl font-bold text-slate-900">
Product Roadmap
</h1>
<h1 class="text-start md:text-center text-4xl md:text-6xl text-slate-900">Product Roadmap</h1>
<p class="text-start md:text-center w-[80%] md:w-[40%] text-slate-700 mt-10">
This is a live document, and will be updated regularly. To submit an idea, or to view a list of
feature requests and ideas that we're considering, check out our <a
href="https://github.com/rotimi-best/classroomio/discussion"
href="https://github.com/classroomio/classroomio/discussion"
class="underline">GitHub Discussions</a
>
</p>
+43 -57
View File
@@ -1,26 +1,23 @@
<script lang="ts">
import { slide } from 'svelte/transition';
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageSignupCTA, PageHeader } from '$lib/components';
import { Button } from '@cio/ui/base/button';
const whyStart = [
{
src: '/teach/teach-from-anywhere.svg',
header: 'Fully Online; Teach from anywhere',
subText:
' Facilitate a subject from the comfort of your space, engaging students from highly diverse background'
subText: ' Facilitate a subject from the comfort of your space, engaging students from highly diverse background'
},
{
src: '/teach/get-paid.svg',
header: 'Get paid extra commission',
subText:
'Get paid for the course you design and earn extra comission based on course perfomance'
subText: 'Get paid for the course you design and earn extra comission based on course perfomance'
},
{
src: '/teach/inspire-learners.svg',
header: 'Inspire learners',
subText:
'Your opportunity to inspire thousands of learners to achieve dreams and academic pursue exellence'
subText: 'Your opportunity to inspire thousands of learners to achieve dreams and academic pursue exellence'
}
];
@@ -79,22 +76,12 @@
<title>Teach | ClassroomIO</title>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/teach.png" />
<meta property="og:title" content="Teach on ClassroomIO" />
<meta
property="og:description"
content="On ClassroomIO you can make side income while doing what you love."
/>
<meta property="og:description" content="On ClassroomIO you can make side income while doing what you love." />
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/teach.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/teach.png" />
<meta name="twitter:title" content="Teach on ClassroomIO" />
<meta
name="twitter:description"
content="On ClassroomIO you can make side income while doing what you love."
/>
<meta name="twitter:description" content="On ClassroomIO you can make side income while doing what you love." />
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/teach.png" />
</svelte:head>
@@ -102,17 +89,16 @@
<PageHeader className="flex items-center justify-evenly px-5 flex-wrap text-left">
<div>
<h1 class="text-5xl font-bold">Teach with us</h1>
<p class="my-5 lg:w-[77%] lg:text-left text-center">
<p class="my-5 text-center lg:w-[77%] lg:text-left">
With ClassroomIO, you can make side income while teaching the subject you love
</p>
<a
<Button
href="/teach/register"
target="_blank"
class="w-full block text-center lg:w-[30%] py-3 text-base text-white rounded-md bg-blue-700"
>
Register
</a>
</Button>
</div>
<div class=" ">
@@ -120,21 +106,21 @@
</div>
</PageHeader>
<div class="mt-10 mx-auto">
<div class="px-[10%] pb-10 border-b border-slate-100">
<h1 class="text-4xl pb-28 text-center font-bold flex gap-3 justify-center items-center">
<div class="mx-auto mt-10">
<div class="border-b border-slate-100 px-[10%] pb-10">
<h1 class="flex items-center justify-center gap-3 pb-28 text-center text-4xl font-bold">
Why you should teach with us? <img src="/teach/star.svg" alt="" />
</h1>
<div class="flex flex-wrap justify-center lg:justify-between mt-10">
<div class="mt-10 flex flex-wrap justify-center lg:justify-between">
{#each whyStart as why}
<figure
class="flex gap-3 flex-col justify-center items-center px-2 w-full h-full max-w-[250px] max-h-[380px] text-center"
class="flex h-full max-h-[380px] w-full max-w-[250px] flex-col items-center justify-center gap-3 px-2 text-center"
>
<img src={why.src} alt="" class="w-[80px] h-[80px]" />
<img src={why.src} alt="" class="h-[80px] w-[80px]" />
<figcaption>
<h1 class="font-bold text-base">{why.header}</h1>
<p class="text-sm mt-1">
<h1 class="text-base font-bold">{why.header}</h1>
<p class="mt-1 text-sm">
{why.subText}
</p>
</figcaption>
@@ -143,66 +129,66 @@
</div>
</div>
<div class="px-[10%] mt-10 py-10">
<h1 class="text-4xl mb-28 text-center font-bold flex gap-3 justify-center items-center">
<div class="mt-10 px-[10%] py-10">
<h1 class="mb-28 flex items-center justify-center gap-3 text-center text-4xl font-bold">
How to get started <img src="/teach/thumbs-up.svg" alt="" />
</h1>
<div class="flex flex-wrap justify-between gap-y-10 mx-auto mt-5">
<div class="mx-auto mt-5 flex flex-wrap justify-between gap-y-10">
{#each steps as step, i}
<div class="lg:max-w-[40%] px-10 py-5">
<h1 class="font-bold text-2xl">
<span class="px-2 py-1 mr-3 bg-[#F7F7F7] rounded-md">{i + 1}</span>
<div class="px-10 py-5 lg:max-w-[40%]">
<h1 class="text-2xl font-bold">
<span class="mr-3 rounded-md bg-[#F7F7F7] px-2 py-1">{i + 1}</span>
{step.title}
</h1>
<p class="text-base text-gray-600 mt-5">
<p class="mt-5 text-base text-gray-600">
{step.subText}
</p>
</div>
{/each}
</div>
<div class="flex justify-center px-[5%] mt-3">
<a
<div class="mt-3 flex justify-center px-[5%]">
<Button
href="/teach/register"
target="_blank"
class="w-full lg:max-w-[20%] py-3 text-base text-white rounded-md bg-blue-700 text-center"
>
rel="noopener noreferrer nofollow"
>
Get Started
</a>
</Button>
</div>
</div>
<div class="px-2 lg:px-[12%] bg-[#F5F8FE] my-10 py-12 h-[75%] lg:h-[80%]">
<h1 class="text-2xl text-center font-bold flex gap-3 justify-center items-center">
<div class="my-10 h-[75%] bg-[#F5F8FE] px-2 py-12 lg:h-[80%] lg:px-[12%]">
<h1 class="flex items-center justify-center gap-3 text-center text-2xl font-bold">
Word from our fufilled facilitators
</h1>
<div
class="mt-10 w-full md:w-2/4 mx-auto border shadow-sm bg-white rounded-md text-center py-6 relative before:w-[4.5%] before:absolute before:left-0 before:top-0 before:h-full before:bg-[#0233BD] before:rounded-tl-md before:rounded-bl-md"
class="relative mx-auto mt-10 w-full rounded-md border bg-white py-6 text-center shadow-sm before:absolute before:left-0 before:top-0 before:h-full before:w-[4.5%] before:rounded-bl-md before:rounded-tl-md before:bg-[#0233BD] md:w-2/4"
>
<h1 class="text-base font-medium leading-5 w-[77%] mx-auto">
With ClassroomIO, I am training over 100 students. I am making more money than expected
with little effort and my students are happy. Love the product
<h1 class="mx-auto w-[77%] text-base font-medium leading-5">
With ClassroomIO, I am training over 100 students. I am making more money than expected with little effort and
my students are happy. Love the product
</h1>
<div class="flex flex-row gap-4 justify-center items-center mt-3">
<div class="mt-3 flex flex-row items-center justify-center gap-4">
<img src="/teach/user-profile.png" alt="" class="w-8" />
<p class="text-sm">
<span class="font-semibold mr-2"> Bilik Diana </span> English tutor
<span class="mr-2 font-semibold"> Bilik Diana </span> English tutor
</p>
</div>
</div>
</div>
<div class="px-10 lg:px-[15%] lg:max-w-[65%] my-10 mt-10 mx-auto">
<h1 class="text-2xl text-center font-bold">Frequently asked questions</h1>
<div class="mx-auto my-10 mt-10 px-10 lg:max-w-[65%] lg:px-[15%]">
<h1 class="text-center text-2xl font-bold">Frequently asked questions</h1>
<div class="mt-10">
{#each faqs as faq, index}
<div class="border-b border-black lg:px-3 pt-1 pb-2">
<div class="border-b border-black pb-2 pt-1 lg:px-3">
<div>
<button
type="button"
class="w-full font-medium text-sm md:text-lg my-2 flex justify-between text-left md:text-center"
class="my-2 flex w-full justify-between text-left text-sm font-medium md:text-center md:text-lg"
on:click={() => toggleAnswer(index)}
>
{faq.question} <span>{faq.showAnswer ? '-' : '+'}</span>
+1 -1
View File
@@ -1,5 +1,5 @@
<script>
import PageSignupCTA from '$lib/PageSignupCTA/index.svelte';
import { PageSignupCTA } from '$lib/components';
</script>
<svelte:head>
+1 -1
View File
@@ -1,5 +1,5 @@
<script>
import PageHeader from '$lib/PageHeader/PageHeader.svelte';
import { PageHeader } from '$lib/components';
import { tools } from '$lib/utils/constants/tools';
const freeTools = tools.filter((item) => item.show);
@@ -1,5 +1,5 @@
<script lang="ts">
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import { ToolsHeader } from '$lib/components';
let namesInput = '';
let numNames = 0;
@@ -103,22 +103,14 @@
<svelte:head>
<title>Random Name Picker | ClassroomIO</title>
<meta
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta property="og:title" content="Random Name Picker | ClassroomIO" />
<meta
property="og:description"
content="Use this online name picker to draw a random name from a list of names for your online or physical classroom."
/>
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta name="twitter:title" content="Random Name Picker | ClassroomIO" />
<meta
@@ -128,41 +120,37 @@
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white">
<section class="bg-white px-5 md:px-0">
<ToolsHeader>
<img
src="/free-tools/name-picker.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
alt=""
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Random Name Picker</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Use this online name picker to draw a random name from a list of names, or to draw several
names randomly out of a list. You can use it as a name randomizer for a class activities.
<img src="/free-tools/name-picker.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Random Name Picker</h1>
<p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
Use this online name picker to draw a random name from a list of names, or to draw several names randomly out of a
list. You can use it as a name randomizer for a class activities.
</p>
</ToolsHeader>
<div class="border rounded-md md:w-[60%] my-10 py-8 md:py-[3%] mx-auto shadow-md bg-white">
<div class="w-[85%] md:w-[70%] mx-auto bg-white">
<h1 class="font-bold text-sm">List of names</h1>
<div class="mx-auto my-10 rounded-md border bg-white py-8 shadow-md md:w-[60%] md:py-[3%]">
<div class="mx-auto w-[85%] bg-white md:w-[70%]">
<h1 class="text-sm font-bold">List of names</h1>
<!-- container -->
<div
class="relative focus-within:border-[#0233BD] border-2 mt-5 h-[12rem] pl-3 pt-2 overflow-hidden rounded-sm bg-[#F1F2F4]"
class="relative mt-5 h-[12rem] overflow-hidden rounded-sm border-2 bg-[#F1F2F4] pl-3 pt-2 focus-within:border-[#0233BD]"
>
<!-- sidebar -->
<div class="flex flex-col gap-y-3 absolute right-2 md:right-7 top-3">
<div class="absolute right-2 top-3 flex flex-col gap-y-3 md:right-7">
<button
type="button"
on:click={sortRandom}
class="p-1.5 rounded-full bg-[#D9E0F5] flex justify-center items-center"
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
>
<img src="/sort-icon.svg" class="w-2.5" alt="" />
</button>
<button
type="button"
on:click={unsortRandom}
class="p-1.5 rounded-full bg-[#D9E0F5] flex justify-center items-center"
class="flex items-center justify-center rounded-full bg-[#D9E0F5] p-1.5"
>
<img src="/unsort-icon.svg" class="w-2.5" alt="" />
</button>
@@ -175,48 +163,46 @@
tabindex={0}
contenteditable
bind:textContent={namesInput}
class="w-full pr-9 md:pr-14 overflow-y-auto text-xs block border-0 outline-none h-full"
class="block h-full w-full overflow-y-auto border-0 pr-9 text-xs outline-none md:pr-14"
></span>
</div>
<!-- word count -->
<p
class="rounded-xl w-[80px] mt-1 py-1 bg-[#D9E0F5] text-center text-[#0F62FE] text-[10px] font-semibold"
>
<p class="mt-1 w-[80px] rounded-xl bg-[#D9E0F5] py-1 text-center text-[10px] font-semibold text-[#0F62FE]">
name count: {wordCount}
</p>
</div>
<p class="py-4 border-b text-xs">Each name must be separated by a comma</p>
<p class="border-b py-4 text-xs">Each name must be separated by a comma</p>
<div class="mt-10">
<h1 class="font-bold text-sm">Number of Names</h1>
<h1 class="text-sm font-bold">Number of Names</h1>
<!-- number input & button -->
<div class="flex flex-wrap gap-y-3 justify-between w-full mt-5">
<div class="mt-5 flex w-full flex-wrap justify-between gap-y-3">
<input
type="number"
bind:value={numNames}
class="px-3 py-2 text-sm w-full md:w-2/4 border-2 rounded-sm outline focus:border-[#0233BD] bg-[#F1F2F4] outline-none"
class="w-full rounded-sm border-2 bg-[#F1F2F4] px-3 py-2 text-sm outline-none outline focus:border-[#0233BD] md:w-2/4"
/>
<button
type="button"
on:click={selectName}
class="bg-[#0F62FE] rounded-md text-sm py-3 md:py-0 text-white w-[70%] md:w-[35%]"
class="w-[70%] rounded-md bg-[#0F62FE] py-3 text-sm text-white md:w-[35%] md:py-0"
>Select name(s) at random</button
>
</div>
</div>
<!-- footer logo -->
<div class="py-3 mt-10 mb-5 bg-[#F1F6FF] flex justify-center items-center gap-3">
<div class="mb-5 mt-10 flex items-center justify-center gap-3 bg-[#F1F6FF] py-3">
<img src="/free-tools/name-picker.svg" class="w-[12%] md:w-[9%]" alt="" />
{#if selectedNames.length > 0}
<h1 class="text-[#0233BD] text-xl font-bold">Selected Names</h1>
<h1 class="text-xl font-bold text-[#0233BD]">Selected Names</h1>
{/if}
</div>
<!-- loading state -->
{#if selectedNames.length > 0 && !avatarUrlsFetched}
<div class="w-full flex justify-center items-center">
<div class="flex w-full items-center justify-center">
<p class="font-bold">Loading...</p>
</div>
{/if}
@@ -226,10 +212,10 @@
<div>
<ul>
{#each selectedNames as name, index}
<li class="py-2 border-b">
<div class="flex item-center gap-3 md:gap-5 justify-center">
<li class="border-b py-2">
<div class="item-center flex justify-center gap-3 md:gap-5">
<img src={avatarUrls[index]} alt="" class="w-7" />
<p class="font-medium text-sm md:text-base flex justify-center items-center">
<p class="flex items-center justify-center text-sm font-medium md:text-base">
{name}
</p>
</div>
@@ -237,17 +223,16 @@
{/each}
</ul>
<div class="flex flex-wrap justify-between md:justify-center items-center md:gap-3 mt-3">
<div class="mt-3 flex flex-wrap items-center justify-between md:justify-center md:gap-3">
<button
type="button"
on:click={resetEntry}
class="py-2 border text-xs md:text-sm rounded-md w-[45%] md:w-[20%]"
>Reset Entries</button
class="w-[45%] rounded-md border py-2 text-xs md:w-[20%] md:text-sm">Reset Entries</button
>
<button
type="button"
on:click={selectName}
class="py-2 bg-[#0F62FE] rounded-md text-xs md:text-sm text-white w-[45%] md:w-[27%]"
class="w-[45%] rounded-md bg-[#0F62FE] py-2 text-xs text-white md:w-[27%] md:text-sm"
>Select other names</button
>
</div>
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import { ToolsHeader } from '$lib/components';
interface TodoItem {
id: number;
@@ -167,15 +167,15 @@
<meta name="twitter:image" content="" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-1 md:px-0 w-full md:w-full bg-white">
<section class=" w-full bg-white px-1 md:w-full md:px-0">
<ToolsHeader>
<img
src="/free-tools/pomodoro.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
class="mx-auto w-[15%] rounded-full border md:w-[5%]"
alt=""
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Pomodoro Timer</h1>
<p class="text-sm mt-10 text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Pomodoro Timer</h1>
<p class="mx-auto mt-10 text-sm font-light text-[#656565] md:w-[45%] md:font-normal">
Make your workday more engaging and effective with the Pomodoro timer, break work into
25-minute focused intervals called "pomodoros," followed by 5-minute breaks. take a longer
break.
@@ -183,20 +183,20 @@
</ToolsHeader>
<div
class="body w-full md:h-[60vh] flex flex-col gap-y-10 md:flex-row justify-evenly px-5 md:px-10 my-10 text-white text-center"
class="body my-10 flex w-full flex-col justify-evenly gap-y-10 px-5 text-center text-white md:h-[60vh] md:flex-row md:px-10"
>
<!-- left side -->
<div
class="w-full md:w-[50%] bg-[#0D4CFF] rounded-lg md:rounded-2xl py-7 px-5 md:p-10 flex flex-col gap-y-16 md:gap-y-0 justify-between"
class="flex w-full flex-col justify-between gap-y-16 rounded-lg bg-[#0D4CFF] px-5 py-7 md:w-[50%] md:gap-y-0 md:rounded-2xl md:p-10"
>
<!-- types -->
<div class="flex item-center justify-evenly">
<div class="item-center flex justify-evenly">
<button
type="button"
on:click={() => setTimerState('pomodoro')}
class:bg-[#3ADFECED]={timerState === 'pomodoro'}
class:bg-[#0233BD]={timerState !== 'pomodoro'}
class="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]"
class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Pomodoro</button
>
<button
@@ -204,7 +204,7 @@
on:click={() => setTimerState('long-break')}
class:bg-[#3ADFECED]={timerState === 'long-break'}
class:bg-[#0233BD]={timerState !== 'long-break'}
class="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]"
class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Short Break
</button>
<button
@@ -212,13 +212,13 @@
on:click={() => setTimerState('short-break')}
class:bg-[#3ADFECED]={timerState === 'short-break'}
class:bg-[#0233BD]={timerState !== 'short-break'}
class="transition-all duration-500 italic py-1.5 md:py-2 w-[25%] text-xs md:text-sm md:font-medium rounded-md hover:bg-[#3ADFECED]"
class="w-[25%] rounded-md py-1.5 text-xs italic transition-all duration-500 hover:bg-[#3ADFECED] md:py-2 md:text-sm md:font-medium"
>Long Break
</button>
</div>
<!-- countdown -->
<h1 class="text-7xl md:text-9xl font-bold">{countdownDisplay}</h1>
<h1 class="text-7xl font-bold md:text-9xl">{countdownDisplay}</h1>
<!-- controls -->
<div class="flex items-center justify-center gap-7">
@@ -227,7 +227,7 @@
<img
src="/free-tools/pomodoro/restart-icon.svg"
alt="Restart Icon"
class="w-7 hover:scale-110 transition-all duration-300"
class="w-7 transition-all duration-300 hover:scale-110"
/>
</button>
@@ -235,7 +235,7 @@
<button
type="button"
on:click={isPaused ? startCountdown : pauseCountdown}
class="bg-white text-[#0542CC] border text-base font-bold uppercase py-3 px-14 rounded-md hover:bg-transparent hover:text-white hover:border-white hover:border transition-all duration-300"
class="rounded-md border bg-white px-14 py-3 text-base font-bold uppercase text-[#0542CC] transition-all duration-300 hover:border hover:border-white hover:bg-transparent hover:text-white"
>
{isPaused ? 'Start' : 'Pause'}
</button>
@@ -245,16 +245,16 @@
<img
src="/free-tools/pomodoro/timer-play-icon.svg"
alt="Play Icon"
class="w-7 hover:scale-110 transition-all duration-300"
class="w-7 transition-all duration-300 hover:scale-110"
/>
</button>
</div>
</div>
<!-- right side -->
<div class="w-full md:w-[36%] relative">
<div class="relative w-full md:w-[36%]">
<h1
class="bg-[#040F2D] uppercase text-xl font-bold py-4"
class="bg-[#040F2D] py-4 text-xl font-bold uppercase"
style="clip-path: polygon(0 0, 100% 1%, 100% 65%, 0% 100%);"
>
To do list
@@ -262,28 +262,28 @@
<div class="overflow-hidden">
<!-- todos -->
<div class="overflow-y-auto mt-3 max-h-[40vh] md:max-h-[50vh]">
<div class="mt-3 max-h-[40vh] overflow-y-auto md:max-h-[50vh]">
{#each todoList as todo, i}
<div class="border text-black p-5">
<div class="border p-5 text-black">
{#if todo.isEditing}
<form on:submit|preventDefault={() => setEditing(i, false)}>
<p class="text-xs text-left text-[#656565] font-semibold">Pomodoro name</p>
<p class="text-left text-xs font-semibold text-[#656565]">Pomodoro name</p>
<input
type="text"
bind:this={addInput}
bind:value={todo.content}
class="bg-[#F1F2F4] border-[#EAEAEA] w-full border mt-2 px-3 py-2 text-base outline-none"
class="mt-2 w-full border border-[#EAEAEA] bg-[#F1F2F4] px-3 py-2 text-base outline-none"
/>
<div class="flex justify-end items-center gap-5 mt-5">
<div class="mt-5 flex items-center justify-end gap-5">
<button
type="button"
on:click={() => deleteTodo(i)}
class="rounded-sm py-2 px-5 text-xs font-semibold bg-[#F7F7F7]"
class="rounded-sm bg-[#F7F7F7] px-5 py-2 text-xs font-semibold"
>Discard</button
>
<button
type="submit"
class="rounded-sm py-2 px-5 text-xs font-semibold bg-[#0D4CFF] text-white"
class="rounded-sm bg-[#0D4CFF] px-5 py-2 text-xs font-semibold text-white"
>Save</button
>
</div>
@@ -294,7 +294,7 @@
<p class="text-sm font-medium">{todo.content}</p>
<!-- pen and menu icon -->
<div class="relative flex justify-between w-[15%]">
<div class="relative flex w-[15%] justify-between">
<button type="button" on:click={() => setEditing(i, true)}>
<img src="/free-tools/pomodoro/pen-icon.svg" alt="Pen icon" class="w-5" />
</button>
@@ -305,19 +305,19 @@
{#if todo.isVisible}
<ul
class="z-40 absolute top-6 -right-3 text-xs w-[8rem] bg-white border shadow-md rounded-[5px]"
class="absolute -right-3 top-6 z-40 w-[8rem] rounded-[5px] border bg-white text-xs shadow-md"
>
<button
type="button"
on:click={() => markTodoAsDone(i)}
class="w-full font-medium text-left py-3 px-5"
class="w-full px-5 py-3 text-left font-medium"
>
Mark as done
</button>
<button
type="button"
on:click={() => deleteTodo(i)}
class="w-full font-medium text-left py-3 px-5 bg-red-600 text-white rounded-b-[5px]"
class="w-full rounded-b-[5px] bg-red-600 px-5 py-3 text-left font-medium text-white"
>
Delete
</button>
@@ -326,9 +326,9 @@
</div>
</div>
<div class="flex justify-between items-center mt-10">
<div class="mt-10 flex items-center justify-between">
<p
class="rounded-[3px] text-xs font-medium text-[#656565] bg-[#EAEAEA] py-0.5 px-1.5"
class="rounded-[3px] bg-[#EAEAEA] px-1.5 py-0.5 text-xs font-medium text-[#656565]"
>
{todo.time}
</p>
@@ -345,12 +345,12 @@
todo.isPaused = !todo.isPaused;
}}
class="{todo.isDone
? 'bg-[#00D06C] border-none text-white'
? 'border-none bg-[#00D06C] text-white'
: todo.isPaused
? 'bg-white text-[#0D4CFF] border-[#0D4CFF]'
: 'bg-[#0D4CFF] text-white border-none'}
? 'border-[#0D4CFF] bg-white text-[#0D4CFF]'
: 'border-none bg-[#0D4CFF] text-white'}
flex items-center border-2 justify-center gap-2 py-1.5 px-4 rounded-[4px] text-xs font-bold uppercase"
flex items-center justify-center gap-2 rounded-[4px] border-2 px-4 py-1.5 text-xs font-bold uppercase"
>
{#if todo.isDone}
<img src="/free-tools/pomodoro/done-icon.svg" alt="Done icon" />
@@ -374,7 +374,7 @@
<button
type="button"
on:click={addTodo}
class="bg-[#1D4ED8] rounded-md text-center font-medium mt-10 py-3 w-full flex justify-center items-center gap-3"
class="mt-10 flex w-full items-center justify-center gap-3 rounded-md bg-[#1D4ED8] py-3 text-center font-medium"
>
Add new item
<img src="/free-tools/pomodoro/add-icon.svg" alt="Add icon" class="w-3" />
@@ -385,9 +385,9 @@
</section>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap');
.body {
font-family: 'Inter', sans-serif;
font-family: 'Geist', sans-serif;
}
</style>
@@ -1,16 +1,19 @@
<script lang="ts">
import { LogoFacebook, LogoLinkedin } from 'carbon-icons-svelte';
import { onDestroy } from 'svelte';
import { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing';
import Mood from './components/Mood.svelte';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import Avatar from './components/Avatar.svelte';
import Background from './components/Background.svelte';
import Report from './components/Report.svelte';
import FullView from './components/FullView.svelte';
import DownloadButton from './components/DownloadButton.svelte';
import { htmlBody, type HtmlBody, openModal, isFormComplete } from './components/store';
import Facebook from '@lucide/svelte/icons/facebook';
import Linkedin from '@lucide/svelte/icons/linkedin';
import { htmlBody, type HtmlBody, openModal, isFormComplete } from '$lib/features/tools/progress/store';
import Mood from '$lib/features/tools/progress/mood.svelte';
import Avatar from '$lib/features/tools/progress/avatar.svelte';
import Report from '$lib/features/tools/progress/report.svelte';
import FullView from '$lib/features/tools/progress/full-view.svelte';
import Background from '$lib/features/tools/progress/background.svelte';
import { ToolsHeader } from '$lib/components';
import DownloadButton from '$lib/features/tools/progress/download-button.svelte';
const MAX_CHARS = 160;
let remainingChars = MAX_CHARS;
@@ -65,22 +68,14 @@
<svelte:head>
<title>Random Name Picker | ClassroomIO</title>
<meta
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta property="og:title" content="Random Name Picker | ClassroomIO" />
<meta
property="og:description"
content="Use this online name picker to draw a random name from a list of names for your online or physical classroom."
/>
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta name="twitter:title" content="Random Name Picker | ClassroomIO" />
<meta
@@ -90,17 +85,12 @@
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-1 md:px-0 w-full md:w-full">
<section class=" w-full px-1 md:w-full md:px-0">
<ToolsHeader>
<img
src="/free-tools/progress-report.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
alt=""
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Progress Report</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Generate cool reports of your learning progress. Share reports with your network for
collaborative learning
<img src="/free-tools/progress-report.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Progress Report</h1>
<p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
Generate cool reports of your learning progress. Share reports with your network for collaborative learning
</p>
</ToolsHeader>
@@ -111,29 +101,25 @@
<FullView />
<div
class="border rounded-md w-full md:w-11/12 lg:w-[80%] my-10 mx-auto shadow-md flex md:flex-row flex-col justify-evenly"
class="mx-auto my-10 flex w-full flex-col justify-evenly rounded-md border shadow-md md:w-11/12 md:flex-row lg:w-[80%]"
>
<!-- left side -->
{#if showSetter}
<div
transition:fly={{ y: 100, easing: sineInOut }}
class="w-full mx-auto md:w-[48%] md:max-w-[500px] p-5 md:border-r"
class="mx-auto w-full p-5 md:w-[48%] md:max-w-[500px] md:border-r"
>
<!-- preview button -->
<div class="flex md:hidden justify-end mb-5">
<div class="mb-5 flex justify-end md:hidden">
<button
type="button"
on:click={() => {
showSetter = false;
showReport = true;
}}
class="flex justify-between items-center gap-2 px-3 py-1 rounded-full text-white text-xs bg-[#0233BD]"
class="flex items-center justify-between gap-2 rounded-full bg-[#0233BD] px-3 py-1 text-xs text-white"
>
<img
src="/free-tools/progress-report/preview-icon.svg"
alt="preview icon"
class="w-4"
/>
<img src="/free-tools/progress-report/preview-icon.svg" alt="preview icon" class="w-4" />
Preview
</button>
</div>
@@ -146,7 +132,7 @@
bind:value={$htmlBody.name}
on:input={(event) => handleInputChange(event, 'name')}
placeholder="Enter your name here"
class="w-full border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-xs placeholder:text-sm placeholder:text-[#ADADAD]"
class="my-3 w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 text-xs outline-none placeholder:text-sm placeholder:text-[#ADADAD]"
/>
</div>
@@ -156,18 +142,17 @@
<button
type="button"
on:click={() => ($openModal.mood = true)}
class="w-full flex justify-between items-center border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-gray-400 text-sm"
class="my-3 flex w-full items-center justify-between rounded-sm border bg-[#F1F2F4] px-3 py-2 text-sm text-gray-400 outline-none"
>
{#if $htmlBody.mood.text}
<div
class="px-3 py-1 border border-[#EDEDED] text-white bg-blue-500 rounded-2xl font-semibold text-[7px] md:text-xs flex items-center justify-between"
class="flex items-center justify-between rounded-2xl border border-[#EDEDED] bg-blue-500 px-3 py-1 text-[7px] font-semibold text-white md:text-xs"
>
{#if $htmlBody.mood.text}
<span class="flex gap-1 items-center">
<p class="font-semibold text-[9px] md:text-xs">is {$htmlBody.mood.text}</p>
<span class="flex items-center gap-1">
<p class="text-[9px] font-semibold md:text-xs">is {$htmlBody.mood.text}</p>
<img
src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood
.iconSrc}.png"
src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood.iconSrc}.png"
alt={$htmlBody.mood.text}
class="w-3"
/>
@@ -176,11 +161,7 @@
</div>
{:else}
What feeling describes you?
<img
src="/free-tools/progress-report/smiley-face-icon.svg"
alt="smiley face"
class="w-3"
/>
<img src="/free-tools/progress-report/smiley-face-icon.svg" alt="smiley face" class="w-3" />
{/if}
</button>
</div>
@@ -193,15 +174,15 @@
maxlength={MAX_CHARS}
on:input={(event) => handleInputChange(event, 'learning')}
placeholder="Tell us what you are learning"
class="w-full h-[17vh] border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-sm placeholder:text-sm placeholder:text-[#ADADAD]"
class="my-3 h-[17vh] w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 text-sm outline-none placeholder:text-sm placeholder:text-[#ADADAD]"
></textarea>
<p class="text-xs text-right text-[#656565]">{remainingChars} characters remaining</p>
<p class="text-right text-xs text-[#656565]">{remainingChars} characters remaining</p>
</div>
<!-- range input -->
<div class="mt-3">
<p class="text-sm text-[#656565] pb-4">Estimate your progress</p>
<div class="flex justify-between items-center">
<p class="pb-4 text-sm text-[#656565]">Estimate your progress</p>
<div class="flex items-center justify-between">
<input
type="range"
min="0"
@@ -216,18 +197,16 @@
</div>
<!-- avatar button -->
<div class="flex flex-wrap justify-between items-center mt-3">
<div class="mt-3 flex flex-wrap items-center justify-between">
<div class="w-full md:w-2/4">
<p class="text-[12px] md:text-sm text-[#656565]">Choose your avatar</p>
<p class="text-[12px] text-[#656565] md:text-sm">Choose your avatar</p>
<button
type="button"
on:click={() => ($openModal.avatar = true)}
class="bg-[#F7F7F7] w-full py-1.5 px-5 md:px-0 mt-3 flex md:w-[65%] items-center justify-between md:justify-center gap-2"
class="mt-3 flex w-full items-center justify-between gap-2 bg-[#F7F7F7] px-5 py-1.5 md:w-[65%] md:justify-center md:px-0"
>
<img
src={`https://assets.cdn.clsrio.com/progress-report/avatars/${
$htmlBody.avatar || 'avatar_m'
}.svg`}
src={`https://assets.cdn.clsrio.com/progress-report/avatars/${$htmlBody.avatar || 'avatar_m'}.svg`}
alt=""
class="w-[15%] md:w-[30%]"
/>
@@ -241,11 +220,11 @@
<!-- & background -->
<div class="w-full md:w-2/4">
<p class="text-[12px] md:text-sm text-[#656565]">Choose your background</p>
<p class="text-[12px] text-[#656565] md:text-sm">Choose your background</p>
<button
type="button"
on:click={() => ($openModal.background = true)}
class="bg-[#F7F7F7] w-full py-1.5 px-5 md:px-0 mt-3 flex md:w-[65%] items-center justify-between md:justify-center gap-2"
class="mt-3 flex w-full items-center justify-between gap-2 bg-[#F7F7F7] px-5 py-1.5 md:w-[65%] md:justify-center md:px-0"
>
<img
src={`https://assets.cdn.clsrio.com/progress-report/backgrounds/${
@@ -270,41 +249,35 @@
<div
transition:fly={{ y: 100, easing: sineInOut }}
class="w-full mx-auto md:w-[48%] md:min-w-[400px] md:max-w-[500px] px-5 md:px-1 md:mt-0 mt-5 md:p-5"
class="mx-auto mt-5 w-full px-5 md:mt-0 md:w-[48%] md:min-w-[400px] md:max-w-[500px] md:p-5 md:px-1"
>
<div class="hidden md:block">
<Report />
<!-- download & share button -->
<div class="mt-9 pt-8 px-2 h-auto border-t">
<div class="mt-9 h-auto border-t px-2 pt-8">
<DownloadButton {isDisabled} {isDownloading} text="Download Image" />
<!-- share button -->
<div class="w-full md:w-[70%] mx-auto my-5">
<h1 class="text-sm font-semibold text-center">Share on image on social media:</h1>
<div class="mx-auto my-5 w-full md:w-[70%]">
<h1 class="text-center text-sm font-semibold">Share on image on social media:</h1>
<div
class="flex justify-evenly border rounded-xl gap-5 w-[60%] md:w-[80%] px-5 py-2 mt-2 mx-auto"
>
<div class="mx-auto mt-2 flex w-[60%] justify-evenly gap-5 rounded-xl border px-5 py-2 md:w-[80%]">
<button
type="button"
on:click={shareOnFacebook}
class="w-10 hover:scale-[1.2] transition-all duration-300"
class="w-10 transition-all duration-300 hover:scale-[1.2]"
>
<LogoFacebook size={32} />
<Facebook size={32} />
</button>
<button
type="button"
on:click={shareOnLinkedIn}
class="w-10 hover:scale-[1.2] transition-all duration-300"
class="w-10 transition-all duration-300 hover:scale-[1.2]"
>
<LogoLinkedin size={32} />
<Linkedin size={32} />
</button>
<button
type="button"
on:click={shareOnTwitter}
class="w-5 hover:scale-[1.2] transition-all duration-300"
>
<button type="button" on:click={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
<img src="/twitter_logo.png" alt="X.com" />
</button>
</div>
@@ -314,14 +287,14 @@
{#if showReport}
<!-- hide button -->
<div class="flex justify-end mb-5">
<div class="mb-5 flex justify-end">
<button
type="button"
on:click={() => {
showReport = false;
showSetter = true;
}}
class="flex justify-between items-center gap-2 px-3 py-1 rounded-full text-white text-xs bg-[#0233BD]"
class="flex items-center justify-between gap-2 rounded-full bg-[#0233BD] px-3 py-1 text-xs text-white"
>
<img src="/free-tools/progress-report/hide-icon.svg" alt="preview icon" class="w-4" />
Close
@@ -330,35 +303,29 @@
<Report />
<!-- download & share button -->
<div class="mt-9 pt-8 px-2 h-auto border-t">
<div class="mt-9 h-auto border-t px-2 pt-8">
<DownloadButton {isDisabled} {isDownloading} />
<!-- share button -->
<div class="w-full md:w-[70%] mx-auto my-5">
<h1 class="text-sm font-semibold text-center">Share on image on social media:</h1>
<div class="mx-auto my-5 w-full md:w-[70%]">
<h1 class="text-center text-sm font-semibold">Share on image on social media:</h1>
<div
class="flex justify-evenly border rounded-xl gap-5 w-[60%] md:w-[80%] px-5 py-2 mt-2 mx-auto"
>
<div class="mx-auto mt-2 flex w-[60%] justify-evenly gap-5 rounded-xl border px-5 py-2 md:w-[80%]">
<button
type="button"
on:click={shareOnFacebook}
class="w-10 hover:scale-[1.2] transition-all duration-300"
class="w-10 transition-all duration-300 hover:scale-[1.2]"
>
<LogoFacebook size={32} />
<Facebook size={32} />
</button>
<button
type="button"
on:click={shareOnLinkedIn}
class="w-10 hover:scale-[1.2] transition-all duration-300"
class="w-10 transition-all duration-300 hover:scale-[1.2]"
>
<LogoLinkedin size={32} />
<Linkedin size={32} />
</button>
<button
type="button"
on:click={shareOnTwitter}
class="w-5 hover:scale-[1.2] transition-all duration-300"
>
<button type="button" on:click={shareOnTwitter} class="w-5 transition-all duration-300 hover:scale-[1.2]">
<img src="/twitter_logo.png" alt="X.com" />
</button>
</div>
@@ -1,88 +0,0 @@
<script lang="ts">
import { openModal, htmlBody } from './store';
import Modal from '$lib/Modal/Modal.svelte';
interface Avatar {
src: string;
}
let isAddIcon = false;
let hoveredIndex: number | null = null;
let avatars: Avatar[] = [
{ src: 'avatar_a' },
{ src: 'avatar_b' },
{ src: 'avatar_c' },
{ src: 'avatar_d' },
{ src: 'avatar_e' },
{ src: 'avatar_f' },
{ src: 'avatar_g' },
{ src: 'avatar_h' },
{ src: 'avatar_i' },
{ src: 'avatar_j' },
{ src: 'avatar_k' },
{ src: 'avatar_l' },
{ src: 'avatar_m' },
{ src: 'avatar_n' },
{ src: 'avatar_o' },
{ src: 'avatar_p' },
{ src: 'avatar_q' },
{ src: 'avatar_r' },
{ src: 'avatar_s' },
{ src: 'avatar_t' }
];
function selectAvatar(avatar: Avatar) {
$htmlBody.avatar = avatar.src;
$openModal.avatar = false;
}
</script>
<Modal bind:open={$openModal.avatar}>
<!-- heading -->
<div class="flex justify-between">
<h1 class="text-sm font-semibold">Choose your Avatars</h1>
<button
type="button"
on:click={() => ($openModal.avatar = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<!-- map for each avatar -->
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
{#each avatars as avatar, index}
<button
on:click={() => {
selectAvatar(avatar);
}}
on:mouseenter={() => {
hoveredIndex = index;
isAddIcon = true;
}}
on:mouseleave={() => {
hoveredIndex = null;
isAddIcon = false;
}}
class="relative w-[17%] flex justify-center items-center shadow-sm rounded-full hover:scale-110 transition-all duration-300"
>
<!-- + icon overlay -->
{#if isAddIcon && hoveredIndex === index}
<div
class="w-full h-full flex justify-center items-center bg-white border absolute rounded-full opacity-[0.7] blur-md"
></div>
<img src="/free-tools/progress-report/hover-plus-icon.svg" alt="" class="absolute" />
{/if}
<!-- avatar -->
<img
src="https://assets.cdn.clsrio.com/progress-report/avatar/{avatar.src}.svg"
alt=""
class="w-full"
/>
</button>
{/each}
</div>
<!-- -->
</Modal>
@@ -1,87 +0,0 @@
<script lang="ts">
import Modal from '$lib/Modal/Modal.svelte';
import { htmlBody, openModal } from './store';
interface Background {
src: string;
}
let backgrounds: Background[] = [
{
src: 'blue_tetiary_background'
},
{
src: 'blue_secondary_background'
},
{
src: 'blue_primary_background'
},
{
src: 'purple_tetiary_background'
},
{
src: 'purple_secondary_background'
},
{
src: 'purple_primary_background'
},
{
src: 'black_tetiary_background'
},
{
src: 'black_secondary_background'
},
{
src: 'black_primary_background'
},
{
src: 'yellow_tetiary_background'
},
{
src: 'yellow_secondary_background'
},
{
src: 'yellow_primary_background'
}
];
function selectBackground(background: Background) {
$htmlBody.background = background.src;
$openModal.background = false;
}
</script>
<Modal className="h-full max-h-[60vh]" bind:open={$openModal.background}>
<!-- -->
<div class="flex justify-between">
<h1 class="text-sm font-semibold">Choose your Background</h1>
<button
type="button"
on:click={() => ($openModal.background = false)}
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
>
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
</button>
</div>
<!-- -->
<div
class="flex flex-wrap justify-between gap-y-3 mt-3 overflow-y-auto overflow-x-hidden h-[85%]"
>
{#each backgrounds as background}
<button
on:click={() => {
selectBackground(background);
}}
class="flex w-[30%] items-center shadow-sm hover:scale-110 transition-all duration-300"
>
<img
src="https://assets.cdn.clsrio.com/progress-report/backgrounds/{background.src}.webp"
alt=""
class=""
/>
</button>
{/each}
</div>
<!-- -->
</Modal>
@@ -1,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 { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing';
import Timer from './components/Timer.svelte';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import Timer from '$lib/features/tools/stopwatch/timer.svelte';
import { ToolsHeader } from '$lib/components';
let timer: NodeJS.Timeout;
let buzzSound: HTMLAudioElement;
@@ -66,9 +66,7 @@
const hrs = Math.floor(totalSeconds / 3600);
const mins = Math.floor((totalSeconds % 3600) / 60);
const secs = totalSeconds % 60;
displayTime = `${String(hrs).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(
secs
).padStart(2, '0')}`;
displayTime = `${String(hrs).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
if (formattedTime === '00:00') {
formattedTime = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
@@ -117,22 +115,14 @@
<svelte:head>
<title>Activity Stopwatch | ClassroomIO</title>
<meta
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta property="og:title" content="Activity Stopwatch | ClassroomIO" />
<meta
property="og:description"
content="Stay on track with a customizable stopwatch for timed tasks, quizzes, and study sessions"
/>
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta name="twitter:title" content="Activity Stopwatch | ClassroomIO" />
<meta
@@ -142,76 +132,72 @@
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white">
<section class=" bg-white px-5 md:px-0">
<ToolsHeader>
<img
src="/free-tools/name-picker.svg"
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
alt=""
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Activity Stopwatch</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
Stay on track and enhance productivity with our customizable stopwatch for timed tasks,
quizzes, and study sessions.
<img src="/free-tools/name-picker.svg" class="mx-auto w-[15%] rounded-full border md:w-[5%]" alt="" />
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Activity Stopwatch</h1>
<p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
Stay on track and enhance productivity with our customizable stopwatch for timed tasks, quizzes, and study
sessions.
</p>
</ToolsHeader>
<div class="w-full md:w-2/4 my-10 mx-auto border rounded-md overflow-hidden bg-white">
<div class="mx-auto my-10 w-full overflow-hidden rounded-md border bg-white md:w-2/4">
<!-- countdown setter -->
{#if !isNextStep}
<div transition:fly={{ y: -300, delay: 0, easing: sineInOut }} class="px-6 py-8 bg-white">
<div transition:fly={{ y: -300, delay: 0, easing: sineInOut }} class="bg-white px-6 py-8">
<form on:submit|preventDefault={startTimer}>
<div>
<h1 class="font-bold text-sm">Name of activity</h1>
<h1 class="text-sm font-bold">Name of activity</h1>
<input
type="text"
bind:value={activityName}
class="w-full rounded-sm border py-2 mt-3 bg-[#F1F2F4] outline-none px-3 focus-within:border-[#0233BD] focus-within:border"
class="mt-3 w-full rounded-sm border bg-[#F1F2F4] px-3 py-2 outline-none focus-within:border focus-within:border-[#0233BD]"
required
/>
</div>
<div class="mt-10 bg-white">
<h1 class="font-bold text-sm">Set an Activity</h1>
<div class="flex flex-wrap justify-between gap-y-5 w-full mt-3 uppercase font-bold">
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2">
<h1 class="text-sm font-bold">Set an Activity</h1>
<div class="mt-3 flex w-full flex-wrap justify-between gap-y-5 font-bold uppercase">
<div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input
type="number"
bind:value={hours}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black"
class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/>
<p class="text-[10px]">hrs</p>
</div>
</div>
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2">
<div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input
type="number"
bind:value={minutes}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black"
class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/>
<p class="text-[10px]">mins</p>
</div>
</div>
<div class="bg-[#F1F2F4] w-full md:w-[30%] border px-4 py-5">
<div class="w-2/4 md:w-full mx-auto flex items-end gap-2">
<div class="w-full border bg-[#F1F2F4] px-4 py-5 md:w-[30%]">
<div class="mx-auto flex w-2/4 items-end gap-2 md:w-full">
<input
type="number"
bind:value={seconds}
class="w-[80%] md:w-[75%] outline-none text-center py-3 px-1.5 text-3xl placeholder:text-3xl placeholder:text-black"
class="w-[80%] px-1.5 py-3 text-center text-3xl outline-none placeholder:text-3xl placeholder:text-black md:w-[75%]"
/>
<p class="text-[10px]">sec</p>
</div>
</div>
</div>
<div class="flex justify-end mt-10">
<div class="mt-10 flex justify-end">
<button
type="submit"
class="bg-[#0F62FE] text-white text-sm px-5 py-3 rounded-md flex gap-3 items-center"
class="flex items-center gap-3 rounded-md bg-[#0F62FE] px-5 py-3 text-sm text-white"
>
<img src="/free-tools/stopwatch/loading-timer.svg" alt="" />
Start Timer</button
@@ -1,10 +1,10 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { sineInOut } from 'svelte/easing';
import Board from './components/Board.svelte';
import { TicTacToe } from './components/board';
import { scores, changeToComputer } from './components/store';
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
import Board from '$lib/features/tools/tic-tac-toe/board.svelte';
import { TicTacToe } from '$lib/features/tools/tic-tac-toe/board';
import { scores, changeToComputer } from '$lib/features/tools/tic-tac-toe/store';
import { ToolsHeader } from '$lib/components';
let startGame = false;
let message: string | null = null;
@@ -78,22 +78,14 @@
<svelte:head>
<title>Tic Tac Toe | ClassroomIO</title>
<meta
property="og:image"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta property="og:title" content="Tic Tac Toe | ClassroomIO" />
<meta
property="og:description"
content="A simple and free tic tac toe game to play to your friends in class or outside the classroom."
/>
<meta
property="og:image:secure_url"
itemprop="image"
content="https://brand.cdn.clsrio.com/og/free-tools.png"
/>
<meta property="og:image:secure_url" itemprop="image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
<meta name="twitter:title" content="Tic Tac Toe | ClassroomIO" />
<meta
@@ -103,42 +95,37 @@
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
</svelte:head>
<section class="mt-[10%] md:mt-16 px-5 md:px-0 bg-white">
<section class=" bg-white px-5 md:px-0">
<ToolsHeader className="mb-10">
<img
src="/free-tools/tic-tac.svg"
class="w-14 md:w-[5%] mx-auto border rounded-full"
alt="Tic Tac Icon"
/>
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Tic-Tac game</h1>
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
More than just a game; it's an educational tool that teaches pattern recognition, and
decision-making.
<img src="/free-tools/tic-tac.svg" class="mx-auto w-14 rounded-full border md:w-[5%]" alt="Tic Tac Icon" />
<h1 class="my-3 text-4xl font-bold text-[#040F2D] md:text-6xl">Tic-Tac game</h1>
<p class="text-md mx-auto font-light text-[#656565] md:w-[45%] md:font-normal">
More than just a game; it's an educational tool that teaches pattern recognition, and decision-making.
</p>
</ToolsHeader>
<div
class="overflow-hidden relative tic-tac-bg text-white w-full h-[55vh] md:w-[59%] md:h-[82vh] border-8 border-black mx-auto flex justify-center items-center bg-white"
class="tic-tac-bg relative mx-auto flex h-[55vh] w-full items-center justify-center overflow-hidden border-8 border-black bg-white text-white md:h-[82vh] md:w-[59%]"
>
<!-- congratulatory message -->
{#if message}
<div
transition:fly={{ x: -100, delay: 600, easing: sineInOut }}
class="absolute w-full top-[43%] flex flex-col justify-center items-center z-[150]"
class="absolute top-[43%] z-[150] flex w-full flex-col items-center justify-center"
>
<div
class="flex gap-10 justify-center py-1 h-[8vh] md:h-[13vh] w-full items-center bg-white font-bold text-[#0233BD]"
class="flex h-[8vh] w-full items-center justify-center gap-10 bg-white py-1 font-bold text-[#0233BD] md:h-[13vh]"
>
{#if message != 'Tie game!'}
<img src="/free-tools/tic-tac/winner-star-icon.svg" alt="Star icon" class="h-full" />
{/if}
<h2 class="md:text-base text-xs">{message}</h2>
<h2 class="text-xs md:text-base">{message}</h2>
</div>
<button
type="button"
on:click={playAgain}
class="px-16 pt-1 pb-1.5 md:text-base text-xs border border-white font-bold rounded-md mt-10 bg-[#0F62FE]"
class="mt-10 rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-xs font-bold md:text-base"
>Play Again</button
>
</div>
@@ -146,17 +133,13 @@
<!-- this condition renders the game -->
{#if startGame}
<div class="w-full h-full p-5 flex flex-col border justify-between items-center">
<div class="flex h-full w-full flex-col items-center justify-between border p-5">
<header class="flex w-full items-center justify-between">
<button
on:click={changePlayer}
class="text-[10px] md:text-xs w-[35%] gap-2 md:w-28 flex flex-row items-center leading-3 font-medium"
class="flex w-[35%] flex-row items-center gap-2 text-[10px] font-medium leading-3 md:w-28 md:text-xs"
>
<img
src="/free-tools/tic-tac/computer-icon.svg"
alt="Computer icon"
class="w-4 md:w-6"
/>
<img src="/free-tools/tic-tac/computer-icon.svg" alt="Computer icon" class="w-4 md:w-6" />
{#if $changeToComputer.change}
Play two players
{:else}
@@ -165,7 +148,7 @@
</button>
<button
on:click={newGame}
class="text-[10px] w-24 flex flex-col gap-1 p-2 shadow-md rounded-md items-center leading-3 font-medium border text-[#0542CC] bg-white"
class="flex w-24 flex-col items-center gap-1 rounded-md border bg-white p-2 text-[10px] font-medium leading-3 text-[#0542CC] shadow-md"
>
<img src="/free-tools/tic-tac/restart-icon.svg" alt="Restart icon" class="w-3 md:w-4" />
Reset Score</button
@@ -176,19 +159,17 @@
<Board {game} {winner} />
<!-- scores -->
<div
class="flex justify-between md:justify-center items-center gap-10 w-full text-white font-semibold"
>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2">
<span class="px-1 md:p-1 bg-[#FE28CB99] md:text-sm text-[10px]">Player 1</span>
<div class="flex w-full items-center justify-between gap-10 font-semibold text-white md:justify-center">
<div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
<span class="bg-[#FE28CB99] px-1 text-[10px] md:p-1 md:text-sm">Player 1</span>
{$scores.X}
</div>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2">
<span class="px-1 md:p-1 bg-[#0F62FE] md:text-sm text-[10px]">Draw</span>
<div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
<span class="bg-[#0F62FE] px-1 text-[10px] md:p-1 md:text-sm">Draw</span>
{$scores.draw}
</div>
<div class="md:text-3xl text-2xl flex items-center md:gap-5 gap-2">
<span class="px-1 md:p-1 bg-[#94F7FFB2] md:text-sm text-[10px]">Player 2</span>
<div class="flex items-center gap-2 text-2xl md:gap-5 md:text-3xl">
<span class="bg-[#94F7FFB2] px-1 text-[10px] md:p-1 md:text-sm">Player 2</span>
{$scores.O}
</div>
</div>
@@ -197,11 +178,7 @@
<!-- this condition renders the homepage -->
{:else}
<div class="flex flex-col items-center gap-5">
<img
src="/free-tools/tic-tac/tic-tac-start-icon.svg"
alt="Tic tac toe game icon"
class="w-[50%] md:w-[70%]"
/>
<img src="/free-tools/tic-tac/tic-tac-start-icon.svg" alt="Tic tac toe game icon" class="w-[50%] md:w-[70%]" />
<div class="flex flex-col gap-3">
<button
type="button"
@@ -209,7 +186,7 @@
changePlayer();
startTicTac();
}}
class="mt-5 px-[3.3rem] pt-1 pb-1.5 border border-white text-sm font-bold rounded-md bg-[#0F62FE]"
class="mt-5 rounded-md border border-white bg-[#0F62FE] px-[3.3rem] pb-1.5 pt-1 text-sm font-bold"
>
Play with computer</button
>
@@ -218,7 +195,7 @@
on:click={() => {
startTicTac();
}}
class="px-16 pt-1 pb-1.5 border border-white text-sm font-bold rounded-md bg-[#0F62FE]"
class="rounded-md border border-white bg-[#0F62FE] px-16 pb-1.5 pt-1 text-sm font-bold"
>Play two players</button
>
</div>
+16 -4
View File
@@ -1,14 +1,17 @@
import sequence from 'svelte-sequential-preprocessor';
import adapter from '@sveltejs/adapter-auto';
import adapterNode from '@sveltejs/adapter-auto';
import adapterVercel from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { mdsvex, escapeSvelte } from 'mdsvex';
import path from 'path';
import shiki from 'shiki';
import remarkUnwrapImages from 'remark-unwrap-images';
import remarkToc from 'remark-toc';
import rehypeSlug from 'rehype-slug';
const useNodeAdapter = process.env.PUBLIC_IS_SELFHOSTED === 'true';
/** @type {import('@sveltejs/kit').Config}*/
/** @type {import('mdsvex').MdsvexOptions} */
@@ -30,9 +33,18 @@ const mdsvexOptions = {
const config = {
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'],
preprocess: sequence([vitePreprocess(), mdsvex(mdsvexOptions)])
preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)]
};
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';
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
silenceDeprecations: ['legacy-js-api']
}
}
},
plugins: [sveltekit()],
build: {
sourcemap: true
},
optimizeDeps: {
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": {
"@internationalized/date": "^3.8.1",
"@lucide/svelte": "^0.554.0",
"@lucide/svelte": "^0.544.0",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.5.27",
"@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"
>
<XIcon />
<span class="sr-only">Close</span>
<span class="ui:sr-only">Close</span>
</DialogPrimitive.Close>
{/if}
</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