diff --git a/ARCHITECHTURE.md b/ARCHITECHTURE.md
index 893fe1f1..f8960a0e 100644
--- a/ARCHITECHTURE.md
+++ b/ARCHITECHTURE.md
@@ -1495,7 +1495,7 @@ Replace Supabase calls with API class method:
**After (API Route):**
```typescript
+```
+
+#### Before (LMS):
+```typescript
+import { generateLmsBreadcrumbs } from './lms-breadcrumb';
+import { currentCommunityQuestion } from '$features/community/utils/store';
+
+const breadcrumbTitle = $derived($currentCommunityQuestion.title);
+const breadcrumbs = generateLmsBreadcrumbs(
+ page.url.pathname,
+ page.url.search,
+ navItems,
+ breadcrumbTitle ? { breadcrumb: breadcrumbTitle } : undefined
+);
+```
+
+#### After (LMS - Reactive Store-Based):
+```svelte
+
+```
+
+### Step 3: Support Multi-Level Nesting (Up to 5 Layers)
+
+The refactored system supports up to 5 layers of nesting through recursive traversal. Here's how it works:
+
+#### How Deep Nesting Works
+
+1. **Recursive Processing**: The `generateNestedBreadcrumbs` function processes path segments recursively, incrementing a `depth` counter
+2. **Depth Limit**: Default `maxDepth` is 10, but can be configured (supports up to 5 layers by default)
+3. **Segment-by-Segment Matching**: Each segment is matched against nested route definitions
+4. **Dynamic Title Resolution**: Dynamic segments resolve titles via `titleResolver` or `dynamicTitleResolver`
+
+#### Example: Courses → Course → Lesson → Assignment → Submission (5 Layers)
+
+**Navigation Config** (in `org-navigation.ts`):
+```typescript
+{
+ titleKey: 'org_navigation.courses',
+ path: '/courses',
+ nestedRoutes: [
+ {
+ path: '[id]',
+ titleKey: 'Course',
+ supportsDynamicSegment: true,
+ // Note: titleResolver can be sync (store-based) or async (API-based)
+ nestedRoutes: [
+ {
+ path: 'lessons',
+ titleKey: 'Lessons',
+ nestedRoutes: [
+ {
+ path: '[lessonId]',
+ titleKey: 'Lesson',
+ supportsDynamicSegment: true,
+ nestedRoutes: [
+ {
+ path: 'assignments',
+ titleKey: 'Assignments',
+ nestedRoutes: [
+ {
+ path: '[assignmentId]',
+ titleKey: 'Assignment',
+ supportsDynamicSegment: true,
+ nestedRoutes: [
+ {
+ path: 'submissions',
+ titleKey: 'Submissions',
+ nestedRoutes: [
+ {
+ path: '[submissionId]',
+ titleKey: 'Submission',
+ supportsDynamicSegment: true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+```
+
+**Breadcrumb Component** (in `app-breadcrumbs.svelte`):
+```svelte
+
+```
+
+**Alternative: Per-Route Store Resolvers** (More Granular):
+```svelte
+
+```
+
+**URL**: `/org/myorg/courses/123/lessons/456/assignments/789/submissions/101`
+
+**Result**: `Courses / React Fundamentals / Lessons / Components / Assignments / Build Todo App / Submissions / Submission #101`
+
+**Key Benefit**: When stores update (e.g., `assignmentStore.title` changes), the breadcrumb automatically updates reactively without any API calls!
+
+#### Key Points for 5-Layer Nesting:
+
+1. **Each Level Must Define Nested Routes**: For nesting to work, each level must have `nestedRoutes` defined
+2. **Dynamic Segments Need `supportsDynamicSegment: true`**: Mark dynamic segments explicitly
+3. **Store-Based Resolvers Are Reactive**: Use stores instead of async requests for automatic updates
+4. **Static Routes Are Checked First**: Static nested routes (like `lessons`, `assignments`) are matched before dynamic segments
+5. **Recursive Depth Tracking**: The function tracks depth and stops at `maxDepth` (default: 10, supports 5 layers easily)
+
+#### Store-Based vs Async Resolvers
+
+**✅ Recommended: Store-Based (Reactive)**
+```typescript
+// In breadcrumb component
+const config = $derived(createOrgBreadcrumbConfigWithStore(
+ $currentOrgPath,
+ () => $assignmentStore.title // Reads reactively from store
+));
+
+// Breadcrumbs update automatically when store changes!
+const breadcrumbs = $derived(generateBreadcrumbsSync(...));
+```
+
+**⚠️ Alternative: Async Resolvers (API Calls)**
+```typescript
+// In navigation config
+titleResolver: async (params) => {
+ const assignment = await fetchAssignment(params.param0);
+ return assignment.title;
+}
+```
+
+**Why Use Stores?**
+- ✅ **No API calls** - data already loaded in component
+- ✅ **Automatic updates** - breadcrumb updates when store changes
+- ✅ **Better performance** - no loading states or delays
+- ✅ **Reactive** - works seamlessly with Svelte's reactivity system
+
+#### Parameter Extraction for Deep Nesting
+
+The `extractParamsFromPath` function extracts all path segments as `param0`, `param1`, `param2`, etc.:
+
+```typescript
+// For URL: /courses/123/lessons/456/assignments/789
+// params = {
+// param0: '123', // courseId
+// param1: '456', // lessonId
+// param2: '789' // assignmentId
+// }
+```
+
+**Note**: When using stores, you typically don't need these params - just read from the store directly!
+
+### Step 4: Add Route-Specific Overrides
+
+```typescript
+{
+ titleKey: 'org_navigation.courses',
+ path: '/courses',
+ breadcrumbLabel: 'My Courses', // Override default title
+ breadcrumbResolver: (params) => {
+ // Custom logic for this route
+ return `Courses (${params.count || ''})`;
+ }
+}
+```
+
+### Step 5: Use Async Resolvers When Needed
+
+For routes that need async data fetching:
+
+```typescript
+import { generateBreadcrumbs } from './breadcrumb-core'; // Note: async version
+
+const breadcrumbs = await generateBreadcrumbs(
+ page.url.pathname,
+ page.url.search,
+ navItems,
+ config
+);
+```
+
+## Benefits of the Refactored System
+
+### ✅ 1. No Code Duplication
+- Single unified generator for all contexts
+- Context-specific configs handle differences
+
+### ✅ 2. Multi-Level Nesting Support
+- Recursive generation handles any depth
+- Configurable max depth prevents infinite loops
+
+### ✅ 3. Flexible Path Prefixes
+- Function-based prefix extraction
+- Supports dynamic prefixes like `/org/[slug]`
+
+### ✅ 4. Consistent Dynamic Title Resolution
+- Unified interface for all resolvers
+- Supports both sync and async resolution
+
+### ✅ 5. Route-Specific Customization
+- Override labels per route
+- Custom resolvers for complex cases
+
+### ✅ 6. Optional Caching
+- Performance optimization
+- Configurable cache TTL
+
+### ✅ 7. Type Safety
+- Full TypeScript support
+- Compile-time error checking
+
+## Example: Complex Multi-Level Route (5 Layers)
+
+**URL**: `/org/myorg/courses/123/lessons/456/assignments/789/submissions/101`
+
+**Config**:
+```typescript
+{
+ path: '/courses',
+ nestedRoutes: [
+ {
+ path: '[id]',
+ supportsDynamicSegment: true,
+ titleResolver: async (params) => {
+ const course = await getCourse(params.param0);
+ return course.title;
+ },
+ nestedRoutes: [
+ {
+ path: 'lessons',
+ titleKey: 'Lessons',
+ nestedRoutes: [
+ {
+ path: '[lessonId]',
+ supportsDynamicSegment: true,
+ titleResolver: async (params) => {
+ const lesson = await getLesson(params.param0, params.param1);
+ return lesson.title;
+ },
+ nestedRoutes: [
+ {
+ path: 'assignments',
+ titleKey: 'Assignments',
+ nestedRoutes: [
+ {
+ path: '[assignmentId]',
+ supportsDynamicSegment: true,
+ titleResolver: async (params) => {
+ const assignment = await getAssignment(
+ params.param0, // courseId
+ params.param1, // lessonId
+ params.param2 // assignmentId
+ );
+ return assignment.title;
+ },
+ nestedRoutes: [
+ {
+ path: 'submissions',
+ titleKey: 'Submissions',
+ nestedRoutes: [
+ {
+ path: '[submissionId]',
+ supportsDynamicSegment: true,
+ titleResolver: async (params) => {
+ const submission = await getSubmission(
+ params.param0, // courseId
+ params.param1, // lessonId
+ params.param2, // assignmentId
+ params.param3 // submissionId
+ );
+ return `Submission #${submission.id}`;
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+```
+
+**Result**: `Courses / React Fundamentals / Lessons / Components / Assignments / Build Todo App / Submissions / Submission #101`
+
+### How the Recursion Works for 5 Layers:
+
+1. **Initial Call** (`depth=0`): Processes `/courses` → finds nav item → adds "Courses"
+2. **First Recursion** (`depth=1`): Processes `123` → dynamic segment → resolves course title → adds "React Fundamentals"
+3. **Second Recursion** (`depth=2`): Processes `lessons` → static nested route → adds "Lessons"
+4. **Third Recursion** (`depth=3`): Processes `456` → dynamic segment → resolves lesson title → adds "Components"
+5. **Fourth Recursion** (`depth=4`): Processes `assignments` → static nested route → adds "Assignments"
+6. **Fifth Recursion** (`depth=5`): Processes `789` → dynamic segment → resolves assignment title → adds "Build Todo App"
+7. **Sixth Recursion** (`depth=6`): Processes `submissions` → static nested route → adds "Submissions"
+8. **Seventh Recursion** (`depth=7`): Processes `101` → dynamic segment → resolves submission → adds "Submission #101"
+
+**Depth Limit**: Default `maxDepth` is 10, so this 5-layer example (which uses depth 7) works perfectly.
+
+## How 5 Layers of Nesting Works
+
+### Depth Calculation
+
+The recursive function processes **each path segment** separately, incrementing depth on each recursive call. For a 5-layer breadcrumb path:
+
+**Example Path**: `/courses/123/lessons/456/assignments/789/submissions/101`
+
+**Breadcrumb Result**: `Courses / React Fundamentals / Lessons / Components / Assignments / Build Todo App / Submissions / Submission #101`
+
+**Depth Breakdown**:
+- `depth=0`: Top-level nav item (`/courses`) → "Courses"
+- `depth=1`: First dynamic segment (`123`) → "React Fundamentals"
+- `depth=2`: Static nested route (`lessons`) → "Lessons"
+- `depth=3`: Second dynamic segment (`456`) → "Components"
+- `depth=4`: Static nested route (`assignments`) → "Assignments"
+- `depth=5`: Third dynamic segment (`789`) → "Build Todo App"
+- `depth=6`: Static nested route (`submissions`) → "Submissions"
+- `depth=7`: Fourth dynamic segment (`101`) → "Submission #101"
+
+**Total Depth**: 7 levels (includes static route labels)
+
+**Default `maxDepth`**: 10, which easily supports 5 layers of actual content nesting.
+
+### Recursive Processing Flow
+
+```typescript
+generateNestedBreadcrumbsSync(segments, navItem, currentPath, ..., depth=0)
+ ├─ Process currentSegment (segments[0])
+ ├─ Check: Is it a static nested route?
+ │ ├─ YES: Add breadcrumb → Recurse with remaining segments (depth+1)
+ │ └─ NO: Continue to dynamic check
+ ├─ Check: Is it a dynamic segment?
+ │ ├─ YES: Resolve title → Add breadcrumb → Recurse with remaining segments (depth+1)
+ │ └─ NO: Return (no more breadcrumbs)
+ └─ Stop if: depth >= maxDepth OR segments.length === 0
+```
+
+### Key Implementation Details
+
+1. **Segment-by-Segment Processing**: Each recursive call processes ONE segment and passes remaining segments to the next call
+2. **Static Routes First**: Static nested routes (like `lessons`, `assignments`) are checked before dynamic segments
+3. **Nested Route Matching**: When a static route is found, its `nestedRoutes` become the `navItem.nestedRoutes` for the next recursion
+4. **Dynamic Segment Handling**: When a dynamic segment is found, it resolves the title and continues with remaining segments if `nestedRoutes` exist
+5. **Depth Safety**: The `maxDepth` check prevents infinite recursion (default: 10, configurable)
+
+### Why This Supports 5 Layers
+
+- **Default `maxDepth` of 10** provides plenty of headroom for 5 layers
+- **Recursive structure** naturally handles any depth
+- **Each layer increments depth by 1**, so 5 layers = depth 5-7 (depending on static route labels)
+- **Configurable `maxDepth`** allows adjustment if needed
+
+## Performance Considerations
+
+1. **Caching**: Enable for routes with expensive resolvers
+2. **Async Resolvers**: Use sparingly - prefer sync resolvers when possible
+3. **Max Depth**: Default is 10 (supports 5 layers easily), but can be customized:
+ ```svelte
+
+ ```
+4. **Cache Invalidation**: Clear cache when navigation config changes
+5. **Recursive Calls**: Each nested level adds one recursive call - 5 layers = ~7-10 calls total (very efficient)
+
+## Testing
+
+```typescript
+import { generateBreadcrumbsSync, clearBreadcrumbCache } from './breadcrumb-core';
+
+// Clear cache before tests
+beforeEach(() => {
+ clearBreadcrumbCache();
+});
+
+// Test breadcrumb generation
+test('generates breadcrumbs for nested route', () => {
+ const breadcrumbs = generateBreadcrumbsSync(
+ '/org/test/courses/123/lessons/456',
+ '',
+ navItems,
+ config
+ );
+
+ expect(breadcrumbs).toHaveLength(4);
+ expect(breadcrumbs[0].label).toBe('Courses');
+});
+```
+
+## Backward Compatibility
+
+The old functions (`generateBreadcrumbs`, `generateLmsBreadcrumbs`) can be kept as wrappers:
+
+```typescript
+// breadcrumb.ts (legacy wrapper - using store-based approach)
+import { generateBreadcrumbsSync } from './breadcrumb-core';
+import { createOrgBreadcrumbConfigWithStore } from './breadcrumb-configs';
+import { courseStore } from '$features/course/utils/store';
+
+export function generateBreadcrumbs(
+ pathname: string,
+ searchParams: string,
+ navItems: NavItem[],
+ currentOrgPath: string,
+ getStoreValue?: () => string | null | undefined
+) {
+ const config = createOrgBreadcrumbConfigWithStore(
+ currentOrgPath,
+ getStoreValue || (() => $courseStore.title || null)
+ );
+ return generateBreadcrumbsSync(pathname, searchParams, navItems, config);
+}
+```
+
+This allows gradual migration without breaking existing code.
+
+## Reactive Store-Based Title Resolution
+
+### Why Use Stores Instead of Async Requests?
+
+**✅ Benefits:**
+- **No API calls** - Data is already loaded in your component
+- **Automatic updates** - Breadcrumbs update reactively when stores change
+- **Better performance** - No loading states, delays, or network requests
+- **Simpler code** - Just read from stores, no async/await needed
+- **Reactive by default** - Works seamlessly with Svelte's reactivity system
+
+### How It Works
+
+1. **Store Setup**: Your component/page loads data into a store
+2. **Reactive Config**: Create breadcrumb config with a function that reads from stores
+3. **Automatic Updates**: When store changes, breadcrumb automatically updates
+
+### Example: Community Post Title
+
+**Store** (`$features/community/utils/store.ts`):
+```typescript
+export const currentCommunityQuestion = writable<{ title: string | null }>({
+ title: null
+});
+```
+
+**Page Component** (`+page.svelte`):
+```svelte
+
+```
+
+**Breadcrumb Component** (`lms-breadcrumbs.svelte`):
+```svelte
+
+```
+
+### Pattern for Multiple Stores
+
+For routes with multiple dynamic segments, use path-based logic:
+
+```svelte
+
+```
+
+### Important Notes
+
+1. **Disable Caching**: When using stores, set `enableCache: false` in config (or use `createOrgBreadcrumbConfigWithStore` which does this automatically)
+2. **Use `$derived`**: Always wrap config and breadcrumbs in `$derived` for reactivity
+3. **Store Updates**: When you update a store, breadcrumbs automatically update - no manual refresh needed!
+4. **No Async Needed**: Store-based resolvers are synchronous - just return the store value
+
diff --git a/apps/dashboard/ai/PAGE_ROUTE_GUIDE.md b/apps/dashboard/ai/PAGE_ROUTE_GUIDE.md
new file mode 100644
index 00000000..b298ede5
--- /dev/null
+++ b/apps/dashboard/ai/PAGE_ROUTE_GUIDE.md
@@ -0,0 +1,846 @@
+# Page and Route Creation Guide
+
+This guide explains how to create pages and routes in the dashboard application, following the established patterns and architecture.
+
+## Table of Contents
+
+1. [Route Structure Overview](#route-structure-overview)
+2. [Creating Basic Pages](#creating-basic-pages)
+3. [Creating Settings Pages](#creating-settings-pages)
+4. [Creating Feature Pages](#creating-feature-pages)
+5. [Layout Patterns](#layout-patterns)
+6. [Form Patterns (Field.Group)](#form-patterns-fieldgroup)
+7. [Navigation Setup](#navigation-setup)
+8. [Breadcrumb Setup](#breadcrumb-setup)
+
+---
+
+## Route Structure Overview
+
+The dashboard has two main route contexts:
+
+### 1. Organization Routes (`/org/[slug]/...`)
+- Uses `OrgSidebar` and `AppHeader`
+- Path pattern: `/org/[slug]/[feature]`
+- Example: `/org/acme/courses`, `/org/acme/settings`
+
+### 2. LMS Routes (`/lms/...`)
+- Uses `LMSSidebar` and `LmsHeader`
+- Path pattern: `/lms/[feature]`
+- Example: `/lms/community`, `/lms/settings`
+
+---
+
+## Creating Basic Pages
+
+### Step 1: Create the Route File
+
+**Location**: `apps/dashboard/src/routes/[context]/[feature]/+page.svelte`
+
+**Pattern**:
+```svelte
+
+
+
+ Feature Name - ClassroomIO
+
+
+
+
+
+ {$t('feature.title')}
+
+
+
+
+
+
+ {#snippet child()}
+
+ {/snippet}
+
+
+```
+
+### Step 2: Create the Feature Page Component
+
+**Location**: `apps/dashboard/src/lib/features/[feature]/pages/[feature].svelte`
+
+**Pattern**:
+```svelte
+
+
+
+
+
+
+
+
+
+
+ {#if isLoading}
+
+ {:else if !data.length}
+
+
+
+ {:else}
+ {#each data as item}
+
+ {/each}
+ {/if}
+
+```
+
+### Step 3: Export the Page
+
+**Location**: `apps/dashboard/src/lib/features/[feature]/pages/index.ts`
+
+```typescript
+export { default as FeaturePage } from './feature.svelte';
+```
+
+---
+
+## Creating Settings Pages
+
+Settings pages follow a specific pattern with layouts and subroutes.
+
+### Step 1: Create Settings Layout
+
+**Location**: `apps/dashboard/src/routes/[context]/settings/+layout.svelte`
+
+```svelte
+
+
+
+ {@render children?.()}
+
+```
+
+### Step 2: Create Default Settings Page
+
+**Location**: `apps/dashboard/src/routes/[context]/settings/+page.svelte`
+
+```svelte
+
+
+
+ Settings - ClassroomIO
+
+
+
+
+ {$t('settings.heading')}
+
+
+
+
+
+
+ {#snippet child()}
+
+ {/snippet}
+
+```
+
+### Step 3: Create Settings Subroute
+
+**Location**: `apps/dashboard/src/routes/[context]/settings/[subroute]/+page.svelte`
+
+```svelte
+
+
+
+ Subroute Name - ClassroomIO
+
+
+
+
+ {$t('settings.subroute.heading')}
+
+
+
+ {#snippet child()}
+
+ {/snippet}
+
+```
+
+**Note**: Settings pages use subroutes instead of tabs. Each subroute has its own `+page.svelte` file.
+
+---
+
+## Creating Feature Pages
+
+### Feature Page Structure
+
+Feature pages contain all business logic and should be placed in `lib/features/[feature]/pages/`.
+
+**Key Principles**:
+- ✅ All business logic goes in the feature page
+- ✅ Use `Page.BodyHeader` for filters, search, and view toggles
+- ✅ Use `Empty` component for empty states
+- ✅ Use `Search` component for search inputs
+- ✅ Handle loading states with skeleton/loader components
+- ❌ Don't use `Page.Header` or `Page.Action` in feature pages (these are in route files)
+
+### Example Feature Page
+
+```svelte
+
+
+
+
+
+
+
+
+
+ All
+
+
+
+
+
+
+ {#if isLoading}
+
+
+
+ {:else if !discussions.length}
+
+
+
+ {:else}
+ {#each discussions as discussion}
+
+ {/each}
+ {/if}
+
+```
+
+---
+
+## Layout Patterns
+
+### Organization Layout
+
+**Location**: `apps/dashboard/src/routes/org/[slug]/+layout.svelte`
+
+```svelte
+
+
+
+
+
+
+
+
+
+ {@render children?.()}
+
+
+
+```
+
+### LMS Layout
+
+**Location**: `apps/dashboard/src/routes/lms/+layout.svelte`
+
+```svelte
+
+
+
+
+
+
+
+
+
+ {@render children?.()}
+
+
+
+```
+
+**Key Differences**:
+- Org routes use `AppHeader` (includes AppSetup)
+- LMS routes use `LmsHeader` (no AppSetup)
+- Org routes use `OrgSidebar`
+- LMS routes use `LMSSidebar`
+
+---
+
+## Form Patterns (Field.Group)
+
+**IMPORTANT**: Always use `Field.Group` pattern for forms. The old `Row/Grid/Column` pattern is deprecated and removed.
+
+### Field.Group Pattern
+
+**Location**: `apps/dashboard/src/lib/features/[feature]/pages/[feature].svelte`
+
+```svelte
+
+
+
+
+
+
+ {$t('form.section_title')}
+
+
+ {$t('form.name')}
+ (hasUnsavedChanges = true)} />
+
+
+ {$t('form.email')}
+ (hasUnsavedChanges = true)} />
+
+
+ (hasUnsavedChanges = true)} />
+ {$t('form.enabled')}
+
+
+
+
+
+
+
+ {$t('form.another_section')}
+
+ (hasUnsavedChanges = true)}
+ />
+
+
+
+```
+
+### Field Components
+
+- `` - Main container (use `class="max-w-md! w-full px-2"`)
+- `` - Section container
+- `` - Section title
+- `` - Individual field wrapper
+- `` - Field label
+- `` - Field description/help text
+- `` - Field error message
+- `` - Visual separator between sections
+- `orientation="horizontal"` - For switches/checkboxes with labels side-by-side
+
+### Pattern Rules
+
+- ✅ DO: Use `Field.Group` as the main container
+- ✅ DO: Use `Field.Set` for logical sections
+- ✅ DO: Use `Field.Separator` between sections
+- ✅ DO: Use `Field.Legend` for section titles
+- ✅ DO: Wrap form inputs in `Field.Field`
+- ✅ DO: Use `Field.Label` for input labels
+- ✅ DO: Use `orientation="horizontal"` for switches/checkboxes
+- ❌ DON'T: Use `Row`, `Grid`, or `Column` components (deprecated)
+- ❌ DON'T: Mix Field.Group with old layout components
+
+---
+
+## Navigation Setup
+
+### Adding Items to Organization Navigation
+
+**Location**: `apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts`
+
+```typescript
+const baseNavConfig: NavItemConfig[] = [
+ // ... existing items
+ {
+ titleKey: 'org_navigation.new_feature',
+ path: '/new-feature',
+ icon: NewFeatureIcon,
+ matchPattern: '^/org/[^/]+/new-feature(/.*)?$',
+ // Optional: requires admin access
+ requiresAdmin: true,
+ // Optional: nested routes
+ nestedRoutes: [
+ {
+ path: 'subroute',
+ titleKey: 'Subroute Name'
+ }
+ ],
+ // Optional: supports dynamic segments
+ supportsDynamicSegment: true
+ }
+];
+```
+
+### Adding Items to LMS Navigation
+
+**Location**: `apps/dashboard/src/lib/features/ui/navigation/lms-navigation.ts`
+
+```typescript
+const baseNavConfig: NavItemConfig[] = [
+ // ... existing items
+ {
+ titleKey: 'lms_navigation.new_feature',
+ path: '/new-feature',
+ icon: NewFeatureIcon,
+ matchPattern: '^/lms/new-feature(/.*)?$',
+ // Optional: conditional display
+ show: (currentOrg) => currentOrg?.customization?.feature?.enabled === true,
+ // Optional: nested routes
+ nestedRoutes: [
+ {
+ path: 'subroute',
+ titleKey: 'Subroute Name'
+ }
+ ],
+ // Optional: supports dynamic segments
+ supportsDynamicSegment: true
+ }
+];
+```
+
+### Navigation Item Properties
+
+- `titleKey` - Translation key for the navigation label
+- `path` - Route path (relative to context)
+- `icon` - Lucide icon component
+- `matchPattern` - Regex pattern for active state matching
+- `requiresAdmin` - Only show for admin users (org navigation only)
+- `show` - Function to conditionally show/hide item
+- `nestedRoutes` - Static nested routes (like `community/ask`)
+- `supportsDynamicSegment` - Supports dynamic routes (like `community/[slug]`)
+- `useHashUrl` - Use '#' for collapsible parent (settings menu)
+- `items` - Nested items for collapsible menus (settings sub-items)
+
+---
+
+## Breadcrumb Setup
+
+Breadcrumbs are automatically generated from navigation configuration.
+
+### For Organization Routes
+
+Breadcrumbs use `generateBreadcrumbs()` from `breadcrumb.ts` which reads from `org-navigation.ts`.
+
+### For LMS Routes
+
+Breadcrumbs use `generateLmsBreadcrumbs()` from `lms-breadcrumb.ts` which reads from `lms-navigation.ts`.
+
+### Adding Dynamic Breadcrumbs
+
+For dynamic routes (like `community/[slug]`), add breadcrumb data in the route's `+page.ts`:
+
+```typescript
+// routes/org/[slug]/community/[slug]/+page.ts
+export async function load({ params }) {
+ // Fetch question data
+ const question = await fetchQuestion(params.slug);
+
+ return {
+ breadcrumb: question.title // This will appear in breadcrumbs
+ };
+}
+```
+
+---
+
+## Complete Examples
+
+### Example 1: Simple Feature Page
+
+**Route**: `routes/org/[slug]/courses/+page.svelte`
+```svelte
+
+
+
+ Courses - ClassroomIO
+
+
+
+
+
+ {$t('courses.title')}
+
+
+
+ {#snippet child()}
+
+ {/snippet}
+
+
+```
+
+### Example 2: Settings Page with Save Action
+
+**Route**: `routes/org/[slug]/settings/customize-lms/+page.svelte`
+```svelte
+
+
+
+ Customize LMS - ClassroomIO
+
+
+
+
+
+ {$t('components.settings.customize_lms.title')}
+
+
+
+
+
+
+ {#snippet child()}
+
+ {/snippet}
+
+
+```
+
+### Example 3: Form Page Component
+
+**Feature Page**: `lib/features/settings/pages/profile.svelte`
+```svelte
+
+
+
+
+
+
+ {$t('settings.profile.profile_picture.heading')}
+
+ (hasUnsavedChanges = true)}
+ />
+
+
+
+
+
+
+ {$t('settings.profile.personal_information.heading')}
+
+
+ {$t('settings.profile.personal_information.full_name')}
+ (hasUnsavedChanges = true)} />
+ {#if profileApi.errors.fullname}
+ {$t(profileApi.errors.fullname)}
+ {/if}
+
+
+
+
+
+```
+
+---
+
+## Checklist for New Pages
+
+When creating a new page, follow this checklist:
+
+### Route Setup
+- [ ] Create route file at `routes/[context]/[feature]/+page.svelte`
+- [ ] Add `Page.Root`, `Page.Header`, `Page.Body` structure
+- [ ] Add `` with page title
+- [ ] Import and render feature page component
+
+### Feature Page
+- [ ] Create feature page at `lib/features/[feature]/pages/[feature].svelte`
+- [ ] Export from `lib/features/[feature]/pages/index.ts`
+- [ ] Use `Page.BodyHeader` for filters/search
+- [ ] Use `Empty` component for empty states
+- [ ] Handle loading states
+
+### Forms (if applicable)
+- [ ] Use `Field.Group` pattern (NOT Row/Grid/Column)
+- [ ] Use `Field.Set` for sections
+- [ ] Use `Field.Separator` between sections
+- [ ] Use `Field.Legend` for section titles
+- [ ] Wrap inputs in `Field.Field`
+- [ ] Add `UnsavedChanges` component if needed
+
+### Navigation (if needed)
+- [ ] Add item to `org-navigation.ts` or `lms-navigation.ts`
+- [ ] Set appropriate `matchPattern` for active state
+- [ ] Add icon component
+- [ ] Configure nested routes if needed
+- [ ] Configure dynamic segments if needed
+
+### Settings Pages (if applicable)
+- [ ] Create `+layout.svelte` with `Page.Root` wrapper
+- [ ] Create default `+page.svelte` for main settings
+- [ ] Create subroute `+page.svelte` files for each subroute
+- [ ] Export page components from `$features/settings/pages`
+
+---
+
+## Common Patterns
+
+### Page Width Classes
+
+- `class="w-full"` - Full width
+- `class="w-full md:max-w-3xl lg:mx-auto"` - Centered, max width on larger screens (settings pages)
+- `class="w-full md:max-w-4xl lg:mx-auto"` - Slightly wider (customize-lms)
+- `class="mx-auto w-full max-w-6xl"` - Wide content area (main layouts)
+
+### Page Actions
+
+Actions in `Page.Action` typically include:
+- Save/Update buttons
+- Create/Add buttons
+- Export buttons
+- Feature-specific action buttons
+
+### Loading States
+
+Use skeleton loaders or spinner components:
+```svelte
+{#if isLoading}
+
+
+
+{:else}
+
+{/if}
+```
+
+### Empty States
+
+Always use the `Empty` component:
+```svelte
+
+
+
+```
+
+---
+
+## Best Practices
+
+1. **Separation of Concerns**: Route files handle routing structure, feature pages handle business logic
+2. **Reusability**: Feature pages can be imported and used in different contexts
+3. **Consistency**: All pages follow the same structure for maintainability
+4. **Design System**: Use established components (`Page.*`, `Field.*`, `Empty`, `Search`)
+5. **State Management**: Keep state local to feature pages or use feature-specific stores
+6. **Translation Keys**: Always use translation keys from `$t()` function
+7. **Type Safety**: Use TypeScript types for props and state
+8. **Accessibility**: Use semantic HTML and proper ARIA labels
+
+---
+
+## Migration from Old Patterns
+
+If you encounter old patterns, migrate them:
+
+### Old: Row/Grid/Column Pattern
+```svelte
+
+
+
+
+ Title
+
+
+
+
+
+
+```
+
+### New: Field.Group Pattern
+```svelte
+
+
+
+ Title
+
+ Label
+
+
+
+
+```
+
+---
+
+## Additional Resources
+
+- See `ROUTE_PAGE_SETUP.md` for more details on feature page structure
+- See existing pages in `routes/org/[slug]/` and `routes/lms/` for examples
+- See `lib/features/settings/pages/` for form examples
+- See `lib/features/community/pages/` for list page examples
+
diff --git a/apps/dashboard/ROUTE_PAGE_SETUP.md b/apps/dashboard/ai/ROUTE_PAGE_SETUP.md
similarity index 95%
rename from apps/dashboard/ROUTE_PAGE_SETUP.md
rename to apps/dashboard/ai/ROUTE_PAGE_SETUP.md
index ef808659..8baeb99b 100644
--- a/apps/dashboard/ROUTE_PAGE_SETUP.md
+++ b/apps/dashboard/ai/ROUTE_PAGE_SETUP.md
@@ -45,8 +45,8 @@ The route file should be minimal and only contain:
```svelte
@@ -75,8 +75,8 @@ The route file should be minimal and only contain:
### Route File Rules
- ✅ DO: Keep it minimal - only Page structure
-- ✅ DO: Import feature page from `$lib/features/[feature]/pages`
-- ✅ DO: Import action buttons/components from `$lib/features/[feature]/components`
+- ✅ DO: Import feature page from `$features/[feature]/pages`
+- ✅ DO: Import action buttons/components from `$features/[feature]/components`
- ✅ DO: Use `Page.Root`, `Page.Header`, `Page.HeaderContent`, `Page.Title`, `Page.Action`, `Page.Body`
- ❌ DON'T: Put business logic in route files
- ❌ DON'T: Put state management in route files
@@ -110,7 +110,6 @@ The feature page contains all the business logic, state management, and UI compo
let isLoading = $state(false);
let searchValue = $state('');
let selectedId = $state('');
-
@@ -194,8 +193,8 @@ export { default as FeaturePage } from './feature.svelte';
```svelte
diff --git a/apps/dashboard/src/app.css b/apps/dashboard/src/app.css
index 2ae336a6..8f9b57bf 100644
--- a/apps/dashboard/src/app.css
+++ b/apps/dashboard/src/app.css
@@ -99,24 +99,14 @@ code {
color: var(--main-primary-color);
}
-svg.lucide-icon:not(.custom) {
+:not([data-slot='button']) > svg.lucide-icon:not(.custom) {
color: #666666;
}
-.dark svg.lucide-icon:not(.custom) {
+.dark :not([data-slot='button']) > svg.lucide-icon:not(.custom) {
color: #fff;
}
-.org-root {
- height: calc(100vh - 48px);
-}
-
-.org-slot {
- min-width: calc(100vw - 250px);
- height: calc(100vh - 48px);
- overflow-y: auto;
-}
-
.shake {
animation: shake 0.85s;
}
@@ -168,980 +158,382 @@ svg.lucide-icon:not(.custom) {
}
}
-/**
- Rose Theme
-*/
-.theme-rose .bg-primary-50 {
- background-color: #fff1f2 !important;
-}
-
-.theme-rose .text-primary-50 {
- color: #fff1f2 !important;
-}
-
-.theme-rose .border-primary-50 {
- border-color: #fff1f2 !important;
-}
-
-.theme-rose .bg-primary-100 {
- background-color: #ffe4e7 !important;
-}
-
-.theme-rose .text-primary-100 {
- color: #ffe4e7 !important;
-}
-
-.theme-rose .border-primary-100 {
- border-color: #ffe4e7 !important;
-}
-
-.theme-rose .bg-primary-200 {
- background-color: #fecdd4 !important;
-}
-
-.theme-rose .text-primary-200 {
- color: #fecdd4 !important;
-}
-
-.theme-rose .border-primary-200 {
- border-color: #fecdd4 !important;
-}
-
-.theme-rose .bg-primary-300 {
- background-color: #fda4b2 !important;
-}
-
-.theme-rose .text-primary-300 {
- color: #fda4b2 !important;
-}
-
-.theme-rose .border-primary-300 {
- border-color: #fda4b2 !important;
-}
-
-.theme-rose .bg-primary-400 {
- background-color: #fb7189 !important;
-}
-
-.theme-rose .text-primary-400 {
- color: #fb7189 !important;
-}
-
-.theme-rose .border-primary-400 {
- border-color: #fb7189 !important;
-}
-
-.theme-rose .bg-primary-500 {
- background-color: #f43f63 !important;
-}
-
-.theme-rose .text-primary-500 {
- color: #f43f63 !important;
-}
-
-.theme-rose .border-primary-500 {
- border-color: #f43f63 !important;
-}
-
-.theme-rose .bg-primary-600 {
- background-color: #e21d4e !important;
-}
-
-.theme-rose .text-primary-600 {
- color: #e21d4e !important;
-}
-
-.theme-rose .border-primary-600 {
- border-color: #e21d4e !important;
-}
-
-.theme-rose .bg-primary-700 {
- background-color: #be1241 !important;
-}
-
-.theme-rose .text-primary-700 {
- color: #be1241 !important;
-}
-
-.theme-rose .border-primary-700 {
- border-color: #be1241 !important;
-}
-
-.theme-rose .bg-primary-800 {
- background-color: #9f123d !important;
-}
-
-.theme-rose .text-primary-800 {
- color: #9f123d !important;
-}
-
-.theme-rose .border-primary-800 {
- border-color: #9f123d !important;
-}
-
-.theme-rose .bg-primary-900 {
- background-color: #88133a !important;
-}
-
-.theme-rose .text-primary-900 {
- color: #88133a !important;
-}
-
-.theme-rose .border-primary-900 {
- border-color: #88133a !important;
-}
-
-/**
- Blue Theme
-*/
-.bg-primary-50 {
- background-color: #eff6ff !important;
-}
-
-.text-primary-50 {
- color: #eff6ff !important;
-}
-
-.border-primary-50 {
- border-color: #eff6ff !important;
-}
-
-.bg-primary-100 {
- background-color: #dbeafe !important;
-}
-
-.text-primary-100 {
- color: #dbeafe !important;
-}
-
-.border-primary-100 {
- border-color: #dbeafe !important;
-}
-
-.bg-primary-200 {
- background-color: #bfdafe !important;
-}
-
-.text-primary-200 {
- color: #bfdafe !important;
-}
-
-.border-primary-200 {
- border-color: #bfdafe !important;
-}
-
-.bg-primary-300 {
- background-color: #92c4fe !important;
-}
-
-.text-primary-300 {
- color: #92c4fe !important;
-}
-
-.border-primary-300 {
- border-color: #92c4fe !important;
-}
-
-.bg-primary-400 {
- background-color: #5fa3fb !important;
-}
-
-.text-primary-400 {
- color: #5fa3fb !important;
-}
-
-.border-primary-400 {
- border-color: #5fa3fb !important;
-}
-
-.bg-primary-500 {
- background-color: #3a7ff7 !important;
-}
-
-.text-primary-500 {
- color: #3a7ff7 !important;
-}
-
-.border-primary-500 {
- border-color: #3a7ff7 !important;
-}
-
-.bg-primary-600 {
- background-color: #2460ec !important;
-}
-
-.text-primary-600 {
- color: #2460ec !important;
-}
-
-.border-primary-600 {
- border-color: #2460ec !important;
-}
-
-.bg-primary-700 {
- background-color: #1d4ee2 !important;
-}
-
-.text-primary-700 {
- color: #1d4ee2 !important;
-}
-
-.border-primary-700 {
- border-color: #1d4ee2 !important;
-}
-
-.bg-primary-800 {
- background-color: #1d3eb0 !important;
-}
-
-.text-primary-800 {
- color: #1d3eb0 !important;
-}
-
-.border-primary-800 {
- border-color: #1d3eb0 !important;
-}
-
-.bg-primary-900 {
- background-color: #1d398b !important;
-}
-
-.text-primary-900 {
- color: #1d398b !important;
-}
-
-.border-primary-900 {
- border-color: #1d398b !important;
-}
-
-/**
- Green Theme
-*/
-.theme-green .bg-primary-50 {
- background-color: #effef0 !important;
-}
-
-.theme-green .text-primary-50 {
- color: #effef0 !important;
-}
-
-.theme-green .border-primary-50 {
- border-color: #effef0 !important;
-}
-
-.theme-green .bg-primary-100 {
- background-color: #d9ffdc !important;
-}
-
-.theme-green .text-primary-100 {
- color: #d9ffdc !important;
-}
-
-.theme-green .border-primary-100 {
- border-color: #d9ffdc !important;
-}
-
-.theme-green .bg-primary-200 {
- background-color: #b6fcbc !important;
-}
-
-.theme-green .text-primary-200 {
- color: #b6fcbc !important;
-}
-
-.theme-green .border-primary-200 {
- border-color: #b6fcbc !important;
-}
-
-.theme-green .bg-primary-300 {
- background-color: #7df88a !important;
-}
-
-.theme-green .text-primary-300 {
- color: #7df88a !important;
-}
-
-.theme-green .border-primary-300 {
- border-color: #7df88a !important;
-}
-
-.theme-green .bg-primary-400 {
- background-color: #3deb51 !important;
-}
-
-.theme-green .text-primary-400 {
- color: #3deb51 !important;
-}
-
-.theme-green .border-primary-400 {
- border-color: #3deb51 !important;
-}
-
-.theme-green .bg-primary-500 {
- background-color: #12bf26 !important;
-}
-
-.theme-green .text-primary-500 {
- color: #12bf26 !important;
-}
-
-.theme-green .border-primary-500 {
- border-color: #12bf26 !important;
-}
-
-.theme-green .bg-primary-600 {
- background-color: #0aaf1d !important;
-}
-
-.theme-green .text-primary-600 {
- color: #0aaf1d !important;
-}
-
-.theme-green .border-primary-600 {
- border-color: #0aaf1d !important;
-}
-
-.theme-green .bg-primary-700 {
- background-color: #0c891b !important;
-}
-
-.theme-green .text-primary-700 {
- color: #0c891b !important;
-}
-
-.theme-green .border-primary-700 {
- border-color: #0c891b !important;
-}
-
-.theme-green .bg-primary-800 {
- background-color: #0f6c1b !important;
-}
-
-.theme-green .text-primary-800 {
- color: #0f6c1b !important;
-}
-
-.theme-green .border-primary-800 {
- border-color: #0f6c1b !important;
-}
-
-.theme-green .bg-primary-900 {
- background-color: #0f581a !important;
-}
-
-.theme-green .text-primary-900 {
- color: #0f581a !important;
-}
-
-.theme-green .border-primary-900 {
- border-color: #0f581a !important;
-}
-
-/**
- Orange Theme
-*/
-.theme-orange .bg-primary-50 {
- background-color: #fff8ec !important;
-}
-
-.theme-orange .text-primary-50 {
- color: #fff8ec !important;
-}
-
-.theme-orange .border-primary-50 {
- border-color: #fff8ec !important;
-}
-
-.theme-orange .bg-primary-100 {
- background-color: #fff0d3 !important;
-}
-
-.theme-orange .text-primary-100 {
- color: #fff0d3 !important;
-}
-
-.theme-orange .border-primary-100 {
- border-color: #fff0d3 !important;
-}
-
-.theme-orange .bg-primary-200 {
- background-color: #ffdca5 !important;
-}
-
-.theme-orange .text-primary-200 {
- color: #ffdca5 !important;
-}
-
-.theme-orange .border-primary-200 {
- border-color: #ffdca5 !important;
-}
-
-.theme-orange .bg-primary-300 {
- background-color: #ffc26d !important;
-}
-
-.theme-orange .text-primary-300 {
- color: #ffc26d !important;
-}
-
-.theme-orange .border-primary-300 {
- border-color: #ffc26d !important;
-}
-
-.theme-orange .bg-primary-400 {
- background-color: #ff9d32 !important;
-}
-
-.theme-orange .text-primary-400 {
- color: #ff9d32 !important;
-}
-
-.theme-orange .border-primary-400 {
- border-color: #ff9d32 !important;
-}
-
-.theme-orange .bg-primary-500 {
- background-color: #ff7f0a !important;
-}
-
-.theme-orange .text-primary-500 {
- color: #ff7f0a !important;
-}
-
-.theme-orange .border-primary-500 {
- border-color: #ff7f0a !important;
-}
-
-.theme-orange .bg-primary-600 {
- background-color: #ff6600 !important;
-}
-
-.theme-orange .text-primary-600 {
- color: #ff6600 !important;
-}
-
-.theme-orange .border-primary-600 {
- border-color: #ff6600 !important;
-}
-
-.theme-orange .bg-primary-700 {
- background-color: #cc4902 !important;
-}
-
-.theme-orange .text-primary-700 {
- color: #cc4902 !important;
-}
-
-.theme-orange .border-primary-700 {
- border-color: #cc4902 !important;
-}
-
-.theme-orange .bg-primary-800 {
- background-color: #a1390b !important;
-}
-
-.theme-orange .text-primary-800 {
- color: #a1390b !important;
-}
-
-.theme-orange .border-primary-800 {
- border-color: #a1390b !important;
-}
-
-.theme-orange .bg-primary-900 {
- background-color: #82310c !important;
-}
-
-.theme-orange .text-primary-900 {
- color: #82310c !important;
-}
-
-.theme-orange .border-primary-900 {
- border-color: #82310c !important;
-}
-
-/**
- Violet Theme
-*/
-.theme-violet .bg-primary-50 {
- background-color: #fff2fe !important;
-}
-
-.theme-violet .text-primary-50 {
- color: #fff2fe !important;
-}
-
-.theme-violet .border-primary-50 {
- border-color: #fff2fe !important;
-}
-
-.theme-violet .bg-primary-100 {
- background-color: #ffe3fe !important;
-}
-
-.theme-violet .text-primary-100 {
- color: #ffe3fe !important;
-}
-
-.theme-violet .border-primary-100 {
- border-color: #ffe3fe !important;
-}
-
-.theme-violet .bg-primary-200 {
- background-color: #ffc6fe !important;
-}
-
-.theme-violet .text-primary-200 {
- color: #ffc6fe !important;
-}
-
-.theme-violet .border-primary-200 {
- border-color: #ffc6fe !important;
-}
-
-.theme-violet .bg-primary-300 {
- background-color: #ff99f7 !important;
-}
-
-.theme-violet .text-primary-300 {
- color: #ff99f7 !important;
-}
-
-.theme-violet .border-primary-300 {
- border-color: #ff99f7 !important;
-}
-
-.theme-violet .bg-primary-400 {
- background-color: #ff5df2 !important;
-}
-
-.theme-violet .text-primary-400 {
- color: #ff5df2 !important;
-}
-
-.theme-violet .border-primary-400 {
- border-color: #ff5df2 !important;
-}
-
-.theme-violet .bg-primary-500 {
- background-color: #ff21f5 !important;
-}
-
-.theme-violet .text-primary-500 {
- color: #ff21f5 !important;
-}
-
-.theme-violet .border-primary-500 {
- border-color: #ff21f5 !important;
-}
-
-.theme-violet .bg-primary-600 {
- background-color: #fb00ff !important;
-}
-
-.theme-violet .text-primary-600 {
- color: #fb00ff !important;
-}
-
-.theme-violet .border-primary-600 {
- border-color: #fb00ff !important;
-}
-
-.theme-violet .bg-primary-700 {
- background-color: #cf00ce !important;
-}
-
-.theme-violet .text-primary-700 {
- color: #cf00ce !important;
-}
-
-.theme-violet .border-primary-700 {
- border-color: #cf00ce !important;
-}
-
-.theme-violet .bg-primary-800 {
- background-color: #a900a6 !important;
-}
-
-.theme-violet .text-primary-800 {
- color: #a900a6 !important;
-}
-
-.theme-violet .border-primary-800 {
- border-color: #a900a6 !important;
-}
-
-.theme-violet .bg-primary-900 {
- background-color: #890684 !important;
-}
-
-.theme-violet .text-primary-900 {
- color: #890684 !important;
-}
-
-.theme-violet .border-primary-900 {
- border-color: #890684 !important;
-}
-
-/* Tailwind v4 Custom Animations and Keyframes */
-@theme {
- --animate-NEWS_FEED: shakeAnnouncement 0.5s ease-in-out;
- --animate-SECTION: flipBook 0.6s ease-in-out;
- --animate-LESSONS: scrollUnroll 0.8s ease-out;
- --animate-ATTENDANCE: attendanceCount 0.8s ease-in-out;
- --animate-SUBMISSIONS: taskComplete 0.6s ease-in-out;
- --animate-MARKS: gradeGlow 1s ease-in-out;
- --animate-PEOPLE: peopleWave 0.8s ease-in-out;
- --animate-ANALYTICS: dataScan 1s ease-in-out;
- --animate-LANDING_PAGE: screenGlow 1s ease-in-out;
- --animate-library-expand: libraryExpand 0.7s ease-out;
- --animate-template-morph: templateMorph 0.6s ease-in-out;
- --animate-ripple: communityRipple 0.9s ease-out;
- --animate-roll: quizRoll 0.8s ease-in-out;
- --animate-gather: audienceGather 0.7s ease-in-out;
- --animate-setup-configure: setupConfigure 1s ease-in-out;
- --animate-border: border 4s linear infinite;
-}
-
-@keyframes shakeAnnouncement {
- 0%,
- 100% {
- transform: translateX(0);
- }
-
- 10%,
- 30%,
- 50%,
- 70%,
- 90% {
- transform: translateX(-2px);
- }
-
- 20%,
- 40%,
- 60%,
- 80% {
- transform: translateX(2px);
- }
-}
-
-@keyframes flipBook {
- 0% {
- transform: rotateY(0deg);
- }
-
- 50% {
- transform: rotateY(90deg);
- }
-
- 100% {
- transform: rotateY(0deg);
- }
-}
-
-@keyframes scrollUnroll {
- 0% {
- transform: scaleY(0) translateY(-50%);
- opacity: 0.5;
- }
-
- 50% {
- transform: scaleY(0.8) translateY(-25%);
- opacity: 0.8;
- }
-
- 100% {
- transform: scaleY(1) translateY(0);
- opacity: 1;
- }
-}
-
-@keyframes attendanceCount {
- 0% {
- transform: scale(1);
- opacity: 0.8;
- }
-
- 20% {
- transform: scale(1.1);
- opacity: 1;
- }
-
- 40% {
- transform: scale(1.05);
- opacity: 0.9;
- }
-
- 60% {
- transform: scale(1.15);
- opacity: 1;
- }
-
- 80% {
- transform: scale(1.02);
- opacity: 0.95;
- }
-
- 100% {
- transform: scale(1);
- opacity: 1;
- }
-}
-
-@keyframes taskComplete {
- 0% {
- transform: scale(1) rotate(0deg);
- }
-
- 25% {
- transform: scale(1.1) rotate(-5deg);
- }
-
- 50% {
- transform: scale(1.2) rotate(5deg);
- }
-
- 75% {
- transform: scale(1.1) rotate(-2deg);
- }
-
- 100% {
- transform: scale(1) rotate(0deg);
- }
-}
-
-@keyframes gradeGlow {
- 0%,
- 100% {
- transform: scale(1);
- filter: brightness(1) drop-shadow(0 0 0 rgba(34, 197, 94, 0));
- }
-
- 50% {
- transform: scale(1.15);
- filter: brightness(1.3) drop-shadow(0 0 8px rgba(34, 197, 94, 0.6));
- }
-}
-
-@keyframes peopleWave {
- 0%,
- 100% {
- transform: rotate(0deg) scale(1);
- }
-
- 25% {
- transform: rotate(-5deg) scale(1.05);
- }
-
- 50% {
- transform: rotate(5deg) scale(1.1);
- }
-
- 75% {
- transform: rotate(-3deg) scale(1.05);
- }
-}
-
-@keyframes dataScan {
- 0% {
- transform: scale(1);
- filter: brightness(1) drop-shadow(0 0 0 rgba(99, 102, 241, 0));
- }
-
- 25% {
- transform: scale(1.05);
- filter: brightness(1.1) drop-shadow(0 0 4px rgba(99, 102, 241, 0.4));
- }
-
- 50% {
- transform: scale(1.1);
- filter: brightness(1.2) drop-shadow(0 0 8px rgba(99, 102, 241, 0.6));
- }
-
- 75% {
- transform: scale(1.15) rotate(2deg);
- filter: brightness(1.3) drop-shadow(0 0 12px rgba(99, 102, 241, 0.8));
- }
-
- 100% {
- transform: scale(1);
- filter: brightness(1) drop-shadow(0 0 0 rgba(99, 102, 241, 0));
- }
-}
-
-@keyframes screenGlow {
- 0%,
- 100% {
- transform: scale(1);
- filter: brightness(1) drop-shadow(0 0 0 rgba(59, 130, 246, 0));
- }
-
- 50% {
- transform: scale(1.1);
- filter: brightness(1.2) drop-shadow(0 0 6px rgba(59, 130, 246, 0.5));
- }
-}
-
-@keyframes libraryExpand {
- 0% {
- transform: translateY(-100%) scale(0.8);
- opacity: 0;
- }
-
- 30% {
- transform: translateY(-20%) scale(0.9);
- opacity: 0.7;
- }
-
- 60% {
- transform: translateY(5%) scale(1.05);
- opacity: 1;
- }
-
- 80% {
- transform: translateY(-2%) scale(1.02);
- opacity: 1;
- }
-
- 100% {
- transform: translateY(0%) scale(1);
- opacity: 1;
- }
-}
-
-@keyframes templateMorph {
- 0% {
- transform: scale(1) rotate(0deg);
- }
-
- 25% {
- transform: scale(1.05) rotate(-2deg) skewX(2deg);
- }
-
- 50% {
- transform: scale(1.1) rotate(2deg) skewX(-2deg);
- }
-
- 75% {
- transform: scale(1.05) rotate(-1deg) skewX(1deg);
- }
-
- 100% {
- transform: scale(1) rotate(0deg) skewX(0deg);
- }
-}
-
-@keyframes communityRipple {
- 0% {
- transform: translateX(-100%) scale(0.8);
- opacity: 0;
- }
-
- 30% {
- transform: translateX(-20%) scale(0.9);
- opacity: 0.7;
- }
-
- 60% {
- transform: translateX(5%) scale(1.05);
- opacity: 1;
- }
-
- 80% {
- transform: translateX(-2%) scale(1.02);
- opacity: 1;
- }
-
- 100% {
- transform: translateX(0%) scale(1);
- opacity: 1;
- }
-}
-
-@keyframes quizRoll {
- 0% {
- transform: scale(1) rotate(0deg);
- }
-
- 20% {
- transform: scale(1.05) rotate(72deg);
- }
-
- 40% {
- transform: scale(1.1) rotate(144deg);
- }
-
- 60% {
- transform: scale(1.15) rotate(216deg);
- }
-
- 80% {
- transform: scale(1.1) rotate(288deg);
- }
-
- 100% {
- transform: scale(1) rotate(360deg);
- }
-}
-
-@keyframes audienceGather {
- 0% {
- transform: scale(1);
- filter: brightness(1) drop-shadow(0 0 0 rgba(147, 51, 234, 0));
- }
-
- 25% {
- transform: scale(0.95) scaleX(1.1);
- filter: brightness(1.05) drop-shadow(0 0 2px rgba(147, 51, 234, 0.3));
- }
-
- 50% {
- transform: scale(1.1) scaleX(0.9);
- filter: brightness(1.1) drop-shadow(0 0 4px rgba(147, 51, 234, 0.5));
- }
-
- 75% {
- transform: scale(1.05) scaleX(1.05);
- filter: brightness(1.05) drop-shadow(0 0 2px rgba(147, 51, 234, 0.3));
- }
-
- 100% {
- transform: scale(1) scaleX(1);
- filter: brightness(1) drop-shadow(0 0 0 rgba(147, 51, 234, 0));
- }
-}
-
-@keyframes setupConfigure {
- 0% {
- transform: scale(1) rotate(0deg);
- }
-
- 15% {
- transform: scale(1.02) rotate(15deg);
- }
-
- 30% {
- transform: scale(1.04) rotate(-10deg);
- }
-
- 45% {
- transform: scale(1.06) rotate(20deg);
- }
-
- 60% {
- transform: scale(1.08) rotate(-15deg);
- }
-
- 75% {
- transform: scale(1.06) rotate(10deg);
- }
-
- 90% {
- transform: scale(1.02) rotate(-5deg);
- }
-
- 100% {
- transform: scale(1) rotate(0deg);
+body[data-theme='default'] {
+ --chart-1: var(--color-blue-300);
+ --chart-2: var(--color-blue-500);
+ --chart-3: var(--color-blue-600);
+ --chart-4: var(--color-blue-700);
+ --chart-5: var(--color-blue-800);
+}
+
+body[data-theme='blue'] {
+ --primary: var(--color-blue-700);
+ --primary-foreground: var(--color-blue-50);
+ /* --ring: var(--color-blue-100); */
+ --sidebar-primary: var(--color-blue-600);
+ --sidebar-primary-foreground: var(--color-blue-50);
+ --sidebar-accent: var(--color-blue-50);
+ --sidebar-accent-foreground: var(--color-blue-700);
+ /* --sidebar-ring: var(--color-blue-400); */
+ --chart-1: var(--color-blue-300);
+ --chart-2: var(--color-blue-500);
+ --chart-3: var(--color-blue-600);
+ --chart-4: var(--color-blue-700);
+ --chart-5: var(--color-blue-800);
+
+ @variant dark {
+ --primary: var(--color-blue-700);
+ --primary-foreground: var(--color-blue-50);
+ /* --ring: var(--color-blue-900); */
+ --sidebar-primary: var(--color-blue-500);
+ --sidebar-primary-foreground: var(--color-blue-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-blue-500);
+ /* --sidebar-ring: var(--color-blue-900); */
+ }
+}
+
+body[data-theme='green'] {
+ --primary: var(--color-lime-600);
+ --primary-foreground: var(--color-lime-50);
+ --ring: var(--color-lime-400);
+ --chart-1: var(--color-green-300);
+ --chart-2: var(--color-green-500);
+ --chart-3: var(--color-green-600);
+ --chart-4: var(--color-green-700);
+ --chart-5: var(--color-green-800);
+ --sidebar-primary: var(--color-lime-600);
+ --sidebar-primary-foreground: var(--color-lime-50);
+ --sidebar-accent: var(--color-lime-50);
+ --sidebar-accent-foreground: var(--color-lime-700);
+ --sidebar-ring: var(--color-lime-400);
+
+ @variant dark {
+ --primary: var(--color-lime-600);
+ --primary-foreground: var(--color-lime-50);
+ --ring: var(--color-lime-900);
+ --sidebar-primary: var(--color-lime-500);
+ --sidebar-primary-foreground: var(--color-lime-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-lime-500);
+ --sidebar-ring: var(--color-lime-900);
+ }
+}
+
+body[data-theme='amber'] {
+ --primary: var(--color-amber-600);
+ --primary-foreground: var(--color-amber-50);
+ --ring: var(--color-amber-400);
+ --chart-1: var(--color-amber-300);
+ --chart-2: var(--color-amber-500);
+ --chart-3: var(--color-amber-600);
+ --chart-4: var(--color-amber-700);
+ --chart-5: var(--color-amber-800);
+ --sidebar-primary: var(--color-amber-600);
+ --sidebar-primary-foreground: var(--color-amber-50);
+ --sidebar-accent: var(--color-amber-50);
+ --sidebar-accent-foreground: var(--color-amber-700);
+ --sidebar-ring: var(--color-amber-400);
+
+ @variant dark {
+ --primary: var(--color-amber-500);
+ --primary-foreground: var(--color-amber-50);
+ --ring: var(--color-amber-900);
+ --sidebar-primary: var(--color-amber-500);
+ --sidebar-primary-foreground: var(--color-amber-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-amber-500);
+ --sidebar-ring: var(--color-amber-900);
+ }
+}
+
+body[data-theme='rose'] {
+ --primary: var(--color-rose-600);
+ --primary-foreground: var(--color-rose-50);
+ --ring: var(--color-rose-400);
+ --chart-1: var(--color-rose-300);
+ --chart-2: var(--color-rose-500);
+ --chart-3: var(--color-rose-600);
+ --chart-4: var(--color-rose-700);
+ --chart-5: var(--color-rose-800);
+ --sidebar-primary: var(--color-rose-600);
+ --sidebar-primary-foreground: var(--color-rose-50);
+ --sidebar-accent: var(--color-rose-50);
+ --sidebar-accent-foreground: var(--color-rose-700);
+ --sidebar-ring: var(--color-rose-400);
+
+ @variant dark {
+ --primary: var(--color-rose-500);
+ --primary-foreground: var(--color-rose-50);
+ --ring: var(--color-rose-900);
+ --sidebar-primary: var(--color-rose-500);
+ --sidebar-primary-foreground: var(--color-rose-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-rose-500);
+ --sidebar-ring: var(--color-rose-900);
+ }
+}
+
+body[data-theme='purple'] {
+ --primary: var(--color-purple-600);
+ --primary-foreground: var(--color-purple-50);
+ --ring: var(--color-purple-400);
+ --chart-1: var(--color-purple-300);
+ --chart-2: var(--color-purple-500);
+ --chart-3: var(--color-purple-600);
+ --chart-4: var(--color-purple-700);
+ --chart-5: var(--color-purple-800);
+ --sidebar-primary: var(--color-purple-600);
+ --sidebar-primary-foreground: var(--color-purple-50);
+ --sidebar-accent: var(--color-purple-50);
+ --sidebar-accent-foreground: var(--color-purple-700);
+ --sidebar-ring: var(--color-purple-400);
+
+ @variant dark {
+ --primary: var(--color-purple-500);
+ --primary-foreground: var(--color-purple-50);
+ --ring: var(--color-purple-900);
+ --sidebar-primary: var(--color-purple-500);
+ --sidebar-primary-foreground: var(--color-purple-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-purple-500);
+ --sidebar-ring: var(--color-purple-900);
+ }
+}
+
+body[data-theme='orange'] {
+ --primary: var(--color-orange-600);
+ --primary-foreground: var(--color-orange-50);
+ --ring: var(--color-orange-400);
+ --chart-1: var(--color-orange-300);
+ --chart-2: var(--color-orange-500);
+ --chart-3: var(--color-orange-600);
+ --chart-4: var(--color-orange-700);
+ --chart-5: var(--color-orange-800);
+ --sidebar-primary: var(--color-orange-600);
+ --sidebar-primary-foreground: var(--color-orange-50);
+ --sidebar-accent: var(--color-orange-50);
+ --sidebar-accent-foreground: var(--color-orange-700);
+ --sidebar-ring: var(--color-orange-400);
+
+ @variant dark {
+ --primary: var(--color-orange-500);
+ --primary-foreground: var(--color-orange-50);
+ --ring: var(--color-orange-900);
+ --sidebar-primary: var(--color-orange-500);
+ --sidebar-primary-foreground: var(--color-orange-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-orange-500);
+ --sidebar-ring: var(--color-orange-900);
+ }
+}
+
+body[data-theme='teal'] {
+ --primary: var(--color-teal-600);
+ --primary-foreground: var(--color-teal-50);
+ --chart-1: var(--color-teal-300);
+ --chart-2: var(--color-teal-500);
+ --chart-3: var(--color-teal-600);
+ --chart-4: var(--color-teal-700);
+ --chart-5: var(--color-teal-800);
+ --sidebar-primary: var(--color-teal-600);
+ --sidebar-primary-foreground: var(--color-teal-50);
+ --sidebar-accent: var(--color-teal-50);
+ --sidebar-accent-foreground: var(--color-teal-700);
+ --sidebar-ring: var(--color-teal-400);
+
+ @variant dark {
+ --primary: var(--color-teal-500);
+ --primary-foreground: var(--color-teal-50);
+ --sidebar-primary: var(--color-teal-500);
+ --sidebar-primary-foreground: var(--color-teal-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-teal-500);
+ --sidebar-ring: var(--color-teal-900);
+ }
+}
+
+body[data-theme='mono'] {
+ --font-sans: var(--font-mono);
+ --primary: var(--color-stone-600);
+ --primary-foreground: var(--color-stone-50);
+ --chart-1: var(--color-stone-300);
+ --chart-2: var(--color-stone-500);
+ --chart-3: var(--color-stone-600);
+ --chart-4: var(--color-stone-700);
+ --chart-5: var(--color-stone-800);
+ --sidebar-primary: var(--color-stone-600);
+ --sidebar-primary-foreground: var(--color-stone-50);
+ --sidebar-accent: var(--color-stone-50);
+ --sidebar-accent-foreground: var(--color-stone-700);
+ --sidebar-ring: var(--color-stone-400);
+
+ @variant dark {
+ --primary: var(--color-stone-500);
+ --primary-foreground: var(--color-stone-50);
+ --sidebar-primary: var(--color-stone-500);
+ --sidebar-primary-foreground: var(--color-stone-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-stone-500);
+ --sidebar-ring: var(--color-stone-900);
+ }
+
+ @media (min-width: 1024px) {
+ --font-sans: var(--font-mono);
+ --radius: 0.45em;
+ --text-lg: 1rem;
+ --text-xl: 1.1rem;
+ --text-2xl: 1.2rem;
+ --text-3xl: 1.3rem;
+ --text-4xl: 1.4rem;
+ --text-5xl: 1.5rem;
+ --text-6xl: 1.6rem;
+ --text-7xl: 1.7rem;
+ --text-8xl: 1.8rem;
+ --text-base: 0.85rem;
+ --text-sm: 0.8rem;
+ --spacing: 0.222222rem;
+ }
+
+ .rounded-xs,
+ .rounded-sm,
+ .rounded-md,
+ .rounded-lg,
+ .rounded-xl {
+ border-radius: 0;
+ }
+
+ .shadow-xs,
+ .shadow-sm,
+ .shadow-md,
+ .shadow-lg,
+ .shadow-xl {
+ box-shadow: none;
+ }
+
+ [data-slot='toggle-group'],
+ [data-slot='toggle-group-item'] {
+ @apply !rounded-none !shadow-none;
+ }
+}
+
+body[data-theme='scaled'] {
+ --chart-1: var(--color-blue-300);
+ --chart-2: var(--color-blue-500);
+ --chart-3: var(--color-blue-600);
+ --chart-4: var(--color-blue-700);
+ --chart-5: var(--color-blue-800);
+
+ @media (min-width: 1024px) {
+ --radius: 0.45em;
+ --text-lg: 1rem;
+ --text-xl: 1.1rem;
+ --text-2xl: 1.2rem;
+ --text-3xl: 1.3rem;
+ --text-4xl: 1.4rem;
+ --text-5xl: 1.5rem;
+ --text-6xl: 1.6rem;
+ --text-7xl: 1.7rem;
+ --text-8xl: 1.8rem;
+ --text-base: 0.85rem;
+ --text-sm: 0.8rem;
+ --spacing: 0.2rem;
+ }
+
+ [data-slot='select-trigger'],
+ [data-slot='toggle-group-item'] {
+ --spacing: 0.2rem;
+ }
+
+ [data-slot='card'] {
+ border-radius: var(--radius);
+ padding-block: calc(var(--spacing) * 4);
+ gap: calc(var(--spacing) * 2);
+ }
+
+ [data-slot='card'].pb-0 {
+ padding-bottom: 0;
+ }
+}
+
+body[data-theme='red'] {
+ --primary: var(--color-red-600);
+ --primary-foreground: var(--color-red-50);
+ --ring: var(--color-red-400);
+ --chart-1: var(--color-red-300);
+ --chart-2: var(--color-red-500);
+ --chart-3: var(--color-red-600);
+ --chart-4: var(--color-red-700);
+ --chart-5: var(--color-red-800);
+ --sidebar-primary: var(--color-red-600);
+ --sidebar-primary-foreground: var(--color-red-50);
+ --sidebar-accent: var(--color-red-50);
+ --sidebar-accent-foreground: var(--color-red-700);
+ --sidebar-ring: var(--color-red-400);
+
+ @variant dark {
+ --primary: var(--color-red-500);
+ --primary-foreground: var(--color-red-50);
+ --ring: var(--color-red-900);
+ --sidebar-primary: var(--color-red-500);
+ --sidebar-primary-foreground: var(--color-red-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-red-500);
+ --sidebar-ring: var(--color-red-900);
+ }
+}
+
+body[data-theme='yellow'] {
+ --primary: var(--color-yellow-400);
+ --primary-foreground: var(--color-yellow-900);
+ --ring: var(--color-yellow-400);
+ --chart-1: var(--color-yellow-300);
+ --chart-2: var(--color-yellow-500);
+ --chart-3: var(--color-yellow-600);
+ --chart-4: var(--color-yellow-700);
+ --chart-5: var(--color-yellow-800);
+ --sidebar-primary: var(--color-yellow-600);
+ --sidebar-primary-foreground: var(--color-yellow-50);
+ --sidebar-accent: var(--color-yellow-50);
+ --sidebar-accent-foreground: var(--color-yellow-700);
+ --sidebar-ring: var(--color-yellow-400);
+
+ @variant dark {
+ --primary: var(--color-yellow-500);
+ --primary-foreground: var(--color-yellow-900);
+ --ring: var(--color-yellow-900);
+ --sidebar-primary: var(--color-yellow-500);
+ --sidebar-primary-foreground: var(--color-yellow-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-yellow-500);
+ --sidebar-ring: var(--color-yellow-900);
+ }
+}
+
+body[data-theme='violet'] {
+ --primary: var(--color-violet-600);
+ --primary-foreground: var(--color-violet-50);
+ --ring: var(--color-violet-400);
+ --chart-1: var(--color-violet-300);
+ --chart-2: var(--color-violet-500);
+ --chart-3: var(--color-violet-600);
+ --chart-4: var(--color-violet-700);
+ --chart-5: var(--color-violet-800);
+ --sidebar-primary: var(--color-violet-600);
+ --sidebar-primary-foreground: var(--color-violet-50);
+ --sidebar-accent: var(--color-violet-50);
+ --sidebar-accent-foreground: var(--color-violet-700);
+ --sidebar-ring: var(--color-violet-400);
+
+ @variant dark {
+ --primary: var(--color-violet-500);
+ --primary-foreground: var(--color-violet-50);
+ --ring: var(--color-violet-900);
+ --sidebar-primary: var(--color-violet-500);
+ --sidebar-primary-foreground: var(--color-violet-50);
+ --sidebar-accent: oklch(0.268 0.007 34.298);
+ --sidebar-accent-foreground: var(--color-violet-500);
+ --sidebar-ring: var(--color-violet-900);
}
}
@@ -1166,9 +558,9 @@ svg.lucide-icon:not(.custom) {
conic-gradient(
from var(--border-angle),
rgb(148 163 184 / 0.3) 75%,
- rgb(37 99 235) 82%,
- rgb(96 165 250) 88%,
- rgb(37 99 235) 92%,
+ var(--primary) 82%,
+ var(--primary-foreground) 88%,
+ var(--primary) 92%,
rgb(148 163 184 / 0.3) 100%
)
border-box;
@@ -1185,9 +577,9 @@ svg.lucide-icon:not(.custom) {
conic-gradient(
from var(--border-angle),
rgb(71 85 105 / 0.48) 75%,
- rgb(37 99 235) 82%,
- rgb(96 165 250) 88%,
- rgb(37 99 235) 92%,
+ var(--primary) 82%,
+ var(--primary-foreground) 88%,
+ var(--primary) 92%,
rgb(71 85 105 / 0.48) 100%
)
border-box;
@@ -1287,7 +679,6 @@ svg.lucide-icon:not(.custom) {
*/
@keyframes flame-sequence {
0% {
- stroke: var(--main-primary-color);
opacity: 1;
transform: translate(-0.5px, 0.9px) scale(1.02);
}
diff --git a/apps/dashboard/src/app.html b/apps/dashboard/src/app.html
index 0ade6572..7b7e40c2 100644
--- a/apps/dashboard/src/app.html
+++ b/apps/dashboard/src/app.html
@@ -25,7 +25,7 @@
- %sveltekit.body%
+ %sveltekit.body%