@avora-labs/meta-forge 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, Injectable, computed, inject, ViewContainerRef, ViewChild, Input, Component, DestroyRef, effect, InjectionToken, forwardRef, EventEmitter, Output, APP_INITIALIZER } from '@angular/core';
2
+ import { signal, Injectable, computed, inject, ViewContainerRef, ViewChild, Input, Component, DestroyRef, effect, InjectionToken, forwardRef, EventEmitter, Output, APP_INITIALIZER, PLATFORM_ID, Inject } from '@angular/core';
3
3
  import * as i1$2 from '@angular/router';
4
4
  import { Router, ActivatedRoute, NavigationEnd, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
5
5
  import { Title, DomSanitizer } from '@angular/platform-browser';
@@ -8,7 +8,7 @@ import { Subject, timeout, firstValueFrom, retry, throwError, timer, filter, cat
8
8
  import { catchError, map, distinctUntilChanged } from 'rxjs/operators';
9
9
  import { toSignal } from '@angular/core/rxjs-interop';
10
10
  import * as i1 from '@angular/common';
11
- import { CommonModule, TitleCasePipe, DatePipe, CurrencyPipe } from '@angular/common';
11
+ import { CommonModule, TitleCasePipe, DatePipe, CurrencyPipe, isPlatformBrowser } from '@angular/common';
12
12
  import * as i1$1 from '@angular/forms';
13
13
  import { Validators, FormControl, FormGroup, ReactiveFormsModule, FormArray, FormsModule } from '@angular/forms';
14
14
  import * as i2 from '@angular/cdk/drag-drop';
@@ -2299,6 +2299,7 @@ class MetaRouterService {
2299
2299
  { path: '**', redirectTo: defaultPath }
2300
2300
  ];
2301
2301
  this.router.resetConfig(finalConfig);
2302
+ this.router.initialNavigation();
2302
2303
  console.log('[AvoraMetaForge] Router configured with', finalConfig.length, 'routes');
2303
2304
  }
2304
2305
  /**
@@ -5370,12 +5371,6 @@ class AppConfigService {
5370
5371
  const compact = this.stateService.get('settings.compactSidebar');
5371
5372
  if (compact !== undefined && compact !== null)
5372
5373
  this.sidebarCollapsed.set(Boolean(compact));
5373
- // Restore dark/light mode preference (default is dark)
5374
- const darkMode = this.stateService.get('settings.darkMode');
5375
- // If no preference is stored yet, default to dark (no attribute = dark variables from :root)
5376
- if (darkMode !== undefined && darkMode !== null) {
5377
- this.applyColorScheme(Boolean(darkMode));
5378
- }
5379
5374
  }
5380
5375
  /**
5381
5376
  * Called whenever a settings.* state key changes (e.g. after the Settings
@@ -5392,8 +5387,8 @@ class AppConfigService {
5392
5387
  case 'settings.compactSidebar':
5393
5388
  this.sidebarCollapsed.set(Boolean(value));
5394
5389
  break;
5390
+ // Dark mode is handled exclusively by ThemeService now
5395
5391
  case 'settings.darkMode':
5396
- this.applyColorScheme(Boolean(value));
5397
5392
  break;
5398
5393
  case 'settings.language':
5399
5394
  // Language preference is stored; full i18n runtime switching requires
@@ -5404,20 +5399,6 @@ class AppConfigService {
5404
5399
  break;
5405
5400
  }
5406
5401
  }
5407
- /**
5408
- * Toggles dark / light mode by setting data-color-scheme on <html>.
5409
- * Dark mode = no attribute (uses :root CSS vars from _variables.scss).
5410
- * Light mode = data-color-scheme="light" (overrides via _themes.scss).
5411
- */
5412
- applyColorScheme(isDark) {
5413
- const html = document.documentElement;
5414
- if (isDark) {
5415
- html.removeAttribute('data-color-scheme');
5416
- }
5417
- else {
5418
- html.setAttribute('data-color-scheme', 'light');
5419
- }
5420
- }
5421
5402
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AppConfigService, deps: [{ token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Injectable });
5422
5403
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AppConfigService, providedIn: 'root' });
5423
5404
  }
@@ -5580,55 +5561,137 @@ const authInterceptor = (req, next) => {
5580
5561
  }));
5581
5562
  };
5582
5563
 
5583
- /**
5584
- * Manages runtime theming by toggling the `data-theme` attribute on the <body> element.
5585
- * Allows switching brand skins without recompiling styles.
5586
- */
5564
+ const THEME_PRESETS = [
5565
+ { name: 'cyan', primary: '#06b6d4', hover: '#0891b2', light: 'rgba(6, 182, 212, 0.15)', glow: 'rgba(6, 182, 212, 0.25)' },
5566
+ { name: 'blue', primary: '#3b82f6', hover: '#2563eb', light: 'rgba(59, 130, 246, 0.15)', glow: 'rgba(59, 130, 246, 0.25)' },
5567
+ { name: 'indigo', primary: '#6366f1', hover: '#4f46e5', light: 'rgba(99, 102, 241, 0.15)', glow: 'rgba(99, 102, 241, 0.25)' },
5568
+ { name: 'violet', primary: '#8b5cf6', hover: '#7c3aed', light: 'rgba(139, 92, 246, 0.15)', glow: 'rgba(139, 92, 246, 0.25)' },
5569
+ { name: 'rose', primary: '#f43f5e', hover: '#e11d48', light: 'rgba(244, 63, 94, 0.15)', glow: 'rgba(244, 63, 94, 0.25)' },
5570
+ { name: 'orange', primary: '#f97316', hover: '#ea580c', light: 'rgba(249, 115, 22, 0.15)', glow: 'rgba(249, 115, 22, 0.25)' },
5571
+ { name: 'emerald', primary: '#10b981', hover: '#059669', light: 'rgba(16, 185, 129, 0.15)', glow: 'rgba(16, 185, 129, 0.25)' },
5572
+ { name: 'green', primary: '#22c55e', hover: '#16a34a', light: 'rgba(34, 197, 94, 0.15)', glow: 'rgba(34, 197, 94, 0.25)' },
5573
+ { name: 'amber', primary: '#f59e0b', hover: '#d97706', light: 'rgba(245, 158, 11, 0.15)', glow: 'rgba(245, 158, 11, 0.25)' }
5574
+ ];
5575
+ const SURFACE_PRESETS = [
5576
+ { name: 'slate', bg: '#0f172a', bgSecondary: '#131d35', sidebarBg: 'rgba(15, 23, 42, 0.85)', navbarBg: 'rgba(15, 23, 42, 0.65)' },
5577
+ { name: 'gray', bg: '#111827', bgSecondary: '#1f2937', sidebarBg: 'rgba(17, 24, 39, 0.85)', navbarBg: 'rgba(17, 24, 39, 0.65)' },
5578
+ { name: 'zinc', bg: '#18181b', bgSecondary: '#27272a', sidebarBg: 'rgba(24, 24, 27, 0.85)', navbarBg: 'rgba(24, 24, 27, 0.65)' },
5579
+ { name: 'neutral', bg: '#171717', bgSecondary: '#262626', sidebarBg: 'rgba(23, 23, 23, 0.85)', navbarBg: 'rgba(23, 23, 23, 0.65)' },
5580
+ { name: 'stone', bg: '#1c1917', bgSecondary: '#292524', sidebarBg: 'rgba(28, 25, 23, 0.85)', navbarBg: 'rgba(28, 25, 23, 0.65)' }
5581
+ ];
5582
+ const DEFAULT_THEME_CONFIG = {
5583
+ primaryPreset: 'cyan',
5584
+ surfacePreset: 'slate',
5585
+ colorScheme: 'dark',
5586
+ menuType: 'static',
5587
+ menuProfile: 'end'
5588
+ };
5589
+
5587
5590
  class ThemeService {
5588
- /** Currently active theme name */
5589
- currentTheme = signal('default', ...(ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []));
5590
- /** Available theme names */
5591
- availableThemes = [
5592
- { name: 'default', label: 'Default' },
5593
- { name: 'client-a', label: 'Client A (Blue)' },
5594
- { name: 'client-b', label: 'Client B (Red)' },
5595
- ];
5596
- constructor() {
5597
- // Sync theme signal to the DOM
5598
- effect(() => {
5599
- const theme = this.currentTheme();
5600
- if (theme === 'default') {
5601
- document.body.removeAttribute('data-theme');
5591
+ platformId;
5592
+ STORAGE_KEY = 'amf-theme-config';
5593
+ // Expose configuration as a signal
5594
+ config = signal(DEFAULT_THEME_CONFIG, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
5595
+ // Customizer UI State
5596
+ customizerOpen = signal(false, ...(ngDevMode ? [{ debugName: "customizerOpen" }] : /* istanbul ignore next */ []));
5597
+ stateService = inject(MetaStateService);
5598
+ constructor(platformId) {
5599
+ this.platformId = platformId;
5600
+ if (isPlatformBrowser(this.platformId)) {
5601
+ this.loadConfig();
5602
+ // Sync theme signal to the DOM and persist
5603
+ effect(() => {
5604
+ const currentConfig = this.config();
5605
+ this.saveConfig(currentConfig);
5606
+ this.applyTheme(currentConfig);
5607
+ });
5608
+ // Sync from MetaStateService (e.g. from Settings page)
5609
+ this.stateService.changes$.pipe(filter(change => change.key === 'settings.darkMode')).subscribe(change => {
5610
+ const isDark = Boolean(change.newValue);
5611
+ this.updateConfig({ colorScheme: isDark ? 'dark' : 'light' });
5612
+ });
5613
+ }
5614
+ }
5615
+ updateConfig(partial) {
5616
+ this.config.update(c => ({ ...c, ...partial }));
5617
+ }
5618
+ loadConfig() {
5619
+ try {
5620
+ const stored = localStorage.getItem(this.STORAGE_KEY);
5621
+ // Legacy check
5622
+ const legacySaved = localStorage.getItem('app-theme');
5623
+ if (stored) {
5624
+ this.config.set({ ...DEFAULT_THEME_CONFIG, ...JSON.parse(stored) });
5602
5625
  }
5603
- else {
5604
- document.body.setAttribute('data-theme', theme);
5626
+ else if (legacySaved && legacySaved !== 'default') {
5627
+ // Fallback for old theme service
5628
+ this.config.update(c => ({ ...c, colorScheme: legacySaved === 'client-b' ? 'dark' : 'light' }));
5605
5629
  }
5606
- });
5607
- // Restore persisted theme
5608
- const saved = localStorage.getItem('app-theme');
5609
- if (saved) {
5610
- this.currentTheme.set(saved);
5630
+ }
5631
+ catch (e) {
5632
+ console.warn('Could not load theme config from localStorage', e);
5611
5633
  }
5612
5634
  }
5613
- /** Switch to a named theme */
5614
- setTheme(themeName) {
5615
- this.currentTheme.set(themeName);
5616
- localStorage.setItem('app-theme', themeName);
5617
- }
5618
- /** Cycle to the next theme */
5619
- cycleTheme() {
5620
- const themes = this.availableThemes;
5621
- const idx = themes.findIndex(t => t.name === this.currentTheme());
5622
- const next = themes[(idx + 1) % themes.length];
5623
- this.setTheme(next.name);
5635
+ saveConfig(config) {
5636
+ try {
5637
+ localStorage.setItem(this.STORAGE_KEY, JSON.stringify(config));
5638
+ }
5639
+ catch (e) {
5640
+ console.warn('Could not save theme config to localStorage', e);
5641
+ }
5624
5642
  }
5625
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5643
+ applyTheme(config) {
5644
+ const doc = document.documentElement;
5645
+ // 1. Apply Color Scheme (light/dark)
5646
+ if (config.colorScheme === 'dark') {
5647
+ doc.removeAttribute('data-color-scheme');
5648
+ }
5649
+ else {
5650
+ doc.setAttribute('data-color-scheme', 'light');
5651
+ }
5652
+ // 2. Apply Primary Colors
5653
+ const primaryPreset = THEME_PRESETS.find(p => p.name === config.primaryPreset) || THEME_PRESETS[0];
5654
+ this.applyPrimaryPalette(primaryPreset, doc);
5655
+ // 3. Apply Surface Colors (applicable if in dark mode)
5656
+ if (config.colorScheme === 'dark') {
5657
+ const surfacePreset = SURFACE_PRESETS.find(p => p.name === config.surfacePreset) || SURFACE_PRESETS[0];
5658
+ this.applySurfacePalette(surfacePreset, doc);
5659
+ }
5660
+ else {
5661
+ doc.style.removeProperty('--app-bg');
5662
+ doc.style.removeProperty('--app-bg-secondary');
5663
+ doc.style.removeProperty('--app-sidebar-bg');
5664
+ doc.style.removeProperty('--app-navbar-bg');
5665
+ }
5666
+ // 4. Apply Layout attributes (these will be handled by the shell layout)
5667
+ doc.setAttribute('data-menu-type', config.menuType);
5668
+ doc.setAttribute('data-menu-profile', config.menuProfile);
5669
+ }
5670
+ applyPrimaryPalette(preset, doc) {
5671
+ doc.style.setProperty('--app-primary', preset.primary);
5672
+ doc.style.setProperty('--app-primary-hover', preset.hover);
5673
+ doc.style.setProperty('--app-primary-light', preset.light);
5674
+ doc.style.setProperty('--app-glow', preset.glow);
5675
+ doc.style.setProperty('--app-gradient-1', preset.primary);
5676
+ }
5677
+ applySurfacePalette(preset, doc) {
5678
+ doc.style.setProperty('--app-bg', preset.bg);
5679
+ doc.style.setProperty('--app-bg-secondary', preset.bgSecondary);
5680
+ doc.style.setProperty('--app-sidebar-bg', preset.sidebarBg);
5681
+ doc.style.setProperty('--app-navbar-bg', preset.navbarBg);
5682
+ }
5683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
5626
5684
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeService, providedIn: 'root' });
5627
5685
  }
5628
5686
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeService, decorators: [{
5629
5687
  type: Injectable,
5630
- args: [{ providedIn: 'root' }]
5631
- }], ctorParameters: () => [] });
5688
+ args: [{
5689
+ providedIn: 'root'
5690
+ }]
5691
+ }], ctorParameters: () => [{ type: Object, decorators: [{
5692
+ type: Inject,
5693
+ args: [PLATFORM_ID]
5694
+ }] }] });
5632
5695
 
5633
5696
  class SidebarComponent {
5634
5697
  config = inject(AppConfigService);
@@ -5788,16 +5851,7 @@ class NavbarComponent {
5788
5851
  iconRegistry = inject(IconRegistryService);
5789
5852
  themeService = inject(ThemeService);
5790
5853
  auth = inject(AuthService);
5791
- themeToggleOpen = signal(false, ...(ngDevMode ? [{ debugName: "themeToggleOpen" }] : /* istanbul ignore next */ []));
5792
5854
  userMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "userMenuOpen" }] : /* istanbul ignore next */ []));
5793
- getThemeColor(name) {
5794
- const colors = {
5795
- 'default': '#06b6d4',
5796
- 'client-a': '#3b82f6',
5797
- 'client-b': '#f43f5e'
5798
- };
5799
- return colors[name] || '#06b6d4';
5800
- }
5801
5855
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NavbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5802
5856
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: NavbarComponent, isStandalone: true, selector: "app-navbar", ngImport: i0, template: `
5803
5857
  <header class="navbar-container">
@@ -5817,26 +5871,11 @@ class NavbarComponent {
5817
5871
  </div>
5818
5872
 
5819
5873
  <div class="right-section">
5820
- <!-- Theme Switcher -->
5821
- <button class="icon-btn theme-btn" (click)="themeToggleOpen.set(!themeToggleOpen())" title="Change Theme">
5874
+ <!-- Theme Customizer Settings -->
5875
+ <button class="icon-btn theme-btn" (click)="themeService.customizerOpen.set(true)" title="Theme Settings">
5822
5876
  <svg viewBox="0 0 24 24">
5823
- <path [attr.d]="iconRegistry.getIcon('palette')"></path>
5877
+ <path [attr.d]="iconRegistry.getIcon('settings')"></path>
5824
5878
  </svg>
5825
-
5826
- @if (themeToggleOpen()) {
5827
- <div class="dropdown theme-dropdown">
5828
- @for (theme of themeService.availableThemes; track theme.name) {
5829
- <div
5830
- class="dropdown-item"
5831
- [class.active]="themeService.currentTheme() === theme.name"
5832
- (click)="themeService.setTheme(theme.name); themeToggleOpen.set(false)"
5833
- >
5834
- <span class="theme-dot" [style.background]="getThemeColor(theme.name)"></span>
5835
- {{ theme.label }}
5836
- </div>
5837
- }
5838
- </div>
5839
- }
5840
5879
  </button>
5841
5880
 
5842
5881
  <!-- User Profile -->
@@ -5898,26 +5937,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
5898
5937
  </div>
5899
5938
 
5900
5939
  <div class="right-section">
5901
- <!-- Theme Switcher -->
5902
- <button class="icon-btn theme-btn" (click)="themeToggleOpen.set(!themeToggleOpen())" title="Change Theme">
5940
+ <!-- Theme Customizer Settings -->
5941
+ <button class="icon-btn theme-btn" (click)="themeService.customizerOpen.set(true)" title="Theme Settings">
5903
5942
  <svg viewBox="0 0 24 24">
5904
- <path [attr.d]="iconRegistry.getIcon('palette')"></path>
5943
+ <path [attr.d]="iconRegistry.getIcon('settings')"></path>
5905
5944
  </svg>
5906
-
5907
- @if (themeToggleOpen()) {
5908
- <div class="dropdown theme-dropdown">
5909
- @for (theme of themeService.availableThemes; track theme.name) {
5910
- <div
5911
- class="dropdown-item"
5912
- [class.active]="themeService.currentTheme() === theme.name"
5913
- (click)="themeService.setTheme(theme.name); themeToggleOpen.set(false)"
5914
- >
5915
- <span class="theme-dot" [style.background]="getThemeColor(theme.name)"></span>
5916
- {{ theme.label }}
5917
- </div>
5918
- }
5919
- </div>
5920
- }
5921
5945
  </button>
5922
5946
 
5923
5947
  <!-- User Profile -->
@@ -6291,6 +6315,242 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
6291
6315
  `, styles: [".notification-wrapper{position:fixed;top:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:12px;pointer-events:none}.toast{pointer-events:auto;min-width:300px;max-width:450px;background:var(--app-surface, #ffffff);border-radius:12px;padding:16px;display:flex;align-items:center;gap:12px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;border-left:4px solid #e5e7eb;cursor:pointer;animation:slideIn .3s cubic-bezier(.4,0,.2,1);transition:all .2s}.toast:hover{transform:translateY(-2px);box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a}.toast.success{border-left-color:#10b981}.toast.error{border-left-color:#ef4444}.toast.warning{border-left-color:#f59e0b}.toast.info{border-left-color:#3b82f6}.toast-icon{width:24px;height:24px;flex-shrink:0}.toast.success .toast-icon{fill:#10b981}.toast.error .toast-icon{fill:#ef4444}.toast.warning .toast-icon{fill:#f59e0b}.toast.info .toast-icon{fill:#3b82f6}.toast-content{flex:1;font-size:.875rem;font-weight:500;color:var(--app-text, #1f2937);line-height:1.4}.toast-close{background:none;border:none;padding:4px;cursor:pointer;color:var(--app-text-muted, #6b7280);opacity:.5;transition:opacity .2s}.toast-close:hover{opacity:1}.toast-close svg{width:16px;height:16px;fill:currentColor}@keyframes slideIn{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}\n"] }]
6292
6316
  }] });
6293
6317
 
6318
+ class ThemeCustomizerComponent {
6319
+ themeService = inject(ThemeService);
6320
+ iconRegistry = inject(IconRegistryService);
6321
+ presets = THEME_PRESETS;
6322
+ surfaces = SURFACE_PRESETS;
6323
+ menuTypes = ['static', 'overlay', 'slim', 'reveal', 'drawer', 'horizontal'];
6324
+ close() {
6325
+ this.themeService.customizerOpen.set(false);
6326
+ }
6327
+ setPrimary(name) {
6328
+ this.themeService.updateConfig({ primaryPreset: name });
6329
+ }
6330
+ setSurface(name) {
6331
+ this.themeService.updateConfig({ surfacePreset: name });
6332
+ }
6333
+ setScheme(scheme) {
6334
+ this.themeService.updateConfig({ colorScheme: scheme });
6335
+ }
6336
+ setMenuType(type) {
6337
+ this.themeService.updateConfig({ menuType: type });
6338
+ }
6339
+ setMenuProfile(profile) {
6340
+ this.themeService.updateConfig({ menuProfile: profile });
6341
+ }
6342
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeCustomizerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6343
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ThemeCustomizerComponent, isStandalone: true, selector: "app-theme-customizer", ngImport: i0, template: `
6344
+ <!-- Overlay -->
6345
+ @if (themeService.customizerOpen()) {
6346
+ <div class="customizer-overlay" (click)="close()"></div>
6347
+ }
6348
+
6349
+ <!-- Drawer -->
6350
+ <div class="customizer-drawer" [class.open]="themeService.customizerOpen()">
6351
+ <div class="drawer-header">
6352
+ <h2>Settings</h2>
6353
+ <button class="close-btn" (click)="close()">
6354
+ <svg viewBox="0 0 24 24"><path [attr.d]="iconRegistry.getIcon('close')"></path></svg>
6355
+ </button>
6356
+ </div>
6357
+
6358
+ <div class="drawer-body">
6359
+
6360
+ <!-- Primary Colors -->
6361
+ <div class="section">
6362
+ <h3>Primary</h3>
6363
+ <div class="color-grid">
6364
+ @for (preset of presets; track preset.name) {
6365
+ <button
6366
+ class="color-swatch"
6367
+ [style.background]="preset.primary"
6368
+ [class.active]="themeService.config().primaryPreset === preset.name"
6369
+ (click)="setPrimary(preset.name)"
6370
+ [title]="preset.name"
6371
+ ></button>
6372
+ }
6373
+ </div>
6374
+ </div>
6375
+
6376
+ <!-- Surface Colors -->
6377
+ <div class="section">
6378
+ <h3>Surface</h3>
6379
+ <div class="color-grid surface-grid">
6380
+ @for (surface of surfaces; track surface.name) {
6381
+ <button
6382
+ class="color-swatch"
6383
+ [style.background]="surface.bgSecondary"
6384
+ [class.active]="themeService.config().surfacePreset === surface.name"
6385
+ (click)="setSurface(surface.name)"
6386
+ [title]="surface.name"
6387
+ ></button>
6388
+ }
6389
+ </div>
6390
+ </div>
6391
+
6392
+ <!-- Color Scheme -->
6393
+ <div class="section">
6394
+ <h3>Color Scheme</h3>
6395
+ <div class="toggle-group">
6396
+ <button
6397
+ [class.active]="themeService.config().colorScheme === 'light'"
6398
+ (click)="setScheme('light')">Light</button>
6399
+ <button
6400
+ [class.active]="themeService.config().colorScheme === 'dark'"
6401
+ (click)="setScheme('dark')">Dark</button>
6402
+ </div>
6403
+ </div>
6404
+
6405
+ <!-- Menu Type -->
6406
+ <div class="section">
6407
+ <h3>Menu Type</h3>
6408
+ <div class="radio-group">
6409
+ @for (type of menuTypes; track type) {
6410
+ <label class="radio-label">
6411
+ <input type="radio" name="menuType"
6412
+ [value]="type"
6413
+ [checked]="themeService.config().menuType === type"
6414
+ (change)="setMenuType(type)">
6415
+ <span class="radio-custom"></span>
6416
+ {{ type | titlecase }}
6417
+ </label>
6418
+ }
6419
+ </div>
6420
+ </div>
6421
+
6422
+ <!-- Menu Profile -->
6423
+ <div class="section">
6424
+ <h3>Menu Profile</h3>
6425
+ <div class="radio-group row-group">
6426
+ <label class="radio-label">
6427
+ <input type="radio" name="menuProfile" value="start"
6428
+ [checked]="themeService.config().menuProfile === 'start'"
6429
+ (change)="setMenuProfile('start')">
6430
+ <span class="radio-custom"></span>
6431
+ Start
6432
+ </label>
6433
+ <label class="radio-label">
6434
+ <input type="radio" name="menuProfile" value="end"
6435
+ [checked]="themeService.config().menuProfile === 'end'"
6436
+ (change)="setMenuProfile('end')">
6437
+ <span class="radio-custom"></span>
6438
+ End
6439
+ </label>
6440
+ </div>
6441
+ </div>
6442
+
6443
+ </div>
6444
+ </div>
6445
+ `, isInline: true, styles: [".customizer-overlay{position:fixed;inset:0;background:#0006;z-index:999;animation:fadeIn .2s ease}.customizer-drawer{position:fixed;top:0;right:-360px;width:340px;height:100vh;background:var(--app-bg-secondary, #fff);color:var(--app-text, #333);z-index:1000;box-shadow:-4px 0 24px #00000026;transition:right .3s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column}.customizer-drawer.open{right:0}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--app-border, #eee)}.drawer-header h2{margin:0;font-size:1.25rem;font-weight:600}.close-btn{background:transparent;border:none;color:var(--app-text-muted);cursor:pointer;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:background .2s}.close-btn:hover{background:var(--app-surface-hover)}.close-btn svg{width:20px;height:20px;fill:currentColor}.drawer-body{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:32px}.section h3{margin:0 0 16px;font-size:.95rem;font-weight:600}.color-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:12px}.color-swatch{width:32px;height:32px;border-radius:50%;border:none;cursor:pointer;transition:transform .2s,box-shadow .2s;outline:2px solid transparent;outline-offset:2px}.color-swatch:hover{transform:scale(1.1)}.color-swatch.active{outline-color:currentColor}.toggle-group{display:flex;background:var(--app-surface);border-radius:8px;padding:4px;border:1px solid var(--app-border)}.toggle-group button{flex:1;padding:10px;background:transparent;border:none;border-radius:6px;color:var(--app-text-muted);font-weight:500;cursor:pointer;transition:all .2s}.toggle-group button.active{background:var(--app-primary-light);color:var(--app-primary)}.radio-group{display:grid;grid-template-columns:1fr 1fr;gap:16px}.radio-group.row-group{grid-template-columns:auto auto;justify-content:flex-start;gap:32px}.radio-label{display:flex;align-items:center;gap:10px;cursor:pointer;font-size:.9rem;color:var(--app-text)}.radio-label input{display:none}.radio-custom{width:20px;height:20px;border:2px solid var(--app-border);border-radius:50%;position:relative;transition:all .2s}.radio-label input:checked+.radio-custom{border-color:var(--app-primary)}.radio-label input:checked+.radio-custom:after{content:\"\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:10px;height:10px;background:var(--app-primary);border-radius:50%}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "pipe", type: TitleCasePipe, name: "titlecase" }] });
6446
+ }
6447
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeCustomizerComponent, decorators: [{
6448
+ type: Component,
6449
+ args: [{ selector: 'app-theme-customizer', standalone: true, imports: [TitleCasePipe], template: `
6450
+ <!-- Overlay -->
6451
+ @if (themeService.customizerOpen()) {
6452
+ <div class="customizer-overlay" (click)="close()"></div>
6453
+ }
6454
+
6455
+ <!-- Drawer -->
6456
+ <div class="customizer-drawer" [class.open]="themeService.customizerOpen()">
6457
+ <div class="drawer-header">
6458
+ <h2>Settings</h2>
6459
+ <button class="close-btn" (click)="close()">
6460
+ <svg viewBox="0 0 24 24"><path [attr.d]="iconRegistry.getIcon('close')"></path></svg>
6461
+ </button>
6462
+ </div>
6463
+
6464
+ <div class="drawer-body">
6465
+
6466
+ <!-- Primary Colors -->
6467
+ <div class="section">
6468
+ <h3>Primary</h3>
6469
+ <div class="color-grid">
6470
+ @for (preset of presets; track preset.name) {
6471
+ <button
6472
+ class="color-swatch"
6473
+ [style.background]="preset.primary"
6474
+ [class.active]="themeService.config().primaryPreset === preset.name"
6475
+ (click)="setPrimary(preset.name)"
6476
+ [title]="preset.name"
6477
+ ></button>
6478
+ }
6479
+ </div>
6480
+ </div>
6481
+
6482
+ <!-- Surface Colors -->
6483
+ <div class="section">
6484
+ <h3>Surface</h3>
6485
+ <div class="color-grid surface-grid">
6486
+ @for (surface of surfaces; track surface.name) {
6487
+ <button
6488
+ class="color-swatch"
6489
+ [style.background]="surface.bgSecondary"
6490
+ [class.active]="themeService.config().surfacePreset === surface.name"
6491
+ (click)="setSurface(surface.name)"
6492
+ [title]="surface.name"
6493
+ ></button>
6494
+ }
6495
+ </div>
6496
+ </div>
6497
+
6498
+ <!-- Color Scheme -->
6499
+ <div class="section">
6500
+ <h3>Color Scheme</h3>
6501
+ <div class="toggle-group">
6502
+ <button
6503
+ [class.active]="themeService.config().colorScheme === 'light'"
6504
+ (click)="setScheme('light')">Light</button>
6505
+ <button
6506
+ [class.active]="themeService.config().colorScheme === 'dark'"
6507
+ (click)="setScheme('dark')">Dark</button>
6508
+ </div>
6509
+ </div>
6510
+
6511
+ <!-- Menu Type -->
6512
+ <div class="section">
6513
+ <h3>Menu Type</h3>
6514
+ <div class="radio-group">
6515
+ @for (type of menuTypes; track type) {
6516
+ <label class="radio-label">
6517
+ <input type="radio" name="menuType"
6518
+ [value]="type"
6519
+ [checked]="themeService.config().menuType === type"
6520
+ (change)="setMenuType(type)">
6521
+ <span class="radio-custom"></span>
6522
+ {{ type | titlecase }}
6523
+ </label>
6524
+ }
6525
+ </div>
6526
+ </div>
6527
+
6528
+ <!-- Menu Profile -->
6529
+ <div class="section">
6530
+ <h3>Menu Profile</h3>
6531
+ <div class="radio-group row-group">
6532
+ <label class="radio-label">
6533
+ <input type="radio" name="menuProfile" value="start"
6534
+ [checked]="themeService.config().menuProfile === 'start'"
6535
+ (change)="setMenuProfile('start')">
6536
+ <span class="radio-custom"></span>
6537
+ Start
6538
+ </label>
6539
+ <label class="radio-label">
6540
+ <input type="radio" name="menuProfile" value="end"
6541
+ [checked]="themeService.config().menuProfile === 'end'"
6542
+ (change)="setMenuProfile('end')">
6543
+ <span class="radio-custom"></span>
6544
+ End
6545
+ </label>
6546
+ </div>
6547
+ </div>
6548
+
6549
+ </div>
6550
+ </div>
6551
+ `, styles: [".customizer-overlay{position:fixed;inset:0;background:#0006;z-index:999;animation:fadeIn .2s ease}.customizer-drawer{position:fixed;top:0;right:-360px;width:340px;height:100vh;background:var(--app-bg-secondary, #fff);color:var(--app-text, #333);z-index:1000;box-shadow:-4px 0 24px #00000026;transition:right .3s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column}.customizer-drawer.open{right:0}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--app-border, #eee)}.drawer-header h2{margin:0;font-size:1.25rem;font-weight:600}.close-btn{background:transparent;border:none;color:var(--app-text-muted);cursor:pointer;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:background .2s}.close-btn:hover{background:var(--app-surface-hover)}.close-btn svg{width:20px;height:20px;fill:currentColor}.drawer-body{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:32px}.section h3{margin:0 0 16px;font-size:.95rem;font-weight:600}.color-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:12px}.color-swatch{width:32px;height:32px;border-radius:50%;border:none;cursor:pointer;transition:transform .2s,box-shadow .2s;outline:2px solid transparent;outline-offset:2px}.color-swatch:hover{transform:scale(1.1)}.color-swatch.active{outline-color:currentColor}.toggle-group{display:flex;background:var(--app-surface);border-radius:8px;padding:4px;border:1px solid var(--app-border)}.toggle-group button{flex:1;padding:10px;background:transparent;border:none;border-radius:6px;color:var(--app-text-muted);font-weight:500;cursor:pointer;transition:all .2s}.toggle-group button.active{background:var(--app-primary-light);color:var(--app-primary)}.radio-group{display:grid;grid-template-columns:1fr 1fr;gap:16px}.radio-group.row-group{grid-template-columns:auto auto;justify-content:flex-start;gap:32px}.radio-label{display:flex;align-items:center;gap:10px;cursor:pointer;font-size:.9rem;color:var(--app-text)}.radio-label input{display:none}.radio-custom{width:20px;height:20px;border:2px solid var(--app-border);border-radius:50%;position:relative;transition:all .2s}.radio-label input:checked+.radio-custom{border-color:var(--app-primary)}.radio-label input:checked+.radio-custom:after{content:\"\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:10px;height:10px;background:var(--app-primary);border-radius:50%}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
6552
+ }] });
6553
+
6294
6554
  /**
6295
6555
  * The Root Renderer (Traffic Controller).
6296
6556
  * Decides which layout to load based on the currentLayout signal from AppConfigService.
@@ -6304,6 +6564,7 @@ class LayoutComponent {
6304
6564
  <amf-modal-host></amf-modal-host>
6305
6565
  <amf-dialog-host></amf-dialog-host>
6306
6566
  <amf-drawer-host></amf-drawer-host>
6567
+ <app-theme-customizer></app-theme-customizer>
6307
6568
  @switch (config.currentLayout()) {
6308
6569
  @case (LayoutType.VERTICAL) {
6309
6570
  <app-vertical-layout></app-vertical-layout>
@@ -6330,7 +6591,7 @@ class LayoutComponent {
6330
6591
  <app-vertical-layout></app-vertical-layout>
6331
6592
  }
6332
6593
  }
6333
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;width:100%}\n"], dependencies: [{ kind: "component", type: VerticalLayoutComponent, selector: "app-vertical-layout" }, { kind: "component", type: HorizontalLayoutComponent, selector: "app-horizontal-layout" }, { kind: "component", type: EmptyLayoutComponent, selector: "app-empty-layout" }, { kind: "component", type: CenteredLayoutComponent, selector: "app-centered-layout" }, { kind: "component", type: TwoColumnLayoutComponent, selector: "app-two-column-layout" }, { kind: "component", type: DashboardGridLayoutComponent, selector: "app-dashboard-grid-layout" }, { kind: "component", type: CompactLayoutComponent, selector: "app-compact-layout" }, { kind: "component", type: NotificationContainerComponent, selector: "app-notification-container" }, { kind: "component", type: MetaModalHostComponent, selector: "amf-modal-host" }, { kind: "component", type: AmfDialogHostComponent, selector: "amf-dialog-host" }, { kind: "component", type: AmfDrawerHostComponent, selector: "amf-drawer-host" }] });
6594
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;width:100%}\n"], dependencies: [{ kind: "component", type: VerticalLayoutComponent, selector: "app-vertical-layout" }, { kind: "component", type: HorizontalLayoutComponent, selector: "app-horizontal-layout" }, { kind: "component", type: EmptyLayoutComponent, selector: "app-empty-layout" }, { kind: "component", type: CenteredLayoutComponent, selector: "app-centered-layout" }, { kind: "component", type: TwoColumnLayoutComponent, selector: "app-two-column-layout" }, { kind: "component", type: DashboardGridLayoutComponent, selector: "app-dashboard-grid-layout" }, { kind: "component", type: CompactLayoutComponent, selector: "app-compact-layout" }, { kind: "component", type: NotificationContainerComponent, selector: "app-notification-container" }, { kind: "component", type: MetaModalHostComponent, selector: "amf-modal-host" }, { kind: "component", type: AmfDialogHostComponent, selector: "amf-dialog-host" }, { kind: "component", type: AmfDrawerHostComponent, selector: "amf-drawer-host" }, { kind: "component", type: ThemeCustomizerComponent, selector: "app-theme-customizer" }] });
6334
6595
  }
6335
6596
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LayoutComponent, decorators: [{
6336
6597
  type: Component,
@@ -6345,12 +6606,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
6345
6606
  NotificationContainerComponent,
6346
6607
  MetaModalHostComponent,
6347
6608
  AmfDialogHostComponent,
6348
- AmfDrawerHostComponent
6609
+ AmfDrawerHostComponent,
6610
+ ThemeCustomizerComponent
6349
6611
  ], template: `
6350
6612
  <app-notification-container></app-notification-container>
6351
6613
  <amf-modal-host></amf-modal-host>
6352
6614
  <amf-dialog-host></amf-dialog-host>
6353
6615
  <amf-drawer-host></amf-drawer-host>
6616
+ <app-theme-customizer></app-theme-customizer>
6354
6617
  @switch (config.currentLayout()) {
6355
6618
  @case (LayoutType.VERTICAL) {
6356
6619
  <app-vertical-layout></app-vertical-layout>
@@ -6392,5 +6655,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
6392
6655
  * Generated bundle index. Do not edit.
6393
6656
  */
6394
6657
 
6395
- export { APP_META_CONFIG_TOKEN, AccordionSectionComponent, ActionDispatcherService, AmfDialogHostComponent, AmfDrawerHostComponent, AppConfigService, AuthService, CardSectionComponent, ComponentRegistryService, ConditionEvaluatorService, FormRendererComponent, LayoutComponent, LayoutType, META_SECTION_CONFIG, META_SECTION_CONTEXT, MetaApiService, MetaModalHostComponent, MetaPageComponent, MetaRendererComponent, MetaRouterService, MetaStateService, NotificationService, PageHeaderSectionComponent, PluginRegistryService, RepeaterFieldComponent, StatsGridSectionComponent, StepperFormRendererComponent, TableRendererComponent, ThemeService, authGuard, authInterceptor, getAmfRoutes, initializeAvoraMetaForge, metaCanDeactivateGuard, metaGuard, provideAvoraMetaForge };
6658
+ export { APP_META_CONFIG_TOKEN, AccordionSectionComponent, ActionDispatcherService, AmfDialogHostComponent, AmfDrawerHostComponent, AppConfigService, AuthService, CardSectionComponent, ComponentRegistryService, ConditionEvaluatorService, DEFAULT_THEME_CONFIG, FormRendererComponent, LayoutComponent, LayoutType, META_SECTION_CONFIG, META_SECTION_CONTEXT, MetaApiService, MetaModalHostComponent, MetaPageComponent, MetaRendererComponent, MetaRouterService, MetaStateService, NotificationService, PageHeaderSectionComponent, PluginRegistryService, RepeaterFieldComponent, SURFACE_PRESETS, StatsGridSectionComponent, StepperFormRendererComponent, THEME_PRESETS, TableRendererComponent, ThemeService, authGuard, authInterceptor, getAmfRoutes, initializeAvoraMetaForge, metaCanDeactivateGuard, metaGuard, provideAvoraMetaForge };
6396
6659
  //# sourceMappingURL=avora-labs-meta-forge.mjs.map