@avora-labs/meta-forge 1.5.1 → 1.7.0

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';
@@ -5580,55 +5580,131 @@ const authInterceptor = (req, next) => {
5580
5580
  }));
5581
5581
  };
5582
5582
 
5583
- /**
5584
- * Manages runtime theming by toggling the `data-theme` attribute on the <body> element.
5585
- * Allows switching brand skins without recompiling styles.
5586
- */
5583
+ const THEME_PRESETS = [
5584
+ { name: 'cyan', primary: '#06b6d4', hover: '#0891b2', light: 'rgba(6, 182, 212, 0.15)', glow: 'rgba(6, 182, 212, 0.25)' },
5585
+ { name: 'blue', primary: '#3b82f6', hover: '#2563eb', light: 'rgba(59, 130, 246, 0.15)', glow: 'rgba(59, 130, 246, 0.25)' },
5586
+ { name: 'indigo', primary: '#6366f1', hover: '#4f46e5', light: 'rgba(99, 102, 241, 0.15)', glow: 'rgba(99, 102, 241, 0.25)' },
5587
+ { name: 'violet', primary: '#8b5cf6', hover: '#7c3aed', light: 'rgba(139, 92, 246, 0.15)', glow: 'rgba(139, 92, 246, 0.25)' },
5588
+ { name: 'rose', primary: '#f43f5e', hover: '#e11d48', light: 'rgba(244, 63, 94, 0.15)', glow: 'rgba(244, 63, 94, 0.25)' },
5589
+ { name: 'orange', primary: '#f97316', hover: '#ea580c', light: 'rgba(249, 115, 22, 0.15)', glow: 'rgba(249, 115, 22, 0.25)' },
5590
+ { name: 'emerald', primary: '#10b981', hover: '#059669', light: 'rgba(16, 185, 129, 0.15)', glow: 'rgba(16, 185, 129, 0.25)' },
5591
+ { name: 'green', primary: '#22c55e', hover: '#16a34a', light: 'rgba(34, 197, 94, 0.15)', glow: 'rgba(34, 197, 94, 0.25)' },
5592
+ { name: 'amber', primary: '#f59e0b', hover: '#d97706', light: 'rgba(245, 158, 11, 0.15)', glow: 'rgba(245, 158, 11, 0.25)' }
5593
+ ];
5594
+ const SURFACE_PRESETS = [
5595
+ { name: 'slate', bg: '#0f172a', bgSecondary: '#131d35', sidebarBg: 'rgba(15, 23, 42, 0.85)', navbarBg: 'rgba(15, 23, 42, 0.65)' },
5596
+ { name: 'gray', bg: '#111827', bgSecondary: '#1f2937', sidebarBg: 'rgba(17, 24, 39, 0.85)', navbarBg: 'rgba(17, 24, 39, 0.65)' },
5597
+ { name: 'zinc', bg: '#18181b', bgSecondary: '#27272a', sidebarBg: 'rgba(24, 24, 27, 0.85)', navbarBg: 'rgba(24, 24, 27, 0.65)' },
5598
+ { name: 'neutral', bg: '#171717', bgSecondary: '#262626', sidebarBg: 'rgba(23, 23, 23, 0.85)', navbarBg: 'rgba(23, 23, 23, 0.65)' },
5599
+ { name: 'stone', bg: '#1c1917', bgSecondary: '#292524', sidebarBg: 'rgba(28, 25, 23, 0.85)', navbarBg: 'rgba(28, 25, 23, 0.65)' }
5600
+ ];
5601
+ const DEFAULT_THEME_CONFIG = {
5602
+ primaryPreset: 'cyan',
5603
+ surfacePreset: 'slate',
5604
+ colorScheme: 'dark',
5605
+ menuType: 'static',
5606
+ menuProfile: 'end'
5607
+ };
5608
+
5587
5609
  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');
5610
+ platformId;
5611
+ STORAGE_KEY = 'amf-theme-config';
5612
+ // Expose configuration as a signal
5613
+ config = signal(DEFAULT_THEME_CONFIG, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
5614
+ // Customizer UI State
5615
+ customizerOpen = signal(false, ...(ngDevMode ? [{ debugName: "customizerOpen" }] : /* istanbul ignore next */ []));
5616
+ constructor(platformId) {
5617
+ this.platformId = platformId;
5618
+ if (isPlatformBrowser(this.platformId)) {
5619
+ this.loadConfig();
5620
+ // Sync theme signal to the DOM and persist
5621
+ effect(() => {
5622
+ const currentConfig = this.config();
5623
+ this.saveConfig(currentConfig);
5624
+ this.applyTheme(currentConfig);
5625
+ });
5626
+ }
5627
+ }
5628
+ updateConfig(partial) {
5629
+ this.config.update(c => ({ ...c, ...partial }));
5630
+ }
5631
+ loadConfig() {
5632
+ try {
5633
+ const stored = localStorage.getItem(this.STORAGE_KEY);
5634
+ // Legacy check
5635
+ const legacySaved = localStorage.getItem('app-theme');
5636
+ if (stored) {
5637
+ this.config.set({ ...DEFAULT_THEME_CONFIG, ...JSON.parse(stored) });
5602
5638
  }
5603
- else {
5604
- document.body.setAttribute('data-theme', theme);
5639
+ else if (legacySaved && legacySaved !== 'default') {
5640
+ // Fallback for old theme service
5641
+ this.config.update(c => ({ ...c, colorScheme: legacySaved === 'client-b' ? 'dark' : 'light' }));
5605
5642
  }
5606
- });
5607
- // Restore persisted theme
5608
- const saved = localStorage.getItem('app-theme');
5609
- if (saved) {
5610
- this.currentTheme.set(saved);
5643
+ }
5644
+ catch (e) {
5645
+ console.warn('Could not load theme config from localStorage', e);
5611
5646
  }
5612
5647
  }
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);
5648
+ saveConfig(config) {
5649
+ try {
5650
+ localStorage.setItem(this.STORAGE_KEY, JSON.stringify(config));
5651
+ }
5652
+ catch (e) {
5653
+ console.warn('Could not save theme config to localStorage', e);
5654
+ }
5624
5655
  }
5625
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5656
+ applyTheme(config) {
5657
+ const doc = document.documentElement;
5658
+ // 1. Apply Color Scheme (light/dark)
5659
+ if (config.colorScheme === 'dark') {
5660
+ doc.removeAttribute('data-color-scheme');
5661
+ }
5662
+ else {
5663
+ doc.setAttribute('data-color-scheme', 'light');
5664
+ }
5665
+ // 2. Apply Primary Colors
5666
+ const primaryPreset = THEME_PRESETS.find(p => p.name === config.primaryPreset) || THEME_PRESETS[0];
5667
+ this.applyPrimaryPalette(primaryPreset, doc);
5668
+ // 3. Apply Surface Colors (applicable if in dark mode)
5669
+ if (config.colorScheme === 'dark') {
5670
+ const surfacePreset = SURFACE_PRESETS.find(p => p.name === config.surfacePreset) || SURFACE_PRESETS[0];
5671
+ this.applySurfacePalette(surfacePreset, doc);
5672
+ }
5673
+ else {
5674
+ doc.style.removeProperty('--app-bg');
5675
+ doc.style.removeProperty('--app-bg-secondary');
5676
+ doc.style.removeProperty('--app-sidebar-bg');
5677
+ doc.style.removeProperty('--app-navbar-bg');
5678
+ }
5679
+ // 4. Apply Layout attributes (these will be handled by the shell layout)
5680
+ doc.setAttribute('data-menu-type', config.menuType);
5681
+ doc.setAttribute('data-menu-profile', config.menuProfile);
5682
+ }
5683
+ applyPrimaryPalette(preset, doc) {
5684
+ doc.style.setProperty('--app-primary', preset.primary);
5685
+ doc.style.setProperty('--app-primary-hover', preset.hover);
5686
+ doc.style.setProperty('--app-primary-light', preset.light);
5687
+ doc.style.setProperty('--app-glow', preset.glow);
5688
+ doc.style.setProperty('--app-gradient-1', preset.primary);
5689
+ }
5690
+ applySurfacePalette(preset, doc) {
5691
+ doc.style.setProperty('--app-bg', preset.bg);
5692
+ doc.style.setProperty('--app-bg-secondary', preset.bgSecondary);
5693
+ doc.style.setProperty('--app-sidebar-bg', preset.sidebarBg);
5694
+ doc.style.setProperty('--app-navbar-bg', preset.navbarBg);
5695
+ }
5696
+ 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
5697
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeService, providedIn: 'root' });
5627
5698
  }
5628
5699
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeService, decorators: [{
5629
5700
  type: Injectable,
5630
- args: [{ providedIn: 'root' }]
5631
- }], ctorParameters: () => [] });
5701
+ args: [{
5702
+ providedIn: 'root'
5703
+ }]
5704
+ }], ctorParameters: () => [{ type: Object, decorators: [{
5705
+ type: Inject,
5706
+ args: [PLATFORM_ID]
5707
+ }] }] });
5632
5708
 
5633
5709
  class SidebarComponent {
5634
5710
  config = inject(AppConfigService);
@@ -5788,16 +5864,7 @@ class NavbarComponent {
5788
5864
  iconRegistry = inject(IconRegistryService);
5789
5865
  themeService = inject(ThemeService);
5790
5866
  auth = inject(AuthService);
5791
- themeToggleOpen = signal(false, ...(ngDevMode ? [{ debugName: "themeToggleOpen" }] : /* istanbul ignore next */ []));
5792
5867
  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
5868
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NavbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5802
5869
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: NavbarComponent, isStandalone: true, selector: "app-navbar", ngImport: i0, template: `
5803
5870
  <header class="navbar-container">
@@ -5817,26 +5884,11 @@ class NavbarComponent {
5817
5884
  </div>
5818
5885
 
5819
5886
  <div class="right-section">
5820
- <!-- Theme Switcher -->
5821
- <button class="icon-btn theme-btn" (click)="themeToggleOpen.set(!themeToggleOpen())" title="Change Theme">
5887
+ <!-- Theme Customizer Settings -->
5888
+ <button class="icon-btn theme-btn" (click)="themeService.customizerOpen.set(true)" title="Theme Settings">
5822
5889
  <svg viewBox="0 0 24 24">
5823
- <path [attr.d]="iconRegistry.getIcon('palette')"></path>
5890
+ <path [attr.d]="iconRegistry.getIcon('settings')"></path>
5824
5891
  </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
5892
  </button>
5841
5893
 
5842
5894
  <!-- User Profile -->
@@ -5898,26 +5950,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
5898
5950
  </div>
5899
5951
 
5900
5952
  <div class="right-section">
5901
- <!-- Theme Switcher -->
5902
- <button class="icon-btn theme-btn" (click)="themeToggleOpen.set(!themeToggleOpen())" title="Change Theme">
5953
+ <!-- Theme Customizer Settings -->
5954
+ <button class="icon-btn theme-btn" (click)="themeService.customizerOpen.set(true)" title="Theme Settings">
5903
5955
  <svg viewBox="0 0 24 24">
5904
- <path [attr.d]="iconRegistry.getIcon('palette')"></path>
5956
+ <path [attr.d]="iconRegistry.getIcon('settings')"></path>
5905
5957
  </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
5958
  </button>
5922
5959
 
5923
5960
  <!-- User Profile -->
@@ -6291,6 +6328,242 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
6291
6328
  `, 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
6329
  }] });
6293
6330
 
6331
+ class ThemeCustomizerComponent {
6332
+ themeService = inject(ThemeService);
6333
+ iconRegistry = inject(IconRegistryService);
6334
+ presets = THEME_PRESETS;
6335
+ surfaces = SURFACE_PRESETS;
6336
+ menuTypes = ['static', 'overlay', 'slim', 'reveal', 'drawer', 'horizontal'];
6337
+ close() {
6338
+ this.themeService.customizerOpen.set(false);
6339
+ }
6340
+ setPrimary(name) {
6341
+ this.themeService.updateConfig({ primaryPreset: name });
6342
+ }
6343
+ setSurface(name) {
6344
+ this.themeService.updateConfig({ surfacePreset: name });
6345
+ }
6346
+ setScheme(scheme) {
6347
+ this.themeService.updateConfig({ colorScheme: scheme });
6348
+ }
6349
+ setMenuType(type) {
6350
+ this.themeService.updateConfig({ menuType: type });
6351
+ }
6352
+ setMenuProfile(profile) {
6353
+ this.themeService.updateConfig({ menuProfile: profile });
6354
+ }
6355
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeCustomizerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6356
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ThemeCustomizerComponent, isStandalone: true, selector: "app-theme-customizer", ngImport: i0, template: `
6357
+ <!-- Overlay -->
6358
+ @if (themeService.customizerOpen()) {
6359
+ <div class="customizer-overlay" (click)="close()"></div>
6360
+ }
6361
+
6362
+ <!-- Drawer -->
6363
+ <div class="customizer-drawer" [class.open]="themeService.customizerOpen()">
6364
+ <div class="drawer-header">
6365
+ <h2>Settings</h2>
6366
+ <button class="close-btn" (click)="close()">
6367
+ <svg viewBox="0 0 24 24"><path [attr.d]="iconRegistry.getIcon('close')"></path></svg>
6368
+ </button>
6369
+ </div>
6370
+
6371
+ <div class="drawer-body">
6372
+
6373
+ <!-- Primary Colors -->
6374
+ <div class="section">
6375
+ <h3>Primary</h3>
6376
+ <div class="color-grid">
6377
+ @for (preset of presets; track preset.name) {
6378
+ <button
6379
+ class="color-swatch"
6380
+ [style.background]="preset.primary"
6381
+ [class.active]="themeService.config().primaryPreset === preset.name"
6382
+ (click)="setPrimary(preset.name)"
6383
+ [title]="preset.name"
6384
+ ></button>
6385
+ }
6386
+ </div>
6387
+ </div>
6388
+
6389
+ <!-- Surface Colors -->
6390
+ <div class="section">
6391
+ <h3>Surface</h3>
6392
+ <div class="color-grid surface-grid">
6393
+ @for (surface of surfaces; track surface.name) {
6394
+ <button
6395
+ class="color-swatch"
6396
+ [style.background]="surface.bgSecondary"
6397
+ [class.active]="themeService.config().surfacePreset === surface.name"
6398
+ (click)="setSurface(surface.name)"
6399
+ [title]="surface.name"
6400
+ ></button>
6401
+ }
6402
+ </div>
6403
+ </div>
6404
+
6405
+ <!-- Color Scheme -->
6406
+ <div class="section">
6407
+ <h3>Color Scheme</h3>
6408
+ <div class="toggle-group">
6409
+ <button
6410
+ [class.active]="themeService.config().colorScheme === 'light'"
6411
+ (click)="setScheme('light')">Light</button>
6412
+ <button
6413
+ [class.active]="themeService.config().colorScheme === 'dark'"
6414
+ (click)="setScheme('dark')">Dark</button>
6415
+ </div>
6416
+ </div>
6417
+
6418
+ <!-- Menu Type -->
6419
+ <div class="section">
6420
+ <h3>Menu Type</h3>
6421
+ <div class="radio-group">
6422
+ @for (type of menuTypes; track type) {
6423
+ <label class="radio-label">
6424
+ <input type="radio" name="menuType"
6425
+ [value]="type"
6426
+ [checked]="themeService.config().menuType === type"
6427
+ (change)="setMenuType(type)">
6428
+ <span class="radio-custom"></span>
6429
+ {{ type | titlecase }}
6430
+ </label>
6431
+ }
6432
+ </div>
6433
+ </div>
6434
+
6435
+ <!-- Menu Profile -->
6436
+ <div class="section">
6437
+ <h3>Menu Profile</h3>
6438
+ <div class="radio-group row-group">
6439
+ <label class="radio-label">
6440
+ <input type="radio" name="menuProfile" value="start"
6441
+ [checked]="themeService.config().menuProfile === 'start'"
6442
+ (change)="setMenuProfile('start')">
6443
+ <span class="radio-custom"></span>
6444
+ Start
6445
+ </label>
6446
+ <label class="radio-label">
6447
+ <input type="radio" name="menuProfile" value="end"
6448
+ [checked]="themeService.config().menuProfile === 'end'"
6449
+ (change)="setMenuProfile('end')">
6450
+ <span class="radio-custom"></span>
6451
+ End
6452
+ </label>
6453
+ </div>
6454
+ </div>
6455
+
6456
+ </div>
6457
+ </div>
6458
+ `, 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" }] });
6459
+ }
6460
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ThemeCustomizerComponent, decorators: [{
6461
+ type: Component,
6462
+ args: [{ selector: 'app-theme-customizer', standalone: true, imports: [TitleCasePipe], template: `
6463
+ <!-- Overlay -->
6464
+ @if (themeService.customizerOpen()) {
6465
+ <div class="customizer-overlay" (click)="close()"></div>
6466
+ }
6467
+
6468
+ <!-- Drawer -->
6469
+ <div class="customizer-drawer" [class.open]="themeService.customizerOpen()">
6470
+ <div class="drawer-header">
6471
+ <h2>Settings</h2>
6472
+ <button class="close-btn" (click)="close()">
6473
+ <svg viewBox="0 0 24 24"><path [attr.d]="iconRegistry.getIcon('close')"></path></svg>
6474
+ </button>
6475
+ </div>
6476
+
6477
+ <div class="drawer-body">
6478
+
6479
+ <!-- Primary Colors -->
6480
+ <div class="section">
6481
+ <h3>Primary</h3>
6482
+ <div class="color-grid">
6483
+ @for (preset of presets; track preset.name) {
6484
+ <button
6485
+ class="color-swatch"
6486
+ [style.background]="preset.primary"
6487
+ [class.active]="themeService.config().primaryPreset === preset.name"
6488
+ (click)="setPrimary(preset.name)"
6489
+ [title]="preset.name"
6490
+ ></button>
6491
+ }
6492
+ </div>
6493
+ </div>
6494
+
6495
+ <!-- Surface Colors -->
6496
+ <div class="section">
6497
+ <h3>Surface</h3>
6498
+ <div class="color-grid surface-grid">
6499
+ @for (surface of surfaces; track surface.name) {
6500
+ <button
6501
+ class="color-swatch"
6502
+ [style.background]="surface.bgSecondary"
6503
+ [class.active]="themeService.config().surfacePreset === surface.name"
6504
+ (click)="setSurface(surface.name)"
6505
+ [title]="surface.name"
6506
+ ></button>
6507
+ }
6508
+ </div>
6509
+ </div>
6510
+
6511
+ <!-- Color Scheme -->
6512
+ <div class="section">
6513
+ <h3>Color Scheme</h3>
6514
+ <div class="toggle-group">
6515
+ <button
6516
+ [class.active]="themeService.config().colorScheme === 'light'"
6517
+ (click)="setScheme('light')">Light</button>
6518
+ <button
6519
+ [class.active]="themeService.config().colorScheme === 'dark'"
6520
+ (click)="setScheme('dark')">Dark</button>
6521
+ </div>
6522
+ </div>
6523
+
6524
+ <!-- Menu Type -->
6525
+ <div class="section">
6526
+ <h3>Menu Type</h3>
6527
+ <div class="radio-group">
6528
+ @for (type of menuTypes; track type) {
6529
+ <label class="radio-label">
6530
+ <input type="radio" name="menuType"
6531
+ [value]="type"
6532
+ [checked]="themeService.config().menuType === type"
6533
+ (change)="setMenuType(type)">
6534
+ <span class="radio-custom"></span>
6535
+ {{ type | titlecase }}
6536
+ </label>
6537
+ }
6538
+ </div>
6539
+ </div>
6540
+
6541
+ <!-- Menu Profile -->
6542
+ <div class="section">
6543
+ <h3>Menu Profile</h3>
6544
+ <div class="radio-group row-group">
6545
+ <label class="radio-label">
6546
+ <input type="radio" name="menuProfile" value="start"
6547
+ [checked]="themeService.config().menuProfile === 'start'"
6548
+ (change)="setMenuProfile('start')">
6549
+ <span class="radio-custom"></span>
6550
+ Start
6551
+ </label>
6552
+ <label class="radio-label">
6553
+ <input type="radio" name="menuProfile" value="end"
6554
+ [checked]="themeService.config().menuProfile === 'end'"
6555
+ (change)="setMenuProfile('end')">
6556
+ <span class="radio-custom"></span>
6557
+ End
6558
+ </label>
6559
+ </div>
6560
+ </div>
6561
+
6562
+ </div>
6563
+ </div>
6564
+ `, 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"] }]
6565
+ }] });
6566
+
6294
6567
  /**
6295
6568
  * The Root Renderer (Traffic Controller).
6296
6569
  * Decides which layout to load based on the currentLayout signal from AppConfigService.
@@ -6304,6 +6577,7 @@ class LayoutComponent {
6304
6577
  <amf-modal-host></amf-modal-host>
6305
6578
  <amf-dialog-host></amf-dialog-host>
6306
6579
  <amf-drawer-host></amf-drawer-host>
6580
+ <app-theme-customizer></app-theme-customizer>
6307
6581
  @switch (config.currentLayout()) {
6308
6582
  @case (LayoutType.VERTICAL) {
6309
6583
  <app-vertical-layout></app-vertical-layout>
@@ -6330,7 +6604,7 @@ class LayoutComponent {
6330
6604
  <app-vertical-layout></app-vertical-layout>
6331
6605
  }
6332
6606
  }
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" }] });
6607
+ `, 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
6608
  }
6335
6609
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LayoutComponent, decorators: [{
6336
6610
  type: Component,
@@ -6345,12 +6619,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
6345
6619
  NotificationContainerComponent,
6346
6620
  MetaModalHostComponent,
6347
6621
  AmfDialogHostComponent,
6348
- AmfDrawerHostComponent
6622
+ AmfDrawerHostComponent,
6623
+ ThemeCustomizerComponent
6349
6624
  ], template: `
6350
6625
  <app-notification-container></app-notification-container>
6351
6626
  <amf-modal-host></amf-modal-host>
6352
6627
  <amf-dialog-host></amf-dialog-host>
6353
6628
  <amf-drawer-host></amf-drawer-host>
6629
+ <app-theme-customizer></app-theme-customizer>
6354
6630
  @switch (config.currentLayout()) {
6355
6631
  @case (LayoutType.VERTICAL) {
6356
6632
  <app-vertical-layout></app-vertical-layout>
@@ -6392,5 +6668,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
6392
6668
  * Generated bundle index. Do not edit.
6393
6669
  */
6394
6670
 
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 };
6671
+ 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
6672
  //# sourceMappingURL=avora-labs-meta-forge.mjs.map