@memberjunction/ng-dashboards 5.39.0 → 5.40.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.
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +128 -4
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +548 -145
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -1
- package/dist/AI/components/autotagging/components/classify-item-drilldown.component.d.ts +56 -0
- package/dist/AI/components/autotagging/components/classify-item-drilldown.component.d.ts.map +1 -0
- package/dist/AI/components/autotagging/components/classify-item-drilldown.component.js +423 -0
- package/dist/AI/components/autotagging/components/classify-item-drilldown.component.js.map +1 -0
- package/dist/AI/components/autotagging/components/classify-item-grid.component.d.ts +70 -0
- package/dist/AI/components/autotagging/components/classify-item-grid.component.d.ts.map +1 -0
- package/dist/AI/components/autotagging/components/classify-item-grid.component.js +308 -0
- package/dist/AI/components/autotagging/components/classify-item-grid.component.js.map +1 -0
- package/dist/AI/components/autotagging/components/classify-org-context-editor.component.d.ts +29 -0
- package/dist/AI/components/autotagging/components/classify-org-context-editor.component.d.ts.map +1 -0
- package/dist/AI/components/autotagging/components/classify-org-context-editor.component.js +186 -0
- package/dist/AI/components/autotagging/components/classify-org-context-editor.component.js.map +1 -0
- package/dist/AI/components/autotagging/components/classify-overview-analytics.component.d.ts +69 -0
- package/dist/AI/components/autotagging/components/classify-overview-analytics.component.d.ts.map +1 -0
- package/dist/AI/components/autotagging/components/classify-overview-analytics.component.js +278 -0
- package/dist/AI/components/autotagging/components/classify-overview-analytics.component.js.map +1 -0
- package/dist/AI/components/autotagging/components/classify-seed-taxonomy.component.d.ts +73 -0
- package/dist/AI/components/autotagging/components/classify-seed-taxonomy.component.d.ts.map +1 -0
- package/dist/AI/components/autotagging/components/classify-seed-taxonomy.component.js +393 -0
- package/dist/AI/components/autotagging/components/classify-seed-taxonomy.component.js.map +1 -0
- package/dist/AI/components/autotagging/dialogs/classify-setup-wizard.component.d.ts +122 -0
- package/dist/AI/components/autotagging/dialogs/classify-setup-wizard.component.d.ts.map +1 -0
- package/dist/AI/components/autotagging/dialogs/classify-setup-wizard.component.js +908 -0
- package/dist/AI/components/autotagging/dialogs/classify-setup-wizard.component.js.map +1 -0
- package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.d.ts +100 -2
- package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.js +603 -213
- package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.js.map +1 -1
- package/dist/AI/components/autotagging/shared/classify.format.d.ts +15 -0
- package/dist/AI/components/autotagging/shared/classify.format.d.ts.map +1 -1
- package/dist/AI/components/autotagging/shared/classify.format.js +51 -0
- package/dist/AI/components/autotagging/shared/classify.format.js.map +1 -1
- package/dist/AI/components/autotagging/shared/classify.types.d.ts +43 -0
- package/dist/AI/components/autotagging/shared/classify.types.d.ts.map +1 -1
- package/dist/AI/components/autotagging/shared/classify.types.js.map +1 -1
- package/dist/AI/components/autotagging/tabs/history-tab.component.d.ts +38 -1
- package/dist/AI/components/autotagging/tabs/history-tab.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/tabs/history-tab.component.js +185 -68
- package/dist/AI/components/autotagging/tabs/history-tab.component.js.map +1 -1
- package/dist/AI/components/autotagging/tabs/pipeline-tab.component.d.ts +10 -1
- package/dist/AI/components/autotagging/tabs/pipeline-tab.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/tabs/pipeline-tab.component.js +249 -188
- package/dist/AI/components/autotagging/tabs/pipeline-tab.component.js.map +1 -1
- package/dist/AI/components/autotagging/tabs/sources-tab.component.d.ts +12 -1
- package/dist/AI/components/autotagging/tabs/sources-tab.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/tabs/sources-tab.component.js +377 -296
- package/dist/AI/components/autotagging/tabs/sources-tab.component.js.map +1 -1
- package/dist/AI/components/autotagging/tabs/tags-tab.component.d.ts +8 -0
- package/dist/AI/components/autotagging/tabs/tags-tab.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/tabs/tags-tab.component.js +112 -68
- package/dist/AI/components/autotagging/tabs/tags-tab.component.js.map +1 -1
- package/dist/AI/components/autotagging/tabs/types-tab.component.d.ts +9 -0
- package/dist/AI/components/autotagging/tabs/types-tab.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/tabs/types-tab.component.js +87 -36
- package/dist/AI/components/autotagging/tabs/types-tab.component.js.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +3 -0
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +15 -3
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
- package/dist/AI/components/execution-monitoring.component.js +1 -1
- package/dist/AI/components/execution-monitoring.component.js.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.d.ts +1 -0
- package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.js +28 -6
- package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts +3 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
- package/dist/AI/components/vectors/vector-management-resource.component.js +330 -302
- package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
- package/dist/APIKeys/api-applications-panel.component.js +2 -2
- package/dist/APIKeys/api-key-create-dialog.component.js +2 -2
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +31 -340
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +468 -1958
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/DataExplorer/data-explorer-resource.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-resource.component.js +10 -0
- package/dist/DataExplorer/data-explorer-resource.component.js.map +1 -1
- package/dist/KnowledgeHub/components/analytics/analytics-resource.component.d.ts.map +1 -1
- package/dist/KnowledgeHub/components/analytics/analytics-resource.component.js +12 -9
- package/dist/KnowledgeHub/components/analytics/analytics-resource.component.js.map +1 -1
- package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.d.ts +27 -2
- package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.d.ts.map +1 -1
- package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.js +244 -120
- package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.js.map +1 -1
- package/dist/KnowledgeHub/components/visualize/record-drilldown/record-drilldown.component.d.ts +65 -0
- package/dist/KnowledgeHub/components/visualize/record-drilldown/record-drilldown.component.d.ts.map +1 -0
- package/dist/KnowledgeHub/components/visualize/record-drilldown/record-drilldown.component.js +176 -0
- package/dist/KnowledgeHub/components/visualize/record-drilldown/record-drilldown.component.js.map +1 -0
- package/dist/KnowledgeHub/components/visualize/tag-cloud/tag-cloud.component.d.ts +81 -0
- package/dist/KnowledgeHub/components/visualize/tag-cloud/tag-cloud.component.d.ts.map +1 -0
- package/dist/KnowledgeHub/components/visualize/tag-cloud/tag-cloud.component.js +308 -0
- package/dist/KnowledgeHub/components/visualize/tag-cloud/tag-cloud.component.js.map +1 -0
- package/dist/KnowledgeHub/components/visualize/visualize-resource.component.d.ts +85 -0
- package/dist/KnowledgeHub/components/visualize/visualize-resource.component.d.ts.map +1 -0
- package/dist/KnowledgeHub/components/visualize/visualize-resource.component.js +362 -0
- package/dist/KnowledgeHub/components/visualize/visualize-resource.component.js.map +1 -0
- package/dist/KnowledgeHub/index.d.ts +3 -0
- package/dist/KnowledgeHub/index.d.ts.map +1 -1
- package/dist/KnowledgeHub/index.js +3 -0
- package/dist/KnowledgeHub/index.js.map +1 -1
- package/dist/MCP/components/mcp-server-dialog.component.js +2 -2
- package/dist/QueryBrowser/query-browser-resource.component.js +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
- package/dist/ai-dashboards.module.d.ts +48 -38
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +41 -1
- package/dist/ai-dashboards.module.js.map +1 -1
- package/dist/data-explorer-dashboards.module.d.ts +12 -14
- package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
- package/dist/data-explorer-dashboards.module.js +5 -14
- package/dist/data-explorer-dashboards.module.js.map +1 -1
- package/dist/public-api.d.ts +3 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +3 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +57 -55
- package/dist/DataExplorer/components/filter-dialog/filter-dialog.component.d.ts +0 -79
- package/dist/DataExplorer/components/filter-dialog/filter-dialog.component.d.ts.map +0 -1
- package/dist/DataExplorer/components/filter-dialog/filter-dialog.component.js +0 -195
- package/dist/DataExplorer/components/filter-dialog/filter-dialog.component.js.map +0 -1
- package/dist/DataExplorer/components/view-selector/view-selector.component.d.ts +0 -226
- package/dist/DataExplorer/components/view-selector/view-selector.component.d.ts.map +0 -1
- package/dist/DataExplorer/components/view-selector/view-selector.component.js +0 -861
- package/dist/DataExplorer/components/view-selector/view-selector.component.js.map +0 -1
|
@@ -29,8 +29,9 @@ import { Subject } from 'rxjs';
|
|
|
29
29
|
import { takeUntil } from 'rxjs/operators';
|
|
30
30
|
import { CompositeKey, RunQuery, RunView } from '@memberjunction/core';
|
|
31
31
|
import { KnowledgeHubMetadataEngine, UserInfoEngine } from '@memberjunction/core-entities';
|
|
32
|
+
import { TagEngineBase } from '@memberjunction/tag-engine-base';
|
|
32
33
|
import { RegisterClass, UUIDsEqual, NormalizeUUID } from '@memberjunction/global';
|
|
33
|
-
import { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';
|
|
34
|
+
import { BaseResourceComponent, NavigationService, ActivityService } from '@memberjunction/ng-shared';
|
|
34
35
|
import { GraphQLAIClient } from '@memberjunction/graphql-dataprovider';
|
|
35
36
|
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
36
37
|
import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
@@ -2555,6 +2556,7 @@ let TagsResourceComponent = class TagsResourceComponent extends BaseResourceComp
|
|
|
2555
2556
|
static { TagsResourceComponent_1 = this; }
|
|
2556
2557
|
destroy$ = new Subject();
|
|
2557
2558
|
cdr = inject(ChangeDetectorRef);
|
|
2559
|
+
activityService = inject(ActivityService);
|
|
2558
2560
|
navigationService = inject(NavigationService);
|
|
2559
2561
|
// ── Global state ──
|
|
2560
2562
|
IsLoading = true;
|
|
@@ -3153,6 +3155,10 @@ let TagsResourceComponent = class TagsResourceComponent extends BaseResourceComp
|
|
|
3153
3155
|
]);
|
|
3154
3156
|
this.loadClassifyPreferences();
|
|
3155
3157
|
this.applyIncomingConfiguration();
|
|
3158
|
+
// Restoring a non-default saved tab here changes ActiveTab (and the derived
|
|
3159
|
+
// header subtitle) after the first render already checked the default 'tags'.
|
|
3160
|
+
// Flush it in its own pass so the bindings don't trip NG0100.
|
|
3161
|
+
this.cdr.detectChanges();
|
|
3156
3162
|
// Tags dashboard's default landing is the Tag Library — load that data
|
|
3157
3163
|
// (and the entity-record-document cache used by drill-down) up front.
|
|
3158
3164
|
await Promise.all([this.loadTagLibraryData(), this.loadEntityRecordDocCache()]);
|
|
@@ -3161,6 +3167,9 @@ let TagsResourceComponent = class TagsResourceComponent extends BaseResourceComp
|
|
|
3161
3167
|
if (this.ActiveTab !== 'tags' && !this.tabDataLoaded.has(this.ActiveTab)) {
|
|
3162
3168
|
await this.loadTabData(this.ActiveTab);
|
|
3163
3169
|
this.tabDataLoaded.add(this.ActiveTab);
|
|
3170
|
+
// The eager load updates header-subtitle inputs (e.g. taxonomy tag
|
|
3171
|
+
// count); flush so the new subtitle doesn't trip NG0100 on next check.
|
|
3172
|
+
this.cdr.detectChanges();
|
|
3164
3173
|
}
|
|
3165
3174
|
this.IsLoading = false;
|
|
3166
3175
|
this.registerAgentTools();
|
|
@@ -3300,7 +3309,7 @@ let TagsResourceComponent = class TagsResourceComponent extends BaseResourceComp
|
|
|
3300
3309
|
get currentTabSubtitle() {
|
|
3301
3310
|
switch (this.ActiveTab) {
|
|
3302
3311
|
case 'tags': return `${this.TagRows.length} unique tags across all content sources`;
|
|
3303
|
-
case 'taxonomy': return
|
|
3312
|
+
case 'taxonomy': return 'Manage tag hierarchy, resolve duplicates, and monitor taxonomy health';
|
|
3304
3313
|
case 'suggestions': return `${this.SuggestionRows.length} pending · select rows for bulk approve / reject`;
|
|
3305
3314
|
case 'health': return 'Automated signals about taxonomy quality';
|
|
3306
3315
|
}
|
|
@@ -5323,13 +5332,18 @@ let TagsResourceComponent = class TagsResourceComponent extends BaseResourceComp
|
|
|
5323
5332
|
*/
|
|
5324
5333
|
async loadTaxonomyData() {
|
|
5325
5334
|
try {
|
|
5335
|
+
// Tags come from the TagEngineBase cache (browser-safe BaseEngine that
|
|
5336
|
+
// caches MJ: Tags and stays fresh via BaseEntity save/delete events) —
|
|
5337
|
+
// no need to RunView them here. Only tagged-items + audit logs are fetched.
|
|
5338
|
+
await TagEngineBase.Instance.Config(false, undefined, this.ProviderToUse);
|
|
5326
5339
|
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
5327
|
-
const [
|
|
5328
|
-
{ EntityName: 'MJ: Tags', OrderBy: 'Name', ResultType: 'simple' },
|
|
5340
|
+
const [taggedItemsResult, auditResult] = await rv.RunViews([
|
|
5329
5341
|
{ EntityName: 'MJ: Tagged Items', ResultType: 'simple' },
|
|
5330
5342
|
{ EntityName: 'MJ: Tag Audit Logs', OrderBy: '__mj_CreatedAt DESC', MaxRows: 200, ResultType: 'simple' }
|
|
5331
5343
|
]);
|
|
5332
|
-
this.tagsRaw =
|
|
5344
|
+
this.tagsRaw = TagEngineBase.Instance.Tags
|
|
5345
|
+
.map(t => t.GetAll())
|
|
5346
|
+
.sort((a, b) => String(a['Name'] ?? '').localeCompare(String(b['Name'] ?? '')));
|
|
5333
5347
|
this.taggedItemsRaw = taggedItemsResult.Success ? taggedItemsResult.Results : [];
|
|
5334
5348
|
this.tagAuditLogsRaw = auditResult.Success ? auditResult.Results : [];
|
|
5335
5349
|
// Also ensure content item tags are loaded for cross-referencing
|
|
@@ -7245,6 +7259,8 @@ let TagsResourceComponent = class TagsResourceComponent extends BaseResourceComp
|
|
|
7245
7259
|
return;
|
|
7246
7260
|
this.HealthRunning = true;
|
|
7247
7261
|
this.cdr.detectChanges();
|
|
7262
|
+
const activityID = this.activityService.Start('Tag health check', { icon: 'fa-solid fa-heart-pulse' });
|
|
7263
|
+
let healthOk = false;
|
|
7248
7264
|
try {
|
|
7249
7265
|
const provider = this.ProviderToUse;
|
|
7250
7266
|
if (!provider)
|
|
@@ -7273,12 +7289,18 @@ let TagsResourceComponent = class TagsResourceComponent extends BaseResourceComp
|
|
|
7273
7289
|
MJNotificationService.Instance.CreateSimpleNotification(`Tag Health: ${r.MergeCount} merge / ${r.LowUsageCount} low-usage / ${r.WideNodeCount} wide-node in ${r.DurationMs}ms.`, 'info', 4000);
|
|
7274
7290
|
// Pull in the new pending suggestions so Duplicates / Orphans / Suggestions all reflect
|
|
7275
7291
|
await this.loadSuggestions();
|
|
7292
|
+
healthOk = true;
|
|
7293
|
+
this.activityService.Complete(activityID, 'success', `${r.MergeCount} merge · ${r.LowUsageCount} low-usage · ${r.WideNodeCount} wide-node`);
|
|
7276
7294
|
}
|
|
7277
7295
|
catch (error) {
|
|
7278
7296
|
const msg = error instanceof Error ? error.message : String(error);
|
|
7279
7297
|
MJNotificationService.Instance.CreateSimpleNotification(`Tag Health failed: ${msg}`, 'error', 5000);
|
|
7298
|
+
this.activityService.Complete(activityID, 'error', msg);
|
|
7280
7299
|
}
|
|
7281
7300
|
finally {
|
|
7301
|
+
if (!healthOk && this.activityService.Activities.find(a => a.ID === activityID && a.Status === 'running')) {
|
|
7302
|
+
this.activityService.Complete(activityID, 'error');
|
|
7303
|
+
}
|
|
7282
7304
|
this.HealthRunning = false;
|
|
7283
7305
|
this.cdr.detectChanges();
|
|
7284
7306
|
}
|
|
@@ -7378,7 +7400,7 @@ export { TagsResourceComponent };
|
|
|
7378
7400
|
type: Component,
|
|
7379
7401
|
args: [{ standalone: false, selector: 'mj-tags-resource', encapsulation: ViewEncapsulation.None, template: "<mj-page-layout>\n <mj-page-header\n Title=\"Tags\"\n Icon=\"fa-solid fa-sitemap\"\n Subtitle=\"Taxonomy management, health monitoring, and tag library\">\n <div actions>\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"RunHealthNow()\" [disabled]=\"HealthRunning\">\n @if (HealthRunning) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> <span class=\"action-btn-label\">Running\u2026</span>\n } @else {\n <i class=\"fa-solid fa-heart-pulse\"></i> <span class=\"action-btn-label\">Run Tag Health</span>\n }\n </button>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\" [Padding]=\"false\">\n <!-- Tags Dashboard -->\n <div class=\"at-dashboard\">\n\n <mj-left-nav\n MobileTitle=\"Sections\"\n [Sections]=\"navSections\"\n [ActiveId]=\"ActiveTab\"\n (ItemClicked)=\"onNavItemClicked($event)\">\n </mj-left-nav>\n\n <mj-left-nav-content>\n\n <mj-page-header-interior\n [Title]=\"currentTabTitle\"\n [Subtitle]=\"currentTabSubtitle\">\n @if (ActiveTab === 'tags' || ActiveTab === 'taxonomy') {\n <div toolbar>\n @switch (ActiveTab) {\n @case ('tags') {\n <input type=\"text\" class=\"at-search-input\" placeholder=\"Search tags...\"\n [(ngModel)]=\"TagSearchQuery\" (input)=\"FilterTags()\">\n }\n @case ('taxonomy') {\n <mj-tab-nav\n [Tabs]=\"taxSubTabsConfig\"\n [ActiveKey]=\"TaxSubTab\"\n (TabChange)=\"onTaxSubTabChange($event)\">\n </mj-tab-nav>\n }\n }\n </div>\n }\n <div actions>\n @switch (ActiveTab) {\n @case ('taxonomy') {\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"RefreshTaxonomyData()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> <span class=\"mj-action-label\">Refresh</span>\n </button>\n }\n @case ('suggestions') {\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"loadSuggestions()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> <span class=\"mj-action-label\">Refresh</span>\n </button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"RunHealthNow()\" [disabled]=\"HealthRunning\">\n @if (HealthRunning) { <i class=\"fa-solid fa-spinner fa-spin\"></i> <span class=\"mj-action-label\">Running\u2026</span> }\n @else { <i class=\"fa-solid fa-heart-pulse\"></i> <span class=\"mj-action-label\">Run Tag Health</span> }\n </button>\n }\n @case ('health') {\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"RebuildEmbeddings()\" [disabled]=\"RebuildEmbeddingsRunning\">\n @if (RebuildEmbeddingsRunning) { <i class=\"fa-solid fa-spinner fa-spin\"></i> <span class=\"mj-action-label\">Rebuilding\u2026</span> }\n @else { <i class=\"fa-solid fa-rotate\"></i> <span class=\"mj-action-label\">Rebuild stale embeddings</span> }\n </button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"RunHealthNow()\" [disabled]=\"HealthRunning\">\n @if (HealthRunning) { <i class=\"fa-solid fa-spinner fa-spin\"></i> <span class=\"mj-action-label\">Running\u2026</span> }\n @else { <i class=\"fa-solid fa-play\"></i> <span class=\"mj-action-label\">Run now</span> }\n </button>\n }\n }\n </div>\n </mj-page-header-interior>\n\n <mj-page-body-interior [Padding]=\"false\" [Flex]=\"true\">\n <div class=\"at-main-area\">\n\n @if (IsLoading) {\n <div class=\"at-loading-overlay\">\n <mj-loading text=\"Loading tag data\u2026\"></mj-loading>\n </div>\n }\n\n @if (!IsLoading) {\n <!-- TAB 4: TAG LIBRARY -->\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ActiveTab === 'tags') {\n <div class=\"at-page-body\">\n <div class=\"at-tag-lib-layout\">\n <div class=\"at-tag-lib-main\">\n <div class=\"at-card\">\n <div class=\"at-card-body\" style=\"max-height: 500px; overflow-y: auto;\">\n <table class=\"at-tag-table\">\n <thead>\n <tr>\n <th>Tag</th>\n <th>Count</th>\n <th>Avg Weight</th>\n <th>Distribution</th>\n <th>Top Source</th>\n <th>First Seen</th>\n </tr>\n </thead>\n <tbody>\n @for (row of FilteredTagRows; track row.Tag) {\n <tr class=\"at-tag-row-clickable\" (click)=\"DrillDownTag(row.Tag)\"\n [class.at-tag-row-selected]=\"SelectedDrillDownTag === row.Tag\">\n <td class=\"at-tag-name-cell\">{{ row.Tag }}</td>\n <td>{{ row.UsageCount }}</td>\n <td>\n <div class=\"at-weight-indicator\">\n <div class=\"at-weight-bar\">\n <div class=\"at-weight-bar-fill\" [style.width.%]=\"row.AvgWeight * 100\"\n [class.at-weight-high]=\"row.AvgWeight >= 0.7\"\n [class.at-weight-medium]=\"row.AvgWeight >= 0.4 && row.AvgWeight < 0.7\"\n [class.at-weight-low]=\"row.AvgWeight < 0.4\"></div>\n </div>\n <span class=\"at-weight-value\">{{ row.AvgWeight.toFixed(2) }}</span>\n </div>\n </td>\n <td>\n <div class=\"at-tag-bar\">\n <div class=\"at-tag-bar-fill\" [style.width.%]=\"row.BarWidthPct\"></div>\n </div>\n </td>\n <td>{{ row.TopSource }}</td>\n <td>{{ row.FirstSeen }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <!-- Tag drill-down: content items matching selected tag -->\n @if (SelectedDrillDownTag) {\n <div class=\"at-card\" style=\"margin-top: 12px;\">\n <div class=\"at-card-header\">\n <span class=\"at-card-title\">\n <i class=\"fa-solid fa-tag\"></i>\n Content items tagged \"{{ SelectedDrillDownTag }}\"\n ({{ TagDrillDownItems.length }})\n </span>\n <button class=\"at-slide-close\" (click)=\"CloseDrillDownTag()\" style=\"background:none;border:none;cursor:pointer;color:var(--mj-text-muted)\" aria-label=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"at-card-body\" style=\"max-height: 300px; overflow-y: auto;\">\n @if (TagDrillDownItems.length === 0) {\n <div class=\"at-empty-state\"><p>No content items found for this tag.</p></div>\n } @else {\n <table class=\"at-tag-table\">\n <thead>\n <tr>\n <th>Name</th>\n <th>Source</th>\n <th>Weight</th>\n <th>Updated</th>\n </tr>\n </thead>\n <tbody>\n @for (di of TagDrillDownItems; track di.ID) {\n <tr class=\"at-tag-row-clickable\" (click)=\"OpenItemDetailByID(di.ID)\">\n <td>{{ di.Name }}</td>\n <td>{{ di.SourceName }}</td>\n <td>{{ FormatWeight(di.Weight) }}</td>\n <td>{{ di.UpdatedAt }}</td>\n </tr>\n }\n </tbody>\n </table>\n }\n </div>\n </div>\n }\n </div>\n <div class=\"at-tag-lib-sidebar\">\n <div class=\"at-card at-tag-cloud-card\">\n <div class=\"at-card-title\" style=\"margin-bottom: 12px;\"><i class=\"fa-solid fa-cloud\"></i> Tag Cloud</div>\n @if (TagCloudWordItems.length > 0) {\n <mj-word-cloud\n [Items]=\"TagCloudWordItems\"\n [MaxFontSize]=\"40\"\n [MinFontSize]=\"12\"\n [MaxItems]=\"20\"\n Layout=\"spiral\"\n ColorMode=\"weight-gradient\"\n [Interactive]=\"true\"\n [Animate]=\"true\">\n </mj-word-cloud>\n } @else {\n <div class=\"at-tag-cloud-empty\">No tags yet</div>\n }\n </div>\n <div class=\"at-card\" style=\"padding: 16px; margin-top: 12px;\">\n <div class=\"at-card-title\" style=\"margin-bottom: 10px;\"><i class=\"fa-solid fa-chart-pie\"></i> By Source</div>\n <div class=\"at-tags-by-source\">\n @for (s of TagsBySource; track s.SourceName) {\n <div class=\"at-tag-source-row\">\n <span>{{ s.SourceName }}</span>\n <strong>{{ s.Count }}</strong>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <!-- TAB 6: TAXONOMY GOVERNANCE -->\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ActiveTab === 'taxonomy') {\n\n <div class=\"at-page-body\">\n\n <!-- \u2550\u2550 SUB-TAB 1: TREE VIEW \u2550\u2550 -->\n @if (TaxSubTab === 'tree') {\n <div class=\"at-tax-split-view\">\n <!-- Tree panel -->\n <div class=\"at-tax-tree-panel\" style=\"position: relative;\">\n <div class=\"at-tax-tree-toolbar\">\n <input type=\"text\" class=\"at-search-input\" style=\"flex: 1;\" placeholder=\"Search tags...\"\n [(ngModel)]=\"TaxTreeSearch\" (input)=\"FilterTaxTree()\">\n <button class=\"at-tax-toolbar-btn\" title=\"Add root tag\" (click)=\"OpenCreateRootTag()\">\n <i class=\"fa-solid fa-plus\"></i>\n </button>\n <button class=\"at-tax-toolbar-btn\" [class.active]=\"TaxMultiSelectMode\"\n title=\"Toggle multi-select for drag reparenting\" (click)=\"ToggleMultiSelectMode()\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n <div class=\"at-tax-tree-body\"\n (dragover)=\"$event.preventDefault()\"\n (drop)=\"OnDropToRoot($event)\">\n @if (TaxFilteredNodes.length === 0) {\n <div class=\"at-empty-state\">\n <i class=\"fa-solid fa-sitemap\"></i>\n <p>No tags found</p>\n </div>\n }\n @for (node of TaxFilteredNodes; track node.ID) {\n <div class=\"at-tax-tree-node\"\n [class.at-tax-node-selected]=\"node.IsSelected\"\n [class.at-tax-node-drag-over]=\"TaxDragOverNodeID === node.ID\"\n [class.at-tax-node-multi-selected]=\"IsNodeMultiSelected(node.ID)\"\n [style.padding-left.px]=\"16 + node.Depth * 20\"\n [attr.draggable]=\"true\"\n (dragstart)=\"OnTreeNodeDragStart($event, node)\"\n (dragover)=\"OnTreeNodeDragOver($event, node)\"\n (dragleave)=\"OnTreeNodeDragLeave()\"\n (drop)=\"OnTreeNodeDrop($event, node); $event.stopPropagation()\"\n (click)=\"SelectTaxNode(node)\">\n @if (TaxMultiSelectMode) {\n <input type=\"checkbox\" class=\"at-tax-tree-checkbox\"\n [checked]=\"IsNodeMultiSelected(node.ID)\"\n (click)=\"ToggleNodeSelection(node, $event)\">\n }\n <span class=\"at-tax-tree-arrow\"\n [class.at-tax-arrow-expanded]=\"node.IsExpanded && node.Children.length > 0\"\n [class.at-tax-arrow-collapsed]=\"!node.IsExpanded && node.Children.length > 0\"\n [class.at-tax-arrow-leaf]=\"node.Children.length === 0\"\n (click)=\"ToggleTaxNode(node); $event.stopPropagation()\"></span>\n <span class=\"at-tax-health-dot\" [class]=\"node.HealthColor\"></span>\n <span class=\"at-tax-tree-label\" [class.at-tax-tree-label-selected]=\"node.IsSelected\">{{ node.Name }}</span>\n <span class=\"at-tax-tree-count\">({{ node.ItemCount }})</span>\n <span class=\"at-tax-tree-add-child\" title=\"Add child tag\"\n (click)=\"OpenCreateChildTagFor(node); $event.stopPropagation()\">\n <i class=\"fa-solid fa-plus\"></i>\n </span>\n </div>\n }\n </div>\n @if (TaxTreeSaving) {\n <div class=\"at-tax-tree-saving-overlay\">\n <mj-loading text=\"Moving tags...\" size=\"small\"></mj-loading>\n </div>\n }\n </div>\n\n <!-- Details panel -->\n <div class=\"at-tax-details-panel\">\n @if (TaxSelectedNode) {\n <div class=\"at-tax-details-header\">\n <!-- Breadcrumb -->\n <div class=\"at-tax-breadcrumb\">\n @for (bc of GetTaxBreadcrumb(TaxSelectedNode); track bc.ID) {\n <span class=\"at-tax-bc-link\" (click)=\"NavigateToBreadcrumb(bc.ID)\">{{ bc.Name }}</span>\n <span class=\"at-tax-bc-sep\">›</span>\n }\n <span class=\"at-tax-bc-current\">{{ TaxSelectedNode.Name }}</span>\n </div>\n\n @if (!TaxIsEditing) {\n <div class=\"at-tax-details-title\">\n {{ TaxSelectedNode.DisplayName }}\n <span class=\"at-tax-edit-icon\" (click)=\"StartEditTag()\" title=\"Edit name\">\n <i class=\"fa-solid fa-pen\"></i>\n </span>\n </div>\n @if (TaxSelectedNode.Description) {\n <div class=\"at-tax-details-desc\">{{ TaxSelectedNode.Description }}</div>\n }\n } @else {\n <div class=\"at-tax-edit-form\">\n <div class=\"at-form-group\">\n <label class=\"at-form-label\">Name</label>\n <input type=\"text\" class=\"at-form-input\" [(ngModel)]=\"TaxEditName\">\n </div>\n <div class=\"at-form-group\">\n <label class=\"at-form-label\">Description</label>\n <textarea class=\"at-form-textarea\" rows=\"3\" [(ngModel)]=\"TaxEditDescription\"></textarea>\n </div>\n <div class=\"at-form-actions\">\n <button class=\"at-action-btn at-primary-btn\" (click)=\"SaveEditTag()\">Save</button>\n <button class=\"at-action-btn at-secondary-btn\" (click)=\"CancelEditTag()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n\n <!-- Stats row -->\n <div class=\"at-tax-stats-row\">\n <div class=\"at-tax-stat-item\">\n <div class=\"at-tax-stat-value\">{{ TaxSelectedNode.ItemCount }}</div>\n <div class=\"at-tax-stat-label\">Items Tagged</div>\n </div>\n <div class=\"at-tax-stat-item\">\n <div class=\"at-tax-stat-value\">{{ TaxSelectedNode.AvgWeight.toFixed(2) }}</div>\n <div class=\"at-tax-stat-label\">Avg Weight</div>\n </div>\n <div class=\"at-tax-stat-item\">\n <div class=\"at-tax-stat-value\">{{ TaxSelectedNode.Children.length }}</div>\n <div class=\"at-tax-stat-label\">Children</div>\n </div>\n <div class=\"at-tax-stat-item\">\n <div class=\"at-tax-stat-value\">{{ TaxSelectedNode.Depth }}</div>\n <div class=\"at-tax-stat-label\">Depth</div>\n </div>\n <div class=\"at-tax-stat-item\">\n <div class=\"at-tax-stat-value at-tax-stat-date\">{{ TaxSelectedNode.FirstSeen }}</div>\n <div class=\"at-tax-stat-label\">First Seen</div>\n </div>\n </div>\n\n <!-- Action toolbar -->\n @if (!TaxIsEditing) {\n <div class=\"at-tax-action-toolbar\">\n <button class=\"at-tax-action-btn\" (click)=\"OpenCreateChildTag()\"><i class=\"fa-solid fa-plus\"></i> Add Child</button>\n <button class=\"at-tax-action-btn\" (click)=\"StartEditTag()\"><i class=\"fa-solid fa-pen\"></i> Rename</button>\n <button class=\"at-tax-action-btn\" (click)=\"OpenMoveDialog(TaxSelectedNode)\"><i class=\"fa-solid fa-arrows-up-down\"></i> Move</button>\n <button class=\"at-tax-action-btn\" (click)=\"OpenMergeIntoDialog(TaxSelectedNode)\"><i class=\"fa-solid fa-compress\"></i> Merge Into...</button>\n <button class=\"at-tax-action-btn\" (click)=\"OpenSplitDialog(TaxSelectedNode)\"><i class=\"fa-solid fa-code-branch\"></i> Split</button>\n <button class=\"at-tax-action-btn at-tax-action-danger\" (click)=\"DeleteTag(TaxSelectedNode)\"><i class=\"fa-solid fa-trash\"></i> Delete</button>\n </div>\n }\n\n <!-- Child tags -->\n @if (TaxSelectedNode.Children.length > 0) {\n <div class=\"at-tax-detail-section\">\n <div class=\"at-tax-section-title\">Child Tags</div>\n <div class=\"at-tax-child-chips\">\n @for (child of TaxSelectedNode.Children; track child.ID) {\n <span class=\"at-tax-child-chip\" (click)=\"SelectTaxNode(child)\">\n {{ child.Name }}\n <span class=\"at-tax-chip-count\">{{ child.ItemCount }}</span>\n </span>\n }\n </div>\n </div>\n }\n\n <!-- Recently tagged items -->\n @if (TaxRecentItems.length > 0) {\n <div class=\"at-tax-detail-section\">\n <div class=\"at-tax-section-title\">Recently Tagged Items</div>\n <div class=\"at-tax-recent-list\">\n @for (item of TaxRecentItems; track $index) {\n <div class=\"at-tax-recent-item\">\n <div class=\"at-tax-recent-icon\"><i [class]=\"item.Icon\"></i></div>\n <div class=\"at-tax-recent-name\">{{ item.Name }}</div>\n <div class=\"at-tax-recent-weight\">{{ item.Weight.toFixed(2) }}</div>\n <div class=\"at-tax-recent-date\">{{ item.Date }}</div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500\u2500 Governance \u2014 per-node controls for the autotagger \u2500\u2500\u2500 -->\n @if (SelectedTagFull) {\n <div class=\"at-tax-detail-section\">\n <div class=\"at-tax-section-title\">Governance \u2014 how the autotagger may grow this subtree</div>\n\n <div class=\"at-tg-toggle-row\">\n <div class=\"at-tg-toggle-label\">\n <span class=\"at-tg-toggle-t\">Allow auto-grow under this tag</span>\n <span class=\"at-tg-toggle-d\">Lets the classifier create child tags below this one. Off = constrained subtree.</span>\n </div>\n <div class=\"at-tg-switch\" [class.on]=\"SelectedTagFull.AllowAutoGrow\"\n [class.disabled]=\"SelectedTagSavingField['AllowAutoGrow']\"\n (click)=\"ToggleGovernanceFlag('AllowAutoGrow')\"></div>\n </div>\n\n <div class=\"at-tg-toggle-row\">\n <div class=\"at-tg-toggle-label\">\n <span class=\"at-tg-toggle-t\">Freeze this subtree</span>\n <span class=\"at-tg-toggle-d\">Locks this entire subtree against auto-creation, regardless of mode.</span>\n </div>\n <div class=\"at-tg-switch\" [class.on]=\"SelectedTagFull.IsFrozen\"\n [class.disabled]=\"SelectedTagSavingField['IsFrozen']\"\n (click)=\"ToggleGovernanceFlag('IsFrozen')\"></div>\n </div>\n\n <div class=\"at-tg-toggle-row\">\n <div class=\"at-tg-toggle-label\">\n <span class=\"at-tg-toggle-t\">Requires review for matches</span>\n <span class=\"at-tg-toggle-d\">High-confidence matches against this tag are routed to the suggestion queue first.</span>\n </div>\n <div class=\"at-tg-switch\" [class.on]=\"SelectedTagFull.RequiresReview\"\n [class.disabled]=\"SelectedTagSavingField['RequiresReview']\"\n (click)=\"ToggleGovernanceFlag('RequiresReview')\"></div>\n </div>\n\n <div class=\"at-tg-grid-3\">\n <div class=\"at-tg-field\">\n <label>Max children</label>\n <input class=\"at-search-input\" type=\"number\" min=\"0\"\n [value]=\"SelectedTagFull.MaxChildren ?? ''\"\n (change)=\"SaveSelectedTagNumber('MaxChildren', $any($event.target).value)\" />\n <span class=\"at-tg-hint\">Hard cap on direct children. Blank = unlimited.</span>\n </div>\n <div class=\"at-tg-field\">\n <label>Max descendant depth</label>\n <input class=\"at-search-input\" type=\"number\" min=\"0\"\n [value]=\"SelectedTagFull.MaxDescendantDepth ?? ''\"\n (change)=\"SaveSelectedTagNumber('MaxDescendantDepth', $any($event.target).value)\" />\n <span class=\"at-tg-hint\">From this node down. 0 = leaf-only.</span>\n </div>\n <div class=\"at-tg-field\">\n <label>Min confidence</label>\n <input class=\"at-search-input\" type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [value]=\"SelectedTagFull.MinWeight ?? ''\"\n (change)=\"SaveSelectedTagNumber('MinWeight', $any($event.target).value)\" />\n <span class=\"at-tg-hint\">Floor for matches against this tag.</span>\n </div>\n </div>\n </div>\n\n <!-- \u2500\u2500\u2500 Scope \u2014 who can see this tag \u2500\u2500\u2500 -->\n <div class=\"at-tax-detail-section\">\n <div class=\"at-tax-section-title\">Scope \u2014 who can see and tag with this</div>\n\n <div class=\"at-tg-toggle-row\">\n <div class=\"at-tg-toggle-label\">\n <span class=\"at-tg-toggle-t\">Global tag (visible to all tenants)</span>\n <span class=\"at-tg-toggle-d\">\n Off = restricted to the scope rows below.\n @if (IsGlobalLocked()) { <strong>Locked while scope rows exist.</strong> }\n </span>\n </div>\n <div class=\"at-tg-switch\" [class.on]=\"SelectedTagFull.IsGlobal\"\n [class.disabled]=\"IsGlobalLocked() || SelectedTagSavingField['IsGlobal']\"\n (click)=\"ToggleGovernanceFlag('IsGlobal')\"></div>\n </div>\n\n <div class=\"at-tg-chip-list\">\n @for (s of SelectedTagScopes; track s.ID) {\n <span class=\"at-tg-chip scoped\">{{ s.EntityName }} \u00B7 {{ s.DisplayName }}</span>\n }\n @if (SelectedTagScopes.length === 0) {\n <span class=\"at-tg-hint\" style=\"font-style: italic;\">\n No scope rows. {{ SelectedTagFull.IsGlobal ? 'Tag is global.' : 'Tag is non-global with no scope \u2014 currently unreachable.' }}\n </span>\n }\n <button class=\"at-action-btn at-btn-sm\" (click)=\"OpenScopeDialog()\">\n <i class=\"fa-solid fa-pencil\"></i> Edit scope\u2026\n </button>\n </div>\n <div class=\"at-tg-hint\" style=\"margin-top: 8px;\">\n <i class=\"fa-solid fa-info-circle\"></i> Children inherit this scope automatically. Promotion to global requires admin approval.\n </div>\n </div>\n\n <!-- \u2500\u2500\u2500 Synonyms \u2014 alternate names that resolve to this tag \u2500\u2500\u2500 -->\n <div class=\"at-tax-detail-section\">\n <div class=\"at-tax-section-title\">Synonyms \u2014 alternate names that resolve to this tag</div>\n\n <div class=\"at-tg-syn-add\">\n <input class=\"at-search-input\" placeholder='e.g. \"GenAI\"'\n [(ngModel)]=\"NewSynonymName\"\n (keyup.enter)=\"AddSynonym()\" />\n <select class=\"at-search-input\" style=\"width: 130px;\"\n [(ngModel)]=\"NewSynonymSource\">\n <option value=\"Manual\">Manual</option>\n <option value=\"Imported\">Imported</option>\n <option value=\"Merged\">Merged</option>\n <option value=\"LLM\">LLM</option>\n </select>\n <button class=\"at-action-btn at-primary-btn\" (click)=\"AddSynonym()\" [disabled]=\"!NewSynonymName.trim()\">+ Add</button>\n </div>\n\n @if (SelectedTagSynonyms.length === 0) {\n <div class=\"at-tg-hint\" style=\"font-style: italic;\">No synonyms yet.</div>\n } @else {\n @for (s of SelectedTagSynonyms; track s.ID) {\n <div class=\"at-tg-syn-row\" [class.pending]=\"s.Source === 'LLM'\">\n <div class=\"at-tg-syn-name\">{{ s.Synonym }}</div>\n <span class=\"at-tg-syn-src {{ s.Source.toLowerCase() }}\">{{ s.Source }}</span>\n <span class=\"at-tg-syn-x\" title=\"Remove\" (click)=\"RemoveSynonym(s)\"><i class=\"fa-solid fa-xmark\"></i></span>\n </div>\n }\n }\n </div>\n }\n } @else {\n <div class=\"at-empty-state\" style=\"height: 100%;\">\n <i class=\"fa-solid fa-hand-pointer\"></i>\n <p>Select a tag from the tree to view details</p>\n </div>\n }\n </div>\n </div>\n\n <!-- Health bar -->\n <div class=\"at-tax-health-bar\">\n <span class=\"at-tax-health-label\">Taxonomy Health</span>\n <div class=\"at-tax-health-stat\">\n <span class=\"at-tax-dot at-tax-dot-total\"></span>\n <span class=\"at-tax-health-value\">{{ TaxHealth.Total }}</span>\n <span class=\"at-tax-health-text\">Total</span>\n </div>\n <div class=\"at-tax-health-stat\">\n <span class=\"at-tax-dot at-tax-dot-healthy\"></span>\n <span class=\"at-tax-health-value at-tax-val-success\">{{ TaxHealth.Healthy }}</span>\n <span class=\"at-tax-health-text\">Healthy</span>\n </div>\n <div class=\"at-tax-health-stat\">\n <span class=\"at-tax-dot at-tax-dot-attention\"></span>\n <span class=\"at-tax-health-value at-tax-val-warning\">{{ TaxHealth.NeedAttention }}</span>\n <span class=\"at-tax-health-text\">Need Attention</span>\n </div>\n <div class=\"at-tax-health-stat\">\n <span class=\"at-tax-dot at-tax-dot-orphaned\"></span>\n <span class=\"at-tax-health-value at-tax-val-error\">{{ TaxHealth.Orphaned }}</span>\n <span class=\"at-tax-health-text\">Orphaned</span>\n </div>\n <div class=\"at-tax-health-stat\">\n <span class=\"at-tax-dot at-tax-dot-duplicates\"></span>\n <span class=\"at-tax-health-value at-tax-val-info\">{{ TaxHealth.Duplicates }}</span>\n <span class=\"at-tax-health-text\">Duplicate Candidates</span>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550 SUB-TAB 2: DUPLICATES \u2550\u2550 -->\n @if (TaxSubTab === 'duplicates') {\n <div class=\"at-tax-dup-stats-bar\">\n <div class=\"at-tax-dup-stat\"><strong>{{ TaxDuplicates.length }}</strong> candidates found</div>\n <div class=\"at-tax-dup-stat at-tax-dup-high\"><strong>{{ TaxHighConfidenceDupeCount }}</strong> high confidence (>85%)</div>\n <div class=\"at-tax-dup-stat at-tax-dup-moderate\"><strong>{{ TaxModerateDupeCount }}</strong> moderate (70-85%)</div>\n </div>\n\n @if (TaxDuplicates.length === 0) {\n <div class=\"at-empty-state\">\n <i class=\"fa-solid fa-check-circle\"></i>\n <p>No duplicate tags detected</p>\n </div>\n }\n\n <div class=\"at-tax-dup-list\">\n @for (pair of TaxDuplicates; track $index) {\n <div class=\"at-tax-dup-card\" [class.at-tax-dup-high]=\"pair.SeverityClass === 'high'\" [class.at-tax-dup-moderate]=\"pair.SeverityClass === 'moderate'\">\n @if (pair.IsExactDuplicate) {\n <!-- Exact-name duplicates: show single tag name with count -->\n <div class=\"at-tax-dup-tag\">{{ pair.TagA }}</div>\n <div class=\"at-tax-dup-similarity\">\n <span class=\"at-tax-sim-percent high\">\n <i class=\"fa-solid fa-clone\"></i> {{ pair.ExactDuplicateCount }} identical records\n </span>\n </div>\n <div class=\"at-tax-dup-actions\">\n <button class=\"at-tax-dup-btn at-tax-dup-btn-primary\" (click)=\"MergeTags(pair.TagAID, pair.TagBID, pair.TagA, pair.TagB)\" [disabled]=\"IsMerging\">@if (IsMerging) { <i class=\"fa-solid fa-spinner fa-spin\"></i> Merging... } @else { Merge }</button>\n <button class=\"at-tax-dup-btn\" (click)=\"DismissDuplicate(pair)\">Dismiss</button>\n </div>\n } @else {\n <!-- Similar (non-exact) pairs: show both tags with similarity -->\n <div class=\"at-tax-dup-tag\">{{ pair.TagA }}</div>\n <div class=\"at-tax-dup-arrow\"><i class=\"fa-solid fa-arrows-left-right\"></i></div>\n <div class=\"at-tax-dup-similarity\">\n <div class=\"at-tax-sim-bar-bg\">\n <div class=\"at-tax-sim-bar-fill\" [class]=\"pair.SeverityClass\" [style.width.%]=\"pair.Similarity\"></div>\n </div>\n <span class=\"at-tax-sim-percent\" [class]=\"pair.SeverityClass\">{{ pair.Similarity }}%</span>\n </div>\n <div class=\"at-tax-dup-arrow\"><i class=\"fa-solid fa-arrows-left-right\"></i></div>\n <div class=\"at-tax-dup-tag\">{{ pair.TagB }}</div>\n <div class=\"at-tax-dup-actions\">\n <button class=\"at-tax-dup-btn at-tax-dup-btn-primary\" (click)=\"MergeTags(pair.TagAID, pair.TagBID, pair.TagA, pair.TagB)\" [disabled]=\"IsMerging\">@if (IsMerging) { <i class=\"fa-solid fa-spinner fa-spin\"></i> Merging... } @else { Merge }</button>\n <button class=\"at-tax-dup-btn\" (click)=\"MakeChildTag(pair.TagAID, pair.TagBID)\">Make Child</button>\n <button class=\"at-tax-dup-btn\" (click)=\"DismissDuplicate(pair)\">Dismiss</button>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550 SUB-TAB 3: ORPHANS \u2550\u2550 -->\n @if (TaxSubTab === 'orphans') {\n <div class=\"at-tax-orphan-toolbar\">\n <span class=\"at-tax-orphan-count\">{{ TaxOrphans.length }} orphaned tags</span>\n <span class=\"at-tax-orphan-desc\">— no parent, no children, low usage</span>\n <div class=\"at-tax-orphan-bulk\">\n <button class=\"at-tax-bulk-btn\" (click)=\"ToggleAllOrphans()\">\n @if (TaxAllOrphansSelected) { <i class=\"fa-solid fa-square-check\"></i> } @else { <i class=\"fa-regular fa-square\"></i> }\n Select All\n </button>\n <button class=\"at-tax-bulk-btn at-tax-bulk-danger\" (click)=\"BulkDeleteOrphans()\">\n <i class=\"fa-solid fa-trash\"></i> Bulk Delete\n </button>\n <button class=\"at-tax-bulk-btn at-tax-bulk-danger\" (click)=\"DeleteAllOrphans()\">\n <i class=\"fa-solid fa-trash-can\"></i> Delete All ({{ TaxOrphans.length }})\n </button>\n </div>\n </div>\n\n @if (TaxOrphans.length === 0) {\n <div class=\"at-empty-state\">\n <i class=\"fa-solid fa-check-circle\"></i>\n <p>No orphaned tags</p>\n </div>\n }\n\n <div class=\"at-tax-orphan-grid\">\n @for (orphan of TaxOrphans; track orphan.ID) {\n <div class=\"at-tax-orphan-card\">\n <div class=\"at-tax-orphan-header\">\n <span class=\"at-tax-orphan-name\">{{ orphan.Name }}</span>\n <input type=\"checkbox\" class=\"at-tax-orphan-checkbox\" [checked]=\"orphan.IsSelected\"\n (change)=\"ToggleOrphanSelection(orphan)\" (click)=\"$event.stopPropagation()\">\n </div>\n <div class=\"at-tax-orphan-stats\">\n <span>Usage: <strong>{{ orphan.UsageCount }}</strong></span>\n <span>Avg Weight: <strong>{{ orphan.AvgWeight.toFixed(2) }}</strong></span>\n </div>\n <div class=\"at-tax-orphan-dates\">\n <span>First: {{ orphan.FirstSeen }}</span>\n <span>Last: {{ orphan.LastSeen }}</span>\n </div>\n <div class=\"at-tax-orphan-actions\">\n <button class=\"at-tax-orphan-btn at-tax-orphan-delete\" (click)=\"DeleteOrphan(orphan)\">Delete</button>\n <button class=\"at-tax-orphan-btn\">Ignore</button>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550 SUB-TAB 4: TREEMAP \u2550\u2550 -->\n @if (TaxSubTab === 'treemap') {\n <div class=\"at-tax-treemap-kpi-strip\">\n @for (kpi of TaxTreemapKPIs; track kpi.Label) {\n <div class=\"at-tax-treemap-kpi\">\n <div class=\"at-tax-treemap-kpi-value\">{{ kpi.Value }}</div>\n <div class=\"at-tax-treemap-kpi-label\">{{ kpi.Label }}</div>\n </div>\n }\n </div>\n\n @if (TaxTreemapCells.length === 0) {\n <div class=\"at-empty-state\">\n <i class=\"fa-solid fa-chart-tree-map\"></i>\n <p>No taxonomy data to visualize</p>\n </div>\n } @else {\n <div class=\"at-tax-treemap-container\">\n @for (cell of TaxTreemapCells; track $index) {\n <div class=\"at-tax-treemap-cell at-tax-treemap-cell-clickable\" [class]=\"cell.ColorClass\"\n [style.grid-row]=\"cell.RowSpan > 1 ? 'span ' + cell.RowSpan : ''\"\n (click)=\"OpenTreemapDrillIn(cell)\">\n <span class=\"at-tax-cell-name\">{{ cell.Name }}</span>\n <span class=\"at-tax-cell-count\">{{ cell.ItemCount }} items</span>\n </div>\n }\n </div>\n }\n\n <!-- Treemap Drill-In Panel -->\n @if (ShowTreemapDrillIn && TreemapDrillInNode) {\n <div class=\"at-tax-drillin-overlay\" (click)=\"CloseTreemapDrillIn()\">\n <div class=\"at-tax-drillin-panel\" (click)=\"$event.stopPropagation()\">\n <div class=\"at-tax-drillin-header\">\n <h3><i class=\"fa-solid fa-tag\"></i> {{ TreemapDrillInNode.Name }}</h3>\n <button class=\"at-schedule-dialog-close\" (click)=\"CloseTreemapDrillIn()\" aria-label=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"at-tax-drillin-body\">\n <div class=\"at-tax-stats-row\">\n <div class=\"at-tax-stat-item\">\n <div class=\"at-tax-stat-value\">{{ TreemapDrillInNode.ItemCount }}</div>\n <div class=\"at-tax-stat-label\">Items Tagged</div>\n </div>\n <div class=\"at-tax-stat-item\">\n <div class=\"at-tax-stat-value\">{{ TreemapDrillInNode.AvgWeight.toFixed(2) }}</div>\n <div class=\"at-tax-stat-label\">Avg Weight</div>\n </div>\n <div class=\"at-tax-stat-item\">\n <div class=\"at-tax-stat-value\">{{ TreemapDrillInNode.Children.length }}</div>\n <div class=\"at-tax-stat-label\">Children</div>\n </div>\n </div>\n\n @if (TreemapDrillInNode.Children.length > 0) {\n <div class=\"at-tax-detail-section\">\n <div class=\"at-tax-section-title\">Child Tags</div>\n <div class=\"at-tax-child-chips\">\n @for (child of TreemapDrillInNode.Children; track child.ID) {\n <span class=\"at-tax-child-chip\">{{ child.Name }} <span class=\"at-tax-chip-count\">{{ child.ItemCount }}</span></span>\n }\n </div>\n </div>\n }\n\n @if (TaxRecentItems.length > 0) {\n <div class=\"at-tax-detail-section\">\n <div class=\"at-tax-section-title\">Recently Tagged Items</div>\n <div class=\"at-tax-recent-list\">\n @for (item of TaxRecentItems; track $index) {\n <div class=\"at-tax-recent-item\">\n <div class=\"at-tax-recent-icon\"><i [class]=\"item.Icon\"></i></div>\n <div class=\"at-tax-recent-name\">{{ item.Name }}</div>\n <div class=\"at-tax-recent-weight\">{{ item.Weight.toFixed(2) }}</div>\n <div class=\"at-tax-recent-date\">{{ item.Date }}</div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n <div class=\"at-tax-drillin-footer\">\n <button class=\"at-action-btn at-secondary-btn\" (click)=\"DrillInToTreeView(TreemapDrillInNode)\">\n <i class=\"fa-solid fa-sitemap\"></i> View in Tree\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- \u2550\u2550 SUB-TAB 5: AUDIT LOG \u2550\u2550 -->\n @if (TaxSubTab === 'audit') {\n <div class=\"at-tax-audit-filters\">\n <span class=\"at-tax-audit-filter-label\">Filter</span>\n <div class=\"at-tax-audit-checkbox-group\">\n <label class=\"at-tax-audit-checkbox\">\n <input type=\"checkbox\" [checked]=\"TaxAuditFilterTypes.has('created')\" (change)=\"ToggleTaxAuditFilter('created')\"> Created\n </label>\n <label class=\"at-tax-audit-checkbox\">\n <input type=\"checkbox\" [checked]=\"TaxAuditFilterTypes.has('merged')\" (change)=\"ToggleTaxAuditFilter('merged')\"> Merged\n </label>\n <label class=\"at-tax-audit-checkbox\">\n <input type=\"checkbox\" [checked]=\"TaxAuditFilterTypes.has('moved')\" (change)=\"ToggleTaxAuditFilter('moved')\"> Moved\n </label>\n <label class=\"at-tax-audit-checkbox\">\n <input type=\"checkbox\" [checked]=\"TaxAuditFilterTypes.has('deleted')\" (change)=\"ToggleTaxAuditFilter('deleted')\"> Deleted\n </label>\n <label class=\"at-tax-audit-checkbox\">\n <input type=\"checkbox\" [checked]=\"TaxAuditFilterTypes.has('renamed')\" (change)=\"ToggleTaxAuditFilter('renamed')\"> Renamed\n </label>\n <label class=\"at-tax-audit-checkbox\">\n <input type=\"checkbox\" [checked]=\"TaxAuditFilterTypes.has('deprecated')\" (change)=\"ToggleTaxAuditFilter('deprecated')\"> Deprecated\n </label>\n <label class=\"at-tax-audit-checkbox\">\n <input type=\"checkbox\" [checked]=\"TaxAuditFilterTypes.has('split')\" (change)=\"ToggleTaxAuditFilter('split')\"> Split\n </label>\n <label class=\"at-tax-audit-checkbox\">\n <input type=\"checkbox\" [checked]=\"TaxAuditFilterTypes.has('reactivated')\" (change)=\"ToggleTaxAuditFilter('reactivated')\"> Reactivated\n </label>\n </div>\n </div>\n\n @if (GetFilteredAuditEvents().length === 0) {\n <div class=\"at-empty-state\">\n <i class=\"fa-solid fa-scroll\"></i>\n <p>No audit events match the current filters</p>\n </div>\n }\n\n <div class=\"at-tax-audit-timeline\">\n @for (event of GetFilteredAuditEvents(); track $index) {\n <div class=\"at-tax-audit-event\">\n <div class=\"at-tax-audit-event-icon\" [class]=\"event.Type\">\n <i [class]=\"GetTaxAuditIcon(event.Type)\"></i>\n </div>\n <div class=\"at-tax-audit-event-body\">\n <div class=\"at-tax-audit-event-desc\">\n {{ event.Description }} <span class=\"at-tax-tag-ref\">{{ event.TagRef }}</span>\n </div>\n <div class=\"at-tax-audit-event-meta\">\n <span>{{ event.User }}</span>\n <span>{{ event.Timestamp }}</span>\n </div>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <!-- TAB: SUGGESTIONS \u2014 server-driven inbox -->\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ActiveTab === 'suggestions') {\n <div class=\"at-page-body\">\n <div class=\"sg-shell\">\n <div class=\"sg-main\">\n <div class=\"sg-toolbar\">\n <input class=\"at-search-input\" style=\"width: 280px;\" placeholder=\"Search proposed name, source text\u2026\"\n [(ngModel)]=\"SuggestionSearch\" (input)=\"applySuggestionFilters()\" />\n <select class=\"at-search-input\" style=\"width: 200px;\"\n [(ngModel)]=\"SuggestionFilterReason\" (change)=\"applySuggestionFilters()\">\n <option value=\"\">All reasons</option>\n @for (r of ReasonOptions; track r) { <option [value]=\"r\">{{ r }}</option> }\n </select>\n <input class=\"at-search-input\" type=\"number\" step=\"0.05\" min=\"0\" max=\"1\"\n style=\"width: 100px;\" placeholder=\"Min score\"\n [ngModel]=\"SuggestionFilterMinScore\"\n (ngModelChange)=\"OnMinScoreChange($event)\" />\n <span style=\"flex: 1;\"></span>\n <span class=\"at-muted\" style=\"font-size: 12px;\">\n Showing <strong>{{ SuggestionRowsFiltered.length }} of {{ SuggestionRows.length }}</strong>\n </span>\n </div>\n\n @if (SelectedSuggestionCount() > 0) {\n <div class=\"sg-bulk-bar\">\n <strong>{{ SelectedSuggestionCount() }} selected</strong>\n <span style=\"flex: 1;\"></span>\n <button class=\"at-action-btn at-success-btn at-btn-sm\" (click)=\"BulkApprove()\" [disabled]=\"SuggestionBulkInProgress\">\n <i class=\"fa-solid fa-check\"></i> Approve all (auto-pick best)\n </button>\n <button class=\"at-action-btn at-danger-btn at-btn-sm\" (click)=\"BulkReject()\" [disabled]=\"SuggestionBulkInProgress\">\n <i class=\"fa-solid fa-xmark\"></i> Reject\n </button>\n </div>\n }\n\n <div class=\"sg-table-body\">\n @if (SuggestionRowsFiltered.length === 0) {\n <div class=\"at-empty-state\"><i class=\"fa-solid fa-inbox\"></i><p>No pending suggestions.</p></div>\n } @else {\n <table class=\"sg-table\">\n <thead>\n <tr>\n <th style=\"width: 30px;\">\n <input type=\"checkbox\"\n [checked]=\"SelectedSuggestionCount() === SuggestionRowsFiltered.length && SuggestionRowsFiltered.length > 0\"\n (change)=\"ToggleAllSuggestions($any($event.target).checked)\" />\n </th>\n <th>Proposed name</th>\n <th>Reason</th>\n <th>Best match</th>\n <th>Score</th>\n <th>Created</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (row of SuggestionRowsFiltered; track row.ID) {\n <tr [class.sg-row-checked]=\"row.selected\"\n [class.sg-row-selected]=\"SuggestionSelected && SuggestionSelected.ID === row.ID\"\n (click)=\"SelectSuggestion(row)\">\n <td><input type=\"checkbox\" [checked]=\"row.selected\" (click)=\"ToggleSuggestionSelected(row, $event)\" /></td>\n <td><strong>{{ row.ProposedName }}</strong></td>\n <td><span class=\"sg-reason {{ ReasonClass(row.Reason) }}\">{{ row.Reason }}</span></td>\n <td>\n @if (row.BestMatchName) {\n {{ row.BestMatchName }}\n <span class=\"at-muted\" style=\"font-size: 11px;\">{{ row.BestMatchPath }}</span>\n } @else {\n <span class=\"at-muted\">\u2014 no match \u2014</span>\n }\n </td>\n <td class=\"sg-score\">\n @if (row.BestMatchScore != null) {\n <span class=\"sg-score-bar\"><span [style.width.%]=\"row.BestMatchScore * 100\"></span></span>\n {{ row.BestMatchScore | number: '1.3-3' }}\n } @else {\n <span class=\"at-muted\">\u2014</span>\n }\n </td>\n <td class=\"at-muted\">{{ row.CreatedAt | date: 'short' }}</td>\n <td>\n @if (row.dispositionInProgress) {\n <span class=\"at-muted\" style=\"font-size: 11px;\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i> {{ row.dispositionInProgress }}\u2026\n </span>\n } @else {\n <div class=\"sg-row-actions\">\n <button class=\"at-action-btn at-btn-xs\" title=\"Create as new tag\"\n (click)=\"DispositionSuggestion(row, 'create-new'); $event.stopPropagation()\">\n <i class=\"fa-solid fa-plus\"></i>\n </button>\n @if (row.BestMatchTagID) {\n <button class=\"at-action-btn at-primary-btn at-btn-xs\" title=\"Merge into best match\"\n (click)=\"DispositionSuggestion(row, 'merge'); $event.stopPropagation()\">\n <i class=\"fa-solid fa-code-merge\"></i>\n </button>\n }\n <button class=\"at-action-btn at-danger-btn at-btn-xs\" title=\"Reject\"\n (click)=\"DispositionSuggestion(row, 'reject'); $event.stopPropagation()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n }\n </div>\n </div>\n\n @if (SuggestionSelected) {\n <aside class=\"sg-drawer\">\n <div class=\"sg-drawer-hd\">\n <div>\n <div style=\"font-weight: 600;\">\"{{ SuggestionSelected.ProposedName }}\"</div>\n <div class=\"at-muted\" style=\"font-size: 11px;\">{{ SuggestionSelected.CreatedAt | date: 'short' }}</div>\n </div>\n <button class=\"at-action-btn at-btn-xs\" (click)=\"CloseDrawer()\" title=\"Close\" aria-label=\"Close\"><i class=\"fa-solid fa-xmark\"></i></button>\n </div>\n <div class=\"sg-drawer-bd\">\n <span class=\"sg-reason {{ ReasonClass(SuggestionSelected.Reason) }}\">\n {{ SuggestionSelected.Reason }}\n @if (SuggestionSelected.BestMatchScore != null) { \u00B7 {{ SuggestionSelected.BestMatchScore | number: '1.3-3' }} }\n </span>\n\n @if (SuggestionSelected.BestMatchTagID) {\n <h4>Proposes merge into</h4>\n <div class=\"sg-candidate\">\n <div style=\"flex: 1;\">\n <div style=\"font-weight: 600;\">{{ SuggestionSelected.BestMatchName }}</div>\n <div class=\"at-muted\" style=\"font-size: 11px;\">{{ SuggestionSelected.BestMatchPath }}</div>\n </div>\n </div>\n }\n\n @if (SuggestionSelected.SourceText) {\n <h4>Source text</h4>\n <p class=\"sg-preview\">\"{{ SuggestionSelected.SourceText }}\"</p>\n }\n\n <h4>If approved</h4>\n <p class=\"sg-preview\">\n @if (SuggestionSelected.BestMatchTagID) {\n Re-point any free-text ContentItemTag rows whose Tag matches \"<strong>{{ SuggestionSelected.ProposedName }}</strong>\" to <strong>{{ SuggestionSelected.BestMatchName }}</strong>, add it as a synonym (Source: Merged), and mark the suggestion as Merged.\n } @else {\n Create a new tag named \"<strong>{{ SuggestionSelected.ProposedName }}</strong>\" (subject to ValidateAutoGrow), inherit parent scope when applicable, and re-point any matching free-text ContentItemTag rows.\n }\n </p>\n </div>\n <div class=\"sg-drawer-ft\">\n @if (SuggestionSelected.BestMatchTagID) {\n <button class=\"at-action-btn at-primary-btn\" (click)=\"DispositionSuggestion(SuggestionSelected!, 'merge')\"\n [disabled]=\"!!SuggestionSelected.dispositionInProgress\">\n <i class=\"fa-solid fa-code-merge\"></i> Merge\n </button>\n }\n <button class=\"at-action-btn\" (click)=\"DispositionSuggestion(SuggestionSelected!, 'create-new')\"\n [disabled]=\"!!SuggestionSelected.dispositionInProgress\">\n <i class=\"fa-solid fa-plus\"></i> Create as new\n </button>\n <button class=\"at-action-btn at-danger-btn\" (click)=\"DispositionSuggestion(SuggestionSelected!, 'reject')\"\n [disabled]=\"!!SuggestionSelected.dispositionInProgress\">\n <i class=\"fa-solid fa-xmark\"></i> Reject\n </button>\n </div>\n </aside>\n }\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <!-- TAB: HEALTH \u2014 Tag Health 3-card summary -->\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ActiveTab === 'health') {\n <div class=\"health-shell\">\n <div class=\"runbar\">\n <div class=\"stat\">\n <div class=\"l\">Last run</div>\n <div class=\"v\">{{ LastHealthSummary.runAt ? (LastHealthSummary.runAt | date: 'short') : '\u2014' }}</div>\n </div>\n <div class=\"stat\">\n <div class=\"l\">Tags scanned</div>\n <div class=\"v\">{{ tagsRaw.length }}</div>\n </div>\n <div class=\"stat\">\n <div class=\"l\">Issues enqueued (last run)</div>\n <div class=\"v\">{{ LastHealthSummary.mergeCount + LastHealthSummary.lowUsageCount + LastHealthSummary.wideNodeCount }}</div>\n </div>\n <div class=\"stat\">\n <div class=\"l\">Pending suggestions</div>\n <div class=\"v\">{{ PendingSuggestionCount }}</div>\n </div>\n <span class=\"grow\"></span>\n <div class=\"stat\">\n <div class=\"l\">Last duration</div>\n <div class=\"v\">{{ LastHealthSummary.durationMs ? (LastHealthSummary.durationMs + 'ms') : '\u2014' }}</div>\n </div>\n </div>\n\n <div class=\"health-cards\">\n <div class=\"h-card merge\">\n <div class=\"top\">\n <div class=\"ic\"><i class=\"fa-solid fa-code-merge\"></i></div>\n <div>\n <div class=\"name\">Merge candidates</div>\n <h3>Likely duplicate tags</h3>\n </div>\n </div>\n <div class=\"big\">{{ LastHealthSummary.mergeCount }}</div>\n <p class=\"sub\">Pairs with high embedding cosine + name similarity. Approving merges the source into the target and re-points existing ContentItemTag rows.</p>\n <div class=\"ft\">\n <button class=\"at-action-btn at-primary-btn at-btn-sm\" (click)=\"SwitchTab('suggestions'); SuggestionFilterReason = 'MergeCandidate'; applySuggestionFilters()\">\n Open in Suggestions\n </button>\n </div>\n </div>\n\n <div class=\"h-card lowusage\">\n <div class=\"top\">\n <div class=\"ic\"><i class=\"fa-solid fa-leaf\"></i></div>\n <div>\n <div class=\"name\">Low usage</div>\n <h3>Deprecation candidates</h3>\n </div>\n </div>\n <div class=\"big\">{{ LastHealthSummary.lowUsageCount }}</div>\n <p class=\"sub\">Active tags used fewer than the threshold over the lookback window. Often abandoned imports or finished-project tags.</p>\n <div class=\"ft\">\n <button class=\"at-action-btn at-primary-btn at-btn-sm\" (click)=\"SwitchTab('suggestions'); SuggestionFilterReason = 'LowUsage'; applySuggestionFilters()\">\n Review\n </button>\n </div>\n </div>\n\n <div class=\"h-card widenode\">\n <div class=\"top\">\n <div class=\"ic\"><i class=\"fa-solid fa-tree\"></i></div>\n <div>\n <div class=\"name\">Wide nodes</div>\n <h3>Parents with too many children</h3>\n </div>\n </div>\n <div class=\"big\">{{ LastHealthSummary.wideNodeCount }}</div>\n <p class=\"sub\">Parents whose direct-child count exceeds <strong>MaxChildren</strong> (or the implicit threshold {{ HealthThresholds.maxImplicitChildren }} when no cap is set).</p>\n <div class=\"ft\">\n <button class=\"at-action-btn at-primary-btn at-btn-sm\" (click)=\"SwitchTab('suggestions'); SuggestionFilterReason = 'WideNode'; applySuggestionFilters()\">\n Review\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"threshold-form\">\n <h3>Health-job thresholds</h3>\n <div class=\"grid\">\n <div class=\"field\"><label>Merge: min co-occurrence</label>\n <input class=\"at-search-input\" type=\"number\" min=\"0\" [(ngModel)]=\"HealthThresholds.minCoOccurrence\" />\n <span class=\"at-hint\">Pairs must appear together in \u2265 this many items.</span>\n </div>\n <div class=\"field\"><label>Merge: min embedding cosine</label>\n <input class=\"at-search-input\" type=\"number\" min=\"0\" max=\"1\" step=\"0.01\" [(ngModel)]=\"HealthThresholds.minEmbeddingSimilarity\" />\n </div>\n <div class=\"field\"><label>Merge: min name similarity</label>\n <input class=\"at-search-input\" type=\"number\" min=\"0\" max=\"1\" step=\"0.05\" [(ngModel)]=\"HealthThresholds.minNameSimilarity\" />\n <span class=\"at-hint\">Dice's-coefficient bigram similarity.</span>\n </div>\n <div class=\"field\"><label>Low usage: max usage</label>\n <input class=\"at-search-input\" type=\"number\" min=\"0\" [(ngModel)]=\"HealthThresholds.maxUsage\" />\n </div>\n <div class=\"field\"><label>Wide node: max implicit children</label>\n <input class=\"at-search-input\" type=\"number\" min=\"0\" [(ngModel)]=\"HealthThresholds.maxImplicitChildren\" />\n <span class=\"at-hint\">Used when a tag has no explicit MaxChildren cap.</span>\n </div>\n </div>\n </div>\n\n <div class=\"run-history\">\n <div class=\"run-history-hd\">\n <h3>Run history</h3>\n <span class=\"at-muted\" style=\"font-size: 11px;\">last {{ HealthRunHistory.length }} runs</span>\n </div>\n @if (HealthRunHistory.length === 0) {\n <div class=\"at-empty-state\" style=\"padding: 32px;\"><i class=\"fa-solid fa-clock-rotate-left\"></i><p>No runs yet.</p></div>\n } @else {\n <table>\n <thead>\n <tr>\n <th>When</th><th>Trigger</th><th>Tags scanned</th><th>Merge</th><th>Low usage</th><th>Wide node</th><th>Total</th><th>Duration</th>\n </tr>\n </thead>\n <tbody>\n @for (run of HealthRunHistory; track run.When) {\n <tr>\n <td class=\"at-muted\">{{ run.When | date: 'short' }}</td>\n <td>{{ run.Trigger }}</td>\n <td>{{ run.TagsScanned }}</td>\n <td>{{ run.Merge }}</td>\n <td>{{ run.LowUsage }}</td>\n <td>{{ run.WideNode }}</td>\n <td><strong>{{ run.Merge + run.LowUsage + run.WideNode }}</strong></td>\n <td>{{ run.DurationMs }}ms</td>\n </tr>\n }\n </tbody>\n </table>\n }\n </div>\n </div>\n }\n\n <!-- end @if (!IsLoading) -->\n }\n\n </div>\n <!-- end at-main-area -->\n </mj-page-body-interior>\n </mj-left-nav-content>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550 ITEM DETAIL SLIDE-IN \u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ShowItemDetail && SelectedFeedItem) {\n <div class=\"at-slide-overlay\" (click)=\"CloseItemDetail()\"></div>\n <div class=\"at-slide-panel at-detail-panel\">\n <div class=\"at-slide-header\">\n <h3><i class=\"fa-solid fa-file-lines\"></i> Content Item</h3>\n <button class=\"at-slide-close\" aria-label=\"Close item detail\" (click)=\"CloseItemDetail()\"><i class=\"fa-solid fa-times\"></i></button>\n </div>\n <div class=\"at-slide-body\">\n <!-- Header -->\n <div class=\"at-detail-item-header\">\n <h4 class=\"at-detail-item-name\">{{ SelectedFeedItem.Name }}</h4>\n <div class=\"at-detail-badges\">\n <span class=\"at-detail-badge at-detail-badge-source\">{{ SelectedFeedItem.SourceName }}</span>\n @if (SelectedFeedItem.RequiresContentType) {\n <span class=\"at-detail-badge at-detail-badge-type\">{{ SelectedFeedItem.ContentTypeName }}</span>\n @if (SelectedFeedItem.FileTypeName) {\n <span class=\"at-detail-badge at-detail-badge-file\"><i class=\"fa-solid fa-file\"></i> {{ SelectedFeedItem.FileTypeName }}</span>\n }\n }\n </div>\n </div>\n\n <!-- URL -->\n @if (SelectedFeedItem.URL) {\n <div class=\"at-detail-section\">\n <div class=\"at-detail-section-label\">URL</div>\n <a [href]=\"SelectedFeedItem.URL\" target=\"_blank\" class=\"at-detail-link\">\n {{ SelectedFeedItem.URL }}\n <i class=\"fa-solid fa-external-link-alt\" style=\"font-size: 0.65rem; margin-left: 4px;\"></i>\n </a>\n </div>\n }\n\n <!-- Text Preview -->\n @if (SelectedFeedItem.TextContent) {\n <div class=\"at-detail-section\">\n <div class=\"at-detail-section-label\">Content Preview</div>\n <div class=\"at-detail-text-preview\">{{ SelectedFeedItem.TextContent }}</div>\n </div>\n }\n\n <!-- Tags (weighted) -->\n @if (SelectedFeedItem.Tags.length > 0) {\n <div class=\"at-detail-section\">\n <div class=\"at-detail-section-label\">Tags ({{ SelectedFeedItem.Tags.length }})</div>\n <div class=\"at-detail-tags\">\n @for (wt of SelectedFeedItem.Tags; track wt.Tag) {\n <span class=\"at-tag-pill at-tag-weighted\" [style.font-size]=\"TagFontSize(wt.Weight)\">\n {{ wt.Tag }}\n <span class=\"at-tag-weight\">{{ FormatWeight(wt.Weight) }}</span>\n </span>\n }\n </div>\n </div>\n }\n\n <!-- Metadata -->\n <div class=\"at-detail-section\">\n <div class=\"at-detail-section-label\">Metadata</div>\n <div class=\"at-detail-meta-grid\">\n @if (SelectedFeedItem.Checksum) {\n <div class=\"at-detail-meta-row\">\n <span class=\"at-detail-meta-key\">Checksum</span>\n <span class=\"at-detail-meta-value at-detail-meta-mono\">{{ SelectedFeedItem.Checksum }}</span>\n </div>\n }\n <div class=\"at-detail-meta-row\">\n <span class=\"at-detail-meta-key\">Created</span>\n <span class=\"at-detail-meta-value\">{{ SelectedFeedItem.CreatedAt }}</span>\n </div>\n <div class=\"at-detail-meta-row\">\n <span class=\"at-detail-meta-key\">Updated</span>\n <span class=\"at-detail-meta-value\">{{ SelectedFeedItem.UpdatedAt }}</span>\n </div>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"at-detail-actions\">\n <button class=\"at-action-btn at-primary-btn\" (click)=\"OpenRecordFromItem(SelectedFeedItem)\">\n <i class=\"fa-solid fa-external-link-alt\"></i> Open Record\n </button>\n <button class=\"at-action-btn at-secondary-btn\" disabled>\n <i class=\"fa-solid fa-magnifying-glass\"></i> See Similar Items\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550 CONFIRMATION DIALOG \u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ShowConfirmDialog) {\n <div class=\"at-schedule-overlay\" (click)=\"ConfirmDialogCancel()\">\n <div class=\"at-schedule-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"at-schedule-dialog-header\">\n <h3><i class=\"fa-solid fa-triangle-exclamation\"></i> {{ ConfirmDialogTitle }}</h3>\n <button class=\"at-schedule-dialog-close\" (click)=\"ConfirmDialogCancel()\" aria-label=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"at-schedule-dialog-body\">\n <p class=\"at-confirm-message\">{{ ConfirmDialogMessage }}</p>\n </div>\n <div class=\"at-schedule-dialog-footer\">\n <button class=\"at-action-btn at-danger-btn\" (click)=\"ConfirmDialogAccept()\">\n <i class=\"fa-solid fa-check\"></i> Confirm\n </button>\n <button class=\"at-action-btn at-secondary-btn\" (click)=\"ConfirmDialogCancel()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550 SPLIT DIALOG \u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ShowSplitDialog) {\n <div class=\"at-schedule-overlay\" (click)=\"CloseSplitDialog()\">\n <div class=\"at-schedule-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"at-schedule-dialog-header\">\n <h3><i class=\"fa-solid fa-code-branch\"></i> Split Tag</h3>\n <button class=\"at-schedule-dialog-close\" (click)=\"CloseSplitDialog()\" aria-label=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"at-schedule-dialog-body\">\n <div class=\"at-schedule-field\">\n <label>New tag names (comma-separated)</label>\n <input type=\"text\" class=\"mj-input\" style=\"width: 100%;\"\n [(ngModel)]=\"SplitChildNames\"\n placeholder=\"Tag A, Tag B, Tag C\" />\n </div>\n <p class=\"at-confirm-message\">New tags will be created as siblings of the original tag under the same parent.</p>\n </div>\n <div class=\"at-schedule-dialog-footer\">\n <button class=\"at-action-btn at-primary-btn\" (click)=\"ExecuteSplit()\" [disabled]=\"!SplitChildNames.trim()\">\n <i class=\"fa-solid fa-code-branch\"></i> Create Tags\n </button>\n <button class=\"at-action-btn at-secondary-btn\" (click)=\"CloseSplitDialog()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550 MOVE DIALOG \u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ShowCreateTagDialog) {\n <div class=\"at-schedule-overlay\" (click)=\"CloseCreateTagDialog()\">\n <div class=\"at-schedule-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"at-schedule-dialog-header\">\n <h3><i class=\"fa-solid fa-plus\"></i> Create Tag</h3>\n <button class=\"at-schedule-dialog-close\" (click)=\"CloseCreateTagDialog()\" aria-label=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"at-schedule-dialog-body\">\n <div class=\"at-tax-create-context\">{{ CreateTagParentLabel }}</div>\n <div class=\"at-schedule-field\">\n <label>Name</label>\n <input type=\"text\" class=\"mj-input\" style=\"width: 100%;\" [(ngModel)]=\"CreateTagName\"\n placeholder=\"Tag name\" (keydown.enter)=\"SaveNewTag()\">\n </div>\n <div class=\"at-schedule-field\">\n <label>Description (optional)</label>\n <textarea class=\"mj-textarea\" rows=\"3\" style=\"width: 100%;\" [(ngModel)]=\"CreateTagDescription\"\n placeholder=\"Brief description of this tag\"></textarea>\n </div>\n </div>\n <div class=\"at-schedule-dialog-footer\">\n <button class=\"at-action-btn at-primary-btn\" (click)=\"SaveNewTag()\" [disabled]=\"!CreateTagName.trim()\">\n <i class=\"fa-solid fa-check\"></i> Create\n </button>\n <button class=\"at-action-btn at-secondary-btn\" (click)=\"CloseCreateTagDialog()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n\n @if (ShowMoveDialog) {\n <div class=\"at-schedule-overlay\" (click)=\"CloseMoveDialog()\">\n <div class=\"at-schedule-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"at-schedule-dialog-header\">\n <h3><i class=\"fa-solid fa-arrows-up-down\"></i> Move Tag</h3>\n <button class=\"at-schedule-dialog-close\" (click)=\"CloseMoveDialog()\" aria-label=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"at-schedule-dialog-body\">\n <div class=\"at-schedule-field\">\n <label>New parent tag</label>\n <select class=\"mj-input\" style=\"width: 100%;\" [(ngModel)]=\"MoveNewParentID\">\n <option [ngValue]=\"null\">(Root level — no parent)</option>\n @for (opt of GetMoveTargetOptions(); track opt.ID) {\n <option [ngValue]=\"opt.ID\">{{ '\\u00A0\\u00A0'.repeat(opt.Depth) }}{{ opt.Name }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"at-schedule-dialog-footer\">\n <button class=\"at-action-btn at-primary-btn\" (click)=\"ExecuteMove()\">\n <i class=\"fa-solid fa-check\"></i> Move\n </button>\n <button class=\"at-action-btn at-secondary-btn\" (click)=\"CloseMoveDialog()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n\n @if (ShowMergeIntoDialog) {\n <div class=\"at-schedule-overlay\" (click)=\"CloseMergeIntoDialog()\">\n <div class=\"at-schedule-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"at-schedule-dialog-header\">\n <h3><i class=\"fa-solid fa-compress\"></i> Merge \"{{ MergeSourceTag?.Name }}\" Into...</h3>\n <button class=\"at-schedule-dialog-close\" (click)=\"CloseMergeIntoDialog()\" aria-label=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"at-schedule-dialog-body\">\n <p style=\"font-size: 0.85rem; color: var(--mj-text-secondary); margin: 0 0 12px;\">\n All tagged items will be moved to the target tag, then \"{{ MergeSourceTag?.Name }}\" will be deleted.\n </p>\n <div class=\"at-schedule-field\">\n <label>Merge into</label>\n <mj-combobox\n [Data]=\"MergeTargetData\"\n TextField=\"Label\"\n ValueField=\"ID\"\n [Filterable]=\"true\"\n [ValuePrimitive]=\"true\"\n Placeholder=\"Search and select a tag...\"\n (ValueChange)=\"OnMergeTargetSelected($event)\">\n </mj-combobox>\n </div>\n </div>\n <div class=\"at-schedule-dialog-footer\">\n <button class=\"at-action-btn at-primary-btn\" (click)=\"ExecuteMergeInto()\" [disabled]=\"!MergeTargetID || IsMerging\">\n @if (IsMerging) { <i class=\"fa-solid fa-spinner fa-spin\"></i> Merging... } @else { <i class=\"fa-solid fa-compress\"></i> Merge }\n </button>\n <button class=\"at-action-btn at-secondary-btn\" (click)=\"CloseMergeIntoDialog()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n\n </div>\n </mj-page-body>\n</mj-page-layout>\n", styles: ["/* ============================================================\n Content Autotagging Dashboard\n All colors use MJ design tokens \u2014 no hardcoded hex values.\n All classes prefixed with at- to prevent leaking (ViewEncapsulation.None).\n ============================================================ */\n\n/* \u2500\u2500 Root layout \u2500\u2500 */\n\n.at-dashboard {\n display: flex;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/*\n Left rail now owned by <mj-left-nav> (~50 lines of bespoke .at-left-nav-items\n / .at-nav-item / .at-nav-badge / .at-nav-divider styles retired). The footer\n stayed bespoke since it's \"Run Pipeline\" CTA content, not nav chrome.\n*/\n\n.at-left-nav-footer {\n padding: 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.at-run-pipeline-btn {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 10px;\n border: none;\n border-radius: 8px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 600;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.at-run-pipeline-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.at-run-pipeline-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 MAIN CONTENT AREA \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-main-area {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.at-loading-overlay {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n}\n\n/* .at-page-header / .at-page-title / .at-page-subtitle / .at-page-actions\n retired \u2014 section identity + actions now live in <mj-page-header-interior>\n (one shared instance at the top of <mj-left-nav-content>, with Title/Subtitle\n driven by currentTabTitle/currentTabSubtitle getters and per-section action\n buttons projected via an @switch into the chrome's [actions] slot). */\n\n.at-page-body {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n padding: 16px 20px;\n min-height: 0;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 SHARED COMPONENTS \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-action-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n border: 1px solid;\n border-radius: 7px;\n cursor: pointer;\n font-size: 0.78rem;\n font-weight: 500;\n transition: all 0.15s ease;\n}\n\n.at-primary-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.at-primary-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.at-primary-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.at-secondary-btn {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n border-color: var(--mj-border-default);\n}\n\n.at-secondary-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.at-card {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n overflow: hidden;\n}\n\n.at-card-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.at-card-title {\n font-size: 0.82rem;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--mj-text-primary);\n}\n\n.at-card-title i {\n color: var(--mj-brand-primary);\n font-size: 0.75rem;\n}\n\n.at-card-body {\n padding: 0;\n}\n\n.at-empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 32px 16px;\n color: var(--mj-text-disabled);\n}\n\n.at-empty-state i {\n font-size: 28px;\n}\n\n.at-empty-state p {\n margin: 0;\n font-size: 13px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 KPI STRIP \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-kpi-strip {\n display: flex;\n gap: 12px;\n margin-bottom: 16px;\n}\n\n.at-kpi-card {\n flex: 1;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n padding: 14px 16px;\n}\n\n.at-kpi-value {\n font-size: 1.4rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-kpi-error-value {\n color: var(--mj-status-error-text);\n}\n\n.at-kpi-label {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.at-kpi-trend {\n font-size: 0.68rem;\n margin-top: 4px;\n display: flex;\n align-items: center;\n gap: 4px;\n color: var(--mj-text-muted);\n}\n\n.at-kpi-trend.up {\n color: var(--mj-status-success-text);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 PIPELINE TAB \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-pipeline-layout {\n display: flex;\n gap: 16px;\n flex: 1;\n min-height: 0;\n}\n\n.at-pipeline-center {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n min-height: 0;\n}\n\n.at-pipeline-right {\n width: 320px;\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n/* Pipeline Stages */\n\n.at-pipeline-stages {\n display: flex;\n gap: 0;\n align-items: center;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n padding: 12px 16px;\n}\n\n.at-pipeline-stage {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.at-pipeline-stage-icon {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n font-size: 0.85rem;\n}\n\n.stage-idle .at-pipeline-stage-icon {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.stage-active .at-pipeline-stage-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n animation: at-pulse 1.5s infinite;\n}\n\n.stage-complete .at-pipeline-stage-icon {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n@keyframes at-pulse {\n 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--mj-brand-primary) 30%, transparent); }\n 50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--mj-brand-primary) 0%, transparent); }\n}\n\n.at-pipeline-stage-name {\n font-size: 0.68rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.at-pipeline-stage-count {\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n}\n\n.at-pipeline-arrow {\n width: 24px;\n text-align: center;\n color: var(--mj-text-disabled);\n font-size: 0.6rem;\n}\n\n.at-stage-connector {\n width: 32px;\n height: 2px;\n background: var(--mj-border-default);\n transition: background 0.4s ease;\n}\n\n.at-stage-connector.connector-complete {\n background: var(--mj-status-success);\n}\n\n/* Progress section */\n\n.at-progress-section {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n padding: 10px 16px;\n}\n\n.at-progress-header {\n display: flex;\n justify-content: space-between;\n font-size: 0.75rem;\n margin-bottom: 4px;\n}\n\n.at-progress-stage-label {\n color: var(--mj-text-secondary);\n font-weight: 500;\n}\n\n.at-progress-pct {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n.at-progress-bar {\n height: 4px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 2px;\n overflow: hidden;\n}\n\n.at-progress-fill {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 2px;\n transition: width 0.3s ease;\n}\n\n.at-progress-current {\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n margin-top: 4px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.at-progress-fill-paused {\n background: var(--mj-status-warning);\n animation: pulse-paused 1.5s ease-in-out infinite;\n}\n\n@keyframes pulse-paused {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n}\n\n.at-pipeline-controls {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n justify-content: flex-end;\n}\n\n.at-danger-btn {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-color: var(--mj-status-error);\n}\n\n.at-danger-btn:hover:not(:disabled) {\n background: var(--mj-status-error-text);\n border-color: var(--mj-status-error-text);\n}\n\n/* Feed items */\n\n.at-feed-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n}\n\n.at-feed-item:last-child {\n border-bottom: none;\n}\n\n.at-feed-status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.at-feed-status-dot.complete {\n background: var(--mj-status-success);\n}\n\n.at-feed-status-dot.processing {\n background: var(--mj-brand-primary);\n animation: at-pulse 1.5s infinite;\n}\n\n.at-feed-status-dot.error {\n background: var(--mj-status-error);\n}\n\n.at-feed-item-name {\n flex: 1;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--mj-text-primary);\n}\n\n.at-feed-item-source {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n min-width: 100px;\n}\n\n.at-feed-item-tags {\n display: flex;\n gap: 4px;\n}\n\n.at-feed-tag {\n padding: 1px 6px;\n border-radius: 3px;\n font-size: 0.62rem;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.at-feed-item-time {\n color: var(--mj-text-muted);\n font-size: 0.68rem;\n white-space: nowrap;\n min-width: 60px;\n text-align: right;\n}\n\n/* Source mini cards (right panel) */\n\n.at-source-mini {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--mj-border-subtle);\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.at-source-mini:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.at-source-mini:last-child {\n border-bottom: none;\n}\n\n.at-source-mini-icon {\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.72rem;\n flex-shrink: 0;\n}\n\n.at-source-mini-info {\n flex: 1;\n min-width: 0;\n}\n\n.at-source-mini-name {\n font-size: 0.78rem;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--mj-text-primary);\n}\n\n.at-source-mini-meta {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n.at-source-mini-status {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.at-source-mini-status.active {\n background: var(--mj-status-success);\n}\n\n.at-source-mini-status.error {\n background: var(--mj-status-error);\n}\n\n.at-source-mini-status.inactive {\n background: var(--mj-text-disabled);\n}\n\n/* Tag cloud card */\n\n.at-tag-cloud-card {\n padding: 16px;\n}\n\n.at-tag-cloud {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n.at-tag-pill {\n padding: 4px 12px;\n border-radius: 14px;\n font-size: 0.72rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-subtle);\n cursor: pointer;\n transition: all 0.12s ease;\n}\n\n.at-tag-pill:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.at-tag-weighted {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.at-tag-weight {\n font-size: 0.6rem;\n opacity: 0.6;\n font-weight: 400;\n}\n\n.at-tag-row-clickable {\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.at-tag-row-clickable:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.at-tag-row-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface)) !important;\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.at-tag-pill.large {\n font-size: 0.85rem;\n padding: 5px 14px;\n}\n\n.at-tag-pill.small {\n font-size: 0.65rem;\n padding: 3px 8px;\n color: var(--mj-text-muted);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 SOURCES TAB \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-sources-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 12px;\n}\n\n.at-source-card-full {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n padding: 16px;\n transition: border-color 0.15s ease;\n}\n\n.at-source-card-full:hover {\n border-color: var(--mj-border-default);\n}\n\n.at-source-card-header {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 12px;\n}\n\n.at-source-card-icon {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.at-source-card-title {\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-source-card-type {\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n}\n\n.at-source-card-status {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 5px;\n font-size: 0.7rem;\n font-weight: 500;\n}\n\n.at-source-card-status.active {\n color: var(--mj-status-success-text);\n}\n\n.at-source-card-status.error {\n color: var(--mj-status-error-text);\n}\n\n.at-source-card-status.inactive {\n color: var(--mj-text-disabled);\n}\n\n.at-source-card-url {\n font-size: 0.7rem;\n color: var(--mj-brand-primary);\n margin-bottom: 10px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.at-source-card-stats {\n display: flex;\n gap: 16px;\n padding-top: 10px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.at-source-stat {\n display: flex;\n flex-direction: column;\n}\n\n.at-source-stat-value {\n font-size: 1rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-source-stat-label {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n.at-source-card-actions {\n display: flex;\n gap: 6px;\n margin-top: 10px;\n}\n\n.at-source-action-btn {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n padding: 6px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 0.7rem;\n cursor: pointer;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n transition: all 0.12s ease;\n}\n\n.at-source-action-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.at-source-delete-btn:hover {\n border-color: var(--mj-status-error);\n color: var(--mj-status-error-text);\n}\n\n.at-add-source-card {\n background: none;\n border: 2px dashed var(--mj-border-default);\n border-radius: 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 40px;\n cursor: pointer;\n transition: all 0.15s ease;\n color: var(--mj-text-muted);\n min-height: 200px;\n}\n\n.at-add-source-card:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.at-add-source-card i {\n font-size: 1.5rem;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 CONTENT TYPES TAB \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-ct-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 12px;\n}\n\n.at-ct-card {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n padding: 16px;\n}\n\n.at-ct-card-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n}\n\n.at-ct-card-name {\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-ct-card-model {\n font-size: 0.68rem;\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.at-ct-field {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n}\n\n.at-ct-field:last-child {\n border-bottom: none;\n}\n\n.at-ct-field-label {\n color: var(--mj-text-muted);\n}\n\n.at-ct-field-value {\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.at-ct-tag-range {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n padding: 8px 10px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 6px;\n font-size: 0.75rem;\n color: var(--mj-text-primary);\n}\n\n.at-ct-tag-range i {\n color: var(--mj-brand-primary);\n}\n\n.at-ct-tag-range-bar {\n flex: 1;\n height: 6px;\n background: var(--mj-bg-surface);\n border-radius: 3px;\n position: relative;\n}\n\n.at-ct-tag-range-fill {\n position: absolute;\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 3px;\n}\n\n.at-ct-range-suffix {\n color: var(--mj-text-muted);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 TAG LIBRARY TAB \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-tag-lib-layout {\n display: flex;\n gap: 16px;\n}\n\n.at-tag-lib-main {\n flex: 1;\n}\n\n.at-tag-lib-sidebar {\n width: 280px;\n flex-shrink: 0;\n}\n\n.at-tag-table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.at-tag-table th {\n text-align: left;\n padding: 8px 12px;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-elevated);\n position: sticky;\n top: 0;\n z-index: 1;\n}\n\n.at-tag-table td {\n padding: 10px 12px;\n font-size: 0.8rem;\n border-bottom: 1px solid var(--mj-border-subtle);\n color: var(--mj-text-primary);\n}\n\n.at-tag-table tr:hover td {\n background: var(--mj-bg-surface-hover);\n}\n\n.at-tag-name-cell {\n font-weight: 600;\n}\n\n.at-tag-bar {\n width: 80px;\n height: 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n display: inline-block;\n vertical-align: middle;\n}\n\n.at-tag-bar-fill {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 3px;\n}\n\n/* Weight indicator in tag table */\n.at-weight-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.at-weight-bar {\n width: 50px;\n height: 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n overflow: hidden;\n}\n\n.at-weight-bar-fill {\n height: 100%;\n border-radius: 3px;\n transition: width 0.2s ease;\n}\n\n.at-weight-bar-fill.at-weight-high {\n background: var(--mj-status-success);\n}\n\n.at-weight-bar-fill.at-weight-medium {\n background: var(--mj-status-warning);\n}\n\n.at-weight-bar-fill.at-weight-low {\n background: var(--mj-status-error);\n}\n\n.at-weight-value {\n font-size: 0.7rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n min-width: 28px;\n}\n\n.at-tags-by-source {\n font-size: 0.78rem;\n}\n\n.at-tag-source-row {\n display: flex;\n justify-content: space-between;\n padding: 5px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n color: var(--mj-text-primary);\n}\n\n.at-tag-source-row:last-child {\n border-bottom: none;\n}\n\n.at-search-input {\n padding: 7px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 7px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.8rem;\n width: 200px;\n outline: none;\n}\n\n.at-search-input::placeholder {\n color: var(--mj-text-disabled);\n}\n\n.at-search-input:focus {\n border-color: var(--mj-brand-primary);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 RUN HISTORY TAB \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-run-table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.at-run-table th {\n text-align: left;\n padding: 10px 14px;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-elevated);\n position: sticky;\n top: 0;\n z-index: 1;\n}\n\n.at-run-table td {\n padding: 12px 14px;\n font-size: 0.8rem;\n border-bottom: 1px solid var(--mj-border-subtle);\n color: var(--mj-text-primary);\n}\n\n.at-run-table tr:hover td {\n background: var(--mj-bg-surface-hover);\n cursor: pointer;\n}\n\n.at-run-status-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 10px;\n border-radius: 10px;\n font-size: 0.7rem;\n font-weight: 600;\n}\n\n.at-run-status-badge.complete {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.at-run-status-badge.failed {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.at-run-status-badge.running {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.at-run-duration {\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n}\n\n.at-run-source-name {\n font-weight: 500;\n}\n\n.at-run-error-text {\n color: var(--mj-status-error-text);\n}\n\n.at-filter-select {\n padding: 7px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 7px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.78rem;\n outline: none;\n}\n\n.at-filter-select:focus {\n border-color: var(--mj-brand-primary);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 SLIDE-IN FORM PANEL \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-slide-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 1000;\n}\n\n.at-slide-panel {\n position: fixed;\n right: 0;\n top: 0;\n bottom: 0;\n width: 420px;\n max-width: 100vw;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n box-shadow: -4px 0 24px color-mix(in srgb, var(--mj-text-primary) 15%, transparent);\n}\n\n.at-slide-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.at-slide-header h3 {\n margin: 0;\n font-size: 1rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-slide-close {\n background: none;\n border: none;\n font-size: 1.1rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 4px;\n}\n\n.at-slide-close:hover {\n color: var(--mj-text-primary);\n}\n\n.at-slide-body {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n}\n\n.at-form-group {\n margin-bottom: 16px;\n}\n\n.at-form-label {\n display: block;\n font-size: 0.78rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n margin-bottom: 6px;\n}\n\n.at-form-input,\n.at-form-select,\n.at-form-textarea {\n width: 100%;\n padding: 9px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 7px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n font-size: 0.82rem;\n outline: none;\n font-family: inherit;\n}\n\n.at-form-input:focus,\n.at-form-select:focus,\n.at-form-textarea:focus {\n border-color: var(--mj-brand-primary);\n}\n\n.at-form-input::placeholder,\n.at-form-textarea::placeholder {\n color: var(--mj-text-disabled);\n}\n\n.at-form-textarea {\n resize: vertical;\n}\n\n.at-form-row {\n display: flex;\n gap: 12px;\n}\n\n.at-form-actions {\n display: flex;\n gap: 8px;\n margin-top: 24px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 RESPONSIVE \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n/* <mj-left-nav> collapses to a full-width top bar at \u2264700px; stack the root\n layout vertically so the content pane sits below it instead of being\n squeezed to zero width in the flex row. */\n@media (max-width: 700px) {\n .at-dashboard {\n flex-direction: column;\n }\n}\n\n@media (max-width: 1100px) {\n .at-pipeline-layout {\n flex-direction: column;\n }\n\n .at-pipeline-right {\n width: 100%;\n flex-direction: row;\n }\n\n .at-tag-lib-layout {\n flex-direction: column;\n }\n\n .at-tag-lib-sidebar {\n width: 100%;\n }\n}\n\n@media (max-width: 768px) {\n .at-left-nav {\n width: 180px;\n }\n\n .at-kpi-strip {\n flex-wrap: wrap;\n }\n\n .at-kpi-card {\n min-width: 140px;\n }\n\n .at-sources-grid {\n grid-template-columns: 1fr;\n }\n\n .at-ct-grid {\n grid-template-columns: 1fr;\n }\n\n .at-slide-panel {\n width: 100vw;\n }\n}\n\n@media (max-width: 480px) {\n .at-left-nav {\n width: 56px;\n }\n\n .at-left-nav-header h2 {\n font-size: 0;\n }\n\n .at-left-nav-header h2 i {\n font-size: 1rem;\n }\n\n .at-nav-item {\n justify-content: center;\n padding: 10px;\n font-size: 0;\n }\n\n .at-nav-item i {\n font-size: 1rem;\n }\n\n .at-nav-badge {\n display: none;\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Slide-in Form Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-slide-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.35);\n z-index: 9999;\n animation: at-fade-in 0.2s ease;\n}\n\n@keyframes at-fade-in { from { opacity: 0; } to { opacity: 1; } }\n\n.at-slide-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 420px;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -8px 0 40px rgba(0, 0, 0, 0.4);\n z-index: 10000;\n display: flex;\n flex-direction: column;\n animation: at-slide-in 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n@keyframes at-slide-in { from { transform: translateX(100%); } to { transform: translateX(0); } }\n\n.at-slide-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.at-slide-header h3 {\n font-size: 1rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-slide-close {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n transition: all 0.15s ease;\n}\n\n.at-slide-close:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.at-slide-body {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.at-form-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.at-form-label {\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.at-form-input,\n.at-form-select,\n.at-form-textarea {\n padding: 9px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 7px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n outline: none;\n transition: border-color 0.15s ease;\n font-family: inherit;\n}\n\n.at-form-input:focus,\n.at-form-select:focus,\n.at-form-textarea:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.at-form-select {\n appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M3 5l3 3 3-3'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 10px center;\n padding-right: 30px;\n}\n\n.at-form-textarea {\n resize: vertical;\n min-height: 70px;\n}\n\n.at-form-row {\n display: flex;\n gap: 12px;\n}\n\n.at-form-actions {\n display: flex;\n gap: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-subtle);\n margin-top: 8px;\n}\n\n.at-form-actions .at-action-btn {\n flex: 1;\n justify-content: center;\n}\n\n/* Also add empty state for Content Types (matching Sources) */\n.at-add-type-card {\n background: none;\n border: 2px dashed var(--mj-border-default);\n border-radius: 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 40px;\n cursor: pointer;\n transition: all 0.15s ease;\n color: var(--mj-text-muted);\n min-height: 200px;\n}\n\n.at-add-type-card:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.at-add-type-card i {\n font-size: 1.5rem;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 FORKED PIPELINE STAGES \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-pipeline-stages-forked {\n display: flex;\n align-items: center;\n gap: 0;\n}\n\n.at-pipeline-fork {\n display: flex;\n align-items: center;\n gap: 0;\n flex: 2;\n}\n\n.at-pipeline-fork-lines {\n width: 28px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n position: relative;\n flex-shrink: 0;\n}\n\n.at-pipeline-fork-line {\n height: 2px;\n background: var(--mj-border-default);\n width: 100%;\n position: relative;\n}\n\n.at-pipeline-fork-line::before {\n content: '';\n position: absolute;\n width: 2px;\n height: 12px;\n background: var(--mj-border-default);\n left: 0;\n}\n\n.at-fork-top::before {\n bottom: 0;\n left: 0;\n}\n\n.at-fork-bottom::before {\n top: 0;\n left: 0;\n}\n\n.at-pipeline-fork-branches {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1;\n}\n\n.at-pipeline-fork-branches .at-pipeline-stage {\n flex: none;\n padding: 6px 0;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 CLICKABLE FEED ITEMS \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-feed-item-clickable {\n cursor: pointer;\n}\n\n.at-feed-item-clickable:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 FEED SEARCH & PAGINATION \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-feed-card {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.at-feed-header-actions {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-left: auto;\n}\n\n.at-feed-sort-btn {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: 5px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.68rem;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n}\n\n.at-feed-sort-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.at-feed-count {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n\n.at-feed-search-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n}\n\n.at-feed-search-icon {\n color: var(--mj-text-disabled);\n font-size: 0.72rem;\n flex-shrink: 0;\n}\n\n.at-feed-search-input {\n flex: 1;\n border: none;\n background: transparent;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n outline: none;\n padding: 4px 0;\n min-width: 0;\n}\n\n.at-feed-search-input::placeholder {\n color: var(--mj-text-disabled);\n}\n\n.at-feed-search-clear {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: var(--mj-border-default);\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 9px;\n flex-shrink: 0;\n}\n\n.at-feed-search-clear:hover {\n background: var(--mj-border-strong);\n color: var(--mj-text-primary);\n}\n\n.at-feed-scroll-body {\n overflow-y: auto;\n min-height: 0;\n flex: 1;\n}\n\n.at-feed-item-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n overflow: hidden;\n}\n\n.at-feed-item-content .at-feed-item-name {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.at-feed-item-source-label {\n font-size: 0.7rem;\n font-weight: 500;\n color: var(--mj-brand-primary);\n}\n\n.at-feed-pagination {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 8px 14px;\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n}\n\n.at-feed-pagination-label {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 CLICKABLE SOURCE CARDS \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-source-card-clickable {\n cursor: pointer;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 FORM HINT \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-form-hint {\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n font-style: italic;\n margin-top: 2px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 DETAIL PANEL (wider) \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-detail-panel {\n width: 500px;\n}\n\n/* \u2500\u2500 Detail: Item header \u2500\u2500 */\n\n.at-detail-item-header {\n margin-bottom: 8px;\n}\n\n.at-detail-item-name {\n font-size: 1.05rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 0 0 6px 0;\n word-break: break-word;\n}\n\n.at-detail-badges {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.at-detail-badge {\n padding: 2px 10px;\n border-radius: 10px;\n font-size: 0.7rem;\n font-weight: 600;\n}\n\n.at-detail-badge-source {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.at-detail-badge-type {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n}\n\n.at-detail-badge-file {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.at-detail-badge-status-active {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.at-detail-badge-status-error {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.at-detail-badge-status-inactive {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-disabled);\n}\n\n/* \u2500\u2500 Detail: Sections \u2500\u2500 */\n\n.at-detail-section {\n margin-bottom: 4px;\n}\n\n.at-detail-section-label {\n font-size: 0.72rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: var(--mj-text-muted);\n margin-bottom: 6px;\n}\n\n.at-detail-link {\n font-size: 0.8rem;\n color: var(--mj-brand-primary);\n text-decoration: none;\n word-break: break-all;\n}\n\n.at-detail-link:hover {\n text-decoration: underline;\n}\n\n/* \u2500\u2500 Detail: Text preview \u2500\u2500 */\n\n.at-detail-text-preview {\n max-height: 200px;\n overflow-y: auto;\n padding: 10px 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 6px;\n font-size: 0.78rem;\n font-family: 'SF Mono', 'Cascadia Code', 'Menlo', monospace;\n color: var(--mj-text-primary);\n white-space: pre-wrap;\n word-break: break-word;\n line-height: 1.5;\n}\n\n/* \u2500\u2500 Detail: Tags \u2500\u2500 */\n\n.at-detail-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n/* \u2500\u2500 Detail: Meta grid \u2500\u2500 */\n\n.at-detail-meta-grid {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n overflow: hidden;\n}\n\n.at-detail-meta-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n}\n\n.at-detail-meta-row:last-child {\n border-bottom: none;\n}\n\n.at-detail-meta-key {\n color: var(--mj-text-muted);\n font-weight: 500;\n flex-shrink: 0;\n margin-right: 12px;\n}\n\n.at-detail-meta-value {\n color: var(--mj-text-primary);\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.at-detail-meta-mono {\n font-family: 'SF Mono', 'Cascadia Code', 'Menlo', monospace;\n font-size: 0.72rem;\n}\n\n/* \u2500\u2500 Detail: Actions \u2500\u2500 */\n\n.at-detail-actions {\n display: flex;\n gap: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-subtle);\n margin-top: 8px;\n}\n\n.at-detail-actions .at-action-btn {\n flex: 1;\n justify-content: center;\n}\n\n/* \u2500\u2500 Detail: Source header \u2500\u2500 */\n\n.at-detail-source-header {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 8px;\n}\n\n/* \u2500\u2500 Detail: Stats strip \u2500\u2500 */\n\n.at-detail-stats-strip {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.at-detail-stat {\n flex: 1;\n min-width: 60px;\n text-align: center;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n padding: 8px 6px;\n}\n\n.at-detail-stat-value {\n font-size: 1rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-detail-stat-label {\n font-size: 0.62rem;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n/* \u2500\u2500 Detail: Content Library \u2500\u2500 */\n\n.at-detail-content-list {\n max-height: 250px;\n overflow-y: auto;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n}\n\n.at-detail-content-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.at-detail-content-item:last-child {\n border-bottom: none;\n}\n\n.at-detail-content-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.at-detail-content-item-name {\n flex: 1;\n font-weight: 500;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.at-detail-content-item-tags {\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.at-detail-content-item-time {\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n/* \u2500\u2500 Detail: Run history \u2500\u2500 */\n\n.at-detail-run-history {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n max-height: 200px;\n overflow-y: auto;\n}\n\n.at-detail-run-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.75rem;\n}\n\n.at-detail-run-row:last-child {\n border-bottom: none;\n}\n\n.at-detail-run-time {\n color: var(--mj-text-secondary);\n flex: 1;\n}\n\n.at-detail-run-duration {\n color: var(--mj-text-muted);\n}\n\n.at-detail-run-items {\n color: var(--mj-text-muted);\n}\n\n@media (max-width: 600px) {\n .at-slide-panel {\n width: 100%;\n }\n\n .at-detail-panel {\n width: 100%;\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n TAXONOMY GOVERNANCE TAB\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n/* Sub-tab strip + badges retired \u2014 taxonomy sub-tabs now driven by <mj-tab-nav>\n in the [toolbar] slot of the taxonomy section, with TabConfig badges\n (warning variant for duplicates, error variant for orphans). */\n\n/* \u2500\u2500 Tree View: Split layout \u2500\u2500 */\n\n.at-tax-split-view {\n display: flex;\n gap: 0;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n height: calc(100vh - 320px);\n min-height: 400px;\n overflow: hidden;\n}\n\n.at-tax-tree-panel {\n width: 40%;\n border-right: 1px solid var(--mj-border-default);\n display: flex;\n flex-direction: column;\n}\n\n.at-tax-tree-toolbar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.at-tax-toolbar-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 0.8rem;\n transition: background 0.15s, color 0.15s;\n}\n\n.at-tax-toolbar-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.at-tax-toolbar-btn.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.at-tax-tree-body {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n}\n\n.at-tax-tree-node {\n padding: 6px 16px;\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.78rem;\n cursor: pointer;\n transition: background 0.1s;\n line-height: 1.4;\n}\n\n.at-tax-tree-node:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.at-tax-node-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n}\n\n.at-tax-node-drag-over {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n outline: 2px dashed var(--mj-brand-primary);\n outline-offset: -2px;\n}\n\n.at-tax-node-multi-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.at-tax-tree-add-child {\n margin-left: auto;\n opacity: 0;\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n transition: opacity 0.15s, color 0.15s, background 0.15s;\n}\n\n.at-tax-tree-node:hover .at-tax-tree-add-child {\n opacity: 1;\n}\n\n.at-tax-tree-add-child:hover {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.at-tax-tree-checkbox {\n width: 14px;\n height: 14px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.at-tax-tree-saving-overlay {\n position: absolute;\n inset: 0;\n background: color-mix(in srgb, var(--mj-bg-surface) 75%, transparent);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10;\n backdrop-filter: blur(1px);\n}\n\n.at-tax-create-context {\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n margin-bottom: 12px;\n padding: 6px 10px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 6px;\n}\n\n.at-tax-tree-arrow {\n width: 16px;\n font-size: 0.55rem;\n color: var(--mj-text-muted);\n flex-shrink: 0;\n text-align: center;\n cursor: pointer;\n}\n\n.at-tax-arrow-collapsed::before {\n content: \"\\25B6\";\n}\n\n.at-tax-arrow-expanded::before {\n content: \"\\25BC\";\n}\n\n.at-tax-arrow-leaf {\n visibility: hidden;\n}\n\n.at-tax-health-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.at-tax-health-dot.green {\n background: var(--mj-status-success);\n}\n\n.at-tax-health-dot.yellow {\n background: var(--mj-status-warning);\n}\n\n.at-tax-health-dot.red {\n background: var(--mj-status-error);\n}\n\n.at-tax-tree-label {\n flex: 1;\n color: var(--mj-text-primary);\n}\n\n.at-tax-tree-label-selected {\n font-weight: 700;\n}\n\n.at-tax-tree-count {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n margin-left: 4px;\n}\n\n/* \u2500\u2500 Details panel \u2500\u2500 */\n\n.at-tax-details-panel {\n width: 60%;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n}\n\n.at-tax-details-header {\n padding: 20px 24px 0;\n}\n\n.at-tax-breadcrumb {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n margin-bottom: 8px;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.at-tax-bc-link {\n color: var(--mj-brand-primary);\n cursor: pointer;\n}\n\n.at-tax-bc-link:hover {\n text-decoration: underline;\n}\n\n.at-tax-bc-sep {\n color: var(--mj-border-default);\n}\n\n.at-tax-bc-current {\n color: var(--mj-text-secondary);\n font-weight: 600;\n}\n\n.at-tax-details-title {\n font-size: 1.2rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 4px;\n}\n\n.at-tax-edit-icon {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n}\n\n.at-tax-edit-icon:hover {\n color: var(--mj-brand-primary);\n}\n\n.at-tax-details-desc {\n font-size: 0.78rem;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n margin-bottom: 16px;\n padding: 8px 12px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 6px;\n border: 1px solid var(--mj-border-subtle);\n}\n\n.at-tax-edit-form {\n margin-bottom: 16px;\n}\n\n/* \u2500\u2500 Stats row \u2500\u2500 */\n\n.at-tax-stats-row {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n padding: 0 24px;\n}\n\n.at-tax-stat-item {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 10px 14px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n min-width: 72px;\n}\n\n.at-tax-stat-value {\n font-size: 1.1rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-tax-stat-date {\n font-size: 0.8rem;\n}\n\n.at-tax-stat-label {\n font-size: 0.62rem;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n/* \u2500\u2500 Action toolbar \u2500\u2500 */\n\n.at-tax-action-toolbar {\n display: flex;\n gap: 8px;\n padding: 0 24px;\n margin-bottom: 20px;\n flex-wrap: wrap;\n}\n\n.at-tax-action-btn {\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 5px;\n transition: all 0.15s;\n}\n\n.at-tax-action-btn:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.at-tax-action-danger:hover {\n border-color: var(--mj-status-error);\n color: var(--mj-status-error);\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--mj-bg-surface));\n}\n\n/* \u2500\u2500 Detail sections \u2500\u2500 */\n\n.at-tax-detail-section {\n padding: 0 24px 20px;\n}\n\n.at-tax-section-title {\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n margin-bottom: 10px;\n}\n\n.at-tax-child-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.at-tax-child-chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 16px;\n font-size: 0.72rem;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.at-tax-child-chip:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.at-tax-chip-count {\n font-size: 0.6rem;\n background: var(--mj-border-default);\n color: var(--mj-text-muted);\n padding: 0 5px;\n border-radius: 8px;\n}\n\n/* \u2500\u2500 Recent items \u2500\u2500 */\n\n.at-tax-recent-list {\n list-style: none;\n}\n\n.at-tax-recent-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n}\n\n.at-tax-recent-item:last-child {\n border-bottom: none;\n}\n\n.at-tax-recent-icon {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.72rem;\n flex-shrink: 0;\n}\n\n.at-tax-recent-name {\n flex: 1;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.at-tax-recent-weight {\n font-size: 0.68rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.at-tax-recent-date {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n/* \u2500\u2500 Health bar \u2500\u2500 */\n\n.at-tax-health-bar {\n display: flex;\n align-items: center;\n gap: 20px;\n padding: 12px 20px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n margin-top: 16px;\n flex-wrap: wrap;\n}\n\n.at-tax-health-label {\n font-size: 0.72rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.at-tax-health-stat {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.78rem;\n}\n\n.at-tax-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n}\n\n.at-tax-dot-total {\n background: var(--mj-text-secondary);\n}\n\n.at-tax-dot-healthy {\n background: var(--mj-status-success);\n}\n\n.at-tax-dot-attention {\n background: var(--mj-status-warning);\n}\n\n.at-tax-dot-orphaned {\n background: var(--mj-status-error);\n}\n\n.at-tax-dot-duplicates {\n background: var(--mj-status-info);\n}\n\n.at-tax-health-value {\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-tax-val-success {\n color: var(--mj-status-success);\n}\n\n.at-tax-val-warning {\n color: var(--mj-status-warning);\n}\n\n.at-tax-val-error {\n color: var(--mj-status-error);\n}\n\n.at-tax-val-info {\n color: var(--mj-status-info);\n}\n\n.at-tax-health-text {\n color: var(--mj-text-muted);\n}\n\n/* \u2550\u2550\u2550\u2550 Duplicates sub-tab \u2550\u2550\u2550\u2550 */\n\n.at-tax-dup-stats-bar {\n display: flex;\n gap: 24px;\n margin-bottom: 16px;\n align-items: center;\n}\n\n.at-tax-dup-stat {\n font-size: 0.78rem;\n color: var(--mj-text-secondary);\n}\n\n.at-tax-dup-stat strong {\n font-size: 1.1rem;\n color: var(--mj-text-primary);\n margin-right: 4px;\n}\n\n.at-tax-dup-high strong {\n color: var(--mj-status-error);\n}\n\n.at-tax-dup-moderate strong {\n color: var(--mj-status-warning);\n}\n\n.at-tax-dup-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.at-tax-dup-card {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 14px 20px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n transition: border-color 0.15s;\n}\n\n.at-tax-dup-card:hover {\n border-color: var(--mj-brand-primary);\n}\n\n.at-tax-dup-card.at-tax-dup-high {\n border-left: 3px solid var(--mj-status-error);\n}\n\n.at-tax-dup-card.at-tax-dup-moderate {\n border-left: 3px solid var(--mj-status-warning);\n}\n\n.at-tax-dup-tag {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n min-width: 120px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 6px;\n text-align: center;\n}\n\n.at-tax-dup-arrow {\n font-size: 0.82rem;\n color: var(--mj-text-muted);\n flex-shrink: 0;\n}\n\n.at-tax-dup-similarity {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n min-width: 100px;\n}\n\n.at-tax-sim-bar-bg {\n width: 100%;\n height: 6px;\n background: var(--mj-border-default);\n border-radius: 3px;\n overflow: hidden;\n}\n\n.at-tax-sim-bar-fill {\n height: 100%;\n border-radius: 3px;\n transition: width 0.3s;\n}\n\n.at-tax-sim-bar-fill.high {\n background: var(--mj-status-error);\n}\n\n.at-tax-sim-bar-fill.moderate {\n background: var(--mj-status-warning);\n}\n\n.at-tax-sim-percent {\n font-size: 0.78rem;\n font-weight: 700;\n}\n\n.at-tax-sim-percent.high {\n color: var(--mj-status-error);\n}\n\n.at-tax-sim-percent.moderate {\n color: var(--mj-status-warning);\n}\n\n.at-tax-dup-actions {\n display: flex;\n gap: 6px;\n flex-shrink: 0;\n}\n\n.at-tax-dup-btn {\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n font-size: 0.68rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s;\n white-space: nowrap;\n}\n\n.at-tax-dup-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.at-tax-dup-btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.at-tax-dup-btn-primary:hover {\n background: var(--mj-brand-primary-hover);\n color: var(--mj-text-inverse);\n}\n\n/* \u2550\u2550\u2550\u2550 Orphans sub-tab \u2550\u2550\u2550\u2550 */\n\n.at-tax-orphan-toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 16px;\n flex-wrap: wrap;\n}\n\n.at-tax-orphan-count {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.at-tax-orphan-desc {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.at-tax-orphan-bulk {\n margin-left: auto;\n display: flex;\n gap: 8px;\n}\n\n.at-tax-bulk-btn {\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 5px;\n transition: all 0.15s;\n}\n\n.at-tax-bulk-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.at-tax-bulk-danger:hover {\n border-color: var(--mj-status-error);\n color: var(--mj-status-error);\n}\n\n.at-tax-orphan-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\n}\n\n.at-tax-orphan-card {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n padding: 16px;\n transition: all 0.15s;\n display: flex;\n flex-direction: column;\n gap: 10px;\n border-top: 3px solid var(--mj-status-error);\n}\n\n.at-tax-orphan-card:hover {\n border-color: var(--mj-brand-primary);\n}\n\n.at-tax-orphan-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.at-tax-orphan-name {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.at-tax-orphan-checkbox {\n width: 16px;\n height: 16px;\n accent-color: var(--mj-brand-primary);\n}\n\n.at-tax-orphan-stats {\n display: flex;\n gap: 12px;\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n.at-tax-orphan-stats strong {\n color: var(--mj-text-secondary);\n}\n\n.at-tax-orphan-dates {\n display: flex;\n justify-content: space-between;\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n.at-tax-orphan-actions {\n display: flex;\n gap: 6px;\n margin-top: 4px;\n}\n\n.at-tax-orphan-btn {\n flex: 1;\n padding: 5px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: 5px;\n background: var(--mj-bg-surface);\n font-size: 0.68rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n cursor: pointer;\n text-align: center;\n transition: all 0.15s;\n}\n\n.at-tax-orphan-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.at-tax-orphan-delete:hover {\n border-color: var(--mj-status-error);\n color: var(--mj-status-error);\n}\n\n/* \u2550\u2550\u2550\u2550 Treemap sub-tab \u2550\u2550\u2550\u2550 */\n\n.at-tax-treemap-kpi-strip {\n display: flex;\n gap: 20px;\n margin-bottom: 16px;\n flex-wrap: wrap;\n}\n\n.at-tax-treemap-kpi {\n padding: 10px 18px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n display: flex;\n flex-direction: column;\n align-items: center;\n min-width: 120px;\n}\n\n.at-tax-treemap-kpi-value {\n font-size: 1.2rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.at-tax-treemap-kpi-label {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.at-tax-treemap-container {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n padding: 8px;\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n grid-auto-rows: 80px;\n gap: 4px;\n min-height: 340px;\n}\n\n.at-tax-treemap-cell {\n border-radius: 6px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-inverse);\n font-weight: 600;\n font-size: 0.78rem;\n cursor: pointer;\n transition: all 0.15s;\n position: relative;\n overflow: hidden;\n}\n\n.at-tax-treemap-cell:hover {\n transform: scale(1.02);\n z-index: 2;\n}\n\n.at-tax-cell-name {\n font-size: 0.78rem;\n}\n\n.at-tax-cell-count {\n font-size: 0.65rem;\n opacity: 0.85;\n font-weight: 400;\n}\n\n/* Treemap color families */\n.at-tm-blue-1 { background: color-mix(in srgb, var(--mj-brand-primary) 90%, black); }\n.at-tm-blue-2 { background: var(--mj-brand-primary); }\n.at-tm-blue-3 { background: color-mix(in srgb, var(--mj-brand-primary) 75%, white); }\n.at-tm-blue-4 { background: color-mix(in srgb, var(--mj-brand-primary) 55%, white); }\n\n.at-tm-green-1 { background: color-mix(in srgb, var(--mj-status-success) 90%, black); }\n.at-tm-green-2 { background: var(--mj-status-success); }\n.at-tm-green-3 { background: color-mix(in srgb, var(--mj-status-success) 60%, white); color: var(--mj-text-primary); }\n\n.at-tm-purple-1 { background: color-mix(in srgb, var(--mj-status-info) 90%, black); }\n.at-tm-purple-2 { background: var(--mj-status-info); }\n.at-tm-purple-3 { background: color-mix(in srgb, var(--mj-status-info) 65%, white); }\n\n.at-tm-orange-1 { background: color-mix(in srgb, var(--mj-status-warning) 90%, black); }\n.at-tm-orange-2 { background: var(--mj-status-warning); }\n.at-tm-orange-3 { background: color-mix(in srgb, var(--mj-status-warning) 60%, white); color: var(--mj-text-primary); }\n\n/* \u2550\u2550\u2550\u2550 Audit Log sub-tab \u2550\u2550\u2550\u2550 */\n\n.at-tax-audit-filters {\n display: flex;\n align-items: center;\n gap: 16px;\n margin-bottom: 16px;\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n flex-wrap: wrap;\n}\n\n.at-tax-audit-filter-label {\n font-size: 0.72rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.at-tax-audit-checkbox-group {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.at-tax-audit-checkbox {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 0.72rem;\n color: var(--mj-text-secondary);\n cursor: pointer;\n}\n\n.at-tax-audit-checkbox input {\n accent-color: var(--mj-brand-primary);\n}\n\n.at-tax-audit-timeline {\n position: relative;\n padding-left: 32px;\n}\n\n.at-tax-audit-timeline::before {\n content: '';\n position: absolute;\n left: 14px;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--mj-border-default);\n}\n\n.at-tax-audit-event {\n position: relative;\n padding: 10px 16px;\n margin-bottom: 4px;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n border-radius: 8px;\n transition: background 0.1s;\n}\n\n.at-tax-audit-event:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.at-tax-audit-event-icon {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.68rem;\n flex-shrink: 0;\n position: absolute;\n left: -32px;\n top: 10px;\n z-index: 1;\n}\n\n.at-tax-audit-event-icon.created {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n.at-tax-audit-event-icon.merged {\n background: color-mix(in srgb, var(--mj-status-info) 15%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n}\n\n.at-tax-audit-event-icon.moved {\n background: color-mix(in srgb, var(--mj-status-info) 15%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n}\n\n.at-tax-audit-event-icon.deleted {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n}\n\n.at-tax-audit-event-icon.renamed {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.at-tax-audit-event-body {\n flex: 1;\n}\n\n.at-tax-audit-event-desc {\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n line-height: 1.5;\n}\n\n.at-tax-tag-ref {\n background: var(--mj-bg-surface-sunken);\n padding: 1px 6px;\n border-radius: 4px;\n font-weight: 600;\n font-size: 0.72rem;\n border: 1px solid var(--mj-border-default);\n}\n\n.at-tax-audit-event-meta {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n margin-top: 2px;\n display: flex;\n gap: 12px;\n}\n\n/* \u2500\u2500 Taxonomy responsive \u2500\u2500 */\n\n@media (max-width: 1100px) {\n .at-tax-split-view {\n flex-direction: column;\n }\n\n .at-tax-tree-panel {\n width: 100%;\n max-height: 300px;\n border-right: none;\n border-bottom: 1px solid var(--mj-border-default);\n }\n\n .at-tax-details-panel {\n width: 100%;\n }\n}\n\n@media (max-width: 768px) {\n .at-tax-dup-card {\n flex-wrap: wrap;\n }\n\n .at-tax-orphan-grid {\n grid-template-columns: 1fr;\n }\n}\n\n/* \u2500\u2500 Pipeline Config Widget \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.at-config-card {\n margin-top: 12px;\n}\n\n.at-config-body {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px !important;\n}\n\n.at-config-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.at-config-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n min-width: 90px;\n flex-shrink: 0;\n}\n\n.at-config-control {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1;\n justify-content: flex-end;\n}\n\n.at-config-input {\n width: 70px;\n padding: 3px 6px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 0.75rem;\n text-align: right;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n}\n\n.at-config-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n}\n\n.at-config-slider {\n flex: 1;\n max-width: 100px;\n accent-color: var(--mj-brand-primary);\n height: 4px;\n}\n\n.at-config-value {\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n min-width: 40px;\n text-align: right;\n}\n\n.at-config-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n.at-config-section-label {\n font-size: 0.68rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Toggle Switch */\n.at-config-toggle {\n position: relative;\n display: inline-block;\n cursor: pointer;\n}\n\n.at-config-toggle input {\n display: none;\n}\n\n.at-toggle-track {\n display: block;\n width: 32px;\n height: 18px;\n background: var(--mj-border-strong);\n border-radius: 9px;\n transition: background 0.2s ease;\n position: relative;\n}\n\n.at-config-toggle input:checked + .at-toggle-track {\n background: var(--mj-brand-primary);\n}\n\n.at-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n background: var(--mj-bg-surface);\n border-radius: 50%;\n transition: transform 0.2s ease;\n box-shadow: 0 1px 3px rgba(0,0,0,0.15);\n}\n\n.at-config-toggle input:checked + .at-toggle-track .at-toggle-thumb {\n transform: translateX(14px);\n}\n\n/* \u2500\u2500 Schedule Indicator on Source Cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.at-schedule-indicator {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 3px 10px;\n margin-top: 6px;\n border-radius: 12px;\n font-size: 0.68rem;\n font-weight: 600;\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-border-default));\n cursor: pointer;\n transition: all 0.15s ease;\n width: fit-content;\n}\n\n.at-schedule-indicator i {\n font-size: 0.62rem;\n}\n\n.at-schedule-indicator:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-border-default));\n}\n\n/* Schedule button in source card actions */\n.at-source-schedule-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n/* \u2500\u2500 Schedule Dialog \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.at-schedule-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--mj-bg-overlay, rgba(0, 0, 0, 0.4));\n}\n\n.at-schedule-dialog {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 12px);\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.16);\n width: 420px;\n max-width: 90vw;\n}\n\n.at-schedule-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.at-schedule-dialog-header h3 {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n font-size: 15px;\n font-weight: 650;\n color: var(--mj-text-primary);\n}\n\n.at-schedule-dialog-header h3 i {\n color: var(--mj-brand-primary);\n}\n\n.at-schedule-dialog-close {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 16px;\n padding: 4px;\n line-height: 1;\n transition: color 0.15s;\n}\n\n.at-schedule-dialog-close:hover {\n color: var(--mj-text-primary);\n}\n\n.at-schedule-dialog-body {\n padding: 20px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.at-schedule-field label {\n display: block;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n margin-bottom: 6px;\n}\n\n.at-schedule-source-name {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13.5px;\n font-weight: 600;\n color: var(--mj-text-primary);\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-sm, 6px);\n border: 1px solid var(--mj-border-subtle);\n}\n\n.at-schedule-source-name i {\n color: var(--mj-text-muted);\n}\n\n.at-schedule-action-name {\n font-size: 13px;\n color: var(--mj-text-secondary);\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-sm, 6px);\n border: 1px solid var(--mj-border-subtle);\n}\n\n.at-schedule-cron-input {\n width: 100%;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n font-size: 13px;\n letter-spacing: 0.02em;\n}\n\n.at-schedule-cron-preview {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.at-schedule-cron-preview i {\n font-size: 11px;\n color: var(--mj-brand-primary);\n}\n\n.at-schedule-toggle-row {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 10px;\n}\n\n.at-schedule-toggle-row label {\n margin-bottom: 0;\n}\n\n.at-schedule-dialog-footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 14px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 D4: Status Badges for Content Items \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-status-badge {\n display: inline-block;\n padding: 1px 6px;\n border-radius: 4px;\n font-size: 0.6rem;\n font-weight: 600;\n white-space: nowrap;\n letter-spacing: 0.02em;\n}\n\n.at-status-badge-complete {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n}\n\n.at-status-badge-processing {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning-text);\n}\n\n.at-status-badge-failed {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--mj-status-error-text);\n}\n\n.at-status-badge-pending {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 D4/D7: Source Detail Controls \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-detail-section-header-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 8px;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.at-detail-section-controls {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.at-detail-filter-select {\n padding: 4px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 0.72rem;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n\n.at-retry-btn {\n font-size: 0.7rem !important;\n padding: 4px 8px !important;\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface)) !important;\n color: var(--mj-status-error-text) !important;\n border: 1px solid color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-border-default)) !important;\n}\n\n.at-retry-btn:hover {\n background: color-mix(in srgb, var(--mj-status-error) 18%, var(--mj-bg-surface)) !important;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 D7: Pagination \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-detail-pagination {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 10px 0 4px;\n border-top: 1px solid var(--mj-border-subtle);\n margin-top: 4px;\n}\n\n.at-page-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n font-size: 0.72rem;\n font-weight: 500;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.12s ease;\n}\n\n.at-page-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.at-page-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.at-page-info {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 Confirmation Dialog \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-confirm-message {\n font-size: 13.5px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n margin: 0;\n}\n\n.at-danger-btn {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border: none;\n border-radius: var(--mj-radius-sm, 6px);\n padding: 7px 14px;\n font-size: 12.5px;\n font-weight: 600;\n cursor: pointer;\n transition: background 0.15s;\n}\n\n.at-danger-btn:hover {\n background: var(--mj-status-error-text);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 Treemap Drill-In \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.at-tax-treemap-cell-clickable {\n cursor: pointer;\n transition: transform 0.15s, box-shadow 0.15s;\n}\n\n.at-tax-treemap-cell-clickable:hover {\n transform: scale(1.02);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);\n z-index: 1;\n}\n\n.at-tax-drillin-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: flex-start;\n justify-content: flex-end;\n background: var(--mj-bg-overlay, rgba(0, 0, 0, 0.4));\n}\n\n.at-tax-drillin-panel {\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n width: 420px;\n max-width: 90vw;\n height: 100vh;\n display: flex;\n flex-direction: column;\n box-shadow: -4px 0 24px rgba(0, 0, 0, 0.12);\n}\n\n.at-tax-drillin-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.at-tax-drillin-header h3 {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n font-size: 15px;\n font-weight: 650;\n color: var(--mj-text-primary);\n}\n\n.at-tax-drillin-header h3 i {\n color: var(--mj-brand-primary);\n}\n\n.at-tax-drillin-body {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n}\n\n.at-tax-drillin-footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 14px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n/* D3/D8: Clickable run history rows */\n.at-run-row-clickable {\n cursor: pointer;\n transition: background 0.12s ease;\n}\n\n.at-run-row-clickable:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.at-run-row-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface)) !important;\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n/* D2: Error text in detail tables */\n.run-error-text {\n color: var(--mj-status-error);\n font-weight: 600;\n}\n\n/* \u2500\u2500 Content Item Duplicates Section (G3) \u2500\u2500 */\n\n.at-dedup-section {\n margin-top: 24px;\n border-top: 1px solid var(--mj-border-default);\n padding-top: 20px;\n}\n\n.at-dedup-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n margin-bottom: 16px;\n}\n\n.at-dedup-title {\n margin: 0;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.at-dedup-subtitle {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.at-dedup-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.at-dedup-card {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 14px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n transition: border-color 0.15s;\n}\n\n.at-dedup-card:hover {\n border-color: var(--mj-border-strong);\n}\n\n.at-dedup-pair {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n min-width: 0;\n}\n\n.at-dedup-item {\n display: flex;\n flex-direction: column;\n min-width: 0;\n flex: 1;\n}\n\n.at-dedup-item-name {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.at-dedup-item-source {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n.at-dedup-vs {\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n flex-shrink: 0;\n}\n\n.at-dedup-meta {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 3px;\n flex-shrink: 0;\n}\n\n.at-dedup-score {\n font-size: 0.78rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.at-dedup-method {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.at-dedup-actions {\n display: flex;\n gap: 6px;\n flex-shrink: 0;\n}\n\n.at-dedup-confirm {\n font-size: 0.75rem !important;\n padding: 4px 10px !important;\n}\n\n.at-dedup-dismiss {\n font-size: 0.75rem !important;\n padding: 4px 10px !important;\n}\n\n.at-dedup-empty {\n text-align: center;\n padding: 32px 16px;\n color: var(--mj-text-muted);\n}\n\n.at-dedup-empty i {\n font-size: 1.5rem;\n margin-bottom: 8px;\n color: var(--mj-status-success);\n}\n\n.at-dedup-empty p {\n margin: 8px 0 0;\n font-size: 0.82rem;\n}\n\n/* =============================================================================\n * Tags-only additions: governance toggles, scope chips, inline synonyms,\n * Suggestions inbox table+drawer, Tag Health 3-card layout. All colors flow\n * through MJ design tokens with `color-mix()` for tinted variants.\n * ============================================================================= */\n\n/* ---------- Governance toggle rows ---------- */\n\n.at-tg-toggle-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 10px 0;\n border-bottom: 1px dashed var(--mj-border-subtle);\n}\n.at-tg-toggle-row:last-child { border-bottom: 0; }\n.at-tg-toggle-label { display: flex; flex-direction: column; gap: 2px; max-width: 70%; }\n.at-tg-toggle-t { font-size: 13px; font-weight: 600; color: var(--mj-text-primary); }\n.at-tg-toggle-d { font-size: 11px; color: var(--mj-text-muted); line-height: 1.4; }\n\n.at-tg-switch {\n position: relative;\n width: 36px;\n height: 20px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 10px;\n cursor: pointer;\n transition: background 0.15s;\n flex-shrink: 0;\n}\n.at-tg-switch::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n transition: left 0.15s;\n box-shadow: 0 1px 2px rgba(0,0,0,0.2);\n}\n.at-tg-switch.on { background: var(--mj-brand-primary); }\n.at-tg-switch.on::after { left: 18px; }\n.at-tg-switch.disabled { opacity: 0.5; pointer-events: none; }\n\n.at-tg-grid-3 {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n gap: 14px 16px;\n margin-top: 14px;\n}\n.at-tg-field { display: flex; flex-direction: column; gap: 4px; }\n.at-tg-field label { font-size: 12px; font-weight: 600; color: var(--mj-text-secondary); }\n.at-tg-hint { font-size: 11px; color: var(--mj-text-muted); }\n\n/* ---------- Scope chips ---------- */\n\n.at-tg-chip-list {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n margin-top: 8px;\n}\n.at-tg-chip {\n display: inline-flex;\n align-items: center;\n padding: 3px 10px;\n border-radius: 999px;\n font-size: 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n.at-tg-chip.scoped {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n color: var(--mj-brand-primary);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n/* ---------- Inline synonyms ---------- */\n\n.at-tg-syn-add {\n display: flex;\n gap: 8px;\n margin-bottom: 12px;\n align-items: stretch;\n}\n.at-tg-syn-add .at-search-input { flex: 1; }\n.at-tg-syn-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border-radius: 5px;\n border: 1px solid var(--mj-border-subtle);\n margin-bottom: 6px;\n background: var(--mj-bg-surface);\n}\n.at-tg-syn-row.pending {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-warning) 25%, transparent);\n}\n.at-tg-syn-name { font-weight: 600; flex: 1; color: var(--mj-text-primary); }\n.at-tg-syn-src {\n font-size: 10px;\n padding: 2px 7px;\n border-radius: 3px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-weight: 600;\n text-transform: uppercase;\n}\n.at-tg-syn-src.manual { background: color-mix(in srgb, var(--mj-text-secondary) 14%, transparent); color: var(--mj-text-secondary); }\n.at-tg-syn-src.imported { background: color-mix(in srgb, var(--mj-status-info, var(--mj-brand-primary)) 14%, transparent); color: var(--mj-status-info-text, var(--mj-brand-primary)); }\n.at-tg-syn-src.merged { background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent); color: var(--mj-brand-primary); }\n.at-tg-syn-src.llm { background: color-mix(in srgb, var(--mj-status-warning) 14%, transparent); color: var(--mj-status-warning-text, var(--mj-status-warning)); }\n.at-tg-syn-x {\n cursor: pointer;\n color: var(--mj-text-muted);\n padding: 2px 6px;\n}\n.at-tg-syn-x:hover { color: var(--mj-status-error-text, var(--mj-status-error)); }\n\n/* ---------- Suggestions inbox table + drawer ---------- */\n\n.sg-shell {\n display: flex;\n height: 100%;\n min-height: 600px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n.sg-main { flex: 1; display: flex; flex-direction: column; min-width: 0; overflow: hidden; }\n.sg-toolbar {\n padding: 10px 14px;\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n gap: 10px;\n align-items: center;\n background: var(--mj-bg-surface-card);\n flex-wrap: wrap;\n}\n.sg-bulk-bar {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n padding: 9px 14px;\n display: flex;\n gap: 10px;\n align-items: center;\n color: var(--mj-brand-primary);\n font-size: 13px;\n font-weight: 500;\n}\n.sg-table-body { flex: 1; overflow: auto; }\n.sg-table { width: 100%; border-collapse: collapse; font-size: 13px; color: var(--mj-text-primary); }\n.sg-table thead { position: sticky; top: 0; background: var(--mj-bg-surface-card); z-index: 1; }\n.sg-table th {\n text-align: left;\n padding: 9px 12px;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--mj-text-muted);\n font-weight: 600;\n border-bottom: 1px solid var(--mj-border-default);\n white-space: nowrap;\n}\n.sg-table td {\n padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n vertical-align: middle;\n}\n.sg-table tr { cursor: pointer; }\n.sg-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.sg-table tr.sg-row-selected td { background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent); }\n.sg-table tr.sg-row-checked td { background: color-mix(in srgb, var(--mj-status-warning) 8%, transparent); }\n.sg-score { font-family: ui-monospace, \"SF Mono\", Menlo, monospace; font-weight: 600; }\n.sg-score-bar {\n display: inline-block;\n width: 70px;\n height: 5px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n overflow: hidden;\n vertical-align: middle;\n margin-right: 6px;\n}\n.sg-score-bar > span {\n display: block;\n height: 100%;\n background: var(--mj-brand-primary);\n}\n.sg-reason {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 500;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n}\n.sg-reason.merge { background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent); color: var(--mj-brand-primary); }\n.sg-reason.below { background: color-mix(in srgb, var(--mj-status-info, var(--mj-brand-primary)) 12%, transparent); color: var(--mj-status-info-text, var(--mj-brand-primary)); }\n.sg-reason.constrained { background: color-mix(in srgb, var(--mj-text-secondary) 14%, transparent); color: var(--mj-text-secondary); }\n.sg-reason.frozen { background: color-mix(in srgb, var(--mj-status-info, var(--mj-brand-primary)) 14%, transparent); color: var(--mj-status-info-text, var(--mj-brand-primary)); }\n.sg-reason.review { background: color-mix(in srgb, var(--mj-status-error) 12%, transparent); color: var(--mj-status-error-text, var(--mj-status-error)); }\n.sg-reason.lowusage { background: color-mix(in srgb, var(--mj-status-warning) 14%, transparent); color: var(--mj-status-warning-text, var(--mj-status-warning)); }\n.sg-reason.widenode { background: color-mix(in srgb, var(--mj-status-warning) 14%, transparent); color: var(--mj-status-warning-text, var(--mj-status-warning)); }\n.sg-reason.autogrow { background: color-mix(in srgb, var(--mj-status-warning) 12%, transparent); color: var(--mj-status-warning-text, var(--mj-status-warning)); }\n.sg-row-actions {\n display: flex;\n gap: 4px;\n opacity: 0;\n transition: opacity 0.1s;\n}\n.sg-table tr:hover .sg-row-actions {\n opacity: 1;\n}\n\n.sg-drawer {\n width: 460px;\n border-left: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n}\n.sg-drawer-hd {\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--mj-bg-surface);\n}\n.sg-drawer-bd { padding: 16px 18px; flex: 1; overflow-y: auto; }\n.sg-drawer-ft {\n padding: 12px 18px;\n border-top: 1px solid var(--mj-border-default);\n display: flex;\n gap: 8px;\n background: var(--mj-bg-surface);\n flex-wrap: wrap;\n}\n.sg-drawer h4 {\n font-size: 12px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n margin: 16px 0 8px;\n font-weight: 700;\n}\n.sg-candidate {\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n display: flex;\n gap: 12px;\n align-items: center;\n}\n.sg-preview {\n font-size: 12px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n margin: 0;\n}\n\n/* ---------- Tag Health cards ---------- */\n\n.health-shell { padding: 18px 22px; }\n.runbar {\n display: flex;\n gap: 22px;\n padding: 14px 18px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n margin-bottom: 18px;\n align-items: center;\n font-size: 13px;\n}\n.runbar .stat .l { font-size: 11px; color: var(--mj-text-muted); }\n.runbar .stat .v { font-size: 16px; font-weight: 600; color: var(--mj-text-primary); }\n.runbar .grow { flex: 1; }\n\n.health-cards {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n gap: 16px;\n margin-bottom: 22px;\n}\n.h-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 16px 18px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-height: 240px;\n}\n.h-card.merge { border-top: 4px solid var(--mj-brand-primary); }\n.h-card.lowusage { border-top: 4px solid var(--mj-status-warning); }\n.h-card.widenode { border-top: 4px solid var(--mj-status-error, var(--mj-status-warning)); }\n.h-card .top { display: flex; align-items: flex-start; gap: 10px; }\n.h-card .top .ic {\n width: 38px;\n height: 38px;\n border-radius: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n}\n.h-card .name {\n font-size: 11px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n.h-card h3 {\n margin: 0;\n font-size: 14px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n.h-card .big {\n font-size: 36px;\n font-weight: 800;\n color: var(--mj-text-primary);\n line-height: 1;\n}\n.h-card .sub { font-size: 12px; color: var(--mj-text-secondary); line-height: 1.4; margin: 0; }\n.h-card .ft { margin-top: auto; display: flex; gap: 6px; flex-wrap: wrap; }\n\n.threshold-form {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 18px;\n margin-top: 18px;\n}\n.threshold-form h3 { margin: 0 0 12px; font-size: 13px; font-weight: 700; color: var(--mj-text-primary); }\n.threshold-form .grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }\n.threshold-form .field { display: flex; flex-direction: column; gap: 4px; }\n.threshold-form .field label { font-size: 12px; font-weight: 600; color: var(--mj-text-secondary); }\n\n.run-history {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n margin-top: 18px;\n overflow: hidden;\n}\n.run-history-hd {\n padding: 12px 18px;\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n justify-content: space-between;\n align-items: center;\n background: var(--mj-bg-surface-card);\n}\n.run-history-hd h3 { margin: 0; font-size: 13px; font-weight: 700; color: var(--mj-text-primary); }\n.run-history table { width: 100%; border-collapse: collapse; font-size: 12px; }\n.run-history th {\n text-align: left;\n padding: 8px 12px;\n font-size: 11px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-subtle);\n font-weight: 600;\n}\n.run-history td {\n padding: 8px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n color: var(--mj-text-primary);\n}\n\n/* Generic helpers used by new sections */\n.at-muted { color: var(--mj-text-muted); }\n.at-success-btn { color: var(--mj-status-success-text, var(--mj-status-success)); border-color: var(--mj-status-success-border, var(--mj-status-success)); }\n.at-btn-sm { font-size: 12px; padding: 4px 9px; }\n.at-btn-xs { font-size: 11px; padding: 2px 7px; }\n"] }]
|
|
7380
7402
|
}], null, null); })();
|
|
7381
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TagsResourceComponent, { className: "TagsResourceComponent", filePath: "src/AI/components/tags/tags-resource.component.ts", lineNumber:
|
|
7403
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TagsResourceComponent, { className: "TagsResourceComponent", filePath: "src/AI/components/tags/tags-resource.component.ts", lineNumber: 392 }); })();
|
|
7382
7404
|
export function LoadTagsResource() {
|
|
7383
7405
|
// Prevents tree-shaking
|
|
7384
7406
|
}
|